oras doesn't prepend index.docker.io to repository ref
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
49d1bc7524
commit
f06caeb844
@ -427,6 +427,7 @@ func RootCommand(streams command.Cli, backend api.Service) *cobra.Command { //no
|
|||||||
imagesCommand(&opts, streams, backend),
|
imagesCommand(&opts, streams, backend),
|
||||||
versionCommand(streams),
|
versionCommand(streams),
|
||||||
buildCommand(&opts, &progress, backend),
|
buildCommand(&opts, &progress, backend),
|
||||||
|
publishCommand(&opts, backend),
|
||||||
pushCommand(&opts, backend),
|
pushCommand(&opts, backend),
|
||||||
pullCommand(&opts, backend),
|
pullCommand(&opts, backend),
|
||||||
createCommand(&opts, backend),
|
createCommand(&opts, backend),
|
||||||
|
55
cmd/compose/publish.go
Normal file
55
cmd/compose/publish.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 Docker Compose CLI authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package compose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/docker/compose/v2/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type publishOptions struct {
|
||||||
|
*ProjectOptions
|
||||||
|
composeOptions
|
||||||
|
Repository string
|
||||||
|
}
|
||||||
|
|
||||||
|
func publishCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||||
|
opts := pushOptions{
|
||||||
|
ProjectOptions: p,
|
||||||
|
}
|
||||||
|
publishCmd := &cobra.Command{
|
||||||
|
Use: "publish [OPTIONS] [REPOSITORY]",
|
||||||
|
Short: "Publish compose application",
|
||||||
|
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||||
|
return runPublish(ctx, backend, opts, args[0])
|
||||||
|
}),
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
}
|
||||||
|
return publishCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func runPublish(ctx context.Context, backend api.Service, opts pushOptions, repository string) error {
|
||||||
|
project, err := opts.ToProject(nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return backend.Publish(ctx, project, repository)
|
||||||
|
}
|
@ -74,6 +74,8 @@ type Service interface {
|
|||||||
Events(ctx context.Context, projectName string, options EventsOptions) error
|
Events(ctx context.Context, projectName string, options EventsOptions) error
|
||||||
// Port executes the equivalent to a `compose port`
|
// Port executes the equivalent to a `compose port`
|
||||||
Port(ctx context.Context, projectName string, service string, port uint16, options PortOptions) (string, int, error)
|
Port(ctx context.Context, projectName string, service string, port uint16, options PortOptions) (string, int, error)
|
||||||
|
// Publish executes the equivalent to a `compose publish`
|
||||||
|
Publish(ctx context.Context, project *types.Project, repository string) error
|
||||||
// Images executes the equivalent of a `compose images`
|
// Images executes the equivalent of a `compose images`
|
||||||
Images(ctx context.Context, projectName string, options ImagesOptions) ([]ImageSummary, error)
|
Images(ctx context.Context, projectName string, options ImagesOptions) ([]ImageSummary, error)
|
||||||
// MaxConcurrency defines upper limit for concurrent operations against engine API
|
// MaxConcurrency defines upper limit for concurrent operations against engine API
|
||||||
|
@ -55,6 +55,7 @@ type ServiceProxy struct {
|
|||||||
DryRunModeFn func(ctx context.Context, dryRun bool) (context.Context, error)
|
DryRunModeFn func(ctx context.Context, dryRun bool) (context.Context, error)
|
||||||
VizFn func(ctx context.Context, project *types.Project, options VizOptions) (string, error)
|
VizFn func(ctx context.Context, project *types.Project, options VizOptions) (string, error)
|
||||||
WaitFn func(ctx context.Context, projectName string, options WaitOptions) (int64, error)
|
WaitFn func(ctx context.Context, projectName string, options WaitOptions) (int64, error)
|
||||||
|
PublishFn func(ctx context.Context, project *types.Project, repository string) error
|
||||||
interceptors []Interceptor
|
interceptors []Interceptor
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,6 +92,7 @@ func (s *ServiceProxy) WithService(service Service) *ServiceProxy {
|
|||||||
s.TopFn = service.Top
|
s.TopFn = service.Top
|
||||||
s.EventsFn = service.Events
|
s.EventsFn = service.Events
|
||||||
s.PortFn = service.Port
|
s.PortFn = service.Port
|
||||||
|
s.PublishFn = service.Publish
|
||||||
s.ImagesFn = service.Images
|
s.ImagesFn = service.Images
|
||||||
s.WatchFn = service.Watch
|
s.WatchFn = service.Watch
|
||||||
s.MaxConcurrencyFn = service.MaxConcurrency
|
s.MaxConcurrencyFn = service.MaxConcurrency
|
||||||
@ -311,6 +313,10 @@ func (s *ServiceProxy) Port(ctx context.Context, projectName string, service str
|
|||||||
return s.PortFn(ctx, projectName, service, port, options)
|
return s.PortFn(ctx, projectName, service, port, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ServiceProxy) Publish(ctx context.Context, project *types.Project, repository string) error {
|
||||||
|
return s.PublishFn(ctx, project, repository)
|
||||||
|
}
|
||||||
|
|
||||||
// Images implements Service interface
|
// Images implements Service interface
|
||||||
func (s *ServiceProxy) Images(ctx context.Context, project string, options ImagesOptions) ([]ImageSummary, error) {
|
func (s *ServiceProxy) Images(ctx context.Context, project string, options ImagesOptions) ([]ImageSummary, error) {
|
||||||
if s.ImagesFn == nil {
|
if s.ImagesFn == nil {
|
||||||
|
185
pkg/compose/publish.go
Normal file
185
pkg/compose/publish.go
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 Docker Compose CLI authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package compose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/compose-spec/compose-go/types"
|
||||||
|
"github.com/distribution/distribution/v3/reference"
|
||||||
|
client2 "github.com/docker/cli/cli/registry/client"
|
||||||
|
"github.com/docker/compose/v2/pkg/api"
|
||||||
|
"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) error {
|
||||||
|
err := s.Push(ctx, project, api.PushOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
target, err := reference.ParseDockerRef(repository)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
client := s.dockerCli.RegistryClient(false)
|
||||||
|
for i, service := range project.Services {
|
||||||
|
ref, err := reference.ParseDockerRef(service.Image)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
auth, err := encodedAuth(ref, s.configFile())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
inspect, err := s.apiClient().DistributionInspect(ctx, ref.String(), auth)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
canonical, err := reference.WithDigest(ref, inspect.Descriptor.Digest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
to, err := reference.WithDigest(target, inspect.Descriptor.Digest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = client.MountBlob(ctx, canonical, to)
|
||||||
|
switch err.(type) {
|
||||||
|
case client2.ErrBlobCreated:
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
service.Image = to.String()
|
||||||
|
project.Services[i] = service
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.publishComposeYaml(ctx, project, repository)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *composeService) publishComposeYaml(ctx context.Context, project *types.Project, repository string) error {
|
||||||
|
ref, err := reference.ParseDockerRef(repository)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var manifests []v1.Descriptor
|
||||||
|
|
||||||
|
for _, composeFile := range project.ComposeFiles {
|
||||||
|
stat, err := os.Stat(composeFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(ctx, "oras", "push", "--artifact-type", "application/vnd.docker.compose.yaml", ref.String(), composeFile)
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd.Stderr = s.stderr()
|
||||||
|
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out, err := io.ReadAll(stdout)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var composeFileDigest string
|
||||||
|
for _, line := range strings.Split(string(out), "\n") {
|
||||||
|
if strings.HasPrefix(line, "Digest: ") {
|
||||||
|
composeFileDigest = line[len("Digest: "):]
|
||||||
|
}
|
||||||
|
fmt.Fprintln(s.stdout(), line)
|
||||||
|
}
|
||||||
|
if composeFileDigest == "" {
|
||||||
|
return fmt.Errorf("expected oras to display `Digest: xxx`")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.Wait()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
manifests = append(manifests, v1.Descriptor{
|
||||||
|
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||||
|
Digest: digest.Digest(composeFileDigest),
|
||||||
|
Size: stat.Size(),
|
||||||
|
ArtifactType: "application/vnd.docker.compose.yaml",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, service := range project.Services {
|
||||||
|
dockerRef, err := reference.ParseDockerRef(service.Image)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
manifests = append(manifests, v1.Descriptor{
|
||||||
|
MediaType: v1.MediaTypeImageIndex,
|
||||||
|
Digest: dockerRef.(reference.Digested).Digest(),
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"com.docker.compose.service": service.Name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest := v1.Index{
|
||||||
|
Versioned: specs.Versioned{
|
||||||
|
SchemaVersion: 2,
|
||||||
|
},
|
||||||
|
MediaType: v1.MediaTypeImageIndex,
|
||||||
|
Manifests: manifests,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"com.docker.compose": api.ComposeVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
manifestContent, err := json.Marshal(manifest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
temp, err := os.CreateTemp(os.TempDir(), "compose")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.WriteFile(temp.Name(), manifestContent, 0o700)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.Remove(temp.Name())
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(ctx, "oras", "manifest", "push", ref.String(), temp.Name())
|
||||||
|
cmd.Stdout = s.stdout()
|
||||||
|
cmd.Stderr = s.stderr()
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user