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

@ -1,7 +1,13 @@
name: Full_Example_project_name
name: full_example_project_name
services:
foo:
bar:
build:
dockerfile_inline: |
FROM alpine
RUN echo "hello" > /world.txt
foo:
build:
context: ./dir
dockerfile: Dockerfile
@ -15,6 +21,8 @@ services:
- foo
- bar
labels: [FOO=BAR]
additional_contexts:
foo: /bar
secrets:
- secret1
- source: secret2

View File

@ -22,6 +22,7 @@ import (
interp "github.com/compose-spec/compose-go/interpolation"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
var interpolateTypeCastMapping = map[interp.Path]interp.Cast{
@ -114,9 +115,15 @@ func toFloat32(value string) (interface{}, error) {
// should match http://yaml.org/type/bool.html
func toBoolean(value string) (interface{}, error) {
switch strings.ToLower(value) {
case "y", "yes", "true", "on":
case "true":
return true, nil
case "n", "no", "false", "off":
case "false":
return false, nil
case "y", "yes", "on":
logrus.Warnf("%q for boolean is not supported by YAML 1.2, please use `true`", value)
return true, nil
case "n", "no", "off":
logrus.Warnf("%q for boolean is not supported by YAML 1.2, please use `false`", value)
return false, nil
default:
return nil, errors.Errorf("invalid boolean: %s", value)

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) {

View File

@ -130,7 +130,7 @@ func _merge(baseService *types.ServiceConfig, overrideService *types.ServiceConf
if overrideService.Command != nil {
baseService.Command = overrideService.Command
}
if overrideService.HealthCheck != nil {
if overrideService.HealthCheck != nil && overrideService.HealthCheck.Test != nil {
baseService.HealthCheck.Test = overrideService.HealthCheck.Test
}
if overrideService.Entrypoint != nil {

View File

@ -20,6 +20,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/compose-spec/compose-go/errdefs"
"github.com/compose-spec/compose-go/types"
@ -27,8 +28,8 @@ import (
"github.com/sirupsen/logrus"
)
// normalize compose project by moving deprecated attributes to their canonical position and injecting implicit defaults
func normalize(project *types.Project, resolvePaths bool) error {
// 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
@ -71,17 +72,26 @@ func normalize(project *types.Project, resolvePaths bool) error {
}
if s.Build != nil {
if s.Build.Dockerfile == "" {
if s.Build.Dockerfile == "" && s.Build.DockerfileInline == "" {
s.Build.Dockerfile = "Dockerfile"
}
localContext := absPath(project.WorkingDir, s.Build.Context)
if _, err := os.Stat(localContext); err == nil {
if resolvePaths {
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
}
// } else {
// 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
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)
}
@ -90,6 +100,41 @@ func normalize(project *types.Project, resolvePaths bool) error {
}
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 {
link = parts[0]
}
s.DependsOn = setIfMissing(s.DependsOn, link, types.ServiceDependency{
Condition: types.ServiceConditionStarted,
Restart: true,
})
}
for _, namespace := range []string{s.NetworkMode, s.Ipc, s.Pid, s.Uts, s.Cgroup} {
if strings.HasPrefix(namespace, types.ServicePrefix) {
name := namespace[len(types.ServicePrefix):]
s.DependsOn = setIfMissing(s.DependsOn, name, types.ServiceDependency{
Condition: types.ServiceConditionStarted,
Restart: true,
})
}
}
for _, vol := range s.VolumesFrom {
if !strings.HasPrefix(vol, types.ContainerPrefix) {
spec := strings.Split(vol, ":")
s.DependsOn = setIfMissing(s.DependsOn, spec[0], types.ServiceDependency{
Condition: types.ServiceConditionStarted,
Restart: false,
})
}
}
err := relocateLogDriver(&s)
if err != nil {
return err
@ -126,9 +171,20 @@ func normalize(project *types.Project, resolvePaths bool) error {
return nil
}
// setIfMissing adds a ServiceDependency for service if not already defined
func setIfMissing(d types.DependsOnConfig, service string, dep types.ServiceDependency) types.DependsOnConfig {
if d == nil {
d = types.DependsOnConfig{}
}
if _, ok := d[service]; !ok {
d[service] = dep
}
return d
}
func relocateScale(s *types.ServiceConfig) error {
scale := uint64(s.Scale)
if scale != 1 {
if scale > 1 {
logrus.Warn("`scale` is deprecated. Use the `deploy.replicas` element")
if s.Deploy == nil {
s.Deploy = &types.DeployConfig{}

View File

@ -32,6 +32,28 @@ func checkConsistency(project *types.Project) error {
return errors.Wrapf(errdefs.ErrInvalid, "service %q has neither an image nor a build context specified", s.Name)
}
if s.Build != nil {
if s.Build.DockerfileInline != "" && s.Build.Dockerfile != "" {
return errors.Wrapf(errdefs.ErrInvalid, "service %q declares mutualy exclusive dockerfile and dockerfile_inline", s.Name)
}
if len(s.Build.Platforms) > 0 && s.Platform != "" {
var found bool
for _, platform := range s.Build.Platforms {
if platform == s.Platform {
found = true
break
}
}
if !found {
return errors.Wrapf(errdefs.ErrInvalid, "service.build.platforms MUST include service.platform %q ", s.Platform)
}
}
}
if s.NetworkMode != "" && len(s.Networks) > 0 {
return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %s declares mutually exclusive `network_mode` and `networks`", s.Name))
}
for network := range s.Networks {
if _, ok := project.Networks[network]; !ok {
return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q refers to undefined network %s", s.Name, network))

View File

@ -44,7 +44,7 @@ func isAbs(path string) (b bool) {
// volumeNameLen returns length of the leading volume name on Windows.
// It returns 0 elsewhere.
//nolint: gocyclo
// nolint: gocyclo
func volumeNameLen(path string) int {
if len(path) < 2 {
return 0