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 }