diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d3ce2cab..308b1864 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: test: strategy: matrix: - go-version: ['1.22', '1.21'] + go-version: ['1.23', '1.24'] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} timeout-minutes: 10 @@ -33,7 +33,7 @@ jobs: cache: true - uses: golangci/golangci-lint-action@v6 with: - version: v1.55.2 + version: v1.64.2 args: --verbose skip-cache: true - name: Test diff --git a/.golangci.yml b/.golangci.yml index edea0803..ac481281 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -4,19 +4,37 @@ issues: linters: disable-all: true enable: - - errorlint - - gocritic - - gofmt - - goimports - - gomodguard - - gosimple - - govet - - ineffassign - - misspell - - nakedret - - revive - - testifylint + - copyloopvar + - depguard + - errcheck + - errorlint + - gocritic + - gocyclo + - gofumpt + - goimports + - gomodguard + - revive + - gosimple + - govet + - ineffassign + - lll + - misspell + - nakedret + - nolintlint + - revive + - staticcheck + - testifylint + - typecheck + - unconvert + - unparam + - unused linters-settings: + depguard: + rules: + all: + deny: + - pkg: gopkg.in/yaml.v2 + desc: 'compose-go uses yaml.v3' gocritic: # Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks. # Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags". @@ -35,6 +53,8 @@ linters-settings: recommendations: - errors - fmt + lll: + line-length: 200 testifylint: disable: - float-compare diff --git a/ci/Dockerfile b/ci/Dockerfile index 6d87e122..dedd69b6 100644 --- a/ci/Dockerfile +++ b/ci/Dockerfile @@ -12,11 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM golang:1.21 +FROM golang:1.23 WORKDIR /go/src -ARG GOLANGCILINT_VERSION=v1.55.2 +ARG GOLANGCILINT_VERSION=v1.64.5 RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCILINT_VERSION} RUN go install github.com/kunalkushwaha/ltag@latest && rm -rf /go/src/github.com/kunalkushwaha diff --git a/cli/options.go b/cli/options.go index 162e1ea7..091a2aae 100644 --- a/cli/options.go +++ b/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/cli/options_test.go b/cli/options_test.go index 5dbba5d6..9f03587d 100644 --- a/cli/options_test.go +++ b/cli/options_test.go @@ -167,8 +167,9 @@ func TestProjectName(t *testing.T) { }) t.Run("by COMPOSE_PROJECT_NAME", func(t *testing.T) { - os.Setenv("COMPOSE_PROJECT_NAME", "my_project_from_env") //nolint:errcheck - defer os.Unsetenv("COMPOSE_PROJECT_NAME") //nolint:errcheck + err := os.Setenv("COMPOSE_PROJECT_NAME", "my_project_from_env") + assert.NilError(t, err) + defer os.Unsetenv("COMPOSE_PROJECT_NAME") opts, err := NewProjectOptions([]string{"testdata/simple/compose.yaml"}, WithOsEnv) assert.NilError(t, err) p, err := ProjectFromOptions(context.TODO(), opts) @@ -254,7 +255,7 @@ services: os.Stdin = originalStdin }() - w.WriteString(composeData) + _, _ = w.WriteString(composeData) w.Close() os.Stdin = r diff --git a/dotenv/godotenv.go b/dotenv/godotenv.go index 76907249..215e86f7 100644 --- a/dotenv/godotenv.go +++ b/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/dotenv/godotenv_test.go b/dotenv/godotenv_test.go index 9e71b7a2..16199dd4 100644 --- a/dotenv/godotenv_test.go +++ b/dotenv/godotenv_test.go @@ -269,7 +269,6 @@ func TestExpanding(t *testing.T) { } }) } - } func TestVariableStringValueSeparator(t *testing.T) { @@ -470,7 +469,7 @@ func TestLinesToIgnore(t *testing.T) { for n, c := range cases { t.Run(n, func(t *testing.T) { - got := string(newParser().getStatementStart(c.input)) + got := newParser().getStatementStart(c.input) if got != c.want { t.Errorf("Expected:\t %q\nGot:\t %q", c.want, got) } @@ -718,7 +717,7 @@ func TestLoadWithFormat(t *testing.T) { "ZOT": "QIX", } - custom := func(r io.Reader, f string, lookup func(key string) (string, bool)) (map[string]string, error) { + custom := func(r io.Reader, _ string, lookup func(key string) (string, bool)) (map[string]string, error) { vars := map[string]string{} scanner := bufio.NewScanner(r) for scanner.Scan() { diff --git a/dotenv/godotenv_var_expansion_test.go b/dotenv/godotenv_var_expansion_test.go index f80fe7fd..5b031e1c 100644 --- a/dotenv/godotenv_var_expansion_test.go +++ b/dotenv/godotenv_var_expansion_test.go @@ -14,7 +14,7 @@ var envMap = map[string]string{ "TEST_VAR": "Test Value", } -var notFoundLookup = func(s string) (string, bool) { +var notFoundLookup = func(_ string) (string, bool) { return "", false } @@ -45,7 +45,7 @@ func TestExpandIfEmptyOrUnset(t *testing.T) { t.Run(expected.name, func(t *testing.T) { result, err := expandVariables(expected.input, envMap, notFoundLookup) require.NoError(t, err) - assert.Equal(t, result, expected.result) + assert.Equal(t, expected.result, result) }) } } @@ -77,7 +77,7 @@ func TestExpandIfUnset(t *testing.T) { t.Run(expected.name, func(t *testing.T) { result, err := expandVariables(expected.input, envMap, notFoundLookup) require.NoError(t, err) - assert.Equal(t, result, expected.result) + assert.Equal(t, expected.result, result) }) } } diff --git a/dotenv/parser.go b/dotenv/parser.go index 85dda738..2db7b907 100644 --- a/dotenv/parser.go +++ b/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/dotenv/parser_test.go b/dotenv/parser_test.go index 54580e2a..9b27e1b0 100644 --- a/dotenv/parser_test.go +++ b/dotenv/parser_test.go @@ -39,7 +39,6 @@ func TestParseBytes(t *testing.T) { func TestParseVariable(t *testing.T) { err := newParser().parse("%!(EXTRA string)=foo", map[string]string{}, nil) assert.Error(t, err, "line 1: unexpected character \"%\" in variable name \"%!(EXTRA string)=foo\"") - } func TestMemoryExplosion(t *testing.T) { diff --git a/go.mod b/go.mod index c5d21185..082bb2f1 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/compose-spec/compose-go/v2 -go 1.21 +go 1.23 require ( github.com/distribution/reference v0.5.0 diff --git a/graph/graph_test.go b/graph/graph_test.go index 1df41a14..ca1d784b 100644 --- a/graph/graph_test.go +++ b/graph/graph_test.go @@ -60,7 +60,7 @@ func TestTraversalWithMultipleParents(t *testing.T) { done <- struct{}{} }() - err := InDependencyOrder(ctx, &project, func(ctx context.Context, name string, _ types.ServiceConfig) error { + err := InDependencyOrder(ctx, &project, func(_ context.Context, name string, _ types.ServiceConfig) error { svc <- name return nil }) @@ -80,7 +80,7 @@ func TestInDependencyUpCommandOrder(t *testing.T) { var order []string result, err := CollectInDependencyOrder(ctx, exampleProject(), - func(ctx context.Context, name string, _ types.ServiceConfig) (string, error) { + func(_ context.Context, name string, _ types.ServiceConfig) (string, error) { order = append(order, name) return name, nil }, WithMaxConcurrency(10)) @@ -98,7 +98,7 @@ func TestInDependencyReverseDownCommandOrder(t *testing.T) { t.Cleanup(cancel) var order []string - fn := func(ctx context.Context, name string, _ types.ServiceConfig) error { + fn := func(_ context.Context, name string, _ types.ServiceConfig) error { order = append(order, name) return nil } @@ -369,7 +369,7 @@ func TestWith_RootNodesAndUp(t *testing.T) { expected.AddAll("C", "G", "D", "F") var visited []string - gt := newTraversal(func(ctx context.Context, name string, service types.ServiceConfig) (any, error) { + gt := newTraversal(func(_ context.Context, name string, _ types.ServiceConfig) (any, error) { mx.Lock() defer mx.Unlock() visited = append(visited, name) diff --git a/graph/traversal.go b/graph/traversal.go index de85d1fc..f0ee6c09 100644 --- a/graph/traversal.go +++ b/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/interpolation/interpolation_test.go b/interpolation/interpolation_test.go index e5056b2f..f9860ca7 100644 --- a/interpolation/interpolation_test.go +++ b/interpolation/interpolation_test.go @@ -98,7 +98,7 @@ func TestInterpolateWithDefaults(t *testing.T) { } func TestValidUnexistentInterpolation(t *testing.T) { - var testcases = []struct { + testcases := []struct { test string expected string errMsg string @@ -154,7 +154,7 @@ func TestValidUnexistentInterpolation(t *testing.T) { } func TestValidExistentInterpolation(t *testing.T) { - var testcases = []struct { + testcases := []struct { test string expected string }{ @@ -215,7 +215,7 @@ func TestInterpolateWithCast(t *testing.T) { } func TestPathMatches(t *testing.T) { - var testcases = []struct { + testcases := []struct { doc string path tree.Path pattern tree.Path diff --git a/loader/extends.go b/loader/extends.go index 4a04654a..f3c8f96f 100644 --- a/loader/extends.go +++ b/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/loader/extends_test.go b/loader/extends_test.go index 6b978e91..943333e2 100644 --- a/loader/extends_test.go +++ b/loader/extends_test.go @@ -64,7 +64,7 @@ services: }, func(options *Options) { options.ResolvePaths = false options.Listeners = []Listener{ - func(event string, metadata map[string]any) { + func(event string, _ map[string]any) { if event == "extends" { extendsCount++ } @@ -238,7 +238,6 @@ services: }) assert.NilError(t, err) assert.Equal(t, len(p.Services["test"].Ports), 1) - } func TestLoadExtendsSameFile(t *testing.T) { @@ -285,7 +284,7 @@ services: options.SkipConsistencyCheck = true options.SetProjectName("project", true) options.Listeners = []Listener{ - func(event string, metadata map[string]any) { + func(event string, _ map[string]any) { if event == "extends" { extendsCount++ } @@ -433,7 +432,7 @@ func TestLoadExtendsListener(t *testing.T) { options.SkipNormalization = true options.ResolvePaths = true options.Listeners = []Listener{ - func(event string, metadata map[string]any) { + func(event string, _ map[string]any) { if event == "extends" { extendsCount++ } @@ -481,7 +480,7 @@ services: options.SkipConsistencyCheck = true options.SetProjectName("project", true) options.Listeners = []Listener{ - func(event string, metadata map[string]any) { + func(event string, _ map[string]any) { if event == "extends" { extendsCount++ } diff --git a/loader/full-struct_test.go b/loader/full-struct_test.go index 187eea59..8c50ef7e 100644 --- a/loader/full-struct_test.go +++ b/loader/full-struct_test.go @@ -167,7 +167,8 @@ func services(workingDir, homeDir string) types.Services { Devices: []types.DeviceMapping{ { Source: "/dev/ttyUSB0", Target: "/dev/ttyUSB0", Permissions: "rwm", - }}, + }, + }, DNS: []string{"8.8.8.8", "9.9.9.9"}, DNSSearch: []string{"dc1.example.com", "dc2.example.com"}, DomainName: "foo.com", diff --git a/loader/include_test.go b/loader/include_test.go index f1827379..dbce5f04 100644 --- a/loader/include_test.go +++ b/loader/include_test.go @@ -160,7 +160,6 @@ services: c := p.Services["c"] assert.Check(t, c.Environment["VAR_NAME"] != nil, "VAR_NAME is not defined in environment") assert.Equal(t, *c.Environment["VAR_NAME"], "value") - } func TestIncludeWithProjectDirectory(t *testing.T) { @@ -181,7 +180,6 @@ func TestIncludeWithProjectDirectory(t *testing.T) { assert.Equal(t, filepath.ToSlash(p.Services["service"].Build.Context), "testdata/subdir") assert.Equal(t, filepath.ToSlash(p.Services["service"].Volumes[0].Source), "testdata/subdir/compose-test-extends-imported.yaml") assert.Equal(t, filepath.ToSlash(p.Services["service"].EnvFiles[0].Path), "testdata/subdir/extra.env") - } func TestNestedIncludeAndExtends(t *testing.T) { @@ -230,10 +228,9 @@ func createFile(t *testing.T, rootDir, content, fileName string) string { return path } -func createFileSubDir(t *testing.T, rootDir, subDir, content, fileName string) string { +func createFileSubDir(t *testing.T, rootDir, subDir, content, fileName string) { subDirPath := filepath.Join(rootDir, subDir) assert.NilError(t, os.Mkdir(subDirPath, 0o700)) path := filepath.Join(subDirPath, fileName) assert.NilError(t, os.WriteFile(path, []byte(content), 0o600)) - return path } diff --git a/loader/loader.go b/loader/loader.go index 0bcef405..322109b1 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -407,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) @@ -524,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 { diff --git a/loader/loader_test.go b/loader/loader_test.go index 9d2d61b7..cd08d1f6 100644 --- a/loader/loader_test.go +++ b/loader/loader_test.go @@ -60,7 +60,8 @@ func buildConfigFiles(yamls []string) []types.ConfigFile { for i, yaml := range yamls { configFiles = append(configFiles, types.ConfigFile{ Filename: fmt.Sprintf("filename%d.yml", i), - Content: []byte(yaml)}) + Content: []byte(yaml), + }) } return configFiles } @@ -107,47 +108,6 @@ networks: - subnet: 172.28.0.0/16 ` -var sampleDict = map[string]interface{}{ - "name": "sample", - "services": map[string]interface{}{ - "foo": map[string]interface{}{ - "image": "busybox", - "networks": map[string]interface{}{"with_me": nil}, - }, - "bar": map[string]interface{}{ - "image": "busybox", - "environment": []interface{}{"FOO=1"}, - "networks": []interface{}{"with_ipam"}, - }, - }, - "volumes": map[string]interface{}{ - "hello": map[string]interface{}{ - "driver": "default", - "driver_opts": map[string]interface{}{ - "beep": "boop", - }, - }, - }, - "networks": map[string]interface{}{ - "default": map[string]interface{}{ - "driver": "bridge", - "driver_opts": map[string]interface{}{ - "beep": "boop", - }, - }, - "with_ipam": map[string]interface{}{ - "ipam": map[string]interface{}{ - "driver": "default", - "config": []interface{}{ - map[string]interface{}{ - "subnet": "172.28.0.0/16", - }, - }, - }, - }, - }, -} - var samplePortsConfig = []types.ServicePortConfig{ { Mode: "ingress", @@ -1014,7 +974,8 @@ services: { Path: "example2.env", Required: false, - }}) + }, + }) assert.DeepEqual(t, configWithEnvFiles.Services["web"].Environment, expectedEnvironmentMap) // Custom behavior removes the `env_file` entries @@ -1660,7 +1621,6 @@ networks: } assert.Check(t, is.DeepEqual(expected, project.Networks)) assert.Check(t, is.Contains(buf.String(), "networks.foo: external.name is deprecated. Please set name and external: true")) - } func TestLoadNetworkInvalidExternalNameAndNameCombination(t *testing.T) { @@ -1879,7 +1839,7 @@ func TestLoadInit(t *testing.T) { booleanTrue := true booleanFalse := false - var testcases = []struct { + testcases := []struct { doc string yaml string init *bool @@ -1914,7 +1874,6 @@ services: }, } for _, testcase := range testcases { - testcase := testcase t.Run(testcase.doc, func(t *testing.T) { config, err := loadYAML(testcase.yaml) assert.NilError(t, err) @@ -2158,7 +2117,8 @@ func TestLoadWithExtends(t *testing.T) { { Path: expectedEnvFilePath, Required: true, - }}, + }, + }, Networks: map[string]*types.ServiceNetworkConfig{"default": nil}, Volumes: []types.ServiceVolumeConfig{{ Type: "bind", @@ -2251,6 +2211,7 @@ services: `) assert.NilError(t, err) } + func TestServiceDeviceRequestWithoutCountAndDeviceIdsType(t *testing.T) { project, err := loadYAML(` name: service-device-request-count-type @@ -2446,7 +2407,7 @@ func TestLoadServiceWithLabelFile_NotExists(t *testing.T) { }, }, } - p, err := p.WithServicesLabelsResolved(false) + _, err := p.WithServicesLabelsResolved(false) assert.ErrorContains(t, err, "label file test not found") } @@ -2701,7 +2662,6 @@ func TestDeviceWriteBps(t *testing.T) { }, }, }) - } func TestInvalidProjectNameType(t *testing.T) { @@ -3328,13 +3288,13 @@ func TestLoadProjectName(t *testing.T) { }{ { name: "default", - options: func(o *Options) {}, + options: func(_ *Options) {}, wantErr: "project name must not be empty", }, { name: "project name from environment", env: map[string]string{"COMPOSE_PROJECT_NAME": projectName}, - options: func(o *Options) {}, + options: func(_ *Options) {}, wantErr: "project name must not be empty", }, { @@ -3539,12 +3499,14 @@ secrets: "config": { Environment: "GA", Content: "BU", - }}, cmpopts.IgnoreUnexported(types.ConfigObjConfig{})) + }, + }, cmpopts.IgnoreUnexported(types.ConfigObjConfig{})) assert.DeepEqual(t, config.Secrets, types.Secrets{ "secret": { Environment: "MEU", Content: "Shadoks", - }}, cmpopts.IgnoreUnexported(types.SecretConfig{})) + }, + }, cmpopts.IgnoreUnexported(types.SecretConfig{})) } func TestLoadDeviceMapping(t *testing.T) { diff --git a/loader/loader_windows_test.go b/loader/loader_windows_test.go index ce98247b..3b3a4424 100644 --- a/loader/loader_windows_test.go +++ b/loader/loader_windows_test.go @@ -24,7 +24,7 @@ import ( ) func TestConvertWindowsVolumePath(t *testing.T) { - var testcases = []struct { + testcases := []struct { windowsPath string expectedConvertedPath string }{ diff --git a/loader/loader_yaml_test.go b/loader/loader_yaml_test.go index 0f084f3b..1ea45660 100644 --- a/loader/loader_yaml_test.go +++ b/loader/loader_yaml_test.go @@ -27,7 +27,8 @@ import ( func TestParseYAMLFiles(t *testing.T) { model, err := loadYamlModel(context.TODO(), types.ConfigDetails{ ConfigFiles: []types.ConfigFile{ - {Filename: "test.yaml", + { + Filename: "test.yaml", Content: []byte(` services: test: @@ -36,14 +37,18 @@ services: init: true `), }, - {Filename: "override.yaml", + { + Filename: "override.yaml", Content: []byte(` services: test: image: bar command: echo world init: false -`)}}}, &Options{}, &cycleTracker{}, nil) +`), + }, + }, + }, &Options{}, &cycleTracker{}, nil) assert.NilError(t, err) assert.DeepEqual(t, model, map[string]interface{}{ "services": map[string]interface{}{ @@ -59,7 +64,8 @@ services: func TestParseYAMLFilesMergeOverride(t *testing.T) { model, err := loadYamlModel(context.TODO(), types.ConfigDetails{ ConfigFiles: []types.ConfigFile{ - {Filename: "override.yaml", + { + Filename: "override.yaml", Content: []byte(` services: base: @@ -80,8 +86,10 @@ configs: credentials: content: | dummy value -`)}, - }}, &Options{}, &cycleTracker{}, nil) +`), + }, + }, + }, &Options{}, &cycleTracker{}, nil) assert.NilError(t, err) assert.DeepEqual(t, model, map[string]interface{}{ "configs": map[string]interface{}{"credentials": map[string]interface{}{"content": string("dummy value\n")}}, diff --git a/loader/omitEmpty.go b/loader/omitEmpty.go index bc1cb1a5..eef6be8c 100644 --- a/loader/omitEmpty.go +++ b/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/loader/paths.go b/loader/paths.go index 102ff036..c03126a8 100644 --- a/loader/paths.go +++ b/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/loader/paths_test.go b/loader/paths_test.go index 28ca2448..33e57697 100644 --- a/loader/paths_test.go +++ b/loader/paths_test.go @@ -48,7 +48,6 @@ func TestResolveComposeFilePaths(t *testing.T) { } func TestResolveBuildContextPaths(t *testing.T) { - yaml := ` name: test-resolve-build-context-paths services: diff --git a/loader/validate.go b/loader/validate.go index 0feb2a96..aa570888 100644 --- a/loader/validate.go +++ b/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/loader/validate_test.go b/loader/validate_test.go index 0575a02e..5c1aa958 100644 --- a/loader/validate_test.go +++ b/loader/validate_test.go @@ -301,7 +301,6 @@ func TestValidateWatch(t *testing.T) { } err := checkConsistency(&project) assert.NilError(t, err) - }) t.Run("watch missing target for sync action", func(t *testing.T) { diff --git a/paths/windows_path.go b/paths/windows_path.go index 746aefd1..968d8ed7 100644 --- a/paths/windows_path.go +++ b/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/template/template.go b/template/template.go index d9483cbd..2d48188d 100644 --- a/template/template.go +++ b/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/template/template_test.go b/template/template_test.go index c52fdcd9..2309ed23 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -218,7 +218,6 @@ func TestInterpolationExternalInterference(t *testing.T) { }, } for i, tc := range testCases { - tc := tc t.Run(fmt.Sprintf("Interpolation Should not be impacted by outer text: %d", i), func(t *testing.T) { result, err := Substitute(tc.template, defaultMapping) assert.NilError(t, err) diff --git a/template/variables_test.go b/template/variables_test.go index 7239b016..b2b18f64 100644 --- a/template/variables_test.go +++ b/template/variables_test.go @@ -193,7 +193,6 @@ func TestExtractVariables(t *testing.T) { }, } for _, tc := range testCases { - tc := tc t.Run(tc.name, func(t *testing.T) { actual := ExtractVariables(tc.dict, DefaultPattern) assert.Check(t, is.DeepEqual(actual, tc.expected)) diff --git a/transform/devices.go b/transform/devices.go index 3ce7fa00..5de0613c 100644 --- a/transform/devices.go +++ b/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/tree/path_test.go b/tree/path_test.go index dbb25a42..638d31d0 100644 --- a/tree/path_test.go +++ b/tree/path_test.go @@ -24,7 +24,7 @@ import ( ) func TestPathMatches(t *testing.T) { - var testcases = []struct { + testcases := []struct { doc string path Path pattern Path diff --git a/types/config.go b/types/config.go index d73d2b9f..4c0d00a8 100644 --- a/types/config.go +++ b/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/types/labels.go b/types/labels.go index 713c28f9..7ea5edc4 100644 --- a/types/labels.go +++ b/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/types/mapping.go b/types/mapping.go index 63f6e58b..87f16314 100644 --- a/types/mapping.go +++ b/types/mapping.go @@ -157,7 +157,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/types/project.go b/types/project.go index d8005c0a..4afdbab8 100644 --- a/types/project.go +++ b/types/project.go @@ -477,7 +477,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 +535,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,7 +725,7 @@ 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 != "" { @@ -746,7 +746,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/types/project_test.go b/types/project_test.go index 040f9360..61c92c19 100644 --- a/types/project_test.go +++ b/types/project_test.go @@ -42,7 +42,6 @@ func Test_ApplyProfiles(t *testing.T) { assert.DeepEqual(t, p.ServiceNames(), []string{"service_1", "service_2", "service_4", "service_5", "service_6"}) assert.DeepEqual(t, p.DisabledServiceNames(), []string{"service_3"}) - } func Test_WithoutUnnecessaryResources(t *testing.T) { @@ -126,7 +125,7 @@ func Test_ForServicesCycle(t *testing.T) { service := p.Services["service_1"] service.Links = []string{"service_2"} p.Services["service_1"] = service - p, err := p.WithSelectedServices([]string{"service_2"}) + _, err := p.WithSelectedServices([]string{"service_2"}) assert.NilError(t, err) } @@ -169,7 +168,7 @@ func makeProject() *Project { func Test_ResolveImages(t *testing.T) { p := makeProject() - resolver := func(named reference.Named) (digest.Digest, error) { + resolver := func(_ reference.Named) (digest.Digest, error) { return "sha256:1234567890123456789012345678901234567890123456789012345678901234", nil } @@ -211,7 +210,7 @@ func Test_ResolveImages(t *testing.T) { func Test_ResolveImages_concurrent(t *testing.T) { const garfield = "sha256:1234567890123456789012345678901234567890123456789012345678901234" - resolver := func(named reference.Named) (digest.Digest, error) { + resolver := func(_ reference.Named) (digest.Digest, error) { return garfield, nil } p := &Project{ @@ -231,7 +230,7 @@ func Test_ResolveImages_concurrent(t *testing.T) { } func Test_ResolveImages_concurrent_interrupted(t *testing.T) { - resolver := func(named reference.Named) (digest.Digest, error) { + resolver := func(_ reference.Named) (digest.Digest, error) { return "", errors.New("something went wrong") } p := Project{ @@ -309,13 +308,13 @@ func TestServicesWithBuild(t *testing.T) { func TestServicesWithExtends(t *testing.T) { p := makeProject() - assert.DeepEqual(t, []string{}, p.ServicesWithExtends()) + assert.Equal(t, 0, len(p.ServicesWithExtends())) service, err := p.GetService("service_1") assert.NilError(t, err) service.Extends = &ExtendsConfig{} p.Services["service_1"] = service - assert.DeepEqual(t, []string{}, p.ServicesWithExtends()) + assert.Equal(t, 0, len(p.ServicesWithExtends())) service.Extends = &ExtendsConfig{ File: ".", diff --git a/types/types.go b/types/types.go index 4c7baa46..4d8b376f 100644 --- a/types/types.go +++ b/types/types.go @@ -479,16 +479,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 +496,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 diff --git a/types/types_test.go b/types/types_test.go index 500f8b6f..527cce31 100644 --- a/types/types_test.go +++ b/types/types_test.go @@ -199,7 +199,7 @@ func TestParsePortConfig(t *testing.T) { } func assertContains(t *testing.T, portConfigs []ServicePortConfig, expected ServicePortConfig) { - var contains = false + contains := false for _, portConfig := range portConfigs { if is.DeepEqual(portConfig, expected)().Success() { contains = true @@ -318,7 +318,6 @@ func TestMarshalServiceEntrypoint(t *testing.T) { } for _, tc := range tcs { - tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() @@ -332,7 +331,6 @@ func TestMarshalServiceEntrypoint(t *testing.T) { assertEqual(t, actualJSON, tc.expectedJSON) }) } - } func TestMarshalBuild_DockerfileInline(t *testing.T) { diff --git a/utils/pathutils.go b/utils/pathutils.go index fd2a635e..211e2999 100644 --- a/utils/pathutils.go +++ b/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/validation/validation.go b/validation/validation.go index 707f247e..793c1930 100644 --- a/validation/validation.go +++ b/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