Merge pull request #3898 from crazy-max/compose-go-2.11.0
vendor: update compose-go to v2.11.0
This commit is contained in:
+12
-6
@@ -84,6 +84,9 @@ type Options struct {
|
||||
KnownExtensions map[string]any
|
||||
// Metada for telemetry
|
||||
Listeners []Listener
|
||||
// MaxNodeVisits caps total YAML node visits during reset/override resolution.
|
||||
// Zero means use the default. Useful for very large compose files that exceed the default cap.
|
||||
MaxNodeVisits int
|
||||
}
|
||||
|
||||
var versionWarning []string
|
||||
@@ -446,8 +449,10 @@ func loadYamlFile(ctx context.Context,
|
||||
|
||||
fixEmptyNotNull(cfg)
|
||||
|
||||
if !opts.SkipExtends {
|
||||
err = ApplyExtends(ctx, cfg, opts, ct, processor)
|
||||
// Process includes first so that extended services have all merged attributes
|
||||
if !opts.SkipInclude {
|
||||
included = append(included, file.Filename)
|
||||
err = ApplyInclude(ctx, workingDir, environment, cfg, opts, included, processor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -457,12 +462,13 @@ func loadYamlFile(ctx context.Context,
|
||||
return err
|
||||
}
|
||||
|
||||
if !opts.SkipInclude {
|
||||
included = append(included, file.Filename)
|
||||
err = ApplyInclude(ctx, workingDir, environment, cfg, opts, included, processor)
|
||||
// Process extends after includes so base services are fully merged
|
||||
if !opts.SkipExtends {
|
||||
err = ApplyExtends(ctx, cfg, opts, ct, processor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dict, err = override.Merge(dict, cfg)
|
||||
@@ -503,7 +509,7 @@ func loadYamlFile(ctx context.Context,
|
||||
decoder := yaml.NewDecoder(r)
|
||||
for {
|
||||
var raw interface{}
|
||||
reset := &ResetProcessor{target: &raw}
|
||||
reset := &ResetProcessor{target: &raw, maxNodeVisits: opts.MaxNodeVisits}
|
||||
err := decoder.Decode(reset)
|
||||
if err != nil && errors.Is(err, io.EOF) {
|
||||
break
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ func omitEmpty(data any, p tree.Path) any {
|
||||
}
|
||||
return v
|
||||
case []any:
|
||||
var c []any
|
||||
c := make([]any, 0, len(v))
|
||||
for _, e := range v {
|
||||
if isEmpty(e) && mustOmit(p) {
|
||||
continue
|
||||
|
||||
+142
-20
@@ -25,17 +25,38 @@ import (
|
||||
"go.yaml.in/yaml/v4"
|
||||
)
|
||||
|
||||
// defaultMaxNodeVisits caps total resolveReset calls per document.
|
||||
// Sized to accommodate large real-world compose files while rejecting documents that would
|
||||
// cause unbounded traversal. Callers can override this via Options.MaxNodeVisits.
|
||||
const defaultMaxNodeVisits = 100_000
|
||||
|
||||
// nodeCache stores a resolved node and the relative sub-paths within its subtree that
|
||||
// carried !reset/!override tags, so cache hits at different call sites can replay them.
|
||||
type nodeCache struct {
|
||||
node *yaml.Node
|
||||
relativePaths []tree.Path
|
||||
}
|
||||
|
||||
type ResetProcessor struct {
|
||||
target interface{}
|
||||
paths []tree.Path
|
||||
visitedNodes map[*yaml.Node][]string
|
||||
target any
|
||||
paths []tree.Path
|
||||
visitedNodes map[*yaml.Node][]string
|
||||
resolvedNodes map[*yaml.Node]nodeCache
|
||||
visitCount int
|
||||
// maxNodeVisits is the per-document cap; when zero, defaultMaxNodeVisits is used.
|
||||
maxNodeVisits int
|
||||
}
|
||||
|
||||
// UnmarshalYAML implement yaml.Unmarshaler
|
||||
func (p *ResetProcessor) UnmarshalYAML(value *yaml.Node) error {
|
||||
p.visitedNodes = make(map[*yaml.Node][]string)
|
||||
p.resolvedNodes = make(map[*yaml.Node]nodeCache)
|
||||
p.visitCount = 0
|
||||
defer func() {
|
||||
p.visitedNodes = nil
|
||||
p.resolvedNodes = nil
|
||||
}()
|
||||
resolved, err := p.resolveReset(value, tree.NewPath())
|
||||
p.visitedNodes = nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -44,21 +65,21 @@ func (p *ResetProcessor) UnmarshalYAML(value *yaml.Node) error {
|
||||
|
||||
// resolveReset detects `!reset` tag being set on yaml nodes and record position in the yaml tree
|
||||
func (p *ResetProcessor) resolveReset(node *yaml.Node, path tree.Path) (*yaml.Node, error) {
|
||||
p.visitCount++
|
||||
limit := p.maxNodeVisits
|
||||
if limit <= 0 {
|
||||
limit = defaultMaxNodeVisits
|
||||
}
|
||||
if p.visitCount > limit {
|
||||
return nil, fmt.Errorf("compose file exceeds maximum node visit limit (%d)", limit)
|
||||
}
|
||||
|
||||
pathStr := path.String()
|
||||
// If the path contains "<<", removing the "<<" element and merging the path
|
||||
if strings.Contains(pathStr, ".<<") {
|
||||
path = tree.NewPath(strings.Replace(pathStr, ".<<", "", 1))
|
||||
}
|
||||
|
||||
// If the node is an alias, We need to process the alias field in order to consider the !override and !reset tags
|
||||
if node.Kind == yaml.AliasNode {
|
||||
if err := p.checkForCycle(node.Alias, path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.resolveReset(node.Alias, path)
|
||||
}
|
||||
|
||||
if node.Tag == "!reset" {
|
||||
p.paths = append(p.paths, path)
|
||||
return nil, nil
|
||||
@@ -68,7 +89,69 @@ func (p *ResetProcessor) resolveReset(node *yaml.Node, path tree.Path) (*yaml.No
|
||||
return node, nil
|
||||
}
|
||||
|
||||
keys := map[string]int{}
|
||||
// If the node is an alias, process the alias target via the cache so each anchor is
|
||||
// processed at most once.
|
||||
if node.Kind == yaml.AliasNode {
|
||||
if err := p.checkForCycle(node.Alias, path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Handle !reset/!override on the alias target before delegating to the cache,
|
||||
// keeping all tag-handling logic in resolveReset rather than split across functions.
|
||||
target := node.Alias
|
||||
if target.Tag == "!reset" {
|
||||
p.paths = append(p.paths, path)
|
||||
return nil, nil
|
||||
}
|
||||
if target.Tag == "!override" {
|
||||
p.paths = append(p.paths, path)
|
||||
return target, nil
|
||||
}
|
||||
return p.cachedResolve(target, path)
|
||||
}
|
||||
|
||||
// Container nodes are resolved through the cache, ensuring resolved containers are
|
||||
// not re-traversed.
|
||||
if node.Kind == yaml.SequenceNode || node.Kind == yaml.MappingNode {
|
||||
return p.cachedResolve(node, path)
|
||||
}
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// cachedResolve resolves node (a container without !reset/!override), serving from cache on
|
||||
// repeat visits to prevent re-traversal. It is only called after tag checks are done in
|
||||
// resolveReset, so it never receives !reset/!override-tagged nodes.
|
||||
func (p *ResetProcessor) cachedResolve(node *yaml.Node, path tree.Path) (*yaml.Node, error) {
|
||||
if cached, ok := p.resolvedNodes[node]; ok {
|
||||
for _, rel := range cached.relativePaths {
|
||||
p.paths = append(p.paths, joinPath(path, rel))
|
||||
}
|
||||
return cached.node, nil
|
||||
}
|
||||
|
||||
startIdx := len(p.paths)
|
||||
resolved, err := p.resolveContainer(node, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var relPaths []tree.Path
|
||||
for _, addedPath := range p.paths[startIdx:] {
|
||||
rel, err := subPath(addedPath, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
relPaths = append(relPaths, rel)
|
||||
}
|
||||
p.resolvedNodes[node] = nodeCache{node: resolved, relativePaths: relPaths}
|
||||
return resolved, nil
|
||||
}
|
||||
|
||||
// resolveContainer processes the children of a Sequence or Mapping node.
|
||||
// AliasNodes must be kept as-is in the output Content; the resolved value is used only
|
||||
// for tag inspection. Changing this will affect how the YAML library handles the document
|
||||
// during decoding.
|
||||
func (p *ResetProcessor) resolveContainer(node *yaml.Node, path tree.Path) (*yaml.Node, error) {
|
||||
switch node.Kind {
|
||||
case yaml.SequenceNode:
|
||||
var nodes []*yaml.Node
|
||||
@@ -78,12 +161,18 @@ func (p *ResetProcessor) resolveReset(node *yaml.Node, path tree.Path) (*yaml.No
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resolved != nil {
|
||||
if resolved == nil {
|
||||
continue
|
||||
}
|
||||
if v.Kind == yaml.AliasNode {
|
||||
nodes = append(nodes, v)
|
||||
} else {
|
||||
nodes = append(nodes, resolved)
|
||||
}
|
||||
}
|
||||
node.Content = nodes
|
||||
case yaml.MappingNode:
|
||||
keys := map[string]int{}
|
||||
var key string
|
||||
var nodes []*yaml.Node
|
||||
for idx, v := range node.Content {
|
||||
@@ -98,7 +187,12 @@ func (p *ResetProcessor) resolveReset(node *yaml.Node, path tree.Path) (*yaml.No
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resolved != nil {
|
||||
if resolved == nil {
|
||||
continue
|
||||
}
|
||||
if v.Kind == yaml.AliasNode {
|
||||
nodes = append(nodes, node.Content[idx-1], v)
|
||||
} else {
|
||||
nodes = append(nodes, node.Content[idx-1], resolved)
|
||||
}
|
||||
}
|
||||
@@ -108,6 +202,38 @@ func (p *ResetProcessor) resolveReset(node *yaml.Node, path tree.Path) (*yaml.No
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// subPath strips base from full to produce a relative path for cache storage.
|
||||
// Returns "" when full == base (the !reset/!override tag is on the node root itself).
|
||||
// Returns an error when full is not rooted at base, which would indicate a logic error
|
||||
// in resolveReset/cachedResolve.
|
||||
func subPath(full, base tree.Path) (tree.Path, error) {
|
||||
if base == "" {
|
||||
return full, nil
|
||||
}
|
||||
fullStr := string(full)
|
||||
baseStr := string(base)
|
||||
if fullStr == baseStr {
|
||||
return "", nil
|
||||
}
|
||||
prefix := baseStr + "."
|
||||
if strings.HasPrefix(fullStr, prefix) {
|
||||
return tree.Path(fullStr[len(prefix):]), nil
|
||||
}
|
||||
return "", fmt.Errorf("internal error: path %q is not a sub-path of %q", fullStr, baseStr)
|
||||
}
|
||||
|
||||
// joinPath reconstructs an absolute path from a call-site base and a cached relative path.
|
||||
// A relative path of "" means the tag was on the node root, so base is returned unchanged.
|
||||
func joinPath(base, rel tree.Path) tree.Path {
|
||||
if rel == "" {
|
||||
return base
|
||||
}
|
||||
if base == "" {
|
||||
return rel
|
||||
}
|
||||
return tree.Path(string(base) + "." + string(rel))
|
||||
}
|
||||
|
||||
// Apply finds the go attributes matching recorded paths and reset them to zero value
|
||||
func (p *ResetProcessor) Apply(target any) error {
|
||||
return p.applyNullOverrides(target, tree.NewPath())
|
||||
@@ -180,15 +306,11 @@ func (p *ResetProcessor) checkForCycle(node *yaml.Node, path tree.Path) error {
|
||||
|
||||
// areInDifferentServices checks if two paths are in different service definitions
|
||||
func areInDifferentServices(path1, path2 string) bool {
|
||||
// Split paths into components
|
||||
parts1 := strings.Split(path1, ".")
|
||||
parts2 := strings.Split(path2, ".")
|
||||
|
||||
// Look for the services component and compare the service names
|
||||
for i := 0; i < len(parts1) && i < len(parts2); i++ {
|
||||
if parts1[i] == "services" && i+1 < len(parts1) &&
|
||||
parts2[i] == "services" && i+1 < len(parts2) {
|
||||
// If they're different services, it's not a cycle
|
||||
return parts1[i+1] != parts2[i+1]
|
||||
}
|
||||
}
|
||||
|
||||
+72
-72
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft-07/schema",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "compose_spec.json",
|
||||
"type": "object",
|
||||
"title": "Compose Specification",
|
||||
@@ -20,7 +20,7 @@
|
||||
"include": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/include"
|
||||
"$ref": "#/$defs/include"
|
||||
},
|
||||
"description": "compose sub-projects to be included."
|
||||
},
|
||||
@@ -29,7 +29,7 @@
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[a-zA-Z0-9._-]+$": {
|
||||
"$ref": "#/definitions/service"
|
||||
"$ref": "#/$defs/service"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -40,7 +40,7 @@
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[a-zA-Z0-9._-]+$": {
|
||||
"$ref": "#/definitions/model"
|
||||
"$ref": "#/$defs/model"
|
||||
}
|
||||
},
|
||||
"description": "Language models that will be used by your application."
|
||||
@@ -51,7 +51,7 @@
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[a-zA-Z0-9._-]+$": {
|
||||
"$ref": "#/definitions/network"
|
||||
"$ref": "#/$defs/network"
|
||||
}
|
||||
},
|
||||
"description": "Networks that are shared among multiple services."
|
||||
@@ -61,7 +61,7 @@
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[a-zA-Z0-9._-]+$": {
|
||||
"$ref": "#/definitions/volume"
|
||||
"$ref": "#/$defs/volume"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -72,7 +72,7 @@
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[a-zA-Z0-9._-]+$": {
|
||||
"$ref": "#/definitions/secret"
|
||||
"$ref": "#/$defs/secret"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -83,7 +83,7 @@
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[a-zA-Z0-9._-]+$": {
|
||||
"$ref": "#/definitions/config"
|
||||
"$ref": "#/$defs/config"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -94,15 +94,15 @@
|
||||
"patternProperties": {"^x-": {}},
|
||||
"additionalProperties": false,
|
||||
|
||||
"definitions": {
|
||||
"$defs": {
|
||||
|
||||
"service": {
|
||||
"type": "object",
|
||||
"description": "Configuration for a service.",
|
||||
"properties": {
|
||||
"develop": {"$ref": "#/definitions/development"},
|
||||
"deploy": {"$ref": "#/definitions/deployment"},
|
||||
"annotations": {"$ref": "#/definitions/list_or_dict"},
|
||||
"develop": {"$ref": "#/$defs/development"},
|
||||
"deploy": {"$ref": "#/$defs/deployment"},
|
||||
"annotations": {"$ref": "#/$defs/list_or_dict"},
|
||||
"attach": {"type": ["boolean", "string"]},
|
||||
"build": {
|
||||
"description": "Configuration options for building the service's image.",
|
||||
@@ -115,26 +115,26 @@
|
||||
"dockerfile": {"type": "string", "description": "Name of the Dockerfile to use for building the image."},
|
||||
"dockerfile_inline": {"type": "string", "description": "Inline Dockerfile content to use instead of a Dockerfile from the build context."},
|
||||
"entitlements": {"type": "array", "items": {"type": "string"}, "description": "List of extra privileged entitlements to grant to the build process."},
|
||||
"args": {"$ref": "#/definitions/list_or_dict", "description": "Build-time variables, specified as a map or a list of KEY=VAL pairs."},
|
||||
"ssh": {"$ref": "#/definitions/list_or_dict", "description": "SSH agent socket or keys to expose to the build. Format is either a string or a list of 'default|<id>[=<socket>|<key>[,<key>]]'."},
|
||||
"labels": {"$ref": "#/definitions/list_or_dict", "description": "Labels to apply to the built image."},
|
||||
"args": {"$ref": "#/$defs/list_or_dict", "description": "Build-time variables, specified as a map or a list of KEY=VAL pairs."},
|
||||
"ssh": {"$ref": "#/$defs/list_or_dict", "description": "SSH agent socket or keys to expose to the build. Format is either a string or a list of 'default|<id>[=<socket>|<key>[,<key>]]'."},
|
||||
"labels": {"$ref": "#/$defs/list_or_dict", "description": "Labels to apply to the built image."},
|
||||
"cache_from": {"type": "array", "items": {"type": "string"}, "description": "List of sources the image builder should use for cache resolution"},
|
||||
"cache_to": {"type": "array", "items": {"type": "string"}, "description": "Cache destinations for the build cache."},
|
||||
"no_cache": {"type": ["boolean", "string"], "description": "Do not use cache when building the image."},
|
||||
"no_cache_filter": {"$ref": "#/definitions/string_or_list", "description": "Do not use build cache for the specified stages."},
|
||||
"additional_contexts": {"$ref": "#/definitions/list_or_dict", "description": "Additional build contexts to use, specified as a map of name to context path or URL."},
|
||||
"no_cache_filter": {"$ref": "#/$defs/string_or_list", "description": "Do not use build cache for the specified stages."},
|
||||
"additional_contexts": {"$ref": "#/$defs/list_or_dict", "description": "Additional build contexts to use, specified as a map of name to context path or URL."},
|
||||
"network": {"type": "string", "description": "Network mode to use for the build. Options include 'default', 'none', 'host', or a network name."},
|
||||
"provenance": {"type": ["string","boolean"], "description": "Add a provenance attestation"},
|
||||
"sbom": {"type": ["string","boolean"], "description": "Add a SBOM attestation"},
|
||||
"pull": {"type": ["boolean", "string"], "description": "Always attempt to pull a newer version of the image."},
|
||||
"target": {"type": "string", "description": "Build stage to target in a multi-stage Dockerfile."},
|
||||
"shm_size": {"type": ["integer", "string"], "description": "Size of /dev/shm for the build container. A string value can use suffix like '2g' for 2 gigabytes."},
|
||||
"extra_hosts": {"$ref": "#/definitions/extra_hosts", "description": "Add hostname mappings for the build container."},
|
||||
"extra_hosts": {"$ref": "#/$defs/extra_hosts", "description": "Add hostname mappings for the build container."},
|
||||
"isolation": {"type": "string", "description": "Container isolation technology to use for the build process."},
|
||||
"privileged": {"type": ["boolean", "string"], "description": "Give extended privileges to the build container."},
|
||||
"secrets": {"$ref": "#/definitions/service_config_or_secret", "description": "Secrets to expose to the build. These are accessible at build-time."},
|
||||
"secrets": {"$ref": "#/$defs/service_config_or_secret", "description": "Secrets to expose to the build. These are accessible at build-time."},
|
||||
"tags": {"type": "array", "items": {"type": "string"}, "description": "Additional tags to apply to the built image."},
|
||||
"ulimits": {"$ref": "#/definitions/ulimits", "description": "Override the default ulimits for the build container."},
|
||||
"ulimits": {"$ref": "#/$defs/ulimits", "description": "Override the default ulimits for the build container."},
|
||||
"platforms": {"type": "array", "items": {"type": "string"}, "description": "Platforms to build for, e.g., 'linux/amd64', 'linux/arm64', or 'windows/amd64'."}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -149,22 +149,22 @@
|
||||
"device_read_bps": {
|
||||
"type": "array",
|
||||
"description": "Limit read rate (bytes per second) from a device.",
|
||||
"items": {"$ref": "#/definitions/blkio_limit"}
|
||||
"items": {"$ref": "#/$defs/blkio_limit"}
|
||||
},
|
||||
"device_read_iops": {
|
||||
"type": "array",
|
||||
"description": "Limit read rate (IO per second) from a device.",
|
||||
"items": {"$ref": "#/definitions/blkio_limit"}
|
||||
"items": {"$ref": "#/$defs/blkio_limit"}
|
||||
},
|
||||
"device_write_bps": {
|
||||
"type": "array",
|
||||
"description": "Limit write rate (bytes per second) to a device.",
|
||||
"items": {"$ref": "#/definitions/blkio_limit"}
|
||||
"items": {"$ref": "#/$defs/blkio_limit"}
|
||||
},
|
||||
"device_write_iops": {
|
||||
"type": "array",
|
||||
"description": "Limit write rate (IO per second) to a device.",
|
||||
"items": {"$ref": "#/definitions/blkio_limit"}
|
||||
"items": {"$ref": "#/$defs/blkio_limit"}
|
||||
},
|
||||
"weight": {
|
||||
"type": ["integer", "string"],
|
||||
@@ -173,7 +173,7 @@
|
||||
"weight_device": {
|
||||
"type": "array",
|
||||
"description": "Block IO weight (relative weight) for specific devices.",
|
||||
"items": {"$ref": "#/definitions/blkio_weight"}
|
||||
"items": {"$ref": "#/$defs/blkio_weight"}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -200,11 +200,11 @@
|
||||
"description": "Specify an optional parent cgroup for the container."
|
||||
},
|
||||
"command": {
|
||||
"$ref": "#/definitions/command",
|
||||
"$ref": "#/$defs/command",
|
||||
"description": "Override the default command declared by the container image, for example 'CMD' in Dockerfile."
|
||||
},
|
||||
"configs": {
|
||||
"$ref": "#/definitions/service_config_or_secret",
|
||||
"$ref": "#/$defs/service_config_or_secret",
|
||||
"description": "Grant access to Configs on a per-service basis."
|
||||
},
|
||||
"container_name": {
|
||||
@@ -276,7 +276,7 @@
|
||||
},
|
||||
"depends_on": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/definitions/list_of_strings"},
|
||||
{"$ref": "#/$defs/list_of_strings"},
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
@@ -309,7 +309,7 @@
|
||||
"description": "Express dependency between services. Service dependencies cause services to be started in dependency order. The dependent service will wait for the dependency to be ready before starting."
|
||||
},
|
||||
"device_cgroup_rules": {
|
||||
"$ref": "#/definitions/list_of_strings",
|
||||
"$ref": "#/$defs/list_of_strings",
|
||||
"description": "Add rules to the cgroup allowed devices list."
|
||||
},
|
||||
"devices": {
|
||||
@@ -342,7 +342,7 @@
|
||||
}
|
||||
},
|
||||
"dns": {
|
||||
"$ref": "#/definitions/string_or_list",
|
||||
"$ref": "#/$defs/string_or_list",
|
||||
"description": "Custom DNS servers to set for the service container."
|
||||
},
|
||||
"dns_opt": {
|
||||
@@ -352,7 +352,7 @@
|
||||
"description": "Custom DNS options to be passed to the container's DNS resolver."
|
||||
},
|
||||
"dns_search": {
|
||||
"$ref": "#/definitions/string_or_list",
|
||||
"$ref": "#/$defs/string_or_list",
|
||||
"description": "Custom DNS search domains to set on the service container."
|
||||
},
|
||||
"domainname": {
|
||||
@@ -360,19 +360,19 @@
|
||||
"description": "Custom domain name to use for the service container."
|
||||
},
|
||||
"entrypoint": {
|
||||
"$ref": "#/definitions/command",
|
||||
"$ref": "#/$defs/command",
|
||||
"description": "Override the default entrypoint declared by the container image, for example 'ENTRYPOINT' in Dockerfile."
|
||||
},
|
||||
"env_file": {
|
||||
"$ref": "#/definitions/env_file",
|
||||
"$ref": "#/$defs/env_file",
|
||||
"description": "Add environment variables from a file or multiple files. Can be a single file path or a list of file paths."
|
||||
},
|
||||
"label_file": {
|
||||
"$ref": "#/definitions/label_file",
|
||||
"$ref": "#/$defs/label_file",
|
||||
"description": "Add metadata to containers using files containing Docker labels."
|
||||
},
|
||||
"environment": {
|
||||
"$ref": "#/definitions/list_or_dict",
|
||||
"$ref": "#/$defs/list_or_dict",
|
||||
"description": "Add environment variables. You can use either an array or a list of KEY=VAL pairs."
|
||||
},
|
||||
"expose": {
|
||||
@@ -434,11 +434,11 @@
|
||||
"description": "Link to services started outside this Compose application. Specify services as <service_name>:<alias>."
|
||||
},
|
||||
"extra_hosts": {
|
||||
"$ref": "#/definitions/extra_hosts",
|
||||
"$ref": "#/$defs/extra_hosts",
|
||||
"description": "Add hostname mappings to the container network interface configuration."
|
||||
},
|
||||
"gpus": {
|
||||
"$ref": "#/definitions/gpus",
|
||||
"$ref": "#/$defs/gpus",
|
||||
"description": "Define GPU devices to use. Can be set to 'all' to use all GPUs, or a list of specific GPU devices."
|
||||
},
|
||||
"group_add": {
|
||||
@@ -450,7 +450,7 @@
|
||||
"description": "Add additional groups which user inside the container should be member of."
|
||||
},
|
||||
"healthcheck": {
|
||||
"$ref": "#/definitions/healthcheck",
|
||||
"$ref": "#/$defs/healthcheck",
|
||||
"description": "Configure a health check for the container to monitor its health status."
|
||||
},
|
||||
"hostname": {
|
||||
@@ -474,7 +474,7 @@
|
||||
"description": "Container isolation technology to use. Supported values are platform-specific."
|
||||
},
|
||||
"labels": {
|
||||
"$ref": "#/definitions/list_or_dict",
|
||||
"$ref": "#/$defs/list_or_dict",
|
||||
"description": "Add metadata to containers using Docker labels. You can use either an array or a list."
|
||||
},
|
||||
"links": {
|
||||
@@ -528,7 +528,7 @@
|
||||
},
|
||||
"models": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/definitions/list_of_strings"},
|
||||
{"$ref": "#/$defs/list_of_strings"},
|
||||
{"type": "object",
|
||||
"patternProperties": {
|
||||
"^[a-zA-Z0-9._-]+$": {
|
||||
@@ -558,7 +558,7 @@
|
||||
},
|
||||
"networks": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/definitions/list_of_strings"},
|
||||
{"$ref": "#/$defs/list_of_strings"},
|
||||
{
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
@@ -568,7 +568,7 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"aliases": {
|
||||
"$ref": "#/definitions/list_of_strings",
|
||||
"$ref": "#/$defs/list_of_strings",
|
||||
"description": "Alternative hostnames for this service on the network."
|
||||
},
|
||||
"interface_name": {
|
||||
@@ -584,7 +584,7 @@
|
||||
"description": "Specify a static IPv6 address for this service on this network."
|
||||
},
|
||||
"link_local_ips": {
|
||||
"$ref": "#/definitions/list_of_strings",
|
||||
"$ref": "#/$defs/list_of_strings",
|
||||
"description": "List of link-local IPs."
|
||||
},
|
||||
"mac_address": {
|
||||
@@ -690,12 +690,12 @@
|
||||
},
|
||||
"post_start": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/definitions/service_hook"},
|
||||
"items": {"$ref": "#/$defs/service_hook"},
|
||||
"description": "Commands to run after the container starts. If any command fails, the container stops."
|
||||
},
|
||||
"pre_stop": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/definitions/service_hook"},
|
||||
"items": {"$ref": "#/$defs/service_hook"},
|
||||
"description": "Commands to run before the container stops. If any command fails, the container stop is aborted."
|
||||
},
|
||||
"privileged": {
|
||||
@@ -703,7 +703,7 @@
|
||||
"description": "Give extended privileges to the service container."
|
||||
},
|
||||
"profiles": {
|
||||
"$ref": "#/definitions/list_of_strings",
|
||||
"$ref": "#/$defs/list_of_strings",
|
||||
"description": "List of profiles for this service. When profiles are specified, services are only started when the profile is activated."
|
||||
},
|
||||
"pull_policy": {
|
||||
@@ -742,11 +742,11 @@
|
||||
"description": "Size of /dev/shm. A string value can use suffix like '2g' for 2 gigabytes."
|
||||
},
|
||||
"secrets": {
|
||||
"$ref": "#/definitions/service_config_or_secret",
|
||||
"$ref": "#/$defs/service_config_or_secret",
|
||||
"description": "Grant access to Secrets on a per-service basis."
|
||||
},
|
||||
"sysctls": {
|
||||
"$ref": "#/definitions/list_or_dict",
|
||||
"$ref": "#/$defs/list_or_dict",
|
||||
"description": "Kernel parameters to set in the container. You can use either an array or a list."
|
||||
},
|
||||
"stdin_open": {
|
||||
@@ -766,7 +766,7 @@
|
||||
"description": "Storage driver options for the container."
|
||||
},
|
||||
"tmpfs": {
|
||||
"$ref": "#/definitions/string_or_list",
|
||||
"$ref": "#/$defs/string_or_list",
|
||||
"description": "Mount a temporary filesystem (tmpfs) into the container. Can be a single value or a list."
|
||||
},
|
||||
"tty": {
|
||||
@@ -774,7 +774,7 @@
|
||||
"description": "Allocate a pseudo-TTY to service container."
|
||||
},
|
||||
"ulimits": {
|
||||
"$ref": "#/definitions/ulimits",
|
||||
"$ref": "#/$defs/ulimits",
|
||||
"description": "Override the default ulimits for a container."
|
||||
},
|
||||
"use_api_socket": {
|
||||
@@ -855,7 +855,7 @@
|
||||
"description": "Configuration specific to volume mounts.",
|
||||
"properties": {
|
||||
"labels": {
|
||||
"$ref": "#/definitions/list_or_dict",
|
||||
"$ref": "#/$defs/list_or_dict",
|
||||
"description": "Labels to apply to the volume."
|
||||
},
|
||||
"nocopy": {
|
||||
@@ -975,11 +975,11 @@
|
||||
"required": ["path", "action"],
|
||||
"properties": {
|
||||
"ignore": {
|
||||
"$ref": "#/definitions/string_or_list",
|
||||
"$ref": "#/$defs/string_or_list",
|
||||
"description": "Patterns to exclude from watching."
|
||||
},
|
||||
"include": {
|
||||
"$ref": "#/definitions/string_or_list",
|
||||
"$ref": "#/$defs/string_or_list",
|
||||
"description": "Patterns to include in watching."
|
||||
},
|
||||
"path": {
|
||||
@@ -996,7 +996,7 @@
|
||||
"description": "Target path in the container for sync operations."
|
||||
},
|
||||
"exec": {
|
||||
"$ref": "#/definitions/service_hook",
|
||||
"$ref": "#/$defs/service_hook",
|
||||
"description": "Command to execute when a change is detected and action is sync+exec."
|
||||
},
|
||||
"initial_sync": {
|
||||
@@ -1029,7 +1029,7 @@
|
||||
"description": "Number of replicas of the service container to run."
|
||||
},
|
||||
"labels": {
|
||||
"$ref": "#/definitions/list_or_dict",
|
||||
"$ref": "#/$defs/list_or_dict",
|
||||
"description": "Labels to apply to the service."
|
||||
},
|
||||
"rollback_config": {
|
||||
@@ -1135,11 +1135,11 @@
|
||||
"description": "Reservation on the amount of memory a container can allocate (e.g., '1g', '1024m')."
|
||||
},
|
||||
"generic_resources": {
|
||||
"$ref": "#/definitions/generic_resources",
|
||||
"$ref": "#/$defs/generic_resources",
|
||||
"description": "User-defined resources to reserve."
|
||||
},
|
||||
"devices": {
|
||||
"$ref": "#/definitions/devices",
|
||||
"$ref": "#/$defs/devices",
|
||||
"description": "Device reservations for the container."
|
||||
}
|
||||
},
|
||||
@@ -1246,7 +1246,7 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"capabilities": {
|
||||
"$ref": "#/definitions/list_of_strings",
|
||||
"$ref": "#/$defs/list_of_strings",
|
||||
"description": "List of capabilities the device needs to have (e.g., 'gpu', 'compute', 'utility')."
|
||||
},
|
||||
"count": {
|
||||
@@ -1254,7 +1254,7 @@
|
||||
"description": "Number of devices of this type to reserve."
|
||||
},
|
||||
"device_ids": {
|
||||
"$ref": "#/definitions/list_of_strings",
|
||||
"$ref": "#/$defs/list_of_strings",
|
||||
"description": "List of specific device IDs to reserve."
|
||||
},
|
||||
"driver": {
|
||||
@@ -1262,7 +1262,7 @@
|
||||
"description": "Device driver to use (e.g., 'nvidia')."
|
||||
},
|
||||
"options": {
|
||||
"$ref": "#/definitions/list_or_dict",
|
||||
"$ref": "#/$defs/list_or_dict",
|
||||
"description": "Driver-specific options for the device."
|
||||
}
|
||||
},
|
||||
@@ -1288,7 +1288,7 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"capabilities": {
|
||||
"$ref": "#/definitions/list_of_strings",
|
||||
"$ref": "#/$defs/list_of_strings",
|
||||
"description": "List of capabilities the GPU needs to have (e.g., 'compute', 'utility')."
|
||||
},
|
||||
"count": {
|
||||
@@ -1296,7 +1296,7 @@
|
||||
"description": "Number of GPUs to use."
|
||||
},
|
||||
"device_ids": {
|
||||
"$ref": "#/definitions/list_of_strings",
|
||||
"$ref": "#/$defs/list_of_strings",
|
||||
"description": "List of specific GPU device IDs to use."
|
||||
},
|
||||
"driver": {
|
||||
@@ -1304,7 +1304,7 @@
|
||||
"description": "GPU driver to use (e.g., 'nvidia')."
|
||||
},
|
||||
"options": {
|
||||
"$ref": "#/definitions/list_or_dict",
|
||||
"$ref": "#/$defs/list_or_dict",
|
||||
"description": "Driver-specific options for the GPU."
|
||||
}
|
||||
}
|
||||
@@ -1323,11 +1323,11 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/string_or_list",
|
||||
"$ref": "#/$defs/string_or_list",
|
||||
"description": "Path to the Compose application or sub-project files to include."
|
||||
},
|
||||
"env_file": {
|
||||
"$ref": "#/definitions/string_or_list",
|
||||
"$ref": "#/$defs/string_or_list",
|
||||
"description": "Path to the environment files to use to define default values when interpolating variables in the Compose files being parsed."
|
||||
},
|
||||
"project_directory": {
|
||||
@@ -1436,7 +1436,7 @@
|
||||
"description": "If true, standalone containers can attach to this network."
|
||||
},
|
||||
"labels": {
|
||||
"$ref": "#/definitions/list_or_dict",
|
||||
"$ref": "#/$defs/list_or_dict",
|
||||
"description": "Add metadata to the network using labels."
|
||||
}
|
||||
},
|
||||
@@ -1477,7 +1477,7 @@
|
||||
"patternProperties": {"^x-": {}}
|
||||
},
|
||||
"labels": {
|
||||
"$ref": "#/definitions/list_or_dict",
|
||||
"$ref": "#/$defs/list_or_dict",
|
||||
"description": "Add metadata to the volume using labels."
|
||||
}
|
||||
},
|
||||
@@ -1512,7 +1512,7 @@
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
"$ref": "#/definitions/list_or_dict",
|
||||
"$ref": "#/$defs/list_or_dict",
|
||||
"description": "Add metadata to the secret using labels."
|
||||
},
|
||||
"driver": {
|
||||
@@ -1567,7 +1567,7 @@
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
"$ref": "#/definitions/list_or_dict",
|
||||
"$ref": "#/$defs/list_or_dict",
|
||||
"description": "Add metadata to the config using labels."
|
||||
},
|
||||
"template_driver": {
|
||||
@@ -1632,7 +1632,7 @@
|
||||
"description": "Configuration for service lifecycle hooks, which are commands executed at specific points in a container's lifecycle.",
|
||||
"properties": {
|
||||
"command": {
|
||||
"$ref": "#/definitions/command",
|
||||
"$ref": "#/$defs/command",
|
||||
"description": "Command to execute as part of the hook."
|
||||
},
|
||||
"user": {
|
||||
@@ -1648,7 +1648,7 @@
|
||||
"description": "Working directory for the command."
|
||||
},
|
||||
"environment": {
|
||||
"$ref": "#/definitions/list_or_dict",
|
||||
"$ref": "#/$defs/list_or_dict",
|
||||
"description": "Environment variables for the command."
|
||||
}
|
||||
},
|
||||
@@ -1725,7 +1725,7 @@
|
||||
"description": "A single string value."
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/list_of_strings",
|
||||
"$ref": "#/$defs/list_of_strings",
|
||||
"description": "A list of string values."
|
||||
}
|
||||
],
|
||||
|
||||
+45
-3
@@ -17,9 +17,12 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/docker/go-units"
|
||||
"go.yaml.in/yaml/v4"
|
||||
)
|
||||
|
||||
// UnitBytes is the bytes type
|
||||
@@ -35,14 +38,53 @@ func (u UnitBytes) MarshalJSON() ([]byte, error) {
|
||||
return []byte(fmt.Sprintf(`"%d"`, u)), nil
|
||||
}
|
||||
|
||||
// parseString parses a string into a UnitBytes value, supporting plain
|
||||
// integers, negative values (e.g., "-1"), and human-readable byte units
|
||||
// (e.g., "1g", "512m").
|
||||
func (u *UnitBytes) parseString(s string) error {
|
||||
if n, err := strconv.ParseInt(s, 10, 64); err == nil {
|
||||
*u = UnitBytes(n)
|
||||
return nil
|
||||
}
|
||||
b, err := units.RAMInBytes(s)
|
||||
*u = UnitBytes(b)
|
||||
return err
|
||||
}
|
||||
|
||||
// UnmarshalJSON makes UnitBytes implement json.Unmarshaler
|
||||
func (u *UnitBytes) UnmarshalJSON(data []byte) error {
|
||||
var v int64
|
||||
if err := json.Unmarshal(data, &v); err == nil {
|
||||
*u = UnitBytes(v)
|
||||
return nil
|
||||
}
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
return u.parseString(s)
|
||||
}
|
||||
|
||||
// UnmarshalYAML makes UnitBytes implement yaml.Unmarshaler
|
||||
func (u *UnitBytes) UnmarshalYAML(value *yaml.Node) error {
|
||||
var v int64
|
||||
if err := value.Decode(&v); err == nil {
|
||||
*u = UnitBytes(v)
|
||||
return nil
|
||||
}
|
||||
var s string
|
||||
if err := value.Decode(&s); err != nil {
|
||||
return err
|
||||
}
|
||||
return u.parseString(s)
|
||||
}
|
||||
|
||||
func (u *UnitBytes) DecodeMapstructure(value interface{}) error {
|
||||
switch v := value.(type) {
|
||||
case int:
|
||||
*u = UnitBytes(v)
|
||||
case string:
|
||||
b, err := units.RAMInBytes(fmt.Sprint(value))
|
||||
*u = UnitBytes(b)
|
||||
return err
|
||||
return u.parseString(v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user