diff --git a/internal/oci/push.go b/internal/oci/push.go index 1e2d0f2e95..89a6daf9e5 100644 --- a/internal/oci/push.go +++ b/internal/oci/push.go @@ -94,12 +94,15 @@ func DescriptorForEnvFile(path string, content []byte) v1.Descriptor { } } -func PushManifest(ctx context.Context, resolver remotes.Resolver, named reference.Named, layers []v1.Descriptor, ociVersion api.OCIVersion) (v1.Descriptor, error) { +// PushManifest pushes the manifest for a Compose OCI artifact. The returned +// bool reports whether the push fell back from OCI 1.1 to OCI 1.0; it is +// only meaningful when err == nil. +func PushManifest(ctx context.Context, resolver remotes.Resolver, named reference.Named, layers []v1.Descriptor, ociVersion api.OCIVersion) (v1.Descriptor, bool, error) { // Check if we need an extra empty layer for the manifest config if ociVersion == api.OCIVersion1_1 || ociVersion == "" { err := push(ctx, resolver, named, v1.DescriptorEmptyJSON) if err != nil { - return v1.Descriptor{}, err + return v1.Descriptor{}, false, err } } // prepare to push the manifest by pushing the layers @@ -107,25 +110,26 @@ func PushManifest(ctx context.Context, resolver remotes.Resolver, named referenc for i := range layers { layerDescriptors[i] = layers[i] if err := push(ctx, resolver, named, layers[i]); err != nil { - return v1.Descriptor{}, err + return v1.Descriptor{}, false, err } } if ociVersion != "" { // if a version was explicitly specified, use it - return createAndPushManifest(ctx, resolver, named, layerDescriptors, ociVersion) + descriptor, err := createAndPushManifest(ctx, resolver, named, layerDescriptors, ociVersion) + return descriptor, false, err } // try to push in the OCI 1.1 format but fallback to OCI 1.0 on 4xx errors - // (other than auth) since it's most likely the result of the registry not - // having support + // (other than auth) since it's most likely the result of the registry + // rejecting the OCI 1.1 request descriptor, err := createAndPushManifest(ctx, resolver, named, layerDescriptors, api.OCIVersion1_1) var pushErr pusherrors.ErrUnexpectedStatus if errors.As(err, &pushErr) && isNonAuthClientError(pushErr.StatusCode) { - // TODO(milas): show a warning here (won't work with logrus) - return createAndPushManifest(ctx, resolver, named, layerDescriptors, api.OCIVersion1_0) + descriptor, err = createAndPushManifest(ctx, resolver, named, layerDescriptors, api.OCIVersion1_0) + return descriptor, true, err } - return descriptor, err + return descriptor, false, err } func push(ctx context.Context, resolver remotes.Resolver, ref reference.Named, descriptor v1.Descriptor) error { diff --git a/pkg/compose/publish.go b/pkg/compose/publish.go index 4f362bef19..6d4380fe93 100644 --- a/pkg/compose/publish.go +++ b/pkg/compose/publish.go @@ -91,26 +91,32 @@ func (s *composeService) publish(ctx context.Context, project *types.Project, re fmt.Println(string(indent)) } } + didFallback := false if !s.dryRun { - err = s.pushComposeArtifact(ctx, project, repository, layers, options) + didFallback, err = s.pushComposeArtifact(ctx, project, repository, layers, options) if err != nil { return err } } + text, status := "published", api.Done + if didFallback { + text, status = "published (registry rejected OCI 1.1; fell back to OCI 1.0)", api.Warning + } s.events.On(api.Resource{ ID: repository, - Text: "published", - Status: api.Done, + Text: text, + Status: status, }) return nil } // pushComposeArtifact pushes the compose artifact manifest to the repository, -// and the application image index when publishing a full application -func (s *composeService) pushComposeArtifact(ctx context.Context, project *types.Project, repository string, layers []v1.Descriptor, options api.PublishOptions) error { +// and the application image index when publishing a full application. The +// returned bool reports whether the push fell back from OCI 1.1 to OCI 1.0. +func (s *composeService) pushComposeArtifact(ctx context.Context, project *types.Project, repository string, layers []v1.Descriptor, options api.PublishOptions) (bool, error) { named, err := reference.ParseDockerRef(repository) if err != nil { - return err + return false, err } var insecureRegistries []string @@ -120,20 +126,20 @@ func (s *composeService) pushComposeArtifact(ctx context.Context, project *types resolver := oci.NewResolver(s.configFile(), desktop.ProxyTransportFor(ctx, s.apiClient()), insecureRegistries...) - descriptor, err := oci.PushManifest(ctx, resolver, named, layers, options.OCIVersion) + descriptor, didFallback, err := oci.PushManifest(ctx, resolver, named, layers, options.OCIVersion) if err != nil { s.events.On(api.Resource{ ID: repository, Text: "publishing", Status: api.Error, }) - return err + return false, err } if options.Application { - return pushApplicationIndex(ctx, resolver, named, descriptor, project) + return didFallback, pushApplicationIndex(ctx, resolver, named, descriptor, project) } - return nil + return didFallback, nil } // pushApplicationIndex pushes an image index referencing every service image,