mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-07-09 21:17:09 +08:00
Merge pull request #1971 from glours/bump-compose-go-v1.17.0
bump compose-go version to v1.17.0 to fix issue with depends_on
This commit is contained in:
62
vendor/github.com/compose-spec/compose-go/cli/options.go
generated
vendored
62
vendor/github.com/compose-spec/compose-go/cli/options.go
generated
vendored
@ -17,7 +17,6 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -250,7 +249,7 @@ func WithDotEnv(o *ProjectOptions) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
envMap, err := GetEnvFromFile(o.Environment, wd, o.EnvFiles)
|
||||
envMap, err := dotenv.GetEnvFromFile(o.Environment, wd, o.EnvFiles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -262,65 +261,6 @@ func WithDotEnv(o *ProjectOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetEnvFromFile(currentEnv map[string]string, workingDir 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 {
|
||||
abs, err := filepath.Abs(dotEnvFile)
|
||||
if err != nil {
|
||||
return envMap, err
|
||||
}
|
||||
dotEnvFile = abs
|
||||
|
||||
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)
|
||||
}
|
||||
if err != nil {
|
||||
return envMap, err
|
||||
}
|
||||
|
||||
if s.IsDir() {
|
||||
if len(filenames) == 0 {
|
||||
return envMap, nil
|
||||
}
|
||||
return envMap, errors.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)
|
||||
}
|
||||
if err != nil {
|
||||
return envMap, err
|
||||
}
|
||||
|
||||
env, err := dotenv.ParseWithLookup(bytes.NewReader(b), func(k string) (string, bool) {
|
||||
v, ok := currentEnv[k]
|
||||
if ok {
|
||||
return v, true
|
||||
}
|
||||
v, ok = envMap[k]
|
||||
return v, ok
|
||||
})
|
||||
if err != nil {
|
||||
return envMap, errors.Wrapf(err, "failed to read %s", dotEnvFile)
|
||||
}
|
||||
for k, v := range env {
|
||||
envMap[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return envMap, nil
|
||||
}
|
||||
|
||||
// WithInterpolation set ProjectOptions to enable/skip interpolation
|
||||
func WithInterpolation(interpolation bool) ProjectOptionsFn {
|
||||
return func(o *ProjectOptions) error {
|
||||
|
84
vendor/github.com/compose-spec/compose-go/dotenv/env.go
generated
vendored
Normal file
84
vendor/github.com/compose-spec/compose-go/dotenv/env.go
generated
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
Copyright 2020 The Compose Specification Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package dotenv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func GetEnvFromFile(currentEnv map[string]string, workingDir 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 {
|
||||
abs, err := filepath.Abs(dotEnvFile)
|
||||
if err != nil {
|
||||
return envMap, err
|
||||
}
|
||||
dotEnvFile = abs
|
||||
|
||||
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)
|
||||
}
|
||||
if err != nil {
|
||||
return envMap, err
|
||||
}
|
||||
|
||||
if s.IsDir() {
|
||||
if len(filenames) == 0 {
|
||||
return envMap, nil
|
||||
}
|
||||
return envMap, errors.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)
|
||||
}
|
||||
if err != nil {
|
||||
return envMap, err
|
||||
}
|
||||
|
||||
env, err := ParseWithLookup(bytes.NewReader(b), func(k string) (string, bool) {
|
||||
v, ok := currentEnv[k]
|
||||
if ok {
|
||||
return v, true
|
||||
}
|
||||
v, ok = envMap[k]
|
||||
return v, ok
|
||||
})
|
||||
if err != nil {
|
||||
return envMap, errors.Wrapf(err, "failed to read %s", dotEnvFile)
|
||||
}
|
||||
for k, v := range env {
|
||||
envMap[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return envMap, nil
|
||||
}
|
13
vendor/github.com/compose-spec/compose-go/dotenv/parser.go
generated
vendored
13
vendor/github.com/compose-spec/compose-go/dotenv/parser.go
generated
vendored
@ -123,8 +123,8 @@ loop:
|
||||
}
|
||||
|
||||
return "", "", inherited, fmt.Errorf(
|
||||
`line %d: unexpected character %q in variable name`,
|
||||
p.line, string(rune))
|
||||
`line %d: unexpected character %q in variable name %q`,
|
||||
p.line, string(rune), strings.Split(src, "\n")[0])
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,17 +153,24 @@ func (p *parser) extractVarValue(src string, envMap map[string]string, lookupFn
|
||||
return retVal, rest, err
|
||||
}
|
||||
|
||||
previousCharIsEscape := false
|
||||
// lookup quoted string terminator
|
||||
for i := 1; i < len(src); i++ {
|
||||
if src[i] == '\n' {
|
||||
p.line++
|
||||
}
|
||||
if char := src[i]; char != quote {
|
||||
if !previousCharIsEscape && char == '\\' {
|
||||
previousCharIsEscape = true
|
||||
} else {
|
||||
previousCharIsEscape = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// skip escaped quote symbol (\" or \', depends on quote)
|
||||
if prevChar := src[i-1]; prevChar == '\\' {
|
||||
if previousCharIsEscape {
|
||||
previousCharIsEscape = false
|
||||
continue
|
||||
}
|
||||
|
||||
|
3
vendor/github.com/compose-spec/compose-go/loader/full-example.yml
generated
vendored
3
vendor/github.com/compose-spec/compose-go/loader/full-example.yml
generated
vendored
@ -24,7 +24,7 @@ services:
|
||||
- bar
|
||||
labels: [FOO=BAR]
|
||||
additional_contexts:
|
||||
foo: /bar
|
||||
foo: ./bar
|
||||
secrets:
|
||||
- secret1
|
||||
- source: secret2
|
||||
@ -181,6 +181,7 @@ services:
|
||||
timeout: 1s
|
||||
retries: 5
|
||||
start_period: 15s
|
||||
start_interval: 5s
|
||||
|
||||
# Any valid image reference - repo, tag, id, sha
|
||||
image: redis
|
||||
|
120
vendor/github.com/compose-spec/compose-go/loader/include.go
generated
vendored
Normal file
120
vendor/github.com/compose-spec/compose-go/loader/include.go
generated
vendored
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
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"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/compose-spec/compose-go/dotenv"
|
||||
"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(configDetails types.ConfigDetails, model *types.Config, options *Options, loaded []string) (*types.Config, error) {
|
||||
for _, r := range model.Include {
|
||||
for i, p := range r.Path {
|
||||
if !filepath.IsAbs(p) {
|
||||
r.Path[i] = filepath.Join(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
|
||||
|
||||
env, err := dotenv.GetEnvFromFile(configDetails.Environment, r.ProjectDirectory, r.EnvFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
imported, err := load(types.ConfigDetails{
|
||||
WorkingDir: r.ProjectDirectory,
|
||||
ConfigFiles: types.ToConfigFiles(r.Path),
|
||||
Environment: env,
|
||||
}, loadOptions, loaded)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = importResources(model, imported, r.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
model.Include = nil
|
||||
return model, 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 _, ok := services[service.Name]; ok {
|
||||
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 _, ok := model.Networks[n]; ok {
|
||||
return fmt.Errorf("imported compose file %s defines conflicting network %s", path, n)
|
||||
}
|
||||
model.Networks[n] = network
|
||||
}
|
||||
for n, volume := range imported.Volumes {
|
||||
if _, ok := model.Volumes[n]; ok {
|
||||
return fmt.Errorf("imported compose file %s defines conflicting volume %s", path, n)
|
||||
}
|
||||
model.Volumes[n] = volume
|
||||
}
|
||||
for n, secret := range imported.Secrets {
|
||||
if _, ok := model.Secrets[n]; ok {
|
||||
return fmt.Errorf("imported compose file %s defines conflicting secret %s", path, n)
|
||||
}
|
||||
model.Secrets[n] = secret
|
||||
}
|
||||
for n, config := range imported.Configs {
|
||||
if _, ok := model.Configs[n]; ok {
|
||||
return fmt.Errorf("imported compose file %s defines conflicting config %s", path, n)
|
||||
}
|
||||
model.Configs[n] = config
|
||||
}
|
||||
return nil
|
||||
}
|
203
vendor/github.com/compose-spec/compose-go/loader/loader.go
generated
vendored
203
vendor/github.com/compose-spec/compose-go/loader/loader.go
generated
vendored
@ -56,6 +56,8 @@ type Options struct {
|
||||
SkipConsistencyCheck bool
|
||||
// Skip extends
|
||||
SkipExtends bool
|
||||
// SkipInclude will ignore `include` and only load model from file(s) set by ConfigDetails
|
||||
SkipInclude bool
|
||||
// Interpolation options
|
||||
Interpolate *interp.Options
|
||||
// Discard 'env_file' entries after resolving to 'environment' section
|
||||
@ -68,6 +70,24 @@ type Options struct {
|
||||
Profiles []string
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Options) SetProjectName(name string, imperativelySet bool) {
|
||||
o.projectName = name
|
||||
o.projectNameImperativelySet = imperativelySet
|
||||
@ -185,6 +205,7 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
|
||||
LookupValue: configDetails.LookupEnv,
|
||||
TypeCastMapping: interpolateTypeCastMapping,
|
||||
},
|
||||
ResolvePaths: true,
|
||||
}
|
||||
|
||||
for _, op := range options {
|
||||
@ -195,8 +216,22 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.projectName = projectName
|
||||
return load(configDetails, opts, nil)
|
||||
}
|
||||
|
||||
func load(configDetails types.ConfigDetails, opts *Options, loaded []string) (*types.Project, error) {
|
||||
var model *types.Config
|
||||
|
||||
mainFile := configDetails.ConfigFiles[0].Filename
|
||||
for _, f := range loaded {
|
||||
if f == mainFile {
|
||||
loaded = append(loaded, mainFile)
|
||||
return nil, errors.Errorf("include cycle detected:\n%s\n include %s", loaded[0], strings.Join(loaded[1:], "\n include "))
|
||||
}
|
||||
}
|
||||
loaded = append(loaded, mainFile)
|
||||
|
||||
for i, file := range configDetails.ConfigFiles {
|
||||
var postProcessor PostProcessor
|
||||
configDict := file.Config
|
||||
@ -231,10 +266,18 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !opts.SkipInclude {
|
||||
cfg, err = loadInclude(configDetails, cfg, opts, loaded)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
model = cfg
|
||||
continue
|
||||
}
|
||||
|
||||
merged, err := merge([]*types.Config{model, cfg})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -248,16 +291,8 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
|
||||
model = merged
|
||||
}
|
||||
|
||||
for _, s := range model.Services {
|
||||
var newEnvFiles types.StringList
|
||||
for _, ef := range s.EnvFile {
|
||||
newEnvFiles = append(newEnvFiles, absPath(configDetails.WorkingDir, ef))
|
||||
}
|
||||
s.EnvFile = newEnvFiles
|
||||
}
|
||||
|
||||
project := &types.Project{
|
||||
Name: projectName,
|
||||
Name: opts.projectName,
|
||||
WorkingDir: configDetails.WorkingDir,
|
||||
Services: model.Services,
|
||||
Networks: model.Networks,
|
||||
@ -269,14 +304,30 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
|
||||
}
|
||||
|
||||
if !opts.SkipNormalization {
|
||||
err = Normalize(project, opts.ResolvePaths)
|
||||
err := Normalize(project)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.ResolvePaths {
|
||||
err := ResolveRelativePaths(project)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.ConvertWindowsPaths {
|
||||
for i, service := range project.Services {
|
||||
for j, volume := range service.Volumes {
|
||||
service.Volumes[j] = convertVolumePath(volume)
|
||||
}
|
||||
project.Services[i] = service
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.SkipConsistencyCheck {
|
||||
err = checkConsistency(project)
|
||||
err := checkConsistency(project)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -287,7 +338,7 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
|
||||
}
|
||||
project.ApplyProfiles(opts.Profiles)
|
||||
|
||||
err = project.ResolveServicesEnvironment(opts.discardEnvFiles)
|
||||
err := project.ResolveServicesEnvironment(opts.discardEnvFiles)
|
||||
|
||||
return project, err
|
||||
}
|
||||
@ -419,7 +470,6 @@ func loadSections(filename string, config map[string]interface{}, configDetails
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg.Networks, err = LoadNetworks(getSection(config, "networks"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -428,11 +478,15 @@ func loadSections(filename string, config map[string]interface{}, configDetails
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.Secrets, err = LoadSecrets(getSection(config, "secrets"), configDetails, opts.ResolvePaths)
|
||||
cfg.Secrets, err = LoadSecrets(getSection(config, "secrets"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.Configs, err = LoadConfigObjs(getSection(config, "configs"), configDetails, opts.ResolvePaths)
|
||||
cfg.Configs, err = LoadConfigObjs(getSection(config, "configs"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.Include, err = LoadIncludeConfig(getSequence(config, "include"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -451,6 +505,14 @@ func getSection(config map[string]interface{}, key string) map[string]interface{
|
||||
return section.(map[string]interface{})
|
||||
}
|
||||
|
||||
func getSequence(config map[string]interface{}, key string) []interface{} {
|
||||
section, ok := config[key]
|
||||
if !ok {
|
||||
return make([]interface{}, 0)
|
||||
}
|
||||
return section.([]interface{})
|
||||
}
|
||||
|
||||
// ForbiddenPropertiesError is returned when there are properties in the Compose
|
||||
// file that are forbidden.
|
||||
type ForbiddenPropertiesError struct {
|
||||
@ -515,6 +577,7 @@ func createTransformHook(additionalTransformers ...Transformer) mapstructure.Dec
|
||||
reflect.TypeOf(types.ExtendsConfig{}): transformExtendsConfig,
|
||||
reflect.TypeOf(types.DeviceRequest{}): transformServiceDeviceRequest,
|
||||
reflect.TypeOf(types.SSHConfig{}): transformSSHConfig,
|
||||
reflect.TypeOf(types.IncludeConfig{}): transformIncludeConfig,
|
||||
}
|
||||
|
||||
for _, transformer := range additionalTransformers {
|
||||
@ -605,6 +668,7 @@ func LoadServices(filename string, servicesDict map[string]interface{}, workingD
|
||||
for k, v := range x.(map[string]interface{}) {
|
||||
servicesDict[k] = v
|
||||
}
|
||||
delete(servicesDict, extensions)
|
||||
}
|
||||
|
||||
for name := range servicesDict {
|
||||
@ -633,7 +697,7 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
|
||||
target = map[string]interface{}{}
|
||||
}
|
||||
|
||||
serviceConfig, err := LoadService(name, target.(map[string]interface{}), workingDir, lookupEnv, opts.ResolvePaths, opts.ConvertWindowsPaths)
|
||||
serviceConfig, err := LoadService(name, target.(map[string]interface{}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -671,21 +735,9 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
|
||||
// make the paths relative to `file` rather than `baseFilePath` so
|
||||
// that the resulting paths won't be absolute if `file` isn't an
|
||||
// absolute path.
|
||||
|
||||
baseFileParent := filepath.Dir(file)
|
||||
if baseService.Build != nil {
|
||||
baseService.Build.Context = resolveBuildContextPath(baseFileParent, baseService.Build.Context)
|
||||
}
|
||||
|
||||
for i, vol := range baseService.Volumes {
|
||||
if vol.Type != types.VolumeTypeBind {
|
||||
continue
|
||||
}
|
||||
baseService.Volumes[i].Source = resolveMaybeUnixPath(vol.Source, baseFileParent, lookupEnv)
|
||||
}
|
||||
|
||||
for i, envFile := range baseService.EnvFile {
|
||||
baseService.EnvFile[i] = resolveMaybeUnixPath(envFile, baseFileParent, lookupEnv)
|
||||
}
|
||||
ResolveServiceRelativePaths(baseFileParent, baseService)
|
||||
}
|
||||
|
||||
serviceConfig, err = _merge(baseService, serviceConfig)
|
||||
@ -698,22 +750,9 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
|
||||
return serviceConfig, nil
|
||||
}
|
||||
|
||||
func resolveBuildContextPath(baseFileParent string, context string) string {
|
||||
// Checks if the context is an HTTP(S) URL or a remote git repository URL
|
||||
for _, prefix := range []string{"https://", "http://", "git://", "github.com/", "git@"} {
|
||||
if strings.HasPrefix(context, prefix) {
|
||||
return context
|
||||
}
|
||||
}
|
||||
|
||||
// Note that the Dockerfile is always defined relative to the
|
||||
// build context, so there's no need to update the Dockerfile field.
|
||||
return absPath(baseFileParent, context)
|
||||
}
|
||||
|
||||
// LoadService produces a single ServiceConfig from a compose file Dict
|
||||
// the serviceDict is not validated if directly used. Use Load() to enable validation
|
||||
func LoadService(name string, serviceDict map[string]interface{}, workingDir string, lookupEnv template.Mapping, resolvePaths bool, convertPaths bool) (*types.ServiceConfig, error) {
|
||||
func LoadService(name string, serviceDict map[string]interface{}) (*types.ServiceConfig, error) {
|
||||
serviceConfig := &types.ServiceConfig{
|
||||
Scale: 1,
|
||||
}
|
||||
@ -730,13 +769,6 @@ func LoadService(name string, serviceDict map[string]interface{}, workingDir str
|
||||
return nil, errors.New(`invalid mount config for type "bind": field Source must not be empty`)
|
||||
}
|
||||
|
||||
if resolvePaths || convertPaths {
|
||||
volume = resolveVolumePath(volume, workingDir, lookupEnv)
|
||||
}
|
||||
|
||||
if convertPaths {
|
||||
volume = convertVolumePath(volume)
|
||||
}
|
||||
serviceConfig.Volumes[i] = volume
|
||||
}
|
||||
|
||||
@ -758,8 +790,8 @@ func convertVolumePath(volume types.ServiceVolumeConfig) types.ServiceVolumeConf
|
||||
return volume
|
||||
}
|
||||
|
||||
func resolveMaybeUnixPath(path string, workingDir string, lookupEnv template.Mapping) string {
|
||||
filePath := expandUser(path, lookupEnv)
|
||||
func resolveMaybeUnixPath(workingDir string, path string) string {
|
||||
filePath := expandUser(path)
|
||||
// Check if source is an absolute path (either Unix or Windows), to
|
||||
// handle a Windows client with a Unix daemon or vice-versa.
|
||||
//
|
||||
@ -772,20 +804,8 @@ func resolveMaybeUnixPath(path string, workingDir string, lookupEnv template.Map
|
||||
return filePath
|
||||
}
|
||||
|
||||
func resolveVolumePath(volume types.ServiceVolumeConfig, workingDir string, lookupEnv template.Mapping) types.ServiceVolumeConfig {
|
||||
volume.Source = resolveMaybeUnixPath(volume.Source, workingDir, lookupEnv)
|
||||
return volume
|
||||
}
|
||||
|
||||
func resolveSecretsPath(secret types.SecretConfig, workingDir string, lookupEnv template.Mapping) types.SecretConfig {
|
||||
if !secret.External.External && secret.File != "" {
|
||||
secret.File = resolveMaybeUnixPath(secret.File, workingDir, lookupEnv)
|
||||
}
|
||||
return secret
|
||||
}
|
||||
|
||||
// TODO: make this more robust
|
||||
func expandUser(path string, lookupEnv template.Mapping) string {
|
||||
func expandUser(path string) string {
|
||||
if strings.HasPrefix(path, "~") {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
@ -885,44 +905,39 @@ func LoadVolumes(source map[string]interface{}) (map[string]types.VolumeConfig,
|
||||
|
||||
// LoadSecrets produces a SecretConfig map from a compose file Dict
|
||||
// the source Dict is not validated if directly used. Use Load() to enable validation
|
||||
func LoadSecrets(source map[string]interface{}, details types.ConfigDetails, resolvePaths bool) (map[string]types.SecretConfig, error) {
|
||||
func LoadSecrets(source map[string]interface{}) (map[string]types.SecretConfig, error) {
|
||||
secrets := make(map[string]types.SecretConfig)
|
||||
if err := Transform(source, &secrets); err != nil {
|
||||
return secrets, err
|
||||
}
|
||||
for name, secret := range secrets {
|
||||
obj, err := loadFileObjectConfig(name, "secret", types.FileObjectConfig(secret), details, false)
|
||||
obj, err := loadFileObjectConfig(name, "secret", types.FileObjectConfig(secret))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secretConfig := types.SecretConfig(obj)
|
||||
if resolvePaths {
|
||||
secretConfig = resolveSecretsPath(secretConfig, details.WorkingDir, details.LookupEnv)
|
||||
}
|
||||
secrets[name] = secretConfig
|
||||
secrets[name] = types.SecretConfig(obj)
|
||||
}
|
||||
return secrets, nil
|
||||
}
|
||||
|
||||
// LoadConfigObjs produces a ConfigObjConfig map from a compose file Dict
|
||||
// the source Dict is not validated if directly used. Use Load() to enable validation
|
||||
func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails, resolvePaths bool) (map[string]types.ConfigObjConfig, error) {
|
||||
func LoadConfigObjs(source map[string]interface{}) (map[string]types.ConfigObjConfig, error) {
|
||||
configs := make(map[string]types.ConfigObjConfig)
|
||||
if err := Transform(source, &configs); err != nil {
|
||||
return configs, err
|
||||
}
|
||||
for name, config := range configs {
|
||||
obj, err := loadFileObjectConfig(name, "config", types.FileObjectConfig(config), details, resolvePaths)
|
||||
obj, err := loadFileObjectConfig(name, "config", types.FileObjectConfig(config))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configConfig := types.ConfigObjConfig(obj)
|
||||
configs[name] = configConfig
|
||||
configs[name] = types.ConfigObjConfig(obj)
|
||||
}
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfig, details types.ConfigDetails, resolvePaths bool) (types.FileObjectConfig, error) {
|
||||
func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfig) (types.FileObjectConfig, error) {
|
||||
// if "external: true"
|
||||
switch {
|
||||
case obj.External.External:
|
||||
@ -942,26 +957,11 @@ func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfi
|
||||
if obj.File != "" {
|
||||
return obj, errors.Errorf("%[1]s %[2]s: %[1]s.driver and %[1]s.file conflict; only use %[1]s.driver", objType, name)
|
||||
}
|
||||
default:
|
||||
if obj.File != "" && resolvePaths {
|
||||
obj.File = absPath(details.WorkingDir, obj.File)
|
||||
}
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func absPath(workingDir string, filePath string) string {
|
||||
if strings.HasPrefix(filePath, "~") {
|
||||
home, _ := os.UserHomeDir()
|
||||
return filepath.Join(home, filePath[1:])
|
||||
}
|
||||
if filepath.IsAbs(filePath) {
|
||||
return filePath
|
||||
}
|
||||
return filepath.Join(workingDir, filePath)
|
||||
}
|
||||
|
||||
var transformMapStringString TransformerFunc = func(data interface{}) (interface{}, error) {
|
||||
switch value := data.(type) {
|
||||
case map[string]interface{}:
|
||||
@ -1088,13 +1088,24 @@ var transformDependsOnConfig TransformerFunc = func(data interface{}) (interface
|
||||
for _, serviceIntf := range value {
|
||||
service, ok := serviceIntf.(string)
|
||||
if !ok {
|
||||
return data, errors.Errorf("invalid type %T for service depends_on elementn, expected string", value)
|
||||
return data, errors.Errorf("invalid type %T for service depends_on element, expected string", value)
|
||||
}
|
||||
transformed[service] = map[string]interface{}{"condition": types.ServiceConditionStarted}
|
||||
transformed[service] = map[string]interface{}{"condition": types.ServiceConditionStarted, "required": true}
|
||||
}
|
||||
return transformed, nil
|
||||
case map[string]interface{}:
|
||||
return groupXFieldsIntoExtensions(data.(map[string]interface{})), nil
|
||||
transformed := map[string]interface{}{}
|
||||
for service, val := range value {
|
||||
dependsConfigIntf, ok := val.(map[string]interface{})
|
||||
if !ok {
|
||||
return data, errors.Errorf("invalid type %T for service depends_on element", value)
|
||||
}
|
||||
if _, ok := dependsConfigIntf["required"]; !ok {
|
||||
dependsConfigIntf["required"] = true
|
||||
}
|
||||
transformed[service] = dependsConfigIntf
|
||||
}
|
||||
return groupXFieldsIntoExtensions(transformed), nil
|
||||
default:
|
||||
return data, errors.Errorf("invalid type %T for service depends_on", value)
|
||||
}
|
||||
|
11
vendor/github.com/compose-spec/compose-go/loader/merge.go
generated
vendored
11
vendor/github.com/compose-spec/compose-go/loader/merge.go
generated
vendored
@ -150,13 +150,12 @@ func unique(slice []string) []string {
|
||||
return nil
|
||||
}
|
||||
uniqMap := make(map[string]struct{})
|
||||
var uniqSlice []string
|
||||
for _, v := range slice {
|
||||
uniqMap[v] = struct{}{}
|
||||
}
|
||||
|
||||
uniqSlice := make([]string, 0, len(uniqMap))
|
||||
for v := range uniqMap {
|
||||
uniqSlice = append(uniqSlice, v)
|
||||
if _, ok := uniqMap[v]; !ok {
|
||||
uniqSlice = append(uniqSlice, v)
|
||||
uniqMap[v] = struct{}{}
|
||||
}
|
||||
}
|
||||
return uniqSlice
|
||||
}
|
||||
|
71
vendor/github.com/compose-spec/compose-go/loader/normalize.go
generated
vendored
71
vendor/github.com/compose-spec/compose-go/loader/normalize.go
generated
vendored
@ -18,8 +18,6 @@ package loader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/errdefs"
|
||||
@ -29,19 +27,7 @@ import (
|
||||
)
|
||||
|
||||
// Normalize compose project by moving deprecated attributes to their canonical position and injecting implicit defaults
|
||||
func Normalize(project *types.Project, resolvePaths bool) error {
|
||||
absWorkingDir, err := filepath.Abs(project.WorkingDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
project.WorkingDir = absWorkingDir
|
||||
|
||||
absComposeFiles, err := absComposeFiles(project.ComposeFiles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
project.ComposeFiles = absComposeFiles
|
||||
|
||||
func Normalize(project *types.Project) error {
|
||||
if project.Networks == nil {
|
||||
project.Networks = make(map[string]types.NetworkConfig)
|
||||
}
|
||||
@ -51,8 +37,7 @@ func Normalize(project *types.Project, resolvePaths bool) error {
|
||||
project.Networks["default"] = types.NetworkConfig{}
|
||||
}
|
||||
|
||||
err = relocateExternalName(project)
|
||||
if err != nil {
|
||||
if err := relocateExternalName(project); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -72,38 +57,16 @@ func Normalize(project *types.Project, resolvePaths bool) error {
|
||||
}
|
||||
|
||||
if s.Build != nil {
|
||||
if s.Build.Context == "" {
|
||||
s.Build.Context = "."
|
||||
}
|
||||
if s.Build.Dockerfile == "" && s.Build.DockerfileInline == "" {
|
||||
s.Build.Dockerfile = "Dockerfile"
|
||||
}
|
||||
if resolvePaths {
|
||||
// Build context might be a remote http/git context. Unfortunately supported "remote"
|
||||
// syntax is highly ambiguous in moby/moby and not defined by compose-spec,
|
||||
// so let's assume runtime will check
|
||||
localContext := absPath(project.WorkingDir, s.Build.Context)
|
||||
if _, err := os.Stat(localContext); err == nil {
|
||||
s.Build.Context = localContext
|
||||
}
|
||||
for name, path := range s.Build.AdditionalContexts {
|
||||
if strings.Contains(path, "://") { // `docker-image://` or any builder specific context type
|
||||
continue
|
||||
}
|
||||
path = absPath(project.WorkingDir, path)
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
s.Build.AdditionalContexts[name] = path
|
||||
}
|
||||
}
|
||||
}
|
||||
s.Build.Args = s.Build.Args.Resolve(fn)
|
||||
}
|
||||
for j, f := range s.EnvFile {
|
||||
s.EnvFile[j] = absPath(project.WorkingDir, f)
|
||||
}
|
||||
s.Environment = s.Environment.Resolve(fn)
|
||||
|
||||
if s.Extends != nil && s.Extends.File != "" {
|
||||
s.Extends.File = absPath(project.WorkingDir, s.Extends.File)
|
||||
}
|
||||
|
||||
for _, link := range s.Links {
|
||||
parts := strings.Split(link, ":")
|
||||
if len(parts) == 2 {
|
||||
@ -112,6 +75,7 @@ func Normalize(project *types.Project, resolvePaths bool) error {
|
||||
s.DependsOn = setIfMissing(s.DependsOn, link, types.ServiceDependency{
|
||||
Condition: types.ServiceConditionStarted,
|
||||
Restart: true,
|
||||
Required: true,
|
||||
})
|
||||
}
|
||||
|
||||
@ -121,6 +85,7 @@ func Normalize(project *types.Project, resolvePaths bool) error {
|
||||
s.DependsOn = setIfMissing(s.DependsOn, name, types.ServiceDependency{
|
||||
Condition: types.ServiceConditionStarted,
|
||||
Restart: true,
|
||||
Required: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -131,6 +96,7 @@ func Normalize(project *types.Project, resolvePaths bool) error {
|
||||
s.DependsOn = setIfMissing(s.DependsOn, spec[0], types.ServiceDependency{
|
||||
Condition: types.ServiceConditionStarted,
|
||||
Restart: false,
|
||||
Required: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -160,14 +126,6 @@ func Normalize(project *types.Project, resolvePaths bool) error {
|
||||
project.Services[i] = s
|
||||
}
|
||||
|
||||
for name, config := range project.Volumes {
|
||||
if config.Driver == "local" && config.DriverOpts["o"] == "bind" {
|
||||
// This is actually a bind mount
|
||||
config.DriverOpts["device"] = absPath(project.WorkingDir, config.DriverOpts["device"])
|
||||
project.Volumes[name] = config
|
||||
}
|
||||
}
|
||||
|
||||
setNameFromKey(project)
|
||||
|
||||
return nil
|
||||
@ -223,6 +181,7 @@ func inferImplicitDependencies(service *types.ServiceConfig) {
|
||||
if _, ok := service.DependsOn[d]; !ok {
|
||||
service.DependsOn[d] = types.ServiceDependency{
|
||||
Condition: types.ServiceConditionStarted,
|
||||
Required: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -254,18 +213,6 @@ func relocateScale(s *types.ServiceConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func absComposeFiles(composeFiles []string) ([]string, error) {
|
||||
absComposeFiles := make([]string, len(composeFiles))
|
||||
for i, composeFile := range composeFiles {
|
||||
absComposefile, err := filepath.Abs(composeFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
absComposeFiles[i] = absComposefile
|
||||
}
|
||||
return absComposeFiles, 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 {
|
||||
|
135
vendor/github.com/compose-spec/compose-go/loader/paths.go
generated
vendored
Normal file
135
vendor/github.com/compose-spec/compose-go/loader/paths.go
generated
vendored
Normal file
@ -0,0 +1,135 @@
|
||||
/*
|
||||
Copyright 2020 The Compose Specification Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package loader
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
)
|
||||
|
||||
// ResolveRelativePaths resolves relative paths based on project WorkingDirectory
|
||||
func ResolveRelativePaths(project *types.Project) error {
|
||||
absWorkingDir, err := filepath.Abs(project.WorkingDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
project.WorkingDir = absWorkingDir
|
||||
|
||||
absComposeFiles, err := absComposeFiles(project.ComposeFiles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
project.ComposeFiles = absComposeFiles
|
||||
|
||||
for i, s := range project.Services {
|
||||
ResolveServiceRelativePaths(project.WorkingDir, &s)
|
||||
project.Services[i] = s
|
||||
}
|
||||
|
||||
for i, obj := range project.Configs {
|
||||
if obj.File != "" {
|
||||
obj.File = absPath(project.WorkingDir, obj.File)
|
||||
project.Configs[i] = obj
|
||||
}
|
||||
}
|
||||
|
||||
for i, obj := range project.Secrets {
|
||||
if obj.File != "" {
|
||||
obj.File = resolveMaybeUnixPath(project.WorkingDir, obj.File)
|
||||
project.Secrets[i] = obj
|
||||
}
|
||||
}
|
||||
|
||||
for name, config := range project.Volumes {
|
||||
if config.Driver == "local" && config.DriverOpts["o"] == "bind" {
|
||||
// This is actually a bind mount
|
||||
config.DriverOpts["device"] = resolveMaybeUnixPath(project.WorkingDir, config.DriverOpts["device"])
|
||||
project.Volumes[name] = config
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func absPath(workingDir string, filePath string) string {
|
||||
if strings.HasPrefix(filePath, "~") {
|
||||
home, _ := os.UserHomeDir()
|
||||
return filepath.Join(home, filePath[1:])
|
||||
}
|
||||
if filepath.IsAbs(filePath) {
|
||||
return filePath
|
||||
}
|
||||
return filepath.Join(workingDir, filePath)
|
||||
}
|
||||
|
||||
func absComposeFiles(composeFiles []string) ([]string, error) {
|
||||
for i, composeFile := range composeFiles {
|
||||
absComposefile, err := filepath.Abs(composeFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
composeFiles[i] = absComposefile
|
||||
}
|
||||
return composeFiles, nil
|
||||
}
|
||||
|
||||
// isRemoteContext returns true if the value is a Git reference or HTTP(S) URL.
|
||||
//
|
||||
// Any other value is assumed to be a local filesystem path and returns false.
|
||||
//
|
||||
// See: https://github.com/moby/buildkit/blob/18fc875d9bfd6e065cd8211abc639434ba65aa56/frontend/dockerui/context.go#L76-L79
|
||||
func isRemoteContext(maybeURL string) bool {
|
||||
for _, prefix := range []string{"https://", "http://", "git://", "ssh://", "github.com/", "git@"} {
|
||||
if strings.HasPrefix(maybeURL, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
35
vendor/github.com/compose-spec/compose-go/schema/compose-spec.json
generated
vendored
35
vendor/github.com/compose-spec/compose-go/schema/compose-spec.json
generated
vendored
@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft/2019-09/schema#",
|
||||
"$schema": "https://json-schema.org/draft/2019-09/schema#",
|
||||
"id": "compose_spec.json",
|
||||
"type": "object",
|
||||
"title": "Compose Specification",
|
||||
@ -17,6 +17,15 @@
|
||||
"description": "define the Compose project name, until user defines one explicitly."
|
||||
},
|
||||
|
||||
"include": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/include"
|
||||
},
|
||||
"description": "compose sub-projects to be included."
|
||||
},
|
||||
|
||||
"services": {
|
||||
"id": "#/properties/services",
|
||||
"type": "object",
|
||||
@ -84,6 +93,7 @@
|
||||
"properties": {
|
||||
"deploy": {"$ref": "#/definitions/deployment"},
|
||||
"annotations": {"$ref": "#/definitions/list_or_dict"},
|
||||
"attach": {"type": "boolean"},
|
||||
"build": {
|
||||
"oneOf": [
|
||||
{"type": "string"},
|
||||
@ -181,6 +191,10 @@
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"restart": {"type": "boolean"},
|
||||
"required": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"condition": {
|
||||
"type": "string",
|
||||
"enum": ["service_started", "service_healthy", "service_completed_successfully"]
|
||||
@ -443,7 +457,8 @@
|
||||
]
|
||||
},
|
||||
"timeout": {"type": "string", "format": "duration"},
|
||||
"start_period": {"type": "string", "format": "duration"}
|
||||
"start_period": {"type": "string", "format": "duration"},
|
||||
"start_interval": {"type": "string", "format": "duration"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
@ -588,6 +603,22 @@
|
||||
}
|
||||
},
|
||||
|
||||
"include": {
|
||||
"id": "#/definitions/include",
|
||||
"oneOf": [
|
||||
{"type": "string"},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {"$ref": "#/definitions/string_or_list"},
|
||||
"env_file": {"$ref": "#/definitions/string_or_list"},
|
||||
"project_directory": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"network": {
|
||||
"id": "#/definitions/network",
|
||||
"type": ["object", "null"],
|
||||
|
7
vendor/github.com/compose-spec/compose-go/schema/schema.go
generated
vendored
7
vendor/github.com/compose-spec/compose-go/schema/schema.go
generated
vendored
@ -17,19 +17,18 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
// Enable support for embedded static resources
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xeipuuv/gojsonschema"
|
||||
|
||||
// Enable support for embedded static resources
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
type portsFormatChecker struct{}
|
||||
|
||||
func (checker portsFormatChecker) IsFormat(input interface{}) bool {
|
||||
func (checker portsFormatChecker) IsFormat(_ interface{}) bool {
|
||||
// TODO: implement this
|
||||
return true
|
||||
}
|
||||
|
190
vendor/github.com/compose-spec/compose-go/template/template.go
generated
vendored
190
vendor/github.com/compose-spec/compose-go/template/template.go
generated
vendored
@ -17,6 +17,7 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
@ -71,77 +72,148 @@ type Mapping func(string) (string, bool)
|
||||
// the substitution and an error.
|
||||
type SubstituteFunc func(string, Mapping) (string, bool, error)
|
||||
|
||||
// SubstituteWith substitute variables in the string with their values.
|
||||
// It accepts additional substitute function.
|
||||
func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, subsFuncs ...SubstituteFunc) (string, error) {
|
||||
var outerErr error
|
||||
// ReplacementFunc is a user-supplied function that is apply to the matching
|
||||
// substring. Returns the value as a string and an error.
|
||||
type ReplacementFunc func(string, Mapping, *Config) (string, error)
|
||||
|
||||
type Config struct {
|
||||
pattern *regexp.Regexp
|
||||
substituteFunc SubstituteFunc
|
||||
replacementFunc ReplacementFunc
|
||||
logging bool
|
||||
}
|
||||
|
||||
type Option func(*Config)
|
||||
|
||||
func WithPattern(pattern *regexp.Regexp) Option {
|
||||
return func(cfg *Config) {
|
||||
cfg.pattern = pattern
|
||||
}
|
||||
}
|
||||
|
||||
func WithSubstitutionFunction(subsFunc SubstituteFunc) Option {
|
||||
return func(cfg *Config) {
|
||||
cfg.substituteFunc = subsFunc
|
||||
}
|
||||
}
|
||||
|
||||
func WithReplacementFunction(replacementFunc ReplacementFunc) Option {
|
||||
return func(cfg *Config) {
|
||||
cfg.replacementFunc = replacementFunc
|
||||
}
|
||||
}
|
||||
|
||||
func WithoutLogging(cfg *Config) {
|
||||
cfg.logging = false
|
||||
}
|
||||
|
||||
// SubstituteWithOptions substitute variables in the string with their values.
|
||||
// It accepts additional options such as a custom function or pattern.
|
||||
func SubstituteWithOptions(template string, mapping Mapping, options ...Option) (string, error) {
|
||||
var returnErr error
|
||||
|
||||
result := pattern.ReplaceAllStringFunc(template, func(substring string) string {
|
||||
_, subsFunc := getSubstitutionFunctionForTemplate(substring)
|
||||
if len(subsFuncs) > 0 {
|
||||
subsFunc = subsFuncs[0]
|
||||
}
|
||||
cfg := &Config{
|
||||
pattern: defaultPattern,
|
||||
replacementFunc: DefaultReplacementFunc,
|
||||
logging: true,
|
||||
}
|
||||
for _, o := range options {
|
||||
o(cfg)
|
||||
}
|
||||
|
||||
closingBraceIndex := getFirstBraceClosingIndex(substring)
|
||||
rest := ""
|
||||
if closingBraceIndex > -1 {
|
||||
rest = substring[closingBraceIndex+1:]
|
||||
substring = substring[0 : closingBraceIndex+1]
|
||||
}
|
||||
|
||||
matches := pattern.FindStringSubmatch(substring)
|
||||
groups := matchGroups(matches, pattern)
|
||||
if escaped := groups["escaped"]; escaped != "" {
|
||||
return escaped
|
||||
}
|
||||
|
||||
braced := false
|
||||
substitution := groups["named"]
|
||||
if substitution == "" {
|
||||
substitution = groups["braced"]
|
||||
braced = true
|
||||
}
|
||||
|
||||
if substitution == "" {
|
||||
outerErr = &InvalidTemplateError{Template: template}
|
||||
result := cfg.pattern.ReplaceAllStringFunc(template, func(substring string) string {
|
||||
replacement, err := cfg.replacementFunc(substring, mapping, cfg)
|
||||
if err != nil {
|
||||
// Add the template for template errors
|
||||
var tmplErr *InvalidTemplateError
|
||||
if errors.As(err, &tmplErr) {
|
||||
if tmplErr.Template == "" {
|
||||
tmplErr.Template = template
|
||||
}
|
||||
}
|
||||
// Save the first error to be returned
|
||||
if returnErr == nil {
|
||||
returnErr = outerErr
|
||||
returnErr = err
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
if braced {
|
||||
var (
|
||||
value string
|
||||
applied bool
|
||||
)
|
||||
value, applied, outerErr = subsFunc(substitution, mapping)
|
||||
if outerErr != nil {
|
||||
if returnErr == nil {
|
||||
returnErr = outerErr
|
||||
}
|
||||
return ""
|
||||
}
|
||||
if applied {
|
||||
interpolatedNested, err := SubstituteWith(rest, mapping, pattern)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return value + interpolatedNested
|
||||
}
|
||||
}
|
||||
|
||||
value, ok := mapping(substitution)
|
||||
if !ok {
|
||||
logrus.Warnf("The %q variable is not set. Defaulting to a blank string.", substitution)
|
||||
}
|
||||
return value
|
||||
return replacement
|
||||
})
|
||||
|
||||
return result, returnErr
|
||||
}
|
||||
|
||||
func DefaultReplacementFunc(substring string, mapping Mapping, cfg *Config) (string, error) {
|
||||
value, _, err := DefaultReplacementAppliedFunc(substring, mapping, cfg)
|
||||
return value, err
|
||||
}
|
||||
|
||||
func DefaultReplacementAppliedFunc(substring string, mapping Mapping, cfg *Config) (string, bool, error) {
|
||||
pattern := cfg.pattern
|
||||
subsFunc := cfg.substituteFunc
|
||||
if subsFunc == nil {
|
||||
_, subsFunc = getSubstitutionFunctionForTemplate(substring)
|
||||
}
|
||||
|
||||
closingBraceIndex := getFirstBraceClosingIndex(substring)
|
||||
rest := ""
|
||||
if closingBraceIndex > -1 {
|
||||
rest = substring[closingBraceIndex+1:]
|
||||
substring = substring[0 : closingBraceIndex+1]
|
||||
}
|
||||
|
||||
matches := pattern.FindStringSubmatch(substring)
|
||||
groups := matchGroups(matches, pattern)
|
||||
if escaped := groups["escaped"]; escaped != "" {
|
||||
return escaped, true, nil
|
||||
}
|
||||
|
||||
braced := false
|
||||
substitution := groups["named"]
|
||||
if substitution == "" {
|
||||
substitution = groups["braced"]
|
||||
braced = true
|
||||
}
|
||||
|
||||
if substitution == "" {
|
||||
return "", false, &InvalidTemplateError{}
|
||||
}
|
||||
|
||||
if braced {
|
||||
value, applied, err := subsFunc(substitution, mapping)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
if applied {
|
||||
interpolatedNested, err := SubstituteWith(rest, mapping, pattern)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
return value + interpolatedNested, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
value, ok := mapping(substitution)
|
||||
if !ok && cfg.logging {
|
||||
logrus.Warnf("The %q variable is not set. Defaulting to a blank string.", substitution)
|
||||
}
|
||||
|
||||
return value, ok, nil
|
||||
}
|
||||
|
||||
// SubstituteWith substitute variables in the string with their values.
|
||||
// It accepts additional substitute function.
|
||||
func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, subsFuncs ...SubstituteFunc) (string, error) {
|
||||
options := []Option{
|
||||
WithPattern(pattern),
|
||||
}
|
||||
if len(subsFuncs) > 0 {
|
||||
options = append(options, WithSubstitutionFunction(subsFuncs[0]))
|
||||
}
|
||||
|
||||
return SubstituteWithOptions(template, mapping, options...)
|
||||
}
|
||||
|
||||
func getSubstitutionFunctionForTemplate(template string) (string, SubstituteFunc) {
|
||||
interpolationMapping := []struct {
|
||||
string
|
||||
|
24
vendor/github.com/compose-spec/compose-go/types/config.go
generated
vendored
24
vendor/github.com/compose-spec/compose-go/types/config.go
generated
vendored
@ -67,16 +67,24 @@ type ConfigFile struct {
|
||||
Config map[string]interface{}
|
||||
}
|
||||
|
||||
func ToConfigFiles(path []string) (f []ConfigFile) {
|
||||
for _, p := range path {
|
||||
f = append(f, ConfigFile{Filename: p})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Config is a full compose file configuration and model
|
||||
type Config struct {
|
||||
Filename string `yaml:"-" json:"-"`
|
||||
Name string `yaml:",omitempty" json:"name,omitempty"`
|
||||
Services Services `json:"services"`
|
||||
Networks Networks `yaml:",omitempty" json:"networks,omitempty"`
|
||||
Volumes Volumes `yaml:",omitempty" json:"volumes,omitempty"`
|
||||
Secrets Secrets `yaml:",omitempty" json:"secrets,omitempty"`
|
||||
Configs Configs `yaml:",omitempty" json:"configs,omitempty"`
|
||||
Extensions Extensions `yaml:",inline" json:"-"`
|
||||
Filename string `yaml:"-" json:"-"`
|
||||
Name string `yaml:"name,omitempty" json:"name,omitempty"`
|
||||
Services Services `yaml:"services" json:"services"`
|
||||
Networks Networks `yaml:"networks,omitempty" json:"networks,omitempty"`
|
||||
Volumes Volumes `yaml:"volumes,omitempty" json:"volumes,omitempty"`
|
||||
Secrets Secrets `yaml:"secrets,omitempty" json:"secrets,omitempty"`
|
||||
Configs Configs `yaml:"configs,omitempty" json:"configs,omitempty"`
|
||||
Extensions Extensions `yaml:",inline" json:"-"`
|
||||
Include []IncludeConfig `yaml:"include,omitempty" json:"include,omitempty"`
|
||||
}
|
||||
|
||||
// Volumes is a map of VolumeConfig
|
||||
|
50
vendor/github.com/compose-spec/compose-go/types/project.go
generated
vendored
50
vendor/github.com/compose-spec/compose-go/types/project.go
generated
vendored
@ -24,6 +24,8 @@ import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/compose-spec/compose-go/utils"
|
||||
|
||||
"github.com/compose-spec/compose-go/dotenv"
|
||||
"github.com/distribution/distribution/v3/reference"
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
@ -102,10 +104,19 @@ 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])
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
|
||||
func (p *Project) getServicesByNames(names ...string) (Services, []string) {
|
||||
if len(names) == 0 {
|
||||
return p.Services, nil
|
||||
}
|
||||
services := Services{}
|
||||
var servicesNotFound []string
|
||||
for _, name := range names {
|
||||
var serviceConfig *ServiceConfig
|
||||
for _, s := range p.Services {
|
||||
@ -115,11 +126,12 @@ func (p *Project) GetServices(names ...string) (Services, error) {
|
||||
}
|
||||
}
|
||||
if serviceConfig == nil {
|
||||
return services, fmt.Errorf("no such service: %s", name)
|
||||
servicesNotFound = append(servicesNotFound, name)
|
||||
continue
|
||||
}
|
||||
services = append(services, *serviceConfig)
|
||||
}
|
||||
return services, nil
|
||||
return services, servicesNotFound
|
||||
}
|
||||
|
||||
// GetDisabledService retrieve disabled service by name
|
||||
@ -159,26 +171,30 @@ func (p *Project) WithServices(names []string, fn ServiceFunc, options ...Depend
|
||||
// backward compatibility
|
||||
options = []DependencyOption{IncludeDependencies}
|
||||
}
|
||||
return p.withServices(names, fn, map[string]bool{}, options)
|
||||
return p.withServices(names, fn, map[string]bool{}, options, map[string]ServiceDependency{})
|
||||
}
|
||||
|
||||
func (p *Project) withServices(names []string, fn ServiceFunc, seen map[string]bool, options []DependencyOption) error {
|
||||
services, err := p.GetServices(names...)
|
||||
if err != nil {
|
||||
return err
|
||||
func (p *Project) withServices(names []string, fn ServiceFunc, seen map[string]bool, options []DependencyOption, dependencies map[string]ServiceDependency) error {
|
||||
services, servicesNotFound := p.getServicesByNames(names...)
|
||||
if len(servicesNotFound) > 0 {
|
||||
for _, serviceNotFound := range servicesNotFound {
|
||||
if dependency, ok := dependencies[serviceNotFound]; !ok || dependency.Required {
|
||||
return fmt.Errorf("no such service: %s", serviceNotFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, service := range services {
|
||||
if seen[service.Name] {
|
||||
continue
|
||||
}
|
||||
seen[service.Name] = true
|
||||
var dependencies []string
|
||||
var dependencies map[string]ServiceDependency
|
||||
for _, policy := range options {
|
||||
switch policy {
|
||||
case IncludeDependents:
|
||||
dependencies = append(dependencies, p.GetDependentsForService(service)...)
|
||||
dependencies = utils.MapsAppend(dependencies, p.dependentsForService(service))
|
||||
case IncludeDependencies:
|
||||
dependencies = append(dependencies, service.GetDependencies()...)
|
||||
dependencies = utils.MapsAppend(dependencies, service.DependsOn)
|
||||
case IgnoreDependencies:
|
||||
// Noop
|
||||
default:
|
||||
@ -186,7 +202,7 @@ func (p *Project) withServices(names []string, fn ServiceFunc, seen map[string]b
|
||||
}
|
||||
}
|
||||
if len(dependencies) > 0 {
|
||||
err := p.withServices(dependencies, fn, seen, options)
|
||||
err := p.withServices(utils.MapKeys(dependencies), fn, seen, options, dependencies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -199,11 +215,15 @@ func (p *Project) withServices(names []string, fn ServiceFunc, seen map[string]b
|
||||
}
|
||||
|
||||
func (p *Project) GetDependentsForService(s ServiceConfig) []string {
|
||||
var dependent []string
|
||||
return utils.MapKeys(p.dependentsForService(s))
|
||||
}
|
||||
|
||||
func (p *Project) dependentsForService(s ServiceConfig) map[string]ServiceDependency {
|
||||
dependent := make(map[string]ServiceDependency)
|
||||
for _, service := range p.Services {
|
||||
for name := range service.DependsOn {
|
||||
for name, dependency := range service.DependsOn {
|
||||
if name == s.Name {
|
||||
dependent = append(dependent, service.Name)
|
||||
dependent[service.Name] = dependency
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -507,7 +527,7 @@ func (p Project) ResolveServicesEnvironment(discardEnvFiles bool) error {
|
||||
|
||||
fileVars, err := dotenv.ParseWithLookup(bytes.NewBuffer(b), resolve)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrapf(err, "failed to read %s", envFile)
|
||||
}
|
||||
environment.OverrideBy(Mapping(fileVars).ToMappingWithEquals())
|
||||
}
|
||||
|
23
vendor/github.com/compose-spec/compose-go/types/types.go
generated
vendored
23
vendor/github.com/compose-spec/compose-go/types/types.go
generated
vendored
@ -89,6 +89,7 @@ type ServiceConfig struct {
|
||||
Profiles []string `yaml:"profiles,omitempty" json:"profiles,omitempty"`
|
||||
|
||||
Annotations Mapping `yaml:"annotations,omitempty" json:"annotations,omitempty"`
|
||||
Attach *bool `yaml:"attach,omitempty" json:"attach,omitempty"`
|
||||
Build *BuildConfig `yaml:"build,omitempty" json:"build,omitempty"`
|
||||
BlkioConfig *BlkioConfig `yaml:"blkio_config,omitempty" json:"blkio_config,omitempty"`
|
||||
CapAdd []string `yaml:"cap_add,omitempty" json:"cap_add,omitempty"`
|
||||
@ -602,12 +603,13 @@ type DeployConfig struct {
|
||||
|
||||
// HealthCheckConfig the healthcheck configuration for a service
|
||||
type HealthCheckConfig struct {
|
||||
Test HealthCheckTest `yaml:"test,omitempty" json:"test,omitempty"`
|
||||
Timeout *Duration `yaml:"timeout,omitempty" json:"timeout,omitempty"`
|
||||
Interval *Duration `yaml:"interval,omitempty" json:"interval,omitempty"`
|
||||
Retries *uint64 `yaml:"retries,omitempty" json:"retries,omitempty"`
|
||||
StartPeriod *Duration `yaml:"start_period,omitempty" json:"start_period,omitempty"`
|
||||
Disable bool `yaml:"disable,omitempty" json:"disable,omitempty"`
|
||||
Test HealthCheckTest `yaml:"test,omitempty" json:"test,omitempty"`
|
||||
Timeout *Duration `yaml:"timeout,omitempty" json:"timeout,omitempty"`
|
||||
Interval *Duration `yaml:"interval,omitempty" json:"interval,omitempty"`
|
||||
Retries *uint64 `yaml:"retries,omitempty" json:"retries,omitempty"`
|
||||
StartPeriod *Duration `yaml:"start_period,omitempty" json:"start_period,omitempty"`
|
||||
StartInterval *Duration `yaml:"start_interval,omitempty" json:"start_interval,omitempty"`
|
||||
Disable bool `yaml:"disable,omitempty" json:"disable,omitempty"`
|
||||
|
||||
Extensions Extensions `yaml:"#extensions,inline" json:"-"`
|
||||
}
|
||||
@ -815,6 +817,8 @@ const (
|
||||
VolumeTypeTmpfs = "tmpfs"
|
||||
// VolumeTypeNamedPipe is the type for mounting Windows named pipes
|
||||
VolumeTypeNamedPipe = "npipe"
|
||||
// VolumeTypeCluster is the type for mounting container storage interface (CSI) volumes
|
||||
VolumeTypeCluster = "cluster"
|
||||
|
||||
// SElinuxShared share the volume content
|
||||
SElinuxShared = "z"
|
||||
@ -1023,6 +1027,7 @@ 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:"-"`
|
||||
Required bool `yaml:"required" json:"required"`
|
||||
}
|
||||
|
||||
type ExtendsConfig struct {
|
||||
@ -1035,3 +1040,9 @@ type SecretConfig FileObjectConfig
|
||||
|
||||
// ConfigObjConfig is the config for the swarm "Config" object
|
||||
type ConfigObjConfig FileObjectConfig
|
||||
|
||||
type IncludeConfig struct {
|
||||
Path StringList `yaml:"path,omitempty" json:"path,omitempty"`
|
||||
ProjectDirectory string `yaml:"project_directory,omitempty" json:"project_directory,omitempty"`
|
||||
EnvFile StringList `yaml:"env_file,omitempty" json:"env_file,omitempty"`
|
||||
}
|
||||
|
51
vendor/github.com/compose-spec/compose-go/utils/collectionutils.go
generated
vendored
Normal file
51
vendor/github.com/compose-spec/compose-go/utils/collectionutils.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 utils
|
||||
|
||||
import "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)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func MapsAppend[T comparable, U any](target map[T]U, source map[T]U) map[T]U {
|
||||
if target == nil {
|
||||
return source
|
||||
}
|
||||
if source == nil {
|
||||
return target
|
||||
}
|
||||
for key, value := range source {
|
||||
if _, ok := target[key]; !ok {
|
||||
target[key] = value
|
||||
}
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
func ArrayContains[T comparable](source []T, toCheck []T) bool {
|
||||
for _, value := range toCheck {
|
||||
if !slices.Contains(source, value) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
20
vendor/github.com/imdario/mergo/README.md
generated
vendored
20
vendor/github.com/imdario/mergo/README.md
generated
vendored
@ -1,17 +1,20 @@
|
||||
# Mergo
|
||||
|
||||
[![GoDoc][3]][4]
|
||||
[![GitHub release][5]][6]
|
||||
[![GoCard][7]][8]
|
||||
[![Build Status][1]][2]
|
||||
[![Coverage Status][9]][10]
|
||||
[![Test status][1]][2]
|
||||
[![OpenSSF Scorecard][21]][22]
|
||||
[![OpenSSF Best Practices][19]][20]
|
||||
[![Coverage status][9]][10]
|
||||
[![Sourcegraph][11]][12]
|
||||
[![FOSSA Status][13]][14]
|
||||
[![FOSSA status][13]][14]
|
||||
|
||||
[![GoDoc][3]][4]
|
||||
[![Become my sponsor][15]][16]
|
||||
[![Tidelift][17]][18]
|
||||
|
||||
[1]: https://travis-ci.org/imdario/mergo.png
|
||||
[2]: https://travis-ci.org/imdario/mergo
|
||||
[1]: https://github.com/imdario/mergo/workflows/tests/badge.svg?branch=master
|
||||
[2]: https://github.com/imdario/mergo/actions/workflows/tests.yml
|
||||
[3]: https://godoc.org/github.com/imdario/mergo?status.svg
|
||||
[4]: https://godoc.org/github.com/imdario/mergo
|
||||
[5]: https://img.shields.io/github/release/imdario/mergo.svg
|
||||
@ -28,6 +31,10 @@
|
||||
[16]: https://github.com/sponsors/imdario
|
||||
[17]: https://tidelift.com/badges/package/go/github.com%2Fimdario%2Fmergo
|
||||
[18]: https://tidelift.com/subscription/pkg/go-github.com-imdario-mergo
|
||||
[19]: https://bestpractices.coreinfrastructure.org/projects/7177/badge
|
||||
[20]: https://bestpractices.coreinfrastructure.org/projects/7177
|
||||
[21]: https://api.securityscorecards.dev/projects/github.com/imdario/mergo/badge
|
||||
[22]: https://api.securityscorecards.dev/projects/github.com/imdario/mergo
|
||||
|
||||
A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
|
||||
|
||||
@ -232,5 +239,4 @@ Written by [Dario Castañé](http://dario.im).
|
||||
|
||||
[BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE).
|
||||
|
||||
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_large)
|
||||
|
Reference in New Issue
Block a user