| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- /*
- 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
- }
|