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:
		
							
								
								
									
										167
									
								
								vendor/github.com/compose-spec/compose-go/loader/include.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										167
									
								
								vendor/github.com/compose-spec/compose-go/loader/include.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,167 +0,0 @@ | ||||
| /* | ||||
|    Copyright 2020 The Compose Specification Authors. | ||||
|  | ||||
|    Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|    you may not use this file except in compliance with the License. | ||||
|    You may obtain a copy of the License at | ||||
|  | ||||
|        http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|    Unless required by applicable law or agreed to in writing, software | ||||
|    distributed under the License is distributed on an "AS IS" BASIS, | ||||
|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|    See the License for the specific language governing permissions and | ||||
|    limitations under the License. | ||||
| */ | ||||
|  | ||||
| package loader | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"path/filepath" | ||||
| 	"reflect" | ||||
|  | ||||
| 	"github.com/compose-spec/compose-go/dotenv" | ||||
| 	interp "github.com/compose-spec/compose-go/interpolation" | ||||
| 	"github.com/compose-spec/compose-go/types" | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
|  | ||||
| // LoadIncludeConfig parse the require config from raw yaml | ||||
| func LoadIncludeConfig(source []interface{}) ([]types.IncludeConfig, error) { | ||||
| 	var requires []types.IncludeConfig | ||||
| 	err := Transform(source, &requires) | ||||
| 	return requires, err | ||||
| } | ||||
|  | ||||
| var transformIncludeConfig TransformerFunc = func(data interface{}) (interface{}, error) { | ||||
| 	switch value := data.(type) { | ||||
| 	case string: | ||||
| 		return map[string]interface{}{"path": value}, nil | ||||
| 	case map[string]interface{}: | ||||
| 		return value, nil | ||||
| 	default: | ||||
| 		return data, errors.Errorf("invalid type %T for `include` configuration", value) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func loadInclude(ctx context.Context, filename string, configDetails types.ConfigDetails, model *types.Config, options *Options, loaded []string) (*types.Config, map[string][]types.IncludeConfig, error) { | ||||
| 	included := make(map[string][]types.IncludeConfig) | ||||
| 	for _, r := range model.Include { | ||||
| 		included[filename] = append(included[filename], r) | ||||
|  | ||||
| 		for i, p := range r.Path { | ||||
| 			for _, loader := range options.ResourceLoaders { | ||||
| 				if loader.Accept(p) { | ||||
| 					path, err := loader.Load(ctx, p) | ||||
| 					if err != nil { | ||||
| 						return nil, nil, err | ||||
| 					} | ||||
| 					p = path | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 			r.Path[i] = absPath(configDetails.WorkingDir, p) | ||||
| 		} | ||||
| 		if r.ProjectDirectory == "" { | ||||
| 			r.ProjectDirectory = filepath.Dir(r.Path[0]) | ||||
| 		} | ||||
|  | ||||
| 		loadOptions := options.clone() | ||||
| 		loadOptions.SetProjectName(model.Name, true) | ||||
| 		loadOptions.ResolvePaths = true | ||||
| 		loadOptions.SkipNormalization = true | ||||
| 		loadOptions.SkipConsistencyCheck = true | ||||
|  | ||||
| 		envFromFile, err := dotenv.GetEnvFromFile(configDetails.Environment, r.ProjectDirectory, r.EnvFile) | ||||
| 		if err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
|  | ||||
| 		config := types.ConfigDetails{ | ||||
| 			WorkingDir:  r.ProjectDirectory, | ||||
| 			ConfigFiles: types.ToConfigFiles(r.Path), | ||||
| 			Environment: configDetails.Environment.Clone().Merge(envFromFile), | ||||
| 		} | ||||
| 		loadOptions.Interpolate = &interp.Options{ | ||||
| 			Substitute:      options.Interpolate.Substitute, | ||||
| 			LookupValue:     config.LookupEnv, | ||||
| 			TypeCastMapping: options.Interpolate.TypeCastMapping, | ||||
| 		} | ||||
| 		imported, err := load(ctx, config, loadOptions, loaded) | ||||
| 		if err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
| 		for k, v := range imported.IncludeReferences { | ||||
| 			included[k] = append(included[k], v...) | ||||
| 		} | ||||
|  | ||||
| 		err = importResources(model, imported, r.Path) | ||||
| 		if err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	model.Include = nil | ||||
| 	return model, included, nil | ||||
| } | ||||
|  | ||||
| // importResources import into model all resources defined by imported, and report error on conflict | ||||
| func importResources(model *types.Config, imported *types.Project, path []string) error { | ||||
| 	services := mapByName(model.Services) | ||||
| 	for _, service := range imported.Services { | ||||
| 		if present, ok := services[service.Name]; ok { | ||||
| 			if reflect.DeepEqual(present, service) { | ||||
| 				continue | ||||
| 			} | ||||
| 			return fmt.Errorf("imported compose file %s defines conflicting service %s", path, service.Name) | ||||
| 		} | ||||
| 		model.Services = append(model.Services, service) | ||||
| 	} | ||||
| 	for _, service := range imported.DisabledServices { | ||||
| 		if disabled, ok := services[service.Name]; ok { | ||||
| 			if reflect.DeepEqual(disabled, service) { | ||||
| 				continue | ||||
| 			} | ||||
| 			return fmt.Errorf("imported compose file %s defines conflicting service %s", path, service.Name) | ||||
| 		} | ||||
| 		model.Services = append(model.Services, service) | ||||
| 	} | ||||
| 	for n, network := range imported.Networks { | ||||
| 		if present, ok := model.Networks[n]; ok { | ||||
| 			if reflect.DeepEqual(present, network) { | ||||
| 				continue | ||||
| 			} | ||||
| 			return fmt.Errorf("imported compose file %s defines conflicting network %s", path, n) | ||||
| 		} | ||||
| 		model.Networks[n] = network | ||||
| 	} | ||||
| 	for n, volume := range imported.Volumes { | ||||
| 		if present, ok := model.Volumes[n]; ok { | ||||
| 			if reflect.DeepEqual(present, volume) { | ||||
| 				continue | ||||
| 			} | ||||
| 			return fmt.Errorf("imported compose file %s defines conflicting volume %s", path, n) | ||||
| 		} | ||||
| 		model.Volumes[n] = volume | ||||
| 	} | ||||
| 	for n, secret := range imported.Secrets { | ||||
| 		if present, ok := model.Secrets[n]; ok { | ||||
| 			if reflect.DeepEqual(present, secret) { | ||||
| 				continue | ||||
| 			} | ||||
| 			return fmt.Errorf("imported compose file %s defines conflicting secret %s", path, n) | ||||
| 		} | ||||
| 		model.Secrets[n] = secret | ||||
| 	} | ||||
| 	for n, config := range imported.Configs { | ||||
| 		if present, ok := model.Configs[n]; ok { | ||||
| 			if reflect.DeepEqual(present, config) { | ||||
| 				continue | ||||
| 			} | ||||
| 			return fmt.Errorf("imported compose file %s defines conflicting config %s", path, n) | ||||
| 		} | ||||
| 		model.Configs[n] = config | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										1277
									
								
								vendor/github.com/compose-spec/compose-go/loader/loader.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										1277
									
								
								vendor/github.com/compose-spec/compose-go/loader/loader.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										378
									
								
								vendor/github.com/compose-spec/compose-go/loader/merge.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										378
									
								
								vendor/github.com/compose-spec/compose-go/loader/merge.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,378 +0,0 @@ | ||||
| /* | ||||
|    Copyright 2020 The Compose Specification Authors. | ||||
|  | ||||
|    Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|    you may not use this file except in compliance with the License. | ||||
|    You may obtain a copy of the License at | ||||
|  | ||||
|        http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|    Unless required by applicable law or agreed to in writing, software | ||||
|    distributed under the License is distributed on an "AS IS" BASIS, | ||||
|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|    See the License for the specific language governing permissions and | ||||
|    limitations under the License. | ||||
| */ | ||||
|  | ||||
| package loader | ||||
|  | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"sort" | ||||
|  | ||||
| 	"github.com/compose-spec/compose-go/types" | ||||
| 	"github.com/imdario/mergo" | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
|  | ||||
| type specials struct { | ||||
| 	m map[reflect.Type]func(dst, src reflect.Value) error | ||||
| } | ||||
|  | ||||
| var serviceSpecials = &specials{ | ||||
| 	m: map[reflect.Type]func(dst, src reflect.Value) error{ | ||||
| 		reflect.TypeOf(&types.LoggingConfig{}):           safelyMerge(mergeLoggingConfig), | ||||
| 		reflect.TypeOf(&types.UlimitsConfig{}):           safelyMerge(mergeUlimitsConfig), | ||||
| 		reflect.TypeOf([]types.ServiceVolumeConfig{}):    mergeSlice(toServiceVolumeConfigsMap, toServiceVolumeConfigsSlice), | ||||
| 		reflect.TypeOf([]types.ServicePortConfig{}):      mergeSlice(toServicePortConfigsMap, toServicePortConfigsSlice), | ||||
| 		reflect.TypeOf([]types.ServiceSecretConfig{}):    mergeSlice(toServiceSecretConfigsMap, toServiceSecretConfigsSlice), | ||||
| 		reflect.TypeOf([]types.ServiceConfigObjConfig{}): mergeSlice(toServiceConfigObjConfigsMap, toSServiceConfigObjConfigsSlice), | ||||
| 		reflect.TypeOf(&types.UlimitsConfig{}):           mergeUlimitsConfig, | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| func (s *specials) Transformer(t reflect.Type) func(dst, src reflect.Value) error { | ||||
| 	// TODO this is a workaround waiting for imdario/mergo#131 | ||||
| 	if t.Kind() == reflect.Pointer && t.Elem().Kind() == reflect.Bool { | ||||
| 		return func(dst, src reflect.Value) error { | ||||
| 			if dst.CanSet() && !src.IsNil() { | ||||
| 				dst.Set(src) | ||||
| 			} | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| 	if fn, ok := s.m[t]; ok { | ||||
| 		return fn | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func merge(configs []*types.Config) (*types.Config, error) { | ||||
| 	base := configs[0] | ||||
| 	for _, override := range configs[1:] { | ||||
| 		var err error | ||||
| 		base.Name = mergeNames(base.Name, override.Name) | ||||
| 		base.Services, err = mergeServices(base.Services, override.Services) | ||||
| 		if err != nil { | ||||
| 			return base, errors.Wrapf(err, "cannot merge services from %s", override.Filename) | ||||
| 		} | ||||
| 		base.Volumes, err = mergeVolumes(base.Volumes, override.Volumes) | ||||
| 		if err != nil { | ||||
| 			return base, errors.Wrapf(err, "cannot merge volumes from %s", override.Filename) | ||||
| 		} | ||||
| 		base.Networks, err = mergeNetworks(base.Networks, override.Networks) | ||||
| 		if err != nil { | ||||
| 			return base, errors.Wrapf(err, "cannot merge networks from %s", override.Filename) | ||||
| 		} | ||||
| 		base.Secrets, err = mergeSecrets(base.Secrets, override.Secrets) | ||||
| 		if err != nil { | ||||
| 			return base, errors.Wrapf(err, "cannot merge secrets from %s", override.Filename) | ||||
| 		} | ||||
| 		base.Configs, err = mergeConfigs(base.Configs, override.Configs) | ||||
| 		if err != nil { | ||||
| 			return base, errors.Wrapf(err, "cannot merge configs from %s", override.Filename) | ||||
| 		} | ||||
| 		base.Extensions, err = mergeExtensions(base.Extensions, override.Extensions) | ||||
| 		if err != nil { | ||||
| 			return base, errors.Wrapf(err, "cannot merge extensions from %s", override.Filename) | ||||
| 		} | ||||
| 	} | ||||
| 	return base, nil | ||||
| } | ||||
|  | ||||
| func mergeNames(base, override string) string { | ||||
| 	if override != "" { | ||||
| 		return override | ||||
| 	} | ||||
| 	return base | ||||
| } | ||||
|  | ||||
| func mergeServices(base, override []types.ServiceConfig) ([]types.ServiceConfig, error) { | ||||
| 	baseServices := mapByName(base) | ||||
| 	overrideServices := mapByName(override) | ||||
| 	for name, overrideService := range overrideServices { | ||||
| 		overrideService := overrideService | ||||
| 		if baseService, ok := baseServices[name]; ok { | ||||
| 			merged, err := _merge(&baseService, &overrideService) | ||||
| 			if err != nil { | ||||
| 				return nil, errors.Wrapf(err, "cannot merge service %s", name) | ||||
| 			} | ||||
| 			baseServices[name] = *merged | ||||
| 			continue | ||||
| 		} | ||||
| 		baseServices[name] = overrideService | ||||
| 	} | ||||
| 	services := []types.ServiceConfig{} | ||||
| 	for _, baseService := range baseServices { | ||||
| 		services = append(services, baseService) | ||||
| 	} | ||||
| 	sort.Slice(services, func(i, j int) bool { return services[i].Name < services[j].Name }) | ||||
| 	return services, nil | ||||
| } | ||||
|  | ||||
| func _merge(baseService *types.ServiceConfig, overrideService *types.ServiceConfig) (*types.ServiceConfig, error) { | ||||
| 	if err := mergo.Merge(baseService, overrideService, | ||||
| 		mergo.WithAppendSlice, | ||||
| 		mergo.WithOverride, | ||||
| 		mergo.WithTransformers(serviceSpecials)); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if overrideService.Command != nil { | ||||
| 		baseService.Command = overrideService.Command | ||||
| 	} | ||||
| 	if overrideService.HealthCheck != nil && overrideService.HealthCheck.Test != nil { | ||||
| 		baseService.HealthCheck.Test = overrideService.HealthCheck.Test | ||||
| 	} | ||||
| 	if overrideService.Entrypoint != nil { | ||||
| 		baseService.Entrypoint = overrideService.Entrypoint | ||||
| 	} | ||||
| 	if baseService.Environment != nil { | ||||
| 		baseService.Environment.OverrideBy(overrideService.Environment) | ||||
| 	} else { | ||||
| 		baseService.Environment = overrideService.Environment | ||||
| 	} | ||||
| 	baseService.Expose = unique(baseService.Expose) | ||||
| 	return baseService, nil | ||||
| } | ||||
|  | ||||
| func unique(slice []string) []string { | ||||
| 	if slice == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	uniqMap := make(map[string]struct{}) | ||||
| 	var uniqSlice []string | ||||
| 	for _, v := range slice { | ||||
| 		if _, ok := uniqMap[v]; !ok { | ||||
| 			uniqSlice = append(uniqSlice, v) | ||||
| 			uniqMap[v] = struct{}{} | ||||
| 		} | ||||
| 	} | ||||
| 	return uniqSlice | ||||
| } | ||||
|  | ||||
| func toServiceSecretConfigsMap(s interface{}) (map[interface{}]interface{}, error) { | ||||
| 	secrets, ok := s.([]types.ServiceSecretConfig) | ||||
| 	if !ok { | ||||
| 		return nil, errors.Errorf("not a serviceSecretConfig: %v", s) | ||||
| 	} | ||||
| 	m := map[interface{}]interface{}{} | ||||
| 	for _, secret := range secrets { | ||||
| 		m[secret.Source] = secret | ||||
| 	} | ||||
| 	return m, nil | ||||
| } | ||||
|  | ||||
| func toServiceConfigObjConfigsMap(s interface{}) (map[interface{}]interface{}, error) { | ||||
| 	secrets, ok := s.([]types.ServiceConfigObjConfig) | ||||
| 	if !ok { | ||||
| 		return nil, errors.Errorf("not a serviceSecretConfig: %v", s) | ||||
| 	} | ||||
| 	m := map[interface{}]interface{}{} | ||||
| 	for _, secret := range secrets { | ||||
| 		m[secret.Source] = secret | ||||
| 	} | ||||
| 	return m, nil | ||||
| } | ||||
|  | ||||
| func toServicePortConfigsMap(s interface{}) (map[interface{}]interface{}, error) { | ||||
| 	ports, ok := s.([]types.ServicePortConfig) | ||||
| 	if !ok { | ||||
| 		return nil, errors.Errorf("not a servicePortConfig slice: %v", s) | ||||
| 	} | ||||
| 	m := map[interface{}]interface{}{} | ||||
| 	type port struct { | ||||
| 		target    uint32 | ||||
| 		published string | ||||
| 		ip        string | ||||
| 		protocol  string | ||||
| 	} | ||||
|  | ||||
| 	for _, p := range ports { | ||||
| 		mergeKey := port{ | ||||
| 			target:    p.Target, | ||||
| 			published: p.Published, | ||||
| 			ip:        p.HostIP, | ||||
| 			protocol:  p.Protocol, | ||||
| 		} | ||||
| 		m[mergeKey] = p | ||||
| 	} | ||||
| 	return m, nil | ||||
| } | ||||
|  | ||||
| func toServiceVolumeConfigsMap(s interface{}) (map[interface{}]interface{}, error) { | ||||
| 	volumes, ok := s.([]types.ServiceVolumeConfig) | ||||
| 	if !ok { | ||||
| 		return nil, errors.Errorf("not a ServiceVolumeConfig slice: %v", s) | ||||
| 	} | ||||
| 	m := map[interface{}]interface{}{} | ||||
| 	for _, v := range volumes { | ||||
| 		m[v.Target] = v | ||||
| 	} | ||||
| 	return m, nil | ||||
| } | ||||
|  | ||||
| func toServiceSecretConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error { | ||||
| 	var s []types.ServiceSecretConfig | ||||
| 	for _, v := range m { | ||||
| 		s = append(s, v.(types.ServiceSecretConfig)) | ||||
| 	} | ||||
| 	sort.Slice(s, func(i, j int) bool { return s[i].Source < s[j].Source }) | ||||
| 	dst.Set(reflect.ValueOf(s)) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func toSServiceConfigObjConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error { | ||||
| 	var s []types.ServiceConfigObjConfig | ||||
| 	for _, v := range m { | ||||
| 		s = append(s, v.(types.ServiceConfigObjConfig)) | ||||
| 	} | ||||
| 	sort.Slice(s, func(i, j int) bool { return s[i].Source < s[j].Source }) | ||||
| 	dst.Set(reflect.ValueOf(s)) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func toServicePortConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error { | ||||
| 	var s []types.ServicePortConfig | ||||
| 	for _, v := range m { | ||||
| 		s = append(s, v.(types.ServicePortConfig)) | ||||
| 	} | ||||
| 	sort.Slice(s, func(i, j int) bool { | ||||
| 		if s[i].Target != s[j].Target { | ||||
| 			return s[i].Target < s[j].Target | ||||
| 		} | ||||
| 		if s[i].Published != s[j].Published { | ||||
| 			return s[i].Published < s[j].Published | ||||
| 		} | ||||
| 		if s[i].HostIP != s[j].HostIP { | ||||
| 			return s[i].HostIP < s[j].HostIP | ||||
| 		} | ||||
| 		return s[i].Protocol < s[j].Protocol | ||||
| 	}) | ||||
| 	dst.Set(reflect.ValueOf(s)) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func toServiceVolumeConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error { | ||||
| 	var s []types.ServiceVolumeConfig | ||||
| 	for _, v := range m { | ||||
| 		s = append(s, v.(types.ServiceVolumeConfig)) | ||||
| 	} | ||||
| 	sort.Slice(s, func(i, j int) bool { return s[i].Target < s[j].Target }) | ||||
| 	dst.Set(reflect.ValueOf(s)) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type toMapFn func(s interface{}) (map[interface{}]interface{}, error) | ||||
| type writeValueFromMapFn func(reflect.Value, map[interface{}]interface{}) error | ||||
|  | ||||
| func safelyMerge(mergeFn func(dst, src reflect.Value) error) func(dst, src reflect.Value) error { | ||||
| 	return func(dst, src reflect.Value) error { | ||||
| 		if src.IsNil() { | ||||
| 			return nil | ||||
| 		} | ||||
| 		if dst.IsNil() { | ||||
| 			dst.Set(src) | ||||
| 			return nil | ||||
| 		} | ||||
| 		return mergeFn(dst, src) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func mergeSlice(toMap toMapFn, writeValue writeValueFromMapFn) func(dst, src reflect.Value) error { | ||||
| 	return func(dst, src reflect.Value) error { | ||||
| 		dstMap, err := sliceToMap(toMap, dst) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		srcMap, err := sliceToMap(toMap, src) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := mergo.Map(&dstMap, srcMap, mergo.WithOverride); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		return writeValue(dst, dstMap) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func sliceToMap(toMap toMapFn, v reflect.Value) (map[interface{}]interface{}, error) { | ||||
| 	// check if valid | ||||
| 	if !v.IsValid() { | ||||
| 		return nil, errors.Errorf("invalid value : %+v", v) | ||||
| 	} | ||||
| 	return toMap(v.Interface()) | ||||
| } | ||||
|  | ||||
| func mergeLoggingConfig(dst, src reflect.Value) error { | ||||
| 	// Same driver, merging options | ||||
| 	if getLoggingDriver(dst.Elem()) == getLoggingDriver(src.Elem()) || | ||||
| 		getLoggingDriver(dst.Elem()) == "" || getLoggingDriver(src.Elem()) == "" { | ||||
| 		if getLoggingDriver(dst.Elem()) == "" { | ||||
| 			dst.Elem().FieldByName("Driver").SetString(getLoggingDriver(src.Elem())) | ||||
| 		} | ||||
| 		dstOptions := dst.Elem().FieldByName("Options").Interface().(types.Options) | ||||
| 		srcOptions := src.Elem().FieldByName("Options").Interface().(types.Options) | ||||
| 		return mergo.Merge(&dstOptions, srcOptions, mergo.WithOverride) | ||||
| 	} | ||||
| 	// Different driver, override with src | ||||
| 	dst.Set(src) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // nolint: unparam | ||||
| func mergeUlimitsConfig(dst, src reflect.Value) error { | ||||
| 	if src.Interface() != reflect.Zero(reflect.TypeOf(src.Interface())).Interface() { | ||||
| 		dst.Elem().Set(src.Elem()) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func getLoggingDriver(v reflect.Value) string { | ||||
| 	return v.FieldByName("Driver").String() | ||||
| } | ||||
|  | ||||
| func mapByName(services []types.ServiceConfig) map[string]types.ServiceConfig { | ||||
| 	m := map[string]types.ServiceConfig{} | ||||
| 	for _, service := range services { | ||||
| 		m[service.Name] = service | ||||
| 	} | ||||
| 	return m | ||||
| } | ||||
|  | ||||
| func mergeVolumes(base, override map[string]types.VolumeConfig) (map[string]types.VolumeConfig, error) { | ||||
| 	err := mergo.Map(&base, &override, mergo.WithOverride) | ||||
| 	return base, err | ||||
| } | ||||
|  | ||||
| func mergeNetworks(base, override map[string]types.NetworkConfig) (map[string]types.NetworkConfig, error) { | ||||
| 	err := mergo.Map(&base, &override, mergo.WithOverride) | ||||
| 	return base, err | ||||
| } | ||||
|  | ||||
| func mergeSecrets(base, override map[string]types.SecretConfig) (map[string]types.SecretConfig, error) { | ||||
| 	err := mergo.Map(&base, &override, mergo.WithOverride) | ||||
| 	return base, err | ||||
| } | ||||
|  | ||||
| func mergeConfigs(base, override map[string]types.ConfigObjConfig) (map[string]types.ConfigObjConfig, error) { | ||||
| 	err := mergo.Map(&base, &override, mergo.WithOverride) | ||||
| 	return base, err | ||||
| } | ||||
|  | ||||
| func mergeExtensions(base, override map[string]interface{}) (map[string]interface{}, error) { | ||||
| 	if base == nil { | ||||
| 		base = map[string]interface{}{} | ||||
| 	} | ||||
| 	err := mergo.Map(&base, &override, mergo.WithOverride) | ||||
| 	return base, err | ||||
| } | ||||
| @@ -18,20 +18,21 @@ package cli | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/pkg/errors" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 
 | ||||
| 	"github.com/compose-spec/compose-go/consts" | ||||
| 	"github.com/compose-spec/compose-go/dotenv" | ||||
| 	"github.com/compose-spec/compose-go/errdefs" | ||||
| 	"github.com/compose-spec/compose-go/loader" | ||||
| 	"github.com/compose-spec/compose-go/types" | ||||
| 	"github.com/compose-spec/compose-go/utils" | ||||
| 	"github.com/compose-spec/compose-go/v2/consts" | ||||
| 	"github.com/compose-spec/compose-go/v2/dotenv" | ||||
| 	"github.com/compose-spec/compose-go/v2/errdefs" | ||||
| 	"github.com/compose-spec/compose-go/v2/loader" | ||||
| 	"github.com/compose-spec/compose-go/v2/types" | ||||
| 	"github.com/compose-spec/compose-go/v2/utils" | ||||
| ) | ||||
| 
 | ||||
| // ProjectOptions provides common configuration for loading a project. | ||||
| @@ -52,7 +53,7 @@ type ProjectOptions struct { | ||||
| 
 | ||||
| 	// ConfigPaths are file paths to one or more Compose files. | ||||
| 	// | ||||
| 	// These are applied in order by the loader following the merge logic | ||||
| 	// These are applied in order by the loader following the override logic | ||||
| 	// as described in the spec. | ||||
| 	// | ||||
| 	// The first entry is required and is the primary Compose file. | ||||
| @@ -248,21 +249,44 @@ func WithEnvFile(file string) ProjectOptionsFn { | ||||
| 	return WithEnvFiles(files...) | ||||
| } | ||||
| 
 | ||||
| // WithEnvFiles set alternate env files | ||||
| // WithEnvFiles set env file(s) to be loaded to set project environment. | ||||
| // defaults to local .env file if no explicit file is selected, until COMPOSE_DISABLE_ENV_FILE is set | ||||
| func WithEnvFiles(file ...string) ProjectOptionsFn { | ||||
| 	return func(options *ProjectOptions) error { | ||||
| 		options.EnvFiles = file | ||||
| 	return func(o *ProjectOptions) error { | ||||
| 		if len(file) > 0 { | ||||
| 			o.EnvFiles = file | ||||
| 			return nil | ||||
| 		} | ||||
| 		if v, ok := os.LookupEnv(consts.ComposeDisableDefaultEnvFile); ok { | ||||
| 			b, err := strconv.ParseBool(v) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			if b { | ||||
| 				return nil | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		wd, err := o.GetWorkingDir() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		defaultDotEnv := filepath.Join(wd, ".env") | ||||
| 
 | ||||
| 		s, err := os.Stat(defaultDotEnv) | ||||
| 		if os.IsNotExist(err) { | ||||
| 			return nil | ||||
| 		} | ||||
| 		if !s.IsDir() { | ||||
| 			o.EnvFiles = []string{defaultDotEnv} | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // WithDotEnv imports environment variables from .env file | ||||
| func WithDotEnv(o *ProjectOptions) error { | ||||
| 	wd, err := o.GetWorkingDir() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	envMap, err := dotenv.GetEnvFromFile(o.Environment, wd, o.EnvFiles) | ||||
| 	envMap, err := dotenv.GetEnvFromFile(o.Environment, o.EnvFiles) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @@ -448,7 +472,7 @@ func getConfigPathsFromOptions(options *ProjectOptions) ([]string, error) { | ||||
| 	if len(options.ConfigPaths) != 0 { | ||||
| 		return absolutePaths(options.ConfigPaths) | ||||
| 	} | ||||
| 	return nil, errors.Wrap(errdefs.ErrNotFound, "no configuration file provided") | ||||
| 	return nil, fmt.Errorf("no configuration file provided: %w", errdefs.ErrNotFound) | ||||
| } | ||||
| 
 | ||||
| func findFiles(names []string, pwd string) []string { | ||||
							
								
								
									
										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{} | ||||
| @@ -18,20 +18,15 @@ package dotenv | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 
 | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
| 
 | ||||
| func GetEnvFromFile(currentEnv map[string]string, workingDir string, filenames []string) (map[string]string, error) { | ||||
| func GetEnvFromFile(currentEnv map[string]string, filenames []string) (map[string]string, error) { | ||||
| 	envMap := make(map[string]string) | ||||
| 
 | ||||
| 	dotEnvFiles := filenames | ||||
| 	if len(dotEnvFiles) == 0 { | ||||
| 		dotEnvFiles = append(dotEnvFiles, filepath.Join(workingDir, ".env")) | ||||
| 	} | ||||
| 	for _, dotEnvFile := range dotEnvFiles { | ||||
| 	for _, dotEnvFile := range filenames { | ||||
| 		abs, err := filepath.Abs(dotEnvFile) | ||||
| 		if err != nil { | ||||
| 			return envMap, err | ||||
| @@ -40,10 +35,7 @@ func GetEnvFromFile(currentEnv map[string]string, workingDir string, filenames [ | ||||
| 
 | ||||
| 		s, err := os.Stat(dotEnvFile) | ||||
| 		if os.IsNotExist(err) { | ||||
| 			if len(filenames) == 0 { | ||||
| 				return envMap, nil | ||||
| 			} | ||||
| 			return envMap, errors.Errorf("Couldn't find env file: %s", dotEnvFile) | ||||
| 			return envMap, fmt.Errorf("Couldn't find env file: %s", dotEnvFile) | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			return envMap, err | ||||
| @@ -53,12 +45,12 @@ func GetEnvFromFile(currentEnv map[string]string, workingDir string, filenames [ | ||||
| 			if len(filenames) == 0 { | ||||
| 				return envMap, nil | ||||
| 			} | ||||
| 			return envMap, errors.Errorf("%s is a directory", dotEnvFile) | ||||
| 			return envMap, fmt.Errorf("%s is a directory", dotEnvFile) | ||||
| 		} | ||||
| 
 | ||||
| 		b, err := os.ReadFile(dotEnvFile) | ||||
| 		if os.IsNotExist(err) { | ||||
| 			return nil, errors.Errorf("Couldn't read env file: %s", dotEnvFile) | ||||
| 			return nil, fmt.Errorf("Couldn't read env file: %s", dotEnvFile) | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			return envMap, err | ||||
| @@ -73,7 +65,7 @@ func GetEnvFromFile(currentEnv map[string]string, workingDir string, filenames [ | ||||
| 			return v, ok | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			return envMap, errors.Wrapf(err, "failed to read %s", dotEnvFile) | ||||
| 			return envMap, fmt.Errorf("failed to read %s: %w", dotEnvFile, err) | ||||
| 		} | ||||
| 		for k, v := range env { | ||||
| 			envMap[k] = v | ||||
| @@ -20,7 +20,7 @@ import ( | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/compose-spec/compose-go/template" | ||||
| 	"github.com/compose-spec/compose-go/v2/template" | ||||
| ) | ||||
| 
 | ||||
| var utf8BOM = []byte("\uFEFF") | ||||
| @@ -159,27 +159,34 @@ func (p *parser) extractVarValue(src string, envMap map[string]string, lookupFn | ||||
| 
 | ||||
| 	previousCharIsEscape := false | ||||
| 	// lookup quoted string terminator | ||||
| 	var chars []byte | ||||
| 	for i := 1; i < len(src); i++ { | ||||
| 		if src[i] == '\n' { | ||||
| 		char := src[i] | ||||
| 		if char == '\n' { | ||||
| 			p.line++ | ||||
| 		} | ||||
| 		if char := src[i]; char != quote { | ||||
| 		if char != quote { | ||||
| 			if !previousCharIsEscape && char == '\\' { | ||||
| 				previousCharIsEscape = true | ||||
| 			} else { | ||||
| 				previousCharIsEscape = false | ||||
| 				continue | ||||
| 			} | ||||
| 			if previousCharIsEscape { | ||||
| 				previousCharIsEscape = false | ||||
| 				chars = append(chars, '\\') | ||||
| 			} | ||||
| 			chars = append(chars, char) | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		// skip escaped quote symbol (\" or \', depends on quote) | ||||
| 		if previousCharIsEscape { | ||||
| 			previousCharIsEscape = false | ||||
| 			chars = append(chars, char) | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		// trim quotes | ||||
| 		value := string(src[1:i]) | ||||
| 		value := string(chars) | ||||
| 		if quote == prefixDoubleQuote { | ||||
| 			// expand standard shell escape sequences & then interpolate | ||||
| 			// variables on the result | ||||
| @@ -14,15 +14,16 @@ | ||||
|    limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package loader | ||||
| package format | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 	"unicode" | ||||
| 	"unicode/utf8" | ||||
| 
 | ||||
| 	"github.com/compose-spec/compose-go/types" | ||||
| 	"github.com/pkg/errors" | ||||
| 	"github.com/compose-spec/compose-go/v2/types" | ||||
| ) | ||||
| 
 | ||||
| const endOfSpec = rune(0) | ||||
| @@ -48,7 +49,7 @@ func ParseVolume(spec string) (types.ServiceVolumeConfig, error) { | ||||
| 		case char == ':' || char == endOfSpec: | ||||
| 			if err := populateFieldFromBuffer(char, buffer, &volume); err != nil { | ||||
| 				populateType(&volume) | ||||
| 				return volume, errors.Wrapf(err, "invalid spec: %s", spec) | ||||
| 				return volume, fmt.Errorf("invalid spec: %s: %w", spec, err) | ||||
| 			} | ||||
| 			buffer = nil | ||||
| 		default: | ||||
							
								
								
									
										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 | ||||
| } | ||||
| @@ -17,11 +17,12 @@ | ||||
| package interpolation | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 
 | ||||
| 	"github.com/compose-spec/compose-go/template" | ||||
| 	"github.com/compose-spec/compose-go/tree" | ||||
| 	"github.com/pkg/errors" | ||||
| 	"github.com/compose-spec/compose-go/v2/template" | ||||
| 	"github.com/compose-spec/compose-go/v2/tree" | ||||
| ) | ||||
| 
 | ||||
| // Options supported by Interpolate | ||||
| @@ -80,7 +81,10 @@ func recursiveInterpolate(value interface{}, path tree.Path, opts Options) (inte | ||||
| 			return newValue, nil | ||||
| 		} | ||||
| 		casted, err := caster(newValue) | ||||
| 		return casted, newPathError(path, errors.Wrap(err, "failed to cast to expected type")) | ||||
| 		if err != nil { | ||||
| 			return casted, newPathError(path, fmt.Errorf("failed to cast to expected type: %w", err)) | ||||
| 		} | ||||
| 		return casted, nil | ||||
| 
 | ||||
| 	case map[string]interface{}: | ||||
| 		out := map[string]interface{}{} | ||||
| @@ -110,15 +114,16 @@ func recursiveInterpolate(value interface{}, path tree.Path, opts Options) (inte | ||||
| } | ||||
| 
 | ||||
| func newPathError(path tree.Path, err error) error { | ||||
| 	switch err := err.(type) { | ||||
| 	case nil: | ||||
| 	var ite *template.InvalidTemplateError | ||||
| 	switch { | ||||
| 	case err == nil: | ||||
| 		return nil | ||||
| 	case *template.InvalidTemplateError: | ||||
| 		return errors.Errorf( | ||||
| 	case errors.As(err, &ite): | ||||
| 		return fmt.Errorf( | ||||
| 			"invalid interpolation format for %s.\nYou may need to escape any $ with another $.\n%s", | ||||
| 			path, err.Template) | ||||
| 			path, ite.Template) | ||||
| 	default: | ||||
| 		return errors.Wrapf(err, "error while interpolating %s", path) | ||||
| 		return fmt.Errorf("error while interpolating %s: %w", path, err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										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 | ||||
| } | ||||
| @@ -141,7 +141,8 @@ services: | ||||
|     # env_file: .env | ||||
|     env_file: | ||||
|       - ./example1.env | ||||
|       - ./example2.env | ||||
|       - path: ./example2.env | ||||
|         required: false | ||||
| 
 | ||||
|     # Mapping or list | ||||
|     # Mapping values can be strings, numbers or null | ||||
| @@ -235,6 +236,7 @@ services: | ||||
|       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" | ||||
| @@ -271,7 +273,8 @@ services: | ||||
|     stop_grace_period: 20s | ||||
| 
 | ||||
|     stop_signal: SIGUSR1 | ||||
| 
 | ||||
|     storage_opt: | ||||
|       size: "20G" | ||||
|     sysctls: | ||||
|       net.core.somaxconn: 1024 | ||||
|       net.ipv4.tcp_syncookies: 0 | ||||
| @@ -295,22 +298,22 @@ services: | ||||
| 
 | ||||
|     volumes: | ||||
|       # Just specify a path and let the Engine create a volume | ||||
|       - /var/lib/mysql | ||||
|       - /var/lib/anonymous | ||||
|       # Specify an absolute path mapping | ||||
|       - /opt/data:/var/lib/mysql | ||||
|       - /opt/data:/var/lib/data | ||||
|       # Path on the host, relative to the Compose file | ||||
|       - .:/code | ||||
|       - ./static:/var/www/html | ||||
|       # User-relative path | ||||
|       - ~/configs:/etc/configs:ro | ||||
|       # Named volume | ||||
|       - datavolume:/var/lib/mysql | ||||
|       - datavolume:/var/lib/volume | ||||
|       - type: bind | ||||
|         source: ./opt | ||||
|         target: /opt | ||||
|         target: /opt/cached | ||||
|         consistency: cached | ||||
|       - type: tmpfs | ||||
|         target: /opt | ||||
|         target: /opt/tmpfs | ||||
|         tmpfs: | ||||
|           size: 10000 | ||||
| 
 | ||||
							
								
								
									
										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 | ||||
| } | ||||
| @@ -17,12 +17,12 @@ | ||||
| package loader | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	interp "github.com/compose-spec/compose-go/interpolation" | ||||
| 	"github.com/compose-spec/compose-go/tree" | ||||
| 	"github.com/pkg/errors" | ||||
| 	interp "github.com/compose-spec/compose-go/v2/interpolation" | ||||
| 	"github.com/compose-spec/compose-go/v2/tree" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| ) | ||||
| 
 | ||||
| @@ -112,6 +112,6 @@ func toBoolean(value string) (interface{}, error) { | ||||
| 		logrus.Warnf("%q for boolean is not supported by YAML 1.2, please use `false`", value) | ||||
| 		return false, nil | ||||
| 	default: | ||||
| 		return nil, errors.Errorf("invalid boolean: %s", value) | ||||
| 		return nil, fmt.Errorf("invalid boolean: %s", value) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										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 | ||||
| } | ||||
| @@ -16,7 +16,10 @@ | ||||
| 
 | ||||
| package loader | ||||
| 
 | ||||
| import "reflect" | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"strconv" | ||||
| ) | ||||
| 
 | ||||
| // comparable to yaml.Unmarshaler, decoder allow a type to define it's own custom logic to convert value | ||||
| // see https://github.com/mitchellh/mapstructure/pull/294 | ||||
| @@ -51,3 +54,26 @@ func decoderHook(from reflect.Value, to reflect.Value) (interface{}, error) { | ||||
| 	} | ||||
| 	return to.Interface(), nil | ||||
| } | ||||
| 
 | ||||
| func cast(from reflect.Value, to reflect.Value) (interface{}, error) { | ||||
| 	switch from.Type().Kind() { | ||||
| 	case reflect.String: | ||||
| 		switch to.Kind() { | ||||
| 		case reflect.Bool: | ||||
| 			return toBoolean(from.String()) | ||||
| 		case reflect.Int: | ||||
| 			return toInt(from.String()) | ||||
| 		case reflect.Int64: | ||||
| 			return toInt64(from.String()) | ||||
| 		case reflect.Float32: | ||||
| 			return toFloat32(from.String()) | ||||
| 		case reflect.Float64: | ||||
| 			return toFloat(from.String()) | ||||
| 		} | ||||
| 	case reflect.Int: | ||||
| 		if to.Kind() == reflect.String { | ||||
| 			return strconv.FormatInt(from.Int(), 10), nil | ||||
| 		} | ||||
| 	} | ||||
| 	return from.Interface(), nil | ||||
| } | ||||
| @@ -20,9 +20,8 @@ import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/compose-spec/compose-go/errdefs" | ||||
| 	"github.com/compose-spec/compose-go/types" | ||||
| 	"github.com/pkg/errors" | ||||
| 	"github.com/compose-spec/compose-go/v2/errdefs" | ||||
| 	"github.com/compose-spec/compose-go/v2/types" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| ) | ||||
| 
 | ||||
| @@ -37,11 +36,7 @@ func Normalize(project *types.Project) error { | ||||
| 		project.Networks["default"] = types.NetworkConfig{} | ||||
| 	} | ||||
| 
 | ||||
| 	if err := relocateExternalName(project); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	for i, s := range project.Services { | ||||
| 	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} | ||||
| @@ -116,14 +111,9 @@ func Normalize(project *types.Project) error { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		err = relocateScale(&s) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		inferImplicitDependencies(&s) | ||||
| 
 | ||||
| 		project.Services[i] = s | ||||
| 		project.Services[name] = s | ||||
| 	} | ||||
| 
 | ||||
| 	setNameFromKey(project) | ||||
| @@ -198,93 +188,51 @@ func setIfMissing(d types.DependsOnConfig, service string, dep types.ServiceDepe | ||||
| 	return d | ||||
| } | ||||
| 
 | ||||
| func relocateScale(s *types.ServiceConfig) error { | ||||
| 	scale := uint64(s.Scale) | ||||
| 	if scale > 1 { | ||||
| 		logrus.Warn("`scale` is deprecated. Use the `deploy.replicas` element") | ||||
| 		if s.Deploy == nil { | ||||
| 			s.Deploy = &types.DeployConfig{} | ||||
| 		} | ||||
| 		if s.Deploy.Replicas != nil && *s.Deploy.Replicas != scale { | ||||
| 			return errors.Wrap(errdefs.ErrInvalid, "can't use both 'scale' (deprecated) and 'deploy.replicas'") | ||||
| 		} | ||||
| 		s.Deploy.Replicas = &scale | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Resources with no explicit name are actually named by their key in map | ||||
| func setNameFromKey(project *types.Project) { | ||||
| 	for i, n := range project.Networks { | ||||
| 	for key, n := range project.Networks { | ||||
| 		if n.Name == "" { | ||||
| 			n.Name = fmt.Sprintf("%s_%s", project.Name, i) | ||||
| 			project.Networks[i] = n | ||||
| 			if n.External { | ||||
| 				n.Name = key | ||||
| 			} else { | ||||
| 				n.Name = fmt.Sprintf("%s_%s", project.Name, key) | ||||
| 			} | ||||
| 			project.Networks[key] = n | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for i, v := range project.Volumes { | ||||
| 	for key, v := range project.Volumes { | ||||
| 		if v.Name == "" { | ||||
| 			v.Name = fmt.Sprintf("%s_%s", project.Name, i) | ||||
| 			project.Volumes[i] = v | ||||
| 			if v.External { | ||||
| 				v.Name = key | ||||
| 			} else { | ||||
| 				v.Name = fmt.Sprintf("%s_%s", project.Name, key) | ||||
| 			} | ||||
| 			project.Volumes[key] = v | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for i, c := range project.Configs { | ||||
| 	for key, c := range project.Configs { | ||||
| 		if c.Name == "" { | ||||
| 			c.Name = fmt.Sprintf("%s_%s", project.Name, i) | ||||
| 			project.Configs[i] = c | ||||
| 			if c.External { | ||||
| 				c.Name = key | ||||
| 			} else { | ||||
| 				c.Name = fmt.Sprintf("%s_%s", project.Name, key) | ||||
| 			} | ||||
| 			project.Configs[key] = c | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for i, s := range project.Secrets { | ||||
| 	for key, s := range project.Secrets { | ||||
| 		if s.Name == "" { | ||||
| 			s.Name = fmt.Sprintf("%s_%s", project.Name, i) | ||||
| 			project.Secrets[i] = s | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func relocateExternalName(project *types.Project) error { | ||||
| 	for i, n := range project.Networks { | ||||
| 		if n.External.Name != "" { | ||||
| 			if n.Name != "" { | ||||
| 				return errors.Wrap(errdefs.ErrInvalid, "can't use both 'networks.external.name' (deprecated) and 'networks.name'") | ||||
| 			if s.External { | ||||
| 				s.Name = key | ||||
| 			} else { | ||||
| 				s.Name = fmt.Sprintf("%s_%s", project.Name, key) | ||||
| 			} | ||||
| 			n.Name = n.External.Name | ||||
| 			project.Secrets[key] = s | ||||
| 		} | ||||
| 		project.Networks[i] = n | ||||
| 	} | ||||
| 
 | ||||
| 	for i, v := range project.Volumes { | ||||
| 		if v.External.Name != "" { | ||||
| 			if v.Name != "" { | ||||
| 				return errors.Wrap(errdefs.ErrInvalid, "can't use both 'volumes.external.name' (deprecated) and 'volumes.name'") | ||||
| 			} | ||||
| 			v.Name = v.External.Name | ||||
| 		} | ||||
| 		project.Volumes[i] = v | ||||
| 	} | ||||
| 
 | ||||
| 	for i, s := range project.Secrets { | ||||
| 		if s.External.Name != "" { | ||||
| 			if s.Name != "" { | ||||
| 				return errors.Wrap(errdefs.ErrInvalid, "can't use both 'secrets.external.name' (deprecated) and 'secrets.name'") | ||||
| 			} | ||||
| 			s.Name = s.External.Name | ||||
| 		} | ||||
| 		project.Secrets[i] = s | ||||
| 	} | ||||
| 
 | ||||
| 	for i, c := range project.Configs { | ||||
| 		if c.External.Name != "" { | ||||
| 			if c.Name != "" { | ||||
| 				return errors.Wrap(errdefs.ErrInvalid, "can't use both 'configs.external.name' (deprecated) and 'configs.name'") | ||||
| 			} | ||||
| 			c.Name = c.External.Name | ||||
| 		} | ||||
| 		project.Configs[i] = c | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func relocateLogOpt(s *types.ServiceConfig) error { | ||||
| @@ -297,7 +245,7 @@ func relocateLogOpt(s *types.ServiceConfig) error { | ||||
| 			if _, ok := s.Logging.Options[k]; !ok { | ||||
| 				s.Logging.Options[k] = v | ||||
| 			} else { | ||||
| 				return errors.Wrap(errdefs.ErrInvalid, "can't use both 'log_opt' (deprecated) and 'logging.options'") | ||||
| 				return fmt.Errorf("can't use both 'log_opt' (deprecated) and 'logging.options': %w", errdefs.ErrInvalid) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| @@ -313,7 +261,7 @@ func relocateLogDriver(s *types.ServiceConfig) error { | ||||
| 		if s.Logging.Driver == "" { | ||||
| 			s.Logging.Driver = s.LogDriver | ||||
| 		} else { | ||||
| 			return errors.Wrap(errdefs.ErrInvalid, "can't use both 'log_driver' (deprecated) and 'logging.driver'") | ||||
| 			return fmt.Errorf("can't use both 'log_driver' (deprecated) and 'logging.driver': %w", errdefs.ErrInvalid) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| @@ -328,7 +276,7 @@ func relocateDockerfile(s *types.ServiceConfig) error { | ||||
| 		if s.Dockerfile == "" { | ||||
| 			s.Build.Dockerfile = s.Dockerfile | ||||
| 		} else { | ||||
| 			return errors.Wrap(errdefs.ErrInvalid, "can't use both 'dockerfile' (deprecated) and 'build.dockerfile'") | ||||
| 			return fmt.Errorf("can't use both 'dockerfile' (deprecated) and 'build.dockerfile': %w", errdefs.ErrInvalid) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| @@ -21,7 +21,7 @@ import ( | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/compose-spec/compose-go/types" | ||||
| 	"github.com/compose-spec/compose-go/v2/types" | ||||
| ) | ||||
| 
 | ||||
| // ResolveRelativePaths resolves relative paths based on project WorkingDirectory | ||||
| @@ -38,33 +38,6 @@ func ResolveRelativePaths(project *types.Project) error { | ||||
| 	} | ||||
| 	project.ComposeFiles = absComposeFiles | ||||
| 
 | ||||
| 	for i, s := range project.Services { | ||||
| 		ResolveServiceRelativePaths(project.WorkingDir, &s) | ||||
| 		project.Services[i] = s | ||||
| 	} | ||||
| 
 | ||||
| 	for i, obj := range project.Configs { | ||||
| 		if obj.File != "" { | ||||
| 			obj.File = absPath(project.WorkingDir, obj.File) | ||||
| 			project.Configs[i] = obj | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for i, obj := range project.Secrets { | ||||
| 		if obj.File != "" { | ||||
| 			obj.File = resolveMaybeUnixPath(project.WorkingDir, obj.File) | ||||
| 			project.Secrets[i] = obj | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for name, config := range project.Volumes { | ||||
| 		if config.Driver == "local" && config.DriverOpts["o"] == "bind" { | ||||
| 			// This is actually a bind mount | ||||
| 			config.DriverOpts["device"] = resolveMaybeUnixPath(project.WorkingDir, config.DriverOpts["device"]) | ||||
| 			project.Volumes[name] = config | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// don't coerce a nil map to an empty map | ||||
| 	if project.IncludeReferences != nil { | ||||
| 		absIncludes := make(map[string][]types.IncludeConfig, len(project.IncludeReferences)) | ||||
| @@ -86,44 +59,6 @@ func ResolveRelativePaths(project *types.Project) error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func ResolveServiceRelativePaths(workingDir string, s *types.ServiceConfig) { | ||||
| 	if s.Build != nil { | ||||
| 		if !isRemoteContext(s.Build.Context) { | ||||
| 			s.Build.Context = absPath(workingDir, s.Build.Context) | ||||
| 		} | ||||
| 		for name, path := range s.Build.AdditionalContexts { | ||||
| 			if strings.Contains(path, "://") { // `docker-image://` or any builder specific context type | ||||
| 				continue | ||||
| 			} | ||||
| 			if isRemoteContext(path) { | ||||
| 				continue | ||||
| 			} | ||||
| 			s.Build.AdditionalContexts[name] = absPath(workingDir, path) | ||||
| 		} | ||||
| 	} | ||||
| 	for j, f := range s.EnvFile { | ||||
| 		s.EnvFile[j] = absPath(workingDir, f) | ||||
| 	} | ||||
| 
 | ||||
| 	if s.Extends != nil && s.Extends.File != "" { | ||||
| 		s.Extends.File = absPath(workingDir, s.Extends.File) | ||||
| 	} | ||||
| 
 | ||||
| 	for i, vol := range s.Volumes { | ||||
| 		if vol.Type != types.VolumeTypeBind { | ||||
| 			continue | ||||
| 		} | ||||
| 		s.Volumes[i].Source = resolveMaybeUnixPath(workingDir, vol.Source) | ||||
| 	} | ||||
| 
 | ||||
| 	if s.Develop != nil { | ||||
| 		for i, w := range s.Develop.Watch { | ||||
| 			w.Path = absPath(workingDir, w.Path) | ||||
| 			s.Develop.Watch[i] = w | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func absPath(workingDir string, filePath string) string { | ||||
| 	if strings.HasPrefix(filePath, "~") { | ||||
| 		home, _ := os.UserHomeDir() | ||||
| @@ -146,20 +81,6 @@ func absComposeFiles(composeFiles []string) ([]string, error) { | ||||
| 	return composeFiles, nil | ||||
| } | ||||
| 
 | ||||
| // isRemoteContext returns true if the value is a Git reference or HTTP(S) URL. | ||||
| // | ||||
| // Any other value is assumed to be a local filesystem path and returns false. | ||||
| // | ||||
| // See: https://github.com/moby/buildkit/blob/18fc875d9bfd6e065cd8211abc639434ba65aa56/frontend/dockerui/context.go#L76-L79 | ||||
| func isRemoteContext(maybeURL string) bool { | ||||
| 	for _, prefix := range []string{"https://", "http://", "git://", "ssh://", "github.com/", "git@"} { | ||||
| 		if strings.HasPrefix(maybeURL, prefix) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| func resolvePaths(basePath string, in types.StringList) types.StringList { | ||||
| 	if in == nil { | ||||
| 		return nil | ||||
| @@ -18,12 +18,9 @@ package loader | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/compose-spec/compose-go/tree" | ||||
| 	"github.com/compose-spec/compose-go/types" | ||||
| 	"github.com/compose-spec/compose-go/v2/tree" | ||||
| 	"gopkg.in/yaml.v3" | ||||
| ) | ||||
| 
 | ||||
| @@ -45,111 +42,81 @@ func (p *ResetProcessor) UnmarshalYAML(value *yaml.Node) error { | ||||
| 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 err error | ||||
| 		var nodes []*yaml.Node | ||||
| 		for idx, v := range node.Content { | ||||
| 			next := path.Next(strconv.Itoa(idx)) | ||||
| 			node.Content[idx], err = p.resolveReset(v, next) | ||||
| 			resolved, err := p.resolveReset(v, next) | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 			if resolved != nil { | ||||
| 				nodes = append(nodes, resolved) | ||||
| 			} | ||||
| 		} | ||||
| 		node.Content = nodes | ||||
| 	case yaml.MappingNode: | ||||
| 		var err error | ||||
| 		var key string | ||||
| 		var nodes []*yaml.Node | ||||
| 		for idx, v := range node.Content { | ||||
| 			if idx%2 == 0 { | ||||
| 				key = v.Value | ||||
| 			} else { | ||||
| 				node.Content[idx], err = p.resolveReset(v, path.Next(key)) | ||||
| 				resolved, err := p.resolveReset(v, path.Next(key)) | ||||
| 				if err != nil { | ||||
| 					return nil, err | ||||
| 				} | ||||
| 				if resolved != nil { | ||||
| 					nodes = append(nodes, node.Content[idx-1], resolved) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		node.Content = nodes | ||||
| 	} | ||||
| 	return node, nil | ||||
| } | ||||
| 
 | ||||
| // Apply finds the go attributes matching recorded paths and reset them to zero value | ||||
| func (p *ResetProcessor) Apply(target *types.Config) error { | ||||
| 	return p.applyNullOverrides(reflect.ValueOf(target), tree.NewPath()) | ||||
| func (p *ResetProcessor) Apply(target any) error { | ||||
| 	return p.applyNullOverrides(target, tree.NewPath()) | ||||
| } | ||||
| 
 | ||||
| // applyNullOverrides set val to Zero if it matches any of the recorded paths | ||||
| func (p *ResetProcessor) applyNullOverrides(val reflect.Value, path tree.Path) error { | ||||
| 	val = reflect.Indirect(val) | ||||
| 	if !val.IsValid() { | ||||
| 		return nil | ||||
| 	} | ||||
| 	typ := val.Type() | ||||
| 	switch { | ||||
| 	case path == "services": | ||||
| 		// Project.Services is a slice in compose-go, but a mapping in yaml | ||||
| 		for i := 0; i < val.Len(); i++ { | ||||
| 			service := val.Index(i) | ||||
| 			name := service.FieldByName("Name") | ||||
| 			next := path.Next(name.String()) | ||||
| 			err := p.applyNullOverrides(service, next) | ||||
| 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 typ.Kind() == reflect.Map: | ||||
| 		iter := val.MapRange() | ||||
| 	KEYS: | ||||
| 		for iter.Next() { | ||||
| 			k := iter.Key() | ||||
| 			next := path.Next(k.String()) | ||||
| 			for _, pattern := range p.paths { | ||||
| 				if next.Matches(pattern) { | ||||
| 					val.SetMapIndex(k, reflect.Value{}) | ||||
| 					continue KEYS | ||||
| 				} | ||||
| 			} | ||||
| 			return p.applyNullOverrides(iter.Value(), next) | ||||
| 		} | ||||
| 	case typ.Kind() == reflect.Slice: | ||||
| 	case []any: | ||||
| 	ITER: | ||||
| 		for i := 0; i < val.Len(); i++ { | ||||
| 		for i, e := range v { | ||||
| 			next := path.Next(fmt.Sprintf("[%d]", i)) | ||||
| 			for _, pattern := range p.paths { | ||||
| 				if next.Matches(pattern) { | ||||
| 
 | ||||
| 					continue ITER | ||||
| 					// TODO(ndeloof) support removal from sequence | ||||
| 				} | ||||
| 			} | ||||
| 			// TODO(ndeloof) support removal from sequence | ||||
| 			return p.applyNullOverrides(val.Index(i), next) | ||||
| 		} | ||||
| 
 | ||||
| 	case typ.Kind() == reflect.Struct: | ||||
| 	FIELDS: | ||||
| 		for i := 0; i < typ.NumField(); i++ { | ||||
| 			field := typ.Field(i) | ||||
| 			name := field.Name | ||||
| 			attr := strings.ToLower(name) | ||||
| 			tag := field.Tag.Get("yaml") | ||||
| 			tag = strings.Split(tag, ",")[0] | ||||
| 			if tag != "" && tag != "-" { | ||||
| 				attr = tag | ||||
| 			} | ||||
| 			next := path.Next(attr) | ||||
| 			f := val.Field(i) | ||||
| 			for _, pattern := range p.paths { | ||||
| 				if next.Matches(pattern) { | ||||
| 					f := f | ||||
| 					if !f.CanSet() { | ||||
| 						return fmt.Errorf("can't override attribute %s", name) | ||||
| 					} | ||||
| 					// f.SetZero() requires go 1.20 | ||||
| 					f.Set(reflect.Zero(f.Type())) | ||||
| 					continue FIELDS | ||||
| 				} | ||||
| 			} | ||||
| 			err := p.applyNullOverrides(f, next) | ||||
| 			err := p.applyNullOverrides(e, next) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| @@ -17,24 +17,26 @@ | ||||
| package loader | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/compose-spec/compose-go/errdefs" | ||||
| 	"github.com/compose-spec/compose-go/types" | ||||
| 	"github.com/pkg/errors" | ||||
| 	"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 errors.Wrapf(errdefs.ErrInvalid, "service %q has neither an image nor a build context specified", s.Name) | ||||
| 			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 errors.Wrapf(errdefs.ErrInvalid, "service %q declares mutualy exclusive dockerfile and dockerfile_inline", s.Name) | ||||
| 				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 != "" { | ||||
| @@ -46,17 +48,17 @@ func checkConsistency(project *types.Project) error { | ||||
| 					} | ||||
| 				} | ||||
| 				if !found { | ||||
| 					return errors.Wrapf(errdefs.ErrInvalid, "service.build.platforms MUST include service.platform %q ", s.Platform) | ||||
| 					return fmt.Errorf("service.build.platforms MUST include service.platform %q: %w", s.Platform, errdefs.ErrInvalid) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if s.NetworkMode != "" && len(s.Networks) > 0 { | ||||
| 			return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %s declares mutually exclusive `network_mode` and `networks`", s.Name)) | ||||
| 			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 errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q refers to undefined network %s", s.Name, network)) | ||||
| 				return fmt.Errorf("service %q refers to undefined network %s: %w", s.Name, network, errdefs.ErrInvalid) | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| @@ -70,9 +72,15 @@ func checkConsistency(project *types.Project) error { | ||||
| 
 | ||||
| 		for dependedService := range s.DependsOn { | ||||
| 			if _, err := project.GetService(dependedService); err != nil { | ||||
| 				return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q depends on undefined service %s", s.Name, dependedService)) | ||||
| 				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):] | ||||
| @@ -84,36 +92,53 @@ func checkConsistency(project *types.Project) error { | ||||
| 		for _, volume := range s.Volumes { | ||||
| 			if volume.Type == types.VolumeTypeVolume && volume.Source != "" { // non anonymous volumes | ||||
| 				if _, ok := project.Volumes[volume.Source]; !ok { | ||||
| 					return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q refers to undefined volume %s", s.Name, volume.Source)) | ||||
| 					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 errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q refers to undefined build secret %s", s.Name, secret.Source)) | ||||
| 					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 errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q refers to undefined config %s", s.Name, config.Source)) | ||||
| 				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 errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q refers to undefined secret %s", s.Name, secret.Source)) | ||||
| 				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.External { | ||||
| 		if secret.External { | ||||
| 			continue | ||||
| 		} | ||||
| 		if secret.File == "" && secret.Environment == "" { | ||||
| 			return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("secret %q must declare either `file` or `environment`", name)) | ||||
| 			return fmt.Errorf("secret %q must declare either `file` or `environment`: %w", name, errdefs.ErrInvalid) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
							
								
								
									
										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 | ||||
| } | ||||
| @@ -14,11 +14,12 @@ | ||||
|    limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package consts | ||||
| package paths | ||||
| 
 | ||||
| const ( | ||||
| 	ComposeProjectName   = "COMPOSE_PROJECT_NAME" | ||||
| 	ComposePathSeparator = "COMPOSE_PATH_SEPARATOR" | ||||
| 	ComposeFilePath      = "COMPOSE_FILE" | ||||
| 	ComposeProfiles      = "COMPOSE_PROFILES" | ||||
| ) | ||||
| func (r *relativePathsResolver) absExtendsPath(value any) (any, error) { | ||||
| 	v := value.(string) | ||||
| 	if r.isRemoteResource(v) { | ||||
| 		return v, nil | ||||
| 	} | ||||
| 	return r.absPath(v) | ||||
| } | ||||
							
								
								
									
										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 | ||||
| } | ||||
| @@ -14,7 +14,7 @@ | ||||
|    limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package loader | ||||
| package paths | ||||
| 
 | ||||
| // Copyright 2010 The Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| @@ -30,7 +30,7 @@ func isSlash(c uint8) bool { | ||||
| } | ||||
| 
 | ||||
| // isAbs reports whether the path is a Windows absolute path. | ||||
| func isAbs(path string) (b bool) { | ||||
| func isWindowsAbs(path string) (b bool) { | ||||
| 	l := volumeNameLen(path) | ||||
| 	if l == 0 { | ||||
| 		return false | ||||
| @@ -120,6 +120,7 @@ | ||||
|                 "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, | ||||
| @@ -214,7 +215,7 @@ | ||||
|         "dns_search": {"$ref": "#/definitions/string_or_list"}, | ||||
|         "domainname": {"type": "string"}, | ||||
|         "entrypoint": {"$ref": "#/definitions/command"}, | ||||
|         "env_file": {"$ref": "#/definitions/string_or_list"}, | ||||
|         "env_file": {"$ref": "#/definitions/env_file"}, | ||||
|         "environment": {"$ref": "#/definitions/list_or_dict"}, | ||||
| 
 | ||||
|         "expose": { | ||||
| @@ -293,6 +294,7 @@ | ||||
|                         "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, | ||||
| @@ -356,26 +358,7 @@ | ||||
|         "storage_opt": {"type": "object"}, | ||||
|         "tmpfs": {"$ref": "#/definitions/string_or_list"}, | ||||
|         "tty": {"type": "boolean"}, | ||||
|         "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-": {}} | ||||
|                 } | ||||
|               ] | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         "ulimits": {"$ref": "#/definitions/ulimits"}, | ||||
|         "user": {"type": "string"}, | ||||
|         "uts": {"type": "string"}, | ||||
|         "userns_mode": {"type": "string"}, | ||||
| @@ -479,6 +462,7 @@ | ||||
|               "target": {"type": "string"} | ||||
|             } | ||||
|           }, | ||||
|           "required": ["path", "action"], | ||||
|           "additionalProperties": false, | ||||
|           "patternProperties": {"^x-": {}} | ||||
|         } | ||||
| @@ -613,12 +597,12 @@ | ||||
|       "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"} | ||||
|           }, | ||||
|           "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-": {}} | ||||
|       } | ||||
| @@ -764,6 +748,8 @@ | ||||
|       "type": "object", | ||||
|       "properties": { | ||||
|         "name": {"type": "string"}, | ||||
|         "content": {"type": "string"}, | ||||
|         "environment": {"type": "string"}, | ||||
|         "file": {"type": "string"}, | ||||
|         "external": { | ||||
|           "type": ["boolean", "object"], | ||||
| @@ -789,6 +775,36 @@ | ||||
|       ] | ||||
|     }, | ||||
| 
 | ||||
|     "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"}, | ||||
| @@ -833,7 +849,6 @@ | ||||
|       }, | ||||
|       "additionalProperties": false | ||||
|     }, | ||||
| 
 | ||||
|     "service_config_or_secret": { | ||||
|       "type": "array", | ||||
|       "items": { | ||||
| @@ -854,7 +869,26 @@ | ||||
|         ] | ||||
|       } | ||||
|     }, | ||||
| 
 | ||||
|     "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", | ||||
| @@ -870,4 +904,4 @@ | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| } | ||||
							
								
								
									
										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) | ||||
| } | ||||
| @@ -16,7 +16,9 @@ | ||||
| 
 | ||||
| package tree | ||||
| 
 | ||||
| import "strings" | ||||
| import ( | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| const pathSeparator = "." | ||||
| 
 | ||||
| @@ -41,6 +43,7 @@ func (p Path) Next(part string) Path { | ||||
| 	if p == "" { | ||||
| 		return Path(part) | ||||
| 	} | ||||
| 	part = strings.ReplaceAll(part, pathSeparator, "👻") | ||||
| 	return Path(string(p) + pathSeparator + part) | ||||
| } | ||||
| 
 | ||||
| @@ -65,3 +68,20 @@ func (p Path) Matches(pattern Path) bool { | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| func (p Path) Last() string { | ||||
| 	parts := p.Parts() | ||||
| 	return parts[len(parts)-1] | ||||
| } | ||||
| 
 | ||||
| func (p Path) Parent() Path { | ||||
| 	index := strings.LastIndex(string(p), pathSeparator) | ||||
| 	if index > 0 { | ||||
| 		return p[0:index] | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
| 
 | ||||
| func (p Path) String() string { | ||||
| 	return strings.ReplaceAll(string(p), "👻", pathSeparator) | ||||
| } | ||||
| @@ -36,7 +36,13 @@ func (u UnitBytes) MarshalJSON() ([]byte, error) { | ||||
| } | ||||
| 
 | ||||
| func (u *UnitBytes) DecodeMapstructure(value interface{}) error { | ||||
| 	v, err := units.RAMInBytes(fmt.Sprint(value)) | ||||
| 	*u = UnitBytes(v) | ||||
| 	return err | ||||
| 	switch v := value.(type) { | ||||
| 	case int: | ||||
| 		*u = UnitBytes(v) | ||||
| 	case string: | ||||
| 		b, err := units.RAMInBytes(fmt.Sprint(value)) | ||||
| 		*u = UnitBytes(b) | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @@ -38,7 +38,7 @@ type ConfigDetails struct { | ||||
| } | ||||
| 
 | ||||
| // LookupEnv provides a lookup function for environment variables | ||||
| func (cd ConfigDetails) LookupEnv(key string) (string, bool) { | ||||
| func (cd *ConfigDetails) LookupEnv(key string) (string, bool) { | ||||
| 	v, ok := cd.Environment[key] | ||||
| 	if !isCaseInsensitiveEnvVars || ok { | ||||
| 		return v, ok | ||||
| @@ -18,6 +18,8 @@ package types | ||||
| 
 | ||||
| type DevelopConfig struct { | ||||
| 	Watch []Trigger `json:"watch,omitempty"` | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| type WatchAction string | ||||
| @@ -17,10 +17,9 @@ | ||||
| package types | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
| 
 | ||||
| type DeviceRequest struct { | ||||
| @@ -43,11 +42,11 @@ func (c *DeviceCount) DecodeMapstructure(value interface{}) error { | ||||
| 		} | ||||
| 		i, err := strconv.ParseInt(v, 10, 64) | ||||
| 		if err != nil { | ||||
| 			return errors.Errorf("invalid value %q, the only value allowed is 'all' or a number", v) | ||||
| 			return fmt.Errorf("invalid value %q, the only value allowed is 'all' or a number", v) | ||||
| 		} | ||||
| 		*c = DeviceCount(i) | ||||
| 	default: | ||||
| 		return errors.Errorf("invalid type %T for device count", v) | ||||
| 		return fmt.Errorf("invalid type %T for device count", v) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										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) | ||||
| } | ||||
| @@ -30,7 +30,7 @@ type HealthCheckConfig struct { | ||||
| 	StartInterval *Duration       `yaml:"start_interval,omitempty" json:"start_interval,omitempty"` | ||||
| 	Disable       bool            `yaml:"disable,omitempty" json:"disable,omitempty"` | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // HealthCheckTest is the command run to test the health of a service | ||||
							
								
								
									
										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 | ||||
| } | ||||
| @@ -66,10 +66,7 @@ func (l *Labels) DecodeMapstructure(value interface{}) error { | ||||
| 	case []interface{}: | ||||
| 		labels := make(map[string]string, len(v)) | ||||
| 		for _, s := range v { | ||||
| 			k, e, ok := strings.Cut(fmt.Sprint(s), "=") | ||||
| 			if !ok { | ||||
| 				return fmt.Errorf("invalid label %q", v) | ||||
| 			} | ||||
| 			k, e, _ := strings.Cut(fmt.Sprint(s), "=") | ||||
| 			labels[k] = labelValue(e) | ||||
| 		} | ||||
| 		*l = labels | ||||
							
								
								
									
										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 | ||||
| } | ||||
| @@ -16,11 +16,7 @@ | ||||
| 
 | ||||
| package types | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
| import "fmt" | ||||
| 
 | ||||
| // Options is a mapping type for options we pass as-is to container runtime | ||||
| type Options map[string]string | ||||
| @@ -40,7 +36,7 @@ func (d *Options) DecodeMapstructure(value interface{}) error { | ||||
| 	case map[string]string: | ||||
| 		*d = v | ||||
| 	default: | ||||
| 		return errors.Errorf("invalid type %T for options", value) | ||||
| 		return fmt.Errorf("invalid type %T for options", value) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @@ -24,16 +24,18 @@ import ( | ||||
| 	"path/filepath" | ||||
| 	"sort" | ||||
| 
 | ||||
| 	"github.com/compose-spec/compose-go/dotenv" | ||||
| 	"github.com/compose-spec/compose-go/utils" | ||||
| 	"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" | ||||
| 	"github.com/pkg/errors" | ||||
| 	"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:"-"` | ||||
| @@ -42,7 +44,7 @@ type Project struct { | ||||
| 	Volumes    Volumes    `yaml:"volumes,omitempty" json:"volumes,omitempty"` | ||||
| 	Secrets    Secrets    `yaml:"secrets,omitempty" json:"secrets,omitempty"` | ||||
| 	Configs    Configs    `yaml:"configs,omitempty" json:"configs,omitempty"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` // https://github.com/golang/go/issues/6213 | ||||
| 	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`. | ||||
| @@ -60,8 +62,18 @@ type Project struct { | ||||
| // ServiceNames return names for all services in this Compose config | ||||
| func (p *Project) ServiceNames() []string { | ||||
| 	var names []string | ||||
| 	for _, s := range p.Services { | ||||
| 		names = append(names, s.Name) | ||||
| 	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 | ||||
| @@ -109,9 +121,16 @@ func (p *Project) ConfigNames() []string { | ||||
| 
 | ||||
| // GetServices retrieve services by names, or return all services if no name specified | ||||
| func (p *Project) GetServices(names ...string) (Services, error) { | ||||
| 	services, servicesNotFound := p.getServicesByNames(names...) | ||||
| 	if len(servicesNotFound) > 0 { | ||||
| 		return services, fmt.Errorf("no such service: %s", servicesNotFound[0]) | ||||
| 	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 | ||||
| } | ||||
| @@ -123,55 +142,53 @@ func (p *Project) getServicesByNames(names ...string) (Services, []string) { | ||||
| 	services := Services{} | ||||
| 	var servicesNotFound []string | ||||
| 	for _, name := range names { | ||||
| 		var serviceConfig *ServiceConfig | ||||
| 		for _, s := range p.Services { | ||||
| 			if s.Name == name { | ||||
| 				serviceConfig = &s | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		if serviceConfig == nil { | ||||
| 		service, ok := p.Services[name] | ||||
| 		if !ok { | ||||
| 			servicesNotFound = append(servicesNotFound, name) | ||||
| 			continue | ||||
| 		} | ||||
| 		services = append(services, *serviceConfig) | ||||
| 		services[name] = service | ||||
| 	} | ||||
| 	return services, servicesNotFound | ||||
| } | ||||
| 
 | ||||
| // GetDisabledService retrieve disabled service by name | ||||
| func (p Project) GetDisabledService(name string) (ServiceConfig, error) { | ||||
| 	for _, config := range p.DisabledServices { | ||||
| 		if config.Name == name { | ||||
| 			return config, nil | ||||
| 		} | ||||
| 	service, ok := p.DisabledServices[name] | ||||
| 	if !ok { | ||||
| 		return ServiceConfig{}, fmt.Errorf("no such service: %s", name) | ||||
| 	} | ||||
| 	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) { | ||||
| 	services, err := p.GetServices(name) | ||||
| 	if err != nil { | ||||
| 		return ServiceConfig{}, err | ||||
| 	} | ||||
| 	if len(services) == 0 { | ||||
| 	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 services[0], nil | ||||
| 	return service, nil | ||||
| } | ||||
| 
 | ||||
| func (p *Project) AllServices() Services { | ||||
| 	var all Services | ||||
| 	all = append(all, p.Services...) | ||||
| 	all = append(all, p.DisabledServices...) | ||||
| 	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(service ServiceConfig) error | ||||
| type ServiceFunc func(name string, service *ServiceConfig) error | ||||
| 
 | ||||
| // WithServices run ServiceFunc on each service and dependencies according to DependencyPolicy | ||||
| func (p *Project) WithServices(names []string, fn ServiceFunc, options ...DependencyOption) error { | ||||
| // 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} | ||||
| @@ -179,6 +196,16 @@ func (p *Project) WithServices(names []string, fn ServiceFunc, options ...Depend | ||||
| 	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 { | ||||
| @@ -188,23 +215,26 @@ func (p *Project) withServices(names []string, fn ServiceFunc, seen map[string]b | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	for _, service := range services { | ||||
| 		if seen[service.Name] { | ||||
| 	opts := withServicesOptions{ | ||||
| 		dependencyPolicy: includeDependencies, | ||||
| 	} | ||||
| 	for _, option := range options { | ||||
| 		option(&opts) | ||||
| 	} | ||||
| 
 | ||||
| 	for name, service := range services { | ||||
| 		if seen[name] { | ||||
| 			continue | ||||
| 		} | ||||
| 		seen[service.Name] = true | ||||
| 		seen[name] = true | ||||
| 		var dependencies map[string]ServiceDependency | ||||
| 		for _, policy := range options { | ||||
| 			switch policy { | ||||
| 			case IncludeDependents: | ||||
| 				dependencies = utils.MapsAppend(dependencies, p.dependentsForService(service)) | ||||
| 			case IncludeDependencies: | ||||
| 				dependencies = utils.MapsAppend(dependencies, service.DependsOn) | ||||
| 			case IgnoreDependencies: | ||||
| 				// Noop | ||||
| 			default: | ||||
| 				return fmt.Errorf("unsupported dependency policy %d", policy) | ||||
| 			} | ||||
| 		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) | ||||
| @@ -212,7 +242,7 @@ func (p *Project) withServices(names []string, fn ServiceFunc, seen map[string]b | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 		if err := fn(service); err != nil { | ||||
| 		if err := fn(name, service.deepCopy()); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| @@ -262,82 +292,64 @@ func (s ServiceConfig) HasProfile(profiles []string) bool { | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| // GetProfiles retrieve the profiles implicitly enabled by explicitly targeting selected services | ||||
| func (s Services) GetProfiles() []string { | ||||
| 	set := map[string]struct{}{} | ||||
| 	for _, service := range s { | ||||
| 		for _, p := range service.Profiles { | ||||
| 			set[p] = struct{}{} | ||||
| 		} | ||||
| 	} | ||||
| 	var profiles []string | ||||
| 	for k := range set { | ||||
| 		profiles = append(profiles, k) | ||||
| 	} | ||||
| 	return profiles | ||||
| } | ||||
| 
 | ||||
| // ApplyProfiles disables service which don't match selected profiles | ||||
| func (p *Project) ApplyProfiles(profiles []string) { | ||||
| // 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 | ||||
| 			return newProject, nil | ||||
| 		} | ||||
| 	} | ||||
| 	var enabled, disabled Services | ||||
| 	for _, service := range p.AllServices() { | ||||
| 	enabled := Services{} | ||||
| 	disabled := Services{} | ||||
| 	for name, service := range newProject.AllServices() { | ||||
| 		if service.HasProfile(profiles) { | ||||
| 			enabled = append(enabled, service) | ||||
| 			enabled[name] = service | ||||
| 		} else { | ||||
| 			disabled = append(disabled, service) | ||||
| 			disabled[name] = service | ||||
| 		} | ||||
| 	} | ||||
| 	p.Services = enabled | ||||
| 	p.DisabledServices = disabled | ||||
| 	p.Profiles = profiles | ||||
| 	newProject.Services = enabled | ||||
| 	newProject.DisabledServices = disabled | ||||
| 	newProject.Profiles = profiles | ||||
| 	return newProject, nil | ||||
| } | ||||
| 
 | ||||
| // EnableServices ensure services are enabled and activate profiles accordingly | ||||
| func (p *Project) EnableServices(names ...string) error { | ||||
| // 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 nil | ||||
| 		return newProject, nil | ||||
| 	} | ||||
| 	var enabled []string | ||||
| 
 | ||||
| 	profiles := append([]string{}, p.Profiles...) | ||||
| 	for _, name := range names { | ||||
| 		_, err := p.GetService(name) | ||||
| 		if err == nil { | ||||
| 		if _, ok := newProject.Services[name]; ok { | ||||
| 			// already enabled | ||||
| 			continue | ||||
| 		} | ||||
| 		def, err := p.GetDisabledService(name) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		enabled = append(enabled, def.Profiles...) | ||||
| 		service := p.DisabledServices[name] | ||||
| 		profiles = append(profiles, service.Profiles...) | ||||
| 	} | ||||
| 	newProject, err := newProject.WithProfiles(profiles) | ||||
| 	if err != nil { | ||||
| 		return newProject, err | ||||
| 	} | ||||
| 
 | ||||
| 	profiles := p.Profiles | ||||
| PROFILES: | ||||
| 	for _, profile := range enabled { | ||||
| 		for _, p := range profiles { | ||||
| 			if p == profile { | ||||
| 				continue PROFILES | ||||
| 			} | ||||
| 		} | ||||
| 		profiles = append(profiles, profile) | ||||
| 	} | ||||
| 	p.ApplyProfiles(profiles) | ||||
| 
 | ||||
| 	return p.ResolveServicesEnvironment(true) | ||||
| 	return newProject.WithServicesEnvironmentResolved(true) | ||||
| } | ||||
| 
 | ||||
| // WithoutUnnecessaryResources drops networks/volumes/secrets/configs that are not referenced by active services | ||||
| func (p *Project) WithoutUnnecessaryResources() { | ||||
| // 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 p.Services { | ||||
| 	for _, s := range newProject.Services { | ||||
| 		for k := range s.Networks { | ||||
| 			requiredNetworks[k] = struct{}{} | ||||
| 		} | ||||
| @@ -366,7 +378,7 @@ func (p *Project) WithoutUnnecessaryResources() { | ||||
| 			networks[k] = value | ||||
| 		} | ||||
| 	} | ||||
| 	p.Networks = networks | ||||
| 	newProject.Networks = networks | ||||
| 
 | ||||
| 	volumes := Volumes{} | ||||
| 	for k := range requiredVolumes { | ||||
| @@ -374,7 +386,7 @@ func (p *Project) WithoutUnnecessaryResources() { | ||||
| 			volumes[k] = value | ||||
| 		} | ||||
| 	} | ||||
| 	p.Volumes = volumes | ||||
| 	newProject.Volumes = volumes | ||||
| 
 | ||||
| 	secrets := Secrets{} | ||||
| 	for k := range requiredSecrets { | ||||
| @@ -382,7 +394,7 @@ func (p *Project) WithoutUnnecessaryResources() { | ||||
| 			secrets[k] = value | ||||
| 		} | ||||
| 	} | ||||
| 	p.Secrets = secrets | ||||
| 	newProject.Secrets = secrets | ||||
| 
 | ||||
| 	configs := Configs{} | ||||
| 	for k := range requiredConfigs { | ||||
| @@ -390,73 +402,95 @@ func (p *Project) WithoutUnnecessaryResources() { | ||||
| 			configs[k] = value | ||||
| 		} | ||||
| 	} | ||||
| 	p.Configs = configs | ||||
| 	newProject.Configs = configs | ||||
| 	return newProject | ||||
| } | ||||
| 
 | ||||
| type DependencyOption int | ||||
| type DependencyOption func(options *withServicesOptions) | ||||
| 
 | ||||
| const ( | ||||
| 	IncludeDependencies = iota | ||||
| 	IncludeDependents | ||||
| 	IgnoreDependencies | ||||
| ) | ||||
| func IncludeDependencies(options *withServicesOptions) { | ||||
| 	options.dependencyPolicy = includeDependencies | ||||
| } | ||||
| 
 | ||||
| // ForServices restrict the project model to selected services and dependencies | ||||
| func (p *Project) ForServices(names []string, options ...DependencyOption) error { | ||||
| 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 nil | ||||
| 		return newProject, nil | ||||
| 	} | ||||
| 
 | ||||
| 	set := map[string]struct{}{} | ||||
| 	err := p.WithServices(names, func(service ServiceConfig) error { | ||||
| 		set[service.Name] = struct{}{} | ||||
| 	set := utils.NewSet[string]() | ||||
| 	err := p.ForEachService(names, func(name string, service *ServiceConfig) error { | ||||
| 		set.Add(name) | ||||
| 		return nil | ||||
| 	}, options...) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	// Disable all services which are not explicit target or dependencies | ||||
| 	var enabled Services | ||||
| 	for _, s := range p.Services { | ||||
| 		if _, ok := set[s.Name]; ok { | ||||
| 			for _, option := range options { | ||||
| 				if option == IgnoreDependencies { | ||||
| 					// remove all dependencies but those implied by explicitly selected services | ||||
| 					dependencies := s.DependsOn | ||||
| 					for d := range dependencies { | ||||
| 						if _, ok := set[d]; !ok { | ||||
| 							delete(dependencies, d) | ||||
| 						} | ||||
| 					} | ||||
| 					s.DependsOn = dependencies | ||||
| 	enabled := 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) | ||||
| 				} | ||||
| 			} | ||||
| 			enabled = append(enabled, s) | ||||
| 			s.DependsOn = dependencies | ||||
| 			enabled[name] = s | ||||
| 		} else { | ||||
| 			p.DisableService(s) | ||||
| 			newProject = newProject.WithServicesDisabled(name) | ||||
| 		} | ||||
| 	} | ||||
| 	p.Services = enabled | ||||
| 	return nil | ||||
| 	newProject.Services = enabled | ||||
| 	return newProject, nil | ||||
| } | ||||
| 
 | ||||
| func (p *Project) DisableService(service ServiceConfig) { | ||||
| 	// We should remove all dependencies which reference the disabled service | ||||
| 	for i, s := range p.Services { | ||||
| 		if _, ok := s.DependsOn[service.Name]; ok { | ||||
| 			delete(s.DependsOn, service.Name) | ||||
| 			p.Services[i] = s | ||||
| // 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) | ||||
| 		} | ||||
| 	} | ||||
| 	p.DisabledServices = append(p.DisabledServices, service) | ||||
| 	return newProject | ||||
| } | ||||
| 
 | ||||
| // ResolveImages updates services images to include digest computed by a resolver function | ||||
| func (p *Project) ResolveImages(resolver func(named reference.Named) (godigest.Digest, error)) error { | ||||
| // 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 p.Services { | ||||
| 	for i, s := range newProject.Services { | ||||
| 		idx := i | ||||
| 		service := s | ||||
| 
 | ||||
| @@ -482,11 +516,11 @@ func (p *Project) ResolveImages(resolver func(named reference.Named) (godigest.D | ||||
| 			} | ||||
| 
 | ||||
| 			service.Image = named.String() | ||||
| 			p.Services[idx] = service | ||||
| 			newProject.Services[idx] = service | ||||
| 			return nil | ||||
| 		}) | ||||
| 	} | ||||
| 	return eg.Wait() | ||||
| 	return newProject, eg.Wait() | ||||
| } | ||||
| 
 | ||||
| // MarshalYAML marshal Project into a yaml tree | ||||
| @@ -527,10 +561,12 @@ func (p *Project) MarshalJSON() ([]byte, error) { | ||||
| 	return json.Marshal(m) | ||||
| } | ||||
| 
 | ||||
| // ResolveServicesEnvironment parse env_files set for services to resolve the actual environment map for services | ||||
| func (p Project) ResolveServicesEnvironment(discardEnvFiles bool) error { | ||||
| 	for i, service := range p.Services { | ||||
| 		service.Environment = service.Environment.Resolve(p.Environment.Resolve) | ||||
| // 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 | ||||
| @@ -539,18 +575,24 @@ func (p Project) ResolveServicesEnvironment(discardEnvFiles bool) error { | ||||
| 			if ok && v != nil { | ||||
| 				return *v, ok | ||||
| 			} | ||||
| 			return p.Environment.Resolve(s) | ||||
| 			return newProject.Environment.Resolve(s) | ||||
| 		} | ||||
| 
 | ||||
| 		for _, envFile := range service.EnvFile { | ||||
| 			b, err := os.ReadFile(envFile) | ||||
| 		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 errors.Wrapf(err, "Failed to load %s", envFile) | ||||
| 				return nil, fmt.Errorf("failed to load %s: %w", envFile.Path, err) | ||||
| 			} | ||||
| 
 | ||||
| 			fileVars, err := dotenv.ParseWithLookup(bytes.NewBuffer(b), resolve) | ||||
| 			if err != nil { | ||||
| 				return errors.Wrapf(err, "failed to read %s", envFile) | ||||
| 				return nil, fmt.Errorf("failed to read %s: %w", envFile.Path, err) | ||||
| 			} | ||||
| 			environment.OverrideBy(Mapping(fileVars).ToMappingWithEquals()) | ||||
| 		} | ||||
| @@ -558,9 +600,17 @@ func (p Project) ResolveServicesEnvironment(discardEnvFiles bool) error { | ||||
| 		service.Environment = environment.OverrideBy(service.Environment) | ||||
| 
 | ||||
| 		if discardEnvFiles { | ||||
| 			service.EnvFile = nil | ||||
| 			service.EnvFiles = nil | ||||
| 		} | ||||
| 		p.Services[i] = service | ||||
| 		newProject.Services[i] = service | ||||
| 	} | ||||
| 	return nil | ||||
| 	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 | ||||
| } | ||||
| @@ -16,11 +16,7 @@ | ||||
| 
 | ||||
| package types | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
| import "fmt" | ||||
| 
 | ||||
| // StringList is a type for fields that can be a string or list of strings | ||||
| type StringList []string | ||||
| @@ -36,7 +32,7 @@ func (l *StringList) DecodeMapstructure(value interface{}) error { | ||||
| 		} | ||||
| 		*l = list | ||||
| 	default: | ||||
| 		return errors.Errorf("invalid type %T for string list", value) | ||||
| 		return fmt.Errorf("invalid type %T for string list", value) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @@ -55,7 +51,7 @@ func (l *StringOrNumberList) DecodeMapstructure(value interface{}) error { | ||||
| 		} | ||||
| 		*l = list | ||||
| 	default: | ||||
| 		return errors.Errorf("invalid type %T for string list", value) | ||||
| 		return fmt.Errorf("invalid type %T for string list", value) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @@ -23,32 +23,12 @@ import ( | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/docker/go-connections/nat" | ||||
| 	"github.com/mitchellh/copystructure" | ||||
| ) | ||||
| 
 | ||||
| // Services is a list of ServiceConfig | ||||
| type Services []ServiceConfig | ||||
| 
 | ||||
| // MarshalYAML makes Services implement yaml.Marshaller | ||||
| func (s Services) MarshalYAML() (interface{}, error) { | ||||
| 	services := map[string]ServiceConfig{} | ||||
| 	for _, service := range s { | ||||
| 		services[service.Name] = service | ||||
| 	} | ||||
| 	return services, nil | ||||
| } | ||||
| 
 | ||||
| // MarshalJSON makes Services implement json.Marshaler | ||||
| func (s Services) MarshalJSON() ([]byte, error) { | ||||
| 	data, err := s.MarshalYAML() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return json.MarshalIndent(data, "", "  ") | ||||
| } | ||||
| 
 | ||||
| // ServiceConfig is the configuration of one service | ||||
| type ServiceConfig struct { | ||||
| 	Name     string   `yaml:"-" json:"-"` | ||||
| 	Name     string   `yaml:"name,omitempty" json:"-"` | ||||
| 	Profiles []string `yaml:"profiles,omitempty" json:"profiles,omitempty"` | ||||
| 
 | ||||
| 	Annotations  Mapping        `yaml:"annotations,omitempty" json:"annotations,omitempty"` | ||||
| @@ -96,7 +76,7 @@ type ServiceConfig struct { | ||||
| 	Entrypoint ShellCommand `yaml:"entrypoint,omitempty" json:"entrypoint"` // NOTE: we can NOT omitempty for JSON! see ShellCommand type for details. | ||||
| 
 | ||||
| 	Environment     MappingWithEquals                `yaml:"environment,omitempty" json:"environment,omitempty"` | ||||
| 	EnvFile         StringList                       `yaml:"env_file,omitempty" json:"env_file,omitempty"` | ||||
| 	EnvFiles        []EnvFile                        `yaml:"env_file,omitempty" json:"env_file,omitempty"` | ||||
| 	Expose          StringOrNumberList               `yaml:"expose,omitempty" json:"expose,omitempty"` | ||||
| 	Extends         *ExtendsConfig                   `yaml:"extends,omitempty" json:"extends,omitempty"` | ||||
| 	ExternalLinks   []string                         `yaml:"external_links,omitempty" json:"external_links,omitempty"` | ||||
| @@ -133,13 +113,14 @@ type ServiceConfig struct { | ||||
| 	ReadOnly        bool                             `yaml:"read_only,omitempty" json:"read_only,omitempty"` | ||||
| 	Restart         string                           `yaml:"restart,omitempty" json:"restart,omitempty"` | ||||
| 	Runtime         string                           `yaml:"runtime,omitempty" json:"runtime,omitempty"` | ||||
| 	Scale           int                              `yaml:"scale,omitempty" json:"scale,omitempty"` | ||||
| 	Scale           *int                             `yaml:"scale,omitempty" json:"scale,omitempty"` | ||||
| 	Secrets         []ServiceSecretConfig            `yaml:"secrets,omitempty" json:"secrets,omitempty"` | ||||
| 	SecurityOpt     []string                         `yaml:"security_opt,omitempty" json:"security_opt,omitempty"` | ||||
| 	ShmSize         UnitBytes                        `yaml:"shm_size,omitempty" json:"shm_size,omitempty"` | ||||
| 	StdinOpen       bool                             `yaml:"stdin_open,omitempty" json:"stdin_open,omitempty"` | ||||
| 	StopGracePeriod *Duration                        `yaml:"stop_grace_period,omitempty" json:"stop_grace_period,omitempty"` | ||||
| 	StopSignal      string                           `yaml:"stop_signal,omitempty" json:"stop_signal,omitempty"` | ||||
| 	StorageOpt      map[string]string                `yaml:"storage_opt,omitempty" json:"storage_opt,omitempty"` | ||||
| 	Sysctls         Mapping                          `yaml:"sysctls,omitempty" json:"sysctls,omitempty"` | ||||
| 	Tmpfs           StringList                       `yaml:"tmpfs,omitempty" json:"tmpfs,omitempty"` | ||||
| 	Tty             bool                             `yaml:"tty,omitempty" json:"tty,omitempty"` | ||||
| @@ -152,25 +133,17 @@ type ServiceConfig struct { | ||||
| 	VolumesFrom     []string                         `yaml:"volumes_from,omitempty" json:"volumes_from,omitempty"` | ||||
| 	WorkingDir      string                           `yaml:"working_dir,omitempty" json:"working_dir,omitempty"` | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // MarshalYAML makes ServiceConfig implement yaml.Marshaller | ||||
| func (s ServiceConfig) MarshalYAML() (interface{}, error) { | ||||
| 	type t ServiceConfig | ||||
| 	value := t(s) | ||||
| 	value.Scale = 0 // deprecated, but default value "1" doesn't match omitempty | ||||
| 	value.Name = "" // set during map to slice conversion, not part of the yaml representation | ||||
| 	return value, nil | ||||
| } | ||||
| 
 | ||||
| // MarshalJSON makes SSHKey implement json.Marshaller | ||||
| func (s ServiceConfig) MarshalJSON() ([]byte, error) { | ||||
| 	type t ServiceConfig | ||||
| 	value := t(s) | ||||
| 	value.Scale = 0 // deprecated, but default value "1" doesn't match omitempty | ||||
| 	return json.Marshal(value) | ||||
| } | ||||
| 
 | ||||
| // NetworksByPriority return the service networks IDs sorted according to Priority | ||||
| func (s *ServiceConfig) NetworksByPriority() []string { | ||||
| 	type key struct { | ||||
| @@ -198,6 +171,32 @@ func (s *ServiceConfig) NetworksByPriority() []string { | ||||
| 	return sorted | ||||
| } | ||||
| 
 | ||||
| func (s *ServiceConfig) GetScale() int { | ||||
| 	if s.Scale != nil { | ||||
| 		return *s.Scale | ||||
| 	} | ||||
| 	if s.Deploy != nil && s.Deploy.Replicas != nil { | ||||
| 		// this should not be required as compose-go enforce consistency between scale anr replicas | ||||
| 		return *s.Deploy.Replicas | ||||
| 	} | ||||
| 	return 1 | ||||
| } | ||||
| 
 | ||||
| func (s *ServiceConfig) SetScale(scale int) { | ||||
| 	s.Scale = &scale | ||||
| 	if s.Deploy != nil { | ||||
| 		s.Deploy.Replicas = &scale | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (s *ServiceConfig) deepCopy() *ServiceConfig { | ||||
| 	instance, err := copystructure.Copy(s) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	return instance.(*ServiceConfig) | ||||
| } | ||||
| 
 | ||||
| const ( | ||||
| 	// PullPolicyAlways always pull images | ||||
| 	PullPolicyAlways = "always" | ||||
| @@ -258,45 +257,31 @@ func (s ServiceConfig) GetDependents(p *Project) []string { | ||||
| 	return dependent | ||||
| } | ||||
| 
 | ||||
| type set map[string]struct{} | ||||
| 
 | ||||
| func (s set) append(strs ...string) { | ||||
| 	for _, str := range strs { | ||||
| 		s[str] = struct{}{} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (s set) toSlice() []string { | ||||
| 	slice := make([]string, 0, len(s)) | ||||
| 	for v := range s { | ||||
| 		slice = append(slice, v) | ||||
| 	} | ||||
| 	return slice | ||||
| } | ||||
| 
 | ||||
| // BuildConfig is a type for build | ||||
| type BuildConfig struct { | ||||
| 	Context            string                `yaml:"context,omitempty" json:"context,omitempty"` | ||||
| 	Dockerfile         string                `yaml:"dockerfile,omitempty" json:"dockerfile,omitempty"` | ||||
| 	DockerfileInline   string                `yaml:"dockerfile_inline,omitempty" json:"dockerfile_inline,omitempty"` | ||||
| 	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"` | ||||
| 	Tags               StringList            `yaml:"tags,omitempty" json:"tags,omitempty"` | ||||
| 	Platforms          StringList            `yaml:"platforms,omitempty" json:"platforms,omitempty"` | ||||
| 	Privileged         bool                  `yaml:"privileged,omitempty" json:"privileged,omitempty"` | ||||
| 	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" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // BlkioConfig define blkio config | ||||
| @@ -308,7 +293,7 @@ type BlkioConfig struct { | ||||
| 	DeviceWriteBps  []ThrottleDevice `yaml:"device_write_bps,omitempty" json:"device_write_bps,omitempty"` | ||||
| 	DeviceWriteIOps []ThrottleDevice `yaml:"device_write_iops,omitempty" json:"device_write_iops,omitempty"` | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // WeightDevice is a structure that holds device:weight pair | ||||
| @@ -316,7 +301,7 @@ type WeightDevice struct { | ||||
| 	Path   string | ||||
| 	Weight uint16 | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // ThrottleDevice is a structure that holds device:rate_per_second pair | ||||
| @@ -324,197 +309,25 @@ type ThrottleDevice struct { | ||||
| 	Path string | ||||
| 	Rate UnitBytes | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // MappingWithEquals is a mapping type that can be converted from a list of | ||||
| // key[=value] strings. | ||||
| // For the key with an empty value (`key=`), the mapped value is set to a pointer to `""`. | ||||
| // For the key without value (`key`), the mapped value is set to nil. | ||||
| type MappingWithEquals map[string]*string | ||||
| 
 | ||||
| // NewMappingWithEquals build a new Mapping from a set of KEY=VALUE strings | ||||
| func NewMappingWithEquals(values []string) MappingWithEquals { | ||||
| 	mapping := MappingWithEquals{} | ||||
| 	for _, env := range values { | ||||
| 		tokens := strings.SplitN(env, "=", 2) | ||||
| 		if len(tokens) > 1 { | ||||
| 			mapping[tokens[0]] = &tokens[1] | ||||
| 		} else { | ||||
| 			mapping[env] = nil | ||||
| 		} | ||||
| 	} | ||||
| 	return mapping | ||||
| } | ||||
| 
 | ||||
| // OverrideBy update MappingWithEquals with values from another MappingWithEquals | ||||
| func (e MappingWithEquals) OverrideBy(other MappingWithEquals) MappingWithEquals { | ||||
| 	for k, v := range other { | ||||
| 		e[k] = v | ||||
| 	} | ||||
| 	return e | ||||
| } | ||||
| 
 | ||||
| // Resolve update a MappingWithEquals for keys without value (`key`, but not `key=`) | ||||
| func (e MappingWithEquals) Resolve(lookupFn func(string) (string, bool)) MappingWithEquals { | ||||
| 	for k, v := range e { | ||||
| 		if v == nil { | ||||
| 			if value, ok := lookupFn(k); ok { | ||||
| 				e[k] = &value | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return e | ||||
| } | ||||
| 
 | ||||
| // RemoveEmpty excludes keys that are not associated with a value | ||||
| func (e MappingWithEquals) RemoveEmpty() MappingWithEquals { | ||||
| 	for k, v := range e { | ||||
| 		if v == nil { | ||||
| 			delete(e, k) | ||||
| 		} | ||||
| 	} | ||||
| 	return e | ||||
| } | ||||
| 
 | ||||
| // Mapping is a mapping type that can be converted from a list of | ||||
| // key[=value] strings. | ||||
| // For the key with an empty value (`key=`), or key without value (`key`), the | ||||
| // mapped value is set to an empty string `""`. | ||||
| type Mapping map[string]string | ||||
| 
 | ||||
| // NewMapping build a new Mapping from a set of KEY=VALUE strings | ||||
| func NewMapping(values []string) Mapping { | ||||
| 	mapping := Mapping{} | ||||
| 	for _, value := range values { | ||||
| 		parts := strings.SplitN(value, "=", 2) | ||||
| 		key := parts[0] | ||||
| 		switch { | ||||
| 		case len(parts) == 1: | ||||
| 			mapping[key] = "" | ||||
| 		default: | ||||
| 			mapping[key] = parts[1] | ||||
| 		} | ||||
| 	} | ||||
| 	return mapping | ||||
| } | ||||
| 
 | ||||
| // convert values into a set of KEY=VALUE strings | ||||
| func (m Mapping) Values() []string { | ||||
| 	values := make([]string, 0, len(m)) | ||||
| 	for k, v := range m { | ||||
| 		values = append(values, fmt.Sprintf("%s=%s", k, v)) | ||||
| 	} | ||||
| 	sort.Strings(values) | ||||
| 	return values | ||||
| } | ||||
| 
 | ||||
| // ToMappingWithEquals converts Mapping into a MappingWithEquals with pointer references | ||||
| func (m Mapping) ToMappingWithEquals() MappingWithEquals { | ||||
| 	mapping := MappingWithEquals{} | ||||
| 	for k, v := range m { | ||||
| 		v := v | ||||
| 		mapping[k] = &v | ||||
| 	} | ||||
| 	return mapping | ||||
| } | ||||
| 
 | ||||
| func (m Mapping) Resolve(s string) (string, bool) { | ||||
| 	v, ok := m[s] | ||||
| 	return v, ok | ||||
| } | ||||
| 
 | ||||
| func (m Mapping) Clone() Mapping { | ||||
| 	clone := Mapping{} | ||||
| 	for k, v := range m { | ||||
| 		clone[k] = v | ||||
| 	} | ||||
| 	return clone | ||||
| } | ||||
| 
 | ||||
| // Merge adds all values from second mapping which are not already defined | ||||
| func (m Mapping) Merge(o Mapping) Mapping { | ||||
| 	for k, v := range o { | ||||
| 		if _, set := m[k]; !set { | ||||
| 			m[k] = v | ||||
| 		} | ||||
| 	} | ||||
| 	return m | ||||
| } | ||||
| 
 | ||||
| type SSHKey struct { | ||||
| 	ID   string | ||||
| 	Path string | ||||
| } | ||||
| 
 | ||||
| // SSHConfig is a mapping type for SSH build config | ||||
| type SSHConfig []SSHKey | ||||
| 
 | ||||
| func (s SSHConfig) Get(id string) (string, error) { | ||||
| 	for _, sshKey := range s { | ||||
| 		if sshKey.ID == id { | ||||
| 			return sshKey.Path, nil | ||||
| 		} | ||||
| 	} | ||||
| 	return "", fmt.Errorf("ID %s not found in SSH keys", id) | ||||
| } | ||||
| 
 | ||||
| // MarshalYAML makes SSHKey implement yaml.Marshaller | ||||
| func (s SSHKey) MarshalYAML() (interface{}, error) { | ||||
| 	if s.Path == "" { | ||||
| 		return s.ID, nil | ||||
| 	} | ||||
| 	return fmt.Sprintf("%s: %s", s.ID, s.Path), nil | ||||
| } | ||||
| 
 | ||||
| // MarshalJSON makes SSHKey implement json.Marshaller | ||||
| func (s SSHKey) MarshalJSON() ([]byte, error) { | ||||
| 	if s.Path == "" { | ||||
| 		return []byte(fmt.Sprintf(`%q`, s.ID)), nil | ||||
| 	} | ||||
| 	return []byte(fmt.Sprintf(`%q: %s`, s.ID, s.Path)), nil | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // MappingWithColon is a mapping type that can be converted from a list of | ||||
| // 'key: value' strings | ||||
| type MappingWithColon map[string]string | ||||
| 
 | ||||
| // HostsList is a list of colon-separated host-ip mappings | ||||
| type HostsList map[string]string | ||||
| 
 | ||||
| // AsList return host-ip mappings as a list of colon-separated strings | ||||
| func (h HostsList) AsList() []string { | ||||
| 	l := make([]string, 0, len(h)) | ||||
| 	for k, v := range h { | ||||
| 		l = append(l, fmt.Sprintf("%s:%s", k, v)) | ||||
| 	} | ||||
| 	return l | ||||
| } | ||||
| 
 | ||||
| func (h HostsList) MarshalYAML() (interface{}, error) { | ||||
| 	list := h.AsList() | ||||
| 	sort.Strings(list) | ||||
| 	return list, nil | ||||
| } | ||||
| 
 | ||||
| func (h HostsList) MarshalJSON() ([]byte, error) { | ||||
| 	list := h.AsList() | ||||
| 	sort.Strings(list) | ||||
| 	return json.Marshal(list) | ||||
| } | ||||
| 
 | ||||
| // LoggingConfig the logging configuration for a service | ||||
| type LoggingConfig struct { | ||||
| 	Driver  string  `yaml:"driver,omitempty" json:"driver,omitempty"` | ||||
| 	Options Options `yaml:"options,omitempty" json:"options,omitempty"` | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // DeployConfig the deployment configuration for a service | ||||
| type DeployConfig struct { | ||||
| 	Mode           string         `yaml:"mode,omitempty" json:"mode,omitempty"` | ||||
| 	Replicas       *uint64        `yaml:"replicas,omitempty" json:"replicas,omitempty"` | ||||
| 	Replicas       *int           `yaml:"replicas,omitempty" json:"replicas,omitempty"` | ||||
| 	Labels         Labels         `yaml:"labels,omitempty" json:"labels,omitempty"` | ||||
| 	UpdateConfig   *UpdateConfig  `yaml:"update_config,omitempty" json:"update_config,omitempty"` | ||||
| 	RollbackConfig *UpdateConfig  `yaml:"rollback_config,omitempty" json:"rollback_config,omitempty"` | ||||
| @@ -523,7 +336,7 @@ type DeployConfig struct { | ||||
| 	Placement      Placement      `yaml:"placement,omitempty" json:"placement,omitempty"` | ||||
| 	EndpointMode   string         `yaml:"endpoint_mode,omitempty" json:"endpoint_mode,omitempty"` | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // UpdateConfig the service update configuration | ||||
| @@ -535,7 +348,7 @@ type UpdateConfig struct { | ||||
| 	MaxFailureRatio float32  `yaml:"max_failure_ratio,omitempty" json:"max_failure_ratio,omitempty"` | ||||
| 	Order           string   `yaml:"order,omitempty" json:"order,omitempty"` | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // Resources the resource limits and reservations | ||||
| @@ -543,7 +356,7 @@ type Resources struct { | ||||
| 	Limits       *Resource `yaml:"limits,omitempty" json:"limits,omitempty"` | ||||
| 	Reservations *Resource `yaml:"reservations,omitempty" json:"reservations,omitempty"` | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // Resource is a resource to be limited or reserved | ||||
| @@ -555,7 +368,7 @@ type Resource struct { | ||||
| 	Devices          []DeviceRequest   `yaml:"devices,omitempty" json:"devices,omitempty"` | ||||
| 	GenericResources []GenericResource `yaml:"generic_resources,omitempty" json:"generic_resources,omitempty"` | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // GenericResource represents a "user defined" resource which can | ||||
| @@ -563,7 +376,7 @@ type Resource struct { | ||||
| type GenericResource struct { | ||||
| 	DiscreteResourceSpec *DiscreteGenericResource `yaml:"discrete_resource_spec,omitempty" json:"discrete_resource_spec,omitempty"` | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // DiscreteGenericResource represents a "user defined" resource which is defined | ||||
| @@ -574,7 +387,7 @@ type DiscreteGenericResource struct { | ||||
| 	Kind  string `json:"kind"` | ||||
| 	Value int64  `json:"value"` | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // RestartPolicy the service restart policy | ||||
| @@ -584,7 +397,7 @@ type RestartPolicy struct { | ||||
| 	MaxAttempts *uint64   `yaml:"max_attempts,omitempty" json:"max_attempts,omitempty"` | ||||
| 	Window      *Duration `yaml:"window,omitempty" json:"window,omitempty"` | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // Placement constraints for the service | ||||
| @@ -593,14 +406,14 @@ type Placement struct { | ||||
| 	Preferences []PlacementPreferences `yaml:"preferences,omitempty" json:"preferences,omitempty"` | ||||
| 	MaxReplicas uint64                 `yaml:"max_replicas_per_node,omitempty" json:"max_replicas_per_node,omitempty"` | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // PlacementPreferences is the preferences for a service placement | ||||
| type PlacementPreferences struct { | ||||
| 	Spread string `yaml:"spread,omitempty" json:"spread,omitempty"` | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // ServiceNetworkConfig is the network configuration for a service | ||||
| @@ -610,8 +423,9 @@ type ServiceNetworkConfig struct { | ||||
| 	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" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // ServicePortConfig is the port configuration for a service | ||||
| @@ -622,7 +436,7 @@ type ServicePortConfig struct { | ||||
| 	Published string `yaml:"published,omitempty" json:"published,omitempty"` | ||||
| 	Protocol  string `yaml:"protocol,omitempty" json:"protocol,omitempty"` | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // ParsePortConfig parse short syntax for service port configuration | ||||
| @@ -675,7 +489,7 @@ type ServiceVolumeConfig struct { | ||||
| 	Volume      *ServiceVolumeVolume `yaml:"volume,omitempty" json:"volume,omitempty"` | ||||
| 	Tmpfs       *ServiceVolumeTmpfs  `yaml:"tmpfs,omitempty" json:"tmpfs,omitempty"` | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // String render ServiceVolumeConfig as a volume string, one can parse back using loader.ParseVolume | ||||
| @@ -721,7 +535,7 @@ type ServiceVolumeBind struct { | ||||
| 	Propagation    string `yaml:"propagation,omitempty" json:"propagation,omitempty"` | ||||
| 	CreateHostPath bool   `yaml:"create_host_path,omitempty" json:"create_host_path,omitempty"` | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // SELinux represents the SELinux re-labeling options. | ||||
| @@ -752,7 +566,7 @@ const ( | ||||
| type ServiceVolumeVolume struct { | ||||
| 	NoCopy bool `yaml:"nocopy,omitempty" json:"nocopy,omitempty"` | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // ServiceVolumeTmpfs are options for a service volume of type tmpfs | ||||
| @@ -761,7 +575,7 @@ type ServiceVolumeTmpfs struct { | ||||
| 
 | ||||
| 	Mode uint32 `yaml:"mode,omitempty" json:"mode,omitempty"` | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // FileReferenceConfig for a reference to a swarm file object | ||||
| @@ -772,7 +586,7 @@ type FileReferenceConfig struct { | ||||
| 	GID    string  `yaml:"gid,omitempty" json:"gid,omitempty"` | ||||
| 	Mode   *uint32 `yaml:"mode,omitempty" json:"mode,omitempty"` | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // ServiceConfigObjConfig is the config obj configuration for a service | ||||
| @@ -787,7 +601,32 @@ type UlimitsConfig struct { | ||||
| 	Soft   int `yaml:"soft,omitempty" json:"soft,omitempty"` | ||||
| 	Hard   int `yaml:"hard,omitempty" json:"hard,omitempty"` | ||||
| 
 | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| func (u *UlimitsConfig) DecodeMapstructure(value interface{}) error { | ||||
| 	switch v := value.(type) { | ||||
| 	case *UlimitsConfig: | ||||
| 		// this call to DecodeMapstructure is triggered after initial value conversion as we use a map[string]*UlimitsConfig | ||||
| 		return nil | ||||
| 	case int: | ||||
| 		u.Single = v | ||||
| 		u.Soft = 0 | ||||
| 		u.Hard = 0 | ||||
| 	case map[string]any: | ||||
| 		u.Single = 0 | ||||
| 		soft, ok := v["soft"] | ||||
| 		if ok { | ||||
| 			u.Soft = soft.(int) | ||||
| 		} | ||||
| 		hard, ok := v["hard"] | ||||
| 		if ok { | ||||
| 			u.Hard = hard.(int) | ||||
| 		} | ||||
| 	default: | ||||
| 		return fmt.Errorf("unexpected value type %T for ulimit", value) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // MarshalYAML makes UlimitsConfig implement yaml.Marshaller | ||||
| @@ -824,14 +663,14 @@ type NetworkConfig struct { | ||||
| 	Attachable bool       `yaml:"attachable,omitempty" json:"attachable,omitempty"` | ||||
| 	Labels     Labels     `yaml:"labels,omitempty" json:"labels,omitempty"` | ||||
| 	EnableIPv6 bool       `yaml:"enable_ipv6,omitempty" json:"enable_ipv6,omitempty"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // IPAMConfig for a network | ||||
| type IPAMConfig struct { | ||||
| 	Driver     string      `yaml:"driver,omitempty" json:"driver,omitempty"` | ||||
| 	Config     []*IPAMPool `yaml:"config,omitempty" json:"config,omitempty"` | ||||
| 	Extensions Extensions  `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions  `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // IPAMPool for a network | ||||
| @@ -850,40 +689,19 @@ type VolumeConfig struct { | ||||
| 	DriverOpts Options    `yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"` | ||||
| 	External   External   `yaml:"external,omitempty" json:"external,omitempty"` | ||||
| 	Labels     Labels     `yaml:"labels,omitempty" json:"labels,omitempty"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // External identifies a Volume or Network as a reference to a resource that is | ||||
| // not managed, and should already exist. | ||||
| // External.name is deprecated and replaced by Volume.name | ||||
| type External struct { | ||||
| 	Name       string     `yaml:"name,omitempty" json:"name,omitempty"` | ||||
| 	External   bool       `yaml:"external,omitempty" json:"external,omitempty"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // MarshalYAML makes External implement yaml.Marshaller | ||||
| func (e External) MarshalYAML() (interface{}, error) { | ||||
| 	if e.Name == "" { | ||||
| 		return e.External, nil | ||||
| 	} | ||||
| 	return External{Name: e.Name}, nil | ||||
| } | ||||
| 
 | ||||
| // MarshalJSON makes External implement json.Marshaller | ||||
| func (e External) MarshalJSON() ([]byte, error) { | ||||
| 	if e.Name == "" { | ||||
| 		return []byte(fmt.Sprintf("%v", e.External)), nil | ||||
| 	} | ||||
| 	return []byte(fmt.Sprintf(`{"name": %q}`, e.Name)), nil | ||||
| } | ||||
| type External bool | ||||
| 
 | ||||
| // CredentialSpecConfig for credential spec on Windows | ||||
| type CredentialSpecConfig struct { | ||||
| 	Config     string     `yaml:"config,omitempty" json:"config,omitempty"` // Config was added in API v1.40 | ||||
| 	File       string     `yaml:"file,omitempty" json:"file,omitempty"` | ||||
| 	Registry   string     `yaml:"registry,omitempty" json:"registry,omitempty"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // FileObjectConfig is a config type for a file used by a service | ||||
| @@ -891,12 +709,13 @@ 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" json:"-"` | ||||
| 	Extensions     Extensions        `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| } | ||||
| 
 | ||||
| const ( | ||||
| @@ -915,7 +734,7 @@ type DependsOnConfig map[string]ServiceDependency | ||||
| type ServiceDependency struct { | ||||
| 	Condition  string     `yaml:"condition,omitempty" json:"condition,omitempty"` | ||||
| 	Restart    bool       `yaml:"restart,omitempty" json:"restart,omitempty"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline" json:"-"` | ||||
| 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` | ||||
| 	Required   bool       `yaml:"required" json:"required"` | ||||
| } | ||||
| 
 | ||||
| @@ -16,13 +16,15 @@ | ||||
| 
 | ||||
| package utils | ||||
| 
 | ||||
| import "golang.org/x/exp/slices" | ||||
| import ( | ||||
| 	"golang.org/x/exp/constraints" | ||||
| 	"golang.org/x/exp/maps" | ||||
| 	"golang.org/x/exp/slices" | ||||
| ) | ||||
| 
 | ||||
| func MapKeys[T comparable, U any](theMap map[T]U) []T { | ||||
| 	var result []T | ||||
| 	for key := range theMap { | ||||
| 		result = append(result, key) | ||||
| 	} | ||||
| func MapKeys[T constraints.Ordered, U any](theMap map[T]U) []T { | ||||
| 	result := maps.Keys(theMap) | ||||
| 	slices.Sort(result) | ||||
| 	return result | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										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 | ||||
| } | ||||
| @@ -22,16 +22,6 @@ import ( | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| // StringContains check if an array contains a specific value | ||||
| func StringContains(array []string, needle string) bool { | ||||
| 	for _, val := range array { | ||||
| 		if val == needle { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| // StringToBool converts a string to a boolean ignoring errors | ||||
| func StringToBool(s string) bool { | ||||
| 	b, _ := strconv.ParseBool(strings.ToLower(strings.TrimSpace(s))) | ||||
							
								
								
									
										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