mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-01 00:23:56 +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
	 CrazyMax
					CrazyMax