mirror of
https://github.com/docker/compose.git
synced 2026-02-13 12:09:29 +08:00
Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3394bf031b | ||
|
|
832a08f579 | ||
|
|
aadce87b16 | ||
|
|
b3207c455d | ||
|
|
769b7391ba | ||
|
|
149b882ebf | ||
|
|
c97e40e2b8 | ||
|
|
22e23bd4dc | ||
|
|
2dde5faeb8 | ||
|
|
f7825a56bf | ||
|
|
4cf075ea0a | ||
|
|
4f491ffa98 | ||
|
|
ea1c26d22a | ||
|
|
9a5fa05ad6 | ||
|
|
276c229458 | ||
|
|
eef448dc64 | ||
|
|
343117233b | ||
|
|
f599a8cdd2 | ||
|
|
63b06f5563 | ||
|
|
1d34661e91 | ||
|
|
0f9e6ab832 | ||
|
|
15c9651a3a | ||
|
|
4893a8b9ad | ||
|
|
97530790fa | ||
|
|
213c03f99a | ||
|
|
ebd7b761f2 | ||
|
|
ea48480d80 | ||
|
|
8151b59288 | ||
|
|
ec49baca56 | ||
|
|
7b9ad96240 | ||
|
|
9b67a48c33 | ||
|
|
0d0e12cc85 | ||
|
|
92fafccfb2 | ||
|
|
fee8aee8f0 | ||
|
|
40f5786e68 | ||
|
|
61e44da936 | ||
|
|
0bf7d1ea25 | ||
|
|
80ace63dfb | ||
|
|
27e90a3fdf | ||
|
|
3ca75bdf55 | ||
|
|
eb3074bbda | ||
|
|
f4fc010d6b | ||
|
|
693b9ef078 | ||
|
|
046879a4a2 | ||
|
|
7c79b23005 | ||
|
|
ad4cbee498 | ||
|
|
60256a875c | ||
|
|
45bd60c33a | ||
|
|
cf89fd1aa1 | ||
|
|
23fef850b9 | ||
|
|
12b73bea73 | ||
|
|
2e71440bee | ||
|
|
d49a68ecbf | ||
|
|
be83f63f26 | ||
|
|
9a9227ce64 | ||
|
|
024f8ebdc5 | ||
|
|
8c622da20b | ||
|
|
bbb2b76a14 |
@@ -30,6 +30,10 @@ linters:
|
||||
deny:
|
||||
- pkg: io/ioutil
|
||||
desc: io/ioutil package has been deprecated
|
||||
- pkg: golang.org/x/exp/maps
|
||||
desc: use stdlib maps package
|
||||
- pkg: golang.org/x/exp/slices
|
||||
desc: use stdlib slices package
|
||||
- pkg: gopkg.in/yaml.v2
|
||||
desc: compose-go uses yaml.v3
|
||||
gocritic:
|
||||
|
||||
149
cmd/compose/bridge.go
Normal file
149
cmd/compose/bridge.go
Normal file
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
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"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/pkg/bridge"
|
||||
)
|
||||
|
||||
func bridgeCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "bridge CMD [OPTIONS]",
|
||||
Short: "Convert compose files into another model",
|
||||
TraverseChildren: true,
|
||||
}
|
||||
cmd.AddCommand(
|
||||
convertCommand(p, dockerCli),
|
||||
transformersCommand(dockerCli),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func convertCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
|
||||
convertOpts := bridge.ConvertOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "convert",
|
||||
Short: "Convert compose files to Kubernetes manifests, Helm charts, or another model",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runConvert(ctx, dockerCli, p, convertOpts)
|
||||
}),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.StringVarP(&convertOpts.Output, "output", "o", "out", "The output directory for the Kubernetes resources")
|
||||
flags.StringArrayVarP(&convertOpts.Transformations, "transformation", "t", nil, "Transformation to apply to compose model (default: docker/compose-bridge-kubernetes)")
|
||||
flags.StringVar(&convertOpts.Templates, "templates", "", "Directory containing transformation templates")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runConvert(ctx context.Context, dockerCli command.Cli, p *ProjectOptions, opts bridge.ConvertOptions) error {
|
||||
project, _, err := p.ToProject(ctx, dockerCli, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bridge.Convert(ctx, dockerCli, project, opts)
|
||||
}
|
||||
|
||||
func transformersCommand(dockerCli command.Cli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "transformations CMD [OPTIONS]",
|
||||
Short: "Manage transformation images",
|
||||
}
|
||||
cmd.AddCommand(
|
||||
listTransformersCommand(dockerCli),
|
||||
createTransformerCommand(dockerCli),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func listTransformersCommand(dockerCli command.Cli) *cobra.Command {
|
||||
options := lsOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Short: "List available transformations",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
transformers, err := bridge.ListTransformers(ctx, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return displayTransformer(dockerCli, transformers, options)
|
||||
}),
|
||||
}
|
||||
cmd.Flags().StringVar(&options.Format, "format", "table", "Format the output. Values: [table | json]")
|
||||
cmd.Flags().BoolVarP(&options.Quiet, "quiet", "q", false, "Only display transformer names")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func displayTransformer(dockerCli command.Cli, transformers []image.Summary, options lsOptions) error {
|
||||
if options.Quiet {
|
||||
for _, t := range transformers {
|
||||
if len(t.RepoTags) > 0 {
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), t.RepoTags[0])
|
||||
} else {
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), t.ID)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return formatter.Print(transformers, options.Format, dockerCli.Out(),
|
||||
func(w io.Writer) {
|
||||
for _, img := range transformers {
|
||||
id := stringid.TruncateID(img.ID)
|
||||
size := units.HumanSizeWithPrecision(float64(img.Size), 3)
|
||||
repo, tag := "<none>", "<none>"
|
||||
if len(img.RepoTags) > 0 {
|
||||
ref, err := reference.ParseDockerRef(img.RepoTags[0])
|
||||
if err == nil {
|
||||
// ParseDockerRef will reject a local image ID
|
||||
repo = reference.FamiliarName(ref)
|
||||
if tagged, ok := ref.(reference.Tagged); ok {
|
||||
tag = tagged.Tag()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", id, repo, tag, size)
|
||||
}
|
||||
},
|
||||
"IMAGE ID", "REPO", "TAGS", "SIZE")
|
||||
}
|
||||
|
||||
func createTransformerCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var opts bridge.CreateTransformerOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "create [OPTION] PATH",
|
||||
Short: "Create a new transformation",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
opts.Dest = args[0]
|
||||
return bridge.CreateTransformer(ctx, dockerCli, opts)
|
||||
}),
|
||||
}
|
||||
cmd.Flags().StringVarP(&opts.From, "from", "f", "", "Existing transformation to copy (default: docker/compose-bridge-kubernetes)")
|
||||
return cmd
|
||||
}
|
||||
@@ -27,7 +27,6 @@ import (
|
||||
"github.com/docker/cli/cli/command"
|
||||
cliopts "github.com/docker/cli/opts"
|
||||
ui "github.com/docker/compose/v2/pkg/progress"
|
||||
buildkit "github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@@ -137,7 +136,7 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
flags.Bool("no-rm", false, "Do not remove intermediate containers after a successful build. DEPRECATED")
|
||||
flags.MarkHidden("no-rm") //nolint:errcheck
|
||||
flags.VarP(&opts.memory, "memory", "m", "Set memory limit for the build container. Not supported by BuildKit.")
|
||||
flags.StringVar(&p.Progress, "progress", string(buildkit.AutoMode), fmt.Sprintf(`Set type of ui output (%s)`, strings.Join(printerModes, ", ")))
|
||||
flags.StringVar(&p.Progress, "progress", "", fmt.Sprintf(`Set type of ui output (%s)`, strings.Join(printerModes, ", ")))
|
||||
flags.MarkHidden("progress") //nolint:errcheck
|
||||
flags.BoolVar(&opts.print, "print", false, "Print equivalent bake file")
|
||||
flags.BoolVar(&opts.check, "check", false, "Check build configuration")
|
||||
|
||||
@@ -47,7 +47,6 @@ import (
|
||||
ui "github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/remote"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
buildkit "github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/morikuni/aec"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -230,7 +229,7 @@ func (o *ProjectOptions) addProjectFlags(f *pflag.FlagSet) {
|
||||
f.StringVar(&o.ProjectDir, "project-directory", "", "Specify an alternate working directory\n(default: the path of the, first specified, Compose file)")
|
||||
f.StringVar(&o.WorkDir, "workdir", "", "DEPRECATED! USE --project-directory INSTEAD.\nSpecify an alternate working directory\n(default: the path of the, first specified, Compose file)")
|
||||
f.BoolVar(&o.Compatibility, "compatibility", false, "Run compose in backward compatibility mode")
|
||||
f.StringVar(&o.Progress, "progress", defaultStringVar(ComposeProgress, string(buildkit.AutoMode)), fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
|
||||
f.StringVar(&o.Progress, "progress", os.Getenv(ComposeProgress), fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
|
||||
f.BoolVar(&o.All, "all-resources", false, "Include all resources, even those not used by services")
|
||||
_ = f.MarkHidden("workdir")
|
||||
}
|
||||
@@ -242,14 +241,6 @@ func defaultStringArrayVar(env string) []string {
|
||||
})
|
||||
}
|
||||
|
||||
// get default value for a command line flag from the env variable, if the env variable is not set, it returns the provided default value 'def'
|
||||
func defaultStringVar(env, def string) string {
|
||||
if v, ok := os.LookupEnv(env); ok {
|
||||
return v
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func (o *ProjectOptions) projectOrName(ctx context.Context, dockerCli command.Cli, services ...string) (*types.Project, string, error) {
|
||||
name := o.ProjectName
|
||||
var project *types.Project
|
||||
@@ -516,8 +507,7 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
|
||||
}
|
||||
|
||||
switch opts.Progress {
|
||||
case ui.ModeAuto:
|
||||
ui.Mode = ui.ModeAuto
|
||||
case "", ui.ModeAuto:
|
||||
if ansi == "never" {
|
||||
ui.Mode = ui.ModePlain
|
||||
}
|
||||
@@ -645,6 +635,7 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
|
||||
watchCommand(&opts, dockerCli, backend),
|
||||
publishCommand(&opts, dockerCli, backend),
|
||||
alphaCommand(&opts, dockerCli, backend),
|
||||
bridgeCommand(&opts, dockerCli),
|
||||
)
|
||||
|
||||
c.Flags().SetInterspersed(false)
|
||||
|
||||
@@ -20,9 +20,12 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/go-units"
|
||||
@@ -30,7 +33,6 @@ import (
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
type imageOptions struct {
|
||||
@@ -76,7 +78,7 @@ func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service,
|
||||
if i := strings.IndexRune(img.ID, ':'); i >= 0 {
|
||||
id = id[i+1:]
|
||||
}
|
||||
if !utils.StringContains(ids, id) {
|
||||
if !slices.Contains(ids, id) {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
@@ -85,14 +87,42 @@ func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if opts.Format == "json" {
|
||||
|
||||
sort.Slice(images, func(i, j int) bool {
|
||||
return images[i].ContainerName < images[j].ContainerName
|
||||
})
|
||||
type img struct {
|
||||
ID string `json:"ID"`
|
||||
ContainerName string `json:"ContainerName"`
|
||||
Repository string `json:"Repository"`
|
||||
Tag string `json:"Tag"`
|
||||
Platform string `json:"Platform"`
|
||||
Size int64 `json:"Size"`
|
||||
LastTagTime time.Time `json:"LastTagTime"`
|
||||
}
|
||||
// Convert map to slice
|
||||
var imageList []img
|
||||
for ctr, i := range images {
|
||||
imageList = append(imageList, img{
|
||||
ContainerName: ctr,
|
||||
ID: i.ID,
|
||||
Repository: i.Repository,
|
||||
Tag: i.Tag,
|
||||
Platform: platforms.Format(i.Platform),
|
||||
Size: i.Size,
|
||||
LastTagTime: i.LastTagTime,
|
||||
})
|
||||
}
|
||||
json, err := formatter.ToJSON(imageList, "", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprintln(dockerCli.Out(), json)
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Print(images, opts.Format, dockerCli.Out(),
|
||||
func(w io.Writer) {
|
||||
for _, img := range images {
|
||||
for _, container := range slices.Sorted(maps.Keys(images)) {
|
||||
img := images[container]
|
||||
id := stringid.TruncateID(img.ID)
|
||||
size := units.HumanSizeWithPrecision(float64(img.Size), 3)
|
||||
repo := img.Repository
|
||||
@@ -103,8 +133,10 @@ func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service,
|
||||
if tag == "" {
|
||||
tag = "<none>"
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", img.ContainerName, repo, tag, id, size)
|
||||
created := units.HumanDuration(time.Now().UTC().Sub(img.LastTagTime)) + " ago"
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
|
||||
container, repo, tag, platforms.Format(img.Platform), id, size, created)
|
||||
}
|
||||
},
|
||||
"CONTAINER", "REPOSITORY", "TAG", "IMAGE ID", "SIZE")
|
||||
"CONTAINER", "REPOSITORY", "TAG", "PLATFORM", "IMAGE ID", "SIZE", "CREATED")
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
@@ -32,7 +33,6 @@ import (
|
||||
"github.com/docker/compose/v2/internal/tracing"
|
||||
ui "github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/prompt"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error {
|
||||
@@ -44,7 +44,7 @@ func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error {
|
||||
|
||||
// default platform only applies if the service doesn't specify
|
||||
if defaultPlatform != "" && service.Platform == "" {
|
||||
if len(service.Build.Platforms) > 0 && !utils.StringContains(service.Build.Platforms, defaultPlatform) {
|
||||
if len(service.Build.Platforms) > 0 && !slices.Contains(service.Build.Platforms, defaultPlatform) {
|
||||
return fmt.Errorf("service %q build.platforms does not support value set by DOCKER_DEFAULT_PLATFORM: %s", name, defaultPlatform)
|
||||
}
|
||||
service.Platform = defaultPlatform
|
||||
@@ -52,7 +52,7 @@ func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error {
|
||||
|
||||
if service.Platform != "" {
|
||||
if len(service.Build.Platforms) > 0 {
|
||||
if !utils.StringContains(service.Build.Platforms, service.Platform) {
|
||||
if !slices.Contains(service.Build.Platforms, service.Platform) {
|
||||
return fmt.Errorf("service %q build configuration does not support platform: %s", name, service.Platform)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
cliformatter "github.com/docker/cli/cli/command/formatter"
|
||||
@@ -101,7 +101,7 @@ func runPs(ctx context.Context, dockerCli command.Cli, backend api.Service, serv
|
||||
names := project.ServiceNames()
|
||||
if len(services) > 0 {
|
||||
for _, service := range services {
|
||||
if !utils.StringContains(names, service) {
|
||||
if !slices.Contains(names, service) {
|
||||
return fmt.Errorf("no such service: %s", service)
|
||||
}
|
||||
}
|
||||
@@ -139,7 +139,7 @@ func runPs(ctx context.Context, dockerCli command.Cli, backend api.Service, serv
|
||||
services := []string{}
|
||||
for _, c := range containers {
|
||||
s := c.Service
|
||||
if !utils.StringContains(services, s) {
|
||||
if !slices.Contains(services, s) {
|
||||
services = append(services, s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/cli"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/morikuni/aec"
|
||||
@@ -97,7 +98,7 @@ func (opts pullOptions) apply(project *types.Project, services []string) (*types
|
||||
}
|
||||
|
||||
func runPull(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pullOptions, services []string) error {
|
||||
project, _, err := opts.ToProject(ctx, dockerCli, services)
|
||||
project, _, err := opts.ToProject(ctx, dockerCli, services, cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -271,22 +271,6 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
return err
|
||||
}
|
||||
|
||||
err = progress.Run(ctx, func(ctx context.Context) error {
|
||||
var buildForDeps *api.BuildOptions
|
||||
if !createOpts.noBuild {
|
||||
// allow dependencies needing build to be implicitly selected
|
||||
bo, err := buildOpts.toAPIBuildOptions(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buildForDeps = &bo
|
||||
}
|
||||
return startDependencies(ctx, backend, *project, buildForDeps, options)
|
||||
}, dockerCli.Err())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
labels := types.Labels{}
|
||||
for _, s := range options.labels {
|
||||
parts := strings.SplitN(s, "=", 2)
|
||||
@@ -298,9 +282,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
|
||||
var buildForRun *api.BuildOptions
|
||||
if !createOpts.noBuild {
|
||||
// dependencies have already been started above, so only the service
|
||||
// being run might need to be built at this point
|
||||
bo, err := buildOpts.toAPIBuildOptions([]string{options.Service})
|
||||
bo, err := buildOpts.toAPIBuildOptions(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -314,7 +296,12 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
|
||||
// start container and attach to container streams
|
||||
runOpts := api.RunOptions{
|
||||
Build: buildForRun,
|
||||
CreateOptions: api.CreateOptions{
|
||||
Build: buildForRun,
|
||||
RemoveOrphans: options.removeOrphans,
|
||||
IgnoreOrphans: options.ignoreOrphans,
|
||||
QuietPull: options.quietPull,
|
||||
},
|
||||
Name: options.name,
|
||||
Service: options.Service,
|
||||
Command: options.Command,
|
||||
@@ -324,15 +311,14 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
Interactive: options.interactive,
|
||||
WorkingDir: options.workdir,
|
||||
User: options.user,
|
||||
CapAdd: options.capAdd.GetAll(),
|
||||
CapDrop: options.capDrop.GetAll(),
|
||||
CapAdd: options.capAdd.GetSlice(),
|
||||
CapDrop: options.capDrop.GetSlice(),
|
||||
Environment: environment.Values(),
|
||||
Entrypoint: options.entrypointCmd,
|
||||
Labels: labels,
|
||||
UseNetworkAliases: options.useAliases,
|
||||
NoDeps: options.noDeps,
|
||||
Index: 0,
|
||||
QuietPull: options.quietPull,
|
||||
}
|
||||
|
||||
for name, service := range project.Services {
|
||||
@@ -352,34 +338,3 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func startDependencies(ctx context.Context, backend api.Service, project types.Project, buildOpts *api.BuildOptions, options runOptions) error {
|
||||
dependencies := types.Services{}
|
||||
var requestedService types.ServiceConfig
|
||||
for name, service := range project.Services {
|
||||
if name != options.Service {
|
||||
dependencies[name] = service
|
||||
} else {
|
||||
requestedService = service
|
||||
}
|
||||
}
|
||||
|
||||
project.Services = dependencies
|
||||
project.DisabledServices[options.Service] = requestedService
|
||||
err := backend.Create(ctx, &project, api.CreateOptions{
|
||||
Build: buildOpts,
|
||||
IgnoreOrphans: options.ignoreOrphans,
|
||||
RemoveOrphans: options.removeOrphans,
|
||||
QuietPull: options.quietPull,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(dependencies) > 0 {
|
||||
return backend.Start(ctx, project.Name, api.StartOptions{
|
||||
Project: &project,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -19,14 +19,13 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -60,7 +59,7 @@ func scaleCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
|
||||
func runScale(ctx context.Context, dockerCli command.Cli, backend api.Service, opts scaleOptions, serviceReplicaTuples map[string]int) error {
|
||||
services := maps.Keys(serviceReplicaTuples)
|
||||
services := slices.Sorted(maps.Keys(serviceReplicaTuples))
|
||||
project, _, err := opts.ToProject(ctx, dockerCli, services)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -261,6 +261,7 @@ func runUp(
|
||||
return err
|
||||
}
|
||||
bo.Services = services
|
||||
bo.Deps = !upOptions.noDeps
|
||||
build = &bo
|
||||
}
|
||||
|
||||
|
||||
@@ -117,9 +117,10 @@ func runWatch(ctx context.Context, dockerCli command.Cli, backend api.Service, w
|
||||
}
|
||||
|
||||
consumer := formatter.NewLogConsumer(ctx, dockerCli.Out(), dockerCli.Err(), false, false, false)
|
||||
return backend.Watch(ctx, project, services, api.WatchOptions{
|
||||
Build: &build,
|
||||
LogTo: consumer,
|
||||
Prune: watchOpts.prune,
|
||||
return backend.Watch(ctx, project, api.WatchOptions{
|
||||
Build: &build,
|
||||
LogTo: consumer,
|
||||
Prune: watchOpts.prune,
|
||||
Services: services,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"reflect"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -29,9 +30,7 @@ import (
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/compose/v2/internal/tracing"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/watch"
|
||||
"github.com/eiannone/keyboard"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
)
|
||||
|
||||
@@ -71,26 +70,14 @@ func (ke *KeyboardError) error() string {
|
||||
}
|
||||
|
||||
type KeyboardWatch struct {
|
||||
Watcher watch.Notify
|
||||
Watching bool
|
||||
WatchFn func(ctx context.Context, doneCh chan bool, project *types.Project, services []string, options api.WatchOptions) error
|
||||
Ctx context.Context
|
||||
Cancel context.CancelFunc
|
||||
Watching bool
|
||||
Watcher Toggle
|
||||
IsConfigured bool
|
||||
}
|
||||
|
||||
func (kw *KeyboardWatch) isWatching() bool {
|
||||
return kw.Watching
|
||||
}
|
||||
|
||||
func (kw *KeyboardWatch) switchWatching() {
|
||||
kw.Watching = !kw.Watching
|
||||
}
|
||||
|
||||
func (kw *KeyboardWatch) newContext(ctx context.Context) context.CancelFunc {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
kw.Ctx = ctx
|
||||
kw.Cancel = cancel
|
||||
return cancel
|
||||
type Toggle interface {
|
||||
Start(context.Context) error
|
||||
Stop() error
|
||||
}
|
||||
|
||||
type KEYBOARD_LOG_LEVEL int
|
||||
@@ -105,36 +92,25 @@ type LogKeyboard struct {
|
||||
kError KeyboardError
|
||||
Watch KeyboardWatch
|
||||
IsDockerDesktopActive bool
|
||||
IsWatchConfigured bool
|
||||
logLevel KEYBOARD_LOG_LEVEL
|
||||
signalChannel chan<- os.Signal
|
||||
}
|
||||
|
||||
var (
|
||||
KeyboardManager *LogKeyboard
|
||||
eg multierror.Group
|
||||
)
|
||||
// FIXME(ndeloof) we should avoid use of such a global reference. see use in logConsumer
|
||||
var KeyboardManager *LogKeyboard
|
||||
|
||||
func NewKeyboardManager(ctx context.Context, isDockerDesktopActive, isWatchConfigured bool,
|
||||
sc chan<- os.Signal,
|
||||
watchFn func(ctx context.Context,
|
||||
doneCh chan bool,
|
||||
project *types.Project,
|
||||
services []string,
|
||||
options api.WatchOptions,
|
||||
) error,
|
||||
) {
|
||||
km := LogKeyboard{}
|
||||
km.IsDockerDesktopActive = isDockerDesktopActive
|
||||
km.IsWatchConfigured = isWatchConfigured
|
||||
km.logLevel = INFO
|
||||
|
||||
km.Watch.Watching = false
|
||||
km.Watch.WatchFn = watchFn
|
||||
|
||||
km.signalChannel = sc
|
||||
|
||||
KeyboardManager = &km
|
||||
func NewKeyboardManager(isDockerDesktopActive bool, sc chan<- os.Signal, w bool, watcher Toggle) *LogKeyboard {
|
||||
KeyboardManager = &LogKeyboard{
|
||||
Watch: KeyboardWatch{
|
||||
Watching: w,
|
||||
Watcher: watcher,
|
||||
IsConfigured: !reflect.ValueOf(watcher).IsNil(),
|
||||
},
|
||||
IsDockerDesktopActive: isDockerDesktopActive,
|
||||
logLevel: INFO,
|
||||
signalChannel: sc,
|
||||
}
|
||||
return KeyboardManager
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) ClearKeyboardInfo() {
|
||||
@@ -233,48 +209,51 @@ func (lk *LogKeyboard) openDockerDesktop(ctx context.Context, project *types.Pro
|
||||
if !lk.IsDockerDesktopActive {
|
||||
return
|
||||
}
|
||||
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/gui", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
link := fmt.Sprintf("docker-desktop://dashboard/apps/%s", project.Name)
|
||||
err := open.Run(link)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not open Docker Desktop")
|
||||
lk.keyboardError("View", err)
|
||||
}
|
||||
return err
|
||||
}),
|
||||
)
|
||||
go func() {
|
||||
_ = tracing.EventWrapFuncForErrGroup(ctx, "menu/gui", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
link := fmt.Sprintf("docker-desktop://dashboard/apps/%s", project.Name)
|
||||
err := open.Run(link)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not open Docker Desktop")
|
||||
lk.keyboardError("View", err)
|
||||
}
|
||||
return err
|
||||
})()
|
||||
}()
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) openDDComposeUI(ctx context.Context, project *types.Project) {
|
||||
if !lk.IsDockerDesktopActive {
|
||||
return
|
||||
}
|
||||
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/gui/composeview", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
link := fmt.Sprintf("docker-desktop://dashboard/docker-compose/%s", project.Name)
|
||||
err := open.Run(link)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not open Docker Desktop Compose UI")
|
||||
lk.keyboardError("View Config", err)
|
||||
}
|
||||
return err
|
||||
}),
|
||||
)
|
||||
go func() {
|
||||
_ = tracing.EventWrapFuncForErrGroup(ctx, "menu/gui/composeview", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
link := fmt.Sprintf("docker-desktop://dashboard/docker-compose/%s", project.Name)
|
||||
err := open.Run(link)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not open Docker Desktop Compose UI")
|
||||
lk.keyboardError("View Config", err)
|
||||
}
|
||||
return err
|
||||
})()
|
||||
}()
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) openDDWatchDocs(ctx context.Context, project *types.Project) {
|
||||
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/gui/watch", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
link := fmt.Sprintf("docker-desktop://dashboard/docker-compose/%s/watch", project.Name)
|
||||
err := open.Run(link)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not open Docker Desktop Compose UI")
|
||||
lk.keyboardError("Watch Docs", err)
|
||||
}
|
||||
return err
|
||||
}),
|
||||
)
|
||||
go func() {
|
||||
_ = tracing.EventWrapFuncForErrGroup(ctx, "menu/gui/watch", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
link := fmt.Sprintf("docker-desktop://dashboard/docker-compose/%s/watch", project.Name)
|
||||
err := open.Run(link)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not open Docker Desktop Compose UI")
|
||||
lk.keyboardError("Watch Docs", err)
|
||||
}
|
||||
return err
|
||||
})()
|
||||
}()
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) keyboardError(prefix string, err error) {
|
||||
@@ -288,58 +267,54 @@ func (lk *LogKeyboard) keyboardError(prefix string, err error) {
|
||||
}()
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) StartWatch(ctx context.Context, doneCh chan bool, project *types.Project, options api.UpOptions) {
|
||||
if !lk.IsWatchConfigured {
|
||||
func (lk *LogKeyboard) ToggleWatch(ctx context.Context, options api.UpOptions) {
|
||||
if !lk.Watch.IsConfigured {
|
||||
return
|
||||
}
|
||||
lk.Watch.switchWatching()
|
||||
if !lk.Watch.isWatching() {
|
||||
lk.Watch.Cancel()
|
||||
if lk.Watch.Watching {
|
||||
err := lk.Watch.Watcher.Stop()
|
||||
if err != nil {
|
||||
options.Start.Attach.Err(api.WatchLogger, err.Error())
|
||||
} else {
|
||||
lk.Watch.Watching = false
|
||||
}
|
||||
} else {
|
||||
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/watch", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
if options.Create.Build == nil {
|
||||
err := fmt.Errorf("cannot run watch mode with flag --no-build")
|
||||
lk.keyboardError("Watch", err)
|
||||
go func() {
|
||||
_ = tracing.EventWrapFuncForErrGroup(ctx, "menu/watch", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
err := lk.Watch.Watcher.Start(ctx)
|
||||
if err != nil {
|
||||
options.Start.Attach.Err(api.WatchLogger, err.Error())
|
||||
} else {
|
||||
lk.Watch.Watching = true
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
lk.Watch.newContext(ctx)
|
||||
buildOpts := *options.Create.Build
|
||||
buildOpts.Quiet = true
|
||||
err := lk.Watch.WatchFn(lk.Watch.Ctx, doneCh, project, options.Start.Services, api.WatchOptions{
|
||||
Build: &buildOpts,
|
||||
LogTo: options.Start.Attach,
|
||||
})
|
||||
if err != nil {
|
||||
lk.Watch.switchWatching()
|
||||
options.Start.Attach.Err(api.WatchLogger, err.Error())
|
||||
}
|
||||
return err
|
||||
}))
|
||||
})()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) HandleKeyEvents(event keyboard.KeyEvent, ctx context.Context, doneCh chan bool, project *types.Project, options api.UpOptions) {
|
||||
func (lk *LogKeyboard) HandleKeyEvents(ctx context.Context, event keyboard.KeyEvent, project *types.Project, options api.UpOptions) {
|
||||
switch kRune := event.Rune; kRune {
|
||||
case 'v':
|
||||
lk.openDockerDesktop(ctx, project)
|
||||
case 'w':
|
||||
if !lk.IsWatchConfigured {
|
||||
if !lk.Watch.IsConfigured {
|
||||
// we try to open watch docs if DD is installed
|
||||
if lk.IsDockerDesktopActive {
|
||||
lk.openDDWatchDocs(ctx, project)
|
||||
}
|
||||
// either way we mark menu/watch as an error
|
||||
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/watch", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
err := fmt.Errorf("watch is not yet configured. Learn more: %s", ansiColor(CYAN, "https://docs.docker.com/compose/file-watch/"))
|
||||
lk.keyboardError("Watch", err)
|
||||
return err
|
||||
}))
|
||||
return
|
||||
go func() {
|
||||
_ = tracing.EventWrapFuncForErrGroup(ctx, "menu/watch", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
err := fmt.Errorf("watch is not yet configured. Learn more: %s", ansiColor(CYAN, "https://docs.docker.com/compose/file-watch/"))
|
||||
lk.keyboardError("Watch", err)
|
||||
return err
|
||||
})()
|
||||
}()
|
||||
}
|
||||
lk.StartWatch(ctx, doneCh, project, options)
|
||||
lk.ToggleWatch(ctx, options)
|
||||
case 'o':
|
||||
lk.openDDComposeUI(ctx, project)
|
||||
}
|
||||
@@ -350,10 +325,6 @@ func (lk *LogKeyboard) HandleKeyEvents(event keyboard.KeyEvent, ctx context.Cont
|
||||
ShowCursor()
|
||||
|
||||
lk.logLevel = NONE
|
||||
if lk.Watch.Watching && lk.Watch.Cancel != nil {
|
||||
lk.Watch.Cancel()
|
||||
_ = eg.Wait().ErrorOrNil() // Need to print this ?
|
||||
}
|
||||
// will notify main thread to kill and will handle gracefully
|
||||
lk.signalChannel <- syscall.SIGINT
|
||||
case keyboard.KeyEnter:
|
||||
|
||||
@@ -17,11 +17,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -43,16 +45,27 @@ func composeCommand() *cobra.Command {
|
||||
TraverseChildren: true,
|
||||
}
|
||||
c.PersistentFlags().String("project-name", "", "compose project name") // unused
|
||||
c.AddCommand(&cobra.Command{
|
||||
upCmd := &cobra.Command{
|
||||
Use: "up",
|
||||
Run: up,
|
||||
Args: cobra.ExactArgs(1),
|
||||
})
|
||||
c.AddCommand(&cobra.Command{
|
||||
}
|
||||
upCmd.Flags().String("type", "", "Database type (mysql, postgres, etc.)")
|
||||
_ = upCmd.MarkFlagRequired("type")
|
||||
upCmd.Flags().Int("size", 10, "Database size in GB")
|
||||
upCmd.Flags().String("name", "", "Name of the database to be created")
|
||||
_ = upCmd.MarkFlagRequired("name")
|
||||
|
||||
downCmd := &cobra.Command{
|
||||
Use: "down",
|
||||
Run: down,
|
||||
Args: cobra.ExactArgs(1),
|
||||
})
|
||||
}
|
||||
downCmd.Flags().String("name", "", "Name of the database to be deleted")
|
||||
_ = downCmd.MarkFlagRequired("name")
|
||||
|
||||
c.AddCommand(upCmd, downCmd)
|
||||
c.AddCommand(metadataCommand(upCmd, downCmd))
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -72,3 +85,58 @@ func up(_ *cobra.Command, args []string) {
|
||||
func down(_ *cobra.Command, _ []string) {
|
||||
fmt.Printf(`{ "type": "error", "message": "Permission error" }%s`, lineSeparator)
|
||||
}
|
||||
|
||||
func metadataCommand(upCmd, downCmd *cobra.Command) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "metadata",
|
||||
Run: func(cmd *cobra.Command, _ []string) {
|
||||
metadata(upCmd, downCmd)
|
||||
},
|
||||
Args: cobra.NoArgs,
|
||||
}
|
||||
}
|
||||
|
||||
func metadata(upCmd, downCmd *cobra.Command) {
|
||||
metadata := ProviderMetadata{}
|
||||
metadata.Description = "Manage services on AwesomeCloud"
|
||||
metadata.Up = commandParameters(upCmd)
|
||||
metadata.Down = commandParameters(downCmd)
|
||||
jsonMetadata, err := json.Marshal(metadata)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(jsonMetadata))
|
||||
}
|
||||
|
||||
func commandParameters(cmd *cobra.Command) CommandMetadata {
|
||||
cmdMetadata := CommandMetadata{}
|
||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||
_, isRequired := f.Annotations[cobra.BashCompOneRequiredFlag]
|
||||
cmdMetadata.Parameters = append(cmdMetadata.Parameters, Metadata{
|
||||
Name: f.Name,
|
||||
Description: f.Usage,
|
||||
Required: isRequired,
|
||||
Type: f.Value.Type(),
|
||||
Default: f.DefValue,
|
||||
})
|
||||
})
|
||||
return cmdMetadata
|
||||
}
|
||||
|
||||
type ProviderMetadata struct {
|
||||
Description string `json:"description"`
|
||||
Up CommandMetadata `json:"up"`
|
||||
Down CommandMetadata `json:"down"`
|
||||
}
|
||||
|
||||
type CommandMetadata struct {
|
||||
Parameters []Metadata `json:"parameters"`
|
||||
}
|
||||
|
||||
type Metadata struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Required bool `json:"required"`
|
||||
Type string `json:"type"`
|
||||
Default string `json:"default,omitempty"`
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ the resource(s) needed to run a service.
|
||||
options:
|
||||
type: mysql
|
||||
size: 256
|
||||
name: myAwesomeCloudDB
|
||||
```
|
||||
|
||||
`provider.type` tells Compose the binary to run, which can be either:
|
||||
@@ -104,8 +105,72 @@ into its runtime environment.
|
||||
## Down lifecycle
|
||||
|
||||
`down` lifecycle is equivalent to `up` with the `<provider> compose --project-name <NAME> down <SERVICE>` command.
|
||||
The provider is responsible for releasing all resources associated with the service.
|
||||
The provider is responsible for releasing all resources associated with the service.
|
||||
|
||||
## Provide metadata about options
|
||||
|
||||
Compose extensions *MAY* optionally implement a `metadata` subcommand to provide information about the parameters accepted by the `up` and `down` commands.
|
||||
|
||||
The `metadata` subcommand takes no parameters and returns a JSON structure on the `stdout` channel that describes the parameters accepted by both the `up` and `down` commands, including whether each parameter is mandatory or optional.
|
||||
|
||||
```console
|
||||
awesomecloud compose metadata
|
||||
```
|
||||
|
||||
The expected JSON output format is:
|
||||
```json
|
||||
{
|
||||
"description": "Manage services on AwesomeCloud",
|
||||
"up": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "type",
|
||||
"description": "Database type (mysql, postgres, etc.)",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "size",
|
||||
"description": "Database size in GB",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
"default": "10"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Name of the database to be created",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"down": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Name of the database to be removed",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
The top elements are:
|
||||
- `description`: Human-readable description of the provider
|
||||
- `up`: Object describing the parameters accepted by the `up` command
|
||||
- `down`: Object describing the parameters accepted by the `down` command
|
||||
|
||||
And for each command parameter, you should include the following properties:
|
||||
- `name`: The parameter name (without `--` prefix)
|
||||
- `description`: Human-readable description of the parameter
|
||||
- `required`: Boolean indicating if the parameter is mandatory
|
||||
- `type`: Parameter type (`string`, `integer`, `boolean`, etc.)
|
||||
- `default`: Default value (optional, only for non-required parameters)
|
||||
- `enum`: List of possible values supported by the parameter separated by `,` (optional, only for parameters with a limited set of values)
|
||||
|
||||
This metadata allows Compose and other tools to understand the provider's interface and provide better user experience, such as validation, auto-completion, and documentation generation.
|
||||
|
||||
## Examples
|
||||
|
||||
See [example](examples/provider.go) for illustration on implementing this API in a command line
|
||||
See [example](examples/provider.go) for illustration on implementing this API in a command line
|
||||
|
||||
@@ -12,6 +12,7 @@ Define and run multi-container applications with Docker
|
||||
| Name | Description |
|
||||
|:--------------------------------|:----------------------------------------------------------------------------------------|
|
||||
| [`attach`](compose_attach.md) | Attach local standard input, output, and error streams to a service's running container |
|
||||
| [`bridge`](compose_bridge.md) | Convert compose files into another model |
|
||||
| [`build`](compose_build.md) | Build or rebuild services |
|
||||
| [`commit`](compose_commit.md) | Create a new image from a service container's changes |
|
||||
| [`config`](compose_config.md) | Parse, resolve and render compose file in canonical format |
|
||||
@@ -58,7 +59,7 @@ Define and run multi-container applications with Docker
|
||||
| `-f`, `--file` | `stringArray` | | Compose configuration files |
|
||||
| `--parallel` | `int` | `-1` | Control max parallelism, -1 for unlimited |
|
||||
| `--profile` | `stringArray` | | Specify a profile to enable |
|
||||
| `--progress` | `string` | `auto` | Set type of progress output (auto, tty, plain, json, quiet) |
|
||||
| `--progress` | `string` | | Set type of progress output (auto, tty, plain, json, quiet) |
|
||||
| `--project-directory` | `string` | | Specify an alternate working directory<br>(default: the path of the, first specified, Compose file) |
|
||||
| `-p`, `--project-name` | `string` | | Project name |
|
||||
|
||||
|
||||
22
docs/reference/compose_bridge.md
Normal file
22
docs/reference/compose_bridge.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# docker compose bridge
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Convert compose files into another model
|
||||
|
||||
### Subcommands
|
||||
|
||||
| Name | Description |
|
||||
|:-------------------------------------------------------|:-----------------------------------------------------------------------------|
|
||||
| [`convert`](compose_bridge_convert.md) | Convert compose files to Kubernetes manifests, Helm charts, or another model |
|
||||
| [`transformations`](compose_bridge_transformations.md) | Manage transformation images |
|
||||
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------|:-------|:--------|:--------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
17
docs/reference/compose_bridge_convert.md
Normal file
17
docs/reference/compose_bridge_convert.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# docker compose bridge convert
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Convert compose files to Kubernetes manifests, Helm charts, or another model
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:-------------------------|:--------------|:--------|:-------------------------------------------------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `-o`, `--output` | `string` | `out` | The output directory for the Kubernetes resources |
|
||||
| `--templates` | `string` | | Directory containing transformation templates |
|
||||
| `-t`, `--transformation` | `stringArray` | | Transformation to apply to compose model (default: docker/compose-bridge-kubernetes) |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
22
docs/reference/compose_bridge_transformations.md
Normal file
22
docs/reference/compose_bridge_transformations.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# docker compose bridge transformations
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Manage transformation images
|
||||
|
||||
### Subcommands
|
||||
|
||||
| Name | Description |
|
||||
|:-----------------------------------------------------|:-------------------------------|
|
||||
| [`create`](compose_bridge_transformations_create.md) | Create a new transformation |
|
||||
| [`list`](compose_bridge_transformations_list.md) | List available transformations |
|
||||
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------|:-------|:--------|:--------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
15
docs/reference/compose_bridge_transformations_create.md
Normal file
15
docs/reference/compose_bridge_transformations_create.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# docker compose bridge transformations create
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Create a new transformation
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------|:---------|:--------|:----------------------------------------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `-f`, `--from` | `string` | | Existing transformation to copy (default: docker/compose-bridge-kubernetes) |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
20
docs/reference/compose_bridge_transformations_list.md
Normal file
20
docs/reference/compose_bridge_transformations_list.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# docker compose bridge transformations list
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
List available transformations
|
||||
|
||||
### Aliases
|
||||
|
||||
`docker compose bridge transformations list`, `docker compose bridge transformations ls`
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------|:---------|:--------|:-------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--format` | `string` | `table` | Format the output. Values: [table \| json] |
|
||||
| `-q`, `--quiet` | `bool` | | Only display transformer names |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -6,6 +6,7 @@ pname: docker
|
||||
plink: docker.yaml
|
||||
cname:
|
||||
- docker compose attach
|
||||
- docker compose bridge
|
||||
- docker compose build
|
||||
- docker compose commit
|
||||
- docker compose config
|
||||
@@ -40,6 +41,7 @@ cname:
|
||||
- docker compose watch
|
||||
clink:
|
||||
- docker_compose_attach.yaml
|
||||
- docker_compose_bridge.yaml
|
||||
- docker_compose_build.yaml
|
||||
- docker_compose_commit.yaml
|
||||
- docker_compose_config.yaml
|
||||
@@ -167,7 +169,6 @@ options:
|
||||
swarm: false
|
||||
- option: progress
|
||||
value_type: string
|
||||
default_value: auto
|
||||
description: Set type of progress output (auto, tty, plain, json, quiet)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
|
||||
@@ -45,7 +45,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: true
|
||||
kubernetes: false
|
||||
|
||||
@@ -58,7 +58,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: true
|
||||
kubernetes: false
|
||||
|
||||
@@ -69,7 +69,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: true
|
||||
kubernetes: false
|
||||
|
||||
29
docs/reference/docker_compose_bridge.yaml
Normal file
29
docs/reference/docker_compose_bridge.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
command: docker compose bridge
|
||||
short: Convert compose files into another model
|
||||
long: Convert compose files into another model
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
cname:
|
||||
- docker compose bridge convert
|
||||
- docker compose bridge transformations
|
||||
clink:
|
||||
- docker_compose_bridge_convert.yaml
|
||||
- docker_compose_bridge_transformations.yaml
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Execute command in dry run mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
|
||||
59
docs/reference/docker_compose_bridge_convert.yaml
Normal file
59
docs/reference/docker_compose_bridge_convert.yaml
Normal file
@@ -0,0 +1,59 @@
|
||||
command: docker compose bridge convert
|
||||
short: |
|
||||
Convert compose files to Kubernetes manifests, Helm charts, or another model
|
||||
long: |
|
||||
Convert compose files to Kubernetes manifests, Helm charts, or another model
|
||||
usage: docker compose bridge convert
|
||||
pname: docker compose bridge
|
||||
plink: docker_compose_bridge.yaml
|
||||
options:
|
||||
- option: output
|
||||
shorthand: o
|
||||
value_type: string
|
||||
default_value: out
|
||||
description: The output directory for the Kubernetes resources
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: templates
|
||||
value_type: string
|
||||
description: Directory containing transformation templates
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: transformation
|
||||
shorthand: t
|
||||
value_type: stringArray
|
||||
default_value: '[]'
|
||||
description: |
|
||||
Transformation to apply to compose model (default: docker/compose-bridge-kubernetes)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Execute command in dry run mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
|
||||
29
docs/reference/docker_compose_bridge_transformations.yaml
Normal file
29
docs/reference/docker_compose_bridge_transformations.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
command: docker compose bridge transformations
|
||||
short: Manage transformation images
|
||||
long: Manage transformation images
|
||||
pname: docker compose bridge
|
||||
plink: docker_compose_bridge.yaml
|
||||
cname:
|
||||
- docker compose bridge transformations create
|
||||
- docker compose bridge transformations list
|
||||
clink:
|
||||
- docker_compose_bridge_transformations_create.yaml
|
||||
- docker_compose_bridge_transformations_list.yaml
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Execute command in dry run mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
command: docker compose bridge transformations create
|
||||
short: Create a new transformation
|
||||
long: Create a new transformation
|
||||
usage: docker compose bridge transformations create [OPTION] PATH
|
||||
pname: docker compose bridge transformations
|
||||
plink: docker_compose_bridge_transformations.yaml
|
||||
options:
|
||||
- option: from
|
||||
shorthand: f
|
||||
value_type: string
|
||||
description: |
|
||||
Existing transformation to copy (default: docker/compose-bridge-kubernetes)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Execute command in dry run mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
command: docker compose bridge transformations list
|
||||
aliases: docker compose bridge transformations list, docker compose bridge transformations ls
|
||||
short: List available transformations
|
||||
long: List available transformations
|
||||
usage: docker compose bridge transformations list
|
||||
pname: docker compose bridge transformations
|
||||
plink: docker_compose_bridge_transformations.yaml
|
||||
options:
|
||||
- option: format
|
||||
value_type: string
|
||||
default_value: table
|
||||
description: 'Format the output. Values: [table | json]'
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: quiet
|
||||
shorthand: q
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Only display transformer names
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Execute command in dry run mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
|
||||
@@ -118,7 +118,6 @@ options:
|
||||
swarm: false
|
||||
- option: progress
|
||||
value_type: string
|
||||
default_value: auto
|
||||
description: Set type of ui output (auto, tty, plain, json, quiet)
|
||||
deprecated: false
|
||||
hidden: true
|
||||
|
||||
38
go.mod
38
go.mod
@@ -8,15 +8,16 @@ require (
|
||||
github.com/Microsoft/go-winio v0.6.2
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
github.com/buger/goterm v1.0.4
|
||||
github.com/compose-spec/compose-go/v2 v2.6.4
|
||||
github.com/containerd/containerd/v2 v2.1.1
|
||||
github.com/compose-spec/compose-go/v2 v2.6.5
|
||||
github.com/containerd/containerd/v2 v2.1.3
|
||||
github.com/containerd/errdefs v1.0.0
|
||||
github.com/containerd/platforms v1.0.0-rc.1
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/docker/buildx v0.24.0
|
||||
github.com/docker/cli v28.1.1+incompatible
|
||||
github.com/docker/cli-docs-tool v0.9.0
|
||||
github.com/docker/docker v28.1.1+incompatible
|
||||
github.com/docker/buildx v0.25.0
|
||||
github.com/docker/cli v28.2.2+incompatible
|
||||
github.com/docker/cli-docs-tool v0.10.0
|
||||
github.com/docker/docker v28.2.2+incompatible
|
||||
github.com/docker/go-connections v0.5.0
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203
|
||||
@@ -28,7 +29,7 @@ require (
|
||||
github.com/mattn/go-shellwords v1.0.12
|
||||
github.com/mitchellh/go-ps v1.0.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/moby/buildkit v0.22.0
|
||||
github.com/moby/buildkit v0.23.1
|
||||
github.com/moby/go-archive v0.1.0
|
||||
github.com/moby/patternmatcher v0.6.0
|
||||
github.com/moby/sys/atomicwriter v0.1.0
|
||||
@@ -42,7 +43,6 @@ require (
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/pflag v1.0.6
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/theupdateframework/notary v0.7.0
|
||||
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0
|
||||
go.opentelemetry.io/otel v1.35.0
|
||||
@@ -53,10 +53,9 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.35.0
|
||||
go.uber.org/goleak v1.3.0
|
||||
go.uber.org/mock v0.5.2
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
|
||||
golang.org/x/sync v0.14.0
|
||||
golang.org/x/sync v0.15.0
|
||||
golang.org/x/sys v0.33.0
|
||||
google.golang.org/grpc v1.72.1
|
||||
google.golang.org/grpc v1.73.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gotest.tools/v3 v3.5.2
|
||||
tags.cncf.io/container-device-interface v1.0.1
|
||||
@@ -83,10 +82,9 @@ require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/containerd/console v1.0.4 // indirect
|
||||
github.com/containerd/console v1.0.5 // indirect
|
||||
github.com/containerd/containerd/api v1.9.0 // indirect
|
||||
github.com/containerd/continuity v0.4.5 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/ttrpc v1.2.7 // indirect
|
||||
@@ -119,7 +117,7 @@ require (
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/in-toto/in-toto-golang v0.5.0 // indirect
|
||||
github.com/in-toto/in-toto-golang v0.9.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
@@ -159,12 +157,14 @@ require (
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.6.0 // indirect
|
||||
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b // indirect
|
||||
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20250417144416-3f76f8130144 // indirect
|
||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f // indirect
|
||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 // indirect
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
|
||||
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
@@ -187,8 +187,8 @@ require (
|
||||
golang.org/x/term v0.31.0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
|
||||
82
go.sum
82
go.sum
@@ -80,16 +80,16 @@ github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e
|
||||
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
|
||||
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
|
||||
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
|
||||
github.com/compose-spec/compose-go/v2 v2.6.4 h1:Gjv6x8eAhqwwWvoXIo0oZ4bDQBh0OMwdU7LUL9PDLiM=
|
||||
github.com/compose-spec/compose-go/v2 v2.6.4/go.mod h1:vPlkN0i+0LjLf9rv52lodNMUTJF5YHVfHVGLLIP67NA=
|
||||
github.com/compose-spec/compose-go/v2 v2.6.5 h1:H7xP5OMKdkN2p0brx01slxIU6dE/q6ybbG+jozPtIqk=
|
||||
github.com/compose-spec/compose-go/v2 v2.6.5/go.mod h1:TmjkIB9W73fwVxkYY+u2uhMbMUakjiif79DlYgXsyvU=
|
||||
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
|
||||
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
|
||||
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
|
||||
github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc=
|
||||
github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/containerd/containerd/api v1.9.0 h1:HZ/licowTRazus+wt9fM6r/9BQO7S0vD5lMcWspGIg0=
|
||||
github.com/containerd/containerd/api v1.9.0/go.mod h1:GhghKFmTR3hNtyznBoQ0EMWr9ju5AqHjcZPsSpTKutI=
|
||||
github.com/containerd/containerd/v2 v2.1.1 h1:znnkm7Ajz8lg8BcIPMhc/9yjBRN3B+OkNKqKisKfwwM=
|
||||
github.com/containerd/containerd/v2 v2.1.1/go.mod h1:zIfkQj4RIodclYQkX7GSSswSwgP8d/XxDOtOAoSDIGU=
|
||||
github.com/containerd/containerd/v2 v2.1.3 h1:eMD2SLcIQPdMlnlNF6fatlrlRLAeDaiGPGwmRKLZKNs=
|
||||
github.com/containerd/containerd/v2 v2.1.3/go.mod h1:8C5QV9djwsYDNhxfTCFjWtTBZrqjditQ4/ghHSYjnHM=
|
||||
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
|
||||
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
|
||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||
@@ -100,8 +100,8 @@ github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY
|
||||
github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/nydus-snapshotter v0.15.0 h1:RqZRs1GPeM6T3wmuxJV9u+2Rg4YETVMwTmiDeX+iWC8=
|
||||
github.com/containerd/nydus-snapshotter v0.15.0/go.mod h1:biq0ijpeZe0I5yZFSJyHzFSjjRZQ7P7y/OuHyd7hYOw=
|
||||
github.com/containerd/nydus-snapshotter v0.15.2 h1:qsHI4M+Wwrf6Jr4eBqhNx8qh+YU0dSiJ+WPmcLFWNcg=
|
||||
github.com/containerd/nydus-snapshotter v0.15.2/go.mod h1:FfwH2KBkNYoisK/e+KsmNr7xTU53DmnavQHMFOcXwfM=
|
||||
github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E=
|
||||
github.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4=
|
||||
github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y=
|
||||
@@ -125,17 +125,19 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/buildx v0.24.0 h1:qiD+xktY+Fs3R79oz8M+7pbhip78qGLx6LBuVmyb+64=
|
||||
github.com/docker/buildx v0.24.0/go.mod h1:vYkdBUBjFo/i5vUE0mkajGlk03gE0T/HaGXXhgIxo8E=
|
||||
github.com/docker/cli v28.1.1+incompatible h1:eyUemzeI45DY7eDPuwUcmDyDj1pM98oD5MdSpiItp8k=
|
||||
github.com/docker/cli v28.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli-docs-tool v0.9.0 h1:CVwQbE+ZziwlPqrJ7LRyUF6GvCA+6gj7MTCsayaK9t0=
|
||||
github.com/docker/cli-docs-tool v0.9.0/go.mod h1:ClrwlNW+UioiRyH9GiAOe1o3J/TsY3Tr1ipoypjAUtc=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/docker/buildx v0.25.0 h1:qs5WxBo0wQKSXcQ+v6UhWaeM2Pu+95ZCymaimRzInaE=
|
||||
github.com/docker/buildx v0.25.0/go.mod h1:xJcOeBhz49tgqN174MMGuOU4bxNmgfaLnZn7Gm641EE=
|
||||
github.com/docker/cli v28.2.2+incompatible h1:qzx5BNUDFqlvyq4AHzdNB7gSyVTmU4cgsyN9SdInc1A=
|
||||
github.com/docker/cli v28.2.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli-docs-tool v0.10.0 h1:bOD6mKynPQgojQi3s2jgcUWGp/Ebqy1SeCr9VfKQLLU=
|
||||
github.com/docker/cli-docs-tool v0.10.0/go.mod h1:5EM5zPnT2E7yCLERZmrDA234Vwn09fzRHP4aX1qwp1U=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I=
|
||||
github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
|
||||
github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
|
||||
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
||||
@@ -216,8 +218,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
@@ -243,8 +245,8 @@ github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/in-toto/in-toto-golang v0.5.0 h1:hb8bgwr0M2hGdDsLjkJ3ZqJ8JFLL/tgYdAxF/XEFBbY=
|
||||
github.com/in-toto/in-toto-golang v0.5.0/go.mod h1:/Rq0IZHLV7Ku5gielPT4wPHJfH1GdHMCq8+WPxw8/BE=
|
||||
github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU=
|
||||
github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
@@ -315,8 +317,8 @@ github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/z
|
||||
github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/buildkit v0.22.0 h1:aWN06w1YGSVN1XfeZbj2ZbgY+zi5xDAjEFI8Cy9fTjA=
|
||||
github.com/moby/buildkit v0.22.0/go.mod h1:j4pP5hxiTWcz7xuTK2cyxQislHl/N2WWHzOy43DlLJw=
|
||||
github.com/moby/buildkit v0.23.1 h1:CZtFmPRF+IFG1C8QfPnktGO1Dzzt5JSwtQ5eDqIh+ag=
|
||||
github.com/moby/buildkit v0.23.1/go.mod h1:keNXljNmKX1T0AtM0bMObc8OV6mA9cOuquVbPcRpU/Y=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
|
||||
@@ -423,8 +425,10 @@ github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.4.0 h1:b23VGrQhTA8cN2CbBw7/FulN9fTtqYUdS5+Oxzt+DUE=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.4.0/go.mod h1:FGBZgq2tXWICsxWQW1msNf49F0Pf2Op5Htayx335Qbs=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.6.0 h1:T65atpAVCJQK14UA57LMdZGpHi4QYSH/9FZyNGqMYIA=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.6.0/go.mod h1:8Mtpo9JKks/qhPG4HGZ2LGMvrPbzuxwfz/f/zLfEWkk=
|
||||
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b h1:h+3JX2VoWTFuyQEo87pStk/a99dzIO1mM9KxIyLPGTU=
|
||||
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc=
|
||||
github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI=
|
||||
@@ -436,8 +440,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/spdx/tools-golang v0.5.3 h1:ialnHeEYUC4+hkm5vJm4qz2x+oEJbS0mAMFrNXdQraY=
|
||||
github.com/spdx/tools-golang v0.5.3/go.mod h1:/ETOahiAo96Ob0/RAIBmFZw6XN0yTnyr/uFZm2NTMhI=
|
||||
github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XOmk=
|
||||
github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE=
|
||||
github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94 h1:JmfC365KywYwHB946TTiQWEb8kqPY+pybPLoGE9GgVk=
|
||||
github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
||||
github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
@@ -472,10 +476,10 @@ github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 h1:QB54BJwA6x8
|
||||
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375/go.mod h1:xRroudyp5iVtxKqZCrA6n2TLFRBf8bmnjr1UD4x+z7g=
|
||||
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 h1:r0p7fK56l8WPequOaR3i9LBqfPtEdXIQbUTzT55iqT4=
|
||||
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323/go.mod h1:3Iuxbr0P7D3zUzBMAZB+ois3h/et0shEz0qApgHYGpY=
|
||||
github.com/tonistiigi/fsutil v0.0.0-20250417144416-3f76f8130144 h1:k9tdF32oJYwtjzMx+D26M6eYiCaAPdJ7tyN7tF1oU5Q=
|
||||
github.com/tonistiigi/fsutil v0.0.0-20250417144416-3f76f8130144/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98=
|
||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 h1:7I5c2Ig/5FgqkYOh/N87NzoyI9U15qUPXhDD8uCupv8=
|
||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE=
|
||||
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f h1:MoxeMfHAe5Qj/ySSBfL8A7l1V+hxuluj8owsIEEZipI=
|
||||
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98=
|
||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 h1:2f304B10LaZdB8kkVEaoXvAMVan2tl9AiK4G0odjQtE=
|
||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE=
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk=
|
||||
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab h1:H6aJ0yKQ0gF49Qb2z5hI1UHxSQt4JMyxebFR15KnApw=
|
||||
@@ -544,8 +548,6 @@ golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWP
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
@@ -571,8 +573,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -620,13 +622,13 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
|
||||
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
pusherrors "github.com/containerd/containerd/v2/core/remotes/errors"
|
||||
@@ -104,7 +105,9 @@ func PushManifest(
|
||||
) error {
|
||||
// Check if we need an extra empty layer for the manifest config
|
||||
if ociVersion == api.OCIVersion1_1 || ociVersion == "" {
|
||||
layers = append(layers, Pushable{Descriptor: v1.DescriptorEmptyJSON, Data: []byte("{}")})
|
||||
if err := resolver.Push(ctx, named, v1.DescriptorEmptyJSON, v1.DescriptorEmptyJSON.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// prepare to push the manifest by pushing the layers
|
||||
layerDescriptors := make([]v1.Descriptor, len(layers))
|
||||
@@ -157,14 +160,7 @@ func isNonAuthClientError(statusCode int) bool {
|
||||
// not a client error
|
||||
return false
|
||||
}
|
||||
for _, v := range clientAuthStatusCodes {
|
||||
if statusCode == v {
|
||||
// client auth error
|
||||
return false
|
||||
}
|
||||
}
|
||||
// any other 4xx client error
|
||||
return true
|
||||
return !slices.Contains(clientAuthStatusCodes, statusCode)
|
||||
}
|
||||
|
||||
func generateManifest(layers []v1.Descriptor, ociCompat api.OCIVersion) ([]Pushable, error) {
|
||||
|
||||
@@ -19,12 +19,13 @@ package api
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
// Service manages a compose project
|
||||
@@ -78,13 +79,13 @@ type Service interface {
|
||||
// Publish executes the equivalent to a `compose publish`
|
||||
Publish(ctx context.Context, project *types.Project, repository string, options PublishOptions) error
|
||||
// Images executes the equivalent of a `compose images`
|
||||
Images(ctx context.Context, projectName string, options ImagesOptions) ([]ImageSummary, error)
|
||||
Images(ctx context.Context, projectName string, options ImagesOptions) (map[string]ImageSummary, error)
|
||||
// MaxConcurrency defines upper limit for concurrent operations against engine API
|
||||
MaxConcurrency(parallel int)
|
||||
// DryRunMode defines if dry run applies to the command
|
||||
DryRunMode(ctx context.Context, dryRun bool) (context.Context, error)
|
||||
// Watch services' development context and sync/notify/rebuild/restart on changes
|
||||
Watch(ctx context.Context, project *types.Project, services []string, options WatchOptions) error
|
||||
Watch(ctx context.Context, project *types.Project, options WatchOptions) error
|
||||
// Viz generates a graphviz graph of the project services
|
||||
Viz(ctx context.Context, project *types.Project, options VizOptions) (string, error)
|
||||
// Wait blocks until at least one of the services' container exits
|
||||
@@ -126,9 +127,10 @@ const WatchLogger = "#watch"
|
||||
|
||||
// WatchOptions group options of the Watch API
|
||||
type WatchOptions struct {
|
||||
Build *BuildOptions
|
||||
LogTo LogConsumer
|
||||
Prune bool
|
||||
Build *BuildOptions
|
||||
LogTo LogConsumer
|
||||
Prune bool
|
||||
Services []string
|
||||
}
|
||||
|
||||
// BuildOptions group options of the Build API
|
||||
@@ -175,13 +177,13 @@ func (o BuildOptions) Apply(project *types.Project) error {
|
||||
continue
|
||||
}
|
||||
if platform != "" {
|
||||
if len(service.Build.Platforms) > 0 && !utils.StringContains(service.Build.Platforms, platform) {
|
||||
if len(service.Build.Platforms) > 0 && !slices.Contains(service.Build.Platforms, platform) {
|
||||
return fmt.Errorf("service %q build.platforms does not support value set by DOCKER_DEFAULT_PLATFORM: %s", name, platform)
|
||||
}
|
||||
service.Platform = platform
|
||||
}
|
||||
if service.Platform != "" {
|
||||
if len(service.Build.Platforms) > 0 && !utils.StringContains(service.Build.Platforms, service.Platform) {
|
||||
if len(service.Build.Platforms) > 0 && !slices.Contains(service.Build.Platforms, service.Platform) {
|
||||
return fmt.Errorf("service %q build configuration does not support platform: %s", name, service.Platform)
|
||||
}
|
||||
}
|
||||
@@ -349,7 +351,7 @@ type RemoveOptions struct {
|
||||
|
||||
// RunOptions group options of the Run API
|
||||
type RunOptions struct {
|
||||
Build *BuildOptions
|
||||
CreateOptions
|
||||
// Project is the compose project used to define this app. Might be nil if user ran command just with project name
|
||||
Project *types.Project
|
||||
Name string
|
||||
@@ -369,8 +371,6 @@ type RunOptions struct {
|
||||
Privileged bool
|
||||
UseNetworkAliases bool
|
||||
NoDeps bool
|
||||
// QuietPull makes the pulling process quiet
|
||||
QuietPull bool
|
||||
// used by exec
|
||||
Index int
|
||||
}
|
||||
@@ -535,12 +535,12 @@ type ContainerProcSummary struct {
|
||||
|
||||
// ImageSummary holds container image description
|
||||
type ImageSummary struct {
|
||||
ID string
|
||||
ContainerName string
|
||||
Repository string
|
||||
Tag string
|
||||
Size int64
|
||||
LastTagTime time.Time
|
||||
ID string
|
||||
Repository string
|
||||
Tag string
|
||||
Platform platforms.Platform
|
||||
Size int64
|
||||
LastTagTime time.Time
|
||||
}
|
||||
|
||||
// ServiceStatus hold status about a service
|
||||
|
||||
@@ -34,6 +34,7 @@ import (
|
||||
"github.com/docker/buildx/util/imagetools"
|
||||
"github.com/docker/cli/cli/command"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/build"
|
||||
"github.com/docker/docker/api/types/checkpoint"
|
||||
containerType "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/events"
|
||||
@@ -205,18 +206,18 @@ func (d *DryRunClient) CopyToContainer(ctx context.Context, container, path stri
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DryRunClient) ImageBuild(ctx context.Context, reader io.Reader, options moby.ImageBuildOptions) (moby.ImageBuildResponse, error) {
|
||||
func (d *DryRunClient) ImageBuild(ctx context.Context, reader io.Reader, options build.ImageBuildOptions) (build.ImageBuildResponse, error) {
|
||||
jsonMessage, err := json.Marshal(&jsonmessage.JSONMessage{
|
||||
Status: fmt.Sprintf("%[1]sSuccessfully built: dryRunID\n%[1]sSuccessfully tagged: %[2]s\n", DRYRUN_PREFIX, options.Tags[0]),
|
||||
Progress: &jsonmessage.JSONProgress{},
|
||||
ID: "",
|
||||
})
|
||||
if err != nil {
|
||||
return moby.ImageBuildResponse{}, err
|
||||
return build.ImageBuildResponse{}, err
|
||||
}
|
||||
rc := io.NopCloser(bytes.NewReader(jsonMessage))
|
||||
|
||||
return moby.ImageBuildResponse{
|
||||
return build.ImageBuildResponse{
|
||||
Body: rc,
|
||||
OSType: "",
|
||||
}, nil
|
||||
@@ -334,11 +335,11 @@ func (d *DryRunClient) ContainerExecStart(ctx context.Context, execID string, co
|
||||
|
||||
// Functions delegated to original APIClient (not used by Compose or not modifying the Compose stack
|
||||
|
||||
func (d *DryRunClient) ConfigList(ctx context.Context, options moby.ConfigListOptions) ([]swarm.Config, error) {
|
||||
func (d *DryRunClient) ConfigList(ctx context.Context, options swarm.ConfigListOptions) ([]swarm.Config, error) {
|
||||
return d.apiClient.ConfigList(ctx, options)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (moby.ConfigCreateResponse, error) {
|
||||
func (d *DryRunClient) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (swarm.ConfigCreateResponse, error) {
|
||||
return d.apiClient.ConfigCreate(ctx, config)
|
||||
}
|
||||
|
||||
@@ -422,7 +423,7 @@ func (d *DryRunClient) DistributionInspect(ctx context.Context, imageName, encod
|
||||
return d.apiClient.DistributionInspect(ctx, imageName, encodedRegistryAuth)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) BuildCachePrune(ctx context.Context, opts moby.BuildCachePruneOptions) (*moby.BuildCachePruneReport, error) {
|
||||
func (d *DryRunClient) BuildCachePrune(ctx context.Context, opts build.CachePruneOptions) (*build.CachePruneReport, error) {
|
||||
return d.apiClient.BuildCachePrune(ctx, opts)
|
||||
}
|
||||
|
||||
@@ -470,11 +471,11 @@ func (d *DryRunClient) NodeInspectWithRaw(ctx context.Context, nodeID string) (s
|
||||
return d.apiClient.NodeInspectWithRaw(ctx, nodeID)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) NodeList(ctx context.Context, options moby.NodeListOptions) ([]swarm.Node, error) {
|
||||
func (d *DryRunClient) NodeList(ctx context.Context, options swarm.NodeListOptions) ([]swarm.Node, error) {
|
||||
return d.apiClient.NodeList(ctx, options)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) NodeRemove(ctx context.Context, nodeID string, options moby.NodeRemoveOptions) error {
|
||||
func (d *DryRunClient) NodeRemove(ctx context.Context, nodeID string, options swarm.NodeRemoveOptions) error {
|
||||
return d.apiClient.NodeRemove(ctx, nodeID, options)
|
||||
}
|
||||
|
||||
@@ -538,15 +539,15 @@ func (d *DryRunClient) PluginCreate(ctx context.Context, createContext io.Reader
|
||||
return d.apiClient.PluginCreate(ctx, createContext, options)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options moby.ServiceCreateOptions) (swarm.ServiceCreateResponse, error) {
|
||||
func (d *DryRunClient) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options swarm.ServiceCreateOptions) (swarm.ServiceCreateResponse, error) {
|
||||
return d.apiClient.ServiceCreate(ctx, service, options)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) ServiceInspectWithRaw(ctx context.Context, serviceID string, options moby.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
func (d *DryRunClient) ServiceInspectWithRaw(ctx context.Context, serviceID string, options swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
return d.apiClient.ServiceInspectWithRaw(ctx, serviceID, options)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) ServiceList(ctx context.Context, options moby.ServiceListOptions) ([]swarm.Service, error) {
|
||||
func (d *DryRunClient) ServiceList(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return d.apiClient.ServiceList(ctx, options)
|
||||
}
|
||||
|
||||
@@ -554,7 +555,7 @@ func (d *DryRunClient) ServiceRemove(ctx context.Context, serviceID string) erro
|
||||
return d.apiClient.ServiceRemove(ctx, serviceID)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options moby.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
|
||||
func (d *DryRunClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
|
||||
return d.apiClient.ServiceUpdate(ctx, serviceID, version, service, options)
|
||||
}
|
||||
|
||||
@@ -570,7 +571,7 @@ func (d *DryRunClient) TaskInspectWithRaw(ctx context.Context, taskID string) (s
|
||||
return d.apiClient.TaskInspectWithRaw(ctx, taskID)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) TaskList(ctx context.Context, options moby.TaskListOptions) ([]swarm.Task, error) {
|
||||
func (d *DryRunClient) TaskList(ctx context.Context, options swarm.TaskListOptions) ([]swarm.Task, error) {
|
||||
return d.apiClient.TaskList(ctx, options)
|
||||
}
|
||||
|
||||
@@ -582,7 +583,7 @@ func (d *DryRunClient) SwarmJoin(ctx context.Context, req swarm.JoinRequest) err
|
||||
return d.apiClient.SwarmJoin(ctx, req)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) SwarmGetUnlockKey(ctx context.Context) (moby.SwarmUnlockKeyResponse, error) {
|
||||
func (d *DryRunClient) SwarmGetUnlockKey(ctx context.Context) (swarm.UnlockKeyResponse, error) {
|
||||
return d.apiClient.SwarmGetUnlockKey(ctx)
|
||||
}
|
||||
|
||||
@@ -602,11 +603,11 @@ func (d *DryRunClient) SwarmUpdate(ctx context.Context, version swarm.Version, s
|
||||
return d.apiClient.SwarmUpdate(ctx, version, swarmSpec, flags)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) SecretList(ctx context.Context, options moby.SecretListOptions) ([]swarm.Secret, error) {
|
||||
func (d *DryRunClient) SecretList(ctx context.Context, options swarm.SecretListOptions) ([]swarm.Secret, error) {
|
||||
return d.apiClient.SecretList(ctx, options)
|
||||
}
|
||||
|
||||
func (d *DryRunClient) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (moby.SecretCreateResponse, error) {
|
||||
func (d *DryRunClient) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (swarm.SecretCreateResponse, error) {
|
||||
return d.apiClient.SecretCreate(ctx, secret)
|
||||
}
|
||||
|
||||
|
||||
218
pkg/bridge/convert.go
Normal file
218
pkg/bridge/convert.go
Normal file
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
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 bridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
cerrdefs "github.com/containerd/errdefs"
|
||||
"github.com/docker/cli/cli/command"
|
||||
cli "github.com/docker/cli/cli/command/container"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type ConvertOptions struct {
|
||||
Output string
|
||||
Templates string
|
||||
Transformations []string
|
||||
}
|
||||
|
||||
func Convert(ctx context.Context, dockerCli command.Cli, project *types.Project, opts ConvertOptions) error {
|
||||
if len(opts.Transformations) == 0 {
|
||||
opts.Transformations = []string{DefaultTransformerImage}
|
||||
}
|
||||
// Load image references, secrets and configs, also expose ports
|
||||
project, err := LoadAdditionalResources(ctx, dockerCli, project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// for user to rely on compose.yaml attribute names, not go struct ones, we marshall back into YAML
|
||||
raw, err := project.MarshalYAML(types.WithSecretContent)
|
||||
// Marshall to YAML
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot render project into yaml: %w", err)
|
||||
}
|
||||
var model map[string]any
|
||||
err = yaml.Unmarshal(raw, &model)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot render project into yaml: %w", err)
|
||||
}
|
||||
|
||||
if opts.Output != "" {
|
||||
_ = os.RemoveAll(opts.Output)
|
||||
err := os.MkdirAll(opts.Output, 0o744)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return fmt.Errorf("cannot create output folder: %w", err)
|
||||
}
|
||||
}
|
||||
// Run Transformers images
|
||||
return convert(ctx, dockerCli, model, opts)
|
||||
}
|
||||
|
||||
func convert(ctx context.Context, dockerCli command.Cli, model map[string]any, opts ConvertOptions) error {
|
||||
raw, err := yaml.Marshal(model)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dir := os.TempDir()
|
||||
composeYaml := filepath.Join(dir, "compose.yaml")
|
||||
err = os.WriteFile(composeYaml, raw, 0o600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := filepath.Abs(opts.Output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
binds := []string{
|
||||
fmt.Sprintf("%s:%s", dir, "/in"),
|
||||
fmt.Sprintf("%s:%s", out, "/out"),
|
||||
}
|
||||
if opts.Templates != "" {
|
||||
templateDir, err := filepath.Abs(opts.Templates)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
binds = append(binds, fmt.Sprintf("%s:%s", templateDir, "/templates"))
|
||||
}
|
||||
|
||||
for _, transformation := range opts.Transformations {
|
||||
_, err = inspectWithPull(ctx, dockerCli, transformation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
created, err := dockerCli.Client().ContainerCreate(ctx, &container.Config{
|
||||
Image: transformation,
|
||||
Env: []string{"LICENSE_AGREEMENT=true"},
|
||||
User: usr.Uid,
|
||||
}, &container.HostConfig{
|
||||
AutoRemove: true,
|
||||
Binds: binds,
|
||||
}, &network.NetworkingConfig{}, nil, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cli.RunStart(ctx, dockerCli, &cli.StartOptions{
|
||||
Attach: true,
|
||||
Containers: []string{created.ID},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadAdditionalResources loads additional resources from the project, such as image references, secrets, configs and exposed ports
|
||||
func LoadAdditionalResources(ctx context.Context, dockerCLI command.Cli, project *types.Project) (*types.Project, error) {
|
||||
for name, service := range project.Services {
|
||||
imageName := api.GetImageNameOrDefault(service, project.Name)
|
||||
|
||||
inspect, err := inspectWithPull(ctx, dockerCLI, imageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
service.Image = imageName
|
||||
exposed := utils.Set[string]{}
|
||||
exposed.AddAll(service.Expose...)
|
||||
for port := range inspect.Config.ExposedPorts {
|
||||
exposed.Add(nat.Port(port).Port())
|
||||
}
|
||||
for _, port := range service.Ports {
|
||||
exposed.Add(strconv.Itoa(int(port.Target)))
|
||||
}
|
||||
service.Expose = exposed.Elements()
|
||||
project.Services[name] = service
|
||||
}
|
||||
|
||||
for name, secret := range project.Secrets {
|
||||
f, err := loadFileObject(types.FileObjectConfig(secret))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
project.Secrets[name] = types.SecretConfig(f)
|
||||
}
|
||||
|
||||
for name, config := range project.Configs {
|
||||
f, err := loadFileObject(types.FileObjectConfig(config))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
project.Configs[name] = types.ConfigObjConfig(f)
|
||||
}
|
||||
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func loadFileObject(conf types.FileObjectConfig) (types.FileObjectConfig, error) {
|
||||
if !conf.External {
|
||||
switch {
|
||||
case conf.Environment != "":
|
||||
conf.Content = os.Getenv(conf.Environment)
|
||||
case conf.File != "":
|
||||
bytes, err := os.ReadFile(conf.File)
|
||||
if err != nil {
|
||||
return conf, err
|
||||
}
|
||||
conf.Content = string(bytes)
|
||||
}
|
||||
}
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
func inspectWithPull(ctx context.Context, dockerCli command.Cli, imageName string) (image.InspectResponse, error) {
|
||||
inspect, err := dockerCli.Client().ImageInspect(ctx, imageName)
|
||||
if cerrdefs.IsNotFound(err) {
|
||||
var stream io.ReadCloser
|
||||
stream, err = dockerCli.Client().ImagePull(ctx, imageName, image.PullOptions{})
|
||||
if err != nil {
|
||||
return image.InspectResponse{}, err
|
||||
}
|
||||
defer func() { _ = stream.Close() }()
|
||||
|
||||
err = jsonmessage.DisplayJSONMessagesToStream(stream, dockerCli.Out(), nil)
|
||||
if err != nil {
|
||||
return image.InspectResponse{}, err
|
||||
}
|
||||
if inspect, err = dockerCli.Client().ImageInspect(ctx, imageName); err != nil {
|
||||
return image.InspectResponse{}, err
|
||||
}
|
||||
}
|
||||
return inspect, err
|
||||
}
|
||||
118
pkg/bridge/transformers.go
Normal file
118
pkg/bridge/transformers.go
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
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 bridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/moby/go-archive"
|
||||
)
|
||||
|
||||
const (
|
||||
TransformerLabel = "com.docker.compose.bridge"
|
||||
DefaultTransformerImage = "docker/compose-bridge-kubernetes"
|
||||
)
|
||||
|
||||
type CreateTransformerOptions struct {
|
||||
Dest string
|
||||
From string
|
||||
}
|
||||
|
||||
func CreateTransformer(ctx context.Context, dockerCli command.Cli, options CreateTransformerOptions) error {
|
||||
if options.From == "" {
|
||||
options.From = DefaultTransformerImage
|
||||
}
|
||||
out, err := filepath.Abs(options.Dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := os.Stat(out); err == nil {
|
||||
return fmt.Errorf("output folder %s already exists", out)
|
||||
}
|
||||
|
||||
tmpl := filepath.Join(out, "templates")
|
||||
err = os.MkdirAll(tmpl, 0o744)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return fmt.Errorf("cannot create output folder: %w", err)
|
||||
}
|
||||
|
||||
if err := command.ValidateOutputPath(out); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
created, err := dockerCli.Client().ContainerCreate(ctx, &container.Config{
|
||||
Image: options.From,
|
||||
}, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "")
|
||||
defer func() {
|
||||
_ = dockerCli.Client().ContainerRemove(context.Background(), created.ID, container.RemoveOptions{Force: true})
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
content, stat, err := dockerCli.Client().CopyFromContainer(ctx, created.ID, "/templates")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = content.Close()
|
||||
}()
|
||||
|
||||
srcInfo := archive.CopyInfo{
|
||||
Path: "/templates",
|
||||
Exists: true,
|
||||
IsDir: stat.Mode.IsDir(),
|
||||
}
|
||||
|
||||
preArchive := content
|
||||
if srcInfo.RebaseName != "" {
|
||||
_, srcBase := archive.SplitPathDirEntry(srcInfo.Path)
|
||||
preArchive = archive.RebaseArchiveEntries(content, srcBase, srcInfo.RebaseName)
|
||||
}
|
||||
|
||||
if err := archive.CopyTo(preArchive, srcInfo, out); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dockerfile := `FROM docker/compose-bridge-transformer
|
||||
LABEL com.docker.compose.bridge=transformation
|
||||
COPY templates /templates
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(out, "Dockerfile"), []byte(dockerfile), 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprintf(dockerCli.Out(), "Transformer created in %q\n", out)
|
||||
return err
|
||||
}
|
||||
|
||||
func ListTransformers(ctx context.Context, dockerCli command.Cli) ([]image.Summary, error) {
|
||||
api := dockerCli.Client()
|
||||
return api.ImageList(ctx, image.ListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
filters.Arg("label", fmt.Sprintf("%s=%s", TransformerLabel, "transformation")),
|
||||
),
|
||||
})
|
||||
}
|
||||
90
pkg/compose/apiSocket.go
Normal file
90
pkg/compose/apiSocket.go
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
)
|
||||
|
||||
// --use-api-socket is not actually supported by the Docker Engine
|
||||
// but is a client-side hack (see https://github.com/docker/cli/blob/master/cli/command/container/create.go#L246)
|
||||
// we replicate here by transforming the project model
|
||||
|
||||
func (s *composeService) useAPISocket(project *types.Project) (*types.Project, error) {
|
||||
useAPISocket := false
|
||||
for _, service := range project.Services {
|
||||
if service.UseAPISocket {
|
||||
useAPISocket = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !useAPISocket {
|
||||
return project, nil
|
||||
}
|
||||
|
||||
socket := s.dockerCli.DockerEndpoint().Host
|
||||
if !strings.HasPrefix(socket, "unix://") {
|
||||
return nil, fmt.Errorf("use_api_socket can only be used with unix sockets: docker endpoint %s is incompatible", socket)
|
||||
}
|
||||
socket = strings.TrimPrefix(socket, "unix://") // should we confirm absolute path?
|
||||
|
||||
creds, err := s.dockerCli.ConfigFile().GetAllCredentials()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolving credentials failed: %w", err)
|
||||
}
|
||||
newConfig := &configfile.ConfigFile{
|
||||
AuthConfigs: creds,
|
||||
}
|
||||
var configBuf bytes.Buffer
|
||||
if err := newConfig.SaveToWriter(&configBuf); err != nil {
|
||||
return nil, fmt.Errorf("saving creds for API socket: %w", err)
|
||||
}
|
||||
|
||||
project.Configs["#apisocket"] = types.ConfigObjConfig{
|
||||
Content: configBuf.String(),
|
||||
}
|
||||
|
||||
for name, service := range project.Services {
|
||||
service.Volumes = append(service.Volumes, types.ServiceVolumeConfig{
|
||||
Type: types.VolumeTypeBind,
|
||||
Source: socket,
|
||||
Target: "/var/run/docker.sock",
|
||||
})
|
||||
|
||||
_, envvarPresent := service.Environment["DOCKER_CONFIG"]
|
||||
|
||||
// If the DOCKER_CONFIG env var is already present, we assume the client knows
|
||||
// what they're doing and don't inject the creds.
|
||||
if !envvarPresent {
|
||||
// Set our special little location for the config file.
|
||||
path := "/run/secrets/docker"
|
||||
service.Environment["DOCKER_CONFIG"] = &path
|
||||
}
|
||||
|
||||
service.Configs = append(service.Configs, types.ServiceConfigObjConfig{
|
||||
Source: "#apisocket",
|
||||
Target: "/run/secrets/docker/config.json",
|
||||
})
|
||||
project.Services[name] = service
|
||||
}
|
||||
return project, nil
|
||||
}
|
||||
@@ -22,19 +22,16 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/controller/pb"
|
||||
"github.com/docker/buildx/store/storeutil"
|
||||
"github.com/docker/buildx/util/buildflags"
|
||||
xprogress "github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/hints"
|
||||
cliopts "github.com/docker/cli/opts"
|
||||
"github.com/docker/compose/v2/internal/tracing"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@@ -71,10 +68,6 @@ func (s *composeService) Build(ctx context.Context, project *types.Project, opti
|
||||
}, s.stdinfo(), "Building")
|
||||
}
|
||||
|
||||
const bakeSuggest = "Compose can now delegate builds to bake for better performance.\n To do so, set COMPOSE_BAKE=true."
|
||||
|
||||
var suggest sync.Once
|
||||
|
||||
//nolint:gocyclo
|
||||
func (s *composeService) build(ctx context.Context, project *types.Project, options api.BuildOptions, localImages map[string]api.ImageSummary) (map[string]string, error) {
|
||||
imageIDs := map[string]string{}
|
||||
@@ -156,11 +149,6 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
w *xprogress.Printer
|
||||
)
|
||||
if buildkitEnabled {
|
||||
if hints.Enabled() && progress.Mode != progress.ModeQuiet && progress.Mode != progress.ModeJSON {
|
||||
suggest.Do(func() {
|
||||
fmt.Fprintln(s.dockerCli.Out(), bakeSuggest) //nolint:errcheck
|
||||
})
|
||||
}
|
||||
builderName := options.Builder
|
||||
if builderName == "" {
|
||||
builderName = os.Getenv("BUILDX_BUILDER")
|
||||
@@ -368,6 +356,7 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
|
||||
Variant: inspect.Variant,
|
||||
}
|
||||
if !platforms.NewMatcher(platform).Match(actual) {
|
||||
logrus.Debugf("local image %s doesn't match expected platform %s", service.Image, service.Platform)
|
||||
// there is a local image, but it's for the wrong platform, so
|
||||
// pretend it doesn't exist so that we can pull/build an image
|
||||
// for the correct platform instead
|
||||
@@ -493,8 +482,8 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
|
||||
DockerfilePath: dockerFilePath(service.Build.Context, service.Build.Dockerfile),
|
||||
NamedContexts: toBuildContexts(service, project),
|
||||
},
|
||||
CacheFrom: pb.CreateCaches(cacheFrom.ToPB()),
|
||||
CacheTo: pb.CreateCaches(cacheTo.ToPB()),
|
||||
CacheFrom: build.CreateCaches(cacheFrom),
|
||||
CacheTo: build.CreateCaches(cacheTo),
|
||||
NoCache: service.Build.NoCache,
|
||||
Pull: service.Build.Pull,
|
||||
BuildArgs: flatten(resolveAndMergeBuildArgs(s.dockerCli, project, service, options)),
|
||||
@@ -638,7 +627,11 @@ func parsePlatforms(service types.ServiceConfig) ([]specs.Platform, error) {
|
||||
func addBuildDependencies(services []string, project *types.Project) []string {
|
||||
servicesWithDependencies := utils.NewSet(services...)
|
||||
for _, service := range services {
|
||||
b := project.Services[service].Build
|
||||
s, ok := project.Services[service]
|
||||
if !ok {
|
||||
s = project.DisabledServices[service]
|
||||
}
|
||||
b := s.Build
|
||||
if b != nil {
|
||||
for _, target := range b.AdditionalContexts {
|
||||
if s, found := strings.CutPrefix(target, types.ServicePrefix); found {
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -51,12 +52,7 @@ import (
|
||||
func buildWithBake(dockerCli command.Cli) (bool, error) {
|
||||
b, ok := os.LookupEnv("COMPOSE_BAKE")
|
||||
if !ok {
|
||||
if dockerCli.ConfigFile().Plugins["compose"]["build"] == "bake" {
|
||||
b, ok = "true", true
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return false, nil
|
||||
b = "true"
|
||||
}
|
||||
bake, err := strconv.ParseBool(b)
|
||||
if err != nil {
|
||||
@@ -72,6 +68,7 @@ func buildWithBake(dockerCli command.Cli) (bool, error) {
|
||||
}
|
||||
if !enabled {
|
||||
logrus.Warnf("Docker Compose is configured to build using Bake, but buildkit isn't enabled")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
_, err = manager.GetPlugin("buildx", dockerCli, &cobra.Command{})
|
||||
@@ -117,6 +114,7 @@ type bakeTarget struct {
|
||||
Ulimits []string `json:"ulimits,omitempty"`
|
||||
Call string `json:"call,omitempty"`
|
||||
Entitlements []string `json:"entitlements,omitempty"`
|
||||
ExtraHosts map[string]string `json:"extra-hosts,omitempty"`
|
||||
Outputs []string `json:"output,omitempty"`
|
||||
}
|
||||
|
||||
@@ -144,13 +142,25 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
||||
Targets: map[string]bakeTarget{},
|
||||
}
|
||||
var (
|
||||
group bakeGroup
|
||||
privileged bool
|
||||
read []string
|
||||
expectedImages = make(map[string]string, len(serviceToBeBuild)) // service name -> expected image
|
||||
group bakeGroup
|
||||
privileged bool
|
||||
read []string
|
||||
targets = make(map[string]string, len(serviceToBeBuild)) // service name -> build target
|
||||
)
|
||||
|
||||
for serviceName, service := range serviceToBeBuild {
|
||||
// produce a unique ID for service used as bake target
|
||||
for serviceName := range project.Services {
|
||||
t := strings.ReplaceAll(serviceName, ".", "_")
|
||||
for {
|
||||
if _, ok := targets[serviceName]; !ok {
|
||||
targets[serviceName] = t
|
||||
break
|
||||
}
|
||||
t += "_"
|
||||
}
|
||||
}
|
||||
|
||||
for serviceName, service := range project.Services {
|
||||
if service.Build == nil {
|
||||
continue
|
||||
}
|
||||
@@ -164,9 +174,6 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
||||
args[k] = *v
|
||||
}
|
||||
|
||||
image := api.GetImageNameOrDefault(service, project.Name)
|
||||
expectedImages[serviceName] = image
|
||||
|
||||
entitlements := build.Entitlements
|
||||
if slices.Contains(build.Entitlements, "security.insecure") {
|
||||
privileged = true
|
||||
@@ -196,17 +203,19 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
||||
}
|
||||
}
|
||||
|
||||
cfg.Targets[serviceName] = bakeTarget{
|
||||
target := targets[serviceName]
|
||||
cfg.Targets[target] = bakeTarget{
|
||||
Context: build.Context,
|
||||
Contexts: additionalContexts(build.AdditionalContexts),
|
||||
Contexts: additionalContexts(build.AdditionalContexts, targets),
|
||||
Dockerfile: dockerFilePath(build.Context, build.Dockerfile),
|
||||
DockerfileInline: strings.ReplaceAll(build.DockerfileInline, "${", "$${"),
|
||||
Args: args,
|
||||
Labels: build.Labels,
|
||||
Tags: append(build.Tags, image),
|
||||
Tags: append(build.Tags, api.GetImageNameOrDefault(service, project.Name)),
|
||||
|
||||
CacheFrom: build.CacheFrom,
|
||||
// CacheTo: TODO
|
||||
CacheFrom: build.CacheFrom,
|
||||
CacheTo: build.CacheTo,
|
||||
NetworkMode: build.Network,
|
||||
Platforms: build.Platforms,
|
||||
Target: build.Target,
|
||||
Secrets: toBakeSecrets(project, build.Secrets),
|
||||
@@ -216,11 +225,19 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
||||
ShmSize: build.ShmSize,
|
||||
Ulimits: toBakeUlimits(build.Ulimits),
|
||||
Entitlements: entitlements,
|
||||
ExtraHosts: toBakeExtraHosts(build.ExtraHosts),
|
||||
|
||||
Outputs: outputs,
|
||||
Call: call,
|
||||
}
|
||||
group.Targets = append(group.Targets, serviceName)
|
||||
}
|
||||
|
||||
// create a bake group with targets for services to build
|
||||
for serviceName, service := range serviceToBeBuild {
|
||||
if service.Build == nil {
|
||||
continue
|
||||
}
|
||||
group.Targets = append(group.Targets, targets[serviceName])
|
||||
}
|
||||
|
||||
cfg.Groups["default"] = group
|
||||
@@ -236,17 +253,25 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
||||
}
|
||||
logrus.Debugf("bake build config:\n%s", string(b))
|
||||
|
||||
metadata, err := os.CreateTemp(os.TempDir(), "compose")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var metadataFile string
|
||||
for {
|
||||
// we don't use os.CreateTemp here as we need a temporary file name, but don't want it actually created
|
||||
// as bake relies on atomicwriter and this creates conflict during rename
|
||||
metadataFile = filepath.Join(os.TempDir(), fmt.Sprintf("compose-build-metadataFile-%d.json", rand.Int31()))
|
||||
if _, err = os.Stat(metadataFile); os.IsNotExist(err) {
|
||||
break
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
_ = os.Remove(metadataFile)
|
||||
}()
|
||||
|
||||
buildx, err := manager.GetPlugin("buildx", s.dockerCli, &cobra.Command{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
args := []string{"bake", "--file", "-", "--progress", "rawjson", "--metadata-file", metadata.Name()}
|
||||
args := []string{"bake", "--file", "-", "--progress", "rawjson", "--metadata-file", metadataFile}
|
||||
mustAllow := buildx.Version != "" && versions.GreaterThanOrEqualTo(buildx.Version[1:], "0.17.0")
|
||||
if mustAllow {
|
||||
// FIXME we should prompt user about this, but this is a breaking change in UX
|
||||
@@ -273,13 +298,15 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
||||
|
||||
// Use docker/cli mechanism to propagate termination signal to child process
|
||||
server, err := socket.NewPluginServer(nil)
|
||||
if err != nil {
|
||||
if err == nil {
|
||||
defer server.Close() //nolint:errcheck
|
||||
cmd.Cancel = server.Close
|
||||
cmd.Env = replace(cmd.Env, socket.EnvKey, server.Addr().String())
|
||||
}
|
||||
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("DOCKER_CONTEXT=%s", s.dockerCli.CurrentContext()))
|
||||
cmd.Env = append(cmd.Env,
|
||||
fmt.Sprintf("DOCKER_CONTEXT=%s", s.dockerCli.CurrentContext()),
|
||||
fmt.Sprintf("DOCKER_HOST=%s", s.dockerCli.DockerEndpoint().Host),
|
||||
)
|
||||
|
||||
// propagate opentelemetry context to child process, see https://github.com/open-telemetry/oteps/blob/main/text/0258-env-context-baggage-carriers.md
|
||||
carrier := propagation.MapCarrier{}
|
||||
@@ -327,7 +354,7 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
||||
return nil, fmt.Errorf("failed to execute bake: %w", err)
|
||||
}
|
||||
|
||||
b, err = os.ReadFile(metadata.Name())
|
||||
b, err = os.ReadFile(metadataFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -340,10 +367,11 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
||||
|
||||
cw := progress.ContextWriter(ctx)
|
||||
results := map[string]string{}
|
||||
for service, name := range expectedImages {
|
||||
built, ok := md[service] // bake target == service name
|
||||
for name := range serviceToBeBuild {
|
||||
target := targets[name]
|
||||
built, ok := md[target]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("build result not found in Bake metadata for service %s", service)
|
||||
return nil, fmt.Errorf("build result not found in Bake metadata for service %s", name)
|
||||
}
|
||||
results[name] = built.Digest
|
||||
cw.Event(progress.BuiltEvent(name))
|
||||
@@ -351,11 +379,19 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func additionalContexts(contexts types.Mapping) map[string]string {
|
||||
func toBakeExtraHosts(hosts types.HostsList) map[string]string {
|
||||
m := make(map[string]string)
|
||||
for k, v := range hosts {
|
||||
m[k] = strings.Join(v, ",")
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func additionalContexts(contexts types.Mapping, targets map[string]string) map[string]string {
|
||||
ac := map[string]string{}
|
||||
for k, v := range contexts {
|
||||
if target, found := strings.CutPrefix(v, types.ServicePrefix); found {
|
||||
v = "target:" + target
|
||||
v = "target:" + targets[target]
|
||||
}
|
||||
ac[k] = v
|
||||
}
|
||||
@@ -420,8 +456,15 @@ func dockerFilePath(ctxName string, dockerfile string) string {
|
||||
if dockerfile == "" {
|
||||
return ""
|
||||
}
|
||||
if urlutil.IsGitURL(ctxName) || filepath.IsAbs(dockerfile) {
|
||||
if urlutil.IsGitURL(ctxName) {
|
||||
return dockerfile
|
||||
}
|
||||
return filepath.Join(ctxName, dockerfile)
|
||||
if !filepath.IsAbs(dockerfile) {
|
||||
dockerfile = filepath.Join(ctxName, dockerfile)
|
||||
}
|
||||
symlinks, err := filepath.EvalSymlinks(dockerfile)
|
||||
if err == nil {
|
||||
return symlinks
|
||||
}
|
||||
return dockerfile
|
||||
}
|
||||
|
||||
@@ -27,23 +27,19 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/image/build"
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
buildtypes "github.com/docker/docker/api/types/build"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/builder/remotecontext/urlutil"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/docker/pkg/progress"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/moby/go-archive"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -179,7 +175,7 @@ func (s *composeService) doBuildClassic(ctx context.Context, project *types.Proj
|
||||
|
||||
imageID := ""
|
||||
aux := func(msg jsonmessage.JSONMessage) {
|
||||
var result dockertypes.BuildResult
|
||||
var result buildtypes.Result
|
||||
if err := json.Unmarshal(*msg.Aux, &result); err != nil {
|
||||
logrus.Errorf("Failed to parse aux message: %s", err)
|
||||
} else {
|
||||
@@ -219,10 +215,10 @@ func isLocalDir(c string) bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func imageBuildOptions(dockerCli command.Cli, project *types.Project, service types.ServiceConfig, options api.BuildOptions) dockertypes.ImageBuildOptions {
|
||||
func imageBuildOptions(dockerCli command.Cli, project *types.Project, service types.ServiceConfig, options api.BuildOptions) buildtypes.ImageBuildOptions {
|
||||
config := service.Build
|
||||
return dockertypes.ImageBuildOptions{
|
||||
Version: dockertypes.BuilderV1,
|
||||
return buildtypes.ImageBuildOptions{
|
||||
Version: buildtypes.BuilderV1,
|
||||
Tags: config.Tags,
|
||||
NoCache: config.NoCache,
|
||||
Remove: true,
|
||||
|
||||
@@ -69,7 +69,7 @@ func (s *composeService) commit(ctx context.Context, projectName string, options
|
||||
Reference: options.Reference,
|
||||
Comment: options.Comment,
|
||||
Author: options.Author,
|
||||
Changes: options.Changes.GetAll(),
|
||||
Changes: options.Changes.GetSlice(),
|
||||
Pause: options.Pause,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -19,12 +19,12 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
)
|
||||
@@ -124,7 +124,7 @@ func matches(c container.Summary, predicates ...containerPredicate) bool {
|
||||
func isService(services ...string) containerPredicate {
|
||||
return func(c container.Summary) bool {
|
||||
service := c.Labels[api.ServiceLabel]
|
||||
return utils.StringContains(services, service)
|
||||
return slices.Contains(services, service)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ func isOrphaned(project *types.Project) containerPredicate {
|
||||
}
|
||||
// Service that is not defined in the compose model
|
||||
service := c.Labels[api.ServiceLabel]
|
||||
return !utils.StringContains(services, service)
|
||||
return !slices.Contains(services, service)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ func (c *convergence) apply(ctx context.Context, project *types.Project, options
|
||||
|
||||
return tracing.SpanWrapFunc("service/apply", tracing.ServiceOptions(service), func(ctx context.Context) error {
|
||||
strategy := options.RecreateDependencies
|
||||
if utils.StringContains(options.Services, name) {
|
||||
if slices.Contains(options.Services, name) {
|
||||
strategy = options.Recreate
|
||||
}
|
||||
return c.ensureService(ctx, project, service, strategy, options.Inherit, options.Timeout)
|
||||
@@ -191,7 +191,6 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||
case ContainerCreated:
|
||||
case ContainerRestarting:
|
||||
case ContainerExited:
|
||||
w.Event(progress.CreatedEvent(name))
|
||||
default:
|
||||
container := container
|
||||
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "service/start", tracing.ContainerOptions(container), func(ctx context.Context) error {
|
||||
@@ -757,14 +756,7 @@ func (s *composeService) createMobyContainer(ctx context.Context,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = s.injectSecrets(ctx, project, service, created.ID)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
|
||||
err = s.injectConfigs(ctx, project, service, created.ID)
|
||||
return created, err
|
||||
return created, nil
|
||||
}
|
||||
|
||||
// getLinks mimics V1 compose/service.py::Service::_get_links()
|
||||
@@ -898,6 +890,17 @@ func (s *composeService) startService(ctx context.Context,
|
||||
if ctr.State == ContainerRunning {
|
||||
continue
|
||||
}
|
||||
|
||||
err = s.injectSecrets(ctx, project, service, ctr.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.injectConfigs(ctx, project, service, ctr.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
eventName := getContainerProgressName(ctr)
|
||||
w.Event(progress.StartingEvent(eventName))
|
||||
err = s.apiClient().ContainerStart(ctx, ctr.ID, containerType.StartOptions{})
|
||||
|
||||
@@ -24,15 +24,13 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/paths"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/prompt"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
cerrdefs "github.com/containerd/errdefs"
|
||||
"github.com/docker/docker/api/types/blkiodev"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
@@ -41,10 +39,13 @@ import (
|
||||
"github.com/docker/docker/api/types/strslice"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
volumetypes "github.com/docker/docker/api/types/volume"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/sirupsen/logrus"
|
||||
cdi "tags.cncf.io/container-device-interface/pkg/parser"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/prompt"
|
||||
)
|
||||
|
||||
type createOptions struct {
|
||||
@@ -113,6 +114,13 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
|
||||
"--remove-orphans flag to clean it up.", orphans.names())
|
||||
}
|
||||
}
|
||||
|
||||
// Temporary implementation of use_api_socket until we get actual support inside docker engine
|
||||
project, err = s.useAPISocket(project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return newConvergence(options.Services, observedState, networks, volumes, s).apply(ctx, project, options)
|
||||
}
|
||||
|
||||
@@ -1253,7 +1261,7 @@ func (s *composeService) ensureNetwork(ctx context.Context, project *types.Proje
|
||||
}
|
||||
|
||||
id, err := s.resolveOrCreateNetwork(ctx, project, name, n)
|
||||
if errdefs.IsConflict(err) {
|
||||
if cerrdefs.IsConflict(err) {
|
||||
// Maybe another execution of `docker compose up|run` created same network
|
||||
// let's retry once
|
||||
return s.resolveOrCreateNetwork(ctx, project, name, n)
|
||||
@@ -1262,6 +1270,9 @@ func (s *composeService) ensureNetwork(ctx context.Context, project *types.Proje
|
||||
}
|
||||
|
||||
func (s *composeService) resolveOrCreateNetwork(ctx context.Context, project *types.Project, name string, n *types.NetworkConfig) (string, error) { //nolint:gocyclo
|
||||
// This is containers that could be left after a diverged network was removed
|
||||
var dangledContainers Containers
|
||||
|
||||
// First, try to find a unique network matching by name or ID
|
||||
inspect, err := s.apiClient().NetworkInspect(ctx, n.Name, network.InspectOptions{})
|
||||
if err == nil {
|
||||
@@ -1295,7 +1306,7 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, project *ty
|
||||
return inspect.ID, nil
|
||||
}
|
||||
|
||||
err = s.removeDivergedNetwork(ctx, project, name, n)
|
||||
dangledContainers, err = s.removeDivergedNetwork(ctx, project, name, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -1312,8 +1323,8 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, project *ty
|
||||
}
|
||||
|
||||
// NetworkList Matches all or part of a network name, so we have to filter for a strict match
|
||||
networks = utils.Filter(networks, func(net network.Summary) bool {
|
||||
return net.Name == n.Name
|
||||
networks = slices.DeleteFunc(networks, func(net network.Summary) bool {
|
||||
return net.Name != n.Name
|
||||
})
|
||||
|
||||
for _, net := range networks {
|
||||
@@ -1392,10 +1403,16 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, project *ty
|
||||
return "", fmt.Errorf("failed to create network %s: %w", n.Name, err)
|
||||
}
|
||||
w.Event(progress.CreatedEvent(networkEventName))
|
||||
|
||||
err = s.connectNetwork(ctx, n.Name, dangledContainers, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return resp.ID, nil
|
||||
}
|
||||
|
||||
func (s *composeService) removeDivergedNetwork(ctx context.Context, project *types.Project, name string, n *types.NetworkConfig) error {
|
||||
func (s *composeService) removeDivergedNetwork(ctx context.Context, project *types.Project, name string, n *types.NetworkConfig) (Containers, error) {
|
||||
// Remove services attached to this network to force recreation
|
||||
var services []string
|
||||
for _, service := range project.Services.Filter(func(config types.ServiceConfig) bool {
|
||||
@@ -1412,13 +1429,54 @@ func (s *composeService) removeDivergedNetwork(ctx context.Context, project *typ
|
||||
Project: project,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
containers, err := s.getContainers(ctx, project.Name, oneOffExclude, true, services...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.disconnectNetwork(ctx, n.Name, containers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.apiClient().NetworkRemove(ctx, n.Name)
|
||||
eventName := fmt.Sprintf("Network %s", n.Name)
|
||||
progress.ContextWriter(ctx).Event(progress.RemovedEvent(eventName))
|
||||
return err
|
||||
return containers, err
|
||||
}
|
||||
|
||||
func (s *composeService) disconnectNetwork(
|
||||
ctx context.Context,
|
||||
network string,
|
||||
containers Containers,
|
||||
) error {
|
||||
for _, c := range containers {
|
||||
err := s.apiClient().NetworkDisconnect(ctx, network, c.ID, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *composeService) connectNetwork(
|
||||
ctx context.Context,
|
||||
network string,
|
||||
containers Containers,
|
||||
config *network.EndpointSettings,
|
||||
) error {
|
||||
for _, c := range containers {
|
||||
err := s.apiClient().NetworkConnect(ctx, network, c.ID, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.NetworkConfig) (string, error) {
|
||||
@@ -1436,18 +1494,19 @@ func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.Ne
|
||||
if len(networks) == 0 {
|
||||
// in this instance, n.Name is really an ID
|
||||
sn, err := s.apiClient().NetworkInspect(ctx, n.Name, network.InspectOptions{})
|
||||
if err != nil && !errdefs.IsNotFound(err) {
|
||||
if err == nil {
|
||||
networks = append(networks, sn)
|
||||
} else if !cerrdefs.IsNotFound(err) {
|
||||
return "", err
|
||||
}
|
||||
networks = append(networks, sn)
|
||||
|
||||
}
|
||||
|
||||
// NetworkList API doesn't return the exact name match, so we can retrieve more than one network with a request
|
||||
networks = utils.Filter(networks, func(net network.Inspect) bool {
|
||||
// later in this function, the name is changed the to ID.
|
||||
networks = slices.DeleteFunc(networks, func(net network.Inspect) bool {
|
||||
// this function is called during the rebuild stage of `compose watch`.
|
||||
// we still require just one network back, but we need to run the search on the ID
|
||||
return net.Name == n.Name || net.ID == n.Name
|
||||
return net.Name != n.Name && net.ID != n.Name
|
||||
})
|
||||
|
||||
switch len(networks) {
|
||||
@@ -1474,7 +1533,7 @@ func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.Ne
|
||||
func (s *composeService) ensureVolume(ctx context.Context, name string, volume types.VolumeConfig, project *types.Project, assumeYes bool) (string, error) {
|
||||
inspected, err := s.apiClient().VolumeInspect(ctx, volume.Name)
|
||||
if err != nil {
|
||||
if !errdefs.IsNotFound(err) {
|
||||
if !cerrdefs.IsNotFound(err) {
|
||||
return "", err
|
||||
}
|
||||
if volume.External {
|
||||
|
||||
@@ -19,14 +19,13 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
// ServiceStatus indicates the status of a service
|
||||
@@ -119,7 +118,7 @@ func WithRootNodesAndDown(nodes []string) func(*graphTraversal) {
|
||||
|
||||
t.ignored = map[string]struct{}{}
|
||||
for k := range graph.Vertices {
|
||||
if !utils.Contains(want, k) {
|
||||
if !slices.Contains(want, k) {
|
||||
t.ignored[k] = struct{}{}
|
||||
}
|
||||
}
|
||||
@@ -434,7 +433,7 @@ func (g *Graph) HasCycles() (bool, error) {
|
||||
path := []string{
|
||||
vertex.Key,
|
||||
}
|
||||
if !utils.StringContains(discovered, vertex.Key) && !utils.StringContains(finished, vertex.Key) {
|
||||
if !slices.Contains(discovered, vertex.Key) && !slices.Contains(finished, vertex.Key) {
|
||||
var err error
|
||||
discovered, finished, err = g.visit(vertex.Key, path, discovered, finished)
|
||||
if err != nil {
|
||||
@@ -451,11 +450,11 @@ func (g *Graph) visit(key string, path []string, discovered []string, finished [
|
||||
|
||||
for _, v := range g.Vertices[key].Children {
|
||||
path := append(path, v.Key)
|
||||
if utils.StringContains(discovered, v.Key) {
|
||||
if slices.Contains(discovered, v.Key) {
|
||||
return nil, nil, fmt.Errorf("cycle found: %s", strings.Join(path, " -> "))
|
||||
}
|
||||
|
||||
if !utils.StringContains(finished, v.Key) {
|
||||
if !slices.Contains(finished, v.Key) {
|
||||
if _, _, err := g.visit(v.Key, path, discovered, finished); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
cerrdefs "github.com/containerd/errdefs"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
@@ -30,7 +31,6 @@ import (
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
imageapi "github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
@@ -219,7 +219,7 @@ func (s *composeService) removeNetwork(ctx context.Context, composeNetworkName s
|
||||
continue
|
||||
}
|
||||
nw, err := s.apiClient().NetworkInspect(ctx, net.ID, network.InspectOptions{})
|
||||
if errdefs.IsNotFound(err) {
|
||||
if cerrdefs.IsNotFound(err) {
|
||||
w.Event(progress.NewEvent(eventName, progress.Warning, "No resource found to remove"))
|
||||
return nil
|
||||
}
|
||||
@@ -233,7 +233,7 @@ func (s *composeService) removeNetwork(ctx context.Context, composeNetworkName s
|
||||
}
|
||||
|
||||
if err := s.apiClient().NetworkRemove(ctx, net.ID); err != nil {
|
||||
if errdefs.IsNotFound(err) {
|
||||
if cerrdefs.IsNotFound(err) {
|
||||
continue
|
||||
}
|
||||
w.Event(progress.ErrorEvent(eventName))
|
||||
@@ -261,11 +261,11 @@ func (s *composeService) removeImage(ctx context.Context, image string, w progre
|
||||
w.Event(progress.NewEvent(id, progress.Done, "Removed"))
|
||||
return nil
|
||||
}
|
||||
if errdefs.IsConflict(err) {
|
||||
if cerrdefs.IsConflict(err) {
|
||||
w.Event(progress.NewEvent(id, progress.Warning, "Resource is still in use"))
|
||||
return nil
|
||||
}
|
||||
if errdefs.IsNotFound(err) {
|
||||
if cerrdefs.IsNotFound(err) {
|
||||
w.Event(progress.NewEvent(id, progress.Done, "Warning: No resource found to remove"))
|
||||
return nil
|
||||
}
|
||||
@@ -276,7 +276,7 @@ func (s *composeService) removeVolume(ctx context.Context, id string, w progress
|
||||
resource := fmt.Sprintf("Volume %s", id)
|
||||
|
||||
_, err := s.apiClient().VolumeInspect(ctx, id)
|
||||
if errdefs.IsNotFound(err) {
|
||||
if cerrdefs.IsNotFound(err) {
|
||||
// Already gone
|
||||
return nil
|
||||
}
|
||||
@@ -287,11 +287,11 @@ func (s *composeService) removeVolume(ctx context.Context, id string, w progress
|
||||
w.Event(progress.NewEvent(resource, progress.Done, "Removed"))
|
||||
return nil
|
||||
}
|
||||
if errdefs.IsConflict(err) {
|
||||
if cerrdefs.IsConflict(err) {
|
||||
w.Event(progress.NewEvent(resource, progress.Warning, "Resource is still in use"))
|
||||
return nil
|
||||
}
|
||||
if errdefs.IsNotFound(err) {
|
||||
if cerrdefs.IsNotFound(err) {
|
||||
w.Event(progress.NewEvent(resource, progress.Done, "Warning: No resource found to remove"))
|
||||
return nil
|
||||
}
|
||||
@@ -345,7 +345,7 @@ func (s *composeService) stopAndRemoveContainer(ctx context.Context, ctr contain
|
||||
w := progress.ContextWriter(ctx)
|
||||
eventName := getContainerProgressName(ctr)
|
||||
err := s.stopContainer(ctx, w, service, ctr, timeout)
|
||||
if errdefs.IsNotFound(err) {
|
||||
if cerrdefs.IsNotFound(err) {
|
||||
w.Event(progress.RemovedEvent(eventName))
|
||||
return nil
|
||||
}
|
||||
@@ -357,7 +357,7 @@ func (s *composeService) stopAndRemoveContainer(ctx context.Context, ctr contain
|
||||
Force: true,
|
||||
RemoveVolumes: volumes,
|
||||
})
|
||||
if err != nil && !errdefs.IsNotFound(err) && !errdefs.IsConflict(err) {
|
||||
if err != nil && !cerrdefs.IsNotFound(err) && !cerrdefs.IsConflict(err) {
|
||||
w.Event(progress.ErrorMessageEvent(eventName, "Error while Removing"))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -25,7 +26,6 @@ import (
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
func (s *composeService) Events(ctx context.Context, projectName string, options api.EventsOptions) error {
|
||||
@@ -47,7 +47,7 @@ func (s *composeService) Events(ctx context.Context, projectName string, options
|
||||
continue
|
||||
}
|
||||
service := event.Actor.Attributes[api.ServiceLabel]
|
||||
if len(options.Services) > 0 && !utils.StringContains(options.Services, service) {
|
||||
if len(options.Services) > 0 && !slices.Contains(options.Services, service) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -19,17 +19,16 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
func (s *composeService) Generate(ctx context.Context, options api.GenerateOptions) (*types.Project, error) {
|
||||
@@ -54,8 +53,11 @@ func (s *composeService) Generate(ctx context.Context, options api.GenerateOptio
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, ctr := range containersByIds {
|
||||
if !utils.Contains(containers, ctr) {
|
||||
if !slices.ContainsFunc(containers, func(summary container.Summary) bool {
|
||||
return summary.ID == ctr.ID
|
||||
}) {
|
||||
containers = append(containers, ctr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@ func (s composeService) runHook(ctx context.Context, ctr container.Summary, serv
|
||||
Env: ToMobyEnv(hook.Environment),
|
||||
WorkingDir: hook.WorkingDir,
|
||||
Cmd: hook.Command,
|
||||
Detach: detached,
|
||||
AttachStdout: !detached,
|
||||
AttachStderr: !detached,
|
||||
})
|
||||
|
||||
@@ -23,11 +23,11 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
cerrdefs "github.com/containerd/errdefs"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@@ -204,7 +204,7 @@ func (p *ImagePruner) filterImagesByExistence(ctx context.Context, imageNames []
|
||||
for _, img := range imageNames {
|
||||
eg.Go(func() error {
|
||||
_, err := p.client.ImageInspect(ctx, img)
|
||||
if errdefs.IsNotFound(err) {
|
||||
if cerrdefs.IsNotFound(err) {
|
||||
// err on the side of caution: only skip if we successfully
|
||||
// queried the API and got back a definitive "not exists"
|
||||
return nil
|
||||
|
||||
@@ -19,20 +19,23 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
cerrdefs "github.com/containerd/errdefs"
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/client"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
func (s *composeService) Images(ctx context.Context, projectName string, options api.ImagesOptions) ([]api.ImageSummary, error) {
|
||||
func (s *composeService) Images(ctx context.Context, projectName string, options api.ImagesOptions) (map[string]api.ImageSummary, error) {
|
||||
projectName = strings.ToLower(projectName)
|
||||
allContainers, err := s.apiClient().ContainerList(ctx, container.ListOptions{
|
||||
All: true,
|
||||
@@ -45,7 +48,7 @@ func (s *composeService) Images(ctx context.Context, projectName string, options
|
||||
if len(options.Services) > 0 {
|
||||
// filter service containers
|
||||
for _, c := range allContainers {
|
||||
if utils.StringContains(options.Services, c.Labels[api.ServiceLabel]) {
|
||||
if slices.Contains(options.Services, c.Labels[api.ServiceLabel]) {
|
||||
containers = append(containers, c)
|
||||
}
|
||||
}
|
||||
@@ -53,27 +56,61 @@ func (s *composeService) Images(ctx context.Context, projectName string, options
|
||||
containers = allContainers
|
||||
}
|
||||
|
||||
images := []string{}
|
||||
for _, c := range containers {
|
||||
if !utils.StringContains(images, c.Image) {
|
||||
images = append(images, c.Image)
|
||||
}
|
||||
}
|
||||
imageSummaries, err := s.getImageSummaries(ctx, images)
|
||||
version, err := s.RuntimeVersion(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
summary := make([]api.ImageSummary, len(containers))
|
||||
for i, c := range containers {
|
||||
img, ok := imageSummaries[c.Image]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to retrieve image for container %s", getCanonicalContainerName(c))
|
||||
}
|
||||
withPlatform := versions.GreaterThanOrEqualTo(version, "1.49")
|
||||
|
||||
summary[i] = img
|
||||
summary[i].ContainerName = getCanonicalContainerName(c)
|
||||
summary := map[string]api.ImageSummary{}
|
||||
var mux sync.Mutex
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
for _, c := range containers {
|
||||
eg.Go(func() error {
|
||||
image, err := s.apiClient().ImageInspect(ctx, c.Image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
id := image.ID // platform-specific image ID can't be combined with image tag, see https://github.com/moby/moby/issues/49995
|
||||
|
||||
if withPlatform && c.ImageManifestDescriptor != nil && c.ImageManifestDescriptor.Platform != nil {
|
||||
image, err = s.apiClient().ImageInspect(ctx, c.Image, client.ImageInspectWithPlatform(c.ImageManifestDescriptor.Platform))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var repository, tag string
|
||||
ref, err := reference.ParseDockerRef(c.Image)
|
||||
if err == nil {
|
||||
// ParseDockerRef will reject a local image ID
|
||||
repository = reference.FamiliarName(ref)
|
||||
if tagged, ok := ref.(reference.Tagged); ok {
|
||||
tag = tagged.Tag()
|
||||
}
|
||||
}
|
||||
|
||||
mux.Lock()
|
||||
defer mux.Unlock()
|
||||
summary[getCanonicalContainerName(c)] = api.ImageSummary{
|
||||
ID: id,
|
||||
Repository: repository,
|
||||
Tag: tag,
|
||||
Platform: platforms.Platform{
|
||||
Architecture: image.Architecture,
|
||||
OS: image.Os,
|
||||
OSVersion: image.OsVersion,
|
||||
Variant: image.Variant,
|
||||
},
|
||||
Size: image.Size,
|
||||
LastTagTime: image.Metadata.LastTagTime,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return summary, nil
|
||||
|
||||
err = eg.Wait()
|
||||
return summary, err
|
||||
}
|
||||
|
||||
func (s *composeService) getImageSummaries(ctx context.Context, repoTags []string) (map[string]api.ImageSummary, error) {
|
||||
@@ -84,7 +121,7 @@ func (s *composeService) getImageSummaries(ctx context.Context, repoTags []strin
|
||||
eg.Go(func() error {
|
||||
inspect, err := s.apiClient().ImageInspect(ctx, repoTag)
|
||||
if err != nil {
|
||||
if errdefs.IsNotFound(err) {
|
||||
if cerrdefs.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unable to get image '%s': %w", repoTag, err)
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
@@ -42,9 +43,10 @@ func TestImages(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
args := filters.NewArgs(projectFilter(strings.ToLower(testProject)))
|
||||
listOpts := container.ListOptions{All: true, Filters: args}
|
||||
api.EXPECT().ServerVersion(gomock.Any()).Return(types.Version{APIVersion: "1.96"}, nil).AnyTimes()
|
||||
image1 := imageInspect("image1", "foo:1", 12345)
|
||||
image2 := imageInspect("image2", "bar:2", 67890)
|
||||
api.EXPECT().ImageInspect(anyCancellableContext(), "foo:1").Return(image1, nil)
|
||||
api.EXPECT().ImageInspect(anyCancellableContext(), "foo:1").Return(image1, nil).MaxTimes(2)
|
||||
api.EXPECT().ImageInspect(anyCancellableContext(), "bar:2").Return(image2, nil)
|
||||
c1 := containerDetail("service1", "123", "running", "foo:1")
|
||||
c2 := containerDetail("service1", "456", "running", "bar:2")
|
||||
@@ -54,27 +56,24 @@ func TestImages(t *testing.T) {
|
||||
|
||||
images, err := tested.Images(ctx, strings.ToLower(testProject), compose.ImagesOptions{})
|
||||
|
||||
expected := []compose.ImageSummary{
|
||||
{
|
||||
ID: "image1",
|
||||
ContainerName: "123",
|
||||
Repository: "foo",
|
||||
Tag: "1",
|
||||
Size: 12345,
|
||||
expected := map[string]compose.ImageSummary{
|
||||
"123": {
|
||||
ID: "image1",
|
||||
Repository: "foo",
|
||||
Tag: "1",
|
||||
Size: 12345,
|
||||
},
|
||||
{
|
||||
ID: "image2",
|
||||
ContainerName: "456",
|
||||
Repository: "bar",
|
||||
Tag: "2",
|
||||
Size: 67890,
|
||||
"456": {
|
||||
ID: "image2",
|
||||
Repository: "bar",
|
||||
Tag: "2",
|
||||
Size: 67890,
|
||||
},
|
||||
{
|
||||
ID: "image1",
|
||||
ContainerName: "789",
|
||||
Repository: "foo",
|
||||
Tag: "1",
|
||||
Size: 12345,
|
||||
"789": {
|
||||
ID: "image1",
|
||||
Repository: "foo",
|
||||
Tag: "1",
|
||||
Size: 12345,
|
||||
},
|
||||
}
|
||||
assert.NilError(t, err)
|
||||
|
||||
@@ -19,11 +19,11 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -74,7 +74,7 @@ func combinedConfigFiles(containers []container.Summary) (string, error) {
|
||||
}
|
||||
|
||||
for _, f := range strings.Split(files, ",") {
|
||||
if !utils.StringContains(configFiles, f) {
|
||||
if !slices.Contains(configFiles, f) {
|
||||
configFiles = append(configFiles, f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -24,11 +25,13 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli-plugins/socket"
|
||||
"github.com/docker/cli/cli/config"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -42,10 +45,11 @@ type JsonMessage struct {
|
||||
}
|
||||
|
||||
const (
|
||||
ErrorType = "error"
|
||||
InfoType = "info"
|
||||
SetEnvType = "setenv"
|
||||
DebugType = "debug"
|
||||
ErrorType = "error"
|
||||
InfoType = "info"
|
||||
SetEnvType = "setenv"
|
||||
DebugType = "debug"
|
||||
providerMetadataDirectory = "compose/providers"
|
||||
)
|
||||
|
||||
func (s *composeService) runPlugin(ctx context.Context, project *types.Project, service types.ServiceConfig, command string) error {
|
||||
@@ -56,7 +60,10 @@ func (s *composeService) runPlugin(ctx context.Context, project *types.Project,
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := s.setupPluginCommand(ctx, project, service, plugin, command)
|
||||
cmd, err := s.setupPluginCommand(ctx, project, service, plugin, command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
variables, err := s.executePlugin(ctx, cmd, command, service)
|
||||
if err != nil {
|
||||
@@ -160,13 +167,27 @@ func (s *composeService) getPluginBinaryPath(provider string) (path string, err
|
||||
return path, err
|
||||
}
|
||||
|
||||
func (s *composeService) setupPluginCommand(ctx context.Context, project *types.Project, service types.ServiceConfig, path, command string) *exec.Cmd {
|
||||
func (s *composeService) setupPluginCommand(ctx context.Context, project *types.Project, service types.ServiceConfig, path, command string) (*exec.Cmd, error) {
|
||||
cmdOptionsMetadata := s.getPluginMetadata(path, service.Provider.Type)
|
||||
var currentCommandMetadata CommandMetadata
|
||||
switch command {
|
||||
case "up":
|
||||
currentCommandMetadata = cmdOptionsMetadata.Up
|
||||
case "down":
|
||||
currentCommandMetadata = cmdOptionsMetadata.Down
|
||||
}
|
||||
commandMetadataIsEmpty := len(currentCommandMetadata.Parameters) == 0
|
||||
provider := *service.Provider
|
||||
if err := currentCommandMetadata.CheckRequiredParameters(provider); !commandMetadataIsEmpty && err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
args := []string{"compose", "--project-name", project.Name, command}
|
||||
for k, v := range provider.Options {
|
||||
for _, value := range v {
|
||||
args = append(args, fmt.Sprintf("--%s=%s", k, value))
|
||||
if _, ok := currentCommandMetadata.GetParameter(k); commandMetadataIsEmpty || ok {
|
||||
args = append(args, fmt.Sprintf("--%s=%s", k, value))
|
||||
}
|
||||
}
|
||||
}
|
||||
args = append(args, service.Name)
|
||||
@@ -186,7 +207,6 @@ func (s *composeService) setupPluginCommand(ctx context.Context, project *types.
|
||||
server, err := socket.NewPluginServer(nil)
|
||||
if err == nil {
|
||||
defer server.Close() //nolint:errcheck
|
||||
cmd.Cancel = server.Close
|
||||
cmd.Env = replace(cmd.Env, socket.EnvKey, server.Addr().String())
|
||||
}
|
||||
|
||||
@@ -196,5 +216,73 @@ func (s *composeService) setupPluginCommand(ctx context.Context, project *types.
|
||||
carrier := propagation.MapCarrier{}
|
||||
otel.GetTextMapPropagator().Inject(ctx, &carrier)
|
||||
cmd.Env = append(cmd.Env, types.Mapping(carrier).Values()...)
|
||||
return cmd
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func (s *composeService) getPluginMetadata(path, command string) ProviderMetadata {
|
||||
cmd := exec.Command(path, "compose", "metadata")
|
||||
stdout := &bytes.Buffer{}
|
||||
cmd.Stdout = stdout
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
logrus.Debugf("failed to start plugin metadata command: %v", err)
|
||||
return ProviderMetadata{}
|
||||
}
|
||||
|
||||
var metadata ProviderMetadata
|
||||
if err := json.Unmarshal(stdout.Bytes(), &metadata); err != nil {
|
||||
output, _ := io.ReadAll(stdout)
|
||||
logrus.Debugf("failed to decode plugin metadata: %v - %s", err, output)
|
||||
return ProviderMetadata{}
|
||||
}
|
||||
// Save metadata into docker home directory to be used by Docker LSP tool
|
||||
// Just log the error as it's not a critical error for the main flow
|
||||
metadataDir := filepath.Join(config.Dir(), providerMetadataDirectory)
|
||||
if err := os.MkdirAll(metadataDir, 0o700); err == nil {
|
||||
metadataFilePath := filepath.Join(metadataDir, command+".json")
|
||||
if err := os.WriteFile(metadataFilePath, stdout.Bytes(), 0o600); err != nil {
|
||||
logrus.Debugf("failed to save plugin metadata: %v", err)
|
||||
}
|
||||
} else {
|
||||
logrus.Debugf("failed to create plugin metadata directory: %v", err)
|
||||
}
|
||||
return metadata
|
||||
}
|
||||
|
||||
type ProviderMetadata struct {
|
||||
Description string `json:"description"`
|
||||
Up CommandMetadata `json:"up"`
|
||||
Down CommandMetadata `json:"down"`
|
||||
}
|
||||
|
||||
type CommandMetadata struct {
|
||||
Parameters []ParameterMetadata `json:"parameters"`
|
||||
}
|
||||
|
||||
type ParameterMetadata struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Required bool `json:"required"`
|
||||
Type string `json:"type"`
|
||||
Default string `json:"default,omitempty"`
|
||||
}
|
||||
|
||||
func (c CommandMetadata) GetParameter(paramName string) (ParameterMetadata, bool) {
|
||||
for _, p := range c.Parameters {
|
||||
if p.Name == paramName {
|
||||
return p, true
|
||||
}
|
||||
}
|
||||
return ParameterMetadata{}, false
|
||||
}
|
||||
|
||||
func (c CommandMetadata) CheckRequiredParameters(provider types.ServiceProviderConfig) error {
|
||||
for _, p := range c.Parameters {
|
||||
if p.Required {
|
||||
if _, ok := provider.Options[p.Name]; !ok {
|
||||
return fmt.Errorf("required parameter %q is missing from provider %q definition", p.Name, provider.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -22,12 +22,13 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"slices"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli"
|
||||
cmd "github.com/docker/cli/cli/command/container"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
)
|
||||
|
||||
@@ -58,6 +59,19 @@ func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.
|
||||
}
|
||||
|
||||
func (s *composeService) prepareRun(ctx context.Context, project *types.Project, opts api.RunOptions) (string, error) {
|
||||
// Temporary implementation of use_api_socket until we get actual support inside docker engine
|
||||
project, err := s.useAPISocket(project)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = progress.Run(ctx, func(ctx context.Context) error {
|
||||
return s.startDependencies(ctx, project, opts)
|
||||
}, s.stdinfo())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
service, err := project.GetService(opts.Service)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -113,7 +127,14 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return created.ID, nil
|
||||
|
||||
err = s.injectSecrets(ctx, project, service, created.ID)
|
||||
if err != nil {
|
||||
return created.ID, err
|
||||
}
|
||||
|
||||
err = s.injectConfigs(ctx, project, service, created.ID)
|
||||
return created.ID, err
|
||||
}
|
||||
|
||||
func applyRunOptions(project *types.Project, service *types.ServiceConfig, opts api.RunOptions) {
|
||||
@@ -130,11 +151,11 @@ func applyRunOptions(project *types.Project, service *types.ServiceConfig, opts
|
||||
|
||||
if len(opts.CapAdd) > 0 {
|
||||
service.CapAdd = append(service.CapAdd, opts.CapAdd...)
|
||||
service.CapDrop = utils.Remove(service.CapDrop, opts.CapAdd...)
|
||||
service.CapDrop = slices.DeleteFunc(service.CapDrop, func(e string) bool { return slices.Contains(opts.CapAdd, e) })
|
||||
}
|
||||
if len(opts.CapDrop) > 0 {
|
||||
service.CapDrop = append(service.CapDrop, opts.CapDrop...)
|
||||
service.CapAdd = utils.Remove(service.CapAdd, opts.CapDrop...)
|
||||
service.CapAdd = slices.DeleteFunc(service.CapAdd, func(e string) bool { return slices.Contains(opts.CapDrop, e) })
|
||||
}
|
||||
if opts.WorkingDir != "" {
|
||||
service.WorkingDir = opts.WorkingDir
|
||||
@@ -160,3 +181,24 @@ func applyRunOptions(project *types.Project, service *types.ServiceConfig, opts
|
||||
service.Labels = service.Labels.Add(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *composeService) startDependencies(ctx context.Context, project *types.Project, options api.RunOptions) error {
|
||||
project = project.WithServicesDisabled(options.Service)
|
||||
|
||||
err := s.Create(ctx, project, api.CreateOptions{
|
||||
Build: options.Build,
|
||||
IgnoreOrphans: options.IgnoreOrphans,
|
||||
RemoveOrphans: options.RemoveOrphans,
|
||||
QuietPull: options.QuietPull,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(project.Services) > 0 {
|
||||
return s.Start(ctx, project.Name, api.StartOptions{
|
||||
Project: project,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -20,15 +20,15 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
containerType "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/errdefs"
|
||||
|
||||
cerrdefs "github.com/containerd/errdefs"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
containerType "github.com/docker/docker/api/types/container"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
@@ -199,7 +199,7 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
|
||||
ofInterest := func(c containerType.Summary) bool {
|
||||
if len(services) > 0 {
|
||||
// we only watch some services
|
||||
return utils.Contains(services, c.Labels[api.ServiceLabel])
|
||||
return slices.Contains(services, c.Labels[api.ServiceLabel])
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -208,7 +208,7 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
|
||||
isRequired := func(c containerType.Summary) bool {
|
||||
if len(services) > 0 && len(required) > 0 {
|
||||
// we only watch some services
|
||||
return utils.Contains(required, c.Labels[api.ServiceLabel])
|
||||
return slices.Contains(required, c.Labels[api.ServiceLabel])
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -237,7 +237,7 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
|
||||
}()
|
||||
inspected, err := s.apiClient().ContainerInspect(ctx, event.Container)
|
||||
if err != nil {
|
||||
if errdefs.IsNotFound(err) {
|
||||
if cerrdefs.IsNotFound(err) {
|
||||
// it's possible to get "destroy" or "kill" events but not
|
||||
// be able to inspect in time before they're gone from the
|
||||
// API, so just remove the watch without erroring
|
||||
@@ -263,8 +263,8 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
|
||||
}
|
||||
if _, ok := watched[container.ID]; ok {
|
||||
eType := api.ContainerEventStopped
|
||||
if utils.Contains(replaced, container.ID) {
|
||||
utils.Remove(replaced, container.ID)
|
||||
if slices.Contains(replaced, container.ID) {
|
||||
replaced = slices.DeleteFunc(replaced, func(e string) bool { return e == container.ID })
|
||||
eType = api.ContainerEventRecreated
|
||||
}
|
||||
listener(api.ContainerEvent{
|
||||
@@ -290,8 +290,8 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
|
||||
}
|
||||
|
||||
eType := api.ContainerEventExit
|
||||
if utils.Contains(replaced, container.ID) {
|
||||
utils.Remove(replaced, container.ID)
|
||||
if slices.Contains(replaced, container.ID) {
|
||||
replaced = slices.DeleteFunc(replaced, func(e string) bool { return e == container.ID })
|
||||
eType = api.ContainerEventRecreated
|
||||
}
|
||||
|
||||
|
||||
@@ -18,11 +18,11 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
func (s *composeService) Stop(ctx context.Context, projectName string, options api.StopOptions) error {
|
||||
@@ -51,7 +51,7 @@ func (s *composeService) stop(ctx context.Context, projectName string, options a
|
||||
|
||||
w := progress.ContextWriter(ctx)
|
||||
return InReverseDependencyOrder(ctx, project, func(c context.Context, service string) error {
|
||||
if !utils.StringContains(options.Services, service) {
|
||||
if !slices.Contains(options.Services, service) {
|
||||
return nil
|
||||
}
|
||||
serv := project.Services[service]
|
||||
|
||||
@@ -25,12 +25,12 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
cerrdefs "github.com/containerd/errdefs"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/internal/tracing"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/eiannone/keyboard"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -72,6 +72,12 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
|
||||
var isTerminated atomic.Bool
|
||||
printer := newLogPrinter(options.Start.Attach)
|
||||
|
||||
watcher, err := NewWatcher(project, options, s.watch)
|
||||
if err != nil && options.Start.Watch {
|
||||
return err
|
||||
}
|
||||
|
||||
var navigationMenu *formatter.LogKeyboard
|
||||
var kEvents <-chan keyboard.KeyEvent
|
||||
if options.Start.NavigationMenu {
|
||||
kEvents, err = keyboard.GetKeys(100)
|
||||
@@ -80,20 +86,14 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
|
||||
options.Start.NavigationMenu = false
|
||||
} else {
|
||||
defer keyboard.Close() //nolint:errcheck
|
||||
isWatchConfigured := s.shouldWatch(project)
|
||||
isDockerDesktopActive := s.isDesktopIntegrationActive()
|
||||
tracing.KeyboardMetrics(ctx, options.Start.NavigationMenu, isDockerDesktopActive, isWatchConfigured)
|
||||
formatter.NewKeyboardManager(ctx, isDockerDesktopActive, isWatchConfigured, signalChan, s.watch)
|
||||
tracing.KeyboardMetrics(ctx, options.Start.NavigationMenu, isDockerDesktopActive, watcher != nil)
|
||||
navigationMenu = formatter.NewKeyboardManager(isDockerDesktopActive, signalChan, options.Start.Watch, watcher)
|
||||
}
|
||||
}
|
||||
|
||||
doneCh := make(chan bool)
|
||||
eg.Go(func() error {
|
||||
if options.Start.NavigationMenu && options.Start.Watch {
|
||||
// Run watch by navigation menu, so we can interactively enable/disable
|
||||
formatter.KeyboardManager.StartWatch(ctx, doneCh, project, options)
|
||||
}
|
||||
|
||||
first := true
|
||||
gracefulTeardown := func() {
|
||||
printer.Cancel()
|
||||
@@ -112,6 +112,9 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
|
||||
for {
|
||||
select {
|
||||
case <-doneCh:
|
||||
if watcher != nil {
|
||||
return watcher.Stop()
|
||||
}
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
if first {
|
||||
@@ -119,6 +122,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
|
||||
}
|
||||
case <-signalChan:
|
||||
if first {
|
||||
keyboard.Close() //nolint:errcheck
|
||||
gracefulTeardown()
|
||||
break
|
||||
}
|
||||
@@ -129,7 +133,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
|
||||
All: true,
|
||||
})
|
||||
// Ignore errors indicating that some of the containers were already stopped or removed.
|
||||
if errdefs.IsNotFound(err) || errdefs.IsConflict(err) {
|
||||
if cerrdefs.IsNotFound(err) || cerrdefs.IsConflict(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -137,7 +141,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
|
||||
})
|
||||
return nil
|
||||
case event := <-kEvents:
|
||||
formatter.KeyboardManager.HandleKeyEvents(event, ctx, doneCh, project, options)
|
||||
navigationMenu.HandleKeyEvents(ctx, event, project, options)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -157,15 +161,11 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
|
||||
return err
|
||||
})
|
||||
|
||||
if options.Start.Watch && !options.Start.NavigationMenu {
|
||||
eg.Go(func() error {
|
||||
buildOpts := *options.Create.Build
|
||||
buildOpts.Quiet = true
|
||||
return s.watch(ctx, doneCh, project, options.Start.Services, api.WatchOptions{
|
||||
Build: &buildOpts,
|
||||
LogTo: options.Start.Attach,
|
||||
})
|
||||
})
|
||||
if options.Start.Watch && watcher != nil {
|
||||
err = watcher.Start(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// We use the parent context without cancellation as we manage sigterm to stop the stack
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
gsync "sync"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
@@ -44,6 +45,68 @@ import (
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type WatchFunc func(ctx context.Context, project *types.Project, options api.WatchOptions) (func() error, error)
|
||||
|
||||
type Watcher struct {
|
||||
project *types.Project
|
||||
options api.WatchOptions
|
||||
watchFn WatchFunc
|
||||
stopFn func()
|
||||
errCh chan error
|
||||
}
|
||||
|
||||
func NewWatcher(project *types.Project, options api.UpOptions, w WatchFunc) (*Watcher, error) {
|
||||
for i := range project.Services {
|
||||
service := project.Services[i]
|
||||
|
||||
if service.Develop != nil && service.Develop.Watch != nil {
|
||||
build := options.Create.Build
|
||||
build.Quiet = true
|
||||
return &Watcher{
|
||||
project: project,
|
||||
options: api.WatchOptions{
|
||||
LogTo: options.Start.Attach,
|
||||
Build: build,
|
||||
},
|
||||
watchFn: w,
|
||||
errCh: make(chan error),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
// none of the services is eligible to watch
|
||||
return nil, fmt.Errorf("none of the selected services is configured for watch, see https://docs.docker.com/compose/how-tos/file-watch/")
|
||||
}
|
||||
|
||||
// ensure state changes are atomic
|
||||
var mx gsync.Mutex
|
||||
|
||||
func (w *Watcher) Start(ctx context.Context) error {
|
||||
mx.Lock()
|
||||
defer mx.Unlock()
|
||||
ctx, cancelFunc := context.WithCancel(ctx)
|
||||
w.stopFn = cancelFunc
|
||||
wait, err := w.watchFn(ctx, w.project, w.options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
w.errCh <- wait()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Watcher) Stop() error {
|
||||
mx.Lock()
|
||||
defer mx.Unlock()
|
||||
if w.stopFn == nil {
|
||||
return nil
|
||||
}
|
||||
w.stopFn()
|
||||
w.stopFn = nil
|
||||
err := <-w.errCh
|
||||
return err
|
||||
}
|
||||
|
||||
// getSyncImplementation returns an appropriate sync implementation for the
|
||||
// project.
|
||||
//
|
||||
@@ -63,20 +126,12 @@ func (s *composeService) getSyncImplementation(project *types.Project) (sync.Syn
|
||||
return sync.NewTar(project.Name, tarDockerClient{s: s}), nil
|
||||
}
|
||||
|
||||
func (s *composeService) shouldWatch(project *types.Project) bool {
|
||||
var shouldWatch bool
|
||||
for i := range project.Services {
|
||||
service := project.Services[i]
|
||||
|
||||
if service.Develop != nil && service.Develop.Watch != nil {
|
||||
shouldWatch = true
|
||||
}
|
||||
func (s *composeService) Watch(ctx context.Context, project *types.Project, options api.WatchOptions) error {
|
||||
wait, err := s.watch(ctx, project, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return shouldWatch
|
||||
}
|
||||
|
||||
func (s *composeService) Watch(ctx context.Context, project *types.Project, services []string, options api.WatchOptions) error {
|
||||
return s.watch(ctx, nil, project, services, options)
|
||||
return wait()
|
||||
}
|
||||
|
||||
type watchRule struct {
|
||||
@@ -127,14 +182,14 @@ func (r watchRule) Matches(event watch.FileEvent) *sync.PathMapping {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *composeService) watch(ctx context.Context, syncChannel chan bool, project *types.Project, services []string, options api.WatchOptions) error { //nolint: gocyclo
|
||||
func (s *composeService) watch(ctx context.Context, project *types.Project, options api.WatchOptions) (func() error, error) { //nolint: gocyclo
|
||||
var err error
|
||||
if project, err = project.WithSelectedServices(services); err != nil {
|
||||
return err
|
||||
if project, err = project.WithSelectedServices(options.Services); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
syncer, err := s.getSyncImplementation(project)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
options.LogTo.Register(api.WatchLogger)
|
||||
@@ -146,7 +201,7 @@ func (s *composeService) watch(ctx context.Context, syncChannel chan bool, proje
|
||||
for serviceName, service := range project.Services {
|
||||
config, err := loadDevelopmentConfig(service, project)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if service.Develop != nil {
|
||||
@@ -160,10 +215,10 @@ func (s *composeService) watch(ctx context.Context, syncChannel chan bool, proje
|
||||
for _, trigger := range config.Watch {
|
||||
if trigger.Action == types.WatchActionRebuild {
|
||||
if service.Build == nil {
|
||||
return fmt.Errorf("can't watch service %q with action %s without a build context", service.Name, types.WatchActionRebuild)
|
||||
return nil, fmt.Errorf("can't watch service %q with action %s without a build context", service.Name, types.WatchActionRebuild)
|
||||
}
|
||||
if options.Build == nil {
|
||||
return fmt.Errorf("--no-build is incompatible with watch action %s in service %s", types.WatchActionRebuild, service.Name)
|
||||
return nil, fmt.Errorf("--no-build is incompatible with watch action %s in service %s", types.WatchActionRebuild, service.Name)
|
||||
}
|
||||
// set the service to always be built - watch triggers `Up()` when it receives a rebuild event
|
||||
service.PullPolicy = types.PullPolicyBuild
|
||||
@@ -182,7 +237,7 @@ func (s *composeService) watch(ctx context.Context, syncChannel chan bool, proje
|
||||
// Need to check initial files are in container that are meant to be synched from watch action
|
||||
err := s.initialSync(ctx, project, service, trigger, syncer)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,45 +246,37 @@ func (s *composeService) watch(ctx context.Context, syncChannel chan bool, proje
|
||||
|
||||
serviceWatchRules, err := getWatchRules(config, service)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
rules = append(rules, serviceWatchRules...)
|
||||
}
|
||||
|
||||
if len(paths) == 0 {
|
||||
return fmt.Errorf("none of the selected services is configured for watch, consider setting an 'develop' section")
|
||||
return nil, fmt.Errorf("none of the selected services is configured for watch, consider setting a 'develop' section")
|
||||
}
|
||||
|
||||
watcher, err := watch.NewWatcher(paths)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = watcher.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := watcher.Close(); err != nil {
|
||||
logrus.Debugf("Error closing watcher: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
eg.Go(func() error {
|
||||
return s.watchEvents(ctx, project, options, watcher, syncer, rules)
|
||||
})
|
||||
options.LogTo.Log(api.WatchLogger, "Watch enabled")
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return eg.Wait()
|
||||
case <-syncChannel:
|
||||
options.LogTo.Log(api.WatchLogger, "Watch disabled")
|
||||
return nil
|
||||
return func() error {
|
||||
err := eg.Wait()
|
||||
if werr := watcher.Close(); werr != nil {
|
||||
logrus.Debugf("Error closing Watcher: %v", werr)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getWatchRules(config *types.DevelopConfig, service types.ServiceConfig) ([]watchRule, error) {
|
||||
@@ -295,8 +342,13 @@ func (s *composeService) watchEvents(ctx context.Context, project *types.Project
|
||||
case <-ctx.Done():
|
||||
options.LogTo.Log(api.WatchLogger, "Watch disabled")
|
||||
return nil
|
||||
case err := <-watcher.Errors():
|
||||
options.LogTo.Err(api.WatchLogger, "Watch disabled with errors")
|
||||
case err, open := <-watcher.Errors():
|
||||
if err != nil {
|
||||
options.LogTo.Err(api.WatchLogger, "Watch disabled with errors: "+err.Error())
|
||||
}
|
||||
if open {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
case batch := <-batchEvents:
|
||||
start := time.Now()
|
||||
@@ -578,7 +630,7 @@ func (s *composeService) rebuild(ctx context.Context, project *types.Project, se
|
||||
return err
|
||||
}
|
||||
|
||||
p, err := project.WithSelectedServices(services)
|
||||
p, err := project.WithSelectedServices(services, types.IncludeDependents)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
59
pkg/e2e/bridge_test.go
Normal file
59
pkg/e2e/bridge_test.go
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
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 e2e
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestConvertAndTransformList(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
const projectName = "bridge"
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
t.Run("kubernetes manifests", func(t *testing.T) {
|
||||
kubedir := filepath.Join(tmpDir, "kubernetes")
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/bridge/compose.yaml", "--project-name", projectName, "bridge", "convert",
|
||||
"--output", kubedir)
|
||||
assert.NilError(t, res.Error)
|
||||
assert.Equal(t, res.ExitCode, 0)
|
||||
res = c.RunCmd(t, "diff", "-r", kubedir, "./fixtures/bridge/expected-kubernetes")
|
||||
assert.NilError(t, res.Error, res.Combined())
|
||||
})
|
||||
|
||||
t.Run("helm charts", func(t *testing.T) {
|
||||
helmDir := filepath.Join(tmpDir, "helm")
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/bridge/compose.yaml", "--project-name", projectName, "bridge", "convert",
|
||||
"--output", helmDir, "--transformation", "docker/compose-bridge-helm")
|
||||
assert.NilError(t, res.Error)
|
||||
assert.Equal(t, res.ExitCode, 0)
|
||||
res = c.RunCmd(t, "diff", "-r", helmDir, "./fixtures/bridge/expected-helm")
|
||||
assert.NilError(t, res.Error, res.Combined())
|
||||
})
|
||||
|
||||
t.Run("list transformers images", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "--project-name", projectName, "bridge", "transformations",
|
||||
"ls")
|
||||
assert.Assert(t, strings.Contains(res.Stdout(), "docker/compose-bridge-helm"), res.Combined())
|
||||
assert.Assert(t, strings.Contains(res.Stdout(), "docker/compose-bridge-kubernetes"), res.Combined())
|
||||
})
|
||||
}
|
||||
@@ -284,7 +284,7 @@ func TestBuildImageDependencies(t *testing.T) {
|
||||
|
||||
t.Run("BuildKit by dependency order", func(t *testing.T) {
|
||||
cli := NewCLI(t, WithEnv(
|
||||
"DOCKER_BUILDKIT=1",
|
||||
"DOCKER_BUILDKIT=1", "COMPOSE_BAKE=0",
|
||||
"COMPOSE_FILE=./fixtures/build-dependencies/classic.yaml",
|
||||
))
|
||||
doTest(t, cli, "build")
|
||||
@@ -293,7 +293,7 @@ func TestBuildImageDependencies(t *testing.T) {
|
||||
|
||||
t.Run("BuildKit by additional contexts", func(t *testing.T) {
|
||||
cli := NewCLI(t, WithEnv(
|
||||
"DOCKER_BUILDKIT=1",
|
||||
"DOCKER_BUILDKIT=1", "COMPOSE_BAKE=0",
|
||||
"COMPOSE_FILE=./fixtures/build-dependencies/compose.yaml",
|
||||
))
|
||||
doTest(t, cli, "build")
|
||||
@@ -524,3 +524,59 @@ func TestBuildEntitlements(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildDependsOn(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd(t, "-f", "fixtures/build-dependencies/compose-depends_on.yaml", "down", "--rmi=local")
|
||||
})
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "-f", "fixtures/build-dependencies/compose-depends_on.yaml", "--progress=plain", "up", "test2")
|
||||
out := res.Combined()
|
||||
assert.Check(t, strings.Contains(out, "test1 Built"))
|
||||
}
|
||||
|
||||
func TestBuildSubset(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd(t, "-f", "fixtures/build-test/subset/compose.yaml", "down", "--rmi=local")
|
||||
})
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "-f", "fixtures/build-test/subset/compose.yaml", "build", "main")
|
||||
out := res.Combined()
|
||||
assert.Check(t, strings.Contains(out, "main Built"))
|
||||
}
|
||||
|
||||
func TestBuildDependentImage(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd(t, "-f", "fixtures/build-test/dependencies/compose.yaml", "down", "--rmi=local")
|
||||
})
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "-f", "fixtures/build-test/dependencies/compose.yaml", "build", "firstbuild")
|
||||
out := res.Combined()
|
||||
assert.Check(t, strings.Contains(out, "firstbuild Built"))
|
||||
|
||||
res = c.RunDockerComposeCmd(t, "-f", "fixtures/build-test/dependencies/compose.yaml", "build", "secondbuild")
|
||||
out = res.Combined()
|
||||
assert.Check(t, strings.Contains(out, "secondbuild Built"))
|
||||
}
|
||||
|
||||
func TestBuildSubDependencies(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd(t, "-f", "fixtures/build-test/sub-dependencies/compose.yaml", "down", "--rmi=local")
|
||||
})
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "-f", "fixtures/build-test/sub-dependencies/compose.yaml", "build", "main")
|
||||
out := res.Combined()
|
||||
assert.Check(t, strings.Contains(out, "main Built"))
|
||||
|
||||
res = c.RunDockerComposeCmd(t, "-f", "fixtures/build-test/sub-dependencies/compose.yaml", "up", "--build", "main")
|
||||
out = res.Combined()
|
||||
assert.Check(t, strings.Contains(out, "main Built"))
|
||||
}
|
||||
|
||||
@@ -196,4 +196,18 @@ func TestLocalComposeRun(t *testing.T) {
|
||||
"front", "env")
|
||||
res.Assert(t, icmd.Expected{Out: "FOO=BAR"})
|
||||
})
|
||||
|
||||
t.Run("compose run -rm with stop signal", func(t *testing.T) {
|
||||
projectName := "run-test"
|
||||
res := c.RunDockerComposeCmd(t, "--project-name", projectName, "-f", "./fixtures/ps-test/compose.yaml", "run", "--rm", "-d", "nginx")
|
||||
res.Assert(t, icmd.Success)
|
||||
|
||||
res = c.RunDockerCmd(t, "ps", "--quiet", "--filter", "name=run-test-nginx")
|
||||
containerID := strings.TrimSpace(res.Stdout())
|
||||
|
||||
res = c.RunDockerCmd(t, "stop", containerID)
|
||||
res.Assert(t, icmd.Success)
|
||||
res = c.RunDockerCmd(t, "ps", "--all", "--filter", "name=run-test-nginx", "--format", "'{{.Names}}'")
|
||||
assert.Assert(t, !strings.Contains(res.Stdout(), "run-test-nginx"), res.Stdout())
|
||||
})
|
||||
}
|
||||
|
||||
18
pkg/e2e/fixtures/bridge/Dockerfile
Normal file
18
pkg/e2e/fixtures/bridge/Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
||||
# 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.
|
||||
|
||||
FROM alpine
|
||||
ENV ENV_FROM_DOCKERFILE=1
|
||||
EXPOSE 8081
|
||||
CMD ["echo", "Hello from Dockerfile"]
|
||||
31
pkg/e2e/fixtures/bridge/compose.yaml
Normal file
31
pkg/e2e/fixtures/bridge/compose.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
services:
|
||||
serviceA:
|
||||
image: alpine
|
||||
build: .
|
||||
ports:
|
||||
- 80:8080
|
||||
networks:
|
||||
- private-network
|
||||
configs:
|
||||
- source: my-config
|
||||
target: /etc/my-config1.txt
|
||||
serviceB:
|
||||
image: alpine
|
||||
build: .
|
||||
ports:
|
||||
- 8081:8082
|
||||
secrets:
|
||||
- my-secrets
|
||||
networks:
|
||||
- private-network
|
||||
- public-network
|
||||
configs:
|
||||
my-config:
|
||||
file: my-config.txt
|
||||
secrets:
|
||||
my-secrets:
|
||||
file: not-so-secret.txt
|
||||
networks:
|
||||
private-network:
|
||||
internal: true
|
||||
public-network: {}
|
||||
12
pkg/e2e/fixtures/bridge/expected-helm/Chart.yaml
Executable file
12
pkg/e2e/fixtures/bridge/expected-helm/Chart.yaml
Executable file
@@ -0,0 +1,12 @@
|
||||
#! Chart.yaml
|
||||
apiVersion: v2
|
||||
name: bridge
|
||||
version: 0.0.1
|
||||
# kubeVersion: >= 1.29.1
|
||||
description: A generated Helm Chart for bridge generated via compose-bridge.
|
||||
type: application
|
||||
keywords:
|
||||
- bridge
|
||||
appVersion: 'v0.0.1'
|
||||
sources:
|
||||
annotations:
|
||||
8
pkg/e2e/fixtures/bridge/expected-helm/templates/0-bridge-namespace.yaml
Executable file
8
pkg/e2e/fixtures/bridge/expected-helm/templates/0-bridge-namespace.yaml
Executable file
@@ -0,0 +1,8 @@
|
||||
#! 0-bridge-namespace.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: bridge
|
||||
labels:
|
||||
com.docker.compose.project: bridge
|
||||
12
pkg/e2e/fixtures/bridge/expected-helm/templates/bridge-configs.yaml
Executable file
12
pkg/e2e/fixtures/bridge/expected-helm/templates/bridge-configs.yaml
Executable file
@@ -0,0 +1,12 @@
|
||||
#! bridge-configs.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: bridge
|
||||
namespace: bridge
|
||||
labels:
|
||||
com.docker.compose.project: bridge
|
||||
data:
|
||||
my-config: |
|
||||
My config file
|
||||
13
pkg/e2e/fixtures/bridge/expected-helm/templates/my-secrets-secret.yaml
Executable file
13
pkg/e2e/fixtures/bridge/expected-helm/templates/my-secrets-secret.yaml
Executable file
@@ -0,0 +1,13 @@
|
||||
#! my-secrets-secret.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: my-secrets
|
||||
namespace: {{ .Values.namespace }}
|
||||
labels:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.secret: my-secrets
|
||||
data:
|
||||
my-secrets: bm90LXNlY3JldA==
|
||||
type: Opaque
|
||||
@@ -0,0 +1,24 @@
|
||||
#! private-network-network-policy.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: private-network-network-policy
|
||||
namespace: {{ .Values.namespace }}
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
com.docker.compose.network.private-network: "true"
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
com.docker.compose.network.private-network: "true"
|
||||
egress:
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
com.docker.compose.network.private-network: "true"
|
||||
@@ -0,0 +1,24 @@
|
||||
#! public-network-network-policy.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: public-network-network-policy
|
||||
namespace: {{ .Values.namespace }}
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
com.docker.compose.network.public-network: "true"
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
com.docker.compose.network.public-network: "true"
|
||||
egress:
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
com.docker.compose.network.public-network: "true"
|
||||
45
pkg/e2e/fixtures/bridge/expected-helm/templates/serviceA-deployment.yaml
Executable file
45
pkg/e2e/fixtures/bridge/expected-helm/templates/serviceA-deployment.yaml
Executable file
@@ -0,0 +1,45 @@
|
||||
#! serviceA-deployment.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: servicea
|
||||
namespace: {{ .Values.namespace }}
|
||||
labels:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceA
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceA
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceA
|
||||
com.docker.compose.network.private-network: "true"
|
||||
spec:
|
||||
containers:
|
||||
- name: servicea
|
||||
image: {{ .Values.serviceA.image }}
|
||||
imagePullPolicy: {{ .Values.serviceA.imagePullPolicy }}
|
||||
ports:
|
||||
- name: servicea-8080
|
||||
containerPort: 8080
|
||||
volumeMounts:
|
||||
- name: etc-my-config1-txt
|
||||
mountPath: /etc/my-config1.txt
|
||||
subPath: my-config
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: etc-my-config1-txt
|
||||
configMap:
|
||||
name: bridge
|
||||
items:
|
||||
- key: my-config
|
||||
path: my-config
|
||||
19
pkg/e2e/fixtures/bridge/expected-helm/templates/serviceA-expose.yaml
Executable file
19
pkg/e2e/fixtures/bridge/expected-helm/templates/serviceA-expose.yaml
Executable file
@@ -0,0 +1,19 @@
|
||||
#! serviceA-expose.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: servicea
|
||||
namespace: {{ .Values.namespace }}
|
||||
labels:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceA
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
spec:
|
||||
selector:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceA
|
||||
ports:
|
||||
- name: servicea-8080
|
||||
port: 8080
|
||||
targetPort: servicea-8080
|
||||
25
pkg/e2e/fixtures/bridge/expected-helm/templates/serviceA-service.yaml
Executable file
25
pkg/e2e/fixtures/bridge/expected-helm/templates/serviceA-service.yaml
Executable file
@@ -0,0 +1,25 @@
|
||||
# check if there is at least one published port
|
||||
|
||||
#! serviceA-service.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: servicea-published
|
||||
namespace: {{ .Values.namespace }}
|
||||
labels:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceA
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
selector:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceA
|
||||
ports:
|
||||
- name: servicea-80
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: servicea-8080
|
||||
|
||||
# check if there is at least one published port
|
||||
46
pkg/e2e/fixtures/bridge/expected-helm/templates/serviceB-deployment.yaml
Executable file
46
pkg/e2e/fixtures/bridge/expected-helm/templates/serviceB-deployment.yaml
Executable file
@@ -0,0 +1,46 @@
|
||||
#! serviceB-deployment.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: serviceb
|
||||
namespace: {{ .Values.namespace }}
|
||||
labels:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceB
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceB
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceB
|
||||
com.docker.compose.network.private-network: "true"
|
||||
com.docker.compose.network.public-network: "true"
|
||||
spec:
|
||||
containers:
|
||||
- name: serviceb
|
||||
image: {{ .Values.serviceB.image }}
|
||||
imagePullPolicy: {{ .Values.serviceB.imagePullPolicy }}
|
||||
ports:
|
||||
- name: serviceb-8082
|
||||
containerPort: 8082
|
||||
volumeMounts:
|
||||
- name: run-secrets-my-secrets
|
||||
mountPath: /run/secrets/my-secrets
|
||||
subPath: my-secrets
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: run-secrets-my-secrets
|
||||
secret:
|
||||
secretName: my-secrets
|
||||
items:
|
||||
- key: my-secrets
|
||||
path: my-secrets
|
||||
19
pkg/e2e/fixtures/bridge/expected-helm/templates/serviceB-expose.yaml
Executable file
19
pkg/e2e/fixtures/bridge/expected-helm/templates/serviceB-expose.yaml
Executable file
@@ -0,0 +1,19 @@
|
||||
#! serviceB-expose.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: serviceb
|
||||
namespace: {{ .Values.namespace }}
|
||||
labels:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceB
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
spec:
|
||||
selector:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceB
|
||||
ports:
|
||||
- name: serviceb-8082
|
||||
port: 8082
|
||||
targetPort: serviceb-8082
|
||||
21
pkg/e2e/fixtures/bridge/expected-helm/templates/serviceB-service.yaml
Executable file
21
pkg/e2e/fixtures/bridge/expected-helm/templates/serviceB-service.yaml
Executable file
@@ -0,0 +1,21 @@
|
||||
#! serviceB-service.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: serviceb-published
|
||||
namespace: {{ .Values.namespace }}
|
||||
labels:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceB
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
selector:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceB
|
||||
ports:
|
||||
- name: serviceb-8081
|
||||
port: 8081
|
||||
protocol: TCP
|
||||
targetPort: serviceb-8082
|
||||
13
pkg/e2e/fixtures/bridge/expected-helm/values.yaml
Executable file
13
pkg/e2e/fixtures/bridge/expected-helm/values.yaml
Executable file
@@ -0,0 +1,13 @@
|
||||
#! values.yaml
|
||||
# Namespace
|
||||
namespace: bridge
|
||||
# Services variables
|
||||
|
||||
serviceA:
|
||||
image: alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
serviceB:
|
||||
image: alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
|
||||
# You can apply the same logic to loop on networks, volumes, secrets and configs...
|
||||
8
pkg/e2e/fixtures/bridge/expected-kubernetes/base/0-bridge-namespace.yaml
Executable file
8
pkg/e2e/fixtures/bridge/expected-kubernetes/base/0-bridge-namespace.yaml
Executable file
@@ -0,0 +1,8 @@
|
||||
#! 0-bridge-namespace.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: bridge
|
||||
labels:
|
||||
com.docker.compose.project: bridge
|
||||
12
pkg/e2e/fixtures/bridge/expected-kubernetes/base/bridge-configs.yaml
Executable file
12
pkg/e2e/fixtures/bridge/expected-kubernetes/base/bridge-configs.yaml
Executable file
@@ -0,0 +1,12 @@
|
||||
#! bridge-configs.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: bridge
|
||||
namespace: bridge
|
||||
labels:
|
||||
com.docker.compose.project: bridge
|
||||
data:
|
||||
my-config: |
|
||||
My config file
|
||||
16
pkg/e2e/fixtures/bridge/expected-kubernetes/base/kustomization.yaml
Executable file
16
pkg/e2e/fixtures/bridge/expected-kubernetes/base/kustomization.yaml
Executable file
@@ -0,0 +1,16 @@
|
||||
#! kustomization.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- 0-bridge-namespace.yaml
|
||||
- bridge-configs.yaml
|
||||
- my-secrets-secret.yaml
|
||||
- private-network-network-policy.yaml
|
||||
- public-network-network-policy.yaml
|
||||
- serviceA-deployment.yaml
|
||||
- serviceA-expose.yaml
|
||||
- serviceA-service.yaml
|
||||
- serviceB-deployment.yaml
|
||||
- serviceB-expose.yaml
|
||||
- serviceB-service.yaml
|
||||
13
pkg/e2e/fixtures/bridge/expected-kubernetes/base/my-secrets-secret.yaml
Executable file
13
pkg/e2e/fixtures/bridge/expected-kubernetes/base/my-secrets-secret.yaml
Executable file
@@ -0,0 +1,13 @@
|
||||
#! my-secrets-secret.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: my-secrets
|
||||
namespace: bridge
|
||||
labels:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.secret: my-secrets
|
||||
data:
|
||||
my-secrets: bm90LXNlY3JldA==
|
||||
type: Opaque
|
||||
@@ -0,0 +1,24 @@
|
||||
#! private-network-network-policy.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: private-network-network-policy
|
||||
namespace: bridge
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
com.docker.compose.network.private-network: "true"
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
com.docker.compose.network.private-network: "true"
|
||||
egress:
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
com.docker.compose.network.private-network: "true"
|
||||
@@ -0,0 +1,24 @@
|
||||
#! public-network-network-policy.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: public-network-network-policy
|
||||
namespace: bridge
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
com.docker.compose.network.public-network: "true"
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
com.docker.compose.network.public-network: "true"
|
||||
egress:
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
com.docker.compose.network.public-network: "true"
|
||||
44
pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceA-deployment.yaml
Executable file
44
pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceA-deployment.yaml
Executable file
@@ -0,0 +1,44 @@
|
||||
#! serviceA-deployment.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: servicea
|
||||
namespace: bridge
|
||||
labels:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceA
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceA
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceA
|
||||
com.docker.compose.network.private-network: "true"
|
||||
spec:
|
||||
containers:
|
||||
- name: servicea
|
||||
image: alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: servicea-8080
|
||||
containerPort: 8080
|
||||
volumeMounts:
|
||||
- name: etc-my-config1-txt
|
||||
mountPath: /etc/my-config1.txt
|
||||
subPath: my-config
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: etc-my-config1-txt
|
||||
configMap:
|
||||
name: bridge
|
||||
items:
|
||||
- key: my-config
|
||||
path: my-config
|
||||
18
pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceA-expose.yaml
Executable file
18
pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceA-expose.yaml
Executable file
@@ -0,0 +1,18 @@
|
||||
#! serviceA-expose.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: servicea
|
||||
namespace: bridge
|
||||
labels:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceA
|
||||
spec:
|
||||
selector:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceA
|
||||
ports:
|
||||
- name: servicea-8080
|
||||
port: 8080
|
||||
targetPort: servicea-8080
|
||||
23
pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceA-service.yaml
Executable file
23
pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceA-service.yaml
Executable file
@@ -0,0 +1,23 @@
|
||||
# check if there is at least one published port
|
||||
|
||||
#! serviceA-service.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: servicea-published
|
||||
namespace: bridge
|
||||
labels:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceA
|
||||
spec:
|
||||
selector:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceA
|
||||
ports:
|
||||
- name: servicea-80
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: servicea-8080
|
||||
|
||||
# check if there is at least one published port
|
||||
45
pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceB-deployment.yaml
Executable file
45
pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceB-deployment.yaml
Executable file
@@ -0,0 +1,45 @@
|
||||
#! serviceB-deployment.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: serviceb
|
||||
namespace: bridge
|
||||
labels:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceB
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceB
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceB
|
||||
com.docker.compose.network.private-network: "true"
|
||||
com.docker.compose.network.public-network: "true"
|
||||
spec:
|
||||
containers:
|
||||
- name: serviceb
|
||||
image: alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: serviceb-8082
|
||||
containerPort: 8082
|
||||
volumeMounts:
|
||||
- name: run-secrets-my-secrets
|
||||
mountPath: /run/secrets/my-secrets
|
||||
subPath: my-secrets
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: run-secrets-my-secrets
|
||||
secret:
|
||||
secretName: my-secrets
|
||||
items:
|
||||
- key: my-secrets
|
||||
path: my-secrets
|
||||
18
pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceB-expose.yaml
Executable file
18
pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceB-expose.yaml
Executable file
@@ -0,0 +1,18 @@
|
||||
#! serviceB-expose.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: serviceb
|
||||
namespace: bridge
|
||||
labels:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceB
|
||||
spec:
|
||||
selector:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceB
|
||||
ports:
|
||||
- name: serviceb-8082
|
||||
port: 8082
|
||||
targetPort: serviceb-8082
|
||||
19
pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceB-service.yaml
Executable file
19
pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceB-service.yaml
Executable file
@@ -0,0 +1,19 @@
|
||||
#! serviceB-service.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: serviceb-published
|
||||
namespace: bridge
|
||||
labels:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceB
|
||||
spec:
|
||||
selector:
|
||||
com.docker.compose.project: bridge
|
||||
com.docker.compose.service: serviceB
|
||||
ports:
|
||||
- name: serviceb-8081
|
||||
port: 8081
|
||||
protocol: TCP
|
||||
targetPort: serviceb-8082
|
||||
@@ -0,0 +1,9 @@
|
||||
#! kustomization.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../../base
|
||||
patches:
|
||||
- path: serviceA-service.yaml
|
||||
- path: serviceB-service.yaml
|
||||
@@ -0,0 +1,13 @@
|
||||
# check if there is at least one published port
|
||||
|
||||
#! serviceA-service.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: servicea-published
|
||||
namespace: bridge
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
|
||||
# check if there is at least one published port
|
||||
@@ -0,0 +1,9 @@
|
||||
#! serviceB-service.yaml
|
||||
# Generated code, do not edit
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: serviceb-published
|
||||
namespace: bridge
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
1
pkg/e2e/fixtures/bridge/my-config.txt
Normal file
1
pkg/e2e/fixtures/bridge/my-config.txt
Normal file
@@ -0,0 +1 @@
|
||||
My config file
|
||||
1
pkg/e2e/fixtures/bridge/not-so-secret.txt
Normal file
1
pkg/e2e/fixtures/bridge/not-so-secret.txt
Normal file
@@ -0,0 +1 @@
|
||||
not-secret
|
||||
15
pkg/e2e/fixtures/build-dependencies/compose-depends_on.yaml
Normal file
15
pkg/e2e/fixtures/build-dependencies/compose-depends_on.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
services:
|
||||
test1:
|
||||
pull_policy: build
|
||||
build:
|
||||
dockerfile_inline: FROM alpine
|
||||
command:
|
||||
- echo
|
||||
- "test 1 success"
|
||||
test2:
|
||||
image: alpine
|
||||
depends_on:
|
||||
- test1
|
||||
command:
|
||||
- echo
|
||||
- "test 2 success"
|
||||
26
pkg/e2e/fixtures/build-test/dependencies/compose.yaml
Normal file
26
pkg/e2e/fixtures/build-test/dependencies/compose.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
services:
|
||||
firstbuild:
|
||||
build:
|
||||
dockerfile_inline: |
|
||||
FROM alpine
|
||||
additional_contexts:
|
||||
dep1: service:dep1
|
||||
entrypoint: ["echo", "Hello from firstbuild"]
|
||||
depends_on:
|
||||
- dep1
|
||||
|
||||
secondbuild:
|
||||
build:
|
||||
dockerfile_inline: |
|
||||
FROM alpine
|
||||
additional_contexts:
|
||||
dep1: service:dep1
|
||||
entrypoint: ["echo", "Hello from secondbuild"]
|
||||
depends_on:
|
||||
- dep1
|
||||
|
||||
dep1:
|
||||
build:
|
||||
dockerfile_inline: |
|
||||
FROM alpine
|
||||
entrypoint: ["echo", "Hello from dep1"]
|
||||
36
pkg/e2e/fixtures/build-test/sub-dependencies/compose.yaml
Normal file
36
pkg/e2e/fixtures/build-test/sub-dependencies/compose.yaml
Normal file
@@ -0,0 +1,36 @@
|
||||
services:
|
||||
main:
|
||||
build:
|
||||
dockerfile_inline: |
|
||||
FROM alpine
|
||||
additional_contexts:
|
||||
dep1: service:dep1
|
||||
dep2: service:dep2
|
||||
entrypoint: ["echo", "Hello from main"]
|
||||
|
||||
dep1:
|
||||
build:
|
||||
dockerfile_inline: |
|
||||
FROM alpine
|
||||
additional_contexts:
|
||||
subdep1: service:subdep1
|
||||
subdep2: service:subdep2
|
||||
entrypoint: ["echo", "Hello from dep1"]
|
||||
|
||||
dep2:
|
||||
build:
|
||||
dockerfile_inline: |
|
||||
FROM alpine
|
||||
entrypoint: ["echo", "Hello from dep2"]
|
||||
|
||||
subdep1:
|
||||
build:
|
||||
dockerfile_inline: |
|
||||
FROM alpine
|
||||
entrypoint: ["echo", "Hello from subdep1"]
|
||||
|
||||
subdep2:
|
||||
build:
|
||||
dockerfile_inline: |
|
||||
FROM alpine
|
||||
entrypoint: ["echo", "Hello from subdep2"]
|
||||
14
pkg/e2e/fixtures/build-test/subset/compose.yaml
Normal file
14
pkg/e2e/fixtures/build-test/subset/compose.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
services:
|
||||
main:
|
||||
build:
|
||||
dockerfile_inline: |
|
||||
FROM alpine
|
||||
entrypoint: ["echo", "Hello from main"]
|
||||
depends_on:
|
||||
- dep1
|
||||
|
||||
dep1:
|
||||
build:
|
||||
dockerfile_inline: |
|
||||
FROM alpine
|
||||
entrypoint: ["echo", "Hello from dep1"]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user