github.com/sealerio/sealer@v0.11.1-0.20240507115618-f4f89c5853ae/pkg/imageengine/buildah/build.go (about) 1 // Copyright © 2022 Alibaba Group Holding Ltd. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package buildah 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "os" 22 "path/filepath" 23 "strings" 24 "time" 25 26 "github.com/sealerio/sealer/pkg/define/options" 27 28 "github.com/containers/buildah/define" 29 "github.com/containers/buildah/imagebuildah" 30 buildahcli "github.com/containers/buildah/pkg/cli" 31 "github.com/containers/buildah/pkg/parse" 32 buildahutil "github.com/containers/buildah/pkg/util" 33 "github.com/containers/buildah/util" 34 "github.com/containers/common/pkg/auth" 35 "github.com/pkg/errors" 36 "github.com/sirupsen/logrus" 37 ) 38 39 type buildFlagsWrapper struct { 40 *buildahcli.BudResults 41 *buildahcli.LayerResults 42 *buildahcli.FromAndBudResults 43 *buildahcli.NameSpaceResults 44 *buildahcli.UserNSResults 45 } 46 47 func (engine *Engine) Build(opts *options.BuildOptions) (string, error) { 48 // The following block is to init buildah default options. 49 // And call migrateFlags2BuildahBuild to set flags based on sealer build options. 50 wrapper := &buildFlagsWrapper{ 51 BudResults: &buildahcli.BudResults{}, 52 LayerResults: &buildahcli.LayerResults{}, 53 FromAndBudResults: &buildahcli.FromAndBudResults{}, 54 NameSpaceResults: &buildahcli.NameSpaceResults{}, 55 UserNSResults: &buildahcli.UserNSResults{}, 56 } 57 58 flags := engine.Flags() 59 buildFlags := buildahcli.GetBudFlags(wrapper.BudResults) 60 buildFlags.StringVar(&wrapper.Runtime, "runtime", util.Runtime(), "`path` to an alternate runtime. Use BUILDAH_RUNTIME environment variable to override.") 61 62 layerFlags := buildahcli.GetLayerFlags(wrapper.LayerResults) 63 fromAndBudFlags, err := buildahcli.GetFromAndBudFlags(wrapper.FromAndBudResults, wrapper.UserNSResults, wrapper.NameSpaceResults) 64 if err != nil { 65 return "", fmt.Errorf("failed to setup From and Build flags: %v", err) 66 } 67 68 flags.AddFlagSet(&buildFlags) 69 flags.AddFlagSet(&layerFlags) 70 flags.AddFlagSet(&fromAndBudFlags) 71 flags.SetNormalizeFunc(buildahcli.AliasFlags) 72 73 err = engine.migrateFlags2Wrapper(opts, wrapper) 74 if err != nil { 75 return "", err 76 } 77 78 opt, kubeFiles, err := engine.wrapper2Options(opts, wrapper) 79 if err != nil { 80 return "", err 81 } 82 83 return engine.build(getContext(), kubeFiles, opt) 84 } 85 86 // this function aims to set buildah configuration based on sealer image engine flags. 87 func (engine *Engine) migrateFlags2Wrapper(opts *options.BuildOptions, wrapper *buildFlagsWrapper) error { 88 flags := engine.Flags() 89 // imageengine cache related flags 90 // cache intermediate layers during build, it is enabled when len(opts.Platforms) <= 1 and "no-cache" is false 91 wrapper.Layers = len(opts.Platforms) <= 1 && !opts.NoCache 92 wrapper.NoCache = opts.NoCache 93 // tags. Like -t kubernetes:v1.16 94 wrapper.Tag = []string{opts.Tag} 95 // Hardcoded for network configuration. 96 // check parse.NamespaceOptions for detailed logic. 97 // this network setup for stage container, especially for RUN wget and so on. 98 // so I think we can set as host network. 99 err := flags.Set("network", "host") 100 if err != nil { 101 return err 102 } 103 104 // use tmp dockerfile as build file 105 wrapper.File = []string{opts.DockerFilePath} 106 wrapper.Pull = opts.PullPolicy 107 wrapper.Label = append(wrapper.Label, opts.Labels...) 108 wrapper.Annotation = append(wrapper.Annotation, opts.Annotations...) 109 return nil 110 } 111 112 func (engine *Engine) wrapper2Options(opts *options.BuildOptions, wrapper *buildFlagsWrapper) (define.BuildOptions, []string, error) { 113 output := "" 114 tags := wrapper.Tag 115 if len(tags) > 0 { 116 output = tags[0] 117 tags = tags[1:] 118 } 119 if engine.Flag("manifest").Changed { 120 for _, tag := range tags { 121 if tag == wrapper.Manifest { 122 return define.BuildOptions{}, []string{}, errors.New("the same name must not be specified for both '--tag' and '--manifest'") 123 } 124 } 125 } 126 127 args, err := parseArgs(opts.BuildArgs) 128 if err != nil { 129 return define.BuildOptions{}, nil, err 130 } 131 132 systemCxt := engine.SystemContext() 133 134 if err := auth.CheckAuthFile(systemCxt.AuthFilePath); err != nil { 135 return define.BuildOptions{}, []string{}, err 136 } 137 tempAuthFile, cleanTmpFile := 138 buildahutil.MirrorToTempFileIfPathIsDescriptor(systemCxt.AuthFilePath) 139 if cleanTmpFile { 140 defer os.Remove(tempAuthFile) 141 } 142 143 // Allow for --pull, --pull=true, --pull=false, --pull=never, --pull=always 144 // --pull-always and --pull-never. The --pull-never and --pull-always options 145 // will not be documented. 146 pullPolicy := define.PullIfMissing 147 if strings.EqualFold(strings.TrimSpace(wrapper.Pull), "true") { 148 pullPolicy = define.PullIfNewer 149 } 150 if wrapper.PullAlways || strings.EqualFold(strings.TrimSpace(wrapper.Pull), "always") { 151 pullPolicy = define.PullAlways 152 } 153 if wrapper.PullNever || strings.EqualFold(strings.TrimSpace(wrapper.Pull), "never") { 154 pullPolicy = define.PullNever 155 } 156 logrus.Debugf("Pull Policy for pull [%v]", pullPolicy) 157 158 format, err := getImageType(wrapper.Format) 159 if err != nil { 160 return define.BuildOptions{}, []string{}, err 161 } 162 163 layers := wrapper.Layers 164 165 contextDir := opts.ContextDir 166 167 // Nothing provided, we assume the current working directory as build 168 // context 169 if len(contextDir) == 0 { 170 contextDir, err = os.Getwd() 171 if err != nil { 172 return define.BuildOptions{}, []string{}, errors.Wrapf(err, "unable to choose current working directory as build context") 173 } 174 } else { 175 // It was local. Use it as is. 176 contextDir, err = filepath.Abs(contextDir) 177 if err != nil { 178 return define.BuildOptions{}, []string{}, errors.Wrapf(err, "error determining path to directory") 179 } 180 } 181 182 kubefiles := getKubeFiles(wrapper.File) 183 if len(kubefiles) == 0 { 184 kubefile, err := DiscoverKubefile(contextDir) 185 if err != nil { 186 return define.BuildOptions{}, []string{}, err 187 } 188 kubefiles = append(kubefiles, kubefile) 189 } 190 191 contextDir, err = filepath.EvalSymlinks(contextDir) 192 if err != nil { 193 return define.BuildOptions{}, []string{}, errors.Wrapf(err, "error evaluating symlinks in build context path") 194 } 195 196 var stdin io.Reader 197 if wrapper.Stdin { 198 stdin = os.Stdin 199 } 200 var stdout, stderr, reporter = os.Stdout, os.Stderr, os.Stderr 201 if engine.Flag("logfile").Changed { 202 f, err := os.OpenFile(wrapper.Logfile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) 203 if err != nil { 204 return define.BuildOptions{}, []string{}, errors.Errorf("error opening logfile %q: %v", wrapper.Logfile, err) 205 } 206 defer func() { 207 // this will incur GoSec warning 208 _ = f.Close() 209 }() 210 logrus.SetOutput(f) 211 stdout = f 212 stderr = f 213 reporter = f 214 } 215 216 isolation, err := defaultIsolationOption() 217 if err != nil { 218 return define.BuildOptions{}, []string{}, err 219 } 220 221 runtimeFlags := []string{} 222 for _, arg := range wrapper.RuntimeFlags { 223 runtimeFlags = append(runtimeFlags, "--"+arg) 224 } 225 226 commonOpts, err := parse.CommonBuildOptions(engine.Command) 227 if err != nil { 228 return define.BuildOptions{}, []string{}, err 229 } 230 231 namespaceOptions, networkPolicy := defaultNamespaceOptions() 232 233 usernsOption, idmappingOptions, err := parse.IDMappingOptions(engine.Command, isolation) 234 if err != nil { 235 return define.BuildOptions{}, []string{}, errors.Wrapf(err, "error parsing ID mapping options") 236 } 237 namespaceOptions.AddOrReplace(usernsOption...) 238 239 platforms, err := parsePlatformsFromOptions(opts.Platforms) 240 if err != nil { 241 return define.BuildOptions{}, nil, err 242 } 243 244 var excludes []string 245 if wrapper.IgnoreFile != "" { 246 if excludes, _, err = parse.ContainerIgnoreFile(contextDir, wrapper.IgnoreFile); err != nil { 247 return define.BuildOptions{}, []string{}, err 248 } 249 } 250 251 var timestamp *time.Time 252 if engine.Command.Flag("timestamp").Changed { 253 t := time.Unix(wrapper.Timestamp, 0).UTC() 254 timestamp = &t 255 } 256 257 compression := define.Gzip 258 if wrapper.DisableCompression { 259 compression = define.Uncompressed 260 } 261 262 options := define.BuildOptions{ 263 AddCapabilities: wrapper.CapAdd, 264 AdditionalTags: tags, 265 AllPlatforms: wrapper.AllPlatforms, 266 Annotations: wrapper.Annotation, 267 Architecture: systemCxt.ArchitectureChoice, 268 Args: args, 269 BlobDirectory: wrapper.BlobCache, 270 CNIConfigDir: wrapper.CNIConfigDir, 271 CNIPluginPath: wrapper.CNIPlugInPath, 272 CommonBuildOpts: commonOpts, 273 Compression: compression, 274 ConfigureNetwork: networkPolicy, 275 ContextDirectory: contextDir, 276 DefaultMountsFilePath: "", 277 Devices: wrapper.Devices, 278 DropCapabilities: wrapper.CapDrop, 279 Err: stderr, 280 ForceRmIntermediateCtrs: wrapper.ForceRm, 281 From: wrapper.From, 282 IDMappingOptions: idmappingOptions, 283 IIDFile: wrapper.Iidfile, 284 In: stdin, 285 Isolation: isolation, 286 IgnoreFile: wrapper.IgnoreFile, 287 Labels: wrapper.Label, 288 Layers: layers, 289 LogRusage: wrapper.LogRusage, 290 Manifest: wrapper.Manifest, 291 MaxPullPushRetries: maxPullPushRetries, 292 NamespaceOptions: namespaceOptions, 293 NoCache: wrapper.NoCache, 294 OS: systemCxt.OSChoice, 295 Out: stdout, 296 Output: output, 297 OutputFormat: format, 298 PullPolicy: pullPolicy, 299 PullPushRetryDelay: pullPushRetryDelay, 300 Quiet: wrapper.Quiet, 301 RemoveIntermediateCtrs: wrapper.Rm, 302 ReportWriter: reporter, 303 Runtime: wrapper.Runtime, 304 RuntimeArgs: runtimeFlags, 305 RusageLogFile: wrapper.RusageLogFile, 306 SignBy: wrapper.SignBy, 307 SignaturePolicyPath: wrapper.SignaturePolicy, 308 Squash: wrapper.Squash, 309 SystemContext: systemCxt, 310 Target: wrapper.Target, 311 TransientMounts: wrapper.Volumes, 312 Jobs: &wrapper.Jobs, 313 Excludes: excludes, 314 Timestamp: timestamp, 315 Platforms: platforms, 316 UnsetEnvs: wrapper.UnsetEnvs, 317 } 318 319 if wrapper.Quiet { 320 options.ReportWriter = io.Discard 321 } 322 323 return options, kubefiles, nil 324 } 325 326 func (engine *Engine) build(cxt context.Context, kubefiles []string, options define.BuildOptions) (id string, err error) { 327 id, ref, err := imagebuildah.BuildDockerfiles(cxt, engine.ImageStore(), options, kubefiles...) 328 if err == nil && options.Manifest != "" { 329 logrus.Debugf("manifest list id = %q, ref = %q", id, ref.String()) 330 } 331 if err != nil { 332 return "", fmt.Errorf("failed to build image %v: %v", options.AdditionalTags, err) 333 } 334 335 return id, nil 336 } 337 338 func parseArgs(buildArgs []string) (map[string]string, error) { 339 res := map[string]string{} 340 for _, arg := range buildArgs { 341 kvs := strings.Split(arg, "=") 342 if len(kvs) < 2 { 343 return nil, errors.New("build args should be key=value") 344 } 345 346 res[kvs[0]] = kvs[1] 347 } 348 349 return res, nil 350 } 351 352 func getKubeFiles(files []string) []string { 353 var kubefiles []string 354 for _, f := range files { 355 if f == "-" { 356 kubefiles = append(kubefiles, "/dev/stdin") 357 } else { 358 kubefiles = append(kubefiles, f) 359 } 360 } 361 return kubefiles 362 } 363 364 func parsePlatformsFromOptions(platformSpecs []string) (platforms []struct{ OS, Arch, Variant string }, err error) { 365 var _os, arch, variant string 366 367 for _, pf := range platformSpecs { 368 if _os, arch, variant, err = parse.Platform(pf); err != nil { 369 return nil, fmt.Errorf("unable to parse platform %q: %w", pf, err) 370 } 371 platforms = append(platforms, struct{ OS, Arch, Variant string }{_os, arch, variant}) 372 } 373 374 return platforms, nil 375 }