cmd/acb/commands/exec/exec.go (234 lines of code) (raw):

// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package exec import ( gocontext "context" "fmt" "log" "runtime" "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/google/uuid" "github.com/pkg/errors" "github.com/urfave/cli" ) const ( defaultTaskFile = "acb.yaml" ) // Command executes a task file. var Command = cli.Command{ Name: "exec", Usage: "execute a task file", Flags: []cli.Flag{ // Task options cli.StringFlag{ Name: "file,f", Usage: "the path to the task file", }, cli.StringFlag{ Name: "encoded-file", Usage: "a base64 encoded task file", }, cli.StringFlag{ Name: "working-directory", Usage: "the default working directory to use if the underlying Task doesn't have one specified", }, cli.StringFlag{ Name: "network", Usage: "the default network to use", }, cli.StringSliceFlag{ Name: "env", Usage: "the default environment variables which are applied to each step (use --env multiple times or use commas: env1=val1,env2=val2)", }, cli.StringSliceFlag{ Name: "credential", Usage: "login credentials for custom registry", }, 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)", }, cli.StringFlag{ Name: "name", Usage: "the name of the task", }, }, Action: func(context *cli.Context) error { var ( // Task options taskFile = context.String("file") encodedTaskFile = context.String("encoded-file") defaultWorkingDirectory = context.String("working-directory") defaultNetwork = context.String("network") defaultEnvs = context.StringSlice("env") creds = context.StringSlice("credential") 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") taskName = context.String("name") ) if taskFile == "" && encodedTaskFile == "" { taskFile = defaultTaskFile } 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) }() } } renderOpts := &templating.BaseRenderOptions{ TaskFile: taskFile, Base64EncodedTaskFile: encodedTaskFile, 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, SecretResolveTimeout: secretmgmt.DefaultSecretResolveTimeout, TaskName: taskName, } var template *templating.Template var err error if taskFile == "" { if template, err = templating.DecodeTemplate(encodedTaskFile); err != nil { return err } } else { if template, err = templating.LoadTemplate(taskFile); err != nil { return err } } // Add all creds provided by the user in the --credential flag credentials, err := graph.CreateRegistryCredentialFromList(creds) if err != nil { return errors.Wrap(err, "error creating registry credentials from given list") } var task *graph.Task var alias *graph.Alias versionInUse := graph.FindVersion(template.GetData()) shouldIncludeAlias := versionInUse >= "v1.1.0" if shouldIncludeAlias { log.Printf("Alias support enabled for version >= 1.1.0, please see https://aka.ms/acr/tasks/task-aliases for more information.") // separate alias and remaining data from the Task aliasData, taskData := graph.SeparateAliasFromRest(template.GetData()) // render alias data renderedAlias, renderAliasErr := templating.LoadAndRenderSteps(ctx, templating.NewTemplate("aliasData", aliasData), renderOpts) if renderAliasErr != nil { return errors.Wrap(renderAliasErr, "unable to render alias data") } aliasData = []byte(renderedAlias) // Preprocess the task to replace all aliases based on the alias sources. processedTask, _alias, aliasErr := graph.SearchReplaceAlias(template.GetData(), aliasData, taskData) alias = _alias if aliasErr != nil { return errors.Wrap(aliasErr, "unable to search/replace aliases in task") } if debug { log.Printf("Processed task before rendering data:\n%s", processedTask) } // update the template.Data template.Data = processedTask } rendered, err := templating.LoadAndRenderSteps(ctx, template, renderOpts) if err != nil { return errors.Wrap(err, "unable to render task") } if debug { log.Printf("Rendered template:\n%s", rendered) } task, errUnmarshal := graph.UnmarshalTaskFromString(ctx, rendered, &graph.TaskOptions{ DefaultWorkingDir: defaultWorkingDirectory, Network: defaultNetwork, Envs: defaultEnvs, Credentials: credentials, TaskName: taskName, Registry: registry, }) if errUnmarshal != nil { return errors.Wrap(errUnmarshal, "failed to unmarshal task before running") } if shouldIncludeAlias { graph.ExpandCommandAliases(alias, task) } 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) }, }