From 922851515c7b9fd47aab77cbe353c89743acd5d1 Mon Sep 17 00:00:00 2001 From: Guillaume Lours <705411+glours@users.noreply.github.com> Date: Fri, 23 May 2025 10:04:57 +0200 Subject: [PATCH 1/5] add bridge command and transformations subcommands Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com> --- cmd/compose/bridge.go | 136 +++++++++++++++++++++++++++++++++++++ cmd/compose/compose.go | 1 + pkg/bridge/transformers.go | 118 ++++++++++++++++++++++++++++++++ 3 files changed, 255 insertions(+) create mode 100644 cmd/compose/bridge.go create mode 100644 pkg/bridge/transformers.go diff --git a/cmd/compose/bridge.go b/cmd/compose/bridge.go new file mode 100644 index 00000000000..ee8fde396e7 --- /dev/null +++ b/cmd/compose/bridge.go @@ -0,0 +1,136 @@ +/* + 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/api" + "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 { + return &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 api.ErrNotImplemented + }), + } +} + +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 := "", "" + 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 +} diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index 1e1377194d7..4b8396974ce 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -645,6 +645,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) diff --git a/pkg/bridge/transformers.go b/pkg/bridge/transformers.go new file mode 100644 index 00000000000..cd3bdd29eab --- /dev/null +++ b/pkg/bridge/transformers.go @@ -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")), + ), + }) +} From b1bbda0c537e3276d7fa45a4ddb628c4165057aa Mon Sep 17 00:00:00 2001 From: Guillaume Lours <705411+glours@users.noreply.github.com> Date: Fri, 23 May 2025 14:06:47 +0200 Subject: [PATCH 2/5] add convert subcommand to bridge command Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com> --- cmd/compose/bridge.go | 19 +++- pkg/bridge/convert.go | 211 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 227 insertions(+), 3 deletions(-) create mode 100644 pkg/bridge/convert.go diff --git a/cmd/compose/bridge.go b/cmd/compose/bridge.go index ee8fde396e7..22f78b68998 100644 --- a/cmd/compose/bridge.go +++ b/cmd/compose/bridge.go @@ -29,7 +29,6 @@ import ( "github.com/spf13/cobra" "github.com/docker/compose/v2/cmd/formatter" - "github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/bridge" ) @@ -47,13 +46,27 @@ func bridgeCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command { } func convertCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command { - return &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 api.ErrNotImplemented + 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 { diff --git a/pkg/bridge/convert.go b/pkg/bridge/convert.go new file mode 100644 index 00000000000..e557466d40a --- /dev/null +++ b/pkg/bridge/convert.go @@ -0,0 +1,211 @@ +/* + 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" + "path/filepath" + "strconv" + + "github.com/compose-spec/compose-go/v2/types" + "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/errdefs" + "github.com/docker/docker/pkg/jsonmessage" + "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 + } + + created, err := dockerCli.Client().ContainerCreate(ctx, &container.Config{ + Image: transformation, + Env: []string{"LICENSE_AGREEMENT=true"}, + }, &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, cli command.Cli, project *types.Project) (*types.Project, error) { + for name, service := range project.Services { + imageName := api.GetImageNameOrDefault(service, project.Name) + + inspect, err := inspectWithPull(ctx, cli, 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(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 errdefs.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 +} From 7d4688bddc4a9a59d7f27106fabb61cd0ffe7636 Mon Sep 17 00:00:00 2001 From: Guillaume Lours <705411+glours@users.noreply.github.com> Date: Fri, 23 May 2025 14:10:26 +0200 Subject: [PATCH 3/5] add new bridge commands documentation Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com> --- docs/reference/compose.md | 1 + docs/reference/compose_bridge.md | 22 +++++++ docs/reference/compose_bridge_convert.md | 17 ++++++ .../compose_bridge_transformations.md | 22 +++++++ .../compose_bridge_transformations_create.md | 15 +++++ .../compose_bridge_transformations_list.md | 20 +++++++ docs/reference/docker_compose.yaml | 2 + docs/reference/docker_compose_bridge.yaml | 29 +++++++++ .../docker_compose_bridge_convert.yaml | 59 +++++++++++++++++++ ...docker_compose_bridge_transformations.yaml | 29 +++++++++ ...compose_bridge_transformations_create.yaml | 36 +++++++++++ ...r_compose_bridge_transformations_list.yaml | 47 +++++++++++++++ 12 files changed, 299 insertions(+) create mode 100644 docs/reference/compose_bridge.md create mode 100644 docs/reference/compose_bridge_convert.md create mode 100644 docs/reference/compose_bridge_transformations.md create mode 100644 docs/reference/compose_bridge_transformations_create.md create mode 100644 docs/reference/compose_bridge_transformations_list.md create mode 100644 docs/reference/docker_compose_bridge.yaml create mode 100644 docs/reference/docker_compose_bridge_convert.yaml create mode 100644 docs/reference/docker_compose_bridge_transformations.yaml create mode 100644 docs/reference/docker_compose_bridge_transformations_create.yaml create mode 100644 docs/reference/docker_compose_bridge_transformations_list.yaml diff --git a/docs/reference/compose.md b/docs/reference/compose.md index d1a1c2a4627..74a300571af 100644 --- a/docs/reference/compose.md +++ b/docs/reference/compose.md @@ -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 | diff --git a/docs/reference/compose_bridge.md b/docs/reference/compose_bridge.md new file mode 100644 index 00000000000..78d3da4934c --- /dev/null +++ b/docs/reference/compose_bridge.md @@ -0,0 +1,22 @@ +# docker compose bridge + + +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 | + + + + diff --git a/docs/reference/compose_bridge_convert.md b/docs/reference/compose_bridge_convert.md new file mode 100644 index 00000000000..d4b91ba172d --- /dev/null +++ b/docs/reference/compose_bridge_convert.md @@ -0,0 +1,17 @@ +# docker compose bridge convert + + +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) | + + + + diff --git a/docs/reference/compose_bridge_transformations.md b/docs/reference/compose_bridge_transformations.md new file mode 100644 index 00000000000..1e1c7be392b --- /dev/null +++ b/docs/reference/compose_bridge_transformations.md @@ -0,0 +1,22 @@ +# docker compose bridge transformations + + +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 | + + + + diff --git a/docs/reference/compose_bridge_transformations_create.md b/docs/reference/compose_bridge_transformations_create.md new file mode 100644 index 00000000000..187e8d9eca3 --- /dev/null +++ b/docs/reference/compose_bridge_transformations_create.md @@ -0,0 +1,15 @@ +# docker compose bridge transformations create + + +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) | + + + + diff --git a/docs/reference/compose_bridge_transformations_list.md b/docs/reference/compose_bridge_transformations_list.md new file mode 100644 index 00000000000..ce0a5e6911a --- /dev/null +++ b/docs/reference/compose_bridge_transformations_list.md @@ -0,0 +1,20 @@ +# docker compose bridge transformations list + + +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 | + + + + diff --git a/docs/reference/docker_compose.yaml b/docs/reference/docker_compose.yaml index 58ec47802a5..ad7c99e59ee 100644 --- a/docs/reference/docker_compose.yaml +++ b/docs/reference/docker_compose.yaml @@ -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 diff --git a/docs/reference/docker_compose_bridge.yaml b/docs/reference/docker_compose_bridge.yaml new file mode 100644 index 00000000000..5ef9ebf5585 --- /dev/null +++ b/docs/reference/docker_compose_bridge.yaml @@ -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 + diff --git a/docs/reference/docker_compose_bridge_convert.yaml b/docs/reference/docker_compose_bridge_convert.yaml new file mode 100644 index 00000000000..f55f0b233c3 --- /dev/null +++ b/docs/reference/docker_compose_bridge_convert.yaml @@ -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 + diff --git a/docs/reference/docker_compose_bridge_transformations.yaml b/docs/reference/docker_compose_bridge_transformations.yaml new file mode 100644 index 00000000000..2ab5661f0b2 --- /dev/null +++ b/docs/reference/docker_compose_bridge_transformations.yaml @@ -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 + diff --git a/docs/reference/docker_compose_bridge_transformations_create.yaml b/docs/reference/docker_compose_bridge_transformations_create.yaml new file mode 100644 index 00000000000..e8dd9e58a51 --- /dev/null +++ b/docs/reference/docker_compose_bridge_transformations_create.yaml @@ -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 + diff --git a/docs/reference/docker_compose_bridge_transformations_list.yaml b/docs/reference/docker_compose_bridge_transformations_list.yaml new file mode 100644 index 00000000000..3afd3a84b8e --- /dev/null +++ b/docs/reference/docker_compose_bridge_transformations_list.yaml @@ -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 + From fa7c455f9b17d0dc2e2beabd828da68598ba8fe8 Mon Sep 17 00:00:00 2001 From: Guillaume Lours <705411+glours@users.noreply.github.com> Date: Mon, 26 May 2025 15:16:40 +0200 Subject: [PATCH 4/5] add e2e tests for bridge convert and transformers ls commands Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com> --- pkg/e2e/bridge_test.go | 59 +++++++++++++++++++ pkg/e2e/fixtures/bridge/Dockerfile | 18 ++++++ pkg/e2e/fixtures/bridge/compose.yaml | 31 ++++++++++ .../fixtures/bridge/expected-helm/Chart.yaml | 12 ++++ .../templates/0-bridge-namespace.yaml | 8 +++ .../templates/bridge-configs.yaml | 12 ++++ .../templates/my-secrets-secret.yaml | 13 ++++ .../private-network-network-policy.yaml | 24 ++++++++ .../public-network-network-policy.yaml | 24 ++++++++ .../templates/serviceA-deployment.yaml | 45 ++++++++++++++ .../templates/serviceA-expose.yaml | 19 ++++++ .../templates/serviceA-service.yaml | 25 ++++++++ .../templates/serviceB-deployment.yaml | 46 +++++++++++++++ .../templates/serviceB-expose.yaml | 19 ++++++ .../templates/serviceB-service.yaml | 21 +++++++ .../fixtures/bridge/expected-helm/values.yaml | 13 ++++ .../base/0-bridge-namespace.yaml | 8 +++ .../base/bridge-configs.yaml | 12 ++++ .../base/kustomization.yaml | 16 +++++ .../base/my-secrets-secret.yaml | 13 ++++ .../base/private-network-network-policy.yaml | 24 ++++++++ .../base/public-network-network-policy.yaml | 24 ++++++++ .../base/serviceA-deployment.yaml | 44 ++++++++++++++ .../base/serviceA-expose.yaml | 18 ++++++ .../base/serviceA-service.yaml | 23 ++++++++ .../base/serviceB-deployment.yaml | 45 ++++++++++++++ .../base/serviceB-expose.yaml | 18 ++++++ .../base/serviceB-service.yaml | 19 ++++++ .../overlays/desktop/kustomization.yaml | 9 +++ .../overlays/desktop/serviceA-service.yaml | 13 ++++ .../overlays/desktop/serviceB-service.yaml | 9 +++ pkg/e2e/fixtures/bridge/my-config.txt | 1 + pkg/e2e/fixtures/bridge/not-so-secret.txt | 1 + 33 files changed, 686 insertions(+) create mode 100644 pkg/e2e/bridge_test.go create mode 100644 pkg/e2e/fixtures/bridge/Dockerfile create mode 100644 pkg/e2e/fixtures/bridge/compose.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-helm/Chart.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-helm/templates/0-bridge-namespace.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-helm/templates/bridge-configs.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-helm/templates/my-secrets-secret.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-helm/templates/private-network-network-policy.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-helm/templates/public-network-network-policy.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-helm/templates/serviceA-deployment.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-helm/templates/serviceA-expose.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-helm/templates/serviceA-service.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-helm/templates/serviceB-deployment.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-helm/templates/serviceB-expose.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-helm/templates/serviceB-service.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-helm/values.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-kubernetes/base/0-bridge-namespace.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-kubernetes/base/bridge-configs.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-kubernetes/base/kustomization.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-kubernetes/base/my-secrets-secret.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-kubernetes/base/private-network-network-policy.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-kubernetes/base/public-network-network-policy.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceA-deployment.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceA-expose.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceA-service.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceB-deployment.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceB-expose.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceB-service.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-kubernetes/overlays/desktop/kustomization.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-kubernetes/overlays/desktop/serviceA-service.yaml create mode 100755 pkg/e2e/fixtures/bridge/expected-kubernetes/overlays/desktop/serviceB-service.yaml create mode 100644 pkg/e2e/fixtures/bridge/my-config.txt create mode 100644 pkg/e2e/fixtures/bridge/not-so-secret.txt diff --git a/pkg/e2e/bridge_test.go b/pkg/e2e/bridge_test.go new file mode 100644 index 00000000000..6f8e10600fb --- /dev/null +++ b/pkg/e2e/bridge_test.go @@ -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 ( + "fmt" + "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 := fmt.Sprintf("%s/kubernetes", tmpDir) + 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 := fmt.Sprintf("%s/helm", tmpDir) + 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()) + }) +} diff --git a/pkg/e2e/fixtures/bridge/Dockerfile b/pkg/e2e/fixtures/bridge/Dockerfile new file mode 100644 index 00000000000..4cdd9857779 --- /dev/null +++ b/pkg/e2e/fixtures/bridge/Dockerfile @@ -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"] diff --git a/pkg/e2e/fixtures/bridge/compose.yaml b/pkg/e2e/fixtures/bridge/compose.yaml new file mode 100644 index 00000000000..4fbd9bd94cf --- /dev/null +++ b/pkg/e2e/fixtures/bridge/compose.yaml @@ -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: {} diff --git a/pkg/e2e/fixtures/bridge/expected-helm/Chart.yaml b/pkg/e2e/fixtures/bridge/expected-helm/Chart.yaml new file mode 100755 index 00000000000..44a00001138 --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-helm/Chart.yaml @@ -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: diff --git a/pkg/e2e/fixtures/bridge/expected-helm/templates/0-bridge-namespace.yaml b/pkg/e2e/fixtures/bridge/expected-helm/templates/0-bridge-namespace.yaml new file mode 100755 index 00000000000..40e4b0e23f4 --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-helm/templates/0-bridge-namespace.yaml @@ -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 diff --git a/pkg/e2e/fixtures/bridge/expected-helm/templates/bridge-configs.yaml b/pkg/e2e/fixtures/bridge/expected-helm/templates/bridge-configs.yaml new file mode 100755 index 00000000000..822d2e076ef --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-helm/templates/bridge-configs.yaml @@ -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 diff --git a/pkg/e2e/fixtures/bridge/expected-helm/templates/my-secrets-secret.yaml b/pkg/e2e/fixtures/bridge/expected-helm/templates/my-secrets-secret.yaml new file mode 100755 index 00000000000..63659713ba7 --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-helm/templates/my-secrets-secret.yaml @@ -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 diff --git a/pkg/e2e/fixtures/bridge/expected-helm/templates/private-network-network-policy.yaml b/pkg/e2e/fixtures/bridge/expected-helm/templates/private-network-network-policy.yaml new file mode 100755 index 00000000000..0300049be68 --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-helm/templates/private-network-network-policy.yaml @@ -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" diff --git a/pkg/e2e/fixtures/bridge/expected-helm/templates/public-network-network-policy.yaml b/pkg/e2e/fixtures/bridge/expected-helm/templates/public-network-network-policy.yaml new file mode 100755 index 00000000000..da042b3e8c1 --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-helm/templates/public-network-network-policy.yaml @@ -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" diff --git a/pkg/e2e/fixtures/bridge/expected-helm/templates/serviceA-deployment.yaml b/pkg/e2e/fixtures/bridge/expected-helm/templates/serviceA-deployment.yaml new file mode 100755 index 00000000000..225ff13a950 --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-helm/templates/serviceA-deployment.yaml @@ -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 diff --git a/pkg/e2e/fixtures/bridge/expected-helm/templates/serviceA-expose.yaml b/pkg/e2e/fixtures/bridge/expected-helm/templates/serviceA-expose.yaml new file mode 100755 index 00000000000..5d733bd2245 --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-helm/templates/serviceA-expose.yaml @@ -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 diff --git a/pkg/e2e/fixtures/bridge/expected-helm/templates/serviceA-service.yaml b/pkg/e2e/fixtures/bridge/expected-helm/templates/serviceA-service.yaml new file mode 100755 index 00000000000..4b29a6477fc --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-helm/templates/serviceA-service.yaml @@ -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 diff --git a/pkg/e2e/fixtures/bridge/expected-helm/templates/serviceB-deployment.yaml b/pkg/e2e/fixtures/bridge/expected-helm/templates/serviceB-deployment.yaml new file mode 100755 index 00000000000..abdf8b2422b --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-helm/templates/serviceB-deployment.yaml @@ -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 diff --git a/pkg/e2e/fixtures/bridge/expected-helm/templates/serviceB-expose.yaml b/pkg/e2e/fixtures/bridge/expected-helm/templates/serviceB-expose.yaml new file mode 100755 index 00000000000..f413254dca0 --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-helm/templates/serviceB-expose.yaml @@ -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 diff --git a/pkg/e2e/fixtures/bridge/expected-helm/templates/serviceB-service.yaml b/pkg/e2e/fixtures/bridge/expected-helm/templates/serviceB-service.yaml new file mode 100755 index 00000000000..52ec6fe944c --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-helm/templates/serviceB-service.yaml @@ -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 diff --git a/pkg/e2e/fixtures/bridge/expected-helm/values.yaml b/pkg/e2e/fixtures/bridge/expected-helm/values.yaml new file mode 100755 index 00000000000..b312fbc696f --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-helm/values.yaml @@ -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... diff --git a/pkg/e2e/fixtures/bridge/expected-kubernetes/base/0-bridge-namespace.yaml b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/0-bridge-namespace.yaml new file mode 100755 index 00000000000..40e4b0e23f4 --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/0-bridge-namespace.yaml @@ -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 diff --git a/pkg/e2e/fixtures/bridge/expected-kubernetes/base/bridge-configs.yaml b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/bridge-configs.yaml new file mode 100755 index 00000000000..822d2e076ef --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/bridge-configs.yaml @@ -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 diff --git a/pkg/e2e/fixtures/bridge/expected-kubernetes/base/kustomization.yaml b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/kustomization.yaml new file mode 100755 index 00000000000..ff8428feae2 --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/kustomization.yaml @@ -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 diff --git a/pkg/e2e/fixtures/bridge/expected-kubernetes/base/my-secrets-secret.yaml b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/my-secrets-secret.yaml new file mode 100755 index 00000000000..559eba6a26e --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/my-secrets-secret.yaml @@ -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 diff --git a/pkg/e2e/fixtures/bridge/expected-kubernetes/base/private-network-network-policy.yaml b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/private-network-network-policy.yaml new file mode 100755 index 00000000000..3f59b22dd9d --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/private-network-network-policy.yaml @@ -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" diff --git a/pkg/e2e/fixtures/bridge/expected-kubernetes/base/public-network-network-policy.yaml b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/public-network-network-policy.yaml new file mode 100755 index 00000000000..04913d4b9a7 --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/public-network-network-policy.yaml @@ -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" diff --git a/pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceA-deployment.yaml b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceA-deployment.yaml new file mode 100755 index 00000000000..0779cf56268 --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceA-deployment.yaml @@ -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 diff --git a/pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceA-expose.yaml b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceA-expose.yaml new file mode 100755 index 00000000000..d0bd013ecff --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceA-expose.yaml @@ -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 diff --git a/pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceA-service.yaml b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceA-service.yaml new file mode 100755 index 00000000000..628cf04189c --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceA-service.yaml @@ -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 diff --git a/pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceB-deployment.yaml b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceB-deployment.yaml new file mode 100755 index 00000000000..191720c2014 --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceB-deployment.yaml @@ -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 diff --git a/pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceB-expose.yaml b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceB-expose.yaml new file mode 100755 index 00000000000..2025868991d --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceB-expose.yaml @@ -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 diff --git a/pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceB-service.yaml b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceB-service.yaml new file mode 100755 index 00000000000..94104185871 --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-kubernetes/base/serviceB-service.yaml @@ -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 diff --git a/pkg/e2e/fixtures/bridge/expected-kubernetes/overlays/desktop/kustomization.yaml b/pkg/e2e/fixtures/bridge/expected-kubernetes/overlays/desktop/kustomization.yaml new file mode 100755 index 00000000000..a192e45f0fe --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-kubernetes/overlays/desktop/kustomization.yaml @@ -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 diff --git a/pkg/e2e/fixtures/bridge/expected-kubernetes/overlays/desktop/serviceA-service.yaml b/pkg/e2e/fixtures/bridge/expected-kubernetes/overlays/desktop/serviceA-service.yaml new file mode 100755 index 00000000000..6453b5adba3 --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-kubernetes/overlays/desktop/serviceA-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 diff --git a/pkg/e2e/fixtures/bridge/expected-kubernetes/overlays/desktop/serviceB-service.yaml b/pkg/e2e/fixtures/bridge/expected-kubernetes/overlays/desktop/serviceB-service.yaml new file mode 100755 index 00000000000..f21b674336b --- /dev/null +++ b/pkg/e2e/fixtures/bridge/expected-kubernetes/overlays/desktop/serviceB-service.yaml @@ -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 diff --git a/pkg/e2e/fixtures/bridge/my-config.txt b/pkg/e2e/fixtures/bridge/my-config.txt new file mode 100644 index 00000000000..24d11e40bb8 --- /dev/null +++ b/pkg/e2e/fixtures/bridge/my-config.txt @@ -0,0 +1 @@ +My config file \ No newline at end of file diff --git a/pkg/e2e/fixtures/bridge/not-so-secret.txt b/pkg/e2e/fixtures/bridge/not-so-secret.txt new file mode 100644 index 00000000000..4e76a78aebb --- /dev/null +++ b/pkg/e2e/fixtures/bridge/not-so-secret.txt @@ -0,0 +1 @@ +not-secret \ No newline at end of file From d3b7169c3e8145478d76a7610de6b82367b9f9c3 Mon Sep 17 00:00:00 2001 From: Guillaume Lours <705411+glours@users.noreply.github.com> Date: Mon, 26 May 2025 17:12:15 +0200 Subject: [PATCH 5/5] bridge - run transformer container as current user Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com> --- pkg/bridge/convert.go | 6 ++++++ pkg/e2e/bridge_test.go | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/pkg/bridge/convert.go b/pkg/bridge/convert.go index e557466d40a..aa2b64a0ecc 100644 --- a/pkg/bridge/convert.go +++ b/pkg/bridge/convert.go @@ -21,6 +21,7 @@ import ( "fmt" "io" "os" + "os/user" "path/filepath" "strconv" @@ -110,9 +111,14 @@ func convert(ctx context.Context, dockerCli command.Cli, model map[string]any, o return err } + user, err := user.Current() + if err != nil { + return err + } created, err := dockerCli.Client().ContainerCreate(ctx, &container.Config{ Image: transformation, Env: []string{"LICENSE_AGREEMENT=true"}, + User: user.Uid, }, &container.HostConfig{ AutoRemove: true, Binds: binds, diff --git a/pkg/e2e/bridge_test.go b/pkg/e2e/bridge_test.go index 6f8e10600fb..6024f456d97 100644 --- a/pkg/e2e/bridge_test.go +++ b/pkg/e2e/bridge_test.go @@ -17,7 +17,7 @@ package e2e import ( - "fmt" + "path/filepath" "strings" "testing" @@ -31,7 +31,7 @@ func TestConvertAndTransformList(t *testing.T) { tmpDir := t.TempDir() t.Run("kubernetes manifests", func(t *testing.T) { - kubedir := fmt.Sprintf("%s/kubernetes", tmpDir) + 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) @@ -41,7 +41,7 @@ func TestConvertAndTransformList(t *testing.T) { }) t.Run("helm charts", func(t *testing.T) { - helmDir := fmt.Sprintf("%s/helm", tmpDir) + 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)