Example #1
0
// 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)
	}
}
Example #2
0
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
}
Example #3
0
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
}
Example #4
0
// 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
}
Example #5
0
// 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
}
Example #6
0
// 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
}
Example #7
0
// 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
}
Example #8
0
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
}
Example #9
0
// 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
}
Example #10
0
// 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
}
Example #11
0
// 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
}
Example #12
0
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
}
Example #13
0
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
}
Example #14
0
// 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
}
Example #15
0
// 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)
}
Example #16
0
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
}
Example #17
0
// 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
}
Example #18
0
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
}
Example #19
0
// 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
}
Example #20
0
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
}
Example #21
0
// 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
}
Example #22
0
// 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
}
Example #23
0
// 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
}
Example #24
0
// 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
}
Example #25
0
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
}