update github.com/compose-spec/compose-go to v1.0.5

Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax
2021-11-09 10:16:12 +01:00
parent da0eb138d0
commit a18829f837
25 changed files with 721 additions and 436 deletions

View File

@ -31,14 +31,13 @@ import (
"github.com/compose-spec/compose-go/schema"
"github.com/compose-spec/compose-go/template"
"github.com/compose-spec/compose-go/types"
units "github.com/docker/go-units"
"github.com/imdario/mergo"
"github.com/joho/godotenv"
shellwords "github.com/mattn/go-shellwords"
"github.com/compose-spec/godotenv"
"github.com/docker/go-units"
"github.com/mattn/go-shellwords"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
yaml "gopkg.in/yaml.v2"
"gopkg.in/yaml.v2"
)
// Options supported by Load
@ -49,6 +48,8 @@ type Options struct {
SkipInterpolation bool
// Skip normalization
SkipNormalization bool
// Resolve paths
ResolvePaths bool
// Skip consistency check
SkipConsistencyCheck bool
// Skip extends
@ -103,6 +104,11 @@ func WithDiscardEnvFiles(opts *Options) {
opts.discardEnvFiles = true
}
// WithSkipValidation sets the Options to skip validation when loading sections
func WithSkipValidation(opts *Options) {
opts.SkipValidation = true
}
// ParseYAML reads the bytes from a file, parses the bytes into a mapping
// structure, and returns it.
func ParseYAML(source []byte) (map[string]interface{}, error) {
@ -199,7 +205,7 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
}
if !opts.SkipNormalization {
err = normalize(project)
err = normalize(project, opts.ResolvePaths)
if err != nil {
return nil, err
}
@ -216,34 +222,14 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
}
func parseConfig(b []byte, opts *Options) (map[string]interface{}, error) {
yaml, err := ParseYAML(b)
if err != nil {
return nil, err
}
if !opts.SkipInterpolation {
withoutComments, err := removeYamlComments(b)
if err != nil {
return nil, err
}
substituted, err := opts.Interpolate.Substitute(string(withoutComments), template.Mapping(opts.Interpolate.LookupValue))
if err != nil {
return nil, err
}
b = []byte(substituted)
return interp.Interpolate(yaml, *opts.Interpolate)
}
return ParseYAML(b)
}
// removeYamlComments drop all comments from the yaml file, so we don't try to apply string substitutions on irrelevant places
func removeYamlComments(b []byte) ([]byte, error) {
var cfg interface{}
err := yaml.Unmarshal(b, &cfg)
if err != nil {
return nil, err
}
b, err = yaml.Marshal(cfg)
if err != nil {
return nil, err
}
return b, nil
return yaml, err
}
func groupXFieldsIntoExtensions(dict map[string]interface{}) map[string]interface{} {
@ -274,7 +260,7 @@ func loadSections(filename string, config map[string]interface{}, configDetails
return nil, err
}
cfg.Networks, err = LoadNetworks(getSection(config, "networks"), configDetails.Version)
cfg.Networks, err = LoadNetworks(getSection(config, "networks"))
if err != nil {
return nil, err
}
@ -282,11 +268,11 @@ func loadSections(filename string, config map[string]interface{}, configDetails
if err != nil {
return nil, err
}
cfg.Secrets, err = LoadSecrets(getSection(config, "secrets"), configDetails)
cfg.Secrets, err = LoadSecrets(getSection(config, "secrets"), configDetails, opts.ResolvePaths)
if err != nil {
return nil, err
}
cfg.Configs, err = LoadConfigObjs(getSection(config, "configs"), configDetails)
cfg.Configs, err = LoadConfigObjs(getSection(config, "configs"), configDetails, opts.ResolvePaths)
if err != nil {
return nil, err
}
@ -439,6 +425,14 @@ 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"]
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{}) {
servicesDict[k] = v
}
}
for name := range servicesDict {
serviceConfig, err := loadServiceWithExtends(filename, name, servicesDict, workingDir, lookupEnv, opts, &cycleTracker{})
if err != nil {
@ -456,7 +450,12 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
return nil, err
}
serviceConfig, err := LoadService(name, servicesDict[name].(map[string]interface{}), workingDir, lookupEnv)
target, ok := servicesDict[name]
if !ok {
return nil, fmt.Errorf("cannot extend service %q in %s: service not found", name, filename)
}
serviceConfig, err := LoadService(name, target.(map[string]interface{}), workingDir, lookupEnv, opts.ResolvePaths)
if err != nil {
return nil, err
}
@ -478,15 +477,7 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
return nil, err
}
if !opts.SkipInterpolation {
substitute, err := opts.Interpolate.Substitute(string(bytes), template.Mapping(opts.Interpolate.LookupValue))
if err != nil {
return nil, err
}
bytes = []byte(substitute)
}
baseFile, err := ParseYAML(bytes)
baseFile, err := parseConfig(bytes, opts)
if err != nil {
return nil, err
}
@ -516,10 +507,10 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
}
}
if err := mergo.Merge(baseService, serviceConfig, mergo.WithAppendSlice, mergo.WithOverride, mergo.WithTransformers(serviceSpecials)); err != nil {
return nil, errors.Wrapf(err, "cannot merge service %s", name)
serviceConfig, err = _merge(baseService, serviceConfig)
if err != nil {
return nil, err
}
serviceConfig = baseService
}
return serviceConfig, nil
@ -527,8 +518,10 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
// 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) (*types.ServiceConfig, error) {
serviceConfig := &types.ServiceConfig{}
func LoadService(name string, serviceDict map[string]interface{}, workingDir string, lookupEnv template.Mapping, resolvePaths bool) (*types.ServiceConfig, error) {
serviceConfig := &types.ServiceConfig{
Scale: 1,
}
if err := Transform(serviceDict, serviceConfig); err != nil {
return nil, err
}
@ -538,8 +531,18 @@ func LoadService(name string, serviceDict map[string]interface{}, workingDir str
return nil, err
}
if err := resolveVolumePaths(serviceConfig.Volumes, workingDir, lookupEnv); err != nil {
return nil, err
for i, volume := range serviceConfig.Volumes {
if volume.Type != "bind" {
continue
}
if volume.Source == "" {
return nil, errors.New(`invalid mount config for type "bind": field Source must not be empty`)
}
if resolvePaths {
serviceConfig.Volumes[i] = resolveVolumePath(volume, workingDir, lookupEnv)
}
}
return serviceConfig, nil
@ -574,30 +577,19 @@ func resolveEnvironment(serviceConfig *types.ServiceConfig, workingDir string, l
return nil
}
func resolveVolumePaths(volumes []types.ServiceVolumeConfig, workingDir string, lookupEnv template.Mapping) error {
for i, volume := range volumes {
if volume.Type != "bind" {
continue
}
if volume.Source == "" {
return errors.New(`invalid mount config for type "bind": field Source must not be empty`)
}
filePath := expandUser(volume.Source, lookupEnv)
// Check if source is an absolute path (either Unix or Windows), to
// handle a Windows client with a Unix daemon or vice-versa.
//
// Note that this is not required for Docker for Windows when specifying
// a local Windows path, because Docker for Windows translates the Windows
// path into a valid path within the VM.
if !path.IsAbs(filePath) && !isAbs(filePath) {
filePath = absPath(workingDir, filePath)
}
volume.Source = filePath
volumes[i] = volume
func resolveVolumePath(volume types.ServiceVolumeConfig, workingDir string, lookupEnv template.Mapping) types.ServiceVolumeConfig {
filePath := expandUser(volume.Source, lookupEnv)
// Check if source is an absolute path (either Unix or Windows), to
// handle a Windows client with a Unix daemon or vice-versa.
//
// Note that this is not required for Docker for Windows when specifying
// a local Windows path, because Docker for Windows translates the Windows
// path into a valid path within the VM.
if !path.IsAbs(filePath) && !isAbs(filePath) {
filePath = absPath(workingDir, filePath)
}
return nil
volume.Source = filePath
return volume
}
// TODO: make this more robust
@ -633,7 +625,7 @@ func transformUlimits(data interface{}) (interface{}, error) {
// LoadNetworks produces a NetworkConfig map from a compose file Dict
// the source Dict is not validated if directly used. Use Load() to enable validation
func LoadNetworks(source map[string]interface{}, version string) (map[string]types.NetworkConfig, error) {
func LoadNetworks(source map[string]interface{}) (map[string]types.NetworkConfig, error) {
networks := make(map[string]types.NetworkConfig)
err := Transform(source, &networks)
if err != nil {
@ -701,13 +693,13 @@ 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) (map[string]types.SecretConfig, error) {
func LoadSecrets(source map[string]interface{}, details types.ConfigDetails, resolvePaths bool) (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)
obj, err := loadFileObjectConfig(name, "secret", types.FileObjectConfig(secret), details, resolvePaths)
if err != nil {
return nil, err
}
@ -719,13 +711,13 @@ func LoadSecrets(source map[string]interface{}, details types.ConfigDetails) (ma
// 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) (map[string]types.ConfigObjConfig, error) {
func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails, resolvePaths bool) (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)
obj, err := loadFileObjectConfig(name, "config", types.FileObjectConfig(config), details, resolvePaths)
if err != nil {
return nil, err
}
@ -735,7 +727,7 @@ func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails)
return configs, nil
}
func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfig, details types.ConfigDetails) (types.FileObjectConfig, error) {
func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfig, details types.ConfigDetails, resolvePaths bool) (types.FileObjectConfig, error) {
// if "external: true"
switch {
case obj.External.External:
@ -758,7 +750,9 @@ func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfi
return obj, errors.Errorf("%[1]s %[2]s: %[1]s.driver and %[1]s.file conflict; only use %[1]s.driver", objType, name)
}
default:
obj.File = absPath(details.WorkingDir, obj.File)
if resolvePaths {
obj.File = absPath(details.WorkingDir, obj.File)
}
}
return obj, nil
@ -1018,10 +1012,13 @@ var transformSize TransformerFunc = func(value interface{}) (interface{}, error)
switch value := value.(type) {
case int:
return int64(value), nil
case int64, types.UnitBytes:
return value, nil
case string:
return units.RAMInBytes(value)
default:
return value, errors.Errorf("invalid type for size %T", value)
}
panic(errors.Errorf("invalid type for size %T", value))
}
var transformStringToDuration TransformerFunc = func(value interface{}) (interface{}, error) {