github.com/nak3/source-to-image@v1.1.10-0.20180319140719-2ed55639898d/pkg/build/strategies/onbuild/onbuild.go (about)

     1  package onbuild
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  
    10  	"github.com/openshift/source-to-image/pkg/api"
    11  	"github.com/openshift/source-to-image/pkg/build"
    12  	"github.com/openshift/source-to-image/pkg/build/strategies/sti"
    13  	"github.com/openshift/source-to-image/pkg/docker"
    14  	"github.com/openshift/source-to-image/pkg/ignore"
    15  	"github.com/openshift/source-to-image/pkg/scm"
    16  	"github.com/openshift/source-to-image/pkg/scm/git"
    17  	"github.com/openshift/source-to-image/pkg/scripts"
    18  	"github.com/openshift/source-to-image/pkg/tar"
    19  	"github.com/openshift/source-to-image/pkg/util/cmd"
    20  	"github.com/openshift/source-to-image/pkg/util/fs"
    21  	utilstatus "github.com/openshift/source-to-image/pkg/util/status"
    22  )
    23  
    24  // OnBuild strategy executes the simple Docker build in case the image does not
    25  // support STI scripts but has ONBUILD instructions recorded.
    26  type OnBuild struct {
    27  	docker  docker.Docker
    28  	git     git.Git
    29  	fs      fs.FileSystem
    30  	tar     tar.Tar
    31  	source  build.SourceHandler
    32  	garbage build.Cleaner
    33  }
    34  
    35  type onBuildSourceHandler struct {
    36  	build.Downloader
    37  	build.Preparer
    38  	build.Ignorer
    39  }
    40  
    41  // New returns a new instance of OnBuild builder
    42  func New(client docker.Client, config *api.Config, fs fs.FileSystem, overrides build.Overrides) (*OnBuild, error) {
    43  	dockerHandler := docker.New(client, config.PullAuthentication)
    44  	builder := &OnBuild{
    45  		docker: dockerHandler,
    46  		git:    git.New(fs, cmd.NewCommandRunner()),
    47  		fs:     fs,
    48  		tar:    tar.New(fs),
    49  	}
    50  	// Use STI Prepare() and download the 'run' script optionally.
    51  	s, err := sti.New(client, config, fs, overrides)
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  	s.SetScripts([]string{}, []string{api.Assemble, api.Run})
    56  
    57  	downloader := overrides.Downloader
    58  	if downloader == nil {
    59  		downloader, err = scm.DownloaderForSource(builder.fs, config.Source, config.ForceCopy)
    60  		if err != nil {
    61  			return nil, err
    62  		}
    63  	}
    64  
    65  	builder.source = onBuildSourceHandler{
    66  		Downloader: downloader,
    67  		Preparer:   s,
    68  		Ignorer:    &ignore.DockerIgnorer{},
    69  	}
    70  
    71  	builder.garbage = build.NewDefaultCleaner(builder.fs, builder.docker)
    72  	return builder, nil
    73  }
    74  
    75  // Build executes the ONBUILD kind of build
    76  func (builder *OnBuild) Build(config *api.Config) (*api.Result, error) {
    77  	buildResult := &api.Result{}
    78  
    79  	if config.BlockOnBuild {
    80  		buildResult.BuildInfo.FailureReason = utilstatus.NewFailureReason(
    81  			utilstatus.ReasonOnBuildForbidden,
    82  			utilstatus.ReasonMessageOnBuildForbidden,
    83  		)
    84  		return buildResult, fmt.Errorf("builder image uses ONBUILD instructions but ONBUILD is not allowed")
    85  	}
    86  	glog.V(2).Info("Preparing the source code for build")
    87  	// Change the installation directory for this config to store scripts inside
    88  	// the application root directory.
    89  	if err := builder.source.Prepare(config); err != nil {
    90  		return buildResult, err
    91  	}
    92  
    93  	// If necessary, copy the STI scripts into application root directory
    94  	builder.copySTIScripts(config)
    95  
    96  	glog.V(2).Info("Creating application Dockerfile")
    97  	if err := builder.CreateDockerfile(config); err != nil {
    98  		buildResult.BuildInfo.FailureReason = utilstatus.NewFailureReason(
    99  			utilstatus.ReasonDockerfileCreateFailed,
   100  			utilstatus.ReasonMessageDockerfileCreateFailed,
   101  		)
   102  		return buildResult, err
   103  	}
   104  
   105  	glog.V(2).Info("Creating application source code image")
   106  	tarStream := builder.tar.CreateTarStreamReader(filepath.Join(config.WorkingDir, "upload", "src"), false)
   107  	defer tarStream.Close()
   108  
   109  	outReader, outWriter := io.Pipe()
   110  	go io.Copy(os.Stdout, outReader)
   111  
   112  	opts := docker.BuildImageOptions{
   113  		Name:         config.Tag,
   114  		Stdin:        tarStream,
   115  		Stdout:       outWriter,
   116  		CGroupLimits: config.CGroupLimits,
   117  	}
   118  
   119  	glog.V(2).Info("Building the application source")
   120  	if err := builder.docker.BuildImage(opts); err != nil {
   121  		buildResult.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   122  			utilstatus.ReasonDockerImageBuildFailed,
   123  			utilstatus.ReasonMessageDockerImageBuildFailed,
   124  		)
   125  		return buildResult, err
   126  	}
   127  
   128  	glog.V(2).Info("Cleaning up temporary containers")
   129  	builder.garbage.Cleanup(config)
   130  
   131  	var imageID string
   132  	var err error
   133  	if len(opts.Name) > 0 {
   134  		if imageID, err = builder.docker.GetImageID(opts.Name); err != nil {
   135  			buildResult.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   136  				utilstatus.ReasonGenericS2IBuildFailed,
   137  				utilstatus.ReasonMessageGenericS2iBuildFailed,
   138  			)
   139  			return buildResult, err
   140  		}
   141  	}
   142  
   143  	return &api.Result{
   144  		Success:    true,
   145  		WorkingDir: config.WorkingDir,
   146  		ImageID:    imageID,
   147  	}, nil
   148  }
   149  
   150  // CreateDockerfile creates the ONBUILD Dockerfile
   151  func (builder *OnBuild) CreateDockerfile(config *api.Config) error {
   152  	buffer := bytes.Buffer{}
   153  	uploadDir := filepath.Join(config.WorkingDir, "upload", "src")
   154  	buffer.WriteString(fmt.Sprintf("FROM %s\n", config.BuilderImage))
   155  	entrypoint, err := GuessEntrypoint(builder.fs, uploadDir)
   156  	if err != nil {
   157  		return err
   158  	}
   159  	env, err := scripts.GetEnvironment(config)
   160  	if err != nil {
   161  		glog.V(1).Infof("Environment: %v", err)
   162  	} else {
   163  		buffer.WriteString(scripts.ConvertEnvironmentToDocker(env))
   164  	}
   165  	// If there is an assemble script present, run it as part of the build process
   166  	// as the last thing.
   167  	if builder.hasAssembleScript(config) {
   168  		buffer.WriteString("RUN sh assemble\n")
   169  	}
   170  	// FIXME: This assumes that the WORKDIR is set to the application source root
   171  	//        directory.
   172  	buffer.WriteString(fmt.Sprintf(`ENTRYPOINT ["./%s"]`+"\n", entrypoint))
   173  	return builder.fs.WriteFile(filepath.Join(uploadDir, "Dockerfile"), buffer.Bytes())
   174  }
   175  
   176  func (builder *OnBuild) copySTIScripts(config *api.Config) {
   177  	scriptsPath := filepath.Join(config.WorkingDir, "upload", "scripts")
   178  	sourcePath := filepath.Join(config.WorkingDir, "upload", "src")
   179  	if _, err := builder.fs.Stat(filepath.Join(scriptsPath, api.Run)); err == nil {
   180  		glog.V(3).Info("Found S2I 'run' script, copying to application source dir")
   181  		builder.fs.Copy(filepath.Join(scriptsPath, api.Run), filepath.Join(sourcePath, api.Run))
   182  	}
   183  	if _, err := builder.fs.Stat(filepath.Join(scriptsPath, api.Assemble)); err == nil {
   184  		glog.V(3).Info("Found S2I 'assemble' script, copying to application source dir")
   185  		builder.fs.Copy(filepath.Join(scriptsPath, api.Assemble), filepath.Join(sourcePath, api.Assemble))
   186  	}
   187  }
   188  
   189  // hasAssembleScript checks if the the assemble script is available
   190  func (builder *OnBuild) hasAssembleScript(config *api.Config) bool {
   191  	assemblePath := filepath.Join(config.WorkingDir, "upload", "src", api.Assemble)
   192  	_, err := builder.fs.Stat(assemblePath)
   193  	return err == nil
   194  }