Compare commits

..

2 Commits

Author SHA1 Message Date
Nicolas De Loof
f06caeb844 oras doesn't prepend index.docker.io to repository ref
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2023-07-07 09:26:13 +02:00
Nicolas De Loof
49d1bc7524 introduce push --repository
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2023-07-05 09:06:41 +02:00
60 changed files with 455 additions and 218 deletions

View File

@@ -91,7 +91,6 @@ FROM build-base AS lint
ARG BUILD_TAGS
RUN --mount=type=bind,target=. \
--mount=type=cache,target=/root/.cache \
--mount=type=cache,target=/go/pkg/mod \
--mount=from=golangci-lint,source=/usr/bin/golangci-lint,target=/usr/bin/golangci-lint \
golangci-lint run --build-tags "$BUILD_TAGS" ./...
@@ -130,7 +129,6 @@ FROM base AS docsgen
WORKDIR /src
RUN --mount=target=. \
--mount=target=/root/.cache,type=cache \
--mount=type=cache,target=/go/pkg/mod \
go build -o /out/docsgen ./docs/yaml/main/generate.go
FROM --platform=${BUILDPLATFORM} alpine AS docs-build

View File

@@ -26,7 +26,6 @@ import (
"strings"
"syscall"
"github.com/compose-spec/compose-go/dotenv"
buildx "github.com/docker/buildx/util/progress"
"github.com/docker/cli/cli/command"
@@ -428,6 +427,7 @@ func RootCommand(streams command.Cli, backend api.Service) *cobra.Command { //no
imagesCommand(&opts, streams, backend),
versionCommand(streams),
buildCommand(&opts, &progress, backend),
publishCommand(&opts, backend),
pushCommand(&opts, backend),
pullCommand(&opts, backend),
createCommand(&opts, backend),
@@ -473,7 +473,7 @@ func setEnvWithDotEnv(prjOpts *ProjectOptions) error {
return err
}
envFromFile, err := dotenv.GetEnvFromFile(composegoutils.GetAsEqualsMap(os.Environ()), workingDir, options.EnvFiles)
envFromFile, err := cli.GetEnvFromFile(composegoutils.GetAsEqualsMap(os.Environ()), workingDir, options.EnvFiles)
if err != nil {
return err
}

View File

@@ -64,10 +64,10 @@ func copyCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
}
flags := copyCmd.Flags()
flags.IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas")
flags.BoolVar(&opts.all, "all", false, "copy to all the containers of the service.")
flags.IntVar(&opts.index, "index", 0, "Index of the container if there are multiple instances of a service .")
flags.BoolVar(&opts.all, "all", false, "Copy to all the containers of the service.")
flags.MarkHidden("all") //nolint:errcheck
flags.MarkDeprecated("all", "by default all the containers of the service will get the source file/directory to be copied.") //nolint:errcheck
flags.MarkDeprecated("all", "By default all the containers of the service will get the source file/directory to be copied.") //nolint:errcheck
flags.BoolVarP(&opts.followLink, "follow-link", "L", false, "Always follow symbol link in SRC_PATH")
flags.BoolVarP(&opts.copyUIDGID, "archive", "a", false, "Archive mode (copy all uid/gid information)")

View File

@@ -65,7 +65,7 @@ func execCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *c
runCmd.Flags().BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: Run command in the background.")
runCmd.Flags().StringArrayVarP(&opts.environment, "env", "e", []string{}, "Set environment variables")
runCmd.Flags().IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas")
runCmd.Flags().IntVar(&opts.index, "index", 1, "index of the container if there are multiple instances of a service [default: 1].")
runCmd.Flags().BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the process.")
runCmd.Flags().StringVarP(&opts.user, "user", "u", "", "Run the command as this user.")
runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", !streams.Out().IsTerminal(), "Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.")

View File

@@ -57,7 +57,7 @@ func portCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *c
ValidArgsFunction: completeServiceNames(p),
}
cmd.Flags().StringVar(&opts.protocol, "protocol", "tcp", "tcp or udp")
cmd.Flags().IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas")
cmd.Flags().IntVar(&opts.index, "index", 1, "index of the container if service has multiple replicas")
return cmd
}

55
cmd/compose/publish.go Normal file
View 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)
}

View File

@@ -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,
})
}

View File

@@ -63,13 +63,6 @@ type runOptions struct {
}
func (options runOptions) apply(project *types.Project) error {
if options.noDeps {
err := project.ForServices([]string{options.Service}, types.IgnoreDependencies)
if err != nil {
return err
}
}
target, err := project.GetService(options.Service)
if err != nil {
return err
@@ -100,6 +93,13 @@ func (options runOptions) apply(project *types.Project) error {
}
}
if options.noDeps {
err := project.ForServices([]string{options.Service}, types.IgnoreDependencies)
if err != nil {
return err
}
}
for i, s := range project.Services {
if s.Name == options.Service {
project.Services[i] = target

View File

@@ -21,8 +21,9 @@ import (
"fmt"
"time"
"github.com/compose-spec/compose-go/types"
"github.com/docker/compose/v2/cmd/formatter"
"github.com/compose-spec/compose-go/types"
"github.com/spf13/cobra"
"github.com/docker/compose/v2/pkg/api"
@@ -163,31 +164,17 @@ func runUp(ctx context.Context, streams api.Streams, backend api.Service, create
consumer = formatter.NewLogConsumer(ctx, streams.Out(), streams.Err(), !upOptions.noColor, !upOptions.noPrefix, upOptions.timestamp)
}
attachTo := utils.Set[string]{}
attachTo := services
if len(upOptions.attach) > 0 {
attachTo.AddAll(upOptions.attach...)
attachTo = upOptions.attach
}
if upOptions.attachDependencies {
if err := project.WithServices(attachTo.Elements(), func(s types.ServiceConfig) error {
if s.Attach == nil || *s.Attach {
attachTo.Add(s.Name)
}
return nil
}); err != nil {
return err
}
attachTo = project.ServiceNames()
}
if len(attachTo) == 0 {
if err := project.WithServices(services, func(s types.ServiceConfig) error {
if s.Attach == nil || *s.Attach {
attachTo.Add(s.Name)
}
return nil
}); err != nil {
return err
}
attachTo = project.ServiceNames()
}
attachTo.RemoveAll(upOptions.noAttach...)
attachTo = utils.Remove(attachTo, upOptions.noAttach...)
create := api.CreateOptions{
Services: services,
@@ -211,7 +198,7 @@ func runUp(ctx context.Context, streams api.Streams, backend api.Service, create
Start: api.StartOptions{
Project: project,
Attach: consumer,
AttachTo: attachTo.Elements(),
AttachTo: attachTo,
ExitCodeFrom: upOptions.exitCodeFrom,
CascadeStop: upOptions.cascadeStop,
Wait: upOptions.wait,

View File

@@ -7,6 +7,7 @@ Define and run multi-container applications with Docker.
| Name | Description |
|:--------------------------------|:------------------------------------------------------------------------|
| [`alpha`](compose_alpha.md) | Experimental commands |
| [`build`](compose_build.md) | Build or rebuild services |
| [`config`](compose_config.md) | Parse, resolve and render compose file in canonical format |
| [`cp`](compose_cp.md) | Copy files/folders between a service container and the local filesystem |

View File

@@ -5,12 +5,12 @@ Copy files/folders between a service container and the local filesystem
### Options
| Name | Type | Default | Description |
|:----------------------|:------|:--------|:--------------------------------------------------------|
| `-a`, `--archive` | | | Archive mode (copy all uid/gid information) |
| `--dry-run` | | | Execute command in dry run mode |
| `-L`, `--follow-link` | | | Always follow symbol link in SRC_PATH |
| `--index` | `int` | `0` | index of the container if service has multiple replicas |
| Name | Type | Default | Description |
|:----------------------|:------|:--------|:----------------------------------------------------------------------|
| `-a`, `--archive` | | | Archive mode (copy all uid/gid information) |
| `--dry-run` | | | Execute command in dry run mode |
| `-L`, `--follow-link` | | | Always follow symbol link in SRC_PATH |
| `--index` | `int` | `0` | Index of the container if there are multiple instances of a service . |
<!---MARKER_GEN_END-->

View File

@@ -5,16 +5,16 @@ Execute a command in a running container.
### Options
| Name | Type | Default | Description |
|:------------------|:--------------|:--------|:---------------------------------------------------------------------------------|
| `-d`, `--detach` | | | Detached mode: Run command in the background. |
| `--dry-run` | | | Execute command in dry run mode |
| `-e`, `--env` | `stringArray` | | Set environment variables |
| `--index` | `int` | `0` | index of the container if service has multiple replicas |
| `-T`, `--no-TTY` | | | Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY. |
| `--privileged` | | | Give extended privileges to the process. |
| `-u`, `--user` | `string` | | Run the command as this user. |
| `-w`, `--workdir` | `string` | | Path to workdir directory for this command. |
| Name | Type | Default | Description |
|:------------------|:--------------|:--------|:----------------------------------------------------------------------------------|
| `-d`, `--detach` | | | Detached mode: Run command in the background. |
| `--dry-run` | | | Execute command in dry run mode |
| `-e`, `--env` | `stringArray` | | Set environment variables |
| `--index` | `int` | `1` | index of the container if there are multiple instances of a service [default: 1]. |
| `-T`, `--no-TTY` | | | Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY. |
| `--privileged` | | | Give extended privileges to the process. |
| `-u`, `--user` | `string` | | Run the command as this user. |
| `-w`, `--workdir` | `string` | | Path to workdir directory for this command. |
<!---MARKER_GEN_END-->

View File

@@ -8,7 +8,7 @@ Print the public port for a port binding.
| Name | Type | Default | Description |
|:-------------|:---------|:--------|:--------------------------------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
| `--index` | `int` | `0` | index of the container if service has multiple replicas |
| `--index` | `int` | `1` | index of the container if service has multiple replicas |
| `--protocol` | `string` | `tcp` | tcp or udp |

View File

@@ -347,7 +347,6 @@ options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -21,7 +21,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: true
experimental: false
experimentalcli: true
kubernetes: false

View File

@@ -69,7 +69,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: true
kubernetes: false

View File

@@ -29,7 +29,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: true
kubernetes: false

View File

@@ -159,7 +159,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -152,7 +152,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -10,7 +10,7 @@ options:
- option: all
value_type: bool
default_value: "false"
description: copy to all the containers of the service.
description: Copy to all the containers of the service.
deprecated: true
hidden: true
experimental: false
@@ -42,7 +42,8 @@ options:
- option: index
value_type: int
default_value: "0"
description: index of the container if service has multiple replicas
description: |
Index of the container if there are multiple instances of a service .
deprecated: false
hidden: false
experimental: false
@@ -61,7 +62,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -90,7 +90,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -73,7 +73,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -46,7 +46,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -33,8 +33,9 @@ options:
swarm: false
- option: index
value_type: int
default_value: "0"
description: index of the container if service has multiple replicas
default_value: "1"
description: |
index of the container if there are multiple instances of a service [default: 1].
deprecated: false
hidden: false
experimental: false
@@ -117,7 +118,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -38,7 +38,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -43,7 +43,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -91,7 +91,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -58,7 +58,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -17,7 +17,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -7,7 +7,7 @@ plink: docker_compose.yaml
options:
- option: index
value_type: int
default_value: "0"
default_value: "1"
description: index of the container if service has multiple replicas
deprecated: false
hidden: false
@@ -37,7 +37,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -180,7 +180,6 @@ examples: |-
The `docker compose ps` command currently only supports the `--filter status=<status>`
option, but additional filter options may be added in the future.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -124,7 +124,6 @@ examples: |-
`docker compose pull` will try to pull image for services with a build section. If pull fails, it will let
user know this service image MUST be built. You can skip this by setting `--ignore-buildable` flag
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -66,7 +66,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -48,7 +48,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -76,7 +76,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -286,7 +286,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -16,7 +16,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -29,7 +29,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -23,7 +23,6 @@ examples: |-
root 142353 142331 2 15:33 ? 00:00:00 ping localhost -c 5
```
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -16,7 +16,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -285,7 +285,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -37,7 +37,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -27,7 +27,6 @@ inherited_options:
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

15
go.mod
View File

@@ -6,15 +6,15 @@ require (
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/Microsoft/go-winio v0.6.1
github.com/buger/goterm v1.0.4
github.com/compose-spec/compose-go v1.16.0
github.com/compose-spec/compose-go v1.15.1
github.com/containerd/console v1.0.3
github.com/containerd/containerd v1.7.2
github.com/cucumber/godog v0.0.0-00010101000000-000000000000 // replaced; see replace for the actual version used
github.com/distribution/distribution/v3 v3.0.0-20230601133803-97b1d649c493
github.com/docker/buildx v0.11.1
github.com/docker/buildx v0.11.0
github.com/docker/cli v24.0.2+incompatible
github.com/docker/cli-docs-tool v0.6.0
github.com/docker/docker v24.0.4+incompatible
github.com/docker/cli-docs-tool v0.5.1
github.com/docker/docker v24.0.2+incompatible
github.com/docker/go-connections v0.4.0
github.com/docker/go-units v0.5.0
github.com/fsnotify/fsevents v0.1.1
@@ -44,9 +44,9 @@ require (
go.opentelemetry.io/otel/trace v1.14.0
go.uber.org/goleak v1.2.1
golang.org/x/sync v0.3.0
google.golang.org/grpc v1.56.2
google.golang.org/grpc v1.56.0
gopkg.in/yaml.v2 v2.4.0
gotest.tools/v3 v3.5.0
gotest.tools/v3 v3.4.0
)
require (
@@ -150,7 +150,7 @@ require (
github.com/subosito/gotenv v1.4.1 // indirect
github.com/tonistiigi/fsutil v0.0.0-20230407161946-9e7a6df48576 // indirect
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
github.com/tonistiigi/vt100 v0.0.0-20230623042737-f9a4f7ef6531 // indirect
github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
@@ -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

32
go.sum
View File

@@ -131,8 +131,8 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
github.com/compose-spec/compose-go v1.16.0 h1:HYk4uYWXgArHh6NG+WE4yGYayOXw+hjqJ+eJxpjWWjk=
github.com/compose-spec/compose-go v1.16.0/go.mod h1:3yngGBGfls6FHGQsg4B1z6gz8ej9SOvmAJtxCwgbcnc=
github.com/compose-spec/compose-go v1.15.1 h1:0yaEt6/66dLN0bNWYDTj0CDx626uCdQ9ipJVIJx8O8M=
github.com/compose-spec/compose-go v1.15.1/go.mod h1:3yngGBGfls6FHGQsg4B1z6gz8ej9SOvmAJtxCwgbcnc=
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
@@ -167,17 +167,17 @@ github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zA
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/distribution/distribution/v3 v3.0.0-20230601133803-97b1d649c493 h1:fm5DpBD+A7o0+x9Nf+o9/4/qPGbfxLpr9qIPVuV8vQc=
github.com/distribution/distribution/v3 v3.0.0-20230601133803-97b1d649c493/go.mod h1:+fqBJ4vPYo4Uu1ZE4d+bUtTLRXfdSL3NvCZIZ9GHv58=
github.com/docker/buildx v0.11.1 h1:xfmrAkOJrN+NLRcwhZn1iBgJVAK1dEBEv8lWu1Wxg14=
github.com/docker/buildx v0.11.1/go.mod h1:qAxs3bsJEfVo7DOc9riES/f9Z187CeGM5nLPmadk8AA=
github.com/docker/buildx v0.11.0 h1:DNCOIYT/7J0sPBlU/ozEhFd4MtbnbFByn45yeTMHXVU=
github.com/docker/buildx v0.11.0/go.mod h1:Yq7ZNjrwXKzW0uSFMk46dl5Gl903k5+bp6U4apsM5rs=
github.com/docker/cli v24.0.2+incompatible h1:QdqR7znue1mtkXIJ+ruQMGQhpw2JzMJLRXp6zpzF6tM=
github.com/docker/cli v24.0.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli-docs-tool v0.6.0 h1:Z9x10SaZgFaB6jHgz3OWooynhSa40CsWkpe5hEnG/qA=
github.com/docker/cli-docs-tool v0.6.0/go.mod h1:zMjqTFCU361PRh8apiXzeAZ1Q/xupbIwTusYpzCXS/o=
github.com/docker/cli-docs-tool v0.5.1 h1:jIk/cCZurZERhALPVKhqlNxTQGxn2kcI+56gE57PQXg=
github.com/docker/cli-docs-tool v0.5.1/go.mod h1:zMjqTFCU361PRh8apiXzeAZ1Q/xupbIwTusYpzCXS/o=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v24.0.4+incompatible h1:s/LVDftw9hjblvqIeTiGYXBCD95nOEEl7qRsRrIOuQI=
github.com/docker/docker v24.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v24.0.2+incompatible h1:eATx+oLz9WdNVkQrr0qjQ8HvRJ4bOOxfzEo8R+dA3cg=
github.com/docker/docker v24.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
@@ -511,6 +511,8 @@ github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
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=
@@ -630,8 +632,8 @@ github.com/tonistiigi/fsutil v0.0.0-20230407161946-9e7a6df48576 h1:fZXPQDVh5fm2x
github.com/tonistiigi/fsutil v0.0.0-20230407161946-9e7a6df48576/go.mod h1:q1CxMSzcAbjUkVGHoZeQUcCaALnaE4XdWk+zJcgMYFw=
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk=
github.com/tonistiigi/vt100 v0.0.0-20230623042737-f9a4f7ef6531 h1:Y/M5lygoNPKwVNLMPXgVfsRT40CSFKXCxuU8LoHySjs=
github.com/tonistiigi/vt100 v0.0.0-20230623042737-f9a4f7ef6531/go.mod h1:ulncasL3N9uLrVann0m+CDlJKWsIAP34MPcOJF6VRvc=
github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f h1:DLpt6B5oaaS8jyXHa9VA4rrZloBVPVXeCtrOsrFauxc=
github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f/go.mod h1:ulncasL3N9uLrVann0m+CDlJKWsIAP34MPcOJF6VRvc=
github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME=
github.com/weppos/publicsuffix-go v0.15.1-0.20220329081811-9a40b608a236 h1:vMJBP3PQViZsF6cOINtvyMC8ptpLsyJ4EwyFnzuWNxc=
github.com/weppos/publicsuffix-go v0.15.1-0.20220329081811-9a40b608a236/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE=
@@ -1039,8 +1041,8 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI=
google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/grpc v1.56.0 h1:+y7Bs8rtMd07LeXmL3NxcTLn7mUkbKZqEpPhMNkwJEE=
google.golang.org/grpc v1.56.0/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1088,8 +1090,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY=
gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -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=

View File

@@ -74,6 +74,8 @@ type Service interface {
Events(ctx context.Context, projectName string, options EventsOptions) error
// Port executes the equivalent to a `compose port`
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(ctx context.Context, projectName string, options ImagesOptions) ([]ImageSummary, error)
// MaxConcurrency defines upper limit for concurrent operations against engine API
@@ -260,6 +262,7 @@ type ConfigOptions struct {
// PushOptions group options of the Push API
type PushOptions struct {
Quiet bool
Repository string
IgnoreFailures bool
}

View File

@@ -70,7 +70,7 @@ type execDetails struct {
}
// NewDryRunClient produces a DryRunClient
func NewDryRunClient(apiClient client.APIClient, cli command.Cli) (*DryRunClient, error) {
func NewDryRunClient(apiClient client.APIClient, cli *command.DockerCli) (*DryRunClient, error) {
b, err := builder.New(cli, builder.WithSkippedValidation())
if err != nil {
return nil, err

View File

@@ -55,6 +55,7 @@ type ServiceProxy struct {
DryRunModeFn func(ctx context.Context, dryRun bool) (context.Context, error)
VizFn func(ctx context.Context, project *types.Project, options VizOptions) (string, 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
}
@@ -91,6 +92,7 @@ func (s *ServiceProxy) WithService(service Service) *ServiceProxy {
s.TopFn = service.Top
s.EventsFn = service.Events
s.PortFn = service.Port
s.PublishFn = service.Publish
s.ImagesFn = service.Images
s.WatchFn = service.Watch
s.MaxConcurrencyFn = service.MaxConcurrency
@@ -217,7 +219,7 @@ func (s *ServiceProxy) List(ctx context.Context, options ListOptions) ([]Stack,
return s.ListFn(ctx, options)
}
// Config implements Service interface
// Convert implements Service interface
func (s *ServiceProxy) Config(ctx context.Context, project *types.Project, options ConfigOptions) ([]byte, error) {
if s.ConfigFn == nil {
return nil, ErrNotImplemented
@@ -311,6 +313,10 @@ func (s *ServiceProxy) Port(ctx context.Context, projectName string, service str
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
func (s *ServiceProxy) Images(ctx context.Context, project string, options ImagesOptions) ([]ImageSummary, error) {
if s.ImagesFn == nil {

View File

@@ -88,11 +88,8 @@ func (s *composeService) DryRunMode(ctx context.Context, dryRun bool) (context.C
if err != nil {
return ctx, err
}
options := flags.NewClientOptions()
options.Context = s.dockerCli.CurrentContext()
err = cli.Initialize(options, command.WithInitializeClient(func(cli *command.DockerCli) (client.APIClient, error) {
return api.NewDryRunClient(s.apiClient(), s.dockerCli)
err = cli.Initialize(flags.NewClientOptions(), command.WithInitializeClient(func(cli *command.DockerCli) (client.APIClient, error) {
return api.NewDryRunClient(s.apiClient(), cli)
}))
if err != nil {
return ctx, err

View File

@@ -20,7 +20,6 @@ import (
"context"
"fmt"
"sort"
"strconv"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/utils"
@@ -73,9 +72,7 @@ func getDefaultFilters(projectName string, oneOff oneOff, selectedServices ...st
func (s *composeService) getSpecifiedContainer(ctx context.Context, projectName string, oneOff oneOff, stopped bool, serviceName string, containerIndex int) (moby.Container, error) {
defaultFilters := getDefaultFilters(projectName, oneOff, serviceName)
if containerIndex > 0 {
defaultFilters = append(defaultFilters, containerNumberFilter(containerIndex))
}
defaultFilters = append(defaultFilters, containerNumberFilter(containerIndex))
containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
Filters: filters.NewArgs(
defaultFilters...,
@@ -86,16 +83,8 @@ func (s *composeService) getSpecifiedContainer(ctx context.Context, projectName
return moby.Container{}, err
}
if len(containers) < 1 {
if containerIndex > 0 {
return moby.Container{}, fmt.Errorf("service %q is not running container #%d", serviceName, containerIndex)
}
return moby.Container{}, fmt.Errorf("service %q is not running", serviceName)
return moby.Container{}, fmt.Errorf("service %q is not running container #%d", serviceName, containerIndex)
}
sort.Slice(containers, func(i, j int) bool {
x, _ := strconv.Atoi(containers[i].Labels[api.ContainerNumberLabel])
y, _ := strconv.Atoi(containers[j].Labels[api.ContainerNumberLabel])
return x < y
})
container := containers[0]
return container, nil
}

View File

@@ -549,15 +549,13 @@ func (s *composeService) createMobyContainer(ctx context.Context,
// call via container.NetworkMode & network.NetworkingConfig
// any remaining networks are connected one-by-one here after creation (but before start)
serviceNetworks := service.NetworksByPriority()
for _, networkKey := range serviceNetworks {
mobyNetworkName := project.Networks[networkKey].Name
if string(cfgs.Host.NetworkMode) == mobyNetworkName {
// primary network already configured as part of ContainerCreate
continue
}
epSettings := createEndpointSettings(project, service, number, networkKey, cfgs.Links, opts.UseNetworkAliases)
if err := s.apiClient().NetworkConnect(ctx, mobyNetworkName, created.ID, epSettings); err != nil {
return created, err
if len(serviceNetworks) > 1 {
for _, networkKey := range serviceNetworks[1:] {
mobyNetworkName := project.Networks[networkKey].Name
epSettings := createEndpointSettings(project, service, number, networkKey, cfgs.Links, opts.UseNetworkAliases)
if err := s.apiClient().NetworkConnect(ctx, mobyNetworkName, created.ID, epSettings); err != nil {
return created, err
}
}
}

View File

@@ -24,14 +24,25 @@ import (
"github.com/docker/compose/v2/pkg/api"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
)
func (s *composeService) Port(ctx context.Context, projectName string, service string, port uint16, options api.PortOptions) (string, int, error) {
projectName = strings.ToLower(projectName)
container, err := s.getSpecifiedContainer(ctx, projectName, oneOffInclude, false, service, options.Index)
list, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
Filters: filters.NewArgs(
projectFilter(projectName),
serviceFilter(service),
containerNumberFilter(options.Index),
),
})
if err != nil {
return "", 0, err
}
if len(list) == 0 {
return "", 0, fmt.Errorf("no container found for %s%s%d", service, api.Separator, options.Index)
}
container := list[0]
for _, p := range container.Ports {
if p.PrivatePort == port && p.Type == options.Protocol {
return p.IP, int(p.PublicPort), nil

185
pkg/compose/publish.go Normal file
View 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
}

View File

@@ -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 {

View File

@@ -58,7 +58,7 @@ func createTar(env string, config types.ServiceSecretConfig) (bytes.Buffer, erro
value := []byte(env)
b := bytes.Buffer{}
tarWriter := tar.NewWriter(&b)
mode := uint32(0o444)
mode := uint32(0o400)
if config.Mode != nil {
mode = *config.Mode
}

View File

@@ -136,12 +136,4 @@ func TestLocalComposeRun(t *testing.T) {
c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/deps.yaml", "down", "--remove-orphans")
})
t.Run("run without dependencies", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/deps.yaml", "run", "--no-deps", "service_a")
assert.Assert(t, !strings.Contains(res.Combined(), "shared_dep"), res.Combined())
assert.Assert(t, !strings.Contains(res.Combined(), "service_b"), res.Combined())
c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/deps.yaml", "down", "--remove-orphans")
})
}

View File

@@ -29,34 +29,47 @@ import (
func TestNetworks(t *testing.T) {
// fixture is shared with TestNetworkModes and is not safe to run concurrently
c := NewCLI(t)
const projectName = "network-e2e"
c := NewCLI(t, WithEnv(
"COMPOSE_PROJECT_NAME="+projectName,
"COMPOSE_FILE=./fixtures/network-test/compose.yaml",
))
c.RunDockerComposeCmd(t, "down", "-t0", "-v")
t.Run("ensure we do not reuse previous networks", func(t *testing.T) {
c.RunDockerOrExitError(t, "network", "rm", projectName+"-dbnet")
c.RunDockerOrExitError(t, "network", "rm", "microservices")
})
c.RunDockerComposeCmd(t, "up", "-d")
t.Run("up", func(t *testing.T) {
c.RunDockerComposeCmd(t, "-f", "./fixtures/network-test/compose.yaml", "--project-name", projectName, "up",
"-d")
})
res := c.RunDockerComposeCmd(t, "ps")
res.Assert(t, icmd.Expected{Out: `web`})
t.Run("check running project", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "-p", projectName, "ps")
res.Assert(t, icmd.Expected{Out: `web`})
endpoint := "http://localhost:80"
output := HTTPGetWithRetry(t, endpoint+"/words/noun", http.StatusOK, 2*time.Second, 20*time.Second)
assert.Assert(t, strings.Contains(output, `"word":`))
endpoint := "http://localhost:80"
output := HTTPGetWithRetry(t, endpoint+"/words/noun", http.StatusOK, 2*time.Second, 20*time.Second)
assert.Assert(t, strings.Contains(output, `"word":`))
res = c.RunDockerCmd(t, "network", "ls")
res.Assert(t, icmd.Expected{Out: projectName + "_dbnet"})
res.Assert(t, icmd.Expected{Out: "microservices"})
res = c.RunDockerCmd(t, "network", "ls")
res.Assert(t, icmd.Expected{Out: projectName + "_dbnet"})
res.Assert(t, icmd.Expected{Out: "microservices"})
})
res = c.RunDockerComposeCmd(t, "port", "words", "8080")
res.Assert(t, icmd.Expected{Out: `0.0.0.0:8080`})
t.Run("port", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "--project-name", projectName, "port", "words", "8080")
res.Assert(t, icmd.Expected{Out: `0.0.0.0:8080`})
})
c.RunDockerComposeCmd(t, "down", "-t0", "-v")
res = c.RunDockerCmd(t, "network", "ls")
assert.Assert(t, !strings.Contains(res.Combined(), projectName), res.Combined())
assert.Assert(t, !strings.Contains(res.Combined(), "microservices"), res.Combined())
t.Run("down", func(t *testing.T) {
_ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
})
t.Run("check networks after down", func(t *testing.T) {
res := c.RunDockerCmd(t, "network", "ls")
assert.Assert(t, !strings.Contains(res.Combined(), projectName), res.Combined())
assert.Assert(t, !strings.Contains(res.Combined(), "microservices"), res.Combined())
})
}
func TestNetworkAliases(t *testing.T) {

View File

@@ -21,8 +21,6 @@ import (
"testing"
"time"
"gotest.tools/v3/icmd"
"gotest.tools/v3/assert"
)
@@ -30,12 +28,6 @@ func TestWaitOnFaster(t *testing.T) {
const projectName = "e2e-wait-faster"
c := NewParallelCLI(t)
cleanup := func() {
c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "--timeout=0", "--remove-orphans")
}
t.Cleanup(cleanup)
cleanup()
c.RunDockerComposeCmd(t, "-f", "./fixtures/wait/compose.yaml", "--project-name", projectName, "up", "-d")
c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "faster")
}
@@ -44,12 +36,6 @@ func TestWaitOnSlower(t *testing.T) {
const projectName = "e2e-wait-slower"
c := NewParallelCLI(t)
cleanup := func() {
c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "--timeout=0", "--remove-orphans")
}
t.Cleanup(cleanup)
cleanup()
c.RunDockerComposeCmd(t, "-f", "./fixtures/wait/compose.yaml", "--project-name", projectName, "up", "-d")
c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "slower")
}
@@ -58,27 +44,12 @@ func TestWaitOnInfinity(t *testing.T) {
const projectName = "e2e-wait-infinity"
c := NewParallelCLI(t)
cleanup := func() {
c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "--timeout=0", "--remove-orphans")
}
t.Cleanup(cleanup)
cleanup()
c.RunDockerComposeCmd(t, "-f", "./fixtures/wait/compose.yaml", "--project-name", projectName, "up", "-d")
cmd := c.NewDockerComposeCmd(t, "--project-name", projectName, "wait", "infinity")
r := icmd.StartCmd(cmd)
assert.NilError(t, r.Error)
t.Cleanup(func() {
if r.Cmd.Process != nil {
_ = r.Cmd.Process.Kill()
}
})
finished := make(chan struct{})
ticker := time.NewTicker(7 * time.Second)
go func() {
_ = r.Cmd.Wait()
c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "infinity")
finished <- struct{}{}
}()
@@ -93,12 +64,6 @@ func TestWaitAndDrop(t *testing.T) {
const projectName = "e2e-wait-and-drop"
c := NewParallelCLI(t)
cleanup := func() {
c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "--timeout=0", "--remove-orphans")
}
t.Cleanup(cleanup)
cleanup()
c.RunDockerComposeCmd(t, "-f", "./fixtures/wait/compose.yaml", "--project-name", projectName, "up", "-d")
c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "--down-project", "faster")

View File

@@ -47,9 +47,3 @@ func (s Set[T]) Elements() []T {
}
return elements
}
func (s Set[T]) RemoveAll(elements ...T) {
for _, e := range elements {
s.Remove(e)
}
}

View File

@@ -29,7 +29,7 @@ func Contains[T any](origin []T, element T) bool {
return false
}
// Remove removes all elements from origin slice
// RemoveAll removes all elements from origin slice
func Remove[T any](origin []T, elements ...T) []T {
var filtered []T
for _, v := range origin {