mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-07-09 21:17:09 +08:00
Merge pull request #2283 from crazy-max/update-compose
vendor: update compose-go to v2.0.0-rc.8
This commit is contained in:
83
vendor/github.com/compose-spec/compose-go/v2/cli/options.go
generated
vendored
83
vendor/github.com/compose-spec/compose-go/v2/cli/options.go
generated
vendored
@ -37,8 +37,6 @@ import (
|
||||
|
||||
// ProjectOptions provides common configuration for loading a project.
|
||||
type ProjectOptions struct {
|
||||
ctx context.Context
|
||||
|
||||
// Name is a valid Compose project name to be used or empty.
|
||||
//
|
||||
// If empty, the project loader will automatically infer a reasonable
|
||||
@ -80,6 +78,10 @@ type ProjectOptions struct {
|
||||
EnvFiles []string
|
||||
|
||||
loadOptions []func(*loader.Options)
|
||||
|
||||
// Callbacks to retrieve metadata information during parse defined before
|
||||
// creating the project
|
||||
Listeners []loader.Listener
|
||||
}
|
||||
|
||||
type ProjectOptionsFn func(*ProjectOptions) error
|
||||
@ -89,6 +91,7 @@ func NewProjectOptions(configs []string, opts ...ProjectOptionsFn) (*ProjectOpti
|
||||
options := &ProjectOptions{
|
||||
ConfigPaths: configs,
|
||||
Environment: map[string]string{},
|
||||
Listeners: []loader.Listener{},
|
||||
}
|
||||
for _, o := range opts {
|
||||
err := o(options)
|
||||
@ -334,14 +337,6 @@ func WithResolvedPaths(resolve bool) ProjectOptionsFn {
|
||||
}
|
||||
}
|
||||
|
||||
// WithContext sets the context used to load model and resources
|
||||
func WithContext(ctx context.Context) ProjectOptionsFn {
|
||||
return func(o *ProjectOptions) error {
|
||||
o.ctx = ctx
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithResourceLoader register support for ResourceLoader to manage remote resources
|
||||
func WithResourceLoader(r loader.ResourceLoader) ProjectOptionsFn {
|
||||
return func(o *ProjectOptions) error {
|
||||
@ -352,6 +347,24 @@ func WithResourceLoader(r loader.ResourceLoader) ProjectOptionsFn {
|
||||
}
|
||||
}
|
||||
|
||||
// WithExtension register a know extension `x-*` with the go struct type to decode into
|
||||
func WithExtension(name string, typ any) ProjectOptionsFn {
|
||||
return func(o *ProjectOptions) error {
|
||||
o.loadOptions = append(o.loadOptions, func(options *loader.Options) {
|
||||
if options.KnownExtensions == nil {
|
||||
options.KnownExtensions = map[string]any{}
|
||||
}
|
||||
options.KnownExtensions[name] = typ
|
||||
})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Append listener to event
|
||||
func (o *ProjectOptions) WithListeners(listeners ...loader.Listener) {
|
||||
o.Listeners = append(o.Listeners, listeners...)
|
||||
}
|
||||
|
||||
// WithoutEnvironmentResolution disable environment resolution
|
||||
func WithoutEnvironmentResolution(o *ProjectOptions) error {
|
||||
o.loadOptions = append(o.loadOptions, func(options *loader.Options) {
|
||||
@ -368,7 +381,7 @@ var DefaultOverrideFileNames = []string{"compose.override.yml", "compose.overrid
|
||||
|
||||
func (o ProjectOptions) GetWorkingDir() (string, error) {
|
||||
if o.WorkingDir != "" {
|
||||
return o.WorkingDir, nil
|
||||
return filepath.Abs(o.WorkingDir)
|
||||
}
|
||||
for _, path := range o.ConfigPaths {
|
||||
if path != "-" {
|
||||
@ -382,9 +395,8 @@ func (o ProjectOptions) GetWorkingDir() (string, error) {
|
||||
return os.Getwd()
|
||||
}
|
||||
|
||||
// ProjectFromOptions load a compose project based on command line options
|
||||
func ProjectFromOptions(options *ProjectOptions) (*types.Project, error) {
|
||||
configPaths, err := getConfigPathsFromOptions(options)
|
||||
func (o ProjectOptions) GeConfigFiles() ([]types.ConfigFile, error) {
|
||||
configPaths, err := o.getConfigPaths()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -412,24 +424,25 @@ func ProjectFromOptions(options *ProjectOptions) (*types.Project, error) {
|
||||
Content: b,
|
||||
})
|
||||
}
|
||||
return configs, err
|
||||
}
|
||||
|
||||
// ProjectFromOptions load a compose project based on command line options
|
||||
func ProjectFromOptions(ctx context.Context, options *ProjectOptions) (*types.Project, error) {
|
||||
configs, err := options.GeConfigFiles()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
workingDir, err := options.GetWorkingDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
absWorkingDir, err := filepath.Abs(workingDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
options.loadOptions = append(options.loadOptions,
|
||||
withNamePrecedenceLoad(absWorkingDir, options),
|
||||
withConvertWindowsPaths(options))
|
||||
|
||||
ctx := options.ctx
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
withNamePrecedenceLoad(workingDir, options),
|
||||
withConvertWindowsPaths(options),
|
||||
withListeners(options))
|
||||
|
||||
project, err := loader.LoadWithContext(ctx, types.ConfigDetails{
|
||||
ConfigFiles: configs,
|
||||
@ -440,7 +453,10 @@ func ProjectFromOptions(options *ProjectOptions) (*types.Project, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
project.ComposeFiles = configPaths
|
||||
for _, config := range configs {
|
||||
project.ComposeFiles = append(project.ComposeFiles, config.Filename)
|
||||
}
|
||||
|
||||
return project, nil
|
||||
}
|
||||
|
||||
@ -467,10 +483,17 @@ func withConvertWindowsPaths(options *ProjectOptions) func(*loader.Options) {
|
||||
}
|
||||
}
|
||||
|
||||
// getConfigPathsFromOptions retrieves the config files for project based on project options
|
||||
func getConfigPathsFromOptions(options *ProjectOptions) ([]string, error) {
|
||||
if len(options.ConfigPaths) != 0 {
|
||||
return absolutePaths(options.ConfigPaths)
|
||||
// save listeners from ProjectOptions (compose) to loader.Options
|
||||
func withListeners(options *ProjectOptions) func(*loader.Options) {
|
||||
return func(opts *loader.Options) {
|
||||
opts.Listeners = append(opts.Listeners, options.Listeners...)
|
||||
}
|
||||
}
|
||||
|
||||
// getConfigPaths retrieves the config files for project based on project options
|
||||
func (o *ProjectOptions) getConfigPaths() ([]string, error) {
|
||||
if len(o.ConfigPaths) != 0 {
|
||||
return absolutePaths(o.ConfigPaths)
|
||||
}
|
||||
return nil, fmt.Errorf("no configuration file provided: %w", errdefs.ErrNotFound)
|
||||
}
|
||||
|
59
vendor/github.com/compose-spec/compose-go/v2/loader/environment.go
generated
vendored
Normal file
59
vendor/github.com/compose-spec/compose-go/v2/loader/environment.go
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
Copyright 2020 The Compose Specification Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package loader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
)
|
||||
|
||||
// Will update the environment variables for the format {- VAR} (without interpolation)
|
||||
// This function should resolve context environment vars for include (passed in env_file)
|
||||
func resolveServicesEnvironment(dict map[string]any, config types.ConfigDetails) {
|
||||
services, ok := dict["services"].(map[string]any)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
for service, cfg := range services {
|
||||
serviceConfig, ok := cfg.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
serviceEnv, ok := serviceConfig["environment"].([]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
envs := []any{}
|
||||
for _, env := range serviceEnv {
|
||||
varEnv, ok := env.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if found, ok := config.Environment[varEnv]; ok {
|
||||
envs = append(envs, fmt.Sprintf("%s=%s", varEnv, found))
|
||||
} else {
|
||||
// either does not exist or it was already resolved in interpolation
|
||||
envs = append(envs, varEnv)
|
||||
}
|
||||
}
|
||||
serviceConfig["environment"] = envs
|
||||
services[service] = serviceConfig
|
||||
}
|
||||
dict["services"] = services
|
||||
}
|
50
vendor/github.com/compose-spec/compose-go/v2/loader/extends.go
generated
vendored
50
vendor/github.com/compose-spec/compose-go/v2/loader/extends.go
generated
vendored
@ -20,6 +20,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/consts"
|
||||
"github.com/compose-spec/compose-go/v2/override"
|
||||
@ -60,11 +61,8 @@ func applyServiceExtends(ctx context.Context, name string, services map[string]a
|
||||
return s, nil
|
||||
}
|
||||
filename := ctx.Value(consts.ComposeFileKey{}).(string)
|
||||
tracker, err := tracker.Add(filename, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
err error
|
||||
ref string
|
||||
file any
|
||||
)
|
||||
@ -72,14 +70,16 @@ func applyServiceExtends(ctx context.Context, name string, services map[string]a
|
||||
case map[string]any:
|
||||
ref = v["service"].(string)
|
||||
file = v["file"]
|
||||
opts.ProcessEvent("extends", v)
|
||||
case string:
|
||||
ref = v
|
||||
opts.ProcessEvent("extends", map[string]any{"service": ref})
|
||||
}
|
||||
|
||||
var base any
|
||||
if file != nil {
|
||||
path := file.(string)
|
||||
services, err = getExtendsBaseFromFile(ctx, ref, path, opts, tracker)
|
||||
filename = file.(string)
|
||||
services, err = getExtendsBaseFromFile(ctx, ref, filename, opts, tracker)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -89,6 +89,12 @@ func applyServiceExtends(ctx context.Context, name string, services map[string]a
|
||||
return nil, fmt.Errorf("cannot extend service %q in %s: service not found", name, filename)
|
||||
}
|
||||
}
|
||||
|
||||
tracker, err = tracker.Add(filename, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// recursively apply `extends`
|
||||
base, err = applyServiceExtends(ctx, ref, services, opts, tracker, post...)
|
||||
if err != nil {
|
||||
@ -99,6 +105,12 @@ func applyServiceExtends(ctx context.Context, name string, services map[string]a
|
||||
return service, nil
|
||||
}
|
||||
source := deepClone(base).(map[string]any)
|
||||
|
||||
err = validateExtendSource(source, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, processor := range post {
|
||||
processor.Apply(map[string]any{
|
||||
"services": map[string]any{
|
||||
@ -111,9 +123,34 @@ func applyServiceExtends(ctx context.Context, name string, services map[string]a
|
||||
return nil, err
|
||||
}
|
||||
delete(merged, "extends")
|
||||
services[name] = merged
|
||||
return merged, nil
|
||||
}
|
||||
|
||||
// validateExtendSource check the source for `extends` doesn't refer to another container/service
|
||||
func validateExtendSource(source map[string]any, ref string) error {
|
||||
forbidden := []string{"links", "volumes_from", "depends_on"}
|
||||
for _, key := range forbidden {
|
||||
if _, ok := source[key]; ok {
|
||||
return fmt.Errorf("service %q can't be used with `extends` as it declare `%s`", ref, key)
|
||||
}
|
||||
}
|
||||
|
||||
sharedNamespace := []string{"network_mode", "ipc", "pid", "net", "cgroup", "userns_mode", "uts"}
|
||||
for _, key := range sharedNamespace {
|
||||
if v, ok := source[key]; ok {
|
||||
val := v.(string)
|
||||
if strings.HasPrefix(val, types.ContainerPrefix) {
|
||||
return fmt.Errorf("service %q can't be used with `extends` as it shares `%s` with another container", ref, key)
|
||||
}
|
||||
if strings.HasPrefix(val, types.ServicePrefix) {
|
||||
return fmt.Errorf("service %q can't be used with `extends` as it shares `%s` with another service", ref, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
return 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) {
|
||||
@ -137,6 +174,7 @@ func getExtendsBaseFromFile(ctx context.Context, name string, path string, opts
|
||||
extendsOpts.SkipInclude = true
|
||||
extendsOpts.SkipExtends = true // we manage extends recursively based on raw service definition
|
||||
extendsOpts.SkipValidation = true // we validate the merge result
|
||||
extendsOpts.SkipDefaultValues = true
|
||||
source, err := loadYamlModel(ctx, types.ConfigDetails{
|
||||
WorkingDir: relworkingdir,
|
||||
ConfigFiles: []types.ConfigFile{
|
||||
|
6
vendor/github.com/compose-spec/compose-go/v2/loader/full-example.yml
generated
vendored
6
vendor/github.com/compose-spec/compose-go/v2/loader/full-example.yml
generated
vendored
@ -26,7 +26,8 @@ services:
|
||||
additional_contexts:
|
||||
foo: ./bar
|
||||
secrets:
|
||||
- secret1
|
||||
- source: secret1
|
||||
target: /run/secrets/secret1
|
||||
- source: secret2
|
||||
target: my_secret
|
||||
uid: '103'
|
||||
@ -257,7 +258,8 @@ services:
|
||||
restart: always
|
||||
|
||||
secrets:
|
||||
- secret1
|
||||
- source: secret1
|
||||
target: /run/secrets/secret1
|
||||
- source: secret2
|
||||
target: my_secret
|
||||
uid: '103'
|
||||
|
43
vendor/github.com/compose-spec/compose-go/v2/loader/include.go
generated
vendored
43
vendor/github.com/compose-spec/compose-go/v2/loader/include.go
generated
vendored
@ -34,6 +34,14 @@ func loadIncludeConfig(source any) ([]types.IncludeConfig, error) {
|
||||
if source == nil {
|
||||
return nil, nil
|
||||
}
|
||||
configs := source.([]any)
|
||||
for i, config := range configs {
|
||||
if v, ok := config.(string); ok {
|
||||
configs[i] = map[string]any{
|
||||
"path": v,
|
||||
}
|
||||
}
|
||||
}
|
||||
var requires []types.IncludeConfig
|
||||
err := Transform(source, &requires)
|
||||
return requires, err
|
||||
@ -45,6 +53,13 @@ func ApplyInclude(ctx context.Context, configDetails types.ConfigDetails, model
|
||||
return err
|
||||
}
|
||||
for _, r := range includeConfig {
|
||||
for _, listener := range options.Listeners {
|
||||
listener("include", map[string]any{
|
||||
"path": r.Path,
|
||||
"workingdir": configDetails.WorkingDir,
|
||||
})
|
||||
}
|
||||
|
||||
for i, p := range r.Path {
|
||||
for _, loader := range options.ResourceLoaders {
|
||||
if loader.Accept(p) {
|
||||
@ -56,7 +71,7 @@ func ApplyInclude(ctx context.Context, configDetails types.ConfigDetails, model
|
||||
break
|
||||
}
|
||||
}
|
||||
r.Path[i] = absPath(configDetails.WorkingDir, p)
|
||||
r.Path[i] = p
|
||||
}
|
||||
|
||||
mainFile := r.Path[0]
|
||||
@ -70,17 +85,41 @@ func ApplyInclude(ctx context.Context, configDetails types.ConfigDetails, model
|
||||
if r.ProjectDirectory == "" {
|
||||
r.ProjectDirectory = filepath.Dir(mainFile)
|
||||
}
|
||||
relworkingdir, err := filepath.Rel(configDetails.WorkingDir, r.ProjectDirectory)
|
||||
if err != nil {
|
||||
// included file path is not inside project working directory => use absolute path
|
||||
relworkingdir = r.ProjectDirectory
|
||||
}
|
||||
|
||||
loadOptions := options.clone()
|
||||
loadOptions.ResolvePaths = true
|
||||
loadOptions.SkipNormalization = true
|
||||
loadOptions.SkipConsistencyCheck = true
|
||||
loadOptions.ResourceLoaders = append(loadOptions.RemoteResourceLoaders(), localResourceLoader{
|
||||
WorkingDir: relworkingdir,
|
||||
})
|
||||
|
||||
if len(r.EnvFile) == 0 {
|
||||
f := filepath.Join(r.ProjectDirectory, ".env")
|
||||
if s, err := os.Stat(f); err == nil && !s.IsDir() {
|
||||
r.EnvFile = types.StringList{f}
|
||||
}
|
||||
} else {
|
||||
envFile := []string{}
|
||||
for _, f := range r.EnvFile {
|
||||
if !filepath.IsAbs(f) {
|
||||
f = filepath.Join(configDetails.WorkingDir, f)
|
||||
s, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if s.IsDir() {
|
||||
return fmt.Errorf("%s is not a file", f)
|
||||
}
|
||||
}
|
||||
envFile = append(envFile, f)
|
||||
}
|
||||
r.EnvFile = envFile
|
||||
}
|
||||
|
||||
envFromFile, err := dotenv.GetEnvFromFile(configDetails.Environment, r.EnvFile)
|
||||
@ -89,7 +128,7 @@ func ApplyInclude(ctx context.Context, configDetails types.ConfigDetails, model
|
||||
}
|
||||
|
||||
config := types.ConfigDetails{
|
||||
WorkingDir: r.ProjectDirectory,
|
||||
WorkingDir: relworkingdir,
|
||||
ConfigFiles: types.ToConfigFiles(r.Path),
|
||||
Environment: configDetails.Environment.Clone().Merge(envFromFile),
|
||||
}
|
||||
|
182
vendor/github.com/compose-spec/compose-go/v2/loader/loader.go
generated
vendored
182
vendor/github.com/compose-spec/compose-go/v2/loader/loader.go
generated
vendored
@ -64,6 +64,8 @@ type Options struct {
|
||||
SkipInclude bool
|
||||
// SkipResolveEnvironment will ignore computing `environment` for services
|
||||
SkipResolveEnvironment bool
|
||||
// SkipDefaultValues will ignore missing required attributes
|
||||
SkipDefaultValues bool
|
||||
// Interpolation options
|
||||
Interpolate *interp.Options
|
||||
// Discard 'env_file' entries after resolving to 'environment' section
|
||||
@ -76,6 +78,19 @@ type Options struct {
|
||||
Profiles []string
|
||||
// ResourceLoaders manages support for remote resources
|
||||
ResourceLoaders []ResourceLoader
|
||||
// KnownExtensions manages x-* attribute we know and the corresponding go structs
|
||||
KnownExtensions map[string]any
|
||||
// Metada for telemetry
|
||||
Listeners []Listener
|
||||
}
|
||||
|
||||
type Listener = func(event string, metadata map[string]any)
|
||||
|
||||
// Invoke all listeners for an event
|
||||
func (o *Options) ProcessEvent(event string, metadata map[string]any) {
|
||||
for _, l := range o.Listeners {
|
||||
l(event, metadata)
|
||||
}
|
||||
}
|
||||
|
||||
// ResourceLoader is a plugable remote resource resolver
|
||||
@ -148,6 +163,8 @@ func (o *Options) clone() *Options {
|
||||
projectNameImperativelySet: o.projectNameImperativelySet,
|
||||
Profiles: o.Profiles,
|
||||
ResourceLoaders: o.ResourceLoaders,
|
||||
KnownExtensions: o.KnownExtensions,
|
||||
Listeners: o.Listeners,
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,18 +305,17 @@ func LoadWithContext(ctx context.Context, configDetails types.ConfigDetails, opt
|
||||
}
|
||||
opts.ResourceLoaders = append(opts.ResourceLoaders, localResourceLoader{configDetails.WorkingDir})
|
||||
|
||||
projectName, err := projectName(configDetails, opts)
|
||||
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 _, ok := configDetails.Environment[consts.ComposeProjectName]; !ok && opts.projectName != "" {
|
||||
if configDetails.Environment == nil {
|
||||
configDetails.Environment = map[string]string{}
|
||||
}
|
||||
configDetails.Environment[consts.ComposeProjectName] = projectName
|
||||
configDetails.Environment[consts.ComposeProjectName] = opts.projectName
|
||||
}
|
||||
|
||||
return load(ctx, configDetails, opts, nil)
|
||||
@ -312,7 +328,7 @@ func loadYamlModel(ctx context.Context, config types.ConfigDetails, opts *Option
|
||||
)
|
||||
for _, file := range config.ConfigFiles {
|
||||
fctx := context.WithValue(ctx, consts.ComposeFileKey{}, file.Filename)
|
||||
if len(file.Content) == 0 && file.Config == nil {
|
||||
if file.Content == nil && file.Config == nil {
|
||||
content, err := os.ReadFile(file.Filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -352,6 +368,14 @@ func loadYamlModel(ctx context.Context, config types.ConfigDetails, opts *Option
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.SkipInclude {
|
||||
included = append(included, config.ConfigFiles[0].Filename)
|
||||
err = ApplyInclude(ctx, config, cfg, opts, included)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
dict, err = override.Merge(dict, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -400,9 +424,14 @@ func loadYamlModel(ctx context.Context, config types.ConfigDetails, opts *Option
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !opts.SkipInclude {
|
||||
included = append(included, config.ConfigFiles[0].Filename)
|
||||
err = ApplyInclude(ctx, config, dict, opts, included)
|
||||
// Canonical transformation can reveal duplicates, typically as ports can be a range and conflict with an override
|
||||
dict, err = override.EnforceUnicity(dict)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !opts.SkipDefaultValues {
|
||||
dict, err = transform.SetDefaultValues(dict)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -424,6 +453,7 @@ func loadYamlModel(ctx context.Context, config types.ConfigDetails, opts *Option
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
resolveServicesEnvironment(dict, config)
|
||||
|
||||
return dict, nil
|
||||
}
|
||||
@ -438,8 +468,6 @@ func load(ctx context.Context, configDetails types.ConfigDetails, opts *Options,
|
||||
}
|
||||
loaded = append(loaded, mainFile)
|
||||
|
||||
includeRefs := make(map[string][]types.IncludeConfig)
|
||||
|
||||
dict, err := loadYamlModel(ctx, configDetails, opts, &cycleTracker{}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -449,6 +477,10 @@ func load(ctx context.Context, configDetails types.ConfigDetails, opts *Options,
|
||||
return nil, errors.New("empty compose file")
|
||||
}
|
||||
|
||||
if opts.projectName == "" {
|
||||
return nil, errors.New("project name must not be empty")
|
||||
}
|
||||
|
||||
project := &types.Project{
|
||||
Name: opts.projectName,
|
||||
WorkingDir: configDetails.WorkingDir,
|
||||
@ -456,14 +488,14 @@ func load(ctx context.Context, configDetails types.ConfigDetails, opts *Options,
|
||||
}
|
||||
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)
|
||||
dict, err = processExtensions(dict, tree.NewPath(), opts.KnownExtensions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(includeRefs) != 0 {
|
||||
project.IncludeReferences = includeRefs
|
||||
err = Transform(dict, project)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !opts.SkipNormalization {
|
||||
@ -516,69 +548,68 @@ func InvalidProjectNameErr(v string) error {
|
||||
//
|
||||
// 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()
|
||||
func projectName(details types.ConfigDetails, opts *Options) error {
|
||||
if opts.projectNameImperativelySet {
|
||||
if NormalizeProjectName(opts.projectName) != opts.projectName {
|
||||
return InvalidProjectNameErr(opts.projectName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type named struct {
|
||||
Name string `yaml:"name"`
|
||||
}
|
||||
|
||||
// if user did NOT provide a name explicitly, then see if one is defined
|
||||
// in any of the config files
|
||||
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
|
||||
var pjNameFromConfigFile string
|
||||
for _, configFile := range details.ConfigFiles {
|
||||
content := configFile.Content
|
||||
if content == nil {
|
||||
// This can be hit when Filename is set but Content is not. One
|
||||
// example is when using ToConfigFiles().
|
||||
d, err := os.ReadFile(configFile.Filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read file %q: %w", configFile.Filename, err)
|
||||
}
|
||||
content = d
|
||||
configFile.Content = d
|
||||
}
|
||||
var n named
|
||||
r := bytes.NewReader(content)
|
||||
decoder := yaml.NewDecoder(r)
|
||||
for {
|
||||
err := decoder.Decode(&n)
|
||||
if err != nil && errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
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
|
||||
break
|
||||
}
|
||||
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 n.Name != "" {
|
||||
pjNameFromConfigFile = n.Name
|
||||
}
|
||||
}
|
||||
if !opts.SkipInterpolation {
|
||||
interpolated, err := interp.Interpolate(
|
||||
map[string]interface{}{"name": pjNameFromConfigFile},
|
||||
*opts.Interpolate,
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pjNameFromConfigFile = interpolated["name"].(string)
|
||||
}
|
||||
pjNameFromConfigFile = NormalizeProjectName(pjNameFromConfigFile)
|
||||
if pjNameFromConfigFile != "" {
|
||||
projectName = pjNameFromConfigFile
|
||||
}
|
||||
if !opts.SkipInterpolation {
|
||||
interpolated, err := interp.Interpolate(
|
||||
map[string]interface{}{"name": pjNameFromConfigFile},
|
||||
*opts.Interpolate,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pjNameFromConfigFile = interpolated["name"].(string)
|
||||
}
|
||||
|
||||
if projectName == "" {
|
||||
return "", errors.New("project name must not be empty")
|
||||
pjNameFromConfigFile = NormalizeProjectName(pjNameFromConfigFile)
|
||||
if pjNameFromConfigFile != "" {
|
||||
opts.projectName = pjNameFromConfigFile
|
||||
}
|
||||
|
||||
if NormalizeProjectName(projectName) != projectName {
|
||||
return "", InvalidProjectNameErr(projectName)
|
||||
}
|
||||
|
||||
return projectName, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func NormalizeProjectName(s string) string {
|
||||
@ -596,8 +627,9 @@ var userDefinedKeys = []tree.Path{
|
||||
"configs",
|
||||
}
|
||||
|
||||
func groupXFieldsIntoExtensions(dict map[string]interface{}, p tree.Path) map[string]interface{} {
|
||||
extras := map[string]interface{}{}
|
||||
func processExtensions(dict map[string]any, p tree.Path, extensions map[string]any) (map[string]interface{}, error) {
|
||||
extras := map[string]any{}
|
||||
var err error
|
||||
for key, value := range dict {
|
||||
skip := false
|
||||
for _, uk := range userDefinedKeys {
|
||||
@ -613,19 +645,35 @@ func groupXFieldsIntoExtensions(dict map[string]interface{}, p tree.Path) map[st
|
||||
}
|
||||
switch v := value.(type) {
|
||||
case map[string]interface{}:
|
||||
dict[key] = groupXFieldsIntoExtensions(v, p.Next(key))
|
||||
dict[key], err = processExtensions(v, p.Next(key), extensions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case []interface{}:
|
||||
for i, e := range v {
|
||||
if m, ok := e.(map[string]interface{}); ok {
|
||||
v[i] = groupXFieldsIntoExtensions(m, p.Next(strconv.Itoa(i)))
|
||||
v[i], err = processExtensions(m, p.Next(strconv.Itoa(i)), extensions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for name, val := range extras {
|
||||
if typ, ok := extensions[name]; ok {
|
||||
target := reflect.New(reflect.TypeOf(typ)).Elem().Interface()
|
||||
err = Transform(val, &target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extras[name] = target
|
||||
}
|
||||
}
|
||||
if len(extras) > 0 {
|
||||
dict[consts.Extensions] = extras
|
||||
}
|
||||
return dict
|
||||
return dict, nil
|
||||
}
|
||||
|
||||
// Transform converts the source into the target struct with compose types transformer
|
||||
|
19
vendor/github.com/compose-spec/compose-go/v2/loader/paths.go
generated
vendored
19
vendor/github.com/compose-spec/compose-go/v2/loader/paths.go
generated
vendored
@ -37,25 +37,6 @@ func ResolveRelativePaths(project *types.Project) error {
|
||||
return err
|
||||
}
|
||||
project.ComposeFiles = absComposeFiles
|
||||
|
||||
// don't coerce a nil map to an empty map
|
||||
if project.IncludeReferences != nil {
|
||||
absIncludes := make(map[string][]types.IncludeConfig, len(project.IncludeReferences))
|
||||
for filename, config := range project.IncludeReferences {
|
||||
filename = absPath(project.WorkingDir, filename)
|
||||
absConfigs := make([]types.IncludeConfig, len(config))
|
||||
for i, c := range config {
|
||||
absConfigs[i] = types.IncludeConfig{
|
||||
Path: resolvePaths(project.WorkingDir, c.Path),
|
||||
ProjectDirectory: absPath(project.WorkingDir, c.ProjectDirectory),
|
||||
EnvFile: resolvePaths(project.WorkingDir, c.EnvFile),
|
||||
}
|
||||
}
|
||||
absIncludes[filename] = absConfigs
|
||||
}
|
||||
project.IncludeReferences = absIncludes
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
17
vendor/github.com/compose-spec/compose-go/v2/loader/validate.go
generated
vendored
17
vendor/github.com/compose-spec/compose-go/v2/loader/validate.go
generated
vendored
@ -29,6 +29,7 @@ import (
|
||||
|
||||
// checkConsistency validate a compose model is consistent
|
||||
func checkConsistency(project *types.Project) error {
|
||||
containerNames := map[string]string{}
|
||||
for _, s := range project.Services {
|
||||
if s.Build == nil && s.Image == "" {
|
||||
return fmt.Errorf("service %q has neither an image nor a build context specified: %w", s.Name, errdefs.ErrInvalid)
|
||||
@ -123,6 +124,13 @@ func checkConsistency(project *types.Project) error {
|
||||
s.Deploy.Replicas = s.Scale
|
||||
}
|
||||
|
||||
if s.ContainerName != "" {
|
||||
if existing, ok := containerNames[s.ContainerName]; ok {
|
||||
return fmt.Errorf(`"services.%s": container name "%s" is already in use by "services.%s": %w`, s.Name, s.ContainerName, existing, errdefs.ErrInvalid)
|
||||
}
|
||||
containerNames[s.ContainerName] = s.Name
|
||||
}
|
||||
|
||||
if s.GetScale() > 1 && s.ContainerName != "" {
|
||||
attr := "scale"
|
||||
if s.Scale == nil {
|
||||
@ -131,6 +139,15 @@ func checkConsistency(project *types.Project) error {
|
||||
return fmt.Errorf("services.%s: can't set container_name and %s as container name must be unique: %w", attr,
|
||||
s.Name, errdefs.ErrInvalid)
|
||||
}
|
||||
|
||||
if s.Develop != nil && s.Develop.Watch != nil {
|
||||
for _, watch := range s.Develop.Watch {
|
||||
if watch.Action != types.WatchActionRebuild && watch.Target == "" {
|
||||
return fmt.Errorf("services.%s.develop.watch: target is required for non-rebuild actions: %w", s.Name, errdefs.ErrInvalid)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
for name, secret := range project.Secrets {
|
||||
|
8
vendor/github.com/compose-spec/compose-go/v2/override/merge.go
generated
vendored
8
vendor/github.com/compose-spec/compose-go/v2/override/merge.go
generated
vendored
@ -17,6 +17,7 @@
|
||||
package override
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@ -40,10 +41,13 @@ var mergeSpecials = map[tree.Path]merger{}
|
||||
|
||||
func init() {
|
||||
mergeSpecials["networks.*.ipam.config"] = mergeIPAMConfig
|
||||
mergeSpecials["networks.*.labels"] = mergeToSequence
|
||||
mergeSpecials["volumes.*.labels"] = mergeToSequence
|
||||
mergeSpecials["services.*.annotations"] = mergeToSequence
|
||||
mergeSpecials["services.*.build"] = mergeBuild
|
||||
mergeSpecials["services.*.build.args"] = mergeToSequence
|
||||
mergeSpecials["services.*.build.additional_contexts"] = mergeToSequence
|
||||
mergeSpecials["services.*.build.extra_hosts"] = mergeToSequence
|
||||
mergeSpecials["services.*.build.labels"] = mergeToSequence
|
||||
mergeSpecials["services.*.command"] = override
|
||||
mergeSpecials["services.*.depends_on"] = mergeDependsOn
|
||||
@ -178,8 +182,8 @@ func convertIntoSequence(value any) []any {
|
||||
}
|
||||
i++
|
||||
}
|
||||
slices.SortFunc(seq, func(a, b any) bool {
|
||||
return a.(string) < b.(string)
|
||||
slices.SortFunc(seq, func(a, b any) int {
|
||||
return cmp.Compare(a.(string), b.(string))
|
||||
})
|
||||
return seq
|
||||
case []any:
|
||||
|
16
vendor/github.com/compose-spec/compose-go/v2/override/uncity.go
generated
vendored
16
vendor/github.com/compose-spec/compose-go/v2/override/uncity.go
generated
vendored
@ -107,13 +107,17 @@ func enforceUnicity(value any, p tree.Path) (any, error) {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func keyValueIndexer(y any, _ tree.Path) (string, error) {
|
||||
value := y.(string)
|
||||
key, _, found := strings.Cut(value, "=")
|
||||
if !found {
|
||||
return value, nil
|
||||
func keyValueIndexer(y any, p tree.Path) (string, error) {
|
||||
switch value := y.(type) {
|
||||
case string:
|
||||
key, _, found := strings.Cut(value, "=")
|
||||
if !found {
|
||||
return value, nil
|
||||
}
|
||||
return key, nil
|
||||
default:
|
||||
return "", fmt.Errorf("%s: unexpected type %T", p, y)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func volumeIndexer(y any, p tree.Path) (string, error) {
|
||||
|
2
vendor/github.com/compose-spec/compose-go/v2/schema/compose-spec.json
generated
vendored
2
vendor/github.com/compose-spec/compose-go/v2/schema/compose-spec.json
generated
vendored
@ -455,6 +455,7 @@
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["path", "action"],
|
||||
"properties": {
|
||||
"ignore": {"type": "array", "items": {"type": "string"}},
|
||||
"path": {"type": "string"},
|
||||
@ -462,7 +463,6 @@
|
||||
"target": {"type": "string"}
|
||||
}
|
||||
},
|
||||
"required": ["path", "action"],
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
}
|
||||
|
2
vendor/github.com/compose-spec/compose-go/v2/template/template.go
generated
vendored
2
vendor/github.com/compose-spec/compose-go/v2/template/template.go
generated
vendored
@ -258,7 +258,7 @@ func getFirstBraceClosingIndex(s string) int {
|
||||
return i
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(s[i:], "${") {
|
||||
if s[i] == '{' {
|
||||
openVariableBraces++
|
||||
i++
|
||||
}
|
||||
|
15
vendor/github.com/compose-spec/compose-go/v2/transform/build.go
generated
vendored
15
vendor/github.com/compose-spec/compose-go/v2/transform/build.go
generated
vendored
@ -25,9 +25,6 @@ import (
|
||||
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{
|
||||
@ -37,3 +34,15 @@ func transformBuild(data any, p tree.Path) (any, error) {
|
||||
return data, fmt.Errorf("%s: invalid type %T for build", p, v)
|
||||
}
|
||||
}
|
||||
|
||||
func defaultBuildContext(data any, _ tree.Path) (any, error) {
|
||||
switch v := data.(type) {
|
||||
case map[string]any:
|
||||
if _, ok := v["context"]; !ok {
|
||||
v["context"] = "."
|
||||
}
|
||||
return v, nil
|
||||
default:
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
87
vendor/github.com/compose-spec/compose-go/v2/transform/defaults.go
generated
vendored
Normal file
87
vendor/github.com/compose-spec/compose-go/v2/transform/defaults.go
generated
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
Copyright 2020 The Compose Specification Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package transform
|
||||
|
||||
import (
|
||||
"github.com/compose-spec/compose-go/v2/tree"
|
||||
)
|
||||
|
||||
var defaultValues = map[tree.Path]transformFunc{}
|
||||
|
||||
func init() {
|
||||
defaultValues["services.*.build"] = defaultBuildContext
|
||||
defaultValues["services.*.secrets.*"] = defaultSecretMount
|
||||
}
|
||||
|
||||
// SetDefaultValues transforms a compose model to set default values to missing attributes
|
||||
func SetDefaultValues(yaml map[string]any) (map[string]any, error) {
|
||||
result, err := setDefaults(yaml, tree.NewPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.(map[string]any), nil
|
||||
}
|
||||
|
||||
func setDefaults(data any, p tree.Path) (any, error) {
|
||||
for pattern, transformer := range defaultValues {
|
||||
if p.Matches(pattern) {
|
||||
t, err := transformer(data, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
switch v := data.(type) {
|
||||
case map[string]any:
|
||||
a, err := setDefaultsMapping(v, p)
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
return v, nil
|
||||
case []any:
|
||||
a, err := setDefaultsSequence(v, p)
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
return v, nil
|
||||
default:
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
func setDefaultsSequence(v []any, p tree.Path) ([]any, error) {
|
||||
for i, e := range v {
|
||||
t, err := setDefaults(e, p.Next("[]"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v[i] = t
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func setDefaultsMapping(v map[string]any, p tree.Path) (map[string]any, error) {
|
||||
for k, e := range v {
|
||||
t, err := setDefaults(e, p.Next(k))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v[k] = t
|
||||
}
|
||||
return v, nil
|
||||
}
|
2
vendor/github.com/compose-spec/compose-go/v2/transform/ports.go
generated
vendored
2
vendor/github.com/compose-spec/compose-go/v2/transform/ports.go
generated
vendored
@ -48,7 +48,7 @@ func transformPorts(data any, p tree.Path) (any, error) {
|
||||
case string:
|
||||
parsed, err := types.ParsePortConfig(value)
|
||||
if err != nil {
|
||||
return data, err
|
||||
return data, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
13
vendor/github.com/compose-spec/compose-go/v2/transform/secrets.go
generated
vendored
13
vendor/github.com/compose-spec/compose-go/v2/transform/secrets.go
generated
vendored
@ -34,3 +34,16 @@ func transformFileMount(data any, p tree.Path) (any, error) {
|
||||
return nil, fmt.Errorf("%s: unsupported type %T", p, data)
|
||||
}
|
||||
}
|
||||
|
||||
func defaultSecretMount(data any, p tree.Path) (any, error) {
|
||||
switch v := data.(type) {
|
||||
case map[string]any:
|
||||
source := v["source"]
|
||||
if _, ok := v["target"]; !ok {
|
||||
v["target"] = fmt.Sprintf("/run/secrets/%s", source)
|
||||
}
|
||||
return v, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("%s: unsupported type %T", p, data)
|
||||
}
|
||||
}
|
||||
|
10
vendor/github.com/compose-spec/compose-go/v2/types/develop.go
generated
vendored
10
vendor/github.com/compose-spec/compose-go/v2/types/develop.go
generated
vendored
@ -17,7 +17,7 @@
|
||||
package types
|
||||
|
||||
type DevelopConfig struct {
|
||||
Watch []Trigger `json:"watch,omitempty"`
|
||||
Watch []Trigger `yaml:"watch,omitempty" json:"watch,omitempty"`
|
||||
|
||||
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
|
||||
}
|
||||
@ -31,8 +31,8 @@ const (
|
||||
)
|
||||
|
||||
type Trigger struct {
|
||||
Path string `json:"path,omitempty"`
|
||||
Action WatchAction `json:"action,omitempty"`
|
||||
Target string `json:"target,omitempty"`
|
||||
Ignore []string `json:"ignore,omitempty"`
|
||||
Path string `yaml:"path" json:"path"`
|
||||
Action WatchAction `yaml:"action" json:"action"`
|
||||
Target string `yaml:"target,omitempty" json:"target,omitempty"`
|
||||
Ignore []string `yaml:"ignore,omitempty" json:"ignore,omitempty"`
|
||||
}
|
||||
|
66
vendor/github.com/compose-spec/compose-go/v2/types/hostList.go
generated
vendored
66
vendor/github.com/compose-spec/compose-go/v2/types/hostList.go
generated
vendored
@ -24,7 +24,33 @@ import (
|
||||
)
|
||||
|
||||
// HostsList is a list of colon-separated host-ip mappings
|
||||
type HostsList map[string]string
|
||||
type HostsList map[string][]string
|
||||
|
||||
// NewHostsList creates a HostsList from a list of `host=ip` strings
|
||||
func NewHostsList(hosts []string) (HostsList, error) {
|
||||
list := HostsList{}
|
||||
for _, s := range hosts {
|
||||
var found bool
|
||||
for _, sep := range hostListSerapators {
|
||||
host, ip, ok := strings.Cut(s, sep)
|
||||
if ok {
|
||||
// Mapping found with this separator, stop here.
|
||||
if ips, ok := list[host]; ok {
|
||||
list[host] = append(ips, ip)
|
||||
} else {
|
||||
list[host] = []string{ip}
|
||||
}
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, fmt.Errorf("invalid additional host, missing IP: %s", s)
|
||||
}
|
||||
}
|
||||
err := list.cleanup()
|
||||
return list, err
|
||||
}
|
||||
|
||||
// AsList returns host-ip mappings as a list of strings, using the given
|
||||
// separator. The Docker Engine API expects ':' separators, the original format
|
||||
@ -34,7 +60,9 @@ type HostsList map[string]string
|
||||
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))
|
||||
for _, ip := range v {
|
||||
l = append(l, fmt.Sprintf("%s%s%s", k, sep, ip))
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
@ -51,6 +79,8 @@ func (h HostsList) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(list)
|
||||
}
|
||||
|
||||
var hostListSerapators = []string{"=", ":"}
|
||||
|
||||
func (h *HostsList) DecodeMapstructure(value interface{}) error {
|
||||
switch v := value.(type) {
|
||||
case map[string]interface{}:
|
||||
@ -59,25 +89,45 @@ func (h *HostsList) DecodeMapstructure(value interface{}) error {
|
||||
if e == nil {
|
||||
e = ""
|
||||
}
|
||||
list[i] = fmt.Sprint(e)
|
||||
list[i] = []string{fmt.Sprint(e)}
|
||||
}
|
||||
err := list.cleanup()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*h = list
|
||||
return nil
|
||||
case []interface{}:
|
||||
*h = decodeMapping(v, "=", ":")
|
||||
s := make([]string, len(v))
|
||||
for i, e := range v {
|
||||
s[i] = fmt.Sprint(e)
|
||||
}
|
||||
list, err := NewHostsList(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*h = list
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unexpected value type %T for mapping", value)
|
||||
}
|
||||
for host, ip := range *h {
|
||||
}
|
||||
|
||||
func (h HostsList) cleanup() error {
|
||||
for host, ips := range h {
|
||||
// Check that there is a hostname and that it doesn't contain either
|
||||
// of the allowed separators, to generate a clearer error than the
|
||||
// engine would do if it splits the string differently.
|
||||
if host == "" || strings.ContainsAny(host, ":=") {
|
||||
return fmt.Errorf("bad host name '%s'", host)
|
||||
}
|
||||
// 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]
|
||||
for i, ip := range ips {
|
||||
// Remove brackets from IP addresses (for example "[::1]" -> "::1").
|
||||
if len(ip) > 2 && ip[0] == '[' && ip[len(ip)-1] == ']' {
|
||||
ips[i] = ip[1 : len(ip)-1]
|
||||
}
|
||||
}
|
||||
h[host] = ips
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
62
vendor/github.com/compose-spec/compose-go/v2/types/project.go
generated
vendored
62
vendor/github.com/compose-spec/compose-go/v2/types/project.go
generated
vendored
@ -29,6 +29,7 @@ import (
|
||||
"github.com/distribution/reference"
|
||||
"github.com/mitchellh/copystructure"
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
@ -46,13 +47,8 @@ type Project struct {
|
||||
Configs Configs `yaml:"configs,omitempty" json:"configs,omitempty"`
|
||||
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` // https://github.com/golang/go/issues/6213
|
||||
|
||||
// IncludeReferences is keyed by Compose YAML filename and contains config for
|
||||
// other Compose YAML files it directly triggered a load of via `include`.
|
||||
//
|
||||
// Note: this is
|
||||
IncludeReferences map[string][]IncludeConfig `yaml:"-" json:"-"`
|
||||
ComposeFiles []string `yaml:"-" json:"-"`
|
||||
Environment Mapping `yaml:"-" json:"-"`
|
||||
ComposeFiles []string `yaml:"-" json:"-"`
|
||||
Environment Mapping `yaml:"-" json:"-"`
|
||||
|
||||
// DisabledServices track services which have been disable as profile is not active
|
||||
DisabledServices Services `yaml:"-" json:"-"`
|
||||
@ -119,6 +115,58 @@ func (p *Project) ConfigNames() []string {
|
||||
return names
|
||||
}
|
||||
|
||||
func (p *Project) ServicesWithBuild() []string {
|
||||
servicesBuild := p.Services.Filter(func(s ServiceConfig) bool {
|
||||
return s.Build != nil && s.Build.Context != ""
|
||||
})
|
||||
return maps.Keys(servicesBuild)
|
||||
}
|
||||
|
||||
func (p *Project) ServicesWithExtends() []string {
|
||||
servicesExtends := p.Services.Filter(func(s ServiceConfig) bool {
|
||||
return s.Extends != nil && *s.Extends != (ExtendsConfig{})
|
||||
})
|
||||
return maps.Keys(servicesExtends)
|
||||
}
|
||||
|
||||
func (p *Project) ServicesWithDependsOn() []string {
|
||||
servicesDependsOn := p.Services.Filter(func(s ServiceConfig) bool {
|
||||
return len(s.DependsOn) > 0
|
||||
})
|
||||
return maps.Keys(servicesDependsOn)
|
||||
}
|
||||
|
||||
func (p *Project) ServicesWithCapabilities() ([]string, []string, []string) {
|
||||
capabilities := []string{}
|
||||
gpu := []string{}
|
||||
tpu := []string{}
|
||||
for _, service := range p.Services {
|
||||
deploy := service.Deploy
|
||||
if deploy == nil {
|
||||
continue
|
||||
}
|
||||
reservation := deploy.Resources.Reservations
|
||||
if reservation == nil {
|
||||
continue
|
||||
}
|
||||
devices := reservation.Devices
|
||||
for _, d := range devices {
|
||||
if len(d.Capabilities) > 0 {
|
||||
capabilities = append(capabilities, service.Name)
|
||||
}
|
||||
for _, c := range d.Capabilities {
|
||||
if c == "gpu" {
|
||||
gpu = append(gpu, service.Name)
|
||||
} else if c == "tpu" {
|
||||
tpu = append(tpu, service.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return utils.RemoveDuplicates(capabilities), utils.RemoveDuplicates(gpu), utils.RemoveDuplicates(tpu)
|
||||
}
|
||||
|
||||
// GetServices retrieve services by names, or return all services if no name specified
|
||||
func (p *Project) GetServices(names ...string) (Services, error) {
|
||||
if len(names) == 0 {
|
||||
|
10
vendor/github.com/compose-spec/compose-go/v2/types/services.go
generated
vendored
10
vendor/github.com/compose-spec/compose-go/v2/types/services.go
generated
vendored
@ -33,3 +33,13 @@ func (s Services) GetProfiles() []string {
|
||||
}
|
||||
return profiles
|
||||
}
|
||||
|
||||
func (s Services) Filter(predicate func(ServiceConfig) bool) Services {
|
||||
services := Services{}
|
||||
for name, service := range s {
|
||||
if predicate(service) {
|
||||
services[name] = service
|
||||
}
|
||||
}
|
||||
return services
|
||||
}
|
||||
|
3
vendor/github.com/compose-spec/compose-go/v2/types/types.go
generated
vendored
3
vendor/github.com/compose-spec/compose-go/v2/types/types.go
generated
vendored
@ -162,6 +162,9 @@ func (s *ServiceConfig) NetworksByPriority() []string {
|
||||
})
|
||||
}
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
if keys[i].priority == keys[j].priority {
|
||||
return keys[i].name < keys[j].name
|
||||
}
|
||||
return keys[i].priority > keys[j].priority
|
||||
})
|
||||
var sorted []string
|
||||
|
15
vendor/github.com/compose-spec/compose-go/v2/utils/collectionutils.go
generated
vendored
15
vendor/github.com/compose-spec/compose-go/v2/utils/collectionutils.go
generated
vendored
@ -51,3 +51,18 @@ func ArrayContains[T comparable](source []T, toCheck []T) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func RemoveDuplicates[T comparable](slice []T) []T {
|
||||
// Create a map to store unique elements
|
||||
seen := make(map[T]bool)
|
||||
result := []T{}
|
||||
|
||||
// Loop through the slice, adding elements to the map if they haven't been seen before
|
||||
for _, val := range slice {
|
||||
if _, ok := seen[val]; !ok {
|
||||
seen[val] = true
|
||||
result = append(result, val)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
Reference in New Issue
Block a user