vendor: update to compose-go 1.13.4

Signed-off-by: Nick Sieger <nick@nicksieger.com>
This commit is contained in:
Nick Sieger
2023-04-21 10:52:58 -05:00
parent afcaa8df5f
commit 956a1be656
34 changed files with 1130 additions and 770 deletions

View File

@ -37,7 +37,7 @@ import (
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)
// Options supported by Load
@ -69,7 +69,7 @@ type Options struct {
}
func (o *Options) SetProjectName(name string, imperativelySet bool) {
o.projectName = NormalizeProjectName(name)
o.projectName = name
o.projectNameImperativelySet = imperativelySet
}
@ -138,6 +138,14 @@ func ParseYAML(source []byte) (map[string]interface{}, error) {
if err := yaml.Unmarshal(source, &cfg); err != nil {
return nil, err
}
stringMap, ok := cfg.(map[string]interface{})
if ok {
converted, err := convertToStringKeysRecursive(stringMap, "")
if err != nil {
return nil, err
}
return converted.(map[string]interface{}), nil
}
cfgMap, ok := cfg.(map[interface{}]interface{})
if !ok {
return nil, errors.Errorf("Top-level object must be a mapping")
@ -185,7 +193,7 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
}
dict, err := parseConfig(file.Content, opts)
if err != nil {
return nil, err
return nil, fmt.Errorf("parsing %s: %w", file.Filename, err)
}
configDict = dict
file.Config = dict
@ -194,7 +202,7 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
if !opts.SkipValidation {
if err := schema.Validate(configDict); err != nil {
return nil, err
return nil, fmt.Errorf("validating %s: %w", file.Filename, err)
}
}
@ -233,7 +241,7 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
}
if !opts.SkipNormalization {
err = normalize(project, opts.ResolvePaths)
err = Normalize(project, opts.ResolvePaths)
if err != nil {
return nil, err
}
@ -246,40 +254,82 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
}
}
if len(opts.Profiles) > 0 {
project.ApplyProfiles(opts.Profiles)
if profiles, ok := project.Environment[consts.ComposeProfiles]; ok && len(opts.Profiles) == 0 {
opts.Profiles = strings.Split(profiles, ",")
}
project.ApplyProfiles(opts.Profiles)
err = project.ResolveServicesEnvironment(opts.discardEnvFiles)
return project, err
}
func InvalidProjectNameErr(v string) error {
return fmt.Errorf(
"%q is not a valid project name: it must contain only "+
"characters from [a-z0-9_-] and start with [a-z0-9]", v,
)
}
// projectName determines the canonical name to use for the project considering
// the loader Options as well as `name` fields in Compose YAML fields (which
// also support interpolation).
//
// TODO(milas): restructure loading so that we don't need to re-parse the YAML
// here, as it's both wasteful and makes this code error-prone.
func projectName(details types.ConfigDetails, opts *Options) (string, error) {
projectName, projectNameImperativelySet := opts.GetProjectName()
var pjNameFromConfigFile string
for _, configFile := range details.ConfigFiles {
yml, err := ParseYAML(configFile.Content)
if err != nil {
return "", nil
// 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 {
yml, err := ParseYAML(configFile.Content)
if err != nil {
// HACK: the way that loading is currently structured, this is
// a duplicative parse just for the `name`. if it fails, we
// give up but don't return the error, knowing that it'll get
// caught downstream for us
return "", nil
}
if val, ok := yml["name"]; ok && val != "" {
sVal, ok := val.(string)
if !ok {
// HACK: see above - this is a temporary parsed version
// that hasn't been schema-validated, but we don't want
// to be the ones to actually report that, so give up,
// knowing that it'll get caught downstream for us
return "", nil
}
pjNameFromConfigFile = sVal
}
}
if val, ok := yml["name"]; ok && val != "" {
pjNameFromConfigFile = yml["name"].(string)
if !opts.SkipInterpolation {
interpolated, err := interp.Interpolate(
map[string]interface{}{"name": pjNameFromConfigFile},
*opts.Interpolate,
)
if err != nil {
return "", err
}
pjNameFromConfigFile = interpolated["name"].(string)
}
}
if !opts.SkipInterpolation {
interpolated, err := interp.Interpolate(map[string]interface{}{"name": pjNameFromConfigFile}, *opts.Interpolate)
if err != nil {
return "", err
pjNameFromConfigFile = NormalizeProjectName(pjNameFromConfigFile)
if pjNameFromConfigFile != "" {
projectName = pjNameFromConfigFile
}
pjNameFromConfigFile = interpolated["name"].(string)
}
pjNameFromConfigFile = NormalizeProjectName(pjNameFromConfigFile)
if !projectNameImperativelySet && pjNameFromConfigFile != "" {
projectName = pjNameFromConfigFile
}
if projectName == "" {
return "", errors.New("project name must not be empty")
}
if NormalizeProjectName(projectName) != projectName {
return "", InvalidProjectNameErr(projectName)
}
// TODO(milas): this should probably ALWAYS set (overriding any existing)
if _, ok := details.Environment[consts.ComposeProjectName]; !ok && projectName != "" {
details.Environment[consts.ComposeProjectName] = projectName
}
@ -304,6 +354,8 @@ func parseConfig(b []byte, opts *Options) (map[string]interface{}, error) {
return yml, err
}
const extensions = "#extensions" // Using # prefix, we prevent risk to conflict with an actual yaml key
func groupXFieldsIntoExtensions(dict map[string]interface{}) map[string]interface{} {
extras := map[string]interface{}{}
for key, value := range dict {
@ -316,7 +368,7 @@ func groupXFieldsIntoExtensions(dict map[string]interface{}) map[string]interfac
}
}
if len(extras) > 0 {
dict["extensions"] = extras
dict[extensions] = extras
}
return dict
}
@ -355,7 +407,7 @@ func loadSections(filename string, config map[string]interface{}, configDetails
if err != nil {
return nil, err
}
extensions := getSection(config, "extensions")
extensions := getSection(config, extensions)
if len(extensions) > 0 {
cfg.Extensions = extensions
}
@ -450,6 +502,22 @@ func createTransformHook(additionalTransformers ...Transformer) mapstructure.Dec
// keys need to be converted to strings for jsonschema
func convertToStringKeysRecursive(value interface{}, keyPrefix string) (interface{}, error) {
if mapping, ok := value.(map[string]interface{}); ok {
for key, entry := range mapping {
var newKeyPrefix string
if keyPrefix == "" {
newKeyPrefix = key
} else {
newKeyPrefix = fmt.Sprintf("%s.%s", keyPrefix, key)
}
convertedEntry, err := convertToStringKeysRecursive(entry, newKeyPrefix)
if err != nil {
return nil, err
}
mapping[key] = convertedEntry
}
return mapping, nil
}
if mapping, ok := value.(map[interface{}]interface{}); ok {
dict := make(map[string]interface{})
for key, entry := range mapping {
@ -501,7 +569,7 @@ func formatInvalidKeyError(keyPrefix string, key interface{}) error {
func LoadServices(filename string, servicesDict map[string]interface{}, workingDir string, lookupEnv template.Mapping, opts *Options) ([]types.ServiceConfig, error) {
var services []types.ServiceConfig
x, ok := servicesDict["extensions"]
x, ok := servicesDict[extensions]
if ok {
// as a top-level attribute, "services" doesn't support extensions, and a service can be named `x-foo`
for k, v := range x.(map[string]interface{}) {
@ -541,16 +609,17 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
}
if serviceConfig.Extends != nil && !opts.SkipExtends {
baseServiceName := *serviceConfig.Extends["service"]
baseServiceName := serviceConfig.Extends.Service
var baseService *types.ServiceConfig
if file := serviceConfig.Extends["file"]; file == nil {
file := serviceConfig.Extends.File
if file == "" {
baseService, err = loadServiceWithExtends(filename, baseServiceName, servicesDict, workingDir, lookupEnv, opts, ct)
if err != nil {
return nil, err
}
} else {
// Resolve the path to the imported file, and load it.
baseFilePath := absPath(workingDir, *file)
baseFilePath := absPath(workingDir, file)
b, err := os.ReadFile(baseFilePath)
if err != nil {
@ -569,10 +638,10 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
}
// Make paths relative to the importing Compose file. Note that we
// make the paths relative to `*file` rather than `baseFilePath` so
// that the resulting paths won't be absolute if `*file` isn't an
// 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)
baseFileParent := filepath.Dir(file)
if baseService.Build != nil {
baseService.Build.Context = resolveBuildContextPath(baseFileParent, baseService.Build.Context)
}
@ -583,12 +652,17 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
}
baseService.Volumes[i].Source = resolveMaybeUnixPath(vol.Source, baseFileParent, lookupEnv)
}
for i, envFile := range baseService.EnvFile {
baseService.EnvFile[i] = resolveMaybeUnixPath(envFile, baseFileParent, lookupEnv)
}
}
serviceConfig, err = _merge(baseService, serviceConfig)
if err != nil {
return nil, err
}
serviceConfig.Extends = nil
}
return serviceConfig, nil
@ -996,14 +1070,15 @@ var transformDependsOnConfig TransformerFunc = func(data interface{}) (interface
}
}
var transformExtendsConfig TransformerFunc = func(data interface{}) (interface{}, error) {
switch data.(type) {
var transformExtendsConfig TransformerFunc = func(value interface{}) (interface{}, error) {
switch value.(type) {
case string:
data = map[string]interface{}{
"service": data,
}
return map[string]interface{}{"service": value}, nil
case map[string]interface{}:
return value, nil
default:
return value, errors.Errorf("invalid type %T for extends", value)
}
return transformMappingOrListFunc("=", true)(data)
}
var transformServiceVolumeConfig TransformerFunc = func(data interface{}) (interface{}, error) {