func cmdPull(c *cli.Context, options *core.PullOptions, dockerOptions *dockerlocal.DockerOptions) error { soft := NewSoftExit(options.GlobalOptions) logger := util.RootLogger().WithField("Logger", "Main") if options.Debug { DumpOptions(options) } client := api.NewAPIClient(&api.APIOptions{ BaseURL: options.GlobalOptions.BaseURL, AuthToken: options.GlobalOptions.AuthToken, }) var buildID string if core.IsBuildID(options.Repository) { buildID = options.Repository } else { username, applicationName, err := core.ParseApplicationID(options.Repository) if err != nil { return soft.Exit(err) } logger.Println("Fetching build information for application", options.Repository) opts := &api.GetBuildsOptions{ Limit: 1, Branch: options.Branch, Result: options.Result, Status: "finished", Stack: 5, } builds, err := client.GetBuilds(username, applicationName, opts) if err != nil { return soft.Exit(err) } if len(builds) != 1 { return soft.Exit(errors.New("No finished builds found for this application")) } buildID = builds[0].ID } if buildID == "" { return soft.Exit(errors.New("Unable to parse argument as application or build-id")) } logger.Println("Downloading Docker repository for build", buildID) if !options.Force { outputExists, err := util.Exists(options.Output) if err != nil { logger.WithField("Error", err).Error("Unable to create output file") return soft.Exit(err) } if outputExists { return soft.Exit(errors.New("The file repository.tar already exists. Delete it, or run again with -f")) } } file, err := os.Create(options.Output) if err != nil { logger.WithField("Error", err).Error("Unable to create output file") return soft.Exit(err) } repository, err := client.GetDockerRepository(buildID) if err != nil { os.Remove(file.Name()) return soft.Exit(err) } defer repository.Content.Close() // Diagram of the various readers/writers // repository <-- tee <-- s <-- [io.Copy] --> file // | // +--> hash *Legend: --> == write, <-- == read counter := util.NewCounterReader(repository.Content) stopEmit := emitProgress(counter, repository.Size, util.NewRawLogger()) hash := sha256.New() tee := io.TeeReader(counter, hash) s := snappystream.NewReader(tee, true) _, err = io.Copy(file, s) if err != nil { logger.WithField("Error", err).Error("Unable to copy data from URL to file") os.Remove(file.Name()) return soft.Exit(err) } stopEmit <- true logger.Println("Download complete") calculatedHash := hex.EncodeToString(hash.Sum(nil)) if calculatedHash != repository.Sha256 { return soft.Exit(fmt.Errorf("Calculated hash did not match provided hash (calculated: %s ; expected: %s)", calculatedHash, repository.Sha256)) } if options.Load { _, err = file.Seek(0, 0) if err != nil { logger.WithField("Error", err).Error("Unable to reset seeker") return soft.Exit(err) } dockerClient, err := dockerlocal.NewDockerClient(dockerOptions) if err != nil { logger.WithField("Error", err).Error("Unable to create Docker client") return soft.Exit(err) } logger.Println("Importing into Docker") importImageOptions := docker.LoadImageOptions{InputStream: file} err = dockerClient.LoadImage(importImageOptions) if err != nil { logger.WithField("Error", err).Error("Unable to load image") return soft.Exit(err) } logger.Println("Finished importing into Docker") } return nil }
// Fetch grabs the Step content (or calls FetchScript for script steps). func (s *ExternalStep) Fetch() (string, error) { // NOTE(termie): polymorphism based on kind, we could probably do something // with interfaces here, but this is okay for now if s.IsScript() { return s.FetchScript() } stepPath := filepath.Join(s.options.StepPath(), s.CachedName()) stepExists, err := util.Exists(stepPath) if err != nil { return "", err } if !stepExists { // If we don't have a url already if s.url == "" { // Grab the info about the step from the api // TODO(termie): probably don't need these in global options? apiOptions := api.APIOptions{ BaseURL: s.options.GlobalOptions.BaseURL, AuthToken: s.options.GlobalOptions.AuthToken, } client := api.NewAPIClient(&apiOptions) stepInfo, err := client.GetStepVersion(s.Owner(), s.Name(), s.Version()) if err != nil { if apiErr, ok := err.(*api.APIError); ok && apiErr.StatusCode == 404 { return "", fmt.Errorf("The step \"%s\" was not found", s.ID()) } return "", err } s.url = stepInfo.TarballURL } // If we have a file uri let's just copytree it. if strings.HasPrefix(s.url, "file:///") { if s.options.EnableDevSteps { localPath := s.url[len("file://"):] err = shutil.CopyTree(localPath, stepPath, nil) if err != nil { return "", err } } else { return "", fmt.Errorf("Dev mode is not enabled so refusing to copy local file urls: %s", s.url) } } else { // Grab the tarball and util.Untargzip it resp, err := util.FetchTarball(s.url) if err != nil { return "", err } // Assuming we have a gzip'd tarball at this point err = util.Untargzip(stepPath, resp.Body) if err != nil { return "", err } } } hostStepPath := s.HostPath() err = shutil.CopyTree(stepPath, hostStepPath, nil) if err != nil { return "", nil } // Now that we have the code, load any step config we might find desc, err := ReadStepDesc(s.HostPath("wercker-step.yml")) if err != nil && !os.IsNotExist(err) { // TODO(termie): Log an error instead of printing s.logger.Println("ERROR: Reading wercker-step.yml:", err) } if err == nil { s.stepDesc = desc } return hostStepPath, nil }