mirror of
https://github.com/docker/compose.git
synced 2026-02-10 02:29:25 +08:00
Compare commits
113 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32005b0bfe | ||
|
|
025a72a417 | ||
|
|
95c4502b81 | ||
|
|
2290ce2c24 | ||
|
|
bac732837e | ||
|
|
b725c56c42 | ||
|
|
cfcc9533b3 | ||
|
|
cffdb69c5e | ||
|
|
709190312c | ||
|
|
e1a38f984b | ||
|
|
4dafeb57a5 | ||
|
|
45956c36fb | ||
|
|
9eb69465b7 | ||
|
|
5f392258cb | ||
|
|
382c1cd68e | ||
|
|
5994050f51 | ||
|
|
28a00571ef | ||
|
|
d00eacbba0 | ||
|
|
a6c76a9c0f | ||
|
|
5754d6084c | ||
|
|
10cd7e130f | ||
|
|
8f9dc2e7f8 | ||
|
|
dfa93d834f | ||
|
|
f69a613e69 | ||
|
|
a8fbbd9e5c | ||
|
|
ba724576a1 | ||
|
|
3261b60fd1 | ||
|
|
cb425a23c0 | ||
|
|
29179840c3 | ||
|
|
7205d918ad | ||
|
|
413f46ade0 | ||
|
|
8a9c4b52b4 | ||
|
|
865b82da6a | ||
|
|
e44222664a | ||
|
|
d4e3f191ac | ||
|
|
577bee955b | ||
|
|
ed2395819d | ||
|
|
e6599c7213 | ||
|
|
9c01e41adf | ||
|
|
6df30f39f2 | ||
|
|
a79346b978 | ||
|
|
125752c127 | ||
|
|
95f0431127 | ||
|
|
2bee75c3c4 | ||
|
|
a1f7be7b5c | ||
|
|
72e4519cbf | ||
|
|
9e6f51d262 | ||
|
|
98b3353cbc | ||
|
|
c756ff3d3e | ||
|
|
fc827f295b | ||
|
|
f10c96a54a | ||
|
|
06c5d8a902 | ||
|
|
0f3c214b48 | ||
|
|
058c779378 | ||
|
|
07a562aa2d | ||
|
|
76fe903deb | ||
|
|
284bad4411 | ||
|
|
04d8212a88 | ||
|
|
d38f278f68 | ||
|
|
4cb6ace1ce | ||
|
|
9d04f3ff73 | ||
|
|
cc5b72b11d | ||
|
|
ba08d39187 | ||
|
|
c4cfaeb12a | ||
|
|
b2d2c67032 | ||
|
|
9bc1dfe036 | ||
|
|
6476e10b93 | ||
|
|
2530bd981a | ||
|
|
d38a315798 | ||
|
|
e3204e7c4e | ||
|
|
27f5b8536b | ||
|
|
85ef72585d | ||
|
|
c3a5eb2269 | ||
|
|
94379769e3 | ||
|
|
7d768e7c1d | ||
|
|
ea5b094a93 | ||
|
|
555b0ab0da | ||
|
|
c2dd40c161 | ||
|
|
3260dcb121 | ||
|
|
55a214a45e | ||
|
|
35b4d1de28 | ||
|
|
d48068d6e1 | ||
|
|
ef786f9245 | ||
|
|
0062703bea | ||
|
|
c55fde7080 | ||
|
|
9ee66b8918 | ||
|
|
fc8a433cee | ||
|
|
98fe57baac | ||
|
|
fdb90ca179 | ||
|
|
1b106f9133 | ||
|
|
4af04b23ec | ||
|
|
0a81a98b7d | ||
|
|
8bba863935 | ||
|
|
7e6af46af0 | ||
|
|
693732c4a7 | ||
|
|
435387c72c | ||
|
|
ecee21b5e5 | ||
|
|
7c0e865960 | ||
|
|
c65aed3f9d | ||
|
|
1faa76ee4e | ||
|
|
7365917244 | ||
|
|
d2af460d81 | ||
|
|
14e42df8f2 | ||
|
|
17354fcc99 | ||
|
|
769ff110f3 | ||
|
|
3dcbc13742 | ||
|
|
7f31fe678f | ||
|
|
6e9d9bf86d | ||
|
|
9e749fa03d | ||
|
|
436c588793 | ||
|
|
fdf89e0e16 | ||
|
|
2b8fd90021 | ||
|
|
17d845b3d2 |
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -20,7 +20,7 @@ The GitHub issue tracker is for bug reports and feature requests.
|
||||
General support can be found at the following locations:
|
||||
|
||||
- Docker Support Forums - https://forums.docker.com
|
||||
- Docker Community Slack - https://dockr.ly/community
|
||||
- Docker Community Slack - https://dockr.ly/slack
|
||||
- Post a question on StackOverflow, using the Docker tag
|
||||
|
||||
---------------------------------------------------
|
||||
|
||||
5
.github/workflows/artifacts.yml
vendored
5
.github/workflows/artifacts.yml
vendored
@@ -7,10 +7,10 @@ jobs:
|
||||
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/generate-artifacts')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.16
|
||||
- name: Set up Go 1.17
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16
|
||||
go-version: 1.17
|
||||
id: go
|
||||
|
||||
- name: Checkout code into the Go module directory
|
||||
@@ -55,4 +55,3 @@ jobs:
|
||||
body: |
|
||||
This PR can be tested using [binaries](https://github.com/docker/compose-cli/actions/runs/${{ github.run_id }}).
|
||||
reactions: eyes
|
||||
|
||||
|
||||
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@@ -13,10 +13,10 @@ jobs:
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
- name: Set up Go 1.16
|
||||
- name: Set up Go 1.17
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16
|
||||
go-version: 1.17
|
||||
id: go
|
||||
|
||||
- name: Checkout code into the Go module directory
|
||||
@@ -40,10 +40,10 @@ jobs:
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
- name: Set up Go 1.16
|
||||
- name: Set up Go 1.17
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16
|
||||
go-version: 1.17
|
||||
id: go
|
||||
|
||||
- name: Checkout code into the Go module directory
|
||||
@@ -65,10 +65,10 @@ jobs:
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
- name: Set up Go 1.16
|
||||
- name: Set up Go 1.17
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16
|
||||
go-version: 1.17
|
||||
id: go
|
||||
|
||||
- name: Set up gosum
|
||||
|
||||
9
.github/workflows/release.yaml
vendored
9
.github/workflows/release.yaml
vendored
@@ -4,17 +4,17 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release Tag'
|
||||
description: "Release Tag"
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
upload-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.16
|
||||
- name: Set up Go 1.17
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16
|
||||
go-version: 1.17
|
||||
id: go
|
||||
|
||||
- name: Setup docker CLI
|
||||
@@ -35,6 +35,9 @@ jobs:
|
||||
- name: Build
|
||||
run: make GIT_TAG=${{ github.event.inputs.tag }} -f builder.Makefile cross
|
||||
|
||||
- name: Compute checksums
|
||||
run: cd bin; for f in *; do shasum --algorithm 256 $f > $f.sha256; done
|
||||
|
||||
- name: License
|
||||
run: cp packaging/* bin/
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
ARG GO_VERSION=1.16-alpine
|
||||
ARG GO_VERSION=1.17-alpine
|
||||
ARG GOLANGCI_LINT_VERSION=v1.40.1-alpine
|
||||
ARG PROTOC_GEN_GO_VERSION=v1.4.3
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
people = [
|
||||
"rumpl",
|
||||
"gtardif",
|
||||
"ndeloof"
|
||||
"chris-crone"
|
||||
"ndeloof",
|
||||
"chris-crone",
|
||||
"ulyssessouza"
|
||||
]
|
||||
|
||||
|
||||
19
README.md
19
README.md
@@ -9,7 +9,13 @@ defined using the [Compose file format](https://compose-spec.io).
|
||||
A Compose file is used to define how the one or more containers that make up
|
||||
your application are configured.
|
||||
Once you have a Compose file, you can create and start your application with a
|
||||
single command: `docker-compose up`.
|
||||
single command: `docker compose up`.
|
||||
|
||||
# About update and backward compatibility
|
||||
|
||||
Docker Compose V2 is a major version bump release of Docker Compose. It has been completely rewritten from scratch in Golang (V1 was in Python). The installation instructions for Compose V2 differ from V1. V2 is not a standalone binary anymore, and installation scripts will have to be adjusted. Some commands are different.
|
||||
|
||||
For a smooth transition from legacy docker-compose 1.xx, please consider installing [compose-switch](https://github.com/docker/compose-switch) to translate `docker-compose ...` commands into Compose V2's `docker compose .... `. Also check V2's `--compatibility` flag.
|
||||
|
||||
# Where to get Docker Compose
|
||||
|
||||
@@ -24,9 +30,16 @@ for Windows and macOS.
|
||||
You can download Docker Compose binaries from the
|
||||
[release page](https://github.com/docker/compose/releases) on this repository.
|
||||
|
||||
Copy the relevant binary for your OS under `$HOME/.docker/cli-plugins/docker-compose`
|
||||
Rename the relevant binary for your OS to `docker-compose` and copy it to `$HOME/.docker/cli-plugins`
|
||||
|
||||
Or copy it into one of these folders for installing it system-wide:
|
||||
|
||||
* `/usr/local/lib/docker/cli-plugins` OR `/usr/local/libexec/docker/cli-plugins`
|
||||
* `/usr/lib/docker/cli-plugins` OR `/usr/libexec/docker/cli-plugins`
|
||||
|
||||
(might require to make the downloaded file executable with `chmod +x`)
|
||||
|
||||
|
||||
Quick Start
|
||||
-----------
|
||||
|
||||
@@ -35,7 +48,7 @@ Using Docker Compose is basically a three-step process:
|
||||
reproduced anywhere.
|
||||
2. Define the services that make up your app in `docker-compose.yml` so
|
||||
they can be run together in an isolated environment.
|
||||
3. Lastly, run `docker-compose up` and Compose will start and run your entire
|
||||
3. Lastly, run `docker compose up` and Compose will start and run your entire
|
||||
app.
|
||||
|
||||
A Compose file looks like this:
|
||||
|
||||
@@ -46,14 +46,14 @@ compose-plugin:
|
||||
|
||||
.PHONY: cross
|
||||
cross:
|
||||
GOOS=linux GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-amd64 ./cmd
|
||||
GOOS=linux GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-arm64 ./cmd
|
||||
GOOS=linux GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-x86_64 ./cmd
|
||||
GOOS=linux GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-aarch64 ./cmd
|
||||
GOOS=linux GOARM=6 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-armv6 ./cmd
|
||||
GOOS=linux GOARM=7 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-armv7 ./cmd
|
||||
GOOS=linux GOARCH=s390x $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-s390x ./cmd
|
||||
GOOS=darwin GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-darwin-amd64 ./cmd
|
||||
GOOS=darwin GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-darwin-arm64 ./cmd
|
||||
GOOS=windows GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-windows-amd64.exe ./cmd
|
||||
GOOS=darwin GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-darwin-x86_64 ./cmd
|
||||
GOOS=darwin GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-darwin-aarch64 ./cmd
|
||||
GOOS=windows GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-windows-x86_64.exe ./cmd
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
|
||||
@@ -20,9 +20,12 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/cli"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
buildx "github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@@ -39,6 +42,13 @@ type buildOptions struct {
|
||||
memory string
|
||||
}
|
||||
|
||||
var printerModes = []string{
|
||||
buildx.PrinterModeAuto,
|
||||
buildx.PrinterModeTty,
|
||||
buildx.PrinterModePlain,
|
||||
buildx.PrinterModeQuiet,
|
||||
}
|
||||
|
||||
func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := buildOptions{
|
||||
projectOptions: p,
|
||||
@@ -51,12 +61,16 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
fmt.Println("WARNING --memory is ignored as not supported in buildkit.")
|
||||
}
|
||||
if opts.quiet {
|
||||
opts.progress = buildx.PrinterModeQuiet
|
||||
devnull, err := os.Open(os.DevNull)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Stdout = devnull
|
||||
}
|
||||
if !utils.StringContains(printerModes, opts.progress) {
|
||||
return fmt.Errorf("unsupported --progress value %q", opts.progress)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
@@ -66,7 +80,7 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Don't print anything to STDOUT")
|
||||
cmd.Flags().BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image.")
|
||||
cmd.Flags().StringVar(&opts.progress, "progress", "auto", `Set type of progress output ("auto", "plain", "noTty")`)
|
||||
cmd.Flags().StringVar(&opts.progress, "progress", buildx.PrinterModeAuto, fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
|
||||
cmd.Flags().StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables for services.")
|
||||
cmd.Flags().Bool("parallel", true, "Build images in parallel. DEPRECATED")
|
||||
cmd.Flags().MarkHidden("parallel") //nolint:errcheck
|
||||
|
||||
@@ -25,15 +25,14 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/compose-spec/compose-go/cli"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
dockercli "github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/morikuni/aec"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
@@ -106,7 +105,7 @@ type ProjectFunc func(ctx context.Context, project *types.Project) error
|
||||
// ProjectServicesFunc does stuff within a types.Project and a selection of services
|
||||
type ProjectServicesFunc func(ctx context.Context, project *types.Project, services []string) error
|
||||
|
||||
// WithServices creates a cobra run command from a ProjectFunc based on configured project options and selected services
|
||||
// WithProject creates a cobra run command from a ProjectFunc based on configured project options and selected services
|
||||
func (o *projectOptions) WithProject(fn ProjectFunc) func(cmd *cobra.Command, args []string) error {
|
||||
return o.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
|
||||
return fn(ctx, project)
|
||||
@@ -177,6 +176,10 @@ func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn
|
||||
return nil, compose.WrapComposeError(err)
|
||||
}
|
||||
|
||||
if o.Compatibility || project.Environment["COMPOSE_COMPATIBILITY"] == "true" {
|
||||
compose.Separator = "_"
|
||||
}
|
||||
|
||||
if len(services) > 0 {
|
||||
s, err := project.GetServices(services...)
|
||||
if err != nil {
|
||||
@@ -200,15 +203,22 @@ func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn
|
||||
func (o *projectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.ProjectOptions, error) {
|
||||
return cli.NewProjectOptions(o.ConfigPaths,
|
||||
append(po,
|
||||
cli.WithWorkingDirectory(o.ProjectDir),
|
||||
cli.WithEnvFile(o.EnvFile),
|
||||
cli.WithDotEnv,
|
||||
cli.WithOsEnv,
|
||||
cli.WithWorkingDirectory(o.ProjectDir),
|
||||
cli.WithConfigFileEnv,
|
||||
cli.WithDefaultConfigPath,
|
||||
cli.WithName(o.ProjectName))...)
|
||||
}
|
||||
|
||||
const pluginName = "compose"
|
||||
|
||||
// RunningAsStandalone detects when running as a standalone program
|
||||
func RunningAsStandalone() bool {
|
||||
return len(os.Args) < 2 || os.Args[1] != manager.MetadataSubcommandName && os.Args[1] != pluginName
|
||||
}
|
||||
|
||||
// RootCommand returns the compose command with its child commands
|
||||
func RootCommand(backend api.Service) *cobra.Command {
|
||||
opts := projectOptions{}
|
||||
@@ -216,16 +226,20 @@ func RootCommand(backend api.Service) *cobra.Command {
|
||||
ansi string
|
||||
noAnsi bool
|
||||
verbose bool
|
||||
version bool
|
||||
)
|
||||
command := &cobra.Command{
|
||||
Short: "Docker Compose",
|
||||
Use: "compose",
|
||||
Use: pluginName,
|
||||
TraverseChildren: true,
|
||||
// By default (no Run/RunE in parent command) for typos in subcommands, cobra displays the help of parent command but exit(0) !
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return cmd.Help()
|
||||
}
|
||||
if version {
|
||||
return versionCommand().Execute()
|
||||
}
|
||||
_ = cmd.Help()
|
||||
return dockercli.StatusError{
|
||||
StatusCode: compose.CommandSyntaxFailure.ExitCode,
|
||||
@@ -234,11 +248,13 @@ func RootCommand(backend api.Service) *cobra.Command {
|
||||
},
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
parent := cmd.Root()
|
||||
parentPrerun := parent.PersistentPreRunE
|
||||
if parentPrerun != nil {
|
||||
err := parentPrerun(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
if parent != nil {
|
||||
parentPrerun := parent.PersistentPreRunE
|
||||
if parentPrerun != nil {
|
||||
err := parentPrerun(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if noAnsi {
|
||||
@@ -259,9 +275,6 @@ func RootCommand(backend api.Service) *cobra.Command {
|
||||
opts.ProjectDir = opts.WorkDir
|
||||
fmt.Fprint(os.Stderr, aec.Apply("option '--workdir' is DEPRECATED at root level! Please use '--project-directory' instead.\n", aec.RedF))
|
||||
}
|
||||
if opts.Compatibility || os.Getenv("COMPOSE_COMPATIBILITY") == "true" {
|
||||
compose.Separator = "_"
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -296,6 +309,8 @@ func RootCommand(backend api.Service) *cobra.Command {
|
||||
command.Flags().SetInterspersed(false)
|
||||
opts.addProjectFlags(command.Flags())
|
||||
command.Flags().StringVar(&ansi, "ansi", "auto", `Control when to print ANSI control characters ("never"|"always"|"auto")`)
|
||||
command.Flags().BoolVarP(&version, "version", "v", false, "Show the Docker Compose version information")
|
||||
command.Flags().MarkHidden("version") //nolint:errcheck
|
||||
command.Flags().BoolVar(&noAnsi, "no-ansi", false, `Do not print ANSI control characters (DEPRECATED)`)
|
||||
command.Flags().MarkHidden("no-ansi") //nolint:errcheck
|
||||
command.Flags().BoolVar(&verbose, "verbose", false, "Show more output")
|
||||
|
||||
@@ -44,9 +44,11 @@ type convertOptions struct {
|
||||
quiet bool
|
||||
resolveImageDigests bool
|
||||
noInterpolate bool
|
||||
noNormalize bool
|
||||
services bool
|
||||
volumes bool
|
||||
profiles bool
|
||||
images bool
|
||||
hash string
|
||||
}
|
||||
|
||||
@@ -66,6 +68,9 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
os.Stdout = devnull
|
||||
}
|
||||
if p.Compatibility {
|
||||
opts.noNormalize = true
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
@@ -81,6 +86,9 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
if opts.profiles {
|
||||
return runProfiles(opts, args)
|
||||
}
|
||||
if opts.images {
|
||||
return runConfigImages(opts, args)
|
||||
}
|
||||
|
||||
return runConvert(ctx, backend, opts, args)
|
||||
}),
|
||||
@@ -91,10 +99,12 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests.")
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only validate the configuration, don't print anything.")
|
||||
flags.BoolVar(&opts.noInterpolate, "no-interpolate", false, "Don't interpolate environment variables.")
|
||||
flags.BoolVar(&opts.noNormalize, "no-normalize", false, "Don't normalize compose model.")
|
||||
|
||||
flags.BoolVar(&opts.services, "services", false, "Print the service names, one per line.")
|
||||
flags.BoolVar(&opts.volumes, "volumes", false, "Print the volume names, one per line.")
|
||||
flags.BoolVar(&opts.profiles, "profiles", false, "Print the profile names, one per line.")
|
||||
flags.BoolVar(&opts.images, "images", false, "Print the image names, one per line.")
|
||||
flags.StringVar(&opts.hash, "hash", "", "Print the service config hash, one per line.")
|
||||
flags.StringVarP(&opts.Output, "output", "o", "", "Save to file (default to stdout)")
|
||||
|
||||
@@ -103,7 +113,10 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
|
||||
func runConvert(ctx context.Context, backend api.Service, opts convertOptions, services []string) error {
|
||||
var json []byte
|
||||
project, err := opts.toProject(services, cli.WithInterpolation(!opts.noInterpolate), cli.WithResolvedPaths(false))
|
||||
project, err := opts.toProject(services,
|
||||
cli.WithInterpolation(!opts.noInterpolate),
|
||||
cli.WithResolvedPaths(true),
|
||||
cli.WithNormalization(!opts.noNormalize))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -207,3 +220,18 @@ func runProfiles(opts convertOptions, services []string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runConfigImages(opts convertOptions, services []string) error {
|
||||
project, err := opts.toProject(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range project.Services {
|
||||
if s.Image != "" {
|
||||
fmt.Println(s.Image)
|
||||
} else {
|
||||
fmt.Printf("%s_%s\n", project.Name, s.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -43,10 +43,8 @@ func downCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
downCmd := &cobra.Command{
|
||||
Use: "down",
|
||||
Short: "Stop and remove containers, networks",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||
opts.timeChanged = cmd.Flags().Changed("timeout")
|
||||
},
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
if opts.images != "" {
|
||||
if opts.images != "all" && opts.images != "local" {
|
||||
return fmt.Errorf("invalid value for --rmi: %q", opts.images)
|
||||
|
||||
@@ -21,11 +21,12 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/containerd/console"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/compose"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type execOpts struct {
|
||||
@@ -77,15 +78,22 @@ func execCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runExec(ctx context.Context, backend api.Service, opts execOpts) error {
|
||||
project, err := opts.toProjectName()
|
||||
projectName, err := opts.toProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
projectOptions, err := opts.composeOptions.toProjectOptions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lookupFn := func(k string) (string, bool) {
|
||||
v, ok := projectOptions.Environment[k]
|
||||
return v, ok
|
||||
}
|
||||
execOpts := api.RunOptions{
|
||||
Service: opts.service,
|
||||
Command: opts.command,
|
||||
Environment: opts.environment,
|
||||
Environment: compose.ToMobyEnv(types.NewMappingWithEquals(opts.environment).Resolve(lookupFn)),
|
||||
Tty: !opts.noTty,
|
||||
User: opts.user,
|
||||
Privileged: opts.privileged,
|
||||
@@ -113,7 +121,7 @@ func runExec(ctx context.Context, backend api.Service, opts execOpts) error {
|
||||
execOpts.Stdout = con
|
||||
execOpts.Stderr = con
|
||||
}
|
||||
exitCode, err := backend.Exec(ctx, project, execOpts)
|
||||
exitCode, err := backend.Exec(ctx, projectName, execOpts)
|
||||
if exitCode != 0 {
|
||||
errMsg := ""
|
||||
if err != nil {
|
||||
|
||||
@@ -34,7 +34,7 @@ func pauseCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "pause [SERVICE...]",
|
||||
Short: "pause services",
|
||||
Short: "Pause services",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runPause(ctx, backend, opts, args)
|
||||
}),
|
||||
@@ -64,7 +64,7 @@ func unpauseCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "unpause [SERVICE...]",
|
||||
Short: "unpause services",
|
||||
Short: "Unpause services",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runUnPause(ctx, backend, opts, args)
|
||||
}),
|
||||
|
||||
@@ -26,13 +26,13 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
|
||||
formatter2 "github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
type psOptions struct {
|
||||
@@ -42,7 +42,7 @@ type psOptions struct {
|
||||
Quiet bool
|
||||
Services bool
|
||||
Filter string
|
||||
Status string
|
||||
Status []string
|
||||
}
|
||||
|
||||
func (p *psOptions) parseFilter() error {
|
||||
@@ -55,7 +55,7 @@ func (p *psOptions) parseFilter() error {
|
||||
}
|
||||
switch parts[0] {
|
||||
case "status":
|
||||
p.Status = parts[1]
|
||||
p.Status = append(p.Status, parts[1])
|
||||
case "source":
|
||||
return api.ErrNotImplemented
|
||||
default:
|
||||
@@ -82,7 +82,7 @@ func psCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
flags := psCmd.Flags()
|
||||
flags.StringVar(&opts.Format, "format", "pretty", "Format the output. Values: [pretty | json]")
|
||||
flags.StringVar(&opts.Filter, "filter", "", "Filter services by a property. Deprecated, use --status instead")
|
||||
flags.StringVar(&opts.Status, "status", "", "Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]")
|
||||
flags.StringArrayVar(&opts.Status, "status", []string{}, "Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]")
|
||||
flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
|
||||
flags.BoolVar(&opts.Services, "services", false, "Display services")
|
||||
flags.BoolVarP(&opts.All, "all", "a", false, "Show all stopped containers (including those created by the run command)")
|
||||
@@ -103,17 +103,6 @@ func runPs(ctx context.Context, backend api.Service, services []string, opts psO
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.Services {
|
||||
services := []string{}
|
||||
for _, s := range containers {
|
||||
if !utils.StringContains(services, s.Service) {
|
||||
services = append(services, s.Service)
|
||||
}
|
||||
}
|
||||
fmt.Println(strings.Join(services, "\n"))
|
||||
return nil
|
||||
}
|
||||
|
||||
SERVICES:
|
||||
for _, s := range services {
|
||||
for _, c := range containers {
|
||||
@@ -124,7 +113,7 @@ SERVICES:
|
||||
return fmt.Errorf("no such service: %s", s)
|
||||
}
|
||||
|
||||
if opts.Status != "" {
|
||||
if len(opts.Status) != 0 {
|
||||
containers = filterByStatus(containers, opts.Status)
|
||||
}
|
||||
|
||||
@@ -139,6 +128,17 @@ SERVICES:
|
||||
return nil
|
||||
}
|
||||
|
||||
if opts.Services {
|
||||
services := []string{}
|
||||
for _, s := range containers {
|
||||
if !utils.StringContains(services, s.Service) {
|
||||
services = append(services, s.Service)
|
||||
}
|
||||
}
|
||||
fmt.Println(strings.Join(services, "\n"))
|
||||
return nil
|
||||
}
|
||||
|
||||
return formatter.Print(containers, opts.Format, os.Stdout,
|
||||
writter(containers),
|
||||
"NAME", "COMMAND", "SERVICE", "STATUS", "PORTS")
|
||||
@@ -160,22 +160,25 @@ func writter(containers []api.ContainerSummary) func(w io.Writer) {
|
||||
}
|
||||
}
|
||||
|
||||
func filterByStatus(containers []api.ContainerSummary, status string) []api.ContainerSummary {
|
||||
hasContainerWithState := map[string]struct{}{}
|
||||
for _, c := range containers {
|
||||
if c.State == status {
|
||||
hasContainerWithState[c.Service] = struct{}{}
|
||||
}
|
||||
}
|
||||
func filterByStatus(containers []api.ContainerSummary, statuses []string) []api.ContainerSummary {
|
||||
var filtered []api.ContainerSummary
|
||||
for _, c := range containers {
|
||||
if _, ok := hasContainerWithState[c.Service]; ok {
|
||||
if hasStatus(c, statuses) {
|
||||
filtered = append(filtered, c)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func hasStatus(c api.ContainerSummary, statuses []string) bool {
|
||||
for _, status := range statuses {
|
||||
if c.State == status {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type portRange struct {
|
||||
pStart int
|
||||
pEnd int
|
||||
|
||||
@@ -54,6 +54,7 @@ type runOptions struct {
|
||||
servicePorts bool
|
||||
name string
|
||||
noDeps bool
|
||||
quietPull bool
|
||||
}
|
||||
|
||||
func (opts runOptions) apply(project *types.Project) error {
|
||||
@@ -153,6 +154,7 @@ func runCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
flags.StringArrayVarP(&opts.publish, "publish", "p", []string{}, "Publish a container's port(s) to the host.")
|
||||
flags.BoolVar(&opts.useAliases, "use-aliases", false, "Use the service's network useAliases in the network(s) the container connects to.")
|
||||
flags.BoolVar(&opts.servicePorts, "service-ports", false, "Run command with the service's ports enabled and mapped to the host.")
|
||||
flags.BoolVar(&opts.quietPull, "quiet-pull", false, "Pull without printing progress information.")
|
||||
|
||||
flags.SetNormalizeFunc(normalizeRunFlags)
|
||||
flags.SetInterspersed(false)
|
||||
@@ -215,6 +217,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
UseNetworkAliases: opts.useAliases,
|
||||
NoDeps: opts.noDeps,
|
||||
Index: 0,
|
||||
QuietPull: opts.quietPull,
|
||||
}
|
||||
exitCode, err := backend.RunOneOffContainer(ctx, project, runOpts)
|
||||
if exitCode != 0 {
|
||||
|
||||
@@ -40,7 +40,6 @@ type composeOptions struct {
|
||||
type upOptions struct {
|
||||
*composeOptions
|
||||
Detach bool
|
||||
Environment []string
|
||||
noStart bool
|
||||
noDeps bool
|
||||
cascadeStop bool
|
||||
@@ -50,6 +49,7 @@ type upOptions struct {
|
||||
noPrefix bool
|
||||
attachDependencies bool
|
||||
attach []string
|
||||
wait bool
|
||||
}
|
||||
|
||||
func (opts upOptions) apply(project *types.Project, services []string) error {
|
||||
@@ -98,26 +98,9 @@ func upCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
upCmd := &cobra.Command{
|
||||
Use: "up [SERVICE...]",
|
||||
Short: "Create and start containers",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||
create.timeChanged = cmd.Flags().Changed("timeout")
|
||||
},
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
if up.exitCodeFrom != "" {
|
||||
up.cascadeStop = true
|
||||
}
|
||||
if create.Build && create.noBuild {
|
||||
return fmt.Errorf("--build and --no-build are incompatible")
|
||||
}
|
||||
if up.Detach && (up.attachDependencies || up.cascadeStop || len(up.attach) > 0) {
|
||||
return fmt.Errorf("--detach cannot be combined with --abort-on-container-exit, --attach or --attach-dependencies")
|
||||
}
|
||||
if create.forceRecreate && create.noRecreate {
|
||||
return fmt.Errorf("--force-recreate and --no-recreate are incompatible")
|
||||
}
|
||||
if create.recreateDeps && create.noRecreate {
|
||||
return fmt.Errorf("--always-recreate-deps and --no-recreate are incompatible")
|
||||
}
|
||||
return nil
|
||||
return validateFlags(&up, &create)
|
||||
}),
|
||||
RunE: p.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
|
||||
ignore := project.Environment["COMPOSE_IGNORE_ORPHANS"]
|
||||
@@ -130,7 +113,6 @@ func upCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
flags := upCmd.Flags()
|
||||
flags.StringArrayVarP(&up.Environment, "environment", "e", []string{}, "Environment variables")
|
||||
flags.BoolVarP(&up.Detach, "detach", "d", false, "Detached mode: Run containers in the background")
|
||||
flags.BoolVar(&create.Build, "build", false, "Build images before starting containers.")
|
||||
flags.BoolVar(&create.noBuild, "no-build", false, "Don't build an image, even if it's missing.")
|
||||
@@ -150,10 +132,36 @@ func upCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
flags.BoolVar(&up.attachDependencies, "attach-dependencies", false, "Attach to dependent containers.")
|
||||
flags.BoolVar(&create.quietPull, "quiet-pull", false, "Pull without printing progress information.")
|
||||
flags.StringArrayVar(&up.attach, "attach", []string{}, "Attach to service output.")
|
||||
flags.BoolVar(&up.wait, "wait", false, "Wait for services to be running|healthy. Implies detached mode.")
|
||||
|
||||
return upCmd
|
||||
}
|
||||
|
||||
func validateFlags(up *upOptions, create *createOptions) error {
|
||||
if up.exitCodeFrom != "" {
|
||||
up.cascadeStop = true
|
||||
}
|
||||
if up.wait {
|
||||
if up.attachDependencies || up.cascadeStop || len(up.attach) > 0 {
|
||||
return fmt.Errorf("--wait cannot be combined with --abort-on-container-exit, --attach or --attach-dependencies")
|
||||
}
|
||||
up.Detach = true
|
||||
}
|
||||
if create.Build && create.noBuild {
|
||||
return fmt.Errorf("--build and --no-build are incompatible")
|
||||
}
|
||||
if up.Detach && (up.attachDependencies || up.cascadeStop || len(up.attach) > 0) {
|
||||
return fmt.Errorf("--detach cannot be combined with --abort-on-container-exit, --attach or --attach-dependencies")
|
||||
}
|
||||
if create.forceRecreate && create.noRecreate {
|
||||
return fmt.Errorf("--force-recreate and --no-recreate are incompatible")
|
||||
}
|
||||
if create.recreateDeps && create.noRecreate {
|
||||
return fmt.Errorf("--always-recreate-deps and --no-recreate are incompatible")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runUp(ctx context.Context, backend api.Service, createOptions createOptions, upOptions upOptions, project *types.Project, services []string) error {
|
||||
if len(project.Services) == 0 {
|
||||
return fmt.Errorf("no service selected")
|
||||
@@ -201,6 +209,7 @@ func runUp(ctx context.Context, backend api.Service, createOptions createOptions
|
||||
AttachTo: attachTo,
|
||||
ExitCodeFrom: upOptions.exitCodeFrom,
|
||||
CascadeStop: upOptions.cascadeStop,
|
||||
Wait: upOptions.wait,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -34,10 +34,9 @@ type versionOptions struct {
|
||||
func versionCommand() *cobra.Command {
|
||||
opts := versionOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Show the Docker Compose version information",
|
||||
Args: cobra.MaximumNArgs(0),
|
||||
Hidden: true,
|
||||
Use: "version",
|
||||
Short: "Show the Docker Compose version information",
|
||||
Args: cobra.MaximumNArgs(0),
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
runVersion(opts)
|
||||
return nil
|
||||
|
||||
12
cmd/main.go
12
cmd/main.go
@@ -17,10 +17,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
dockercli "github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli-plugins/plugin"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose-switch/redirect"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
commands "github.com/docker/compose/v2/cmd/compose"
|
||||
@@ -34,7 +37,7 @@ func init() {
|
||||
"To provide feedback or request new features please open issues at https://github.com/docker/compose"
|
||||
}
|
||||
|
||||
func main() {
|
||||
func pluginMain() {
|
||||
plugin.Run(func(dockerCli command.Cli) *cobra.Command {
|
||||
lazyInit := api.NewServiceProxy()
|
||||
cmd := commands.RootCommand(lazyInit)
|
||||
@@ -63,3 +66,10 @@ func main() {
|
||||
Version: internal.Version,
|
||||
})
|
||||
}
|
||||
|
||||
func main() {
|
||||
if commands.RunningAsStandalone() {
|
||||
os.Args = append([]string{"docker"}, redirect.Convert(os.Args[1:])...)
|
||||
}
|
||||
pluginMain()
|
||||
}
|
||||
|
||||
100
go.mod
100
go.mod
@@ -1,27 +1,25 @@
|
||||
module github.com/docker/compose/v2
|
||||
|
||||
go 1.16
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.2.3
|
||||
github.com/buger/goterm v1.0.0
|
||||
github.com/cnabio/cnab-to-oci v0.3.1-beta1
|
||||
github.com/compose-spec/compose-go v1.0.1
|
||||
github.com/compose-spec/compose-go v1.0.8
|
||||
github.com/containerd/console v1.0.2
|
||||
github.com/containerd/containerd v1.5.4
|
||||
github.com/containerd/containerd v1.5.8
|
||||
github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e
|
||||
github.com/docker/buildx v0.5.2-0.20210422185057-908a856079fc
|
||||
github.com/docker/cli v20.10.7+incompatible
|
||||
github.com/docker/cli-docs-tool v0.1.1
|
||||
github.com/docker/compose-switch v1.0.2
|
||||
github.com/docker/docker v20.10.7+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/docker/go-units v0.4.0
|
||||
github.com/gofrs/flock v0.8.0 // indirect
|
||||
github.com/golang/mock v1.5.0
|
||||
github.com/hashicorp/go-multierror v1.1.0
|
||||
github.com/hashicorp/go-version v1.3.0
|
||||
github.com/kr/pty v1.1.8 // indirect
|
||||
github.com/mattn/go-colorable v0.1.6 // indirect
|
||||
github.com/mattn/go-isatty v0.0.12
|
||||
github.com/mattn/go-shellwords v1.0.12
|
||||
github.com/moby/buildkit v0.8.2-0.20210401015549-df49b648c8bf
|
||||
@@ -31,16 +29,102 @@ require (
|
||||
github.com/opencontainers/image-spec v1.0.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.2.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
gotest.tools v2.2.0+incompatible
|
||||
gotest.tools/v3 v3.0.3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.4.17 // indirect
|
||||
github.com/Microsoft/hcsshim v0.8.23 // indirect
|
||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
||||
github.com/cnabio/cnab-go v0.10.0-beta1 // indirect
|
||||
github.com/compose-spec/godotenv v1.1.1 // indirect
|
||||
github.com/containerd/cgroups v1.0.1 // indirect
|
||||
github.com/containerd/continuity v0.1.0 // indirect
|
||||
github.com/containerd/typeurl v1.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4-0.20210125172408-38bea2ce277a // indirect
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/fvbommel/sortorder v1.0.1 // indirect
|
||||
github.com/go-logr/logr v0.4.0 // indirect
|
||||
github.com/gofrs/flock v0.8.0 // indirect
|
||||
github.com/gogo/googleapis v1.4.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.5 // indirect
|
||||
github.com/google/gofuzz v1.1.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea // indirect
|
||||
github.com/json-iterator/go v1.1.11 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.11.13 // indirect
|
||||
github.com/kr/pty v1.1.8 // indirect
|
||||
github.com/mattn/go-colorable v0.1.6 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/miekg/pkcs11 v1.0.3 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.2 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/sys/mount v0.2.0 // indirect
|
||||
github.com/moby/sys/mountinfo v0.4.1 // indirect
|
||||
github.com/moby/sys/symlink v0.1.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/opencontainers/runc v1.0.2 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.7.1 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.10.0 // indirect
|
||||
github.com/prometheus/procfs v0.6.0 // indirect
|
||||
github.com/qri-io/jsonpointer v0.1.0 // indirect
|
||||
github.com/qri-io/jsonschema v0.1.1 // indirect
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/theupdateframework/notary v0.6.1 // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85 // indirect
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // 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
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 // indirect
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect
|
||||
golang.org/x/text v0.3.5 // indirect
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect
|
||||
google.golang.org/grpc v1.38.0 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
k8s.io/apimachinery v0.21.0 // indirect
|
||||
k8s.io/client-go v0.21.0 // indirect
|
||||
k8s.io/klog/v2 v2.8.0 // indirect
|
||||
k8s.io/utils v0.0.0-20201110183641-67b214c5f920 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.0 // indirect
|
||||
sigs.k8s.io/yaml v1.2.0 // indirect
|
||||
)
|
||||
|
||||
// (for buildx)
|
||||
|
||||
47
go.sum
47
go.sum
@@ -91,8 +91,8 @@ github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg3
|
||||
github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg=
|
||||
github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00=
|
||||
github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600=
|
||||
github.com/Microsoft/hcsshim v0.8.18 h1:cYnKADiM1869gvBpos3YCteeT6sZLB48lB5dmMMs8Tg=
|
||||
github.com/Microsoft/hcsshim v0.8.18/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4=
|
||||
github.com/Microsoft/hcsshim v0.8.23 h1:47MSwtKGXet80aIn+7h4YI6fwPmwIghAnsx2aOUrG2M=
|
||||
github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg=
|
||||
github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU=
|
||||
github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
@@ -139,6 +139,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
|
||||
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
|
||||
github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
|
||||
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
|
||||
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
@@ -161,6 +163,7 @@ github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywR
|
||||
github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
@@ -169,6 +172,7 @@ github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tj
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
|
||||
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
@@ -176,6 +180,7 @@ github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmE
|
||||
github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc=
|
||||
github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=
|
||||
github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
|
||||
github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a/go.mod h1:rzgs2ZOiguV6/NpiDgADjRLPNyZlApIWxKpkT+X8SdY=
|
||||
github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
|
||||
@@ -192,10 +197,10 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||
github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||
github.com/compose-spec/compose-go v1.0.1 h1:yXTL3UEefSeGIFCYam7D4acrC1Z4sBlTLtjHc/MDQ2s=
|
||||
github.com/compose-spec/compose-go v1.0.1/go.mod h1:JJXU4DzTtNGWocymZcRWMpRRWl2NbmTZEA977BQqr5w=
|
||||
github.com/compose-spec/godotenv v1.0.0 h1:TV24JYhh5GCC1G14npQVhCtxeoiwd0NcT0VdwcCQyXU=
|
||||
github.com/compose-spec/godotenv v1.0.0/go.mod h1:zF/3BOa18Z24tts5qnO/E9YURQanJTBUf7nlcCTNsyc=
|
||||
github.com/compose-spec/compose-go v1.0.8 h1:fgT7mYYu5Sp37i2lUIAAvwJpkAHk6dP5ITHy/LlutUk=
|
||||
github.com/compose-spec/compose-go v1.0.8/go.mod h1:REnCbBugoIdHB7S1sfkN/aJ7AJpNApGNjNiVjA9L8x4=
|
||||
github.com/compose-spec/godotenv v1.1.1 h1:lp+WpAInnw06YN9sV/XLUOV/9z4C+6wjJdWlrdVac7o=
|
||||
github.com/compose-spec/godotenv v1.1.1/go.mod h1:zF/3BOa18Z24tts5qnO/E9YURQanJTBUf7nlcCTNsyc=
|
||||
github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=
|
||||
github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=
|
||||
github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=
|
||||
@@ -227,14 +232,14 @@ github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMX
|
||||
github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/containerd v1.5.0-beta.0.0.20210122062454-5a66c2ae5cec/go.mod h1:p9z+oqCID32tZ7LKgej316N9pJf1iIkQ/7nK1VvEFZE=
|
||||
github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ=
|
||||
github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU=
|
||||
github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI=
|
||||
github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s=
|
||||
github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g=
|
||||
github.com/containerd/containerd v1.5.4 h1:uPF0og3ByFzDnaStfiQj3fVGTEtaSNyU+bW7GR/nqGA=
|
||||
github.com/containerd/containerd v1.5.4/go.mod h1:sx18RgvW6ABJ4iYUw7Q5x7bgFOAB9B6G7+yO0XBc4zw=
|
||||
github.com/containerd/containerd v1.5.8 h1:NmkCC1/QxyZFBny8JogwLpOy2f+VEbO/f6bV2Mqtwuw=
|
||||
github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s=
|
||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||
github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||
@@ -273,8 +278,9 @@ github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDG
|
||||
github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
|
||||
github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8=
|
||||
github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
|
||||
github.com/containerd/ttrpc v1.0.2 h1:2/O3oTZN36q2xRolk0a2WWGgh7/Vf/liElg5hFYLX9U=
|
||||
github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
|
||||
github.com/containerd/ttrpc v1.1.0 h1:GbtyLRxb0gOLR0TYQWt3O6B0NvT8tMdorEHqIQo/lWI=
|
||||
github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ=
|
||||
github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
|
||||
github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk=
|
||||
github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg=
|
||||
@@ -347,6 +353,8 @@ github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHv
|
||||
github.com/docker/cli-docs-tool v0.1.1 h1:c6vuTMvogCkSFQCXIr6Mb4gFgUpdZ+28YMbCBfaQLik=
|
||||
github.com/docker/cli-docs-tool v0.1.1/go.mod h1:oMzPNt1wC3TcxuY22GMnOODNOxkwGH51gV3AhqAjFQ4=
|
||||
github.com/docker/compose-on-kubernetes v0.4.19-0.20190128150448-356b2919c496/go.mod h1:iT2pYfi580XlpaV4KmK0T6+4/9+XoKmk/fhoDod1emE=
|
||||
github.com/docker/compose-switch v1.0.2 h1:chXFNNcnRvmtQYzwTaVsv/KSLRt8riSRAiSav89mLfk=
|
||||
github.com/docker/compose-switch v1.0.2/go.mod h1:uyPj8S3oH1O9rSZ5QVozw28OIjdNIflSSYElC2P0plQ=
|
||||
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
|
||||
github.com/docker/distribution v2.6.0-rc.1.0.20180327202408-83389a148052+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
@@ -748,8 +756,9 @@ github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1D
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo=
|
||||
github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
|
||||
github.com/moby/buildkit v0.8.2-0.20210401015549-df49b648c8bf h1:dHwWBX8OhYb69qVcT27rFSwzKsn4CRbg0HInQyVh33c=
|
||||
github.com/moby/buildkit v0.8.2-0.20210401015549-df49b648c8bf/go.mod h1:GJcrUlTGFAPlEmPQtbrTsJYn+cy+Jwl7vTZS7jYAoow=
|
||||
@@ -764,6 +773,7 @@ github.com/moby/sys/mountinfo v0.1.3/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+S
|
||||
github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
|
||||
github.com/moby/sys/mountinfo v0.4.1 h1:1O+1cHA1aujwEwwVMa2Xm2l+gIpUHyd3+D+d7LZh1kM=
|
||||
github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
|
||||
github.com/moby/sys/symlink v0.1.0 h1:MTFZ74KtNI6qQQpuBxU+uKCim4WtOMokr03hCfJcazE=
|
||||
github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ=
|
||||
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
|
||||
github.com/moby/term v0.0.0-20201110203204-bea5bbe245bf/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
|
||||
@@ -826,19 +836,22 @@ github.com/opencontainers/runc v1.0.0-rc10/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2r
|
||||
github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runc v1.0.0-rc92/go.mod h1:X1zlU4p7wOlX4+WRCz+hvlRv8phdL7UqbYD+vQwNMmE=
|
||||
github.com/opencontainers/runc v1.0.0-rc93 h1:x2UMpOOVf3kQ8arv/EsDGwim8PTNqzL1/EYDr/+scOM=
|
||||
github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0=
|
||||
github.com/opencontainers/runc v1.0.2 h1:opHZMaswlyxz1OuGpBE53Dwe4/xF7EZTY0A2L/FpCOg=
|
||||
github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0=
|
||||
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20200728170252-4d89ac9fbff6/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d h1:pNa8metDkwZjb9g4T8s+krQ+HRgZAkqnXml+wNir/+s=
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 h1:3snG66yBm59tKhhSPQrQ/0bCrv1LQbKt40LnUPiUxdc=
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=
|
||||
github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE=
|
||||
github.com/opencontainers/selinux v1.8.0 h1:+77ba4ar4jsCbL1GLbFL8fFM57w6suPfSS9PDLDY7KM=
|
||||
github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=
|
||||
github.com/opencontainers/selinux v1.8.2 h1:c4ca10UMgRcvZ6h0K4HtS15UaVSBEaE+iln2LVpAuGc=
|
||||
github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=
|
||||
github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
||||
@@ -953,6 +966,7 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
|
||||
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
|
||||
github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
|
||||
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
@@ -1024,7 +1038,6 @@ github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln
|
||||
github.com/weppos/publicsuffix-go v0.5.0 h1:rutRtjBJViU/YjcI5d80t4JAVvDltS6bciJg2K1HrLU=
|
||||
github.com/weppos/publicsuffix-go v0.5.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
|
||||
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
github.com/willf/bitset v1.1.11 h1:N7Z7E9UvjW+sGsEl7k/SJrvY2reP1A07MrGuCjIOjRE=
|
||||
github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
@@ -1309,6 +1322,7 @@ golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -1514,8 +1528,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -124,6 +124,8 @@ type StartOptions struct {
|
||||
CascadeStop bool
|
||||
// ExitCodeFrom return exit code from specified service
|
||||
ExitCodeFrom string
|
||||
// Wait won't return until containers reached the running|healthy state
|
||||
Wait bool
|
||||
}
|
||||
|
||||
// RestartOptions group options of the Restart API
|
||||
@@ -225,6 +227,8 @@ type RunOptions struct {
|
||||
Privileged bool
|
||||
UseNetworkAliases bool
|
||||
NoDeps bool
|
||||
// QuietPull makes the pulling process quiet
|
||||
QuietPull bool
|
||||
// used by exec
|
||||
Index int
|
||||
}
|
||||
|
||||
@@ -20,14 +20,17 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/driver"
|
||||
_ "github.com/docker/buildx/driver/docker" // required to get default driver registered
|
||||
"github.com/docker/buildx/util/buildflags"
|
||||
xprogress "github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/pkg/urlutil"
|
||||
bclient "github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/session"
|
||||
"github.com/moby/buildkit/session/auth/authprovider"
|
||||
@@ -69,7 +72,6 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
buildOptions.Pull = options.Pull
|
||||
buildOptions.BuildArgs = mergeArgs(buildOptions.BuildArgs, args)
|
||||
buildOptions.NoCache = options.NoCache
|
||||
opts[imageName] = buildOptions
|
||||
buildOptions.CacheFrom, err = buildflags.ParseCacheEntry(service.Build.CacheFrom)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -81,6 +83,8 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
Attrs: map[string]string{"ref": image},
|
||||
})
|
||||
}
|
||||
|
||||
opts[imageName] = buildOptions
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,64 +193,31 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
|
||||
return images, nil
|
||||
}
|
||||
|
||||
func (s *composeService) doBuild(ctx context.Context, project *types.Project, opts map[string]build.Options, mode string) (map[string]string, error) {
|
||||
info, err := s.apiClient.Info(ctx)
|
||||
func (s *composeService) serverInfo(ctx context.Context) (command.ServerInfo, error) {
|
||||
ping, err := s.apiClient.Ping(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return command.ServerInfo{}, err
|
||||
}
|
||||
serverInfo := command.ServerInfo{
|
||||
HasExperimental: ping.Experimental,
|
||||
OSType: ping.OSType,
|
||||
BuildkitVersion: ping.BuilderVersion,
|
||||
}
|
||||
return serverInfo, err
|
||||
}
|
||||
|
||||
if info.OSType == "windows" {
|
||||
// no support yet for Windows container builds in Buildkit
|
||||
// https://docs.docker.com/develop/develop-images/build_enhancements/#limitations
|
||||
err := s.windowsBuild(opts, mode)
|
||||
return nil, WrapCategorisedComposeError(err, BuildFailure)
|
||||
}
|
||||
func (s *composeService) doBuild(ctx context.Context, project *types.Project, opts map[string]build.Options, mode string) (map[string]string, error) {
|
||||
if len(opts) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
const drivername = "default"
|
||||
|
||||
d, err := driver.GetDriver(ctx, drivername, nil, s.apiClient, s.configFile, nil, nil, "", nil, nil, project.WorkingDir)
|
||||
serverInfo, err := s.serverInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
driverInfo := []build.DriverInfo{
|
||||
{
|
||||
Name: "default",
|
||||
Driver: d,
|
||||
},
|
||||
if buildkitEnabled, err := command.BuildKitEnabled(serverInfo); err != nil || !buildkitEnabled {
|
||||
return s.doBuildClassic(ctx, opts)
|
||||
}
|
||||
|
||||
// Progress needs its own context that lives longer than the
|
||||
// build one otherwise it won't read all the messages from
|
||||
// build and will lock
|
||||
progressCtx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
w := xprogress.NewPrinter(progressCtx, os.Stdout, mode)
|
||||
|
||||
// We rely on buildx "docker" builder integrated in docker engine, so don't need a DockerAPI here
|
||||
response, err := build.Build(ctx, driverInfo, opts, nil, nil, w)
|
||||
errW := w.Wait()
|
||||
if err == nil {
|
||||
err = errW
|
||||
}
|
||||
if err != nil {
|
||||
return nil, WrapCategorisedComposeError(err, BuildFailure)
|
||||
}
|
||||
|
||||
imagesBuilt := map[string]string{}
|
||||
for name, img := range response {
|
||||
if img == nil || len(img.ExporterResponse) == 0 {
|
||||
continue
|
||||
}
|
||||
digest, ok := img.ExporterResponse["containerimage.digest"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
imagesBuilt[name] = digest
|
||||
}
|
||||
|
||||
return imagesBuilt, err
|
||||
return s.doBuildBuildkit(ctx, project, opts, mode)
|
||||
}
|
||||
|
||||
func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, imageTag string) (build.Options, error) {
|
||||
@@ -259,6 +230,13 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
|
||||
}))
|
||||
|
||||
var plats []specs.Platform
|
||||
if platform, ok := project.Environment["DOCKER_DEFAULT_PLATFORM"]; ok {
|
||||
p, err := platforms.Parse(platform)
|
||||
if err != nil {
|
||||
return build.Options{}, err
|
||||
}
|
||||
plats = append(plats, p)
|
||||
}
|
||||
if service.Platform != "" {
|
||||
p, err := platforms.Parse(service.Platform)
|
||||
if err != nil {
|
||||
@@ -270,7 +248,7 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
|
||||
return build.Options{
|
||||
Inputs: build.Inputs{
|
||||
ContextPath: service.Build.Context,
|
||||
DockerfilePath: service.Build.Dockerfile,
|
||||
DockerfilePath: dockerFilePath(service.Build.Context, service.Build.Dockerfile),
|
||||
},
|
||||
BuildArgs: buildArgs,
|
||||
Tags: tags,
|
||||
@@ -309,3 +287,10 @@ func mergeArgs(m ...types.Mapping) types.Mapping {
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
func dockerFilePath(context string, dockerfile string) string {
|
||||
if urlutil.IsGitURL(context) || path.IsAbs(dockerfile) {
|
||||
return dockerfile
|
||||
}
|
||||
return filepath.Join(context, dockerfile)
|
||||
}
|
||||
|
||||
72
pkg/compose/build_buildkit.go
Normal file
72
pkg/compose/build_buildkit.go
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
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"
|
||||
"os"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/driver"
|
||||
xprogress "github.com/docker/buildx/util/progress"
|
||||
)
|
||||
|
||||
func (s *composeService) doBuildBuildkit(ctx context.Context, project *types.Project, opts map[string]build.Options, mode string) (map[string]string, error) {
|
||||
const drivername = "default"
|
||||
d, err := driver.GetDriver(ctx, drivername, nil, s.apiClient, s.configFile, nil, nil, "", nil, nil, project.WorkingDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
driverInfo := []build.DriverInfo{
|
||||
{
|
||||
Name: drivername,
|
||||
Driver: d,
|
||||
},
|
||||
}
|
||||
|
||||
// Progress needs its own context that lives longer than the
|
||||
// build one otherwise it won't read all the messages from
|
||||
// build and will lock
|
||||
progressCtx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
w := xprogress.NewPrinter(progressCtx, os.Stdout, mode)
|
||||
|
||||
// We rely on buildx "docker" builder integrated in docker engine, so don't need a DockerAPI here
|
||||
response, err := build.Build(ctx, driverInfo, opts, nil, nil, w)
|
||||
errW := w.Wait()
|
||||
if err == nil {
|
||||
err = errW
|
||||
}
|
||||
if err != nil {
|
||||
return nil, WrapCategorisedComposeError(err, BuildFailure)
|
||||
}
|
||||
|
||||
imagesBuilt := map[string]string{}
|
||||
for name, img := range response {
|
||||
if img == nil || len(img.ExporterResponse) == 0 {
|
||||
continue
|
||||
}
|
||||
digest, ok := img.ExporterResponse["containerimage.digest"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
imagesBuilt[name] = digest
|
||||
}
|
||||
|
||||
return imagesBuilt, err
|
||||
}
|
||||
250
pkg/compose/build_classic.go
Normal file
250
pkg/compose/build_classic.go
Normal file
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
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"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
buildx "github.com/docker/buildx/build"
|
||||
"github.com/docker/cli/cli/command/image/build"
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/docker/pkg/progress"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/docker/docker/pkg/urlutil"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (s *composeService) doBuildClassic(ctx context.Context, opts map[string]buildx.Options) (map[string]string, error) {
|
||||
var nameDigests = make(map[string]string)
|
||||
var errs error
|
||||
for name, o := range opts {
|
||||
digest, err := s.doBuildClassicSimpleImage(ctx, o)
|
||||
if err != nil {
|
||||
errs = multierror.Append(errs, err).ErrorOrNil()
|
||||
}
|
||||
nameDigests[name] = digest
|
||||
}
|
||||
|
||||
return nameDigests, errs
|
||||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options buildx.Options) (string, error) {
|
||||
var (
|
||||
buildCtx io.ReadCloser
|
||||
dockerfileCtx io.ReadCloser
|
||||
contextDir string
|
||||
tempDir string
|
||||
relDockerfile string
|
||||
|
||||
err error
|
||||
)
|
||||
|
||||
dockerfileName := options.Inputs.DockerfilePath
|
||||
specifiedContext := options.Inputs.ContextPath
|
||||
progBuff := os.Stdout
|
||||
buildBuff := os.Stdout
|
||||
if options.ImageIDFile != "" {
|
||||
// Avoid leaving a stale file if we eventually fail
|
||||
if err := os.Remove(options.ImageIDFile); err != nil && !os.IsNotExist(err) {
|
||||
return "", errors.Wrap(err, "removing image ID file")
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case isLocalDir(specifiedContext):
|
||||
contextDir, relDockerfile, err = build.GetContextFromLocalDir(specifiedContext, dockerfileName)
|
||||
if err == nil && strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) {
|
||||
// Dockerfile is outside of build-context; read the Dockerfile and pass it as dockerfileCtx
|
||||
dockerfileCtx, err = os.Open(dockerfileName)
|
||||
if err != nil {
|
||||
return "", errors.Errorf("unable to open Dockerfile: %v", err)
|
||||
}
|
||||
defer dockerfileCtx.Close() // nolint:errcheck
|
||||
}
|
||||
case urlutil.IsGitURL(specifiedContext):
|
||||
tempDir, relDockerfile, err = build.GetContextFromGitURL(specifiedContext, dockerfileName)
|
||||
case urlutil.IsURL(specifiedContext):
|
||||
buildCtx, relDockerfile, err = build.GetContextFromURL(progBuff, specifiedContext, dockerfileName)
|
||||
default:
|
||||
return "", errors.Errorf("unable to prepare context: path %q not found", specifiedContext)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", errors.Errorf("unable to prepare context: %s", err)
|
||||
}
|
||||
|
||||
if tempDir != "" {
|
||||
defer os.RemoveAll(tempDir) // nolint:errcheck
|
||||
contextDir = tempDir
|
||||
}
|
||||
|
||||
// read from a directory into tar archive
|
||||
if buildCtx == nil {
|
||||
excludes, err := build.ReadDockerignore(contextDir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := build.ValidateContextDirectory(contextDir, excludes); err != nil {
|
||||
return "", errors.Wrap(err, "checking context")
|
||||
}
|
||||
|
||||
// And canonicalize dockerfile name to a platform-independent one
|
||||
relDockerfile = archive.CanonicalTarNameForPath(relDockerfile)
|
||||
|
||||
excludes = build.TrimBuildFilesFromExcludes(excludes, relDockerfile, false)
|
||||
buildCtx, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
|
||||
ExcludePatterns: excludes,
|
||||
ChownOpts: &idtools.Identity{},
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// replace Dockerfile if it was added from stdin or a file outside the build-context, and there is archive context
|
||||
if dockerfileCtx != nil && buildCtx != nil {
|
||||
buildCtx, relDockerfile, err = build.AddDockerfileToBuildContext(dockerfileCtx, buildCtx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
buildCtx, err = build.Compress(buildCtx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// if up to this point nothing has set the context then we must have another
|
||||
// way for sending it(streaming) and set the context to the Dockerfile
|
||||
if dockerfileCtx != nil && buildCtx == nil {
|
||||
buildCtx = dockerfileCtx
|
||||
}
|
||||
|
||||
progressOutput := streamformatter.NewProgressOutput(progBuff)
|
||||
var body io.Reader
|
||||
if buildCtx != nil {
|
||||
body = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
|
||||
}
|
||||
|
||||
configFile := s.configFile
|
||||
creds, err := configFile.GetAllCredentials()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
authConfigs := make(map[string]dockertypes.AuthConfig, len(creds))
|
||||
for k, auth := range creds {
|
||||
authConfigs[k] = dockertypes.AuthConfig(auth)
|
||||
}
|
||||
buildOptions := imageBuildOptions(options)
|
||||
buildOptions.Version = dockertypes.BuilderV1
|
||||
buildOptions.Dockerfile = relDockerfile
|
||||
buildOptions.AuthConfigs = authConfigs
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
response, err := s.apiClient.ImageBuild(ctx, body, buildOptions)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer response.Body.Close() // nolint:errcheck
|
||||
|
||||
imageID := ""
|
||||
aux := func(msg jsonmessage.JSONMessage) {
|
||||
var result dockertypes.BuildResult
|
||||
if err := json.Unmarshal(*msg.Aux, &result); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to parse aux message: %s", err)
|
||||
} else {
|
||||
imageID = result.ID
|
||||
}
|
||||
}
|
||||
|
||||
err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, progBuff.Fd(), true, aux)
|
||||
if err != nil {
|
||||
if jerr, ok := err.(*jsonmessage.JSONError); ok {
|
||||
// If no error code is set, default to 1
|
||||
if jerr.Code == 0 {
|
||||
jerr.Code = 1
|
||||
}
|
||||
return "", cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Windows: show error message about modified file permissions if the
|
||||
// daemon isn't running Windows.
|
||||
if response.OSType != "windows" && runtime.GOOS == "windows" {
|
||||
// if response.OSType != "windows" && runtime.GOOS == "windows" && !options.quiet {
|
||||
fmt.Fprintln(os.Stdout, "SECURITY WARNING: You are building a Docker "+
|
||||
"image from Windows against a non-Windows Docker host. All files and "+
|
||||
"directories added to build context will have '-rwxr-xr-x' permissions. "+
|
||||
"It is recommended to double check and reset permissions for sensitive "+
|
||||
"files and directories.")
|
||||
}
|
||||
|
||||
if options.ImageIDFile != "" {
|
||||
if imageID == "" {
|
||||
return "", errors.Errorf("Server did not provide an image ID. Cannot write %s", options.ImageIDFile)
|
||||
}
|
||||
if err := ioutil.WriteFile(options.ImageIDFile, []byte(imageID), 0666); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return imageID, nil
|
||||
}
|
||||
|
||||
func isLocalDir(c string) bool {
|
||||
_, err := os.Stat(c)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func imageBuildOptions(options buildx.Options) dockertypes.ImageBuildOptions {
|
||||
return dockertypes.ImageBuildOptions{
|
||||
Tags: options.Tags,
|
||||
NoCache: options.NoCache,
|
||||
PullParent: options.Pull,
|
||||
BuildArgs: toMapStringStringPtr(options.BuildArgs),
|
||||
Labels: options.Labels,
|
||||
NetworkMode: options.NetworkMode,
|
||||
ExtraHosts: options.ExtraHosts,
|
||||
Target: options.Target,
|
||||
}
|
||||
}
|
||||
|
||||
func toMapStringStringPtr(source map[string]string) map[string]*string {
|
||||
dest := make(map[string]*string)
|
||||
for k, v := range source {
|
||||
v := v
|
||||
dest[k] = &v
|
||||
}
|
||||
return dest
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
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 (
|
||||
"github.com/docker/buildx/build"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
func (s *composeService) windowsBuild(opts map[string]build.Options, mode string) error {
|
||||
// FIXME copy/paste or reuse code from https://github.com/docker/cli/blob/master/cli/command/image/build.go
|
||||
return api.ErrNotImplemented
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"github.com/sanathkr/go-yaml"
|
||||
)
|
||||
|
||||
// Separator is used for naming components
|
||||
var Separator = "-"
|
||||
|
||||
// NewComposeService create a local implementation of the compose.Service API
|
||||
|
||||
@@ -109,56 +109,61 @@ func (c *convergence) apply(ctx context.Context, project *types.Project, options
|
||||
var mu sync.Mutex
|
||||
|
||||
// updateProject updates project after service converged, so dependent services relying on `service:xx` can refer to actual containers.
|
||||
func (c *convergence) updateProject(project *types.Project, service string) {
|
||||
containers := c.getObservedState(service)
|
||||
if len(containers) == 0 {
|
||||
return
|
||||
}
|
||||
container := containers[0]
|
||||
|
||||
func (c *convergence) updateProject(project *types.Project, serviceName string) {
|
||||
// operation is protected by a Mutex so that we can safely update project.Services while running concurrent convergence on services
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
cnts := c.getObservedState(serviceName)
|
||||
for i, s := range project.Services {
|
||||
if d := getDependentServiceFromMode(s.NetworkMode); d == service {
|
||||
s.NetworkMode = types.NetworkModeContainerPrefix + container.ID
|
||||
}
|
||||
if d := getDependentServiceFromMode(s.Ipc); d == service {
|
||||
s.Ipc = types.NetworkModeContainerPrefix + container.ID
|
||||
}
|
||||
if d := getDependentServiceFromMode(s.Pid); d == service {
|
||||
s.Pid = types.NetworkModeContainerPrefix + container.ID
|
||||
}
|
||||
var links []string
|
||||
for _, serviceLink := range s.Links {
|
||||
parts := strings.Split(serviceLink, ":")
|
||||
serviceName := serviceLink
|
||||
serviceAlias := ""
|
||||
if len(parts) == 2 {
|
||||
serviceName = parts[0]
|
||||
serviceAlias = parts[1]
|
||||
}
|
||||
if serviceName != service {
|
||||
links = append(links, serviceLink)
|
||||
continue
|
||||
}
|
||||
for _, container := range containers {
|
||||
name := getCanonicalContainerName(container)
|
||||
if serviceAlias != "" {
|
||||
links = append(links,
|
||||
fmt.Sprintf("%s:%s", name, serviceAlias))
|
||||
}
|
||||
links = append(links,
|
||||
fmt.Sprintf("%s:%s", name, name),
|
||||
fmt.Sprintf("%s:%s", name, getContainerNameWithoutProject(container)))
|
||||
}
|
||||
s.Links = links
|
||||
}
|
||||
updateServices(&s, cnts)
|
||||
project.Services[i] = s
|
||||
}
|
||||
}
|
||||
|
||||
func updateServices(service *types.ServiceConfig, cnts Containers) {
|
||||
if len(cnts) == 0 {
|
||||
return
|
||||
}
|
||||
cnt := cnts[0]
|
||||
serviceName := cnt.Labels[api.ServiceLabel]
|
||||
|
||||
if d := getDependentServiceFromMode(service.NetworkMode); d == serviceName {
|
||||
service.NetworkMode = types.NetworkModeContainerPrefix + cnt.ID
|
||||
}
|
||||
if d := getDependentServiceFromMode(service.Ipc); d == serviceName {
|
||||
service.Ipc = types.NetworkModeContainerPrefix + cnt.ID
|
||||
}
|
||||
if d := getDependentServiceFromMode(service.Pid); d == serviceName {
|
||||
service.Pid = types.NetworkModeContainerPrefix + cnt.ID
|
||||
}
|
||||
var links []string
|
||||
for _, serviceLink := range service.Links {
|
||||
parts := strings.Split(serviceLink, ":")
|
||||
serviceName := serviceLink
|
||||
serviceAlias := ""
|
||||
if len(parts) == 2 {
|
||||
serviceName = parts[0]
|
||||
serviceAlias = parts[1]
|
||||
}
|
||||
if serviceName != service.Name {
|
||||
links = append(links, serviceLink)
|
||||
continue
|
||||
}
|
||||
for _, container := range cnts {
|
||||
name := getCanonicalContainerName(container)
|
||||
if serviceAlias != "" {
|
||||
links = append(links,
|
||||
fmt.Sprintf("%s:%s", name, serviceAlias))
|
||||
}
|
||||
links = append(links,
|
||||
fmt.Sprintf("%s:%s", name, name),
|
||||
fmt.Sprintf("%s:%s", name, getContainerNameWithoutProject(container)))
|
||||
}
|
||||
service.Links = links
|
||||
}
|
||||
}
|
||||
|
||||
func (c *convergence) ensureService(ctx context.Context, project *types.Project, service types.ServiceConfig, recreate string, inherit bool, timeout *time.Duration) error {
|
||||
expected, err := getScale(service)
|
||||
if err != nil {
|
||||
@@ -256,9 +261,11 @@ func getContainerProgressName(container moby.Container) string {
|
||||
return "Container " + getCanonicalContainerName(container)
|
||||
}
|
||||
|
||||
func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
|
||||
const ServiceConditionRunningOrHealthy = "running_or_healthy"
|
||||
|
||||
func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, dependencies types.DependsOnConfig) error {
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
for dep, config := range service.DependsOn {
|
||||
for dep, config := range dependencies {
|
||||
dep, config := dep, config
|
||||
eg.Go(func() error {
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
@@ -266,8 +273,16 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
|
||||
for {
|
||||
<-ticker.C
|
||||
switch config.Condition {
|
||||
case ServiceConditionRunningOrHealthy:
|
||||
healthy, err := s.isServiceHealthy(ctx, project, dep, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if healthy {
|
||||
return nil
|
||||
}
|
||||
case types.ServiceConditionHealthy:
|
||||
healthy, err := s.isServiceHealthy(ctx, project, dep)
|
||||
healthy, err := s.isServiceHealthy(ctx, project, dep, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -433,7 +448,7 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
|
||||
Networks: inspectedContainer.NetworkSettings.Networks,
|
||||
},
|
||||
}
|
||||
links := append(service.Links, service.ExternalLinks...)
|
||||
links := s.getLinks(ctx, project.Name, service, number)
|
||||
for _, netName := range service.NetworksByPriority() {
|
||||
netwrk := project.Networks[netName]
|
||||
cfg := service.Networks[netName]
|
||||
@@ -461,6 +476,64 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
|
||||
return created, err
|
||||
}
|
||||
|
||||
// getLinks mimics V1 compose/service.py::Service::_get_links()
|
||||
func (s composeService) getLinks(ctx context.Context, projectName string, service types.ServiceConfig, number int) []string {
|
||||
var links []string
|
||||
format := func(k, v string) string {
|
||||
return fmt.Sprintf("%s:%s", k, v)
|
||||
}
|
||||
getServiceContainers := func(serviceName string) (Containers, error) {
|
||||
return s.getContainers(ctx, projectName, oneOffExclude, true, serviceName)
|
||||
}
|
||||
|
||||
for _, rawLink := range service.Links {
|
||||
linkSplit := strings.Split(rawLink, ":")
|
||||
linkServiceName := linkSplit[0]
|
||||
linkName := linkServiceName
|
||||
if len(linkSplit) == 2 {
|
||||
linkName = linkSplit[1] // linkName if informed like in: "serviceName:linkName"
|
||||
}
|
||||
cnts, err := getServiceContainers(linkServiceName)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
for _, c := range cnts {
|
||||
containerName := getCanonicalContainerName(c)
|
||||
links = append(links,
|
||||
format(containerName, linkName),
|
||||
format(containerName, strings.Join([]string{linkServiceName, strconv.Itoa(number)}, Separator)),
|
||||
format(containerName, strings.Join([]string{projectName, linkServiceName, strconv.Itoa(number)}, Separator)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if service.Labels[api.OneoffLabel] == "True" {
|
||||
cnts, err := getServiceContainers(service.Name)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
for _, c := range cnts {
|
||||
containerName := getCanonicalContainerName(c)
|
||||
links = append(links,
|
||||
format(containerName, service.Name),
|
||||
format(containerName, strings.TrimPrefix(containerName, projectName+Separator)),
|
||||
format(containerName, containerName),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for _, rawExtLink := range service.ExternalLinks {
|
||||
extLinkSplit := strings.Split(rawExtLink, ":")
|
||||
externalLink := extLinkSplit[0]
|
||||
linkName := externalLink
|
||||
if len(extLinkSplit) == 2 {
|
||||
linkName = extLinkSplit[1]
|
||||
}
|
||||
links = append(links, format(externalLink, linkName))
|
||||
}
|
||||
return links
|
||||
}
|
||||
|
||||
func shortIDAliasExists(containerID string, aliases ...string) bool {
|
||||
for _, alias := range aliases {
|
||||
if alias == containerID[:12] {
|
||||
@@ -497,7 +570,7 @@ func (s *composeService) connectContainerToNetwork(ctx context.Context, id strin
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Project, service string) (bool, error) {
|
||||
func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Project, service string, fallbackRunning bool) (bool, error) {
|
||||
containers, err := s.getContainers(ctx, project.Name, oneOffExclude, false, service)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -511,6 +584,11 @@ func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Pr
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if container.Config.Healthcheck == nil && fallbackRunning {
|
||||
// Container does not define a health check, but we can fall back to "running" state
|
||||
return container.State != nil && container.State.Status == "running", nil
|
||||
}
|
||||
|
||||
if container.State == nil || container.State.Health == nil {
|
||||
return false, fmt.Errorf("container for service %q has no healthcheck configured", service)
|
||||
}
|
||||
@@ -539,7 +617,11 @@ func (s *composeService) isServiceCompleted(ctx context.Context, project *types.
|
||||
}
|
||||
|
||||
func (s *composeService) startService(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
|
||||
err := s.waitDependencies(ctx, project, service)
|
||||
if service.Deploy != nil && service.Deploy.Replicas != nil && *service.Deploy.Replicas == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := s.waitDependencies(ctx, project, service.DependsOn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -559,7 +641,7 @@ func (s *composeService) startService(ctx context.Context, project *types.Projec
|
||||
if scale, err := getScale(service); err != nil && scale == 0 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("no containers to start")
|
||||
return fmt.Errorf("service %q has no container to start", service.Name)
|
||||
}
|
||||
|
||||
w := progress.ContextWriter(ctx)
|
||||
|
||||
@@ -17,10 +17,16 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/mocks"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/golang/mock/gomock"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
@@ -46,3 +52,128 @@ func TestContainerName(t *testing.T) {
|
||||
_, err = getScale(s)
|
||||
assert.Error(t, err, fmt.Sprintf(doubledContainerNameWarning, s.Name, s.ContainerName))
|
||||
}
|
||||
|
||||
func TestServiceLinks(t *testing.T) {
|
||||
const dbContainerName = "/" + testProject + "-db-1"
|
||||
const webContainerName = "/" + testProject + "-web-1"
|
||||
s := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Scale: 1,
|
||||
}
|
||||
|
||||
containerListOptions := moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
projectFilter(testProject),
|
||||
serviceFilter("db"),
|
||||
oneOffFilter(false),
|
||||
),
|
||||
All: true,
|
||||
}
|
||||
|
||||
t.Run("service links default", func(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
tested.apiClient = apiClient
|
||||
|
||||
s.Links = []string{"db"}
|
||||
|
||||
c := testContainer("db", dbContainerName, false)
|
||||
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return([]moby.Container{c}, nil)
|
||||
|
||||
links := tested.getLinks(context.Background(), testProject, s, 1)
|
||||
|
||||
assert.Equal(t, len(links), 3)
|
||||
assert.Equal(t, links[0], "testProject-db-1:db")
|
||||
assert.Equal(t, links[1], "testProject-db-1:db-1")
|
||||
assert.Equal(t, links[2], "testProject-db-1:testProject-db-1")
|
||||
})
|
||||
|
||||
t.Run("service links", func(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
tested.apiClient = apiClient
|
||||
|
||||
s.Links = []string{"db:db"}
|
||||
|
||||
c := testContainer("db", dbContainerName, false)
|
||||
|
||||
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return([]moby.Container{c}, nil)
|
||||
links := tested.getLinks(context.Background(), testProject, s, 1)
|
||||
|
||||
assert.Equal(t, len(links), 3)
|
||||
assert.Equal(t, links[0], "testProject-db-1:db")
|
||||
assert.Equal(t, links[1], "testProject-db-1:db-1")
|
||||
assert.Equal(t, links[2], "testProject-db-1:testProject-db-1")
|
||||
})
|
||||
|
||||
t.Run("service links name", func(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
tested.apiClient = apiClient
|
||||
|
||||
s.Links = []string{"db:dbname"}
|
||||
|
||||
c := testContainer("db", dbContainerName, false)
|
||||
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return([]moby.Container{c}, nil)
|
||||
|
||||
links := tested.getLinks(context.Background(), testProject, s, 1)
|
||||
|
||||
assert.Equal(t, len(links), 3)
|
||||
assert.Equal(t, links[0], "testProject-db-1:dbname")
|
||||
assert.Equal(t, links[1], "testProject-db-1:db-1")
|
||||
assert.Equal(t, links[2], "testProject-db-1:testProject-db-1")
|
||||
})
|
||||
|
||||
t.Run("service links external links", func(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
tested.apiClient = apiClient
|
||||
|
||||
s.Links = []string{"db:dbname"}
|
||||
s.ExternalLinks = []string{"db1:db2"}
|
||||
|
||||
c := testContainer("db", dbContainerName, false)
|
||||
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return([]moby.Container{c}, nil)
|
||||
|
||||
links := tested.getLinks(context.Background(), testProject, s, 1)
|
||||
assert.Equal(t, len(links), 4)
|
||||
assert.Equal(t, links[0], "testProject-db-1:dbname")
|
||||
assert.Equal(t, links[1], "testProject-db-1:db-1")
|
||||
assert.Equal(t, links[2], "testProject-db-1:testProject-db-1")
|
||||
|
||||
// ExternalLink
|
||||
assert.Equal(t, links[3], "db1:db2")
|
||||
})
|
||||
|
||||
t.Run("service links itself oneoff", func(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
tested.apiClient = apiClient
|
||||
|
||||
s.Links = []string{}
|
||||
s.ExternalLinks = []string{}
|
||||
s.Labels = s.Labels.Add(api.OneoffLabel, "True")
|
||||
|
||||
c := testContainer("web", webContainerName, true)
|
||||
containerListOptionsOneOff := moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
projectFilter(testProject),
|
||||
serviceFilter("web"),
|
||||
oneOffFilter(false),
|
||||
),
|
||||
All: true,
|
||||
}
|
||||
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptionsOneOff).Return([]moby.Container{c}, nil)
|
||||
|
||||
links := tested.getLinks(context.Background(), testProject, s, 1)
|
||||
assert.Equal(t, len(links), 3)
|
||||
assert.Equal(t, links[0], "testProject-web-1:web")
|
||||
assert.Equal(t, links[1], "testProject-web-1:web-1")
|
||||
assert.Equal(t, links[2], "testProject-web-1:testProject-web-1")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ func (s *composeService) ensureProjectVolumes(ctx context.Context, project *type
|
||||
volume.Labels = volume.Labels.Add(api.VolumeLabel, k)
|
||||
volume.Labels = volume.Labels.Add(api.ProjectLabel, project.Name)
|
||||
volume.Labels = volume.Labels.Add(api.VersionLabel, api.ComposeVersion)
|
||||
err := s.ensureVolume(ctx, volume)
|
||||
err := s.ensureVolume(ctx, volume, project.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -378,6 +378,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
|
||||
PidMode: container.PidMode(service.Pid),
|
||||
Tmpfs: tmpfs,
|
||||
Isolation: container.Isolation(service.Isolation),
|
||||
Runtime: service.Runtime,
|
||||
LogConfig: logConfig,
|
||||
}
|
||||
|
||||
@@ -492,6 +493,7 @@ func getDeployResources(s types.ServiceConfig) container.Resources {
|
||||
MemorySwap: int64(s.MemSwapLimit),
|
||||
MemorySwappiness: swappiness,
|
||||
MemoryReservation: int64(s.MemReservation),
|
||||
OomKillDisable: &s.OomKillDisable,
|
||||
CPUCount: s.CPUCount,
|
||||
CPUPeriod: s.CPUPeriod,
|
||||
CPUQuota: s.CPUQuota,
|
||||
@@ -502,6 +504,10 @@ func getDeployResources(s types.ServiceConfig) container.Resources {
|
||||
CpusetCpus: s.CPUSet,
|
||||
}
|
||||
|
||||
if s.PidsLimit != 0 {
|
||||
resources.PidsLimit = &s.PidsLimit
|
||||
}
|
||||
|
||||
setBlkio(s.BlkioConfig, &resources)
|
||||
|
||||
if s.Deploy != nil {
|
||||
@@ -525,6 +531,9 @@ func getDeployResources(s types.ServiceConfig) container.Resources {
|
||||
case 1:
|
||||
src = arr[0]
|
||||
}
|
||||
if dst == "" {
|
||||
dst = src
|
||||
}
|
||||
resources.Devices = append(resources.Devices, container.DeviceMapping{
|
||||
PathOnHost: src,
|
||||
PathInContainer: dst,
|
||||
@@ -747,24 +756,21 @@ func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, v := range s.Volumes {
|
||||
if v.Target != m.Destination {
|
||||
volumes := []types.ServiceVolumeConfig{}
|
||||
for _, v := range s.Volumes {
|
||||
if v.Target != m.Destination || v.Source != "" {
|
||||
volumes = append(volumes, v)
|
||||
continue
|
||||
}
|
||||
if v.Source == "" {
|
||||
// inherit previous container's anonymous volume
|
||||
mounts[m.Destination] = mount.Mount{
|
||||
Type: m.Type,
|
||||
Source: src,
|
||||
Target: m.Destination,
|
||||
ReadOnly: !m.RW,
|
||||
}
|
||||
// Avoid mount to be later re-defined
|
||||
l := len(s.Volumes) - 1
|
||||
s.Volumes[i] = s.Volumes[l]
|
||||
s.Volumes = s.Volumes[:l]
|
||||
// inherit previous container's anonymous volume
|
||||
mounts[m.Destination] = mount.Mount{
|
||||
Type: m.Type,
|
||||
Source: src,
|
||||
Target: m.Destination,
|
||||
ReadOnly: !m.RW,
|
||||
}
|
||||
}
|
||||
s.Volumes = volumes
|
||||
}
|
||||
}
|
||||
|
||||
@@ -946,8 +952,8 @@ func buildMountOptions(volume types.ServiceVolumeConfig) (*mount.BindOptions, *m
|
||||
if volume.Bind != nil {
|
||||
logrus.Warnf("mount of type `tmpfs` should not define `bind` option")
|
||||
}
|
||||
if volume.Tmpfs != nil {
|
||||
logrus.Warnf("mount of type `tmpfs` should not define `volumeZ` option")
|
||||
if volume.Volume != nil {
|
||||
logrus.Warnf("mount of type `tmpfs` should not define `volume` option")
|
||||
}
|
||||
return nil, nil, buildTmpfsOptions(volume.Tmpfs)
|
||||
}
|
||||
@@ -980,7 +986,7 @@ func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
|
||||
return nil
|
||||
}
|
||||
return &mount.TmpfsOptions{
|
||||
SizeBytes: tmpfs.Size,
|
||||
SizeBytes: int64(tmpfs.Size),
|
||||
// Mode: , // FIXME missing from model ?
|
||||
}
|
||||
}
|
||||
@@ -1034,6 +1040,7 @@ func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfi
|
||||
Internal: n.Internal,
|
||||
Attachable: n.Attachable,
|
||||
IPAM: ipam,
|
||||
EnableIPv6: n.EnableIPv6,
|
||||
}
|
||||
|
||||
if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 {
|
||||
@@ -1079,27 +1086,49 @@ func (s *composeService) removeNetwork(ctx context.Context, networkID string, ne
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig) error {
|
||||
// TODO could identify volume by label vs name
|
||||
_, err := s.apiClient.VolumeInspect(ctx, volume.Name)
|
||||
func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig, project string) error {
|
||||
inspected, err := s.apiClient.VolumeInspect(ctx, volume.Name)
|
||||
if err != nil {
|
||||
if !errdefs.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
eventName := fmt.Sprintf("Volume %q", volume.Name)
|
||||
w := progress.ContextWriter(ctx)
|
||||
w.Event(progress.CreatingEvent(eventName))
|
||||
_, err := s.apiClient.VolumeCreate(ctx, volume_api.VolumeCreateBody{
|
||||
Labels: volume.Labels,
|
||||
Name: volume.Name,
|
||||
Driver: volume.Driver,
|
||||
DriverOpts: volume.DriverOpts,
|
||||
})
|
||||
if err != nil {
|
||||
w.Event(progress.ErrorEvent(eventName))
|
||||
return err
|
||||
if volume.External.External {
|
||||
return fmt.Errorf("external volume %q not found", volume.External.Name)
|
||||
}
|
||||
w.Event(progress.CreatedEvent(eventName))
|
||||
err := s.createVolume(ctx, volume)
|
||||
return err
|
||||
}
|
||||
|
||||
if volume.External.External {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Volume exists with name, but let's double check this is the expected one
|
||||
// (better safe than sorry when it comes to user's data)
|
||||
p, ok := inspected.Labels[api.ProjectLabel]
|
||||
if !ok {
|
||||
return fmt.Errorf("volume %q already exists but was not created by Docker Compose. Use `external: true` to use an existing volume", volume.Name)
|
||||
}
|
||||
if p != project {
|
||||
return fmt.Errorf("volume %q already exists but was not created for project %q. Use `external: true` to use an existing volume", volume.Name, p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *composeService) createVolume(ctx context.Context, volume types.VolumeConfig) error {
|
||||
eventName := fmt.Sprintf("Volume %q", volume.Name)
|
||||
w := progress.ContextWriter(ctx)
|
||||
w.Event(progress.CreatingEvent(eventName))
|
||||
_, err := s.apiClient.VolumeCreate(ctx, volume_api.VolumeCreateBody{
|
||||
Labels: volume.Labels,
|
||||
Name: volume.Name,
|
||||
Driver: volume.Driver,
|
||||
DriverOpts: volume.DriverOpts,
|
||||
})
|
||||
if err != nil {
|
||||
w.Event(progress.ErrorEvent(eventName))
|
||||
return err
|
||||
}
|
||||
w.Event(progress.CreatedEvent(eventName))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -19,12 +19,13 @@ package compose
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
composetypes "github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
mountTypes "github.com/docker/docker/api/types/mount"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
@@ -81,3 +82,63 @@ func TestPrepareNetworkLabels(t *testing.T) {
|
||||
"com.docker.compose.version": api.ComposeVersion,
|
||||
}))
|
||||
}
|
||||
|
||||
func TestBuildContainerMountOptions(t *testing.T) {
|
||||
project := composetypes.Project{
|
||||
Name: "myProject",
|
||||
Services: []composetypes.ServiceConfig{
|
||||
{
|
||||
Name: "myService",
|
||||
Volumes: []composetypes.ServiceVolumeConfig{
|
||||
{
|
||||
Type: composetypes.VolumeTypeVolume,
|
||||
Target: "/var/myvolume1",
|
||||
},
|
||||
{
|
||||
Type: composetypes.VolumeTypeVolume,
|
||||
Target: "/var/myvolume2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: composetypes.Volumes(map[string]composetypes.VolumeConfig{
|
||||
"myVolume1": {
|
||||
Name: "myProject_myVolume1",
|
||||
},
|
||||
"myVolume2": {
|
||||
Name: "myProject_myVolume2",
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
inherit := &moby.Container{
|
||||
Mounts: []moby.MountPoint{
|
||||
{
|
||||
Type: composetypes.VolumeTypeVolume,
|
||||
Destination: "/var/myvolume1",
|
||||
},
|
||||
{
|
||||
Type: composetypes.VolumeTypeVolume,
|
||||
Destination: "/var/myvolume2",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mounts, err := buildContainerMountOptions(project, project.Services[0], moby.ImageInspect{}, inherit)
|
||||
sort.Slice(mounts, func(i, j int) bool {
|
||||
return mounts[i].Target < mounts[j].Target
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, len(mounts) == 2)
|
||||
assert.Equal(t, mounts[0].Target, "/var/myvolume1")
|
||||
assert.Equal(t, mounts[1].Target, "/var/myvolume2")
|
||||
|
||||
mounts, err = buildContainerMountOptions(project, project.Services[0], moby.ImageInspect{}, inherit)
|
||||
sort.Slice(mounts, func(i, j int) bool {
|
||||
return mounts[i].Target < mounts[j].Target
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, len(mounts) == 2)
|
||||
assert.Equal(t, mounts[0].Target, "/var/myvolume1")
|
||||
assert.Equal(t, mounts[1].Target, "/var/myvolume2")
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ func run(ctx context.Context, graph *Graph, eg *errgroup.Group, nodes []*Vertex,
|
||||
for _, node := range nodes {
|
||||
// Don't start this service yet if all of its children have
|
||||
// not been started yet.
|
||||
if len(traversalConfig.filterAdjacentByStatusFn(graph, node.Service, traversalConfig.adjacentServiceStatusToSkip)) != 0 {
|
||||
if len(traversalConfig.filterAdjacentByStatusFn(graph, node.Key, traversalConfig.adjacentServiceStatusToSkip)) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ func run(ctx context.Context, graph *Graph, eg *errgroup.Group, nodes []*Vertex,
|
||||
return err
|
||||
}
|
||||
|
||||
graph.UpdateStatus(node.Service, traversalConfig.targetServiceStatus)
|
||||
graph.UpdateStatus(node.Key, traversalConfig.targetServiceStatus)
|
||||
|
||||
return run(ctx, graph, eg, traversalConfig.adjacentNodesFn(node), traversalConfig, fn)
|
||||
})
|
||||
|
||||
@@ -75,7 +75,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||
}
|
||||
}
|
||||
|
||||
ops, err := s.ensureNetwoksDown(ctx, projectName)
|
||||
ops, err := s.ensureNetworksDown(ctx, projectName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -129,7 +129,7 @@ func (s *composeService) ensureImagesDown(ctx context.Context, projectName strin
|
||||
return ops
|
||||
}
|
||||
|
||||
func (s *composeService) ensureNetwoksDown(ctx context.Context, projectName string) ([]downOp, error) {
|
||||
func (s *composeService) ensureNetworksDown(ctx context.Context, projectName string) ([]downOp, error) {
|
||||
var ops []downOp
|
||||
networks, err := s.apiClient.NetworkList(ctx, moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(projectName))})
|
||||
if err != nil {
|
||||
|
||||
@@ -90,7 +90,7 @@ func (s *composeService) interactiveExec(ctx context.Context, opts api.RunOption
|
||||
|
||||
stdout := ContainerStdout{HijackedResponse: resp}
|
||||
stdin := ContainerStdin{HijackedResponse: resp}
|
||||
r, err := s.getEscapeKeyProxy(opts.Stdin)
|
||||
r, err := s.getEscapeKeyProxy(opts.Stdin, opts.Tty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -35,12 +35,16 @@ func (s *composeService) Logs(ctx context.Context, projectName string, consumer
|
||||
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
if options.Follow {
|
||||
printer := newLogPrinter(consumer)
|
||||
eg.Go(func() error {
|
||||
printer := newLogPrinter(consumer)
|
||||
return s.watchContainers(ctx, projectName, options.Services, printer.HandleEvent, containers, func(c types.Container) error {
|
||||
return s.logContainers(ctx, consumer, c, options)
|
||||
})
|
||||
})
|
||||
eg.Go(func() error {
|
||||
_, err := printer.Run(ctx, false, "", nil)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
for _, c := range containers {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@@ -27,7 +28,7 @@ import (
|
||||
// logPrinter watch application containers an collect their logs
|
||||
type logPrinter interface {
|
||||
HandleEvent(event api.ContainerEvent)
|
||||
Run(cascadeStop bool, exitCodeFrom string, stopFn func() error) (int, error)
|
||||
Run(ctx context.Context, cascadeStop bool, exitCodeFrom string, stopFn func() error) (int, error)
|
||||
Cancel()
|
||||
}
|
||||
|
||||
@@ -56,56 +57,61 @@ func (p *printer) HandleEvent(event api.ContainerEvent) {
|
||||
p.queue <- event
|
||||
}
|
||||
|
||||
func (p *printer) Run(cascadeStop bool, exitCodeFrom string, stopFn func() error) (int, error) {
|
||||
//nolint:gocyclo
|
||||
func (p *printer) Run(ctx context.Context, cascadeStop bool, exitCodeFrom string, stopFn func() error) (int, error) {
|
||||
var (
|
||||
aborting bool
|
||||
exitCode int
|
||||
)
|
||||
containers := map[string]struct{}{}
|
||||
for {
|
||||
event := <-p.queue
|
||||
container := event.Container
|
||||
switch event.Type {
|
||||
case api.UserCancel:
|
||||
aborting = true
|
||||
case api.ContainerEventAttach:
|
||||
if _, ok := containers[container]; ok {
|
||||
continue
|
||||
}
|
||||
containers[container] = struct{}{}
|
||||
p.consumer.Register(container)
|
||||
case api.ContainerEventExit:
|
||||
if !event.Restarting {
|
||||
delete(containers, container)
|
||||
}
|
||||
if !aborting {
|
||||
p.consumer.Status(container, fmt.Sprintf("exited with code %d", event.ExitCode))
|
||||
}
|
||||
if cascadeStop {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return exitCode, ctx.Err()
|
||||
case event := <-p.queue:
|
||||
container := event.Container
|
||||
switch event.Type {
|
||||
case api.UserCancel:
|
||||
aborting = true
|
||||
case api.ContainerEventAttach:
|
||||
if _, ok := containers[container]; ok {
|
||||
continue
|
||||
}
|
||||
containers[container] = struct{}{}
|
||||
p.consumer.Register(container)
|
||||
case api.ContainerEventExit:
|
||||
if !event.Restarting {
|
||||
delete(containers, container)
|
||||
}
|
||||
if !aborting {
|
||||
aborting = true
|
||||
fmt.Println("Aborting on container exit...")
|
||||
err := stopFn()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
p.consumer.Status(container, fmt.Sprintf("exited with code %d", event.ExitCode))
|
||||
}
|
||||
if cascadeStop {
|
||||
if !aborting {
|
||||
aborting = true
|
||||
fmt.Println("Aborting on container exit...")
|
||||
err := stopFn()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
if exitCodeFrom == "" {
|
||||
exitCodeFrom = event.Service
|
||||
}
|
||||
if exitCodeFrom == event.Service {
|
||||
logrus.Error(event.ExitCode)
|
||||
exitCode = event.ExitCode
|
||||
}
|
||||
}
|
||||
if exitCodeFrom == "" {
|
||||
exitCodeFrom = event.Service
|
||||
if len(containers) == 0 {
|
||||
// Last container terminated, done
|
||||
return exitCode, nil
|
||||
}
|
||||
if exitCodeFrom == event.Service {
|
||||
logrus.Error(event.ExitCode)
|
||||
exitCode = event.ExitCode
|
||||
case api.ContainerEventLog:
|
||||
if !aborting {
|
||||
p.consumer.Log(container, event.Service, event.Line)
|
||||
}
|
||||
}
|
||||
if len(containers) == 0 {
|
||||
// Last container terminated, done
|
||||
return exitCode, nil
|
||||
}
|
||||
case api.ContainerEventLog:
|
||||
if !aborting {
|
||||
p.consumer.Log(container, event.Service, event.Line)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.
|
||||
}
|
||||
|
||||
func (s *composeService) runInteractive(ctx context.Context, containerID string, opts api.RunOptions) (int, error) {
|
||||
r, err := s.getEscapeKeyProxy(opts.Stdin)
|
||||
r, err := s.getEscapeKeyProxy(opts.Stdin, opts.Tty)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -62,7 +62,7 @@ func (s *composeService) runInteractive(ctx context.Context, containerID string,
|
||||
}
|
||||
|
||||
in := streams.NewIn(opts.Stdin)
|
||||
if in.IsTerminal() {
|
||||
if in.IsTerminal() && opts.Tty {
|
||||
state, err := term.SetRawTerminal(in.FD())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -156,14 +156,21 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
|
||||
service.Labels = service.Labels.Add(api.SlugLabel, slug)
|
||||
service.Labels = service.Labels.Add(api.OneoffLabel, "True")
|
||||
|
||||
if err := s.ensureImagesExists(ctx, project, false); err != nil { // all dependencies already checked, but might miss service img
|
||||
if err := s.ensureImagesExists(ctx, project, opts.QuietPull); err != nil { // all dependencies already checked, but might miss service img
|
||||
return "", err
|
||||
}
|
||||
if !opts.NoDeps {
|
||||
if err := s.waitDependencies(ctx, project, service); err != nil {
|
||||
if err := s.waitDependencies(ctx, project, service.DependsOn); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
observedState, err := s.getContainers(ctx, project.Name, oneOffInclude, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
updateServices(&service, observedState)
|
||||
|
||||
created, err := s.createContainer(ctx, project, service, service.ContainerName, 1, opts.Detach && opts.AutoRemove, opts.UseNetworkAliases, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -172,7 +179,10 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
|
||||
return containerID, nil
|
||||
}
|
||||
|
||||
func (s *composeService) getEscapeKeyProxy(r io.ReadCloser) (io.ReadCloser, error) {
|
||||
func (s *composeService) getEscapeKeyProxy(r io.ReadCloser, isTty bool) (io.ReadCloser, error) {
|
||||
if !isTty {
|
||||
return r, nil
|
||||
}
|
||||
var escapeKeys = []byte{16, 17}
|
||||
if s.configFile.DetachKeys != "" {
|
||||
customEscapeKeys, err := term.ToBytes(s.configFile.DetachKeys)
|
||||
|
||||
@@ -58,11 +58,26 @@ func (s *composeService) start(ctx context.Context, project *types.Project, opti
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.startService(ctx, project, service)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if options.Wait {
|
||||
depends := types.DependsOnConfig{}
|
||||
for _, s := range project.Services {
|
||||
depends[s.Name] = types.ServiceDependency{
|
||||
Condition: ServiceConditionRunningOrHealthy,
|
||||
}
|
||||
}
|
||||
err = s.waitDependencies(ctx, project, depends)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
@@ -79,6 +94,12 @@ func (s *composeService) watchContainers(ctx context.Context, projectName string
|
||||
err := s.Events(ctx, projectName, api.EventsOptions{
|
||||
Services: services,
|
||||
Consumer: func(event api.Event) error {
|
||||
if event.Status == "destroy" {
|
||||
// This container can't be inspected, because it's gone.
|
||||
// It's already been removed from the watched map.
|
||||
return nil
|
||||
}
|
||||
|
||||
inspected, err := s.apiClient.ContainerInspect(ctx, event.Container)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -94,7 +115,7 @@ func (s *composeService) watchContainers(ctx context.Context, projectName string
|
||||
restarted := watched[container.ID]
|
||||
watched[container.ID] = restarted + 1
|
||||
// Container terminated.
|
||||
willRestart := inspected.HostConfig.RestartPolicy.MaximumRetryCount > restarted
|
||||
willRestart := willContainerRestart(inspected, restarted)
|
||||
|
||||
listener(api.ContainerEvent{
|
||||
Type: api.ContainerEventExit,
|
||||
@@ -141,3 +162,14 @@ func (s *composeService) watchContainers(ctx context.Context, projectName string
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func willContainerRestart(container moby.ContainerJSON, restarted int) bool {
|
||||
policy := container.HostConfig.RestartPolicy
|
||||
if policy.IsAlways() || policy.IsUnlessStopped() {
|
||||
return true
|
||||
}
|
||||
if policy.IsOnFailure() {
|
||||
return container.State.ExitCode != 0 && policy.MaximumRetryCount > restarted
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
|
||||
var exitCode int
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
eg.Go(func() error {
|
||||
code, err := printer.Run(options.Start.CascadeStop, options.Start.ExitCodeFrom, stopFunc)
|
||||
code, err := printer.Run(context.Background(), options.Start.CascadeStop, options.Start.ExitCodeFrom, stopFunc)
|
||||
exitCode = code
|
||||
return err
|
||||
})
|
||||
|
||||
@@ -17,10 +17,7 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -69,6 +66,19 @@ func TestLocalComposeBuild(t *testing.T) {
|
||||
res.Assert(t, icmd.Expected{Out: `"FOO": "BAR"`})
|
||||
})
|
||||
|
||||
t.Run("build with multiple build-args ", func(t *testing.T) {
|
||||
// ensure local test run does not reuse previously build image
|
||||
c.RunDockerOrExitError("rmi", "-f", "multi-args_multiargs")
|
||||
cmd := c.NewDockerCmd("compose", "--project-directory", "fixtures/build-test/multi-args", "build")
|
||||
|
||||
icmd.RunCmd(cmd, func(cmd *icmd.Cmd) {
|
||||
cmd.Env = append(cmd.Env, "DOCKER_BUILDKIT=0")
|
||||
})
|
||||
|
||||
res := c.RunDockerCmd("image", "inspect", "multi-args_multiargs")
|
||||
res.Assert(t, icmd.Expected{Out: `"RESULT": "SUCCESS"`})
|
||||
})
|
||||
|
||||
t.Run("build as part of up", func(t *testing.T) {
|
||||
c.RunDockerOrExitError("rmi", "build-test_nginx")
|
||||
c.RunDockerOrExitError("rmi", "custom-nginx")
|
||||
@@ -107,41 +117,3 @@ func TestLocalComposeBuild(t *testing.T) {
|
||||
c.RunDockerCmd("rmi", "custom-nginx")
|
||||
})
|
||||
}
|
||||
|
||||
func TestLocalComposeBuildStaticDockerfilePath(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
|
||||
t.Run("build ddev-style compose files", func(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "ddev")
|
||||
assert.NilError(t, err)
|
||||
defer os.RemoveAll(dir) //nolint:errcheck
|
||||
|
||||
assert.NilError(t, ioutil.WriteFile(filepath.Join(dir, "compose.yaml"), []byte(`services:
|
||||
service1:
|
||||
build:
|
||||
context: `+dir+`/service1
|
||||
dockerfile: Dockerfile
|
||||
service2:
|
||||
build:
|
||||
context: `+dir+`/service2
|
||||
dockerfile: `+dir+`/service2/Dockerfile
|
||||
`), 0644))
|
||||
|
||||
assert.NilError(t, os.Mkdir(filepath.Join(dir, "service1"), 0700))
|
||||
assert.NilError(t, ioutil.WriteFile(filepath.Join(dir, "service1", "Dockerfile"), []byte(`FROM alpine
|
||||
RUN echo "hello"
|
||||
`), 0644))
|
||||
|
||||
assert.NilError(t, os.Mkdir(filepath.Join(dir, "service2"), 0700))
|
||||
assert.NilError(t, ioutil.WriteFile(filepath.Join(dir, "service2", "Dockerfile"), []byte(`FROM alpine
|
||||
RUN echo "world"
|
||||
`), 0644))
|
||||
|
||||
res := c.RunDockerCmd("compose", "-f", filepath.Join(dir, "compose.yaml"), "build")
|
||||
|
||||
res.Assert(t, icmd.Expected{Out: `RUN echo "hello"`})
|
||||
res.Assert(t, icmd.Expected{Out: `RUN echo "world"`})
|
||||
|
||||
c.RunDockerCmd("compose", "-f", filepath.Join(dir, "compose.yaml"), "down", "--rmi", "all")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -219,3 +219,25 @@ func TestCompatibility(t *testing.T) {
|
||||
c.RunDockerCmd("compose", "-p", projectName, "down")
|
||||
})
|
||||
}
|
||||
|
||||
func TestConvert(t *testing.T) {
|
||||
const projectName = "compose-e2e-convert"
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
|
||||
wd, err := os.Getwd()
|
||||
assert.NilError(t, err)
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "-f", "./fixtures/simple-build-test/compose.yaml", "-p", projectName, "convert")
|
||||
res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`services:
|
||||
nginx:
|
||||
build:
|
||||
context: %s
|
||||
dockerfile: Dockerfile
|
||||
networks:
|
||||
default: null
|
||||
networks:
|
||||
default:
|
||||
name: compose-e2e-convert_default`, filepath.Join(wd, "fixtures", "simple-build-test", "nginx-build")), ExitCode: 0})
|
||||
})
|
||||
}
|
||||
|
||||
19
pkg/e2e/fixtures/build-test/multi-args/Dockerfile
Normal file
19
pkg/e2e/fixtures/build-test/multi-args/Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
||||
# 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.
|
||||
|
||||
ARG IMAGE=666
|
||||
ARG TAG=666
|
||||
|
||||
FROM ${IMAGE}:${TAG}
|
||||
RUN echo "SUCCESS"
|
||||
9
pkg/e2e/fixtures/build-test/multi-args/compose.yaml
Normal file
9
pkg/e2e/fixtures/build-test/multi-args/compose.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
services:
|
||||
multiargs:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
IMAGE: alpine
|
||||
TAG: latest
|
||||
labels:
|
||||
- RESULT=SUCCESS
|
||||
@@ -1,3 +1,5 @@
|
||||
services:
|
||||
nginx:
|
||||
build: nginx-build
|
||||
build:
|
||||
context: nginx-build
|
||||
dockerfile: Dockerfile
|
||||
|
||||
@@ -104,9 +104,9 @@ func newE2eCLI(t *testing.T, binDir string) *E2eCLI {
|
||||
}
|
||||
|
||||
func dirContents(dir string) []string {
|
||||
res := []string{}
|
||||
var res []string
|
||||
_ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
res = append(res, filepath.Join(dir, path))
|
||||
res = append(res, path)
|
||||
return nil
|
||||
})
|
||||
return res
|
||||
|
||||
@@ -36,7 +36,7 @@ func TestComposeMetrics(t *testing.T) {
|
||||
res = c.RunDockerOrExitError("compose", "-f", "fixtures/wrong-composefile/compose.yaml", "up", "-d")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 15, Err: "services.simple Additional property wrongField is not allowed"})
|
||||
res = c.RunDockerOrExitError("compose", "up")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 14, Err: "can't find a suitable configuration file in this directory or any parent: not found"})
|
||||
res.Assert(t, icmd.Expected{ExitCode: 14, Err: "no configuration file provided: not found"})
|
||||
res = c.RunDockerOrExitError("compose", "up", "-f", "fixtures/wrong-composefile/compose.yaml")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 16, Err: "unknown shorthand flag: 'f' in -f"})
|
||||
res = c.RunDockerOrExitError("compose", "up", "--file", "fixtures/wrong-composefile/compose.yaml")
|
||||
|
||||
@@ -115,3 +115,19 @@ func TestIPAMConfig(t *testing.T) {
|
||||
_ = c.RunDockerCmd("compose", "--project-name", projectName, "down")
|
||||
})
|
||||
}
|
||||
|
||||
func TestNetworkModes(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
|
||||
const projectName = "network_mode_service_run"
|
||||
|
||||
t.Run("run with service mode dependency", func(t *testing.T) {
|
||||
res := c.RunDockerOrExitError("compose", "-f", "./fixtures/network-test/compose.yaml", "--project-name", projectName, "run", "mydb", "echo", "success")
|
||||
res.Assert(t, icmd.Expected{Out: "success"})
|
||||
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
_ = c.RunDockerCmd("compose", "--project-name", projectName, "down")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -38,12 +38,13 @@ type ttyWriter struct {
|
||||
repeated bool
|
||||
numLines int
|
||||
done chan bool
|
||||
mtx *sync.RWMutex
|
||||
mtx *sync.Mutex
|
||||
tailEvents []string
|
||||
}
|
||||
|
||||
func (w *ttyWriter) Start(ctx context.Context) error {
|
||||
ticker := time.NewTicker(100 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
@@ -188,7 +189,7 @@ func lineText(event Event, pad string, terminalWidth, statusPadding int, color b
|
||||
padding = 0
|
||||
}
|
||||
// calculate the max length for the status text, on errors it
|
||||
// is 2-3 lines long and breaks the line formating
|
||||
// is 2-3 lines long and breaks the line formatting
|
||||
maxStatusLen := terminalWidth - textLen - statusPadding - 15
|
||||
status := event.StatusText
|
||||
// in some cases (debugging under VS Code), terminalWidth is set to zero by goterm.Width() ; ensuring we don't tweak strings with negative char index
|
||||
|
||||
@@ -78,7 +78,7 @@ func TestLineTextSingleEvent(t *testing.T) {
|
||||
func TestErrorEvent(t *testing.T) {
|
||||
w := &ttyWriter{
|
||||
events: map[string]Event{},
|
||||
mtx: &sync.RWMutex{},
|
||||
mtx: &sync.Mutex{},
|
||||
}
|
||||
e := Event{
|
||||
ID: "id",
|
||||
|
||||
@@ -105,7 +105,7 @@ func NewWriter(out console.File) (Writer, error) {
|
||||
events: map[string]Event{},
|
||||
repeated: false,
|
||||
done: make(chan bool),
|
||||
mtx: &sync.RWMutex{},
|
||||
mtx: &sync.Mutex{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user