Browse Source

add bridge command and transformations subcommands

Signed-off-by: Guillaume Lours <[email protected]>
Guillaume Lours 6 months ago
parent
commit
8c622da20b
3 changed files with 255 additions and 0 deletions
  1. 136 0
      cmd/compose/bridge.go
  2. 1 0
      cmd/compose/compose.go
  3. 118 0
      pkg/bridge/transformers.go

+ 136 - 0
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 := "<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
+}

+ 1 - 0
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)

+ 118 - 0
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")),
+		),
+	})
+}