From bf95aa3dfa389056ea8bb2985b772099dfa085e9 Mon Sep 17 00:00:00 2001
From: Guillaume Lours <705411+glours@users.noreply.github.com>
Date: Fri, 14 Mar 2025 14:40:03 +0100
Subject: [PATCH 1/2] bump compose-go to v2.4.9
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
---
bake/compose.go | 2 +-
go.mod | 7 +-
go.sum | 6 +-
.../compose-spec/compose-go/v2/cli/options.go | 10 -
.../compose-go/v2/dotenv/format.go | 19 +-
.../compose-go/v2/dotenv/godotenv.go | 2 +-
.../compose-go/v2/dotenv/parser.go | 4 +-
.../compose-go/v2/graph/traversal.go | 4 +-
.../compose-go/v2/loader/extends.go | 5 +-
.../compose-go/v2/loader/interpolate.go | 2 -
.../compose-go/v2/loader/loader.go | 54 +--
.../compose-go/v2/loader/omitEmpty.go | 3 +-
.../compose-go/v2/loader/paths.go | 24 --
.../compose-go/v2/loader/validate.go | 3 +-
.../compose-go/v2/paths/windows_path.go | 1 -
.../compose-go/v2/schema/compose-spec.json | 13 +-
.../compose-go/v2/template/template.go | 37 +-
.../compose-go/v2/transform/canonical.go | 2 +
.../compose-go/v2/transform/devices.go | 4 +-
.../compose-go/v2/types/config.go | 6 +-
.../compose-go/v2/types/derived.gen.go | 23 +-
.../compose-go/v2/types/develop.go | 1 +
.../compose-go/v2/types/duration.go | 4 +-
.../compose-go/v2/types/labels.go | 1 -
.../compose-go/v2/types/mapping.go | 5 +-
.../compose-go/v2/types/project.go | 21 +-
.../compose-spec/compose-go/v2/types/types.go | 79 ++++-
.../compose-go/v2/utils/pathutils.go | 1 -
.../compose-go/v2/validation/validation.go | 5 +-
.../xhit/go-str2duration/v2/LICENSE | 27 ++
.../xhit/go-str2duration/v2/README.md | 88 +++++
.../xhit/go-str2duration/v2/str2duration.go | 331 ++++++++++++++++++
vendor/modules.txt | 7 +-
33 files changed, 637 insertions(+), 164 deletions(-)
create mode 100644 vendor/github.com/xhit/go-str2duration/v2/LICENSE
create mode 100644 vendor/github.com/xhit/go-str2duration/v2/README.md
create mode 100644 vendor/github.com/xhit/go-str2duration/v2/str2duration.go
diff --git a/bake/compose.go b/bake/compose.go
index 87cd5fa3c1e2..84a6e989f3a8 100644
--- a/bake/compose.go
+++ b/bake/compose.go
@@ -214,7 +214,7 @@ func validateComposeFile(dt []byte, fn string) (bool, error) {
}
func validateCompose(dt []byte, envs map[string]string) error {
- _, err := loader.Load(composetypes.ConfigDetails{
+ _, err := loader.LoadWithContext(context.Background(), composetypes.ConfigDetails{
ConfigFiles: []composetypes.ConfigFile{
{
Content: dt,
diff --git a/go.mod b/go.mod
index c09539430e3d..aa40920aa6c9 100644
--- a/go.mod
+++ b/go.mod
@@ -1,12 +1,14 @@
module github.com/docker/buildx
-go 1.22.0
+go 1.23
+
+toolchain go1.23.7
require (
github.com/Masterminds/semver/v3 v3.2.1
github.com/Microsoft/go-winio v0.6.2
github.com/aws/aws-sdk-go-v2/config v1.27.27
- github.com/compose-spec/compose-go/v2 v2.4.8
+ github.com/compose-spec/compose-go/v2 v2.4.9
github.com/containerd/console v1.0.4
github.com/containerd/containerd/v2 v2.0.3
github.com/containerd/continuity v0.4.5
@@ -159,6 +161,7 @@ require (
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
+ github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect
diff --git a/go.sum b/go.sum
index 20556210d884..19b4dcd1ddee 100644
--- a/go.sum
+++ b/go.sum
@@ -77,8 +77,8 @@ github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
-github.com/compose-spec/compose-go/v2 v2.4.8 h1:7Myl8wDRl/4mRz77S+eyDJymGGEHu0diQdGSSeyq90A=
-github.com/compose-spec/compose-go/v2 v2.4.8/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc=
+github.com/compose-spec/compose-go/v2 v2.4.9 h1:2K4TDw+1ba2idiR6empXHKRXvWYpnvAKoNQy93/sSOs=
+github.com/compose-spec/compose-go/v2 v2.4.9/go.mod h1:6k5l/0TxCg0/2uLEhRVEsoBWBprS2uvZi32J7xub3lo=
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
@@ -459,6 +459,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
+github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
+github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zclconf/go-cty v1.4.0/go.mod h1:nHzOclRkoj++EU9ZjSrZvRG0BXIWt8c7loYc0qXAFGQ=
diff --git a/vendor/github.com/compose-spec/compose-go/v2/cli/options.go b/vendor/github.com/compose-spec/compose-go/v2/cli/options.go
index 162e1ea74609..091a2aae9db0 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/cli/options.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/cli/options.go
@@ -18,7 +18,6 @@ package cli
import (
"context"
- "fmt"
"io"
"os"
"path/filepath"
@@ -30,7 +29,6 @@ import (
"github.com/compose-spec/compose-go/v2/consts"
"github.com/compose-spec/compose-go/v2/dotenv"
- "github.com/compose-spec/compose-go/v2/errdefs"
"github.com/compose-spec/compose-go/v2/loader"
"github.com/compose-spec/compose-go/v2/types"
"github.com/compose-spec/compose-go/v2/utils"
@@ -551,14 +549,6 @@ func withListeners(options *ProjectOptions) func(*loader.Options) {
}
}
-// getConfigPaths retrieves the config files for project based on project options
-func (o *ProjectOptions) getConfigPaths() ([]string, error) {
- if len(o.ConfigPaths) != 0 {
- return absolutePaths(o.ConfigPaths)
- }
- return nil, fmt.Errorf("no configuration file provided: %w", errdefs.ErrNotFound)
-}
-
func findFiles(names []string, pwd string) []string {
candidates := []string{}
for _, n := range names {
diff --git a/vendor/github.com/compose-spec/compose-go/v2/dotenv/format.go b/vendor/github.com/compose-spec/compose-go/v2/dotenv/format.go
index c583d2126e02..7b744bc09241 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/dotenv/format.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/dotenv/format.go
@@ -21,7 +21,17 @@ import (
"io"
)
-var formats = map[string]Parser{}
+const DotEnv = ".env"
+
+var formats = map[string]Parser{
+ DotEnv: func(r io.Reader, filename string, lookup func(key string) (string, bool)) (map[string]string, error) {
+ m, err := ParseWithLookup(r, lookup)
+ if err != nil {
+ return nil, fmt.Errorf("failed to read %s: %w", filename, err)
+ }
+ return m, nil
+ },
+}
type Parser func(r io.Reader, filename string, lookup func(key string) (string, bool)) (map[string]string, error)
@@ -30,9 +40,12 @@ func RegisterFormat(format string, p Parser) {
}
func ParseWithFormat(r io.Reader, filename string, resolve LookupFn, format string) (map[string]string, error) {
- parser, ok := formats[format]
+ if format == "" {
+ format = DotEnv
+ }
+ fn, ok := formats[format]
if !ok {
return nil, fmt.Errorf("unsupported env_file format %q", format)
}
- return parser(r, filename, resolve)
+ return fn(r, filename, resolve)
}
diff --git a/vendor/github.com/compose-spec/compose-go/v2/dotenv/godotenv.go b/vendor/github.com/compose-spec/compose-go/v2/dotenv/godotenv.go
index 76907249ba5f..215e86f780c6 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/dotenv/godotenv.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/dotenv/godotenv.go
@@ -30,7 +30,7 @@ var startsWithDigitRegex = regexp.MustCompile(`^\s*\d.*`) // Keys starting with
// LookupFn represents a lookup function to resolve variables from
type LookupFn func(string) (string, bool)
-var noLookupFn = func(s string) (string, bool) {
+var noLookupFn = func(_ string) (string, bool) {
return "", false
}
diff --git a/vendor/github.com/compose-spec/compose-go/v2/dotenv/parser.go b/vendor/github.com/compose-spec/compose-go/v2/dotenv/parser.go
index 85dda7385263..2db7b9072485 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/dotenv/parser.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/dotenv/parser.go
@@ -115,7 +115,7 @@ loop:
switch rune {
case '=', ':', '\n':
// library also supports yaml-style value declaration
- key = string(src[0:i])
+ key = src[0:i]
offset = i + 1
inherited = rune == '\n'
break loop
@@ -157,7 +157,7 @@ func (p *parser) extractVarValue(src string, envMap map[string]string, lookupFn
// Remove inline comments on unquoted lines
value, _, _ = strings.Cut(value, " #")
value = strings.TrimRightFunc(value, unicode.IsSpace)
- retVal, err := expandVariables(string(value), envMap, lookupFn)
+ retVal, err := expandVariables(value, envMap, lookupFn)
return retVal, rest, err
}
diff --git a/vendor/github.com/compose-spec/compose-go/v2/graph/traversal.go b/vendor/github.com/compose-spec/compose-go/v2/graph/traversal.go
index de85d1fceca1..f0ee6c09e507 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/graph/traversal.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/graph/traversal.go
@@ -63,9 +63,9 @@ func newTraversal[S, T any](fn CollectorFn[S, T]) *traversal[S, T] {
}
// WithMaxConcurrency configure traversal to limit concurrency walking graph nodes
-func WithMaxConcurrency(max int) func(*Options) {
+func WithMaxConcurrency(concurrency int) func(*Options) {
return func(o *Options) {
- o.maxConcurrency = max
+ o.maxConcurrency = concurrency
}
}
diff --git a/vendor/github.com/compose-spec/compose-go/v2/loader/extends.go b/vendor/github.com/compose-spec/compose-go/v2/loader/extends.go
index 4a04654a13e5..f3c8f96faa43 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/loader/extends.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/loader/extends.go
@@ -113,11 +113,14 @@ func applyServiceExtends(ctx context.Context, name string, services map[string]a
source := deepClone(base).(map[string]any)
for _, processor := range post {
- processor.Apply(map[string]any{
+ err = processor.Apply(map[string]any{
"services": map[string]any{
name: source,
},
})
+ if err != nil {
+ return nil, err
+ }
}
merged, err := override.ExtendService(source, service)
if err != nil {
diff --git a/vendor/github.com/compose-spec/compose-go/v2/loader/interpolate.go b/vendor/github.com/compose-spec/compose-go/v2/loader/interpolate.go
index 481c66b5b110..491de5bdca2d 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/loader/interpolate.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/loader/interpolate.go
@@ -27,7 +27,6 @@ import (
)
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,
@@ -53,7 +52,6 @@ var interpolateTypeCastMapping = map[tree.Path]interp.Cast{
servicePath("privileged"): toBoolean,
servicePath("read_only"): toBoolean,
servicePath("scale"): toInt,
- servicePath("secrets", tree.PathMatchList, "mode"): toInt,
servicePath("stdin_open"): toBoolean,
servicePath("tty"): toBoolean,
servicePath("ulimits", tree.PathMatchAll): toInt,
diff --git a/vendor/github.com/compose-spec/compose-go/v2/loader/loader.go b/vendor/github.com/compose-spec/compose-go/v2/loader/loader.go
index 612b91caff45..d413cba06dcc 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/loader/loader.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/loader/loader.go
@@ -257,15 +257,6 @@ 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) {
- r := bytes.NewReader(source)
- decoder := yaml.NewDecoder(r)
- m, _, err := parseYAML(decoder)
- 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 {
@@ -275,32 +266,6 @@ type PostProcessor interface {
Apply(interface{}) error
}
-func parseYAML(decoder *yaml.Decoder) (map[string]interface{}, PostProcessor, error) {
- var cfg interface{}
- processor := ResetProcessor{target: &cfg}
-
- if err := decoder.Decode(&processor); err != nil {
- return nil, nil, err
- }
- stringMap, ok := cfg.(map[string]interface{})
- if ok {
- converted, err := convertToStringKeysRecursive(stringMap, "")
- if err != nil {
- return nil, nil, err
- }
- return converted.(map[string]interface{}), &processor, nil
- }
- cfgMap, ok := cfg.(map[interface{}]interface{})
- if !ok {
- return nil, nil, errors.New("Top-level object must be a mapping")
- }
- converted, err := convertToStringKeysRecursive(cfgMap, "")
- if err != nil {
- return nil, nil, err
- }
- return converted.(map[string]interface{}), &processor, nil
-}
-
// LoadConfigFiles ingests config files with ResourceLoader and returns config details with paths to local copies
func LoadConfigFiles(ctx context.Context, configFiles []string, workingDir string, options ...func(*Options)) (*types.ConfigDetails, error) {
if len(configFiles) < 1 {
@@ -353,12 +318,6 @@ func LoadConfigFiles(ctx context.Context, configFiles []string, workingDir strin
return config, nil
}
-// Load reads a ConfigDetails and returns a fully loaded configuration.
-// Deprecated: use LoadWithContext.
-func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.Project, error) {
- return LoadWithContext(context.Background(), configDetails, options...)
-}
-
// LoadWithContext reads a ConfigDetails and returns a fully loaded configuration as a compose-go Project
func LoadWithContext(ctx context.Context, configDetails types.ConfigDetails, options ...func(*Options)) (*types.Project, error) {
opts := toOptions(&configDetails, options)
@@ -448,7 +407,15 @@ func loadYamlModel(ctx context.Context, config types.ConfigDetails, opts *Option
return dict, nil
}
-func loadYamlFile(ctx context.Context, file types.ConfigFile, opts *Options, workingDir string, environment types.Mapping, ct *cycleTracker, dict map[string]interface{}, included []string) (map[string]interface{}, PostProcessor, error) {
+func loadYamlFile(ctx context.Context,
+ file types.ConfigFile,
+ opts *Options,
+ workingDir string,
+ environment types.Mapping,
+ ct *cycleTracker,
+ dict map[string]interface{},
+ included []string,
+) (map[string]interface{}, PostProcessor, error) {
ctx = context.WithValue(ctx, consts.ComposeFileKey{}, file.Filename)
if file.Content == nil && file.Config == nil {
content, err := os.ReadFile(file.Filename)
@@ -565,7 +532,6 @@ func load(ctx context.Context, configDetails types.ConfigDetails, opts *Options,
return nil, fmt.Errorf("include cycle detected:\n%s\n include %s", loaded[0], strings.Join(loaded[1:], "\n include "))
}
}
- loaded = append(loaded, mainFile)
dict, err := loadYamlModel(ctx, configDetails, opts, &cycleTracker{}, nil)
if err != nil {
@@ -576,7 +542,7 @@ func load(ctx context.Context, configDetails types.ConfigDetails, opts *Options,
return nil, errors.New("empty compose file")
}
- if opts.projectName == "" {
+ if !opts.SkipValidation && opts.projectName == "" {
return nil, errors.New("project name must not be empty")
}
diff --git a/vendor/github.com/compose-spec/compose-go/v2/loader/omitEmpty.go b/vendor/github.com/compose-spec/compose-go/v2/loader/omitEmpty.go
index bc1cb1a55a49..eef6be8c56ce 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/loader/omitEmpty.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/loader/omitEmpty.go
@@ -19,7 +19,8 @@ package loader
import "github.com/compose-spec/compose-go/v2/tree"
var omitempty = []tree.Path{
- "services.*.dns"}
+ "services.*.dns",
+}
// OmitEmpty removes empty attributes which are irrelevant when unset
func OmitEmpty(yaml map[string]any) map[string]any {
diff --git a/vendor/github.com/compose-spec/compose-go/v2/loader/paths.go b/vendor/github.com/compose-spec/compose-go/v2/loader/paths.go
index 102ff036e066..c03126a83a7d 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/loader/paths.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/loader/paths.go
@@ -17,9 +17,7 @@
package loader
import (
- "os"
"path/filepath"
- "strings"
"github.com/compose-spec/compose-go/v2/types"
)
@@ -40,17 +38,6 @@ func ResolveRelativePaths(project *types.Project) error {
return nil
}
-func absPath(workingDir string, filePath string) string {
- if strings.HasPrefix(filePath, "~") {
- home, _ := os.UserHomeDir()
- return filepath.Join(home, filePath[1:])
- }
- if filepath.IsAbs(filePath) {
- return filePath
- }
- return filepath.Join(workingDir, filePath)
-}
-
func absComposeFiles(composeFiles []string) ([]string, error) {
for i, composeFile := range composeFiles {
absComposefile, err := filepath.Abs(composeFile)
@@ -61,14 +48,3 @@ func absComposeFiles(composeFiles []string) ([]string, error) {
}
return composeFiles, nil
}
-
-func resolvePaths(basePath string, in types.StringList) types.StringList {
- if in == nil {
- return nil
- }
- ret := make(types.StringList, len(in))
- for i := range in {
- ret[i] = absPath(basePath, in[i])
- }
- return ret
-}
diff --git a/vendor/github.com/compose-spec/compose-go/v2/loader/validate.go b/vendor/github.com/compose-spec/compose-go/v2/loader/validate.go
index 0feb2a967f38..aa570888ccea 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/loader/validate.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/loader/validate.go
@@ -27,7 +27,7 @@ import (
)
// checkConsistency validate a compose model is consistent
-func checkConsistency(project *types.Project) error {
+func checkConsistency(project *types.Project) error { //nolint:gocyclo
for name, s := range project.Services {
if s.Build == nil && s.Image == "" {
return fmt.Errorf("service %q has neither an image nor a build context specified: %w", s.Name, errdefs.ErrInvalid)
@@ -171,7 +171,6 @@ func checkConsistency(project *types.Project) error {
return fmt.Errorf("services.%s.develop.watch: target is required for non-rebuild actions: %w", s.Name, errdefs.ErrInvalid)
}
}
-
}
}
diff --git a/vendor/github.com/compose-spec/compose-go/v2/paths/windows_path.go b/vendor/github.com/compose-spec/compose-go/v2/paths/windows_path.go
index 746aefd15e35..968d8ed7bb91 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/paths/windows_path.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/paths/windows_path.go
@@ -44,7 +44,6 @@ func isWindowsAbs(path string) (b bool) {
// volumeNameLen returns length of the leading volume name on Windows.
// It returns 0 elsewhere.
-// nolint: gocyclo
func volumeNameLen(path string) int {
if len(path) < 2 {
return 0
diff --git a/vendor/github.com/compose-spec/compose-go/v2/schema/compose-spec.json b/vendor/github.com/compose-spec/compose-go/v2/schema/compose-spec.json
index 1da7f228b0ad..4b6df9a8ac19 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/schema/compose-spec.json
+++ b/vendor/github.com/compose-spec/compose-go/v2/schema/compose-spec.json
@@ -370,9 +370,10 @@
"pre_stop": {"type": "array", "items": {"$ref": "#/definitions/service_hook"}},
"privileged": {"type": ["boolean", "string"]},
"profiles": {"$ref": "#/definitions/list_of_strings"},
- "pull_policy": {"type": "string", "enum": [
- "always", "never", "if_not_present", "build", "missing"
- ]},
+ "pull_policy": {"type": "string",
+ "pattern": "always|never|build|if_not_present|missing|refresh|daily|weekly|every_([0-9]+[wdhms])+"
+ },
+ "pull_refresh_after": {"type": "string"},
"read_only": {"type": ["boolean", "string"]},
"restart": {"type": "string"},
"runtime": {
@@ -490,7 +491,8 @@
"type": "object",
"required": ["path", "action"],
"properties": {
- "ignore": {"type": "array", "items": {"type": "string"}},
+ "ignore": {"$ref": "#/definitions/string_or_list"},
+ "include": {"$ref": "#/definitions/string_or_list"},
"path": {"type": "string"},
"action": {"type": "string", "enum": ["rebuild", "sync", "restart", "sync+restart", "sync+exec"]},
"target": {"type": "string"},
@@ -837,7 +839,8 @@
"environment": {"$ref": "#/definitions/list_or_dict"}
},
"additionalProperties": false,
- "patternProperties": {"^x-": {}}
+ "patternProperties": {"^x-": {}},
+ "required": ["command"]
},
"env_file": {
diff --git a/vendor/github.com/compose-spec/compose-go/v2/template/template.go b/vendor/github.com/compose-spec/compose-go/v2/template/template.go
index d9483cbdbcea..2d48188dd759 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/template/template.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/template/template.go
@@ -26,25 +26,28 @@ import (
"github.com/sirupsen/logrus"
)
-var delimiter = "\\$"
-var substitutionNamed = "[_a-z][_a-z0-9]*"
-var substitutionBraced = "[_a-z][_a-z0-9]*(?::?[-+?](.*))?"
-
-var groupEscaped = "escaped"
-var groupNamed = "named"
-var groupBraced = "braced"
-var groupInvalid = "invalid"
-
-var patternString = fmt.Sprintf(
- "%s(?i:(?P<%s>%s)|(?P<%s>%s)|{(?:(?P<%s>%s)}|(?P<%s>)))",
- delimiter,
- groupEscaped, delimiter,
- groupNamed, substitutionNamed,
- groupBraced, substitutionBraced,
- groupInvalid,
+const (
+ delimiter = "\\$"
+ substitutionNamed = "[_a-z][_a-z0-9]*"
+ substitutionBraced = "[_a-z][_a-z0-9]*(?::?[-+?](.*))?"
+ groupEscaped = "escaped"
+ groupNamed = "named"
+ groupBraced = "braced"
+ groupInvalid = "invalid"
)
-var DefaultPattern = regexp.MustCompile(patternString)
+var (
+ patternString = fmt.Sprintf(
+ "%s(?i:(?P<%s>%s)|(?P<%s>%s)|{(?:(?P<%s>%s)}|(?P<%s>)))",
+ delimiter,
+ groupEscaped, delimiter,
+ groupNamed, substitutionNamed,
+ groupBraced, substitutionBraced,
+ groupInvalid,
+ )
+
+ DefaultPattern = regexp.MustCompile(patternString)
+)
// InvalidTemplateError is returned when a variable template is not in a valid
// format
diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/canonical.go b/vendor/github.com/compose-spec/compose-go/v2/transform/canonical.go
index d37eb1e29246..3f65d117be0a 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/transform/canonical.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/transform/canonical.go
@@ -44,6 +44,8 @@ func init() {
transformers["services.*.build.ssh"] = transformSSH
transformers["services.*.ulimits.*"] = transformUlimits
transformers["services.*.build.ulimits.*"] = transformUlimits
+ transformers["services.*.develop.watch.*.ignore"] = transformStringOrList
+ transformers["services.*.develop.watch.*.include"] = transformStringOrList
transformers["volumes.*"] = transformMaybeExternal
transformers["networks.*"] = transformMaybeExternal
transformers["secrets.*"] = transformMaybeExternal
diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/devices.go b/vendor/github.com/compose-spec/compose-go/v2/transform/devices.go
index 3ce7fa0045be..5de0613c748b 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/transform/devices.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/transform/devices.go
@@ -28,8 +28,8 @@ func deviceRequestDefaults(data any, p tree.Path, _ bool) (any, error) {
return data, fmt.Errorf("%s: invalid type %T for device request", p, v)
}
_, hasCount := v["count"]
- _, hasIds := v["device_ids"]
- if !hasCount && !hasIds {
+ _, hasIDs := v["device_ids"]
+ if !hasCount && !hasIDs {
v["count"] = "all"
}
return v, nil
diff --git a/vendor/github.com/compose-spec/compose-go/v2/types/config.go b/vendor/github.com/compose-spec/compose-go/v2/types/config.go
index d73d2b9fa76c..4c0d00a8d434 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/types/config.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/types/config.go
@@ -24,10 +24,8 @@ import (
"github.com/go-viper/mapstructure/v2"
)
-var (
- // isCaseInsensitiveEnvVars is true on platforms where environment variable names are treated case-insensitively.
- isCaseInsensitiveEnvVars = (runtime.GOOS == "windows")
-)
+// isCaseInsensitiveEnvVars is true on platforms where environment variable names are treated case-insensitively.
+var isCaseInsensitiveEnvVars = (runtime.GOOS == "windows")
// ConfigDetails are the details about a group of ConfigFiles
type ConfigDetails struct {
diff --git a/vendor/github.com/compose-spec/compose-go/v2/types/derived.gen.go b/vendor/github.com/compose-spec/compose-go/v2/types/derived.gen.go
index 445d1cd3bccc..fd8e059e1be6 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/types/derived.gen.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/types/derived.gen.go
@@ -1605,7 +1605,7 @@ func deriveDeepCopy_31(dst, src *ServiceConfigObjConfig) {
if src.Mode == nil {
dst.Mode = nil
} else {
- dst.Mode = new(uint32)
+ dst.Mode = new(FileMode)
*dst.Mode = *src.Mode
}
if src.Extensions != nil {
@@ -1812,6 +1812,7 @@ func deriveDeepCopy_38(dst, src *DeviceRequest) {
// deriveDeepCopy_39 recursively copies the contents of src into dst.
func deriveDeepCopy_39(dst, src *ServiceNetworkConfig) {
dst.Priority = src.Priority
+ dst.GatewayPriority = src.GatewayPriority
if src.Aliases == nil {
dst.Aliases = nil
} else {
@@ -1891,7 +1892,7 @@ func deriveDeepCopy_41(dst, src *ServiceSecretConfig) {
if src.Mode == nil {
dst.Mode = nil
} else {
- dst.Mode = new(uint32)
+ dst.Mode = new(FileMode)
*dst.Mode = *src.Mode
}
if src.Extensions != nil {
@@ -2024,6 +2025,24 @@ func deriveDeepCopy_46(dst, src *Trigger) {
deriveDeepCopy_44(field, &src.Exec)
dst.Exec = *field
}()
+ if src.Include == nil {
+ dst.Include = nil
+ } else {
+ if dst.Include != nil {
+ if len(src.Include) > len(dst.Include) {
+ if cap(dst.Include) >= len(src.Include) {
+ dst.Include = (dst.Include)[:len(src.Include)]
+ } else {
+ dst.Include = make([]string, len(src.Include))
+ }
+ } else if len(src.Include) < len(dst.Include) {
+ dst.Include = (dst.Include)[:len(src.Include)]
+ }
+ } else {
+ dst.Include = make([]string, len(src.Include))
+ }
+ copy(dst.Include, src.Include)
+ }
if src.Ignore == nil {
dst.Ignore = nil
} else {
diff --git a/vendor/github.com/compose-spec/compose-go/v2/types/develop.go b/vendor/github.com/compose-spec/compose-go/v2/types/develop.go
index 8f7c8fa553bb..fa306152e091 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/types/develop.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/types/develop.go
@@ -37,6 +37,7 @@ type Trigger struct {
Action WatchAction `yaml:"action" json:"action"`
Target string `yaml:"target,omitempty" json:"target,omitempty"`
Exec ServiceHook `yaml:"exec,omitempty" json:"exec,omitempty"`
+ Include []string `yaml:"include,omitempty" json:"include,omitempty"`
Ignore []string `yaml:"ignore,omitempty" json:"ignore,omitempty"`
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
}
diff --git a/vendor/github.com/compose-spec/compose-go/v2/types/duration.go b/vendor/github.com/compose-spec/compose-go/v2/types/duration.go
index 95f562a7cf2b..c1c39730dd73 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/types/duration.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/types/duration.go
@@ -21,6 +21,8 @@ import (
"fmt"
"strings"
"time"
+
+ "github.com/xhit/go-str2duration/v2"
)
// Duration is a thin wrapper around time.Duration with improved JSON marshalling
@@ -31,7 +33,7 @@ func (d Duration) String() string {
}
func (d *Duration) DecodeMapstructure(value interface{}) error {
- v, err := time.ParseDuration(fmt.Sprint(value))
+ v, err := str2duration.ParseDuration(fmt.Sprint(value))
if err != nil {
return err
}
diff --git a/vendor/github.com/compose-spec/compose-go/v2/types/labels.go b/vendor/github.com/compose-spec/compose-go/v2/types/labels.go
index 713c28f945b2..7ea5edc41014 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/types/labels.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/types/labels.go
@@ -55,7 +55,6 @@ func (l Labels) AsList() []string {
func (l Labels) ToMappingWithEquals() MappingWithEquals {
mapping := MappingWithEquals{}
for k, v := range l {
- v := v
mapping[k] = &v
}
return mapping
diff --git a/vendor/github.com/compose-spec/compose-go/v2/types/mapping.go b/vendor/github.com/compose-spec/compose-go/v2/types/mapping.go
index 63f6e58b8967..1fc2aea19f37 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/types/mapping.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/types/mapping.go
@@ -20,6 +20,7 @@ import (
"fmt"
"sort"
"strings"
+ "unicode"
)
// MappingWithEquals is a mapping type that can be converted from a list of
@@ -94,6 +95,9 @@ func (m *MappingWithEquals) DecodeMapstructure(value interface{}) error {
mapping := make(MappingWithEquals, len(v))
for _, s := range v {
k, e, ok := strings.Cut(fmt.Sprint(s), "=")
+ if unicode.IsSpace(rune(k[len(k)-1])) {
+ return fmt.Errorf("environment variable %s is declared with a trailing space", k)
+ }
if !ok {
mapping[k] = nil
} else {
@@ -157,7 +161,6 @@ func (m Mapping) Values() []string {
func (m Mapping) ToMappingWithEquals() MappingWithEquals {
mapping := MappingWithEquals{}
for k, v := range m {
- v := v
mapping[k] = &v
}
return mapping
diff --git a/vendor/github.com/compose-spec/compose-go/v2/types/project.go b/vendor/github.com/compose-spec/compose-go/v2/types/project.go
index d8005c0ad786..e28554ab4204 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/types/project.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/types/project.go
@@ -380,12 +380,7 @@ func (p *Project) WithServicesEnabled(names ...string) (*Project, error) {
service := p.DisabledServices[name]
profiles = append(profiles, service.Profiles...)
}
- newProject, err := newProject.WithProfiles(profiles)
- if err != nil {
- return newProject, err
- }
-
- return newProject.WithServicesEnvironmentResolved(true)
+ return newProject.WithProfiles(profiles)
}
// WithoutUnnecessaryResources drops networks/volumes/secrets/configs that are not referenced by active services
@@ -477,7 +472,7 @@ func (p *Project) WithSelectedServices(names []string, options ...DependencyOpti
}
set := utils.NewSet[string]()
- err := p.ForEachService(names, func(name string, service *ServiceConfig) error {
+ err := p.ForEachService(names, func(name string, _ *ServiceConfig) error {
set.Add(name)
return nil
}, options...)
@@ -535,7 +530,7 @@ func (p *Project) WithServicesDisabled(names ...string) *Project {
// WithImagesResolved updates services images to include digest computed by a resolver function
// It returns a new Project instance with the changes and keep the original Project unchanged
func (p *Project) WithImagesResolved(resolver func(named reference.Named) (godigest.Digest, error)) (*Project, error) {
- return p.WithServicesTransform(func(name string, service ServiceConfig) (ServiceConfig, error) {
+ return p.WithServicesTransform(func(_ string, service ServiceConfig) (ServiceConfig, error) {
if service.Image == "" {
return service, nil
}
@@ -725,14 +720,9 @@ func loadMappingFile(path string, format string, resolve dotenv.LookupFn) (Mappi
if err != nil {
return nil, err
}
- defer file.Close() //nolint:errcheck
+ defer file.Close()
- var fileVars map[string]string
- if format != "" {
- fileVars, err = dotenv.ParseWithFormat(file, path, resolve, format)
- } else {
- fileVars, err = dotenv.ParseWithLookup(file, resolve)
- }
+ fileVars, err := dotenv.ParseWithFormat(file, path, resolve, format)
if err != nil {
return nil, err
}
@@ -746,7 +736,6 @@ func (p *Project) deepCopy() *Project {
n := &Project{}
deriveDeepCopyProject(n, p)
return n
-
}
// WithServicesTransform applies a transformation to project services and return a new project with transformation results
diff --git a/vendor/github.com/compose-spec/compose-go/v2/types/types.go b/vendor/github.com/compose-spec/compose-go/v2/types/types.go
index 4c7baa46ed33..fe95444d7e5e 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/types/types.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/types/types.go
@@ -20,9 +20,12 @@ import (
"encoding/json"
"fmt"
"sort"
+ "strconv"
"strings"
+ "time"
"github.com/docker/go-connections/nat"
+ "github.com/xhit/go-str2duration/v2"
)
// ServiceConfig is the configuration of one service
@@ -215,6 +218,8 @@ const (
PullPolicyMissing = "missing"
// PullPolicyBuild force building images
PullPolicyBuild = "build"
+ // PullPolicyRefresh checks if image needs to be updated
+ PullPolicyRefresh = "refresh"
)
const (
@@ -268,6 +273,27 @@ func (s ServiceConfig) GetDependents(p *Project) []string {
return dependent
}
+func (s ServiceConfig) GetPullPolicy() (string, time.Duration, error) {
+ switch s.PullPolicy {
+ case PullPolicyAlways, PullPolicyNever, PullPolicyIfNotPresent, PullPolicyMissing, PullPolicyBuild:
+ return s.PullPolicy, 0, nil
+ case "daily":
+ return PullPolicyRefresh, 24 * time.Hour, nil
+ case "weekly":
+ return PullPolicyRefresh, 7 * 24 * time.Hour, nil
+ default:
+ if strings.HasPrefix(s.PullPolicy, "every_") {
+ delay := s.PullPolicy[6:]
+ duration, err := str2duration.ParseDuration(delay)
+ if err != nil {
+ return "", 0, err
+ }
+ return PullPolicyRefresh, duration, nil
+ }
+ return PullPolicyMissing, 0, nil
+ }
+}
+
// BuildConfig is a type for build
type BuildConfig struct {
Context string `yaml:"context,omitempty" json:"context,omitempty"`
@@ -479,16 +505,13 @@ func ParsePortConfig(value string) ([]ServicePortConfig, error) {
for _, key := range keys {
port := nat.Port(key)
- converted, err := convertPortToPortConfig(port, portBindings)
- if err != nil {
- return nil, err
- }
+ converted := convertPortToPortConfig(port, portBindings)
portConfigs = append(portConfigs, converted...)
}
return portConfigs, nil
}
-func convertPortToPortConfig(port nat.Port, portBindings map[nat.Port][]nat.PortBinding) ([]ServicePortConfig, error) {
+func convertPortToPortConfig(port nat.Port, portBindings map[nat.Port][]nat.PortBinding) []ServicePortConfig {
var portConfigs []ServicePortConfig
for _, binding := range portBindings[port] {
portConfigs = append(portConfigs, ServicePortConfig{
@@ -499,7 +522,7 @@ func convertPortToPortConfig(port nat.Port, portBindings map[nat.Port][]nat.Port
Mode: "ingress",
})
}
- return portConfigs, nil
+ return portConfigs
}
// ServiceVolumeConfig are references to a volume used by a service
@@ -604,17 +627,51 @@ type ServiceVolumeTmpfs struct {
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
}
+type FileMode int64
+
// FileReferenceConfig for a reference to a swarm file object
type FileReferenceConfig struct {
- Source string `yaml:"source,omitempty" json:"source,omitempty"`
- Target string `yaml:"target,omitempty" json:"target,omitempty"`
- UID string `yaml:"uid,omitempty" json:"uid,omitempty"`
- GID string `yaml:"gid,omitempty" json:"gid,omitempty"`
- Mode *uint32 `yaml:"mode,omitempty" json:"mode,omitempty"`
+ Source string `yaml:"source,omitempty" json:"source,omitempty"`
+ Target string `yaml:"target,omitempty" json:"target,omitempty"`
+ UID string `yaml:"uid,omitempty" json:"uid,omitempty"`
+ GID string `yaml:"gid,omitempty" json:"gid,omitempty"`
+ Mode *FileMode `yaml:"mode,omitempty" json:"mode,omitempty"`
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
}
+func (f *FileMode) DecodeMapstructure(value interface{}) error {
+ switch v := value.(type) {
+ case *FileMode:
+ return nil
+ case string:
+ i, err := strconv.ParseInt(v, 8, 64)
+ if err != nil {
+ return err
+ }
+ *f = FileMode(i)
+ case int:
+ *f = FileMode(v)
+ default:
+ return fmt.Errorf("unexpected value type %T for mode", value)
+ }
+ return nil
+}
+
+// MarshalYAML makes FileMode implement yaml.Marshaller
+func (f *FileMode) MarshalYAML() (interface{}, error) {
+ return f.String(), nil
+}
+
+// MarshalJSON makes FileMode implement json.Marshaller
+func (f *FileMode) MarshalJSON() ([]byte, error) {
+ return []byte("\"" + f.String() + "\""), nil
+}
+
+func (f *FileMode) String() string {
+ return fmt.Sprintf("0%o", int64(*f))
+}
+
// ServiceConfigObjConfig is the config obj configuration for a service
type ServiceConfigObjConfig FileReferenceConfig
diff --git a/vendor/github.com/compose-spec/compose-go/v2/utils/pathutils.go b/vendor/github.com/compose-spec/compose-go/v2/utils/pathutils.go
index fd2a635ec8cb..211e2999356e 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/utils/pathutils.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/utils/pathutils.go
@@ -41,7 +41,6 @@ func ResolveSymbolicLink(path string) (string, error) {
return path, nil
}
return strings.Replace(path, part, sym, 1), nil
-
}
// getSymbolinkLink parses all parts of the path and returns the
diff --git a/vendor/github.com/compose-spec/compose-go/v2/validation/validation.go b/vendor/github.com/compose-spec/compose-go/v2/validation/validation.go
index 707f247e36a3..793c1930147f 100644
--- a/vendor/github.com/compose-spec/compose-go/v2/validation/validation.go
+++ b/vendor/github.com/compose-spec/compose-go/v2/validation/validation.go
@@ -65,7 +65,6 @@ func check(value any, p tree.Path) error {
func checkFileObject(keys ...string) checkerFunc {
return func(value any, p tree.Path) error {
-
v := value.(map[string]any)
count := 0
for _, s := range keys {
@@ -100,8 +99,8 @@ func checkPath(value any, p tree.Path) error {
func checkDeviceRequest(value any, p tree.Path) error {
v := value.(map[string]any)
_, hasCount := v["count"]
- _, hasIds := v["device_ids"]
- if hasCount && hasIds {
+ _, hasIDs := v["device_ids"]
+ if hasCount && hasIDs {
return fmt.Errorf(`%s: "count" and "device_ids" attributes are exclusive`, p)
}
return nil
diff --git a/vendor/github.com/xhit/go-str2duration/v2/LICENSE b/vendor/github.com/xhit/go-str2duration/v2/LICENSE
new file mode 100644
index 000000000000..ea5ea8986925
--- /dev/null
+++ b/vendor/github.com/xhit/go-str2duration/v2/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/vendor/github.com/xhit/go-str2duration/v2/README.md b/vendor/github.com/xhit/go-str2duration/v2/README.md
new file mode 100644
index 000000000000..03263737a284
--- /dev/null
+++ b/vendor/github.com/xhit/go-str2duration/v2/README.md
@@ -0,0 +1,88 @@
+# Go String To Duration (go-str2duration)
+
+This package allows to get a time.Duration from a string. The string can be a string retorned for time.Duration or a similar string with weeks or days too!.
+
+
+
+
+## Download
+
+```bash
+go get github.com/xhit/go-str2duration/v2
+```
+
+## Features
+
+Go String To Duration supports this strings conversions to duration:
+- All strings returned in time.Duration String.
+- A string more readable like 1w2d6h3ns (1 week 2 days 6 hours and 3 nanoseconds).
+- `µs` and `us` are microsecond.
+
+It's the same `time.ParseDuration` standard function in Go, but with days and week support.
+
+**Note**: a day is 24 hour.
+
+If you don't need days and weeks, use [`time.ParseDuration`](https://golang.org/pkg/time/#ParseDuration).
+
+## Usage
+
+```go
+package main
+
+import (
+ "fmt"
+ str2duration "github.com/xhit/go-str2duration/v2"
+ "os"
+ "time"
+)
+
+func main() {
+
+ for i, tt := range []struct {
+ dur string
+ expected time.Duration
+ }{
+ //This times are returned with time.Duration string
+ {"1h", time.Duration(time.Hour)},
+ {"1m", time.Duration(time.Minute)},
+ {"1s", time.Duration(time.Second)},
+ {"1ms", time.Duration(time.Millisecond)},
+ {"1µs", time.Duration(time.Microsecond)},
+ {"1us", time.Duration(time.Microsecond)},
+ {"1ns", time.Duration(time.Nanosecond)},
+ {"4.000000001s", time.Duration(4*time.Second + time.Nanosecond)},
+ {"1h0m4.000000001s", time.Duration(time.Hour + 4*time.Second + time.Nanosecond)},
+ {"1h1m0.01s", time.Duration(61*time.Minute + 10*time.Millisecond)},
+ {"1h1m0.123456789s", time.Duration(61*time.Minute + 123456789*time.Nanosecond)},
+ {"1.00002ms", time.Duration(time.Millisecond + 20*time.Nanosecond)},
+ {"1.00000002s", time.Duration(time.Second + 20*time.Nanosecond)},
+ {"693ns", time.Duration(693 * time.Nanosecond)},
+
+ //This times aren't returned with time.Duration string, but are easily readable and can be parsed too!
+ {"1ms1ns", time.Duration(time.Millisecond + 1*time.Nanosecond)},
+ {"1s20ns", time.Duration(time.Second + 20*time.Nanosecond)},
+ {"60h8ms", time.Duration(60*time.Hour + 8*time.Millisecond)},
+ {"96h63s", time.Duration(96*time.Hour + 63*time.Second)},
+
+ //And works with days and weeks!
+ {"2d3s96ns", time.Duration(48*time.Hour + 3*time.Second + 96*time.Nanosecond)},
+ {"1w2d3s96ns", time.Duration(168*time.Hour + 48*time.Hour + 3*time.Second + 96*time.Nanosecond)},
+
+ {"10s1us693ns", time.Duration(10*time.Second + time.Microsecond + 693*time.Nanosecond)},
+
+ } {
+ durationFromString, err := str2duration.ParseDuration(tt.dur)
+ if err != nil {
+ panic(err)
+
+ //Check if expected time is the time returned by the parser
+ } else if tt.expected != durationFromString {
+ fmt.Println(fmt.Sprintf("index %d -> in: %s returned: %s\tnot equal to %s", i, tt.dur, durationFromString.String(), tt.expected.String()))
+ }else{
+ fmt.Println(fmt.Sprintf("index %d -> in: %s parsed succesfully", i, tt.dur))
+ }
+ }
+}
+```
+
+Also, you can convert to string the duration using `String(t time.Duration)` function. This support weeks and days and not return the ugly decimals from golang standard `t.String()` function. Units with 0 values aren't returned. For example: `1d1ms` means 1 day 1 millisecond.
\ No newline at end of file
diff --git a/vendor/github.com/xhit/go-str2duration/v2/str2duration.go b/vendor/github.com/xhit/go-str2duration/v2/str2duration.go
new file mode 100644
index 000000000000..51631db5e796
--- /dev/null
+++ b/vendor/github.com/xhit/go-str2duration/v2/str2duration.go
@@ -0,0 +1,331 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in https://raw.githubusercontent.com/golang/go/master/LICENSE
+
+package str2duration
+
+import (
+ "errors"
+ "time"
+)
+
+var unitMap = map[string]int64{
+ "ns": int64(time.Nanosecond),
+ "us": int64(time.Microsecond),
+ "µs": int64(time.Microsecond), // U+00B5 = micro symbol
+ "μs": int64(time.Microsecond), // U+03BC = Greek letter mu
+ "ms": int64(time.Millisecond),
+ "s": int64(time.Second),
+ "m": int64(time.Minute),
+ "h": int64(time.Hour),
+ "d": int64(time.Hour) * 24,
+ "w": int64(time.Hour) * 168,
+}
+
+// ParseDuration parses a duration string.
+// A duration string is a possibly signed sequence of
+// decimal numbers, each with optional fraction and a unit suffix,
+// such as "300ms", "-1.5h" or "2h45m".
+// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h", "d", "w".
+func ParseDuration(s string) (time.Duration, error) {
+ // [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
+ orig := s
+ var d int64
+ neg := false
+
+ // Consume [-+]?
+ if s != "" {
+ c := s[0]
+ if c == '-' || c == '+' {
+ neg = c == '-'
+ s = s[1:]
+ }
+ }
+ // Special case: if all that is left is "0", this is zero.
+ if s == "0" {
+ return 0, nil
+ }
+ if s == "" {
+ return 0, errors.New("time: invalid duration " + quote(orig))
+ }
+ for s != "" {
+ var (
+ v, f int64 // integers before, after decimal point
+ scale float64 = 1 // value = v + f/scale
+ )
+
+ var err error
+
+ // The next character must be [0-9.]
+ if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') {
+ return 0, errors.New("time: invalid duration " + quote(orig))
+ }
+ // Consume [0-9]*
+ pl := len(s)
+ v, s, err = leadingInt(s)
+ if err != nil {
+ return 0, errors.New("time: invalid duration " + quote(orig))
+ }
+ pre := pl != len(s) // whether we consumed anything before a period
+
+ // Consume (\.[0-9]*)?
+ post := false
+ if s != "" && s[0] == '.' {
+ s = s[1:]
+ pl := len(s)
+ f, scale, s = leadingFraction(s)
+ post = pl != len(s)
+ }
+ if !pre && !post {
+ // no digits (e.g. ".s" or "-.s")
+ return 0, errors.New("time: invalid duration " + quote(orig))
+ }
+
+ // Consume unit.
+ i := 0
+ for ; i < len(s); i++ {
+ c := s[i]
+ if c == '.' || '0' <= c && c <= '9' {
+ break
+ }
+ }
+ if i == 0 {
+ return 0, errors.New("time: missing unit in duration " + quote(orig))
+ }
+ u := s[:i]
+ s = s[i:]
+ unit, ok := unitMap[u]
+ if !ok {
+ return 0, errors.New("time: unknown unit " + quote(u) + " in duration " + quote(orig))
+ }
+ if v > (1<<63-1)/unit {
+ // overflow
+ return 0, errors.New("time: invalid duration " + quote(orig))
+ }
+ v *= unit
+ if f > 0 {
+ // float64 is needed to be nanosecond accurate for fractions of hours.
+ // v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit)
+ v += int64(float64(f) * (float64(unit) / scale))
+ if v < 0 {
+ // overflow
+ return 0, errors.New("time: invalid duration " + quote(orig))
+ }
+ }
+ d += v
+ if d < 0 {
+ // overflow
+ return 0, errors.New("time: invalid duration " + quote(orig))
+ }
+ }
+
+ if neg {
+ d = -d
+ }
+ return time.Duration(d), nil
+}
+
+func quote(s string) string {
+ return "\"" + s + "\""
+}
+
+var errLeadingInt = errors.New("time: bad [0-9]*") // never printed
+
+// leadingInt consumes the leading [0-9]* from s.
+func leadingInt(s string) (x int64, rem string, err error) {
+ i := 0
+ for ; i < len(s); i++ {
+ c := s[i]
+ if c < '0' || c > '9' {
+ break
+ }
+ if x > (1<<63-1)/10 {
+ // overflow
+ return 0, "", errLeadingInt
+ }
+ x = x*10 + int64(c) - '0'
+ if x < 0 {
+ // overflow
+ return 0, "", errLeadingInt
+ }
+ }
+ return x, s[i:], nil
+}
+
+// leadingFraction consumes the leading [0-9]* from s.
+// It is used only for fractions, so does not return an error on overflow,
+// it just stops accumulating precision.
+func leadingFraction(s string) (x int64, scale float64, rem string) {
+ i := 0
+ scale = 1
+ overflow := false
+ for ; i < len(s); i++ {
+ c := s[i]
+ if c < '0' || c > '9' {
+ break
+ }
+ if overflow {
+ continue
+ }
+ if x > (1<<63-1)/10 {
+ // It's possible for overflow to give a positive number, so take care.
+ overflow = true
+ continue
+ }
+ y := x*10 + int64(c) - '0'
+ if y < 0 {
+ overflow = true
+ continue
+ }
+ x = y
+ scale *= 10
+ }
+ return x, scale, s[i:]
+}
+
+// String returns a string representing the duration in the form "1w4d2h3m5s".
+// Units with 0 values aren't returned, for example: 1d1ms is 1 day 1 milliseconds
+func String(d time.Duration) string {
+ if d == 0 {
+ return "0s"
+ }
+
+ // Largest time is 15250w1d23h47m16s854ms775us807ns
+ var buf [32]byte
+ w := len(buf)
+ var sign string
+
+ u := uint64(d)
+ neg := d < 0
+ if neg {
+ u = -u
+ sign = "-"
+ }
+
+ // u is nanoseconds (ns)
+ if u > 0 {
+ w--
+
+ if u%1000 > 0 {
+ buf[w] = 's'
+ w--
+ buf[w] = 'n'
+ w = fmtInt(buf[:w], u%1000)
+ } else {
+ w++
+ }
+
+ u /= 1000
+
+ // u is now integer microseconds (us)
+ if u > 0 {
+ w--
+ if u%1000 > 0 {
+ buf[w] = 's'
+ w--
+ buf[w] = 'u'
+ w = fmtInt(buf[:w], u%1000)
+ } else {
+ w++
+ }
+ u /= 1000
+
+ // u is now integer milliseconds (ms)
+ if u > 0 {
+ w--
+ if u%1000 > 0 {
+ buf[w] = 's'
+ w--
+ buf[w] = 'm'
+ w = fmtInt(buf[:w], u%1000)
+ } else {
+ w++
+ }
+ u /= 1000
+
+ // u is now integer seconds (s)
+ if u > 0 {
+ w--
+ if u%60 > 0 {
+ buf[w] = 's'
+ w = fmtInt(buf[:w], u%60)
+ } else {
+ w++
+ }
+ u /= 60
+
+ // u is now integer minutes (m)
+ if u > 0 {
+ w--
+
+ if u%60 > 0 {
+ buf[w] = 'm'
+ w = fmtInt(buf[:w], u%60)
+ } else {
+ w++
+ }
+
+ u /= 60
+
+ // u is now integer hours (h)
+ if u > 0 {
+ w--
+
+ if u%24 > 0 {
+ buf[w] = 'h'
+ w = fmtInt(buf[:w], u%24)
+ } else {
+ w++
+ }
+
+ u /= 24
+
+ // u is now integer days (d)
+ if u > 0 {
+ w--
+
+ if u%7 > 0 {
+ buf[w] = 'd'
+ w = fmtInt(buf[:w], u%7)
+ } else {
+ w++
+ }
+
+ u /= 7
+
+ // u is now integer weeks (w)
+ if u > 0 {
+ w--
+ buf[w] = 'w'
+ w = fmtInt(buf[:w], u)
+ }
+
+ }
+
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+ return sign + string(buf[w:])
+}
+
+// fmtInt formats v into the tail of buf.
+// It returns the index where the output begins.
+func fmtInt(buf []byte, v uint64) int {
+ w := len(buf)
+ if v == 0 {
+ w--
+ buf[w] = '0'
+ } else {
+ for v > 0 {
+ w--
+ buf[w] = byte(v%10) + '0'
+ v /= 10
+ }
+ }
+ return w
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 2d453226a84e..3f242f52b470 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -130,8 +130,8 @@ github.com/cenkalti/backoff/v4
# github.com/cespare/xxhash/v2 v2.3.0
## explicit; go 1.11
github.com/cespare/xxhash/v2
-# github.com/compose-spec/compose-go/v2 v2.4.8
-## explicit; go 1.21
+# github.com/compose-spec/compose-go/v2 v2.4.9
+## explicit; go 1.23
github.com/compose-spec/compose-go/v2/cli
github.com/compose-spec/compose-go/v2/consts
github.com/compose-spec/compose-go/v2/dotenv
@@ -761,6 +761,9 @@ github.com/xeipuuv/gojsonreference
# github.com/xeipuuv/gojsonschema v1.2.0
## explicit
github.com/xeipuuv/gojsonschema
+# github.com/xhit/go-str2duration/v2 v2.1.0
+## explicit; go 1.13
+github.com/xhit/go-str2duration/v2
# github.com/zclconf/go-cty v1.16.0
## explicit; go 1.18
github.com/zclconf/go-cty/cty
From 212d598ab1601f84bcd94929cc7a2e302bf3534c Mon Sep 17 00:00:00 2001
From: CrazyMax <1951866+crazy-max@users.noreply.github.com>
Date: Wed, 19 Mar 2025 11:52:08 +0100
Subject: [PATCH 2/2] fix go.mod and lint issues
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
---
bake/bake.go | 5 ++---
bake/compose.go | 5 ++---
build/build.go | 5 ++---
build/git.go | 5 ++---
build/provenance.go | 5 ++---
commands/ls.go | 5 ++---
controller/pb/cache.go | 10 ++++++----
controller/pb/export.go | 5 ++---
go.mod | 4 +---
store/nodegroup.go | 9 +++------
util/imagetools/create.go | 4 +---
util/imagetools/loader.go | 11 ++++-------
12 files changed, 29 insertions(+), 44 deletions(-)
diff --git a/bake/bake.go b/bake/bake.go
index 5cb6c7564dfc..df9c559e6ffd 100644
--- a/bake/bake.go
+++ b/bake/bake.go
@@ -4,6 +4,7 @@ import (
"context"
"encoding"
"io"
+ "maps"
"os"
"path"
"path/filepath"
@@ -1104,9 +1105,7 @@ func (t *Target) GetEvalContexts(ectx *hcl.EvalContext, block *hcl.Block, loadDe
e2 := ectx.NewChild()
e2.Variables = make(map[string]cty.Value)
if e != ectx {
- for k, v := range e.Variables {
- e2.Variables[k] = v
- }
+ maps.Copy(e2.Variables, e.Variables)
}
e2.Variables[k] = v
ectxs2 = append(ectxs2, e2)
diff --git a/bake/compose.go b/bake/compose.go
index 84a6e989f3a8..ccf8098cdc8e 100644
--- a/bake/compose.go
+++ b/bake/compose.go
@@ -3,6 +3,7 @@ package bake
import (
"context"
"fmt"
+ "maps"
"os"
"path/filepath"
"slices"
@@ -91,9 +92,7 @@ func ParseCompose(cfgs []composetypes.ConfigFile, envs map[string]string) (*Conf
var additionalContexts map[string]string
if s.Build.AdditionalContexts != nil {
additionalContexts = map[string]string{}
- for k, v := range s.Build.AdditionalContexts {
- additionalContexts[k] = v
- }
+ maps.Copy(additionalContexts, s.Build.AdditionalContexts)
}
var shmSize *string
diff --git a/build/build.go b/build/build.go
index 96f850694821..95d658b3c6fb 100644
--- a/build/build.go
+++ b/build/build.go
@@ -8,6 +8,7 @@ import (
"encoding/json"
"fmt"
"io"
+ "maps"
"os"
"slices"
"strconv"
@@ -431,9 +432,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
FrontendInputs: frontendInputs,
FrontendOpt: make(map[string]string),
}
- for k, v := range so.FrontendAttrs {
- req.FrontendOpt[k] = v
- }
+ maps.Copy(req.FrontendOpt, so.FrontendAttrs)
so.Frontend = ""
so.FrontendInputs = nil
diff --git a/build/git.go b/build/git.go
index 0b7aba10407d..4672860ce133 100644
--- a/build/git.go
+++ b/build/git.go
@@ -2,6 +2,7 @@ package build
import (
"context"
+ "maps"
"os"
"path"
"path/filepath"
@@ -127,9 +128,7 @@ func getGitAttributes(ctx context.Context, contextPath, dockerfilePath string) (
if so.FrontendAttrs == nil {
so.FrontendAttrs = make(map[string]string)
}
- for k, v := range res {
- so.FrontendAttrs[k] = v
- }
+ maps.Copy(so.FrontendAttrs, res)
if !setGitInfo || root == "" {
return
diff --git a/build/provenance.go b/build/provenance.go
index 5fd8d08c3704..b6cca56f495c 100644
--- a/build/provenance.go
+++ b/build/provenance.go
@@ -5,6 +5,7 @@ import (
"encoding/base64"
"encoding/json"
"io"
+ "maps"
"strings"
"sync"
@@ -40,9 +41,7 @@ func setRecordProvenance(ctx context.Context, c *client.Client, sr *client.Solve
if err != nil {
return err
}
- for k, v := range res {
- sr.ExporterResponse[k] = v
- }
+ maps.Copy(sr.ExporterResponse, res)
return nil
})
}
diff --git a/commands/ls.go b/commands/ls.go
index ffd4c10d42a9..8c247679d05d 100644
--- a/commands/ls.go
+++ b/commands/ls.go
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
+ "maps"
"sort"
"strings"
"time"
@@ -409,9 +410,7 @@ func truncPlatforms(pfs []string, max int) truncatedPlatforms {
left[ppf] = append(left[ppf], pf)
}
}
- for k, v := range left {
- res[k] = v
- }
+ maps.Copy(res, left)
return truncatedPlatforms{
res: res,
input: pfs,
diff --git a/controller/pb/cache.go b/controller/pb/cache.go
index 4b7c2b7546e8..f363a41902a4 100644
--- a/controller/pb/cache.go
+++ b/controller/pb/cache.go
@@ -1,6 +1,10 @@
package pb
-import "github.com/moby/buildkit/client"
+import (
+ "maps"
+
+ "github.com/moby/buildkit/client"
+)
func CreateCaches(entries []*CacheOptionsEntry) []client.CacheOptionsEntry {
var outs []client.CacheOptionsEntry
@@ -12,9 +16,7 @@ func CreateCaches(entries []*CacheOptionsEntry) []client.CacheOptionsEntry {
Type: entry.Type,
Attrs: map[string]string{},
}
- for k, v := range entry.Attrs {
- out.Attrs[k] = v
- }
+ maps.Copy(out.Attrs, entry.Attrs)
outs = append(outs, out)
}
return outs
diff --git a/controller/pb/export.go b/controller/pb/export.go
index 8df341b30346..c7eef8c9fa9e 100644
--- a/controller/pb/export.go
+++ b/controller/pb/export.go
@@ -2,6 +2,7 @@ package pb
import (
"io"
+ "maps"
"os"
"strconv"
@@ -26,9 +27,7 @@ func CreateExports(entries []*ExportEntry) ([]client.ExportEntry, []string, erro
Type: entry.Type,
Attrs: map[string]string{},
}
- for k, v := range entry.Attrs {
- out.Attrs[k] = v
- }
+ maps.Copy(out.Attrs, entry.Attrs)
supportFile := false
supportDir := false
diff --git a/go.mod b/go.mod
index aa40920aa6c9..3eb47868276a 100644
--- a/go.mod
+++ b/go.mod
@@ -1,8 +1,6 @@
module github.com/docker/buildx
-go 1.23
-
-toolchain go1.23.7
+go 1.23.0
require (
github.com/Masterminds/semver/v3 v3.2.1
diff --git a/store/nodegroup.go b/store/nodegroup.go
index 06ca070ee9ae..9d986d17b78a 100644
--- a/store/nodegroup.go
+++ b/store/nodegroup.go
@@ -2,6 +2,7 @@ package store
import (
"fmt"
+ "maps"
"slices"
"time"
@@ -93,9 +94,7 @@ func (ng *NodeGroup) Update(name, endpoint string, platforms []string, endpoints
needsRestart = true
}
if buildkitdConfigFile != "" {
- for k, v := range files {
- n.Files[k] = v
- }
+ maps.Copy(n.Files, files)
needsRestart = true
}
if needsRestart {
@@ -147,9 +146,7 @@ func (n *Node) Copy() *Node {
buildkitdFlags := []string{}
copy(buildkitdFlags, n.BuildkitdFlags)
driverOpts := map[string]string{}
- for k, v := range n.DriverOpts {
- driverOpts[k] = v
- }
+ maps.Copy(driverOpts, n.DriverOpts)
files := map[string][]byte{}
for k, v := range n.Files {
vv := []byte{}
diff --git a/util/imagetools/create.go b/util/imagetools/create.go
index 6fdcac96d125..9b1ca6b48f53 100644
--- a/util/imagetools/create.go
+++ b/util/imagetools/create.go
@@ -107,9 +107,7 @@ func (r *Resolver) Combine(ctx context.Context, srcs []*Source, ann map[exptypes
if old.Annotations == nil {
old.Annotations = map[string]string{}
}
- for k, v := range d.Annotations {
- old.Annotations[k] = v
- }
+ maps.Copy(old.Annotations, d.Annotations)
newDescs[idx] = old
} else {
m[d.Digest] = len(newDescs)
diff --git a/util/imagetools/loader.go b/util/imagetools/loader.go
index 457e684e5743..eaba4d11e0f2 100644
--- a/util/imagetools/loader.go
+++ b/util/imagetools/loader.go
@@ -6,6 +6,7 @@ import (
"context"
"encoding/base64"
"encoding/json"
+ "maps"
"regexp"
"sort"
"strings"
@@ -126,13 +127,9 @@ func (l *loader) Load(ctx context.Context, ref string) (*result, error) {
}
var a asset
- annotations := make(map[string]string, len(mfst.manifest.Annotations)+len(mfst.desc.Annotations))
- for k, v := range mfst.desc.Annotations {
- annotations[k] = v
- }
- for k, v := range mfst.manifest.Annotations {
- annotations[k] = v
- }
+ annotations := map[string]string{}
+ maps.Copy(annotations, mfst.desc.Annotations)
+ maps.Copy(annotations, mfst.manifest.Annotations)
if err := l.scanConfig(ctx, fetcher, mfst.manifest.Config, &a); err != nil {
return nil, err