diff --git a/cmd/compose/push.go b/cmd/compose/push.go index 9e878f96b..bb5f6a36c 100644 --- a/cmd/compose/push.go +++ b/cmd/compose/push.go @@ -31,6 +31,7 @@ type pushOptions struct { IncludeDeps bool Ignorefailures bool Quiet bool + Repository string } func pushCommand(p *ProjectOptions, backend api.Service) *cobra.Command { @@ -48,6 +49,7 @@ func pushCommand(p *ProjectOptions, backend api.Service) *cobra.Command { pushCmd.Flags().BoolVar(&opts.Ignorefailures, "ignore-push-failures", false, "Push what it can and ignores images with push failures") pushCmd.Flags().BoolVar(&opts.IncludeDeps, "include-deps", false, "Also push images of services declared as dependencies") pushCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Push without printing progress information") + pushCmd.Flags().StringVarP(&opts.Repository, "repository", "r", "", "Also publish the compose application in repository") return pushCmd } @@ -68,5 +70,6 @@ func runPush(ctx context.Context, backend api.Service, opts pushOptions, service return backend.Push(ctx, project, api.PushOptions{ IgnoreFailures: opts.Ignorefailures, Quiet: opts.Quiet, + Repository: opts.Repository, }) } diff --git a/go.mod b/go.mod index da2dadb9f..8da5aad5a 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/moby/term v0.5.0 github.com/morikuni/aec v1.0.0 github.com/opencontainers/go-digest v1.0.0 - github.com/opencontainers/image-spec v1.1.0-rc3 + github.com/opencontainers/image-spec v1.1.0-rc4 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 @@ -183,6 +183,7 @@ require ( k8s.io/klog/v2 v2.90.1 // indirect k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect + oras.land/oras-go/v2 v2.2.0 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/go.sum b/go.sum index 63d312d6a..90d4ca5ad 100644 --- a/go.sum +++ b/go.sum @@ -513,6 +513,8 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8= github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0= +github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/opencontainers/runc v1.1.7 h1:y2EZDS8sNng4Ksf0GUYNhKbTShZJPJg1FiXJNH/uoCk= github.com/opencontainers/runc v1.1.7/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50= github.com/opencontainers/runtime-spec v1.1.0-rc.2 h1:ucBtEms2tamYYW/SvGpvq9yUN0NEVL6oyLEwDcTSrk8= @@ -1110,6 +1112,8 @@ k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+O k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +oras.land/oras-go/v2 v2.2.0 h1:E1fqITD56Eg5neZbxBtAdZVgDHD6wBabJo6xESTcQyo= +oras.land/oras-go/v2 v2.2.0/go.mod h1:pXjn0+KfarspMHHNR3A56j3tgvr+mxArHuI8qVn59v8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/pkg/api/api.go b/pkg/api/api.go index a693abb50..a916822b0 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -260,6 +260,7 @@ type ConfigOptions struct { // PushOptions group options of the Push API type PushOptions struct { Quiet bool + Repository string IgnoreFailures bool } diff --git a/pkg/compose/push.go b/pkg/compose/push.go index c60262b6f..a95cade0d 100644 --- a/pkg/compose/push.go +++ b/pkg/compose/push.go @@ -17,6 +17,7 @@ package compose import ( + "bytes" "context" "encoding/base64" "encoding/json" @@ -26,14 +27,17 @@ import ( "github.com/compose-spec/compose-go/types" "github.com/distribution/distribution/v3/reference" "github.com/docker/buildx/driver" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/progress" moby "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/registry" + "github.com/opencontainers/go-digest" + v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "golang.org/x/sync/errgroup" - - "github.com/docker/compose/v2/pkg/api" - "github.com/docker/compose/v2/pkg/progress" + "oras.land/oras-go/v2/content" + "oras.land/oras-go/v2/registry/remote" ) func (s *composeService) Push(ctx context.Context, project *types.Project, options api.PushOptions) error { @@ -45,8 +49,8 @@ func (s *composeService) Push(ctx context.Context, project *types.Project, optio }, s.stdinfo(), "Pushing") } -func (s *composeService) push(ctx context.Context, project *types.Project, options api.PushOptions) error { - eg, ctx := errgroup.WithContext(ctx) +func (s *composeService) push(upctx context.Context, project *types.Project, options api.PushOptions) error { + eg, ctx := errgroup.WithContext(upctx) eg.SetLimit(s.maxConcurrency) info, err := s.apiClient().Info(ctx) @@ -79,7 +83,66 @@ func (s *composeService) push(ctx context.Context, project *types.Project, optio return nil }) } - return eg.Wait() + + err = eg.Wait() + if err != nil { + return err + } + ctx = upctx + + if options.Repository != "" { + repository, err := remote.NewRepository(options.Repository) + if err != nil { + return err + } + + yaml, err := project.MarshalYAML() + if err != nil { + return err + } + manifests := []v1.Descriptor{ + { + MediaType: "application/vnd.oci.artifact.manifest.v1+json", + Digest: digest.FromBytes(yaml), + Size: int64(len(yaml)), + Data: yaml, + ArtifactType: "application/vnd.docker.compose.yaml", + }, + } + for _, service := range project.Services { + inspected, _, err := s.dockerCli.Client().ImageInspectWithRaw(ctx, service.Image) + if err != nil { + return err + } + manifests = append(manifests, v1.Descriptor{ + MediaType: v1.MediaTypeImageIndex, + Digest: digest.Digest(inspected.RepoDigests[0]), + Size: inspected.Size, + Annotations: map[string]string{ + "com.docker.compose.service": service.Name, + }, + }) + } + + manifest := v1.Index{ + MediaType: v1.MediaTypeImageIndex, + Manifests: manifests, + Annotations: map[string]string{ + "com.docker.compose": api.ComposeVersion, + }, + } + manifestContent, err := json.Marshal(manifest) + if err != nil { + return err + } + manifestDescriptor := content.NewDescriptorFromBytes(v1.MediaTypeImageIndex, manifestContent) + + err = repository.Push(ctx, manifestDescriptor, bytes.NewReader(manifestContent)) + if err != nil { + return err + } + } + return nil } func (s *composeService) pushServiceImage(ctx context.Context, service types.ServiceConfig, info moby.Info, configFile driver.Auth, w progress.Writer, quietPush bool) error {