From 7a2f9a9f48baffb2ffc2e5895d4c43b0c15a344b Mon Sep 17 00:00:00 2001 From: Suleiman Dibirov Date: Fri, 22 Nov 2024 10:50:45 +0200 Subject: [PATCH 01/10] feat: Add label_file support to service Signed-off-by: Suleiman Dibirov --- loader/loader.go | 2 ++ loader/loader_test.go | 25 ++++++++++++++++++ override/merge.go | 1 + override/uncity.go | 14 ++++++++++ paths/resolve.go | 1 + schema/compose-spec.json | 30 ++++++++++++++++++++- transform/canonical.go | 1 + transform/labelfile.go | 55 +++++++++++++++++++++++++++++++++++++++ types/derived.gen.go | 30 +++++++++++++++++++++ types/labelfile.go | 47 +++++++++++++++++++++++++++++++++ types/labels.go | 18 +++++++++++++ types/project.go | 56 +++++++++++++++++++++++++++++++++++++--- types/types.go | 1 + 13 files changed, 277 insertions(+), 4 deletions(-) create mode 100644 transform/labelfile.go create mode 100644 types/labelfile.go diff --git a/loader/loader.go b/loader/loader.go index 8fb95088..af6df117 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -637,6 +637,8 @@ func modelToProject(dict map[string]interface{}, opts *Options, configDetails ty return nil, err } } + + project, err = project.WithServicesLabelsResolved(opts.discardEnvFiles) return project, nil } diff --git a/loader/loader_test.go b/loader/loader_test.go index 53186366..8f5f2cd2 100644 --- a/loader/loader_test.go +++ b/loader/loader_test.go @@ -2315,6 +2315,31 @@ func TestLoadServiceWithEnvFile(t *testing.T) { assert.Equal(t, "YES", *service.Environment["HALLO"]) } +func TestLoadServiceWithLabelFile(t *testing.T) { + file, err := os.CreateTemp("", "test-compose-go") + assert.NilError(t, err) + defer os.Remove(file.Name()) + + _, err = file.Write([]byte("MY_LABEL=MY_VALUE")) + assert.NilError(t, err) + + p := &types.Project{ + Services: types.Services{ + "test": { + Name: "test", + LabelFiles: []types.LabelFile{ + {Path: file.Name(), Required: true}, + }, + }, + }, + } + p, err = p.WithServicesLabelsResolved(false) + assert.NilError(t, err) + service, err := p.GetService("test") + assert.NilError(t, err) + assert.Equal(t, "MY_VALUE", service.Labels["MY_LABEL"]) +} + func TestLoadNoSSHInBuildConfig(t *testing.T) { actual, err := loadYAML(` name: load-no-ssh-in-build-config diff --git a/override/merge.go b/override/merge.go index 697dbc74..8cb0ed52 100644 --- a/override/merge.go +++ b/override/merge.go @@ -57,6 +57,7 @@ func init() { mergeSpecials["services.*.dns_search"] = mergeToSequence mergeSpecials["services.*.entrypoint"] = override mergeSpecials["services.*.env_file"] = mergeToSequence + mergeSpecials["services.*.label_file"] = mergeToSequence mergeSpecials["services.*.environment"] = mergeToSequence mergeSpecials["services.*.extra_hosts"] = mergeExtraHosts mergeSpecials["services.*.healthcheck.test"] = override diff --git a/override/uncity.go b/override/uncity.go index 3b0c63d3..6b5b8ae9 100644 --- a/override/uncity.go +++ b/override/uncity.go @@ -51,6 +51,7 @@ func init() { unique["services.*.env_file"] = envFileIndexer unique["services.*.expose"] = exposeIndexer unique["services.*.labels"] = keyValueIndexer + unique["services.*.label_file"] = labelFileIndexer unique["services.*.links"] = keyValueIndexer unique["services.*.networks.*.aliases"] = keyValueIndexer unique["services.*.networks.*.link_local_ips"] = keyValueIndexer @@ -227,3 +228,16 @@ func envFileIndexer(y any, p tree.Path) (string, error) { } return "", nil } + +func labelFileIndexer(y any, p tree.Path) (string, error) { + switch value := y.(type) { + case string: + return value, nil + case map[string]any: + if pathValue, ok := value["path"]; ok { + return pathValue.(string), nil + } + return "", fmt.Errorf("label path attribute %s is missing", p) + } + return "", nil +} diff --git a/paths/resolve.go b/paths/resolve.go index 303f39e2..0569e658 100644 --- a/paths/resolve.go +++ b/paths/resolve.go @@ -37,6 +37,7 @@ func ResolveRelativePaths(project map[string]any, base string, remotes []RemoteR "services.*.build.context": r.absContextPath, "services.*.build.additional_contexts.*": r.absContextPath, "services.*.env_file.*.path": r.absPath, + "services.*.label_file.*.path": r.absPath, "services.*.extends.file": r.absExtendsPath, "services.*.develop.watch.*.path": r.absSymbolicLink, "services.*.volumes.*": r.absVolumeMount, diff --git a/schema/compose-spec.json b/schema/compose-spec.json index b95a1498..4768fd99 100644 --- a/schema/compose-spec.json +++ b/schema/compose-spec.json @@ -240,6 +240,7 @@ "domainname": {"type": "string"}, "entrypoint": {"$ref": "#/definitions/command"}, "env_file": {"$ref": "#/definitions/env_file"}, + "label_file": {"$ref": "#/definitions/label_file"}, "environment": {"$ref": "#/definitions/list_or_dict"}, "expose": { @@ -866,7 +867,34 @@ "path": { "type": "string" }, - "format": { + "required": { + "type": ["boolean", "string"], + "default": true + } + }, + "required": [ + "path" + ] + } + ] + } + } + ] + }, + + "label_file": { + "oneOf": [ + {"type": "string"}, + { + "type": "array", + "items": { + "oneOf": [ + {"type": "string"}, + { + "type": "object", + "additionalProperties": false, + "properties": { + "path": { "type": "string" }, "required": { diff --git a/transform/canonical.go b/transform/canonical.go index ff5bb37d..27d03810 100644 --- a/transform/canonical.go +++ b/transform/canonical.go @@ -30,6 +30,7 @@ func init() { transformers["services.*.build.additional_contexts"] = transformKeyValue transformers["services.*.depends_on"] = transformDependsOn transformers["services.*.env_file"] = transformEnvFile + transformers["services.*.label_file"] = transformLabelFile transformers["services.*.extends"] = transformExtends transformers["services.*.networks"] = transformServiceNetworks transformers["services.*.volumes.*"] = transformVolumeMount diff --git a/transform/labelfile.go b/transform/labelfile.go new file mode 100644 index 00000000..db8470df --- /dev/null +++ b/transform/labelfile.go @@ -0,0 +1,55 @@ +/* + Copyright 2020 The Compose Specification Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package transform + +import ( + "fmt" + + "github.com/compose-spec/compose-go/v2/tree" +) + +func transformLabelFile(data any, p tree.Path, _ bool) (any, error) { + switch v := data.(type) { + case string: + return []any{ + transformLabelFileValue(v), + }, nil + case []any: + for i, e := range v { + v[i] = transformLabelFileValue(e) + } + return v, nil + default: + return nil, fmt.Errorf("%s: invalid type %T for label_file", p, v) + } +} + +func transformLabelFileValue(data any) any { + switch v := data.(type) { + case string: + return map[string]any{ + "path": v, + "required": true, + } + case map[string]any: + if _, ok := v["required"]; !ok { + v["required"] = true + } + return v + } + return nil +} diff --git a/types/derived.gen.go b/types/derived.gen.go index d33ae280..3f8d92b7 100644 --- a/types/derived.gen.go +++ b/types/derived.gen.go @@ -485,6 +485,24 @@ func deriveDeepCopyService(dst, src *ServiceConfig) { } else { dst.Labels = nil } + if src.LabelFiles == nil { + dst.LabelFiles = nil + } else { + if dst.LabelFiles != nil { + if len(src.LabelFiles) > len(dst.LabelFiles) { + if cap(dst.LabelFiles) >= len(src.LabelFiles) { + dst.LabelFiles = (dst.LabelFiles)[:len(src.LabelFiles)] + } else { + dst.LabelFiles = make([]LabelFile, len(src.LabelFiles)) + } + } else if len(src.LabelFiles) < len(dst.LabelFiles) { + dst.LabelFiles = (dst.LabelFiles)[:len(src.LabelFiles)] + } + } else { + dst.LabelFiles = make([]LabelFile, len(src.LabelFiles)) + } + copy(dst.LabelFiles, src.LabelFiles) + } if src.CustomLabels != nil { dst.CustomLabels = make(map[string]string, len(src.CustomLabels)) deriveDeepCopy_4(dst.CustomLabels, src.CustomLabels) @@ -1428,6 +1446,12 @@ func deriveDeepCopy_24(dst, src *NetworkConfig) { } else { dst.Labels = nil } + if src.CustomLabels != nil { + dst.CustomLabels = make(map[string]string, len(src.CustomLabels)) + deriveDeepCopy_4(dst.CustomLabels, src.CustomLabels) + } else { + dst.CustomLabels = nil + } if src.EnableIPv6 == nil { dst.EnableIPv6 = nil } else { @@ -1459,6 +1483,12 @@ func deriveDeepCopy_25(dst, src *VolumeConfig) { } else { dst.Labels = nil } + if src.CustomLabels != nil { + dst.CustomLabels = make(map[string]string, len(src.CustomLabels)) + deriveDeepCopy_4(dst.CustomLabels, src.CustomLabels) + } else { + dst.CustomLabels = nil + } if src.Extensions != nil { dst.Extensions = make(map[string]any, len(src.Extensions)) src.Extensions.DeepCopy(dst.Extensions) diff --git a/types/labelfile.go b/types/labelfile.go new file mode 100644 index 00000000..ff0980e0 --- /dev/null +++ b/types/labelfile.go @@ -0,0 +1,47 @@ +/* + Copyright 2020 The Compose Specification Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package types + +import ( + "encoding/json" +) + +type LabelFile struct { + Path string `yaml:"path,omitempty" json:"path,omitempty"` + Required bool `yaml:"required" json:"required"` + Format string `yaml:"format,omitempty" json:"format,omitempty"` +} + +// MarshalYAML makes LabelFile implement yaml.Marshaler +func (e LabelFile) MarshalYAML() (interface{}, error) { + if e.Required { + return e.Path, nil + } + return map[string]any{ + "path": e.Path, + "required": e.Required, + }, nil +} + +// MarshalJSON makes LabelFile implement json.Marshaler +func (e *LabelFile) MarshalJSON() ([]byte, error) { + if e.Required { + return json.Marshal(e.Path) + } + // Pass as a value to avoid re-entering this method and use the default implementation + return json.Marshal(*e) +} diff --git a/types/labels.go b/types/labels.go index 000476bf..7ea5edc4 100644 --- a/types/labels.go +++ b/types/labels.go @@ -24,6 +24,16 @@ import ( // Labels is a mapping type for labels type Labels map[string]string +func NewLabelsFromMappingWithEquals(mapping MappingWithEquals) Labels { + labels := Labels{} + for k, v := range mapping { + if v != nil { + labels[k] = *v + } + } + return labels +} + func (l Labels) Add(key, value string) Labels { if l == nil { l = Labels{} @@ -42,6 +52,14 @@ func (l Labels) AsList() []string { return s } +func (l Labels) ToMappingWithEquals() MappingWithEquals { + mapping := MappingWithEquals{} + for k, v := range l { + mapping[k] = &v + } + return mapping +} + // label value can be a string | number | boolean | null (empty) func labelValue(e interface{}) string { if e == nil { diff --git a/types/project.go b/types/project.go index 19d6e32b..9b8d75c6 100644 --- a/types/project.go +++ b/types/project.go @@ -633,6 +633,40 @@ func (p Project) WithServicesEnvironmentResolved(discardEnvFiles bool) (*Project return newProject, nil } +// WithServicesLabelsResolved parses label_files set for services to resolve the actual label map for services +// It returns a new Project instance with the changes and keep the original Project unchanged +func (p Project) WithServicesLabelsResolved(discardLabelFiles bool) (*Project, error) { + newProject := p.deepCopy() + for i, service := range newProject.Services { + labels := MappingWithEquals{} + // resolve variables based on other files we already parsed + var resolve dotenv.LookupFn = func(s string) (string, bool) { + v, ok := labels[s] + if ok && v != nil { + return *v, ok + } + return "", false + } + + for _, labelFile := range service.LabelFiles { + vars, err := loadLabelFile(labelFile, resolve) + if err != nil { + return nil, err + } + labels.OverrideBy(vars.ToMappingWithEquals()) + } + + labels = labels.OverrideBy(service.Labels.ToMappingWithEquals()) + service.Labels = NewLabelsFromMappingWithEquals(labels) + + if discardLabelFiles { + service.LabelFiles = nil + } + newProject.Services[i] = service + } + return newProject, nil +} + func loadEnvFile(envFile EnvFile, resolve dotenv.LookupFn) (Mapping, error) { if _, err := os.Stat(envFile.Path); os.IsNotExist(err) { if envFile.Required { @@ -640,15 +674,31 @@ func loadEnvFile(envFile EnvFile, resolve dotenv.LookupFn) (Mapping, error) { } return nil, nil } - file, err := os.Open(envFile.Path) + + return loadMappingFile(envFile.Path, envFile.Format, resolve) +} + +func loadLabelFile(labelFile LabelFile, resolve dotenv.LookupFn) (Mapping, error) { + if _, err := os.Stat(labelFile.Path); os.IsNotExist(err) { + if labelFile.Required { + return nil, fmt.Errorf("label file %s not found: %w", labelFile.Path, err) + } + return nil, nil + } + + return loadMappingFile(labelFile.Path, labelFile.Format, resolve) +} + +func loadMappingFile(path string, format string, resolve dotenv.LookupFn) (Mapping, error) { + file, err := os.Open(path) if err != nil { return nil, err } defer file.Close() //nolint:errcheck var fileVars map[string]string - if envFile.Format != "" { - fileVars, err = dotenv.ParseWithFormat(file, envFile.Path, resolve, envFile.Format) + if format != "" { + fileVars, err = dotenv.ParseWithFormat(file, path, resolve, format) } else { fileVars, err = dotenv.ParseWithLookup(file, resolve) } diff --git a/types/types.go b/types/types.go index 3cae5390..7be08c30 100644 --- a/types/types.go +++ b/types/types.go @@ -89,6 +89,7 @@ type ServiceConfig struct { Ipc string `yaml:"ipc,omitempty" json:"ipc,omitempty"` Isolation string `yaml:"isolation,omitempty" json:"isolation,omitempty"` Labels Labels `yaml:"labels,omitempty" json:"labels,omitempty"` + LabelFiles []LabelFile `yaml:"label_file,omitempty" json:"label_file,omitempty"` CustomLabels Labels `yaml:"-" json:"-"` Links []string `yaml:"links,omitempty" json:"links,omitempty"` Logging *LoggingConfig `yaml:"logging,omitempty" json:"logging,omitempty"` From e608b093a0e030f4b12808b43e319fce8ef5b81e Mon Sep 17 00:00:00 2001 From: Suleiman Dibirov Date: Wed, 27 Nov 2024 11:08:54 +0200 Subject: [PATCH 02/10] fix loader.go Signed-off-by: Suleiman Dibirov --- loader/loader.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/loader/loader.go b/loader/loader.go index af6df117..612b91ca 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -639,6 +639,10 @@ func modelToProject(dict map[string]interface{}, opts *Options, configDetails ty } project, err = project.WithServicesLabelsResolved(opts.discardEnvFiles) + if err != nil { + return nil, err + } + return project, nil } From b378ecd3f04cf873e604bd33e262311ddf741a75 Mon Sep 17 00:00:00 2001 From: Suleiman Dibirov Date: Wed, 27 Nov 2024 11:19:51 +0200 Subject: [PATCH 03/10] fixed project.go Signed-off-by: Suleiman Dibirov --- types/project.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/types/project.go b/types/project.go index 9b8d75c6..813f2d1f 100644 --- a/types/project.go +++ b/types/project.go @@ -657,7 +657,11 @@ func (p Project) WithServicesLabelsResolved(discardLabelFiles bool) (*Project, e } labels = labels.OverrideBy(service.Labels.ToMappingWithEquals()) - service.Labels = NewLabelsFromMappingWithEquals(labels) + if len(labels) == 0 { + labels = nil + } else { + service.Labels = NewLabelsFromMappingWithEquals(labels) + } if discardLabelFiles { service.LabelFiles = nil From 457e8f03a2fa26f6c5dfccf14fbf56bcf18e4b0d Mon Sep 17 00:00:00 2001 From: Suleiman Dibirov Date: Wed, 27 Nov 2024 11:30:20 +0200 Subject: [PATCH 04/10] fixed labels.go Signed-off-by: Suleiman Dibirov --- types/labels.go | 1 + 1 file changed, 1 insertion(+) diff --git a/types/labels.go b/types/labels.go index 7ea5edc4..713c28f9 100644 --- a/types/labels.go +++ b/types/labels.go @@ -55,6 +55,7 @@ func (l Labels) AsList() []string { func (l Labels) ToMappingWithEquals() MappingWithEquals { mapping := MappingWithEquals{} for k, v := range l { + v := v mapping[k] = &v } return mapping From c697f1394cc95cc361099c2ec08283518aa521a9 Mon Sep 17 00:00:00 2001 From: Suleiman Dibirov Date: Wed, 27 Nov 2024 12:04:31 +0200 Subject: [PATCH 05/10] removed required from LabelFile Signed-off-by: Suleiman Dibirov --- loader/example1.label | 10 ++++++++++ loader/example2.label | 4 ++++ loader/full-example.yml | 4 ++++ loader/full-struct_test.go | 37 +++++++++++++++++++++++++++++++++++++ loader/loader_test.go | 17 ++++++++++++++++- types/labelfile.go | 19 ++++--------------- types/project.go | 5 +---- 7 files changed, 76 insertions(+), 20 deletions(-) create mode 100644 loader/example1.label create mode 100644 loader/example2.label diff --git a/loader/example1.label b/loader/example1.label new file mode 100644 index 00000000..27d43cff --- /dev/null +++ b/loader/example1.label @@ -0,0 +1,10 @@ +# passed through +FOO=foo_from_label_file +LABEL.WITH.DOT=ok +LABEL_WITH_UNDERSCORE=ok + +# overridden in example2.label +BAR=bar_from_label_file + +# overridden in full-example.yml +BAZ=baz_from_label_file diff --git a/loader/example2.label b/loader/example2.label new file mode 100644 index 00000000..aa667c30 --- /dev/null +++ b/loader/example2.label @@ -0,0 +1,4 @@ +BAR=bar_from_label_file_2 + +# overridden in configDetails.Labels +QUX=quz_from_label_file_2 diff --git a/loader/full-example.yml b/loader/full-example.yml index 2f7b8c43..944b2d47 100644 --- a/loader/full-example.yml +++ b/loader/full-example.yml @@ -210,6 +210,10 @@ services: # - "com.example.number=42" # - "com.example.empty-label" + label_file: + - ./example1.label + - ./example2.label + links: - db - db:database diff --git a/loader/full-struct_test.go b/loader/full-struct_test.go index 7e8ac9e4..25ee7b3c 100644 --- a/loader/full-struct_test.go +++ b/loader/full-struct_test.go @@ -217,10 +217,24 @@ func services(workingDir, homeDir string) types.Services { Ipc: "host", Uts: "host", Labels: map[string]string{ + "FOO": "foo_from_label_file", + "BAR": "bar_from_label_file_2", + "BAZ": "baz_from_label_file", + "QUX": "quz_from_label_file_2", + "LABEL.WITH.DOT": "ok", + "LABEL_WITH_UNDERSCORE": "ok", "com.example.description": "Accounting webapp", "com.example.number": "42", "com.example.empty-label": "", }, + LabelFiles: []types.LabelFile{ + { + Path: filepath.Join(workingDir, "example1.label"), + }, + { + Path: filepath.Join(workingDir, "example2.label"), + }, + }, Links: []string{ "db", "db:database", @@ -770,9 +784,18 @@ services: image: redis ipc: host labels: + BAR: bar_from_label_file_2 + BAZ: baz_from_label_file + FOO: foo_from_label_file + LABEL.WITH.DOT: ok + LABEL_WITH_UNDERSCORE: ok + QUX: quz_from_label_file_2 com.example.description: Accounting webapp com.example.empty-label: "" com.example.number: "42" + label_file: + - %s + - %s links: - db - db:database @@ -1058,6 +1081,8 @@ x-nested: filepath.Join(workingDir, "bar"), filepath.Join(workingDir, "example1.env"), filepath.Join(workingDir, "example2.env"), + filepath.Join(workingDir, "example1.label"), + filepath.Join(workingDir, "example2.label"), workingDir, filepath.Join(workingDir, "static"), filepath.Join(homeDir, "configs"), @@ -1382,10 +1407,20 @@ func fullExampleJSON(workingDir, homeDir string) string { "image": "redis", "ipc": "host", "labels": { + "BAR": "bar_from_label_file_2", + "BAZ": "baz_from_label_file", + "FOO": "foo_from_label_file", + "LABEL.WITH.DOT": "ok", + "LABEL_WITH_UNDERSCORE": "ok", + "QUX": "quz_from_label_file_2", "com.example.description": "Accounting webapp", "com.example.empty-label": "", "com.example.number": "42" }, + "label_file": [ + "%s", + "%s" + ], "links": [ "db", "db:database", @@ -1707,6 +1742,8 @@ func fullExampleJSON(workingDir, homeDir string) string { toPath(workingDir, "bar"), toPath(workingDir, "example1.env"), toPath(workingDir, "example2.env"), + toPath(workingDir, "example1.label"), + toPath(workingDir, "example2.label"), toPath(workingDir), toPath(workingDir, "static"), toPath(homeDir, "configs"), diff --git a/loader/loader_test.go b/loader/loader_test.go index 85fb40ed..11feaccc 100644 --- a/loader/loader_test.go +++ b/loader/loader_test.go @@ -2332,7 +2332,7 @@ func TestLoadServiceWithLabelFile(t *testing.T) { "test": { Name: "test", LabelFiles: []types.LabelFile{ - {Path: file.Name(), Required: true}, + {Path: file.Name()}, }, }, }, @@ -2344,6 +2344,21 @@ func TestLoadServiceWithLabelFile(t *testing.T) { assert.Equal(t, "MY_VALUE", service.Labels["MY_LABEL"]) } +func TestLoadServiceWithLabelFile_NotExists(t *testing.T) { + p := &types.Project{ + Services: types.Services{ + "test": { + Name: "test", + LabelFiles: []types.LabelFile{ + {Path: "test"}, + }, + }, + }, + } + p, err := p.WithServicesLabelsResolved(false) + assert.Error(t, err, "label file test not found: stat test: no such file or directory") +} + func TestLoadNoSSHInBuildConfig(t *testing.T) { actual, err := loadYAML(` name: load-no-ssh-in-build-config diff --git a/types/labelfile.go b/types/labelfile.go index ff0980e0..b5c78ad4 100644 --- a/types/labelfile.go +++ b/types/labelfile.go @@ -21,27 +21,16 @@ import ( ) type LabelFile struct { - Path string `yaml:"path,omitempty" json:"path,omitempty"` - Required bool `yaml:"required" json:"required"` - Format string `yaml:"format,omitempty" json:"format,omitempty"` + Path string `yaml:"path,omitempty" json:"path,omitempty"` + Format string `yaml:"format,omitempty" json:"format,omitempty"` } // MarshalYAML makes LabelFile implement yaml.Marshaler func (e LabelFile) MarshalYAML() (interface{}, error) { - if e.Required { - return e.Path, nil - } - return map[string]any{ - "path": e.Path, - "required": e.Required, - }, nil + return e.Path, nil } // MarshalJSON makes LabelFile implement json.Marshaler func (e *LabelFile) MarshalJSON() ([]byte, error) { - if e.Required { - return json.Marshal(e.Path) - } - // Pass as a value to avoid re-entering this method and use the default implementation - return json.Marshal(*e) + return json.Marshal(e.Path) } diff --git a/types/project.go b/types/project.go index 2c087aae..9eda922a 100644 --- a/types/project.go +++ b/types/project.go @@ -714,10 +714,7 @@ func loadEnvFile(envFile EnvFile, resolve dotenv.LookupFn) (Mapping, error) { func loadLabelFile(labelFile LabelFile, resolve dotenv.LookupFn) (Mapping, error) { if _, err := os.Stat(labelFile.Path); os.IsNotExist(err) { - if labelFile.Required { - return nil, fmt.Errorf("label file %s not found: %w", labelFile.Path, err) - } - return nil, nil + return nil, fmt.Errorf("label file %s not found: %w", labelFile.Path, err) } return loadMappingFile(labelFile.Path, labelFile.Format, resolve) From 88117ef3f5340f6d1f674ad6dad9f90d5deffbe9 Mon Sep 17 00:00:00 2001 From: Suleiman Dibirov Date: Wed, 27 Nov 2024 12:59:54 +0200 Subject: [PATCH 06/10] fix loader_test.go Signed-off-by: Suleiman Dibirov --- loader/loader_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader/loader_test.go b/loader/loader_test.go index 11feaccc..86cc666d 100644 --- a/loader/loader_test.go +++ b/loader/loader_test.go @@ -2356,7 +2356,7 @@ func TestLoadServiceWithLabelFile_NotExists(t *testing.T) { }, } p, err := p.WithServicesLabelsResolved(false) - assert.Error(t, err, "label file test not found: stat test: no such file or directory") + assert.ErrorContains(t, err, "label file test not found") } func TestLoadNoSSHInBuildConfig(t *testing.T) { From eeda72744725abed7d51f154cac19619f8ccbbf2 Mon Sep 17 00:00:00 2001 From: Suleiman Dibirov Date: Wed, 27 Nov 2024 16:09:21 +0200 Subject: [PATCH 07/10] fix compose-spec.json Signed-off-by: Suleiman Dibirov --- schema/compose-spec.json | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/schema/compose-spec.json b/schema/compose-spec.json index 4768fd99..29885e7c 100644 --- a/schema/compose-spec.json +++ b/schema/compose-spec.json @@ -887,27 +887,7 @@ {"type": "string"}, { "type": "array", - "items": { - "oneOf": [ - {"type": "string"}, - { - "type": "object", - "additionalProperties": false, - "properties": { - "path": { - "type": "string" - }, - "required": { - "type": ["boolean", "string"], - "default": true - } - }, - "required": [ - "path" - ] - } - ] - } + "items": {"type": "string"} } ] }, From 983142780b975d3a3a219a4547dc2b12f6f1d920 Mon Sep 17 00:00:00 2001 From: Suleiman Dibirov Date: Wed, 27 Nov 2024 18:19:26 +0200 Subject: [PATCH 08/10] convert LabelFile into slice of strings Signed-off-by: Suleiman Dibirov --- loader/full-struct_test.go | 10 +++---- loader/loader_test.go | 8 +++--- override/uncity.go | 14 ---------- paths/resolve.go | 2 +- transform/canonical.go | 1 - transform/labelfile.go | 55 -------------------------------------- types/derived.gen.go | 6 +++-- types/project.go | 8 +++--- types/types.go | 2 +- 9 files changed, 17 insertions(+), 89 deletions(-) delete mode 100644 transform/labelfile.go diff --git a/loader/full-struct_test.go b/loader/full-struct_test.go index 25ee7b3c..187eea59 100644 --- a/loader/full-struct_test.go +++ b/loader/full-struct_test.go @@ -227,13 +227,9 @@ func services(workingDir, homeDir string) types.Services { "com.example.number": "42", "com.example.empty-label": "", }, - LabelFiles: []types.LabelFile{ - { - Path: filepath.Join(workingDir, "example1.label"), - }, - { - Path: filepath.Join(workingDir, "example2.label"), - }, + LabelFiles: []string{ + filepath.Join(workingDir, "example1.label"), + filepath.Join(workingDir, "example2.label"), }, Links: []string{ "db", diff --git a/loader/loader_test.go b/loader/loader_test.go index 86cc666d..8f062c00 100644 --- a/loader/loader_test.go +++ b/loader/loader_test.go @@ -2331,8 +2331,8 @@ func TestLoadServiceWithLabelFile(t *testing.T) { Services: types.Services{ "test": { Name: "test", - LabelFiles: []types.LabelFile{ - {Path: file.Name()}, + LabelFiles: []string{ + file.Name(), }, }, }, @@ -2349,8 +2349,8 @@ func TestLoadServiceWithLabelFile_NotExists(t *testing.T) { Services: types.Services{ "test": { Name: "test", - LabelFiles: []types.LabelFile{ - {Path: "test"}, + LabelFiles: []string{ + "test", }, }, }, diff --git a/override/uncity.go b/override/uncity.go index 6b5b8ae9..3b0c63d3 100644 --- a/override/uncity.go +++ b/override/uncity.go @@ -51,7 +51,6 @@ func init() { unique["services.*.env_file"] = envFileIndexer unique["services.*.expose"] = exposeIndexer unique["services.*.labels"] = keyValueIndexer - unique["services.*.label_file"] = labelFileIndexer unique["services.*.links"] = keyValueIndexer unique["services.*.networks.*.aliases"] = keyValueIndexer unique["services.*.networks.*.link_local_ips"] = keyValueIndexer @@ -228,16 +227,3 @@ func envFileIndexer(y any, p tree.Path) (string, error) { } return "", nil } - -func labelFileIndexer(y any, p tree.Path) (string, error) { - switch value := y.(type) { - case string: - return value, nil - case map[string]any: - if pathValue, ok := value["path"]; ok { - return pathValue.(string), nil - } - return "", fmt.Errorf("label path attribute %s is missing", p) - } - return "", nil -} diff --git a/paths/resolve.go b/paths/resolve.go index 0569e658..8bab0b43 100644 --- a/paths/resolve.go +++ b/paths/resolve.go @@ -37,7 +37,7 @@ func ResolveRelativePaths(project map[string]any, base string, remotes []RemoteR "services.*.build.context": r.absContextPath, "services.*.build.additional_contexts.*": r.absContextPath, "services.*.env_file.*.path": r.absPath, - "services.*.label_file.*.path": r.absPath, + "services.*.label_file.*": r.absPath, "services.*.extends.file": r.absExtendsPath, "services.*.develop.watch.*.path": r.absSymbolicLink, "services.*.volumes.*": r.absVolumeMount, diff --git a/transform/canonical.go b/transform/canonical.go index 27d03810..ff5bb37d 100644 --- a/transform/canonical.go +++ b/transform/canonical.go @@ -30,7 +30,6 @@ func init() { transformers["services.*.build.additional_contexts"] = transformKeyValue transformers["services.*.depends_on"] = transformDependsOn transformers["services.*.env_file"] = transformEnvFile - transformers["services.*.label_file"] = transformLabelFile transformers["services.*.extends"] = transformExtends transformers["services.*.networks"] = transformServiceNetworks transformers["services.*.volumes.*"] = transformVolumeMount diff --git a/transform/labelfile.go b/transform/labelfile.go deleted file mode 100644 index db8470df..00000000 --- a/transform/labelfile.go +++ /dev/null @@ -1,55 +0,0 @@ -/* - Copyright 2020 The Compose Specification Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package transform - -import ( - "fmt" - - "github.com/compose-spec/compose-go/v2/tree" -) - -func transformLabelFile(data any, p tree.Path, _ bool) (any, error) { - switch v := data.(type) { - case string: - return []any{ - transformLabelFileValue(v), - }, nil - case []any: - for i, e := range v { - v[i] = transformLabelFileValue(e) - } - return v, nil - default: - return nil, fmt.Errorf("%s: invalid type %T for label_file", p, v) - } -} - -func transformLabelFileValue(data any) any { - switch v := data.(type) { - case string: - return map[string]any{ - "path": v, - "required": true, - } - case map[string]any: - if _, ok := v["required"]; !ok { - v["required"] = true - } - return v - } - return nil -} diff --git a/types/derived.gen.go b/types/derived.gen.go index 3f8d92b7..295a8514 100644 --- a/types/derived.gen.go +++ b/types/derived.gen.go @@ -493,13 +493,13 @@ func deriveDeepCopyService(dst, src *ServiceConfig) { if cap(dst.LabelFiles) >= len(src.LabelFiles) { dst.LabelFiles = (dst.LabelFiles)[:len(src.LabelFiles)] } else { - dst.LabelFiles = make([]LabelFile, len(src.LabelFiles)) + dst.LabelFiles = make([]string, len(src.LabelFiles)) } } else if len(src.LabelFiles) < len(dst.LabelFiles) { dst.LabelFiles = (dst.LabelFiles)[:len(src.LabelFiles)] } } else { - dst.LabelFiles = make([]LabelFile, len(src.LabelFiles)) + dst.LabelFiles = make([]string, len(src.LabelFiles)) } copy(dst.LabelFiles, src.LabelFiles) } @@ -1503,6 +1503,7 @@ func deriveDeepCopy_26(dst, src *SecretConfig) { dst.File = src.File dst.Environment = src.Environment dst.Content = src.Content + dst.marshallContent = src.marshallContent dst.External = src.External if src.Labels != nil { dst.Labels = make(map[string]string, len(src.Labels)) @@ -1532,6 +1533,7 @@ func deriveDeepCopy_27(dst, src *ConfigObjConfig) { dst.File = src.File dst.Environment = src.Environment dst.Content = src.Content + dst.marshallContent = src.marshallContent dst.External = src.External if src.Labels != nil { dst.Labels = make(map[string]string, len(src.Labels)) diff --git a/types/project.go b/types/project.go index 9eda922a..d8005c0a 100644 --- a/types/project.go +++ b/types/project.go @@ -712,12 +712,12 @@ func loadEnvFile(envFile EnvFile, resolve dotenv.LookupFn) (Mapping, error) { return loadMappingFile(envFile.Path, envFile.Format, resolve) } -func loadLabelFile(labelFile LabelFile, resolve dotenv.LookupFn) (Mapping, error) { - if _, err := os.Stat(labelFile.Path); os.IsNotExist(err) { - return nil, fmt.Errorf("label file %s not found: %w", labelFile.Path, err) +func loadLabelFile(labelFile string, resolve dotenv.LookupFn) (Mapping, error) { + if _, err := os.Stat(labelFile); os.IsNotExist(err) { + return nil, fmt.Errorf("label file %s not found: %w", labelFile, err) } - return loadMappingFile(labelFile.Path, labelFile.Format, resolve) + return loadMappingFile(labelFile, "", resolve) } func loadMappingFile(path string, format string, resolve dotenv.LookupFn) (Mapping, error) { diff --git a/types/types.go b/types/types.go index f09f6029..5da8b853 100644 --- a/types/types.go +++ b/types/types.go @@ -89,7 +89,7 @@ type ServiceConfig struct { Ipc string `yaml:"ipc,omitempty" json:"ipc,omitempty"` Isolation string `yaml:"isolation,omitempty" json:"isolation,omitempty"` Labels Labels `yaml:"labels,omitempty" json:"labels,omitempty"` - LabelFiles []LabelFile `yaml:"label_file,omitempty" json:"label_file,omitempty"` + LabelFiles []string `yaml:"label_file,omitempty" json:"label_file,omitempty"` CustomLabels Labels `yaml:"-" json:"-"` Links []string `yaml:"links,omitempty" json:"links,omitempty"` Logging *LoggingConfig `yaml:"logging,omitempty" json:"logging,omitempty"` From 381733bef59199081365c0ea382455b9d60b4872 Mon Sep 17 00:00:00 2001 From: Suleiman Dibirov Date: Wed, 27 Nov 2024 19:59:17 +0200 Subject: [PATCH 09/10] removed labelfile.go Signed-off-by: Suleiman Dibirov --- types/labelfile.go | 36 ------------------------------------ 1 file changed, 36 deletions(-) delete mode 100644 types/labelfile.go diff --git a/types/labelfile.go b/types/labelfile.go deleted file mode 100644 index b5c78ad4..00000000 --- a/types/labelfile.go +++ /dev/null @@ -1,36 +0,0 @@ -/* - Copyright 2020 The Compose Specification Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package types - -import ( - "encoding/json" -) - -type LabelFile struct { - Path string `yaml:"path,omitempty" json:"path,omitempty"` - Format string `yaml:"format,omitempty" json:"format,omitempty"` -} - -// MarshalYAML makes LabelFile implement yaml.Marshaler -func (e LabelFile) MarshalYAML() (interface{}, error) { - return e.Path, nil -} - -// MarshalJSON makes LabelFile implement json.Marshaler -func (e *LabelFile) MarshalJSON() ([]byte, error) { - return json.Marshal(e.Path) -} From ccf14e4ca4e86ca5748bcfdb86602de35a70acea Mon Sep 17 00:00:00 2001 From: Suleiman Dibirov Date: Wed, 27 Nov 2024 20:31:42 +0200 Subject: [PATCH 10/10] fix compose-spec.json Signed-off-by: Suleiman Dibirov --- schema/compose-spec.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/schema/compose-spec.json b/schema/compose-spec.json index 29885e7c..a0310c59 100644 --- a/schema/compose-spec.json +++ b/schema/compose-spec.json @@ -867,6 +867,9 @@ "path": { "type": "string" }, + "format": { + "type": "string" + }, "required": { "type": ["boolean", "string"], "default": true