// Restore loads the arguments from disk and prefills the Request func Restore(config *api.Config, cmd *cobra.Command) { data, err := ioutil.ReadFile(DefaultConfigPath) if err != nil { data, err = ioutil.ReadFile(".stifile") if err != nil { glog.V(1).Infof("Unable to restore %s: %v", DefaultConfigPath, err) return } glog.Infof("DEPRECATED: Use %s instead of .stifile", DefaultConfigPath) } c := Config{} if err := json.Unmarshal(data, &c); err != nil { glog.V(1).Infof("Unable to parse %s: %v", DefaultConfigPath, err) return } config.BuilderImage = c.BuilderImage config.Source = c.Source config.Tag = c.Tag for name, value := range c.Flags { // Do not change flags that user sets. Allow overriding of stored flags. if cmd.Flag(name).Changed { continue } cmd.Flags().Set(name, value) } }
func newCmdRebuild(cfg *api.Config) *cobra.Command { buildCmd := &cobra.Command{ Use: "rebuild <image> [<new-tag>]", Short: "Rebuild an existing image", Long: "Rebuild an existing application image that was build by S2I previously.", Run: func(cmd *cobra.Command, args []string) { // If user specifies the arguments, then we override the stored ones if len(args) >= 0 { cfg.Tag = args[0] } else { cmd.Help() os.Exit(1) } if r, err := os.Open(cfg.DockerCfgPath); err == nil { cfg.PullAuthentication = docker.GetImageRegistryAuth(r, cfg.Tag) } err := build.GenerateConfigFromLabels(cfg.Tag, cfg) checkErr(err) if len(args) >= 2 { cfg.Tag = args[1] } // Attempt to read the .dockercfg and extract the authentication for // docker pull if r, err := os.Open(cfg.DockerCfgPath); err == nil { cfg.PullAuthentication = docker.GetImageRegistryAuth(r, cfg.BuilderImage) } if glog.V(2) { fmt.Printf("\n%s\n", describe.DescribeConfig(cfg)) } builder, err := strategies.GetStrategy(cfg) checkErr(err) result, err := builder.Build(cfg) checkErr(err) for _, message := range result.Messages { glog.V(1).Infof(message) } }, } buildCmd.Flags().BoolVarP(&(cfg.Quiet), "quiet", "q", false, "Operate quietly. Suppress all non-error output.") buildCmd.Flags().BoolVar(&(cfg.Incremental), "incremental", true, "Perform an incremental build") buildCmd.Flags().BoolVar(&(cfg.RemovePreviousImage), "rm", false, "Remove the previous image during incremental builds") buildCmd.Flags().StringVar(&(cfg.CallbackURL), "callback-url", "", "Specify a URL to invoke via HTTP POST upon build completion") buildCmd.Flags().BoolVar(&(cfg.ForcePull), "force-pull", true, "Always pull the builder image even if it is present locally") buildCmd.Flags().BoolVar(&(cfg.PreserveWorkingDir), "save-temp-dir", false, "Save the temporary directory used by STI instead of deleting it") buildCmd.Flags().StringVarP(&(cfg.DockerCfgPath), "dockercfg-path", "", filepath.Join(os.Getenv("HOME"), ".dockercfg"), "Specify the path to the Docker configuration file") return buildCmd }
func newCmdRebuild(cfg *api.Config) *cobra.Command { buildCmd := &cobra.Command{ Use: "rebuild <image> [<new-tag>]", Short: "Rebuild an existing image", Long: "Rebuild an existing application image that was built by S2I previously.", Run: func(cmd *cobra.Command, args []string) { // If user specifies the arguments, then we override the stored ones if len(args) >= 1 { cfg.Tag = args[0] } else { cmd.Help() os.Exit(1) } if r, err := os.Open(cfg.DockerCfgPath); err == nil { cfg.PullAuthentication = docker.LoadAndGetImageRegistryAuth(r, cfg.Tag) } err := build.GenerateConfigFromLabels(cfg.Tag, cfg) checkErr(err) if len(args) >= 2 { cfg.Tag = args[1] } // Attempt to read the .dockercfg and extract the authentication for // docker pull if r, err := os.Open(cfg.DockerCfgPath); err == nil { cfg.PullAuthentication = docker.LoadAndGetImageRegistryAuth(r, cfg.BuilderImage) } if len(cfg.BuilderPullPolicy) == 0 { cfg.BuilderPullPolicy = api.DefaultBuilderPullPolicy } if glog.V(2) { fmt.Printf("\n%s\n", describe.DescribeConfig(cfg)) } builder, err := strategies.GetStrategy(cfg) checkErr(err) result, err := builder.Build(cfg) checkErr(err) for _, message := range result.Messages { glog.V(1).Infof(message) } }, } cmdutil.AddCommonFlags(buildCmd, cfg) return buildCmd }
// GenerateConfigFromLabels generates the S2I Config struct from the Docker // image labels. func GenerateConfigFromLabels(image string, config *api.Config) error { d, err := docker.New(config.DockerConfig, config.PullAuthentication) if err != nil { return err } var source *dockerclient.Image if config.ForcePull { source, err = d.PullImage(image) } else { source, err = d.CheckAndPullImage(image) } if err != nil { return err } if builderVersion, ok := source.Config.Labels["io.openshift.builder-version"]; ok { config.BuilderImageVersion = builderVersion config.BuilderBaseImageVersion = source.Config.Labels["io.openshift.builder-base-version"] } config.ScriptsURL = source.Config.Labels[api.DefaultNamespace+"scripts-url"] if len(config.ScriptsURL) == 0 { // FIXME: Backward compatibility config.ScriptsURL = source.Config.Labels["io.s2i.scripts-url"] } config.Description = source.Config.Labels[api.KubernetesNamespace+"description"] config.DisplayName = source.Config.Labels[api.KubernetesNamespace+"display-name"] if builder, ok := source.Config.Labels[api.DefaultNamespace+"build.image"]; ok { config.BuilderImage = builder } else { return fmt.Errorf("Required label %q not found in image", api.DefaultNamespace+"build.image") } if repo, ok := source.Config.Labels[api.DefaultNamespace+"build.source-location"]; ok { config.Source = repo } else { return fmt.Errorf("Required label %q not found in image", api.DefaultNamespace+"source-location") } config.ContextDir = source.Config.Labels[api.DefaultNamespace+"build.source-context-dir"] config.Ref = source.Config.Labels[api.DefaultNamespace+"build.commit.ref"] return nil }
// Download downloads the application source code from the GIT repository // and checkout the Ref specified in the config. func (c *Clone) Download(config *api.Config) (*api.SourceInfo, error) { targetSourceDir := filepath.Join(config.WorkingDir, api.Source) config.WorkingSourceDir = targetSourceDir var info *api.SourceInfo if c.ValidCloneSpec(config.Source) { if len(config.ContextDir) > 0 { targetSourceDir = filepath.Join(config.WorkingDir, api.ContextTmp) } glog.V(2).Infof("Cloning into %s", targetSourceDir) if err := c.Clone(config.Source, targetSourceDir); err != nil { glog.V(1).Infof("Git clone failed: %+v", err) return nil, err } if len(config.Ref) > 0 { if err := c.Checkout(targetSourceDir, config.Ref); err != nil { return nil, err } glog.V(1).Infof("Checked out %q", config.Ref) } if len(config.ContextDir) > 0 { originalTargetDir := filepath.Join(config.WorkingDir, api.Source) c.RemoveDirectory(originalTargetDir) // we want to copy entire dir contents, thus we need to use dir/. construct path := filepath.Join(targetSourceDir, config.ContextDir) + string(filepath.Separator) + "." err := c.Copy(path, originalTargetDir) if err != nil { return nil, err } info = c.GetInfo(targetSourceDir) c.RemoveDirectory(targetSourceDir) } else { info = c.GetInfo(targetSourceDir) } if len(config.ContextDir) > 0 { info.ContextDir = config.ContextDir } return info, nil } // we want to copy entire dir contents, thus we need to use dir/. construct path := filepath.Join(config.Source, config.ContextDir) + string(filepath.Separator) + "." if !c.Exists(path) { return nil, errors.NewSourcePathError(path) } if err := c.Copy(path, targetSourceDir); err != nil { return nil, err } // When building from a local directory (not using GIT clone spec scheme) we // skip gathering informations about the source as there is no guarantee that // the folder is a GIT repository or it requires context-dir to be set. if !config.Quiet { glog.Warning("You are using <source> location that is not valid GIT repository. The source code information will not be stored into the output image. Use this image only for local testing and development.") } return nil, nil }
// Strategy creates the appropriate build strategy for the provided config, using // the overrides provided. Not all strategies support all overrides. func Strategy(config *api.Config, overrides build.Overrides) (build.Builder, api.BuildInfo, error) { var builder build.Builder var buildInfo api.BuildInfo image, err := docker.GetBuilderImage(config) if err != nil { buildInfo.FailureReason = utilstatus.NewFailureReason(utilstatus.ReasonPullBuilderImageFailed, utilstatus.ReasonMessagePullBuilderImageFailed) return nil, buildInfo, err } config.HasOnBuild = image.OnBuild // if we're blocking onbuild, just do a normal s2i build flow // which won't do a docker build and invoke the onbuild commands if image.OnBuild && !config.BlockOnBuild { builder, err = onbuild.New(config, overrides) if err != nil { buildInfo.FailureReason = utilstatus.NewFailureReason(utilstatus.ReasonGenericS2IBuildFailed, utilstatus.ReasonMessageGenericS2iBuildFailed) return nil, buildInfo, err } return builder, buildInfo, nil } builder, err = sti.New(config, overrides) if err != nil { buildInfo.FailureReason = utilstatus.NewFailureReason(utilstatus.ReasonGenericS2IBuildFailed, utilstatus.ReasonMessageGenericS2iBuildFailed) return nil, buildInfo, err } return builder, buildInfo, err }
// New returns a new instance of OnBuild builder func New(config *api.Config, overrides build.Overrides) (*OnBuild, error) { dockerHandler, err := docker.New(config.DockerConfig, config.PullAuthentication) if err != nil { return nil, err } b := &OnBuild{ docker: dockerHandler, git: git.New(), fs: util.NewFileSystem(), tar: tar.New(), } // Use STI Prepare() and download the 'run' script optionally. s, err := sti.New(config, overrides) s.SetScripts([]string{}, []string{api.Assemble, api.Run}) downloader := overrides.Downloader if downloader == nil { d, sourceURL, err := scm.DownloaderForSource(config.Source) if err != nil { return nil, err } downloader = d config.Source = sourceURL } b.source = onBuildSourceHandler{ Downloader: downloader, Preparer: s, Ignorer: &ignore.DockerIgnorer{}, } b.garbage = &build.DefaultCleaner{b.fs, b.docker} return b, nil }
func (f *File) Download(config *api.Config) (*api.SourceInfo, error) { targetSourceDir := filepath.Join(config.WorkingDir, api.Source) sourceDir := strings.TrimPrefix(config.Source, "file://") config.WorkingSourceDir = targetSourceDir if len(config.ContextDir) > 0 { targetSourceDir = filepath.Join(config.WorkingDir, api.ContextTmp) } glog.V(1).Infof("Copying sources from %q to %q", sourceDir, targetSourceDir) err := f.CopyContents(sourceDir, targetSourceDir) if err != nil { return nil, err } if len(config.ContextDir) > 0 { originalTargetDir := filepath.Join(config.WorkingDir, api.Source) f.RemoveDirectory(originalTargetDir) // we want to copy entire dir contents, thus we need to use dir/. construct path := filepath.Join(targetSourceDir, config.ContextDir) + string(filepath.Separator) + "." err := f.Copy(path, originalTargetDir) if err != nil { return nil, err } f.RemoveDirectory(targetSourceDir) } return &api.SourceInfo{ Location: sourceDir, ContextDir: config.ContextDir, }, nil }
// New returns the instance of STI builder strategy for the given config. // If the layeredBuilder parameter is specified, then the builder provided will // be used for the case that the base Docker image does not have 'tar' or 'bash' // installed. func New(req *api.Config, overrides build.Overrides) (*STI, error) { docker, err := dockerpkg.New(req.DockerConfig, req.PullAuthentication) if err != nil { return nil, err } var incrementalDocker dockerpkg.Docker if req.Incremental { incrementalDocker, err = dockerpkg.New(req.DockerConfig, req.IncrementalAuthentication) if err != nil { return nil, err } } inst := scripts.NewInstaller(req.BuilderImage, req.ScriptsURL, docker, req.PullAuthentication) b := &STI{ installer: inst, config: req, docker: docker, incrementalDocker: incrementalDocker, git: git.New(), fs: util.NewFileSystem(), tar: tar.New(), callbackInvoker: util.NewCallbackInvoker(), requiredScripts: []string{api.Assemble, api.Run}, optionalScripts: []string{api.SaveArtifacts}, externalScripts: map[string]bool{}, installedScripts: map[string]bool{}, scriptsURL: map[string]string{}, } // The sources are downloaded using the GIT downloader. // TODO: Add more SCM in future. // TODO: explicit decision made to customize processing for usage specifically vs. // leveraging overrides; also, we ultimately want to simplify s2i usage a good bit, // which would lead to replacing this quick short circuit (so this change is tactical) b.source = overrides.Downloader if b.source == nil && !req.Usage { downloader, sourceURL, err := scm.DownloaderForSource(req.Source, req.ForceCopy) if err != nil { return nil, err } b.source = downloader req.Source = sourceURL } b.garbage = &build.DefaultCleaner{b.fs, b.docker} b.layered, err = layered.New(req, b, overrides) // Set interfaces b.preparer = b // later on, if we support say .gitignore func in addition to .dockerignore func, setting // ignorer will be based on config setting b.ignorer = &ignore.DockerIgnorer{} b.artifacts = b b.scripts = b b.postExecutor = b return b, err }
// New returns the instance of STI builder strategy for the given config. // If the layeredBuilder parameter is specified, then the builder provided will // be used for the case that the base Docker image does not have 'tar' or 'bash' // installed. func New(req *api.Config, overrides build.Overrides) (*STI, error) { docker, err := dockerpkg.New(req.DockerConfig, req.PullAuthentication) if err != nil { return nil, err } var incrementalDocker dockerpkg.Docker if req.Incremental { incrementalDocker, err = dockerpkg.New(req.DockerConfig, req.IncrementalAuthentication) if err != nil { return nil, err } } inst := scripts.NewInstaller(req.BuilderImage, req.ScriptsURL, docker, req.PullAuthentication) b := &STI{ installer: inst, config: req, docker: docker, incrementalDocker: incrementalDocker, git: git.New(), fs: util.NewFileSystem(), tar: tar.New(), callbackInvoker: util.NewCallbackInvoker(), requiredScripts: []string{api.Assemble, api.Run}, optionalScripts: []string{api.SaveArtifacts}, externalScripts: map[string]bool{}, installedScripts: map[string]bool{}, scriptsURL: map[string]string{}, } // The sources are downloaded using the GIT downloader. // TODO: Add more SCM in future. b.source = overrides.Downloader if b.source == nil { downloader, sourceURL, err := scm.DownloaderForSource(req.Source) if err != nil { return nil, err } b.source = downloader req.Source = sourceURL } b.garbage = &build.DefaultCleaner{b.fs, b.docker} b.layered, err = layered.New(req, b, overrides) // Set interfaces b.preparer = b // later on, if we support say .gitignore func in addition to .dockerignore func, setting // ignorer will be based on config setting b.ignorer = &ignore.DockerIgnorer{} b.artifacts = b b.scripts = b b.postExecutor = b return b, err }
// GenerateConfigFromLabels generates the S2I Config struct from the Docker // image labels. func GenerateConfigFromLabels(config *api.Config, metadata *docker.PullResult) error { if config == nil { return errors.New("config must be provided to GenerateConfigFromLabels") } if metadata == nil { return errors.New("image metadata must be provided to GenerateConfigFromLabels") } labels := metadata.Image.Config.Labels if builderVersion, ok := labels["io.openshift.builder-version"]; ok { config.BuilderImageVersion = builderVersion config.BuilderBaseImageVersion = labels["io.openshift.builder-base-version"] } config.ScriptsURL = labels[api.DefaultNamespace+"scripts-url"] if len(config.ScriptsURL) == 0 { // FIXME: Backward compatibility config.ScriptsURL = labels["io.s2i.scripts-url"] } config.Description = labels[api.KubernetesNamespace+"description"] config.DisplayName = labels[api.KubernetesNamespace+"display-name"] if builder, ok := labels[api.DefaultNamespace+"build.image"]; ok { config.BuilderImage = builder } else { return fmt.Errorf("Required label %q not found in image", api.DefaultNamespace+"build.image") } if repo, ok := labels[api.DefaultNamespace+"build.source-location"]; ok { config.Source = repo } else { return fmt.Errorf("Required label %q not found in image", api.DefaultNamespace+"source-location") } config.ContextDir = labels[api.DefaultNamespace+"build.source-context-dir"] config.Ref = labels[api.DefaultNamespace+"build.commit.ref"] return nil }
func newCmdUsage(cfg *api.Config) *cobra.Command { oldScriptsFlag := "" oldDestination := "" usageCmd := &cobra.Command{ Use: "usage <image>", Short: "Print usage of the assemble script associated with the image", Long: "Create and start a container from the image and invoke its usage script.", Run: func(cmd *cobra.Command, args []string) { if len(args) == 0 { cmd.Help() os.Exit(1) } cfg.Usage = true cfg.BuilderImage = args[0] if len(oldScriptsFlag) != 0 { glog.Warning("DEPRECATED: Flag --scripts is deprecated, use --scripts-url instead") cfg.ScriptsURL = oldScriptsFlag } if len(cfg.BuilderPullPolicy) == 0 { cfg.BuilderPullPolicy = api.DefaultBuilderPullPolicy } if len(cfg.PreviousImagePullPolicy) == 0 { cfg.PreviousImagePullPolicy = api.DefaultPreviousImagePullPolicy } uh, err := sti.NewUsage(cfg) checkErr(err) err = uh.Show() checkErr(err) }, } usageCmd.Flags().StringVarP(&(oldDestination), "location", "l", "", "Specify a destination location for untar operation") cmdutil.AddCommonFlags(usageCmd, cfg) return usageCmd }
func newCmdUsage(cfg *api.Config) *cobra.Command { oldScriptsFlag := "" oldDestination := "" usageCmd := &cobra.Command{ Use: "usage <image>", Short: "Print usage of the assemble script associated with the image", Long: "Create and start a container from the image and invoke its usage script.", Run: func(cmd *cobra.Command, args []string) { if len(args) == 0 { cmd.Help() os.Exit(1) } cfg.BuilderImage = args[0] envs, err := parseEnvs(cmd, "env") checkErr(err) cfg.Environment = envs if len(oldScriptsFlag) != 0 { glog.Warning("Flag --scripts is deprecated, use --scripts-url instead") cfg.ScriptsURL = oldScriptsFlag } uh, err := sti.NewUsage(cfg) checkErr(err) err = uh.Show() checkErr(err) }, } usageCmd.Flags().StringP("env", "e", "", "Specify an environment var NAME=VALUE,NAME2=VALUE2,...") usageCmd.Flags().StringVarP(&(cfg.ScriptsURL), "scripts-url", "s", "", "Specify a URL for the assemble and run scripts") usageCmd.Flags().StringVar(&(oldScriptsFlag), "scripts", "", "Specify a URL for the assemble and run scripts") usageCmd.Flags().BoolVar(&(cfg.ForcePull), "force-pull", true, "Always pull the builder image even if it is present locally") usageCmd.Flags().BoolVar(&(cfg.PreserveWorkingDir), "save-temp-dir", false, "Save the temporary directory used by STI instead of deleting it") usageCmd.Flags().StringVarP(&(oldDestination), "location", "l", "", "Specify a destination location for untar operation") usageCmd.Flags().StringVarP(&(cfg.Destination), "destination", "d", "", "Specify a destination location for untar operation") return usageCmd }
// GenerateConfigFromLabels generates the S2I Config struct from the Docker // image labels. func GenerateConfigFromLabels(config *api.Config) error { result, err := docker.GetBuilderImage(config) if err != nil { return err } labels := result.Image.Config.Labels if builderVersion, ok := labels["io.openshift.builder-version"]; ok { config.BuilderImageVersion = builderVersion config.BuilderBaseImageVersion = labels["io.openshift.builder-base-version"] } config.ScriptsURL = labels[api.DefaultNamespace+"scripts-url"] if len(config.ScriptsURL) == 0 { // FIXME: Backward compatibility config.ScriptsURL = labels["io.s2i.scripts-url"] } config.Description = labels[api.KubernetesNamespace+"description"] config.DisplayName = labels[api.KubernetesNamespace+"display-name"] if builder, ok := labels[api.DefaultNamespace+"build.image"]; ok { config.BuilderImage = builder } else { return fmt.Errorf("Required label %q not found in image", api.DefaultNamespace+"build.image") } if repo, ok := labels[api.DefaultNamespace+"build.source-location"]; ok { config.Source = repo } else { return fmt.Errorf("Required label %q not found in image", api.DefaultNamespace+"source-location") } config.ContextDir = labels[api.DefaultNamespace+"build.source-context-dir"] config.Ref = labels[api.DefaultNamespace+"build.commit.ref"] return nil }
// Strategy creates the appropriate build strategy for the provided config, using // the overrides provided. Not all strategies support all overrides. func Strategy(config *api.Config, overrides build.Overrides) (build.Builder, error) { image, err := docker.GetBuilderImage(config) if err != nil { return nil, err } config.HasOnBuild = image.OnBuild // if we're blocking onbuild, just do a normal s2i build flow // which won't do a docker build and invoke the onbuild commands if image.OnBuild && !config.BlockOnBuild { return onbuild.New(config, overrides) } return sti.New(config, overrides) }
func (f *File) Download(config *api.Config) (*api.SourceInfo, error) { targetSourceDir := filepath.Join(config.WorkingDir, api.Source) sourceDir := strings.TrimPrefix(config.Source, "file://") config.WorkingSourceDir = targetSourceDir if len(config.ContextDir) > 0 { targetSourceDir = filepath.Join(targetSourceDir, config.ContextDir, ".") } glog.V(1).Infof("Copying sources from %q to %q", sourceDir, targetSourceDir) err := f.Copy(sourceDir, targetSourceDir) if err != nil { return nil, err } return &api.SourceInfo{ Location: sourceDir, ContextDir: config.ContextDir, }, nil }
// Download copies sources from a local directory into the working directory func (f *File) Download(config *api.Config) (*api.SourceInfo, error) { config.WorkingSourceDir = filepath.Join(config.WorkingDir, api.Source) source := strings.TrimPrefix(config.Source, "file://") copySrc := source if len(config.ContextDir) > 0 { copySrc = filepath.Join(source, config.ContextDir) } glog.V(1).Infof("Copying sources from %q to %q", copySrc, config.WorkingSourceDir) err := f.CopyContents(copySrc, config.WorkingSourceDir) if err != nil { return nil, err } return &api.SourceInfo{ Location: source, ContextDir: config.ContextDir, }, nil }
func newCmdBuild(cfg *api.Config) *cobra.Command { useConfig := false oldScriptsFlag := "" oldDestination := "" buildCmd := &cobra.Command{ Use: "build <source> <image> [<tag>]", Short: "Build a new image", Long: "Build a new Docker image named <tag> (if provided) from a source repository and base image.", Run: func(cmd *cobra.Command, args []string) { go func() { for { sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGQUIT) buf := make([]byte, 1<<20) for { <-sigs runtime.Stack(buf, true) if file, err := ioutil.TempFile(os.TempDir(), "sti_dump"); err == nil { defer file.Close() file.Write(buf) } glog.Infof("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end\n", buf) } } }() // Attempt to restore the build command from the configuration file if useConfig { config.Restore(cfg, cmd) } // If user specifies the arguments, then we override the stored ones if len(args) >= 2 { cfg.Source = args[0] cfg.BuilderImage = args[1] if len(args) >= 3 { cfg.Tag = args[2] } } if len(validation.ValidateConfig(cfg)) != 0 { cmd.Help() os.Exit(1) } // Persists the current command line options and config into .stifile if useConfig { config.Save(cfg, cmd) } // Attempt to read the .dockercfg and extract the authentication for // docker pull if r, err := os.Open(cfg.DockerCfgPath); err == nil { cfg.PullAuthentication = docker.GetImageRegistryAuth(r, cfg.BuilderImage) } cfg.Environment = map[string]string{} if len(cfg.EnvironmentFile) > 0 { result, err := util.ReadEnvironmentFile(cfg.EnvironmentFile) if err != nil { glog.Warningf("Unable to read %s: %v", cfg.EnvironmentFile, err) } else { cfg.Environment = result } } envs, err := parseEnvs(cmd, "env") checkErr(err) for k, v := range envs { cfg.Environment[k] = v } if len(oldScriptsFlag) != 0 { glog.Warning("Flag --scripts is deprecated, use --scripts-url instead") cfg.ScriptsURL = oldScriptsFlag } if len(oldDestination) != 0 { glog.Warning("Flag --location is deprecated, use --destination instead") cfg.Destination = oldDestination } if glog.V(2) { fmt.Printf("\n%s\n", describe.DescribeConfig(cfg)) } builder, err := strategies.GetStrategy(cfg) checkErr(err) result, err := builder.Build(cfg) checkErr(err) for _, message := range result.Messages { glog.V(1).Infof(message) } }, } buildCmd.Flags().BoolVarP(&(cfg.Quiet), "quiet", "q", false, "Operate quietly. Suppress all non-error output.") buildCmd.Flags().BoolVar(&(cfg.Incremental), "incremental", false, "Perform an incremental build") buildCmd.Flags().BoolVar(&(cfg.RemovePreviousImage), "rm", false, "Remove the previous image during incremental builds") buildCmd.Flags().StringP("env", "e", "", "Specify an environment var NAME=VALUE,NAME2=VALUE2,...") buildCmd.Flags().StringVarP(&(cfg.Ref), "ref", "r", "", "Specify a ref to check-out") buildCmd.Flags().StringVar(&(cfg.CallbackURL), "callback-url", "", "Specify a URL to invoke via HTTP POST upon build completion") buildCmd.Flags().StringVarP(&(cfg.ScriptsURL), "scripts-url", "s", "", "Specify a URL for the assemble and run scripts") buildCmd.Flags().StringVar(&(oldScriptsFlag), "scripts", "", "Specify a URL for the assemble and run scripts") buildCmd.Flags().StringVarP(&(oldDestination), "location", "l", "", "Specify a destination location for untar operation") buildCmd.Flags().StringVarP(&(cfg.Destination), "destination", "d", "", "Specify a destination location for untar operation") buildCmd.Flags().BoolVar(&(cfg.ForcePull), "force-pull", true, "Always pull the builder image even if it is present locally") buildCmd.Flags().BoolVar(&(cfg.PreserveWorkingDir), "save-temp-dir", false, "Save the temporary directory used by STI instead of deleting it") buildCmd.Flags().BoolVar(&(useConfig), "use-config", false, "Store command line options to .stifile") buildCmd.Flags().StringVarP(&(cfg.ContextDir), "context-dir", "", "", "Specify the sub-directory inside the repository with the application sources") buildCmd.Flags().StringVarP(&(cfg.DockerCfgPath), "dockercfg-path", "", filepath.Join(os.Getenv("HOME"), ".dockercfg"), "Specify the path to the Docker configuration file") buildCmd.Flags().StringVarP(&(cfg.EnvironmentFile), "environment-file", "E", "", "Specify the path to the file with environment") buildCmd.Flags().StringVarP(&(cfg.DisplayName), "application-name", "n", "", "Specify the display name for the application (default: output image name)") buildCmd.Flags().StringVarP(&(cfg.Description), "description", "", "", "Specify the description of the application") return buildCmd }
// Execute runs the specified STI script in the builder image. func (builder *STI) Execute(command string, user string, config *api.Config) error { glog.V(2).Infof("Using image name %s", config.BuilderImage) // we can't invoke this method before (for example in New() method) // because of later initialization of config.WorkingDir builder.env = createBuildEnvironment(config) errOutput := "" outReader, outWriter := io.Pipe() errReader, errWriter := io.Pipe() externalScripts := builder.externalScripts[command] // if LayeredBuild is called then all the scripts will be placed inside the image if config.LayeredBuild { externalScripts = false } opts := dockerpkg.RunContainerOptions{ Image: config.BuilderImage, Stdout: outWriter, Stderr: errWriter, // The PullImage is false because the PullImage function should be called // before we run the container PullImage: false, ExternalScripts: externalScripts, ScriptsURL: config.ScriptsURL, Destination: config.Destination, Command: command, Env: builder.env, User: user, PostExec: builder.postExecutor, NetworkMode: string(config.DockerNetworkMode), CGroupLimits: config.CGroupLimits, CapDrop: config.DropCapabilities, Binds: config.BuildVolumes.AsBinds(), } // If there are injections specified, override the original assemble script // and wait till all injections are uploaded into the container that runs the // assemble script. injectionError := make(chan error) if len(config.Injections) > 0 && command == api.Assemble { workdir, err := builder.docker.GetImageWorkdir(config.BuilderImage) if err != nil { builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason( utilstatus.ReasonGenericS2IBuildFailed, utilstatus.ReasonMessageGenericS2iBuildFailed, ) return err } config.Injections = util.FixInjectionsWithRelativePath(workdir, config.Injections) injectedFiles, err := util.ExpandInjectedFiles(builder.fs, config.Injections) if err != nil { builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason( utilstatus.ReasonInstallScriptsFailed, utilstatus.ReasonMessageInstallScriptsFailed, ) return err } rmScript, err := util.CreateInjectedFilesRemovalScript(injectedFiles, "/tmp/rm-injections") if err != nil { builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason( utilstatus.ReasonGenericS2IBuildFailed, utilstatus.ReasonMessageGenericS2iBuildFailed, ) return err } defer os.Remove(rmScript) opts.CommandOverrides = func(cmd string) string { return fmt.Sprintf("while [ ! -f %q ]; do sleep 0.5; done; %s; result=$?; source %[1]s; exit $result", "/tmp/rm-injections", cmd) } originalOnStart := opts.OnStart opts.OnStart = func(containerID string) error { defer close(injectionError) glog.V(2).Info("starting the injections uploading ...") for _, s := range config.Injections { if err := builder.docker.UploadToContainer(builder.fs, s.Source, s.Destination, containerID); err != nil { injectionError <- util.HandleInjectionError(s, err) return err } } if err := builder.docker.UploadToContainer(builder.fs, rmScript, "/tmp/rm-injections", containerID); err != nil { injectionError <- util.HandleInjectionError(api.VolumeSpec{Source: rmScript, Destination: "/tmp/rm-injections"}, err) return err } if originalOnStart != nil { return originalOnStart(containerID) } return nil } } else { close(injectionError) } if !config.LayeredBuild { r, w := io.Pipe() opts.Stdin = r go func() { // Wait for the injections to complete and check the error. Do not start // streaming the sources when the injection failed. if <-injectionError != nil { w.Close() return } glog.V(2).Info("starting the source uploading ...") uploadDir := filepath.Join(config.WorkingDir, "upload") w.CloseWithError(builder.tar.CreateTarStream(uploadDir, false, w)) }() } dockerpkg.StreamContainerIO(outReader, nil, func(s string) { if !config.Quiet { glog.Info(strings.TrimSpace(s)) } }) c := dockerpkg.StreamContainerIO(errReader, &errOutput, func(s string) { glog.Info(s) }) err := builder.docker.RunContainer(opts) if e, ok := err.(s2ierr.ContainerError); ok { // Must wait for StreamContainerIO goroutine above to exit before reading errOutput. <-c err = s2ierr.NewContainerError(config.BuilderImage, e.ErrorCode, errOutput) } return err }
func newCmdBuild(cfg *api.Config) *cobra.Command { useConfig := false oldScriptsFlag := "" oldDestination := "" buildCmd := &cobra.Command{ Use: "build <source> <image> [<tag>]", Short: "Build a new image", Long: "Build a new Docker image named <tag> (if provided) from a source repository and base image.", Example: ` # Build an application Docker image from a Git repository $ s2i build git://github.com/openshift/ruby-hello-world centos/ruby-22-centos7 hello-world-app # Build from a local directory $ s2i build . centos/ruby-22-centos7 hello-world-app `, Run: func(cmd *cobra.Command, args []string) { glog.V(1).Infof("Running S2I version %q\n", version.Get()) // Attempt to restore the build command from the configuration file if useConfig { config.Restore(cfg, cmd) } // If user specifies the arguments, then we override the stored ones if len(args) >= 2 { cfg.Source = args[0] cfg.BuilderImage = args[1] if len(args) >= 3 { cfg.Tag = args[2] } } if cfg.Incremental && len(cfg.RuntimeImage) > 0 { fmt.Fprintln(os.Stderr, "ERROR: Incremental build with runtime image isn't supported") os.Exit(1) } if cfg.ForcePull { glog.Warning("DEPRECATED: The '--force-pull' option is deprecated. Use '--pull-policy' instead") } if len(cfg.BuilderPullPolicy) == 0 { cfg.BuilderPullPolicy = api.DefaultBuilderPullPolicy } if len(cfg.PreviousImagePullPolicy) == 0 { cfg.PreviousImagePullPolicy = api.DefaultPreviousImagePullPolicy } if errs := validation.ValidateConfig(cfg); len(errs) > 0 { for _, e := range errs { fmt.Fprintf(os.Stderr, "ERROR: %s\n", e) } fmt.Println() cmd.Help() os.Exit(1) } // Persists the current command line options and config into .s2ifile if useConfig { config.Save(cfg, cmd) } // Attempt to read the .dockercfg and extract the authentication for // docker pull if r, err := os.Open(cfg.DockerCfgPath); err == nil { defer r.Close() auths := docker.LoadImageRegistryAuth(r) cfg.PullAuthentication = docker.GetImageRegistryAuth(auths, cfg.BuilderImage) if cfg.Incremental { cfg.IncrementalAuthentication = docker.GetImageRegistryAuth(auths, cfg.Tag) } if len(cfg.RuntimeImage) > 0 { cfg.RuntimeAuthentication = docker.GetImageRegistryAuth(auths, cfg.RuntimeImage) } } if len(cfg.EnvironmentFile) > 0 { result, err := util.ReadEnvironmentFile(cfg.EnvironmentFile) if err != nil { glog.Warningf("Unable to read environment file %q: %v", cfg.EnvironmentFile, err) } else { for name, value := range result { cfg.Environment = append(cfg.Environment, api.EnvironmentSpec{Name: name, Value: value}) } } } if len(oldScriptsFlag) != 0 { glog.Warning("DEPRECATED: Flag --scripts is deprecated, use --scripts-url instead") cfg.ScriptsURL = oldScriptsFlag } if len(oldDestination) != 0 { glog.Warning("DEPRECATED: Flag --location is deprecated, use --destination instead") cfg.Destination = oldDestination } glog.V(2).Infof("\n%s\n", describe.Config(cfg)) err := docker.CheckReachable(cfg) if err != nil { glog.Fatal(err) } builder, _, err := strategies.GetStrategy(cfg) checkErr(err) result, err := builder.Build(cfg) checkErr(err) for _, message := range result.Messages { glog.V(1).Infof(message) } if cfg.RunImage { runner, err := run.New(cfg) checkErr(err) err = runner.Run(cfg) checkErr(err) } }, } cmdutil.AddCommonFlags(buildCmd, cfg) buildCmd.Flags().BoolVar(&(cfg.RunImage), "run", false, "Run resulting image as part of invocation of this command") buildCmd.Flags().BoolVar(&(cfg.IgnoreSubmodules), "ignore-submodules", false, "Ignore all git submodules when cloning application repository") buildCmd.Flags().VarP(&(cfg.Environment), "env", "e", "Specify an single environment variable in NAME=VALUE format") buildCmd.Flags().StringVarP(&(cfg.Ref), "ref", "r", "", "Specify a ref to check-out") buildCmd.Flags().StringVarP(&(cfg.AssembleUser), "assemble-user", "", "", "Specify the user to run assemble with") buildCmd.Flags().StringVarP(&(cfg.ContextDir), "context-dir", "", "", "Specify the sub-directory inside the repository with the application sources") buildCmd.Flags().StringVarP(&(cfg.ExcludeRegExp), "exclude", "", tar.DefaultExclusionPattern.String(), "Regular expression for selecting files from the source tree to exclude from the build, where the default excludes the '.git' directory (see https://golang.org/pkg/regexp for syntax, but note that \"\" will be interpreted as allow all files and exclude no files)") buildCmd.Flags().StringVarP(&(cfg.ScriptsURL), "scripts-url", "s", "", "Specify a URL for the assemble, assemble-runtime and run scripts") buildCmd.Flags().StringVar(&(oldScriptsFlag), "scripts", "", "DEPRECATED: Specify a URL for the assemble and run scripts") buildCmd.Flags().BoolVar(&(useConfig), "use-config", false, "Store command line options to .s2ifile") buildCmd.Flags().StringVarP(&(cfg.EnvironmentFile), "environment-file", "E", "", "Specify the path to the file with environment") buildCmd.Flags().StringVarP(&(cfg.DisplayName), "application-name", "n", "", "Specify the display name for the application (default: output image name)") buildCmd.Flags().StringVarP(&(cfg.Description), "description", "", "", "Specify the description of the application") buildCmd.Flags().VarP(&(cfg.AllowedUIDs), "allowed-uids", "u", "Specify a range of allowed user ids for the builder and runtime images") buildCmd.Flags().VarP(&(cfg.Injections), "inject", "i", "Specify a directory to inject into the assemble container") buildCmd.Flags().VarP(&(cfg.BuildVolumes), "volume", "v", "Specify a volume to mount into the assemble container") buildCmd.Flags().StringSliceVar(&(cfg.DropCapabilities), "cap-drop", []string{}, "Specify a comma-separated list of capabilities to drop when running Docker containers") buildCmd.Flags().StringVarP(&(oldDestination), "location", "l", "", "DEPRECATED: Specify a destination location for untar operation") buildCmd.Flags().BoolVarP(&(cfg.ForceCopy), "copy", "c", false, "Use local file system copy instead of git cloning the source url") buildCmd.Flags().StringVar(&(cfg.RuntimeImage), "runtime-image", "", "Image that will be used as the base for the runtime image") buildCmd.Flags().VarP(&(cfg.RuntimeArtifacts), "runtime-artifact", "a", "Specify a file or directory to be copied from the builder to the runtime image") return buildCmd }
// Download downloads the application source code from the Git repository // and checkout the Ref specified in the config. func (c *Clone) Download(config *api.Config) (*api.SourceInfo, error) { targetSourceDir := filepath.Join(config.WorkingDir, api.Source) config.WorkingSourceDir = targetSourceDir ok, err := c.ValidCloneSpec(config.Source) if err != nil { return nil, err } if !ok { glog.Errorf("Clone.Download was passed an invalid source %s", config.Source) return nil, fmt.Errorf("invalid source %s", config.Source) } ref := "HEAD" if config.Ref != "" { ref = config.Ref } if strings.HasPrefix(config.Source, "file://") { s := strings.TrimPrefix(config.Source, "file://") if util.UsingCygwinGit { var err error s, err = util.ToSlashCygwin(s) if err != nil { glog.V(0).Infof("error: Cygwin path conversion failed: %v", err) return nil, err } } config.Source = "file://" + s } if len(config.ContextDir) > 0 { targetSourceDir = filepath.Join(config.WorkingDir, api.ContextTmp) glog.V(1).Infof("Downloading %q (%q) ...", config.Source, config.ContextDir) } else { glog.V(1).Infof("Downloading %q ...", config.Source) } if !config.IgnoreSubmodules { glog.V(2).Infof("Cloning sources into %q", targetSourceDir) } else { glog.V(2).Infof("Cloning sources (ignoring submodules) into %q", targetSourceDir) } cloneConfig := api.CloneConfig{Quiet: true} err = c.Clone(config.Source, targetSourceDir, cloneConfig) if err != nil { glog.V(0).Infof("error: git clone failed: %v", err) return nil, err } err = c.Checkout(targetSourceDir, ref) if err != nil { return nil, err } glog.V(1).Infof("Checked out %q", ref) if !config.IgnoreSubmodules { err = c.SubmoduleUpdate(targetSourceDir, true, true) if err != nil { return nil, err } glog.V(1).Infof("Updated submodules for %q", ref) } // Record Git's knowledge about file permissions if runtime.GOOS == "windows" { filemodes, err := c.LsTree(filepath.Join(targetSourceDir, config.ContextDir), ref, true) if err != nil { return nil, err } for _, filemode := range filemodes { c.Chmod(filepath.Join(targetSourceDir, config.ContextDir, filemode.Name()), os.FileMode(filemode.Mode())&os.ModePerm) } } info := c.GetInfo(targetSourceDir) if len(config.ContextDir) > 0 { originalTargetDir := filepath.Join(config.WorkingDir, api.Source) c.RemoveDirectory(originalTargetDir) path := filepath.Join(targetSourceDir, config.ContextDir) err := c.CopyContents(path, originalTargetDir) if err != nil { return nil, err } c.RemoveDirectory(targetSourceDir) } if len(config.ContextDir) > 0 { info.ContextDir = config.ContextDir } return info, nil }
// Download downloads the application source code from the Git repository // and checkout the Ref specified in the config. func (c *Clone) Download(config *api.Config) (*api.SourceInfo, error) { targetSourceDir := filepath.Join(config.WorkingDir, api.Source) config.WorkingSourceDir = targetSourceDir var info *api.SourceInfo hasRef := len(config.Ref) > 0 hasSubmodules := !config.DisableRecursive cloneConfig := api.CloneConfig{Quiet: true, Recursive: hasSubmodules && !hasRef} if c.ValidCloneSpec(config.Source) { if len(config.ContextDir) > 0 { targetSourceDir = filepath.Join(config.WorkingDir, api.ContextTmp) glog.V(1).Infof("Downloading %q (%q) ...", config.Source, config.ContextDir) } else { glog.V(1).Infof("Downloading %q ...", config.Source) } // If we have a specific checkout ref, use submodule update instead of recursive // Otherwise the versions will be incorrect. if hasRef && hasSubmodules { glog.V(2).Infof("Cloning sources (deferring submodule init) into %q", targetSourceDir) } else if cloneConfig.Recursive { glog.V(2).Infof("Cloning sources and all Git submodules into %q", targetSourceDir) } else { glog.V(2).Infof("Cloning sources into %q", targetSourceDir) } if err := c.Clone(config.Source, targetSourceDir, cloneConfig); err != nil { glog.V(1).Infof("Git clone failed: %+v", err) return nil, err } if hasRef { if err := c.Checkout(targetSourceDir, config.Ref); err != nil { return nil, err } glog.V(1).Infof("Checked out %q", config.Ref) if hasSubmodules { if err := c.SubmoduleUpdate(targetSourceDir, true, true); err != nil { return nil, err } glog.V(1).Infof("Updated submodules for %q", config.Ref) } } if len(config.ContextDir) > 0 { originalTargetDir := filepath.Join(config.WorkingDir, api.Source) c.RemoveDirectory(originalTargetDir) // we want to copy entire dir contents, thus we need to use dir/. construct path := filepath.Join(targetSourceDir, config.ContextDir) + string(filepath.Separator) + "." err := c.Copy(path, originalTargetDir) if err != nil { return nil, err } info = c.GetInfo(targetSourceDir) c.RemoveDirectory(targetSourceDir) } else { info = c.GetInfo(targetSourceDir) } if len(config.ContextDir) > 0 { info.ContextDir = config.ContextDir } return info, nil } // we want to copy entire dir contents, thus we need to use dir/. construct path := filepath.Join(config.Source, config.ContextDir) + string(filepath.Separator) + "." if !c.Exists(path) { return nil, errors.NewSourcePathError(path) } if err := c.Copy(path, targetSourceDir); err != nil { return nil, err } // When building from a local directory (not using Git clone spec scheme) we // skip gathering informations about the source as there is no guarantee that // the folder is a Git repository or it requires context-dir to be set. if !config.Quiet { glog.Warning("You are using <source> location that is not valid Git repository. The source code information will not be stored into the output image. Use this image only for local testing and development.") } return nil, nil }
// New returns the instance of STI builder strategy for the given config. // If the layeredBuilder parameter is specified, then the builder provided will // be used for the case that the base Docker image does not have 'tar' or 'bash' // installed. func New(config *api.Config, overrides build.Overrides) (*STI, error) { docker, err := dockerpkg.New(config.DockerConfig, config.PullAuthentication) if err != nil { return nil, err } var incrementalDocker dockerpkg.Docker if config.Incremental { incrementalDocker, err = dockerpkg.New(config.DockerConfig, config.IncrementalAuthentication) if err != nil { return nil, err } } inst := scripts.NewInstaller(config.BuilderImage, config.ScriptsURL, config.ScriptDownloadProxyConfig, docker, config.PullAuthentication) tarHandler := tar.New() tarHandler.SetExclusionPattern(regexp.MustCompile(config.ExcludeRegExp)) builder := &STI{ installer: inst, config: config, docker: docker, incrementalDocker: incrementalDocker, git: git.New(), fs: util.NewFileSystem(), tar: tarHandler, callbackInvoker: util.NewCallbackInvoker(), requiredScripts: []string{api.Assemble, api.Run}, optionalScripts: []string{api.SaveArtifacts}, optionalRuntimeScripts: []string{api.AssembleRuntime}, externalScripts: map[string]bool{}, installedScripts: map[string]bool{}, scriptsURL: map[string]string{}, } if len(config.RuntimeImage) > 0 { builder.runtimeInstaller = scripts.NewInstaller(config.RuntimeImage, config.ScriptsURL, config.ScriptDownloadProxyConfig, docker, config.PullAuthentication) builder.runtimeDocker, err = dockerpkg.New(config.DockerConfig, config.RuntimeAuthentication) if err != nil { return builder, err } } // The sources are downloaded using the Git downloader. // TODO: Add more SCM in future. // TODO: explicit decision made to customize processing for usage specifically vs. // leveraging overrides; also, we ultimately want to simplify s2i usage a good bit, // which would lead to replacing this quick short circuit (so this change is tactical) builder.source = overrides.Downloader if builder.source == nil && !config.Usage { downloader, sourceURL, err := scm.DownloaderForSource(config.Source, config.ForceCopy) if err != nil { return nil, err } builder.source = downloader config.Source = sourceURL } builder.garbage = build.NewDefaultCleaner(builder.fs, builder.docker) builder.layered, err = layered.New(config, builder, overrides) // Set interfaces builder.preparer = builder // later on, if we support say .gitignore func in addition to .dockerignore func, setting // ignorer will be based on config setting builder.ignorer = &ignore.DockerIgnorer{} builder.artifacts = builder builder.scripts = builder builder.postExecutor = builder builder.initPostExecutorSteps() return builder, err }
// Execute runs the specified STI script in the builder image. func (builder *STI) Execute(command string, user string, config *api.Config) error { glog.V(2).Infof("Using image name %s", config.BuilderImage) // we can't invoke this method before (for example in New() method) // because of later initialization of config.WorkingDir builder.env = createBuildEnvironment(config) errOutput := "" outReader, outWriter := io.Pipe() errReader, errWriter := io.Pipe() defer outReader.Close() defer outWriter.Close() defer errReader.Close() defer errWriter.Close() externalScripts := builder.externalScripts[command] // if LayeredBuild is called then all the scripts will be placed inside the image if config.LayeredBuild { externalScripts = false } opts := dockerpkg.RunContainerOptions{ Image: config.BuilderImage, Entrypoint: DefaultEntrypoint, Stdout: outWriter, Stderr: errWriter, // The PullImage is false because the PullImage function should be called // before we run the container PullImage: false, ExternalScripts: externalScripts, ScriptsURL: config.ScriptsURL, Destination: config.Destination, Command: command, Env: builder.env, User: user, PostExec: builder.postExecutor, NetworkMode: string(config.DockerNetworkMode), CGroupLimits: config.CGroupLimits, CapDrop: config.DropCapabilities, Binds: config.BuildVolumes.AsBinds(), } // If there are injections specified, override the original assemble script // and wait till all injections are uploaded into the container that runs the // assemble script. injectionComplete := make(chan struct{}) var injectionError error if len(config.Injections) > 0 && command == api.Assemble { workdir, err := builder.docker.GetImageWorkdir(config.BuilderImage) if err != nil { return err } config.Injections = util.FixInjectionsWithRelativePath(workdir, config.Injections) injectedFiles, err := util.ExpandInjectedFiles(config.Injections) if err != nil { return err } rmScript, err := util.CreateInjectedFilesRemovalScript(injectedFiles, "/tmp/rm-injections") if err != nil { return err } defer os.Remove(rmScript) opts.CommandOverrides = func(cmd string) string { return fmt.Sprintf("while [ ! -f %q ]; do sleep 0.5; done; %s; result=$?; source %[1]s; exit $result", "/tmp/rm-injections", cmd) } originalOnStart := opts.OnStart opts.OnStart = func(containerID string) error { defer close(injectionComplete) if err != nil { injectionError = err return err } glog.V(2).Info("starting the injections uploading ...") for _, s := range config.Injections { if err := builder.docker.UploadToContainer(s.Source, s.Destination, containerID); err != nil { injectionError = util.HandleInjectionError(s, err) return err } } if err := builder.docker.UploadToContainer(rmScript, "/tmp/rm-injections", containerID); err != nil { injectionError = util.HandleInjectionError(api.VolumeSpec{Source: rmScript, Destination: "/tmp/rm-injections"}, err) return err } if originalOnStart != nil { return originalOnStart(containerID) } return nil } } else { close(injectionComplete) } wg := sync.WaitGroup{} if !config.LayeredBuild { wg.Add(1) uploadDir := filepath.Join(config.WorkingDir, "upload") // TODO: be able to pass a stream directly to the Docker build to avoid the double temp hit r, w := io.Pipe() go func() { // Wait for the injections to complete and check the error. Do not start // streaming the sources when the injection failed. <-injectionComplete if injectionError != nil { wg.Done() return } glog.V(2).Info("starting the source uploading ...") var err error defer func() { w.CloseWithError(err) if r := recover(); r != nil { glog.Errorf("recovered panic: %#v", r) } wg.Done() }() err = builder.tar.CreateTarStream(uploadDir, false, w) }() opts.Stdin = r defer wg.Wait() } go func(reader io.Reader) { scanner := bufio.NewReader(reader) // Precede build output with newline glog.Info() for { text, err := scanner.ReadString('\n') if err != nil { // we're ignoring ErrClosedPipe, as this is information // the docker container ended streaming logs if glog.Is(2) && err != io.ErrClosedPipe && err != io.EOF { glog.Errorf("Error reading docker stdout, %v", err) } break } // Nothing is printed when the quiet option is set if config.Quiet { continue } glog.Info(strings.TrimSpace(text)) } // Terminate build output with new line glog.Info() }(outReader) go dockerpkg.StreamContainerIO(errReader, &errOutput, glog.Error) err := builder.docker.RunContainer(opts) if util.IsTimeoutError(err) { // Cancel waiting for source input if the container timeouts wg.Done() } if e, ok := err.(errors.ContainerError); ok { return errors.NewContainerError(config.BuilderImage, e.ErrorCode, errOutput) } return err }
func newCmdBuild(cfg *api.Config) *cobra.Command { useConfig := false oldScriptsFlag := "" oldDestination := "" buildCmd := &cobra.Command{ Use: "build <source> <image> [<tag>]", Short: "Build a new image", Long: "Build a new Docker image named <tag> (if provided) from a source repository and base image.", Example: ` # Build an application Docker image from a Git repository $ s2i build git://github.com/openshift/ruby-hello-world centos/ruby-22-centos7 hello-world-app # Build from a local directory $ s2i build . centos/ruby-22-centos7 hello-world-app `, Run: func(cmd *cobra.Command, args []string) { go cmdutil.InstallDumpOnSignal() // Attempt to restore the build command from the configuration file if useConfig { config.Restore(cfg, cmd) } // If user specifies the arguments, then we override the stored ones if len(args) >= 2 { cfg.Source = args[0] cfg.BuilderImage = args[1] if len(args) >= 3 { cfg.Tag = args[2] } } if cfg.ForcePull { glog.Warning("DEPRECATED: The '--force-pull' option is deprecated. Use '--pull-policy' instead") } if len(cfg.BuilderPullPolicy) == 0 { cfg.BuilderPullPolicy = api.DefaultBuilderPullPolicy } if errs := validation.ValidateConfig(cfg); len(errs) > 0 { for _, e := range errs { fmt.Fprintf(os.Stderr, "ERROR: %s\n", e) } fmt.Println() cmd.Help() os.Exit(1) } // Persists the current command line options and config into .stifile if useConfig { config.Save(cfg, cmd) } // Attempt to read the .dockercfg and extract the authentication for // docker pull if r, err := os.Open(cfg.DockerCfgPath); err == nil { auths := docker.LoadImageRegistryAuth(r) cfg.PullAuthentication = docker.GetImageRegistryAuth(auths, cfg.BuilderImage) if cfg.Incremental { cfg.IncrementalAuthentication = docker.GetImageRegistryAuth(auths, cfg.Tag) } } cfg.Environment = map[string]string{} if len(cfg.EnvironmentFile) > 0 { result, err := util.ReadEnvironmentFile(cfg.EnvironmentFile) if err != nil { glog.Warningf("Unable to read environment file %q: %v", cfg.EnvironmentFile, err) } else { cfg.Environment = result } } envs, err := cmdutil.ParseEnvs(cmd, "env") checkErr(err) for k, v := range envs { cfg.Environment[k] = v } if len(oldScriptsFlag) != 0 { glog.Warning("DEPRECATED: Flag --scripts is deprecated, use --scripts-url instead") cfg.ScriptsURL = oldScriptsFlag } if len(oldDestination) != 0 { glog.Warning("DEPRECATED: Flag --location is deprecated, use --destination instead") cfg.Destination = oldDestination } if glog.V(2) { fmt.Printf("\n%s\n", describe.DescribeConfig(cfg)) } if !docker.IsReachable(cfg) { glog.Fatalf("Unable to connect to Docker daemon. Please set the DOCKER_HOST or make sure the Docker socket %q exists", cfg.DockerConfig.Endpoint) } builder, err := strategies.GetStrategy(cfg) checkErr(err) result, err := builder.Build(cfg) checkErr(err) for _, message := range result.Messages { glog.V(1).Infof(message) } if cfg.RunImage { runner, err := run.New(cfg) checkErr(err) err = runner.Run(cfg) checkErr(err) } }, } cmdutil.AddCommonFlags(buildCmd, cfg) buildCmd.Flags().BoolVar(&(cfg.RunImage), "run", false, "Run resulting image as part of invocation of this command") buildCmd.Flags().BoolVar(&(cfg.DisableRecursive), "recursive", true, "Fetch all git submodules when cloning application repository") buildCmd.Flags().StringP("env", "e", "", "Specify an environment var NAME=VALUE,NAME2=VALUE2,...") buildCmd.Flags().StringVarP(&(cfg.Ref), "ref", "r", "", "Specify a ref to check-out") buildCmd.Flags().StringVarP(&(cfg.AssembleUser), "assemble-user", "", "", "Specify the user to run assemble with") buildCmd.Flags().StringVarP(&(cfg.ContextDir), "context-dir", "", "", "Specify the sub-directory inside the repository with the application sources") buildCmd.Flags().StringVarP(&(cfg.ScriptsURL), "scripts-url", "s", "", "Specify a URL for the assemble and run scripts") buildCmd.Flags().StringVar(&(oldScriptsFlag), "scripts", "", "DEPRECATED: Specify a URL for the assemble and run scripts") buildCmd.Flags().BoolVar(&(useConfig), "use-config", false, "Store command line options to .stifile") buildCmd.Flags().StringVarP(&(cfg.EnvironmentFile), "environment-file", "E", "", "Specify the path to the file with environment") buildCmd.Flags().StringVarP(&(cfg.DisplayName), "application-name", "n", "", "Specify the display name for the application (default: output image name)") buildCmd.Flags().StringVarP(&(cfg.Description), "description", "", "", "Specify the description of the application") buildCmd.Flags().VarP(&(cfg.AllowedUIDs), "allowed-uids", "u", "Specify a range of allowed user ids for the builder image") buildCmd.Flags().StringVarP(&(oldDestination), "location", "l", "", "DEPRECATED: Specify a destination location for untar operation") return buildCmd }