package push import ( "context" "fmt" "github.com/google/go-containerregistry/pkg/crane" "edge-infra.dev/hack/build/ci" "edge-infra.dev/pkg/lib/cli/rags" "edge-infra.dev/pkg/lib/cli/sink" "edge-infra.dev/pkg/tools/hack/bazelx" "edge-infra.dev/pkg/tools/hack/containers" ) func New(name string) *sink.Command { var ( repo string tags []string insecure, relTags, rc bool bzlx = &bazelx.Bazelx{} ) return &sink.Command{ Use: fmt.Sprintf("%s [flags] [-- bazel flags]", name), Short: "Push OCI build artifacts to registries using Bazel", Extensions: []sink.Extension{bzlx}, Flags: []*rags.Rag{ { Name: "repo", Short: "r", Usage: "Override the default destination repository", Value: &rags.String{Var: &repo}, }, { Name: "tag", Short: "t", Usage: "One or more tags to apply to pushed containers. Multiple flags can be provided as comma separated strings or multiple instances of the flag.", Value: &rags.StringSet{Var: &tags}, }, // TODO: This is needed for initial pass because rules_docker doesn't // support multiple tags on a push target. Once we are on rules_oci completely, // we should use stamping to create remote_tags file with multiple tags in it, // instead of achieving release tags by wrapping with this tool. { Name: "release-tags", Usage: "Tag pushed containers with build information", Value: &rags.Bool{Var: &relTags}, }, { Name: "rc", Usage: "Whether or not this build is a release candidate", Value: &rags.Bool{Var: &rc, Default: true}, }, // TODO: This is rules_docker specific and should be removed as part of the // migration. Make sure HTTP flows are supported in rules_oci. Looks like // only consumer is Edge IAM. { Name: "insecure-repository", Usage: "Allow HTTP repositories. Only valid for rules_docker push rules", Value: &rags.Bool{Var: &insecure}, }, }, Exec: func(_ context.Context, r sink.Run) error { if len(r.Args()) == 0 { return fmt.Errorf("one or more Bazel expressions required") } log := r.Log pushes, err := containers.QueryPushes(r.Args()...) if err != nil { return err } if len(pushes) == 0 { return fmt.Errorf("no container_push targets found") } log.Info("container_push targets to execute", "targets", pushes) opts := []containers.Option{} if repo != "" { opts = append(opts, containers.ToRepository(repo)) } if insecure { opts = append(opts, containers.Insecure) } refs, err := containers.Push(bzlx.Bazel, pushes, opts...) if err != nil { return err } log.Info("successfully pushed", "refs", refs) if relTags { rt, err := containerTags(rc) if err != nil { return err } tags = append(tags, rt...) } // If no tags, we are done. if len(tags) == 0 { return nil } // TODO: once rules_docker is entirely gone, we can just provide the // tags as flags to the individual container_push binaries instead for _, r := range refs { for _, t := range tags { if err := crane.Tag(r.String(), t); err != nil { return fmt.Errorf("failed to tag %s with %s: %w", r, t, err) } } log.Info("tagged", "ref", r, "tags", tags) } return nil }, } } func containerTags(rc bool) ([]string, error) { version, branch, err := ci.BuildInfo(rc, ci.RepoRoot) if err != nil { return nil, err } // get the tags we will apply to each container return version.ContainerTags(branch) }