cmd/acb/commands/build/build.go (329 lines of code) (raw):

// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package build import ( gocontext "context" "errors" "fmt" "log" "runtime" "strings" "time" "github.com/Azure/acr-builder/builder" "github.com/Azure/acr-builder/graph" "github.com/Azure/acr-builder/pkg/procmanager" "github.com/Azure/acr-builder/pkg/volume" "github.com/Azure/acr-builder/secretmgmt" "github.com/Azure/acr-builder/templating" "github.com/Azure/acr-builder/util" "github.com/google/uuid" "github.com/urfave/cli" ) const ( buildTimeoutInSec = 60 * 60 * 8 // 8 hours pushTimeoutInSec = 60 * 60 // 1 hour ) // Command executes a container build. var Command = cli.Command{ Name: "build", Usage: "build container images", ArgsUsage: "[path|url]", Flags: []cli.Flag{ // Build options cli.StringFlag{ Name: "file,f", Usage: "the path to the Dockerfile", Value: "Dockerfile", }, cli.StringFlag{ Name: "working-directory", Usage: "the default working directory to use", }, cli.StringFlag{ Name: "target", Usage: "specifies the target stage to build in a multi-stage build", }, cli.StringFlag{ Name: "isolation", Usage: "build isolation", }, cli.StringFlag{ Name: "platform", Usage: "sets the platform if the server is capable of multiple platforms", }, cli.StringSliceFlag{ Name: "tag,t", Usage: "name and optionally a tag in 'name:tag' format", }, cli.StringSliceFlag{ Name: "build-arg", Usage: "build arguments", }, cli.StringSliceFlag{ Name: "secret-build-arg", Usage: "secret build arguments", }, cli.StringSliceFlag{ Name: "label", Usage: "set metadata for an image", }, cli.StringSliceFlag{ Name: "credential", Usage: "login credentials for custom registry", }, cli.BoolFlag{ Name: "pull", Usage: "attempt to pull a newer version of the base image during build", }, cli.BoolFlag{ Name: "no-cache", Usage: "ignore all cached layers when building an image", }, cli.BoolFlag{ Name: "push", Usage: "push the image on success", }, cli.BoolFlag{ Name: "dry-run", Usage: "evaluates the command, but doesn't execute it", }, cli.BoolFlag{ Name: "debug", Usage: "enables diagnostic logging", }, // Rendering options cli.StringFlag{ Name: "values", Usage: "the path to the values file to use for rendering", }, cli.StringFlag{ Name: "encoded-values", Usage: "a base64 encoded values file to use for rendering", }, cli.StringFlag{ Name: "homevol", Usage: "the home volume to use", }, cli.StringFlag{ Name: "id", Usage: "the unique run identifier", }, cli.StringFlag{ Name: "commit,c", Usage: "the commit SHA that triggered the run", }, cli.StringFlag{ Name: "repository", Usage: "the run's repository", }, cli.StringFlag{ Name: "branch", Usage: "the git branch", }, cli.StringFlag{ Name: "triggered-by", Usage: "describes what the run was triggered by", }, cli.StringFlag{ Name: "git-tag", Usage: "the git tag that triggered the run", }, cli.StringFlag{ Name: "registry,r", Usage: "the fully qualified name of the registry", }, cli.StringFlag{ Name: "os-version", Usage: "the version of the OS", }, cli.StringSliceFlag{ Name: "set", Usage: "set values on the command line (use --set multiple times or use commas: key1=val1,key2=val2)", }, }, Action: func(context *cli.Context) error { var ( // Build options buildContext = context.Args().First() dockerfile = context.String("file") defaultWorkingDirectory = context.String("working-directory") target = context.String("target") isolation = context.String("isolation") platform = context.String("platform") tags = context.StringSlice("tag") buildArgs = context.StringSlice("build-arg") secretBuildArgs = context.StringSlice("secret-build-arg") labels = context.StringSlice("label") creds = context.StringSlice("credential") pull = context.Bool("pull") noCache = context.Bool("no-cache") push = context.Bool("push") dryRun = context.Bool("dry-run") debug = context.Bool("debug") // Rendering options values = context.String("values") encodedValues = context.String("encoded-values") homevol = context.String("homevol") id = context.String("id") commit = context.String("commit") repository = context.String("repository") branch = context.String("branch") triggeredBy = context.String("triggered-by") tag = context.String("git-tag") registry = context.String("registry") osVersion = context.String("os-version") setVals = context.StringSlice("set") ) if buildContext == "" { return errors.New("build requires exactly 1 argument, see build --help") } if err := validateIsolation(isolation); err != nil { return err } if err := validatePush(push, creds); err != nil { return err } ctx := gocontext.Background() pm := procmanager.NewProcManager(dryRun) if homevol == "" { if !dryRun { homevol = fmt.Sprintf("%s%s", volume.DockerVolumeHelperPrefix, uuid.New()) v := volume.NewDockerVolumeHelper(homevol, pm) if msg, err := v.Create(ctx); err != nil { return fmt.Errorf("failed to create volume. Msg: %s, Err: %v", msg, err) } defer func() { _, _ = v.Delete(ctx) }() } } log.Printf("Using %s as the home volume\n", homevol) renderOpts := &templating.BaseRenderOptions{ ValuesFile: values, Base64EncodedValuesFile: encodedValues, TemplateValues: setVals, ID: id, Commit: commit, Repository: repository, Branch: branch, TriggeredBy: triggeredBy, GitTag: tag, Registry: registry, Date: time.Now().UTC(), SharedVolume: homevol, OS: runtime.GOOS, OSVersion: osVersion, Architecture: runtime.GOARCH, } task, err := createBuildTask( ctx, isolation, pull, labels, noCache, dockerfile, tags, buildArgs, secretBuildArgs, target, platform, buildContext, renderOpts, debug, registry, push, creds, defaultWorkingDirectory) if err != nil { return err } builder := builder.NewBuilder(pm, debug, homevol) defer builder.CleanTask(gocontext.Background(), task) // Use a separate context since the other may have expired. return builder.RunTask(gocontext.Background(), task) }, } func createBuildTask( ctx gocontext.Context, isolation string, pull bool, labels []string, noCache bool, dockerfile string, tags []string, buildArgs []string, secretBuildArgs []string, target string, platform string, buildContext string, renderOpts *templating.BaseRenderOptions, debug bool, registry string, push bool, creds []string, workingDirectory string, ) (*graph.Task, error) { // Create the run command to be used in the template args := []string{} if isolation != "" { args = append(args, fmt.Sprintf("--isolation=%s", isolation)) } if pull { args = append(args, "--pull") } for _, label := range labels { args = append(args, "--label", label) } if noCache { args = append(args, "--no-cache") } if dockerfile != "" { args = append(args, "-f", dockerfile) } for _, tag := range tags { args = append(args, "-t", tag) } for _, buildArg := range buildArgs { args = append(args, "--build-arg", buildArg) } for _, secretBuildArg := range secretBuildArgs { args = append(args, "--build-arg", secretBuildArg) } if target != "" { args = append(args, "--target", target) } if platform != "" { args = append(args, "--platform", platform) } args = append(args, buildContext) runCmd := strings.Join(args, " ") // Create the template template := templating.NewTemplate("build", []byte(runCmd)) rendered, err := templating.LoadAndRenderBuildSteps(ctx, template, renderOpts) if err != nil { return nil, err } if debug { log.Println("Rendered template:") log.Println(rendered) } var credentials []*graph.RegistryCredential allKnownRegistries := []string{registry} for _, credString := range creds { cred, err := graph.CreateRegistryCredentialFromString(credString) if err != nil { return nil, err } credentials = append(credentials, cred) allKnownRegistries = append(allKnownRegistries, cred.Registry) } // After the template has rendered, we have to parse the tags again // so we can properly set the build/push tags. rendered, prefixedTags := util.PrefixTags(rendered, registry, allKnownRegistries) tags = prefixedTags buildStep := &graph.Step{ ID: "build", Build: rendered, Timeout: buildTimeoutInSec, Tags: tags, } steps := []*graph.Step{buildStep} if push { pushStep := &graph.Step{ ID: "push", Push: tags, Timeout: pushTimeoutInSec, When: []string{buildStep.ID}, } steps = append(steps, pushStep) } return graph.NewTask(ctx, steps, []*secretmgmt.Secret{}, registry, credentials, true, workingDirectory, "") }