mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-07-09 21:17:09 +08:00
vendor: update compose-go to v2.0.0-rc.3
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
191
vendor/github.com/compose-spec/compose-go/v2/LICENSE
generated
vendored
Normal file
191
vendor/github.com/compose-spec/compose-go/v2/LICENSE
generated
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
https://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2013-2017 Docker, Inc.
|
||||
|
||||
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
|
||||
|
||||
https://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.
|
2
vendor/github.com/compose-spec/compose-go/v2/NOTICE
generated
vendored
Normal file
2
vendor/github.com/compose-spec/compose-go/v2/NOTICE
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
The Compose Specification
|
||||
Copyright 2020 The Compose Specification Authors
|
507
vendor/github.com/compose-spec/compose-go/v2/cli/options.go
generated
vendored
Normal file
507
vendor/github.com/compose-spec/compose-go/v2/cli/options.go
generated
vendored
Normal file
@ -0,0 +1,507 @@
|
||||
/*
|
||||
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 cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"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
|
||||
// project name if possible.
|
||||
Name string
|
||||
|
||||
// WorkingDir is a file path to use as the project directory or empty.
|
||||
//
|
||||
// If empty, the project loader will automatically infer a reasonable
|
||||
// working directory if possible.
|
||||
WorkingDir string
|
||||
|
||||
// ConfigPaths are file paths to one or more Compose files.
|
||||
//
|
||||
// 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.
|
||||
// For convenience, WithConfigFileEnv and WithDefaultConfigPath
|
||||
// are provided to populate this in a predictable manner.
|
||||
ConfigPaths []string
|
||||
|
||||
// Environment are additional environment variables to make available
|
||||
// for interpolation.
|
||||
//
|
||||
// NOTE: For security, the loader does not automatically expose any
|
||||
// process environment variables. For convenience, WithOsEnv can be
|
||||
// used if appropriate.
|
||||
Environment types.Mapping
|
||||
|
||||
// EnvFiles are file paths to ".env" files with additional environment
|
||||
// variable data.
|
||||
//
|
||||
// These are loaded in-order, so it is possible to override variables or
|
||||
// in subsequent files.
|
||||
//
|
||||
// This field is optional, but any file paths that are included here must
|
||||
// exist or an error will be returned during load.
|
||||
EnvFiles []string
|
||||
|
||||
loadOptions []func(*loader.Options)
|
||||
}
|
||||
|
||||
type ProjectOptionsFn func(*ProjectOptions) error
|
||||
|
||||
// NewProjectOptions creates ProjectOptions
|
||||
func NewProjectOptions(configs []string, opts ...ProjectOptionsFn) (*ProjectOptions, error) {
|
||||
options := &ProjectOptions{
|
||||
ConfigPaths: configs,
|
||||
Environment: map[string]string{},
|
||||
}
|
||||
for _, o := range opts {
|
||||
err := o(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
// WithName defines ProjectOptions' name
|
||||
func WithName(name string) ProjectOptionsFn {
|
||||
return func(o *ProjectOptions) error {
|
||||
// a project (once loaded) cannot have an empty name
|
||||
// however, on the options object, the name is optional: if unset,
|
||||
// a name will be inferred by the loader, so it's legal to set the
|
||||
// name to an empty string here
|
||||
if name != loader.NormalizeProjectName(name) {
|
||||
return loader.InvalidProjectNameErr(name)
|
||||
}
|
||||
o.Name = name
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithWorkingDirectory defines ProjectOptions' working directory
|
||||
func WithWorkingDirectory(wd string) ProjectOptionsFn {
|
||||
return func(o *ProjectOptions) error {
|
||||
if wd == "" {
|
||||
return nil
|
||||
}
|
||||
abs, err := filepath.Abs(wd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.WorkingDir = abs
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithConfigFileEnv allow to set compose config file paths by COMPOSE_FILE environment variable
|
||||
func WithConfigFileEnv(o *ProjectOptions) error {
|
||||
if len(o.ConfigPaths) > 0 {
|
||||
return nil
|
||||
}
|
||||
sep := o.Environment[consts.ComposePathSeparator]
|
||||
if sep == "" {
|
||||
sep = string(os.PathListSeparator)
|
||||
}
|
||||
f, ok := o.Environment[consts.ComposeFilePath]
|
||||
if ok {
|
||||
paths, err := absolutePaths(strings.Split(f, sep))
|
||||
o.ConfigPaths = paths
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithDefaultConfigPath searches for default config files from working directory
|
||||
func WithDefaultConfigPath(o *ProjectOptions) error {
|
||||
if len(o.ConfigPaths) > 0 {
|
||||
return nil
|
||||
}
|
||||
pwd, err := o.GetWorkingDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
candidates := findFiles(DefaultFileNames, pwd)
|
||||
if len(candidates) > 0 {
|
||||
winner := candidates[0]
|
||||
if len(candidates) > 1 {
|
||||
logrus.Warnf("Found multiple config files with supported names: %s", strings.Join(candidates, ", "))
|
||||
logrus.Warnf("Using %s", winner)
|
||||
}
|
||||
o.ConfigPaths = append(o.ConfigPaths, winner)
|
||||
|
||||
overrides := findFiles(DefaultOverrideFileNames, pwd)
|
||||
if len(overrides) > 0 {
|
||||
if len(overrides) > 1 {
|
||||
logrus.Warnf("Found multiple override files with supported names: %s", strings.Join(overrides, ", "))
|
||||
logrus.Warnf("Using %s", overrides[0])
|
||||
}
|
||||
o.ConfigPaths = append(o.ConfigPaths, overrides[0])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
parent := filepath.Dir(pwd)
|
||||
if parent == pwd {
|
||||
// no config file found, but that's not a blocker if caller only needs project name
|
||||
return nil
|
||||
}
|
||||
pwd = parent
|
||||
}
|
||||
}
|
||||
|
||||
// WithEnv defines a key=value set of variables used for compose file interpolation
|
||||
func WithEnv(env []string) ProjectOptionsFn {
|
||||
return func(o *ProjectOptions) error {
|
||||
for k, v := range utils.GetAsEqualsMap(env) {
|
||||
o.Environment[k] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithDiscardEnvFile sets discards the `env_file` section after resolving to
|
||||
// the `environment` section
|
||||
func WithDiscardEnvFile(o *ProjectOptions) error {
|
||||
o.loadOptions = append(o.loadOptions, loader.WithDiscardEnvFiles)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithLoadOptions provides a hook to control how compose files are loaded
|
||||
func WithLoadOptions(loadOptions ...func(*loader.Options)) ProjectOptionsFn {
|
||||
return func(o *ProjectOptions) error {
|
||||
o.loadOptions = append(o.loadOptions, loadOptions...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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), ",")
|
||||
}
|
||||
return WithProfiles(profile)
|
||||
}
|
||||
|
||||
// WithProfiles sets profiles to be activated
|
||||
func WithProfiles(profiles []string) ProjectOptionsFn {
|
||||
return func(o *ProjectOptions) error {
|
||||
o.loadOptions = append(o.loadOptions, loader.WithProfiles(profiles))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithOsEnv imports environment variables from OS
|
||||
func WithOsEnv(o *ProjectOptions) error {
|
||||
for k, v := range utils.GetAsEqualsMap(os.Environ()) {
|
||||
if _, set := o.Environment[k]; set {
|
||||
continue
|
||||
}
|
||||
o.Environment[k] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithEnvFile sets an alternate env file.
|
||||
//
|
||||
// Deprecated: use WithEnvFiles instead.
|
||||
func WithEnvFile(file string) ProjectOptionsFn {
|
||||
var files []string
|
||||
if file != "" {
|
||||
files = []string{file}
|
||||
}
|
||||
return WithEnvFiles(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(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 !s.IsDir() {
|
||||
o.EnvFiles = []string{defaultDotEnv}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithDotEnv imports environment variables from .env file
|
||||
func WithDotEnv(o *ProjectOptions) error {
|
||||
envMap, err := dotenv.GetEnvFromFile(o.Environment, o.EnvFiles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Environment.Merge(envMap)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithInterpolation set ProjectOptions to enable/skip interpolation
|
||||
func WithInterpolation(interpolation bool) ProjectOptionsFn {
|
||||
return func(o *ProjectOptions) error {
|
||||
o.loadOptions = append(o.loadOptions, func(options *loader.Options) {
|
||||
options.SkipInterpolation = !interpolation
|
||||
})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithNormalization set ProjectOptions to enable/skip normalization
|
||||
func WithNormalization(normalization bool) ProjectOptionsFn {
|
||||
return func(o *ProjectOptions) error {
|
||||
o.loadOptions = append(o.loadOptions, func(options *loader.Options) {
|
||||
options.SkipNormalization = !normalization
|
||||
})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithConsistency set ProjectOptions to enable/skip consistency
|
||||
func WithConsistency(consistency bool) ProjectOptionsFn {
|
||||
return func(o *ProjectOptions) error {
|
||||
o.loadOptions = append(o.loadOptions, func(options *loader.Options) {
|
||||
options.SkipConsistencyCheck = !consistency
|
||||
})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithResolvedPaths set ProjectOptions to enable paths resolution
|
||||
func WithResolvedPaths(resolve bool) ProjectOptionsFn {
|
||||
return func(o *ProjectOptions) error {
|
||||
o.loadOptions = append(o.loadOptions, func(options *loader.Options) {
|
||||
options.ResolvePaths = resolve
|
||||
})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithContext sets the context used to load model and resources
|
||||
func WithContext(ctx context.Context) ProjectOptionsFn {
|
||||
return func(o *ProjectOptions) error {
|
||||
o.ctx = ctx
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithResourceLoader register support for ResourceLoader to manage remote resources
|
||||
func WithResourceLoader(r loader.ResourceLoader) ProjectOptionsFn {
|
||||
return func(o *ProjectOptions) error {
|
||||
o.loadOptions = append(o.loadOptions, func(options *loader.Options) {
|
||||
options.ResourceLoaders = append(options.ResourceLoaders, r)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithoutEnvironmentResolution disable environment resolution
|
||||
func WithoutEnvironmentResolution(o *ProjectOptions) error {
|
||||
o.loadOptions = append(o.loadOptions, func(options *loader.Options) {
|
||||
options.SkipResolveEnvironment = true
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultFileNames defines the Compose file names for auto-discovery (in order of preference)
|
||||
var DefaultFileNames = []string{"compose.yaml", "compose.yml", "docker-compose.yml", "docker-compose.yaml"}
|
||||
|
||||
// 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) {
|
||||
if o.WorkingDir != "" {
|
||||
return o.WorkingDir, nil
|
||||
}
|
||||
for _, path := range o.ConfigPaths {
|
||||
if path != "-" {
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Dir(absPath), nil
|
||||
}
|
||||
}
|
||||
return os.Getwd()
|
||||
}
|
||||
|
||||
// ProjectFromOptions load a compose project based on command line options
|
||||
func ProjectFromOptions(options *ProjectOptions) (*types.Project, error) {
|
||||
configPaths, err := getConfigPathsFromOptions(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var configs []types.ConfigFile
|
||||
for _, f := range configPaths {
|
||||
var b []byte
|
||||
if f == "-" {
|
||||
b, err = io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
f, err := filepath.Abs(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, err = os.ReadFile(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
configs = append(configs, types.ConfigFile{
|
||||
Filename: f,
|
||||
Content: b,
|
||||
})
|
||||
}
|
||||
|
||||
workingDir, err := options.GetWorkingDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
absWorkingDir, err := filepath.Abs(workingDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
options.loadOptions = append(options.loadOptions,
|
||||
withNamePrecedenceLoad(absWorkingDir, options),
|
||||
withConvertWindowsPaths(options))
|
||||
|
||||
ctx := options.ctx
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
project, err := loader.LoadWithContext(ctx, types.ConfigDetails{
|
||||
ConfigFiles: configs,
|
||||
WorkingDir: workingDir,
|
||||
Environment: options.Environment,
|
||||
}, options.loadOptions...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
project.ComposeFiles = configPaths
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func withNamePrecedenceLoad(absWorkingDir string, options *ProjectOptions) func(*loader.Options) {
|
||||
return func(opts *loader.Options) {
|
||||
if options.Name != "" {
|
||||
opts.SetProjectName(options.Name, true)
|
||||
} else if nameFromEnv, ok := options.Environment[consts.ComposeProjectName]; ok && nameFromEnv != "" {
|
||||
opts.SetProjectName(nameFromEnv, true)
|
||||
} else {
|
||||
opts.SetProjectName(
|
||||
loader.NormalizeProjectName(filepath.Base(absWorkingDir)),
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func withConvertWindowsPaths(options *ProjectOptions) func(*loader.Options) {
|
||||
return func(o *loader.Options) {
|
||||
if o.ResolvePaths {
|
||||
o.ConvertWindowsPaths = utils.StringToBool(options.Environment["COMPOSE_CONVERT_WINDOWS_PATHS"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
return nil, fmt.Errorf("no configuration file provided: %w", errdefs.ErrNotFound)
|
||||
}
|
||||
|
||||
func findFiles(names []string, pwd string) []string {
|
||||
candidates := []string{}
|
||||
for _, n := range names {
|
||||
f := filepath.Join(pwd, n)
|
||||
if _, err := os.Stat(f); err == nil {
|
||||
candidates = append(candidates, f)
|
||||
}
|
||||
}
|
||||
return candidates
|
||||
}
|
||||
|
||||
func absolutePaths(p []string) ([]string, error) {
|
||||
var paths []string
|
||||
for _, f := range p {
|
||||
if f == "-" {
|
||||
paths = append(paths, f)
|
||||
continue
|
||||
}
|
||||
abs, err := filepath.Abs(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f = abs
|
||||
if _, err := os.Stat(f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paths = append(paths, f)
|
||||
}
|
||||
return paths, nil
|
||||
}
|
29
vendor/github.com/compose-spec/compose-go/v2/consts/consts.go
generated
vendored
Normal file
29
vendor/github.com/compose-spec/compose-go/v2/consts/consts.go
generated
vendored
Normal file
@ -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{}
|
22
vendor/github.com/compose-spec/compose-go/v2/dotenv/LICENSE
generated
vendored
Normal file
22
vendor/github.com/compose-spec/compose-go/v2/dotenv/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
Copyright (c) 2013 John Barton
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
76
vendor/github.com/compose-spec/compose-go/v2/dotenv/env.go
generated
vendored
Normal file
76
vendor/github.com/compose-spec/compose-go/v2/dotenv/env.go
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
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 dotenv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func GetEnvFromFile(currentEnv map[string]string, filenames []string) (map[string]string, error) {
|
||||
envMap := make(map[string]string)
|
||||
|
||||
for _, dotEnvFile := range filenames {
|
||||
abs, err := filepath.Abs(dotEnvFile)
|
||||
if err != nil {
|
||||
return envMap, err
|
||||
}
|
||||
dotEnvFile = abs
|
||||
|
||||
s, err := os.Stat(dotEnvFile)
|
||||
if os.IsNotExist(err) {
|
||||
return envMap, fmt.Errorf("Couldn't find env file: %s", dotEnvFile)
|
||||
}
|
||||
if err != nil {
|
||||
return envMap, err
|
||||
}
|
||||
|
||||
if s.IsDir() {
|
||||
if len(filenames) == 0 {
|
||||
return envMap, nil
|
||||
}
|
||||
return envMap, fmt.Errorf("%s is a directory", dotEnvFile)
|
||||
}
|
||||
|
||||
b, err := os.ReadFile(dotEnvFile)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("Couldn't read env file: %s", dotEnvFile)
|
||||
}
|
||||
if err != nil {
|
||||
return envMap, err
|
||||
}
|
||||
|
||||
env, err := ParseWithLookup(bytes.NewReader(b), func(k string) (string, bool) {
|
||||
v, ok := currentEnv[k]
|
||||
if ok {
|
||||
return v, true
|
||||
}
|
||||
v, ok = envMap[k]
|
||||
return v, ok
|
||||
})
|
||||
if err != nil {
|
||||
return envMap, fmt.Errorf("failed to read %s: %w", dotEnvFile, err)
|
||||
}
|
||||
for k, v := range env {
|
||||
envMap[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return envMap, nil
|
||||
}
|
175
vendor/github.com/compose-spec/compose-go/v2/dotenv/godotenv.go
generated
vendored
Normal file
175
vendor/github.com/compose-spec/compose-go/v2/dotenv/godotenv.go
generated
vendored
Normal file
@ -0,0 +1,175 @@
|
||||
// Package dotenv is a go port of the ruby dotenv library (https://github.com/bkeepers/dotenv)
|
||||
//
|
||||
// Examples/readme can be found on the github page at https://github.com/joho/godotenv
|
||||
//
|
||||
// The TL;DR is that you make a .env file that looks something like
|
||||
//
|
||||
// SOME_ENV_VAR=somevalue
|
||||
//
|
||||
// and then in your go code you can call
|
||||
//
|
||||
// godotenv.Load()
|
||||
//
|
||||
// and all the env vars declared in .env will be available through os.Getenv("SOME_ENV_VAR")
|
||||
package dotenv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/template"
|
||||
)
|
||||
|
||||
var utf8BOM = []byte("\uFEFF")
|
||||
|
||||
var startsWithDigitRegex = regexp.MustCompile(`^\s*\d.*`) // Keys starting with numbers are ignored
|
||||
|
||||
// LookupFn represents a lookup function to resolve variables from
|
||||
type LookupFn func(string) (string, bool)
|
||||
|
||||
var noLookupFn = func(s string) (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Parse reads an env file from io.Reader, returning a map of keys and values.
|
||||
func Parse(r io.Reader) (map[string]string, error) {
|
||||
return ParseWithLookup(r, nil)
|
||||
}
|
||||
|
||||
// ParseWithLookup reads an env file from io.Reader, returning a map of keys and values.
|
||||
func ParseWithLookup(r io.Reader, lookupFn LookupFn) (map[string]string, error) {
|
||||
data, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// seek past the UTF-8 BOM if it exists (particularly on Windows, some
|
||||
// editors tend to add it, and it'll cause parsing to fail)
|
||||
data = bytes.TrimPrefix(data, utf8BOM)
|
||||
|
||||
return UnmarshalBytesWithLookup(data, lookupFn)
|
||||
}
|
||||
|
||||
// Load will read your env file(s) and load them into ENV for this process.
|
||||
//
|
||||
// Call this function as close as possible to the start of your program (ideally in main).
|
||||
//
|
||||
// If you call Load without any args it will default to loading .env in the current path.
|
||||
//
|
||||
// You can otherwise tell it which files to load (there can be more than one) like:
|
||||
//
|
||||
// godotenv.Load("fileone", "filetwo")
|
||||
//
|
||||
// It's important to note that it WILL NOT OVERRIDE an env variable that already exists - consider the .env file to set dev vars or sensible defaults
|
||||
func Load(filenames ...string) error {
|
||||
return load(false, filenames...)
|
||||
}
|
||||
|
||||
func load(overload bool, filenames ...string) error {
|
||||
filenames = filenamesOrDefault(filenames)
|
||||
for _, filename := range filenames {
|
||||
err := loadFile(filename, overload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadWithLookup gets all env vars from the files and/or lookup function and return values as
|
||||
// a map rather than automatically writing values into env
|
||||
func ReadWithLookup(lookupFn LookupFn, filenames ...string) (map[string]string, error) {
|
||||
filenames = filenamesOrDefault(filenames)
|
||||
envMap := make(map[string]string)
|
||||
|
||||
for _, filename := range filenames {
|
||||
individualEnvMap, individualErr := readFile(filename, lookupFn)
|
||||
|
||||
if individualErr != nil {
|
||||
return envMap, individualErr
|
||||
}
|
||||
|
||||
for key, value := range individualEnvMap {
|
||||
if startsWithDigitRegex.MatchString(key) {
|
||||
continue
|
||||
}
|
||||
envMap[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return envMap, nil
|
||||
}
|
||||
|
||||
// Read all env (with same file loading semantics as Load) but return values as
|
||||
// a map rather than automatically writing values into env
|
||||
func Read(filenames ...string) (map[string]string, error) {
|
||||
return ReadWithLookup(nil, filenames...)
|
||||
}
|
||||
|
||||
// UnmarshalBytesWithLookup parses env file from byte slice of chars, returning a map of keys and values.
|
||||
func UnmarshalBytesWithLookup(src []byte, lookupFn LookupFn) (map[string]string, error) {
|
||||
return UnmarshalWithLookup(string(src), lookupFn)
|
||||
}
|
||||
|
||||
// UnmarshalWithLookup parses env file from string, returning a map of keys and values.
|
||||
func UnmarshalWithLookup(src string, lookupFn LookupFn) (map[string]string, error) {
|
||||
out := make(map[string]string)
|
||||
err := newParser().parse(src, out, lookupFn)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func filenamesOrDefault(filenames []string) []string {
|
||||
if len(filenames) == 0 {
|
||||
return []string{".env"}
|
||||
}
|
||||
return filenames
|
||||
}
|
||||
|
||||
func loadFile(filename string, overload bool) error {
|
||||
envMap, err := readFile(filename, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentEnv := map[string]bool{}
|
||||
rawEnv := os.Environ()
|
||||
for _, rawEnvLine := range rawEnv {
|
||||
key := strings.Split(rawEnvLine, "=")[0]
|
||||
currentEnv[key] = true
|
||||
}
|
||||
|
||||
for key, value := range envMap {
|
||||
if !currentEnv[key] || overload {
|
||||
_ = os.Setenv(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readFile(filename string, lookupFn LookupFn) (map[string]string, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
return ParseWithLookup(file, lookupFn)
|
||||
}
|
||||
|
||||
func expandVariables(value string, envMap map[string]string, lookupFn LookupFn) (string, error) {
|
||||
retVal, err := template.Substitute(value, func(k string) (string, bool) {
|
||||
if v, ok := lookupFn(k); ok {
|
||||
return v, true
|
||||
}
|
||||
v, ok := envMap[k]
|
||||
return v, ok
|
||||
})
|
||||
if err != nil {
|
||||
return value, err
|
||||
}
|
||||
return retVal, nil
|
||||
}
|
282
vendor/github.com/compose-spec/compose-go/v2/dotenv/parser.go
generated
vendored
Normal file
282
vendor/github.com/compose-spec/compose-go/v2/dotenv/parser.go
generated
vendored
Normal file
@ -0,0 +1,282 @@
|
||||
package dotenv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
charComment = '#'
|
||||
prefixSingleQuote = '\''
|
||||
prefixDoubleQuote = '"'
|
||||
)
|
||||
|
||||
var (
|
||||
escapeSeqRegex = regexp.MustCompile(`(\\(?:[abcfnrtv$"\\]|0\d{0,3}))`)
|
||||
exportRegex = regexp.MustCompile(`^export\s+`)
|
||||
)
|
||||
|
||||
type parser struct {
|
||||
line int
|
||||
}
|
||||
|
||||
func newParser() *parser {
|
||||
return &parser{
|
||||
line: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) parse(src string, out map[string]string, lookupFn LookupFn) error {
|
||||
cutset := src
|
||||
if lookupFn == nil {
|
||||
lookupFn = noLookupFn
|
||||
}
|
||||
for {
|
||||
cutset = p.getStatementStart(cutset)
|
||||
if cutset == "" {
|
||||
// reached end of file
|
||||
break
|
||||
}
|
||||
|
||||
key, left, inherited, err := p.locateKeyName(cutset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.Contains(key, " ") {
|
||||
return fmt.Errorf("line %d: key cannot contain a space", p.line)
|
||||
}
|
||||
|
||||
if inherited {
|
||||
value, ok := lookupFn(key)
|
||||
if ok {
|
||||
out[key] = value
|
||||
}
|
||||
cutset = left
|
||||
continue
|
||||
}
|
||||
|
||||
value, left, err := p.extractVarValue(left, out, lookupFn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out[key] = value
|
||||
cutset = left
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getStatementPosition returns position of statement begin.
|
||||
//
|
||||
// It skips any comment line or non-whitespace character.
|
||||
func (p *parser) getStatementStart(src string) string {
|
||||
pos := p.indexOfNonSpaceChar(src)
|
||||
if pos == -1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
src = src[pos:]
|
||||
if src[0] != charComment {
|
||||
return src
|
||||
}
|
||||
|
||||
// skip comment section
|
||||
pos = strings.IndexFunc(src, isCharFunc('\n'))
|
||||
if pos == -1 {
|
||||
return ""
|
||||
}
|
||||
return p.getStatementStart(src[pos:])
|
||||
}
|
||||
|
||||
// locateKeyName locates and parses key name and returns rest of slice
|
||||
func (p *parser) locateKeyName(src string) (string, string, bool, error) {
|
||||
var key string
|
||||
var inherited bool
|
||||
// trim "export" and space at beginning
|
||||
if exportRegex.MatchString(src) {
|
||||
// we use a `strings.trim` to preserve the pointer to the same underlying memory.
|
||||
// a regexp replace would copy the string.
|
||||
src = strings.TrimLeftFunc(strings.TrimPrefix(src, "export"), isSpace)
|
||||
}
|
||||
|
||||
// locate key name end and validate it in single loop
|
||||
offset := 0
|
||||
loop:
|
||||
for i, rune := range src {
|
||||
if isSpace(rune) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch rune {
|
||||
case '=', ':', '\n':
|
||||
// library also supports yaml-style value declaration
|
||||
key = string(src[0:i])
|
||||
offset = i + 1
|
||||
inherited = rune == '\n'
|
||||
break loop
|
||||
case '_', '.', '-', '[', ']':
|
||||
default:
|
||||
// variable name should match [A-Za-z0-9_.-]
|
||||
if unicode.IsLetter(rune) || unicode.IsNumber(rune) {
|
||||
continue
|
||||
}
|
||||
|
||||
return "", "", inherited, fmt.Errorf(
|
||||
`line %d: unexpected character %q in variable name %q`,
|
||||
p.line, string(rune), strings.Split(src, "\n")[0])
|
||||
}
|
||||
}
|
||||
|
||||
if src == "" {
|
||||
return "", "", inherited, errors.New("zero length string")
|
||||
}
|
||||
|
||||
// trim whitespace
|
||||
key = strings.TrimRightFunc(key, unicode.IsSpace)
|
||||
cutset := strings.TrimLeftFunc(src[offset:], isSpace)
|
||||
return key, cutset, inherited, nil
|
||||
}
|
||||
|
||||
// extractVarValue extracts variable value and returns rest of slice
|
||||
func (p *parser) extractVarValue(src string, envMap map[string]string, lookupFn LookupFn) (string, string, error) {
|
||||
quote, isQuoted := hasQuotePrefix(src)
|
||||
if !isQuoted {
|
||||
// unquoted value - read until new line
|
||||
value, rest, _ := strings.Cut(src, "\n")
|
||||
p.line++
|
||||
|
||||
// Remove inline comments on unquoted lines
|
||||
value, _, _ = strings.Cut(value, " #")
|
||||
value = strings.TrimRightFunc(value, unicode.IsSpace)
|
||||
retVal, err := expandVariables(string(value), envMap, lookupFn)
|
||||
return retVal, rest, err
|
||||
}
|
||||
|
||||
previousCharIsEscape := false
|
||||
// lookup quoted string terminator
|
||||
var chars []byte
|
||||
for i := 1; i < len(src); i++ {
|
||||
char := src[i]
|
||||
if char == '\n' {
|
||||
p.line++
|
||||
}
|
||||
if char != quote {
|
||||
if !previousCharIsEscape && char == '\\' {
|
||||
previousCharIsEscape = true
|
||||
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(chars)
|
||||
if quote == prefixDoubleQuote {
|
||||
// expand standard shell escape sequences & then interpolate
|
||||
// variables on the result
|
||||
retVal, err := expandVariables(expandEscapes(value), envMap, lookupFn)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
value = retVal
|
||||
}
|
||||
|
||||
return value, src[i+1:], nil
|
||||
}
|
||||
|
||||
// return formatted error if quoted string is not terminated
|
||||
valEndIndex := strings.IndexFunc(src, isCharFunc('\n'))
|
||||
if valEndIndex == -1 {
|
||||
valEndIndex = len(src)
|
||||
}
|
||||
|
||||
return "", "", fmt.Errorf("line %d: unterminated quoted value %s", p.line, src[:valEndIndex])
|
||||
}
|
||||
|
||||
func expandEscapes(str string) string {
|
||||
out := escapeSeqRegex.ReplaceAllStringFunc(str, func(match string) string {
|
||||
if match == `\$` {
|
||||
// `\$` is not a Go escape sequence, the expansion parser uses
|
||||
// the special `$$` syntax
|
||||
// both `FOO=\$bar` and `FOO=$$bar` are valid in an env file and
|
||||
// will result in FOO w/ literal value of "$bar" (no interpolation)
|
||||
return "$$"
|
||||
}
|
||||
|
||||
if strings.HasPrefix(match, `\0`) {
|
||||
// octal escape sequences in Go are not prefixed with `\0`, so
|
||||
// rewrite the prefix, e.g. `\0123` -> `\123` -> literal value "S"
|
||||
match = strings.Replace(match, `\0`, `\`, 1)
|
||||
}
|
||||
|
||||
// use Go to unquote (unescape) the literal
|
||||
// see https://go.dev/ref/spec#Rune_literals
|
||||
//
|
||||
// NOTE: Go supports ADDITIONAL escapes like `\x` & `\u` & `\U`!
|
||||
// These are NOT supported, which is why we use a regex to find
|
||||
// only matches we support and then use `UnquoteChar` instead of a
|
||||
// `Unquote` on the entire value
|
||||
v, _, _, err := strconv.UnquoteChar(match, '"')
|
||||
if err != nil {
|
||||
return match
|
||||
}
|
||||
return string(v)
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
func (p *parser) indexOfNonSpaceChar(src string) int {
|
||||
return strings.IndexFunc(src, func(r rune) bool {
|
||||
if r == '\n' {
|
||||
p.line++
|
||||
}
|
||||
return !unicode.IsSpace(r)
|
||||
})
|
||||
}
|
||||
|
||||
// hasQuotePrefix reports whether charset starts with single or double quote and returns quote character
|
||||
func hasQuotePrefix(src string) (byte, bool) {
|
||||
if src == "" {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
switch quote := src[0]; quote {
|
||||
case prefixDoubleQuote, prefixSingleQuote:
|
||||
return quote, true // isQuoted
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
func isCharFunc(char rune) func(rune) bool {
|
||||
return func(v rune) bool {
|
||||
return v == char
|
||||
}
|
||||
}
|
||||
|
||||
// isSpace reports whether the rune is a space character but not line break character
|
||||
//
|
||||
// this differs from unicode.IsSpace, which also applies line break as space
|
||||
func isSpace(r rune) bool {
|
||||
switch r {
|
||||
case '\t', '\v', '\f', '\r', ' ', 0x85, 0xA0:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
53
vendor/github.com/compose-spec/compose-go/v2/errdefs/errors.go
generated
vendored
Normal file
53
vendor/github.com/compose-spec/compose-go/v2/errdefs/errors.go
generated
vendored
Normal file
@ -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 errdefs
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrNotFound is returned when an object is not found
|
||||
ErrNotFound = errors.New("not found")
|
||||
|
||||
// ErrInvalid is returned when a compose project is invalid
|
||||
ErrInvalid = errors.New("invalid compose project")
|
||||
|
||||
// ErrUnsupported is returned when a compose project uses an unsupported attribute
|
||||
ErrUnsupported = errors.New("unsupported attribute")
|
||||
|
||||
// ErrIncompatible is returned when a compose project uses an incompatible attribute
|
||||
ErrIncompatible = errors.New("incompatible attribute")
|
||||
)
|
||||
|
||||
// IsNotFoundError returns true if the unwrapped error is ErrNotFound
|
||||
func IsNotFoundError(err error) bool {
|
||||
return errors.Is(err, ErrNotFound)
|
||||
}
|
||||
|
||||
// IsInvalidError returns true if the unwrapped error is ErrInvalid
|
||||
func IsInvalidError(err error) bool {
|
||||
return errors.Is(err, ErrInvalid)
|
||||
}
|
||||
|
||||
// IsUnsupportedError returns true if the unwrapped error is ErrUnsupported
|
||||
func IsUnsupportedError(err error) bool {
|
||||
return errors.Is(err, ErrUnsupported)
|
||||
}
|
||||
|
||||
// IsUnsupportedError returns true if the unwrapped error is ErrIncompatible
|
||||
func IsIncompatibleError(err error) bool {
|
||||
return errors.Is(err, ErrIncompatible)
|
||||
}
|
184
vendor/github.com/compose-spec/compose-go/v2/format/volume.go
generated
vendored
Normal file
184
vendor/github.com/compose-spec/compose-go/v2/format/volume.go
generated
vendored
Normal file
@ -0,0 +1,184 @@
|
||||
/*
|
||||
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 format
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
)
|
||||
|
||||
const endOfSpec = rune(0)
|
||||
|
||||
// ParseVolume parses a volume spec without any knowledge of the target platform
|
||||
func ParseVolume(spec string) (types.ServiceVolumeConfig, error) {
|
||||
volume := types.ServiceVolumeConfig{}
|
||||
|
||||
switch len(spec) {
|
||||
case 0:
|
||||
return volume, errors.New("invalid empty volume spec")
|
||||
case 1, 2:
|
||||
volume.Target = spec
|
||||
volume.Type = types.VolumeTypeVolume
|
||||
return volume, nil
|
||||
}
|
||||
|
||||
var buffer []rune
|
||||
for _, char := range spec + string(endOfSpec) {
|
||||
switch {
|
||||
case isWindowsDrive(buffer, char):
|
||||
buffer = append(buffer, char)
|
||||
case char == ':' || char == endOfSpec:
|
||||
if err := populateFieldFromBuffer(char, buffer, &volume); err != nil {
|
||||
populateType(&volume)
|
||||
return volume, fmt.Errorf("invalid spec: %s: %w", spec, err)
|
||||
}
|
||||
buffer = nil
|
||||
default:
|
||||
buffer = append(buffer, char)
|
||||
}
|
||||
}
|
||||
|
||||
populateType(&volume)
|
||||
return volume, nil
|
||||
}
|
||||
|
||||
func isWindowsDrive(buffer []rune, char rune) bool {
|
||||
return char == ':' && len(buffer) == 1 && unicode.IsLetter(buffer[0])
|
||||
}
|
||||
|
||||
func populateFieldFromBuffer(char rune, buffer []rune, volume *types.ServiceVolumeConfig) error {
|
||||
strBuffer := string(buffer)
|
||||
switch {
|
||||
case len(buffer) == 0:
|
||||
return errors.New("empty section between colons")
|
||||
// Anonymous volume
|
||||
case volume.Source == "" && char == endOfSpec:
|
||||
volume.Target = strBuffer
|
||||
return nil
|
||||
case volume.Source == "":
|
||||
volume.Source = strBuffer
|
||||
return nil
|
||||
case volume.Target == "":
|
||||
volume.Target = strBuffer
|
||||
return nil
|
||||
case char == ':':
|
||||
return errors.New("too many colons")
|
||||
}
|
||||
for _, option := range strings.Split(strBuffer, ",") {
|
||||
switch option {
|
||||
case "ro":
|
||||
volume.ReadOnly = true
|
||||
case "rw":
|
||||
volume.ReadOnly = false
|
||||
case "nocopy":
|
||||
volume.Volume = &types.ServiceVolumeVolume{NoCopy: true}
|
||||
default:
|
||||
if isBindOption(option) {
|
||||
setBindOption(volume, option)
|
||||
}
|
||||
// ignore unknown options
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var Propagations = []string{
|
||||
types.PropagationRPrivate,
|
||||
types.PropagationPrivate,
|
||||
types.PropagationRShared,
|
||||
types.PropagationShared,
|
||||
types.PropagationRSlave,
|
||||
types.PropagationSlave,
|
||||
}
|
||||
|
||||
type setBindOptionFunc func(bind *types.ServiceVolumeBind, option string)
|
||||
|
||||
var bindOptions = map[string]setBindOptionFunc{
|
||||
types.PropagationRPrivate: setBindPropagation,
|
||||
types.PropagationPrivate: setBindPropagation,
|
||||
types.PropagationRShared: setBindPropagation,
|
||||
types.PropagationShared: setBindPropagation,
|
||||
types.PropagationRSlave: setBindPropagation,
|
||||
types.PropagationSlave: setBindPropagation,
|
||||
types.SELinuxShared: setBindSELinux,
|
||||
types.SELinuxPrivate: setBindSELinux,
|
||||
}
|
||||
|
||||
func setBindPropagation(bind *types.ServiceVolumeBind, option string) {
|
||||
bind.Propagation = option
|
||||
}
|
||||
|
||||
func setBindSELinux(bind *types.ServiceVolumeBind, option string) {
|
||||
bind.SELinux = option
|
||||
}
|
||||
|
||||
func isBindOption(option string) bool {
|
||||
_, ok := bindOptions[option]
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
func setBindOption(volume *types.ServiceVolumeConfig, option string) {
|
||||
if volume.Bind == nil {
|
||||
volume.Bind = &types.ServiceVolumeBind{}
|
||||
}
|
||||
|
||||
bindOptions[option](volume.Bind, option)
|
||||
}
|
||||
|
||||
func populateType(volume *types.ServiceVolumeConfig) {
|
||||
if isFilePath(volume.Source) {
|
||||
volume.Type = types.VolumeTypeBind
|
||||
if volume.Bind == nil {
|
||||
volume.Bind = &types.ServiceVolumeBind{}
|
||||
}
|
||||
// For backward compatibility with docker-compose legacy, using short notation involves
|
||||
// bind will create missing host path
|
||||
volume.Bind.CreateHostPath = true
|
||||
} else {
|
||||
volume.Type = types.VolumeTypeVolume
|
||||
if volume.Volume == nil {
|
||||
volume.Volume = &types.ServiceVolumeVolume{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isFilePath(source string) bool {
|
||||
if source == "" {
|
||||
return false
|
||||
}
|
||||
switch source[0] {
|
||||
case '.', '/', '~':
|
||||
return true
|
||||
}
|
||||
|
||||
// windows named pipes
|
||||
if strings.HasPrefix(source, `\\`) {
|
||||
return true
|
||||
}
|
||||
|
||||
first, nextIndex := utf8.DecodeRuneInString(source)
|
||||
if len(source) <= nextIndex {
|
||||
return false
|
||||
}
|
||||
return isWindowsDrive([]rune{first}, rune(source[nextIndex]))
|
||||
}
|
111
vendor/github.com/compose-spec/compose-go/v2/graph/graph.go
generated
vendored
Normal file
111
vendor/github.com/compose-spec/compose-go/v2/graph/graph.go
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
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/utils"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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", strings.Join(path[i:], " -> "))
|
||||
}
|
||||
ch := v.children[name]
|
||||
err := searchCycle(append(path, name), ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
80
vendor/github.com/compose-spec/compose-go/v2/graph/services.go
generated
vendored
Normal file
80
vendor/github.com/compose-spec/compose-go/v2/graph/services.go
generated
vendored
Normal file
@ -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
|
||||
}
|
211
vendor/github.com/compose-spec/compose-go/v2/graph/traversal.go
generated
vendored
Normal file
211
vendor/github.com/compose-spec/compose-go/v2/graph/traversal.go
generated
vendored
Normal file
@ -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
|
||||
}
|
137
vendor/github.com/compose-spec/compose-go/v2/interpolation/interpolation.go
generated
vendored
Normal file
137
vendor/github.com/compose-spec/compose-go/v2/interpolation/interpolation.go
generated
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
/*
|
||||
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 interpolation
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/template"
|
||||
"github.com/compose-spec/compose-go/v2/tree"
|
||||
)
|
||||
|
||||
// Options supported by Interpolate
|
||||
type Options struct {
|
||||
// LookupValue from a key
|
||||
LookupValue LookupValue
|
||||
// TypeCastMapping maps key paths to functions to cast to a type
|
||||
TypeCastMapping map[tree.Path]Cast
|
||||
// Substitution function to use
|
||||
Substitute func(string, template.Mapping) (string, error)
|
||||
}
|
||||
|
||||
// LookupValue is a function which maps from variable names to values.
|
||||
// Returns the value as a string and a bool indicating whether
|
||||
// the value is present, to distinguish between an empty string
|
||||
// and the absence of a value.
|
||||
type LookupValue func(key string) (string, bool)
|
||||
|
||||
// Cast a value to a new type, or return an error if the value can't be cast
|
||||
type Cast func(value string) (interface{}, error)
|
||||
|
||||
// Interpolate replaces variables in a string with the values from a mapping
|
||||
func Interpolate(config map[string]interface{}, opts Options) (map[string]interface{}, error) {
|
||||
if opts.LookupValue == nil {
|
||||
opts.LookupValue = os.LookupEnv
|
||||
}
|
||||
if opts.TypeCastMapping == nil {
|
||||
opts.TypeCastMapping = make(map[tree.Path]Cast)
|
||||
}
|
||||
if opts.Substitute == nil {
|
||||
opts.Substitute = template.Substitute
|
||||
}
|
||||
|
||||
out := map[string]interface{}{}
|
||||
|
||||
for key, value := range config {
|
||||
interpolatedValue, err := recursiveInterpolate(value, tree.NewPath(key), opts)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
out[key] = interpolatedValue
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func recursiveInterpolate(value interface{}, path tree.Path, opts Options) (interface{}, error) {
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
newValue, err := opts.Substitute(value, template.Mapping(opts.LookupValue))
|
||||
if err != nil {
|
||||
return value, newPathError(path, err)
|
||||
}
|
||||
caster, ok := opts.getCasterForPath(path)
|
||||
if !ok {
|
||||
return newValue, nil
|
||||
}
|
||||
casted, err := caster(newValue)
|
||||
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{}{}
|
||||
for key, elem := range value {
|
||||
interpolatedElem, err := recursiveInterpolate(elem, path.Next(key), opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out[key] = interpolatedElem
|
||||
}
|
||||
return out, nil
|
||||
|
||||
case []interface{}:
|
||||
out := make([]interface{}, len(value))
|
||||
for i, elem := range value {
|
||||
interpolatedElem, err := recursiveInterpolate(elem, path.Next(tree.PathMatchList), opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out[i] = interpolatedElem
|
||||
}
|
||||
return out, nil
|
||||
|
||||
default:
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
|
||||
func newPathError(path tree.Path, err error) error {
|
||||
var ite *template.InvalidTemplateError
|
||||
switch {
|
||||
case err == nil:
|
||||
return nil
|
||||
case errors.As(err, &ite):
|
||||
return fmt.Errorf(
|
||||
"invalid interpolation format for %s.\nYou may need to escape any $ with another $.\n%s",
|
||||
path, ite.Template)
|
||||
default:
|
||||
return fmt.Errorf("error while interpolating %s: %w", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (o Options) getCasterForPath(path tree.Path) (Cast, bool) {
|
||||
for pattern, caster := range o.TypeCastMapping {
|
||||
if path.Matches(pattern) {
|
||||
return caster, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
10
vendor/github.com/compose-spec/compose-go/v2/loader/example1.env
generated
vendored
Normal file
10
vendor/github.com/compose-spec/compose-go/v2/loader/example1.env
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
# passed through
|
||||
FOO=foo_from_env_file
|
||||
ENV.WITH.DOT=ok
|
||||
ENV_WITH_UNDERSCORE=ok
|
||||
|
||||
# overridden in example2.env
|
||||
BAR=bar_from_env_file
|
||||
|
||||
# overridden in full-example.yml
|
||||
BAZ=baz_from_env_file
|
4
vendor/github.com/compose-spec/compose-go/v2/loader/example2.env
generated
vendored
Normal file
4
vendor/github.com/compose-spec/compose-go/v2/loader/example2.env
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
BAR=bar_from_env_file_2
|
||||
|
||||
# overridden in configDetails.Environment
|
||||
QUX=quz_from_env_file_2
|
176
vendor/github.com/compose-spec/compose-go/v2/loader/extends.go
generated
vendored
Normal file
176
vendor/github.com/compose-spec/compose-go/v2/loader/extends.go
generated
vendored
Normal file
@ -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 (
|
||||
"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/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)
|
||||
tracker, err := tracker.Add(filename, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
ref string
|
||||
file any
|
||||
)
|
||||
switch v := extends.(type) {
|
||||
case map[string]any:
|
||||
ref = v["service"].(string)
|
||||
file = v["file"]
|
||||
case string:
|
||||
ref = v
|
||||
}
|
||||
|
||||
var base any
|
||||
if file != nil {
|
||||
path := file.(string)
|
||||
services, err = getExtendsBaseFromFile(ctx, ref, path, opts, tracker)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
_, ok := services[ref]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot extend service %q in %s: service not found", name, filename)
|
||||
}
|
||||
}
|
||||
// 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")
|
||||
return merged, nil
|
||||
}
|
||||
|
||||
func getExtendsBaseFromFile(ctx context.Context, name string, path string, opts *Options, ct *cycleTracker) (map[string]any, error) {
|
||||
for _, loader := range opts.ResourceLoaders {
|
||||
if !loader.Accept(path) {
|
||||
continue
|
||||
}
|
||||
local, err := loader.Load(ctx, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
localdir := filepath.Dir(local)
|
||||
relworkingdir := loader.Dir(path)
|
||||
|
||||
extendsOpts := opts.clone()
|
||||
// replace localResourceLoader with a new flavour, using extended file base path
|
||||
extendsOpts.ResourceLoaders = append(opts.RemoteResourceLoaders(), localResourceLoader{
|
||||
WorkingDir: localdir,
|
||||
})
|
||||
extendsOpts.ResolvePaths = true
|
||||
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
|
||||
source, err := loadYamlModel(ctx, types.ConfigDetails{
|
||||
WorkingDir: relworkingdir,
|
||||
ConfigFiles: []types.ConfigFile{
|
||||
{Filename: local},
|
||||
},
|
||||
}, extendsOpts, ct, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
services := source["services"].(map[string]any)
|
||||
_, ok := services[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot extend service %q in %s: service not found", name, path)
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
return nil, fmt.Errorf("cannot read %s", path)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
36
vendor/github.com/compose-spec/compose-go/v2/loader/fix.go
generated
vendored
Normal file
36
vendor/github.com/compose-spec/compose-go/v2/loader/fix.go
generated
vendored
Normal file
@ -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
|
||||
}
|
453
vendor/github.com/compose-spec/compose-go/v2/loader/full-example.yml
generated
vendored
Normal file
453
vendor/github.com/compose-spec/compose-go/v2/loader/full-example.yml
generated
vendored
Normal file
@ -0,0 +1,453 @@
|
||||
name: full_example_project_name
|
||||
services:
|
||||
|
||||
bar:
|
||||
build:
|
||||
dockerfile_inline: |
|
||||
FROM alpine
|
||||
RUN echo "hello" > /world.txt
|
||||
|
||||
foo:
|
||||
annotations:
|
||||
- com.example.foo=bar
|
||||
build:
|
||||
context: ./dir
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
foo: bar
|
||||
ssh:
|
||||
- default
|
||||
target: foo
|
||||
network: foo
|
||||
cache_from:
|
||||
- foo
|
||||
- bar
|
||||
labels: [FOO=BAR]
|
||||
additional_contexts:
|
||||
foo: ./bar
|
||||
secrets:
|
||||
- secret1
|
||||
- source: secret2
|
||||
target: my_secret
|
||||
uid: '103'
|
||||
gid: '103'
|
||||
mode: 0440
|
||||
tags:
|
||||
- foo:v1.0.0
|
||||
- docker.io/username/foo:my-other-tag
|
||||
- ${COMPOSE_PROJECT_NAME}:1.0.0
|
||||
platforms:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
|
||||
|
||||
cap_add:
|
||||
- ALL
|
||||
|
||||
cap_drop:
|
||||
- NET_ADMIN
|
||||
- SYS_ADMIN
|
||||
|
||||
cgroup_parent: m-executor-abcd
|
||||
|
||||
# String or list
|
||||
command: bundle exec thin -p 3000
|
||||
# command: ["bundle", "exec", "thin", "-p", "3000"]
|
||||
|
||||
configs:
|
||||
- config1
|
||||
- source: config2
|
||||
target: /my_config
|
||||
uid: '103'
|
||||
gid: '103'
|
||||
mode: 0440
|
||||
|
||||
container_name: my-web-container
|
||||
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 6
|
||||
labels: [FOO=BAR]
|
||||
rollback_config:
|
||||
parallelism: 3
|
||||
delay: 10s
|
||||
failure_action: continue
|
||||
monitor: 60s
|
||||
max_failure_ratio: 0.3
|
||||
order: start-first
|
||||
update_config:
|
||||
parallelism: 3
|
||||
delay: 10s
|
||||
failure_action: continue
|
||||
monitor: 60s
|
||||
max_failure_ratio: 0.3
|
||||
order: start-first
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.001'
|
||||
memory: 50M
|
||||
reservations:
|
||||
cpus: '0.0001'
|
||||
memory: 20M
|
||||
generic_resources:
|
||||
- discrete_resource_spec:
|
||||
kind: 'gpu'
|
||||
value: 2
|
||||
- discrete_resource_spec:
|
||||
kind: 'ssd'
|
||||
value: 1
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
delay: 5s
|
||||
max_attempts: 3
|
||||
window: 120s
|
||||
placement:
|
||||
constraints: [node=foo]
|
||||
max_replicas_per_node: 5
|
||||
preferences:
|
||||
- spread: node.labels.az
|
||||
endpoint_mode: dnsrr
|
||||
|
||||
device_cgroup_rules:
|
||||
- "c 1:3 mr"
|
||||
- "a 7:* rmw"
|
||||
|
||||
devices:
|
||||
- "/dev/ttyUSB0:/dev/ttyUSB0"
|
||||
|
||||
# String or list
|
||||
# dns: 8.8.8.8
|
||||
dns:
|
||||
- 8.8.8.8
|
||||
- 9.9.9.9
|
||||
|
||||
# String or list
|
||||
# dns_search: example.com
|
||||
dns_search:
|
||||
- dc1.example.com
|
||||
- dc2.example.com
|
||||
|
||||
domainname: foo.com
|
||||
|
||||
# String or list
|
||||
# entrypoint: /code/entrypoint.sh -p 3000
|
||||
entrypoint: ["/code/entrypoint.sh", "-p", "3000"]
|
||||
|
||||
# String or list
|
||||
# env_file: .env
|
||||
env_file:
|
||||
- ./example1.env
|
||||
- path: ./example2.env
|
||||
required: false
|
||||
|
||||
# Mapping or list
|
||||
# Mapping values can be strings, numbers or null
|
||||
# Booleans are not allowed - must be quoted
|
||||
environment:
|
||||
BAZ: baz_from_service_def
|
||||
QUX:
|
||||
# environment:
|
||||
# - RACK_ENV=development
|
||||
# - SHOW=true
|
||||
# - SESSION_SECRET
|
||||
|
||||
# Items can be strings or numbers
|
||||
expose:
|
||||
- "3000"
|
||||
- 8000
|
||||
|
||||
external_links:
|
||||
- redis_1
|
||||
- project_db_1:mysql
|
||||
- project_db_1:postgresql
|
||||
|
||||
# Mapping or list
|
||||
# Mapping values must be strings
|
||||
# extra_hosts:
|
||||
# somehost: "162.242.195.82"
|
||||
# otherhost: "50.31.209.229"
|
||||
extra_hosts:
|
||||
- "otherhost:50.31.209.229"
|
||||
- "somehost:162.242.195.82"
|
||||
|
||||
hostname: foo
|
||||
|
||||
healthcheck:
|
||||
test: echo "hello world"
|
||||
interval: 10s
|
||||
timeout: 1s
|
||||
retries: 5
|
||||
start_period: 15s
|
||||
start_interval: 5s
|
||||
|
||||
# Any valid image reference - repo, tag, id, sha
|
||||
image: redis
|
||||
# image: ubuntu:14.04
|
||||
# image: tutum/influxdb
|
||||
# image: example-registry.com:4000/postgresql
|
||||
# image: a4bc65fd
|
||||
# image: busybox@sha256:38a203e1986cf79639cfb9b2e1d6e773de84002feea2d4eb006b52004ee8502d
|
||||
|
||||
ipc: host
|
||||
|
||||
uts: host
|
||||
|
||||
# Mapping or list
|
||||
# Mapping values can be strings, numbers or null
|
||||
labels:
|
||||
com.example.description: "Accounting webapp"
|
||||
com.example.number: 42
|
||||
com.example.empty-label:
|
||||
# labels:
|
||||
# - "com.example.description=Accounting webapp"
|
||||
# - "com.example.number=42"
|
||||
# - "com.example.empty-label"
|
||||
|
||||
links:
|
||||
- db
|
||||
- db:database
|
||||
- redis
|
||||
|
||||
logging:
|
||||
driver: syslog
|
||||
options:
|
||||
syslog-address: "tcp://192.168.0.42:123"
|
||||
|
||||
mac_address: 02:42:ac:11:65:43
|
||||
|
||||
# network_mode: "bridge"
|
||||
# network_mode: "host"
|
||||
# network_mode: "none"
|
||||
# Use the network mode of an arbitrary container from another service
|
||||
# network_mode: "service:db"
|
||||
# Use the network mode of another container, specified by name or id
|
||||
# network_mode: "container:some-container"
|
||||
network_mode: "container:0cfeab0f748b9a743dc3da582046357c6ef497631c1a016d28d2bf9b4f899f7b"
|
||||
|
||||
networks:
|
||||
some-network:
|
||||
aliases:
|
||||
- alias1
|
||||
- alias3
|
||||
other-network:
|
||||
ipv4_address: 172.16.238.10
|
||||
ipv6_address: 2001:3984:3989::10
|
||||
mac_address: 02:42:72:98:65:08
|
||||
other-other-network:
|
||||
|
||||
pid: "host"
|
||||
|
||||
ports:
|
||||
- 3000
|
||||
- "3001-3005"
|
||||
- "8000:8000"
|
||||
- "9090-9091:8080-8081"
|
||||
- "49100:22"
|
||||
- "127.0.0.1:8001:8001"
|
||||
- "127.0.0.1:5000-5010:5000-5010"
|
||||
|
||||
privileged: true
|
||||
|
||||
read_only: true
|
||||
|
||||
restart: always
|
||||
|
||||
secrets:
|
||||
- secret1
|
||||
- source: secret2
|
||||
target: my_secret
|
||||
uid: '103'
|
||||
gid: '103'
|
||||
mode: 0440
|
||||
|
||||
security_opt:
|
||||
- label=level:s0:c100,c200
|
||||
- label=type:svirt_apache_t
|
||||
|
||||
stdin_open: true
|
||||
|
||||
stop_grace_period: 20s
|
||||
|
||||
stop_signal: SIGUSR1
|
||||
storage_opt:
|
||||
size: "20G"
|
||||
sysctls:
|
||||
net.core.somaxconn: 1024
|
||||
net.ipv4.tcp_syncookies: 0
|
||||
|
||||
# String or list
|
||||
# tmpfs: /run
|
||||
tmpfs:
|
||||
- /run
|
||||
- /tmp
|
||||
|
||||
tty: true
|
||||
|
||||
ulimits:
|
||||
# Single number or mapping with soft + hard limits
|
||||
nproc: 65535
|
||||
nofile:
|
||||
soft: 20000
|
||||
hard: 40000
|
||||
|
||||
user: someone
|
||||
|
||||
volumes:
|
||||
# Just specify a path and let the Engine create a volume
|
||||
- /var/lib/anonymous
|
||||
# Specify an absolute path mapping
|
||||
- /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/volume
|
||||
- type: bind
|
||||
source: ./opt
|
||||
target: /opt/cached
|
||||
consistency: cached
|
||||
- type: tmpfs
|
||||
target: /opt/tmpfs
|
||||
tmpfs:
|
||||
size: 10000
|
||||
|
||||
working_dir: /code
|
||||
x-bar: baz
|
||||
x-foo: bar
|
||||
|
||||
networks:
|
||||
# Entries can be null, which specifies simply that a network
|
||||
# called "{project name}_some-network" should be created and
|
||||
# use the default driver
|
||||
some-network:
|
||||
|
||||
other-network:
|
||||
driver: overlay
|
||||
|
||||
driver_opts:
|
||||
# Values can be strings or numbers
|
||||
foo: "bar"
|
||||
baz: 1
|
||||
|
||||
ipam:
|
||||
driver: overlay
|
||||
# driver_opts:
|
||||
# # Values can be strings or numbers
|
||||
# com.docker.network.enable_ipv6: "true"
|
||||
# com.docker.network.numeric_value: 1
|
||||
config:
|
||||
- subnet: 172.28.0.0/16
|
||||
ip_range: 172.28.5.0/24
|
||||
gateway: 172.28.5.254
|
||||
aux_addresses:
|
||||
host1: 172.28.1.5
|
||||
host2: 172.28.1.6
|
||||
host3: 172.28.1.7
|
||||
- subnet: 2001:3984:3989::/64
|
||||
gateway: 2001:3984:3989::1
|
||||
|
||||
labels:
|
||||
foo: bar
|
||||
|
||||
external-network:
|
||||
# Specifies that a pre-existing network called "external-network"
|
||||
# can be referred to within this file as "external-network"
|
||||
external: true
|
||||
|
||||
other-external-network:
|
||||
# Specifies that a pre-existing network called "my-cool-network"
|
||||
# can be referred to within this file as "other-external-network"
|
||||
external:
|
||||
name: my-cool-network
|
||||
x-bar: baz
|
||||
x-foo: bar
|
||||
|
||||
volumes:
|
||||
# Entries can be null, which specifies simply that a volume
|
||||
# called "{project name}_some-volume" should be created and
|
||||
# use the default driver
|
||||
some-volume:
|
||||
|
||||
other-volume:
|
||||
driver: flocker
|
||||
|
||||
driver_opts:
|
||||
# Values can be strings or numbers
|
||||
foo: "bar"
|
||||
baz: 1
|
||||
labels:
|
||||
foo: bar
|
||||
|
||||
another-volume:
|
||||
name: "user_specified_name"
|
||||
driver: vsphere
|
||||
|
||||
driver_opts:
|
||||
# Values can be strings or numbers
|
||||
foo: "bar"
|
||||
baz: 1
|
||||
|
||||
external-volume:
|
||||
# Specifies that a pre-existing volume called "external-volume"
|
||||
# can be referred to within this file as "external-volume"
|
||||
external: true
|
||||
|
||||
other-external-volume:
|
||||
# Specifies that a pre-existing volume called "my-cool-volume"
|
||||
# can be referred to within this file as "other-external-volume"
|
||||
# This example uses the deprecated "volume.external.name" (replaced by "volume.name")
|
||||
external:
|
||||
name: my-cool-volume
|
||||
|
||||
external-volume3:
|
||||
# Specifies that a pre-existing volume called "this-is-volume3"
|
||||
# can be referred to within this file as "external-volume3"
|
||||
name: this-is-volume3
|
||||
external: true
|
||||
x-bar: baz
|
||||
x-foo: bar
|
||||
|
||||
configs:
|
||||
config1:
|
||||
file: ./config_data
|
||||
labels:
|
||||
foo: bar
|
||||
config2:
|
||||
external:
|
||||
name: my_config
|
||||
config3:
|
||||
external: true
|
||||
config4:
|
||||
name: foo
|
||||
file: ~/config_data
|
||||
x-bar: baz
|
||||
x-foo: bar
|
||||
|
||||
secrets:
|
||||
secret1:
|
||||
file: ./secret_data
|
||||
labels:
|
||||
foo: bar
|
||||
secret2:
|
||||
external:
|
||||
name: my_secret
|
||||
secret3:
|
||||
external: true
|
||||
secret4:
|
||||
name: bar
|
||||
environment: BAR
|
||||
x-bar: baz
|
||||
x-foo: bar
|
||||
secret5:
|
||||
file: /abs/secret_data
|
||||
x-bar: baz
|
||||
x-foo: bar
|
||||
x-nested:
|
||||
bar: baz
|
||||
foo: bar
|
155
vendor/github.com/compose-spec/compose-go/v2/loader/include.go
generated
vendored
Normal file
155
vendor/github.com/compose-spec/compose-go/v2/loader/include.go
generated
vendored
Normal file
@ -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 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 require config from raw yaml
|
||||
func loadIncludeConfig(source any) ([]types.IncludeConfig, error) {
|
||||
if source == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var requires []types.IncludeConfig
|
||||
err := Transform(source, &requires)
|
||||
return requires, err
|
||||
}
|
||||
|
||||
func ApplyInclude(ctx context.Context, configDetails types.ConfigDetails, model map[string]any, options *Options, included []string) error {
|
||||
includeConfig, err := loadIncludeConfig(model["include"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, r := range includeConfig {
|
||||
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 err
|
||||
}
|
||||
p = path
|
||||
break
|
||||
}
|
||||
}
|
||||
r.Path[i] = absPath(configDetails.WorkingDir, p)
|
||||
}
|
||||
|
||||
mainFile := r.Path[0]
|
||||
for _, f := range included {
|
||||
if f == mainFile {
|
||||
included = append(included, mainFile)
|
||||
return fmt.Errorf("include cycle detected:\n%s\n include %s", included[0], strings.Join(included[1:], "\n include "))
|
||||
}
|
||||
}
|
||||
|
||||
if r.ProjectDirectory == "" {
|
||||
r.ProjectDirectory = filepath.Dir(mainFile)
|
||||
}
|
||||
|
||||
loadOptions := options.clone()
|
||||
loadOptions.ResolvePaths = true
|
||||
loadOptions.SkipNormalization = true
|
||||
loadOptions.SkipConsistencyCheck = true
|
||||
|
||||
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}
|
||||
}
|
||||
}
|
||||
|
||||
envFromFile, err := dotenv.GetEnvFromFile(configDetails.Environment, r.EnvFile)
|
||||
if err != nil {
|
||||
return 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 := 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
|
||||
}
|
117
vendor/github.com/compose-spec/compose-go/v2/loader/interpolate.go
generated
vendored
Normal file
117
vendor/github.com/compose-spec/compose-go/v2/loader/interpolate.go
generated
vendored
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
interp "github.com/compose-spec/compose-go/v2/interpolation"
|
||||
"github.com/compose-spec/compose-go/v2/tree"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var interpolateTypeCastMapping = map[tree.Path]interp.Cast{
|
||||
servicePath("configs", tree.PathMatchList, "mode"): toInt,
|
||||
servicePath("cpu_count"): toInt64,
|
||||
servicePath("cpu_percent"): toFloat,
|
||||
servicePath("cpu_period"): toInt64,
|
||||
servicePath("cpu_quota"): toInt64,
|
||||
servicePath("cpu_rt_period"): toInt64,
|
||||
servicePath("cpu_rt_runtime"): toInt64,
|
||||
servicePath("cpus"): toFloat32,
|
||||
servicePath("cpu_shares"): toInt64,
|
||||
servicePath("init"): toBoolean,
|
||||
servicePath("deploy", "replicas"): toInt,
|
||||
servicePath("deploy", "update_config", "parallelism"): toInt,
|
||||
servicePath("deploy", "update_config", "max_failure_ratio"): toFloat,
|
||||
servicePath("deploy", "rollback_config", "parallelism"): toInt,
|
||||
servicePath("deploy", "rollback_config", "max_failure_ratio"): toFloat,
|
||||
servicePath("deploy", "restart_policy", "max_attempts"): toInt,
|
||||
servicePath("deploy", "placement", "max_replicas_per_node"): toInt,
|
||||
servicePath("healthcheck", "retries"): toInt,
|
||||
servicePath("healthcheck", "disable"): toBoolean,
|
||||
servicePath("oom_kill_disable"): toBoolean,
|
||||
servicePath("oom_score_adj"): toInt64,
|
||||
servicePath("pids_limit"): toInt64,
|
||||
servicePath("ports", tree.PathMatchList, "target"): toInt,
|
||||
servicePath("privileged"): toBoolean,
|
||||
servicePath("read_only"): toBoolean,
|
||||
servicePath("scale"): toInt,
|
||||
servicePath("secrets", tree.PathMatchList, "mode"): toInt,
|
||||
servicePath("stdin_open"): toBoolean,
|
||||
servicePath("tty"): toBoolean,
|
||||
servicePath("ulimits", tree.PathMatchAll): toInt,
|
||||
servicePath("ulimits", tree.PathMatchAll, "hard"): toInt,
|
||||
servicePath("ulimits", tree.PathMatchAll, "soft"): toInt,
|
||||
servicePath("volumes", tree.PathMatchList, "read_only"): toBoolean,
|
||||
servicePath("volumes", tree.PathMatchList, "volume", "nocopy"): toBoolean,
|
||||
iPath("networks", tree.PathMatchAll, "external"): toBoolean,
|
||||
iPath("networks", tree.PathMatchAll, "internal"): toBoolean,
|
||||
iPath("networks", tree.PathMatchAll, "attachable"): toBoolean,
|
||||
iPath("networks", tree.PathMatchAll, "enable_ipv6"): toBoolean,
|
||||
iPath("volumes", tree.PathMatchAll, "external"): toBoolean,
|
||||
iPath("secrets", tree.PathMatchAll, "external"): toBoolean,
|
||||
iPath("configs", tree.PathMatchAll, "external"): toBoolean,
|
||||
}
|
||||
|
||||
func iPath(parts ...string) tree.Path {
|
||||
return tree.NewPath(parts...)
|
||||
}
|
||||
|
||||
func servicePath(parts ...string) tree.Path {
|
||||
return iPath(append([]string{"services", tree.PathMatchAll}, parts...)...)
|
||||
}
|
||||
|
||||
func toInt(value string) (interface{}, error) {
|
||||
return strconv.Atoi(value)
|
||||
}
|
||||
|
||||
func toInt64(value string) (interface{}, error) {
|
||||
return strconv.ParseInt(value, 10, 64)
|
||||
}
|
||||
|
||||
func toFloat(value string) (interface{}, error) {
|
||||
return strconv.ParseFloat(value, 64)
|
||||
}
|
||||
|
||||
func toFloat32(value string) (interface{}, error) {
|
||||
f, err := strconv.ParseFloat(value, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return float32(f), nil
|
||||
}
|
||||
|
||||
// should match http://yaml.org/type/bool.html
|
||||
func toBoolean(value string) (interface{}, error) {
|
||||
switch strings.ToLower(value) {
|
||||
case "true":
|
||||
return true, nil
|
||||
case "false":
|
||||
return false, nil
|
||||
case "y", "yes", "on":
|
||||
logrus.Warnf("%q for boolean is not supported by YAML 1.2, please use `true`", value)
|
||||
return true, nil
|
||||
case "n", "no", "off":
|
||||
logrus.Warnf("%q for boolean is not supported by YAML 1.2, please use `false`", value)
|
||||
return false, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid boolean: %s", value)
|
||||
}
|
||||
}
|
742
vendor/github.com/compose-spec/compose-go/v2/loader/loader.go
generated
vendored
Normal file
742
vendor/github.com/compose-spec/compose-go/v2/loader/loader.go
generated
vendored
Normal file
@ -0,0 +1,742 @@
|
||||
/*
|
||||
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/mitchellh/mapstructure"
|
||||
"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 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
|
||||
// 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)
|
||||
// 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(path string) string {
|
||||
path = l.abs(filepath.Dir(path))
|
||||
rel, err := filepath.Rel(l.WorkingDir, path)
|
||||
if err != nil {
|
||||
return path
|
||||
}
|
||||
return rel
|
||||
}
|
||||
|
||||
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) (*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
|
||||
func LoadWithContext(ctx context.Context, configDetails types.ConfigDetails, options ...func(*Options)) (*types.Project, error) {
|
||||
if len(configDetails.ConfigFiles) < 1 {
|
||||
return nil, errors.New("No files specified")
|
||||
}
|
||||
|
||||
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})
|
||||
|
||||
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 loadYamlModel(ctx context.Context, config types.ConfigDetails, opts *Options, ct *cycleTracker, included []string) (map[string]interface{}, error) {
|
||||
var (
|
||||
dict = map[string]interface{}{}
|
||||
err error
|
||||
)
|
||||
for _, file := range config.ConfigFiles {
|
||||
fctx := context.WithValue(ctx, consts.ComposeFileKey{}, file.Filename)
|
||||
if len(file.Content) == 0 && file.Config == nil {
|
||||
content, err := os.ReadFile(file.Filename)
|
||||
if err != nil {
|
||||
return 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(fctx, cfg, opts, ct, processors...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, processor := range processors {
|
||||
if err := processor.Apply(dict); 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)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if file.Config == nil {
|
||||
r := bytes.NewReader(file.Content)
|
||||
decoder := yaml.NewDecoder(r)
|
||||
for {
|
||||
var raw interface{}
|
||||
processor := &ResetProcessor{target: &raw}
|
||||
err := decoder.Decode(processor)
|
||||
if err != nil && errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := processRawYaml(raw, processor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err := processRawYaml(file.Config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dict, err = transform.Canonical(dict)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !opts.SkipInclude {
|
||||
included = append(included, config.ConfigFiles[0].Filename)
|
||||
err = ApplyInclude(ctx, config, dict, opts, included)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
return dict, nil
|
||||
}
|
||||
|
||||
func load(ctx context.Context, configDetails types.ConfigDetails, opts *Options, loaded []string) (*types.Project, 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)
|
||||
|
||||
includeRefs := make(map[string][]types.IncludeConfig)
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
dict = groupXFieldsIntoExtensions(dict, tree.NewPath())
|
||||
err = Transform(dict, project)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(includeRefs) != 0 {
|
||||
project.IncludeReferences = includeRefs
|
||||
}
|
||||
|
||||
if !opts.SkipNormalization {
|
||||
err := Normalize(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
|
||||
}
|
||||
}
|
||||
|
||||
if project, err = project.WithProfiles(opts.Profiles); 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).
|
||||
//
|
||||
// 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 {
|
||||
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
|
||||
}
|
||||
yml, err := ParseYAML(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, "_-")
|
||||
}
|
||||
|
||||
var userDefinedKeys = []tree.Path{
|
||||
"services",
|
||||
"volumes",
|
||||
"networks",
|
||||
"secrets",
|
||||
"configs",
|
||||
}
|
||||
|
||||
func groupXFieldsIntoExtensions(dict map[string]interface{}, p tree.Path) map[string]interface{} {
|
||||
extras := map[string]interface{}{}
|
||||
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] = groupXFieldsIntoExtensions(v, p.Next(key))
|
||||
case []interface{}:
|
||||
for i, e := range v {
|
||||
if m, ok := e.(map[string]interface{}); ok {
|
||||
v[i] = groupXFieldsIntoExtensions(m, p.Next(strconv.Itoa(i)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(extras) > 0 {
|
||||
dict[consts.Extensions] = extras
|
||||
}
|
||||
return dict
|
||||
}
|
||||
|
||||
// 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),
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
79
vendor/github.com/compose-spec/compose-go/v2/loader/mapstructure.go
generated
vendored
Normal file
79
vendor/github.com/compose-spec/compose-go/v2/loader/mapstructure.go
generated
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
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"
|
||||
"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
|
||||
type decoder interface {
|
||||
DecodeMapstructure(interface{}) error
|
||||
}
|
||||
|
||||
// see https://github.com/mitchellh/mapstructure/issues/115#issuecomment-735287466
|
||||
// adapted to support types derived from built-in types, as DecodeMapstructure would not be able to mutate internal
|
||||
// value, so need to invoke DecodeMapstructure defined by pointer to type
|
||||
func decoderHook(from reflect.Value, to reflect.Value) (interface{}, error) {
|
||||
// If the destination implements the decoder interface
|
||||
u, ok := to.Interface().(decoder)
|
||||
if !ok {
|
||||
// for non-struct types we need to invoke func (*type) DecodeMapstructure()
|
||||
if to.CanAddr() {
|
||||
pto := to.Addr()
|
||||
u, ok = pto.Interface().(decoder)
|
||||
}
|
||||
if !ok {
|
||||
return from.Interface(), nil
|
||||
}
|
||||
}
|
||||
// If it is nil and a pointer, create and assign the target value first
|
||||
if to.Type().Kind() == reflect.Ptr && to.IsNil() {
|
||||
to.Set(reflect.New(to.Type().Elem()))
|
||||
u = to.Interface().(decoder)
|
||||
}
|
||||
// Call the custom DecodeMapstructure method
|
||||
if err := u.DecodeMapstructure(from.Interface()); err != nil {
|
||||
return to.Interface(), err
|
||||
}
|
||||
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
|
||||
}
|
283
vendor/github.com/compose-spec/compose-go/v2/loader/normalize.go
generated
vendored
Normal file
283
vendor/github.com/compose-spec/compose-go/v2/loader/normalize.go
generated
vendored
Normal file
@ -0,0 +1,283 @@
|
||||
/*
|
||||
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/v2/errdefs"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"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{}
|
||||
}
|
||||
|
||||
for name, 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
|
||||
}
|
||||
|
||||
inferImplicitDependencies(&s)
|
||||
|
||||
project.Services[name] = 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
|
||||
}
|
||||
|
||||
// Resources with no explicit name are actually named by their key in map
|
||||
func setNameFromKey(project *types.Project) {
|
||||
for key, n := range project.Networks {
|
||||
if n.Name == "" {
|
||||
if n.External {
|
||||
n.Name = key
|
||||
} else {
|
||||
n.Name = fmt.Sprintf("%s_%s", project.Name, key)
|
||||
}
|
||||
project.Networks[key] = n
|
||||
}
|
||||
}
|
||||
|
||||
for key, v := range project.Volumes {
|
||||
if v.Name == "" {
|
||||
if v.External {
|
||||
v.Name = key
|
||||
} else {
|
||||
v.Name = fmt.Sprintf("%s_%s", project.Name, key)
|
||||
}
|
||||
project.Volumes[key] = v
|
||||
}
|
||||
}
|
||||
|
||||
for key, c := range project.Configs {
|
||||
if c.Name == "" {
|
||||
if c.External {
|
||||
c.Name = key
|
||||
} else {
|
||||
c.Name = fmt.Sprintf("%s_%s", project.Name, key)
|
||||
}
|
||||
project.Configs[key] = c
|
||||
}
|
||||
}
|
||||
|
||||
for key, s := range project.Secrets {
|
||||
if s.Name == "" {
|
||||
if s.External {
|
||||
s.Name = key
|
||||
} else {
|
||||
s.Name = fmt.Sprintf("%s_%s", project.Name, key)
|
||||
}
|
||||
project.Secrets[key] = s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 fmt.Errorf("can't use both 'log_opt' (deprecated) and 'logging.options': %w", errdefs.ErrInvalid)
|
||||
}
|
||||
}
|
||||
}
|
||||
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 fmt.Errorf("can't use both 'log_driver' (deprecated) and 'logging.driver': %w", errdefs.ErrInvalid)
|
||||
}
|
||||
}
|
||||
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 fmt.Errorf("can't use both 'dockerfile' (deprecated) and 'build.dockerfile': %w", errdefs.ErrInvalid)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
93
vendor/github.com/compose-spec/compose-go/v2/loader/paths.go
generated
vendored
Normal file
93
vendor/github.com/compose-spec/compose-go/v2/loader/paths.go
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
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
|
||||
|
||||
// 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 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
|
||||
}
|
126
vendor/github.com/compose-spec/compose-go/v2/loader/reset.go
generated
vendored
Normal file
126
vendor/github.com/compose-spec/compose-go/v2/loader/reset.go
generated
vendored
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/tree"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type ResetProcessor struct {
|
||||
target interface{}
|
||||
paths []tree.Path
|
||||
}
|
||||
|
||||
// UnmarshalYAML implement yaml.Unmarshaler
|
||||
func (p *ResetProcessor) UnmarshalYAML(value *yaml.Node) error {
|
||||
resolved, err := p.resolveReset(value, tree.NewPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return resolved.Decode(p.target)
|
||||
}
|
||||
|
||||
// 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 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 nodes []*yaml.Node
|
||||
for idx, v := range node.Content {
|
||||
next := path.Next(strconv.Itoa(idx))
|
||||
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 key string
|
||||
var nodes []*yaml.Node
|
||||
for idx, v := range node.Content {
|
||||
if idx%2 == 0 {
|
||||
key = v.Value
|
||||
} else {
|
||||
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 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(target any, path tree.Path) error {
|
||||
switch v := target.(type) {
|
||||
case map[string]any:
|
||||
KEYS:
|
||||
for k, e := range v {
|
||||
next := path.Next(k)
|
||||
for _, pattern := range p.paths {
|
||||
if next.Matches(pattern) {
|
||||
delete(v, k)
|
||||
continue KEYS
|
||||
}
|
||||
}
|
||||
err := p.applyNullOverrides(e, next)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case []any:
|
||||
ITER:
|
||||
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
|
||||
}
|
||||
}
|
||||
err := p.applyNullOverrides(e, next)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
146
vendor/github.com/compose-spec/compose-go/v2/loader/validate.go
generated
vendored
Normal file
146
vendor/github.com/compose-spec/compose-go/v2/loader/validate.go
generated
vendored
Normal file
@ -0,0 +1,146 @@
|
||||
/*
|
||||
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"
|
||||
"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 := range s.DependsOn {
|
||||
if _, err := project.GetService(dependedService); err != nil {
|
||||
return fmt.Errorf("service %q depends on undefined service %s: %w", s.Name, dependedService, errdefs.ErrInvalid)
|
||||
}
|
||||
}
|
||||
// Check there isn't a cycle in depends_on declarations
|
||||
if err := graph.InDependencyOrder(context.Background(), project, func(ctx context.Context, s string, config types.ServiceConfig) error {
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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.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)
|
||||
}
|
||||
}
|
||||
|
||||
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 nil
|
||||
}
|
27
vendor/github.com/compose-spec/compose-go/v2/override/extends.go
generated
vendored
Normal file
27
vendor/github.com/compose-spec/compose-go/v2/override/extends.go
generated
vendored
Normal file
@ -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
|
||||
}
|
252
vendor/github.com/compose-spec/compose-go/v2/override/merge.go
generated
vendored
Normal file
252
vendor/github.com/compose-spec/compose-go/v2/override/merge.go
generated
vendored
Normal file
@ -0,0 +1,252 @@
|
||||
/*
|
||||
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"
|
||||
"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["services.*.annotations"] = mergeToSequence
|
||||
mergeSpecials["services.*.build"] = mergeBuild
|
||||
mergeSpecials["services.*.build.args"] = mergeToSequence
|
||||
mergeSpecials["services.*.build.additional_contexts"] = 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) bool {
|
||||
return 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 any) map[string]any {
|
||||
switch v := a.(type) {
|
||||
case map[string]any:
|
||||
return v
|
||||
case []any:
|
||||
converted := map[string]any{}
|
||||
for _, s := range v {
|
||||
converted[s.(string)] = defaultValue
|
||||
}
|
||||
return converted
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func override(_ any, other any, _ tree.Path) (any, error) {
|
||||
return other, nil
|
||||
}
|
204
vendor/github.com/compose-spec/compose-go/v2/override/uncity.go
generated
vendored
Normal file
204
vendor/github.com/compose-spec/compose-go/v2/override/uncity.go
generated
vendored
Normal file
@ -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 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.extra_hosts"] = 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.*.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.*.extra_hosts"] = keyValueIndexer
|
||||
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
|
||||
}
|
||||
|
||||
// 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(y any, _ tree.Path) (string, error) {
|
||||
value := y.(string)
|
||||
key, _, found := strings.Cut(value, "=")
|
||||
if !found {
|
||||
return value, nil
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
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 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, _ tree.Path) (string, error) {
|
||||
switch value := y.(type) {
|
||||
case string:
|
||||
return value, nil
|
||||
case map[string]any:
|
||||
return value["path"].(string), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
44
vendor/github.com/compose-spec/compose-go/v2/paths/context.go
generated
vendored
Normal file
44
vendor/github.com/compose-spec/compose-go/v2/paths/context.go
generated
vendored
Normal file
@ -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
|
||||
}
|
25
vendor/github.com/compose-spec/compose-go/v2/paths/extends.go
generated
vendored
Normal file
25
vendor/github.com/compose-spec/compose-go/v2/paths/extends.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
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
|
||||
|
||||
func (r *relativePathsResolver) absExtendsPath(value any) (any, error) {
|
||||
v := value.(string)
|
||||
if r.isRemoteResource(v) {
|
||||
return v, nil
|
||||
}
|
||||
return r.absPath(v)
|
||||
}
|
37
vendor/github.com/compose-spec/compose-go/v2/paths/home.go
generated
vendored
Normal file
37
vendor/github.com/compose-spec/compose-go/v2/paths/home.go
generated
vendored
Normal file
@ -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
|
||||
}
|
161
vendor/github.com/compose-spec/compose-go/v2/paths/resolve.go
generated
vendored
Normal file
161
vendor/github.com/compose-spec/compose-go/v2/paths/resolve.go
generated
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
/*
|
||||
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.absPath,
|
||||
"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) {
|
||||
vol := a.(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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
40
vendor/github.com/compose-spec/compose-go/v2/paths/unix.go
generated
vendored
Normal file
40
vendor/github.com/compose-spec/compose-go/v2/paths/unix.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
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"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
82
vendor/github.com/compose-spec/compose-go/v2/paths/windows_path.go
generated
vendored
Normal file
82
vendor/github.com/compose-spec/compose-go/v2/paths/windows_path.go
generated
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
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
|
||||
|
||||
// Copyright 2010 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.
|
||||
// https://github.com/golang/go/blob/master/LICENSE
|
||||
|
||||
// This file contains utilities to check for Windows absolute paths on Linux.
|
||||
// The code in this file was largely copied from the Golang filepath package
|
||||
// https://github.com/golang/go/blob/1d0e94b1e13d5e8a323a63cd1cc1ef95290c9c36/src/path/filepath/path_windows.go#L12-L65
|
||||
|
||||
func isSlash(c uint8) bool {
|
||||
return c == '\\' || c == '/'
|
||||
}
|
||||
|
||||
// isAbs reports whether the path is a Windows absolute path.
|
||||
func isWindowsAbs(path string) (b bool) {
|
||||
l := volumeNameLen(path)
|
||||
if l == 0 {
|
||||
return false
|
||||
}
|
||||
path = path[l:]
|
||||
if path == "" {
|
||||
return false
|
||||
}
|
||||
return isSlash(path[0])
|
||||
}
|
||||
|
||||
// volumeNameLen returns length of the leading volume name on Windows.
|
||||
// It returns 0 elsewhere.
|
||||
// nolint: gocyclo
|
||||
func volumeNameLen(path string) int {
|
||||
if len(path) < 2 {
|
||||
return 0
|
||||
}
|
||||
// with drive letter
|
||||
c := path[0]
|
||||
if path[1] == ':' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') {
|
||||
return 2
|
||||
}
|
||||
// is it UNC? https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
|
||||
if l := len(path); l >= 5 && isSlash(path[0]) && isSlash(path[1]) &&
|
||||
!isSlash(path[2]) && path[2] != '.' {
|
||||
// first, leading `\\` and next shouldn't be `\`. its server name.
|
||||
for n := 3; n < l-1; n++ {
|
||||
// second, next '\' shouldn't be repeated.
|
||||
if isSlash(path[n]) {
|
||||
n++
|
||||
// third, following something characters. its share name.
|
||||
if !isSlash(path[n]) {
|
||||
if path[n] == '.' {
|
||||
break
|
||||
}
|
||||
for ; n < l; n++ {
|
||||
if isSlash(path[n]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
907
vendor/github.com/compose-spec/compose-go/v2/schema/compose-spec.json
generated
vendored
Normal file
907
vendor/github.com/compose-spec/compose-go/v2/schema/compose-spec.json
generated
vendored
Normal file
@ -0,0 +1,907 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2019-09/schema#",
|
||||
"id": "compose_spec.json",
|
||||
"type": "object",
|
||||
"title": "Compose Specification",
|
||||
"description": "The Compose file is a YAML file defining a multi-containers based application.",
|
||||
|
||||
"properties": {
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "declared for backward compatibility, ignored."
|
||||
},
|
||||
|
||||
"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."
|
||||
},
|
||||
|
||||
"services": {
|
||||
"id": "#/properties/services",
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[a-zA-Z0-9._-]+$": {
|
||||
"$ref": "#/definitions/service"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
"networks": {
|
||||
"id": "#/properties/networks",
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[a-zA-Z0-9._-]+$": {
|
||||
"$ref": "#/definitions/network"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"volumes": {
|
||||
"id": "#/properties/volumes",
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[a-zA-Z0-9._-]+$": {
|
||||
"$ref": "#/definitions/volume"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
"secrets": {
|
||||
"id": "#/properties/secrets",
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[a-zA-Z0-9._-]+$": {
|
||||
"$ref": "#/definitions/secret"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
"configs": {
|
||||
"id": "#/properties/configs",
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[a-zA-Z0-9._-]+$": {
|
||||
"$ref": "#/definitions/config"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
|
||||
"patternProperties": {"^x-": {}},
|
||||
"additionalProperties": false,
|
||||
|
||||
"definitions": {
|
||||
|
||||
"service": {
|
||||
"id": "#/definitions/service",
|
||||
"type": "object",
|
||||
|
||||
"properties": {
|
||||
"develop": {"$ref": "#/definitions/development"},
|
||||
"deploy": {"$ref": "#/definitions/deployment"},
|
||||
"annotations": {"$ref": "#/definitions/list_or_dict"},
|
||||
"attach": {"type": "boolean"},
|
||||
"build": {
|
||||
"oneOf": [
|
||||
{"type": "string"},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"context": {"type": "string"},
|
||||
"dockerfile": {"type": "string"},
|
||||
"dockerfile_inline": {"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"},
|
||||
"additional_contexts": {"$ref": "#/definitions/list_or_dict"},
|
||||
"network": {"type": "string"},
|
||||
"pull": {"type": "boolean"},
|
||||
"target": {"type": "string"},
|
||||
"shm_size": {"type": ["integer", "string"]},
|
||||
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
|
||||
"isolation": {"type": "string"},
|
||||
"privileged": {"type": "boolean"},
|
||||
"secrets": {"$ref": "#/definitions/service_config_or_secret"},
|
||||
"tags": {"type": "array", "items": {"type": "string"}},
|
||||
"ulimits": {"$ref": "#/definitions/ulimits"},
|
||||
"platforms": {"type": "array", "items": {"type": "string"}}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
}
|
||||
]
|
||||
},
|
||||
"blkio_config": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"device_read_bps": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/definitions/blkio_limit"}
|
||||
},
|
||||
"device_read_iops": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/definitions/blkio_limit"}
|
||||
},
|
||||
"device_write_bps": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/definitions/blkio_limit"}
|
||||
},
|
||||
"device_write_iops": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/definitions/blkio_limit"}
|
||||
},
|
||||
"weight": {"type": "integer"},
|
||||
"weight_device": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/definitions/blkio_weight"}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"cgroup": {"type": "string", "enum": ["host", "private"]},
|
||||
"cgroup_parent": {"type": "string"},
|
||||
"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_shares": {"type": ["number", "string"]},
|
||||
"cpu_quota": {"type": ["number", "string"]},
|
||||
"cpu_period": {"type": ["number", "string"]},
|
||||
"cpu_rt_period": {"type": ["number", "string"]},
|
||||
"cpu_rt_runtime": {"type": ["number", "string"]},
|
||||
"cpus": {"type": ["number", "string"]},
|
||||
"cpuset": {"type": "string"},
|
||||
"credential_spec": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"config": {"type": "string"},
|
||||
"file": {"type": "string"},
|
||||
"registry": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
},
|
||||
"depends_on": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/definitions/list_of_strings"},
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {
|
||||
"^[a-zA-Z0-9._-]+$": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"restart": {"type": "boolean"},
|
||||
"required": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"condition": {
|
||||
"type": "string",
|
||||
"enum": ["service_started", "service_healthy", "service_completed_successfully"]
|
||||
}
|
||||
},
|
||||
"required": ["condition"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"device_cgroup_rules": {"$ref": "#/definitions/list_of_strings"},
|
||||
"devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"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/env_file"},
|
||||
"environment": {"$ref": "#/definitions/list_or_dict"},
|
||||
|
||||
"expose": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": ["string", "number"],
|
||||
"format": "expose"
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"extends": {
|
||||
"oneOf": [
|
||||
{"type": "string"},
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
"properties": {
|
||||
"service": {"type": "string"},
|
||||
"file": {"type": "string"}
|
||||
},
|
||||
"required": ["service"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
|
||||
"group_add": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": ["string", "number"]
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"healthcheck": {"$ref": "#/definitions/healthcheck"},
|
||||
"hostname": {"type": "string"},
|
||||
"image": {"type": "string"},
|
||||
"init": {"type": "boolean"},
|
||||
"ipc": {"type": "string"},
|
||||
"isolation": {"type": "string"},
|
||||
"labels": {"$ref": "#/definitions/list_or_dict"},
|
||||
"links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"logging": {
|
||||
"type": "object",
|
||||
|
||||
"properties": {
|
||||
"driver": {"type": "string"},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^.+$": {"type": ["string", "number", "null"]}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
},
|
||||
"mac_address": {"type": "string"},
|
||||
"mem_limit": {"type": ["number", "string"]},
|
||||
"mem_reservation": {"type": ["string", "integer"]},
|
||||
"mem_swappiness": {"type": "integer"},
|
||||
"memswap_limit": {"type": ["number", "string"]},
|
||||
"network_mode": {"type": "string"},
|
||||
"networks": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/definitions/list_of_strings"},
|
||||
{
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[a-zA-Z0-9._-]+$": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"aliases": {"$ref": "#/definitions/list_of_strings"},
|
||||
"ipv4_address": {"type": "string"},
|
||||
"ipv6_address": {"type": "string"},
|
||||
"link_local_ips": {"$ref": "#/definitions/list_of_strings"},
|
||||
"mac_address": {"type": "string"},
|
||||
"priority": {"type": "number"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
},
|
||||
{"type": "null"}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"oom_kill_disable": {"type": "boolean"},
|
||||
"oom_score_adj": {"type": "integer", "minimum": -1000, "maximum": 1000},
|
||||
"pid": {"type": ["string", "null"]},
|
||||
"pids_limit": {"type": ["number", "string"]},
|
||||
"platform": {"type": "string"},
|
||||
"ports": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{"type": "number", "format": "ports"},
|
||||
{"type": "string", "format": "ports"},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"mode": {"type": "string"},
|
||||
"host_ip": {"type": "string"},
|
||||
"target": {"type": "integer"},
|
||||
"published": {"type": ["string", "integer"]},
|
||||
"protocol": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
}
|
||||
]
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"privileged": {"type": "boolean"},
|
||||
"profiles": {"$ref": "#/definitions/list_of_strings"},
|
||||
"pull_policy": {"type": "string", "enum": [
|
||||
"always", "never", "if_not_present", "build", "missing"
|
||||
]},
|
||||
"read_only": {"type": "boolean"},
|
||||
"restart": {"type": "string"},
|
||||
"runtime": {
|
||||
"type": "string"
|
||||
},
|
||||
"scale": {
|
||||
"type": "integer"
|
||||
},
|
||||
"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"},
|
||||
"stop_signal": {"type": "string"},
|
||||
"storage_opt": {"type": "object"},
|
||||
"tmpfs": {"$ref": "#/definitions/string_or_list"},
|
||||
"tty": {"type": "boolean"},
|
||||
"ulimits": {"$ref": "#/definitions/ulimits"},
|
||||
"user": {"type": "string"},
|
||||
"uts": {"type": "string"},
|
||||
"userns_mode": {"type": "string"},
|
||||
"volumes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{"type": "string"},
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["type"],
|
||||
"properties": {
|
||||
"type": {"type": "string"},
|
||||
"source": {"type": "string"},
|
||||
"target": {"type": "string"},
|
||||
"read_only": {"type": "boolean"},
|
||||
"consistency": {"type": "string"},
|
||||
"bind": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"propagation": {"type": "string"},
|
||||
"create_host_path": {"type": "boolean"},
|
||||
"selinux": {"type": "string", "enum": ["z", "Z"]}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
},
|
||||
"volume": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"nocopy": {"type": "boolean"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
},
|
||||
"tmpfs": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"size": {
|
||||
"oneOf": [
|
||||
{"type": "integer", "minimum": 0},
|
||||
{"type": "string"}
|
||||
]
|
||||
},
|
||||
"mode": {"type": "number"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
}
|
||||
]
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"volumes_from": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"working_dir": {"type": "string"}
|
||||
},
|
||||
"patternProperties": {"^x-": {}},
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
"healthcheck": {
|
||||
"id": "#/definitions/healthcheck",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"disable": {"type": "boolean"},
|
||||
"interval": {"type": "string", "format": "duration"},
|
||||
"retries": {"type": "number"},
|
||||
"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"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
},
|
||||
"development": {
|
||||
"id": "#/definitions/development",
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"watch": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ignore": {"type": "array", "items": {"type": "string"}},
|
||||
"path": {"type": "string"},
|
||||
"action": {"type": "string", "enum": ["rebuild", "sync", "sync+restart"]},
|
||||
"target": {"type": "string"}
|
||||
}
|
||||
},
|
||||
"required": ["path", "action"],
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
}
|
||||
}
|
||||
},
|
||||
"deployment": {
|
||||
"id": "#/definitions/deployment",
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"mode": {"type": "string"},
|
||||
"endpoint_mode": {"type": "string"},
|
||||
"replicas": {"type": "integer"},
|
||||
"labels": {"$ref": "#/definitions/list_or_dict"},
|
||||
"rollback_config": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"parallelism": {"type": "integer"},
|
||||
"delay": {"type": "string", "format": "duration"},
|
||||
"failure_action": {"type": "string"},
|
||||
"monitor": {"type": "string", "format": "duration"},
|
||||
"max_failure_ratio": {"type": "number"},
|
||||
"order": {"type": "string", "enum": [
|
||||
"start-first", "stop-first"
|
||||
]}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
},
|
||||
"update_config": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"parallelism": {"type": "integer"},
|
||||
"delay": {"type": "string", "format": "duration"},
|
||||
"failure_action": {"type": "string"},
|
||||
"monitor": {"type": "string", "format": "duration"},
|
||||
"max_failure_ratio": {"type": "number"},
|
||||
"order": {"type": "string", "enum": [
|
||||
"start-first", "stop-first"
|
||||
]}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
},
|
||||
"resources": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limits": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cpus": {"type": ["number", "string"]},
|
||||
"memory": {"type": "string"},
|
||||
"pids": {"type": "integer"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
},
|
||||
"reservations": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cpus": {"type": ["number", "string"]},
|
||||
"memory": {"type": "string"},
|
||||
"generic_resources": {"$ref": "#/definitions/generic_resources"},
|
||||
"devices": {"$ref": "#/definitions/devices"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
},
|
||||
"restart_policy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"condition": {"type": "string"},
|
||||
"delay": {"type": "string", "format": "duration"},
|
||||
"max_attempts": {"type": "integer"},
|
||||
"window": {"type": "string", "format": "duration"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
},
|
||||
"placement": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"constraints": {"type": "array", "items": {"type": "string"}},
|
||||
"preferences": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"spread": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
}
|
||||
},
|
||||
"max_replicas_per_node": {"type": "integer"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
},
|
||||
|
||||
"generic_resources": {
|
||||
"id": "#/definitions/generic_resources",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"discrete_resource_spec": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {"type": "string"},
|
||||
"value": {"type": "number"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
}
|
||||
},
|
||||
|
||||
"devices": {
|
||||
"id": "#/definitions/devices",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"capabilities": {"$ref": "#/definitions/list_of_strings"},
|
||||
"count": {"type": ["string", "integer"]},
|
||||
"device_ids": {"$ref": "#/definitions/list_of_strings"},
|
||||
"driver":{"type": "string"},
|
||||
"options":{"$ref": "#/definitions/list_or_dict"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
}
|
||||
},
|
||||
|
||||
"include": {
|
||||
"id": "#/definitions/include",
|
||||
"oneOf": [
|
||||
{"type": "string"},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {"$ref": "#/definitions/string_or_list"},
|
||||
"env_file": {"$ref": "#/definitions/string_or_list"},
|
||||
"project_directory": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"network": {
|
||||
"id": "#/definitions/network",
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"driver": {"type": "string"},
|
||||
"driver_opts": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^.+$": {"type": ["string", "number"]}
|
||||
}
|
||||
},
|
||||
"ipam": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"driver": {"type": "string"},
|
||||
"config": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"subnet": {"type": "string", "format": "subnet_ip_address"},
|
||||
"ip_range": {"type": "string"},
|
||||
"gateway": {"type": "string"},
|
||||
"aux_addresses": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^.+$": {"type": "string"}}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^.+$": {"type": "string"}}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
},
|
||||
"external": {
|
||||
"type": ["boolean", "object"],
|
||||
"properties": {
|
||||
"name": {
|
||||
"deprecated": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
},
|
||||
"internal": {"type": "boolean"},
|
||||
"enable_ipv6": {"type": "boolean"},
|
||||
"attachable": {"type": "boolean"},
|
||||
"labels": {"$ref": "#/definitions/list_or_dict"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
},
|
||||
|
||||
"volume": {
|
||||
"id": "#/definitions/volume",
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"driver": {"type": "string"},
|
||||
"driver_opts": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^.+$": {"type": ["string", "number"]}
|
||||
}
|
||||
},
|
||||
"external": {
|
||||
"type": ["boolean", "object"],
|
||||
"properties": {
|
||||
"name": {
|
||||
"deprecated": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
},
|
||||
"labels": {"$ref": "#/definitions/list_or_dict"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
},
|
||||
|
||||
"secret": {
|
||||
"id": "#/definitions/secret",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"environment": {"type": "string"},
|
||||
"file": {"type": "string"},
|
||||
"external": {
|
||||
"type": ["boolean", "object"],
|
||||
"properties": {
|
||||
"name": {"type": "string"}
|
||||
}
|
||||
},
|
||||
"labels": {"$ref": "#/definitions/list_or_dict"},
|
||||
"driver": {"type": "string"},
|
||||
"driver_opts": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^.+$": {"type": ["string", "number"]}
|
||||
}
|
||||
},
|
||||
"template_driver": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
},
|
||||
|
||||
"config": {
|
||||
"id": "#/definitions/config",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"content": {"type": "string"},
|
||||
"environment": {"type": "string"},
|
||||
"file": {"type": "string"},
|
||||
"external": {
|
||||
"type": ["boolean", "object"],
|
||||
"properties": {
|
||||
"name": {
|
||||
"deprecated": true,
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"labels": {"$ref": "#/definitions/list_or_dict"},
|
||||
"template_driver": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
},
|
||||
|
||||
"command": {
|
||||
"oneOf": [
|
||||
{"type": "null"},
|
||||
{"type": "string"},
|
||||
{"type": "array","items": {"type": "string"}}
|
||||
]
|
||||
},
|
||||
|
||||
"env_file": {
|
||||
"oneOf": [
|
||||
{"type": "string"},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{"type": "string"},
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"string_or_list": {
|
||||
"oneOf": [
|
||||
{"type": "string"},
|
||||
{"$ref": "#/definitions/list_of_strings"}
|
||||
]
|
||||
},
|
||||
|
||||
"list_of_strings": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"uniqueItems": true
|
||||
},
|
||||
|
||||
"list_or_dict": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
".+": {
|
||||
"type": ["string", "number", "boolean", "null"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
|
||||
]
|
||||
},
|
||||
|
||||
"blkio_limit": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {"type": "string"},
|
||||
"rate": {"type": ["integer", "string"]}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"blkio_weight": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {"type": "string"},
|
||||
"weight": {"type": "integer"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"service_config_or_secret": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{"type": "string"},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"source": {"type": "string"},
|
||||
"target": {"type": "string"},
|
||||
"uid": {"type": "string"},
|
||||
"gid": {"type": "string"},
|
||||
"mode": {"type": "number"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ulimits": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[a-z]+$": {
|
||||
"oneOf": [
|
||||
{"type": "integer"},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hard": {"type": "integer"},
|
||||
"soft": {"type": "integer"}
|
||||
},
|
||||
"required": ["soft", "hard"],
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"constraints": {
|
||||
"service": {
|
||||
"id": "#/definitions/constraints/service",
|
||||
"anyOf": [
|
||||
{"required": ["build"]},
|
||||
{"required": ["image"]}
|
||||
],
|
||||
"properties": {
|
||||
"build": {
|
||||
"required": ["context"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
164
vendor/github.com/compose-spec/compose-go/v2/schema/schema.go
generated
vendored
Normal file
164
vendor/github.com/compose-spec/compose-go/v2/schema/schema.go
generated
vendored
Normal file
@ -0,0 +1,164 @@
|
||||
/*
|
||||
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 schema
|
||||
|
||||
import (
|
||||
// Enable support for embedded static resources
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xeipuuv/gojsonschema"
|
||||
)
|
||||
|
||||
type portsFormatChecker struct{}
|
||||
|
||||
func (checker portsFormatChecker) IsFormat(_ interface{}) bool {
|
||||
// TODO: implement this
|
||||
return true
|
||||
}
|
||||
|
||||
type durationFormatChecker struct{}
|
||||
|
||||
func (checker durationFormatChecker) IsFormat(input interface{}) bool {
|
||||
value, ok := input.(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
_, err := time.ParseDuration(value)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
gojsonschema.FormatCheckers.Add("expose", portsFormatChecker{})
|
||||
gojsonschema.FormatCheckers.Add("ports", portsFormatChecker{})
|
||||
gojsonschema.FormatCheckers.Add("duration", durationFormatChecker{})
|
||||
}
|
||||
|
||||
// Schema is the compose-spec JSON schema
|
||||
//
|
||||
//go:embed compose-spec.json
|
||||
var Schema string
|
||||
|
||||
// Validate uses the jsonschema to validate the configuration
|
||||
func Validate(config map[string]interface{}) error {
|
||||
schemaLoader := gojsonschema.NewStringLoader(Schema)
|
||||
dataLoader := gojsonschema.NewGoLoader(config)
|
||||
|
||||
result, err := gojsonschema.Validate(schemaLoader, dataLoader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !result.Valid() {
|
||||
return toError(result)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func toError(result *gojsonschema.Result) error {
|
||||
err := getMostSpecificError(result.Errors())
|
||||
return err
|
||||
}
|
||||
|
||||
const (
|
||||
jsonschemaOneOf = "number_one_of"
|
||||
jsonschemaAnyOf = "number_any_of"
|
||||
)
|
||||
|
||||
func getDescription(err validationError) string {
|
||||
switch err.parent.Type() {
|
||||
case "invalid_type":
|
||||
if expectedType, ok := err.parent.Details()["expected"].(string); ok {
|
||||
return fmt.Sprintf("must be a %s", humanReadableType(expectedType))
|
||||
}
|
||||
case jsonschemaOneOf, jsonschemaAnyOf:
|
||||
if err.child == nil {
|
||||
return err.parent.Description()
|
||||
}
|
||||
return err.child.Description()
|
||||
}
|
||||
return err.parent.Description()
|
||||
}
|
||||
|
||||
func humanReadableType(definition string) string {
|
||||
if definition[0:1] == "[" {
|
||||
allTypes := strings.Split(definition[1:len(definition)-1], ",")
|
||||
for i, t := range allTypes {
|
||||
allTypes[i] = humanReadableType(t)
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"%s or %s",
|
||||
strings.Join(allTypes[0:len(allTypes)-1], ", "),
|
||||
allTypes[len(allTypes)-1],
|
||||
)
|
||||
}
|
||||
if definition == "object" {
|
||||
return "mapping"
|
||||
}
|
||||
if definition == "array" {
|
||||
return "list"
|
||||
}
|
||||
return definition
|
||||
}
|
||||
|
||||
type validationError struct {
|
||||
parent gojsonschema.ResultError
|
||||
child gojsonschema.ResultError
|
||||
}
|
||||
|
||||
func (err validationError) Error() string {
|
||||
description := getDescription(err)
|
||||
return fmt.Sprintf("%s %s", err.parent.Field(), description)
|
||||
}
|
||||
|
||||
func getMostSpecificError(errors []gojsonschema.ResultError) validationError {
|
||||
mostSpecificError := 0
|
||||
for i, err := range errors {
|
||||
if specificity(err) > specificity(errors[mostSpecificError]) {
|
||||
mostSpecificError = i
|
||||
continue
|
||||
}
|
||||
|
||||
if specificity(err) == specificity(errors[mostSpecificError]) {
|
||||
// Invalid type errors win in a tie-breaker for most specific field name
|
||||
if err.Type() == "invalid_type" && errors[mostSpecificError].Type() != "invalid_type" {
|
||||
mostSpecificError = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if mostSpecificError+1 == len(errors) {
|
||||
return validationError{parent: errors[mostSpecificError]}
|
||||
}
|
||||
|
||||
switch errors[mostSpecificError].Type() {
|
||||
case "number_one_of", "number_any_of":
|
||||
return validationError{
|
||||
parent: errors[mostSpecificError],
|
||||
child: errors[mostSpecificError+1],
|
||||
}
|
||||
default:
|
||||
return validationError{parent: errors[mostSpecificError]}
|
||||
}
|
||||
}
|
||||
|
||||
func specificity(err gojsonschema.ResultError) int {
|
||||
return len(strings.Split(err.Field(), "."))
|
||||
}
|
473
vendor/github.com/compose-spec/compose-go/v2/template/template.go
generated
vendored
Normal file
473
vendor/github.com/compose-spec/compose-go/v2/template/template.go
generated
vendored
Normal file
@ -0,0 +1,473 @@
|
||||
/*
|
||||
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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var delimiter = "\\$"
|
||||
var substitutionNamed = "[_a-z][_a-z0-9]*"
|
||||
var substitutionBraced = "[_a-z][_a-z0-9]*(?::?[-+?](.*))?"
|
||||
|
||||
var groupEscaped = "escaped"
|
||||
var groupNamed = "named"
|
||||
var groupBraced = "braced"
|
||||
var groupInvalid = "invalid"
|
||||
|
||||
var patternString = fmt.Sprintf(
|
||||
"%s(?i:(?P<%s>%s)|(?P<%s>%s)|{(?:(?P<%s>%s)}|(?P<%s>)))",
|
||||
delimiter,
|
||||
groupEscaped, delimiter,
|
||||
groupNamed, substitutionNamed,
|
||||
groupBraced, substitutionBraced,
|
||||
groupInvalid,
|
||||
)
|
||||
|
||||
var defaultPattern = regexp.MustCompile(patternString)
|
||||
|
||||
// InvalidTemplateError is returned when a variable template is not in a valid
|
||||
// format
|
||||
type InvalidTemplateError struct {
|
||||
Template string
|
||||
}
|
||||
|
||||
func (e InvalidTemplateError) Error() string {
|
||||
return fmt.Sprintf("Invalid template: %#v", e.Template)
|
||||
}
|
||||
|
||||
// MissingRequiredError is returned when a variable template is missing
|
||||
type MissingRequiredError struct {
|
||||
Variable string
|
||||
Reason string
|
||||
}
|
||||
|
||||
func (e MissingRequiredError) Error() string {
|
||||
if e.Reason != "" {
|
||||
return fmt.Sprintf("required variable %s is missing a value: %s", e.Variable, e.Reason)
|
||||
}
|
||||
return fmt.Sprintf("required variable %s is missing a value", e.Variable)
|
||||
}
|
||||
|
||||
// Mapping is a user-supplied function which maps from variable names to values.
|
||||
// Returns the value as a string and a bool indicating whether
|
||||
// the value is present, to distinguish between an empty string
|
||||
// and the absence of a value.
|
||||
type Mapping func(string) (string, bool)
|
||||
|
||||
// SubstituteFunc is a user-supplied function that apply substitution.
|
||||
// Returns the value as a string, a bool indicating if the function could apply
|
||||
// the substitution and an error.
|
||||
type SubstituteFunc func(string, Mapping) (string, bool, error)
|
||||
|
||||
// ReplacementFunc is a user-supplied function that is apply to the matching
|
||||
// substring. Returns the value as a string and an error.
|
||||
type ReplacementFunc func(string, Mapping, *Config) (string, error)
|
||||
|
||||
type Config struct {
|
||||
pattern *regexp.Regexp
|
||||
substituteFunc SubstituteFunc
|
||||
replacementFunc ReplacementFunc
|
||||
logging bool
|
||||
}
|
||||
|
||||
type Option func(*Config)
|
||||
|
||||
func WithPattern(pattern *regexp.Regexp) Option {
|
||||
return func(cfg *Config) {
|
||||
cfg.pattern = pattern
|
||||
}
|
||||
}
|
||||
|
||||
func WithSubstitutionFunction(subsFunc SubstituteFunc) Option {
|
||||
return func(cfg *Config) {
|
||||
cfg.substituteFunc = subsFunc
|
||||
}
|
||||
}
|
||||
|
||||
func WithReplacementFunction(replacementFunc ReplacementFunc) Option {
|
||||
return func(cfg *Config) {
|
||||
cfg.replacementFunc = replacementFunc
|
||||
}
|
||||
}
|
||||
|
||||
func WithoutLogging(cfg *Config) {
|
||||
cfg.logging = false
|
||||
}
|
||||
|
||||
// SubstituteWithOptions substitute variables in the string with their values.
|
||||
// It accepts additional options such as a custom function or pattern.
|
||||
func SubstituteWithOptions(template string, mapping Mapping, options ...Option) (string, error) {
|
||||
var returnErr error
|
||||
|
||||
cfg := &Config{
|
||||
pattern: defaultPattern,
|
||||
replacementFunc: DefaultReplacementFunc,
|
||||
logging: true,
|
||||
}
|
||||
for _, o := range options {
|
||||
o(cfg)
|
||||
}
|
||||
|
||||
result := cfg.pattern.ReplaceAllStringFunc(template, func(substring string) string {
|
||||
replacement, err := cfg.replacementFunc(substring, mapping, cfg)
|
||||
if err != nil {
|
||||
// Add the template for template errors
|
||||
var tmplErr *InvalidTemplateError
|
||||
if errors.As(err, &tmplErr) {
|
||||
if tmplErr.Template == "" {
|
||||
tmplErr.Template = template
|
||||
}
|
||||
}
|
||||
// Save the first error to be returned
|
||||
if returnErr == nil {
|
||||
returnErr = err
|
||||
}
|
||||
|
||||
}
|
||||
return replacement
|
||||
})
|
||||
|
||||
return result, returnErr
|
||||
}
|
||||
|
||||
func DefaultReplacementFunc(substring string, mapping Mapping, cfg *Config) (string, error) {
|
||||
value, _, err := DefaultReplacementAppliedFunc(substring, mapping, cfg)
|
||||
return value, err
|
||||
}
|
||||
|
||||
func DefaultReplacementAppliedFunc(substring string, mapping Mapping, cfg *Config) (string, bool, error) {
|
||||
pattern := cfg.pattern
|
||||
subsFunc := cfg.substituteFunc
|
||||
if subsFunc == nil {
|
||||
_, subsFunc = getSubstitutionFunctionForTemplate(substring)
|
||||
}
|
||||
|
||||
closingBraceIndex := getFirstBraceClosingIndex(substring)
|
||||
rest := ""
|
||||
if closingBraceIndex > -1 {
|
||||
rest = substring[closingBraceIndex+1:]
|
||||
substring = substring[0 : closingBraceIndex+1]
|
||||
}
|
||||
|
||||
matches := pattern.FindStringSubmatch(substring)
|
||||
groups := matchGroups(matches, pattern)
|
||||
if escaped := groups[groupEscaped]; escaped != "" {
|
||||
return escaped, true, nil
|
||||
}
|
||||
|
||||
braced := false
|
||||
substitution := groups[groupNamed]
|
||||
if substitution == "" {
|
||||
substitution = groups[groupBraced]
|
||||
braced = true
|
||||
}
|
||||
|
||||
if substitution == "" {
|
||||
return "", false, &InvalidTemplateError{}
|
||||
}
|
||||
|
||||
if braced {
|
||||
value, applied, err := subsFunc(substitution, mapping)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
if applied {
|
||||
interpolatedNested, err := SubstituteWith(rest, mapping, pattern)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
return value + interpolatedNested, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
value, ok := mapping(substitution)
|
||||
if !ok && cfg.logging {
|
||||
logrus.Warnf("The %q variable is not set. Defaulting to a blank string.", substitution)
|
||||
}
|
||||
|
||||
return value, ok, nil
|
||||
}
|
||||
|
||||
// SubstituteWith substitute variables in the string with their values.
|
||||
// It accepts additional substitute function.
|
||||
func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, subsFuncs ...SubstituteFunc) (string, error) {
|
||||
options := []Option{
|
||||
WithPattern(pattern),
|
||||
}
|
||||
if len(subsFuncs) > 0 {
|
||||
options = append(options, WithSubstitutionFunction(subsFuncs[0]))
|
||||
}
|
||||
|
||||
return SubstituteWithOptions(template, mapping, options...)
|
||||
}
|
||||
|
||||
func getSubstitutionFunctionForTemplate(template string) (string, SubstituteFunc) {
|
||||
interpolationMapping := []struct {
|
||||
string
|
||||
SubstituteFunc
|
||||
}{
|
||||
{":?", requiredErrorWhenEmptyOrUnset},
|
||||
{"?", requiredErrorWhenUnset},
|
||||
{":-", defaultWhenEmptyOrUnset},
|
||||
{"-", defaultWhenUnset},
|
||||
{":+", defaultWhenNotEmpty},
|
||||
{"+", defaultWhenSet},
|
||||
}
|
||||
sort.Slice(interpolationMapping, func(i, j int) bool {
|
||||
idxI := strings.Index(template, interpolationMapping[i].string)
|
||||
idxJ := strings.Index(template, interpolationMapping[j].string)
|
||||
if idxI < 0 {
|
||||
return false
|
||||
}
|
||||
if idxJ < 0 {
|
||||
return true
|
||||
}
|
||||
return idxI < idxJ
|
||||
})
|
||||
|
||||
return interpolationMapping[0].string, interpolationMapping[0].SubstituteFunc
|
||||
}
|
||||
|
||||
func getFirstBraceClosingIndex(s string) int {
|
||||
openVariableBraces := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == '}' {
|
||||
openVariableBraces--
|
||||
if openVariableBraces == 0 {
|
||||
return i
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(s[i:], "${") {
|
||||
openVariableBraces++
|
||||
i++
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Soft default (fall back if unset or empty)
|
||||
func defaultWhenEmptyOrUnset(substitution string, mapping Mapping) (string, bool, error) {
|
||||
return withDefaultWhenAbsence(substitution, mapping, true)
|
||||
}
|
||||
|
||||
// Hard default (fall back if-and-only-if empty)
|
||||
func defaultWhenUnset(substitution string, mapping Mapping) (string, bool, error) {
|
||||
return withDefaultWhenAbsence(substitution, mapping, false)
|
||||
}
|
||||
|
||||
func defaultWhenNotEmpty(substitution string, mapping Mapping) (string, bool, error) {
|
||||
return withDefaultWhenPresence(substitution, mapping, true)
|
||||
}
|
||||
|
||||
func defaultWhenSet(substitution string, mapping Mapping) (string, bool, error) {
|
||||
return withDefaultWhenPresence(substitution, mapping, false)
|
||||
}
|
||||
|
||||
func requiredErrorWhenEmptyOrUnset(substitution string, mapping Mapping) (string, bool, error) {
|
||||
return withRequired(substitution, mapping, ":?", func(v string) bool { return v != "" })
|
||||
}
|
||||
|
||||
func requiredErrorWhenUnset(substitution string, mapping Mapping) (string, bool, error) {
|
||||
return withRequired(substitution, mapping, "?", func(_ string) bool { return true })
|
||||
}
|
||||
|
||||
func withDefaultWhenPresence(substitution string, mapping Mapping, notEmpty bool) (string, bool, error) {
|
||||
sep := "+"
|
||||
if notEmpty {
|
||||
sep = ":+"
|
||||
}
|
||||
if !strings.Contains(substitution, sep) {
|
||||
return "", false, nil
|
||||
}
|
||||
name, defaultValue := partition(substitution, sep)
|
||||
defaultValue, err := Substitute(defaultValue, mapping)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
value, ok := mapping(name)
|
||||
if ok && (!notEmpty || (notEmpty && value != "")) {
|
||||
return defaultValue, true, nil
|
||||
}
|
||||
return value, true, nil
|
||||
}
|
||||
|
||||
func withDefaultWhenAbsence(substitution string, mapping Mapping, emptyOrUnset bool) (string, bool, error) {
|
||||
sep := "-"
|
||||
if emptyOrUnset {
|
||||
sep = ":-"
|
||||
}
|
||||
if !strings.Contains(substitution, sep) {
|
||||
return "", false, nil
|
||||
}
|
||||
name, defaultValue := partition(substitution, sep)
|
||||
defaultValue, err := Substitute(defaultValue, mapping)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
value, ok := mapping(name)
|
||||
if !ok || (emptyOrUnset && value == "") {
|
||||
return defaultValue, true, nil
|
||||
}
|
||||
return value, true, nil
|
||||
}
|
||||
|
||||
func withRequired(substitution string, mapping Mapping, sep string, valid func(string) bool) (string, bool, error) {
|
||||
if !strings.Contains(substitution, sep) {
|
||||
return "", false, nil
|
||||
}
|
||||
name, errorMessage := partition(substitution, sep)
|
||||
errorMessage, err := Substitute(errorMessage, mapping)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
value, ok := mapping(name)
|
||||
if !ok || !valid(value) {
|
||||
return "", true, &MissingRequiredError{
|
||||
Reason: errorMessage,
|
||||
Variable: name,
|
||||
}
|
||||
}
|
||||
return value, true, nil
|
||||
}
|
||||
|
||||
func matchGroups(matches []string, pattern *regexp.Regexp) map[string]string {
|
||||
groups := make(map[string]string)
|
||||
for i, name := range pattern.SubexpNames()[1:] {
|
||||
groups[name] = matches[i+1]
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
// Split the string at the first occurrence of sep, and return the part before the separator,
|
||||
// and the part after the separator.
|
||||
//
|
||||
// If the separator is not found, return the string itself, followed by an empty string.
|
||||
func partition(s, sep string) (string, string) {
|
||||
if strings.Contains(s, sep) {
|
||||
parts := strings.SplitN(s, sep, 2)
|
||||
return parts[0], parts[1]
|
||||
}
|
||||
return s, ""
|
||||
}
|
39
vendor/github.com/compose-spec/compose-go/v2/transform/build.go
generated
vendored
Normal file
39
vendor/github.com/compose-spec/compose-go/v2/transform/build.go
generated
vendored
Normal file
@ -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 transform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/tree"
|
||||
)
|
||||
|
||||
func transformBuild(data any, p tree.Path) (any, error) {
|
||||
switch v := data.(type) {
|
||||
case map[string]any:
|
||||
if _, ok := v["context"]; !ok {
|
||||
v["context"] = "." // TODO(ndeloof) maybe we miss an explicit "set-defaults" loading phase
|
||||
}
|
||||
return transformMapping(v, p)
|
||||
case string:
|
||||
return map[string]any{
|
||||
"context": v,
|
||||
}, nil
|
||||
default:
|
||||
return data, fmt.Errorf("%s: invalid type %T for build", p, v)
|
||||
}
|
||||
}
|
107
vendor/github.com/compose-spec/compose-go/v2/transform/canonical.go
generated
vendored
Normal file
107
vendor/github.com/compose-spec/compose-go/v2/transform/canonical.go
generated
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
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) (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.*.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) (map[string]any, error) {
|
||||
canonical, err := transform(yaml, tree.NewPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return canonical.(map[string]any), nil
|
||||
}
|
||||
|
||||
func transform(data any, p tree.Path) (any, error) {
|
||||
for pattern, transformer := range transformers {
|
||||
if p.Matches(pattern) {
|
||||
t, err := transformer(data, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
switch v := data.(type) {
|
||||
case map[string]any:
|
||||
a, err := transformMapping(v, p)
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
return v, nil
|
||||
case []any:
|
||||
a, err := transformSequence(v, p)
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
return v, nil
|
||||
default:
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
func transformSequence(v []any, p tree.Path) ([]any, error) {
|
||||
for i, e := range v {
|
||||
t, err := transform(e, p.Next("[]"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v[i] = t
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func transformMapping(v map[string]any, p tree.Path) (map[string]any, error) {
|
||||
for k, e := range v {
|
||||
t, err := transform(e, p.Next(k))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v[k] = t
|
||||
}
|
||||
return v, nil
|
||||
}
|
53
vendor/github.com/compose-spec/compose-go/v2/transform/dependson.go
generated
vendored
Normal file
53
vendor/github.com/compose-spec/compose-go/v2/transform/dependson.go
generated
vendored
Normal file
@ -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) (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)
|
||||
}
|
||||
}
|
55
vendor/github.com/compose-spec/compose-go/v2/transform/envfile.go
generated
vendored
Normal file
55
vendor/github.com/compose-spec/compose-go/v2/transform/envfile.go
generated
vendored
Normal file
@ -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) (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
|
||||
}
|
36
vendor/github.com/compose-spec/compose-go/v2/transform/extends.go
generated
vendored
Normal file
36
vendor/github.com/compose-spec/compose-go/v2/transform/extends.go
generated
vendored
Normal file
@ -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) (any, error) {
|
||||
switch v := data.(type) {
|
||||
case map[string]any:
|
||||
return transformMapping(v, p)
|
||||
case string:
|
||||
return map[string]any{
|
||||
"service": v,
|
||||
}, nil
|
||||
default:
|
||||
return data, fmt.Errorf("%s: invalid type %T for extends", p, v)
|
||||
}
|
||||
}
|
54
vendor/github.com/compose-spec/compose-go/v2/transform/external.go
generated
vendored
Normal file
54
vendor/github.com/compose-spec/compose-go/v2/transform/external.go
generated
vendored
Normal file
@ -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) (any, error) {
|
||||
if data == nil {
|
||||
return nil, nil
|
||||
}
|
||||
resource, err := transformMapping(data.(map[string]any), p)
|
||||
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
|
||||
}
|
36
vendor/github.com/compose-spec/compose-go/v2/transform/include.go
generated
vendored
Normal file
36
vendor/github.com/compose-spec/compose-go/v2/transform/include.go
generated
vendored
Normal file
@ -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) (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)
|
||||
}
|
||||
}
|
43
vendor/github.com/compose-spec/compose-go/v2/transform/mapping.go
generated
vendored
Normal file
43
vendor/github.com/compose-spec/compose-go/v2/transform/mapping.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
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) (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 {
|
||||
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)
|
||||
}
|
||||
}
|
86
vendor/github.com/compose-spec/compose-go/v2/transform/ports.go
generated
vendored
Normal file
86
vendor/github.com/compose-spec/compose-go/v2/transform/ports.go
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
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/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
func transformPorts(data any, p tree.Path) (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 {
|
||||
return data, 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
|
||||
}
|
36
vendor/github.com/compose-spec/compose-go/v2/transform/secrets.go
generated
vendored
Normal file
36
vendor/github.com/compose-spec/compose-go/v2/transform/secrets.go
generated
vendored
Normal file
@ -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 transformFileMount(data any, p tree.Path) (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)
|
||||
}
|
||||
}
|
41
vendor/github.com/compose-spec/compose-go/v2/transform/services.go
generated
vendored
Normal file
41
vendor/github.com/compose-spec/compose-go/v2/transform/services.go
generated
vendored
Normal file
@ -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) (any, error) {
|
||||
switch value := data.(type) {
|
||||
case map[string]any:
|
||||
return transformMapping(value, p)
|
||||
default:
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
|
||||
func transformServiceNetworks(data any, _ tree.Path) (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
|
||||
}
|
51
vendor/github.com/compose-spec/compose-go/v2/transform/ssh.go
generated
vendored
Normal file
51
vendor/github.com/compose-spec/compose-go/v2/transform/ssh.go
generated
vendored
Normal file
@ -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) (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)
|
||||
}
|
||||
}
|
34
vendor/github.com/compose-spec/compose-go/v2/transform/ulimits.go
generated
vendored
Normal file
34
vendor/github.com/compose-spec/compose-go/v2/transform/ulimits.go
generated
vendored
Normal file
@ -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) (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)
|
||||
}
|
||||
}
|
49
vendor/github.com/compose-spec/compose-go/v2/transform/volume.go
generated
vendored
Normal file
49
vendor/github.com/compose-spec/compose-go/v2/transform/volume.go
generated
vendored
Normal file
@ -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"
|
||||
"path"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/format"
|
||||
"github.com/compose-spec/compose-go/v2/tree"
|
||||
)
|
||||
|
||||
func transformVolumeMount(data any, p tree.Path) (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 {
|
||||
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)
|
||||
}
|
87
vendor/github.com/compose-spec/compose-go/v2/tree/path.go
generated
vendored
Normal file
87
vendor/github.com/compose-spec/compose-go/v2/tree/path.go
generated
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
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 tree
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const pathSeparator = "."
|
||||
|
||||
// PathMatchAll is a token used as part of a Path to match any key at that level
|
||||
// in the nested structure
|
||||
const PathMatchAll = "*"
|
||||
|
||||
// PathMatchList is a token used as part of a Path to match items in a list
|
||||
const PathMatchList = "[]"
|
||||
|
||||
// Path is a dotted path of keys to a value in a nested mapping structure. A *
|
||||
// section in a path will match any key in the mapping structure.
|
||||
type Path string
|
||||
|
||||
// NewPath returns a new Path
|
||||
func NewPath(items ...string) Path {
|
||||
return Path(strings.Join(items, pathSeparator))
|
||||
}
|
||||
|
||||
// Next returns a new path by append part to the current path
|
||||
func (p Path) Next(part string) Path {
|
||||
if p == "" {
|
||||
return Path(part)
|
||||
}
|
||||
part = strings.ReplaceAll(part, pathSeparator, "👻")
|
||||
return Path(string(p) + pathSeparator + part)
|
||||
}
|
||||
|
||||
func (p Path) Parts() []string {
|
||||
return strings.Split(string(p), pathSeparator)
|
||||
}
|
||||
|
||||
func (p Path) Matches(pattern Path) bool {
|
||||
patternParts := pattern.Parts()
|
||||
parts := p.Parts()
|
||||
|
||||
if len(patternParts) != len(parts) {
|
||||
return false
|
||||
}
|
||||
for index, part := range parts {
|
||||
switch patternParts[index] {
|
||||
case PathMatchAll, part:
|
||||
continue
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
48
vendor/github.com/compose-spec/compose-go/v2/types/bytes.go
generated
vendored
Normal file
48
vendor/github.com/compose-spec/compose-go/v2/types/bytes.go
generated
vendored
Normal file
@ -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 types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
// UnitBytes is the bytes type
|
||||
type UnitBytes int64
|
||||
|
||||
// MarshalYAML makes UnitBytes implement yaml.Marshaller
|
||||
func (u UnitBytes) MarshalYAML() (interface{}, error) {
|
||||
return fmt.Sprintf("%d", u), nil
|
||||
}
|
||||
|
||||
// MarshalJSON makes UnitBytes implement json.Marshaler
|
||||
func (u UnitBytes) MarshalJSON() ([]byte, error) {
|
||||
return []byte(fmt.Sprintf(`"%d"`, u)), nil
|
||||
}
|
||||
|
||||
func (u *UnitBytes) DecodeMapstructure(value interface{}) error {
|
||||
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
|
||||
}
|
86
vendor/github.com/compose-spec/compose-go/v2/types/command.go
generated
vendored
Normal file
86
vendor/github.com/compose-spec/compose-go/v2/types/command.go
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
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 "github.com/mattn/go-shellwords"
|
||||
|
||||
// ShellCommand is a string or list of string args.
|
||||
//
|
||||
// When marshaled to YAML, nil command fields will be omitted if `omitempty`
|
||||
// is specified as a struct tag. Explicitly empty commands (i.e. `[]` or
|
||||
// empty string will serialize to an empty array (`[]`).
|
||||
//
|
||||
// When marshaled to JSON, the `omitempty` struct must NOT be specified.
|
||||
// If the command field is nil, it will be serialized as `null`.
|
||||
// Explicitly empty commands (i.e. `[]` or empty string) will serialize to
|
||||
// an empty array (`[]`).
|
||||
//
|
||||
// The distinction between nil and explicitly empty is important to distinguish
|
||||
// between an unset value and a provided, but empty, value, which should be
|
||||
// preserved so that it can override any base value (e.g. container entrypoint).
|
||||
//
|
||||
// The different semantics between YAML and JSON are due to limitations with
|
||||
// JSON marshaling + `omitempty` in the Go stdlib, while gopkg.in/yaml.v3 gives
|
||||
// us more flexibility via the yaml.IsZeroer interface.
|
||||
//
|
||||
// In the future, it might make sense to make fields of this type be
|
||||
// `*ShellCommand` to avoid this situation, but that would constitute a
|
||||
// breaking change.
|
||||
type ShellCommand []string
|
||||
|
||||
// IsZero returns true if the slice is nil.
|
||||
//
|
||||
// Empty (but non-nil) slices are NOT considered zero values.
|
||||
func (s ShellCommand) IsZero() bool {
|
||||
// we do NOT want len(s) == 0, ONLY explicitly nil
|
||||
return s == nil
|
||||
}
|
||||
|
||||
// MarshalYAML returns nil (which will be serialized as `null`) for nil slices
|
||||
// and delegates to the standard marshaller behavior otherwise.
|
||||
//
|
||||
// NOTE: Typically the nil case here is not hit because IsZero has already
|
||||
// short-circuited marshalling, but this ensures that the type serializes
|
||||
// accurately if the `omitempty` struct tag is omitted/forgotten.
|
||||
//
|
||||
// A similar MarshalJSON() implementation is not needed because the Go stdlib
|
||||
// already serializes nil slices to `null`, whereas gopkg.in/yaml.v3 by default
|
||||
// serializes nil slices to `[]`.
|
||||
func (s ShellCommand) MarshalYAML() (interface{}, error) {
|
||||
if s == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return []string(s), nil
|
||||
}
|
||||
|
||||
func (s *ShellCommand) DecodeMapstructure(value interface{}) error {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
cmd, err := shellwords.Parse(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*s = cmd
|
||||
case []interface{}:
|
||||
cmd := make([]string, len(v))
|
||||
for i, s := range v {
|
||||
cmd[i] = s.(string)
|
||||
}
|
||||
*s = cmd
|
||||
}
|
||||
return nil
|
||||
}
|
135
vendor/github.com/compose-spec/compose-go/v2/types/config.go
generated
vendored
Normal file
135
vendor/github.com/compose-spec/compose-go/v2/types/config.go
generated
vendored
Normal file
@ -0,0 +1,135 @@
|
||||
/*
|
||||
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"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
var (
|
||||
// isCaseInsensitiveEnvVars is true on platforms where environment variable names are treated case-insensitively.
|
||||
isCaseInsensitiveEnvVars = (runtime.GOOS == "windows")
|
||||
)
|
||||
|
||||
// ConfigDetails are the details about a group of ConfigFiles
|
||||
type ConfigDetails struct {
|
||||
Version string
|
||||
WorkingDir string
|
||||
ConfigFiles []ConfigFile
|
||||
Environment Mapping
|
||||
}
|
||||
|
||||
// LookupEnv provides a lookup function for environment variables
|
||||
func (cd *ConfigDetails) LookupEnv(key string) (string, bool) {
|
||||
v, ok := cd.Environment[key]
|
||||
if !isCaseInsensitiveEnvVars || ok {
|
||||
return v, ok
|
||||
}
|
||||
// variable names must be treated case-insensitively on some platforms (that is, Windows).
|
||||
// Resolves in this way:
|
||||
// * Return the value if its name matches with the passed name case-sensitively.
|
||||
// * Otherwise, return the value if its lower-cased name matches lower-cased passed name.
|
||||
// * The value is indefinite if multiple variables match.
|
||||
lowerKey := strings.ToLower(key)
|
||||
for k, v := range cd.Environment {
|
||||
if strings.ToLower(k) == lowerKey {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// ConfigFile is a filename and the contents of the file as a Dict
|
||||
type ConfigFile struct {
|
||||
// Filename is the name of the yaml configuration file
|
||||
Filename string
|
||||
// Content is the raw yaml content. Will be loaded from Filename if not set
|
||||
Content []byte
|
||||
// Config if the yaml tree for this config file. Will be parsed from Content if not set
|
||||
Config map[string]interface{}
|
||||
}
|
||||
|
||||
func ToConfigFiles(path []string) (f []ConfigFile) {
|
||||
for _, p := range path {
|
||||
f = append(f, ConfigFile{Filename: p})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Config is a full compose file configuration and model
|
||||
type Config struct {
|
||||
Filename string `yaml:"-" json:"-"`
|
||||
Name string `yaml:"name,omitempty" json:"name,omitempty"`
|
||||
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:",inline" json:"-"`
|
||||
Include []IncludeConfig `yaml:"include,omitempty" json:"include,omitempty"`
|
||||
}
|
||||
|
||||
// Volumes is a map of VolumeConfig
|
||||
type Volumes map[string]VolumeConfig
|
||||
|
||||
// Networks is a map of NetworkConfig
|
||||
type Networks map[string]NetworkConfig
|
||||
|
||||
// Secrets is a map of SecretConfig
|
||||
type Secrets map[string]SecretConfig
|
||||
|
||||
// Configs is a map of ConfigObjConfig
|
||||
type Configs map[string]ConfigObjConfig
|
||||
|
||||
// Extensions is a map of custom extension
|
||||
type Extensions map[string]interface{}
|
||||
|
||||
// MarshalJSON makes Config implement json.Marshaler
|
||||
func (c Config) MarshalJSON() ([]byte, error) {
|
||||
m := map[string]interface{}{
|
||||
"services": c.Services,
|
||||
}
|
||||
|
||||
if len(c.Networks) > 0 {
|
||||
m["networks"] = c.Networks
|
||||
}
|
||||
if len(c.Volumes) > 0 {
|
||||
m["volumes"] = c.Volumes
|
||||
}
|
||||
if len(c.Secrets) > 0 {
|
||||
m["secrets"] = c.Secrets
|
||||
}
|
||||
if len(c.Configs) > 0 {
|
||||
m["configs"] = c.Configs
|
||||
}
|
||||
for k, v := range c.Extensions {
|
||||
m[k] = v
|
||||
}
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
func (e Extensions) Get(name string, target interface{}) (bool, error) {
|
||||
if v, ok := e[name]; ok {
|
||||
err := mapstructure.Decode(v, target)
|
||||
return true, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
38
vendor/github.com/compose-spec/compose-go/v2/types/develop.go
generated
vendored
Normal file
38
vendor/github.com/compose-spec/compose-go/v2/types/develop.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
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
|
||||
|
||||
type DevelopConfig struct {
|
||||
Watch []Trigger `json:"watch,omitempty"`
|
||||
|
||||
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
type WatchAction string
|
||||
|
||||
const (
|
||||
WatchActionSync WatchAction = "sync"
|
||||
WatchActionRebuild WatchAction = "rebuild"
|
||||
WatchActionSyncRestart WatchAction = "sync+restart"
|
||||
)
|
||||
|
||||
type Trigger struct {
|
||||
Path string `json:"path,omitempty"`
|
||||
Action WatchAction `json:"action,omitempty"`
|
||||
Target string `json:"target,omitempty"`
|
||||
Ignore []string `json:"ignore,omitempty"`
|
||||
}
|
52
vendor/github.com/compose-spec/compose-go/v2/types/device.go
generated
vendored
Normal file
52
vendor/github.com/compose-spec/compose-go/v2/types/device.go
generated
vendored
Normal file
@ -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 types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type DeviceRequest struct {
|
||||
Capabilities []string `yaml:"capabilities,omitempty" json:"capabilities,omitempty"`
|
||||
Driver string `yaml:"driver,omitempty" json:"driver,omitempty"`
|
||||
Count DeviceCount `yaml:"count,omitempty" json:"count,omitempty"`
|
||||
IDs []string `yaml:"device_ids,omitempty" json:"device_ids,omitempty"`
|
||||
}
|
||||
|
||||
type DeviceCount int64
|
||||
|
||||
func (c *DeviceCount) DecodeMapstructure(value interface{}) error {
|
||||
switch v := value.(type) {
|
||||
case int:
|
||||
*c = DeviceCount(v)
|
||||
case string:
|
||||
if strings.ToLower(v) == "all" {
|
||||
*c = -1
|
||||
return nil
|
||||
}
|
||||
i, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value %q, the only value allowed is 'all' or a number", v)
|
||||
}
|
||||
*c = DeviceCount(i)
|
||||
default:
|
||||
return fmt.Errorf("invalid type %T for device count", v)
|
||||
}
|
||||
return nil
|
||||
}
|
60
vendor/github.com/compose-spec/compose-go/v2/types/duration.go
generated
vendored
Normal file
60
vendor/github.com/compose-spec/compose-go/v2/types/duration.go
generated
vendored
Normal file
@ -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 types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Duration is a thin wrapper around time.Duration with improved JSON marshalling
|
||||
type Duration time.Duration
|
||||
|
||||
func (d Duration) String() string {
|
||||
return time.Duration(d).String()
|
||||
}
|
||||
|
||||
func (d *Duration) DecodeMapstructure(value interface{}) error {
|
||||
v, err := time.ParseDuration(fmt.Sprint(value))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*d = Duration(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON makes Duration implement json.Marshaler
|
||||
func (d Duration) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(d.String())
|
||||
}
|
||||
|
||||
// MarshalYAML makes Duration implement yaml.Marshaler
|
||||
func (d Duration) MarshalYAML() (interface{}, error) {
|
||||
return d.String(), nil
|
||||
}
|
||||
|
||||
func (d *Duration) UnmarshalJSON(b []byte) error {
|
||||
s := strings.Trim(string(b), "\"")
|
||||
timeDuration, err := time.ParseDuration(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*d = Duration(timeDuration)
|
||||
return nil
|
||||
}
|
46
vendor/github.com/compose-spec/compose-go/v2/types/envfile.go
generated
vendored
Normal file
46
vendor/github.com/compose-spec/compose-go/v2/types/envfile.go
generated
vendored
Normal file
@ -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)
|
||||
}
|
53
vendor/github.com/compose-spec/compose-go/v2/types/healthcheck.go
generated
vendored
Normal file
53
vendor/github.com/compose-spec/compose-go/v2/types/healthcheck.go
generated
vendored
Normal file
@ -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 types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// HealthCheckConfig the healthcheck configuration for a service
|
||||
type HealthCheckConfig struct {
|
||||
Test HealthCheckTest `yaml:"test,omitempty" json:"test,omitempty"`
|
||||
Timeout *Duration `yaml:"timeout,omitempty" json:"timeout,omitempty"`
|
||||
Interval *Duration `yaml:"interval,omitempty" json:"interval,omitempty"`
|
||||
Retries *uint64 `yaml:"retries,omitempty" json:"retries,omitempty"`
|
||||
StartPeriod *Duration `yaml:"start_period,omitempty" json:"start_period,omitempty"`
|
||||
StartInterval *Duration `yaml:"start_interval,omitempty" json:"start_interval,omitempty"`
|
||||
Disable bool `yaml:"disable,omitempty" json:"disable,omitempty"`
|
||||
|
||||
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
// HealthCheckTest is the command run to test the health of a service
|
||||
type HealthCheckTest []string
|
||||
|
||||
func (l *HealthCheckTest) DecodeMapstructure(value interface{}) error {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
*l = []string{"CMD-SHELL", v}
|
||||
case []interface{}:
|
||||
seq := make([]string, len(v))
|
||||
for i, e := range v {
|
||||
seq[i] = e.(string)
|
||||
}
|
||||
*l = seq
|
||||
default:
|
||||
return fmt.Errorf("unexpected value type %T for healthcheck.test", value)
|
||||
}
|
||||
return nil
|
||||
}
|
83
vendor/github.com/compose-spec/compose-go/v2/types/hostList.go
generated
vendored
Normal file
83
vendor/github.com/compose-spec/compose-go/v2/types/hostList.go
generated
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
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
|
||||
|
||||
// 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 {
|
||||
l = append(l, fmt.Sprintf("%s%s%s", k, sep, 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)
|
||||
}
|
||||
|
||||
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 = ""
|
||||
}
|
||||
list[i] = fmt.Sprint(e)
|
||||
}
|
||||
*h = list
|
||||
case []interface{}:
|
||||
*h = decodeMapping(v, "=", ":")
|
||||
default:
|
||||
return fmt.Errorf("unexpected value type %T for mapping", value)
|
||||
}
|
||||
for host, ip := 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)
|
||||
}
|
||||
// Remove brackets from IP addresses (for example "[::1]" -> "::1").
|
||||
if len(ip) > 2 && ip[0] == '[' && ip[len(ip)-1] == ']' {
|
||||
(*h)[host] = ip[1 : len(ip)-1]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
77
vendor/github.com/compose-spec/compose-go/v2/types/labels.go
generated
vendored
Normal file
77
vendor/github.com/compose-spec/compose-go/v2/types/labels.go
generated
vendored
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
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"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Labels is a mapping type for labels
|
||||
type Labels map[string]string
|
||||
|
||||
func (l Labels) Add(key, value string) Labels {
|
||||
if l == nil {
|
||||
l = Labels{}
|
||||
}
|
||||
l[key] = value
|
||||
return l
|
||||
}
|
||||
|
||||
func (l Labels) AsList() []string {
|
||||
s := make([]string, len(l))
|
||||
i := 0
|
||||
for k, v := range l {
|
||||
s[i] = fmt.Sprintf("%s=%s", k, v)
|
||||
i++
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// label value can be a string | number | boolean | null (empty)
|
||||
func labelValue(e interface{}) string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
switch v := e.(type) {
|
||||
case string:
|
||||
return v
|
||||
default:
|
||||
return fmt.Sprint(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Labels) DecodeMapstructure(value interface{}) error {
|
||||
switch v := value.(type) {
|
||||
case map[string]interface{}:
|
||||
labels := make(map[string]string, len(v))
|
||||
for k, e := range v {
|
||||
labels[k] = labelValue(e)
|
||||
}
|
||||
*l = labels
|
||||
case []interface{}:
|
||||
labels := make(map[string]string, len(v))
|
||||
for _, s := range v {
|
||||
k, e, _ := strings.Cut(fmt.Sprint(s), "=")
|
||||
labels[k] = labelValue(e)
|
||||
}
|
||||
*l = labels
|
||||
default:
|
||||
return fmt.Errorf("unexpected value type %T for labels", value)
|
||||
}
|
||||
return nil
|
||||
}
|
217
vendor/github.com/compose-spec/compose-go/v2/types/mapping.go
generated
vendored
Normal file
217
vendor/github.com/compose-spec/compose-go/v2/types/mapping.go
generated
vendored
Normal file
@ -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
|
||||
}
|
42
vendor/github.com/compose-spec/compose-go/v2/types/options.go
generated
vendored
Normal file
42
vendor/github.com/compose-spec/compose-go/v2/types/options.go
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
// Options is a mapping type for options we pass as-is to container runtime
|
||||
type Options map[string]string
|
||||
|
||||
func (d *Options) DecodeMapstructure(value interface{}) error {
|
||||
switch v := value.(type) {
|
||||
case map[string]interface{}:
|
||||
m := make(map[string]string)
|
||||
for key, e := range v {
|
||||
if e == nil {
|
||||
m[key] = ""
|
||||
} else {
|
||||
m[key] = fmt.Sprint(e)
|
||||
}
|
||||
}
|
||||
*d = m
|
||||
case map[string]string:
|
||||
*d = v
|
||||
default:
|
||||
return fmt.Errorf("invalid type %T for options", value)
|
||||
}
|
||||
return nil
|
||||
}
|
616
vendor/github.com/compose-spec/compose-go/v2/types/project.go
generated
vendored
Normal file
616
vendor/github.com/compose-spec/compose-go/v2/types/project.go
generated
vendored
Normal file
@ -0,0 +1,616 @@
|
||||
/*
|
||||
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/v2/dotenv"
|
||||
"github.com/compose-spec/compose-go/v2/utils"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/mitchellh/copystructure"
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
"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
|
||||
|
||||
// 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 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
|
||||
}
|
||||
|
||||
// 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("service %s is disabled", name)
|
||||
}
|
||||
return ServiceConfig{}, fmt.Errorf("no such service: %s", name)
|
||||
}
|
||||
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 {
|
||||
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()
|
||||
for _, p := range profiles {
|
||||
if p == "*" {
|
||||
return newProject, nil
|
||||
}
|
||||
}
|
||||
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) {
|
||||
newProject := p.deepCopy()
|
||||
eg := errgroup.Group{}
|
||||
for i, s := range newProject.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()
|
||||
newProject.Services[idx] = service
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return newProject, 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)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
instance, err := copystructure.Copy(p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return instance.(*Project)
|
||||
}
|
35
vendor/github.com/compose-spec/compose-go/v2/types/services.go
generated
vendored
Normal file
35
vendor/github.com/compose-spec/compose-go/v2/types/services.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
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
|
||||
}
|
73
vendor/github.com/compose-spec/compose-go/v2/types/ssh.go
generated
vendored
Normal file
73
vendor/github.com/compose-spec/compose-go/v2/types/ssh.go
generated
vendored
Normal file
@ -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
|
||||
}
|
57
vendor/github.com/compose-spec/compose-go/v2/types/stringOrList.go
generated
vendored
Normal file
57
vendor/github.com/compose-spec/compose-go/v2/types/stringOrList.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
// StringList is a type for fields that can be a string or list of strings
|
||||
type StringList []string
|
||||
|
||||
func (l *StringList) DecodeMapstructure(value interface{}) error {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
*l = []string{v}
|
||||
case []interface{}:
|
||||
list := make([]string, len(v))
|
||||
for i, e := range v {
|
||||
list[i] = e.(string)
|
||||
}
|
||||
*l = list
|
||||
default:
|
||||
return fmt.Errorf("invalid type %T for string list", value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StringOrNumberList is a type for fields that can be a list of strings or numbers
|
||||
type StringOrNumberList []string
|
||||
|
||||
func (l *StringOrNumberList) DecodeMapstructure(value interface{}) error {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
*l = []string{v}
|
||||
case []interface{}:
|
||||
list := make([]string, len(v))
|
||||
for i, e := range v {
|
||||
list[i] = fmt.Sprint(e)
|
||||
}
|
||||
*l = list
|
||||
default:
|
||||
return fmt.Errorf("invalid type %T for string list", value)
|
||||
}
|
||||
return nil
|
||||
}
|
756
vendor/github.com/compose-spec/compose-go/v2/types/types.go
generated
vendored
Normal file
756
vendor/github.com/compose-spec/compose-go/v2/types/types.go
generated
vendored
Normal file
@ -0,0 +1,756 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/mitchellh/copystructure"
|
||||
)
|
||||
|
||||
// ServiceConfig is the configuration of one service
|
||||
type ServiceConfig struct {
|
||||
Name string `yaml:"name,omitempty" json:"-"`
|
||||
Profiles []string `yaml:"profiles,omitempty" json:"profiles,omitempty"`
|
||||
|
||||
Annotations Mapping `yaml:"annotations,omitempty" json:"annotations,omitempty"`
|
||||
Attach *bool `yaml:"attach,omitempty" json:"attach,omitempty"`
|
||||
Build *BuildConfig `yaml:"build,omitempty" json:"build,omitempty"`
|
||||
Develop *DevelopConfig `yaml:"develop,omitempty" json:"develop,omitempty"`
|
||||
BlkioConfig *BlkioConfig `yaml:"blkio_config,omitempty" json:"blkio_config,omitempty"`
|
||||
CapAdd []string `yaml:"cap_add,omitempty" json:"cap_add,omitempty"`
|
||||
CapDrop []string `yaml:"cap_drop,omitempty" json:"cap_drop,omitempty"`
|
||||
CgroupParent string `yaml:"cgroup_parent,omitempty" json:"cgroup_parent,omitempty"`
|
||||
Cgroup string `yaml:"cgroup,omitempty" json:"cgroup,omitempty"`
|
||||
CPUCount int64 `yaml:"cpu_count,omitempty" json:"cpu_count,omitempty"`
|
||||
CPUPercent float32 `yaml:"cpu_percent,omitempty" json:"cpu_percent,omitempty"`
|
||||
CPUPeriod int64 `yaml:"cpu_period,omitempty" json:"cpu_period,omitempty"`
|
||||
CPUQuota int64 `yaml:"cpu_quota,omitempty" json:"cpu_quota,omitempty"`
|
||||
CPURTPeriod int64 `yaml:"cpu_rt_period,omitempty" json:"cpu_rt_period,omitempty"`
|
||||
CPURTRuntime int64 `yaml:"cpu_rt_runtime,omitempty" json:"cpu_rt_runtime,omitempty"`
|
||||
CPUS float32 `yaml:"cpus,omitempty" json:"cpus,omitempty"`
|
||||
CPUSet string `yaml:"cpuset,omitempty" json:"cpuset,omitempty"`
|
||||
CPUShares int64 `yaml:"cpu_shares,omitempty" json:"cpu_shares,omitempty"`
|
||||
|
||||
// Command for the service containers.
|
||||
// If set, overrides COMMAND from the image.
|
||||
//
|
||||
// Set to `[]` or an empty string to clear the command from the image.
|
||||
Command ShellCommand `yaml:"command,omitempty" json:"command"` // NOTE: we can NOT omitempty for JSON! see ShellCommand type for details.
|
||||
|
||||
Configs []ServiceConfigObjConfig `yaml:"configs,omitempty" json:"configs,omitempty"`
|
||||
ContainerName string `yaml:"container_name,omitempty" json:"container_name,omitempty"`
|
||||
CredentialSpec *CredentialSpecConfig `yaml:"credential_spec,omitempty" json:"credential_spec,omitempty"`
|
||||
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"`
|
||||
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"`
|
||||
Dockerfile string `yaml:"dockerfile,omitempty" json:"dockerfile,omitempty"`
|
||||
DomainName string `yaml:"domainname,omitempty" json:"domainname,omitempty"`
|
||||
|
||||
// Entrypoint for the service containers.
|
||||
// If set, overrides ENTRYPOINT from the image.
|
||||
//
|
||||
// Set to `[]` or an empty string to clear the entrypoint from the image.
|
||||
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"`
|
||||
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"`
|
||||
ExtraHosts HostsList `yaml:"extra_hosts,omitempty" json:"extra_hosts,omitempty"`
|
||||
GroupAdd []string `yaml:"group_add,omitempty" json:"group_add,omitempty"`
|
||||
Hostname string `yaml:"hostname,omitempty" json:"hostname,omitempty"`
|
||||
HealthCheck *HealthCheckConfig `yaml:"healthcheck,omitempty" json:"healthcheck,omitempty"`
|
||||
Image string `yaml:"image,omitempty" json:"image,omitempty"`
|
||||
Init *bool `yaml:"init,omitempty" json:"init,omitempty"`
|
||||
Ipc string `yaml:"ipc,omitempty" json:"ipc,omitempty"`
|
||||
Isolation string `yaml:"isolation,omitempty" json:"isolation,omitempty"`
|
||||
Labels Labels `yaml:"labels,omitempty" json:"labels,omitempty"`
|
||||
CustomLabels Labels `yaml:"-" json:"-"`
|
||||
Links []string `yaml:"links,omitempty" json:"links,omitempty"`
|
||||
Logging *LoggingConfig `yaml:"logging,omitempty" json:"logging,omitempty"`
|
||||
LogDriver string `yaml:"log_driver,omitempty" json:"log_driver,omitempty"`
|
||||
LogOpt map[string]string `yaml:"log_opt,omitempty" json:"log_opt,omitempty"`
|
||||
MemLimit UnitBytes `yaml:"mem_limit,omitempty" json:"mem_limit,omitempty"`
|
||||
MemReservation UnitBytes `yaml:"mem_reservation,omitempty" json:"mem_reservation,omitempty"`
|
||||
MemSwapLimit UnitBytes `yaml:"memswap_limit,omitempty" json:"memswap_limit,omitempty"`
|
||||
MemSwappiness UnitBytes `yaml:"mem_swappiness,omitempty" json:"mem_swappiness,omitempty"`
|
||||
MacAddress string `yaml:"mac_address,omitempty" json:"mac_address,omitempty"`
|
||||
Net string `yaml:"net,omitempty" json:"net,omitempty"`
|
||||
NetworkMode string `yaml:"network_mode,omitempty" json:"network_mode,omitempty"`
|
||||
Networks map[string]*ServiceNetworkConfig `yaml:"networks,omitempty" json:"networks,omitempty"`
|
||||
OomKillDisable bool `yaml:"oom_kill_disable,omitempty" json:"oom_kill_disable,omitempty"`
|
||||
OomScoreAdj int64 `yaml:"oom_score_adj,omitempty" json:"oom_score_adj,omitempty"`
|
||||
Pid string `yaml:"pid,omitempty" json:"pid,omitempty"`
|
||||
PidsLimit int64 `yaml:"pids_limit,omitempty" json:"pids_limit,omitempty"`
|
||||
Platform string `yaml:"platform,omitempty" json:"platform,omitempty"`
|
||||
Ports []ServicePortConfig `yaml:"ports,omitempty" json:"ports,omitempty"`
|
||||
Privileged bool `yaml:"privileged,omitempty" json:"privileged,omitempty"`
|
||||
PullPolicy string `yaml:"pull_policy,omitempty" json:"pull_policy,omitempty"`
|
||||
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"`
|
||||
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"`
|
||||
Ulimits map[string]*UlimitsConfig `yaml:"ulimits,omitempty" json:"ulimits,omitempty"`
|
||||
User string `yaml:"user,omitempty" json:"user,omitempty"`
|
||||
UserNSMode string `yaml:"userns_mode,omitempty" json:"userns_mode,omitempty"`
|
||||
Uts string `yaml:"uts,omitempty" json:"uts,omitempty"`
|
||||
VolumeDriver string `yaml:"volume_driver,omitempty" json:"volume_driver,omitempty"`
|
||||
Volumes []ServiceVolumeConfig `yaml:"volumes,omitempty" json:"volumes,omitempty"`
|
||||
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,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
// MarshalYAML makes ServiceConfig implement yaml.Marshaller
|
||||
func (s ServiceConfig) MarshalYAML() (interface{}, error) {
|
||||
type t ServiceConfig
|
||||
value := t(s)
|
||||
value.Name = "" // set during map to slice conversion, not part of the yaml representation
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// NetworksByPriority return the service networks IDs sorted according to Priority
|
||||
func (s *ServiceConfig) NetworksByPriority() []string {
|
||||
type key struct {
|
||||
name string
|
||||
priority int
|
||||
}
|
||||
var keys []key
|
||||
for k, v := range s.Networks {
|
||||
priority := 0
|
||||
if v != nil {
|
||||
priority = v.Priority
|
||||
}
|
||||
keys = append(keys, key{
|
||||
name: k,
|
||||
priority: priority,
|
||||
})
|
||||
}
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
return keys[i].priority > keys[j].priority
|
||||
})
|
||||
var sorted []string
|
||||
for _, k := range keys {
|
||||
sorted = append(sorted, k.name)
|
||||
}
|
||||
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 {
|
||||
instance, err := copystructure.Copy(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return instance.(*ServiceConfig)
|
||||
}
|
||||
|
||||
const (
|
||||
// PullPolicyAlways always pull images
|
||||
PullPolicyAlways = "always"
|
||||
// PullPolicyNever never pull images
|
||||
PullPolicyNever = "never"
|
||||
// PullPolicyIfNotPresent pull missing images
|
||||
PullPolicyIfNotPresent = "if_not_present"
|
||||
// PullPolicyMissing pull missing images
|
||||
PullPolicyMissing = "missing"
|
||||
// PullPolicyBuild force building images
|
||||
PullPolicyBuild = "build"
|
||||
)
|
||||
|
||||
const (
|
||||
// RestartPolicyAlways always restart the container if it stops
|
||||
RestartPolicyAlways = "always"
|
||||
// RestartPolicyOnFailure restart the container if it exits due to an error
|
||||
RestartPolicyOnFailure = "on-failure"
|
||||
// RestartPolicyNo do not automatically restart the container
|
||||
RestartPolicyNo = "no"
|
||||
// RestartPolicyUnlessStopped always restart the container unless the container is stopped (manually or otherwise)
|
||||
RestartPolicyUnlessStopped = "unless-stopped"
|
||||
)
|
||||
|
||||
const (
|
||||
// ServicePrefix is the prefix for references pointing to a service
|
||||
ServicePrefix = "service:"
|
||||
// ContainerPrefix is the prefix for references pointing to a container
|
||||
ContainerPrefix = "container:"
|
||||
|
||||
// NetworkModeServicePrefix is the prefix for network_mode pointing to a service
|
||||
// Deprecated prefer ServicePrefix
|
||||
NetworkModeServicePrefix = ServicePrefix
|
||||
// NetworkModeContainerPrefix is the prefix for network_mode pointing to a container
|
||||
// Deprecated prefer ContainerPrefix
|
||||
NetworkModeContainerPrefix = ContainerPrefix
|
||||
)
|
||||
|
||||
// GetDependencies retrieves all services this service depends on
|
||||
func (s ServiceConfig) GetDependencies() []string {
|
||||
var dependencies []string
|
||||
for service := range s.DependsOn {
|
||||
dependencies = append(dependencies, service)
|
||||
}
|
||||
return dependencies
|
||||
}
|
||||
|
||||
// GetDependents retrieves all services which depend on this service
|
||||
func (s ServiceConfig) GetDependents(p *Project) []string {
|
||||
var dependent []string
|
||||
for _, service := range p.Services {
|
||||
for name := range service.DependsOn {
|
||||
if name == s.Name {
|
||||
dependent = append(dependent, service.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return dependent
|
||||
}
|
||||
|
||||
// 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"`
|
||||
Args MappingWithEquals `yaml:"args,omitempty" json:"args,omitempty"`
|
||||
SSH SSHConfig `yaml:"ssh,omitempty" json:"ssh,omitempty"`
|
||||
Labels Labels `yaml:"labels,omitempty" json:"labels,omitempty"`
|
||||
CacheFrom StringList `yaml:"cache_from,omitempty" json:"cache_from,omitempty"`
|
||||
CacheTo StringList `yaml:"cache_to,omitempty" json:"cache_to,omitempty"`
|
||||
NoCache bool `yaml:"no_cache,omitempty" json:"no_cache,omitempty"`
|
||||
AdditionalContexts Mapping `yaml:"additional_contexts,omitempty" json:"additional_contexts,omitempty"`
|
||||
Pull bool `yaml:"pull,omitempty" json:"pull,omitempty"`
|
||||
ExtraHosts HostsList `yaml:"extra_hosts,omitempty" json:"extra_hosts,omitempty"`
|
||||
Isolation string `yaml:"isolation,omitempty" json:"isolation,omitempty"`
|
||||
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,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
// BlkioConfig define blkio config
|
||||
type BlkioConfig struct {
|
||||
Weight uint16 `yaml:"weight,omitempty" json:"weight,omitempty"`
|
||||
WeightDevice []WeightDevice `yaml:"weight_device,omitempty" json:"weight_device,omitempty"`
|
||||
DeviceReadBps []ThrottleDevice `yaml:"device_read_bps,omitempty" json:"device_read_bps,omitempty"`
|
||||
DeviceReadIOps []ThrottleDevice `yaml:"device_read_iops,omitempty" json:"device_read_iops,omitempty"`
|
||||
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,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
// WeightDevice is a structure that holds device:weight pair
|
||||
type WeightDevice struct {
|
||||
Path string
|
||||
Weight uint16
|
||||
|
||||
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
// ThrottleDevice is a structure that holds device:rate_per_second pair
|
||||
type ThrottleDevice struct {
|
||||
Path string
|
||||
Rate UnitBytes
|
||||
|
||||
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
|
||||
|
||||
// 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,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
// DeployConfig the deployment configuration for a service
|
||||
type DeployConfig struct {
|
||||
Mode string `yaml:"mode,omitempty" json:"mode,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"`
|
||||
Resources Resources `yaml:"resources,omitempty" json:"resources,omitempty"`
|
||||
RestartPolicy *RestartPolicy `yaml:"restart_policy,omitempty" json:"restart_policy,omitempty"`
|
||||
Placement Placement `yaml:"placement,omitempty" json:"placement,omitempty"`
|
||||
EndpointMode string `yaml:"endpoint_mode,omitempty" json:"endpoint_mode,omitempty"`
|
||||
|
||||
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
// UpdateConfig the service update configuration
|
||||
type UpdateConfig struct {
|
||||
Parallelism *uint64 `yaml:"parallelism,omitempty" json:"parallelism,omitempty"`
|
||||
Delay Duration `yaml:"delay,omitempty" json:"delay,omitempty"`
|
||||
FailureAction string `yaml:"failure_action,omitempty" json:"failure_action,omitempty"`
|
||||
Monitor Duration `yaml:"monitor,omitempty" json:"monitor,omitempty"`
|
||||
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,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
// Resources the resource limits and reservations
|
||||
type Resources struct {
|
||||
Limits *Resource `yaml:"limits,omitempty" json:"limits,omitempty"`
|
||||
Reservations *Resource `yaml:"reservations,omitempty" json:"reservations,omitempty"`
|
||||
|
||||
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"`
|
||||
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,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
// GenericResource represents a "user defined" resource which can
|
||||
// only be an integer (e.g: SSD=3) for a service
|
||||
type GenericResource struct {
|
||||
DiscreteResourceSpec *DiscreteGenericResource `yaml:"discrete_resource_spec,omitempty" json:"discrete_resource_spec,omitempty"`
|
||||
|
||||
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
// DiscreteGenericResource represents a "user defined" resource which is defined
|
||||
// as an integer
|
||||
// "Kind" is used to describe the Kind of a resource (e.g: "GPU", "FPGA", "SSD", ...)
|
||||
// Value is used to count the resource (SSD=5, HDD=3, ...)
|
||||
type DiscreteGenericResource struct {
|
||||
Kind string `json:"kind"`
|
||||
Value int64 `json:"value"`
|
||||
|
||||
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
// RestartPolicy the service restart policy
|
||||
type RestartPolicy struct {
|
||||
Condition string `yaml:"condition,omitempty" json:"condition,omitempty"`
|
||||
Delay *Duration `yaml:"delay,omitempty" json:"delay,omitempty"`
|
||||
MaxAttempts *uint64 `yaml:"max_attempts,omitempty" json:"max_attempts,omitempty"`
|
||||
Window *Duration `yaml:"window,omitempty" json:"window,omitempty"`
|
||||
|
||||
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
// Placement constraints for the service
|
||||
type Placement struct {
|
||||
Constraints []string `yaml:"constraints,omitempty" json:"constraints,omitempty"`
|
||||
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,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,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
// ServiceNetworkConfig is the network configuration for a service
|
||||
type ServiceNetworkConfig struct {
|
||||
Priority int `yaml:"priority,omitempty" json:"priority,omitempty"`
|
||||
Aliases []string `yaml:"aliases,omitempty" json:"aliases,omitempty"`
|
||||
Ipv4Address string `yaml:"ipv4_address,omitempty" json:"ipv4_address,omitempty"`
|
||||
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"`
|
||||
|
||||
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"`
|
||||
|
||||
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
// ParsePortConfig parse short syntax for service port configuration
|
||||
func ParsePortConfig(value string) ([]ServicePortConfig, error) {
|
||||
var portConfigs []ServicePortConfig
|
||||
ports, portBindings, err := nat.ParsePortSpecs([]string{value})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We need to sort the key of the ports to make sure it is consistent
|
||||
keys := []string{}
|
||||
for port := range ports {
|
||||
keys = append(keys, string(port))
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, key := range keys {
|
||||
port := nat.Port(key)
|
||||
converted, err := convertPortToPortConfig(port, portBindings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
portConfigs = append(portConfigs, converted...)
|
||||
}
|
||||
return portConfigs, nil
|
||||
}
|
||||
|
||||
func convertPortToPortConfig(port nat.Port, portBindings map[nat.Port][]nat.PortBinding) ([]ServicePortConfig, error) {
|
||||
var portConfigs []ServicePortConfig
|
||||
for _, binding := range portBindings[port] {
|
||||
portConfigs = append(portConfigs, ServicePortConfig{
|
||||
HostIP: binding.HostIP,
|
||||
Protocol: strings.ToLower(port.Proto()),
|
||||
Target: uint32(port.Int()),
|
||||
Published: binding.HostPort,
|
||||
Mode: "ingress",
|
||||
})
|
||||
}
|
||||
return portConfigs, nil
|
||||
}
|
||||
|
||||
// ServiceVolumeConfig are references to a volume used by a service
|
||||
type ServiceVolumeConfig struct {
|
||||
Type string `yaml:"type,omitempty" json:"type,omitempty"`
|
||||
Source string `yaml:"source,omitempty" json:"source,omitempty"`
|
||||
Target string `yaml:"target,omitempty" json:"target,omitempty"`
|
||||
ReadOnly bool `yaml:"read_only,omitempty" json:"read_only,omitempty"`
|
||||
Consistency string `yaml:"consistency,omitempty" json:"consistency,omitempty"`
|
||||
Bind *ServiceVolumeBind `yaml:"bind,omitempty" json:"bind,omitempty"`
|
||||
Volume *ServiceVolumeVolume `yaml:"volume,omitempty" json:"volume,omitempty"`
|
||||
Tmpfs *ServiceVolumeTmpfs `yaml:"tmpfs,omitempty" json:"tmpfs,omitempty"`
|
||||
|
||||
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
// String render ServiceVolumeConfig as a volume string, one can parse back using loader.ParseVolume
|
||||
func (s ServiceVolumeConfig) String() string {
|
||||
access := "rw"
|
||||
if s.ReadOnly {
|
||||
access = "ro"
|
||||
}
|
||||
options := []string{access}
|
||||
if s.Bind != nil && s.Bind.SELinux != "" {
|
||||
options = append(options, s.Bind.SELinux)
|
||||
}
|
||||
if s.Bind != nil && s.Bind.Propagation != "" {
|
||||
options = append(options, s.Bind.Propagation)
|
||||
}
|
||||
if s.Volume != nil && s.Volume.NoCopy {
|
||||
options = append(options, "nocopy")
|
||||
}
|
||||
return fmt.Sprintf("%s:%s:%s", s.Source, s.Target, strings.Join(options, ","))
|
||||
}
|
||||
|
||||
const (
|
||||
// VolumeTypeBind is the type for mounting host dir
|
||||
VolumeTypeBind = "bind"
|
||||
// VolumeTypeVolume is the type for remote storage volumes
|
||||
VolumeTypeVolume = "volume"
|
||||
// VolumeTypeTmpfs is the type for mounting tmpfs
|
||||
VolumeTypeTmpfs = "tmpfs"
|
||||
// VolumeTypeNamedPipe is the type for mounting Windows named pipes
|
||||
VolumeTypeNamedPipe = "npipe"
|
||||
// VolumeTypeCluster is the type for mounting container storage interface (CSI) volumes
|
||||
VolumeTypeCluster = "cluster"
|
||||
|
||||
// SElinuxShared share the volume content
|
||||
SElinuxShared = "z"
|
||||
// SElinuxUnshared label content as private unshared
|
||||
SElinuxUnshared = "Z"
|
||||
)
|
||||
|
||||
// ServiceVolumeBind are options for a service volume of type bind
|
||||
type ServiceVolumeBind struct {
|
||||
SELinux string `yaml:"selinux,omitempty" json:"selinux,omitempty"`
|
||||
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,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
// SELinux represents the SELinux re-labeling options.
|
||||
const (
|
||||
// SELinuxShared option indicates that the bind mount content is shared among multiple containers
|
||||
SELinuxShared string = "z"
|
||||
// SELinuxPrivate option indicates that the bind mount content is private and unshared
|
||||
SELinuxPrivate string = "Z"
|
||||
)
|
||||
|
||||
// Propagation represents the propagation of a mount.
|
||||
const (
|
||||
// PropagationRPrivate RPRIVATE
|
||||
PropagationRPrivate string = "rprivate"
|
||||
// PropagationPrivate PRIVATE
|
||||
PropagationPrivate string = "private"
|
||||
// PropagationRShared RSHARED
|
||||
PropagationRShared string = "rshared"
|
||||
// PropagationShared SHARED
|
||||
PropagationShared string = "shared"
|
||||
// PropagationRSlave RSLAVE
|
||||
PropagationRSlave string = "rslave"
|
||||
// PropagationSlave SLAVE
|
||||
PropagationSlave string = "slave"
|
||||
)
|
||||
|
||||
// ServiceVolumeVolume are options for a service volume of type volume
|
||||
type ServiceVolumeVolume struct {
|
||||
NoCopy bool `yaml:"nocopy,omitempty" json:"nocopy,omitempty"`
|
||||
|
||||
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
// ServiceVolumeTmpfs are options for a service volume of type tmpfs
|
||||
type ServiceVolumeTmpfs struct {
|
||||
Size UnitBytes `yaml:"size,omitempty" json:"size,omitempty"`
|
||||
|
||||
Mode uint32 `yaml:"mode,omitempty" json:"mode,omitempty"`
|
||||
|
||||
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
// FileReferenceConfig for a reference to a swarm file object
|
||||
type FileReferenceConfig struct {
|
||||
Source string `yaml:"source,omitempty" json:"source,omitempty"`
|
||||
Target string `yaml:"target,omitempty" json:"target,omitempty"`
|
||||
UID string `yaml:"uid,omitempty" json:"uid,omitempty"`
|
||||
GID string `yaml:"gid,omitempty" json:"gid,omitempty"`
|
||||
Mode *uint32 `yaml:"mode,omitempty" json:"mode,omitempty"`
|
||||
|
||||
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
// ServiceConfigObjConfig is the config obj configuration for a service
|
||||
type ServiceConfigObjConfig FileReferenceConfig
|
||||
|
||||
// ServiceSecretConfig is the secret configuration for a service
|
||||
type ServiceSecretConfig FileReferenceConfig
|
||||
|
||||
// UlimitsConfig the ulimit configuration
|
||||
type UlimitsConfig struct {
|
||||
Single int `yaml:"single,omitempty" json:"single,omitempty"`
|
||||
Soft int `yaml:"soft,omitempty" json:"soft,omitempty"`
|
||||
Hard int `yaml:"hard,omitempty" json:"hard,omitempty"`
|
||||
|
||||
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
|
||||
func (u *UlimitsConfig) MarshalYAML() (interface{}, error) {
|
||||
if u.Single != 0 {
|
||||
return u.Single, nil
|
||||
}
|
||||
return struct {
|
||||
Soft int
|
||||
Hard int
|
||||
}{
|
||||
Soft: u.Soft,
|
||||
Hard: u.Hard,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MarshalJSON makes UlimitsConfig implement json.Marshaller
|
||||
func (u *UlimitsConfig) MarshalJSON() ([]byte, error) {
|
||||
if u.Single != 0 {
|
||||
return json.Marshal(u.Single)
|
||||
}
|
||||
// Pass as a value to avoid re-entering this method and use the default implementation
|
||||
return json.Marshal(*u)
|
||||
}
|
||||
|
||||
// NetworkConfig for a network
|
||||
type NetworkConfig struct {
|
||||
Name string `yaml:"name,omitempty" json:"name,omitempty"`
|
||||
Driver string `yaml:"driver,omitempty" json:"driver,omitempty"`
|
||||
DriverOpts Options `yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"`
|
||||
Ipam IPAMConfig `yaml:"ipam,omitempty" json:"ipam,omitempty"`
|
||||
External External `yaml:"external,omitempty" json:"external,omitempty"`
|
||||
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,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,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:"-"`
|
||||
}
|
||||
|
||||
// VolumeConfig for a volume
|
||||
type VolumeConfig struct {
|
||||
Name string `yaml:"name,omitempty" json:"name,omitempty"`
|
||||
Driver string `yaml:"driver,omitempty" json:"driver,omitempty"`
|
||||
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,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
// External identifies a Volume or Network as a reference to a resource that is
|
||||
// not managed, and should already exist.
|
||||
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,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
// FileObjectConfig is a config type for a file used by a service
|
||||
type FileObjectConfig struct {
|
||||
Name string `yaml:"name,omitempty" json:"name,omitempty"`
|
||||
File string `yaml:"file,omitempty" json:"file,omitempty"`
|
||||
Environment string `yaml:"environment,omitempty" json:"environment,omitempty"`
|
||||
Content string `yaml:"content,omitempty" json:"content,omitempty"`
|
||||
External External `yaml:"external,omitempty" json:"external,omitempty"`
|
||||
Labels Labels `yaml:"labels,omitempty" json:"labels,omitempty"`
|
||||
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,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
const (
|
||||
// ServiceConditionCompletedSuccessfully is the type for waiting until a service has completed successfully (exit code 0).
|
||||
ServiceConditionCompletedSuccessfully = "service_completed_successfully"
|
||||
|
||||
// ServiceConditionHealthy is the type for waiting until a service is healthy.
|
||||
ServiceConditionHealthy = "service_healthy"
|
||||
|
||||
// ServiceConditionStarted is the type for waiting until a service has started (default).
|
||||
ServiceConditionStarted = "service_started"
|
||||
)
|
||||
|
||||
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,omitempty" json:"-"`
|
||||
Required bool `yaml:"required" json:"required"`
|
||||
}
|
||||
|
||||
type ExtendsConfig struct {
|
||||
File string `yaml:"file,omitempty" json:"file,omitempty"`
|
||||
Service string `yaml:"service,omitempty" json:"service,omitempty"`
|
||||
}
|
||||
|
||||
// SecretConfig for a secret
|
||||
type SecretConfig FileObjectConfig
|
||||
|
||||
// ConfigObjConfig is the config for the swarm "Config" object
|
||||
type ConfigObjConfig FileObjectConfig
|
||||
|
||||
type IncludeConfig struct {
|
||||
Path StringList `yaml:"path,omitempty" json:"path,omitempty"`
|
||||
ProjectDirectory string `yaml:"project_directory,omitempty" json:"project_directory,omitempty"`
|
||||
EnvFile StringList `yaml:"env_file,omitempty" json:"env_file,omitempty"`
|
||||
}
|
53
vendor/github.com/compose-spec/compose-go/v2/utils/collectionutils.go
generated
vendored
Normal file
53
vendor/github.com/compose-spec/compose-go/v2/utils/collectionutils.go
generated
vendored
Normal file
@ -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 utils
|
||||
|
||||
import (
|
||||
"golang.org/x/exp/constraints"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func MapKeys[T constraints.Ordered, U any](theMap map[T]U) []T {
|
||||
result := maps.Keys(theMap)
|
||||
slices.Sort(result)
|
||||
return result
|
||||
}
|
||||
|
||||
func MapsAppend[T comparable, U any](target map[T]U, source map[T]U) map[T]U {
|
||||
if target == nil {
|
||||
return source
|
||||
}
|
||||
if source == nil {
|
||||
return target
|
||||
}
|
||||
for key, value := range source {
|
||||
if _, ok := target[key]; !ok {
|
||||
target[key] = value
|
||||
}
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
func ArrayContains[T comparable](source []T, toCheck []T) bool {
|
||||
for _, value := range toCheck {
|
||||
if !slices.Contains(source, value) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
95
vendor/github.com/compose-spec/compose-go/v2/utils/set.go
generated
vendored
Normal file
95
vendor/github.com/compose-spec/compose-go/v2/utils/set.go
generated
vendored
Normal file
@ -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
|
||||
}
|
48
vendor/github.com/compose-spec/compose-go/v2/utils/stringutils.go
generated
vendored
Normal file
48
vendor/github.com/compose-spec/compose-go/v2/utils/stringutils.go
generated
vendored
Normal file
@ -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 utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StringToBool converts a string to a boolean ignoring errors
|
||||
func StringToBool(s string) bool {
|
||||
b, _ := strconv.ParseBool(strings.ToLower(strings.TrimSpace(s)))
|
||||
return b
|
||||
}
|
||||
|
||||
// GetAsEqualsMap split key=value formatted strings into a key : value map
|
||||
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]
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// GetAsEqualsMap format a key : value map into key=value strings
|
||||
func GetAsStringList(em map[string]string) []string {
|
||||
m := make([]string, 0, len(em))
|
||||
for k, v := range em {
|
||||
m = append(m, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
return m
|
||||
}
|
49
vendor/github.com/compose-spec/compose-go/v2/validation/external.go
generated
vendored
Normal file
49
vendor/github.com/compose-spec/compose-go/v2/validation/external.go
generated
vendored
Normal file
@ -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
|
||||
}
|
96
vendor/github.com/compose-spec/compose-go/v2/validation/validation.go
generated
vendored
Normal file
96
vendor/github.com/compose-spec/compose-go/v2/validation/validation.go
generated
vendored
Normal file
@ -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
|
||||
}
|
39
vendor/github.com/compose-spec/compose-go/v2/validation/volume.go
generated
vendored
Normal file
39
vendor/github.com/compose-spec/compose-go/v2/validation/volume.go
generated
vendored
Normal file
@ -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
|
||||
}
|
Reference in New Issue
Block a user