From 1689bbf06fee866dfb99278d5e262203289cdf3d Mon Sep 17 00:00:00 2001 From: Bryan Kneis Date: Mon, 7 Oct 2024 10:29:12 +0100 Subject: [PATCH] Update docker compose to 2.2.0 --- go.mod | 3 +- go.sum | 4 + pkg/compose/helper.go | 8 +- pkg/devcontainer/build.go | 2 +- pkg/devcontainer/compose.go | 18 +- .../compose-spec/compose-go/loader/include.go | 167 -- .../compose-spec/compose-go/loader/loader.go | 1277 ---------- .../compose-spec/compose-go/loader/merge.go | 378 --- .../compose-go/loader/normalize.go | 335 --- .../compose-spec/compose-go/loader/paths.go | 172 -- .../compose-go/loader/validate.go | 121 - .../compose-spec/compose-go/types/project.go | 566 ----- .../compose-spec/compose-go/{ => v2}/LICENSE | 0 .../compose-spec/compose-go/{ => v2}/NOTICE | 0 .../compose-go/{ => v2}/cli/options.go | 188 +- .../compose-go/v2/consts/consts.go | 29 + .../compose-go/{ => v2}/dotenv/LICENSE | 0 .../compose-go/{ => v2}/dotenv/env.go | 22 +- .../compose-go/{ => v2}/dotenv/godotenv.go | 2 +- .../compose-go/{ => v2}/dotenv/parser.go | 17 +- .../compose-go/{ => v2}/errdefs/errors.go | 3 + .../{loader => v2/format}/volume.go | 9 +- .../compose-spec/compose-go/v2/graph/cycle.go | 63 + .../compose-spec/compose-go/v2/graph/graph.go | 75 + .../compose-go/v2/graph/services.go | 80 + .../compose-go/v2/graph/traversal.go | 211 ++ .../{ => v2}/interpolation/interpolation.go | 25 +- .../compose-go/v2/loader/environment.go | 110 + .../compose-go/{ => v2}/loader/example1.env | 0 .../compose-go/{ => v2}/loader/example2.env | 0 .../compose-go/v2/loader/extends.go | 216 ++ .../compose-spec/compose-go/v2/loader/fix.go | 36 + .../{ => v2}/loader/full-example.yml | 26 +- .../compose-go/v2/loader/include.go | 204 ++ .../compose-go/{ => v2}/loader/interpolate.go | 8 +- .../compose-go/v2/loader/loader.go | 868 +++++++ .../{ => v2}/loader/mapstructure.go | 28 +- .../compose-go/v2/loader/normalize.go | 251 ++ .../compose-go/v2/loader/paths.go | 74 + .../{loader/null.go => v2/loader/reset.go} | 109 +- .../compose-go/v2/loader/validate.go | 176 ++ .../compose-go/v2/override/extends.go | 27 + .../compose-go/v2/override/merge.go | 269 +++ .../compose-go/v2/override/uncity.go | 229 ++ .../compose-go/v2/paths/context.go | 44 + .../{consts/consts.go => v2/paths/extends.go} | 15 +- .../compose-spec/compose-go/v2/paths/home.go | 37 + .../compose-go/v2/paths/resolve.go | 167 ++ .../compose-spec/compose-go/v2/paths/unix.go | 54 + .../{loader => v2/paths}/windows_path.go | 4 +- .../{ => v2}/schema/compose-spec.json | 219 +- .../compose-go/{ => v2}/schema/schema.go | 0 .../compose-go/v2/schema/using-variables.yaml | 123 + .../compose-go/{ => v2}/template/template.go | 104 +- .../compose-go/v2/template/variables.go | 155 ++ .../compose-go/v2/transform/build.go | 48 + .../compose-go/v2/transform/canonical.go | 108 + .../compose-go/v2/transform/defaults.go | 88 + .../compose-go/v2/transform/dependson.go | 53 + .../compose-go/v2/transform/device.go | 60 + .../compose-go/v2/transform/envfile.go | 55 + .../compose-go/v2/transform/extends.go | 36 + .../compose-go/v2/transform/external.go | 54 + .../compose-go/v2/transform/include.go | 36 + .../compose-go/v2/transform/mapping.go | 46 + .../compose-go/v2/transform/ports.go | 104 + .../compose-go/v2/transform/secrets.go | 49 + .../compose-go/v2/transform/services.go | 41 + .../compose-go/v2/transform/ssh.go | 51 + .../compose-go/v2/transform/ulimits.go | 34 + .../compose-go/v2/transform/volume.go | 52 + .../compose-go/{ => v2}/tree/path.go | 22 +- .../compose-go/{ => v2}/types/bytes.go | 12 +- .../compose-go/{ => v2}/types/command.go | 0 .../compose-go/{ => v2}/types/config.go | 12 +- .../compose-go/v2/types/derived.gen.go | 2078 +++++++++++++++++ .../compose-go/{ => v2}/types/develop.go | 13 +- .../compose-go/{ => v2}/types/device.go | 7 +- .../compose-go/{ => v2}/types/duration.go | 0 .../compose-go/v2/types/envfile.go | 46 + .../compose-go/{ => v2}/types/healthcheck.go | 2 +- .../compose-go/v2/types/hostList.go | 144 ++ .../compose-go/{ => v2}/types/labels.go | 0 .../compose-go/v2/types/mapping.go | 217 ++ .../compose-go/{ => v2}/types/options.go | 8 +- .../compose-go/v2/types/project.go | 713 ++++++ .../compose-go/v2/types/services.go | 45 + .../compose-spec/compose-go/v2/types/ssh.go | 73 + .../compose-go/{ => v2}/types/stringOrList.go | 16 +- .../compose-go/{ => v2}/types/types.go | 470 ++-- .../{ => v2}/utils/collectionutils.go | 29 +- .../compose-go/v2/utils/pathutils.go | 92 + .../compose-spec/compose-go/v2/utils/set.go | 95 + .../compose-go/{ => v2}/utils/stringutils.go | 16 +- .../compose-go/v2/validation/external.go | 49 + .../compose-go/v2/validation/validation.go | 96 + .../compose-go/v2/validation/volume.go | 39 + .../go-viper/mapstructure/v2/.editorconfig | 18 + .../go-viper/mapstructure/v2/.envrc | 4 + .../go-viper/mapstructure/v2/.gitignore | 6 + .../go-viper/mapstructure/v2/.golangci.yaml | 23 + .../mapstructure/v2}/CHANGELOG.md | 8 + .../mapstructure/v2}/LICENSE | 0 .../go-viper/mapstructure/v2/README.md | 80 + .../go-viper/mapstructure/v2/decode_hooks.go | 577 +++++ .../go-viper/mapstructure/v2/flake.lock | 472 ++++ .../go-viper/mapstructure/v2/flake.nix | 39 + .../mapstructure/v2/internal/errors/errors.go | 11 + .../mapstructure/v2/internal/errors/join.go | 9 + .../v2/internal/errors/join_go1_19.go | 61 + .../mapstructure/v2}/mapstructure.go | 232 +- .../mapstructure/v2/reflect_go1_19.go | 44 + .../mapstructure/v2/reflect_go1_20.go | 10 + .../mitchellh/mapstructure/README.md | 46 - .../mitchellh/mapstructure/decode_hooks.go | 279 --- .../mitchellh/mapstructure/error.go | 50 - vendor/modules.txt | 37 +- 117 files changed, 10342 insertions(+), 4201 deletions(-) delete mode 100644 vendor/github.com/compose-spec/compose-go/loader/include.go delete mode 100644 vendor/github.com/compose-spec/compose-go/loader/loader.go delete mode 100644 vendor/github.com/compose-spec/compose-go/loader/merge.go delete mode 100644 vendor/github.com/compose-spec/compose-go/loader/normalize.go delete mode 100644 vendor/github.com/compose-spec/compose-go/loader/paths.go delete mode 100644 vendor/github.com/compose-spec/compose-go/loader/validate.go delete mode 100644 vendor/github.com/compose-spec/compose-go/types/project.go rename vendor/github.com/compose-spec/compose-go/{ => v2}/LICENSE (100%) rename vendor/github.com/compose-spec/compose-go/{ => v2}/NOTICE (100%) rename vendor/github.com/compose-spec/compose-go/{ => v2}/cli/options.go (72%) create mode 100644 vendor/github.com/compose-spec/compose-go/v2/consts/consts.go rename vendor/github.com/compose-spec/compose-go/{ => v2}/dotenv/LICENSE (100%) rename vendor/github.com/compose-spec/compose-go/{ => v2}/dotenv/env.go (68%) rename vendor/github.com/compose-spec/compose-go/{ => v2}/dotenv/godotenv.go (98%) rename vendor/github.com/compose-spec/compose-go/{ => v2}/dotenv/parser.go (95%) rename vendor/github.com/compose-spec/compose-go/{ => v2}/errdefs/errors.go (93%) rename vendor/github.com/compose-spec/compose-go/{loader => v2/format}/volume.go (96%) create mode 100644 vendor/github.com/compose-spec/compose-go/v2/graph/cycle.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/graph/graph.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/graph/services.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/graph/traversal.go rename vendor/github.com/compose-spec/compose-go/{ => v2}/interpolation/interpolation.go (88%) create mode 100644 vendor/github.com/compose-spec/compose-go/v2/loader/environment.go rename vendor/github.com/compose-spec/compose-go/{ => v2}/loader/example1.env (100%) rename vendor/github.com/compose-spec/compose-go/{ => v2}/loader/example2.env (100%) create mode 100644 vendor/github.com/compose-spec/compose-go/v2/loader/extends.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/loader/fix.go rename vendor/github.com/compose-spec/compose-go/{ => v2}/loader/full-example.yml (95%) create mode 100644 vendor/github.com/compose-spec/compose-go/v2/loader/include.go rename vendor/github.com/compose-spec/compose-go/{ => v2}/loader/interpolate.go (96%) create mode 100644 vendor/github.com/compose-spec/compose-go/v2/loader/loader.go rename vendor/github.com/compose-spec/compose-go/{ => v2}/loader/mapstructure.go (75%) create mode 100644 vendor/github.com/compose-spec/compose-go/v2/loader/normalize.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/loader/paths.go rename vendor/github.com/compose-spec/compose-go/{loader/null.go => v2/loader/reset.go} (53%) create mode 100644 vendor/github.com/compose-spec/compose-go/v2/loader/validate.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/override/extends.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/override/merge.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/override/uncity.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/paths/context.go rename vendor/github.com/compose-spec/compose-go/{consts/consts.go => v2/paths/extends.go} (75%) create mode 100644 vendor/github.com/compose-spec/compose-go/v2/paths/home.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/paths/resolve.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/paths/unix.go rename vendor/github.com/compose-spec/compose-go/{loader => v2/paths}/windows_path.go (97%) rename vendor/github.com/compose-spec/compose-go/{ => v2}/schema/compose-spec.json (80%) rename vendor/github.com/compose-spec/compose-go/{ => v2}/schema/schema.go (100%) create mode 100644 vendor/github.com/compose-spec/compose-go/v2/schema/using-variables.yaml rename vendor/github.com/compose-spec/compose-go/{ => v2}/template/template.go (79%) create mode 100644 vendor/github.com/compose-spec/compose-go/v2/template/variables.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/transform/build.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/transform/canonical.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/transform/defaults.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/transform/dependson.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/transform/device.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/transform/envfile.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/transform/extends.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/transform/external.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/transform/include.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/transform/mapping.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/transform/ports.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/transform/secrets.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/transform/services.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/transform/ssh.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/transform/ulimits.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/transform/volume.go rename vendor/github.com/compose-spec/compose-go/{ => v2}/tree/path.go (81%) rename vendor/github.com/compose-spec/compose-go/{ => v2}/types/bytes.go (86%) rename vendor/github.com/compose-spec/compose-go/{ => v2}/types/command.go (100%) rename vendor/github.com/compose-spec/compose-go/{ => v2}/types/config.go (94%) create mode 100644 vendor/github.com/compose-spec/compose-go/v2/types/derived.gen.go rename vendor/github.com/compose-spec/compose-go/{ => v2}/types/develop.go (65%) rename vendor/github.com/compose-spec/compose-go/{ => v2}/types/device.go (88%) rename vendor/github.com/compose-spec/compose-go/{ => v2}/types/duration.go (100%) create mode 100644 vendor/github.com/compose-spec/compose-go/v2/types/envfile.go rename vendor/github.com/compose-spec/compose-go/{ => v2}/types/healthcheck.go (96%) create mode 100644 vendor/github.com/compose-spec/compose-go/v2/types/hostList.go rename vendor/github.com/compose-spec/compose-go/{ => v2}/types/labels.go (100%) create mode 100644 vendor/github.com/compose-spec/compose-go/v2/types/mapping.go rename vendor/github.com/compose-spec/compose-go/{ => v2}/types/options.go (91%) create mode 100644 vendor/github.com/compose-spec/compose-go/v2/types/project.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/types/services.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/types/ssh.go rename vendor/github.com/compose-spec/compose-go/{ => v2}/types/stringOrList.go (84%) rename vendor/github.com/compose-spec/compose-go/{ => v2}/types/types.go (77%) rename vendor/github.com/compose-spec/compose-go/{ => v2}/utils/collectionutils.go (65%) create mode 100644 vendor/github.com/compose-spec/compose-go/v2/utils/pathutils.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/utils/set.go rename vendor/github.com/compose-spec/compose-go/{ => v2}/utils/stringutils.go (83%) create mode 100644 vendor/github.com/compose-spec/compose-go/v2/validation/external.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/validation/validation.go create mode 100644 vendor/github.com/compose-spec/compose-go/v2/validation/volume.go create mode 100644 vendor/github.com/go-viper/mapstructure/v2/.editorconfig create mode 100644 vendor/github.com/go-viper/mapstructure/v2/.envrc create mode 100644 vendor/github.com/go-viper/mapstructure/v2/.gitignore create mode 100644 vendor/github.com/go-viper/mapstructure/v2/.golangci.yaml rename vendor/github.com/{mitchellh/mapstructure => go-viper/mapstructure/v2}/CHANGELOG.md (92%) rename vendor/github.com/{mitchellh/mapstructure => go-viper/mapstructure/v2}/LICENSE (100%) create mode 100644 vendor/github.com/go-viper/mapstructure/v2/README.md create mode 100644 vendor/github.com/go-viper/mapstructure/v2/decode_hooks.go create mode 100644 vendor/github.com/go-viper/mapstructure/v2/flake.lock create mode 100644 vendor/github.com/go-viper/mapstructure/v2/flake.nix create mode 100644 vendor/github.com/go-viper/mapstructure/v2/internal/errors/errors.go create mode 100644 vendor/github.com/go-viper/mapstructure/v2/internal/errors/join.go create mode 100644 vendor/github.com/go-viper/mapstructure/v2/internal/errors/join_go1_19.go rename vendor/github.com/{mitchellh/mapstructure => go-viper/mapstructure/v2}/mapstructure.go (92%) create mode 100644 vendor/github.com/go-viper/mapstructure/v2/reflect_go1_19.go create mode 100644 vendor/github.com/go-viper/mapstructure/v2/reflect_go1_20.go delete mode 100644 vendor/github.com/mitchellh/mapstructure/README.md delete mode 100644 vendor/github.com/mitchellh/mapstructure/decode_hooks.go delete mode 100644 vendor/github.com/mitchellh/mapstructure/error.go diff --git a/go.mod b/go.mod index c24fc56ac..679df6b67 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/alessio/shellescape v1.4.1 github.com/blang/semver v3.5.1+incompatible github.com/bmatcuk/doublestar/v4 v4.6.0 - github.com/compose-spec/compose-go v1.20.2 + github.com/compose-spec/compose-go/v2 v2.2.0 github.com/creack/pty v1.1.21 github.com/docker/cli v27.1.1+incompatible github.com/docker/docker v25.0.5+incompatible @@ -115,6 +115,7 @@ require ( github.com/go-openapi/jsonreference v0.20.4 // indirect github.com/go-openapi/swag v0.22.7 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/go-viper/mapstructure/v2 v2.0.0 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/cel-go v0.17.8 // indirect diff --git a/go.sum b/go.sum index 5572db102..e42fd6b63 100644 --- a/go.sum +++ b/go.sum @@ -203,6 +203,8 @@ github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWH github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/compose-spec/compose-go v1.20.2 h1:u/yfZHn4EaHGdidrZycWpxXgFffjYULlTbRfJ51ykjQ= github.com/compose-spec/compose-go v1.20.2/go.mod h1:+MdqXV4RA7wdFsahh/Kb8U0pAJqkg7mr4PM9tFKU8RM= +github.com/compose-spec/compose-go/v2 v2.2.0 h1:VsQosGhuO+H9wh5laiIiAe4TVd73kQ5NWwmNrdm0HRA= +github.com/compose-spec/compose-go/v2 v2.2.0/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= @@ -460,6 +462,8 @@ github.com/go-openapi/swag v0.22.7/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmrid github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc= +github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= diff --git a/pkg/compose/helper.go b/pkg/compose/helper.go index ed1ff0761..6ebc2047c 100644 --- a/pkg/compose/helper.go +++ b/pkg/compose/helper.go @@ -10,8 +10,8 @@ import ( "strings" "github.com/blang/semver" - composecli "github.com/compose-spec/compose-go/cli" - composetypes "github.com/compose-spec/compose-go/types" + composecli "github.com/compose-spec/compose-go/v2/cli" + composetypes "github.com/compose-spec/compose-go/v2/types" "github.com/loft-sh/devpod/pkg/devcontainer/config" "github.com/loft-sh/devpod/pkg/docker" "github.com/pkg/errors" @@ -24,7 +24,7 @@ const ( ServiceLabel = "com.docker.compose.service" ) -func LoadDockerComposeProject(paths []string, envFiles []string) (*composetypes.Project, error) { +func LoadDockerComposeProject(ctx context.Context, paths []string, envFiles []string) (*composetypes.Project, error) { projectOptions, err := composecli.NewProjectOptions( paths, composecli.WithOsEnv, @@ -36,7 +36,7 @@ func LoadDockerComposeProject(paths []string, envFiles []string) (*composetypes. return nil, err } - project, err := composecli.ProjectFromOptions(projectOptions) + project, err := composecli.ProjectFromOptions(ctx, projectOptions) if err != nil { return nil, err } diff --git a/pkg/devcontainer/build.go b/pkg/devcontainer/build.go index fa5ae3101..ff2525a58 100644 --- a/pkg/devcontainer/build.go +++ b/pkg/devcontainer/build.go @@ -54,7 +54,7 @@ func (r *runner) build( } r.Log.Debugf("Loading docker compose project %+v", composeFiles) - project, err := compose.LoadDockerComposeProject(composeFiles, envFiles) + project, err := compose.LoadDockerComposeProject(ctx, composeFiles, envFiles) if err != nil { return nil, errors.Wrap(err, "load docker compose project") } diff --git a/pkg/devcontainer/compose.go b/pkg/devcontainer/compose.go index 15eea6131..02499ee6e 100644 --- a/pkg/devcontainer/compose.go +++ b/pkg/devcontainer/compose.go @@ -11,7 +11,7 @@ import ( "strings" "time" - composetypes "github.com/compose-spec/compose-go/types" + composetypes "github.com/compose-spec/compose-go/v2/types" "github.com/joho/godotenv" "github.com/loft-sh/devpod/pkg/compose" "github.com/loft-sh/devpod/pkg/devcontainer/config" @@ -128,7 +128,7 @@ func (r *runner) runDockerCompose( } r.Log.Debugf("Loading docker compose project %+v", composeFiles) - project, err := compose.LoadDockerComposeProject(composeFiles, envFiles) + project, err := compose.LoadDockerComposeProject(ctx, composeFiles, envFiles) if err != nil { return nil, errors.Wrap(err, "load docker compose project") } @@ -570,8 +570,8 @@ func (r *runner) extendedDockerComposeBuild(composeService *composetypes.Service } project := &composetypes.Project{} - project.Services = composetypes.Services{ - *service, + project.Services = map[string]composetypes.ServiceConfig{ + service.Name: *service, } dockerComposeFolder := getDockerComposeFolder(r.WorkspaceConfig.Origin) @@ -736,8 +736,8 @@ while sleep 1 & wait $$!; do :; done`, } project := &composetypes.Project{} - project.Services = composetypes.Services{ - *overrideService, + project.Services = map[string]composetypes.ServiceConfig{ + overrideService.Name: *overrideService, } // Configure volumes @@ -745,10 +745,8 @@ while sleep 1 & wait $$!; do :; done`, for _, m := range mergedConfig.Mounts { if m.Type == "volume" { volumeMounts = append(volumeMounts, composetypes.VolumeConfig{ - Name: m.Source, - External: composetypes.External{ - External: m.External, - }, + Name: m.Source, + External: composetypes.External(m.External), }) } } diff --git a/vendor/github.com/compose-spec/compose-go/loader/include.go b/vendor/github.com/compose-spec/compose-go/loader/include.go deleted file mode 100644 index aaebfd301..000000000 --- a/vendor/github.com/compose-spec/compose-go/loader/include.go +++ /dev/null @@ -1,167 +0,0 @@ -/* - Copyright 2020 The Compose Specification 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 loader - -import ( - "context" - "fmt" - "path/filepath" - "reflect" - - "github.com/compose-spec/compose-go/dotenv" - interp "github.com/compose-spec/compose-go/interpolation" - "github.com/compose-spec/compose-go/types" - "github.com/pkg/errors" -) - -// LoadIncludeConfig parse the require config from raw yaml -func LoadIncludeConfig(source []interface{}) ([]types.IncludeConfig, error) { - var requires []types.IncludeConfig - err := Transform(source, &requires) - return requires, err -} - -var transformIncludeConfig TransformerFunc = func(data interface{}) (interface{}, error) { - switch value := data.(type) { - case string: - return map[string]interface{}{"path": value}, nil - case map[string]interface{}: - return value, nil - default: - return data, errors.Errorf("invalid type %T for `include` configuration", value) - } -} - -func loadInclude(ctx context.Context, filename string, configDetails types.ConfigDetails, model *types.Config, options *Options, loaded []string) (*types.Config, map[string][]types.IncludeConfig, error) { - included := make(map[string][]types.IncludeConfig) - for _, r := range model.Include { - included[filename] = append(included[filename], r) - - for i, p := range r.Path { - for _, loader := range options.ResourceLoaders { - if loader.Accept(p) { - path, err := loader.Load(ctx, p) - if err != nil { - return nil, nil, err - } - p = path - break - } - } - r.Path[i] = absPath(configDetails.WorkingDir, p) - } - if r.ProjectDirectory == "" { - r.ProjectDirectory = filepath.Dir(r.Path[0]) - } - - loadOptions := options.clone() - loadOptions.SetProjectName(model.Name, true) - loadOptions.ResolvePaths = true - loadOptions.SkipNormalization = true - loadOptions.SkipConsistencyCheck = true - - envFromFile, err := dotenv.GetEnvFromFile(configDetails.Environment, r.ProjectDirectory, r.EnvFile) - if err != nil { - return nil, nil, err - } - - config := types.ConfigDetails{ - WorkingDir: r.ProjectDirectory, - ConfigFiles: types.ToConfigFiles(r.Path), - Environment: configDetails.Environment.Clone().Merge(envFromFile), - } - loadOptions.Interpolate = &interp.Options{ - Substitute: options.Interpolate.Substitute, - LookupValue: config.LookupEnv, - TypeCastMapping: options.Interpolate.TypeCastMapping, - } - imported, err := load(ctx, config, loadOptions, loaded) - if err != nil { - return nil, nil, err - } - for k, v := range imported.IncludeReferences { - included[k] = append(included[k], v...) - } - - err = importResources(model, imported, r.Path) - if err != nil { - return nil, nil, err - } - } - model.Include = nil - return model, included, nil -} - -// importResources import into model all resources defined by imported, and report error on conflict -func importResources(model *types.Config, imported *types.Project, path []string) error { - services := mapByName(model.Services) - for _, service := range imported.Services { - if present, ok := services[service.Name]; ok { - if reflect.DeepEqual(present, service) { - continue - } - return fmt.Errorf("imported compose file %s defines conflicting service %s", path, service.Name) - } - model.Services = append(model.Services, service) - } - for _, service := range imported.DisabledServices { - if disabled, ok := services[service.Name]; ok { - if reflect.DeepEqual(disabled, service) { - continue - } - return fmt.Errorf("imported compose file %s defines conflicting service %s", path, service.Name) - } - model.Services = append(model.Services, service) - } - for n, network := range imported.Networks { - if present, ok := model.Networks[n]; ok { - if reflect.DeepEqual(present, network) { - continue - } - return fmt.Errorf("imported compose file %s defines conflicting network %s", path, n) - } - model.Networks[n] = network - } - for n, volume := range imported.Volumes { - if present, ok := model.Volumes[n]; ok { - if reflect.DeepEqual(present, volume) { - continue - } - return fmt.Errorf("imported compose file %s defines conflicting volume %s", path, n) - } - model.Volumes[n] = volume - } - for n, secret := range imported.Secrets { - if present, ok := model.Secrets[n]; ok { - if reflect.DeepEqual(present, secret) { - continue - } - return fmt.Errorf("imported compose file %s defines conflicting secret %s", path, n) - } - model.Secrets[n] = secret - } - for n, config := range imported.Configs { - if present, ok := model.Configs[n]; ok { - if reflect.DeepEqual(present, config) { - continue - } - return fmt.Errorf("imported compose file %s defines conflicting config %s", path, n) - } - model.Configs[n] = config - } - return nil -} diff --git a/vendor/github.com/compose-spec/compose-go/loader/loader.go b/vendor/github.com/compose-spec/compose-go/loader/loader.go deleted file mode 100644 index a70004671..000000000 --- a/vendor/github.com/compose-spec/compose-go/loader/loader.go +++ /dev/null @@ -1,1277 +0,0 @@ -/* - Copyright 2020 The Compose Specification 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 loader - -import ( - "bytes" - "context" - "fmt" - "io" - "os" - paths "path" - "path/filepath" - "reflect" - "regexp" - "strconv" - "strings" - - "github.com/compose-spec/compose-go/consts" - interp "github.com/compose-spec/compose-go/interpolation" - "github.com/compose-spec/compose-go/schema" - "github.com/compose-spec/compose-go/template" - "github.com/compose-spec/compose-go/types" - "github.com/mitchellh/mapstructure" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "gopkg.in/yaml.v3" -) - -// Options supported by Load -type Options struct { - // Skip schema validation - SkipValidation bool - // Skip interpolation - SkipInterpolation bool - // Skip normalization - SkipNormalization bool - // Resolve paths - ResolvePaths bool - // Convert Windows paths - ConvertWindowsPaths bool - // Skip consistency check - SkipConsistencyCheck bool - // Skip extends - SkipExtends bool - // SkipInclude will ignore `include` and only load model from file(s) set by ConfigDetails - SkipInclude bool - // SkipResolveEnvironment will ignore computing `environment` for services - SkipResolveEnvironment bool - // Interpolation options - Interpolate *interp.Options - // Discard 'env_file' entries after resolving to 'environment' section - discardEnvFiles bool - // Set project projectName - projectName string - // Indicates when the projectName was imperatively set or guessed from path - projectNameImperativelySet bool - // Profiles set profiles to enable - Profiles []string - // ResourceLoaders manages support for remote resources - ResourceLoaders []ResourceLoader -} - -// ResourceLoader is a plugable remote resource resolver -type ResourceLoader interface { - // Accept returns `true` is the resource reference matches ResourceLoader supported protocol(s) - Accept(path string) bool - // Load returns the path to a local copy of remote resource identified by `path`. - Load(ctx context.Context, path string) (string, error) -} - -func (o *Options) clone() *Options { - return &Options{ - SkipValidation: o.SkipValidation, - SkipInterpolation: o.SkipInterpolation, - SkipNormalization: o.SkipNormalization, - ResolvePaths: o.ResolvePaths, - ConvertWindowsPaths: o.ConvertWindowsPaths, - SkipConsistencyCheck: o.SkipConsistencyCheck, - SkipExtends: o.SkipExtends, - SkipInclude: o.SkipInclude, - Interpolate: o.Interpolate, - discardEnvFiles: o.discardEnvFiles, - projectName: o.projectName, - projectNameImperativelySet: o.projectNameImperativelySet, - Profiles: o.Profiles, - ResourceLoaders: o.ResourceLoaders, - } -} - -func (o *Options) SetProjectName(name string, imperativelySet bool) { - o.projectName = name - o.projectNameImperativelySet = imperativelySet -} - -func (o Options) GetProjectName() (string, bool) { - return o.projectName, o.projectNameImperativelySet -} - -// serviceRef identifies a reference to a service. It's used to detect cyclic -// references in "extends". -type serviceRef struct { - filename string - service string -} - -type cycleTracker struct { - loaded []serviceRef -} - -func (ct *cycleTracker) Add(filename, service string) error { - toAdd := serviceRef{filename: filename, service: service} - for _, loaded := range ct.loaded { - if toAdd == loaded { - // Create an error message of the form: - // Circular reference: - // service-a in docker-compose.yml - // extends service-b in docker-compose.yml - // extends service-a in docker-compose.yml - errLines := []string{ - "Circular reference:", - fmt.Sprintf(" %s in %s", ct.loaded[0].service, ct.loaded[0].filename), - } - for _, service := range append(ct.loaded[1:], toAdd) { - errLines = append(errLines, fmt.Sprintf(" extends %s in %s", service.service, service.filename)) - } - - return errors.New(strings.Join(errLines, "\n")) - } - } - - ct.loaded = append(ct.loaded, toAdd) - return nil -} - -// WithDiscardEnvFiles sets the Options to discard the `env_file` section after resolving to -// the `environment` section -func WithDiscardEnvFiles(opts *Options) { - opts.discardEnvFiles = true -} - -// WithSkipValidation sets the Options to skip validation when loading sections -func WithSkipValidation(opts *Options) { - opts.SkipValidation = true -} - -// WithProfiles sets profiles to be activated -func WithProfiles(profiles []string) func(*Options) { - return func(opts *Options) { - opts.Profiles = profiles - } -} - -// ParseYAML reads the bytes from a file, parses the bytes into a mapping -// structure, and returns it. -func ParseYAML(source []byte) (map[string]interface{}, error) { - r := bytes.NewReader(source) - decoder := yaml.NewDecoder(r) - m, _, err := parseYAML(decoder) - return m, err -} - -// PostProcessor is used to tweak compose model based on metadata extracted during yaml Unmarshal phase -// that hardly can be implemented using go-yaml and mapstructure -type PostProcessor interface { - yaml.Unmarshaler - - // Apply changes to compose model based on recorder metadata - Apply(config *types.Config) error -} - -func parseYAML(decoder *yaml.Decoder) (map[string]interface{}, PostProcessor, error) { - var cfg interface{} - processor := ResetProcessor{target: &cfg} - - if err := decoder.Decode(&processor); err != nil { - return nil, nil, err - } - stringMap, ok := cfg.(map[string]interface{}) - if ok { - converted, err := convertToStringKeysRecursive(stringMap, "") - if err != nil { - return nil, nil, err - } - return converted.(map[string]interface{}), &processor, nil - } - cfgMap, ok := cfg.(map[interface{}]interface{}) - if !ok { - return nil, nil, errors.Errorf("Top-level object must be a mapping") - } - converted, err := convertToStringKeysRecursive(cfgMap, "") - if err != nil { - return nil, nil, err - } - return converted.(map[string]interface{}), &processor, nil -} - -// Load reads a ConfigDetails and returns a fully loaded configuration. -// Deprecated: use LoadWithContext. -func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.Project, error) { - return LoadWithContext(context.Background(), configDetails, options...) -} - -// LoadWithContext reads a ConfigDetails and returns a fully loaded configuration -func LoadWithContext(ctx context.Context, configDetails types.ConfigDetails, options ...func(*Options)) (*types.Project, error) { - if len(configDetails.ConfigFiles) < 1 { - return nil, errors.Errorf("No files specified") - } - - opts := &Options{ - Interpolate: &interp.Options{ - Substitute: template.Substitute, - LookupValue: configDetails.LookupEnv, - TypeCastMapping: interpolateTypeCastMapping, - }, - ResolvePaths: true, - } - - for _, op := range options { - op(opts) - } - - projectName, err := projectName(configDetails, opts) - if err != nil { - return nil, err - } - opts.projectName = projectName - - // TODO(milas): this should probably ALWAYS set (overriding any existing) - if _, ok := configDetails.Environment[consts.ComposeProjectName]; !ok && projectName != "" { - if configDetails.Environment == nil { - configDetails.Environment = map[string]string{} - } - configDetails.Environment[consts.ComposeProjectName] = projectName - } - - return load(ctx, configDetails, opts, nil) -} - -func load(ctx context.Context, configDetails types.ConfigDetails, opts *Options, loaded []string) (*types.Project, error) { - var model *types.Config - - mainFile := configDetails.ConfigFiles[0].Filename - for _, f := range loaded { - if f == mainFile { - loaded = append(loaded, mainFile) - return nil, errors.Errorf("include cycle detected:\n%s\n include %s", loaded[0], strings.Join(loaded[1:], "\n include ")) - } - } - loaded = append(loaded, mainFile) - - includeRefs := make(map[string][]types.IncludeConfig) - for _, file := range configDetails.ConfigFiles { - var postProcessor PostProcessor - configDict := file.Config - - processYaml := func() error { - if !opts.SkipValidation { - if err := schema.Validate(configDict); err != nil { - return fmt.Errorf("validating %s: %w", file.Filename, err) - } - } - - configDict = groupXFieldsIntoExtensions(configDict) - - cfg, err := loadSections(ctx, file.Filename, configDict, configDetails, opts) - if err != nil { - return err - } - - if !opts.SkipInclude { - var included map[string][]types.IncludeConfig - cfg, included, err = loadInclude(ctx, file.Filename, configDetails, cfg, opts, loaded) - if err != nil { - return err - } - for k, v := range included { - includeRefs[k] = append(includeRefs[k], v...) - } - } - - if model == nil { - model = cfg - } else { - merged, err := merge([]*types.Config{model, cfg}) - if err != nil { - return err - } - model = merged - } - if postProcessor != nil { - err = postProcessor.Apply(model) - if err != nil { - return err - } - } - return nil - } - - if configDict == nil { - if len(file.Content) == 0 { - content, err := os.ReadFile(file.Filename) - if err != nil { - return nil, err - } - file.Content = content - } - - r := bytes.NewReader(file.Content) - decoder := yaml.NewDecoder(r) - for { - dict, p, err := parseConfig(decoder, opts) - if err != nil { - if err != io.EOF { - return nil, fmt.Errorf("parsing %s: %w", file.Filename, err) - } - break - } - configDict = dict - postProcessor = p - - if err := processYaml(); err != nil { - return nil, err - } - } - } else { - if err := processYaml(); err != nil { - return nil, err - } - } - } - - if model == nil { - return nil, errors.New("empty compose file") - } - - project := &types.Project{ - Name: opts.projectName, - WorkingDir: configDetails.WorkingDir, - Services: model.Services, - Networks: model.Networks, - Volumes: model.Volumes, - Secrets: model.Secrets, - Configs: model.Configs, - Environment: configDetails.Environment, - Extensions: model.Extensions, - } - - if len(includeRefs) != 0 { - project.IncludeReferences = includeRefs - } - - if !opts.SkipNormalization { - err := Normalize(project) - if err != nil { - return nil, err - } - } - - if opts.ResolvePaths { - err := ResolveRelativePaths(project) - if err != nil { - return nil, err - } - } - - if opts.ConvertWindowsPaths { - for i, service := range project.Services { - for j, volume := range service.Volumes { - service.Volumes[j] = convertVolumePath(volume) - } - project.Services[i] = service - } - } - - if !opts.SkipConsistencyCheck { - err := checkConsistency(project) - if err != nil { - return nil, err - } - } - - project.ApplyProfiles(opts.Profiles) - - if !opts.SkipResolveEnvironment { - err := project.ResolveServicesEnvironment(opts.discardEnvFiles) - if err != nil { - return nil, err - } - } - - return project, nil -} - -func InvalidProjectNameErr(v string) error { - return fmt.Errorf( - "invalid project name %q: must consist only of lowercase alphanumeric characters, hyphens, and underscores as well as start with a letter or number", - v, - ) -} - -// projectName determines the canonical name to use for the project considering -// the loader Options as well as `name` fields in Compose YAML fields (which -// also support interpolation). -// -// TODO(milas): restructure loading so that we don't need to re-parse the YAML -// here, as it's both wasteful and makes this code error-prone. -func projectName(details types.ConfigDetails, opts *Options) (string, error) { - projectName, projectNameImperativelySet := opts.GetProjectName() - - // if user did NOT provide a name explicitly, then see if one is defined - // in any of the config files - if !projectNameImperativelySet { - var pjNameFromConfigFile string - for _, configFile := range details.ConfigFiles { - yml, err := ParseYAML(configFile.Content) - if err != nil { - // HACK: the way that loading is currently structured, this is - // a duplicative parse just for the `name`. if it fails, we - // give up but don't return the error, knowing that it'll get - // caught downstream for us - return "", nil - } - if val, ok := yml["name"]; ok && val != "" { - sVal, ok := val.(string) - if !ok { - // HACK: see above - this is a temporary parsed version - // that hasn't been schema-validated, but we don't want - // to be the ones to actually report that, so give up, - // knowing that it'll get caught downstream for us - return "", nil - } - pjNameFromConfigFile = sVal - } - } - if !opts.SkipInterpolation { - interpolated, err := interp.Interpolate( - map[string]interface{}{"name": pjNameFromConfigFile}, - *opts.Interpolate, - ) - if err != nil { - return "", err - } - pjNameFromConfigFile = interpolated["name"].(string) - } - pjNameFromConfigFile = NormalizeProjectName(pjNameFromConfigFile) - if pjNameFromConfigFile != "" { - projectName = pjNameFromConfigFile - } - } - - if projectName == "" { - return "", errors.New("project name must not be empty") - } - - if NormalizeProjectName(projectName) != projectName { - return "", InvalidProjectNameErr(projectName) - } - - return projectName, nil -} - -func NormalizeProjectName(s string) string { - r := regexp.MustCompile("[a-z0-9_-]") - s = strings.ToLower(s) - s = strings.Join(r.FindAllString(s, -1), "") - return strings.TrimLeft(s, "_-") -} - -func parseConfig(decoder *yaml.Decoder, opts *Options) (map[string]interface{}, PostProcessor, error) { - yml, postProcessor, err := parseYAML(decoder) - if err != nil { - return nil, nil, err - } - if !opts.SkipInterpolation { - interpolated, err := interp.Interpolate(yml, *opts.Interpolate) - return interpolated, postProcessor, err - } - return yml, postProcessor, err -} - -const extensions = "#extensions" // Using # prefix, we prevent risk to conflict with an actual yaml key - -func groupXFieldsIntoExtensions(dict map[string]interface{}) map[string]interface{} { - extras := map[string]interface{}{} - for key, value := range dict { - if strings.HasPrefix(key, "x-") { - extras[key] = value - delete(dict, key) - } - if d, ok := value.(map[string]interface{}); ok { - dict[key] = groupXFieldsIntoExtensions(d) - } - } - if len(extras) > 0 { - dict[extensions] = extras - } - return dict -} - -func loadSections(ctx context.Context, filename string, config map[string]interface{}, configDetails types.ConfigDetails, opts *Options) (*types.Config, error) { - var err error - cfg := types.Config{ - Filename: filename, - } - name := "" - if n, ok := config["name"]; ok { - name, ok = n.(string) - if !ok { - return nil, errors.New("project name must be a string") - } - } - cfg.Name = name - cfg.Services, err = LoadServices(ctx, filename, getSection(config, "services"), configDetails.WorkingDir, configDetails.LookupEnv, opts) - if err != nil { - return nil, err - } - cfg.Networks, err = LoadNetworks(getSection(config, "networks")) - if err != nil { - return nil, err - } - cfg.Volumes, err = LoadVolumes(getSection(config, "volumes")) - if err != nil { - return nil, err - } - cfg.Secrets, err = LoadSecrets(getSection(config, "secrets")) - if err != nil { - return nil, err - } - cfg.Configs, err = LoadConfigObjs(getSection(config, "configs")) - if err != nil { - return nil, err - } - cfg.Include, err = LoadIncludeConfig(getSequence(config, "include")) - if err != nil { - return nil, err - } - extensions := getSection(config, extensions) - if len(extensions) > 0 { - cfg.Extensions = extensions - } - return &cfg, nil -} - -func getSection(config map[string]interface{}, key string) map[string]interface{} { - section, ok := config[key] - if !ok { - return make(map[string]interface{}) - } - return section.(map[string]interface{}) -} - -func getSequence(config map[string]interface{}, key string) []interface{} { - section, ok := config[key] - if !ok { - return make([]interface{}, 0) - } - return section.([]interface{}) -} - -// ForbiddenPropertiesError is returned when there are properties in the Compose -// file that are forbidden. -type ForbiddenPropertiesError struct { - Properties map[string]string -} - -func (e *ForbiddenPropertiesError) Error() string { - return "Configuration contains forbidden properties" -} - -// Transform converts the source into the target struct with compose types transformer -// and the specified transformers if any. -func Transform(source interface{}, target interface{}, additionalTransformers ...Transformer) error { - data := mapstructure.Metadata{} - config := &mapstructure.DecoderConfig{ - DecodeHook: mapstructure.ComposeDecodeHookFunc( - createTransformHook(additionalTransformers...), - decoderHook), - Result: target, - TagName: "yaml", - Metadata: &data, - } - decoder, err := mapstructure.NewDecoder(config) - if err != nil { - return err - } - return decoder.Decode(source) -} - -// TransformerFunc defines a function to perform the actual transformation -type TransformerFunc func(interface{}) (interface{}, error) - -// Transformer defines a map to type transformer -type Transformer struct { - TypeOf reflect.Type - Func TransformerFunc -} - -func createTransformHook(additionalTransformers ...Transformer) mapstructure.DecodeHookFuncType { - transforms := map[reflect.Type]func(interface{}) (interface{}, error){ - reflect.TypeOf(types.External{}): transformExternal, - reflect.TypeOf(types.Options{}): transformOptions, - reflect.TypeOf(types.UlimitsConfig{}): transformUlimits, - reflect.TypeOf([]types.ServicePortConfig{}): transformServicePort, - reflect.TypeOf(types.ServiceSecretConfig{}): transformFileReferenceConfig, - reflect.TypeOf(types.ServiceConfigObjConfig{}): transformFileReferenceConfig, - reflect.TypeOf(map[string]*types.ServiceNetworkConfig{}): transformServiceNetworkMap, - reflect.TypeOf(types.Mapping{}): transformMappingOrListFunc("=", false), - reflect.TypeOf(types.MappingWithEquals{}): transformMappingOrListFunc("=", true), - reflect.TypeOf(types.MappingWithColon{}): transformMappingOrListFunc(":", false), - reflect.TypeOf(types.HostsList{}): transformMappingOrListFunc(":", false), - reflect.TypeOf(types.ServiceVolumeConfig{}): transformServiceVolumeConfig, - reflect.TypeOf(types.BuildConfig{}): transformBuildConfig, - reflect.TypeOf(types.DependsOnConfig{}): transformDependsOnConfig, - reflect.TypeOf(types.ExtendsConfig{}): transformExtendsConfig, - reflect.TypeOf(types.SSHConfig{}): transformSSHConfig, - reflect.TypeOf(types.IncludeConfig{}): transformIncludeConfig, - } - - for _, transformer := range additionalTransformers { - transforms[transformer.TypeOf] = transformer.Func - } - - return func(_ reflect.Type, target reflect.Type, data interface{}) (interface{}, error) { - transform, ok := transforms[target] - if !ok { - return data, nil - } - return transform(data) - } -} - -// keys need to be converted to strings for jsonschema -func convertToStringKeysRecursive(value interface{}, keyPrefix string) (interface{}, error) { - if mapping, ok := value.(map[string]interface{}); ok { - for key, entry := range mapping { - var newKeyPrefix string - if keyPrefix == "" { - newKeyPrefix = key - } else { - newKeyPrefix = fmt.Sprintf("%s.%s", keyPrefix, key) - } - convertedEntry, err := convertToStringKeysRecursive(entry, newKeyPrefix) - if err != nil { - return nil, err - } - mapping[key] = convertedEntry - } - return mapping, nil - } - if mapping, ok := value.(map[interface{}]interface{}); ok { - dict := make(map[string]interface{}) - for key, entry := range mapping { - str, ok := key.(string) - if !ok { - return nil, formatInvalidKeyError(keyPrefix, key) - } - var newKeyPrefix string - if keyPrefix == "" { - newKeyPrefix = str - } else { - newKeyPrefix = fmt.Sprintf("%s.%s", keyPrefix, str) - } - convertedEntry, err := convertToStringKeysRecursive(entry, newKeyPrefix) - if err != nil { - return nil, err - } - dict[str] = convertedEntry - } - return dict, nil - } - if list, ok := value.([]interface{}); ok { - var convertedList []interface{} - for index, entry := range list { - newKeyPrefix := fmt.Sprintf("%s[%d]", keyPrefix, index) - convertedEntry, err := convertToStringKeysRecursive(entry, newKeyPrefix) - if err != nil { - return nil, err - } - convertedList = append(convertedList, convertedEntry) - } - return convertedList, nil - } - return value, nil -} - -func formatInvalidKeyError(keyPrefix string, key interface{}) error { - var location string - if keyPrefix == "" { - location = "at top level" - } else { - location = fmt.Sprintf("in %s", keyPrefix) - } - return errors.Errorf("Non-string key %s: %#v", location, key) -} - -// LoadServices produces a ServiceConfig map from a compose file Dict -// the servicesDict is not validated if directly used. Use Load() to enable validation -func LoadServices(ctx context.Context, filename string, servicesDict map[string]interface{}, workingDir string, lookupEnv template.Mapping, opts *Options) ([]types.ServiceConfig, error) { - var services []types.ServiceConfig - - x, ok := servicesDict[extensions] - if ok { - // as a top-level attribute, "services" doesn't support extensions, and a service can be named `x-foo` - for k, v := range x.(map[string]interface{}) { - servicesDict[k] = v - } - delete(servicesDict, extensions) - } - - for name := range servicesDict { - serviceConfig, err := loadServiceWithExtends(ctx, filename, name, servicesDict, workingDir, lookupEnv, opts, &cycleTracker{}) - if err != nil { - return nil, err - } - - services = append(services, *serviceConfig) - } - - return services, nil -} - -func loadServiceWithExtends(ctx context.Context, filename, name string, servicesDict map[string]interface{}, workingDir string, lookupEnv template.Mapping, opts *Options, ct *cycleTracker) (*types.ServiceConfig, error) { - if err := ct.Add(filename, name); err != nil { - return nil, err - } - - target, ok := servicesDict[name] - if !ok { - return nil, fmt.Errorf("cannot extend service %q in %s: service not found", name, filename) - } - - if target == nil { - target = map[string]interface{}{} - } - - serviceConfig, err := LoadService(name, target.(map[string]interface{})) - if err != nil { - return nil, err - } - - if serviceConfig.Extends != nil && !opts.SkipExtends { - baseServiceName := serviceConfig.Extends.Service - var baseService *types.ServiceConfig - file := serviceConfig.Extends.File - if file == "" { - baseService, err = loadServiceWithExtends(ctx, filename, baseServiceName, servicesDict, workingDir, lookupEnv, opts, ct) - if err != nil { - return nil, err - } - } else { - for _, loader := range opts.ResourceLoaders { - if loader.Accept(file) { - path, err := loader.Load(ctx, file) - if err != nil { - return nil, err - } - file = path - break - } - } - // Resolve the path to the imported file, and load it. - baseFilePath := absPath(workingDir, file) - - b, err := os.ReadFile(baseFilePath) - if err != nil { - return nil, err - } - - r := bytes.NewReader(b) - decoder := yaml.NewDecoder(r) - - baseFile, _, err := parseConfig(decoder, opts) - if err != nil { - return nil, err - } - - baseFileServices := getSection(baseFile, "services") - baseService, err = loadServiceWithExtends(ctx, baseFilePath, baseServiceName, baseFileServices, filepath.Dir(baseFilePath), lookupEnv, opts, ct) - if err != nil { - return nil, err - } - - // Make paths relative to the importing Compose file. Note that we - // make the paths relative to `file` rather than `baseFilePath` so - // that the resulting paths won't be absolute if `file` isn't an - // absolute path. - - baseFileParent := filepath.Dir(file) - ResolveServiceRelativePaths(baseFileParent, baseService) - } - - serviceConfig, err = _merge(baseService, serviceConfig) - if err != nil { - return nil, err - } - serviceConfig.Extends = nil - } - - return serviceConfig, nil -} - -// LoadService produces a single ServiceConfig from a compose file Dict -// the serviceDict is not validated if directly used. Use Load() to enable validation -func LoadService(name string, serviceDict map[string]interface{}) (*types.ServiceConfig, error) { - serviceConfig := &types.ServiceConfig{ - Scale: 1, - } - if err := Transform(serviceDict, serviceConfig); err != nil { - return nil, err - } - serviceConfig.Name = name - - for i, volume := range serviceConfig.Volumes { - if volume.Type != types.VolumeTypeBind { - continue - } - if volume.Source == "" { - return nil, errors.New(`invalid mount config for type "bind": field Source must not be empty`) - } - - serviceConfig.Volumes[i] = volume - } - - return serviceConfig, nil -} - -// Windows paths, c:\\my\\path\\shiny, need to be changed to be compatible with -// the Engine. Volume paths are expected to be linux style /c/my/path/shiny/ -func convertVolumePath(volume types.ServiceVolumeConfig) types.ServiceVolumeConfig { - volumeName := strings.ToLower(filepath.VolumeName(volume.Source)) - if len(volumeName) != 2 { - return volume - } - - convertedSource := fmt.Sprintf("/%c%s", volumeName[0], volume.Source[len(volumeName):]) - convertedSource = strings.ReplaceAll(convertedSource, "\\", "/") - - volume.Source = convertedSource - return volume -} - -func resolveMaybeUnixPath(workingDir string, path string) string { - filePath := expandUser(path) - // Check if source is an absolute path (either Unix or Windows), to - // handle a Windows client with a Unix daemon or vice-versa. - // - // Note that this is not required for Docker for Windows when specifying - // a local Windows path, because Docker for Windows translates the Windows - // path into a valid path within the VM. - if !paths.IsAbs(filePath) && !isAbs(filePath) { - filePath = absPath(workingDir, filePath) - } - return filePath -} - -// TODO: make this more robust -func expandUser(path string) string { - if strings.HasPrefix(path, "~") { - home, err := os.UserHomeDir() - if err != nil { - logrus.Warn("cannot expand '~', because the environment lacks HOME") - return path - } - return filepath.Join(home, path[1:]) - } - return path -} - -func transformUlimits(data interface{}) (interface{}, error) { - switch value := data.(type) { - case int: - return types.UlimitsConfig{Single: value}, nil - case map[string]interface{}: - ulimit := types.UlimitsConfig{} - if v, ok := value["soft"]; ok { - ulimit.Soft = v.(int) - } - if v, ok := value["hard"]; ok { - ulimit.Hard = v.(int) - } - return ulimit, nil - default: - return data, errors.Errorf("invalid type %T for ulimits", value) - } -} - -// LoadNetworks produces a NetworkConfig map from a compose file Dict -// the source Dict is not validated if directly used. Use Load() to enable validation -func LoadNetworks(source map[string]interface{}) (map[string]types.NetworkConfig, error) { - networks := make(map[string]types.NetworkConfig) - err := Transform(source, &networks) - if err != nil { - return networks, err - } - for name, network := range networks { - if !network.External.External { - continue - } - switch { - case network.External.Name != "": - if network.Name != "" { - return nil, errors.Errorf("network %s: network.external.name and network.name conflict; only use network.name", name) - } - logrus.Warnf("network %s: network.external.name is deprecated. Please set network.name with external: true", name) - network.Name = network.External.Name - network.External.Name = "" - case network.Name == "": - network.Name = name - } - networks[name] = network - } - return networks, nil -} - -func externalVolumeError(volume, key string) error { - return errors.Errorf( - "conflicting parameters \"external\" and %q specified for volume %q", - key, volume) -} - -// LoadVolumes produces a VolumeConfig map from a compose file Dict -// the source Dict is not validated if directly used. Use Load() to enable validation -func LoadVolumes(source map[string]interface{}) (map[string]types.VolumeConfig, error) { - volumes := make(map[string]types.VolumeConfig) - if err := Transform(source, &volumes); err != nil { - return volumes, err - } - - for name, volume := range volumes { - if !volume.External.External { - continue - } - switch { - case volume.Driver != "": - return nil, externalVolumeError(name, "driver") - case len(volume.DriverOpts) > 0: - return nil, externalVolumeError(name, "driver_opts") - case len(volume.Labels) > 0: - return nil, externalVolumeError(name, "labels") - case volume.External.Name != "": - if volume.Name != "" { - return nil, errors.Errorf("volume %s: volume.external.name and volume.name conflict; only use volume.name", name) - } - logrus.Warnf("volume %s: volume.external.name is deprecated in favor of volume.name", name) - volume.Name = volume.External.Name - volume.External.Name = "" - case volume.Name == "": - volume.Name = name - } - volumes[name] = volume - } - return volumes, nil -} - -// LoadSecrets produces a SecretConfig map from a compose file Dict -// the source Dict is not validated if directly used. Use Load() to enable validation -func LoadSecrets(source map[string]interface{}) (map[string]types.SecretConfig, error) { - secrets := make(map[string]types.SecretConfig) - if err := Transform(source, &secrets); err != nil { - return secrets, err - } - for name, secret := range secrets { - obj, err := loadFileObjectConfig(name, "secret", types.FileObjectConfig(secret)) - if err != nil { - return nil, err - } - secrets[name] = types.SecretConfig(obj) - } - return secrets, nil -} - -// LoadConfigObjs produces a ConfigObjConfig map from a compose file Dict -// the source Dict is not validated if directly used. Use Load() to enable validation -func LoadConfigObjs(source map[string]interface{}) (map[string]types.ConfigObjConfig, error) { - configs := make(map[string]types.ConfigObjConfig) - if err := Transform(source, &configs); err != nil { - return configs, err - } - for name, config := range configs { - obj, err := loadFileObjectConfig(name, "config", types.FileObjectConfig(config)) - if err != nil { - return nil, err - } - configs[name] = types.ConfigObjConfig(obj) - } - return configs, nil -} - -func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfig) (types.FileObjectConfig, error) { - // if "external: true" - switch { - case obj.External.External: - // handle deprecated external.name - if obj.External.Name != "" { - if obj.Name != "" { - return obj, errors.Errorf("%[1]s %[2]s: %[1]s.external.name and %[1]s.name conflict; only use %[1]s.name", objType, name) - } - logrus.Warnf("%[1]s %[2]s: %[1]s.external.name is deprecated in favor of %[1]s.name", objType, name) - obj.Name = obj.External.Name - obj.External.Name = "" - } else if obj.Name == "" { - obj.Name = name - } - // if not "external: true" - case obj.Driver != "": - if obj.File != "" { - return obj, errors.Errorf("%[1]s %[2]s: %[1]s.driver and %[1]s.file conflict; only use %[1]s.driver", objType, name) - } - } - - return obj, nil -} - -var transformOptions TransformerFunc = func(data interface{}) (interface{}, error) { - switch value := data.(type) { - case map[string]interface{}: - return toMapStringString(value, false), nil - case map[string]string: - return value, nil - default: - return data, errors.Errorf("invalid type %T for map[string]string", value) - } -} - -var transformExternal TransformerFunc = func(data interface{}) (interface{}, error) { - switch value := data.(type) { - case bool: - return map[string]interface{}{"external": value}, nil - case map[string]interface{}: - return map[string]interface{}{"external": true, "name": value["name"]}, nil - default: - return data, errors.Errorf("invalid type %T for external", value) - } -} - -var transformServicePort TransformerFunc = func(data interface{}) (interface{}, error) { - switch entries := data.(type) { - case []interface{}: - // We process the list instead of individual items here. - // The reason is that one entry might be mapped to multiple ServicePortConfig. - // Therefore we take an input of a list and return an output of a list. - var ports []interface{} - for _, entry := range entries { - switch value := entry.(type) { - case int: - parsed, err := types.ParsePortConfig(fmt.Sprint(value)) - if err != nil { - return data, err - } - for _, v := range parsed { - ports = append(ports, v) - } - case string: - parsed, err := types.ParsePortConfig(value) - if err != nil { - return data, err - } - for _, v := range parsed { - ports = append(ports, v) - } - case map[string]interface{}: - published := value["published"] - if v, ok := published.(int); ok { - value["published"] = strconv.Itoa(v) - } - ports = append(ports, groupXFieldsIntoExtensions(value)) - default: - return data, errors.Errorf("invalid type %T for port", value) - } - } - return ports, nil - default: - return data, errors.Errorf("invalid type %T for port", entries) - } -} - -var transformFileReferenceConfig TransformerFunc = func(data interface{}) (interface{}, error) { - switch value := data.(type) { - case string: - return map[string]interface{}{"source": value}, nil - case map[string]interface{}: - if target, ok := value["target"]; ok { - value["target"] = cleanTarget(target.(string)) - } - return groupXFieldsIntoExtensions(value), nil - default: - return data, errors.Errorf("invalid type %T for secret", value) - } -} - -func cleanTarget(target string) string { - if target == "" { - return "" - } - return paths.Clean(target) -} - -var transformBuildConfig TransformerFunc = func(data interface{}) (interface{}, error) { - switch value := data.(type) { - case string: - return map[string]interface{}{"context": value}, nil - case map[string]interface{}: - return groupXFieldsIntoExtensions(data.(map[string]interface{})), nil - default: - return data, errors.Errorf("invalid type %T for service build", value) - } -} - -var transformDependsOnConfig TransformerFunc = func(data interface{}) (interface{}, error) { - switch value := data.(type) { - case []interface{}: - transformed := map[string]interface{}{} - for _, serviceIntf := range value { - service, ok := serviceIntf.(string) - if !ok { - return data, errors.Errorf("invalid type %T for service depends_on element, expected string", value) - } - transformed[service] = map[string]interface{}{"condition": types.ServiceConditionStarted, "required": true} - } - return transformed, nil - case map[string]interface{}: - transformed := map[string]interface{}{} - for service, val := range value { - dependsConfigIntf, ok := val.(map[string]interface{}) - if !ok { - return data, errors.Errorf("invalid type %T for service depends_on element", value) - } - if _, ok := dependsConfigIntf["required"]; !ok { - dependsConfigIntf["required"] = true - } - transformed[service] = dependsConfigIntf - } - return groupXFieldsIntoExtensions(transformed), nil - default: - return data, errors.Errorf("invalid type %T for service depends_on", value) - } -} - -var transformExtendsConfig TransformerFunc = func(value interface{}) (interface{}, error) { - switch value.(type) { - case string: - return map[string]interface{}{"service": value}, nil - case map[string]interface{}: - return value, nil - default: - return value, errors.Errorf("invalid type %T for extends", value) - } -} - -var transformServiceVolumeConfig TransformerFunc = func(data interface{}) (interface{}, error) { - switch value := data.(type) { - case string: - volume, err := ParseVolume(value) - volume.Target = cleanTarget(volume.Target) - return volume, err - case map[string]interface{}: - data := groupXFieldsIntoExtensions(data.(map[string]interface{})) - if target, ok := data["target"]; ok { - data["target"] = cleanTarget(target.(string)) - } - return data, nil - default: - return data, errors.Errorf("invalid type %T for service volume", value) - } -} - -var transformServiceNetworkMap TransformerFunc = func(value interface{}) (interface{}, error) { - if list, ok := value.([]interface{}); ok { - mapValue := map[interface{}]interface{}{} - for _, name := range list { - mapValue[name] = nil - } - return mapValue, nil - } - return value, nil -} - -var transformSSHConfig TransformerFunc = func(data interface{}) (interface{}, error) { - switch value := data.(type) { - case map[string]interface{}: - var result []types.SSHKey - for key, val := range value { - if val == nil { - val = "" - } - result = append(result, types.SSHKey{ID: key, Path: val.(string)}) - } - return result, nil - case []interface{}: - var result []types.SSHKey - for _, v := range value { - key, val := transformValueToMapEntry(v.(string), "=", false) - result = append(result, types.SSHKey{ID: key, Path: val.(string)}) - } - return result, nil - case string: - return ParseShortSSHSyntax(value) - } - return nil, errors.Errorf("expected a sting, map or a list, got %T: %#v", data, data) -} - -// ParseShortSSHSyntax parse short syntax for SSH authentications -func ParseShortSSHSyntax(value string) ([]types.SSHKey, error) { - if value == "" { - value = "default" - } - key, val := transformValueToMapEntry(value, "=", false) - result := []types.SSHKey{{ID: key, Path: val.(string)}} - return result, nil -} - -func transformMappingOrListFunc(sep string, allowNil bool) TransformerFunc { - return func(data interface{}) (interface{}, error) { - return transformMappingOrList(data, sep, allowNil) - } -} - -func transformMappingOrList(mappingOrList interface{}, sep string, allowNil bool) (interface{}, error) { - switch value := mappingOrList.(type) { - case map[string]interface{}: - return toMapStringString(value, allowNil), nil - case []interface{}: - result := make(map[string]interface{}) - for _, value := range value { - key, val := transformValueToMapEntry(value.(string), sep, allowNil) - result[key] = val - } - return result, nil - } - return nil, errors.Errorf("expected a map or a list, got %T: %#v", mappingOrList, mappingOrList) -} - -func transformValueToMapEntry(value string, separator string, allowNil bool) (string, interface{}) { - parts := strings.SplitN(value, separator, 2) - key := parts[0] - switch { - case len(parts) == 1 && allowNil: - return key, nil - case len(parts) == 1 && !allowNil: - return key, "" - default: - return key, parts[1] - } -} - -func toMapStringString(value map[string]interface{}, allowNil bool) map[string]interface{} { - output := make(map[string]interface{}) - for key, value := range value { - output[key] = toString(value, allowNil) - } - return output -} - -func toString(value interface{}, allowNil bool) interface{} { - switch { - case value != nil: - return fmt.Sprint(value) - case allowNil: - return nil - default: - return "" - } -} diff --git a/vendor/github.com/compose-spec/compose-go/loader/merge.go b/vendor/github.com/compose-spec/compose-go/loader/merge.go deleted file mode 100644 index 654d711de..000000000 --- a/vendor/github.com/compose-spec/compose-go/loader/merge.go +++ /dev/null @@ -1,378 +0,0 @@ -/* - Copyright 2020 The Compose Specification 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 loader - -import ( - "reflect" - "sort" - - "github.com/compose-spec/compose-go/types" - "github.com/imdario/mergo" - "github.com/pkg/errors" -) - -type specials struct { - m map[reflect.Type]func(dst, src reflect.Value) error -} - -var serviceSpecials = &specials{ - m: map[reflect.Type]func(dst, src reflect.Value) error{ - reflect.TypeOf(&types.LoggingConfig{}): safelyMerge(mergeLoggingConfig), - reflect.TypeOf(&types.UlimitsConfig{}): safelyMerge(mergeUlimitsConfig), - reflect.TypeOf([]types.ServiceVolumeConfig{}): mergeSlice(toServiceVolumeConfigsMap, toServiceVolumeConfigsSlice), - reflect.TypeOf([]types.ServicePortConfig{}): mergeSlice(toServicePortConfigsMap, toServicePortConfigsSlice), - reflect.TypeOf([]types.ServiceSecretConfig{}): mergeSlice(toServiceSecretConfigsMap, toServiceSecretConfigsSlice), - reflect.TypeOf([]types.ServiceConfigObjConfig{}): mergeSlice(toServiceConfigObjConfigsMap, toSServiceConfigObjConfigsSlice), - reflect.TypeOf(&types.UlimitsConfig{}): mergeUlimitsConfig, - }, -} - -func (s *specials) Transformer(t reflect.Type) func(dst, src reflect.Value) error { - // TODO this is a workaround waiting for imdario/mergo#131 - if t.Kind() == reflect.Pointer && t.Elem().Kind() == reflect.Bool { - return func(dst, src reflect.Value) error { - if dst.CanSet() && !src.IsNil() { - dst.Set(src) - } - return nil - } - } - if fn, ok := s.m[t]; ok { - return fn - } - return nil -} - -func merge(configs []*types.Config) (*types.Config, error) { - base := configs[0] - for _, override := range configs[1:] { - var err error - base.Name = mergeNames(base.Name, override.Name) - base.Services, err = mergeServices(base.Services, override.Services) - if err != nil { - return base, errors.Wrapf(err, "cannot merge services from %s", override.Filename) - } - base.Volumes, err = mergeVolumes(base.Volumes, override.Volumes) - if err != nil { - return base, errors.Wrapf(err, "cannot merge volumes from %s", override.Filename) - } - base.Networks, err = mergeNetworks(base.Networks, override.Networks) - if err != nil { - return base, errors.Wrapf(err, "cannot merge networks from %s", override.Filename) - } - base.Secrets, err = mergeSecrets(base.Secrets, override.Secrets) - if err != nil { - return base, errors.Wrapf(err, "cannot merge secrets from %s", override.Filename) - } - base.Configs, err = mergeConfigs(base.Configs, override.Configs) - if err != nil { - return base, errors.Wrapf(err, "cannot merge configs from %s", override.Filename) - } - base.Extensions, err = mergeExtensions(base.Extensions, override.Extensions) - if err != nil { - return base, errors.Wrapf(err, "cannot merge extensions from %s", override.Filename) - } - } - return base, nil -} - -func mergeNames(base, override string) string { - if override != "" { - return override - } - return base -} - -func mergeServices(base, override []types.ServiceConfig) ([]types.ServiceConfig, error) { - baseServices := mapByName(base) - overrideServices := mapByName(override) - for name, overrideService := range overrideServices { - overrideService := overrideService - if baseService, ok := baseServices[name]; ok { - merged, err := _merge(&baseService, &overrideService) - if err != nil { - return nil, errors.Wrapf(err, "cannot merge service %s", name) - } - baseServices[name] = *merged - continue - } - baseServices[name] = overrideService - } - services := []types.ServiceConfig{} - for _, baseService := range baseServices { - services = append(services, baseService) - } - sort.Slice(services, func(i, j int) bool { return services[i].Name < services[j].Name }) - return services, nil -} - -func _merge(baseService *types.ServiceConfig, overrideService *types.ServiceConfig) (*types.ServiceConfig, error) { - if err := mergo.Merge(baseService, overrideService, - mergo.WithAppendSlice, - mergo.WithOverride, - mergo.WithTransformers(serviceSpecials)); err != nil { - return nil, err - } - if overrideService.Command != nil { - baseService.Command = overrideService.Command - } - if overrideService.HealthCheck != nil && overrideService.HealthCheck.Test != nil { - baseService.HealthCheck.Test = overrideService.HealthCheck.Test - } - if overrideService.Entrypoint != nil { - baseService.Entrypoint = overrideService.Entrypoint - } - if baseService.Environment != nil { - baseService.Environment.OverrideBy(overrideService.Environment) - } else { - baseService.Environment = overrideService.Environment - } - baseService.Expose = unique(baseService.Expose) - return baseService, nil -} - -func unique(slice []string) []string { - if slice == nil { - return nil - } - uniqMap := make(map[string]struct{}) - var uniqSlice []string - for _, v := range slice { - if _, ok := uniqMap[v]; !ok { - uniqSlice = append(uniqSlice, v) - uniqMap[v] = struct{}{} - } - } - return uniqSlice -} - -func toServiceSecretConfigsMap(s interface{}) (map[interface{}]interface{}, error) { - secrets, ok := s.([]types.ServiceSecretConfig) - if !ok { - return nil, errors.Errorf("not a serviceSecretConfig: %v", s) - } - m := map[interface{}]interface{}{} - for _, secret := range secrets { - m[secret.Source] = secret - } - return m, nil -} - -func toServiceConfigObjConfigsMap(s interface{}) (map[interface{}]interface{}, error) { - secrets, ok := s.([]types.ServiceConfigObjConfig) - if !ok { - return nil, errors.Errorf("not a serviceSecretConfig: %v", s) - } - m := map[interface{}]interface{}{} - for _, secret := range secrets { - m[secret.Source] = secret - } - return m, nil -} - -func toServicePortConfigsMap(s interface{}) (map[interface{}]interface{}, error) { - ports, ok := s.([]types.ServicePortConfig) - if !ok { - return nil, errors.Errorf("not a servicePortConfig slice: %v", s) - } - m := map[interface{}]interface{}{} - type port struct { - target uint32 - published string - ip string - protocol string - } - - for _, p := range ports { - mergeKey := port{ - target: p.Target, - published: p.Published, - ip: p.HostIP, - protocol: p.Protocol, - } - m[mergeKey] = p - } - return m, nil -} - -func toServiceVolumeConfigsMap(s interface{}) (map[interface{}]interface{}, error) { - volumes, ok := s.([]types.ServiceVolumeConfig) - if !ok { - return nil, errors.Errorf("not a ServiceVolumeConfig slice: %v", s) - } - m := map[interface{}]interface{}{} - for _, v := range volumes { - m[v.Target] = v - } - return m, nil -} - -func toServiceSecretConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error { - var s []types.ServiceSecretConfig - for _, v := range m { - s = append(s, v.(types.ServiceSecretConfig)) - } - sort.Slice(s, func(i, j int) bool { return s[i].Source < s[j].Source }) - dst.Set(reflect.ValueOf(s)) - return nil -} - -func toSServiceConfigObjConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error { - var s []types.ServiceConfigObjConfig - for _, v := range m { - s = append(s, v.(types.ServiceConfigObjConfig)) - } - sort.Slice(s, func(i, j int) bool { return s[i].Source < s[j].Source }) - dst.Set(reflect.ValueOf(s)) - return nil -} - -func toServicePortConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error { - var s []types.ServicePortConfig - for _, v := range m { - s = append(s, v.(types.ServicePortConfig)) - } - sort.Slice(s, func(i, j int) bool { - if s[i].Target != s[j].Target { - return s[i].Target < s[j].Target - } - if s[i].Published != s[j].Published { - return s[i].Published < s[j].Published - } - if s[i].HostIP != s[j].HostIP { - return s[i].HostIP < s[j].HostIP - } - return s[i].Protocol < s[j].Protocol - }) - dst.Set(reflect.ValueOf(s)) - return nil -} - -func toServiceVolumeConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error { - var s []types.ServiceVolumeConfig - for _, v := range m { - s = append(s, v.(types.ServiceVolumeConfig)) - } - sort.Slice(s, func(i, j int) bool { return s[i].Target < s[j].Target }) - dst.Set(reflect.ValueOf(s)) - return nil -} - -type toMapFn func(s interface{}) (map[interface{}]interface{}, error) -type writeValueFromMapFn func(reflect.Value, map[interface{}]interface{}) error - -func safelyMerge(mergeFn func(dst, src reflect.Value) error) func(dst, src reflect.Value) error { - return func(dst, src reflect.Value) error { - if src.IsNil() { - return nil - } - if dst.IsNil() { - dst.Set(src) - return nil - } - return mergeFn(dst, src) - } -} - -func mergeSlice(toMap toMapFn, writeValue writeValueFromMapFn) func(dst, src reflect.Value) error { - return func(dst, src reflect.Value) error { - dstMap, err := sliceToMap(toMap, dst) - if err != nil { - return err - } - srcMap, err := sliceToMap(toMap, src) - if err != nil { - return err - } - if err := mergo.Map(&dstMap, srcMap, mergo.WithOverride); err != nil { - return err - } - return writeValue(dst, dstMap) - } -} - -func sliceToMap(toMap toMapFn, v reflect.Value) (map[interface{}]interface{}, error) { - // check if valid - if !v.IsValid() { - return nil, errors.Errorf("invalid value : %+v", v) - } - return toMap(v.Interface()) -} - -func mergeLoggingConfig(dst, src reflect.Value) error { - // Same driver, merging options - if getLoggingDriver(dst.Elem()) == getLoggingDriver(src.Elem()) || - getLoggingDriver(dst.Elem()) == "" || getLoggingDriver(src.Elem()) == "" { - if getLoggingDriver(dst.Elem()) == "" { - dst.Elem().FieldByName("Driver").SetString(getLoggingDriver(src.Elem())) - } - dstOptions := dst.Elem().FieldByName("Options").Interface().(types.Options) - srcOptions := src.Elem().FieldByName("Options").Interface().(types.Options) - return mergo.Merge(&dstOptions, srcOptions, mergo.WithOverride) - } - // Different driver, override with src - dst.Set(src) - return nil -} - -// nolint: unparam -func mergeUlimitsConfig(dst, src reflect.Value) error { - if src.Interface() != reflect.Zero(reflect.TypeOf(src.Interface())).Interface() { - dst.Elem().Set(src.Elem()) - } - return nil -} - -func getLoggingDriver(v reflect.Value) string { - return v.FieldByName("Driver").String() -} - -func mapByName(services []types.ServiceConfig) map[string]types.ServiceConfig { - m := map[string]types.ServiceConfig{} - for _, service := range services { - m[service.Name] = service - } - return m -} - -func mergeVolumes(base, override map[string]types.VolumeConfig) (map[string]types.VolumeConfig, error) { - err := mergo.Map(&base, &override, mergo.WithOverride) - return base, err -} - -func mergeNetworks(base, override map[string]types.NetworkConfig) (map[string]types.NetworkConfig, error) { - err := mergo.Map(&base, &override, mergo.WithOverride) - return base, err -} - -func mergeSecrets(base, override map[string]types.SecretConfig) (map[string]types.SecretConfig, error) { - err := mergo.Map(&base, &override, mergo.WithOverride) - return base, err -} - -func mergeConfigs(base, override map[string]types.ConfigObjConfig) (map[string]types.ConfigObjConfig, error) { - err := mergo.Map(&base, &override, mergo.WithOverride) - return base, err -} - -func mergeExtensions(base, override map[string]interface{}) (map[string]interface{}, error) { - if base == nil { - base = map[string]interface{}{} - } - err := mergo.Map(&base, &override, mergo.WithOverride) - return base, err -} diff --git a/vendor/github.com/compose-spec/compose-go/loader/normalize.go b/vendor/github.com/compose-spec/compose-go/loader/normalize.go deleted file mode 100644 index 58863b5fa..000000000 --- a/vendor/github.com/compose-spec/compose-go/loader/normalize.go +++ /dev/null @@ -1,335 +0,0 @@ -/* - Copyright 2020 The Compose Specification 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 loader - -import ( - "fmt" - "strings" - - "github.com/compose-spec/compose-go/errdefs" - "github.com/compose-spec/compose-go/types" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -// Normalize compose project by moving deprecated attributes to their canonical position and injecting implicit defaults -func Normalize(project *types.Project) error { - if project.Networks == nil { - project.Networks = make(map[string]types.NetworkConfig) - } - - // If not declared explicitly, Compose model involves an implicit "default" network - if _, ok := project.Networks["default"]; !ok { - project.Networks["default"] = types.NetworkConfig{} - } - - if err := relocateExternalName(project); err != nil { - return err - } - - for i, s := range project.Services { - if len(s.Networks) == 0 && s.NetworkMode == "" { - // Service without explicit network attachment are implicitly exposed on default network - s.Networks = map[string]*types.ServiceNetworkConfig{"default": nil} - } - - if s.PullPolicy == types.PullPolicyIfNotPresent { - s.PullPolicy = types.PullPolicyMissing - } - - fn := func(s string) (string, bool) { - v, ok := project.Environment[s] - return v, ok - } - - if s.Build != nil { - if s.Build.Context == "" { - s.Build.Context = "." - } - if s.Build.Dockerfile == "" && s.Build.DockerfileInline == "" { - s.Build.Dockerfile = "Dockerfile" - } - s.Build.Args = s.Build.Args.Resolve(fn) - } - s.Environment = s.Environment.Resolve(fn) - - for _, link := range s.Links { - parts := strings.Split(link, ":") - if len(parts) == 2 { - link = parts[0] - } - s.DependsOn = setIfMissing(s.DependsOn, link, types.ServiceDependency{ - Condition: types.ServiceConditionStarted, - Restart: true, - Required: true, - }) - } - - for _, namespace := range []string{s.NetworkMode, s.Ipc, s.Pid, s.Uts, s.Cgroup} { - if strings.HasPrefix(namespace, types.ServicePrefix) { - name := namespace[len(types.ServicePrefix):] - s.DependsOn = setIfMissing(s.DependsOn, name, types.ServiceDependency{ - Condition: types.ServiceConditionStarted, - Restart: true, - Required: true, - }) - } - } - - for _, vol := range s.VolumesFrom { - if !strings.HasPrefix(vol, types.ContainerPrefix) { - spec := strings.Split(vol, ":") - s.DependsOn = setIfMissing(s.DependsOn, spec[0], types.ServiceDependency{ - Condition: types.ServiceConditionStarted, - Restart: false, - Required: true, - }) - } - } - - err := relocateLogDriver(&s) - if err != nil { - return err - } - - err = relocateLogOpt(&s) - if err != nil { - return err - } - - err = relocateDockerfile(&s) - if err != nil { - return err - } - - err = relocateScale(&s) - if err != nil { - return err - } - - inferImplicitDependencies(&s) - - project.Services[i] = s - } - - setNameFromKey(project) - - return nil -} - -// IsServiceDependency check the relation set by ref refers to a service -func IsServiceDependency(ref string) (string, bool) { - if strings.HasPrefix( - ref, - types.ServicePrefix, - ) { - return ref[len(types.ServicePrefix):], true - } - return "", false -} - -func inferImplicitDependencies(service *types.ServiceConfig) { - var dependencies []string - - maybeReferences := []string{ - service.NetworkMode, - service.Ipc, - service.Pid, - service.Uts, - service.Cgroup, - } - for _, ref := range maybeReferences { - if dep, ok := IsServiceDependency(ref); ok { - dependencies = append(dependencies, dep) - } - } - - for _, vol := range service.VolumesFrom { - spec := strings.Split(vol, ":") - if len(spec) == 0 { - continue - } - if spec[0] == "container" { - continue - } - dependencies = append(dependencies, spec[0]) - } - - for _, link := range service.Links { - dependencies = append(dependencies, strings.Split(link, ":")[0]) - } - - if len(dependencies) > 0 && service.DependsOn == nil { - service.DependsOn = make(types.DependsOnConfig) - } - - for _, d := range dependencies { - if _, ok := service.DependsOn[d]; !ok { - service.DependsOn[d] = types.ServiceDependency{ - Condition: types.ServiceConditionStarted, - Required: true, - } - } - } -} - -// setIfMissing adds a ServiceDependency for service if not already defined -func setIfMissing(d types.DependsOnConfig, service string, dep types.ServiceDependency) types.DependsOnConfig { - if d == nil { - d = types.DependsOnConfig{} - } - if _, ok := d[service]; !ok { - d[service] = dep - } - return d -} - -func relocateScale(s *types.ServiceConfig) error { - scale := uint64(s.Scale) - if scale > 1 { - logrus.Warn("`scale` is deprecated. Use the `deploy.replicas` element") - if s.Deploy == nil { - s.Deploy = &types.DeployConfig{} - } - if s.Deploy.Replicas != nil && *s.Deploy.Replicas != scale { - return errors.Wrap(errdefs.ErrInvalid, "can't use both 'scale' (deprecated) and 'deploy.replicas'") - } - s.Deploy.Replicas = &scale - } - return nil -} - -// Resources with no explicit name are actually named by their key in map -func setNameFromKey(project *types.Project) { - for i, n := range project.Networks { - if n.Name == "" { - n.Name = fmt.Sprintf("%s_%s", project.Name, i) - project.Networks[i] = n - } - } - - for i, v := range project.Volumes { - if v.Name == "" { - v.Name = fmt.Sprintf("%s_%s", project.Name, i) - project.Volumes[i] = v - } - } - - for i, c := range project.Configs { - if c.Name == "" { - c.Name = fmt.Sprintf("%s_%s", project.Name, i) - project.Configs[i] = c - } - } - - for i, s := range project.Secrets { - if s.Name == "" { - s.Name = fmt.Sprintf("%s_%s", project.Name, i) - project.Secrets[i] = s - } - } -} - -func relocateExternalName(project *types.Project) error { - for i, n := range project.Networks { - if n.External.Name != "" { - if n.Name != "" { - return errors.Wrap(errdefs.ErrInvalid, "can't use both 'networks.external.name' (deprecated) and 'networks.name'") - } - n.Name = n.External.Name - } - project.Networks[i] = n - } - - for i, v := range project.Volumes { - if v.External.Name != "" { - if v.Name != "" { - return errors.Wrap(errdefs.ErrInvalid, "can't use both 'volumes.external.name' (deprecated) and 'volumes.name'") - } - v.Name = v.External.Name - } - project.Volumes[i] = v - } - - for i, s := range project.Secrets { - if s.External.Name != "" { - if s.Name != "" { - return errors.Wrap(errdefs.ErrInvalid, "can't use both 'secrets.external.name' (deprecated) and 'secrets.name'") - } - s.Name = s.External.Name - } - project.Secrets[i] = s - } - - for i, c := range project.Configs { - if c.External.Name != "" { - if c.Name != "" { - return errors.Wrap(errdefs.ErrInvalid, "can't use both 'configs.external.name' (deprecated) and 'configs.name'") - } - c.Name = c.External.Name - } - project.Configs[i] = c - } - return nil -} - -func relocateLogOpt(s *types.ServiceConfig) error { - if len(s.LogOpt) != 0 { - logrus.Warn("`log_opts` is deprecated. Use the `logging` element") - if s.Logging == nil { - s.Logging = &types.LoggingConfig{} - } - for k, v := range s.LogOpt { - if _, ok := s.Logging.Options[k]; !ok { - s.Logging.Options[k] = v - } else { - return errors.Wrap(errdefs.ErrInvalid, "can't use both 'log_opt' (deprecated) and 'logging.options'") - } - } - } - return nil -} - -func relocateLogDriver(s *types.ServiceConfig) error { - if s.LogDriver != "" { - logrus.Warn("`log_driver` is deprecated. Use the `logging` element") - if s.Logging == nil { - s.Logging = &types.LoggingConfig{} - } - if s.Logging.Driver == "" { - s.Logging.Driver = s.LogDriver - } else { - return errors.Wrap(errdefs.ErrInvalid, "can't use both 'log_driver' (deprecated) and 'logging.driver'") - } - } - return nil -} - -func relocateDockerfile(s *types.ServiceConfig) error { - if s.Dockerfile != "" { - logrus.Warn("`dockerfile` is deprecated. Use the `build` element") - if s.Build == nil { - s.Build = &types.BuildConfig{} - } - if s.Dockerfile == "" { - s.Build.Dockerfile = s.Dockerfile - } else { - return errors.Wrap(errdefs.ErrInvalid, "can't use both 'dockerfile' (deprecated) and 'build.dockerfile'") - } - } - return nil -} diff --git a/vendor/github.com/compose-spec/compose-go/loader/paths.go b/vendor/github.com/compose-spec/compose-go/loader/paths.go deleted file mode 100644 index 519a6a690..000000000 --- a/vendor/github.com/compose-spec/compose-go/loader/paths.go +++ /dev/null @@ -1,172 +0,0 @@ -/* - Copyright 2020 The Compose Specification 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 loader - -import ( - "os" - "path/filepath" - "strings" - - "github.com/compose-spec/compose-go/types" -) - -// ResolveRelativePaths resolves relative paths based on project WorkingDirectory -func ResolveRelativePaths(project *types.Project) error { - absWorkingDir, err := filepath.Abs(project.WorkingDir) - if err != nil { - return err - } - project.WorkingDir = absWorkingDir - - absComposeFiles, err := absComposeFiles(project.ComposeFiles) - if err != nil { - return err - } - project.ComposeFiles = absComposeFiles - - for i, s := range project.Services { - ResolveServiceRelativePaths(project.WorkingDir, &s) - project.Services[i] = s - } - - for i, obj := range project.Configs { - if obj.File != "" { - obj.File = absPath(project.WorkingDir, obj.File) - project.Configs[i] = obj - } - } - - for i, obj := range project.Secrets { - if obj.File != "" { - obj.File = resolveMaybeUnixPath(project.WorkingDir, obj.File) - project.Secrets[i] = obj - } - } - - for name, config := range project.Volumes { - if config.Driver == "local" && config.DriverOpts["o"] == "bind" { - // This is actually a bind mount - config.DriverOpts["device"] = resolveMaybeUnixPath(project.WorkingDir, config.DriverOpts["device"]) - project.Volumes[name] = config - } - } - - // don't coerce a nil map to an empty map - if project.IncludeReferences != nil { - absIncludes := make(map[string][]types.IncludeConfig, len(project.IncludeReferences)) - for filename, config := range project.IncludeReferences { - filename = absPath(project.WorkingDir, filename) - absConfigs := make([]types.IncludeConfig, len(config)) - for i, c := range config { - absConfigs[i] = types.IncludeConfig{ - Path: resolvePaths(project.WorkingDir, c.Path), - ProjectDirectory: absPath(project.WorkingDir, c.ProjectDirectory), - EnvFile: resolvePaths(project.WorkingDir, c.EnvFile), - } - } - absIncludes[filename] = absConfigs - } - project.IncludeReferences = absIncludes - } - - return nil -} - -func ResolveServiceRelativePaths(workingDir string, s *types.ServiceConfig) { - if s.Build != nil { - if !isRemoteContext(s.Build.Context) { - s.Build.Context = absPath(workingDir, s.Build.Context) - } - for name, path := range s.Build.AdditionalContexts { - if strings.Contains(path, "://") { // `docker-image://` or any builder specific context type - continue - } - if isRemoteContext(path) { - continue - } - s.Build.AdditionalContexts[name] = absPath(workingDir, path) - } - } - for j, f := range s.EnvFile { - s.EnvFile[j] = absPath(workingDir, f) - } - - if s.Extends != nil && s.Extends.File != "" { - s.Extends.File = absPath(workingDir, s.Extends.File) - } - - for i, vol := range s.Volumes { - if vol.Type != types.VolumeTypeBind { - continue - } - s.Volumes[i].Source = resolveMaybeUnixPath(workingDir, vol.Source) - } - - if s.Develop != nil { - for i, w := range s.Develop.Watch { - w.Path = absPath(workingDir, w.Path) - s.Develop.Watch[i] = w - } - } -} - -func absPath(workingDir string, filePath string) string { - if strings.HasPrefix(filePath, "~") { - home, _ := os.UserHomeDir() - return filepath.Join(home, filePath[1:]) - } - if filepath.IsAbs(filePath) { - return filePath - } - return filepath.Join(workingDir, filePath) -} - -func absComposeFiles(composeFiles []string) ([]string, error) { - for i, composeFile := range composeFiles { - absComposefile, err := filepath.Abs(composeFile) - if err != nil { - return nil, err - } - composeFiles[i] = absComposefile - } - return composeFiles, nil -} - -// isRemoteContext returns true if the value is a Git reference or HTTP(S) URL. -// -// Any other value is assumed to be a local filesystem path and returns false. -// -// See: https://github.com/moby/buildkit/blob/18fc875d9bfd6e065cd8211abc639434ba65aa56/frontend/dockerui/context.go#L76-L79 -func isRemoteContext(maybeURL string) bool { - for _, prefix := range []string{"https://", "http://", "git://", "ssh://", "github.com/", "git@"} { - if strings.HasPrefix(maybeURL, prefix) { - return true - } - } - return false -} - -func resolvePaths(basePath string, in types.StringList) types.StringList { - if in == nil { - return nil - } - ret := make(types.StringList, len(in)) - for i := range in { - ret[i] = absPath(basePath, in[i]) - } - return ret -} diff --git a/vendor/github.com/compose-spec/compose-go/loader/validate.go b/vendor/github.com/compose-spec/compose-go/loader/validate.go deleted file mode 100644 index b4c42c7f1..000000000 --- a/vendor/github.com/compose-spec/compose-go/loader/validate.go +++ /dev/null @@ -1,121 +0,0 @@ -/* - Copyright 2020 The Compose Specification 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 loader - -import ( - "fmt" - "strings" - - "github.com/compose-spec/compose-go/errdefs" - "github.com/compose-spec/compose-go/types" - "github.com/pkg/errors" -) - -// checkConsistency validate a compose model is consistent -func checkConsistency(project *types.Project) error { - for _, s := range project.Services { - if s.Build == nil && s.Image == "" { - return errors.Wrapf(errdefs.ErrInvalid, "service %q has neither an image nor a build context specified", s.Name) - } - - if s.Build != nil { - if s.Build.DockerfileInline != "" && s.Build.Dockerfile != "" { - return errors.Wrapf(errdefs.ErrInvalid, "service %q declares mutualy exclusive dockerfile and dockerfile_inline", s.Name) - } - - if len(s.Build.Platforms) > 0 && s.Platform != "" { - var found bool - for _, platform := range s.Build.Platforms { - if platform == s.Platform { - found = true - break - } - } - if !found { - return errors.Wrapf(errdefs.ErrInvalid, "service.build.platforms MUST include service.platform %q ", s.Platform) - } - } - } - - if s.NetworkMode != "" && len(s.Networks) > 0 { - return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %s declares mutually exclusive `network_mode` and `networks`", s.Name)) - } - for network := range s.Networks { - if _, ok := project.Networks[network]; !ok { - return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q refers to undefined network %s", s.Name, network)) - } - } - - if s.HealthCheck != nil && len(s.HealthCheck.Test) > 0 { - switch s.HealthCheck.Test[0] { - case "CMD", "CMD-SHELL", "NONE": - default: - return errors.New(`healthcheck.test must start either by "CMD", "CMD-SHELL" or "NONE"`) - } - } - - for dependedService := range s.DependsOn { - if _, err := project.GetService(dependedService); err != nil { - return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q depends on undefined service %s", s.Name, dependedService)) - } - } - - if strings.HasPrefix(s.NetworkMode, types.ServicePrefix) { - serviceName := s.NetworkMode[len(types.ServicePrefix):] - if _, err := project.GetServices(serviceName); err != nil { - return fmt.Errorf("service %q not found for network_mode 'service:%s'", serviceName, serviceName) - } - } - - for _, volume := range s.Volumes { - if volume.Type == types.VolumeTypeVolume && volume.Source != "" { // non anonymous volumes - if _, ok := project.Volumes[volume.Source]; !ok { - return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q refers to undefined volume %s", s.Name, volume.Source)) - } - } - } - if s.Build != nil { - for _, secret := range s.Build.Secrets { - if _, ok := project.Secrets[secret.Source]; !ok { - return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q refers to undefined build secret %s", s.Name, secret.Source)) - } - } - } - for _, config := range s.Configs { - if _, ok := project.Configs[config.Source]; !ok { - return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q refers to undefined config %s", s.Name, config.Source)) - } - } - - for _, secret := range s.Secrets { - if _, ok := project.Secrets[secret.Source]; !ok { - return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q refers to undefined secret %s", s.Name, secret.Source)) - } - } - } - - for name, secret := range project.Secrets { - if secret.External.External { - continue - } - if secret.File == "" && secret.Environment == "" { - return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("secret %q must declare either `file` or `environment`", name)) - } - } - - return nil -} diff --git a/vendor/github.com/compose-spec/compose-go/types/project.go b/vendor/github.com/compose-spec/compose-go/types/project.go deleted file mode 100644 index 713b20747..000000000 --- a/vendor/github.com/compose-spec/compose-go/types/project.go +++ /dev/null @@ -1,566 +0,0 @@ -/* - Copyright 2020 The Compose Specification 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 types - -import ( - "bytes" - "encoding/json" - "fmt" - "os" - "path/filepath" - "sort" - - "github.com/compose-spec/compose-go/dotenv" - "github.com/compose-spec/compose-go/utils" - "github.com/distribution/reference" - godigest "github.com/opencontainers/go-digest" - "github.com/pkg/errors" - "golang.org/x/sync/errgroup" - "gopkg.in/yaml.v3" -) - -// Project is the result of loading a set of compose files -type Project struct { - Name string `yaml:"name,omitempty" json:"name,omitempty"` - WorkingDir string `yaml:"-" json:"-"` - Services Services `yaml:"services" json:"services"` - Networks Networks `yaml:"networks,omitempty" json:"networks,omitempty"` - Volumes Volumes `yaml:"volumes,omitempty" json:"volumes,omitempty"` - Secrets Secrets `yaml:"secrets,omitempty" json:"secrets,omitempty"` - Configs Configs `yaml:"configs,omitempty" json:"configs,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` // https://github.com/golang/go/issues/6213 - - // IncludeReferences is keyed by Compose YAML filename and contains config for - // other Compose YAML files it directly triggered a load of via `include`. - // - // Note: this is - IncludeReferences map[string][]IncludeConfig `yaml:"-" json:"-"` - ComposeFiles []string `yaml:"-" json:"-"` - Environment Mapping `yaml:"-" json:"-"` - - // DisabledServices track services which have been disable as profile is not active - DisabledServices Services `yaml:"-" json:"-"` - Profiles []string `yaml:"-" json:"-"` -} - -// ServiceNames return names for all services in this Compose config -func (p *Project) ServiceNames() []string { - var names []string - for _, s := range p.Services { - names = append(names, s.Name) - } - sort.Strings(names) - return names -} - -// VolumeNames return names for all volumes in this Compose config -func (p *Project) VolumeNames() []string { - var names []string - for k := range p.Volumes { - names = append(names, k) - } - sort.Strings(names) - return names -} - -// NetworkNames return names for all volumes in this Compose config -func (p *Project) NetworkNames() []string { - var names []string - for k := range p.Networks { - names = append(names, k) - } - sort.Strings(names) - return names -} - -// SecretNames return names for all secrets in this Compose config -func (p *Project) SecretNames() []string { - var names []string - for k := range p.Secrets { - names = append(names, k) - } - sort.Strings(names) - return names -} - -// ConfigNames return names for all configs in this Compose config -func (p *Project) ConfigNames() []string { - var names []string - for k := range p.Configs { - names = append(names, k) - } - sort.Strings(names) - return names -} - -// GetServices retrieve services by names, or return all services if no name specified -func (p *Project) GetServices(names ...string) (Services, error) { - services, servicesNotFound := p.getServicesByNames(names...) - if len(servicesNotFound) > 0 { - return services, fmt.Errorf("no such service: %s", servicesNotFound[0]) - } - return services, nil -} - -func (p *Project) getServicesByNames(names ...string) (Services, []string) { - if len(names) == 0 { - return p.Services, nil - } - services := Services{} - var servicesNotFound []string - for _, name := range names { - var serviceConfig *ServiceConfig - for _, s := range p.Services { - if s.Name == name { - serviceConfig = &s - break - } - } - if serviceConfig == nil { - servicesNotFound = append(servicesNotFound, name) - continue - } - services = append(services, *serviceConfig) - } - return services, servicesNotFound -} - -// GetDisabledService retrieve disabled service by name -func (p Project) GetDisabledService(name string) (ServiceConfig, error) { - for _, config := range p.DisabledServices { - if config.Name == name { - return config, nil - } - } - return ServiceConfig{}, fmt.Errorf("no such service: %s", name) -} - -// GetService retrieve a specific service by name -func (p *Project) GetService(name string) (ServiceConfig, error) { - services, err := p.GetServices(name) - if err != nil { - return ServiceConfig{}, err - } - if len(services) == 0 { - return ServiceConfig{}, fmt.Errorf("no such service: %s", name) - } - return services[0], nil -} - -func (p *Project) AllServices() Services { - var all Services - all = append(all, p.Services...) - all = append(all, p.DisabledServices...) - return all -} - -type ServiceFunc func(service ServiceConfig) error - -// WithServices run ServiceFunc on each service and dependencies according to DependencyPolicy -func (p *Project) WithServices(names []string, fn ServiceFunc, options ...DependencyOption) error { - if len(options) == 0 { - // backward compatibility - options = []DependencyOption{IncludeDependencies} - } - return p.withServices(names, fn, map[string]bool{}, options, map[string]ServiceDependency{}) -} - -func (p *Project) withServices(names []string, fn ServiceFunc, seen map[string]bool, options []DependencyOption, dependencies map[string]ServiceDependency) error { - services, servicesNotFound := p.getServicesByNames(names...) - if len(servicesNotFound) > 0 { - for _, serviceNotFound := range servicesNotFound { - if dependency, ok := dependencies[serviceNotFound]; !ok || dependency.Required { - return fmt.Errorf("no such service: %s", serviceNotFound) - } - } - } - for _, service := range services { - if seen[service.Name] { - continue - } - seen[service.Name] = true - var dependencies map[string]ServiceDependency - for _, policy := range options { - switch policy { - case IncludeDependents: - dependencies = utils.MapsAppend(dependencies, p.dependentsForService(service)) - case IncludeDependencies: - dependencies = utils.MapsAppend(dependencies, service.DependsOn) - case IgnoreDependencies: - // Noop - default: - return fmt.Errorf("unsupported dependency policy %d", policy) - } - } - if len(dependencies) > 0 { - err := p.withServices(utils.MapKeys(dependencies), fn, seen, options, dependencies) - if err != nil { - return err - } - } - if err := fn(service); err != nil { - return err - } - } - return nil -} - -func (p *Project) GetDependentsForService(s ServiceConfig) []string { - return utils.MapKeys(p.dependentsForService(s)) -} - -func (p *Project) dependentsForService(s ServiceConfig) map[string]ServiceDependency { - dependent := make(map[string]ServiceDependency) - for _, service := range p.Services { - for name, dependency := range service.DependsOn { - if name == s.Name { - dependent[service.Name] = dependency - } - } - } - return dependent -} - -// RelativePath resolve a relative path based project's working directory -func (p *Project) RelativePath(path string) string { - if path[0] == '~' { - home, _ := os.UserHomeDir() - path = filepath.Join(home, path[1:]) - } - if filepath.IsAbs(path) { - return path - } - return filepath.Join(p.WorkingDir, path) -} - -// HasProfile return true if service has no profile declared or has at least one profile matching -func (s ServiceConfig) HasProfile(profiles []string) bool { - if len(s.Profiles) == 0 { - return true - } - for _, p := range profiles { - for _, sp := range s.Profiles { - if sp == p { - return true - } - } - } - return false -} - -// GetProfiles retrieve the profiles implicitly enabled by explicitly targeting selected services -func (s Services) GetProfiles() []string { - set := map[string]struct{}{} - for _, service := range s { - for _, p := range service.Profiles { - set[p] = struct{}{} - } - } - var profiles []string - for k := range set { - profiles = append(profiles, k) - } - return profiles -} - -// ApplyProfiles disables service which don't match selected profiles -func (p *Project) ApplyProfiles(profiles []string) { - for _, p := range profiles { - if p == "*" { - return - } - } - var enabled, disabled Services - for _, service := range p.AllServices() { - if service.HasProfile(profiles) { - enabled = append(enabled, service) - } else { - disabled = append(disabled, service) - } - } - p.Services = enabled - p.DisabledServices = disabled - p.Profiles = profiles -} - -// EnableServices ensure services are enabled and activate profiles accordingly -func (p *Project) EnableServices(names ...string) error { - if len(names) == 0 { - return nil - } - var enabled []string - for _, name := range names { - _, err := p.GetService(name) - if err == nil { - // already enabled - continue - } - def, err := p.GetDisabledService(name) - if err != nil { - return err - } - enabled = append(enabled, def.Profiles...) - } - - profiles := p.Profiles -PROFILES: - for _, profile := range enabled { - for _, p := range profiles { - if p == profile { - continue PROFILES - } - } - profiles = append(profiles, profile) - } - p.ApplyProfiles(profiles) - - return p.ResolveServicesEnvironment(true) -} - -// WithoutUnnecessaryResources drops networks/volumes/secrets/configs that are not referenced by active services -func (p *Project) WithoutUnnecessaryResources() { - requiredNetworks := map[string]struct{}{} - requiredVolumes := map[string]struct{}{} - requiredSecrets := map[string]struct{}{} - requiredConfigs := map[string]struct{}{} - for _, s := range p.Services { - for k := range s.Networks { - requiredNetworks[k] = struct{}{} - } - for _, v := range s.Volumes { - if v.Type != VolumeTypeVolume || v.Source == "" { - continue - } - requiredVolumes[v.Source] = struct{}{} - } - for _, v := range s.Secrets { - requiredSecrets[v.Source] = struct{}{} - } - if s.Build != nil { - for _, v := range s.Build.Secrets { - requiredSecrets[v.Source] = struct{}{} - } - } - for _, v := range s.Configs { - requiredConfigs[v.Source] = struct{}{} - } - } - - networks := Networks{} - for k := range requiredNetworks { - if value, ok := p.Networks[k]; ok { - networks[k] = value - } - } - p.Networks = networks - - volumes := Volumes{} - for k := range requiredVolumes { - if value, ok := p.Volumes[k]; ok { - volumes[k] = value - } - } - p.Volumes = volumes - - secrets := Secrets{} - for k := range requiredSecrets { - if value, ok := p.Secrets[k]; ok { - secrets[k] = value - } - } - p.Secrets = secrets - - configs := Configs{} - for k := range requiredConfigs { - if value, ok := p.Configs[k]; ok { - configs[k] = value - } - } - p.Configs = configs -} - -type DependencyOption int - -const ( - IncludeDependencies = iota - IncludeDependents - IgnoreDependencies -) - -// ForServices restrict the project model to selected services and dependencies -func (p *Project) ForServices(names []string, options ...DependencyOption) error { - if len(names) == 0 { - // All services - return nil - } - - set := map[string]struct{}{} - err := p.WithServices(names, func(service ServiceConfig) error { - set[service.Name] = struct{}{} - return nil - }, options...) - if err != nil { - return err - } - - // Disable all services which are not explicit target or dependencies - var enabled Services - for _, s := range p.Services { - if _, ok := set[s.Name]; ok { - for _, option := range options { - if option == IgnoreDependencies { - // remove all dependencies but those implied by explicitly selected services - dependencies := s.DependsOn - for d := range dependencies { - if _, ok := set[d]; !ok { - delete(dependencies, d) - } - } - s.DependsOn = dependencies - } - } - enabled = append(enabled, s) - } else { - p.DisableService(s) - } - } - p.Services = enabled - return nil -} - -func (p *Project) DisableService(service ServiceConfig) { - // We should remove all dependencies which reference the disabled service - for i, s := range p.Services { - if _, ok := s.DependsOn[service.Name]; ok { - delete(s.DependsOn, service.Name) - p.Services[i] = s - } - } - p.DisabledServices = append(p.DisabledServices, service) -} - -// ResolveImages updates services images to include digest computed by a resolver function -func (p *Project) ResolveImages(resolver func(named reference.Named) (godigest.Digest, error)) error { - eg := errgroup.Group{} - for i, s := range p.Services { - idx := i - service := s - - if service.Image == "" { - continue - } - eg.Go(func() error { - named, err := reference.ParseDockerRef(service.Image) - if err != nil { - return err - } - - if _, ok := named.(reference.Canonical); !ok { - // image is named but not digested reference - digest, err := resolver(named) - if err != nil { - return err - } - named, err = reference.WithDigest(named, digest) - if err != nil { - return err - } - } - - service.Image = named.String() - p.Services[idx] = service - return nil - }) - } - return eg.Wait() -} - -// MarshalYAML marshal Project into a yaml tree -func (p *Project) MarshalYAML() ([]byte, error) { - buf := bytes.NewBuffer([]byte{}) - encoder := yaml.NewEncoder(buf) - encoder.SetIndent(2) - // encoder.CompactSeqIndent() FIXME https://github.com/go-yaml/yaml/pull/753 - err := encoder.Encode(p) - if err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -// MarshalJSON makes Config implement json.Marshaler -func (p *Project) MarshalJSON() ([]byte, error) { - m := map[string]interface{}{ - "name": p.Name, - "services": p.Services, - } - - if len(p.Networks) > 0 { - m["networks"] = p.Networks - } - if len(p.Volumes) > 0 { - m["volumes"] = p.Volumes - } - if len(p.Secrets) > 0 { - m["secrets"] = p.Secrets - } - if len(p.Configs) > 0 { - m["configs"] = p.Configs - } - for k, v := range p.Extensions { - m[k] = v - } - return json.Marshal(m) -} - -// ResolveServicesEnvironment parse env_files set for services to resolve the actual environment map for services -func (p Project) ResolveServicesEnvironment(discardEnvFiles bool) error { - for i, service := range p.Services { - service.Environment = service.Environment.Resolve(p.Environment.Resolve) - - environment := MappingWithEquals{} - // resolve variables based on other files we already parsed, + project's environment - var resolve dotenv.LookupFn = func(s string) (string, bool) { - v, ok := environment[s] - if ok && v != nil { - return *v, ok - } - return p.Environment.Resolve(s) - } - - for _, envFile := range service.EnvFile { - b, err := os.ReadFile(envFile) - if err != nil { - return errors.Wrapf(err, "Failed to load %s", envFile) - } - - fileVars, err := dotenv.ParseWithLookup(bytes.NewBuffer(b), resolve) - if err != nil { - return errors.Wrapf(err, "failed to read %s", envFile) - } - environment.OverrideBy(Mapping(fileVars).ToMappingWithEquals()) - } - - service.Environment = environment.OverrideBy(service.Environment) - - if discardEnvFiles { - service.EnvFile = nil - } - p.Services[i] = service - } - return nil -} diff --git a/vendor/github.com/compose-spec/compose-go/LICENSE b/vendor/github.com/compose-spec/compose-go/v2/LICENSE similarity index 100% rename from vendor/github.com/compose-spec/compose-go/LICENSE rename to vendor/github.com/compose-spec/compose-go/v2/LICENSE diff --git a/vendor/github.com/compose-spec/compose-go/NOTICE b/vendor/github.com/compose-spec/compose-go/v2/NOTICE similarity index 100% rename from vendor/github.com/compose-spec/compose-go/NOTICE rename to vendor/github.com/compose-spec/compose-go/v2/NOTICE diff --git a/vendor/github.com/compose-spec/compose-go/cli/options.go b/vendor/github.com/compose-spec/compose-go/v2/cli/options.go similarity index 72% rename from vendor/github.com/compose-spec/compose-go/cli/options.go rename to vendor/github.com/compose-spec/compose-go/v2/cli/options.go index 6981ebc10..c4b2f57ad 100644 --- a/vendor/github.com/compose-spec/compose-go/cli/options.go +++ b/vendor/github.com/compose-spec/compose-go/v2/cli/options.go @@ -18,26 +18,25 @@ package cli import ( "context" + "fmt" "io" "os" "path/filepath" + "strconv" "strings" - "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/compose-spec/compose-go/consts" - "github.com/compose-spec/compose-go/dotenv" - "github.com/compose-spec/compose-go/errdefs" - "github.com/compose-spec/compose-go/loader" - "github.com/compose-spec/compose-go/types" - "github.com/compose-spec/compose-go/utils" + "github.com/compose-spec/compose-go/v2/consts" + "github.com/compose-spec/compose-go/v2/dotenv" + "github.com/compose-spec/compose-go/v2/errdefs" + "github.com/compose-spec/compose-go/v2/loader" + "github.com/compose-spec/compose-go/v2/types" + "github.com/compose-spec/compose-go/v2/utils" ) // ProjectOptions provides common configuration for loading a project. type ProjectOptions struct { - ctx context.Context - // Name is a valid Compose project name to be used or empty. // // If empty, the project loader will automatically infer a reasonable @@ -52,7 +51,7 @@ type ProjectOptions struct { // ConfigPaths are file paths to one or more Compose files. // - // These are applied in order by the loader following the merge logic + // These are applied in order by the loader following the override logic // as described in the spec. // // The first entry is required and is the primary Compose file. @@ -79,6 +78,10 @@ type ProjectOptions struct { EnvFiles []string loadOptions []func(*loader.Options) + + // Callbacks to retrieve metadata information during parse defined before + // creating the project + Listeners []loader.Listener } type ProjectOptionsFn func(*ProjectOptions) error @@ -88,6 +91,7 @@ func NewProjectOptions(configs []string, opts ...ProjectOptionsFn) (*ProjectOpti options := &ProjectOptions{ ConfigPaths: configs, Environment: map[string]string{}, + Listeners: []loader.Listener{}, } for _, o := range opts { err := o(options) @@ -211,11 +215,16 @@ func WithLoadOptions(loadOptions ...func(*loader.Options)) ProjectOptionsFn { // WithDefaultProfiles uses the provided profiles (if any), and falls back to // profiles specified via the COMPOSE_PROFILES environment variable otherwise. -func WithDefaultProfiles(profile ...string) ProjectOptionsFn { - if len(profile) == 0 { - profile = strings.Split(os.Getenv(consts.ComposeProfiles), ",") +func WithDefaultProfiles(profiles ...string) ProjectOptionsFn { + return func(o *ProjectOptions) error { + if len(profiles) == 0 { + for _, s := range strings.Split(o.Environment[consts.ComposeProfiles], ",") { + profiles = append(profiles, strings.TrimSpace(s)) + } + } + o.loadOptions = append(o.loadOptions, loader.WithProfiles(profiles)) + return nil } - return WithProfiles(profile) } // WithProfiles sets profiles to be activated @@ -248,21 +257,47 @@ func WithEnvFile(file string) ProjectOptionsFn { return WithEnvFiles(files...) } -// WithEnvFiles set alternate env files +// WithEnvFiles set env file(s) to be loaded to set project environment. +// defaults to local .env file if no explicit file is selected, until COMPOSE_DISABLE_ENV_FILE is set func WithEnvFiles(file ...string) ProjectOptionsFn { - return func(options *ProjectOptions) error { - options.EnvFiles = file + return func(o *ProjectOptions) error { + if len(file) > 0 { + o.EnvFiles = file + return nil + } + if v, ok := os.LookupEnv(consts.ComposeDisableDefaultEnvFile); ok { + b, err := strconv.ParseBool(v) + if err != nil { + return err + } + if b { + return nil + } + } + + wd, err := o.GetWorkingDir() + if err != nil { + return err + } + defaultDotEnv := filepath.Join(wd, ".env") + + s, err := os.Stat(defaultDotEnv) + if os.IsNotExist(err) { + return nil + } + if err != nil { + return err + } + if !s.IsDir() { + o.EnvFiles = []string{defaultDotEnv} + } return nil } } // WithDotEnv imports environment variables from .env file func WithDotEnv(o *ProjectOptions) error { - wd, err := o.GetWorkingDir() - if err != nil { - return err - } - envMap, err := dotenv.GetEnvFromFile(o.Environment, wd, o.EnvFiles) + envMap, err := dotenv.GetEnvFromFile(o.Environment, o.EnvFiles) if err != nil { return err } @@ -310,24 +345,34 @@ func WithResolvedPaths(resolve bool) ProjectOptionsFn { } } -// WithContext sets the context used to load model and resources -func WithContext(ctx context.Context) ProjectOptionsFn { +// WithResourceLoader register support for ResourceLoader to manage remote resources +func WithResourceLoader(r loader.ResourceLoader) ProjectOptionsFn { return func(o *ProjectOptions) error { - o.ctx = ctx + o.loadOptions = append(o.loadOptions, func(options *loader.Options) { + options.ResourceLoaders = append(options.ResourceLoaders, r) + }) return nil } } -// WithResourceLoader register support for ResourceLoader to manage remote resources -func WithResourceLoader(r loader.ResourceLoader) ProjectOptionsFn { +// WithExtension register a know extension `x-*` with the go struct type to decode into +func WithExtension(name string, typ any) ProjectOptionsFn { return func(o *ProjectOptions) error { o.loadOptions = append(o.loadOptions, func(options *loader.Options) { - options.ResourceLoaders = append(options.ResourceLoaders, r) + if options.KnownExtensions == nil { + options.KnownExtensions = map[string]any{} + } + options.KnownExtensions[name] = typ }) return nil } } +// Append listener to event +func (o *ProjectOptions) WithListeners(listeners ...loader.Listener) { + o.Listeners = append(o.Listeners, listeners...) +} + // WithoutEnvironmentResolution disable environment resolution func WithoutEnvironmentResolution(o *ProjectOptions) error { o.loadOptions = append(o.loadOptions, func(options *loader.Options) { @@ -342,9 +387,9 @@ var DefaultFileNames = []string{"compose.yaml", "compose.yml", "docker-compose.y // DefaultOverrideFileNames defines the Compose override file names for auto-discovery (in order of preference) var DefaultOverrideFileNames = []string{"compose.override.yml", "compose.override.yaml", "docker-compose.override.yml", "docker-compose.override.yaml"} -func (o ProjectOptions) GetWorkingDir() (string, error) { +func (o *ProjectOptions) GetWorkingDir() (string, error) { if o.WorkingDir != "" { - return o.WorkingDir, nil + return filepath.Abs(o.WorkingDir) } for _, path := range o.ConfigPaths { if path != "-" { @@ -358,9 +403,8 @@ func (o ProjectOptions) GetWorkingDir() (string, error) { return os.Getwd() } -// ProjectFromOptions load a compose project based on command line options -func ProjectFromOptions(options *ProjectOptions) (*types.Project, error) { - configPaths, err := getConfigPathsFromOptions(options) +func (o *ProjectOptions) GetConfigFiles() ([]types.ConfigFile, error) { + configPaths, err := o.getConfigPaths() if err != nil { return nil, err } @@ -388,36 +432,67 @@ func ProjectFromOptions(options *ProjectOptions) (*types.Project, error) { Content: b, }) } + return configs, err +} - workingDir, err := options.GetWorkingDir() +// LoadProject loads compose file according to options and bind to types.Project go structs +func (o *ProjectOptions) LoadProject(ctx context.Context) (*types.Project, error) { + configDetails, err := o.prepare() if err != nil { return nil, err } - absWorkingDir, err := filepath.Abs(workingDir) + + project, err := loader.LoadWithContext(ctx, configDetails, o.loadOptions...) if err != nil { return nil, err } - options.loadOptions = append(options.loadOptions, - withNamePrecedenceLoad(absWorkingDir, options), - withConvertWindowsPaths(options)) + for _, config := range configDetails.ConfigFiles { + project.ComposeFiles = append(project.ComposeFiles, config.Filename) + } + + return project, nil +} - ctx := options.ctx - if ctx == nil { - ctx = context.Background() +// LoadModel loads compose file according to options and returns a raw (yaml tree) model +func (o *ProjectOptions) LoadModel(ctx context.Context) (map[string]any, error) { + configDetails, err := o.prepare() + if err != nil { + return nil, err } - project, err := loader.LoadWithContext(ctx, types.ConfigDetails{ + return loader.LoadModelWithContext(ctx, configDetails, o.loadOptions...) +} + +// prepare converts ProjectOptions into loader's types.ConfigDetails and configures default load options +func (o *ProjectOptions) prepare() (types.ConfigDetails, error) { + configs, err := o.GetConfigFiles() + if err != nil { + return types.ConfigDetails{}, err + } + + workingDir, err := o.GetWorkingDir() + if err != nil { + return types.ConfigDetails{}, err + } + + configDetails := types.ConfigDetails{ ConfigFiles: configs, WorkingDir: workingDir, - Environment: options.Environment, - }, options.loadOptions...) - if err != nil { - return nil, err + Environment: o.Environment, } - project.ComposeFiles = configPaths - return project, nil + o.loadOptions = append(o.loadOptions, + withNamePrecedenceLoad(workingDir, o), + withConvertWindowsPaths(o), + withListeners(o)) + return configDetails, nil +} + +// ProjectFromOptions load a compose project based on command line options +// Deprecated: use ProjectOptions.LoadProject or ProjectOptions.LoadModel +func ProjectFromOptions(ctx context.Context, options *ProjectOptions) (*types.Project, error) { + return options.LoadProject(ctx) } func withNamePrecedenceLoad(absWorkingDir string, options *ProjectOptions) func(*loader.Options) { @@ -443,12 +518,19 @@ func withConvertWindowsPaths(options *ProjectOptions) func(*loader.Options) { } } -// getConfigPathsFromOptions retrieves the config files for project based on project options -func getConfigPathsFromOptions(options *ProjectOptions) ([]string, error) { - if len(options.ConfigPaths) != 0 { - return absolutePaths(options.ConfigPaths) +// save listeners from ProjectOptions (compose) to loader.Options +func withListeners(options *ProjectOptions) func(*loader.Options) { + return func(opts *loader.Options) { + opts.Listeners = append(opts.Listeners, options.Listeners...) + } +} + +// getConfigPaths retrieves the config files for project based on project options +func (o *ProjectOptions) getConfigPaths() ([]string, error) { + if len(o.ConfigPaths) != 0 { + return absolutePaths(o.ConfigPaths) } - return nil, errors.Wrap(errdefs.ErrNotFound, "no configuration file provided") + return nil, fmt.Errorf("no configuration file provided: %w", errdefs.ErrNotFound) } func findFiles(names []string, pwd string) []string { diff --git a/vendor/github.com/compose-spec/compose-go/v2/consts/consts.go b/vendor/github.com/compose-spec/compose-go/v2/consts/consts.go new file mode 100644 index 000000000..592e6f067 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/consts/consts.go @@ -0,0 +1,29 @@ +/* + Copyright 2020 The Compose Specification 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 consts + +const ( + ComposeProjectName = "COMPOSE_PROJECT_NAME" + ComposePathSeparator = "COMPOSE_PATH_SEPARATOR" + ComposeFilePath = "COMPOSE_FILE" + ComposeDisableDefaultEnvFile = "COMPOSE_DISABLE_ENV_FILE" + ComposeProfiles = "COMPOSE_PROFILES" +) + +const Extensions = "#extensions" // Using # prefix, we prevent risk to conflict with an actual yaml key + +type ComposeFileKey struct{} diff --git a/vendor/github.com/compose-spec/compose-go/dotenv/LICENSE b/vendor/github.com/compose-spec/compose-go/v2/dotenv/LICENSE similarity index 100% rename from vendor/github.com/compose-spec/compose-go/dotenv/LICENSE rename to vendor/github.com/compose-spec/compose-go/v2/dotenv/LICENSE diff --git a/vendor/github.com/compose-spec/compose-go/dotenv/env.go b/vendor/github.com/compose-spec/compose-go/v2/dotenv/env.go similarity index 68% rename from vendor/github.com/compose-spec/compose-go/dotenv/env.go rename to vendor/github.com/compose-spec/compose-go/v2/dotenv/env.go index c8a538bcb..cc472a60a 100644 --- a/vendor/github.com/compose-spec/compose-go/dotenv/env.go +++ b/vendor/github.com/compose-spec/compose-go/v2/dotenv/env.go @@ -18,20 +18,15 @@ package dotenv import ( "bytes" + "fmt" "os" "path/filepath" - - "github.com/pkg/errors" ) -func GetEnvFromFile(currentEnv map[string]string, workingDir string, filenames []string) (map[string]string, error) { +func GetEnvFromFile(currentEnv map[string]string, filenames []string) (map[string]string, error) { envMap := make(map[string]string) - dotEnvFiles := filenames - if len(dotEnvFiles) == 0 { - dotEnvFiles = append(dotEnvFiles, filepath.Join(workingDir, ".env")) - } - for _, dotEnvFile := range dotEnvFiles { + for _, dotEnvFile := range filenames { abs, err := filepath.Abs(dotEnvFile) if err != nil { return envMap, err @@ -40,10 +35,7 @@ func GetEnvFromFile(currentEnv map[string]string, workingDir string, filenames [ s, err := os.Stat(dotEnvFile) if os.IsNotExist(err) { - if len(filenames) == 0 { - return envMap, nil - } - return envMap, errors.Errorf("Couldn't find env file: %s", dotEnvFile) + return envMap, fmt.Errorf("Couldn't find env file: %s", dotEnvFile) } if err != nil { return envMap, err @@ -53,12 +45,12 @@ func GetEnvFromFile(currentEnv map[string]string, workingDir string, filenames [ if len(filenames) == 0 { return envMap, nil } - return envMap, errors.Errorf("%s is a directory", dotEnvFile) + return envMap, fmt.Errorf("%s is a directory", dotEnvFile) } b, err := os.ReadFile(dotEnvFile) if os.IsNotExist(err) { - return nil, errors.Errorf("Couldn't read env file: %s", dotEnvFile) + return nil, fmt.Errorf("Couldn't read env file: %s", dotEnvFile) } if err != nil { return envMap, err @@ -73,7 +65,7 @@ func GetEnvFromFile(currentEnv map[string]string, workingDir string, filenames [ return v, ok }) if err != nil { - return envMap, errors.Wrapf(err, "failed to read %s", dotEnvFile) + return envMap, fmt.Errorf("failed to read %s: %w", dotEnvFile, err) } for k, v := range env { envMap[k] = v diff --git a/vendor/github.com/compose-spec/compose-go/dotenv/godotenv.go b/vendor/github.com/compose-spec/compose-go/v2/dotenv/godotenv.go similarity index 98% rename from vendor/github.com/compose-spec/compose-go/dotenv/godotenv.go rename to vendor/github.com/compose-spec/compose-go/v2/dotenv/godotenv.go index 9b95c990e..e6635ce33 100644 --- a/vendor/github.com/compose-spec/compose-go/dotenv/godotenv.go +++ b/vendor/github.com/compose-spec/compose-go/v2/dotenv/godotenv.go @@ -20,7 +20,7 @@ import ( "regexp" "strings" - "github.com/compose-spec/compose-go/template" + "github.com/compose-spec/compose-go/v2/template" ) var utf8BOM = []byte("\uFEFF") diff --git a/vendor/github.com/compose-spec/compose-go/dotenv/parser.go b/vendor/github.com/compose-spec/compose-go/v2/dotenv/parser.go similarity index 95% rename from vendor/github.com/compose-spec/compose-go/dotenv/parser.go rename to vendor/github.com/compose-spec/compose-go/v2/dotenv/parser.go index 11b6d027c..d7b4a6463 100644 --- a/vendor/github.com/compose-spec/compose-go/dotenv/parser.go +++ b/vendor/github.com/compose-spec/compose-go/v2/dotenv/parser.go @@ -119,7 +119,7 @@ loop: offset = i + 1 inherited = rune == '\n' break loop - case '_', '.', '-', '[', ']': + case '_', '.', '[', ']': default: // variable name should match [A-Za-z0-9_.-] if unicode.IsLetter(rune) || unicode.IsNumber(rune) { @@ -159,27 +159,34 @@ func (p *parser) extractVarValue(src string, envMap map[string]string, lookupFn previousCharIsEscape := false // lookup quoted string terminator + var chars []byte for i := 1; i < len(src); i++ { - if src[i] == '\n' { + char := src[i] + if char == '\n' { p.line++ } - if char := src[i]; char != quote { + if char != quote { if !previousCharIsEscape && char == '\\' { previousCharIsEscape = true - } else { + continue + } + if previousCharIsEscape { previousCharIsEscape = false + chars = append(chars, '\\') } + chars = append(chars, char) continue } // skip escaped quote symbol (\" or \', depends on quote) if previousCharIsEscape { previousCharIsEscape = false + chars = append(chars, char) continue } // trim quotes - value := string(src[1:i]) + value := string(chars) if quote == prefixDoubleQuote { // expand standard shell escape sequences & then interpolate // variables on the result diff --git a/vendor/github.com/compose-spec/compose-go/errdefs/errors.go b/vendor/github.com/compose-spec/compose-go/v2/errdefs/errors.go similarity index 93% rename from vendor/github.com/compose-spec/compose-go/errdefs/errors.go rename to vendor/github.com/compose-spec/compose-go/v2/errdefs/errors.go index a54407007..1990ddd25 100644 --- a/vendor/github.com/compose-spec/compose-go/errdefs/errors.go +++ b/vendor/github.com/compose-spec/compose-go/v2/errdefs/errors.go @@ -30,6 +30,9 @@ var ( // ErrIncompatible is returned when a compose project uses an incompatible attribute ErrIncompatible = errors.New("incompatible attribute") + + // ErrDisabled is returned when a resource was found in model but is disabled + ErrDisabled = errors.New("disabled") ) // IsNotFoundError returns true if the unwrapped error is ErrNotFound diff --git a/vendor/github.com/compose-spec/compose-go/loader/volume.go b/vendor/github.com/compose-spec/compose-go/v2/format/volume.go similarity index 96% rename from vendor/github.com/compose-spec/compose-go/loader/volume.go rename to vendor/github.com/compose-spec/compose-go/v2/format/volume.go index dd83414ac..0083a62cb 100644 --- a/vendor/github.com/compose-spec/compose-go/loader/volume.go +++ b/vendor/github.com/compose-spec/compose-go/v2/format/volume.go @@ -14,15 +14,16 @@ limitations under the License. */ -package loader +package format import ( + "errors" + "fmt" "strings" "unicode" "unicode/utf8" - "github.com/compose-spec/compose-go/types" - "github.com/pkg/errors" + "github.com/compose-spec/compose-go/v2/types" ) const endOfSpec = rune(0) @@ -48,7 +49,7 @@ func ParseVolume(spec string) (types.ServiceVolumeConfig, error) { case char == ':' || char == endOfSpec: if err := populateFieldFromBuffer(char, buffer, &volume); err != nil { populateType(&volume) - return volume, errors.Wrapf(err, "invalid spec: %s", spec) + return volume, fmt.Errorf("invalid spec: %s: %w", spec, err) } buffer = nil default: diff --git a/vendor/github.com/compose-spec/compose-go/v2/graph/cycle.go b/vendor/github.com/compose-spec/compose-go/v2/graph/cycle.go new file mode 100644 index 000000000..60c37cefc --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/graph/cycle.go @@ -0,0 +1,63 @@ +/* + Copyright 2020 The Compose Specification 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 graph + +import ( + "fmt" + "strings" + + "github.com/compose-spec/compose-go/v2/types" + "github.com/compose-spec/compose-go/v2/utils" + "golang.org/x/exp/slices" +) + +// CheckCycle analyze project's depends_on relation and report an error on cycle detection +func CheckCycle(project *types.Project) error { + g, err := newGraph(project) + if err != nil { + return err + } + return g.checkCycle() +} + +func (g *graph[T]) checkCycle() error { + // iterate on vertices in a name-order to render a predicable error message + // this is required by tests and enforce command reproducibility by user, which otherwise could be confusing + names := utils.MapKeys(g.vertices) + for _, name := range names { + err := searchCycle([]string{name}, g.vertices[name]) + if err != nil { + return err + } + } + return nil +} + +func searchCycle[T any](path []string, v *vertex[T]) error { + names := utils.MapKeys(v.children) + for _, name := range names { + if i := slices.Index(path, name); i >= 0 { + return fmt.Errorf("dependency cycle detected: %s -> %s", strings.Join(path[i:], " -> "), name) + } + ch := v.children[name] + err := searchCycle(append(path, name), ch) + if err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/graph/graph.go b/vendor/github.com/compose-spec/compose-go/v2/graph/graph.go new file mode 100644 index 000000000..de4e9e107 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/graph/graph.go @@ -0,0 +1,75 @@ +/* + Copyright 2020 The Compose Specification 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 graph + +// graph represents project as service dependencies +type graph[T any] struct { + vertices map[string]*vertex[T] +} + +// vertex represents a service in the dependencies structure +type vertex[T any] struct { + key string + service *T + children map[string]*vertex[T] + parents map[string]*vertex[T] +} + +func (g *graph[T]) addVertex(name string, service T) { + g.vertices[name] = &vertex[T]{ + key: name, + service: &service, + parents: map[string]*vertex[T]{}, + children: map[string]*vertex[T]{}, + } +} + +func (g *graph[T]) addEdge(src, dest string) { + g.vertices[src].children[dest] = g.vertices[dest] + g.vertices[dest].parents[src] = g.vertices[src] +} + +func (g *graph[T]) roots() []*vertex[T] { + var res []*vertex[T] + for _, v := range g.vertices { + if len(v.parents) == 0 { + res = append(res, v) + } + } + return res +} + +func (g *graph[T]) leaves() []*vertex[T] { + var res []*vertex[T] + for _, v := range g.vertices { + if len(v.children) == 0 { + res = append(res, v) + } + } + + return res +} + +// descendents return all descendents for a vertex, might contain duplicates +func (v *vertex[T]) descendents() []string { + var vx []string + for _, n := range v.children { + vx = append(vx, n.key) + vx = append(vx, n.descendents()...) + } + return vx +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/graph/services.go b/vendor/github.com/compose-spec/compose-go/v2/graph/services.go new file mode 100644 index 000000000..44b36a3f3 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/graph/services.go @@ -0,0 +1,80 @@ +/* + Copyright 2020 The Compose Specification 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 graph + +import ( + "context" + "fmt" + + "github.com/compose-spec/compose-go/v2/types" +) + +// InDependencyOrder walk the service graph an invoke VisitorFn in respect to dependency order +func InDependencyOrder(ctx context.Context, project *types.Project, fn VisitorFn[types.ServiceConfig], options ...func(*Options)) error { + _, err := CollectInDependencyOrder[any](ctx, project, func(ctx context.Context, s string, config types.ServiceConfig) (any, error) { + return nil, fn(ctx, s, config) + }, options...) + return err +} + +// CollectInDependencyOrder walk the service graph an invoke CollectorFn in respect to dependency order, then return result for each call +func CollectInDependencyOrder[T any](ctx context.Context, project *types.Project, fn CollectorFn[types.ServiceConfig, T], options ...func(*Options)) (map[string]T, error) { + graph, err := newGraph(project) + if err != nil { + return nil, err + } + t := newTraversal(fn) + for _, option := range options { + option(t.Options) + } + err = walk(ctx, graph, t) + return t.results, err +} + +// newGraph creates a service graph from project +func newGraph(project *types.Project) (*graph[types.ServiceConfig], error) { + g := &graph[types.ServiceConfig]{ + vertices: map[string]*vertex[types.ServiceConfig]{}, + } + + for name, s := range project.Services { + g.addVertex(name, s) + } + + for name, s := range project.Services { + src := g.vertices[name] + for dep, condition := range s.DependsOn { + dest, ok := g.vertices[dep] + if !ok { + if condition.Required { + if ds, exists := project.DisabledServices[dep]; exists { + return nil, fmt.Errorf("service %q is required by %q but is disabled. Can be enabled by profiles %s", dep, name, ds.Profiles) + } + return nil, fmt.Errorf("service %q depends on unknown service %q", name, dep) + } + delete(s.DependsOn, name) + project.Services[name] = s + continue + } + src.children[dep] = dest + dest.parents[name] = src + } + } + + err := g.checkCycle() + return g, err +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/graph/traversal.go b/vendor/github.com/compose-spec/compose-go/v2/graph/traversal.go new file mode 100644 index 000000000..de85d1fce --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/graph/traversal.go @@ -0,0 +1,211 @@ +/* + Copyright 2020 The Compose Specification 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 graph + +import ( + "context" + "sync" + + "golang.org/x/exp/slices" + "golang.org/x/sync/errgroup" +) + +// CollectorFn executes on each graph vertex based on visit order and return associated value +type CollectorFn[S any, T any] func(context.Context, string, S) (T, error) + +// VisitorFn executes on each graph nodes based on visit order +type VisitorFn[S any] func(context.Context, string, S) error + +type traversal[S any, T any] struct { + *Options + visitor CollectorFn[S, T] + + mu sync.Mutex + status map[string]int + results map[string]T +} + +type Options struct { + // inverse reverse the traversal direction + inverse bool + // maxConcurrency limit the concurrent execution of visitorFn while walking the graph + maxConcurrency int + // after marks a set of node as starting points walking the graph + after []string +} + +const ( + vertexEntered = iota + vertexVisited +) + +func newTraversal[S, T any](fn CollectorFn[S, T]) *traversal[S, T] { + return &traversal[S, T]{ + Options: &Options{}, + status: map[string]int{}, + results: map[string]T{}, + visitor: fn, + } +} + +// WithMaxConcurrency configure traversal to limit concurrency walking graph nodes +func WithMaxConcurrency(max int) func(*Options) { + return func(o *Options) { + o.maxConcurrency = max + } +} + +// InReverseOrder configure traversal to walk the graph in reverse dependency order +func InReverseOrder(o *Options) { + o.inverse = true +} + +// WithRootNodesAndDown creates a graphTraversal to start from selected nodes +func WithRootNodesAndDown(nodes []string) func(*Options) { + return func(o *Options) { + o.after = nodes + } +} + +func walk[S, T any](ctx context.Context, g *graph[S], t *traversal[S, T]) error { + expect := len(g.vertices) + if expect == 0 { + return nil + } + // nodeCh need to allow n=expect writers while reader goroutine could have returned after ctx.Done + nodeCh := make(chan *vertex[S], expect) + defer close(nodeCh) + + eg, ctx := errgroup.WithContext(ctx) + if t.maxConcurrency > 0 { + eg.SetLimit(t.maxConcurrency + 1) + } + + eg.Go(func() error { + for { + select { + case <-ctx.Done(): + return nil + case node := <-nodeCh: + expect-- + if expect == 0 { + return nil + } + + for _, adj := range t.adjacentNodes(node) { + t.visit(ctx, eg, adj, nodeCh) + } + } + } + }) + + // select nodes to start walking the graph based on traversal.direction + for _, node := range t.extremityNodes(g) { + t.visit(ctx, eg, node, nodeCh) + } + + return eg.Wait() +} + +func (t *traversal[S, T]) visit(ctx context.Context, eg *errgroup.Group, node *vertex[S], nodeCh chan *vertex[S]) { + if !t.ready(node) { + // don't visit this service yet as dependencies haven't been visited + return + } + if !t.enter(node) { + // another worker already acquired this node + return + } + eg.Go(func() error { + var ( + err error + result T + ) + if !t.skip(node) { + result, err = t.visitor(ctx, node.key, *node.service) + } + t.done(node, result) + nodeCh <- node + return err + }) +} + +func (t *traversal[S, T]) extremityNodes(g *graph[S]) []*vertex[S] { + if t.inverse { + return g.roots() + } + return g.leaves() +} + +func (t *traversal[S, T]) adjacentNodes(v *vertex[S]) map[string]*vertex[S] { + if t.inverse { + return v.children + } + return v.parents +} + +func (t *traversal[S, T]) ready(v *vertex[S]) bool { + t.mu.Lock() + defer t.mu.Unlock() + + depends := v.children + if t.inverse { + depends = v.parents + } + for name := range depends { + if t.status[name] != vertexVisited { + return false + } + } + return true +} + +func (t *traversal[S, T]) enter(v *vertex[S]) bool { + t.mu.Lock() + defer t.mu.Unlock() + + if _, ok := t.status[v.key]; ok { + return false + } + t.status[v.key] = vertexEntered + return true +} + +func (t *traversal[S, T]) done(v *vertex[S], result T) { + t.mu.Lock() + defer t.mu.Unlock() + t.status[v.key] = vertexVisited + t.results[v.key] = result +} + +func (t *traversal[S, T]) skip(node *vertex[S]) bool { + if len(t.after) == 0 { + return false + } + if slices.Contains(t.after, node.key) { + return false + } + + // is none of our starting node is a descendent, skip visit + ancestors := node.descendents() + for _, name := range t.after { + if slices.Contains(ancestors, name) { + return false + } + } + return true +} diff --git a/vendor/github.com/compose-spec/compose-go/interpolation/interpolation.go b/vendor/github.com/compose-spec/compose-go/v2/interpolation/interpolation.go similarity index 88% rename from vendor/github.com/compose-spec/compose-go/interpolation/interpolation.go rename to vendor/github.com/compose-spec/compose-go/v2/interpolation/interpolation.go index 305730838..b56e0afeb 100644 --- a/vendor/github.com/compose-spec/compose-go/interpolation/interpolation.go +++ b/vendor/github.com/compose-spec/compose-go/v2/interpolation/interpolation.go @@ -17,11 +17,12 @@ package interpolation import ( + "errors" + "fmt" "os" - "github.com/compose-spec/compose-go/template" - "github.com/compose-spec/compose-go/tree" - "github.com/pkg/errors" + "github.com/compose-spec/compose-go/v2/template" + "github.com/compose-spec/compose-go/v2/tree" ) // Options supported by Interpolate @@ -80,7 +81,10 @@ func recursiveInterpolate(value interface{}, path tree.Path, opts Options) (inte return newValue, nil } casted, err := caster(newValue) - return casted, newPathError(path, errors.Wrap(err, "failed to cast to expected type")) + if err != nil { + return casted, newPathError(path, fmt.Errorf("failed to cast to expected type: %w", err)) + } + return casted, nil case map[string]interface{}: out := map[string]interface{}{} @@ -110,15 +114,16 @@ func recursiveInterpolate(value interface{}, path tree.Path, opts Options) (inte } func newPathError(path tree.Path, err error) error { - switch err := err.(type) { - case nil: + var ite *template.InvalidTemplateError + switch { + case err == nil: return nil - case *template.InvalidTemplateError: - return errors.Errorf( + case errors.As(err, &ite): + return fmt.Errorf( "invalid interpolation format for %s.\nYou may need to escape any $ with another $.\n%s", - path, err.Template) + path, ite.Template) default: - return errors.Wrapf(err, "error while interpolating %s", path) + return fmt.Errorf("error while interpolating %s: %w", path, err) } } diff --git a/vendor/github.com/compose-spec/compose-go/v2/loader/environment.go b/vendor/github.com/compose-spec/compose-go/v2/loader/environment.go new file mode 100644 index 000000000..3f7277b8a --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/loader/environment.go @@ -0,0 +1,110 @@ +/* + Copyright 2020 The Compose Specification 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 loader + +import ( + "fmt" + + "github.com/compose-spec/compose-go/v2/types" +) + +// ResolveEnvironment update the environment variables for the format {- VAR} (without interpolation) +func ResolveEnvironment(dict map[string]any, environment types.Mapping) { + resolveServicesEnvironment(dict, environment) + resolveSecretsEnvironment(dict, environment) + resolveConfigsEnvironment(dict, environment) +} + +func resolveServicesEnvironment(dict map[string]any, environment types.Mapping) { + services, ok := dict["services"].(map[string]any) + if !ok { + return + } + + for service, cfg := range services { + serviceConfig, ok := cfg.(map[string]any) + if !ok { + continue + } + serviceEnv, ok := serviceConfig["environment"].([]any) + if !ok { + continue + } + envs := []any{} + for _, env := range serviceEnv { + varEnv, ok := env.(string) + if !ok { + continue + } + if found, ok := environment[varEnv]; ok { + envs = append(envs, fmt.Sprintf("%s=%s", varEnv, found)) + } else { + // either does not exist or it was already resolved in interpolation + envs = append(envs, varEnv) + } + } + serviceConfig["environment"] = envs + services[service] = serviceConfig + } + dict["services"] = services +} + +func resolveSecretsEnvironment(dict map[string]any, environment types.Mapping) { + secrets, ok := dict["secrets"].(map[string]any) + if !ok { + return + } + + for name, cfg := range secrets { + secret, ok := cfg.(map[string]any) + if !ok { + continue + } + env, ok := secret["environment"].(string) + if !ok { + continue + } + if found, ok := environment[env]; ok { + secret[types.SecretConfigXValue] = found + } + secrets[name] = secret + } + dict["secrets"] = secrets +} + +func resolveConfigsEnvironment(dict map[string]any, environment types.Mapping) { + configs, ok := dict["configs"].(map[string]any) + if !ok { + return + } + + for name, cfg := range configs { + config, ok := cfg.(map[string]any) + if !ok { + continue + } + env, ok := config["environment"].(string) + if !ok { + continue + } + if found, ok := environment[env]; ok { + config["content"] = found + } + configs[name] = config + } + dict["configs"] = configs +} diff --git a/vendor/github.com/compose-spec/compose-go/loader/example1.env b/vendor/github.com/compose-spec/compose-go/v2/loader/example1.env similarity index 100% rename from vendor/github.com/compose-spec/compose-go/loader/example1.env rename to vendor/github.com/compose-spec/compose-go/v2/loader/example1.env diff --git a/vendor/github.com/compose-spec/compose-go/loader/example2.env b/vendor/github.com/compose-spec/compose-go/v2/loader/example2.env similarity index 100% rename from vendor/github.com/compose-spec/compose-go/loader/example2.env rename to vendor/github.com/compose-spec/compose-go/v2/loader/example2.env diff --git a/vendor/github.com/compose-spec/compose-go/v2/loader/extends.go b/vendor/github.com/compose-spec/compose-go/v2/loader/extends.go new file mode 100644 index 000000000..c48b1e4b9 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/loader/extends.go @@ -0,0 +1,216 @@ +/* + Copyright 2020 The Compose Specification 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 loader + +import ( + "context" + "fmt" + "path/filepath" + + "github.com/compose-spec/compose-go/v2/consts" + "github.com/compose-spec/compose-go/v2/override" + "github.com/compose-spec/compose-go/v2/paths" + "github.com/compose-spec/compose-go/v2/types" +) + +func ApplyExtends(ctx context.Context, dict map[string]any, opts *Options, tracker *cycleTracker, post ...PostProcessor) error { + a, ok := dict["services"] + if !ok { + return nil + } + services, ok := a.(map[string]any) + if !ok { + return fmt.Errorf("services must be a mapping") + } + for name := range services { + merged, err := applyServiceExtends(ctx, name, services, opts, tracker, post...) + if err != nil { + return err + } + services[name] = merged + } + dict["services"] = services + return nil +} + +func applyServiceExtends(ctx context.Context, name string, services map[string]any, opts *Options, tracker *cycleTracker, post ...PostProcessor) (any, error) { + s := services[name] + if s == nil { + return nil, nil + } + service, ok := s.(map[string]any) + if !ok { + return nil, fmt.Errorf("services.%s must be a mapping", name) + } + extends, ok := service["extends"] + if !ok { + return s, nil + } + filename := ctx.Value(consts.ComposeFileKey{}).(string) + var ( + err error + ref string + file any + ) + switch v := extends.(type) { + case map[string]any: + ref = v["service"].(string) + file = v["file"] + opts.ProcessEvent("extends", v) + case string: + ref = v + opts.ProcessEvent("extends", map[string]any{"service": ref}) + } + + var ( + base any + processor PostProcessor + ) + + if file != nil { + refFilename := file.(string) + services, processor, err = getExtendsBaseFromFile(ctx, name, ref, filename, refFilename, opts, tracker) + post = append(post, processor) + if err != nil { + return nil, err + } + filename = refFilename + } else { + _, ok := services[ref] + if !ok { + return nil, fmt.Errorf("cannot extend service %q in %s: service %q not found", name, filename, ref) + } + } + + tracker, err = tracker.Add(filename, name) + if err != nil { + return nil, err + } + + // recursively apply `extends` + base, err = applyServiceExtends(ctx, ref, services, opts, tracker, post...) + if err != nil { + return nil, err + } + + if base == nil { + return service, nil + } + source := deepClone(base).(map[string]any) + + for _, processor := range post { + processor.Apply(map[string]any{ + "services": map[string]any{ + name: source, + }, + }) + } + merged, err := override.ExtendService(source, service) + if err != nil { + return nil, err + } + delete(merged, "extends") + services[name] = merged + return merged, nil +} + +func getExtendsBaseFromFile( + ctx context.Context, + name, ref string, + path, refPath string, + opts *Options, + ct *cycleTracker, +) (map[string]any, PostProcessor, error) { + for _, loader := range opts.ResourceLoaders { + if !loader.Accept(refPath) { + continue + } + local, err := loader.Load(ctx, refPath) + if err != nil { + return nil, nil, err + } + localdir := filepath.Dir(local) + relworkingdir := loader.Dir(refPath) + + extendsOpts := opts.clone() + // replace localResourceLoader with a new flavour, using extended file base path + extendsOpts.ResourceLoaders = append(opts.RemoteResourceLoaders(), localResourceLoader{ + WorkingDir: localdir, + }) + extendsOpts.ResolvePaths = false // we do relative path resolution after file has been loaded + extendsOpts.SkipNormalization = true + extendsOpts.SkipConsistencyCheck = true + extendsOpts.SkipInclude = true + extendsOpts.SkipExtends = true // we manage extends recursively based on raw service definition + extendsOpts.SkipValidation = true // we validate the merge result + extendsOpts.SkipDefaultValues = true + source, processor, err := loadYamlFile(ctx, types.ConfigFile{Filename: local}, + extendsOpts, relworkingdir, nil, ct, map[string]any{}, nil) + if err != nil { + return nil, nil, err + } + m, ok := source["services"] + if !ok { + return nil, nil, fmt.Errorf("cannot extend service %q in %s: no services section", name, local) + } + services, ok := m.(map[string]any) + if !ok { + return nil, nil, fmt.Errorf("cannot extend service %q in %s: services must be a mapping", name, local) + } + _, ok = services[ref] + if !ok { + return nil, nil, fmt.Errorf( + "cannot extend service %q in %s: service %q not found in %s", + name, + path, + ref, + refPath, + ) + } + + var remotes []paths.RemoteResource + for _, loader := range opts.RemoteResourceLoaders() { + remotes = append(remotes, loader.Accept) + } + err = paths.ResolveRelativePaths(source, relworkingdir, remotes) + if err != nil { + return nil, nil, err + } + + return services, processor, nil + } + return nil, nil, fmt.Errorf("cannot read %s", refPath) +} + +func deepClone(value any) any { + switch v := value.(type) { + case []any: + cp := make([]any, len(v)) + for i, e := range v { + cp[i] = deepClone(e) + } + return cp + case map[string]any: + cp := make(map[string]any, len(v)) + for k, e := range v { + cp[k] = deepClone(e) + } + return cp + default: + return value + } +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/loader/fix.go b/vendor/github.com/compose-spec/compose-go/v2/loader/fix.go new file mode 100644 index 000000000..7a6e88d81 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/loader/fix.go @@ -0,0 +1,36 @@ +/* + Copyright 2020 The Compose Specification 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 loader + +// fixEmptyNotNull is a workaround for https://github.com/xeipuuv/gojsonschema/issues/141 +// as go-yaml `[]` will load as a `[]any(nil)`, which is not the same as an empty array +func fixEmptyNotNull(value any) interface{} { + switch v := value.(type) { + case []any: + if v == nil { + return []any{} + } + for i, e := range v { + v[i] = fixEmptyNotNull(e) + } + case map[string]any: + for k, e := range v { + v[k] = fixEmptyNotNull(e) + } + } + return value +} diff --git a/vendor/github.com/compose-spec/compose-go/loader/full-example.yml b/vendor/github.com/compose-spec/compose-go/v2/loader/full-example.yml similarity index 95% rename from vendor/github.com/compose-spec/compose-go/loader/full-example.yml rename to vendor/github.com/compose-spec/compose-go/v2/loader/full-example.yml index 6ef27a9a4..2f7b8c43c 100644 --- a/vendor/github.com/compose-spec/compose-go/loader/full-example.yml +++ b/vendor/github.com/compose-spec/compose-go/v2/loader/full-example.yml @@ -26,7 +26,8 @@ services: additional_contexts: foo: ./bar secrets: - - secret1 + - source: secret1 + target: /run/secrets/secret1 - source: secret2 target: my_secret uid: '103' @@ -117,7 +118,9 @@ services: - "a 7:* rmw" devices: - - "/dev/ttyUSB0:/dev/ttyUSB0" + - source: /dev/ttyUSB0 + target: /dev/ttyUSB0 + permissions: rwm # String or list # dns: 8.8.8.8 @@ -141,7 +144,8 @@ services: # env_file: .env env_file: - ./example1.env - - ./example2.env + - path: ./example2.env + required: false # Mapping or list # Mapping values can be strings, numbers or null @@ -256,7 +260,8 @@ services: restart: always secrets: - - secret1 + - source: secret1 + target: /run/secrets/secret1 - source: secret2 target: my_secret uid: '103' @@ -272,7 +277,8 @@ services: stop_grace_period: 20s stop_signal: SIGUSR1 - + storage_opt: + size: "20G" sysctls: net.core.somaxconn: 1024 net.ipv4.tcp_syncookies: 0 @@ -296,22 +302,22 @@ services: volumes: # Just specify a path and let the Engine create a volume - - /var/lib/mysql + - /var/lib/anonymous # Specify an absolute path mapping - - /opt/data:/var/lib/mysql + - /opt/data:/var/lib/data # Path on the host, relative to the Compose file - .:/code - ./static:/var/www/html # User-relative path - ~/configs:/etc/configs:ro # Named volume - - datavolume:/var/lib/mysql + - datavolume:/var/lib/volume - type: bind source: ./opt - target: /opt + target: /opt/cached consistency: cached - type: tmpfs - target: /opt + target: /opt/tmpfs tmpfs: size: 10000 diff --git a/vendor/github.com/compose-spec/compose-go/v2/loader/include.go b/vendor/github.com/compose-spec/compose-go/v2/loader/include.go new file mode 100644 index 000000000..823c2f7ac --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/loader/include.go @@ -0,0 +1,204 @@ +/* + Copyright 2020 The Compose Specification 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 loader + +import ( + "context" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + + "github.com/compose-spec/compose-go/v2/dotenv" + interp "github.com/compose-spec/compose-go/v2/interpolation" + "github.com/compose-spec/compose-go/v2/types" +) + +// loadIncludeConfig parse the required config from raw yaml +func loadIncludeConfig(source any) ([]types.IncludeConfig, error) { + if source == nil { + return nil, nil + } + configs, ok := source.([]any) + if !ok { + return nil, fmt.Errorf("`include` must be a list, got %s", source) + } + for i, config := range configs { + if v, ok := config.(string); ok { + configs[i] = map[string]any{ + "path": v, + } + } + } + var requires []types.IncludeConfig + err := Transform(source, &requires) + return requires, err +} + +func ApplyInclude(ctx context.Context, workingDir string, environment types.Mapping, model map[string]any, options *Options, included []string) error { + includeConfig, err := loadIncludeConfig(model["include"]) + if err != nil { + return err + } + + for _, r := range includeConfig { + for _, listener := range options.Listeners { + listener("include", map[string]any{ + "path": r.Path, + "workingdir": workingDir, + }) + } + + var relworkingdir string + for i, p := range r.Path { + for _, loader := range options.ResourceLoaders { + if !loader.Accept(p) { + continue + } + path, err := loader.Load(ctx, p) + if err != nil { + return err + } + p = path + + if i == 0 { // This is the "main" file, used to define project-directory. Others are overrides + + switch { + case r.ProjectDirectory == "": + relworkingdir = loader.Dir(path) + r.ProjectDirectory = filepath.Dir(path) + case !filepath.IsAbs(r.ProjectDirectory): + relworkingdir = loader.Dir(r.ProjectDirectory) + r.ProjectDirectory = filepath.Join(workingDir, r.ProjectDirectory) + + default: + relworkingdir = r.ProjectDirectory + + } + for _, f := range included { + if f == path { + included = append(included, path) + return fmt.Errorf("include cycle detected:\n%s\n include %s", included[0], strings.Join(included[1:], "\n include ")) + } + } + } + } + r.Path[i] = p + } + + loadOptions := options.clone() + loadOptions.ResolvePaths = true + loadOptions.SkipNormalization = true + loadOptions.SkipConsistencyCheck = true + loadOptions.ResourceLoaders = append(loadOptions.RemoteResourceLoaders(), localResourceLoader{ + WorkingDir: r.ProjectDirectory, + }) + + if len(r.EnvFile) == 0 { + f := filepath.Join(r.ProjectDirectory, ".env") + if s, err := os.Stat(f); err == nil && !s.IsDir() { + r.EnvFile = types.StringList{f} + } + } else { + envFile := []string{} + for _, f := range r.EnvFile { + if !filepath.IsAbs(f) { + f = filepath.Join(workingDir, f) + s, err := os.Stat(f) + if err != nil { + return err + } + if s.IsDir() { + return fmt.Errorf("%s is not a file", f) + } + } + envFile = append(envFile, f) + } + r.EnvFile = envFile + } + + envFromFile, err := dotenv.GetEnvFromFile(environment, r.EnvFile) + if err != nil { + return err + } + + config := types.ConfigDetails{ + WorkingDir: relworkingdir, + ConfigFiles: types.ToConfigFiles(r.Path), + Environment: environment.Clone().Merge(envFromFile), + } + loadOptions.Interpolate = &interp.Options{ + Substitute: options.Interpolate.Substitute, + LookupValue: config.LookupEnv, + TypeCastMapping: options.Interpolate.TypeCastMapping, + } + imported, err := loadYamlModel(ctx, config, loadOptions, &cycleTracker{}, included) + if err != nil { + return err + } + err = importResources(imported, model) + if err != nil { + return err + } + } + delete(model, "include") + return nil +} + +// importResources import into model all resources defined by imported, and report error on conflict +func importResources(source map[string]any, target map[string]any) error { + if err := importResource(source, target, "services"); err != nil { + return err + } + if err := importResource(source, target, "volumes"); err != nil { + return err + } + if err := importResource(source, target, "networks"); err != nil { + return err + } + if err := importResource(source, target, "secrets"); err != nil { + return err + } + if err := importResource(source, target, "configs"); err != nil { + return err + } + return nil +} + +func importResource(source map[string]any, target map[string]any, key string) error { + from := source[key] + if from != nil { + var to map[string]any + if v, ok := target[key]; ok { + to = v.(map[string]any) + } else { + to = map[string]any{} + } + for name, a := range from.(map[string]any) { + if conflict, ok := to[name]; ok { + if reflect.DeepEqual(a, conflict) { + continue + } + return fmt.Errorf("%s.%s conflicts with imported resource", key, name) + } + to[name] = a + } + target[key] = to + } + return nil +} diff --git a/vendor/github.com/compose-spec/compose-go/loader/interpolate.go b/vendor/github.com/compose-spec/compose-go/v2/loader/interpolate.go similarity index 96% rename from vendor/github.com/compose-spec/compose-go/loader/interpolate.go rename to vendor/github.com/compose-spec/compose-go/v2/loader/interpolate.go index 655e58e11..a1cef1ecc 100644 --- a/vendor/github.com/compose-spec/compose-go/loader/interpolate.go +++ b/vendor/github.com/compose-spec/compose-go/v2/loader/interpolate.go @@ -17,12 +17,12 @@ package loader import ( + "fmt" "strconv" "strings" - interp "github.com/compose-spec/compose-go/interpolation" - "github.com/compose-spec/compose-go/tree" - "github.com/pkg/errors" + interp "github.com/compose-spec/compose-go/v2/interpolation" + "github.com/compose-spec/compose-go/v2/tree" "github.com/sirupsen/logrus" ) @@ -112,6 +112,6 @@ func toBoolean(value string) (interface{}, error) { logrus.Warnf("%q for boolean is not supported by YAML 1.2, please use `false`", value) return false, nil default: - return nil, errors.Errorf("invalid boolean: %s", value) + return nil, fmt.Errorf("invalid boolean: %s", value) } } diff --git a/vendor/github.com/compose-spec/compose-go/v2/loader/loader.go b/vendor/github.com/compose-spec/compose-go/v2/loader/loader.go new file mode 100644 index 000000000..25d2d0c6a --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/loader/loader.go @@ -0,0 +1,868 @@ +/* + Copyright 2020 The Compose Specification 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 loader + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "reflect" + "regexp" + "strconv" + "strings" + + "github.com/compose-spec/compose-go/v2/consts" + interp "github.com/compose-spec/compose-go/v2/interpolation" + "github.com/compose-spec/compose-go/v2/override" + "github.com/compose-spec/compose-go/v2/paths" + "github.com/compose-spec/compose-go/v2/schema" + "github.com/compose-spec/compose-go/v2/template" + "github.com/compose-spec/compose-go/v2/transform" + "github.com/compose-spec/compose-go/v2/tree" + "github.com/compose-spec/compose-go/v2/types" + "github.com/compose-spec/compose-go/v2/validation" + "github.com/go-viper/mapstructure/v2" + "github.com/sirupsen/logrus" + "golang.org/x/exp/slices" + "gopkg.in/yaml.v3" +) + +// Options supported by Load +type Options struct { + // Skip schema validation + SkipValidation bool + // Skip interpolation + SkipInterpolation bool + // Skip normalization + SkipNormalization bool + // Resolve path + ResolvePaths bool + // Convert Windows path + ConvertWindowsPaths bool + // Skip consistency check + SkipConsistencyCheck bool + // Skip extends + SkipExtends bool + // SkipInclude will ignore `include` and only load model from file(s) set by ConfigDetails + SkipInclude bool + // SkipResolveEnvironment will ignore computing `environment` for services + SkipResolveEnvironment bool + // SkipDefaultValues will ignore missing required attributes + SkipDefaultValues bool + // Interpolation options + Interpolate *interp.Options + // Discard 'env_file' entries after resolving to 'environment' section + discardEnvFiles bool + // Set project projectName + projectName string + // Indicates when the projectName was imperatively set or guessed from path + projectNameImperativelySet bool + // Profiles set profiles to enable + Profiles []string + // ResourceLoaders manages support for remote resources + ResourceLoaders []ResourceLoader + // KnownExtensions manages x-* attribute we know and the corresponding go structs + KnownExtensions map[string]any + // Metada for telemetry + Listeners []Listener +} + +var versionWarning []string + +func (o *Options) warnObsoleteVersion(file string) { + if !slices.Contains(versionWarning, file) { + logrus.Warning(fmt.Sprintf("%s: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion", file)) + } + versionWarning = append(versionWarning, file) +} + +type Listener = func(event string, metadata map[string]any) + +// Invoke all listeners for an event +func (o *Options) ProcessEvent(event string, metadata map[string]any) { + for _, l := range o.Listeners { + l(event, metadata) + } +} + +// ResourceLoader is a plugable remote resource resolver +type ResourceLoader interface { + // Accept returns `true` is the resource reference matches ResourceLoader supported protocol(s) + Accept(path string) bool + // Load returns the path to a local copy of remote resource identified by `path`. + Load(ctx context.Context, path string) (string, error) + // Dir computes path to resource"s parent folder, made relative if possible + Dir(path string) string +} + +// RemoteResourceLoaders excludes localResourceLoader from ResourceLoaders +func (o Options) RemoteResourceLoaders() []ResourceLoader { + var loaders []ResourceLoader + for i, loader := range o.ResourceLoaders { + if _, ok := loader.(localResourceLoader); ok { + if i != len(o.ResourceLoaders)-1 { + logrus.Warning("misconfiguration of ResourceLoaders: localResourceLoader should be last") + } + continue + } + loaders = append(loaders, loader) + } + return loaders +} + +type localResourceLoader struct { + WorkingDir string +} + +func (l localResourceLoader) abs(p string) string { + if filepath.IsAbs(p) { + return p + } + return filepath.Join(l.WorkingDir, p) +} + +func (l localResourceLoader) Accept(p string) bool { + _, err := os.Stat(l.abs(p)) + return err == nil +} + +func (l localResourceLoader) Load(_ context.Context, p string) (string, error) { + return l.abs(p), nil +} + +func (l localResourceLoader) Dir(originalPath string) string { + path := l.abs(originalPath) + if !l.isDir(path) { + path = l.abs(filepath.Dir(originalPath)) + } + rel, err := filepath.Rel(l.WorkingDir, path) + if err != nil { + return path + } + return rel +} + +func (l localResourceLoader) isDir(path string) bool { + fileInfo, err := os.Stat(path) + if err != nil { + return false + } + return fileInfo.IsDir() +} + +func (o *Options) clone() *Options { + return &Options{ + SkipValidation: o.SkipValidation, + SkipInterpolation: o.SkipInterpolation, + SkipNormalization: o.SkipNormalization, + ResolvePaths: o.ResolvePaths, + ConvertWindowsPaths: o.ConvertWindowsPaths, + SkipConsistencyCheck: o.SkipConsistencyCheck, + SkipExtends: o.SkipExtends, + SkipInclude: o.SkipInclude, + Interpolate: o.Interpolate, + discardEnvFiles: o.discardEnvFiles, + projectName: o.projectName, + projectNameImperativelySet: o.projectNameImperativelySet, + Profiles: o.Profiles, + ResourceLoaders: o.ResourceLoaders, + KnownExtensions: o.KnownExtensions, + Listeners: o.Listeners, + } +} + +func (o *Options) SetProjectName(name string, imperativelySet bool) { + o.projectName = name + o.projectNameImperativelySet = imperativelySet +} + +func (o Options) GetProjectName() (string, bool) { + return o.projectName, o.projectNameImperativelySet +} + +// serviceRef identifies a reference to a service. It's used to detect cyclic +// references in "extends". +type serviceRef struct { + filename string + service string +} + +type cycleTracker struct { + loaded []serviceRef +} + +func (ct *cycleTracker) Add(filename, service string) (*cycleTracker, error) { + toAdd := serviceRef{filename: filename, service: service} + for _, loaded := range ct.loaded { + if toAdd == loaded { + // Create an error message of the form: + // Circular reference: + // service-a in docker-compose.yml + // extends service-b in docker-compose.yml + // extends service-a in docker-compose.yml + errLines := []string{ + "Circular reference:", + fmt.Sprintf(" %s in %s", ct.loaded[0].service, ct.loaded[0].filename), + } + for _, service := range append(ct.loaded[1:], toAdd) { + errLines = append(errLines, fmt.Sprintf(" extends %s in %s", service.service, service.filename)) + } + + return nil, errors.New(strings.Join(errLines, "\n")) + } + } + + var branch []serviceRef + branch = append(branch, ct.loaded...) + branch = append(branch, toAdd) + return &cycleTracker{ + loaded: branch, + }, nil +} + +// WithDiscardEnvFiles sets the Options to discard the `env_file` section after resolving to +// the `environment` section +func WithDiscardEnvFiles(opts *Options) { + opts.discardEnvFiles = true +} + +// WithSkipValidation sets the Options to skip validation when loading sections +func WithSkipValidation(opts *Options) { + opts.SkipValidation = true +} + +// WithProfiles sets profiles to be activated +func WithProfiles(profiles []string) func(*Options) { + return func(opts *Options) { + opts.Profiles = profiles + } +} + +// ParseYAML reads the bytes from a file, parses the bytes into a mapping +// structure, and returns it. +func ParseYAML(source []byte) (map[string]interface{}, error) { + r := bytes.NewReader(source) + decoder := yaml.NewDecoder(r) + m, _, err := parseYAML(decoder) + return m, err +} + +// PostProcessor is used to tweak compose model based on metadata extracted during yaml Unmarshal phase +// that hardly can be implemented using go-yaml and mapstructure +type PostProcessor interface { + yaml.Unmarshaler + + // Apply changes to compose model based on recorder metadata + Apply(interface{}) error +} + +func parseYAML(decoder *yaml.Decoder) (map[string]interface{}, PostProcessor, error) { + var cfg interface{} + processor := ResetProcessor{target: &cfg} + + if err := decoder.Decode(&processor); err != nil { + return nil, nil, err + } + stringMap, ok := cfg.(map[string]interface{}) + if ok { + converted, err := convertToStringKeysRecursive(stringMap, "") + if err != nil { + return nil, nil, err + } + return converted.(map[string]interface{}), &processor, nil + } + cfgMap, ok := cfg.(map[interface{}]interface{}) + if !ok { + return nil, nil, errors.New("Top-level object must be a mapping") + } + converted, err := convertToStringKeysRecursive(cfgMap, "") + if err != nil { + return nil, nil, err + } + return converted.(map[string]interface{}), &processor, nil +} + +// Load reads a ConfigDetails and returns a fully loaded configuration. +// Deprecated: use LoadWithContext. +func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.Project, error) { + return LoadWithContext(context.Background(), configDetails, options...) +} + +// LoadWithContext reads a ConfigDetails and returns a fully loaded configuration as a compose-go Project +func LoadWithContext(ctx context.Context, configDetails types.ConfigDetails, options ...func(*Options)) (*types.Project, error) { + opts := toOptions(&configDetails, options) + dict, err := loadModelWithContext(ctx, &configDetails, opts) + if err != nil { + return nil, err + } + return modelToProject(dict, opts, configDetails) +} + +// LoadModelWithContext reads a ConfigDetails and returns a fully loaded configuration as a yaml dictionary +func LoadModelWithContext(ctx context.Context, configDetails types.ConfigDetails, options ...func(*Options)) (map[string]any, error) { + opts := toOptions(&configDetails, options) + return loadModelWithContext(ctx, &configDetails, opts) +} + +// LoadModelWithContext reads a ConfigDetails and returns a fully loaded configuration as a yaml dictionary +func loadModelWithContext(ctx context.Context, configDetails *types.ConfigDetails, opts *Options) (map[string]any, error) { + if len(configDetails.ConfigFiles) < 1 { + return nil, errors.New("No files specified") + } + + err := projectName(configDetails, opts) + if err != nil { + return nil, err + } + + return load(ctx, *configDetails, opts, nil) +} + +func toOptions(configDetails *types.ConfigDetails, options []func(*Options)) *Options { + opts := &Options{ + Interpolate: &interp.Options{ + Substitute: template.Substitute, + LookupValue: configDetails.LookupEnv, + TypeCastMapping: interpolateTypeCastMapping, + }, + ResolvePaths: true, + } + + for _, op := range options { + op(opts) + } + opts.ResourceLoaders = append(opts.ResourceLoaders, localResourceLoader{configDetails.WorkingDir}) + return opts +} + +func loadYamlModel(ctx context.Context, config types.ConfigDetails, opts *Options, ct *cycleTracker, included []string) (map[string]interface{}, error) { + var ( + dict = map[string]interface{}{} + err error + ) + workingDir, environment := config.WorkingDir, config.Environment + + for _, file := range config.ConfigFiles { + dict, _, err = loadYamlFile(ctx, file, opts, workingDir, environment, ct, dict, included) + if err != nil { + return nil, err + } + } + + if !opts.SkipDefaultValues { + dict, err = transform.SetDefaultValues(dict) + if err != nil { + return nil, err + } + } + + if !opts.SkipValidation { + if err := validation.Validate(dict); err != nil { + return nil, err + } + } + + if opts.ResolvePaths { + var remotes []paths.RemoteResource + for _, loader := range opts.RemoteResourceLoaders() { + remotes = append(remotes, loader.Accept) + } + err = paths.ResolveRelativePaths(dict, config.WorkingDir, remotes) + if err != nil { + return nil, err + } + } + ResolveEnvironment(dict, config.Environment) + + return dict, nil +} + +func loadYamlFile(ctx context.Context, file types.ConfigFile, opts *Options, workingDir string, environment types.Mapping, ct *cycleTracker, dict map[string]interface{}, included []string) (map[string]interface{}, PostProcessor, error) { + ctx = context.WithValue(ctx, consts.ComposeFileKey{}, file.Filename) + if file.Content == nil && file.Config == nil { + content, err := os.ReadFile(file.Filename) + if err != nil { + return nil, nil, err + } + file.Content = content + } + + processRawYaml := func(raw interface{}, processors ...PostProcessor) error { + converted, err := convertToStringKeysRecursive(raw, "") + if err != nil { + return err + } + cfg, ok := converted.(map[string]interface{}) + if !ok { + return errors.New("Top-level object must be a mapping") + } + + if opts.Interpolate != nil && !opts.SkipInterpolation { + cfg, err = interp.Interpolate(cfg, *opts.Interpolate) + if err != nil { + return err + } + } + + fixEmptyNotNull(cfg) + + if !opts.SkipExtends { + err = ApplyExtends(ctx, cfg, opts, ct, processors...) + if err != nil { + return err + } + } + + for _, processor := range processors { + if err := processor.Apply(dict); err != nil { + return err + } + } + + if !opts.SkipInclude { + included = append(included, file.Filename) + err = ApplyInclude(ctx, workingDir, environment, cfg, opts, included) + if err != nil { + return err + } + } + + dict, err = override.Merge(dict, cfg) + if err != nil { + return err + } + + dict, err = override.EnforceUnicity(dict) + if err != nil { + return err + } + + if !opts.SkipValidation { + if err := schema.Validate(dict); err != nil { + return fmt.Errorf("validating %s: %w", file.Filename, err) + } + if _, ok := dict["version"]; ok { + opts.warnObsoleteVersion(file.Filename) + delete(dict, "version") + } + } + + dict, err = transform.Canonical(dict, opts.SkipInterpolation) + if err != nil { + return err + } + + // Canonical transformation can reveal duplicates, typically as ports can be a range and conflict with an override + dict, err = override.EnforceUnicity(dict) + return err + } + + var processor PostProcessor + if file.Config == nil { + r := bytes.NewReader(file.Content) + decoder := yaml.NewDecoder(r) + for { + var raw interface{} + reset := &ResetProcessor{target: &raw} + err := decoder.Decode(reset) + if err != nil && errors.Is(err, io.EOF) { + break + } + if err != nil { + return nil, nil, err + } + processor = reset + if err := processRawYaml(raw, processor); err != nil { + return nil, nil, err + } + } + } else { + if err := processRawYaml(file.Config); err != nil { + return nil, nil, err + } + } + return dict, processor, nil +} + +func load(ctx context.Context, configDetails types.ConfigDetails, opts *Options, loaded []string) (map[string]interface{}, error) { + mainFile := configDetails.ConfigFiles[0].Filename + for _, f := range loaded { + if f == mainFile { + loaded = append(loaded, mainFile) + return nil, fmt.Errorf("include cycle detected:\n%s\n include %s", loaded[0], strings.Join(loaded[1:], "\n include ")) + } + } + loaded = append(loaded, mainFile) + + dict, err := loadYamlModel(ctx, configDetails, opts, &cycleTracker{}, nil) + if err != nil { + return nil, err + } + + if len(dict) == 0 { + return nil, errors.New("empty compose file") + } + + if opts.projectName == "" { + return nil, errors.New("project name must not be empty") + } + + if !opts.SkipNormalization { + dict["name"] = opts.projectName + dict, err = Normalize(dict, configDetails.Environment) + if err != nil { + return nil, err + } + } + + return dict, nil +} + +// modelToProject binds a canonical yaml dict into compose-go structs +func modelToProject(dict map[string]interface{}, opts *Options, configDetails types.ConfigDetails) (*types.Project, error) { + project := &types.Project{ + Name: opts.projectName, + WorkingDir: configDetails.WorkingDir, + Environment: configDetails.Environment, + } + delete(dict, "name") // project name set by yaml must be identified by caller as opts.projectName + + var err error + dict, err = processExtensions(dict, tree.NewPath(), opts.KnownExtensions) + if err != nil { + return nil, err + } + + err = Transform(dict, project) + if err != nil { + return nil, err + } + + if opts.ConvertWindowsPaths { + for i, service := range project.Services { + for j, volume := range service.Volumes { + service.Volumes[j] = convertVolumePath(volume) + } + project.Services[i] = service + } + } + + if project, err = project.WithProfiles(opts.Profiles); err != nil { + return nil, err + } + + if !opts.SkipConsistencyCheck { + err := checkConsistency(project) + if err != nil { + return nil, err + } + } + + if !opts.SkipResolveEnvironment { + project, err = project.WithServicesEnvironmentResolved(opts.discardEnvFiles) + if err != nil { + return nil, err + } + } + return project, nil +} + +func InvalidProjectNameErr(v string) error { + return fmt.Errorf( + "invalid project name %q: must consist only of lowercase alphanumeric characters, hyphens, and underscores as well as start with a letter or number", + v, + ) +} + +// projectName determines the canonical name to use for the project considering +// the loader Options as well as `name` fields in Compose YAML fields (which +// also support interpolation). +func projectName(details *types.ConfigDetails, opts *Options) error { + defer func() { + if details.Environment == nil { + details.Environment = map[string]string{} + } + details.Environment[consts.ComposeProjectName] = opts.projectName + }() + + if opts.projectNameImperativelySet { + if NormalizeProjectName(opts.projectName) != opts.projectName { + return InvalidProjectNameErr(opts.projectName) + } + return nil + } + + type named struct { + Name string `yaml:"name"` + } + + // if user did NOT provide a name explicitly, then see if one is defined + // in any of the config files + var pjNameFromConfigFile string + for _, configFile := range details.ConfigFiles { + content := configFile.Content + if content == nil { + // This can be hit when Filename is set but Content is not. One + // example is when using ToConfigFiles(). + d, err := os.ReadFile(configFile.Filename) + if err != nil { + return fmt.Errorf("failed to read file %q: %w", configFile.Filename, err) + } + content = d + configFile.Content = d + } + var n named + r := bytes.NewReader(content) + decoder := yaml.NewDecoder(r) + for { + err := decoder.Decode(&n) + if err != nil && errors.Is(err, io.EOF) { + break + } + if err != nil { + // HACK: the way that loading is currently structured, this is + // a duplicative parse just for the `name`. if it fails, we + // give up but don't return the error, knowing that it'll get + // caught downstream for us + break + } + if n.Name != "" { + pjNameFromConfigFile = n.Name + } + } + } + if !opts.SkipInterpolation { + interpolated, err := interp.Interpolate( + map[string]interface{}{"name": pjNameFromConfigFile}, + *opts.Interpolate, + ) + if err != nil { + return err + } + pjNameFromConfigFile = interpolated["name"].(string) + } + pjNameFromConfigFile = NormalizeProjectName(pjNameFromConfigFile) + if pjNameFromConfigFile != "" { + opts.projectName = pjNameFromConfigFile + } + return nil +} + +func NormalizeProjectName(s string) string { + r := regexp.MustCompile("[a-z0-9_-]") + s = strings.ToLower(s) + s = strings.Join(r.FindAllString(s, -1), "") + return strings.TrimLeft(s, "_-") +} + +var userDefinedKeys = []tree.Path{ + "services", + "volumes", + "networks", + "secrets", + "configs", +} + +func processExtensions(dict map[string]any, p tree.Path, extensions map[string]any) (map[string]interface{}, error) { + extras := map[string]any{} + var err error + for key, value := range dict { + skip := false + for _, uk := range userDefinedKeys { + if uk.Matches(p) { + skip = true + break + } + } + if !skip && strings.HasPrefix(key, "x-") { + extras[key] = value + delete(dict, key) + continue + } + switch v := value.(type) { + case map[string]interface{}: + dict[key], err = processExtensions(v, p.Next(key), extensions) + if err != nil { + return nil, err + } + case []interface{}: + for i, e := range v { + if m, ok := e.(map[string]interface{}); ok { + v[i], err = processExtensions(m, p.Next(strconv.Itoa(i)), extensions) + if err != nil { + return nil, err + } + } + } + } + } + for name, val := range extras { + if typ, ok := extensions[name]; ok { + target := reflect.New(reflect.TypeOf(typ)).Elem().Interface() + err = Transform(val, &target) + if err != nil { + return nil, err + } + extras[name] = target + } + } + if len(extras) > 0 { + dict[consts.Extensions] = extras + } + return dict, nil +} + +// Transform converts the source into the target struct with compose types transformer +// and the specified transformers if any. +func Transform(source interface{}, target interface{}) error { + data := mapstructure.Metadata{} + config := &mapstructure.DecoderConfig{ + DecodeHook: mapstructure.ComposeDecodeHookFunc( + nameServices, + decoderHook, + cast, + secretConfigDecoderHook, + ), + Result: target, + TagName: "yaml", + Metadata: &data, + } + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return err + } + return decoder.Decode(source) +} + +// nameServices create implicit `name` key for convenience accessing service +func nameServices(from reflect.Value, to reflect.Value) (interface{}, error) { + if to.Type() == reflect.TypeOf(types.Services{}) { + nameK := reflect.ValueOf("name") + iter := from.MapRange() + for iter.Next() { + name := iter.Key() + elem := iter.Value() + elem.Elem().SetMapIndex(nameK, name) + } + } + return from.Interface(), nil +} + +func secretConfigDecoderHook(from, to reflect.Type, data interface{}) (interface{}, error) { + // Check if the input is a map and we're decoding into a SecretConfig + if from.Kind() == reflect.Map && to == reflect.TypeOf(types.SecretConfig{}) { + if v, ok := data.(map[string]interface{}); ok { + if ext, ok := v["#extensions"].(map[string]interface{}); ok { + if val, ok := ext[types.SecretConfigXValue].(string); ok { + // Return a map with the Content field populated + v["Content"] = val + delete(ext, types.SecretConfigXValue) + + if len(ext) == 0 { + delete(v, "#extensions") + } + } + } + } + } + + // Return the original data so the rest is handled by default mapstructure logic + return data, nil +} + +// keys need to be converted to strings for jsonschema +func convertToStringKeysRecursive(value interface{}, keyPrefix string) (interface{}, error) { + if mapping, ok := value.(map[string]interface{}); ok { + for key, entry := range mapping { + var newKeyPrefix string + if keyPrefix == "" { + newKeyPrefix = key + } else { + newKeyPrefix = fmt.Sprintf("%s.%s", keyPrefix, key) + } + convertedEntry, err := convertToStringKeysRecursive(entry, newKeyPrefix) + if err != nil { + return nil, err + } + mapping[key] = convertedEntry + } + return mapping, nil + } + if mapping, ok := value.(map[interface{}]interface{}); ok { + dict := make(map[string]interface{}) + for key, entry := range mapping { + str, ok := key.(string) + if !ok { + return nil, formatInvalidKeyError(keyPrefix, key) + } + var newKeyPrefix string + if keyPrefix == "" { + newKeyPrefix = str + } else { + newKeyPrefix = fmt.Sprintf("%s.%s", keyPrefix, str) + } + convertedEntry, err := convertToStringKeysRecursive(entry, newKeyPrefix) + if err != nil { + return nil, err + } + dict[str] = convertedEntry + } + return dict, nil + } + if list, ok := value.([]interface{}); ok { + var convertedList []interface{} + for index, entry := range list { + newKeyPrefix := fmt.Sprintf("%s[%d]", keyPrefix, index) + convertedEntry, err := convertToStringKeysRecursive(entry, newKeyPrefix) + if err != nil { + return nil, err + } + convertedList = append(convertedList, convertedEntry) + } + return convertedList, nil + } + return value, nil +} + +func formatInvalidKeyError(keyPrefix string, key interface{}) error { + var location string + if keyPrefix == "" { + location = "at top level" + } else { + location = fmt.Sprintf("in %s", keyPrefix) + } + return fmt.Errorf("Non-string key %s: %#v", location, key) +} + +// Windows path, c:\\my\\path\\shiny, need to be changed to be compatible with +// the Engine. Volume path are expected to be linux style /c/my/path/shiny/ +func convertVolumePath(volume types.ServiceVolumeConfig) types.ServiceVolumeConfig { + volumeName := strings.ToLower(filepath.VolumeName(volume.Source)) + if len(volumeName) != 2 { + return volume + } + + convertedSource := fmt.Sprintf("/%c%s", volumeName[0], volume.Source[len(volumeName):]) + convertedSource = strings.ReplaceAll(convertedSource, "\\", "/") + + volume.Source = convertedSource + return volume +} diff --git a/vendor/github.com/compose-spec/compose-go/loader/mapstructure.go b/vendor/github.com/compose-spec/compose-go/v2/loader/mapstructure.go similarity index 75% rename from vendor/github.com/compose-spec/compose-go/loader/mapstructure.go rename to vendor/github.com/compose-spec/compose-go/v2/loader/mapstructure.go index 97a2e39c1..e5b902ab2 100644 --- a/vendor/github.com/compose-spec/compose-go/loader/mapstructure.go +++ b/vendor/github.com/compose-spec/compose-go/v2/loader/mapstructure.go @@ -16,7 +16,10 @@ package loader -import "reflect" +import ( + "reflect" + "strconv" +) // comparable to yaml.Unmarshaler, decoder allow a type to define it's own custom logic to convert value // see https://github.com/mitchellh/mapstructure/pull/294 @@ -51,3 +54,26 @@ func decoderHook(from reflect.Value, to reflect.Value) (interface{}, error) { } return to.Interface(), nil } + +func cast(from reflect.Value, to reflect.Value) (interface{}, error) { + switch from.Type().Kind() { + case reflect.String: + switch to.Kind() { + case reflect.Bool: + return toBoolean(from.String()) + case reflect.Int: + return toInt(from.String()) + case reflect.Int64: + return toInt64(from.String()) + case reflect.Float32: + return toFloat32(from.String()) + case reflect.Float64: + return toFloat(from.String()) + } + case reflect.Int: + if to.Kind() == reflect.String { + return strconv.FormatInt(from.Int(), 10), nil + } + } + return from.Interface(), nil +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/loader/normalize.go b/vendor/github.com/compose-spec/compose-go/v2/loader/normalize.go new file mode 100644 index 000000000..e99b61b5c --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/loader/normalize.go @@ -0,0 +1,251 @@ +/* + Copyright 2020 The Compose Specification 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 loader + +import ( + "fmt" + "strconv" + "strings" + + "github.com/compose-spec/compose-go/v2/types" +) + +// Normalize compose project by moving deprecated attributes to their canonical position and injecting implicit defaults +func Normalize(dict map[string]any, env types.Mapping) (map[string]any, error) { + normalizeNetworks(dict) + + if d, ok := dict["services"]; ok { + services := d.(map[string]any) + for name, s := range services { + service := s.(map[string]any) + + if service["pull_policy"] == types.PullPolicyIfNotPresent { + service["pull_policy"] = types.PullPolicyMissing + } + + fn := func(s string) (string, bool) { + v, ok := env[s] + return v, ok + } + + if b, ok := service["build"]; ok { + build := b.(map[string]any) + if build["context"] == nil { + build["context"] = "." + } + if build["dockerfile"] == nil && build["dockerfile_inline"] == nil { + build["dockerfile"] = "Dockerfile" + } + + if a, ok := build["args"]; ok { + build["args"], _ = resolve(a, fn, false) + } + + service["build"] = build + } + + if e, ok := service["environment"]; ok { + service["environment"], _ = resolve(e, fn, true) + } + + var dependsOn map[string]any + if d, ok := service["depends_on"]; ok { + dependsOn = d.(map[string]any) + } else { + dependsOn = map[string]any{} + } + if l, ok := service["links"]; ok { + links := l.([]any) + for _, e := range links { + link := e.(string) + parts := strings.Split(link, ":") + if len(parts) == 2 { + link = parts[0] + } + if _, ok := dependsOn[link]; !ok { + dependsOn[link] = map[string]any{ + "condition": types.ServiceConditionStarted, + "restart": true, + "required": true, + } + } + } + } + + for _, namespace := range []string{"network_mode", "ipc", "pid", "uts", "cgroup"} { + if n, ok := service[namespace]; ok { + ref := n.(string) + if strings.HasPrefix(ref, types.ServicePrefix) { + shared := ref[len(types.ServicePrefix):] + if _, ok := dependsOn[shared]; !ok { + dependsOn[shared] = map[string]any{ + "condition": types.ServiceConditionStarted, + "restart": true, + "required": true, + } + } + } + } + } + + if n, ok := service["volumes_from"]; ok { + volumesFrom := n.([]any) + for _, v := range volumesFrom { + vol := v.(string) + if !strings.HasPrefix(vol, types.ContainerPrefix) { + spec := strings.Split(vol, ":") + if _, ok := dependsOn[spec[0]]; !ok { + dependsOn[spec[0]] = map[string]any{ + "condition": types.ServiceConditionStarted, + "restart": false, + "required": true, + } + } + } + } + } + if len(dependsOn) > 0 { + service["depends_on"] = dependsOn + } + services[name] = service + } + dict["services"] = services + } + + setNameFromKey(dict) + + return dict, nil +} + +func normalizeNetworks(dict map[string]any) { + var networks map[string]any + if n, ok := dict["networks"]; ok { + networks = n.(map[string]any) + } else { + networks = map[string]any{} + } + + // implicit `default` network must be introduced only if actually used by some service + usesDefaultNetwork := false + + if s, ok := dict["services"]; ok { + services := s.(map[string]any) + for name, se := range services { + service := se.(map[string]any) + if _, ok := service["network_mode"]; ok { + continue + } + if n, ok := service["networks"]; !ok { + // If none explicitly declared, service is connected to default network + service["networks"] = map[string]any{"default": nil} + usesDefaultNetwork = true + } else { + net := n.(map[string]any) + if len(net) == 0 { + // networks section declared but empty (corner case) + service["networks"] = map[string]any{"default": nil} + usesDefaultNetwork = true + } else if _, ok := net["default"]; ok { + usesDefaultNetwork = true + } + } + services[name] = service + } + dict["services"] = services + } + + if _, ok := networks["default"]; !ok && usesDefaultNetwork { + // If not declared explicitly, Compose model involves an implicit "default" network + networks["default"] = nil + } + + if len(networks) > 0 { + dict["networks"] = networks + } +} + +func resolve(a any, fn func(s string) (string, bool), keepEmpty bool) (any, bool) { + switch v := a.(type) { + case []any: + var resolved []any + for _, val := range v { + if r, ok := resolve(val, fn, keepEmpty); ok { + resolved = append(resolved, r) + } + } + return resolved, true + case map[string]any: + resolved := map[string]any{} + for key, val := range v { + if val != nil { + resolved[key] = val + continue + } + if s, ok := fn(key); ok { + resolved[key] = s + } else if keepEmpty { + resolved[key] = nil + } + } + return resolved, true + case string: + if !strings.Contains(v, "=") { + if val, ok := fn(v); ok { + return fmt.Sprintf("%s=%s", v, val), true + } + if keepEmpty { + return v, true + } + return "", false + } + return v, true + default: + return v, false + } +} + +// Resources with no explicit name are actually named by their key in map +func setNameFromKey(dict map[string]any) { + for _, r := range []string{"networks", "volumes", "configs", "secrets"} { + a, ok := dict[r] + if !ok { + continue + } + toplevel := a.(map[string]any) + for key, r := range toplevel { + var resource map[string]any + if r != nil { + resource = r.(map[string]any) + } else { + resource = map[string]any{} + } + if resource["name"] == nil { + if x, ok := resource["external"]; ok && isTrue(x) { + resource["name"] = key + } else { + resource["name"] = fmt.Sprintf("%s_%s", dict["name"], key) + } + } + toplevel[key] = resource + } + } +} + +func isTrue(x any) bool { + parseBool, _ := strconv.ParseBool(fmt.Sprint(x)) + return parseBool +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/loader/paths.go b/vendor/github.com/compose-spec/compose-go/v2/loader/paths.go new file mode 100644 index 000000000..102ff036e --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/loader/paths.go @@ -0,0 +1,74 @@ +/* + Copyright 2020 The Compose Specification 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 loader + +import ( + "os" + "path/filepath" + "strings" + + "github.com/compose-spec/compose-go/v2/types" +) + +// ResolveRelativePaths resolves relative paths based on project WorkingDirectory +func ResolveRelativePaths(project *types.Project) error { + absWorkingDir, err := filepath.Abs(project.WorkingDir) + if err != nil { + return err + } + project.WorkingDir = absWorkingDir + + absComposeFiles, err := absComposeFiles(project.ComposeFiles) + if err != nil { + return err + } + project.ComposeFiles = absComposeFiles + return nil +} + +func absPath(workingDir string, filePath string) string { + if strings.HasPrefix(filePath, "~") { + home, _ := os.UserHomeDir() + return filepath.Join(home, filePath[1:]) + } + if filepath.IsAbs(filePath) { + return filePath + } + return filepath.Join(workingDir, filePath) +} + +func absComposeFiles(composeFiles []string) ([]string, error) { + for i, composeFile := range composeFiles { + absComposefile, err := filepath.Abs(composeFile) + if err != nil { + return nil, err + } + composeFiles[i] = absComposefile + } + return composeFiles, nil +} + +func resolvePaths(basePath string, in types.StringList) types.StringList { + if in == nil { + return nil + } + ret := make(types.StringList, len(in)) + for i := range in { + ret[i] = absPath(basePath, in[i]) + } + return ret +} diff --git a/vendor/github.com/compose-spec/compose-go/loader/null.go b/vendor/github.com/compose-spec/compose-go/v2/loader/reset.go similarity index 53% rename from vendor/github.com/compose-spec/compose-go/loader/null.go rename to vendor/github.com/compose-spec/compose-go/v2/loader/reset.go index 648aacde4..2b7f04c3a 100644 --- a/vendor/github.com/compose-spec/compose-go/loader/null.go +++ b/vendor/github.com/compose-spec/compose-go/v2/loader/reset.go @@ -18,12 +18,10 @@ package loader import ( "fmt" - "reflect" "strconv" "strings" - "github.com/compose-spec/compose-go/tree" - "github.com/compose-spec/compose-go/types" + "github.com/compose-spec/compose-go/v2/tree" "gopkg.in/yaml.v3" ) @@ -43,113 +41,92 @@ func (p *ResetProcessor) UnmarshalYAML(value *yaml.Node) error { // resolveReset detects `!reset` tag being set on yaml nodes and record position in the yaml tree func (p *ResetProcessor) resolveReset(node *yaml.Node, path tree.Path) (*yaml.Node, error) { + // If the path contains "<<", removing the "<<" element and merging the path + if strings.Contains(path.String(), ".<<") { + path = tree.NewPath(strings.Replace(path.String(), ".<<", "", 1)) + } + // If the node is an alias, We need to process the alias field in order to consider the !override and !reset tags + if node.Kind == yaml.AliasNode { + return p.resolveReset(node.Alias, path) + } + if node.Tag == "!reset" { p.paths = append(p.paths, path) + return nil, nil + } + if node.Tag == "!override" { + p.paths = append(p.paths, path) + return node, nil } switch node.Kind { case yaml.SequenceNode: - var err error + var nodes []*yaml.Node for idx, v := range node.Content { next := path.Next(strconv.Itoa(idx)) - node.Content[idx], err = p.resolveReset(v, next) + resolved, err := p.resolveReset(v, next) if err != nil { return nil, err } + if resolved != nil { + nodes = append(nodes, resolved) + } } + node.Content = nodes case yaml.MappingNode: - var err error var key string + var nodes []*yaml.Node for idx, v := range node.Content { if idx%2 == 0 { key = v.Value } else { - node.Content[idx], err = p.resolveReset(v, path.Next(key)) + resolved, err := p.resolveReset(v, path.Next(key)) if err != nil { return nil, err } + if resolved != nil { + nodes = append(nodes, node.Content[idx-1], resolved) + } } } + node.Content = nodes } return node, nil } // Apply finds the go attributes matching recorded paths and reset them to zero value -func (p *ResetProcessor) Apply(target *types.Config) error { - return p.applyNullOverrides(reflect.ValueOf(target), tree.NewPath()) +func (p *ResetProcessor) Apply(target any) error { + return p.applyNullOverrides(target, tree.NewPath()) } // applyNullOverrides set val to Zero if it matches any of the recorded paths -func (p *ResetProcessor) applyNullOverrides(val reflect.Value, path tree.Path) error { - val = reflect.Indirect(val) - if !val.IsValid() { - return nil - } - typ := val.Type() - switch { - case path == "services": - // Project.Services is a slice in compose-go, but a mapping in yaml - for i := 0; i < val.Len(); i++ { - service := val.Index(i) - name := service.FieldByName("Name") - next := path.Next(name.String()) - err := p.applyNullOverrides(service, next) - if err != nil { - return err - } - } - case typ.Kind() == reflect.Map: - iter := val.MapRange() +func (p *ResetProcessor) applyNullOverrides(target any, path tree.Path) error { + switch v := target.(type) { + case map[string]any: KEYS: - for iter.Next() { - k := iter.Key() - next := path.Next(k.String()) + for k, e := range v { + next := path.Next(k) for _, pattern := range p.paths { if next.Matches(pattern) { - val.SetMapIndex(k, reflect.Value{}) + delete(v, k) continue KEYS } } - return p.applyNullOverrides(iter.Value(), next) + err := p.applyNullOverrides(e, next) + if err != nil { + return err + } } - case typ.Kind() == reflect.Slice: + case []any: ITER: - for i := 0; i < val.Len(); i++ { + for i, e := range v { next := path.Next(fmt.Sprintf("[%d]", i)) for _, pattern := range p.paths { if next.Matches(pattern) { - continue ITER + // TODO(ndeloof) support removal from sequence } } - // TODO(ndeloof) support removal from sequence - return p.applyNullOverrides(val.Index(i), next) - } - - case typ.Kind() == reflect.Struct: - FIELDS: - for i := 0; i < typ.NumField(); i++ { - field := typ.Field(i) - name := field.Name - attr := strings.ToLower(name) - tag := field.Tag.Get("yaml") - tag = strings.Split(tag, ",")[0] - if tag != "" && tag != "-" { - attr = tag - } - next := path.Next(attr) - f := val.Field(i) - for _, pattern := range p.paths { - if next.Matches(pattern) { - f := f - if !f.CanSet() { - return fmt.Errorf("can't override attribute %s", name) - } - // f.SetZero() requires go 1.20 - f.Set(reflect.Zero(f.Type())) - continue FIELDS - } - } - err := p.applyNullOverrides(f, next) + err := p.applyNullOverrides(e, next) if err != nil { return err } diff --git a/vendor/github.com/compose-spec/compose-go/v2/loader/validate.go b/vendor/github.com/compose-spec/compose-go/v2/loader/validate.go new file mode 100644 index 000000000..cfaf139fa --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/loader/validate.go @@ -0,0 +1,176 @@ +/* + Copyright 2020 The Compose Specification 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 loader + +import ( + "errors" + "fmt" + "strings" + + "github.com/compose-spec/compose-go/v2/errdefs" + "github.com/compose-spec/compose-go/v2/graph" + "github.com/compose-spec/compose-go/v2/types" +) + +// checkConsistency validate a compose model is consistent +func checkConsistency(project *types.Project) error { + for _, s := range project.Services { + if s.Build == nil && s.Image == "" { + return fmt.Errorf("service %q has neither an image nor a build context specified: %w", s.Name, errdefs.ErrInvalid) + } + + if s.Build != nil { + if s.Build.DockerfileInline != "" && s.Build.Dockerfile != "" { + return fmt.Errorf("service %q declares mutualy exclusive dockerfile and dockerfile_inline: %w", s.Name, errdefs.ErrInvalid) + } + + if len(s.Build.Platforms) > 0 && s.Platform != "" { + var found bool + for _, platform := range s.Build.Platforms { + if platform == s.Platform { + found = true + break + } + } + if !found { + return fmt.Errorf("service.build.platforms MUST include service.platform %q: %w", s.Platform, errdefs.ErrInvalid) + } + } + } + + if s.NetworkMode != "" && len(s.Networks) > 0 { + return fmt.Errorf("service %s declares mutually exclusive `network_mode` and `networks`: %w", s.Name, errdefs.ErrInvalid) + } + for network := range s.Networks { + if _, ok := project.Networks[network]; !ok { + return fmt.Errorf("service %q refers to undefined network %s: %w", s.Name, network, errdefs.ErrInvalid) + } + } + + if s.HealthCheck != nil && len(s.HealthCheck.Test) > 0 { + switch s.HealthCheck.Test[0] { + case "CMD", "CMD-SHELL", "NONE": + default: + return errors.New(`healthcheck.test must start either by "CMD", "CMD-SHELL" or "NONE"`) + } + } + + for dependedService, cfg := range s.DependsOn { + if _, err := project.GetService(dependedService); err != nil { + if errors.Is(err, errdefs.ErrDisabled) && !cfg.Required { + continue + } + return fmt.Errorf("service %q depends on undefined service %q: %w", s.Name, dependedService, errdefs.ErrInvalid) + } + } + + if strings.HasPrefix(s.NetworkMode, types.ServicePrefix) { + serviceName := s.NetworkMode[len(types.ServicePrefix):] + if _, err := project.GetServices(serviceName); err != nil { + return fmt.Errorf("service %q not found for network_mode 'service:%s'", serviceName, serviceName) + } + } + + for _, volume := range s.Volumes { + if volume.Type == types.VolumeTypeVolume && volume.Source != "" { // non anonymous volumes + if _, ok := project.Volumes[volume.Source]; !ok { + return fmt.Errorf("service %q refers to undefined volume %s: %w", s.Name, volume.Source, errdefs.ErrInvalid) + } + } + } + if s.Build != nil { + for _, secret := range s.Build.Secrets { + if _, ok := project.Secrets[secret.Source]; !ok { + return fmt.Errorf("service %q refers to undefined build secret %s: %w", s.Name, secret.Source, errdefs.ErrInvalid) + } + } + } + for _, config := range s.Configs { + if _, ok := project.Configs[config.Source]; !ok { + return fmt.Errorf("service %q refers to undefined config %s: %w", s.Name, config.Source, errdefs.ErrInvalid) + } + } + + for _, secret := range s.Secrets { + if _, ok := project.Secrets[secret.Source]; !ok { + return fmt.Errorf("service %q refers to undefined secret %s: %w", s.Name, secret.Source, errdefs.ErrInvalid) + } + } + + if s.Scale != nil && s.Deploy != nil { + if s.Deploy.Replicas != nil && *s.Scale != *s.Deploy.Replicas { + return fmt.Errorf("services.%s: can't set distinct values on 'scale' and 'deploy.replicas': %w", + s.Name, errdefs.ErrInvalid) + } + s.Deploy.Replicas = s.Scale + } + + if s.CPUS != 0 && s.Deploy != nil { + if s.Deploy.Resources.Limits != nil && s.Deploy.Resources.Limits.NanoCPUs.Value() != s.CPUS { + return fmt.Errorf("services.%s: can't set distinct values on 'cpus' and 'deploy.resources.limits.cpus': %w", + s.Name, errdefs.ErrInvalid) + } + } + if s.MemLimit != 0 && s.Deploy != nil { + if s.Deploy.Resources.Limits != nil && s.Deploy.Resources.Limits.MemoryBytes != s.MemLimit { + return fmt.Errorf("services.%s: can't set distinct values on 'mem_limit' and 'deploy.resources.limits.memory': %w", + s.Name, errdefs.ErrInvalid) + } + } + if s.MemReservation != 0 && s.Deploy != nil { + if s.Deploy.Resources.Reservations != nil && s.Deploy.Resources.Reservations.MemoryBytes != s.MemReservation { + return fmt.Errorf("services.%s: can't set distinct values on 'mem_reservation' and 'deploy.resources.reservations.memory': %w", + s.Name, errdefs.ErrInvalid) + } + } + if s.PidsLimit != 0 && s.Deploy != nil { + if s.Deploy.Resources.Limits != nil && s.Deploy.Resources.Limits.Pids != s.PidsLimit { + return fmt.Errorf("services.%s: can't set distinct values on 'pids_limit' and 'deploy.resources.limits.pids': %w", + s.Name, errdefs.ErrInvalid) + } + } + + if s.GetScale() > 1 && s.ContainerName != "" { + attr := "scale" + if s.Scale == nil { + attr = "deploy.replicas" + } + return fmt.Errorf("services.%s: can't set container_name and %s as container name must be unique: %w", attr, + s.Name, errdefs.ErrInvalid) + } + + if s.Develop != nil && s.Develop.Watch != nil { + for _, watch := range s.Develop.Watch { + if watch.Action != types.WatchActionRebuild && watch.Target == "" { + return fmt.Errorf("services.%s.develop.watch: target is required for non-rebuild actions: %w", s.Name, errdefs.ErrInvalid) + } + } + + } + } + + for name, secret := range project.Secrets { + if secret.External { + continue + } + if secret.File == "" && secret.Environment == "" { + return fmt.Errorf("secret %q must declare either `file` or `environment`: %w", name, errdefs.ErrInvalid) + } + } + + return graph.CheckCycle(project) +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/override/extends.go b/vendor/github.com/compose-spec/compose-go/v2/override/extends.go new file mode 100644 index 000000000..f47912ddf --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/override/extends.go @@ -0,0 +1,27 @@ +/* + Copyright 2020 The Compose Specification 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 override + +import "github.com/compose-spec/compose-go/v2/tree" + +func ExtendService(base, override map[string]any) (map[string]any, error) { + yaml, err := mergeYaml(base, override, tree.NewPath("services.x")) + if err != nil { + return nil, err + } + return yaml.(map[string]any), nil +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/override/merge.go b/vendor/github.com/compose-spec/compose-go/v2/override/merge.go new file mode 100644 index 000000000..8a468dde9 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/override/merge.go @@ -0,0 +1,269 @@ +/* + Copyright 2020 The Compose Specification 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 override + +import ( + "cmp" + "fmt" + "strings" + + "github.com/compose-spec/compose-go/v2/tree" + "golang.org/x/exp/slices" +) + +// Merge applies overrides to a config model +func Merge(right, left map[string]any) (map[string]any, error) { + merged, err := mergeYaml(right, left, tree.NewPath()) + if err != nil { + return nil, err + } + return merged.(map[string]any), nil +} + +type merger func(any, any, tree.Path) (any, error) + +// mergeSpecials defines the custom rules applied by compose when merging yaml trees +var mergeSpecials = map[tree.Path]merger{} + +func init() { + mergeSpecials["networks.*.ipam.config"] = mergeIPAMConfig + mergeSpecials["networks.*.labels"] = mergeToSequence + mergeSpecials["volumes.*.labels"] = mergeToSequence + mergeSpecials["services.*.annotations"] = mergeToSequence + mergeSpecials["services.*.build"] = mergeBuild + mergeSpecials["services.*.build.args"] = mergeToSequence + mergeSpecials["services.*.build.additional_contexts"] = mergeToSequence + mergeSpecials["services.*.build.extra_hosts"] = mergeToSequence + mergeSpecials["services.*.build.labels"] = mergeToSequence + mergeSpecials["services.*.command"] = override + mergeSpecials["services.*.depends_on"] = mergeDependsOn + mergeSpecials["services.*.deploy.labels"] = mergeToSequence + mergeSpecials["services.*.dns"] = mergeToSequence + mergeSpecials["services.*.dns_opt"] = mergeToSequence + mergeSpecials["services.*.dns_search"] = mergeToSequence + mergeSpecials["services.*.entrypoint"] = override + mergeSpecials["services.*.env_file"] = mergeToSequence + mergeSpecials["services.*.environment"] = mergeToSequence + mergeSpecials["services.*.extra_hosts"] = mergeToSequence + mergeSpecials["services.*.healthcheck.test"] = override + mergeSpecials["services.*.labels"] = mergeToSequence + mergeSpecials["services.*.logging"] = mergeLogging + mergeSpecials["services.*.networks"] = mergeNetworks + mergeSpecials["services.*.sysctls"] = mergeToSequence + mergeSpecials["services.*.tmpfs"] = mergeToSequence + mergeSpecials["services.*.ulimits.*"] = mergeUlimit +} + +// mergeYaml merges map[string]any yaml trees handling special rules +func mergeYaml(e any, o any, p tree.Path) (any, error) { + for pattern, merger := range mergeSpecials { + if p.Matches(pattern) { + merged, err := merger(e, o, p) + if err != nil { + return nil, err + } + return merged, nil + } + } + if o == nil { + return e, nil + } + switch value := e.(type) { + case map[string]any: + other, ok := o.(map[string]any) + if !ok { + return nil, fmt.Errorf("cannot override %s", p) + } + return mergeMappings(value, other, p) + case []any: + other, ok := o.([]any) + if !ok { + return nil, fmt.Errorf("cannot override %s", p) + } + return append(value, other...), nil + default: + return o, nil + } +} + +func mergeMappings(mapping map[string]any, other map[string]any, p tree.Path) (map[string]any, error) { + for k, v := range other { + e, ok := mapping[k] + if !ok || strings.HasPrefix(k, "x-") { + mapping[k] = v + continue + } + next := p.Next(k) + merged, err := mergeYaml(e, v, next) + if err != nil { + return nil, err + } + mapping[k] = merged + } + return mapping, nil +} + +// logging driver options are merged only when both compose file define the same driver +func mergeLogging(c any, o any, p tree.Path) (any, error) { + config := c.(map[string]any) + other := o.(map[string]any) + // we override logging config if source and override have the same driver set, or none + d, ok1 := other["driver"] + o, ok2 := config["driver"] + if d == o || !ok1 || !ok2 { + return mergeMappings(config, other, p) + } + return other, nil +} + +func mergeBuild(c any, o any, path tree.Path) (any, error) { + toBuild := func(c any) map[string]any { + switch v := c.(type) { + case string: + return map[string]any{ + "context": v, + } + case map[string]any: + return v + } + return nil + } + return mergeMappings(toBuild(c), toBuild(o), path) +} + +func mergeDependsOn(c any, o any, path tree.Path) (any, error) { + right := convertIntoMapping(c, map[string]any{ + "condition": "service_started", + "required": true, + }) + left := convertIntoMapping(o, map[string]any{ + "condition": "service_started", + "required": true, + }) + return mergeMappings(right, left, path) +} + +func mergeNetworks(c any, o any, path tree.Path) (any, error) { + right := convertIntoMapping(c, nil) + left := convertIntoMapping(o, nil) + return mergeMappings(right, left, path) +} + +func mergeToSequence(c any, o any, _ tree.Path) (any, error) { + right := convertIntoSequence(c) + left := convertIntoSequence(o) + return append(right, left...), nil +} + +func convertIntoSequence(value any) []any { + switch v := value.(type) { + case map[string]any: + seq := make([]any, len(v)) + i := 0 + for k, v := range v { + if v == nil { + seq[i] = k + } else { + seq[i] = fmt.Sprintf("%s=%v", k, v) + } + i++ + } + slices.SortFunc(seq, func(a, b any) int { + return cmp.Compare(a.(string), b.(string)) + }) + return seq + case []any: + return v + case string: + return []any{v} + } + return nil +} + +func mergeUlimit(_ any, o any, p tree.Path) (any, error) { + over, ismapping := o.(map[string]any) + if base, ok := o.(map[string]any); ok && ismapping { + return mergeMappings(base, over, p) + } + return o, nil +} + +func mergeIPAMConfig(c any, o any, path tree.Path) (any, error) { + var ipamConfigs []any + for _, original := range c.([]any) { + right := convertIntoMapping(original, nil) + for _, override := range o.([]any) { + left := convertIntoMapping(override, nil) + if left["subnet"] != right["subnet"] { + // check if left is already in ipamConfigs, add it if not and continue with the next config + if !slices.ContainsFunc(ipamConfigs, func(a any) bool { + return a.(map[string]any)["subnet"] == left["subnet"] + }) { + ipamConfigs = append(ipamConfigs, left) + continue + } + } + merged, err := mergeMappings(right, left, path) + if err != nil { + return nil, err + } + // find index of potential previous config with the same subnet in ipamConfigs + indexIfExist := slices.IndexFunc(ipamConfigs, func(a any) bool { + return a.(map[string]any)["subnet"] == merged["subnet"] + }) + // if a previous config is already in ipamConfigs, replace it + if indexIfExist >= 0 { + ipamConfigs[indexIfExist] = merged + } else { + // or add the new config to ipamConfigs + ipamConfigs = append(ipamConfigs, merged) + } + } + } + return ipamConfigs, nil +} + +func convertIntoMapping(a any, defaultValue map[string]any) map[string]any { + switch v := a.(type) { + case map[string]any: + return v + case []any: + converted := map[string]any{} + for _, s := range v { + if defaultValue == nil { + converted[s.(string)] = nil + } else { + // Create a new map for each key + converted[s.(string)] = copyMap(defaultValue) + } + } + return converted + } + return nil +} + +func copyMap(m map[string]any) map[string]any { + c := make(map[string]any) + for k, v := range m { + c[k] = v + } + return c +} + +func override(_ any, other any, _ tree.Path) (any, error) { + return other, nil +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/override/uncity.go b/vendor/github.com/compose-spec/compose-go/v2/override/uncity.go new file mode 100644 index 000000000..3b0c63d3a --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/override/uncity.go @@ -0,0 +1,229 @@ +/* + Copyright 2020 The Compose Specification 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 override + +import ( + "fmt" + "strconv" + "strings" + + "github.com/compose-spec/compose-go/v2/format" + "github.com/compose-spec/compose-go/v2/tree" +) + +type indexer func(any, tree.Path) (string, error) + +// mergeSpecials defines the custom rules applied by compose when merging yaml trees +var unique = map[tree.Path]indexer{} + +func init() { + unique["networks.*.labels"] = keyValueIndexer + unique["networks.*.ipam.options"] = keyValueIndexer + unique["services.*.annotations"] = keyValueIndexer + unique["services.*.build.args"] = keyValueIndexer + unique["services.*.build.additional_contexts"] = keyValueIndexer + unique["services.*.build.platform"] = keyValueIndexer + unique["services.*.build.tags"] = keyValueIndexer + unique["services.*.build.labels"] = keyValueIndexer + unique["services.*.cap_add"] = keyValueIndexer + unique["services.*.cap_drop"] = keyValueIndexer + unique["services.*.devices"] = volumeIndexer + unique["services.*.configs"] = mountIndexer("") + unique["services.*.deploy.labels"] = keyValueIndexer + unique["services.*.dns"] = keyValueIndexer + unique["services.*.dns_opt"] = keyValueIndexer + unique["services.*.dns_search"] = keyValueIndexer + unique["services.*.environment"] = keyValueIndexer + unique["services.*.env_file"] = envFileIndexer + unique["services.*.expose"] = exposeIndexer + unique["services.*.labels"] = keyValueIndexer + unique["services.*.links"] = keyValueIndexer + unique["services.*.networks.*.aliases"] = keyValueIndexer + unique["services.*.networks.*.link_local_ips"] = keyValueIndexer + unique["services.*.ports"] = portIndexer + unique["services.*.profiles"] = keyValueIndexer + unique["services.*.secrets"] = mountIndexer("/run/secrets") + unique["services.*.sysctls"] = keyValueIndexer + unique["services.*.tmpfs"] = keyValueIndexer + unique["services.*.volumes"] = volumeIndexer + unique["services.*.devices"] = deviceMappingIndexer +} + +// EnforceUnicity removes redefinition of elements declared in a sequence +func EnforceUnicity(value map[string]any) (map[string]any, error) { + uniq, err := enforceUnicity(value, tree.NewPath()) + if err != nil { + return nil, err + } + return uniq.(map[string]any), nil +} + +func enforceUnicity(value any, p tree.Path) (any, error) { + switch v := value.(type) { + case map[string]any: + for k, e := range v { + u, err := enforceUnicity(e, p.Next(k)) + if err != nil { + return nil, err + } + v[k] = u + } + return v, nil + case []any: + for pattern, indexer := range unique { + if p.Matches(pattern) { + seq := []any{} + keys := map[string]int{} + for i, entry := range v { + key, err := indexer(entry, p.Next(fmt.Sprintf("[%d]", i))) + if err != nil { + return nil, err + } + if j, ok := keys[key]; ok { + seq[j] = entry + } else { + seq = append(seq, entry) + keys[key] = len(seq) - 1 + } + } + return seq, nil + } + } + } + return value, nil +} + +func keyValueIndexer(v any, p tree.Path) (string, error) { + switch value := v.(type) { + case string: + key, _, found := strings.Cut(value, "=") + if found { + return key, nil + } + return value, nil + default: + return "", fmt.Errorf("%s: unexpected type %T", p, v) + } +} + +func volumeIndexer(y any, p tree.Path) (string, error) { + switch value := y.(type) { + case map[string]any: + target, ok := value["target"].(string) + if !ok { + return "", fmt.Errorf("service volume %s is missing a mount target", p) + } + return target, nil + case string: + volume, err := format.ParseVolume(value) + if err != nil { + return "", err + } + return volume.Target, nil + } + return "", nil +} + +func deviceMappingIndexer(y any, p tree.Path) (string, error) { + switch value := y.(type) { + case map[string]any: + target, ok := value["target"].(string) + if !ok { + return "", fmt.Errorf("service device %s is missing a mount target", p) + } + return target, nil + case string: + arr := strings.Split(value, ":") + if len(arr) == 1 { + return arr[0], nil + } + return arr[1], nil + } + return "", nil +} + +func exposeIndexer(a any, path tree.Path) (string, error) { + switch v := a.(type) { + case string: + return v, nil + case int: + return strconv.Itoa(v), nil + default: + return "", fmt.Errorf("%s: unsupported expose value %s", path, a) + } +} + +func mountIndexer(defaultPath string) indexer { + return func(a any, path tree.Path) (string, error) { + switch v := a.(type) { + case string: + return fmt.Sprintf("%s/%s", defaultPath, v), nil + case map[string]any: + t, ok := v["target"] + if ok { + return t.(string), nil + } + return fmt.Sprintf("%s/%s", defaultPath, v["source"]), nil + default: + return "", fmt.Errorf("%s: unsupported expose value %s", path, a) + } + } +} + +func portIndexer(y any, p tree.Path) (string, error) { + switch value := y.(type) { + case int: + return strconv.Itoa(value), nil + case map[string]any: + target, ok := value["target"] + if !ok { + return "", fmt.Errorf("service ports %s is missing a target port", p) + } + published, ok := value["published"] + if !ok { + // try to parse it as an int + if pub, ok := value["published"]; ok { + published = fmt.Sprintf("%d", pub) + } + } + host, ok := value["host_ip"] + if !ok { + host = "0.0.0.0" + } + protocol, ok := value["protocol"] + if !ok { + protocol = "tcp" + } + return fmt.Sprintf("%s:%s:%d/%s", host, published, target, protocol), nil + case string: + return value, nil + } + return "", nil +} + +func envFileIndexer(y any, p tree.Path) (string, error) { + switch value := y.(type) { + case string: + return value, nil + case map[string]any: + if pathValue, ok := value["path"]; ok { + return pathValue.(string), nil + } + return "", fmt.Errorf("environment path attribute %s is missing", p) + } + return "", nil +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/paths/context.go b/vendor/github.com/compose-spec/compose-go/v2/paths/context.go new file mode 100644 index 000000000..b4585dc4e --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/paths/context.go @@ -0,0 +1,44 @@ +/* + Copyright 2020 The Compose Specification 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 paths + +import "strings" + +func (r *relativePathsResolver) absContextPath(value any) (any, error) { + v := value.(string) + if strings.Contains(v, "://") { // `docker-image://` or any builder specific context type + return v, nil + } + if isRemoteContext(v) { + return v, nil + } + return r.absPath(v) +} + +// isRemoteContext returns true if the value is a Git reference or HTTP(S) URL. +// +// Any other value is assumed to be a local filesystem path and returns false. +// +// See: https://github.com/moby/buildkit/blob/18fc875d9bfd6e065cd8211abc639434ba65aa56/frontend/dockerui/context.go#L76-L79 +func isRemoteContext(maybeURL string) bool { + for _, prefix := range []string{"https://", "http://", "git://", "ssh://", "github.com/", "git@"} { + if strings.HasPrefix(maybeURL, prefix) { + return true + } + } + return false +} diff --git a/vendor/github.com/compose-spec/compose-go/consts/consts.go b/vendor/github.com/compose-spec/compose-go/v2/paths/extends.go similarity index 75% rename from vendor/github.com/compose-spec/compose-go/consts/consts.go rename to vendor/github.com/compose-spec/compose-go/v2/paths/extends.go index 76bdb82e1..aa61a9f9a 100644 --- a/vendor/github.com/compose-spec/compose-go/consts/consts.go +++ b/vendor/github.com/compose-spec/compose-go/v2/paths/extends.go @@ -14,11 +14,12 @@ limitations under the License. */ -package consts +package paths -const ( - ComposeProjectName = "COMPOSE_PROJECT_NAME" - ComposePathSeparator = "COMPOSE_PATH_SEPARATOR" - ComposeFilePath = "COMPOSE_FILE" - ComposeProfiles = "COMPOSE_PROFILES" -) +func (r *relativePathsResolver) absExtendsPath(value any) (any, error) { + v := value.(string) + if r.isRemoteResource(v) { + return v, nil + } + return r.absPath(v) +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/paths/home.go b/vendor/github.com/compose-spec/compose-go/v2/paths/home.go new file mode 100644 index 000000000..a5579262b --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/paths/home.go @@ -0,0 +1,37 @@ +/* + Copyright 2020 The Compose Specification 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 paths + +import ( + "os" + "path/filepath" + "strings" + + "github.com/sirupsen/logrus" +) + +func ExpandUser(p string) string { + if strings.HasPrefix(p, "~") { + home, err := os.UserHomeDir() + if err != nil { + logrus.Warn("cannot expand '~', because the environment lacks HOME") + return p + } + return filepath.Join(home, p[1:]) + } + return p +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/paths/resolve.go b/vendor/github.com/compose-spec/compose-go/v2/paths/resolve.go new file mode 100644 index 000000000..303f39e20 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/paths/resolve.go @@ -0,0 +1,167 @@ +/* + Copyright 2020 The Compose Specification 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 paths + +import ( + "errors" + "fmt" + "path/filepath" + + "github.com/compose-spec/compose-go/v2/tree" + "github.com/compose-spec/compose-go/v2/types" +) + +type resolver func(any) (any, error) + +// ResolveRelativePaths make relative paths absolute +func ResolveRelativePaths(project map[string]any, base string, remotes []RemoteResource) error { + r := relativePathsResolver{ + workingDir: base, + remotes: remotes, + } + r.resolvers = map[tree.Path]resolver{ + "services.*.build.context": r.absContextPath, + "services.*.build.additional_contexts.*": r.absContextPath, + "services.*.env_file.*.path": r.absPath, + "services.*.extends.file": r.absExtendsPath, + "services.*.develop.watch.*.path": r.absSymbolicLink, + "services.*.volumes.*": r.absVolumeMount, + "configs.*.file": r.maybeUnixPath, + "secrets.*.file": r.maybeUnixPath, + "include.path": r.absPath, + "include.project_directory": r.absPath, + "include.env_file": r.absPath, + "volumes.*": r.volumeDriverOpts, + } + _, err := r.resolveRelativePaths(project, tree.NewPath()) + return err +} + +type RemoteResource func(path string) bool + +type relativePathsResolver struct { + workingDir string + remotes []RemoteResource + resolvers map[tree.Path]resolver +} + +func (r *relativePathsResolver) isRemoteResource(path string) bool { + for _, remote := range r.remotes { + if remote(path) { + return true + } + } + return false +} + +func (r *relativePathsResolver) resolveRelativePaths(value any, p tree.Path) (any, error) { + for pattern, resolver := range r.resolvers { + if p.Matches(pattern) { + return resolver(value) + } + } + switch v := value.(type) { + case map[string]any: + for k, e := range v { + resolved, err := r.resolveRelativePaths(e, p.Next(k)) + if err != nil { + return nil, err + } + v[k] = resolved + } + case []any: + for i, e := range v { + resolved, err := r.resolveRelativePaths(e, p.Next("[]")) + if err != nil { + return nil, err + } + v[i] = resolved + } + } + return value, nil +} + +func (r *relativePathsResolver) absPath(value any) (any, error) { + switch v := value.(type) { + case []any: + for i, s := range v { + abs, err := r.absPath(s) + if err != nil { + return nil, err + } + v[i] = abs + } + return v, nil + case string: + v = ExpandUser(v) + if filepath.IsAbs(v) { + return v, nil + } + if v != "" { + return filepath.Join(r.workingDir, v), nil + } + return v, nil + } + + return nil, fmt.Errorf("unexpected type %T", value) +} + +func (r *relativePathsResolver) absVolumeMount(a any) (any, error) { + switch vol := a.(type) { + case map[string]any: + if vol["type"] != types.VolumeTypeBind { + return vol, nil + } + src, ok := vol["source"] + if !ok { + return nil, errors.New(`invalid mount config for type "bind": field Source must not be empty`) + } + abs, err := r.maybeUnixPath(src.(string)) + if err != nil { + return nil, err + } + vol["source"] = abs + return vol, nil + default: + // not using canonical format, skip + return a, nil + } +} + +func (r *relativePathsResolver) volumeDriverOpts(a any) (any, error) { + if a == nil { + return nil, nil + } + vol := a.(map[string]any) + if vol["driver"] != "local" { + return vol, nil + } + do, ok := vol["driver_opts"] + if !ok { + return vol, nil + } + opts := do.(map[string]any) + if dev, ok := opts["device"]; opts["o"] == "bind" && ok { + // This is actually a bind mount + path, err := r.maybeUnixPath(dev) + if err != nil { + return nil, err + } + opts["device"] = path + } + return vol, nil +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/paths/unix.go b/vendor/github.com/compose-spec/compose-go/v2/paths/unix.go new file mode 100644 index 000000000..5ab2616ef --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/paths/unix.go @@ -0,0 +1,54 @@ +/* + Copyright 2020 The Compose Specification 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 paths + +import ( + "path" + "path/filepath" + + "github.com/compose-spec/compose-go/v2/utils" +) + +func (r *relativePathsResolver) maybeUnixPath(a any) (any, error) { + p := a.(string) + p = ExpandUser(p) + // Check if source is an absolute path (either Unix or Windows), to + // handle a Windows client with a Unix daemon or vice-versa. + // + // Note that this is not required for Docker for Windows when specifying + // a local Windows path, because Docker for Windows translates the Windows + // path into a valid path within the VM. + if !path.IsAbs(p) && !isWindowsAbs(p) { + if filepath.IsAbs(p) { + return p, nil + } + return filepath.Join(r.workingDir, p), nil + } + return p, nil +} + +func (r *relativePathsResolver) absSymbolicLink(value any) (any, error) { + abs, err := r.absPath(value) + if err != nil { + return nil, err + } + str, ok := abs.(string) + if !ok { + return abs, nil + } + return utils.ResolveSymbolicLink(str) +} diff --git a/vendor/github.com/compose-spec/compose-go/loader/windows_path.go b/vendor/github.com/compose-spec/compose-go/v2/paths/windows_path.go similarity index 97% rename from vendor/github.com/compose-spec/compose-go/loader/windows_path.go rename to vendor/github.com/compose-spec/compose-go/v2/paths/windows_path.go index 5094f5b57..746aefd15 100644 --- a/vendor/github.com/compose-spec/compose-go/loader/windows_path.go +++ b/vendor/github.com/compose-spec/compose-go/v2/paths/windows_path.go @@ -14,7 +14,7 @@ limitations under the License. */ -package loader +package paths // Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style @@ -30,7 +30,7 @@ func isSlash(c uint8) bool { } // isAbs reports whether the path is a Windows absolute path. -func isAbs(path string) (b bool) { +func isWindowsAbs(path string) (b bool) { l := volumeNameLen(path) if l == 0 { return false diff --git a/vendor/github.com/compose-spec/compose-go/schema/compose-spec.json b/vendor/github.com/compose-spec/compose-go/v2/schema/compose-spec.json similarity index 80% rename from vendor/github.com/compose-spec/compose-go/schema/compose-spec.json rename to vendor/github.com/compose-spec/compose-go/v2/schema/compose-spec.json index 0469c9e0d..a2f5e2448 100644 --- a/vendor/github.com/compose-spec/compose-go/schema/compose-spec.json +++ b/vendor/github.com/compose-spec/compose-go/v2/schema/compose-spec.json @@ -13,14 +13,12 @@ "name": { "type": "string", - "pattern": "^[a-z0-9][a-z0-9_-]*$", "description": "define the Compose project name, until user defines one explicitly." }, "include": { "type": "array", "items": { - "type": "object", "$ref": "#/definitions/include" }, "description": "compose sub-projects to be included." @@ -94,7 +92,7 @@ "develop": {"$ref": "#/definitions/development"}, "deploy": {"$ref": "#/definitions/deployment"}, "annotations": {"$ref": "#/definitions/list_or_dict"}, - "attach": {"type": "boolean"}, + "attach": {"type": ["boolean", "string"]}, "build": { "oneOf": [ {"type": "string"}, @@ -104,20 +102,21 @@ "context": {"type": "string"}, "dockerfile": {"type": "string"}, "dockerfile_inline": {"type": "string"}, + "entitlements": {"type": "array", "items": {"type": "string"}}, "args": {"$ref": "#/definitions/list_or_dict"}, "ssh": {"$ref": "#/definitions/list_or_dict"}, "labels": {"$ref": "#/definitions/list_or_dict"}, "cache_from": {"type": "array", "items": {"type": "string"}}, "cache_to": {"type": "array", "items": {"type": "string"}}, - "no_cache": {"type": "boolean"}, + "no_cache": {"type": ["boolean", "string"]}, "additional_contexts": {"$ref": "#/definitions/list_or_dict"}, "network": {"type": "string"}, - "pull": {"type": "boolean"}, + "pull": {"type": ["boolean", "string"]}, "target": {"type": "string"}, "shm_size": {"type": ["integer", "string"]}, - "extra_hosts": {"$ref": "#/definitions/list_or_dict"}, + "extra_hosts": {"$ref": "#/definitions/extra_hosts"}, "isolation": {"type": "string"}, - "privileged": {"type": "boolean"}, + "privileged": {"type": ["boolean", "string"]}, "secrets": {"$ref": "#/definitions/service_config_or_secret"}, "tags": {"type": "array", "items": {"type": "string"}}, "ulimits": {"$ref": "#/definitions/ulimits"}, @@ -147,7 +146,7 @@ "type": "array", "items": {"$ref": "#/definitions/blkio_limit"} }, - "weight": {"type": "integer"}, + "weight": {"type": ["integer", "string"]}, "weight_device": { "type": "array", "items": {"$ref": "#/definitions/blkio_weight"} @@ -162,8 +161,14 @@ "command": {"$ref": "#/definitions/command"}, "configs": {"$ref": "#/definitions/service_config_or_secret"}, "container_name": {"type": "string"}, - "cpu_count": {"type": "integer", "minimum": 0}, - "cpu_percent": {"type": "integer", "minimum": 0, "maximum": 100}, + "cpu_count": {"oneOf": [ + {"type": "string"}, + {"type": "integer", "minimum": 0} + ]}, + "cpu_percent": {"oneOf": [ + {"type": "string"}, + {"type": "integer", "minimum": 0, "maximum": 100} + ]}, "cpu_shares": {"type": ["number", "string"]}, "cpu_quota": {"type": ["number", "string"]}, "cpu_period": {"type": ["number", "string"]}, @@ -191,8 +196,9 @@ "^[a-zA-Z0-9._-]+$": { "type": "object", "additionalProperties": false, + "patternProperties": {"^x-": {}}, "properties": { - "restart": {"type": "boolean"}, + "restart": {"type": ["boolean", "string"]}, "required": { "type": "boolean", "default": true @@ -209,13 +215,31 @@ ] }, "device_cgroup_rules": {"$ref": "#/definitions/list_of_strings"}, - "devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, + "devices": { + "type": "array", + "items": { + "oneOf": [ + {"type": "string"}, + { + "type": "object", + "required": ["source"], + "properties": { + "source": {"type": "string"}, + "target": {"type": "string"}, + "permissions": {"type": "string"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} + } + ] + } + }, "dns": {"$ref": "#/definitions/string_or_list"}, "dns_opt": {"type": "array","items": {"type": "string"}, "uniqueItems": true}, "dns_search": {"$ref": "#/definitions/string_or_list"}, "domainname": {"type": "string"}, "entrypoint": {"$ref": "#/definitions/command"}, - "env_file": {"$ref": "#/definitions/string_or_list"}, + "env_file": {"$ref": "#/definitions/env_file"}, "environment": {"$ref": "#/definitions/list_or_dict"}, "expose": { @@ -242,7 +266,7 @@ ] }, "external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, - "extra_hosts": {"$ref": "#/definitions/list_or_dict"}, + "extra_hosts": {"$ref": "#/definitions/extra_hosts"}, "group_add": { "type": "array", "items": { @@ -253,7 +277,7 @@ "healthcheck": {"$ref": "#/definitions/healthcheck"}, "hostname": {"type": "string"}, "image": {"type": "string"}, - "init": {"type": "boolean"}, + "init": {"type": ["boolean", "string"]}, "ipc": {"type": "string"}, "isolation": {"type": "string"}, "labels": {"$ref": "#/definitions/list_or_dict"}, @@ -276,7 +300,7 @@ "mac_address": {"type": "string"}, "mem_limit": {"type": ["number", "string"]}, "mem_reservation": {"type": ["string", "integer"]}, - "mem_swappiness": {"type": "integer"}, + "mem_swappiness": {"type": ["integer", "string"]}, "memswap_limit": {"type": ["number", "string"]}, "network_mode": {"type": "string"}, "networks": { @@ -295,6 +319,12 @@ "ipv6_address": {"type": "string"}, "link_local_ips": {"$ref": "#/definitions/list_of_strings"}, "mac_address": {"type": "string"}, + "driver_opts": { + "type": "object", + "patternProperties": { + "^.+$": {"type": ["string", "number"]} + } + }, "priority": {"type": "number"} }, "additionalProperties": false, @@ -308,8 +338,11 @@ } ] }, - "oom_kill_disable": {"type": "boolean"}, - "oom_score_adj": {"type": "integer", "minimum": -1000, "maximum": 1000}, + "oom_kill_disable": {"type": ["boolean", "string"]}, + "oom_score_adj": {"oneOf": [ + {"type": "string"}, + {"type": "integer", "minimum": -1000, "maximum": 1000} + ]}, "pid": {"type": ["string", "null"]}, "pids_limit": {"type": ["number", "string"]}, "platform": {"type": "string"}, @@ -317,16 +350,18 @@ "type": "array", "items": { "oneOf": [ - {"type": "number", "format": "ports"}, - {"type": "string", "format": "ports"}, + {"type": "number"}, + {"type": "string"}, { "type": "object", "properties": { + "name": {"type": "string"}, "mode": {"type": "string"}, "host_ip": {"type": "string"}, - "target": {"type": "integer"}, + "target": {"type": ["integer", "string"]}, "published": {"type": ["string", "integer"]}, - "protocol": {"type": "string"} + "protocol": {"type": "string"}, + "app_protocol": {"type": "string"} }, "additionalProperties": false, "patternProperties": {"^x-": {}} @@ -335,29 +370,29 @@ }, "uniqueItems": true }, - "privileged": {"type": "boolean"}, + "privileged": {"type": ["boolean", "string"]}, "profiles": {"$ref": "#/definitions/list_of_strings"}, "pull_policy": {"type": "string", "enum": [ "always", "never", "if_not_present", "build", "missing" ]}, - "read_only": {"type": "boolean"}, + "read_only": {"type": ["boolean", "string"]}, "restart": {"type": "string"}, "runtime": { "type": "string" }, "scale": { - "type": "integer" + "type": ["integer", "string"] }, "security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, "shm_size": {"type": ["number", "string"]}, "secrets": {"$ref": "#/definitions/service_config_or_secret"}, "sysctls": {"$ref": "#/definitions/list_or_dict"}, - "stdin_open": {"type": "boolean"}, - "stop_grace_period": {"type": "string", "format": "duration"}, + "stdin_open": {"type": ["boolean", "string"]}, + "stop_grace_period": {"type": "string"}, "stop_signal": {"type": "string"}, "storage_opt": {"type": "object"}, "tmpfs": {"$ref": "#/definitions/string_or_list"}, - "tty": {"type": "boolean"}, + "tty": {"type": ["boolean", "string"]}, "ulimits": {"$ref": "#/definitions/ulimits"}, "user": {"type": "string"}, "uts": {"type": "string"}, @@ -374,13 +409,13 @@ "type": {"type": "string"}, "source": {"type": "string"}, "target": {"type": "string"}, - "read_only": {"type": "boolean"}, + "read_only": {"type": ["boolean", "string"]}, "consistency": {"type": "string"}, "bind": { "type": "object", "properties": { "propagation": {"type": "string"}, - "create_host_path": {"type": "boolean"}, + "create_host_path": {"type": ["boolean", "string"]}, "selinux": {"type": "string", "enum": ["z", "Z"]} }, "additionalProperties": false, @@ -389,7 +424,8 @@ "volume": { "type": "object", "properties": { - "nocopy": {"type": "boolean"} + "nocopy": {"type": ["boolean", "string"]}, + "subpath": {"type": "string"} }, "additionalProperties": false, "patternProperties": {"^x-": {}} @@ -403,7 +439,7 @@ {"type": "string"} ] }, - "mode": {"type": "number"} + "mode": {"type": ["number", "string"]} }, "additionalProperties": false, "patternProperties": {"^x-": {}} @@ -431,18 +467,18 @@ "id": "#/definitions/healthcheck", "type": "object", "properties": { - "disable": {"type": "boolean"}, - "interval": {"type": "string", "format": "duration"}, - "retries": {"type": "number"}, + "disable": {"type": ["boolean", "string"]}, + "interval": {"type": "string"}, + "retries": {"type": ["number", "string"]}, "test": { "oneOf": [ {"type": "string"}, {"type": "array", "items": {"type": "string"}} ] }, - "timeout": {"type": "string", "format": "duration"}, - "start_period": {"type": "string", "format": "duration"}, - "start_interval": {"type": "string", "format": "duration"} + "timeout": {"type": "string"}, + "start_period": {"type": "string"}, + "start_interval": {"type": "string"} }, "additionalProperties": false, "patternProperties": {"^x-": {}} @@ -455,14 +491,16 @@ "type": "array", "items": { "type": "object", + "required": ["path", "action"], "properties": { "ignore": {"type": "array", "items": {"type": "string"}}, "path": {"type": "string"}, "action": {"type": "string", "enum": ["rebuild", "sync", "sync+restart"]}, "target": {"type": "string"} - } + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} }, - "required": ["path", "action"], "additionalProperties": false, "patternProperties": {"^x-": {}} } @@ -474,16 +512,16 @@ "properties": { "mode": {"type": "string"}, "endpoint_mode": {"type": "string"}, - "replicas": {"type": "integer"}, + "replicas": {"type": ["integer", "string"]}, "labels": {"$ref": "#/definitions/list_or_dict"}, "rollback_config": { "type": "object", "properties": { - "parallelism": {"type": "integer"}, - "delay": {"type": "string", "format": "duration"}, + "parallelism": {"type": ["integer", "string"]}, + "delay": {"type": "string"}, "failure_action": {"type": "string"}, - "monitor": {"type": "string", "format": "duration"}, - "max_failure_ratio": {"type": "number"}, + "monitor": {"type": "string"}, + "max_failure_ratio": {"type": ["number", "string"]}, "order": {"type": "string", "enum": [ "start-first", "stop-first" ]} @@ -494,11 +532,11 @@ "update_config": { "type": "object", "properties": { - "parallelism": {"type": "integer"}, - "delay": {"type": "string", "format": "duration"}, + "parallelism": {"type": ["integer", "string"]}, + "delay": {"type": "string"}, "failure_action": {"type": "string"}, - "monitor": {"type": "string", "format": "duration"}, - "max_failure_ratio": {"type": "number"}, + "monitor": {"type": "string"}, + "max_failure_ratio": {"type": ["number", "string"]}, "order": {"type": "string", "enum": [ "start-first", "stop-first" ]} @@ -514,7 +552,7 @@ "properties": { "cpus": {"type": ["number", "string"]}, "memory": {"type": "string"}, - "pids": {"type": "integer"} + "pids": {"type": ["integer", "string"]} }, "additionalProperties": false, "patternProperties": {"^x-": {}} @@ -538,9 +576,9 @@ "type": "object", "properties": { "condition": {"type": "string"}, - "delay": {"type": "string", "format": "duration"}, - "max_attempts": {"type": "integer"}, - "window": {"type": "string", "format": "duration"} + "delay": {"type": "string"}, + "max_attempts": {"type": ["integer", "string"]}, + "window": {"type": "string"} }, "additionalProperties": false, "patternProperties": {"^x-": {}} @@ -560,7 +598,7 @@ "patternProperties": {"^x-": {}} } }, - "max_replicas_per_node": {"type": "integer"} + "max_replicas_per_node": {"type": ["integer", "string"]} }, "additionalProperties": false, "patternProperties": {"^x-": {}} @@ -580,7 +618,7 @@ "type": "object", "properties": { "kind": {"type": "string"}, - "value": {"type": "number"} + "value": {"type": ["number", "string"]} }, "additionalProperties": false, "patternProperties": {"^x-": {}} @@ -645,7 +683,7 @@ "items": { "type": "object", "properties": { - "subnet": {"type": "string", "format": "subnet_ip_address"}, + "subnet": {"type": "string"}, "ip_range": {"type": "string"}, "gateway": {"type": "string"}, "aux_addresses": { @@ -668,7 +706,7 @@ "patternProperties": {"^x-": {}} }, "external": { - "type": ["boolean", "object"], + "type": ["boolean", "string", "object"], "properties": { "name": { "deprecated": true, @@ -678,9 +716,9 @@ "additionalProperties": false, "patternProperties": {"^x-": {}} }, - "internal": {"type": "boolean"}, - "enable_ipv6": {"type": "boolean"}, - "attachable": {"type": "boolean"}, + "internal": {"type": ["boolean", "string"]}, + "enable_ipv6": {"type": ["boolean", "string"]}, + "attachable": {"type": ["boolean", "string"]}, "labels": {"$ref": "#/definitions/list_or_dict"} }, "additionalProperties": false, @@ -700,7 +738,7 @@ } }, "external": { - "type": ["boolean", "object"], + "type": ["boolean", "string", "object"], "properties": { "name": { "deprecated": true, @@ -724,7 +762,7 @@ "environment": {"type": "string"}, "file": {"type": "string"}, "external": { - "type": ["boolean", "object"], + "type": ["boolean", "string", "object"], "properties": { "name": {"type": "string"} } @@ -752,7 +790,7 @@ "environment": {"type": "string"}, "file": {"type": "string"}, "external": { - "type": ["boolean", "object"], + "type": ["boolean", "string", "object"], "properties": { "name": { "deprecated": true, @@ -775,6 +813,36 @@ ] }, + "env_file": { + "oneOf": [ + {"type": "string"}, + { + "type": "array", + "items": { + "oneOf": [ + {"type": "string"}, + { + "type": "object", + "additionalProperties": false, + "properties": { + "path": { + "type": "string" + }, + "required": { + "type": ["boolean", "string"], + "default": true + } + }, + "required": [ + "path" + ] + } + ] + } + } + ] + }, + "string_or_list": { "oneOf": [ {"type": "string"}, @@ -803,6 +871,21 @@ ] }, + "extra_hosts": { + "oneOf": [ + { + "type": "object", + "patternProperties": { + ".+": { + "type": ["string", "array"] + } + }, + "additionalProperties": false + }, + {"type": "array", "items": {"type": "string"}, "uniqueItems": true} + ] + }, + "blkio_limit": { "type": "object", "properties": { @@ -815,7 +898,7 @@ "type": "object", "properties": { "path": {"type": "string"}, - "weight": {"type": "integer"} + "weight": {"type": ["integer", "string"]} }, "additionalProperties": false }, @@ -831,7 +914,7 @@ "target": {"type": "string"}, "uid": {"type": "string"}, "gid": {"type": "string"}, - "mode": {"type": "number"} + "mode": {"type": ["number", "string"]} }, "additionalProperties": false, "patternProperties": {"^x-": {}} @@ -844,12 +927,12 @@ "patternProperties": { "^[a-z]+$": { "oneOf": [ - {"type": "integer"}, + {"type": ["integer", "string"]}, { "type": "object", "properties": { - "hard": {"type": "integer"}, - "soft": {"type": "integer"} + "hard": {"type": ["integer", "string"]}, + "soft": {"type": ["integer", "string"]} }, "required": ["soft", "hard"], "additionalProperties": false, diff --git a/vendor/github.com/compose-spec/compose-go/schema/schema.go b/vendor/github.com/compose-spec/compose-go/v2/schema/schema.go similarity index 100% rename from vendor/github.com/compose-spec/compose-go/schema/schema.go rename to vendor/github.com/compose-spec/compose-go/v2/schema/schema.go diff --git a/vendor/github.com/compose-spec/compose-go/v2/schema/using-variables.yaml b/vendor/github.com/compose-spec/compose-go/v2/schema/using-variables.yaml new file mode 100644 index 000000000..3f302cd6e --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/schema/using-variables.yaml @@ -0,0 +1,123 @@ +name: ${VARIABLE} +services: + foo: + deploy: + mode: ${VARIABLE} + replicas: ${VARIABLE} + rollback_config: + parallelism: ${VARIABLE} + delay: ${VARIABLE} + failure_action: ${VARIABLE} + monitor: ${VARIABLE} + max_failure_ratio: ${VARIABLE} + update_config: + parallelism: ${VARIABLE} + delay: ${VARIABLE} + failure_action: ${VARIABLE} + monitor: ${VARIABLE} + max_failure_ratio: ${VARIABLE} + resources: + limits: + memory: ${VARIABLE} + reservations: + memory: ${VARIABLE} + generic_resources: + - discrete_resource_spec: + kind: ${VARIABLE} + value: ${VARIABLE} + - discrete_resource_spec: + kind: ${VARIABLE} + value: ${VARIABLE} + restart_policy: + condition: ${VARIABLE} + delay: ${VARIABLE} + max_attempts: ${VARIABLE} + window: ${VARIABLE} + placement: + max_replicas_per_node: ${VARIABLE} + preferences: + - spread: ${VARIABLE} + endpoint_mode: ${VARIABLE} + expose: + - ${VARIABLE} + external_links: + - ${VARIABLE} + extra_hosts: + - ${VARIABLE} + hostname: ${VARIABLE} + + healthcheck: + test: ${VARIABLE} + interval: ${VARIABLE} + timeout: ${VARIABLE} + retries: ${VARIABLE} + start_period: ${VARIABLE} + start_interval: ${VARIABLE} + image: ${VARIABLE} + mac_address: ${VARIABLE} + networks: + some-network: + aliases: + - ${VARIABLE} + other-network: + ipv4_address: ${VARIABLE} + ipv6_address: ${VARIABLE} + mac_address: ${VARIABLE} + ports: + - ${VARIABLE} + privileged: ${VARIABLE} + read_only: ${VARIABLE} + restart: ${VARIABLE} + secrets: + - source: ${VARIABLE} + target: ${VARIABLE} + uid: ${VARIABLE} + gid: ${VARIABLE} + mode: ${VARIABLE} + stdin_open: ${VARIABLE} + stop_grace_period: ${VARIABLE} + stop_signal: ${VARIABLE} + storage_opt: + size: ${VARIABLE} + sysctls: + net.core.somaxconn: ${VARIABLE} + tmpfs: + - ${VARIABLE} + tty: ${VARIABLE} + ulimits: + nproc: ${VARIABLE} + nofile: + soft: ${VARIABLE} + hard: ${VARIABLE} + user: ${VARIABLE} + volumes: + - ${VARIABLE}:${VARIABLE} + - type: tmpfs + target: ${VARIABLE} + tmpfs: + size: ${VARIABLE} + +networks: + network: + ipam: + driver: ${VARIABLE} + config: + - subnet: ${VARIABLE} + ip_range: ${VARIABLE} + gateway: ${VARIABLE} + aux_addresses: + host1: ${VARIABLE} + external-network: + external: ${VARIABLE} + +volumes: + external-volume: + external: ${VARIABLE} + +configs: + config1: + external: ${VARIABLE} + +secrets: + secret1: + external: ${VARIABLE} diff --git a/vendor/github.com/compose-spec/compose-go/template/template.go b/vendor/github.com/compose-spec/compose-go/v2/template/template.go similarity index 79% rename from vendor/github.com/compose-spec/compose-go/template/template.go rename to vendor/github.com/compose-spec/compose-go/v2/template/template.go index 9367f3954..d9483cbdb 100644 --- a/vendor/github.com/compose-spec/compose-go/template/template.go +++ b/vendor/github.com/compose-spec/compose-go/v2/template/template.go @@ -44,7 +44,7 @@ var patternString = fmt.Sprintf( groupInvalid, ) -var defaultPattern = regexp.MustCompile(patternString) +var DefaultPattern = regexp.MustCompile(patternString) // InvalidTemplateError is returned when a variable template is not in a valid // format @@ -121,7 +121,7 @@ func SubstituteWithOptions(template string, mapping Mapping, options ...Option) var returnErr error cfg := &Config{ - pattern: defaultPattern, + pattern: DefaultPattern, replacementFunc: DefaultReplacementFunc, logging: true, } @@ -258,7 +258,7 @@ func getFirstBraceClosingIndex(s string) int { return i } } - if strings.HasPrefix(s[i:], "${") { + if s[i] == '{' { openVariableBraces++ i++ } @@ -268,103 +268,7 @@ func getFirstBraceClosingIndex(s string) int { // Substitute variables in the string with their values func Substitute(template string, mapping Mapping) (string, error) { - return SubstituteWith(template, mapping, defaultPattern) -} - -// ExtractVariables returns a map of all the variables defined in the specified -// composefile (dict representation) and their default value if any. -func ExtractVariables(configDict map[string]interface{}, pattern *regexp.Regexp) map[string]Variable { - if pattern == nil { - pattern = defaultPattern - } - return recurseExtract(configDict, pattern) -} - -func recurseExtract(value interface{}, pattern *regexp.Regexp) map[string]Variable { - m := map[string]Variable{} - - switch value := value.(type) { - case string: - if values, is := extractVariable(value, pattern); is { - for _, v := range values { - m[v.Name] = v - } - } - case map[string]interface{}: - for _, elem := range value { - submap := recurseExtract(elem, pattern) - for key, value := range submap { - m[key] = value - } - } - - case []interface{}: - for _, elem := range value { - if values, is := extractVariable(elem, pattern); is { - for _, v := range values { - m[v.Name] = v - } - } - } - } - - return m -} - -type Variable struct { - Name string - DefaultValue string - PresenceValue string - Required bool -} - -func extractVariable(value interface{}, pattern *regexp.Regexp) ([]Variable, bool) { - sValue, ok := value.(string) - if !ok { - return []Variable{}, false - } - matches := pattern.FindAllStringSubmatch(sValue, -1) - if len(matches) == 0 { - return []Variable{}, false - } - values := []Variable{} - for _, match := range matches { - groups := matchGroups(match, pattern) - if escaped := groups[groupEscaped]; escaped != "" { - continue - } - val := groups[groupNamed] - if val == "" { - val = groups[groupBraced] - } - name := val - var defaultValue string - var presenceValue string - var required bool - switch { - case strings.Contains(val, ":?"): - name, _ = partition(val, ":?") - required = true - case strings.Contains(val, "?"): - name, _ = partition(val, "?") - required = true - case strings.Contains(val, ":-"): - name, defaultValue = partition(val, ":-") - case strings.Contains(val, "-"): - name, defaultValue = partition(val, "-") - case strings.Contains(val, ":+"): - name, presenceValue = partition(val, ":+") - case strings.Contains(val, "+"): - name, presenceValue = partition(val, "+") - } - values = append(values, Variable{ - Name: name, - DefaultValue: defaultValue, - PresenceValue: presenceValue, - Required: required, - }) - } - return values, len(values) > 0 + return SubstituteWith(template, mapping, DefaultPattern) } // Soft default (fall back if unset or empty) diff --git a/vendor/github.com/compose-spec/compose-go/v2/template/variables.go b/vendor/github.com/compose-spec/compose-go/v2/template/variables.go new file mode 100644 index 000000000..fb32f9c37 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/template/variables.go @@ -0,0 +1,155 @@ +/* + Copyright 2020 The Compose Specification 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 template + +import ( + "regexp" + "strings" +) + +type Variable struct { + Name string + DefaultValue string + PresenceValue string + Required bool +} + +// ExtractVariables returns a map of all the variables defined in the specified +// compose file (dict representation) and their default value if any. +func ExtractVariables(configDict map[string]interface{}, pattern *regexp.Regexp) map[string]Variable { + if pattern == nil { + pattern = DefaultPattern + } + return recurseExtract(configDict, pattern) +} + +func recurseExtract(value interface{}, pattern *regexp.Regexp) map[string]Variable { + m := map[string]Variable{} + + switch value := value.(type) { + case string: + if values, is := extractVariable(value, pattern); is { + for _, v := range values { + m[v.Name] = v + } + } + case map[string]interface{}: + for _, elem := range value { + submap := recurseExtract(elem, pattern) + for key, value := range submap { + m[key] = value + } + } + + case []interface{}: + for _, elem := range value { + if values, is := extractVariable(elem, pattern); is { + for _, v := range values { + m[v.Name] = v + } + } + } + } + + return m +} + +func extractVariable(value interface{}, pattern *regexp.Regexp) ([]Variable, bool) { + sValue, ok := value.(string) + if !ok { + return []Variable{}, false + } + matches := pattern.FindAllStringSubmatch(sValue, -1) + if len(matches) == 0 { + return []Variable{}, false + } + values := []Variable{} + for _, match := range matches { + groups := matchGroups(match, pattern) + if escaped := groups[groupEscaped]; escaped != "" { + continue + } + val := groups[groupNamed] + if val == "" { + val = groups[groupBraced] + s := match[0] + i := getFirstBraceClosingIndex(s) + if i > 0 { + val = s[2:i] + if len(s) > i { + if v, b := extractVariable(s[i+1:], pattern); b { + values = append(values, v...) + } + } + } + } + name := val + var defaultValue string + var presenceValue string + var required bool + i := strings.IndexFunc(val, func(r rune) bool { + if r >= 'a' && r <= 'z' { + return false + } + if r >= 'A' && r <= 'Z' { + return false + } + if r == '_' { + return false + } + return true + }) + + if i > 0 { + name = val[:i] + rest := val[i:] + switch { + case strings.HasPrefix(rest, ":?"): + required = true + case strings.HasPrefix(rest, "?"): + required = true + case strings.HasPrefix(rest, ":-"): + defaultValue = rest[2:] + case strings.HasPrefix(rest, "-"): + defaultValue = rest[1:] + case strings.HasPrefix(rest, ":+"): + presenceValue = rest[2:] + case strings.HasPrefix(rest, "+"): + presenceValue = rest[1:] + } + } + + values = append(values, Variable{ + Name: name, + DefaultValue: defaultValue, + PresenceValue: presenceValue, + Required: required, + }) + + if defaultValue != "" { + if v, b := extractVariable(defaultValue, pattern); b { + values = append(values, v...) + } + } + if presenceValue != "" { + if v, b := extractVariable(presenceValue, pattern); b { + values = append(values, v...) + } + } + } + return values, len(values) > 0 +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/build.go b/vendor/github.com/compose-spec/compose-go/v2/transform/build.go new file mode 100644 index 000000000..90a996ccd --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/build.go @@ -0,0 +1,48 @@ +/* + Copyright 2020 The Compose Specification 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 transform + +import ( + "fmt" + + "github.com/compose-spec/compose-go/v2/tree" +) + +func transformBuild(data any, p tree.Path, ignoreParseError bool) (any, error) { + switch v := data.(type) { + case map[string]any: + return transformMapping(v, p, ignoreParseError) + case string: + return map[string]any{ + "context": v, + }, nil + default: + return data, fmt.Errorf("%s: invalid type %T for build", p, v) + } +} + +func defaultBuildContext(data any, _ tree.Path, _ bool) (any, error) { + switch v := data.(type) { + case map[string]any: + if _, ok := v["context"]; !ok { + v["context"] = "." + } + return v, nil + default: + return data, nil + } +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/canonical.go b/vendor/github.com/compose-spec/compose-go/v2/transform/canonical.go new file mode 100644 index 000000000..a248b4be2 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/canonical.go @@ -0,0 +1,108 @@ +/* + Copyright 2020 The Compose Specification 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 transform + +import ( + "github.com/compose-spec/compose-go/v2/tree" +) + +type transformFunc func(data any, p tree.Path, ignoreParseError bool) (any, error) + +var transformers = map[tree.Path]transformFunc{} + +func init() { + transformers["services.*"] = transformService + transformers["services.*.build.secrets.*"] = transformFileMount + transformers["services.*.build.additional_contexts"] = transformKeyValue + transformers["services.*.depends_on"] = transformDependsOn + transformers["services.*.env_file"] = transformEnvFile + transformers["services.*.extends"] = transformExtends + transformers["services.*.networks"] = transformServiceNetworks + transformers["services.*.volumes.*"] = transformVolumeMount + transformers["services.*.devices.*"] = transformDeviceMapping + transformers["services.*.secrets.*"] = transformFileMount + transformers["services.*.configs.*"] = transformFileMount + transformers["services.*.ports"] = transformPorts + transformers["services.*.build"] = transformBuild + transformers["services.*.build.ssh"] = transformSSH + transformers["services.*.ulimits.*"] = transformUlimits + transformers["services.*.build.ulimits.*"] = transformUlimits + transformers["volumes.*"] = transformMaybeExternal + transformers["networks.*"] = transformMaybeExternal + transformers["secrets.*"] = transformMaybeExternal + transformers["configs.*"] = transformMaybeExternal + transformers["include.*"] = transformInclude +} + +// Canonical transforms a compose model into canonical syntax +func Canonical(yaml map[string]any, ignoreParseError bool) (map[string]any, error) { + canonical, err := transform(yaml, tree.NewPath(), ignoreParseError) + if err != nil { + return nil, err + } + return canonical.(map[string]any), nil +} + +func transform(data any, p tree.Path, ignoreParseError bool) (any, error) { + for pattern, transformer := range transformers { + if p.Matches(pattern) { + t, err := transformer(data, p, ignoreParseError) + if err != nil { + return nil, err + } + return t, nil + } + } + switch v := data.(type) { + case map[string]any: + a, err := transformMapping(v, p, ignoreParseError) + if err != nil { + return a, err + } + return v, nil + case []any: + a, err := transformSequence(v, p, ignoreParseError) + if err != nil { + return a, err + } + return v, nil + default: + return data, nil + } +} + +func transformSequence(v []any, p tree.Path, ignoreParseError bool) ([]any, error) { + for i, e := range v { + t, err := transform(e, p.Next("[]"), ignoreParseError) + if err != nil { + return nil, err + } + v[i] = t + } + return v, nil +} + +func transformMapping(v map[string]any, p tree.Path, ignoreParseError bool) (map[string]any, error) { + for k, e := range v { + t, err := transform(e, p.Next(k), ignoreParseError) + if err != nil { + return nil, err + } + v[k] = t + } + return v, nil +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/defaults.go b/vendor/github.com/compose-spec/compose-go/v2/transform/defaults.go new file mode 100644 index 000000000..96693c652 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/defaults.go @@ -0,0 +1,88 @@ +/* + Copyright 2020 The Compose Specification 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 transform + +import ( + "github.com/compose-spec/compose-go/v2/tree" +) + +var defaultValues = map[tree.Path]transformFunc{} + +func init() { + defaultValues["services.*.build"] = defaultBuildContext + defaultValues["services.*.secrets.*"] = defaultSecretMount + defaultValues["services.*.ports.*"] = portDefaults +} + +// SetDefaultValues transforms a compose model to set default values to missing attributes +func SetDefaultValues(yaml map[string]any) (map[string]any, error) { + result, err := setDefaults(yaml, tree.NewPath()) + if err != nil { + return nil, err + } + return result.(map[string]any), nil +} + +func setDefaults(data any, p tree.Path) (any, error) { + for pattern, transformer := range defaultValues { + if p.Matches(pattern) { + t, err := transformer(data, p, false) + if err != nil { + return nil, err + } + return t, nil + } + } + switch v := data.(type) { + case map[string]any: + a, err := setDefaultsMapping(v, p) + if err != nil { + return a, err + } + return v, nil + case []any: + a, err := setDefaultsSequence(v, p) + if err != nil { + return a, err + } + return v, nil + default: + return data, nil + } +} + +func setDefaultsSequence(v []any, p tree.Path) ([]any, error) { + for i, e := range v { + t, err := setDefaults(e, p.Next("[]")) + if err != nil { + return nil, err + } + v[i] = t + } + return v, nil +} + +func setDefaultsMapping(v map[string]any, p tree.Path) (map[string]any, error) { + for k, e := range v { + t, err := setDefaults(e, p.Next(k)) + if err != nil { + return nil, err + } + v[k] = t + } + return v, nil +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/dependson.go b/vendor/github.com/compose-spec/compose-go/v2/transform/dependson.go new file mode 100644 index 000000000..0a72ffa41 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/dependson.go @@ -0,0 +1,53 @@ +/* + Copyright 2020 The Compose Specification 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 transform + +import ( + "fmt" + + "github.com/compose-spec/compose-go/v2/tree" +) + +func transformDependsOn(data any, p tree.Path, _ bool) (any, error) { + switch v := data.(type) { + case map[string]any: + for i, e := range v { + d, ok := e.(map[string]any) + if !ok { + return nil, fmt.Errorf("%s.%s: unsupported value %s", p, i, v) + } + if _, ok := d["condition"]; !ok { + d["condition"] = "service_started" + } + if _, ok := d["required"]; !ok { + d["required"] = true + } + } + return v, nil + case []any: + d := map[string]any{} + for _, k := range v { + d[k.(string)] = map[string]any{ + "condition": "service_started", + "required": true, + } + } + return d, nil + default: + return data, fmt.Errorf("%s: invalid type %T for depend_on", p, v) + } +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/device.go b/vendor/github.com/compose-spec/compose-go/v2/transform/device.go new file mode 100644 index 000000000..351d81513 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/device.go @@ -0,0 +1,60 @@ +/* + Copyright 2020 The Compose Specification 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 transform + +import ( + "fmt" + "strings" + + "github.com/compose-spec/compose-go/v2/tree" +) + +func transformDeviceMapping(data any, p tree.Path, ignoreParseError bool) (any, error) { + switch v := data.(type) { + case map[string]any: + return v, nil + case string: + src := "" + dst := "" + permissions := "rwm" + arr := strings.Split(v, ":") + switch len(arr) { + case 3: + permissions = arr[2] + fallthrough + case 2: + dst = arr[1] + fallthrough + case 1: + src = arr[0] + default: + if !ignoreParseError { + return nil, fmt.Errorf("confusing device mapping, please use long syntax: %s", v) + } + } + if dst == "" { + dst = src + } + return map[string]any{ + "source": src, + "target": dst, + "permissions": permissions, + }, nil + default: + return data, fmt.Errorf("%s: invalid type %T for service volume mount", p, v) + } +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/envfile.go b/vendor/github.com/compose-spec/compose-go/v2/transform/envfile.go new file mode 100644 index 000000000..e51005309 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/envfile.go @@ -0,0 +1,55 @@ +/* + Copyright 2020 The Compose Specification 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 transform + +import ( + "fmt" + + "github.com/compose-spec/compose-go/v2/tree" +) + +func transformEnvFile(data any, p tree.Path, _ bool) (any, error) { + switch v := data.(type) { + case string: + return []any{ + transformEnvFileValue(v), + }, nil + case []any: + for i, e := range v { + v[i] = transformEnvFileValue(e) + } + return v, nil + default: + return nil, fmt.Errorf("%s: invalid type %T for env_file", p, v) + } +} + +func transformEnvFileValue(data any) any { + switch v := data.(type) { + case string: + return map[string]any{ + "path": v, + "required": true, + } + case map[string]any: + if _, ok := v["required"]; !ok { + v["required"] = true + } + return v + } + return nil +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/extends.go b/vendor/github.com/compose-spec/compose-go/v2/transform/extends.go new file mode 100644 index 000000000..e0f9be2d1 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/extends.go @@ -0,0 +1,36 @@ +/* + Copyright 2020 The Compose Specification 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 transform + +import ( + "fmt" + + "github.com/compose-spec/compose-go/v2/tree" +) + +func transformExtends(data any, p tree.Path, ignoreParseError bool) (any, error) { + switch v := data.(type) { + case map[string]any: + return transformMapping(v, p, ignoreParseError) + case string: + return map[string]any{ + "service": v, + }, nil + default: + return data, fmt.Errorf("%s: invalid type %T for extends", p, v) + } +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/external.go b/vendor/github.com/compose-spec/compose-go/v2/transform/external.go new file mode 100644 index 000000000..be718f03c --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/external.go @@ -0,0 +1,54 @@ +/* + Copyright 2020 The Compose Specification 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 transform + +import ( + "fmt" + + "github.com/compose-spec/compose-go/v2/tree" + "github.com/sirupsen/logrus" +) + +func transformMaybeExternal(data any, p tree.Path, ignoreParseError bool) (any, error) { + if data == nil { + return nil, nil + } + resource, err := transformMapping(data.(map[string]any), p, ignoreParseError) + if err != nil { + return nil, err + } + + if ext, ok := resource["external"]; ok { + name, named := resource["name"] + if external, ok := ext.(map[string]any); ok { + resource["external"] = true + if extname, extNamed := external["name"]; extNamed { + logrus.Warnf("%s: external.name is deprecated. Please set name and external: true", p) + if named && extname != name { + return nil, fmt.Errorf("%s: name and external.name conflict; only use name", p) + } + if !named { + // adopt (deprecated) external.name if set + resource["name"] = extname + return resource, nil + } + } + } + } + + return resource, nil +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/include.go b/vendor/github.com/compose-spec/compose-go/v2/transform/include.go new file mode 100644 index 000000000..8a80439e4 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/include.go @@ -0,0 +1,36 @@ +/* + Copyright 2020 The Compose Specification 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 transform + +import ( + "fmt" + + "github.com/compose-spec/compose-go/v2/tree" +) + +func transformInclude(data any, p tree.Path, _ bool) (any, error) { + switch v := data.(type) { + case map[string]any: + return v, nil + case string: + return map[string]any{ + "path": v, + }, nil + default: + return data, fmt.Errorf("%s: invalid type %T for external", p, v) + } +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/mapping.go b/vendor/github.com/compose-spec/compose-go/v2/transform/mapping.go new file mode 100644 index 000000000..007aa9ed5 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/mapping.go @@ -0,0 +1,46 @@ +/* + Copyright 2020 The Compose Specification 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 transform + +import ( + "fmt" + "strings" + + "github.com/compose-spec/compose-go/v2/tree" +) + +func transformKeyValue(data any, p tree.Path, ignoreParseError bool) (any, error) { + switch v := data.(type) { + case map[string]any: + return v, nil + case []any: + mapping := map[string]any{} + for _, e := range v { + before, after, found := strings.Cut(e.(string), "=") + if !found { + if ignoreParseError { + return data, nil + } + return nil, fmt.Errorf("%s: invalid value %s, expected key=value", p, e) + } + mapping[before] = after + } + return mapping, nil + default: + return nil, fmt.Errorf("%s: invalid type %T", p, v) + } +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/ports.go b/vendor/github.com/compose-spec/compose-go/v2/transform/ports.go new file mode 100644 index 000000000..68e26f3d4 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/ports.go @@ -0,0 +1,104 @@ +/* + Copyright 2020 The Compose Specification 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 transform + +import ( + "fmt" + + "github.com/compose-spec/compose-go/v2/tree" + "github.com/compose-spec/compose-go/v2/types" + "github.com/go-viper/mapstructure/v2" +) + +func transformPorts(data any, p tree.Path, ignoreParseError bool) (any, error) { + switch entries := data.(type) { + case []any: + // We process the list instead of individual items here. + // The reason is that one entry might be mapped to multiple ServicePortConfig. + // Therefore we take an input of a list and return an output of a list. + var ports []any + for _, entry := range entries { + switch value := entry.(type) { + case int: + parsed, err := types.ParsePortConfig(fmt.Sprint(value)) + if err != nil { + return data, err + } + for _, v := range parsed { + m, err := encode(v) + if err != nil { + return nil, err + } + ports = append(ports, m) + } + case string: + parsed, err := types.ParsePortConfig(value) + if err != nil { + if ignoreParseError { + return data, nil + } + return nil, err + } + if err != nil { + return nil, err + } + for _, v := range parsed { + m, err := encode(v) + if err != nil { + return nil, err + } + ports = append(ports, m) + } + case map[string]any: + ports = append(ports, value) + default: + return data, fmt.Errorf("%s: invalid type %T for port", p, value) + } + } + return ports, nil + default: + return data, fmt.Errorf("%s: invalid type %T for port", p, entries) + } +} + +func encode(v any) (map[string]any, error) { + m := map[string]any{} + decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + Result: &m, + TagName: "yaml", + }) + if err != nil { + return nil, err + } + err = decoder.Decode(v) + return m, err +} + +func portDefaults(data any, _ tree.Path, _ bool) (any, error) { + switch v := data.(type) { + case map[string]any: + if _, ok := v["protocol"]; !ok { + v["protocol"] = "tcp" + } + if _, ok := v["mode"]; !ok { + v["mode"] = "ingress" + } + return v, nil + default: + return data, nil + } +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/secrets.go b/vendor/github.com/compose-spec/compose-go/v2/transform/secrets.go new file mode 100644 index 000000000..c2db13523 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/secrets.go @@ -0,0 +1,49 @@ +/* + Copyright 2020 The Compose Specification 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 transform + +import ( + "fmt" + + "github.com/compose-spec/compose-go/v2/tree" +) + +func transformFileMount(data any, p tree.Path, _ bool) (any, error) { + switch v := data.(type) { + case map[string]any: + return data, nil + case string: + return map[string]any{ + "source": v, + }, nil + default: + return nil, fmt.Errorf("%s: unsupported type %T", p, data) + } +} + +func defaultSecretMount(data any, p tree.Path, _ bool) (any, error) { + switch v := data.(type) { + case map[string]any: + source := v["source"] + if _, ok := v["target"]; !ok { + v["target"] = fmt.Sprintf("/run/secrets/%s", source) + } + return v, nil + default: + return nil, fmt.Errorf("%s: unsupported type %T", p, data) + } +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/services.go b/vendor/github.com/compose-spec/compose-go/v2/transform/services.go new file mode 100644 index 000000000..960c3e7ec --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/services.go @@ -0,0 +1,41 @@ +/* + Copyright 2020 The Compose Specification 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 transform + +import ( + "github.com/compose-spec/compose-go/v2/tree" +) + +func transformService(data any, p tree.Path, ignoreParseError bool) (any, error) { + switch value := data.(type) { + case map[string]any: + return transformMapping(value, p, ignoreParseError) + default: + return value, nil + } +} + +func transformServiceNetworks(data any, _ tree.Path, _ bool) (any, error) { + if slice, ok := data.([]any); ok { + networks := make(map[string]any, len(slice)) + for _, net := range slice { + networks[net.(string)] = nil + } + return networks, nil + } + return data, nil +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/ssh.go b/vendor/github.com/compose-spec/compose-go/v2/transform/ssh.go new file mode 100644 index 000000000..2663461e9 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/ssh.go @@ -0,0 +1,51 @@ +/* + Copyright 2020 The Compose Specification 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 transform + +import ( + "fmt" + "strings" + + "github.com/compose-spec/compose-go/v2/tree" +) + +func transformSSH(data any, p tree.Path, _ bool) (any, error) { + switch v := data.(type) { + case map[string]any: + return v, nil + case []any: + result := make(map[string]any, len(v)) + for _, e := range v { + s, ok := e.(string) + if !ok { + return nil, fmt.Errorf("invalid ssh key type %T", e) + } + id, path, ok := strings.Cut(s, "=") + if !ok { + if id != "default" { + return nil, fmt.Errorf("invalid ssh key %q", s) + } + result[id] = nil + continue + } + result[id] = path + } + return result, nil + default: + return data, fmt.Errorf("%s: invalid type %T for ssh", p, v) + } +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/ulimits.go b/vendor/github.com/compose-spec/compose-go/v2/transform/ulimits.go new file mode 100644 index 000000000..57cce4fb6 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/ulimits.go @@ -0,0 +1,34 @@ +/* + Copyright 2020 The Compose Specification 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 transform + +import ( + "fmt" + + "github.com/compose-spec/compose-go/v2/tree" +) + +func transformUlimits(data any, p tree.Path, _ bool) (any, error) { + switch v := data.(type) { + case map[string]any: + return v, nil + case int: + return v, nil + default: + return data, fmt.Errorf("%s: invalid type %T for external", p, v) + } +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/volume.go b/vendor/github.com/compose-spec/compose-go/v2/transform/volume.go new file mode 100644 index 000000000..b08e8b1aa --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/volume.go @@ -0,0 +1,52 @@ +/* + Copyright 2020 The Compose Specification 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 transform + +import ( + "fmt" + "path" + + "github.com/compose-spec/compose-go/v2/format" + "github.com/compose-spec/compose-go/v2/tree" +) + +func transformVolumeMount(data any, p tree.Path, ignoreParseError bool) (any, error) { + switch v := data.(type) { + case map[string]any: + return v, nil + case string: + volume, err := format.ParseVolume(v) // TODO(ndeloof) ParseVolume should not rely on types and return map[string] + if err != nil { + if ignoreParseError { + return v, nil + } + return nil, err + } + volume.Target = cleanTarget(volume.Target) + + return encode(volume) + default: + return data, fmt.Errorf("%s: invalid type %T for service volume mount", p, v) + } +} + +func cleanTarget(target string) string { + if target == "" { + return "" + } + return path.Clean(target) +} diff --git a/vendor/github.com/compose-spec/compose-go/tree/path.go b/vendor/github.com/compose-spec/compose-go/v2/tree/path.go similarity index 81% rename from vendor/github.com/compose-spec/compose-go/tree/path.go rename to vendor/github.com/compose-spec/compose-go/v2/tree/path.go index 59c250307..f8a8d9a64 100644 --- a/vendor/github.com/compose-spec/compose-go/tree/path.go +++ b/vendor/github.com/compose-spec/compose-go/v2/tree/path.go @@ -16,7 +16,9 @@ package tree -import "strings" +import ( + "strings" +) const pathSeparator = "." @@ -41,6 +43,7 @@ func (p Path) Next(part string) Path { if p == "" { return Path(part) } + part = strings.ReplaceAll(part, pathSeparator, "👻") return Path(string(p) + pathSeparator + part) } @@ -65,3 +68,20 @@ func (p Path) Matches(pattern Path) bool { } return true } + +func (p Path) Last() string { + parts := p.Parts() + return parts[len(parts)-1] +} + +func (p Path) Parent() Path { + index := strings.LastIndex(string(p), pathSeparator) + if index > 0 { + return p[0:index] + } + return "" +} + +func (p Path) String() string { + return strings.ReplaceAll(string(p), "👻", pathSeparator) +} diff --git a/vendor/github.com/compose-spec/compose-go/types/bytes.go b/vendor/github.com/compose-spec/compose-go/v2/types/bytes.go similarity index 86% rename from vendor/github.com/compose-spec/compose-go/types/bytes.go rename to vendor/github.com/compose-spec/compose-go/v2/types/bytes.go index 4c873cded..1b2cd4196 100644 --- a/vendor/github.com/compose-spec/compose-go/types/bytes.go +++ b/vendor/github.com/compose-spec/compose-go/v2/types/bytes.go @@ -36,7 +36,13 @@ func (u UnitBytes) MarshalJSON() ([]byte, error) { } func (u *UnitBytes) DecodeMapstructure(value interface{}) error { - v, err := units.RAMInBytes(fmt.Sprint(value)) - *u = UnitBytes(v) - return err + switch v := value.(type) { + case int: + *u = UnitBytes(v) + case string: + b, err := units.RAMInBytes(fmt.Sprint(value)) + *u = UnitBytes(b) + return err + } + return nil } diff --git a/vendor/github.com/compose-spec/compose-go/types/command.go b/vendor/github.com/compose-spec/compose-go/v2/types/command.go similarity index 100% rename from vendor/github.com/compose-spec/compose-go/types/command.go rename to vendor/github.com/compose-spec/compose-go/v2/types/command.go diff --git a/vendor/github.com/compose-spec/compose-go/types/config.go b/vendor/github.com/compose-spec/compose-go/v2/types/config.go similarity index 94% rename from vendor/github.com/compose-spec/compose-go/types/config.go rename to vendor/github.com/compose-spec/compose-go/v2/types/config.go index 25e6f82ee..a2382fb3d 100644 --- a/vendor/github.com/compose-spec/compose-go/types/config.go +++ b/vendor/github.com/compose-spec/compose-go/v2/types/config.go @@ -21,7 +21,7 @@ import ( "runtime" "strings" - "github.com/mitchellh/mapstructure" + "github.com/go-viper/mapstructure/v2" ) var ( @@ -38,7 +38,7 @@ type ConfigDetails struct { } // LookupEnv provides a lookup function for environment variables -func (cd ConfigDetails) LookupEnv(key string) (string, bool) { +func (cd *ConfigDetails) LookupEnv(key string) (string, bool) { v, ok := cd.Environment[key] if !isCaseInsensitiveEnvVars || ok { return v, ok @@ -100,7 +100,13 @@ type Secrets map[string]SecretConfig type Configs map[string]ConfigObjConfig // Extensions is a map of custom extension -type Extensions map[string]interface{} +type Extensions map[string]any + +func (e Extensions) DeepCopy(t Extensions) { + for k, v := range e { + t[k] = v + } +} // MarshalJSON makes Config implement json.Marshaler func (c Config) MarshalJSON() ([]byte, error) { diff --git a/vendor/github.com/compose-spec/compose-go/v2/types/derived.gen.go b/vendor/github.com/compose-spec/compose-go/v2/types/derived.gen.go new file mode 100644 index 000000000..d5d1d0248 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/types/derived.gen.go @@ -0,0 +1,2078 @@ +// Code generated by goderive DO NOT EDIT. + +package types + +// deriveDeepCopyProject recursively copies the contents of src into dst. +func deriveDeepCopyProject(dst, src *Project) { + dst.Name = src.Name + dst.WorkingDir = src.WorkingDir + if src.Services != nil { + dst.Services = make(map[string]ServiceConfig, len(src.Services)) + deriveDeepCopy(dst.Services, src.Services) + } else { + dst.Services = nil + } + if src.Networks != nil { + dst.Networks = make(map[string]NetworkConfig, len(src.Networks)) + deriveDeepCopy_(dst.Networks, src.Networks) + } else { + dst.Networks = nil + } + if src.Volumes != nil { + dst.Volumes = make(map[string]VolumeConfig, len(src.Volumes)) + deriveDeepCopy_1(dst.Volumes, src.Volumes) + } else { + dst.Volumes = nil + } + if src.Secrets != nil { + dst.Secrets = make(map[string]SecretConfig, len(src.Secrets)) + deriveDeepCopy_2(dst.Secrets, src.Secrets) + } else { + dst.Secrets = nil + } + if src.Configs != nil { + dst.Configs = make(map[string]ConfigObjConfig, len(src.Configs)) + deriveDeepCopy_3(dst.Configs, src.Configs) + } else { + dst.Configs = nil + } + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } + if src.ComposeFiles == nil { + dst.ComposeFiles = nil + } else { + if dst.ComposeFiles != nil { + if len(src.ComposeFiles) > len(dst.ComposeFiles) { + if cap(dst.ComposeFiles) >= len(src.ComposeFiles) { + dst.ComposeFiles = (dst.ComposeFiles)[:len(src.ComposeFiles)] + } else { + dst.ComposeFiles = make([]string, len(src.ComposeFiles)) + } + } else if len(src.ComposeFiles) < len(dst.ComposeFiles) { + dst.ComposeFiles = (dst.ComposeFiles)[:len(src.ComposeFiles)] + } + } else { + dst.ComposeFiles = make([]string, len(src.ComposeFiles)) + } + copy(dst.ComposeFiles, src.ComposeFiles) + } + if src.Environment != nil { + dst.Environment = make(map[string]string, len(src.Environment)) + deriveDeepCopy_4(dst.Environment, src.Environment) + } else { + dst.Environment = nil + } + if src.DisabledServices != nil { + dst.DisabledServices = make(map[string]ServiceConfig, len(src.DisabledServices)) + deriveDeepCopy(dst.DisabledServices, src.DisabledServices) + } else { + dst.DisabledServices = nil + } + if src.Profiles == nil { + dst.Profiles = nil + } else { + if dst.Profiles != nil { + if len(src.Profiles) > len(dst.Profiles) { + if cap(dst.Profiles) >= len(src.Profiles) { + dst.Profiles = (dst.Profiles)[:len(src.Profiles)] + } else { + dst.Profiles = make([]string, len(src.Profiles)) + } + } else if len(src.Profiles) < len(dst.Profiles) { + dst.Profiles = (dst.Profiles)[:len(src.Profiles)] + } + } else { + dst.Profiles = make([]string, len(src.Profiles)) + } + copy(dst.Profiles, src.Profiles) + } +} + +// deriveDeepCopyService recursively copies the contents of src into dst. +func deriveDeepCopyService(dst, src *ServiceConfig) { + dst.Name = src.Name + if src.Profiles == nil { + dst.Profiles = nil + } else { + if dst.Profiles != nil { + if len(src.Profiles) > len(dst.Profiles) { + if cap(dst.Profiles) >= len(src.Profiles) { + dst.Profiles = (dst.Profiles)[:len(src.Profiles)] + } else { + dst.Profiles = make([]string, len(src.Profiles)) + } + } else if len(src.Profiles) < len(dst.Profiles) { + dst.Profiles = (dst.Profiles)[:len(src.Profiles)] + } + } else { + dst.Profiles = make([]string, len(src.Profiles)) + } + copy(dst.Profiles, src.Profiles) + } + if src.Annotations != nil { + dst.Annotations = make(map[string]string, len(src.Annotations)) + deriveDeepCopy_4(dst.Annotations, src.Annotations) + } else { + dst.Annotations = nil + } + if src.Attach == nil { + dst.Attach = nil + } else { + dst.Attach = new(bool) + *dst.Attach = *src.Attach + } + if src.Build == nil { + dst.Build = nil + } else { + dst.Build = new(BuildConfig) + deriveDeepCopy_5(dst.Build, src.Build) + } + if src.Develop == nil { + dst.Develop = nil + } else { + dst.Develop = new(DevelopConfig) + deriveDeepCopy_6(dst.Develop, src.Develop) + } + if src.BlkioConfig == nil { + dst.BlkioConfig = nil + } else { + dst.BlkioConfig = new(BlkioConfig) + deriveDeepCopy_7(dst.BlkioConfig, src.BlkioConfig) + } + if src.CapAdd == nil { + dst.CapAdd = nil + } else { + if dst.CapAdd != nil { + if len(src.CapAdd) > len(dst.CapAdd) { + if cap(dst.CapAdd) >= len(src.CapAdd) { + dst.CapAdd = (dst.CapAdd)[:len(src.CapAdd)] + } else { + dst.CapAdd = make([]string, len(src.CapAdd)) + } + } else if len(src.CapAdd) < len(dst.CapAdd) { + dst.CapAdd = (dst.CapAdd)[:len(src.CapAdd)] + } + } else { + dst.CapAdd = make([]string, len(src.CapAdd)) + } + copy(dst.CapAdd, src.CapAdd) + } + if src.CapDrop == nil { + dst.CapDrop = nil + } else { + if dst.CapDrop != nil { + if len(src.CapDrop) > len(dst.CapDrop) { + if cap(dst.CapDrop) >= len(src.CapDrop) { + dst.CapDrop = (dst.CapDrop)[:len(src.CapDrop)] + } else { + dst.CapDrop = make([]string, len(src.CapDrop)) + } + } else if len(src.CapDrop) < len(dst.CapDrop) { + dst.CapDrop = (dst.CapDrop)[:len(src.CapDrop)] + } + } else { + dst.CapDrop = make([]string, len(src.CapDrop)) + } + copy(dst.CapDrop, src.CapDrop) + } + dst.CgroupParent = src.CgroupParent + dst.Cgroup = src.Cgroup + dst.CPUCount = src.CPUCount + dst.CPUPercent = src.CPUPercent + dst.CPUPeriod = src.CPUPeriod + dst.CPUQuota = src.CPUQuota + dst.CPURTPeriod = src.CPURTPeriod + dst.CPURTRuntime = src.CPURTRuntime + dst.CPUS = src.CPUS + dst.CPUSet = src.CPUSet + dst.CPUShares = src.CPUShares + if src.Command == nil { + dst.Command = nil + } else { + if dst.Command != nil { + if len(src.Command) > len(dst.Command) { + if cap(dst.Command) >= len(src.Command) { + dst.Command = (dst.Command)[:len(src.Command)] + } else { + dst.Command = make([]string, len(src.Command)) + } + } else if len(src.Command) < len(dst.Command) { + dst.Command = (dst.Command)[:len(src.Command)] + } + } else { + dst.Command = make([]string, len(src.Command)) + } + copy(dst.Command, src.Command) + } + if src.Configs == nil { + dst.Configs = nil + } else { + if dst.Configs != nil { + if len(src.Configs) > len(dst.Configs) { + if cap(dst.Configs) >= len(src.Configs) { + dst.Configs = (dst.Configs)[:len(src.Configs)] + } else { + dst.Configs = make([]ServiceConfigObjConfig, len(src.Configs)) + } + } else if len(src.Configs) < len(dst.Configs) { + dst.Configs = (dst.Configs)[:len(src.Configs)] + } + } else { + dst.Configs = make([]ServiceConfigObjConfig, len(src.Configs)) + } + deriveDeepCopy_8(dst.Configs, src.Configs) + } + dst.ContainerName = src.ContainerName + if src.CredentialSpec == nil { + dst.CredentialSpec = nil + } else { + dst.CredentialSpec = new(CredentialSpecConfig) + deriveDeepCopy_9(dst.CredentialSpec, src.CredentialSpec) + } + if src.DependsOn != nil { + dst.DependsOn = make(map[string]ServiceDependency, len(src.DependsOn)) + deriveDeepCopy_10(dst.DependsOn, src.DependsOn) + } else { + dst.DependsOn = nil + } + if src.Deploy == nil { + dst.Deploy = nil + } else { + dst.Deploy = new(DeployConfig) + deriveDeepCopy_11(dst.Deploy, src.Deploy) + } + if src.DeviceCgroupRules == nil { + dst.DeviceCgroupRules = nil + } else { + if dst.DeviceCgroupRules != nil { + if len(src.DeviceCgroupRules) > len(dst.DeviceCgroupRules) { + if cap(dst.DeviceCgroupRules) >= len(src.DeviceCgroupRules) { + dst.DeviceCgroupRules = (dst.DeviceCgroupRules)[:len(src.DeviceCgroupRules)] + } else { + dst.DeviceCgroupRules = make([]string, len(src.DeviceCgroupRules)) + } + } else if len(src.DeviceCgroupRules) < len(dst.DeviceCgroupRules) { + dst.DeviceCgroupRules = (dst.DeviceCgroupRules)[:len(src.DeviceCgroupRules)] + } + } else { + dst.DeviceCgroupRules = make([]string, len(src.DeviceCgroupRules)) + } + copy(dst.DeviceCgroupRules, src.DeviceCgroupRules) + } + if src.Devices == nil { + dst.Devices = nil + } else { + if dst.Devices != nil { + if len(src.Devices) > len(dst.Devices) { + if cap(dst.Devices) >= len(src.Devices) { + dst.Devices = (dst.Devices)[:len(src.Devices)] + } else { + dst.Devices = make([]DeviceMapping, len(src.Devices)) + } + } else if len(src.Devices) < len(dst.Devices) { + dst.Devices = (dst.Devices)[:len(src.Devices)] + } + } else { + dst.Devices = make([]DeviceMapping, len(src.Devices)) + } + copy(dst.Devices, src.Devices) + } + if src.DNS == nil { + dst.DNS = nil + } else { + if dst.DNS != nil { + if len(src.DNS) > len(dst.DNS) { + if cap(dst.DNS) >= len(src.DNS) { + dst.DNS = (dst.DNS)[:len(src.DNS)] + } else { + dst.DNS = make([]string, len(src.DNS)) + } + } else if len(src.DNS) < len(dst.DNS) { + dst.DNS = (dst.DNS)[:len(src.DNS)] + } + } else { + dst.DNS = make([]string, len(src.DNS)) + } + copy(dst.DNS, src.DNS) + } + if src.DNSOpts == nil { + dst.DNSOpts = nil + } else { + if dst.DNSOpts != nil { + if len(src.DNSOpts) > len(dst.DNSOpts) { + if cap(dst.DNSOpts) >= len(src.DNSOpts) { + dst.DNSOpts = (dst.DNSOpts)[:len(src.DNSOpts)] + } else { + dst.DNSOpts = make([]string, len(src.DNSOpts)) + } + } else if len(src.DNSOpts) < len(dst.DNSOpts) { + dst.DNSOpts = (dst.DNSOpts)[:len(src.DNSOpts)] + } + } else { + dst.DNSOpts = make([]string, len(src.DNSOpts)) + } + copy(dst.DNSOpts, src.DNSOpts) + } + if src.DNSSearch == nil { + dst.DNSSearch = nil + } else { + if dst.DNSSearch != nil { + if len(src.DNSSearch) > len(dst.DNSSearch) { + if cap(dst.DNSSearch) >= len(src.DNSSearch) { + dst.DNSSearch = (dst.DNSSearch)[:len(src.DNSSearch)] + } else { + dst.DNSSearch = make([]string, len(src.DNSSearch)) + } + } else if len(src.DNSSearch) < len(dst.DNSSearch) { + dst.DNSSearch = (dst.DNSSearch)[:len(src.DNSSearch)] + } + } else { + dst.DNSSearch = make([]string, len(src.DNSSearch)) + } + copy(dst.DNSSearch, src.DNSSearch) + } + dst.Dockerfile = src.Dockerfile + dst.DomainName = src.DomainName + if src.Entrypoint == nil { + dst.Entrypoint = nil + } else { + if dst.Entrypoint != nil { + if len(src.Entrypoint) > len(dst.Entrypoint) { + if cap(dst.Entrypoint) >= len(src.Entrypoint) { + dst.Entrypoint = (dst.Entrypoint)[:len(src.Entrypoint)] + } else { + dst.Entrypoint = make([]string, len(src.Entrypoint)) + } + } else if len(src.Entrypoint) < len(dst.Entrypoint) { + dst.Entrypoint = (dst.Entrypoint)[:len(src.Entrypoint)] + } + } else { + dst.Entrypoint = make([]string, len(src.Entrypoint)) + } + copy(dst.Entrypoint, src.Entrypoint) + } + if src.Environment != nil { + dst.Environment = make(map[string]*string, len(src.Environment)) + deriveDeepCopy_12(dst.Environment, src.Environment) + } else { + dst.Environment = nil + } + if src.EnvFiles == nil { + dst.EnvFiles = nil + } else { + if dst.EnvFiles != nil { + if len(src.EnvFiles) > len(dst.EnvFiles) { + if cap(dst.EnvFiles) >= len(src.EnvFiles) { + dst.EnvFiles = (dst.EnvFiles)[:len(src.EnvFiles)] + } else { + dst.EnvFiles = make([]EnvFile, len(src.EnvFiles)) + } + } else if len(src.EnvFiles) < len(dst.EnvFiles) { + dst.EnvFiles = (dst.EnvFiles)[:len(src.EnvFiles)] + } + } else { + dst.EnvFiles = make([]EnvFile, len(src.EnvFiles)) + } + copy(dst.EnvFiles, src.EnvFiles) + } + if src.Expose == nil { + dst.Expose = nil + } else { + if dst.Expose != nil { + if len(src.Expose) > len(dst.Expose) { + if cap(dst.Expose) >= len(src.Expose) { + dst.Expose = (dst.Expose)[:len(src.Expose)] + } else { + dst.Expose = make([]string, len(src.Expose)) + } + } else if len(src.Expose) < len(dst.Expose) { + dst.Expose = (dst.Expose)[:len(src.Expose)] + } + } else { + dst.Expose = make([]string, len(src.Expose)) + } + copy(dst.Expose, src.Expose) + } + if src.Extends == nil { + dst.Extends = nil + } else { + dst.Extends = new(ExtendsConfig) + *dst.Extends = *src.Extends + } + if src.ExternalLinks == nil { + dst.ExternalLinks = nil + } else { + if dst.ExternalLinks != nil { + if len(src.ExternalLinks) > len(dst.ExternalLinks) { + if cap(dst.ExternalLinks) >= len(src.ExternalLinks) { + dst.ExternalLinks = (dst.ExternalLinks)[:len(src.ExternalLinks)] + } else { + dst.ExternalLinks = make([]string, len(src.ExternalLinks)) + } + } else if len(src.ExternalLinks) < len(dst.ExternalLinks) { + dst.ExternalLinks = (dst.ExternalLinks)[:len(src.ExternalLinks)] + } + } else { + dst.ExternalLinks = make([]string, len(src.ExternalLinks)) + } + copy(dst.ExternalLinks, src.ExternalLinks) + } + if src.ExtraHosts != nil { + dst.ExtraHosts = make(map[string][]string, len(src.ExtraHosts)) + deriveDeepCopy_13(dst.ExtraHosts, src.ExtraHosts) + } else { + dst.ExtraHosts = nil + } + if src.GroupAdd == nil { + dst.GroupAdd = nil + } else { + if dst.GroupAdd != nil { + if len(src.GroupAdd) > len(dst.GroupAdd) { + if cap(dst.GroupAdd) >= len(src.GroupAdd) { + dst.GroupAdd = (dst.GroupAdd)[:len(src.GroupAdd)] + } else { + dst.GroupAdd = make([]string, len(src.GroupAdd)) + } + } else if len(src.GroupAdd) < len(dst.GroupAdd) { + dst.GroupAdd = (dst.GroupAdd)[:len(src.GroupAdd)] + } + } else { + dst.GroupAdd = make([]string, len(src.GroupAdd)) + } + copy(dst.GroupAdd, src.GroupAdd) + } + dst.Hostname = src.Hostname + if src.HealthCheck == nil { + dst.HealthCheck = nil + } else { + dst.HealthCheck = new(HealthCheckConfig) + deriveDeepCopy_14(dst.HealthCheck, src.HealthCheck) + } + dst.Image = src.Image + if src.Init == nil { + dst.Init = nil + } else { + dst.Init = new(bool) + *dst.Init = *src.Init + } + dst.Ipc = src.Ipc + dst.Isolation = src.Isolation + if src.Labels != nil { + dst.Labels = make(map[string]string, len(src.Labels)) + deriveDeepCopy_4(dst.Labels, src.Labels) + } else { + dst.Labels = nil + } + if src.CustomLabels != nil { + dst.CustomLabels = make(map[string]string, len(src.CustomLabels)) + deriveDeepCopy_4(dst.CustomLabels, src.CustomLabels) + } else { + dst.CustomLabels = nil + } + if src.Links == nil { + dst.Links = nil + } else { + if dst.Links != nil { + if len(src.Links) > len(dst.Links) { + if cap(dst.Links) >= len(src.Links) { + dst.Links = (dst.Links)[:len(src.Links)] + } else { + dst.Links = make([]string, len(src.Links)) + } + } else if len(src.Links) < len(dst.Links) { + dst.Links = (dst.Links)[:len(src.Links)] + } + } else { + dst.Links = make([]string, len(src.Links)) + } + copy(dst.Links, src.Links) + } + if src.Logging == nil { + dst.Logging = nil + } else { + dst.Logging = new(LoggingConfig) + deriveDeepCopy_15(dst.Logging, src.Logging) + } + dst.LogDriver = src.LogDriver + if src.LogOpt != nil { + dst.LogOpt = make(map[string]string, len(src.LogOpt)) + deriveDeepCopy_4(dst.LogOpt, src.LogOpt) + } else { + dst.LogOpt = nil + } + dst.MemLimit = src.MemLimit + dst.MemReservation = src.MemReservation + dst.MemSwapLimit = src.MemSwapLimit + dst.MemSwappiness = src.MemSwappiness + dst.MacAddress = src.MacAddress + dst.Net = src.Net + dst.NetworkMode = src.NetworkMode + if src.Networks != nil { + dst.Networks = make(map[string]*ServiceNetworkConfig, len(src.Networks)) + deriveDeepCopy_16(dst.Networks, src.Networks) + } else { + dst.Networks = nil + } + dst.OomKillDisable = src.OomKillDisable + dst.OomScoreAdj = src.OomScoreAdj + dst.Pid = src.Pid + dst.PidsLimit = src.PidsLimit + dst.Platform = src.Platform + if src.Ports == nil { + dst.Ports = nil + } else { + if dst.Ports != nil { + if len(src.Ports) > len(dst.Ports) { + if cap(dst.Ports) >= len(src.Ports) { + dst.Ports = (dst.Ports)[:len(src.Ports)] + } else { + dst.Ports = make([]ServicePortConfig, len(src.Ports)) + } + } else if len(src.Ports) < len(dst.Ports) { + dst.Ports = (dst.Ports)[:len(src.Ports)] + } + } else { + dst.Ports = make([]ServicePortConfig, len(src.Ports)) + } + deriveDeepCopy_17(dst.Ports, src.Ports) + } + dst.Privileged = src.Privileged + dst.PullPolicy = src.PullPolicy + dst.ReadOnly = src.ReadOnly + dst.Restart = src.Restart + dst.Runtime = src.Runtime + if src.Scale == nil { + dst.Scale = nil + } else { + dst.Scale = new(int) + *dst.Scale = *src.Scale + } + if src.Secrets == nil { + dst.Secrets = nil + } else { + if dst.Secrets != nil { + if len(src.Secrets) > len(dst.Secrets) { + if cap(dst.Secrets) >= len(src.Secrets) { + dst.Secrets = (dst.Secrets)[:len(src.Secrets)] + } else { + dst.Secrets = make([]ServiceSecretConfig, len(src.Secrets)) + } + } else if len(src.Secrets) < len(dst.Secrets) { + dst.Secrets = (dst.Secrets)[:len(src.Secrets)] + } + } else { + dst.Secrets = make([]ServiceSecretConfig, len(src.Secrets)) + } + deriveDeepCopy_18(dst.Secrets, src.Secrets) + } + if src.SecurityOpt == nil { + dst.SecurityOpt = nil + } else { + if dst.SecurityOpt != nil { + if len(src.SecurityOpt) > len(dst.SecurityOpt) { + if cap(dst.SecurityOpt) >= len(src.SecurityOpt) { + dst.SecurityOpt = (dst.SecurityOpt)[:len(src.SecurityOpt)] + } else { + dst.SecurityOpt = make([]string, len(src.SecurityOpt)) + } + } else if len(src.SecurityOpt) < len(dst.SecurityOpt) { + dst.SecurityOpt = (dst.SecurityOpt)[:len(src.SecurityOpt)] + } + } else { + dst.SecurityOpt = make([]string, len(src.SecurityOpt)) + } + copy(dst.SecurityOpt, src.SecurityOpt) + } + dst.ShmSize = src.ShmSize + dst.StdinOpen = src.StdinOpen + if src.StopGracePeriod == nil { + dst.StopGracePeriod = nil + } else { + dst.StopGracePeriod = new(Duration) + *dst.StopGracePeriod = *src.StopGracePeriod + } + dst.StopSignal = src.StopSignal + if src.StorageOpt != nil { + dst.StorageOpt = make(map[string]string, len(src.StorageOpt)) + deriveDeepCopy_4(dst.StorageOpt, src.StorageOpt) + } else { + dst.StorageOpt = nil + } + if src.Sysctls != nil { + dst.Sysctls = make(map[string]string, len(src.Sysctls)) + deriveDeepCopy_4(dst.Sysctls, src.Sysctls) + } else { + dst.Sysctls = nil + } + if src.Tmpfs == nil { + dst.Tmpfs = nil + } else { + if dst.Tmpfs != nil { + if len(src.Tmpfs) > len(dst.Tmpfs) { + if cap(dst.Tmpfs) >= len(src.Tmpfs) { + dst.Tmpfs = (dst.Tmpfs)[:len(src.Tmpfs)] + } else { + dst.Tmpfs = make([]string, len(src.Tmpfs)) + } + } else if len(src.Tmpfs) < len(dst.Tmpfs) { + dst.Tmpfs = (dst.Tmpfs)[:len(src.Tmpfs)] + } + } else { + dst.Tmpfs = make([]string, len(src.Tmpfs)) + } + copy(dst.Tmpfs, src.Tmpfs) + } + dst.Tty = src.Tty + if src.Ulimits != nil { + dst.Ulimits = make(map[string]*UlimitsConfig, len(src.Ulimits)) + deriveDeepCopy_19(dst.Ulimits, src.Ulimits) + } else { + dst.Ulimits = nil + } + dst.User = src.User + dst.UserNSMode = src.UserNSMode + dst.Uts = src.Uts + dst.VolumeDriver = src.VolumeDriver + if src.Volumes == nil { + dst.Volumes = nil + } else { + if dst.Volumes != nil { + if len(src.Volumes) > len(dst.Volumes) { + if cap(dst.Volumes) >= len(src.Volumes) { + dst.Volumes = (dst.Volumes)[:len(src.Volumes)] + } else { + dst.Volumes = make([]ServiceVolumeConfig, len(src.Volumes)) + } + } else if len(src.Volumes) < len(dst.Volumes) { + dst.Volumes = (dst.Volumes)[:len(src.Volumes)] + } + } else { + dst.Volumes = make([]ServiceVolumeConfig, len(src.Volumes)) + } + deriveDeepCopy_20(dst.Volumes, src.Volumes) + } + if src.VolumesFrom == nil { + dst.VolumesFrom = nil + } else { + if dst.VolumesFrom != nil { + if len(src.VolumesFrom) > len(dst.VolumesFrom) { + if cap(dst.VolumesFrom) >= len(src.VolumesFrom) { + dst.VolumesFrom = (dst.VolumesFrom)[:len(src.VolumesFrom)] + } else { + dst.VolumesFrom = make([]string, len(src.VolumesFrom)) + } + } else if len(src.VolumesFrom) < len(dst.VolumesFrom) { + dst.VolumesFrom = (dst.VolumesFrom)[:len(src.VolumesFrom)] + } + } else { + dst.VolumesFrom = make([]string, len(src.VolumesFrom)) + } + copy(dst.VolumesFrom, src.VolumesFrom) + } + dst.WorkingDir = src.WorkingDir + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy recursively copies the contents of src into dst. +func deriveDeepCopy(dst, src map[string]ServiceConfig) { + for src_key, src_value := range src { + func() { + field := new(ServiceConfig) + deriveDeepCopyService(field, &src_value) + dst[src_key] = *field + }() + } +} + +// deriveDeepCopy_ recursively copies the contents of src into dst. +func deriveDeepCopy_(dst, src map[string]NetworkConfig) { + for src_key, src_value := range src { + func() { + field := new(NetworkConfig) + deriveDeepCopy_21(field, &src_value) + dst[src_key] = *field + }() + } +} + +// deriveDeepCopy_1 recursively copies the contents of src into dst. +func deriveDeepCopy_1(dst, src map[string]VolumeConfig) { + for src_key, src_value := range src { + func() { + field := new(VolumeConfig) + deriveDeepCopy_22(field, &src_value) + dst[src_key] = *field + }() + } +} + +// deriveDeepCopy_2 recursively copies the contents of src into dst. +func deriveDeepCopy_2(dst, src map[string]SecretConfig) { + for src_key, src_value := range src { + func() { + field := new(SecretConfig) + deriveDeepCopy_23(field, &src_value) + dst[src_key] = *field + }() + } +} + +// deriveDeepCopy_3 recursively copies the contents of src into dst. +func deriveDeepCopy_3(dst, src map[string]ConfigObjConfig) { + for src_key, src_value := range src { + func() { + field := new(ConfigObjConfig) + deriveDeepCopy_24(field, &src_value) + dst[src_key] = *field + }() + } +} + +// deriveDeepCopy_4 recursively copies the contents of src into dst. +func deriveDeepCopy_4(dst, src map[string]string) { + for src_key, src_value := range src { + dst[src_key] = src_value + } +} + +// deriveDeepCopy_5 recursively copies the contents of src into dst. +func deriveDeepCopy_5(dst, src *BuildConfig) { + dst.Context = src.Context + dst.Dockerfile = src.Dockerfile + dst.DockerfileInline = src.DockerfileInline + if src.Entitlements == nil { + dst.Entitlements = nil + } else { + if dst.Entitlements != nil { + if len(src.Entitlements) > len(dst.Entitlements) { + if cap(dst.Entitlements) >= len(src.Entitlements) { + dst.Entitlements = (dst.Entitlements)[:len(src.Entitlements)] + } else { + dst.Entitlements = make([]string, len(src.Entitlements)) + } + } else if len(src.Entitlements) < len(dst.Entitlements) { + dst.Entitlements = (dst.Entitlements)[:len(src.Entitlements)] + } + } else { + dst.Entitlements = make([]string, len(src.Entitlements)) + } + copy(dst.Entitlements, src.Entitlements) + } + if src.Args != nil { + dst.Args = make(map[string]*string, len(src.Args)) + deriveDeepCopy_12(dst.Args, src.Args) + } else { + dst.Args = nil + } + if src.SSH == nil { + dst.SSH = nil + } else { + if dst.SSH != nil { + if len(src.SSH) > len(dst.SSH) { + if cap(dst.SSH) >= len(src.SSH) { + dst.SSH = (dst.SSH)[:len(src.SSH)] + } else { + dst.SSH = make([]SSHKey, len(src.SSH)) + } + } else if len(src.SSH) < len(dst.SSH) { + dst.SSH = (dst.SSH)[:len(src.SSH)] + } + } else { + dst.SSH = make([]SSHKey, len(src.SSH)) + } + copy(dst.SSH, src.SSH) + } + if src.Labels != nil { + dst.Labels = make(map[string]string, len(src.Labels)) + deriveDeepCopy_4(dst.Labels, src.Labels) + } else { + dst.Labels = nil + } + if src.CacheFrom == nil { + dst.CacheFrom = nil + } else { + if dst.CacheFrom != nil { + if len(src.CacheFrom) > len(dst.CacheFrom) { + if cap(dst.CacheFrom) >= len(src.CacheFrom) { + dst.CacheFrom = (dst.CacheFrom)[:len(src.CacheFrom)] + } else { + dst.CacheFrom = make([]string, len(src.CacheFrom)) + } + } else if len(src.CacheFrom) < len(dst.CacheFrom) { + dst.CacheFrom = (dst.CacheFrom)[:len(src.CacheFrom)] + } + } else { + dst.CacheFrom = make([]string, len(src.CacheFrom)) + } + copy(dst.CacheFrom, src.CacheFrom) + } + if src.CacheTo == nil { + dst.CacheTo = nil + } else { + if dst.CacheTo != nil { + if len(src.CacheTo) > len(dst.CacheTo) { + if cap(dst.CacheTo) >= len(src.CacheTo) { + dst.CacheTo = (dst.CacheTo)[:len(src.CacheTo)] + } else { + dst.CacheTo = make([]string, len(src.CacheTo)) + } + } else if len(src.CacheTo) < len(dst.CacheTo) { + dst.CacheTo = (dst.CacheTo)[:len(src.CacheTo)] + } + } else { + dst.CacheTo = make([]string, len(src.CacheTo)) + } + copy(dst.CacheTo, src.CacheTo) + } + dst.NoCache = src.NoCache + if src.AdditionalContexts != nil { + dst.AdditionalContexts = make(map[string]string, len(src.AdditionalContexts)) + deriveDeepCopy_4(dst.AdditionalContexts, src.AdditionalContexts) + } else { + dst.AdditionalContexts = nil + } + dst.Pull = src.Pull + if src.ExtraHosts != nil { + dst.ExtraHosts = make(map[string][]string, len(src.ExtraHosts)) + deriveDeepCopy_13(dst.ExtraHosts, src.ExtraHosts) + } else { + dst.ExtraHosts = nil + } + dst.Isolation = src.Isolation + dst.Network = src.Network + dst.Target = src.Target + if src.Secrets == nil { + dst.Secrets = nil + } else { + if dst.Secrets != nil { + if len(src.Secrets) > len(dst.Secrets) { + if cap(dst.Secrets) >= len(src.Secrets) { + dst.Secrets = (dst.Secrets)[:len(src.Secrets)] + } else { + dst.Secrets = make([]ServiceSecretConfig, len(src.Secrets)) + } + } else if len(src.Secrets) < len(dst.Secrets) { + dst.Secrets = (dst.Secrets)[:len(src.Secrets)] + } + } else { + dst.Secrets = make([]ServiceSecretConfig, len(src.Secrets)) + } + deriveDeepCopy_18(dst.Secrets, src.Secrets) + } + dst.ShmSize = src.ShmSize + if src.Tags == nil { + dst.Tags = nil + } else { + if dst.Tags != nil { + if len(src.Tags) > len(dst.Tags) { + if cap(dst.Tags) >= len(src.Tags) { + dst.Tags = (dst.Tags)[:len(src.Tags)] + } else { + dst.Tags = make([]string, len(src.Tags)) + } + } else if len(src.Tags) < len(dst.Tags) { + dst.Tags = (dst.Tags)[:len(src.Tags)] + } + } else { + dst.Tags = make([]string, len(src.Tags)) + } + copy(dst.Tags, src.Tags) + } + if src.Ulimits != nil { + dst.Ulimits = make(map[string]*UlimitsConfig, len(src.Ulimits)) + deriveDeepCopy_19(dst.Ulimits, src.Ulimits) + } else { + dst.Ulimits = nil + } + if src.Platforms == nil { + dst.Platforms = nil + } else { + if dst.Platforms != nil { + if len(src.Platforms) > len(dst.Platforms) { + if cap(dst.Platforms) >= len(src.Platforms) { + dst.Platforms = (dst.Platforms)[:len(src.Platforms)] + } else { + dst.Platforms = make([]string, len(src.Platforms)) + } + } else if len(src.Platforms) < len(dst.Platforms) { + dst.Platforms = (dst.Platforms)[:len(src.Platforms)] + } + } else { + dst.Platforms = make([]string, len(src.Platforms)) + } + copy(dst.Platforms, src.Platforms) + } + dst.Privileged = src.Privileged + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_6 recursively copies the contents of src into dst. +func deriveDeepCopy_6(dst, src *DevelopConfig) { + if src.Watch == nil { + dst.Watch = nil + } else { + if dst.Watch != nil { + if len(src.Watch) > len(dst.Watch) { + if cap(dst.Watch) >= len(src.Watch) { + dst.Watch = (dst.Watch)[:len(src.Watch)] + } else { + dst.Watch = make([]Trigger, len(src.Watch)) + } + } else if len(src.Watch) < len(dst.Watch) { + dst.Watch = (dst.Watch)[:len(src.Watch)] + } + } else { + dst.Watch = make([]Trigger, len(src.Watch)) + } + deriveDeepCopy_25(dst.Watch, src.Watch) + } + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_7 recursively copies the contents of src into dst. +func deriveDeepCopy_7(dst, src *BlkioConfig) { + dst.Weight = src.Weight + if src.WeightDevice == nil { + dst.WeightDevice = nil + } else { + if dst.WeightDevice != nil { + if len(src.WeightDevice) > len(dst.WeightDevice) { + if cap(dst.WeightDevice) >= len(src.WeightDevice) { + dst.WeightDevice = (dst.WeightDevice)[:len(src.WeightDevice)] + } else { + dst.WeightDevice = make([]WeightDevice, len(src.WeightDevice)) + } + } else if len(src.WeightDevice) < len(dst.WeightDevice) { + dst.WeightDevice = (dst.WeightDevice)[:len(src.WeightDevice)] + } + } else { + dst.WeightDevice = make([]WeightDevice, len(src.WeightDevice)) + } + deriveDeepCopy_26(dst.WeightDevice, src.WeightDevice) + } + if src.DeviceReadBps == nil { + dst.DeviceReadBps = nil + } else { + if dst.DeviceReadBps != nil { + if len(src.DeviceReadBps) > len(dst.DeviceReadBps) { + if cap(dst.DeviceReadBps) >= len(src.DeviceReadBps) { + dst.DeviceReadBps = (dst.DeviceReadBps)[:len(src.DeviceReadBps)] + } else { + dst.DeviceReadBps = make([]ThrottleDevice, len(src.DeviceReadBps)) + } + } else if len(src.DeviceReadBps) < len(dst.DeviceReadBps) { + dst.DeviceReadBps = (dst.DeviceReadBps)[:len(src.DeviceReadBps)] + } + } else { + dst.DeviceReadBps = make([]ThrottleDevice, len(src.DeviceReadBps)) + } + deriveDeepCopy_27(dst.DeviceReadBps, src.DeviceReadBps) + } + if src.DeviceReadIOps == nil { + dst.DeviceReadIOps = nil + } else { + if dst.DeviceReadIOps != nil { + if len(src.DeviceReadIOps) > len(dst.DeviceReadIOps) { + if cap(dst.DeviceReadIOps) >= len(src.DeviceReadIOps) { + dst.DeviceReadIOps = (dst.DeviceReadIOps)[:len(src.DeviceReadIOps)] + } else { + dst.DeviceReadIOps = make([]ThrottleDevice, len(src.DeviceReadIOps)) + } + } else if len(src.DeviceReadIOps) < len(dst.DeviceReadIOps) { + dst.DeviceReadIOps = (dst.DeviceReadIOps)[:len(src.DeviceReadIOps)] + } + } else { + dst.DeviceReadIOps = make([]ThrottleDevice, len(src.DeviceReadIOps)) + } + deriveDeepCopy_27(dst.DeviceReadIOps, src.DeviceReadIOps) + } + if src.DeviceWriteBps == nil { + dst.DeviceWriteBps = nil + } else { + if dst.DeviceWriteBps != nil { + if len(src.DeviceWriteBps) > len(dst.DeviceWriteBps) { + if cap(dst.DeviceWriteBps) >= len(src.DeviceWriteBps) { + dst.DeviceWriteBps = (dst.DeviceWriteBps)[:len(src.DeviceWriteBps)] + } else { + dst.DeviceWriteBps = make([]ThrottleDevice, len(src.DeviceWriteBps)) + } + } else if len(src.DeviceWriteBps) < len(dst.DeviceWriteBps) { + dst.DeviceWriteBps = (dst.DeviceWriteBps)[:len(src.DeviceWriteBps)] + } + } else { + dst.DeviceWriteBps = make([]ThrottleDevice, len(src.DeviceWriteBps)) + } + deriveDeepCopy_27(dst.DeviceWriteBps, src.DeviceWriteBps) + } + if src.DeviceWriteIOps == nil { + dst.DeviceWriteIOps = nil + } else { + if dst.DeviceWriteIOps != nil { + if len(src.DeviceWriteIOps) > len(dst.DeviceWriteIOps) { + if cap(dst.DeviceWriteIOps) >= len(src.DeviceWriteIOps) { + dst.DeviceWriteIOps = (dst.DeviceWriteIOps)[:len(src.DeviceWriteIOps)] + } else { + dst.DeviceWriteIOps = make([]ThrottleDevice, len(src.DeviceWriteIOps)) + } + } else if len(src.DeviceWriteIOps) < len(dst.DeviceWriteIOps) { + dst.DeviceWriteIOps = (dst.DeviceWriteIOps)[:len(src.DeviceWriteIOps)] + } + } else { + dst.DeviceWriteIOps = make([]ThrottleDevice, len(src.DeviceWriteIOps)) + } + deriveDeepCopy_27(dst.DeviceWriteIOps, src.DeviceWriteIOps) + } + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_8 recursively copies the contents of src into dst. +func deriveDeepCopy_8(dst, src []ServiceConfigObjConfig) { + for src_i, src_value := range src { + func() { + field := new(ServiceConfigObjConfig) + deriveDeepCopy_28(field, &src_value) + dst[src_i] = *field + }() + } +} + +// deriveDeepCopy_9 recursively copies the contents of src into dst. +func deriveDeepCopy_9(dst, src *CredentialSpecConfig) { + dst.Config = src.Config + dst.File = src.File + dst.Registry = src.Registry + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_10 recursively copies the contents of src into dst. +func deriveDeepCopy_10(dst, src map[string]ServiceDependency) { + for src_key, src_value := range src { + func() { + field := new(ServiceDependency) + deriveDeepCopy_29(field, &src_value) + dst[src_key] = *field + }() + } +} + +// deriveDeepCopy_11 recursively copies the contents of src into dst. +func deriveDeepCopy_11(dst, src *DeployConfig) { + dst.Mode = src.Mode + if src.Replicas == nil { + dst.Replicas = nil + } else { + dst.Replicas = new(int) + *dst.Replicas = *src.Replicas + } + if src.Labels != nil { + dst.Labels = make(map[string]string, len(src.Labels)) + deriveDeepCopy_4(dst.Labels, src.Labels) + } else { + dst.Labels = nil + } + if src.UpdateConfig == nil { + dst.UpdateConfig = nil + } else { + dst.UpdateConfig = new(UpdateConfig) + deriveDeepCopy_30(dst.UpdateConfig, src.UpdateConfig) + } + if src.RollbackConfig == nil { + dst.RollbackConfig = nil + } else { + dst.RollbackConfig = new(UpdateConfig) + deriveDeepCopy_30(dst.RollbackConfig, src.RollbackConfig) + } + func() { + field := new(Resources) + deriveDeepCopy_31(field, &src.Resources) + dst.Resources = *field + }() + if src.RestartPolicy == nil { + dst.RestartPolicy = nil + } else { + dst.RestartPolicy = new(RestartPolicy) + deriveDeepCopy_32(dst.RestartPolicy, src.RestartPolicy) + } + func() { + field := new(Placement) + deriveDeepCopy_33(field, &src.Placement) + dst.Placement = *field + }() + dst.EndpointMode = src.EndpointMode + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_12 recursively copies the contents of src into dst. +func deriveDeepCopy_12(dst, src map[string]*string) { + for src_key, src_value := range src { + if src_value == nil { + dst[src_key] = nil + } + if src_value == nil { + dst[src_key] = nil + } else { + dst[src_key] = new(string) + *dst[src_key] = *src_value + } + } +} + +// deriveDeepCopy_13 recursively copies the contents of src into dst. +func deriveDeepCopy_13(dst, src map[string][]string) { + for src_key, src_value := range src { + if src_value == nil { + dst[src_key] = nil + } + if src_value == nil { + dst[src_key] = nil + } else { + if dst[src_key] != nil { + if len(src_value) > len(dst[src_key]) { + if cap(dst[src_key]) >= len(src_value) { + dst[src_key] = (dst[src_key])[:len(src_value)] + } else { + dst[src_key] = make([]string, len(src_value)) + } + } else if len(src_value) < len(dst[src_key]) { + dst[src_key] = (dst[src_key])[:len(src_value)] + } + } else { + dst[src_key] = make([]string, len(src_value)) + } + copy(dst[src_key], src_value) + } + } +} + +// deriveDeepCopy_14 recursively copies the contents of src into dst. +func deriveDeepCopy_14(dst, src *HealthCheckConfig) { + if src.Test == nil { + dst.Test = nil + } else { + if dst.Test != nil { + if len(src.Test) > len(dst.Test) { + if cap(dst.Test) >= len(src.Test) { + dst.Test = (dst.Test)[:len(src.Test)] + } else { + dst.Test = make([]string, len(src.Test)) + } + } else if len(src.Test) < len(dst.Test) { + dst.Test = (dst.Test)[:len(src.Test)] + } + } else { + dst.Test = make([]string, len(src.Test)) + } + copy(dst.Test, src.Test) + } + if src.Timeout == nil { + dst.Timeout = nil + } else { + dst.Timeout = new(Duration) + *dst.Timeout = *src.Timeout + } + if src.Interval == nil { + dst.Interval = nil + } else { + dst.Interval = new(Duration) + *dst.Interval = *src.Interval + } + if src.Retries == nil { + dst.Retries = nil + } else { + dst.Retries = new(uint64) + *dst.Retries = *src.Retries + } + if src.StartPeriod == nil { + dst.StartPeriod = nil + } else { + dst.StartPeriod = new(Duration) + *dst.StartPeriod = *src.StartPeriod + } + if src.StartInterval == nil { + dst.StartInterval = nil + } else { + dst.StartInterval = new(Duration) + *dst.StartInterval = *src.StartInterval + } + dst.Disable = src.Disable + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_15 recursively copies the contents of src into dst. +func deriveDeepCopy_15(dst, src *LoggingConfig) { + dst.Driver = src.Driver + if src.Options != nil { + dst.Options = make(map[string]string, len(src.Options)) + deriveDeepCopy_4(dst.Options, src.Options) + } else { + dst.Options = nil + } + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_16 recursively copies the contents of src into dst. +func deriveDeepCopy_16(dst, src map[string]*ServiceNetworkConfig) { + for src_key, src_value := range src { + if src_value == nil { + dst[src_key] = nil + } + if src_value == nil { + dst[src_key] = nil + } else { + dst[src_key] = new(ServiceNetworkConfig) + deriveDeepCopy_34(dst[src_key], src_value) + } + } +} + +// deriveDeepCopy_17 recursively copies the contents of src into dst. +func deriveDeepCopy_17(dst, src []ServicePortConfig) { + for src_i, src_value := range src { + func() { + field := new(ServicePortConfig) + deriveDeepCopy_35(field, &src_value) + dst[src_i] = *field + }() + } +} + +// deriveDeepCopy_18 recursively copies the contents of src into dst. +func deriveDeepCopy_18(dst, src []ServiceSecretConfig) { + for src_i, src_value := range src { + func() { + field := new(ServiceSecretConfig) + deriveDeepCopy_36(field, &src_value) + dst[src_i] = *field + }() + } +} + +// deriveDeepCopy_19 recursively copies the contents of src into dst. +func deriveDeepCopy_19(dst, src map[string]*UlimitsConfig) { + for src_key, src_value := range src { + if src_value == nil { + dst[src_key] = nil + } + if src_value == nil { + dst[src_key] = nil + } else { + dst[src_key] = new(UlimitsConfig) + deriveDeepCopy_37(dst[src_key], src_value) + } + } +} + +// deriveDeepCopy_20 recursively copies the contents of src into dst. +func deriveDeepCopy_20(dst, src []ServiceVolumeConfig) { + for src_i, src_value := range src { + func() { + field := new(ServiceVolumeConfig) + deriveDeepCopy_38(field, &src_value) + dst[src_i] = *field + }() + } +} + +// deriveDeepCopy_21 recursively copies the contents of src into dst. +func deriveDeepCopy_21(dst, src *NetworkConfig) { + dst.Name = src.Name + dst.Driver = src.Driver + if src.DriverOpts != nil { + dst.DriverOpts = make(map[string]string, len(src.DriverOpts)) + deriveDeepCopy_4(dst.DriverOpts, src.DriverOpts) + } else { + dst.DriverOpts = nil + } + func() { + field := new(IPAMConfig) + deriveDeepCopy_39(field, &src.Ipam) + dst.Ipam = *field + }() + dst.External = src.External + dst.Internal = src.Internal + dst.Attachable = src.Attachable + if src.Labels != nil { + dst.Labels = make(map[string]string, len(src.Labels)) + deriveDeepCopy_4(dst.Labels, src.Labels) + } else { + dst.Labels = nil + } + if src.EnableIPv6 == nil { + dst.EnableIPv6 = nil + } else { + dst.EnableIPv6 = new(bool) + *dst.EnableIPv6 = *src.EnableIPv6 + } + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_22 recursively copies the contents of src into dst. +func deriveDeepCopy_22(dst, src *VolumeConfig) { + dst.Name = src.Name + dst.Driver = src.Driver + if src.DriverOpts != nil { + dst.DriverOpts = make(map[string]string, len(src.DriverOpts)) + deriveDeepCopy_4(dst.DriverOpts, src.DriverOpts) + } else { + dst.DriverOpts = nil + } + dst.External = src.External + if src.Labels != nil { + dst.Labels = make(map[string]string, len(src.Labels)) + deriveDeepCopy_4(dst.Labels, src.Labels) + } else { + dst.Labels = nil + } + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_23 recursively copies the contents of src into dst. +func deriveDeepCopy_23(dst, src *SecretConfig) { + dst.Name = src.Name + dst.File = src.File + dst.Environment = src.Environment + dst.Content = src.Content + dst.External = src.External + if src.Labels != nil { + dst.Labels = make(map[string]string, len(src.Labels)) + deriveDeepCopy_4(dst.Labels, src.Labels) + } else { + dst.Labels = nil + } + dst.Driver = src.Driver + if src.DriverOpts != nil { + dst.DriverOpts = make(map[string]string, len(src.DriverOpts)) + deriveDeepCopy_4(dst.DriverOpts, src.DriverOpts) + } else { + dst.DriverOpts = nil + } + dst.TemplateDriver = src.TemplateDriver + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_24 recursively copies the contents of src into dst. +func deriveDeepCopy_24(dst, src *ConfigObjConfig) { + dst.Name = src.Name + dst.File = src.File + dst.Environment = src.Environment + dst.Content = src.Content + dst.External = src.External + if src.Labels != nil { + dst.Labels = make(map[string]string, len(src.Labels)) + deriveDeepCopy_4(dst.Labels, src.Labels) + } else { + dst.Labels = nil + } + dst.Driver = src.Driver + if src.DriverOpts != nil { + dst.DriverOpts = make(map[string]string, len(src.DriverOpts)) + deriveDeepCopy_4(dst.DriverOpts, src.DriverOpts) + } else { + dst.DriverOpts = nil + } + dst.TemplateDriver = src.TemplateDriver + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_25 recursively copies the contents of src into dst. +func deriveDeepCopy_25(dst, src []Trigger) { + for src_i, src_value := range src { + func() { + field := new(Trigger) + deriveDeepCopy_40(field, &src_value) + dst[src_i] = *field + }() + } +} + +// deriveDeepCopy_26 recursively copies the contents of src into dst. +func deriveDeepCopy_26(dst, src []WeightDevice) { + for src_i, src_value := range src { + func() { + field := new(WeightDevice) + deriveDeepCopy_41(field, &src_value) + dst[src_i] = *field + }() + } +} + +// deriveDeepCopy_27 recursively copies the contents of src into dst. +func deriveDeepCopy_27(dst, src []ThrottleDevice) { + for src_i, src_value := range src { + func() { + field := new(ThrottleDevice) + deriveDeepCopy_42(field, &src_value) + dst[src_i] = *field + }() + } +} + +// deriveDeepCopy_28 recursively copies the contents of src into dst. +func deriveDeepCopy_28(dst, src *ServiceConfigObjConfig) { + dst.Source = src.Source + dst.Target = src.Target + dst.UID = src.UID + dst.GID = src.GID + if src.Mode == nil { + dst.Mode = nil + } else { + dst.Mode = new(uint32) + *dst.Mode = *src.Mode + } + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_29 recursively copies the contents of src into dst. +func deriveDeepCopy_29(dst, src *ServiceDependency) { + dst.Condition = src.Condition + dst.Restart = src.Restart + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } + dst.Required = src.Required +} + +// deriveDeepCopy_30 recursively copies the contents of src into dst. +func deriveDeepCopy_30(dst, src *UpdateConfig) { + if src.Parallelism == nil { + dst.Parallelism = nil + } else { + dst.Parallelism = new(uint64) + *dst.Parallelism = *src.Parallelism + } + dst.Delay = src.Delay + dst.FailureAction = src.FailureAction + dst.Monitor = src.Monitor + dst.MaxFailureRatio = src.MaxFailureRatio + dst.Order = src.Order + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_31 recursively copies the contents of src into dst. +func deriveDeepCopy_31(dst, src *Resources) { + if src.Limits == nil { + dst.Limits = nil + } else { + dst.Limits = new(Resource) + deriveDeepCopy_43(dst.Limits, src.Limits) + } + if src.Reservations == nil { + dst.Reservations = nil + } else { + dst.Reservations = new(Resource) + deriveDeepCopy_43(dst.Reservations, src.Reservations) + } + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_32 recursively copies the contents of src into dst. +func deriveDeepCopy_32(dst, src *RestartPolicy) { + dst.Condition = src.Condition + if src.Delay == nil { + dst.Delay = nil + } else { + dst.Delay = new(Duration) + *dst.Delay = *src.Delay + } + if src.MaxAttempts == nil { + dst.MaxAttempts = nil + } else { + dst.MaxAttempts = new(uint64) + *dst.MaxAttempts = *src.MaxAttempts + } + if src.Window == nil { + dst.Window = nil + } else { + dst.Window = new(Duration) + *dst.Window = *src.Window + } + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_33 recursively copies the contents of src into dst. +func deriveDeepCopy_33(dst, src *Placement) { + if src.Constraints == nil { + dst.Constraints = nil + } else { + if dst.Constraints != nil { + if len(src.Constraints) > len(dst.Constraints) { + if cap(dst.Constraints) >= len(src.Constraints) { + dst.Constraints = (dst.Constraints)[:len(src.Constraints)] + } else { + dst.Constraints = make([]string, len(src.Constraints)) + } + } else if len(src.Constraints) < len(dst.Constraints) { + dst.Constraints = (dst.Constraints)[:len(src.Constraints)] + } + } else { + dst.Constraints = make([]string, len(src.Constraints)) + } + copy(dst.Constraints, src.Constraints) + } + if src.Preferences == nil { + dst.Preferences = nil + } else { + if dst.Preferences != nil { + if len(src.Preferences) > len(dst.Preferences) { + if cap(dst.Preferences) >= len(src.Preferences) { + dst.Preferences = (dst.Preferences)[:len(src.Preferences)] + } else { + dst.Preferences = make([]PlacementPreferences, len(src.Preferences)) + } + } else if len(src.Preferences) < len(dst.Preferences) { + dst.Preferences = (dst.Preferences)[:len(src.Preferences)] + } + } else { + dst.Preferences = make([]PlacementPreferences, len(src.Preferences)) + } + deriveDeepCopy_44(dst.Preferences, src.Preferences) + } + dst.MaxReplicas = src.MaxReplicas + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_34 recursively copies the contents of src into dst. +func deriveDeepCopy_34(dst, src *ServiceNetworkConfig) { + dst.Priority = src.Priority + if src.Aliases == nil { + dst.Aliases = nil + } else { + if dst.Aliases != nil { + if len(src.Aliases) > len(dst.Aliases) { + if cap(dst.Aliases) >= len(src.Aliases) { + dst.Aliases = (dst.Aliases)[:len(src.Aliases)] + } else { + dst.Aliases = make([]string, len(src.Aliases)) + } + } else if len(src.Aliases) < len(dst.Aliases) { + dst.Aliases = (dst.Aliases)[:len(src.Aliases)] + } + } else { + dst.Aliases = make([]string, len(src.Aliases)) + } + copy(dst.Aliases, src.Aliases) + } + dst.Ipv4Address = src.Ipv4Address + dst.Ipv6Address = src.Ipv6Address + if src.LinkLocalIPs == nil { + dst.LinkLocalIPs = nil + } else { + if dst.LinkLocalIPs != nil { + if len(src.LinkLocalIPs) > len(dst.LinkLocalIPs) { + if cap(dst.LinkLocalIPs) >= len(src.LinkLocalIPs) { + dst.LinkLocalIPs = (dst.LinkLocalIPs)[:len(src.LinkLocalIPs)] + } else { + dst.LinkLocalIPs = make([]string, len(src.LinkLocalIPs)) + } + } else if len(src.LinkLocalIPs) < len(dst.LinkLocalIPs) { + dst.LinkLocalIPs = (dst.LinkLocalIPs)[:len(src.LinkLocalIPs)] + } + } else { + dst.LinkLocalIPs = make([]string, len(src.LinkLocalIPs)) + } + copy(dst.LinkLocalIPs, src.LinkLocalIPs) + } + dst.MacAddress = src.MacAddress + if src.DriverOpts != nil { + dst.DriverOpts = make(map[string]string, len(src.DriverOpts)) + deriveDeepCopy_4(dst.DriverOpts, src.DriverOpts) + } else { + dst.DriverOpts = nil + } + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_35 recursively copies the contents of src into dst. +func deriveDeepCopy_35(dst, src *ServicePortConfig) { + dst.Name = src.Name + dst.Mode = src.Mode + dst.HostIP = src.HostIP + dst.Target = src.Target + dst.Published = src.Published + dst.Protocol = src.Protocol + dst.AppProtocol = src.AppProtocol + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_36 recursively copies the contents of src into dst. +func deriveDeepCopy_36(dst, src *ServiceSecretConfig) { + dst.Source = src.Source + dst.Target = src.Target + dst.UID = src.UID + dst.GID = src.GID + if src.Mode == nil { + dst.Mode = nil + } else { + dst.Mode = new(uint32) + *dst.Mode = *src.Mode + } + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_37 recursively copies the contents of src into dst. +func deriveDeepCopy_37(dst, src *UlimitsConfig) { + dst.Single = src.Single + dst.Soft = src.Soft + dst.Hard = src.Hard + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_38 recursively copies the contents of src into dst. +func deriveDeepCopy_38(dst, src *ServiceVolumeConfig) { + dst.Type = src.Type + dst.Source = src.Source + dst.Target = src.Target + dst.ReadOnly = src.ReadOnly + dst.Consistency = src.Consistency + if src.Bind == nil { + dst.Bind = nil + } else { + dst.Bind = new(ServiceVolumeBind) + deriveDeepCopy_45(dst.Bind, src.Bind) + } + if src.Volume == nil { + dst.Volume = nil + } else { + dst.Volume = new(ServiceVolumeVolume) + deriveDeepCopy_46(dst.Volume, src.Volume) + } + if src.Tmpfs == nil { + dst.Tmpfs = nil + } else { + dst.Tmpfs = new(ServiceVolumeTmpfs) + deriveDeepCopy_47(dst.Tmpfs, src.Tmpfs) + } + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_39 recursively copies the contents of src into dst. +func deriveDeepCopy_39(dst, src *IPAMConfig) { + dst.Driver = src.Driver + if src.Config == nil { + dst.Config = nil + } else { + if dst.Config != nil { + if len(src.Config) > len(dst.Config) { + if cap(dst.Config) >= len(src.Config) { + dst.Config = (dst.Config)[:len(src.Config)] + } else { + dst.Config = make([]*IPAMPool, len(src.Config)) + } + } else if len(src.Config) < len(dst.Config) { + dst.Config = (dst.Config)[:len(src.Config)] + } + } else { + dst.Config = make([]*IPAMPool, len(src.Config)) + } + deriveDeepCopy_48(dst.Config, src.Config) + } + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_40 recursively copies the contents of src into dst. +func deriveDeepCopy_40(dst, src *Trigger) { + dst.Path = src.Path + dst.Action = src.Action + dst.Target = src.Target + if src.Ignore == nil { + dst.Ignore = nil + } else { + if dst.Ignore != nil { + if len(src.Ignore) > len(dst.Ignore) { + if cap(dst.Ignore) >= len(src.Ignore) { + dst.Ignore = (dst.Ignore)[:len(src.Ignore)] + } else { + dst.Ignore = make([]string, len(src.Ignore)) + } + } else if len(src.Ignore) < len(dst.Ignore) { + dst.Ignore = (dst.Ignore)[:len(src.Ignore)] + } + } else { + dst.Ignore = make([]string, len(src.Ignore)) + } + copy(dst.Ignore, src.Ignore) + } + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_41 recursively copies the contents of src into dst. +func deriveDeepCopy_41(dst, src *WeightDevice) { + dst.Path = src.Path + dst.Weight = src.Weight + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_42 recursively copies the contents of src into dst. +func deriveDeepCopy_42(dst, src *ThrottleDevice) { + dst.Path = src.Path + dst.Rate = src.Rate + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_43 recursively copies the contents of src into dst. +func deriveDeepCopy_43(dst, src *Resource) { + dst.NanoCPUs = src.NanoCPUs + dst.MemoryBytes = src.MemoryBytes + dst.Pids = src.Pids + if src.Devices == nil { + dst.Devices = nil + } else { + if dst.Devices != nil { + if len(src.Devices) > len(dst.Devices) { + if cap(dst.Devices) >= len(src.Devices) { + dst.Devices = (dst.Devices)[:len(src.Devices)] + } else { + dst.Devices = make([]DeviceRequest, len(src.Devices)) + } + } else if len(src.Devices) < len(dst.Devices) { + dst.Devices = (dst.Devices)[:len(src.Devices)] + } + } else { + dst.Devices = make([]DeviceRequest, len(src.Devices)) + } + deriveDeepCopy_49(dst.Devices, src.Devices) + } + if src.GenericResources == nil { + dst.GenericResources = nil + } else { + if dst.GenericResources != nil { + if len(src.GenericResources) > len(dst.GenericResources) { + if cap(dst.GenericResources) >= len(src.GenericResources) { + dst.GenericResources = (dst.GenericResources)[:len(src.GenericResources)] + } else { + dst.GenericResources = make([]GenericResource, len(src.GenericResources)) + } + } else if len(src.GenericResources) < len(dst.GenericResources) { + dst.GenericResources = (dst.GenericResources)[:len(src.GenericResources)] + } + } else { + dst.GenericResources = make([]GenericResource, len(src.GenericResources)) + } + deriveDeepCopy_50(dst.GenericResources, src.GenericResources) + } + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_44 recursively copies the contents of src into dst. +func deriveDeepCopy_44(dst, src []PlacementPreferences) { + for src_i, src_value := range src { + func() { + field := new(PlacementPreferences) + deriveDeepCopy_51(field, &src_value) + dst[src_i] = *field + }() + } +} + +// deriveDeepCopy_45 recursively copies the contents of src into dst. +func deriveDeepCopy_45(dst, src *ServiceVolumeBind) { + dst.SELinux = src.SELinux + dst.Propagation = src.Propagation + dst.CreateHostPath = src.CreateHostPath + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_46 recursively copies the contents of src into dst. +func deriveDeepCopy_46(dst, src *ServiceVolumeVolume) { + dst.NoCopy = src.NoCopy + dst.Subpath = src.Subpath + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_47 recursively copies the contents of src into dst. +func deriveDeepCopy_47(dst, src *ServiceVolumeTmpfs) { + dst.Size = src.Size + dst.Mode = src.Mode + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_48 recursively copies the contents of src into dst. +func deriveDeepCopy_48(dst, src []*IPAMPool) { + for src_i, src_value := range src { + if src_value == nil { + dst[src_i] = nil + } else { + dst[src_i] = new(IPAMPool) + deriveDeepCopy_52(dst[src_i], src_value) + } + } +} + +// deriveDeepCopy_49 recursively copies the contents of src into dst. +func deriveDeepCopy_49(dst, src []DeviceRequest) { + for src_i, src_value := range src { + func() { + field := new(DeviceRequest) + deriveDeepCopy_53(field, &src_value) + dst[src_i] = *field + }() + } +} + +// deriveDeepCopy_50 recursively copies the contents of src into dst. +func deriveDeepCopy_50(dst, src []GenericResource) { + for src_i, src_value := range src { + func() { + field := new(GenericResource) + deriveDeepCopy_54(field, &src_value) + dst[src_i] = *field + }() + } +} + +// deriveDeepCopy_51 recursively copies the contents of src into dst. +func deriveDeepCopy_51(dst, src *PlacementPreferences) { + dst.Spread = src.Spread + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_52 recursively copies the contents of src into dst. +func deriveDeepCopy_52(dst, src *IPAMPool) { + dst.Subnet = src.Subnet + dst.Gateway = src.Gateway + dst.IPRange = src.IPRange + if src.AuxiliaryAddresses != nil { + dst.AuxiliaryAddresses = make(map[string]string, len(src.AuxiliaryAddresses)) + deriveDeepCopy_4(dst.AuxiliaryAddresses, src.AuxiliaryAddresses) + } else { + dst.AuxiliaryAddresses = nil + } + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_53 recursively copies the contents of src into dst. +func deriveDeepCopy_53(dst, src *DeviceRequest) { + if src.Capabilities == nil { + dst.Capabilities = nil + } else { + if dst.Capabilities != nil { + if len(src.Capabilities) > len(dst.Capabilities) { + if cap(dst.Capabilities) >= len(src.Capabilities) { + dst.Capabilities = (dst.Capabilities)[:len(src.Capabilities)] + } else { + dst.Capabilities = make([]string, len(src.Capabilities)) + } + } else if len(src.Capabilities) < len(dst.Capabilities) { + dst.Capabilities = (dst.Capabilities)[:len(src.Capabilities)] + } + } else { + dst.Capabilities = make([]string, len(src.Capabilities)) + } + copy(dst.Capabilities, src.Capabilities) + } + dst.Driver = src.Driver + dst.Count = src.Count + if src.IDs == nil { + dst.IDs = nil + } else { + if dst.IDs != nil { + if len(src.IDs) > len(dst.IDs) { + if cap(dst.IDs) >= len(src.IDs) { + dst.IDs = (dst.IDs)[:len(src.IDs)] + } else { + dst.IDs = make([]string, len(src.IDs)) + } + } else if len(src.IDs) < len(dst.IDs) { + dst.IDs = (dst.IDs)[:len(src.IDs)] + } + } else { + dst.IDs = make([]string, len(src.IDs)) + } + copy(dst.IDs, src.IDs) + } +} + +// deriveDeepCopy_54 recursively copies the contents of src into dst. +func deriveDeepCopy_54(dst, src *GenericResource) { + if src.DiscreteResourceSpec == nil { + dst.DiscreteResourceSpec = nil + } else { + dst.DiscreteResourceSpec = new(DiscreteGenericResource) + deriveDeepCopy_55(dst.DiscreteResourceSpec, src.DiscreteResourceSpec) + } + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_55 recursively copies the contents of src into dst. +func deriveDeepCopy_55(dst, src *DiscreteGenericResource) { + dst.Kind = src.Kind + dst.Value = src.Value + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} diff --git a/vendor/github.com/compose-spec/compose-go/types/develop.go b/vendor/github.com/compose-spec/compose-go/v2/types/develop.go similarity index 65% rename from vendor/github.com/compose-spec/compose-go/types/develop.go rename to vendor/github.com/compose-spec/compose-go/v2/types/develop.go index 5fc10716f..24e142c3a 100644 --- a/vendor/github.com/compose-spec/compose-go/types/develop.go +++ b/vendor/github.com/compose-spec/compose-go/v2/types/develop.go @@ -17,7 +17,9 @@ package types type DevelopConfig struct { - Watch []Trigger `json:"watch,omitempty"` + Watch []Trigger `yaml:"watch,omitempty" json:"watch,omitempty"` + + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } type WatchAction string @@ -29,8 +31,9 @@ const ( ) type Trigger struct { - Path string `json:"path,omitempty"` - Action WatchAction `json:"action,omitempty"` - Target string `json:"target,omitempty"` - Ignore []string `json:"ignore,omitempty"` + Path string `yaml:"path" json:"path"` + Action WatchAction `yaml:"action" json:"action"` + Target string `yaml:"target,omitempty" json:"target,omitempty"` + Ignore []string `yaml:"ignore,omitempty" json:"ignore,omitempty"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } diff --git a/vendor/github.com/compose-spec/compose-go/types/device.go b/vendor/github.com/compose-spec/compose-go/v2/types/device.go similarity index 88% rename from vendor/github.com/compose-spec/compose-go/types/device.go rename to vendor/github.com/compose-spec/compose-go/v2/types/device.go index 81b4bea4a..240e87786 100644 --- a/vendor/github.com/compose-spec/compose-go/types/device.go +++ b/vendor/github.com/compose-spec/compose-go/v2/types/device.go @@ -17,10 +17,9 @@ package types import ( + "fmt" "strconv" "strings" - - "github.com/pkg/errors" ) type DeviceRequest struct { @@ -43,11 +42,11 @@ func (c *DeviceCount) DecodeMapstructure(value interface{}) error { } i, err := strconv.ParseInt(v, 10, 64) if err != nil { - return errors.Errorf("invalid value %q, the only value allowed is 'all' or a number", v) + return fmt.Errorf("invalid value %q, the only value allowed is 'all' or a number", v) } *c = DeviceCount(i) default: - return errors.Errorf("invalid type %T for device count", v) + return fmt.Errorf("invalid type %T for device count", v) } return nil } diff --git a/vendor/github.com/compose-spec/compose-go/types/duration.go b/vendor/github.com/compose-spec/compose-go/v2/types/duration.go similarity index 100% rename from vendor/github.com/compose-spec/compose-go/types/duration.go rename to vendor/github.com/compose-spec/compose-go/v2/types/duration.go diff --git a/vendor/github.com/compose-spec/compose-go/v2/types/envfile.go b/vendor/github.com/compose-spec/compose-go/v2/types/envfile.go new file mode 100644 index 000000000..f0fa72213 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/types/envfile.go @@ -0,0 +1,46 @@ +/* + Copyright 2020 The Compose Specification 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 types + +import ( + "encoding/json" +) + +type EnvFile struct { + Path string `yaml:"path,omitempty" json:"path,omitempty"` + Required bool `yaml:"required" json:"required"` +} + +// MarshalYAML makes EnvFile implement yaml.Marshaler +func (e EnvFile) MarshalYAML() (interface{}, error) { + if e.Required { + return e.Path, nil + } + return map[string]any{ + "path": e.Path, + "required": e.Required, + }, nil +} + +// MarshalJSON makes EnvFile implement json.Marshaler +func (e *EnvFile) MarshalJSON() ([]byte, error) { + if e.Required { + return json.Marshal(e.Path) + } + // Pass as a value to avoid re-entering this method and use the default implementation + return json.Marshal(*e) +} diff --git a/vendor/github.com/compose-spec/compose-go/types/healthcheck.go b/vendor/github.com/compose-spec/compose-go/v2/types/healthcheck.go similarity index 96% rename from vendor/github.com/compose-spec/compose-go/types/healthcheck.go rename to vendor/github.com/compose-spec/compose-go/v2/types/healthcheck.go index 1bbf5e9e2..c6c3b37e0 100644 --- a/vendor/github.com/compose-spec/compose-go/types/healthcheck.go +++ b/vendor/github.com/compose-spec/compose-go/v2/types/healthcheck.go @@ -30,7 +30,7 @@ type HealthCheckConfig struct { StartInterval *Duration `yaml:"start_interval,omitempty" json:"start_interval,omitempty"` Disable bool `yaml:"disable,omitempty" json:"disable,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // HealthCheckTest is the command run to test the health of a service diff --git a/vendor/github.com/compose-spec/compose-go/v2/types/hostList.go b/vendor/github.com/compose-spec/compose-go/v2/types/hostList.go new file mode 100644 index 000000000..9bc0fbc5d --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/types/hostList.go @@ -0,0 +1,144 @@ +/* + Copyright 2020 The Compose Specification 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 types + +import ( + "encoding/json" + "fmt" + "sort" + "strings" +) + +// HostsList is a list of colon-separated host-ip mappings +type HostsList map[string][]string + +// NewHostsList creates a HostsList from a list of `host=ip` strings +func NewHostsList(hosts []string) (HostsList, error) { + list := HostsList{} + for _, s := range hosts { + var found bool + for _, sep := range hostListSerapators { + host, ip, ok := strings.Cut(s, sep) + if ok { + // Mapping found with this separator, stop here. + if ips, ok := list[host]; ok { + list[host] = append(ips, strings.Split(ip, ",")...) + } else { + list[host] = strings.Split(ip, ",") + } + found = true + break + } + } + if !found { + return nil, fmt.Errorf("invalid additional host, missing IP: %s", s) + } + } + err := list.cleanup() + return list, err +} + +// AsList returns host-ip mappings as a list of strings, using the given +// separator. The Docker Engine API expects ':' separators, the original format +// for '--add-hosts'. But an '=' separator is used in YAML/JSON renderings to +// make IPv6 addresses more readable (for example "my-host=::1" instead of +// "my-host:::1"). +func (h HostsList) AsList(sep string) []string { + l := make([]string, 0, len(h)) + for k, v := range h { + for _, ip := range v { + l = append(l, fmt.Sprintf("%s%s%s", k, sep, ip)) + } + } + return l +} + +func (h HostsList) MarshalYAML() (interface{}, error) { + list := h.AsList("=") + sort.Strings(list) + return list, nil +} + +func (h HostsList) MarshalJSON() ([]byte, error) { + list := h.AsList("=") + sort.Strings(list) + return json.Marshal(list) +} + +var hostListSerapators = []string{"=", ":"} + +func (h *HostsList) DecodeMapstructure(value interface{}) error { + switch v := value.(type) { + case map[string]interface{}: + list := make(HostsList, len(v)) + for i, e := range v { + if e == nil { + e = "" + } + switch t := e.(type) { + case string: + list[i] = []string{t} + case []any: + hosts := make([]string, len(t)) + for j, h := range t { + hosts[j] = fmt.Sprint(h) + } + list[i] = hosts + default: + return fmt.Errorf("unexpected value type %T for extra_hosts entry", value) + } + } + err := list.cleanup() + if err != nil { + return err + } + *h = list + return nil + case []interface{}: + s := make([]string, len(v)) + for i, e := range v { + s[i] = fmt.Sprint(e) + } + list, err := NewHostsList(s) + if err != nil { + return err + } + *h = list + return nil + default: + return fmt.Errorf("unexpected value type %T for extra_hosts", value) + } +} + +func (h HostsList) cleanup() error { + for host, ips := range h { + // Check that there is a hostname and that it doesn't contain either + // of the allowed separators, to generate a clearer error than the + // engine would do if it splits the string differently. + if host == "" || strings.ContainsAny(host, ":=") { + return fmt.Errorf("bad host name '%s'", host) + } + for i, ip := range ips { + // Remove brackets from IP addresses (for example "[::1]" -> "::1"). + if len(ip) > 2 && ip[0] == '[' && ip[len(ip)-1] == ']' { + ips[i] = ip[1 : len(ip)-1] + } + } + h[host] = ips + } + return nil +} diff --git a/vendor/github.com/compose-spec/compose-go/types/labels.go b/vendor/github.com/compose-spec/compose-go/v2/types/labels.go similarity index 100% rename from vendor/github.com/compose-spec/compose-go/types/labels.go rename to vendor/github.com/compose-spec/compose-go/v2/types/labels.go diff --git a/vendor/github.com/compose-spec/compose-go/v2/types/mapping.go b/vendor/github.com/compose-spec/compose-go/v2/types/mapping.go new file mode 100644 index 000000000..de6fb1233 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/types/mapping.go @@ -0,0 +1,217 @@ +/* + Copyright 2020 The Compose Specification 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 types + +import ( + "fmt" + "sort" + "strings" +) + +// MappingWithEquals is a mapping type that can be converted from a list of +// key[=value] strings. +// For the key with an empty value (`key=`), the mapped value is set to a pointer to `""`. +// For the key without value (`key`), the mapped value is set to nil. +type MappingWithEquals map[string]*string + +// NewMappingWithEquals build a new Mapping from a set of KEY=VALUE strings +func NewMappingWithEquals(values []string) MappingWithEquals { + mapping := MappingWithEquals{} + for _, env := range values { + tokens := strings.SplitN(env, "=", 2) + if len(tokens) > 1 { + mapping[tokens[0]] = &tokens[1] + } else { + mapping[env] = nil + } + } + return mapping +} + +// OverrideBy update MappingWithEquals with values from another MappingWithEquals +func (m MappingWithEquals) OverrideBy(other MappingWithEquals) MappingWithEquals { + for k, v := range other { + m[k] = v + } + return m +} + +// Resolve update a MappingWithEquals for keys without value (`key`, but not `key=`) +func (m MappingWithEquals) Resolve(lookupFn func(string) (string, bool)) MappingWithEquals { + for k, v := range m { + if v == nil { + if value, ok := lookupFn(k); ok { + m[k] = &value + } + } + } + return m +} + +// RemoveEmpty excludes keys that are not associated with a value +func (m MappingWithEquals) RemoveEmpty() MappingWithEquals { + for k, v := range m { + if v == nil { + delete(m, k) + } + } + return m +} + +func (m *MappingWithEquals) DecodeMapstructure(value interface{}) error { + switch v := value.(type) { + case map[string]interface{}: + mapping := make(MappingWithEquals, len(v)) + for k, e := range v { + mapping[k] = mappingValue(e) + } + *m = mapping + case []interface{}: + mapping := make(MappingWithEquals, len(v)) + for _, s := range v { + k, e, ok := strings.Cut(fmt.Sprint(s), "=") + if !ok { + mapping[k] = nil + } else { + mapping[k] = mappingValue(e) + } + } + *m = mapping + default: + return fmt.Errorf("unexpected value type %T for mapping", value) + } + return nil +} + +// label value can be a string | number | boolean | null +func mappingValue(e interface{}) *string { + if e == nil { + return nil + } + switch v := e.(type) { + case string: + return &v + default: + s := fmt.Sprint(v) + return &s + } +} + +// Mapping is a mapping type that can be converted from a list of +// key[=value] strings. +// For the key with an empty value (`key=`), or key without value (`key`), the +// mapped value is set to an empty string `""`. +type Mapping map[string]string + +// NewMapping build a new Mapping from a set of KEY=VALUE strings +func NewMapping(values []string) Mapping { + mapping := Mapping{} + for _, value := range values { + parts := strings.SplitN(value, "=", 2) + key := parts[0] + switch { + case len(parts) == 1: + mapping[key] = "" + default: + mapping[key] = parts[1] + } + } + return mapping +} + +// convert values into a set of KEY=VALUE strings +func (m Mapping) Values() []string { + values := make([]string, 0, len(m)) + for k, v := range m { + values = append(values, fmt.Sprintf("%s=%s", k, v)) + } + sort.Strings(values) + return values +} + +// ToMappingWithEquals converts Mapping into a MappingWithEquals with pointer references +func (m Mapping) ToMappingWithEquals() MappingWithEquals { + mapping := MappingWithEquals{} + for k, v := range m { + v := v + mapping[k] = &v + } + return mapping +} + +func (m Mapping) Resolve(s string) (string, bool) { + v, ok := m[s] + return v, ok +} + +func (m Mapping) Clone() Mapping { + clone := Mapping{} + for k, v := range m { + clone[k] = v + } + return clone +} + +// Merge adds all values from second mapping which are not already defined +func (m Mapping) Merge(o Mapping) Mapping { + for k, v := range o { + if _, set := m[k]; !set { + m[k] = v + } + } + return m +} + +func (m *Mapping) DecodeMapstructure(value interface{}) error { + switch v := value.(type) { + case map[string]interface{}: + mapping := make(Mapping, len(v)) + for k, e := range v { + if e == nil { + e = "" + } + mapping[k] = fmt.Sprint(e) + } + *m = mapping + case []interface{}: + *m = decodeMapping(v, "=") + default: + return fmt.Errorf("unexpected value type %T for mapping", value) + } + return nil +} + +// Generate a mapping by splitting strings at any of seps, which will be tried +// in-order for each input string. (For example, to allow the preferred 'host=ip' +// in 'extra_hosts', as well as 'host:ip' for backwards compatibility.) +func decodeMapping(v []interface{}, seps ...string) map[string]string { + mapping := make(Mapping, len(v)) + for _, s := range v { + for i, sep := range seps { + k, e, ok := strings.Cut(fmt.Sprint(s), sep) + if ok { + // Mapping found with this separator, stop here. + mapping[k] = e + break + } else if i == len(seps)-1 { + // No more separators to try, map to empty string. + mapping[k] = "" + } + } + } + return mapping +} diff --git a/vendor/github.com/compose-spec/compose-go/types/options.go b/vendor/github.com/compose-spec/compose-go/v2/types/options.go similarity index 91% rename from vendor/github.com/compose-spec/compose-go/types/options.go rename to vendor/github.com/compose-spec/compose-go/v2/types/options.go index 7ae85793d..c603d0513 100644 --- a/vendor/github.com/compose-spec/compose-go/types/options.go +++ b/vendor/github.com/compose-spec/compose-go/v2/types/options.go @@ -16,11 +16,7 @@ package types -import ( - "fmt" - - "github.com/pkg/errors" -) +import "fmt" // Options is a mapping type for options we pass as-is to container runtime type Options map[string]string @@ -40,7 +36,7 @@ func (d *Options) DecodeMapstructure(value interface{}) error { case map[string]string: *d = v default: - return errors.Errorf("invalid type %T for options", value) + return fmt.Errorf("invalid type %T for options", value) } return nil } diff --git a/vendor/github.com/compose-spec/compose-go/v2/types/project.go b/vendor/github.com/compose-spec/compose-go/v2/types/project.go new file mode 100644 index 000000000..7e3ef5f06 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/types/project.go @@ -0,0 +1,713 @@ +/* + Copyright 2020 The Compose Specification 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 types + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "sort" + + "github.com/compose-spec/compose-go/v2/dotenv" + "github.com/compose-spec/compose-go/v2/errdefs" + "github.com/compose-spec/compose-go/v2/utils" + "github.com/distribution/reference" + godigest "github.com/opencontainers/go-digest" + "golang.org/x/exp/maps" + "golang.org/x/sync/errgroup" + "gopkg.in/yaml.v3" +) + +// Project is the result of loading a set of compose files +// Since v2, Project are managed as immutable objects. +// Each public functions which mutate Project state now return a copy of the original Project with the expected changes. +type Project struct { + Name string `yaml:"name,omitempty" json:"name,omitempty"` + WorkingDir string `yaml:"-" json:"-"` + Services Services `yaml:"services" json:"services"` + Networks Networks `yaml:"networks,omitempty" json:"networks,omitempty"` + Volumes Volumes `yaml:"volumes,omitempty" json:"volumes,omitempty"` + Secrets Secrets `yaml:"secrets,omitempty" json:"secrets,omitempty"` + Configs Configs `yaml:"configs,omitempty" json:"configs,omitempty"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` // https://github.com/golang/go/issues/6213 + + ComposeFiles []string `yaml:"-" json:"-"` + Environment Mapping `yaml:"-" json:"-"` + + // DisabledServices track services which have been disable as profile is not active + DisabledServices Services `yaml:"-" json:"-"` + Profiles []string `yaml:"-" json:"-"` +} + +// ServiceNames return names for all services in this Compose config +func (p *Project) ServiceNames() []string { + var names []string + for k := range p.Services { + names = append(names, k) + } + sort.Strings(names) + return names +} + +// DisabledServiceNames return names for all disabled services in this Compose config +func (p *Project) DisabledServiceNames() []string { + var names []string + for k := range p.DisabledServices { + names = append(names, k) + } + sort.Strings(names) + return names +} + +// VolumeNames return names for all volumes in this Compose config +func (p *Project) VolumeNames() []string { + var names []string + for k := range p.Volumes { + names = append(names, k) + } + sort.Strings(names) + return names +} + +// NetworkNames return names for all volumes in this Compose config +func (p *Project) NetworkNames() []string { + var names []string + for k := range p.Networks { + names = append(names, k) + } + sort.Strings(names) + return names +} + +// SecretNames return names for all secrets in this Compose config +func (p *Project) SecretNames() []string { + var names []string + for k := range p.Secrets { + names = append(names, k) + } + sort.Strings(names) + return names +} + +// ConfigNames return names for all configs in this Compose config +func (p *Project) ConfigNames() []string { + var names []string + for k := range p.Configs { + names = append(names, k) + } + sort.Strings(names) + return names +} + +func (p *Project) ServicesWithBuild() []string { + servicesBuild := p.Services.Filter(func(s ServiceConfig) bool { + return s.Build != nil && s.Build.Context != "" + }) + return maps.Keys(servicesBuild) +} + +func (p *Project) ServicesWithExtends() []string { + servicesExtends := p.Services.Filter(func(s ServiceConfig) bool { + return s.Extends != nil && *s.Extends != (ExtendsConfig{}) + }) + return maps.Keys(servicesExtends) +} + +func (p *Project) ServicesWithDependsOn() []string { + servicesDependsOn := p.Services.Filter(func(s ServiceConfig) bool { + return len(s.DependsOn) > 0 + }) + return maps.Keys(servicesDependsOn) +} + +func (p *Project) ServicesWithCapabilities() ([]string, []string, []string) { + capabilities := []string{} + gpu := []string{} + tpu := []string{} + for _, service := range p.Services { + deploy := service.Deploy + if deploy == nil { + continue + } + reservation := deploy.Resources.Reservations + if reservation == nil { + continue + } + devices := reservation.Devices + for _, d := range devices { + if len(d.Capabilities) > 0 { + capabilities = append(capabilities, service.Name) + } + for _, c := range d.Capabilities { + if c == "gpu" { + gpu = append(gpu, service.Name) + } else if c == "tpu" { + tpu = append(tpu, service.Name) + } + } + } + } + + return utils.RemoveDuplicates(capabilities), utils.RemoveDuplicates(gpu), utils.RemoveDuplicates(tpu) +} + +// GetServices retrieve services by names, or return all services if no name specified +func (p *Project) GetServices(names ...string) (Services, error) { + if len(names) == 0 { + return p.Services, nil + } + services := Services{} + for _, name := range names { + service, err := p.GetService(name) + if err != nil { + return nil, err + } + services[name] = service + } + return services, nil +} + +func (p *Project) getServicesByNames(names ...string) (Services, []string) { + if len(names) == 0 { + return p.Services, nil + } + services := Services{} + var servicesNotFound []string + for _, name := range names { + service, ok := p.Services[name] + if !ok { + servicesNotFound = append(servicesNotFound, name) + continue + } + services[name] = service + } + return services, servicesNotFound +} + +// GetDisabledService retrieve disabled service by name +func (p Project) GetDisabledService(name string) (ServiceConfig, error) { + service, ok := p.DisabledServices[name] + if !ok { + return ServiceConfig{}, fmt.Errorf("no such service: %s", name) + } + return service, nil +} + +// GetService retrieve a specific service by name +func (p *Project) GetService(name string) (ServiceConfig, error) { + service, ok := p.Services[name] + if !ok { + _, ok := p.DisabledServices[name] + if ok { + return ServiceConfig{}, fmt.Errorf("no such service: %s: %w", name, errdefs.ErrDisabled) + } + return ServiceConfig{}, fmt.Errorf("no such service: %s: %w", name, errdefs.ErrNotFound) + } + return service, nil +} + +func (p *Project) AllServices() Services { + all := Services{} + for name, service := range p.Services { + all[name] = service + } + for name, service := range p.DisabledServices { + all[name] = service + } + return all +} + +type ServiceFunc func(name string, service *ServiceConfig) error + +// ForEachService runs ServiceFunc on each service and dependencies according to DependencyPolicy +func (p *Project) ForEachService(names []string, fn ServiceFunc, options ...DependencyOption) error { + if len(options) == 0 { + // backward compatibility + options = []DependencyOption{IncludeDependencies} + } + return p.withServices(names, fn, map[string]bool{}, options, map[string]ServiceDependency{}) +} + +type withServicesOptions struct { + dependencyPolicy int +} + +const ( + includeDependencies = iota + includeDependents + ignoreDependencies +) + +func (p *Project) withServices(names []string, fn ServiceFunc, seen map[string]bool, options []DependencyOption, dependencies map[string]ServiceDependency) error { + services, servicesNotFound := p.getServicesByNames(names...) + if len(servicesNotFound) > 0 { + for _, serviceNotFound := range servicesNotFound { + if dependency, ok := dependencies[serviceNotFound]; !ok || dependency.Required { + return fmt.Errorf("no such service: %s", serviceNotFound) + } + } + } + opts := withServicesOptions{ + dependencyPolicy: includeDependencies, + } + for _, option := range options { + option(&opts) + } + + for name, service := range services { + if seen[name] { + continue + } + seen[name] = true + var dependencies map[string]ServiceDependency + switch opts.dependencyPolicy { + case includeDependents: + dependencies = utils.MapsAppend(dependencies, p.dependentsForService(service)) + case includeDependencies: + dependencies = utils.MapsAppend(dependencies, service.DependsOn) + case ignoreDependencies: + // Noop + } + if len(dependencies) > 0 { + err := p.withServices(utils.MapKeys(dependencies), fn, seen, options, dependencies) + if err != nil { + return err + } + } + if err := fn(name, service.deepCopy()); err != nil { + return err + } + } + return nil +} + +func (p *Project) GetDependentsForService(s ServiceConfig) []string { + return utils.MapKeys(p.dependentsForService(s)) +} + +func (p *Project) dependentsForService(s ServiceConfig) map[string]ServiceDependency { + dependent := make(map[string]ServiceDependency) + for _, service := range p.Services { + for name, dependency := range service.DependsOn { + if name == s.Name { + dependent[service.Name] = dependency + } + } + } + return dependent +} + +// RelativePath resolve a relative path based project's working directory +func (p *Project) RelativePath(path string) string { + if path[0] == '~' { + home, _ := os.UserHomeDir() + path = filepath.Join(home, path[1:]) + } + if filepath.IsAbs(path) { + return path + } + return filepath.Join(p.WorkingDir, path) +} + +// HasProfile return true if service has no profile declared or has at least one profile matching +func (s ServiceConfig) HasProfile(profiles []string) bool { + if len(s.Profiles) == 0 { + return true + } + for _, p := range profiles { + if p == "*" { + return true + } + for _, sp := range s.Profiles { + if sp == p { + return true + } + } + } + return false +} + +// WithProfiles disables services which don't match selected profiles +// It returns a new Project instance with the changes and keep the original Project unchanged +func (p *Project) WithProfiles(profiles []string) (*Project, error) { + newProject := p.deepCopy() + enabled := Services{} + disabled := Services{} + for name, service := range newProject.AllServices() { + if service.HasProfile(profiles) { + enabled[name] = service + } else { + disabled[name] = service + } + } + newProject.Services = enabled + newProject.DisabledServices = disabled + newProject.Profiles = profiles + return newProject, nil +} + +// WithServicesEnabled ensures services are enabled and activate profiles accordingly +// It returns a new Project instance with the changes and keep the original Project unchanged +func (p *Project) WithServicesEnabled(names ...string) (*Project, error) { + newProject := p.deepCopy() + if len(names) == 0 { + return newProject, nil + } + + profiles := append([]string{}, p.Profiles...) + for _, name := range names { + if _, ok := newProject.Services[name]; ok { + // already enabled + continue + } + service := p.DisabledServices[name] + profiles = append(profiles, service.Profiles...) + } + newProject, err := newProject.WithProfiles(profiles) + if err != nil { + return newProject, err + } + + return newProject.WithServicesEnvironmentResolved(true) +} + +// WithoutUnnecessaryResources drops networks/volumes/secrets/configs that are not referenced by active services +// It returns a new Project instance with the changes and keep the original Project unchanged +func (p *Project) WithoutUnnecessaryResources() *Project { + newProject := p.deepCopy() + requiredNetworks := map[string]struct{}{} + requiredVolumes := map[string]struct{}{} + requiredSecrets := map[string]struct{}{} + requiredConfigs := map[string]struct{}{} + for _, s := range newProject.Services { + for k := range s.Networks { + requiredNetworks[k] = struct{}{} + } + for _, v := range s.Volumes { + if v.Type != VolumeTypeVolume || v.Source == "" { + continue + } + requiredVolumes[v.Source] = struct{}{} + } + for _, v := range s.Secrets { + requiredSecrets[v.Source] = struct{}{} + } + if s.Build != nil { + for _, v := range s.Build.Secrets { + requiredSecrets[v.Source] = struct{}{} + } + } + for _, v := range s.Configs { + requiredConfigs[v.Source] = struct{}{} + } + } + + networks := Networks{} + for k := range requiredNetworks { + if value, ok := p.Networks[k]; ok { + networks[k] = value + } + } + newProject.Networks = networks + + volumes := Volumes{} + for k := range requiredVolumes { + if value, ok := p.Volumes[k]; ok { + volumes[k] = value + } + } + newProject.Volumes = volumes + + secrets := Secrets{} + for k := range requiredSecrets { + if value, ok := p.Secrets[k]; ok { + secrets[k] = value + } + } + newProject.Secrets = secrets + + configs := Configs{} + for k := range requiredConfigs { + if value, ok := p.Configs[k]; ok { + configs[k] = value + } + } + newProject.Configs = configs + return newProject +} + +type DependencyOption func(options *withServicesOptions) + +func IncludeDependencies(options *withServicesOptions) { + options.dependencyPolicy = includeDependencies +} + +func IncludeDependents(options *withServicesOptions) { + options.dependencyPolicy = includeDependents +} + +func IgnoreDependencies(options *withServicesOptions) { + options.dependencyPolicy = ignoreDependencies +} + +// WithSelectedServices restricts the project model to selected services and dependencies +// It returns a new Project instance with the changes and keep the original Project unchanged +func (p *Project) WithSelectedServices(names []string, options ...DependencyOption) (*Project, error) { + newProject := p.deepCopy() + if len(names) == 0 { + // All services + return newProject, nil + } + + set := utils.NewSet[string]() + err := p.ForEachService(names, func(name string, service *ServiceConfig) error { + set.Add(name) + return nil + }, options...) + if err != nil { + return nil, err + } + + // Disable all services which are not explicit target or dependencies + enabled := Services{} + for name, s := range newProject.Services { + if _, ok := set[name]; ok { + // remove all dependencies but those implied by explicitly selected services + dependencies := s.DependsOn + for d := range dependencies { + if _, ok := set[d]; !ok { + delete(dependencies, d) + } + } + s.DependsOn = dependencies + enabled[name] = s + } else { + newProject = newProject.WithServicesDisabled(name) + } + } + newProject.Services = enabled + return newProject, nil +} + +// WithServicesDisabled removes from the project model the given services and their references in all dependencies +// It returns a new Project instance with the changes and keep the original Project unchanged +func (p *Project) WithServicesDisabled(names ...string) *Project { + newProject := p.deepCopy() + if len(names) == 0 { + return newProject + } + if newProject.DisabledServices == nil { + newProject.DisabledServices = Services{} + } + for _, name := range names { + // We should remove all dependencies which reference the disabled service + for i, s := range newProject.Services { + if _, ok := s.DependsOn[name]; ok { + delete(s.DependsOn, name) + newProject.Services[i] = s + } + } + if service, ok := newProject.Services[name]; ok { + newProject.DisabledServices[name] = service + delete(newProject.Services, name) + } + } + return newProject +} + +// WithImagesResolved updates services images to include digest computed by a resolver function +// It returns a new Project instance with the changes and keep the original Project unchanged +func (p *Project) WithImagesResolved(resolver func(named reference.Named) (godigest.Digest, error)) (*Project, error) { + return p.WithServicesTransform(func(name string, service ServiceConfig) (ServiceConfig, error) { + if service.Image == "" { + return service, nil + } + named, err := reference.ParseDockerRef(service.Image) + if err != nil { + return service, err + } + + if _, ok := named.(reference.Canonical); !ok { + // image is named but not digested reference + digest, err := resolver(named) + if err != nil { + return service, err + } + named, err = reference.WithDigest(named, digest) + if err != nil { + return service, err + } + } + service.Image = named.String() + return service, nil + }) +} + +// MarshalYAML marshal Project into a yaml tree +func (p *Project) MarshalYAML() ([]byte, error) { + buf := bytes.NewBuffer([]byte{}) + encoder := yaml.NewEncoder(buf) + encoder.SetIndent(2) + // encoder.CompactSeqIndent() FIXME https://github.com/go-yaml/yaml/pull/753 + err := encoder.Encode(p) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// MarshalJSON makes Config implement json.Marshaler +func (p *Project) MarshalJSON() ([]byte, error) { + m := map[string]interface{}{ + "name": p.Name, + "services": p.Services, + } + + if len(p.Networks) > 0 { + m["networks"] = p.Networks + } + if len(p.Volumes) > 0 { + m["volumes"] = p.Volumes + } + if len(p.Secrets) > 0 { + m["secrets"] = p.Secrets + } + if len(p.Configs) > 0 { + m["configs"] = p.Configs + } + for k, v := range p.Extensions { + m[k] = v + } + return json.MarshalIndent(m, "", " ") +} + +// WithServicesEnvironmentResolved parses env_files set for services to resolve the actual environment map for services +// It returns a new Project instance with the changes and keep the original Project unchanged +func (p Project) WithServicesEnvironmentResolved(discardEnvFiles bool) (*Project, error) { + newProject := p.deepCopy() + for i, service := range newProject.Services { + service.Environment = service.Environment.Resolve(newProject.Environment.Resolve) + + environment := MappingWithEquals{} + // resolve variables based on other files we already parsed, + project's environment + var resolve dotenv.LookupFn = func(s string) (string, bool) { + v, ok := environment[s] + if ok && v != nil { + return *v, ok + } + return newProject.Environment.Resolve(s) + } + + for _, envFile := range service.EnvFiles { + if _, err := os.Stat(envFile.Path); os.IsNotExist(err) { + if envFile.Required { + return nil, fmt.Errorf("env file %s not found: %w", envFile.Path, err) + } + continue + } + b, err := os.ReadFile(envFile.Path) + if err != nil { + return nil, fmt.Errorf("failed to load %s: %w", envFile.Path, err) + } + + fileVars, err := dotenv.ParseWithLookup(bytes.NewBuffer(b), resolve) + if err != nil { + return nil, fmt.Errorf("failed to read %s: %w", envFile.Path, err) + } + environment.OverrideBy(Mapping(fileVars).ToMappingWithEquals()) + } + + service.Environment = environment.OverrideBy(service.Environment) + + if discardEnvFiles { + service.EnvFiles = nil + } + newProject.Services[i] = service + } + return newProject, nil +} + +func (p *Project) deepCopy() *Project { + if p == nil { + return nil + } + n := &Project{} + deriveDeepCopyProject(n, p) + return n + +} + +// WithServicesTransform applies a transformation to project services and return a new project with transformation results +func (p *Project) WithServicesTransform(fn func(name string, s ServiceConfig) (ServiceConfig, error)) (*Project, error) { + type result struct { + name string + service ServiceConfig + } + expect := len(p.Services) + resultCh := make(chan result, expect) + newProject := p.deepCopy() + + eg, ctx := errgroup.WithContext(context.Background()) + eg.Go(func() error { + s := Services{} + for expect > 0 { + select { + case <-ctx.Done(): + // interrupted as some goroutine returned an error + return nil + case r := <-resultCh: + s[r.name] = r.service + expect-- + } + } + newProject.Services = s + return nil + }) + for n, s := range newProject.Services { + name := n + service := s + eg.Go(func() error { + updated, err := fn(name, service) + if err != nil { + return err + } + resultCh <- result{ + name: name, + service: updated, + } + return nil + }) + } + return newProject, eg.Wait() +} + +// CheckContainerNameUnicity validate project doesn't have services declaring the same container_name +func (p *Project) CheckContainerNameUnicity() error { + names := utils.Set[string]{} + for name, s := range p.Services { + if s.ContainerName != "" { + if existing, ok := names[s.ContainerName]; ok { + return fmt.Errorf(`services.%s: container name %q is already in use by service %s"`, name, s.ContainerName, existing) + } + names.Add(s.ContainerName) + } + } + return nil +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/types/services.go b/vendor/github.com/compose-spec/compose-go/v2/types/services.go new file mode 100644 index 000000000..0efc4b9fa --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/types/services.go @@ -0,0 +1,45 @@ +/* + Copyright 2020 The Compose Specification 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 types + +// Services is a map of ServiceConfig +type Services map[string]ServiceConfig + +// GetProfiles retrieve the profiles implicitly enabled by explicitly targeting selected services +func (s Services) GetProfiles() []string { + set := map[string]struct{}{} + for _, service := range s { + for _, p := range service.Profiles { + set[p] = struct{}{} + } + } + var profiles []string + for k := range set { + profiles = append(profiles, k) + } + return profiles +} + +func (s Services) Filter(predicate func(ServiceConfig) bool) Services { + services := Services{} + for name, service := range s { + if predicate(service) { + services[name] = service + } + } + return services +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/types/ssh.go b/vendor/github.com/compose-spec/compose-go/v2/types/ssh.go new file mode 100644 index 000000000..6d0edb695 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/types/ssh.go @@ -0,0 +1,73 @@ +/* + Copyright 2020 The Compose Specification 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 types + +import ( + "fmt" +) + +type SSHKey struct { + ID string `yaml:"id,omitempty" json:"id,omitempty"` + Path string `path:"path,omitempty" json:"path,omitempty"` +} + +// SSHConfig is a mapping type for SSH build config +type SSHConfig []SSHKey + +func (s SSHConfig) Get(id string) (string, error) { + for _, sshKey := range s { + if sshKey.ID == id { + return sshKey.Path, nil + } + } + return "", fmt.Errorf("ID %s not found in SSH keys", id) +} + +// MarshalYAML makes SSHKey implement yaml.Marshaller +func (s SSHKey) MarshalYAML() (interface{}, error) { + if s.Path == "" { + return s.ID, nil + } + return fmt.Sprintf("%s: %s", s.ID, s.Path), nil +} + +// MarshalJSON makes SSHKey implement json.Marshaller +func (s SSHKey) MarshalJSON() ([]byte, error) { + if s.Path == "" { + return []byte(fmt.Sprintf(`%q`, s.ID)), nil + } + return []byte(fmt.Sprintf(`%q: %s`, s.ID, s.Path)), nil +} + +func (s *SSHConfig) DecodeMapstructure(value interface{}) error { + v, ok := value.(map[string]any) + if !ok { + return fmt.Errorf("invalid ssh config type %T", value) + } + result := make(SSHConfig, len(v)) + i := 0 + for id, path := range v { + key := SSHKey{ID: id} + if path != nil { + key.Path = fmt.Sprint(path) + } + result[i] = key + i++ + } + *s = result + return nil +} diff --git a/vendor/github.com/compose-spec/compose-go/types/stringOrList.go b/vendor/github.com/compose-spec/compose-go/v2/types/stringOrList.go similarity index 84% rename from vendor/github.com/compose-spec/compose-go/types/stringOrList.go rename to vendor/github.com/compose-spec/compose-go/v2/types/stringOrList.go index 3d91ad2a5..a6720df08 100644 --- a/vendor/github.com/compose-spec/compose-go/types/stringOrList.go +++ b/vendor/github.com/compose-spec/compose-go/v2/types/stringOrList.go @@ -16,11 +16,7 @@ package types -import ( - "fmt" - - "github.com/pkg/errors" -) +import "fmt" // StringList is a type for fields that can be a string or list of strings type StringList []string @@ -32,11 +28,15 @@ func (l *StringList) DecodeMapstructure(value interface{}) error { case []interface{}: list := make([]string, len(v)) for i, e := range v { - list[i] = e.(string) + val, ok := e.(string) + if !ok { + return fmt.Errorf("invalid type %T for string list", value) + } + list[i] = val } *l = list default: - return errors.Errorf("invalid type %T for string list", value) + return fmt.Errorf("invalid type %T for string list", value) } return nil } @@ -55,7 +55,7 @@ func (l *StringOrNumberList) DecodeMapstructure(value interface{}) error { } *l = list default: - return errors.Errorf("invalid type %T for string list", value) + return fmt.Errorf("invalid type %T for string list", value) } return nil } diff --git a/vendor/github.com/compose-spec/compose-go/types/types.go b/vendor/github.com/compose-spec/compose-go/v2/types/types.go similarity index 77% rename from vendor/github.com/compose-spec/compose-go/types/types.go rename to vendor/github.com/compose-spec/compose-go/v2/types/types.go index d84be3ffc..bee644674 100644 --- a/vendor/github.com/compose-spec/compose-go/types/types.go +++ b/vendor/github.com/compose-spec/compose-go/v2/types/types.go @@ -20,35 +20,15 @@ import ( "encoding/json" "fmt" "sort" + "strconv" "strings" "github.com/docker/go-connections/nat" ) -// Services is a list of ServiceConfig -type Services []ServiceConfig - -// MarshalYAML makes Services implement yaml.Marshaller -func (s Services) MarshalYAML() (interface{}, error) { - services := map[string]ServiceConfig{} - for _, service := range s { - services[service.Name] = service - } - return services, nil -} - -// MarshalJSON makes Services implement json.Marshaler -func (s Services) MarshalJSON() ([]byte, error) { - data, err := s.MarshalYAML() - if err != nil { - return nil, err - } - return json.MarshalIndent(data, "", " ") -} - // ServiceConfig is the configuration of one service type ServiceConfig struct { - Name string `yaml:"-" json:"-"` + Name string `yaml:"name,omitempty" json:"-"` Profiles []string `yaml:"profiles,omitempty" json:"profiles,omitempty"` Annotations Mapping `yaml:"annotations,omitempty" json:"annotations,omitempty"` @@ -82,7 +62,7 @@ type ServiceConfig struct { DependsOn DependsOnConfig `yaml:"depends_on,omitempty" json:"depends_on,omitempty"` Deploy *DeployConfig `yaml:"deploy,omitempty" json:"deploy,omitempty"` DeviceCgroupRules []string `yaml:"device_cgroup_rules,omitempty" json:"device_cgroup_rules,omitempty"` - Devices []string `yaml:"devices,omitempty" json:"devices,omitempty"` + Devices []DeviceMapping `yaml:"devices,omitempty" json:"devices,omitempty"` DNS StringList `yaml:"dns,omitempty" json:"dns,omitempty"` DNSOpts []string `yaml:"dns_opt,omitempty" json:"dns_opt,omitempty"` DNSSearch StringList `yaml:"dns_search,omitempty" json:"dns_search,omitempty"` @@ -96,7 +76,7 @@ type ServiceConfig struct { Entrypoint ShellCommand `yaml:"entrypoint,omitempty" json:"entrypoint"` // NOTE: we can NOT omitempty for JSON! see ShellCommand type for details. Environment MappingWithEquals `yaml:"environment,omitempty" json:"environment,omitempty"` - EnvFile StringList `yaml:"env_file,omitempty" json:"env_file,omitempty"` + EnvFiles []EnvFile `yaml:"env_file,omitempty" json:"env_file,omitempty"` Expose StringOrNumberList `yaml:"expose,omitempty" json:"expose,omitempty"` Extends *ExtendsConfig `yaml:"extends,omitempty" json:"extends,omitempty"` ExternalLinks []string `yaml:"external_links,omitempty" json:"external_links,omitempty"` @@ -133,13 +113,14 @@ type ServiceConfig struct { ReadOnly bool `yaml:"read_only,omitempty" json:"read_only,omitempty"` Restart string `yaml:"restart,omitempty" json:"restart,omitempty"` Runtime string `yaml:"runtime,omitempty" json:"runtime,omitempty"` - Scale int `yaml:"scale,omitempty" json:"scale,omitempty"` + Scale *int `yaml:"scale,omitempty" json:"scale,omitempty"` Secrets []ServiceSecretConfig `yaml:"secrets,omitempty" json:"secrets,omitempty"` SecurityOpt []string `yaml:"security_opt,omitempty" json:"security_opt,omitempty"` ShmSize UnitBytes `yaml:"shm_size,omitempty" json:"shm_size,omitempty"` StdinOpen bool `yaml:"stdin_open,omitempty" json:"stdin_open,omitempty"` StopGracePeriod *Duration `yaml:"stop_grace_period,omitempty" json:"stop_grace_period,omitempty"` StopSignal string `yaml:"stop_signal,omitempty" json:"stop_signal,omitempty"` + StorageOpt map[string]string `yaml:"storage_opt,omitempty" json:"storage_opt,omitempty"` Sysctls Mapping `yaml:"sysctls,omitempty" json:"sysctls,omitempty"` Tmpfs StringList `yaml:"tmpfs,omitempty" json:"tmpfs,omitempty"` Tty bool `yaml:"tty,omitempty" json:"tty,omitempty"` @@ -152,25 +133,17 @@ type ServiceConfig struct { VolumesFrom []string `yaml:"volumes_from,omitempty" json:"volumes_from,omitempty"` WorkingDir string `yaml:"working_dir,omitempty" json:"working_dir,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // MarshalYAML makes ServiceConfig implement yaml.Marshaller func (s ServiceConfig) MarshalYAML() (interface{}, error) { type t ServiceConfig value := t(s) - value.Scale = 0 // deprecated, but default value "1" doesn't match omitempty + value.Name = "" // set during map to slice conversion, not part of the yaml representation return value, nil } -// MarshalJSON makes SSHKey implement json.Marshaller -func (s ServiceConfig) MarshalJSON() ([]byte, error) { - type t ServiceConfig - value := t(s) - value.Scale = 0 // deprecated, but default value "1" doesn't match omitempty - return json.Marshal(value) -} - // NetworksByPriority return the service networks IDs sorted according to Priority func (s *ServiceConfig) NetworksByPriority() []string { type key struct { @@ -189,6 +162,9 @@ func (s *ServiceConfig) NetworksByPriority() []string { }) } sort.Slice(keys, func(i, j int) bool { + if keys[i].priority == keys[j].priority { + return keys[i].name < keys[j].name + } return keys[i].priority > keys[j].priority }) var sorted []string @@ -198,6 +174,33 @@ func (s *ServiceConfig) NetworksByPriority() []string { return sorted } +func (s *ServiceConfig) GetScale() int { + if s.Scale != nil { + return *s.Scale + } + if s.Deploy != nil && s.Deploy.Replicas != nil { + // this should not be required as compose-go enforce consistency between scale anr replicas + return *s.Deploy.Replicas + } + return 1 +} + +func (s *ServiceConfig) SetScale(scale int) { + s.Scale = &scale + if s.Deploy != nil { + s.Deploy.Replicas = &scale + } +} + +func (s *ServiceConfig) deepCopy() *ServiceConfig { + if s == nil { + return nil + } + n := &ServiceConfig{} + deriveDeepCopyService(n, s) + return n +} + const ( // PullPolicyAlways always pull images PullPolicyAlways = "always" @@ -236,6 +239,10 @@ const ( NetworkModeContainerPrefix = ContainerPrefix ) +const ( + SecretConfigXValue = "x-#value" +) + // GetDependencies retrieves all services this service depends on func (s ServiceConfig) GetDependencies() []string { var dependencies []string @@ -258,27 +265,12 @@ func (s ServiceConfig) GetDependents(p *Project) []string { return dependent } -type set map[string]struct{} - -func (s set) append(strs ...string) { - for _, str := range strs { - s[str] = struct{}{} - } -} - -func (s set) toSlice() []string { - slice := make([]string, 0, len(s)) - for v := range s { - slice = append(slice, v) - } - return slice -} - // BuildConfig is a type for build type BuildConfig struct { Context string `yaml:"context,omitempty" json:"context,omitempty"` Dockerfile string `yaml:"dockerfile,omitempty" json:"dockerfile,omitempty"` DockerfileInline string `yaml:"dockerfile_inline,omitempty" json:"dockerfile_inline,omitempty"` + Entitlements []string `yaml:"entitlements,omitempty" json:"entitlements,omitempty"` Args MappingWithEquals `yaml:"args,omitempty" json:"args,omitempty"` SSH SSHConfig `yaml:"ssh,omitempty" json:"ssh,omitempty"` Labels Labels `yaml:"labels,omitempty" json:"labels,omitempty"` @@ -292,12 +284,13 @@ type BuildConfig struct { Network string `yaml:"network,omitempty" json:"network,omitempty"` Target string `yaml:"target,omitempty" json:"target,omitempty"` Secrets []ServiceSecretConfig `yaml:"secrets,omitempty" json:"secrets,omitempty"` + ShmSize UnitBytes `yaml:"shm_size,omitempty" json:"shm_size,omitempty"` Tags StringList `yaml:"tags,omitempty" json:"tags,omitempty"` Ulimits map[string]*UlimitsConfig `yaml:"ulimits,omitempty" json:"ulimits,omitempty"` Platforms StringList `yaml:"platforms,omitempty" json:"platforms,omitempty"` Privileged bool `yaml:"privileged,omitempty" json:"privileged,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // BlkioConfig define blkio config @@ -309,7 +302,15 @@ type BlkioConfig struct { DeviceWriteBps []ThrottleDevice `yaml:"device_write_bps,omitempty" json:"device_write_bps,omitempty"` DeviceWriteIOps []ThrottleDevice `yaml:"device_write_iops,omitempty" json:"device_write_iops,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` +} + +type DeviceMapping struct { + Source string `yaml:"source,omitempty" json:"source,omitempty"` + Target string `yaml:"target,omitempty" json:"target,omitempty"` + Permissions string `yaml:"permissions,omitempty" json:"permissions,omitempty"` + + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // WeightDevice is a structure that holds device:weight pair @@ -317,7 +318,7 @@ type WeightDevice struct { Path string Weight uint16 - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // ThrottleDevice is a structure that holds device:rate_per_second pair @@ -325,197 +326,25 @@ type ThrottleDevice struct { Path string Rate UnitBytes - Extensions Extensions `yaml:"#extensions,inline" json:"-"` -} - -// MappingWithEquals is a mapping type that can be converted from a list of -// key[=value] strings. -// For the key with an empty value (`key=`), the mapped value is set to a pointer to `""`. -// For the key without value (`key`), the mapped value is set to nil. -type MappingWithEquals map[string]*string - -// NewMappingWithEquals build a new Mapping from a set of KEY=VALUE strings -func NewMappingWithEquals(values []string) MappingWithEquals { - mapping := MappingWithEquals{} - for _, env := range values { - tokens := strings.SplitN(env, "=", 2) - if len(tokens) > 1 { - mapping[tokens[0]] = &tokens[1] - } else { - mapping[env] = nil - } - } - return mapping -} - -// OverrideBy update MappingWithEquals with values from another MappingWithEquals -func (e MappingWithEquals) OverrideBy(other MappingWithEquals) MappingWithEquals { - for k, v := range other { - e[k] = v - } - return e -} - -// Resolve update a MappingWithEquals for keys without value (`key`, but not `key=`) -func (e MappingWithEquals) Resolve(lookupFn func(string) (string, bool)) MappingWithEquals { - for k, v := range e { - if v == nil { - if value, ok := lookupFn(k); ok { - e[k] = &value - } - } - } - return e -} - -// RemoveEmpty excludes keys that are not associated with a value -func (e MappingWithEquals) RemoveEmpty() MappingWithEquals { - for k, v := range e { - if v == nil { - delete(e, k) - } - } - return e -} - -// Mapping is a mapping type that can be converted from a list of -// key[=value] strings. -// For the key with an empty value (`key=`), or key without value (`key`), the -// mapped value is set to an empty string `""`. -type Mapping map[string]string - -// NewMapping build a new Mapping from a set of KEY=VALUE strings -func NewMapping(values []string) Mapping { - mapping := Mapping{} - for _, value := range values { - parts := strings.SplitN(value, "=", 2) - key := parts[0] - switch { - case len(parts) == 1: - mapping[key] = "" - default: - mapping[key] = parts[1] - } - } - return mapping -} - -// convert values into a set of KEY=VALUE strings -func (m Mapping) Values() []string { - values := make([]string, 0, len(m)) - for k, v := range m { - values = append(values, fmt.Sprintf("%s=%s", k, v)) - } - sort.Strings(values) - return values -} - -// ToMappingWithEquals converts Mapping into a MappingWithEquals with pointer references -func (m Mapping) ToMappingWithEquals() MappingWithEquals { - mapping := MappingWithEquals{} - for k, v := range m { - v := v - mapping[k] = &v - } - return mapping -} - -func (m Mapping) Resolve(s string) (string, bool) { - v, ok := m[s] - return v, ok -} - -func (m Mapping) Clone() Mapping { - clone := Mapping{} - for k, v := range m { - clone[k] = v - } - return clone -} - -// Merge adds all values from second mapping which are not already defined -func (m Mapping) Merge(o Mapping) Mapping { - for k, v := range o { - if _, set := m[k]; !set { - m[k] = v - } - } - return m -} - -type SSHKey struct { - ID string - Path string -} - -// SSHConfig is a mapping type for SSH build config -type SSHConfig []SSHKey - -func (s SSHConfig) Get(id string) (string, error) { - for _, sshKey := range s { - if sshKey.ID == id { - return sshKey.Path, nil - } - } - return "", fmt.Errorf("ID %s not found in SSH keys", id) -} - -// MarshalYAML makes SSHKey implement yaml.Marshaller -func (s SSHKey) MarshalYAML() (interface{}, error) { - if s.Path == "" { - return s.ID, nil - } - return fmt.Sprintf("%s: %s", s.ID, s.Path), nil -} - -// MarshalJSON makes SSHKey implement json.Marshaller -func (s SSHKey) MarshalJSON() ([]byte, error) { - if s.Path == "" { - return []byte(fmt.Sprintf(`%q`, s.ID)), nil - } - return []byte(fmt.Sprintf(`%q: %s`, s.ID, s.Path)), nil + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // MappingWithColon is a mapping type that can be converted from a list of // 'key: value' strings type MappingWithColon map[string]string -// HostsList is a list of colon-separated host-ip mappings -type HostsList map[string]string - -// AsList return host-ip mappings as a list of colon-separated strings -func (h HostsList) AsList() []string { - l := make([]string, 0, len(h)) - for k, v := range h { - l = append(l, fmt.Sprintf("%s:%s", k, v)) - } - return l -} - -func (h HostsList) MarshalYAML() (interface{}, error) { - list := h.AsList() - sort.Strings(list) - return list, nil -} - -func (h HostsList) MarshalJSON() ([]byte, error) { - list := h.AsList() - sort.Strings(list) - return json.Marshal(list) -} - // LoggingConfig the logging configuration for a service type LoggingConfig struct { Driver string `yaml:"driver,omitempty" json:"driver,omitempty"` Options Options `yaml:"options,omitempty" json:"options,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // DeployConfig the deployment configuration for a service type DeployConfig struct { Mode string `yaml:"mode,omitempty" json:"mode,omitempty"` - Replicas *uint64 `yaml:"replicas,omitempty" json:"replicas,omitempty"` + Replicas *int `yaml:"replicas,omitempty" json:"replicas,omitempty"` Labels Labels `yaml:"labels,omitempty" json:"labels,omitempty"` UpdateConfig *UpdateConfig `yaml:"update_config,omitempty" json:"update_config,omitempty"` RollbackConfig *UpdateConfig `yaml:"rollback_config,omitempty" json:"rollback_config,omitempty"` @@ -524,7 +353,7 @@ type DeployConfig struct { Placement Placement `yaml:"placement,omitempty" json:"placement,omitempty"` EndpointMode string `yaml:"endpoint_mode,omitempty" json:"endpoint_mode,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // UpdateConfig the service update configuration @@ -536,7 +365,7 @@ type UpdateConfig struct { MaxFailureRatio float32 `yaml:"max_failure_ratio,omitempty" json:"max_failure_ratio,omitempty"` Order string `yaml:"order,omitempty" json:"order,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // Resources the resource limits and reservations @@ -544,19 +373,43 @@ type Resources struct { Limits *Resource `yaml:"limits,omitempty" json:"limits,omitempty"` Reservations *Resource `yaml:"reservations,omitempty" json:"reservations,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // Resource is a resource to be limited or reserved type Resource struct { // TODO: types to convert from units and ratios - NanoCPUs string `yaml:"cpus,omitempty" json:"cpus,omitempty"` + NanoCPUs NanoCPUs `yaml:"cpus,omitempty" json:"cpus,omitempty"` MemoryBytes UnitBytes `yaml:"memory,omitempty" json:"memory,omitempty"` Pids int64 `yaml:"pids,omitempty" json:"pids,omitempty"` Devices []DeviceRequest `yaml:"devices,omitempty" json:"devices,omitempty"` GenericResources []GenericResource `yaml:"generic_resources,omitempty" json:"generic_resources,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` +} + +type NanoCPUs float32 + +func (n *NanoCPUs) DecodeMapstructure(a any) error { + switch v := a.(type) { + case string: + f, err := strconv.ParseFloat(v, 64) + if err != nil { + return err + } + *n = NanoCPUs(f) + case float32: + *n = NanoCPUs(v) + case float64: + *n = NanoCPUs(v) + default: + return fmt.Errorf("unexpected value type %T for cpus", v) + } + return nil +} + +func (n *NanoCPUs) Value() float32 { + return float32(*n) } // GenericResource represents a "user defined" resource which can @@ -564,7 +417,7 @@ type Resource struct { type GenericResource struct { DiscreteResourceSpec *DiscreteGenericResource `yaml:"discrete_resource_spec,omitempty" json:"discrete_resource_spec,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // DiscreteGenericResource represents a "user defined" resource which is defined @@ -575,7 +428,7 @@ type DiscreteGenericResource struct { Kind string `json:"kind"` Value int64 `json:"value"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // RestartPolicy the service restart policy @@ -585,7 +438,7 @@ type RestartPolicy struct { MaxAttempts *uint64 `yaml:"max_attempts,omitempty" json:"max_attempts,omitempty"` Window *Duration `yaml:"window,omitempty" json:"window,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // Placement constraints for the service @@ -594,14 +447,14 @@ type Placement struct { Preferences []PlacementPreferences `yaml:"preferences,omitempty" json:"preferences,omitempty"` MaxReplicas uint64 `yaml:"max_replicas_per_node,omitempty" json:"max_replicas_per_node,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // PlacementPreferences is the preferences for a service placement type PlacementPreferences struct { Spread string `yaml:"spread,omitempty" json:"spread,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // ServiceNetworkConfig is the network configuration for a service @@ -612,19 +465,22 @@ type ServiceNetworkConfig struct { Ipv6Address string `yaml:"ipv6_address,omitempty" json:"ipv6_address,omitempty"` LinkLocalIPs []string `yaml:"link_local_ips,omitempty" json:"link_local_ips,omitempty"` MacAddress string `yaml:"mac_address,omitempty" json:"mac_address,omitempty"` + DriverOpts Options `yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // ServicePortConfig is the port configuration for a service type ServicePortConfig struct { - Mode string `yaml:"mode,omitempty" json:"mode,omitempty"` - HostIP string `yaml:"host_ip,omitempty" json:"host_ip,omitempty"` - Target uint32 `yaml:"target,omitempty" json:"target,omitempty"` - Published string `yaml:"published,omitempty" json:"published,omitempty"` - Protocol string `yaml:"protocol,omitempty" json:"protocol,omitempty"` + Name string `yaml:"name,omitempty" json:"name,omitempty"` + Mode string `yaml:"mode,omitempty" json:"mode,omitempty"` + HostIP string `yaml:"host_ip,omitempty" json:"host_ip,omitempty"` + Target uint32 `yaml:"target,omitempty" json:"target,omitempty"` + Published string `yaml:"published,omitempty" json:"published,omitempty"` + Protocol string `yaml:"protocol,omitempty" json:"protocol,omitempty"` + AppProtocol string `yaml:"app_protocol,omitempty" json:"app_protocol,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // ParsePortConfig parse short syntax for service port configuration @@ -677,7 +533,7 @@ type ServiceVolumeConfig struct { Volume *ServiceVolumeVolume `yaml:"volume,omitempty" json:"volume,omitempty"` Tmpfs *ServiceVolumeTmpfs `yaml:"tmpfs,omitempty" json:"tmpfs,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // String render ServiceVolumeConfig as a volume string, one can parse back using loader.ParseVolume @@ -696,6 +552,9 @@ func (s ServiceVolumeConfig) String() string { if s.Volume != nil && s.Volume.NoCopy { options = append(options, "nocopy") } + if s.Volume != nil && s.Volume.Subpath != "" { + options = append(options, s.Volume.Subpath) + } return fmt.Sprintf("%s:%s:%s", s.Source, s.Target, strings.Join(options, ",")) } @@ -723,7 +582,7 @@ type ServiceVolumeBind struct { Propagation string `yaml:"propagation,omitempty" json:"propagation,omitempty"` CreateHostPath bool `yaml:"create_host_path,omitempty" json:"create_host_path,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // SELinux represents the SELinux re-labeling options. @@ -752,9 +611,10 @@ const ( // ServiceVolumeVolume are options for a service volume of type volume type ServiceVolumeVolume struct { - NoCopy bool `yaml:"nocopy,omitempty" json:"nocopy,omitempty"` + NoCopy bool `yaml:"nocopy,omitempty" json:"nocopy,omitempty"` + Subpath string `yaml:"subpath,omitempty" json:"subpath,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // ServiceVolumeTmpfs are options for a service volume of type tmpfs @@ -763,7 +623,7 @@ type ServiceVolumeTmpfs struct { Mode uint32 `yaml:"mode,omitempty" json:"mode,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // FileReferenceConfig for a reference to a swarm file object @@ -774,7 +634,7 @@ type FileReferenceConfig struct { GID string `yaml:"gid,omitempty" json:"gid,omitempty"` Mode *uint32 `yaml:"mode,omitempty" json:"mode,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // ServiceConfigObjConfig is the config obj configuration for a service @@ -789,7 +649,32 @@ type UlimitsConfig struct { Soft int `yaml:"soft,omitempty" json:"soft,omitempty"` Hard int `yaml:"hard,omitempty" json:"hard,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` +} + +func (u *UlimitsConfig) DecodeMapstructure(value interface{}) error { + switch v := value.(type) { + case *UlimitsConfig: + // this call to DecodeMapstructure is triggered after initial value conversion as we use a map[string]*UlimitsConfig + return nil + case int: + u.Single = v + u.Soft = 0 + u.Hard = 0 + case map[string]any: + u.Single = 0 + soft, ok := v["soft"] + if ok { + u.Soft = soft.(int) + } + hard, ok := v["hard"] + if ok { + u.Hard = hard.(int) + } + default: + return fmt.Errorf("unexpected value type %T for ulimit", value) + } + return nil } // MarshalYAML makes UlimitsConfig implement yaml.Marshaller @@ -825,24 +710,24 @@ type NetworkConfig struct { Internal bool `yaml:"internal,omitempty" json:"internal,omitempty"` Attachable bool `yaml:"attachable,omitempty" json:"attachable,omitempty"` Labels Labels `yaml:"labels,omitempty" json:"labels,omitempty"` - EnableIPv6 bool `yaml:"enable_ipv6,omitempty" json:"enable_ipv6,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + EnableIPv6 *bool `yaml:"enable_ipv6,omitempty" json:"enable_ipv6,omitempty"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // IPAMConfig for a network type IPAMConfig struct { Driver string `yaml:"driver,omitempty" json:"driver,omitempty"` Config []*IPAMPool `yaml:"config,omitempty" json:"config,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // IPAMPool for a network type IPAMPool struct { - Subnet string `yaml:"subnet,omitempty" json:"subnet,omitempty"` - Gateway string `yaml:"gateway,omitempty" json:"gateway,omitempty"` - IPRange string `yaml:"ip_range,omitempty" json:"ip_range,omitempty"` - AuxiliaryAddresses Mapping `yaml:"aux_addresses,omitempty" json:"aux_addresses,omitempty"` - Extensions map[string]interface{} `yaml:",inline" json:"-"` + Subnet string `yaml:"subnet,omitempty" json:"subnet,omitempty"` + Gateway string `yaml:"gateway,omitempty" json:"gateway,omitempty"` + IPRange string `yaml:"ip_range,omitempty" json:"ip_range,omitempty"` + AuxiliaryAddresses Mapping `yaml:"aux_addresses,omitempty" json:"aux_addresses,omitempty"` + Extensions Extensions `yaml:",inline" json:"-"` } // VolumeConfig for a volume @@ -852,40 +737,19 @@ type VolumeConfig struct { DriverOpts Options `yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"` External External `yaml:"external,omitempty" json:"external,omitempty"` Labels Labels `yaml:"labels,omitempty" json:"labels,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // External identifies a Volume or Network as a reference to a resource that is // not managed, and should already exist. -// External.name is deprecated and replaced by Volume.name -type External struct { - Name string `yaml:"name,omitempty" json:"name,omitempty"` - External bool `yaml:"external,omitempty" json:"external,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` -} - -// MarshalYAML makes External implement yaml.Marshaller -func (e External) MarshalYAML() (interface{}, error) { - if e.Name == "" { - return e.External, nil - } - return External{Name: e.Name}, nil -} - -// MarshalJSON makes External implement json.Marshaller -func (e External) MarshalJSON() ([]byte, error) { - if e.Name == "" { - return []byte(fmt.Sprintf("%v", e.External)), nil - } - return []byte(fmt.Sprintf(`{"name": %q}`, e.Name)), nil -} +type External bool // CredentialSpecConfig for credential spec on Windows type CredentialSpecConfig struct { Config string `yaml:"config,omitempty" json:"config,omitempty"` // Config was added in API v1.40 File string `yaml:"file,omitempty" json:"file,omitempty"` Registry string `yaml:"registry,omitempty" json:"registry,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } // FileObjectConfig is a config type for a file used by a service @@ -899,7 +763,7 @@ type FileObjectConfig struct { Driver string `yaml:"driver,omitempty" json:"driver,omitempty"` DriverOpts map[string]string `yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"` TemplateDriver string `yaml:"template_driver,omitempty" json:"template_driver,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } const ( @@ -918,7 +782,7 @@ type DependsOnConfig map[string]ServiceDependency type ServiceDependency struct { Condition string `yaml:"condition,omitempty" json:"condition,omitempty"` Restart bool `yaml:"restart,omitempty" json:"restart,omitempty"` - Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` Required bool `yaml:"required" json:"required"` } @@ -930,9 +794,41 @@ type ExtendsConfig struct { // SecretConfig for a secret type SecretConfig FileObjectConfig +// MarshalYAML makes SecretConfig implement yaml.Marshaller +func (s SecretConfig) MarshalYAML() (interface{}, error) { + // secret content is set while loading model. Never marshall it + s.Content = "" + return FileObjectConfig(s), nil +} + +// MarshalJSON makes SecretConfig implement json.Marshaller +func (s SecretConfig) MarshalJSON() ([]byte, error) { + // secret content is set while loading model. Never marshall it + s.Content = "" + return json.Marshal(FileObjectConfig(s)) +} + // ConfigObjConfig is the config for the swarm "Config" object type ConfigObjConfig FileObjectConfig +// MarshalYAML makes ConfigObjConfig implement yaml.Marshaller +func (s ConfigObjConfig) MarshalYAML() (interface{}, error) { + // config content may have been set from environment while loading model. Marshall actual source + if s.Environment != "" { + s.Content = "" + } + return FileObjectConfig(s), nil +} + +// MarshalJSON makes ConfigObjConfig implement json.Marshaller +func (s ConfigObjConfig) MarshalJSON() ([]byte, error) { + // config content may have been set from environment while loading model. Marshall actual source + if s.Environment != "" { + s.Content = "" + } + return json.Marshal(FileObjectConfig(s)) +} + type IncludeConfig struct { Path StringList `yaml:"path,omitempty" json:"path,omitempty"` ProjectDirectory string `yaml:"project_directory,omitempty" json:"project_directory,omitempty"` diff --git a/vendor/github.com/compose-spec/compose-go/utils/collectionutils.go b/vendor/github.com/compose-spec/compose-go/v2/utils/collectionutils.go similarity index 65% rename from vendor/github.com/compose-spec/compose-go/utils/collectionutils.go rename to vendor/github.com/compose-spec/compose-go/v2/utils/collectionutils.go index 343692250..4df8fb1f6 100644 --- a/vendor/github.com/compose-spec/compose-go/utils/collectionutils.go +++ b/vendor/github.com/compose-spec/compose-go/v2/utils/collectionutils.go @@ -16,13 +16,15 @@ package utils -import "golang.org/x/exp/slices" +import ( + "golang.org/x/exp/constraints" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" +) -func MapKeys[T comparable, U any](theMap map[T]U) []T { - var result []T - for key := range theMap { - result = append(result, key) - } +func MapKeys[T constraints.Ordered, U any](theMap map[T]U) []T { + result := maps.Keys(theMap) + slices.Sort(result) return result } @@ -49,3 +51,18 @@ func ArrayContains[T comparable](source []T, toCheck []T) bool { } return true } + +func RemoveDuplicates[T comparable](slice []T) []T { + // Create a map to store unique elements + seen := make(map[T]bool) + result := []T{} + + // Loop through the slice, adding elements to the map if they haven't been seen before + for _, val := range slice { + if _, ok := seen[val]; !ok { + seen[val] = true + result = append(result, val) + } + } + return result +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/utils/pathutils.go b/vendor/github.com/compose-spec/compose-go/v2/utils/pathutils.go new file mode 100644 index 000000000..fd2a635ec --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/utils/pathutils.go @@ -0,0 +1,92 @@ +/* + Copyright 2020 The Compose Specification 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 utils + +import ( + "os" + "path/filepath" + "strings" +) + +// ResolveSymbolicLink converts the section of an absolute path if it is a +// symbolic link +// +// Parameters: +// - path: an absolute path +// +// Returns: +// - converted path if it has a symbolic link or the same path if there is +// no symbolic link +func ResolveSymbolicLink(path string) (string, error) { + sym, part, err := getSymbolinkLink(path) + if err != nil { + return "", err + } + if sym == "" && part == "" { + // no symbolic link detected + return path, nil + } + return strings.Replace(path, part, sym, 1), nil + +} + +// getSymbolinkLink parses all parts of the path and returns the +// the symbolic link part as well as the correspondent original part +// Parameters: +// - path: an absolute path +// +// Returns: +// - string section of the path that is a symbolic link +// - string correspondent path section of the symbolic link +// - An error +func getSymbolinkLink(path string) (string, string, error) { + parts := strings.Split(path, string(os.PathSeparator)) + + // Reconstruct the path step by step, checking each component + var currentPath string + if filepath.IsAbs(path) { + currentPath = string(os.PathSeparator) + } + + for _, part := range parts { + if part == "" { + continue + } + currentPath = filepath.Join(currentPath, part) + + if isSymLink := isSymbolicLink(currentPath); isSymLink { + // return symbolic link, and correspondent part + target, err := filepath.EvalSymlinks(currentPath) + if err != nil { + return "", "", err + } + return target, currentPath, nil + } + } + return "", "", nil // no symbolic link +} + +// isSymbolicLink validates if the path is a symbolic link +func isSymbolicLink(path string) bool { + info, err := os.Lstat(path) + if err != nil { + return false + } + + // Check if the file mode indicates a symbolic link + return info.Mode()&os.ModeSymlink != 0 +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/utils/set.go b/vendor/github.com/compose-spec/compose-go/v2/utils/set.go new file mode 100644 index 000000000..bbbeaa966 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/utils/set.go @@ -0,0 +1,95 @@ +/* + Copyright 2020 The Compose Specification 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 utils + +type Set[T comparable] map[T]struct{} + +func NewSet[T comparable](v ...T) Set[T] { + if len(v) == 0 { + return make(Set[T]) + } + + out := make(Set[T], len(v)) + for i := range v { + out.Add(v[i]) + } + return out +} + +func (s Set[T]) Has(v T) bool { + _, ok := s[v] + return ok +} + +func (s Set[T]) Add(v T) { + s[v] = struct{}{} +} + +func (s Set[T]) AddAll(v ...T) { + for _, e := range v { + s[e] = struct{}{} + } +} + +func (s Set[T]) Remove(v T) bool { + _, ok := s[v] + if ok { + delete(s, v) + } + return ok +} + +func (s Set[T]) Clear() { + for v := range s { + delete(s, v) + } +} + +func (s Set[T]) Elements() []T { + elements := make([]T, 0, len(s)) + for v := range s { + elements = append(elements, v) + } + return elements +} + +func (s Set[T]) RemoveAll(elements ...T) { + for _, e := range elements { + s.Remove(e) + } +} + +func (s Set[T]) Diff(other Set[T]) Set[T] { + out := make(Set[T]) + for k := range s { + if _, ok := other[k]; !ok { + out[k] = struct{}{} + } + } + return out +} + +func (s Set[T]) Union(other Set[T]) Set[T] { + out := make(Set[T]) + for k := range s { + out[k] = struct{}{} + } + for k := range other { + out[k] = struct{}{} + } + return out +} diff --git a/vendor/github.com/compose-spec/compose-go/utils/stringutils.go b/vendor/github.com/compose-spec/compose-go/v2/utils/stringutils.go similarity index 83% rename from vendor/github.com/compose-spec/compose-go/utils/stringutils.go rename to vendor/github.com/compose-spec/compose-go/v2/utils/stringutils.go index 182ddf830..fc6b2035f 100644 --- a/vendor/github.com/compose-spec/compose-go/utils/stringutils.go +++ b/vendor/github.com/compose-spec/compose-go/v2/utils/stringutils.go @@ -22,16 +22,6 @@ import ( "strings" ) -// StringContains check if an array contains a specific value -func StringContains(array []string, needle string) bool { - for _, val := range array { - if val == needle { - return true - } - } - return false -} - // StringToBool converts a string to a boolean ignoring errors func StringToBool(s string) bool { b, _ := strconv.ParseBool(strings.ToLower(strings.TrimSpace(s))) @@ -42,8 +32,10 @@ func StringToBool(s string) bool { func GetAsEqualsMap(em []string) map[string]string { m := make(map[string]string) for _, v := range em { - kv := strings.SplitN(v, "=", 2) - m[kv[0]] = kv[1] + key, val, found := strings.Cut(v, "=") + if found { + m[key] = val + } } return m } diff --git a/vendor/github.com/compose-spec/compose-go/v2/validation/external.go b/vendor/github.com/compose-spec/compose-go/v2/validation/external.go new file mode 100644 index 000000000..b74d551a0 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/validation/external.go @@ -0,0 +1,49 @@ +/* + Copyright 2020 The Compose Specification 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 validation + +import ( + "fmt" + "strings" + + "github.com/compose-spec/compose-go/v2/consts" + "github.com/compose-spec/compose-go/v2/tree" +) + +func checkExternal(v map[string]any, p tree.Path) error { + b, ok := v["external"] + if !ok { + return nil + } + if !b.(bool) { + return nil + } + + for k := range v { + switch k { + case "name", "external", consts.Extensions: + continue + default: + if strings.HasPrefix(k, "x-") { + // custom extension, ignored + continue + } + return fmt.Errorf("%s: conflicting parameters \"external\" and %q specified", p, k) + } + } + return nil +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/validation/validation.go b/vendor/github.com/compose-spec/compose-go/v2/validation/validation.go new file mode 100644 index 000000000..e7cd67545 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/validation/validation.go @@ -0,0 +1,96 @@ +/* + Copyright 2020 The Compose Specification 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 validation + +import ( + "fmt" + "strings" + + "github.com/compose-spec/compose-go/v2/tree" +) + +type checkerFunc func(value any, p tree.Path) error + +var checks = map[tree.Path]checkerFunc{ + "volumes.*": checkVolume, + "configs.*": checkFileObject("file", "environment", "content"), + "secrets.*": checkFileObject("file", "environment"), + "services.*.develop.watch.*.path": checkPath, +} + +func Validate(dict map[string]any) error { + return check(dict, tree.NewPath()) +} + +func check(value any, p tree.Path) error { + for pattern, fn := range checks { + if p.Matches(pattern) { + return fn(value, p) + } + } + switch v := value.(type) { + case map[string]any: + for k, v := range v { + err := check(v, p.Next(k)) + if err != nil { + return err + } + } + case []any: + for _, e := range v { + err := check(e, p.Next("[]")) + if err != nil { + return err + } + } + } + return nil +} + +func checkFileObject(keys ...string) checkerFunc { + return func(value any, p tree.Path) error { + + v := value.(map[string]any) + count := 0 + for _, s := range keys { + if _, ok := v[s]; ok { + count++ + } + } + if count > 1 { + return fmt.Errorf("%s: %s attributes are mutually exclusive", p, strings.Join(keys, "|")) + } + if count == 0 { + if _, ok := v["driver"]; ok { + // User specified a custom driver, which might have it's own way to set content + return nil + } + if _, ok := v["external"]; !ok { + return fmt.Errorf("%s: one of %s must be set", p, strings.Join(keys, "|")) + } + } + return nil + } +} + +func checkPath(value any, p tree.Path) error { + v := value.(string) + if v == "" { + return fmt.Errorf("%s: value can't be blank", p) + } + return nil +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/validation/volume.go b/vendor/github.com/compose-spec/compose-go/v2/validation/volume.go new file mode 100644 index 000000000..5b4006811 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/validation/volume.go @@ -0,0 +1,39 @@ +/* + Copyright 2020 The Compose Specification 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 validation + +import ( + "fmt" + + "github.com/compose-spec/compose-go/v2/tree" +) + +func checkVolume(value any, p tree.Path) error { + if value == nil { + return nil + } + v, ok := value.(map[string]any) + if !ok { + return fmt.Errorf("expected volume, got %s", value) + } + + err := checkExternal(v, p) + if err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/go-viper/mapstructure/v2/.editorconfig b/vendor/github.com/go-viper/mapstructure/v2/.editorconfig new file mode 100644 index 000000000..1f664d13a --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.go] +indent_style = tab + +[{Makefile,*.mk}] +indent_style = tab + +[*.nix] +indent_size = 2 diff --git a/vendor/github.com/go-viper/mapstructure/v2/.envrc b/vendor/github.com/go-viper/mapstructure/v2/.envrc new file mode 100644 index 000000000..2e0f9f5f7 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/.envrc @@ -0,0 +1,4 @@ +if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4=" +fi +use flake . --impure diff --git a/vendor/github.com/go-viper/mapstructure/v2/.gitignore b/vendor/github.com/go-viper/mapstructure/v2/.gitignore new file mode 100644 index 000000000..470e7ca2b --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/.gitignore @@ -0,0 +1,6 @@ +/.devenv/ +/.direnv/ +/.pre-commit-config.yaml +/bin/ +/build/ +/var/ diff --git a/vendor/github.com/go-viper/mapstructure/v2/.golangci.yaml b/vendor/github.com/go-viper/mapstructure/v2/.golangci.yaml new file mode 100644 index 000000000..763143aa7 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/.golangci.yaml @@ -0,0 +1,23 @@ +run: + timeout: 5m + +linters-settings: + gci: + sections: + - standard + - default + - prefix(github.com/go-viper/mapstructure) + golint: + min-confidence: 0 + goimports: + local-prefixes: github.com/go-viper/maptstructure + +linters: + disable-all: true + enable: + - gci + - gofmt + - gofumpt + - goimports + - staticcheck + # - stylecheck diff --git a/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md b/vendor/github.com/go-viper/mapstructure/v2/CHANGELOG.md similarity index 92% rename from vendor/github.com/mitchellh/mapstructure/CHANGELOG.md rename to vendor/github.com/go-viper/mapstructure/v2/CHANGELOG.md index c75823490..afd44e5f5 100644 --- a/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md +++ b/vendor/github.com/go-viper/mapstructure/v2/CHANGELOG.md @@ -1,3 +1,11 @@ +> [!WARNING] +> As of v2 of this library, change log can be found in GitHub releases. + +## 1.5.1 + +* Wrap errors so they're compatible with `errors.Is` and `errors.As` [GH-282] +* Fix map of slices not decoding properly in certain cases. [GH-266] + ## 1.5.0 * New option `IgnoreUntaggedFields` to ignore decoding to any fields diff --git a/vendor/github.com/mitchellh/mapstructure/LICENSE b/vendor/github.com/go-viper/mapstructure/v2/LICENSE similarity index 100% rename from vendor/github.com/mitchellh/mapstructure/LICENSE rename to vendor/github.com/go-viper/mapstructure/v2/LICENSE diff --git a/vendor/github.com/go-viper/mapstructure/v2/README.md b/vendor/github.com/go-viper/mapstructure/v2/README.md new file mode 100644 index 000000000..dd5ec69dd --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/README.md @@ -0,0 +1,80 @@ +# mapstructure + +[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/go-viper/mapstructure/ci.yaml?branch=main&style=flat-square)](https://github.com/go-viper/mapstructure/actions?query=workflow%3ACI) +[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/mod/github.com/go-viper/mapstructure/v2) +![Go Version](https://img.shields.io/badge/go%20version-%3E=1.18-61CFDD.svg?style=flat-square) + +mapstructure is a Go library for decoding generic map values to structures +and vice versa, while providing helpful error handling. + +This library is most useful when decoding values from some data stream (JSON, +Gob, etc.) where you don't _quite_ know the structure of the underlying data +until you read a part of it. You can therefore read a `map[string]interface{}` +and use this library to decode it into the proper underlying native Go +structure. + +## Installation + +```shell +go get github.com/go-viper/mapstructure/v2 +``` + +## Migrating from `github.com/mitchellh/mapstructure` + +[@mitchehllh](https://github.com/mitchellh) announced his intent to archive some of his unmaintained projects (see [here](https://gist.github.com/mitchellh/90029601268e59a29e64e55bab1c5bdc) and [here](https://github.com/mitchellh/mapstructure/issues/349)). This is a repository achieved the "blessed fork" status. + +You can migrate to this package by changing your import paths in your Go files to `github.com/go-viper/mapstructure/v2`. +The API is the same, so you don't need to change anything else. + +Here is a script that can help you with the migration: + +```shell +sed -i 's/github.com\/mitchellh\/mapstructure/github.com\/go-viper\/mapstructure\/v2/g' $(find . -type f -name '*.go') +``` + +If you need more time to migrate your code, that is absolutely fine. + +Some of the latest fixes are backported to the v1 release branch of this package, so you can use the Go modules `replace` feature until you are ready to migrate: + +```shell +replace github.com/mitchellh/mapstructure => github.com/go-viper/mapstructure v1.6.0 +``` + +## Usage & Example + +For usage and examples see the [documentation](https://pkg.go.dev/mod/github.com/go-viper/mapstructure/v2). + +The `Decode` function has examples associated with it there. + +## But Why?! + +Go offers fantastic standard libraries for decoding formats such as JSON. +The standard method is to have a struct pre-created, and populate that struct +from the bytes of the encoded format. This is great, but the problem is if +you have configuration or an encoding that changes slightly depending on +specific fields. For example, consider this JSON: + +```json +{ + "type": "person", + "name": "Mitchell" +} +``` + +Perhaps we can't populate a specific structure without first reading +the "type" field from the JSON. We could always do two passes over the +decoding of the JSON (reading the "type" first, and the rest later). +However, it is much simpler to just decode this into a `map[string]interface{}` +structure, read the "type" key, then use something like this library +to decode it into the proper structure. + +## Credits + +Mapstructure was originally created by [@mitchellh](https://github.com/mitchellh). +This is a maintained fork of the original library. + +Read more about the reasons for the fork [here](https://github.com/mitchellh/mapstructure/issues/349). + +## License + +The project is licensed under the [MIT License](LICENSE). diff --git a/vendor/github.com/go-viper/mapstructure/v2/decode_hooks.go b/vendor/github.com/go-viper/mapstructure/v2/decode_hooks.go new file mode 100644 index 000000000..24d82f07c --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/decode_hooks.go @@ -0,0 +1,577 @@ +package mapstructure + +import ( + "encoding" + "errors" + "fmt" + "net" + "net/netip" + "reflect" + "strconv" + "strings" + "time" +) + +// typedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns +// it into the proper DecodeHookFunc type, such as DecodeHookFuncType. +func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc { + // Create variables here so we can reference them with the reflect pkg + var f1 DecodeHookFuncType + var f2 DecodeHookFuncKind + var f3 DecodeHookFuncValue + + // Fill in the variables into this interface and the rest is done + // automatically using the reflect package. + potential := []interface{}{f1, f2, f3} + + v := reflect.ValueOf(h) + vt := v.Type() + for _, raw := range potential { + pt := reflect.ValueOf(raw).Type() + if vt.ConvertibleTo(pt) { + return v.Convert(pt).Interface() + } + } + + return nil +} + +// DecodeHookExec executes the given decode hook. This should be used +// since it'll naturally degrade to the older backwards compatible DecodeHookFunc +// that took reflect.Kind instead of reflect.Type. +func DecodeHookExec( + raw DecodeHookFunc, + from reflect.Value, to reflect.Value, +) (interface{}, error) { + switch f := typedDecodeHook(raw).(type) { + case DecodeHookFuncType: + return f(from.Type(), to.Type(), from.Interface()) + case DecodeHookFuncKind: + return f(from.Kind(), to.Kind(), from.Interface()) + case DecodeHookFuncValue: + return f(from, to) + default: + return nil, errors.New("invalid decode hook signature") + } +} + +// ComposeDecodeHookFunc creates a single DecodeHookFunc that +// automatically composes multiple DecodeHookFuncs. +// +// The composed funcs are called in order, with the result of the +// previous transformation. +func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc { + return func(f reflect.Value, t reflect.Value) (interface{}, error) { + var err error + data := f.Interface() + + newFrom := f + for _, f1 := range fs { + data, err = DecodeHookExec(f1, newFrom, t) + if err != nil { + return nil, err + } + newFrom = reflect.ValueOf(data) + } + + return data, nil + } +} + +// OrComposeDecodeHookFunc executes all input hook functions until one of them returns no error. In that case its value is returned. +// If all hooks return an error, OrComposeDecodeHookFunc returns an error concatenating all error messages. +func OrComposeDecodeHookFunc(ff ...DecodeHookFunc) DecodeHookFunc { + return func(a, b reflect.Value) (interface{}, error) { + var allErrs string + var out interface{} + var err error + + for _, f := range ff { + out, err = DecodeHookExec(f, a, b) + if err != nil { + allErrs += err.Error() + "\n" + continue + } + + return out, nil + } + + return nil, errors.New(allErrs) + } +} + +// StringToSliceHookFunc returns a DecodeHookFunc that converts +// string to []string by splitting on the given sep. +func StringToSliceHookFunc(sep string) DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.SliceOf(f) { + return data, nil + } + + raw := data.(string) + if raw == "" { + return []string{}, nil + } + + return strings.Split(raw, sep), nil + } +} + +// StringToTimeDurationHookFunc returns a DecodeHookFunc that converts +// strings to time.Duration. +func StringToTimeDurationHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(time.Duration(5)) { + return data, nil + } + + // Convert it by parsing + return time.ParseDuration(data.(string)) + } +} + +// StringToIPHookFunc returns a DecodeHookFunc that converts +// strings to net.IP +func StringToIPHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(net.IP{}) { + return data, nil + } + + // Convert it by parsing + ip := net.ParseIP(data.(string)) + if ip == nil { + return net.IP{}, fmt.Errorf("failed parsing ip %v", data) + } + + return ip, nil + } +} + +// StringToIPNetHookFunc returns a DecodeHookFunc that converts +// strings to net.IPNet +func StringToIPNetHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(net.IPNet{}) { + return data, nil + } + + // Convert it by parsing + _, net, err := net.ParseCIDR(data.(string)) + return net, err + } +} + +// StringToTimeHookFunc returns a DecodeHookFunc that converts +// strings to time.Time. +func StringToTimeHookFunc(layout string) DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(time.Time{}) { + return data, nil + } + + // Convert it by parsing + return time.Parse(layout, data.(string)) + } +} + +// WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to +// the decoder. +// +// Note that this is significantly different from the WeaklyTypedInput option +// of the DecoderConfig. +func WeaklyTypedHook( + f reflect.Kind, + t reflect.Kind, + data interface{}, +) (interface{}, error) { + dataVal := reflect.ValueOf(data) + switch t { + case reflect.String: + switch f { + case reflect.Bool: + if dataVal.Bool() { + return "1", nil + } + return "0", nil + case reflect.Float32: + return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil + case reflect.Int: + return strconv.FormatInt(dataVal.Int(), 10), nil + case reflect.Slice: + dataType := dataVal.Type() + elemKind := dataType.Elem().Kind() + if elemKind == reflect.Uint8 { + return string(dataVal.Interface().([]uint8)), nil + } + case reflect.Uint: + return strconv.FormatUint(dataVal.Uint(), 10), nil + } + } + + return data, nil +} + +func RecursiveStructToMapHookFunc() DecodeHookFunc { + return func(f reflect.Value, t reflect.Value) (interface{}, error) { + if f.Kind() != reflect.Struct { + return f.Interface(), nil + } + + var i interface{} = struct{}{} + if t.Type() != reflect.TypeOf(&i).Elem() { + return f.Interface(), nil + } + + m := make(map[string]interface{}) + t.Set(reflect.ValueOf(m)) + + return f.Interface(), nil + } +} + +// TextUnmarshallerHookFunc returns a DecodeHookFunc that applies +// strings to the UnmarshalText function, when the target type +// implements the encoding.TextUnmarshaler interface +func TextUnmarshallerHookFunc() DecodeHookFuncType { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + result := reflect.New(t).Interface() + unmarshaller, ok := result.(encoding.TextUnmarshaler) + if !ok { + return data, nil + } + str, ok := data.(string) + if !ok { + str = reflect.Indirect(reflect.ValueOf(&data)).Elem().String() + } + if err := unmarshaller.UnmarshalText([]byte(str)); err != nil { + return nil, err + } + return result, nil + } +} + +// StringToNetIPAddrHookFunc returns a DecodeHookFunc that converts +// strings to netip.Addr. +func StringToNetIPAddrHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(netip.Addr{}) { + return data, nil + } + + // Convert it by parsing + return netip.ParseAddr(data.(string)) + } +} + +// StringToNetIPAddrPortHookFunc returns a DecodeHookFunc that converts +// strings to netip.AddrPort. +func StringToNetIPAddrPortHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(netip.AddrPort{}) { + return data, nil + } + + // Convert it by parsing + return netip.ParseAddrPort(data.(string)) + } +} + +// StringToBasicTypeHookFunc returns a DecodeHookFunc that converts +// strings to basic types. +// int8, uint8, int16, uint16, int32, uint32, int64, uint64, int, uint, float32, float64, bool, byte, rune, complex64, complex128 +func StringToBasicTypeHookFunc() DecodeHookFunc { + return ComposeDecodeHookFunc( + StringToInt8HookFunc(), + StringToUint8HookFunc(), + StringToInt16HookFunc(), + StringToUint16HookFunc(), + StringToInt32HookFunc(), + StringToUint32HookFunc(), + StringToInt64HookFunc(), + StringToUint64HookFunc(), + StringToIntHookFunc(), + StringToUintHookFunc(), + StringToFloat32HookFunc(), + StringToFloat64HookFunc(), + StringToBoolHookFunc(), + // byte and rune are aliases for uint8 and int32 respectively + // StringToByteHookFunc(), + // StringToRuneHookFunc(), + StringToComplex64HookFunc(), + StringToComplex128HookFunc(), + ) +} + +// StringToInt8HookFunc returns a DecodeHookFunc that converts +// strings to int8. +func StringToInt8HookFunc() DecodeHookFunc { + return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if f.Kind() != reflect.String || t.Kind() != reflect.Int8 { + return data, nil + } + + // Convert it by parsing + i64, err := strconv.ParseInt(data.(string), 0, 8) + return int8(i64), err + } +} + +// StringToUint8HookFunc returns a DecodeHookFunc that converts +// strings to uint8. +func StringToUint8HookFunc() DecodeHookFunc { + return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if f.Kind() != reflect.String || t.Kind() != reflect.Uint8 { + return data, nil + } + + // Convert it by parsing + u64, err := strconv.ParseUint(data.(string), 0, 8) + return uint8(u64), err + } +} + +// StringToInt16HookFunc returns a DecodeHookFunc that converts +// strings to int16. +func StringToInt16HookFunc() DecodeHookFunc { + return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if f.Kind() != reflect.String || t.Kind() != reflect.Int16 { + return data, nil + } + + // Convert it by parsing + i64, err := strconv.ParseInt(data.(string), 0, 16) + return int16(i64), err + } +} + +// StringToUint16HookFunc returns a DecodeHookFunc that converts +// strings to uint16. +func StringToUint16HookFunc() DecodeHookFunc { + return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if f.Kind() != reflect.String || t.Kind() != reflect.Uint16 { + return data, nil + } + + // Convert it by parsing + u64, err := strconv.ParseUint(data.(string), 0, 16) + return uint16(u64), err + } +} + +// StringToInt32HookFunc returns a DecodeHookFunc that converts +// strings to int32. +func StringToInt32HookFunc() DecodeHookFunc { + return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if f.Kind() != reflect.String || t.Kind() != reflect.Int32 { + return data, nil + } + + // Convert it by parsing + i64, err := strconv.ParseInt(data.(string), 0, 32) + return int32(i64), err + } +} + +// StringToUint32HookFunc returns a DecodeHookFunc that converts +// strings to uint32. +func StringToUint32HookFunc() DecodeHookFunc { + return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if f.Kind() != reflect.String || t.Kind() != reflect.Uint32 { + return data, nil + } + + // Convert it by parsing + u64, err := strconv.ParseUint(data.(string), 0, 32) + return uint32(u64), err + } +} + +// StringToInt64HookFunc returns a DecodeHookFunc that converts +// strings to int64. +func StringToInt64HookFunc() DecodeHookFunc { + return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if f.Kind() != reflect.String || t.Kind() != reflect.Int64 { + return data, nil + } + + // Convert it by parsing + return strconv.ParseInt(data.(string), 0, 64) + } +} + +// StringToUint64HookFunc returns a DecodeHookFunc that converts +// strings to uint64. +func StringToUint64HookFunc() DecodeHookFunc { + return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if f.Kind() != reflect.String || t.Kind() != reflect.Uint64 { + return data, nil + } + + // Convert it by parsing + return strconv.ParseUint(data.(string), 0, 64) + } +} + +// StringToIntHookFunc returns a DecodeHookFunc that converts +// strings to int. +func StringToIntHookFunc() DecodeHookFunc { + return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if f.Kind() != reflect.String || t.Kind() != reflect.Int { + return data, nil + } + + // Convert it by parsing + i64, err := strconv.ParseInt(data.(string), 0, 0) + return int(i64), err + } +} + +// StringToUintHookFunc returns a DecodeHookFunc that converts +// strings to uint. +func StringToUintHookFunc() DecodeHookFunc { + return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if f.Kind() != reflect.String || t.Kind() != reflect.Uint { + return data, nil + } + + // Convert it by parsing + u64, err := strconv.ParseUint(data.(string), 0, 0) + return uint(u64), err + } +} + +// StringToFloat32HookFunc returns a DecodeHookFunc that converts +// strings to float32. +func StringToFloat32HookFunc() DecodeHookFunc { + return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if f.Kind() != reflect.String || t.Kind() != reflect.Float32 { + return data, nil + } + + // Convert it by parsing + f64, err := strconv.ParseFloat(data.(string), 32) + return float32(f64), err + } +} + +// StringToFloat64HookFunc returns a DecodeHookFunc that converts +// strings to float64. +func StringToFloat64HookFunc() DecodeHookFunc { + return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if f.Kind() != reflect.String || t.Kind() != reflect.Float64 { + return data, nil + } + + // Convert it by parsing + return strconv.ParseFloat(data.(string), 64) + } +} + +// StringToBoolHookFunc returns a DecodeHookFunc that converts +// strings to bool. +func StringToBoolHookFunc() DecodeHookFunc { + return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if f.Kind() != reflect.String || t.Kind() != reflect.Bool { + return data, nil + } + + // Convert it by parsing + return strconv.ParseBool(data.(string)) + } +} + +// StringToByteHookFunc returns a DecodeHookFunc that converts +// strings to byte. +func StringToByteHookFunc() DecodeHookFunc { + return StringToUint8HookFunc() +} + +// StringToRuneHookFunc returns a DecodeHookFunc that converts +// strings to rune. +func StringToRuneHookFunc() DecodeHookFunc { + return StringToInt32HookFunc() +} + +// StringToComplex64HookFunc returns a DecodeHookFunc that converts +// strings to complex64. +func StringToComplex64HookFunc() DecodeHookFunc { + return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if f.Kind() != reflect.String || t.Kind() != reflect.Complex64 { + return data, nil + } + + // Convert it by parsing + c128, err := strconv.ParseComplex(data.(string), 64) + return complex64(c128), err + } +} + +// StringToComplex128HookFunc returns a DecodeHookFunc that converts +// strings to complex128. +func StringToComplex128HookFunc() DecodeHookFunc { + return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if f.Kind() != reflect.String || t.Kind() != reflect.Complex128 { + return data, nil + } + + // Convert it by parsing + return strconv.ParseComplex(data.(string), 128) + } +} diff --git a/vendor/github.com/go-viper/mapstructure/v2/flake.lock b/vendor/github.com/go-viper/mapstructure/v2/flake.lock new file mode 100644 index 000000000..4bea8154e --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/flake.lock @@ -0,0 +1,472 @@ +{ + "nodes": { + "cachix": { + "inputs": { + "devenv": "devenv_2", + "flake-compat": [ + "devenv", + "flake-compat" + ], + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "pre-commit-hooks": [ + "devenv", + "pre-commit-hooks" + ] + }, + "locked": { + "lastModified": 1712055811, + "narHash": "sha256-7FcfMm5A/f02yyzuavJe06zLa9hcMHsagE28ADcmQvk=", + "owner": "cachix", + "repo": "cachix", + "rev": "02e38da89851ec7fec3356a5c04bc8349cae0e30", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "cachix", + "type": "github" + } + }, + "devenv": { + "inputs": { + "cachix": "cachix", + "flake-compat": "flake-compat_2", + "nix": "nix_2", + "nixpkgs": "nixpkgs_2", + "pre-commit-hooks": "pre-commit-hooks" + }, + "locked": { + "lastModified": 1717245169, + "narHash": "sha256-+mW3rTBjGU8p1THJN0lX/Dd/8FbnF+3dB+mJuSaxewE=", + "owner": "cachix", + "repo": "devenv", + "rev": "c3f9f053c077c6f88a3de5276d9178c62baa3fc3", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "devenv_2": { + "inputs": { + "flake-compat": [ + "devenv", + "cachix", + "flake-compat" + ], + "nix": "nix", + "nixpkgs": "nixpkgs", + "poetry2nix": "poetry2nix", + "pre-commit-hooks": [ + "devenv", + "cachix", + "pre-commit-hooks" + ] + }, + "locked": { + "lastModified": 1708704632, + "narHash": "sha256-w+dOIW60FKMaHI1q5714CSibk99JfYxm0CzTinYWr+Q=", + "owner": "cachix", + "repo": "devenv", + "rev": "2ee4450b0f4b95a1b90f2eb5ffea98b90e48c196", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "python-rewrite", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1717285511, + "narHash": "sha256-iKzJcpdXih14qYVcZ9QC9XuZYnPc6T8YImb6dX166kw=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "2a55567fcf15b1b1c7ed712a2c6fadaec7412ea8", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1689068808, + "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "devenv", + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nix": { + "inputs": { + "flake-compat": "flake-compat", + "nixpkgs": [ + "devenv", + "cachix", + "devenv", + "nixpkgs" + ], + "nixpkgs-regression": "nixpkgs-regression" + }, + "locked": { + "lastModified": 1712911606, + "narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=", + "owner": "domenkozar", + "repo": "nix", + "rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12", + "type": "github" + }, + "original": { + "owner": "domenkozar", + "ref": "devenv-2.21", + "repo": "nix", + "type": "github" + } + }, + "nix-github-actions": { + "inputs": { + "nixpkgs": [ + "devenv", + "cachix", + "devenv", + "poetry2nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1688870561, + "narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=", + "owner": "nix-community", + "repo": "nix-github-actions", + "rev": "165b1650b753316aa7f1787f3005a8d2da0f5301", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nix-github-actions", + "type": "github" + } + }, + "nix_2": { + "inputs": { + "flake-compat": [ + "devenv", + "flake-compat" + ], + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "nixpkgs-regression": "nixpkgs-regression_2" + }, + "locked": { + "lastModified": 1712911606, + "narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=", + "owner": "domenkozar", + "repo": "nix", + "rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12", + "type": "github" + }, + "original": { + "owner": "domenkozar", + "ref": "devenv-2.21", + "repo": "nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1692808169, + "narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9201b5ff357e781bf014d0330d18555695df7ba8", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1717284937, + "narHash": "sha256-lIbdfCsf8LMFloheeE6N31+BMIeixqyQWbSr2vk79EQ=", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/eb9ceca17df2ea50a250b6b27f7bf6ab0186f198.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/eb9ceca17df2ea50a250b6b27f7bf6ab0186f198.tar.gz" + } + }, + "nixpkgs-regression": { + "locked": { + "lastModified": 1643052045, + "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + } + }, + "nixpkgs-regression_2": { + "locked": { + "lastModified": 1643052045, + "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1710695816, + "narHash": "sha256-3Eh7fhEID17pv9ZxrPwCLfqXnYP006RKzSs0JptsN84=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "614b4613980a522ba49f0d194531beddbb7220d3", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1713361204, + "narHash": "sha256-TA6EDunWTkc5FvDCqU3W2T3SFn0gRZqh6D/hJnM02MM=", + "owner": "cachix", + "repo": "devenv-nixpkgs", + "rev": "285676e87ad9f0ca23d8714a6ab61e7e027020c6", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "rolling", + "repo": "devenv-nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 1717112898, + "narHash": "sha256-7R2ZvOnvd9h8fDd65p0JnB7wXfUvreox3xFdYWd1BnY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "6132b0f6e344ce2fe34fc051b72fb46e34f668e0", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "poetry2nix": { + "inputs": { + "flake-utils": "flake-utils", + "nix-github-actions": "nix-github-actions", + "nixpkgs": [ + "devenv", + "cachix", + "devenv", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1692876271, + "narHash": "sha256-IXfZEkI0Mal5y1jr6IRWMqK8GW2/f28xJenZIPQqkY0=", + "owner": "nix-community", + "repo": "poetry2nix", + "rev": "d5006be9c2c2417dafb2e2e5034d83fabd207ee3", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "poetry2nix", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": [ + "devenv", + "flake-compat" + ], + "flake-utils": "flake-utils_2", + "gitignore": "gitignore", + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1713775815, + "narHash": "sha256-Wu9cdYTnGQQwtT20QQMg7jzkANKQjwBD9iccfGKkfls=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "2ac4dcbf55ed43f3be0bae15e181f08a57af24a4", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs_3" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/vendor/github.com/go-viper/mapstructure/v2/flake.nix b/vendor/github.com/go-viper/mapstructure/v2/flake.nix new file mode 100644 index 000000000..4ed0f5331 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/flake.nix @@ -0,0 +1,39 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + flake-parts.url = "github:hercules-ci/flake-parts"; + devenv.url = "github:cachix/devenv"; + }; + + outputs = inputs@{ flake-parts, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + imports = [ + inputs.devenv.flakeModule + ]; + + systems = [ "x86_64-linux" "x86_64-darwin" "aarch64-darwin" ]; + + perSystem = { config, self', inputs', pkgs, system, ... }: rec { + devenv.shells = { + default = { + languages = { + go.enable = true; + }; + + pre-commit.hooks = { + nixpkgs-fmt.enable = true; + }; + + packages = with pkgs; [ + golangci-lint + ]; + + # https://github.com/cachix/devenv/issues/528#issuecomment-1556108767 + containers = pkgs.lib.mkForce { }; + }; + + ci = devenv.shells.default; + }; + }; + }; +} diff --git a/vendor/github.com/go-viper/mapstructure/v2/internal/errors/errors.go b/vendor/github.com/go-viper/mapstructure/v2/internal/errors/errors.go new file mode 100644 index 000000000..d1c15e474 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/internal/errors/errors.go @@ -0,0 +1,11 @@ +package errors + +import "errors" + +func New(text string) error { + return errors.New(text) +} + +func As(err error, target interface{}) bool { + return errors.As(err, target) +} diff --git a/vendor/github.com/go-viper/mapstructure/v2/internal/errors/join.go b/vendor/github.com/go-viper/mapstructure/v2/internal/errors/join.go new file mode 100644 index 000000000..d74e3a0b5 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/internal/errors/join.go @@ -0,0 +1,9 @@ +//go:build go1.20 + +package errors + +import "errors" + +func Join(errs ...error) error { + return errors.Join(errs...) +} diff --git a/vendor/github.com/go-viper/mapstructure/v2/internal/errors/join_go1_19.go b/vendor/github.com/go-viper/mapstructure/v2/internal/errors/join_go1_19.go new file mode 100644 index 000000000..700b40229 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/internal/errors/join_go1_19.go @@ -0,0 +1,61 @@ +//go:build !go1.20 + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package errors + +// Join returns an error that wraps the given errors. +// Any nil error values are discarded. +// Join returns nil if every value in errs is nil. +// The error formats as the concatenation of the strings obtained +// by calling the Error method of each element of errs, with a newline +// between each string. +// +// A non-nil error returned by Join implements the Unwrap() []error method. +func Join(errs ...error) error { + n := 0 + for _, err := range errs { + if err != nil { + n++ + } + } + if n == 0 { + return nil + } + e := &joinError{ + errs: make([]error, 0, n), + } + for _, err := range errs { + if err != nil { + e.errs = append(e.errs, err) + } + } + return e +} + +type joinError struct { + errs []error +} + +func (e *joinError) Error() string { + // Since Join returns nil if every value in errs is nil, + // e.errs cannot be empty. + if len(e.errs) == 1 { + return e.errs[0].Error() + } + + b := []byte(e.errs[0].Error()) + for _, err := range e.errs[1:] { + b = append(b, '\n') + b = append(b, err.Error()...) + } + // At this point, b has at least one byte '\n'. + // return unsafe.String(&b[0], len(b)) + return string(b) +} + +func (e *joinError) Unwrap() []error { + return e.errs +} diff --git a/vendor/github.com/mitchellh/mapstructure/mapstructure.go b/vendor/github.com/go-viper/mapstructure/v2/mapstructure.go similarity index 92% rename from vendor/github.com/mitchellh/mapstructure/mapstructure.go rename to vendor/github.com/go-viper/mapstructure/v2/mapstructure.go index 1efb22ac3..4b54fae08 100644 --- a/vendor/github.com/mitchellh/mapstructure/mapstructure.go +++ b/vendor/github.com/go-viper/mapstructure/v2/mapstructure.go @@ -9,84 +9,84 @@ // // The simplest function to start with is Decode. // -// Field Tags +// # Field Tags // // When decoding to a struct, mapstructure will use the field name by // default to perform the mapping. For example, if a struct has a field // "Username" then mapstructure will look for a key in the source value // of "username" (case insensitive). // -// type User struct { -// Username string -// } +// type User struct { +// Username string +// } // // You can change the behavior of mapstructure by using struct tags. // The default struct tag that mapstructure looks for is "mapstructure" // but you can customize it using DecoderConfig. // -// Renaming Fields +// # Renaming Fields // // To rename the key that mapstructure looks for, use the "mapstructure" // tag and set a value directly. For example, to change the "username" example // above to "user": // -// type User struct { -// Username string `mapstructure:"user"` -// } +// type User struct { +// Username string `mapstructure:"user"` +// } // -// Embedded Structs and Squashing +// # Embedded Structs and Squashing // // Embedded structs are treated as if they're another field with that name. // By default, the two structs below are equivalent when decoding with // mapstructure: // -// type Person struct { -// Name string -// } +// type Person struct { +// Name string +// } // -// type Friend struct { -// Person -// } +// type Friend struct { +// Person +// } // -// type Friend struct { -// Person Person -// } +// type Friend struct { +// Person Person +// } // // This would require an input that looks like below: // -// map[string]interface{}{ -// "person": map[string]interface{}{"name": "alice"}, -// } +// map[string]interface{}{ +// "person": map[string]interface{}{"name": "alice"}, +// } // // If your "person" value is NOT nested, then you can append ",squash" to // your tag value and mapstructure will treat it as if the embedded struct // were part of the struct directly. Example: // -// type Friend struct { -// Person `mapstructure:",squash"` -// } +// type Friend struct { +// Person `mapstructure:",squash"` +// } // // Now the following input would be accepted: // -// map[string]interface{}{ -// "name": "alice", -// } +// map[string]interface{}{ +// "name": "alice", +// } // // When decoding from a struct to a map, the squash tag squashes the struct // fields into a single map. Using the example structs from above: // -// Friend{Person: Person{Name: "alice"}} +// Friend{Person: Person{Name: "alice"}} // // Will be decoded into a map: // -// map[string]interface{}{ -// "name": "alice", -// } +// map[string]interface{}{ +// "name": "alice", +// } // // DecoderConfig has a field that changes the behavior of mapstructure // to always squash embedded structs. // -// Remainder Values +// # Remainder Values // // If there are any unmapped keys in the source value, mapstructure by // default will silently ignore them. You can error by setting ErrorUnused @@ -98,20 +98,20 @@ // probably be a "map[string]interface{}" or "map[interface{}]interface{}". // See example below: // -// type Friend struct { -// Name string -// Other map[string]interface{} `mapstructure:",remain"` -// } +// type Friend struct { +// Name string +// Other map[string]interface{} `mapstructure:",remain"` +// } // // Given the input below, Other would be populated with the other // values that weren't used (everything but "name"): // -// map[string]interface{}{ -// "name": "bob", -// "address": "123 Maple St.", -// } +// map[string]interface{}{ +// "name": "bob", +// "address": "123 Maple St.", +// } // -// Omit Empty Values +// # Omit Empty Values // // When decoding from a struct to any other value, you may use the // ",omitempty" suffix on your tag to omit that value if it equates to @@ -122,37 +122,37 @@ // field value is zero and a numeric type, the field is empty, and it won't // be encoded into the destination type. // -// type Source struct { -// Age int `mapstructure:",omitempty"` -// } +// type Source struct { +// Age int `mapstructure:",omitempty"` +// } // -// Unexported fields +// # Unexported fields // // Since unexported (private) struct fields cannot be set outside the package // where they are defined, the decoder will simply skip them. // // For this output type definition: // -// type Exported struct { -// private string // this unexported field will be skipped -// Public string -// } +// type Exported struct { +// private string // this unexported field will be skipped +// Public string +// } // // Using this map as input: // -// map[string]interface{}{ -// "private": "I will be ignored", -// "Public": "I made it through!", -// } +// map[string]interface{}{ +// "private": "I will be ignored", +// "Public": "I made it through!", +// } // // The following struct will be decoded: // -// type Exported struct { -// private: "" // field is left with an empty string (zero value) -// Public: "I made it through!" -// } +// type Exported struct { +// private: "" // field is left with an empty string (zero value) +// Public: "I made it through!" +// } // -// Other Configuration +// # Other Configuration // // mapstructure is highly configurable. See the DecoderConfig struct // for other features and options that are supported. @@ -160,12 +160,13 @@ package mapstructure import ( "encoding/json" - "errors" "fmt" "reflect" "sort" "strconv" "strings" + + "github.com/go-viper/mapstructure/v2/internal/errors" ) // DecodeHookFunc is the callback function that can be used for @@ -414,7 +415,15 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) { // Decode decodes the given raw interface to the target pointer specified // by the configuration. func (d *Decoder) Decode(input interface{}) error { - return d.decode("", input, reflect.ValueOf(d.config.Result).Elem()) + err := d.decode("", input, reflect.ValueOf(d.config.Result).Elem()) + + // Retain some of the original behavior when multiple errors ocurr + var joinedErr interface{ Unwrap() []error } + if errors.As(err, &joinedErr) { + return fmt.Errorf("decoding failed due to the following error(s):\n\n%w", err) + } + + return err } // Decodes an unknown data type into a specific reflection value. @@ -458,7 +467,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e var err error input, err = DecodeHookExec(d.config.DecodeHook, inputVal, outVal) if err != nil { - return fmt.Errorf("error decoding '%s': %s", name, err) + return fmt.Errorf("error decoding '%s': %w", name, err) } } @@ -478,6 +487,8 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e err = d.decodeUint(name, input, outVal) case reflect.Float32: err = d.decodeFloat(name, input, outVal) + case reflect.Complex64: + err = d.decodeComplex(name, input, outVal) case reflect.Struct: err = d.decodeStruct(name, input, outVal) case reflect.Map: @@ -796,6 +807,22 @@ func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) return nil } +func (d *Decoder) decodeComplex(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + + switch { + case dataKind == reflect.Complex64: + val.SetComplex(dataVal.Complex()) + default: + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", + name, val.Type(), dataVal.Type(), data) + } + + return nil +} + func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error { valType := val.Type() valKeyType := valType.Key() @@ -811,8 +838,14 @@ func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) er valMap = reflect.MakeMap(mapType) } + dataVal := reflect.ValueOf(data) + + // Resolve any levels of indirection + for dataVal.Kind() == reflect.Pointer { + dataVal = reflect.Indirect(dataVal) + } + // Check input type and based on the input type jump to the proper func - dataVal := reflect.Indirect(reflect.ValueOf(data)) switch dataVal.Kind() { case reflect.Map: return d.decodeMapFromMap(name, dataVal, val, valMap) @@ -857,7 +890,7 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle valElemType := valType.Elem() // Accumulate errors - errors := make([]string, 0) + var errs []error // If the input data is empty, then we just match what the input data is. if dataVal.Len() == 0 { @@ -879,7 +912,7 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle // First decode the key into the proper type currentKey := reflect.Indirect(reflect.New(valKeyType)) if err := d.decode(fieldName, k.Interface(), currentKey); err != nil { - errors = appendErrors(errors, err) + errs = append(errs, err) continue } @@ -887,7 +920,7 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle v := dataVal.MapIndex(k).Interface() currentVal := reflect.Indirect(reflect.New(valElemType)) if err := d.decode(fieldName, v, currentVal); err != nil { - errors = appendErrors(errors, err) + errs = append(errs, err) continue } @@ -897,12 +930,7 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle // Set the built up map to the value val.Set(valMap) - // If we had errors, return those - if len(errors) > 0 { - return &Error{errors} - } - - return nil + return errors.Join(errs...) } func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { @@ -956,6 +984,18 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re if v.Kind() != reflect.Struct { return fmt.Errorf("cannot squash non-struct type '%s'", v.Type()) } + } else { + if strings.Index(tagValue[index+1:], "remain") != -1 { + if v.Kind() != reflect.Map { + return fmt.Errorf("error remain-tag field with invalid type: '%s'", v.Type()) + } + + ptr := v.MapRange() + for ptr.Next() { + valMap.SetMapIndex(ptr.Key(), ptr.Value()) + } + continue + } } if keyNameTagValue := tagValue[:index]; keyNameTagValue != "" { keyName = keyNameTagValue @@ -1123,10 +1163,12 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) if valSlice.IsNil() || d.config.ZeroFields { // Make a new slice to hold our result, same size as the original data. valSlice = reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len()) + } else if valSlice.Len() > dataVal.Len() { + valSlice = valSlice.Slice(0, dataVal.Len()) } // Accumulate any errors - errors := make([]string, 0) + var errs []error for i := 0; i < dataVal.Len(); i++ { currentData := dataVal.Index(i).Interface() @@ -1137,19 +1179,14 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) fieldName := name + "[" + strconv.Itoa(i) + "]" if err := d.decode(fieldName, currentData, currentField); err != nil { - errors = appendErrors(errors, err) + errs = append(errs, err) } } // Finally, set the value to the slice we built up val.Set(valSlice) - // If there were errors, we return those - if len(errors) > 0 { - return &Error{errors} - } - - return nil + return errors.Join(errs...) } func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value) error { @@ -1161,7 +1198,7 @@ func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value) valArray := val - if valArray.Interface() == reflect.Zero(valArray.Type()).Interface() || d.config.ZeroFields { + if isComparable(valArray) && valArray.Interface() == reflect.Zero(valArray.Type()).Interface() || d.config.ZeroFields { // Check input type if dataValKind != reflect.Array && dataValKind != reflect.Slice { if d.config.WeaklyTypedInput { @@ -1188,7 +1225,6 @@ func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value) if dataVal.Len() > arrayType.Len() { return fmt.Errorf( "'%s': expected source data to have length less or equal to %d, got %d", name, arrayType.Len(), dataVal.Len()) - } // Make a new array to hold our result, same size as the original data. @@ -1196,7 +1232,7 @@ func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value) } // Accumulate any errors - errors := make([]string, 0) + var errs []error for i := 0; i < dataVal.Len(); i++ { currentData := dataVal.Index(i).Interface() @@ -1204,19 +1240,14 @@ func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value) fieldName := name + "[" + strconv.Itoa(i) + "]" if err := d.decode(fieldName, currentData, currentField); err != nil { - errors = appendErrors(errors, err) + errs = append(errs, err) } } // Finally, set the value to the array we built up val.Set(valArray) - // If there were errors, we return those - if len(errors) > 0 { - return &Error{errors} - } - - return nil + return errors.Join(errs...) } func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) error { @@ -1278,7 +1309,8 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e } targetValKeysUnused := make(map[interface{}]struct{}) - errors := make([]string, 0) + + var errs []error // This slice will keep track of all the structs we'll be decoding. // There can be more than one struct if there are embedded structs @@ -1332,8 +1364,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e if squash { if fieldVal.Kind() != reflect.Struct { - errors = appendErrors(errors, - fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldVal.Kind())) + errs = append(errs, fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldVal.Kind())) } else { structs = append(structs, fieldVal) } @@ -1356,6 +1387,9 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e fieldName := field.Name tagValue := field.Tag.Get(d.config.TagName) + if tagValue == "" && d.config.IgnoreUntaggedFields { + continue + } tagValue = strings.SplitN(tagValue, ",", 2)[0] if tagValue != "" { fieldName = tagValue @@ -1409,7 +1443,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e } if err := d.decode(fieldName, rawMapVal.Interface(), fieldValue); err != nil { - errors = appendErrors(errors, err) + errs = append(errs, err) } } @@ -1424,7 +1458,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e // Decode it as-if we were just decoding this map onto our map. if err := d.decodeMap(name, remain, remainField.val); err != nil { - errors = appendErrors(errors, err) + errs = append(errs, err) } // Set the map to nil so we have none so that the next check will @@ -1440,7 +1474,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e sort.Strings(keys) err := fmt.Errorf("'%s' has invalid keys: %s", name, strings.Join(keys, ", ")) - errors = appendErrors(errors, err) + errs = append(errs, err) } if d.config.ErrorUnset && len(targetValKeysUnused) > 0 { @@ -1451,11 +1485,11 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e sort.Strings(keys) err := fmt.Errorf("'%s' has unset fields: %s", name, strings.Join(keys, ", ")) - errors = appendErrors(errors, err) + errs = append(errs, err) } - if len(errors) > 0 { - return &Error{errors} + if err := errors.Join(errs...); err != nil { + return err } // Add the unused keys to the list of unused keys if we're tracking metadata @@ -1509,6 +1543,8 @@ func getKind(val reflect.Value) reflect.Kind { return reflect.Uint case kind >= reflect.Float32 && kind <= reflect.Float64: return reflect.Float32 + case kind >= reflect.Complex64 && kind <= reflect.Complex128: + return reflect.Complex64 default: return kind } diff --git a/vendor/github.com/go-viper/mapstructure/v2/reflect_go1_19.go b/vendor/github.com/go-viper/mapstructure/v2/reflect_go1_19.go new file mode 100644 index 000000000..d0913fff6 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/reflect_go1_19.go @@ -0,0 +1,44 @@ +//go:build !go1.20 + +package mapstructure + +import "reflect" + +func isComparable(v reflect.Value) bool { + k := v.Kind() + switch k { + case reflect.Invalid: + return false + + case reflect.Array: + switch v.Type().Elem().Kind() { + case reflect.Interface, reflect.Array, reflect.Struct: + for i := 0; i < v.Type().Len(); i++ { + // if !v.Index(i).Comparable() { + if !isComparable(v.Index(i)) { + return false + } + } + return true + } + return v.Type().Comparable() + + case reflect.Interface: + // return v.Elem().Comparable() + return isComparable(v.Elem()) + + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + return false + + // if !v.Field(i).Comparable() { + if !isComparable(v.Field(i)) { + return false + } + } + return true + + default: + return v.Type().Comparable() + } +} diff --git a/vendor/github.com/go-viper/mapstructure/v2/reflect_go1_20.go b/vendor/github.com/go-viper/mapstructure/v2/reflect_go1_20.go new file mode 100644 index 000000000..f8255a1b1 --- /dev/null +++ b/vendor/github.com/go-viper/mapstructure/v2/reflect_go1_20.go @@ -0,0 +1,10 @@ +//go:build go1.20 + +package mapstructure + +import "reflect" + +// TODO: remove once we drop support for Go <1.20 +func isComparable(v reflect.Value) bool { + return v.Comparable() +} diff --git a/vendor/github.com/mitchellh/mapstructure/README.md b/vendor/github.com/mitchellh/mapstructure/README.md deleted file mode 100644 index 0018dc7d9..000000000 --- a/vendor/github.com/mitchellh/mapstructure/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# mapstructure [![Godoc](https://godoc.org/github.com/mitchellh/mapstructure?status.svg)](https://godoc.org/github.com/mitchellh/mapstructure) - -mapstructure is a Go library for decoding generic map values to structures -and vice versa, while providing helpful error handling. - -This library is most useful when decoding values from some data stream (JSON, -Gob, etc.) where you don't _quite_ know the structure of the underlying data -until you read a part of it. You can therefore read a `map[string]interface{}` -and use this library to decode it into the proper underlying native Go -structure. - -## Installation - -Standard `go get`: - -``` -$ go get github.com/mitchellh/mapstructure -``` - -## Usage & Example - -For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/mapstructure). - -The `Decode` function has examples associated with it there. - -## But Why?! - -Go offers fantastic standard libraries for decoding formats such as JSON. -The standard method is to have a struct pre-created, and populate that struct -from the bytes of the encoded format. This is great, but the problem is if -you have configuration or an encoding that changes slightly depending on -specific fields. For example, consider this JSON: - -```json -{ - "type": "person", - "name": "Mitchell" -} -``` - -Perhaps we can't populate a specific structure without first reading -the "type" field from the JSON. We could always do two passes over the -decoding of the JSON (reading the "type" first, and the rest later). -However, it is much simpler to just decode this into a `map[string]interface{}` -structure, read the "type" key, then use something like this library -to decode it into the proper structure. diff --git a/vendor/github.com/mitchellh/mapstructure/decode_hooks.go b/vendor/github.com/mitchellh/mapstructure/decode_hooks.go deleted file mode 100644 index 3a754ca72..000000000 --- a/vendor/github.com/mitchellh/mapstructure/decode_hooks.go +++ /dev/null @@ -1,279 +0,0 @@ -package mapstructure - -import ( - "encoding" - "errors" - "fmt" - "net" - "reflect" - "strconv" - "strings" - "time" -) - -// typedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns -// it into the proper DecodeHookFunc type, such as DecodeHookFuncType. -func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc { - // Create variables here so we can reference them with the reflect pkg - var f1 DecodeHookFuncType - var f2 DecodeHookFuncKind - var f3 DecodeHookFuncValue - - // Fill in the variables into this interface and the rest is done - // automatically using the reflect package. - potential := []interface{}{f1, f2, f3} - - v := reflect.ValueOf(h) - vt := v.Type() - for _, raw := range potential { - pt := reflect.ValueOf(raw).Type() - if vt.ConvertibleTo(pt) { - return v.Convert(pt).Interface() - } - } - - return nil -} - -// DecodeHookExec executes the given decode hook. This should be used -// since it'll naturally degrade to the older backwards compatible DecodeHookFunc -// that took reflect.Kind instead of reflect.Type. -func DecodeHookExec( - raw DecodeHookFunc, - from reflect.Value, to reflect.Value) (interface{}, error) { - - switch f := typedDecodeHook(raw).(type) { - case DecodeHookFuncType: - return f(from.Type(), to.Type(), from.Interface()) - case DecodeHookFuncKind: - return f(from.Kind(), to.Kind(), from.Interface()) - case DecodeHookFuncValue: - return f(from, to) - default: - return nil, errors.New("invalid decode hook signature") - } -} - -// ComposeDecodeHookFunc creates a single DecodeHookFunc that -// automatically composes multiple DecodeHookFuncs. -// -// The composed funcs are called in order, with the result of the -// previous transformation. -func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc { - return func(f reflect.Value, t reflect.Value) (interface{}, error) { - var err error - data := f.Interface() - - newFrom := f - for _, f1 := range fs { - data, err = DecodeHookExec(f1, newFrom, t) - if err != nil { - return nil, err - } - newFrom = reflect.ValueOf(data) - } - - return data, nil - } -} - -// OrComposeDecodeHookFunc executes all input hook functions until one of them returns no error. In that case its value is returned. -// If all hooks return an error, OrComposeDecodeHookFunc returns an error concatenating all error messages. -func OrComposeDecodeHookFunc(ff ...DecodeHookFunc) DecodeHookFunc { - return func(a, b reflect.Value) (interface{}, error) { - var allErrs string - var out interface{} - var err error - - for _, f := range ff { - out, err = DecodeHookExec(f, a, b) - if err != nil { - allErrs += err.Error() + "\n" - continue - } - - return out, nil - } - - return nil, errors.New(allErrs) - } -} - -// StringToSliceHookFunc returns a DecodeHookFunc that converts -// string to []string by splitting on the given sep. -func StringToSliceHookFunc(sep string) DecodeHookFunc { - return func( - f reflect.Kind, - t reflect.Kind, - data interface{}) (interface{}, error) { - if f != reflect.String || t != reflect.Slice { - return data, nil - } - - raw := data.(string) - if raw == "" { - return []string{}, nil - } - - return strings.Split(raw, sep), nil - } -} - -// StringToTimeDurationHookFunc returns a DecodeHookFunc that converts -// strings to time.Duration. -func StringToTimeDurationHookFunc() DecodeHookFunc { - return func( - f reflect.Type, - t reflect.Type, - data interface{}) (interface{}, error) { - if f.Kind() != reflect.String { - return data, nil - } - if t != reflect.TypeOf(time.Duration(5)) { - return data, nil - } - - // Convert it by parsing - return time.ParseDuration(data.(string)) - } -} - -// StringToIPHookFunc returns a DecodeHookFunc that converts -// strings to net.IP -func StringToIPHookFunc() DecodeHookFunc { - return func( - f reflect.Type, - t reflect.Type, - data interface{}) (interface{}, error) { - if f.Kind() != reflect.String { - return data, nil - } - if t != reflect.TypeOf(net.IP{}) { - return data, nil - } - - // Convert it by parsing - ip := net.ParseIP(data.(string)) - if ip == nil { - return net.IP{}, fmt.Errorf("failed parsing ip %v", data) - } - - return ip, nil - } -} - -// StringToIPNetHookFunc returns a DecodeHookFunc that converts -// strings to net.IPNet -func StringToIPNetHookFunc() DecodeHookFunc { - return func( - f reflect.Type, - t reflect.Type, - data interface{}) (interface{}, error) { - if f.Kind() != reflect.String { - return data, nil - } - if t != reflect.TypeOf(net.IPNet{}) { - return data, nil - } - - // Convert it by parsing - _, net, err := net.ParseCIDR(data.(string)) - return net, err - } -} - -// StringToTimeHookFunc returns a DecodeHookFunc that converts -// strings to time.Time. -func StringToTimeHookFunc(layout string) DecodeHookFunc { - return func( - f reflect.Type, - t reflect.Type, - data interface{}) (interface{}, error) { - if f.Kind() != reflect.String { - return data, nil - } - if t != reflect.TypeOf(time.Time{}) { - return data, nil - } - - // Convert it by parsing - return time.Parse(layout, data.(string)) - } -} - -// WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to -// the decoder. -// -// Note that this is significantly different from the WeaklyTypedInput option -// of the DecoderConfig. -func WeaklyTypedHook( - f reflect.Kind, - t reflect.Kind, - data interface{}) (interface{}, error) { - dataVal := reflect.ValueOf(data) - switch t { - case reflect.String: - switch f { - case reflect.Bool: - if dataVal.Bool() { - return "1", nil - } - return "0", nil - case reflect.Float32: - return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil - case reflect.Int: - return strconv.FormatInt(dataVal.Int(), 10), nil - case reflect.Slice: - dataType := dataVal.Type() - elemKind := dataType.Elem().Kind() - if elemKind == reflect.Uint8 { - return string(dataVal.Interface().([]uint8)), nil - } - case reflect.Uint: - return strconv.FormatUint(dataVal.Uint(), 10), nil - } - } - - return data, nil -} - -func RecursiveStructToMapHookFunc() DecodeHookFunc { - return func(f reflect.Value, t reflect.Value) (interface{}, error) { - if f.Kind() != reflect.Struct { - return f.Interface(), nil - } - - var i interface{} = struct{}{} - if t.Type() != reflect.TypeOf(&i).Elem() { - return f.Interface(), nil - } - - m := make(map[string]interface{}) - t.Set(reflect.ValueOf(m)) - - return f.Interface(), nil - } -} - -// TextUnmarshallerHookFunc returns a DecodeHookFunc that applies -// strings to the UnmarshalText function, when the target type -// implements the encoding.TextUnmarshaler interface -func TextUnmarshallerHookFunc() DecodeHookFuncType { - return func( - f reflect.Type, - t reflect.Type, - data interface{}) (interface{}, error) { - if f.Kind() != reflect.String { - return data, nil - } - result := reflect.New(t).Interface() - unmarshaller, ok := result.(encoding.TextUnmarshaler) - if !ok { - return data, nil - } - if err := unmarshaller.UnmarshalText([]byte(data.(string))); err != nil { - return nil, err - } - return result, nil - } -} diff --git a/vendor/github.com/mitchellh/mapstructure/error.go b/vendor/github.com/mitchellh/mapstructure/error.go deleted file mode 100644 index 47a99e5af..000000000 --- a/vendor/github.com/mitchellh/mapstructure/error.go +++ /dev/null @@ -1,50 +0,0 @@ -package mapstructure - -import ( - "errors" - "fmt" - "sort" - "strings" -) - -// Error implements the error interface and can represents multiple -// errors that occur in the course of a single decode. -type Error struct { - Errors []string -} - -func (e *Error) Error() string { - points := make([]string, len(e.Errors)) - for i, err := range e.Errors { - points[i] = fmt.Sprintf("* %s", err) - } - - sort.Strings(points) - return fmt.Sprintf( - "%d error(s) decoding:\n\n%s", - len(e.Errors), strings.Join(points, "\n")) -} - -// WrappedErrors implements the errwrap.Wrapper interface to make this -// return value more useful with the errwrap and go-multierror libraries. -func (e *Error) WrappedErrors() []error { - if e == nil { - return nil - } - - result := make([]error, len(e.Errors)) - for i, e := range e.Errors { - result[i] = errors.New(e) - } - - return result -} - -func appendErrors(errors []string, err error) []string { - switch e := err.(type) { - case *Error: - return append(errors, e.Errors...) - default: - return append(errors, e.Error()) - } -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 71a67bc4c..06d37055e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -196,19 +196,25 @@ github.com/cespare/xxhash/v2 github.com/chrismellard/docker-credential-acr-env/pkg/credhelper github.com/chrismellard/docker-credential-acr-env/pkg/registry github.com/chrismellard/docker-credential-acr-env/pkg/token -# github.com/compose-spec/compose-go v1.20.2 -## explicit; go 1.19 -github.com/compose-spec/compose-go/cli -github.com/compose-spec/compose-go/consts -github.com/compose-spec/compose-go/dotenv -github.com/compose-spec/compose-go/errdefs -github.com/compose-spec/compose-go/interpolation -github.com/compose-spec/compose-go/loader -github.com/compose-spec/compose-go/schema -github.com/compose-spec/compose-go/template -github.com/compose-spec/compose-go/tree -github.com/compose-spec/compose-go/types -github.com/compose-spec/compose-go/utils +# github.com/compose-spec/compose-go/v2 v2.2.0 +## explicit; go 1.21 +github.com/compose-spec/compose-go/v2/cli +github.com/compose-spec/compose-go/v2/consts +github.com/compose-spec/compose-go/v2/dotenv +github.com/compose-spec/compose-go/v2/errdefs +github.com/compose-spec/compose-go/v2/format +github.com/compose-spec/compose-go/v2/graph +github.com/compose-spec/compose-go/v2/interpolation +github.com/compose-spec/compose-go/v2/loader +github.com/compose-spec/compose-go/v2/override +github.com/compose-spec/compose-go/v2/paths +github.com/compose-spec/compose-go/v2/schema +github.com/compose-spec/compose-go/v2/template +github.com/compose-spec/compose-go/v2/transform +github.com/compose-spec/compose-go/v2/tree +github.com/compose-spec/compose-go/v2/types +github.com/compose-spec/compose-go/v2/utils +github.com/compose-spec/compose-go/v2/validation # github.com/containerd/console v1.0.3 ## explicit; go 1.13 github.com/containerd/console @@ -355,6 +361,10 @@ github.com/go-openapi/swag # github.com/go-task/slim-sprig/v3 v3.0.0 ## explicit; go 1.20 github.com/go-task/slim-sprig/v3 +# github.com/go-viper/mapstructure/v2 v2.0.0 +## explicit; go 1.18 +github.com/go-viper/mapstructure/v2 +github.com/go-viper/mapstructure/v2/internal/errors # github.com/gofrs/flock v0.8.1 ## explicit github.com/gofrs/flock @@ -595,7 +605,6 @@ github.com/mitchellh/go-homedir github.com/mitchellh/go-wordwrap # github.com/mitchellh/mapstructure v1.5.0 ## explicit; go 1.14 -github.com/mitchellh/mapstructure # github.com/moby/buildkit v0.11.6 ## explicit; go 1.18 github.com/moby/buildkit/api/services/control