vendor: update compose-go to v1.20.0

Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax 2023-10-21 08:29:01 +02:00
parent be4fd7110d
commit 05a0fdf744
No known key found for this signature in database
GPG Key ID: 3248E46B6BB8C7F7
20 changed files with 605 additions and 303 deletions

2
go.mod
View File

@ -5,7 +5,7 @@ go 1.20
require (
github.com/Masterminds/semver/v3 v3.2.1
github.com/aws/aws-sdk-go-v2/config v1.18.16
github.com/compose-spec/compose-go v1.19.0
github.com/compose-spec/compose-go v1.20.0
github.com/containerd/console v1.0.3
github.com/containerd/containerd v1.7.2
github.com/containerd/continuity v0.4.1

4
go.sum
View File

@ -123,8 +123,8 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
github.com/compose-spec/compose-go v1.19.0 h1:t68gAcwStDg0hy2kFvqHJIksf6xkqRnlSKfL45/ETqo=
github.com/compose-spec/compose-go v1.19.0/go.mod h1:+MdqXV4RA7wdFsahh/Kb8U0pAJqkg7mr4PM9tFKU8RM=
github.com/compose-spec/compose-go v1.20.0 h1:h4ZKOst1EF/DwZp7dWkb+wbTVE4nEyT9Lc89to84Ol4=
github.com/compose-spec/compose-go v1.20.0/go.mod h1:+MdqXV4RA7wdFsahh/Kb8U0pAJqkg7mr4PM9tFKU8RM=
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=

View File

@ -98,7 +98,11 @@ func (p *parser) locateKeyName(src string) (string, string, bool, error) {
var key string
var inherited bool
// trim "export" and space at beginning
src = strings.TrimLeftFunc(exportRegex.ReplaceAllString(src, ""), isSpace)
if exportRegex.MatchString(src) {
// we use a `strings.trim` to preserve the pointer to the same underlying memory.
// a regexp replace would copy the string.
src = strings.TrimLeftFunc(strings.TrimPrefix(src, "export"), isSpace)
}
// locate key name end and validate it in single loop
offset := 0

View File

@ -20,6 +20,7 @@ import (
"context"
"fmt"
"path/filepath"
"reflect"
"github.com/compose-spec/compose-go/dotenv"
interp "github.com/compose-spec/compose-go/interpolation"
@ -109,37 +110,55 @@ func loadInclude(ctx context.Context, filename string, configDetails types.Confi
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 {
if present, ok := services[service.Name]; ok {
if reflect.DeepEqual(present, service) {
continue
}
return fmt.Errorf("imported compose file %s defines conflicting service %s", path, service.Name)
}
model.Services = append(model.Services, service)
}
for _, service := range imported.DisabledServices {
if _, ok := services[service.Name]; ok {
if disabled, ok := services[service.Name]; ok {
if reflect.DeepEqual(disabled, service) {
continue
}
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 {
if present, ok := model.Networks[n]; ok {
if reflect.DeepEqual(present, network) {
continue
}
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 {
if present, ok := model.Volumes[n]; ok {
if reflect.DeepEqual(present, volume) {
continue
}
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 {
if present, ok := model.Secrets[n]; ok {
if reflect.DeepEqual(present, secret) {
continue
}
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 {
if present, ok := model.Configs[n]; ok {
if reflect.DeepEqual(present, config) {
continue
}
return fmt.Errorf("imported compose file %s defines conflicting config %s", path, n)
}
model.Configs[n] = config

View File

@ -46,10 +46,6 @@ var interpolateTypeCastMapping = map[tree.Path]interp.Cast{
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,
@ -58,16 +54,13 @@ var interpolateTypeCastMapping = map[tree.Path]interp.Cast{
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,
@ -93,14 +86,6 @@ func toInt64(value string) (interface{}, error) {
return strconv.ParseInt(value, 10, 64)
}
func toUnitBytes(value string) (interface{}, error) {
return transformSize(value)
}
func toDuration(value string) (interface{}, error) {
return transformStringToDuration(value)
}
func toFloat(value string) (interface{}, error) {
return strconv.ParseFloat(value, 64)
}

View File

@ -28,15 +28,12 @@ import (
"regexp"
"strconv"
"strings"
"time"
"github.com/compose-spec/compose-go/consts"
interp "github.com/compose-spec/compose-go/interpolation"
"github.com/compose-spec/compose-go/schema"
"github.com/compose-spec/compose-go/template"
"github.com/compose-spec/compose-go/types"
"github.com/docker/go-units"
"github.com/mattn/go-shellwords"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@ -241,6 +238,15 @@ func LoadWithContext(ctx context.Context, configDetails types.ConfigDetails, opt
return nil, err
}
opts.projectName = projectName
// TODO(milas): this should probably ALWAYS set (overriding any existing)
if _, ok := configDetails.Environment[consts.ComposeProjectName]; !ok && projectName != "" {
if configDetails.Environment == nil {
configDetails.Environment = map[string]string{}
}
configDetails.Environment[consts.ComposeProjectName] = projectName
}
return load(ctx, configDetails, opts, nil)
}
@ -464,10 +470,6 @@ func projectName(details types.ConfigDetails, opts *Options) (string, error) {
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
}
return projectName, nil
}
@ -586,7 +588,7 @@ func Transform(source interface{}, target interface{}, additionalTransformers ..
config := &mapstructure.DecoderConfig{
DecodeHook: mapstructure.ComposeDecodeHookFunc(
createTransformHook(additionalTransformers...),
mapstructure.StringToTimeDurationHookFunc()),
decoderHook),
Result: target,
TagName: "yaml",
Metadata: &data,
@ -610,28 +612,20 @@ type Transformer struct {
func createTransformHook(additionalTransformers ...Transformer) mapstructure.DecodeHookFuncType {
transforms := map[reflect.Type]func(interface{}) (interface{}, error){
reflect.TypeOf(types.External{}): transformExternal,
reflect.TypeOf(types.HealthCheckTest{}): transformHealthCheckTest,
reflect.TypeOf(types.ShellCommand{}): transformShellCommand,
reflect.TypeOf(types.StringList{}): transformStringList,
reflect.TypeOf(map[string]string{}): transformMapStringString,
reflect.TypeOf(types.Options{}): transformOptions,
reflect.TypeOf(types.UlimitsConfig{}): transformUlimits,
reflect.TypeOf(types.UnitBytes(0)): transformSize,
reflect.TypeOf([]types.ServicePortConfig{}): transformServicePort,
reflect.TypeOf(types.ServiceSecretConfig{}): transformFileReferenceConfig,
reflect.TypeOf(types.ServiceConfigObjConfig{}): transformFileReferenceConfig,
reflect.TypeOf(types.StringOrNumberList{}): transformStringOrNumberList,
reflect.TypeOf(map[string]*types.ServiceNetworkConfig{}): transformServiceNetworkMap,
reflect.TypeOf(types.Mapping{}): transformMappingOrListFunc("=", false),
reflect.TypeOf(types.MappingWithEquals{}): transformMappingOrListFunc("=", true),
reflect.TypeOf(types.Labels{}): transformMappingOrListFunc("=", false),
reflect.TypeOf(types.MappingWithColon{}): transformMappingOrListFunc(":", false),
reflect.TypeOf(types.HostsList{}): transformMappingOrListFunc(":", false),
reflect.TypeOf(types.ServiceVolumeConfig{}): transformServiceVolumeConfig,
reflect.TypeOf(types.BuildConfig{}): transformBuildConfig,
reflect.TypeOf(types.Duration(0)): transformStringToDuration,
reflect.TypeOf(types.DependsOnConfig{}): transformDependsOnConfig,
reflect.TypeOf(types.ExtendsConfig{}): transformExtendsConfig,
reflect.TypeOf(types.DeviceRequest{}): transformServiceDeviceRequest,
reflect.TypeOf(types.SSHConfig{}): transformSSHConfig,
reflect.TypeOf(types.IncludeConfig{}): transformIncludeConfig,
}
@ -1031,7 +1025,7 @@ func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfi
return obj, nil
}
var transformMapStringString TransformerFunc = func(data interface{}) (interface{}, error) {
var transformOptions TransformerFunc = func(data interface{}) (interface{}, error) {
switch value := data.(type) {
case map[string]interface{}:
return toMapStringString(value, false), nil
@ -1094,35 +1088,6 @@ var transformServicePort TransformerFunc = func(data interface{}) (interface{},
}
}
var transformServiceDeviceRequest TransformerFunc = func(data interface{}) (interface{}, error) {
switch value := data.(type) {
case map[string]interface{}:
count, ok := value["count"]
if ok {
switch val := count.(type) {
case int:
return value, nil
case string:
if strings.ToLower(val) == "all" {
value["count"] = -1
return value, nil
}
i, err := strconv.ParseInt(val, 10, 64)
if err == nil {
value["count"] = i
return value, nil
}
return data, errors.Errorf("invalid string value for 'count' (the only value allowed is 'all' or a number)")
default:
return data, errors.Errorf("invalid type %T for device count", val)
}
}
return data, nil
default:
return data, errors.Errorf("invalid type %T for resource reservation", value)
}
}
var transformFileReferenceConfig TransformerFunc = func(data interface{}) (interface{}, error) {
switch value := data.(type) {
case string:
@ -1258,26 +1223,6 @@ func ParseShortSSHSyntax(value string) ([]types.SSHKey, error) {
return result, nil
}
var transformStringOrNumberList TransformerFunc = func(value interface{}) (interface{}, error) {
list := value.([]interface{})
result := make([]string, len(list))
for i, item := range list {
result[i] = fmt.Sprint(item)
}
return result, nil
}
var transformStringList TransformerFunc = func(data interface{}) (interface{}, error) {
switch value := data.(type) {
case string:
return []string{value}, nil
case []interface{}:
return value, nil
default:
return data, errors.Errorf("invalid type %T for string list", value)
}
}
func transformMappingOrListFunc(sep string, allowNil bool) TransformerFunc {
return func(data interface{}) (interface{}, error) {
return transformMappingOrList(data, sep, allowNil)
@ -1312,52 +1257,6 @@ func transformValueToMapEntry(value string, separator string, allowNil bool) (st
}
}
var transformShellCommand TransformerFunc = func(value interface{}) (interface{}, error) {
if str, ok := value.(string); ok {
return shellwords.Parse(str)
}
return value, nil
}
var transformHealthCheckTest TransformerFunc = func(data interface{}) (interface{}, error) {
switch value := data.(type) {
case string:
return append([]string{"CMD-SHELL"}, value), nil
case []interface{}:
return value, nil
default:
return value, errors.Errorf("invalid type %T for healthcheck.test", value)
}
}
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)
}
}
var transformStringToDuration TransformerFunc = func(value interface{}) (interface{}, error) {
switch value := value.(type) {
case string:
d, err := time.ParseDuration(value)
if err != nil {
return value, err
}
return types.Duration(d), nil
case types.Duration:
return value, nil
default:
return value, errors.Errorf("invalid type %T for duration", value)
}
}
func toMapStringString(value map[string]interface{}, allowNil bool) map[string]interface{} {
output := make(map[string]interface{})
for key, value := range value {

View File

@ -0,0 +1,53 @@
/*
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 "reflect"
// comparable to yaml.Unmarshaler, decoder allow a type to define it's own custom logic to convert value
// see https://github.com/mitchellh/mapstructure/pull/294
type decoder interface {
DecodeMapstructure(interface{}) error
}
// see https://github.com/mitchellh/mapstructure/issues/115#issuecomment-735287466
// adapted to support types derived from built-in types, as DecodeMapstructure would not be able to mutate internal
// value, so need to invoke DecodeMapstructure defined by pointer to type
func decoderHook(from reflect.Value, to reflect.Value) (interface{}, error) {
// If the destination implements the decoder interface
u, ok := to.Interface().(decoder)
if !ok {
// for non-struct types we need to invoke func (*type) DecodeMapstructure()
if to.CanAddr() {
pto := to.Addr()
u, ok = pto.Interface().(decoder)
}
if !ok {
return from.Interface(), nil
}
}
// If it is nil and a pointer, create and assign the target value first
if to.Type().Kind() == reflect.Ptr && to.IsNil() {
to.Set(reflect.New(to.Type().Elem()))
u = to.Interface().(decoder)
}
// Call the custom DecodeMapstructure method
if err := u.DecodeMapstructure(from.Interface()); err != nil {
return to.Interface(), err
}
return to.Interface(), nil
}

View File

@ -320,8 +320,8 @@ func mergeLoggingConfig(dst, src reflect.Value) error {
if getLoggingDriver(dst.Elem()) == "" {
dst.Elem().FieldByName("Driver").SetString(getLoggingDriver(src.Elem()))
}
dstOptions := dst.Elem().FieldByName("Options").Interface().(map[string]string)
srcOptions := src.Elem().FieldByName("Options").Interface().(map[string]string)
dstOptions := dst.Elem().FieldByName("Options").Interface().(types.Options)
srcOptions := src.Elem().FieldByName("Options").Interface().(types.Options)
return mergo.Merge(&dstOptions, srcOptions, mergo.WithOverride)
}
// Different driver, override with src

View File

@ -475,7 +475,7 @@
"properties": {
"ignore": {"type": "array", "items": {"type": "string"}},
"path": {"type": "string"},
"action": {"type": "string", "enum": ["rebuild", "sync"]},
"action": {"type": "string", "enum": ["rebuild", "sync", "sync+restart"]},
"target": {"type": "string"}
}
},

View File

@ -0,0 +1,42 @@
/*
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 types
import (
"fmt"
"github.com/docker/go-units"
)
// UnitBytes is the bytes type
type UnitBytes int64
// MarshalYAML makes UnitBytes implement yaml.Marshaller
func (u UnitBytes) MarshalYAML() (interface{}, error) {
return fmt.Sprintf("%d", u), nil
}
// MarshalJSON makes UnitBytes implement json.Marshaler
func (u UnitBytes) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%d"`, u)), nil
}
func (u *UnitBytes) DecodeMapstructure(value interface{}) error {
v, err := units.RAMInBytes(fmt.Sprint(value))
*u = UnitBytes(v)
return err
}

View File

@ -0,0 +1,86 @@
/*
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 types
import "github.com/mattn/go-shellwords"
// ShellCommand is a string or list of string args.
//
// When marshaled to YAML, nil command fields will be omitted if `omitempty`
// is specified as a struct tag. Explicitly empty commands (i.e. `[]` or
// empty string will serialize to an empty array (`[]`).
//
// When marshaled to JSON, the `omitempty` struct must NOT be specified.
// If the command field is nil, it will be serialized as `null`.
// Explicitly empty commands (i.e. `[]` or empty string) will serialize to
// an empty array (`[]`).
//
// The distinction between nil and explicitly empty is important to distinguish
// between an unset value and a provided, but empty, value, which should be
// preserved so that it can override any base value (e.g. container entrypoint).
//
// The different semantics between YAML and JSON are due to limitations with
// JSON marshaling + `omitempty` in the Go stdlib, while gopkg.in/yaml.v3 gives
// us more flexibility via the yaml.IsZeroer interface.
//
// In the future, it might make sense to make fields of this type be
// `*ShellCommand` to avoid this situation, but that would constitute a
// breaking change.
type ShellCommand []string
// IsZero returns true if the slice is nil.
//
// Empty (but non-nil) slices are NOT considered zero values.
func (s ShellCommand) IsZero() bool {
// we do NOT want len(s) == 0, ONLY explicitly nil
return s == nil
}
// MarshalYAML returns nil (which will be serialized as `null`) for nil slices
// and delegates to the standard marshaller behavior otherwise.
//
// NOTE: Typically the nil case here is not hit because IsZero has already
// short-circuited marshalling, but this ensures that the type serializes
// accurately if the `omitempty` struct tag is omitted/forgotten.
//
// A similar MarshalJSON() implementation is not needed because the Go stdlib
// already serializes nil slices to `null`, whereas gopkg.in/yaml.v3 by default
// serializes nil slices to `[]`.
func (s ShellCommand) MarshalYAML() (interface{}, error) {
if s == nil {
return nil, nil
}
return []string(s), nil
}
func (s *ShellCommand) DecodeMapstructure(value interface{}) error {
switch v := value.(type) {
case string:
cmd, err := shellwords.Parse(v)
if err != nil {
return err
}
*s = cmd
case []interface{}:
cmd := make([]string, len(v))
for i, s := range v {
cmd[i] = s.(string)
}
*s = cmd
}
return nil
}

View File

@ -23,8 +23,9 @@ type DevelopConfig struct {
type WatchAction string
const (
WatchActionSync WatchAction = "sync"
WatchActionRebuild WatchAction = "rebuild"
WatchActionSync WatchAction = "sync"
WatchActionRebuild WatchAction = "rebuild"
WatchActionSyncRestart WatchAction = "sync+restart"
)
type Trigger struct {

View File

@ -0,0 +1,53 @@
/*
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 types
import (
"strconv"
"strings"
"github.com/pkg/errors"
)
type DeviceRequest struct {
Capabilities []string `yaml:"capabilities,omitempty" json:"capabilities,omitempty"`
Driver string `yaml:"driver,omitempty" json:"driver,omitempty"`
Count DeviceCount `yaml:"count,omitempty" json:"count,omitempty"`
IDs []string `yaml:"device_ids,omitempty" json:"device_ids,omitempty"`
}
type DeviceCount int64
func (c *DeviceCount) DecodeMapstructure(value interface{}) error {
switch v := value.(type) {
case int:
*c = DeviceCount(v)
case string:
if strings.ToLower(v) == "all" {
*c = -1
return nil
}
i, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return errors.Errorf("invalid value %q, the only value allowed is 'all' or a number", v)
}
*c = DeviceCount(i)
default:
return errors.Errorf("invalid type %T for device count", v)
}
return nil
}

View File

@ -0,0 +1,60 @@
/*
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 types
import (
"encoding/json"
"fmt"
"strings"
"time"
)
// Duration is a thin wrapper around time.Duration with improved JSON marshalling
type Duration time.Duration
func (d Duration) String() string {
return time.Duration(d).String()
}
func (d *Duration) DecodeMapstructure(value interface{}) error {
v, err := time.ParseDuration(fmt.Sprint(value))
if err != nil {
return err
}
*d = Duration(v)
return nil
}
// MarshalJSON makes Duration implement json.Marshaler
func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String())
}
// MarshalYAML makes Duration implement yaml.Marshaler
func (d Duration) MarshalYAML() (interface{}, error) {
return d.String(), nil
}
func (d *Duration) UnmarshalJSON(b []byte) error {
s := strings.Trim(string(b), "\"")
timeDuration, err := time.ParseDuration(s)
if err != nil {
return err
}
*d = Duration(timeDuration)
return nil
}

View File

@ -0,0 +1,53 @@
/*
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 types
import (
"fmt"
)
// 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"`
StartInterval *Duration `yaml:"start_interval,omitempty" json:"start_interval,omitempty"`
Disable bool `yaml:"disable,omitempty" json:"disable,omitempty"`
Extensions Extensions `yaml:"#extensions,inline" json:"-"`
}
// HealthCheckTest is the command run to test the health of a service
type HealthCheckTest []string
func (l *HealthCheckTest) DecodeMapstructure(value interface{}) error {
switch v := value.(type) {
case string:
*l = []string{"CMD-SHELL", v}
case []interface{}:
seq := make([]string, len(v))
for i, e := range v {
seq[i] = e.(string)
}
*l = seq
default:
return fmt.Errorf("unexpected value type %T for healthcheck.test", value)
}
return nil
}

View File

@ -0,0 +1,80 @@
/*
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 types
import (
"fmt"
"strings"
)
// Labels is a mapping type for labels
type Labels map[string]string
func (l Labels) Add(key, value string) Labels {
if l == nil {
l = Labels{}
}
l[key] = value
return l
}
func (l Labels) AsList() []string {
s := make([]string, len(l))
i := 0
for k, v := range l {
s[i] = fmt.Sprintf("%s=%s", k, v)
i++
}
return s
}
// label value can be a string | number | boolean | null (empty)
func labelValue(e interface{}) string {
if e == nil {
return ""
}
switch v := e.(type) {
case string:
return v
default:
return fmt.Sprint(v)
}
}
func (l *Labels) DecodeMapstructure(value interface{}) error {
switch v := value.(type) {
case map[string]interface{}:
labels := make(map[string]string, len(v))
for k, e := range v {
labels[k] = labelValue(e)
}
*l = labels
case []interface{}:
labels := make(map[string]string, len(v))
for _, s := range v {
k, e, ok := strings.Cut(fmt.Sprint(s), "=")
if !ok {
return fmt.Errorf("invalid label %q", v)
}
labels[k] = labelValue(e)
}
*l = labels
default:
return fmt.Errorf("unexpected value type %T for labels", value)
}
return nil
}

View File

@ -0,0 +1,46 @@
/*
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 types
import (
"fmt"
"github.com/pkg/errors"
)
// Options is a mapping type for options we pass as-is to container runtime
type Options map[string]string
func (d *Options) DecodeMapstructure(value interface{}) error {
switch v := value.(type) {
case map[string]interface{}:
m := make(map[string]string)
for key, e := range v {
if e == nil {
m[key] = ""
} else {
m[key] = fmt.Sprint(e)
}
}
*d = m
case map[string]string:
*d = v
default:
return errors.Errorf("invalid type %T for options", value)
}
return nil
}

View File

@ -0,0 +1,61 @@
/*
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 types
import (
"fmt"
"github.com/pkg/errors"
)
// StringList is a type for fields that can be a string or list of strings
type StringList []string
func (l *StringList) DecodeMapstructure(value interface{}) error {
switch v := value.(type) {
case string:
*l = []string{v}
case []interface{}:
list := make([]string, len(v))
for i, e := range v {
list[i] = e.(string)
}
*l = list
default:
return errors.Errorf("invalid type %T for string list", value)
}
return nil
}
// StringOrNumberList is a type for fields that can be a list of strings or numbers
type StringOrNumberList []string
func (l *StringOrNumberList) DecodeMapstructure(value interface{}) error {
switch v := value.(type) {
case string:
*l = []string{v}
case []interface{}:
list := make([]string, len(v))
for i, e := range v {
list[i] = fmt.Sprint(e)
}
*l = list
default:
return errors.Errorf("invalid type %T for string list", value)
}
return nil
}

View File

@ -21,47 +21,10 @@ import (
"fmt"
"sort"
"strings"
"time"
"github.com/docker/go-connections/nat"
)
// Duration is a thin wrapper around time.Duration with improved JSON marshalling
type Duration time.Duration
func (d Duration) String() string {
return time.Duration(d).String()
}
// ConvertDurationPtr converts a type defined Duration pointer to a time.Duration pointer with the same value.
func ConvertDurationPtr(d *Duration) *time.Duration {
if d == nil {
return nil
}
res := time.Duration(*d)
return &res
}
// MarshalJSON makes Duration implement json.Marshaler
func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String())
}
// MarshalYAML makes Duration implement yaml.Marshaler
func (d Duration) MarshalYAML() (interface{}, error) {
return d.String(), nil
}
func (d *Duration) UnmarshalJSON(b []byte) error {
s := strings.Trim(string(b), "\"")
timeDuration, err := time.ParseDuration(s)
if err != nil {
return err
}
*d = Duration(timeDuration)
return nil
}
// Services is a list of ServiceConfig
type Services []ServiceConfig
@ -364,62 +327,6 @@ type ThrottleDevice struct {
Extensions Extensions `yaml:"#extensions,inline" json:"-"`
}
// ShellCommand is a string or list of string args.
//
// When marshaled to YAML, nil command fields will be omitted if `omitempty`
// is specified as a struct tag. Explicitly empty commands (i.e. `[]` or
// empty string will serialize to an empty array (`[]`).
//
// When marshaled to JSON, the `omitempty` struct must NOT be specified.
// If the command field is nil, it will be serialized as `null`.
// Explicitly empty commands (i.e. `[]` or empty string) will serialize to
// an empty array (`[]`).
//
// The distinction between nil and explicitly empty is important to distinguish
// between an unset value and a provided, but empty, value, which should be
// preserved so that it can override any base value (e.g. container entrypoint).
//
// The different semantics between YAML and JSON are due to limitations with
// JSON marshaling + `omitempty` in the Go stdlib, while gopkg.in/yaml.v3 gives
// us more flexibility via the yaml.IsZeroer interface.
//
// In the future, it might make sense to make fields of this type be
// `*ShellCommand` to avoid this situation, but that would constitute a
// breaking change.
type ShellCommand []string
// IsZero returns true if the slice is nil.
//
// Empty (but non-nil) slices are NOT considered zero values.
func (s ShellCommand) IsZero() bool {
// we do NOT want len(s) == 0, ONLY explicitly nil
return s == nil
}
// MarshalYAML returns nil (which will be serialized as `null`) for nil slices
// and delegates to the standard marshaller behavior otherwise.
//
// NOTE: Typically the nil case here is not hit because IsZero has already
// short-circuited marshalling, but this ensures that the type serializes
// accurately if the `omitempty` struct tag is omitted/forgotten.
//
// A similar MarshalJSON() implementation is not needed because the Go stdlib
// already serializes nil slices to `null`, whereas gopkg.in/yaml.v3 by default
// serializes nil slices to `[]`.
func (s ShellCommand) MarshalYAML() (interface{}, error) {
if s == nil {
return nil, nil
}
return []string(s), nil
}
// StringList is a type for fields that can be a string or list of strings
type StringList []string
// StringOrNumberList is a type for fields that can be a list of strings or
// numbers
type StringOrNumberList []string
// MappingWithEquals is a mapping type that can be converted from a list of
// key[=value] strings.
// For the key with an empty value (`key=`), the mapped value is set to a pointer to `""`.
@ -535,17 +442,6 @@ func (m Mapping) Merge(o Mapping) Mapping {
return m
}
// Labels is a mapping type for labels
type Labels map[string]string
func (l Labels) Add(key, value string) Labels {
if l == nil {
l = Labels{}
}
l[key] = value
return l
}
type SSHKey struct {
ID string
Path string
@ -609,8 +505,8 @@ func (h HostsList) MarshalJSON() ([]byte, error) {
// LoggingConfig the logging configuration for a service
type LoggingConfig struct {
Driver string `yaml:"driver,omitempty" json:"driver,omitempty"`
Options map[string]string `yaml:"options,omitempty" json:"options,omitempty"`
Driver string `yaml:"driver,omitempty" json:"driver,omitempty"`
Options Options `yaml:"options,omitempty" json:"options,omitempty"`
Extensions Extensions `yaml:"#extensions,inline" json:"-"`
}
@ -630,22 +526,6 @@ type DeployConfig struct {
Extensions Extensions `yaml:"#extensions,inline" json:"-"`
}
// 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"`
StartInterval *Duration `yaml:"start_interval,omitempty" json:"start_interval,omitempty"`
Disable bool `yaml:"disable,omitempty" json:"disable,omitempty"`
Extensions Extensions `yaml:"#extensions,inline" json:"-"`
}
// HealthCheckTest is the command run to test the health of a service
type HealthCheckTest []string
// UpdateConfig the service update configuration
type UpdateConfig struct {
Parallelism *uint64 `yaml:"parallelism,omitempty" json:"parallelism,omitempty"`
@ -678,13 +558,6 @@ type Resource struct {
Extensions Extensions `yaml:"#extensions,inline" json:"-"`
}
type DeviceRequest struct {
Capabilities []string `yaml:"capabilities,omitempty" json:"capabilities,omitempty"`
Driver string `yaml:"driver,omitempty" json:"driver,omitempty"`
Count int64 `yaml:"count,omitempty" json:"count,omitempty"`
IDs []string `yaml:"device_ids,omitempty" json:"device_ids,omitempty"`
}
// GenericResource represents a "user defined" resource which can
// only be an integer (e.g: SSD=3) for a service
type GenericResource struct {
@ -704,19 +577,6 @@ type DiscreteGenericResource struct {
Extensions Extensions `yaml:"#extensions,inline" json:"-"`
}
// UnitBytes is the bytes type
type UnitBytes int64
// MarshalYAML makes UnitBytes implement yaml.Marshaller
func (u UnitBytes) MarshalYAML() (interface{}, error) {
return fmt.Sprintf("%d", u), nil
}
// MarshalJSON makes UnitBytes implement json.Marshaler
func (u UnitBytes) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%d"`, u)), nil
}
// RestartPolicy the service restart policy
type RestartPolicy struct {
Condition string `yaml:"condition,omitempty" json:"condition,omitempty"`
@ -955,16 +815,16 @@ func (u *UlimitsConfig) MarshalJSON() ([]byte, error) {
// NetworkConfig for a network
type NetworkConfig struct {
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Driver string `yaml:"driver,omitempty" json:"driver,omitempty"`
DriverOpts map[string]string `yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"`
Ipam IPAMConfig `yaml:"ipam,omitempty" json:"ipam,omitempty"`
External External `yaml:"external,omitempty" json:"external,omitempty"`
Internal bool `yaml:"internal,omitempty" json:"internal,omitempty"`
Attachable bool `yaml:"attachable,omitempty" json:"attachable,omitempty"`
Labels Labels `yaml:"labels,omitempty" json:"labels,omitempty"`
EnableIPv6 bool `yaml:"enable_ipv6,omitempty" json:"enable_ipv6,omitempty"`
Extensions Extensions `yaml:"#extensions,inline" json:"-"`
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Driver string `yaml:"driver,omitempty" json:"driver,omitempty"`
DriverOpts Options `yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"`
Ipam IPAMConfig `yaml:"ipam,omitempty" json:"ipam,omitempty"`
External External `yaml:"external,omitempty" json:"external,omitempty"`
Internal bool `yaml:"internal,omitempty" json:"internal,omitempty"`
Attachable bool `yaml:"attachable,omitempty" json:"attachable,omitempty"`
Labels Labels `yaml:"labels,omitempty" json:"labels,omitempty"`
EnableIPv6 bool `yaml:"enable_ipv6,omitempty" json:"enable_ipv6,omitempty"`
Extensions Extensions `yaml:"#extensions,inline" json:"-"`
}
// IPAMConfig for a network
@ -979,18 +839,18 @@ type IPAMPool struct {
Subnet string `yaml:"subnet,omitempty" json:"subnet,omitempty"`
Gateway string `yaml:"gateway,omitempty" json:"gateway,omitempty"`
IPRange string `yaml:"ip_range,omitempty" json:"ip_range,omitempty"`
AuxiliaryAddresses map[string]string `yaml:"aux_addresses,omitempty" json:"aux_addresses,omitempty"`
AuxiliaryAddresses Mapping `yaml:"aux_addresses,omitempty" json:"aux_addresses,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"`
}
// VolumeConfig for a volume
type VolumeConfig struct {
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Driver string `yaml:"driver,omitempty" json:"driver,omitempty"`
DriverOpts map[string]string `yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"`
External External `yaml:"external,omitempty" json:"external,omitempty"`
Labels Labels `yaml:"labels,omitempty" json:"labels,omitempty"`
Extensions Extensions `yaml:"#extensions,inline" json:"-"`
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Driver string `yaml:"driver,omitempty" json:"driver,omitempty"`
DriverOpts Options `yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"`
External External `yaml:"external,omitempty" json:"external,omitempty"`
Labels Labels `yaml:"labels,omitempty" json:"labels,omitempty"`
Extensions Extensions `yaml:"#extensions,inline" json:"-"`
}
// External identifies a Volume or Network as a reference to a resource that is

2
vendor/modules.txt vendored
View File

@ -118,7 +118,7 @@ github.com/cenkalti/backoff/v4
# github.com/cespare/xxhash/v2 v2.2.0
## explicit; go 1.11
github.com/cespare/xxhash/v2
# github.com/compose-spec/compose-go v1.19.0
# github.com/compose-spec/compose-go v1.20.0
## explicit; go 1.19
github.com/compose-spec/compose-go/cli
github.com/compose-spec/compose-go/consts