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