From 9b2675dce0a588bb4b3ea6d4affd7985acf00e0d Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Fri, 15 Sep 2023 19:48:21 +0200 Subject: [PATCH] implement publish Signed-off-by: Nicolas De Loof --- pkg/compose/publish.go | 118 +++++++++++++++++++++++++++++++++++++++-- pkg/remote/oci.go | 7 ++- 2 files changed, 121 insertions(+), 4 deletions(-) diff --git a/pkg/compose/publish.go b/pkg/compose/publish.go index 5f83b9b72a9..41808df80e6 100644 --- a/pkg/compose/publish.go +++ b/pkg/compose/publish.go @@ -18,24 +18,136 @@ package compose import ( "context" + "encoding/json" + "os" "github.com/compose-spec/compose-go/types" "github.com/distribution/reference" + "github.com/docker/buildx/util/imagetools" "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/progress" + "github.com/opencontainers/go-digest" + "github.com/opencontainers/image-spec/specs-go" + v1 "github.com/opencontainers/image-spec/specs-go/v1" ) func (s *composeService) Publish(ctx context.Context, project *types.Project, repository string, options api.PublishOptions) error { + return progress.RunWithTitle(ctx, func(ctx context.Context) error { + return s.publish(ctx, project, repository, options) + }, s.stdinfo(), "Publishing") +} + +func (s *composeService) publish(ctx context.Context, project *types.Project, repository string, options api.PublishOptions) error { err := s.Push(ctx, project, api.PushOptions{}) if err != nil { return err } - _, err = reference.ParseDockerRef(repository) + w := progress.ContextWriter(ctx) + + named, err := reference.ParseDockerRef(repository) + if err != nil { + return err + } + + resolver := imagetools.New(imagetools.Opt{ + Auth: s.configFile(), + }) + + var layers []v1.Descriptor + for _, file := range project.ComposeFiles { + f, err := os.ReadFile(file) + if err != nil { + return err + } + + w.Event(progress.Event{ + ID: file, + Text: "publishing", + Status: progress.Working, + }) + layer := v1.Descriptor{ + MediaType: "application/vnd.docker.compose.file+yaml", + Digest: digest.FromString(string(f)), + Size: int64(len(f)), + Annotations: map[string]string{ + "com.docker.compose": api.ComposeVersion, + }, + } + layers = append(layers, layer) + err = resolver.Push(ctx, named, layer, f) + if err != nil { + w.Event(progress.Event{ + ID: file, + Text: "publishing", + Status: progress.Error, + }) + + return err + } + + w.Event(progress.Event{ + ID: file, + Text: "published", + Status: progress.Done, + }) + } + + emptyConfig, err := json.Marshal(v1.ImageConfig{}) + if err != nil { + return err + } + configDescriptor := v1.Descriptor{ + MediaType: "application/vnd.docker.compose.project", + Digest: digest.FromBytes(emptyConfig), + Size: int64(len(emptyConfig)), + Annotations: map[string]string{ + "com.docker.compose.version": api.ComposeVersion, + }, + } + err = resolver.Push(ctx, named, configDescriptor, emptyConfig) + if err != nil { + return err + } + + imageManifest, err := json.Marshal(v1.Manifest{ + Versioned: specs.Versioned{SchemaVersion: 2}, + MediaType: v1.MediaTypeImageManifest, + ArtifactType: "application/vnd.docker.compose.project", + Config: configDescriptor, + Layers: layers, + }) if err != nil { return err } - // TODO publish project.ComposeFiles + w.Event(progress.Event{ + ID: repository, + Text: "publishing", + Status: progress.Working, + }) - return api.ErrNotImplemented + err = resolver.Push(ctx, named, v1.Descriptor{ + MediaType: v1.MediaTypeImageManifest, + Digest: digest.FromString(string(imageManifest)), + Size: int64(len(imageManifest)), + Annotations: map[string]string{ + "com.docker.compose.version": api.ComposeVersion, + }, + ArtifactType: "application/vnd.docker.compose.project", + }, imageManifest) + if err != nil { + w.Event(progress.Event{ + ID: repository, + Text: "publishing", + Status: progress.Error, + }) + return err + } + w.Event(progress.Event{ + ID: repository, + Text: "published", + Status: progress.Done, + }) + return nil } diff --git a/pkg/remote/oci.go b/pkg/remote/oci.go index 152f930228c..f7746b954d8 100644 --- a/pkg/remote/oci.go +++ b/pkg/remote/oci.go @@ -70,7 +70,7 @@ type ociRemoteLoader struct { offline bool } -const prefix = "oci:" +const prefix = "oci://" func (g ociRemoteLoader) Accept(path string) bool { return strings.HasPrefix(path, prefix) @@ -117,6 +117,11 @@ func (g ociRemoteLoader) Load(ctx context.Context, path string) (string, error) if err != nil { return "", err } + + if descriptor.Config.MediaType != "application/vnd.docker.compose.project" { + return "", fmt.Errorf("%s is not a compose project OCI artifact, but %s", ref.String(), descriptor.Config.MediaType) + } + for i, layer := range descriptor.Layers { digested, err := reference.WithDigest(ref, layer.Digest) if err != nil {