vendor: github.com/compose-spec/compose-go v1.14.0

https: //github.com/compose-spec/compose-go/compare/v1.13.4...v1.14.0
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax
2023-06-06 17:34:34 +02:00
parent 7cef021a8a
commit 9a125afba0
17 changed files with 701 additions and 405 deletions

View File

@ -8,6 +8,8 @@ services:
RUN echo "hello" > /world.txt
foo:
annotations:
- com.example.foo=bar
build:
context: ./dir
dockerfile: Dockerfile

View File

@ -21,67 +21,68 @@ import (
"strings"
interp "github.com/compose-spec/compose-go/interpolation"
"github.com/compose-spec/compose-go/tree"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
var interpolateTypeCastMapping = map[interp.Path]interp.Cast{
servicePath("configs", interp.PathMatchList, "mode"): toInt,
servicePath("cpu_count"): toInt64,
servicePath("cpu_percent"): toFloat,
servicePath("cpu_period"): toInt64,
servicePath("cpu_quota"): toInt64,
servicePath("cpu_rt_period"): toInt64,
servicePath("cpu_rt_runtime"): toInt64,
servicePath("cpus"): toFloat32,
servicePath("cpu_shares"): toInt64,
servicePath("init"): toBoolean,
servicePath("deploy", "replicas"): toInt,
servicePath("deploy", "update_config", "parallelism"): toInt,
servicePath("deploy", "update_config", "max_failure_ratio"): toFloat,
servicePath("deploy", "rollback_config", "parallelism"): toInt,
servicePath("deploy", "rollback_config", "max_failure_ratio"): toFloat,
servicePath("deploy", "restart_policy", "max_attempts"): toInt,
servicePath("deploy", "placement", "max_replicas_per_node"): toInt,
servicePath("healthcheck", "retries"): toInt,
servicePath("healthcheck", "disable"): toBoolean,
servicePath("mem_limit"): toUnitBytes,
servicePath("mem_reservation"): toUnitBytes,
servicePath("memswap_limit"): toUnitBytes,
servicePath("mem_swappiness"): toUnitBytes,
servicePath("oom_kill_disable"): toBoolean,
servicePath("oom_score_adj"): toInt64,
servicePath("pids_limit"): toInt64,
servicePath("ports", interp.PathMatchList, "target"): toInt,
servicePath("privileged"): toBoolean,
servicePath("read_only"): toBoolean,
servicePath("scale"): toInt,
servicePath("secrets", interp.PathMatchList, "mode"): toInt,
servicePath("shm_size"): toUnitBytes,
servicePath("stdin_open"): toBoolean,
servicePath("stop_grace_period"): toDuration,
servicePath("tty"): toBoolean,
servicePath("ulimits", interp.PathMatchAll): toInt,
servicePath("ulimits", interp.PathMatchAll, "hard"): toInt,
servicePath("ulimits", interp.PathMatchAll, "soft"): toInt,
servicePath("volumes", interp.PathMatchList, "read_only"): toBoolean,
servicePath("volumes", interp.PathMatchList, "volume", "nocopy"): toBoolean,
servicePath("volumes", interp.PathMatchList, "tmpfs", "size"): toUnitBytes,
iPath("networks", interp.PathMatchAll, "external"): toBoolean,
iPath("networks", interp.PathMatchAll, "internal"): toBoolean,
iPath("networks", interp.PathMatchAll, "attachable"): toBoolean,
iPath("networks", interp.PathMatchAll, "enable_ipv6"): toBoolean,
iPath("volumes", interp.PathMatchAll, "external"): toBoolean,
iPath("secrets", interp.PathMatchAll, "external"): toBoolean,
iPath("configs", interp.PathMatchAll, "external"): toBoolean,
var interpolateTypeCastMapping = map[tree.Path]interp.Cast{
servicePath("configs", tree.PathMatchList, "mode"): toInt,
servicePath("cpu_count"): toInt64,
servicePath("cpu_percent"): toFloat,
servicePath("cpu_period"): toInt64,
servicePath("cpu_quota"): toInt64,
servicePath("cpu_rt_period"): toInt64,
servicePath("cpu_rt_runtime"): toInt64,
servicePath("cpus"): toFloat32,
servicePath("cpu_shares"): toInt64,
servicePath("init"): toBoolean,
servicePath("deploy", "replicas"): toInt,
servicePath("deploy", "update_config", "parallelism"): toInt,
servicePath("deploy", "update_config", "max_failure_ratio"): toFloat,
servicePath("deploy", "rollback_config", "parallelism"): toInt,
servicePath("deploy", "rollback_config", "max_failure_ratio"): toFloat,
servicePath("deploy", "restart_policy", "max_attempts"): toInt,
servicePath("deploy", "placement", "max_replicas_per_node"): toInt,
servicePath("healthcheck", "retries"): toInt,
servicePath("healthcheck", "disable"): toBoolean,
servicePath("mem_limit"): toUnitBytes,
servicePath("mem_reservation"): toUnitBytes,
servicePath("memswap_limit"): toUnitBytes,
servicePath("mem_swappiness"): toUnitBytes,
servicePath("oom_kill_disable"): toBoolean,
servicePath("oom_score_adj"): toInt64,
servicePath("pids_limit"): toInt64,
servicePath("ports", tree.PathMatchList, "target"): toInt,
servicePath("privileged"): toBoolean,
servicePath("read_only"): toBoolean,
servicePath("scale"): toInt,
servicePath("secrets", tree.PathMatchList, "mode"): toInt,
servicePath("shm_size"): toUnitBytes,
servicePath("stdin_open"): toBoolean,
servicePath("stop_grace_period"): toDuration,
servicePath("tty"): toBoolean,
servicePath("ulimits", tree.PathMatchAll): toInt,
servicePath("ulimits", tree.PathMatchAll, "hard"): toInt,
servicePath("ulimits", tree.PathMatchAll, "soft"): toInt,
servicePath("volumes", tree.PathMatchList, "read_only"): toBoolean,
servicePath("volumes", tree.PathMatchList, "volume", "nocopy"): toBoolean,
servicePath("volumes", tree.PathMatchList, "tmpfs", "size"): toUnitBytes,
iPath("networks", tree.PathMatchAll, "external"): toBoolean,
iPath("networks", tree.PathMatchAll, "internal"): toBoolean,
iPath("networks", tree.PathMatchAll, "attachable"): toBoolean,
iPath("networks", tree.PathMatchAll, "enable_ipv6"): toBoolean,
iPath("volumes", tree.PathMatchAll, "external"): toBoolean,
iPath("secrets", tree.PathMatchAll, "external"): toBoolean,
iPath("configs", tree.PathMatchAll, "external"): toBoolean,
}
func iPath(parts ...string) interp.Path {
return interp.NewPath(parts...)
func iPath(parts ...string) tree.Path {
return tree.NewPath(parts...)
}
func servicePath(parts ...string) interp.Path {
return iPath(append([]string{"services", interp.PathMatchAll}, parts...)...)
func servicePath(parts ...string) tree.Path {
return iPath(append([]string{"services", tree.PathMatchAll}, parts...)...)
}
func toInt(value string) (interface{}, error) {

View File

@ -134,27 +134,43 @@ func WithProfiles(profiles []string) func(*Options) {
// 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) {
m, _, err := parseYAML(source)
return m, err
}
// PostProcessor is used to tweak compose model based on metadata extracted during yaml Unmarshal phase
// that hardly can be implemented using go-yaml and mapstructure
type PostProcessor interface {
yaml.Unmarshaler
// Apply changes to compose model based on recorder metadata
Apply(config *types.Config) error
}
func parseYAML(source []byte) (map[string]interface{}, PostProcessor, error) {
var cfg interface{}
if err := yaml.Unmarshal(source, &cfg); err != nil {
return nil, err
processor := ResetProcessor{target: &cfg}
if err := yaml.Unmarshal(source, &processor); err != nil {
return nil, nil, err
}
stringMap, ok := cfg.(map[string]interface{})
if ok {
converted, err := convertToStringKeysRecursive(stringMap, "")
if err != nil {
return nil, err
return nil, nil, err
}
return converted.(map[string]interface{}), nil
return converted.(map[string]interface{}), &processor, nil
}
cfgMap, ok := cfg.(map[interface{}]interface{})
if !ok {
return nil, errors.Errorf("Top-level object must be a mapping")
return nil, nil, errors.Errorf("Top-level object must be a mapping")
}
converted, err := convertToStringKeysRecursive(cfgMap, "")
if err != nil {
return nil, err
return nil, nil, err
}
return converted.(map[string]interface{}), nil
return converted.(map[string]interface{}), &processor, nil
}
// Load reads a ConfigDetails and returns a fully loaded configuration
@ -180,8 +196,9 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
return nil, err
}
var configs []*types.Config
var model *types.Config
for i, file := range configDetails.ConfigFiles {
var postProcessor PostProcessor
configDict := file.Config
if configDict == nil {
if len(file.Content) == 0 {
@ -191,13 +208,14 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
}
file.Content = content
}
dict, err := parseConfig(file.Content, opts)
dict, p, err := parseConfig(file.Content, opts)
if err != nil {
return nil, fmt.Errorf("parsing %s: %w", file.Filename, err)
}
configDict = dict
file.Config = dict
configDetails.ConfigFiles[i] = file
postProcessor = p
}
if !opts.SkipValidation {
@ -212,12 +230,22 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
if err != nil {
return nil, err
}
configs = append(configs, cfg)
}
model, err := merge(configs)
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
}
if postProcessor != nil {
err = postProcessor.Apply(merged)
if err != nil {
return nil, err
}
}
model = merged
}
for _, s := range model.Services {
@ -266,8 +294,8 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
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,
"invalid project name %q: must consist only of lowercase alphanumeric characters, hyphens, and underscores as well as start with a letter or number",
v,
)
}
@ -343,15 +371,16 @@ func NormalizeProjectName(s string) string {
return strings.TrimLeft(s, "_-")
}
func parseConfig(b []byte, opts *Options) (map[string]interface{}, error) {
yml, err := ParseYAML(b)
func parseConfig(b []byte, opts *Options) (map[string]interface{}, PostProcessor, error) {
yml, postProcessor, err := parseYAML(b)
if err != nil {
return nil, err
return nil, nil, err
}
if !opts.SkipInterpolation {
return interp.Interpolate(yml, *opts.Interpolate)
interpolated, err := interp.Interpolate(yml, *opts.Interpolate)
return interpolated, postProcessor, err
}
return yml, err
return yml, postProcessor, err
}
const extensions = "#extensions" // Using # prefix, we prevent risk to conflict with an actual yaml key
@ -441,6 +470,7 @@ func Transform(source interface{}, target interface{}, additionalTransformers ..
createTransformHook(additionalTransformers...),
mapstructure.StringToTimeDurationHookFunc()),
Result: target,
TagName: "yaml",
Metadata: &data,
}
decoder, err := mapstructure.NewDecoder(config)
@ -626,7 +656,7 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
return nil, err
}
baseFile, err := parseConfig(b, opts)
baseFile, _, err := parseConfig(b, opts)
if err != nil {
return nil, err
}

View File

@ -155,6 +155,8 @@ func Normalize(project *types.Project, resolvePaths bool) error {
return err
}
inferImplicitDependencies(&s)
project.Services[i] = s
}
@ -171,6 +173,61 @@ func Normalize(project *types.Project, resolvePaths bool) error {
return nil
}
// IsServiceDependency check the relation set by ref refers to a service
func IsServiceDependency(ref string) (string, bool) {
if strings.HasPrefix(
ref,
types.ServicePrefix,
) {
return ref[len(types.ServicePrefix):], true
}
return "", false
}
func inferImplicitDependencies(service *types.ServiceConfig) {
var dependencies []string
maybeReferences := []string{
service.NetworkMode,
service.Ipc,
service.Pid,
service.Uts,
service.Cgroup,
}
for _, ref := range maybeReferences {
if dep, ok := IsServiceDependency(ref); ok {
dependencies = append(dependencies, dep)
}
}
for _, vol := range service.VolumesFrom {
spec := strings.Split(vol, ":")
if len(spec) == 0 {
continue
}
if spec[0] == "container" {
continue
}
dependencies = append(dependencies, spec[0])
}
for _, link := range service.Links {
dependencies = append(dependencies, strings.Split(link, ":")[0])
}
if len(dependencies) > 0 && service.DependsOn == nil {
service.DependsOn = make(types.DependsOnConfig)
}
for _, d := range dependencies {
if _, ok := service.DependsOn[d]; !ok {
service.DependsOn[d] = types.ServiceDependency{
Condition: types.ServiceConditionStarted,
}
}
}
}
// 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 {

View File

@ -0,0 +1,159 @@
/*
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"
"reflect"
"strconv"
"strings"
"github.com/compose-spec/compose-go/tree"
"github.com/compose-spec/compose-go/types"
"gopkg.in/yaml.v3"
)
type ResetProcessor struct {
target interface{}
paths []tree.Path
}
// UnmarshalYAML implement yaml.Unmarshaler
func (p *ResetProcessor) UnmarshalYAML(value *yaml.Node) error {
resolved, err := p.resolveReset(value, tree.NewPath())
if err != nil {
return err
}
return resolved.Decode(p.target)
}
// resolveReset detects `!reset` tag being set on yaml nodes and record position in the yaml tree
func (p *ResetProcessor) resolveReset(node *yaml.Node, path tree.Path) (*yaml.Node, error) {
if node.Tag == "!reset" {
p.paths = append(p.paths, path)
}
switch node.Kind {
case yaml.SequenceNode:
var err error
for idx, v := range node.Content {
next := path.Next(strconv.Itoa(idx))
node.Content[idx], err = p.resolveReset(v, next)
if err != nil {
return nil, err
}
}
case yaml.MappingNode:
var err error
var key string
for idx, v := range node.Content {
if idx%2 == 0 {
key = v.Value
} else {
node.Content[idx], err = p.resolveReset(v, path.Next(key))
if err != nil {
return nil, err
}
}
}
}
return node, nil
}
// Apply finds the go attributes matching recorded paths and reset them to zero value
func (p *ResetProcessor) Apply(target *types.Config) error {
return p.applyNullOverrides(reflect.ValueOf(target), tree.NewPath())
}
// applyNullOverrides set val to Zero if it matches any of the recorded paths
func (p *ResetProcessor) applyNullOverrides(val reflect.Value, path tree.Path) error {
val = reflect.Indirect(val)
if !val.IsValid() {
return nil
}
typ := val.Type()
switch {
case path == "services":
// Project.Services is a slice in compose-go, but a mapping in yaml
for i := 0; i < val.Len(); i++ {
service := val.Index(i)
name := service.FieldByName("Name")
next := path.Next(name.String())
err := p.applyNullOverrides(service, next)
if err != nil {
return err
}
}
case typ.Kind() == reflect.Map:
iter := val.MapRange()
KEYS:
for iter.Next() {
k := iter.Key()
next := path.Next(k.String())
for _, pattern := range p.paths {
if next.Matches(pattern) {
val.SetMapIndex(k, reflect.Value{})
continue KEYS
}
}
return p.applyNullOverrides(iter.Value(), next)
}
case typ.Kind() == reflect.Slice:
ITER:
for i := 0; i < val.Len(); i++ {
next := path.Next(fmt.Sprintf("[%d]", i))
for _, pattern := range p.paths {
if next.Matches(pattern) {
continue ITER
}
}
// TODO(ndeloof) support removal from sequence
return p.applyNullOverrides(val.Index(i), next)
}
case typ.Kind() == reflect.Struct:
FIELDS:
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
name := field.Name
attr := strings.ToLower(name)
tag := field.Tag.Get("yaml")
tag = strings.Split(tag, ",")[0]
if tag != "" && tag != "-" {
attr = tag
}
next := path.Next(attr)
f := val.Field(i)
for _, pattern := range p.paths {
if next.Matches(pattern) {
f := f
if !f.CanSet() {
return fmt.Errorf("can't override attribute %s", name)
}
// f.SetZero() requires go 1.20
f.Set(reflect.Zero(f.Type()))
continue FIELDS
}
}
err := p.applyNullOverrides(f, next)
if err != nil {
return err
}
}
}
return nil
}

View File

@ -176,5 +176,8 @@ func isFilePath(source string) bool {
}
first, nextIndex := utf8.DecodeRuneInString(source)
if len(source) <= nextIndex {
return false
}
return isWindowsDrive([]rune{first}, rune(source[nextIndex]))
}