diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9abc65f..a1e3ae2 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,16 +1,12 @@ version: 2 updates: - # Go modules. Several dependencies are deliberately held at their newest - # go1.18-compatible release. Reject Dependabot PRs that move them until - # the go directive is raised: + # Go modules. Nothing is held back any more: the go directive is 1.25.0, + # which is what x/net, x/text and gomega each require, so Dependabot PRs + # for them can be taken on their merits. # - # golang.org/x/net v0.35.0 - newer requires go 1.23 - # golang.org/x/text v0.22.0 - newer requires go 1.23 - # github.com/onsi/gomega v1.27.10 - newer requires go 1.25 - # - # gomega is the trap: it is a direct requirement, so Dependabot proposes - # major jumps that look routine. v1.42.1 declares `go 1.25.0` and would - # silently break the declared 1.18 floor. + # A dependency that raises its own go directive above this floor is still + # a breaking change for consumers, so check the `go` line in any proposed + # bump before merging it. - package-ecosystem: gomod directory: "/" schedule: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2acb997..a2a4a22 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ permissions: contents: read jobs: - # Keeps the declared go 1.18 floor honest. Compile only: this leg runs an + # Keeps the declared go 1.25 floor honest. Compile only: this leg runs an # old toolchain deliberately, so no test, lint, or vulnerability scan runs # here — they would report that toolchain's stdlib advisories, which say # nothing about this code. @@ -24,7 +24,7 @@ jobs: persist-credentials: false - uses: actions/setup-go@v7 with: - go-version: "1.18" + go-version: "1.25" - run: go build ./... test: diff --git a/.modrotignore b/.modrotignore index abc28f6..ae0a409 100644 --- a/.modrotignore +++ b/.modrotignore @@ -3,18 +3,7 @@ # Every entry needs a reason and a condition for removing it again. This # list is not a way to silence the gate; it exists so the gate stays # meaningful for dependencies we can actually act on. - -# gopkg.in/yaml.v3 — go-yaml/yaml, archived 2025-04-01. # -# Indirect and test-only: the sole path to it is -# github.com/onsi/gomega/matchers, so it is never part of a consumer's -# build of this library. Nothing imports it from non-test code here. -# -# Not removable by upgrading: every published gomega release, current -# ones included, requires gopkg.in/yaml.v3, so raising the go directive -# would not drop it either. Only dropping gomega from the test suite -# would, which is a larger change than a dependency release should make. -# -# Revisit if gomega ever drops the dependency, or if the test suite -# moves off gomega. -gopkg.in/yaml.v3 +# Currently empty. gopkg.in/yaml.v3 was exempted here while gomega +# required it; gomega v1.42.1 moved to the maintained go.yaml.in/yaml/v3 +# fork, which is the removal condition that entry recorded. diff --git a/Makefile b/Makefile index 3f021ae..4156228 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,8 @@ GH_ASSETS := SECURITY_SCANS := govulncheck gosec gitleaks +FUZZTIME ?= 30s + # Release notes come from the changelog.d fragments: `make tag` embeds the # assembled notes in the annotated tag body and `make publish` reads them # back with --notes-from-tag. Nothing is hand-edited at release time. @@ -39,6 +41,13 @@ test: ## Run tests test-race: ## Run tests with the race detector go test -race ./... +# `make test` already runs the seed corpus, as Go fuzz targets double as +# ordinary tests. This target is for actually searching: raise FUZZTIME +# when chasing something. +.PHONY: fuzz +fuzz: ## Fuzz the VCAP_APPLICATION/VCAP_SERVICES parser (FUZZTIME=30s) + go test -run '^$$' -fuzz FuzzNew -fuzztime $(FUZZTIME) . + .PHONY: cover cover: ## Run tests and report coverage go test -coverprofile=coverage.out ./... diff --git a/README.md b/README.md index 9f39828..c422f99 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ ### Requirements -Go 1.18 or later. +Go 1.25 or later. ### Usage diff --git a/changelog.d/0001-file-based-bindings.md b/changelog.d/0001-file-based-bindings.md deleted file mode 100644 index 6721e6b..0000000 --- a/changelog.d/0001-file-based-bindings.md +++ /dev/null @@ -1,2 +0,0 @@ -[Features] -- Service bindings are now read from the file named by `VCAP_SERVICES_FILE_PATH` when Cloud Foundry provides it, so apps with the `file-based-vcap-services` feature enabled work without any application change. That feature exists for bindings too large for an environment variable (RFC-0030); with it enabled Cloud Foundry does not set `VCAP_SERVICES` at all, which previously made `cfenv.Current` fail outright. The `SERVICE_BINDING_ROOT` form of the RFC is not supported — it follows the Kubernetes servicebinding.io layout, which has no faithful translation into the `VCAP_SERVICES` shape. (#28) diff --git a/changelog.d/0001-raise-go-floor.md b/changelog.d/0001-raise-go-floor.md new file mode 100644 index 0000000..8978837 --- /dev/null +++ b/changelog.d/0001-raise-go-floor.md @@ -0,0 +1,10 @@ +[Breaking Changes] +- The minimum Go version is now **1.25** (was 1.18). Consumers on an older toolchain must stay on v1.22.0. The old floor had become what blocked dependency maintenance: every current release of `golang.org/x/net`, `golang.org/x/text` and `gomega` declares a directive above it. (#38) + +[Security Updates] +- `golang.org/x/net` and `golang.org/x/text` moved to current releases, clearing the 16 known vulnerabilities their pinned versions carried between them (15 in `x/net` v0.35.0, 1 in `x/text` v0.22.0). None were reachable from this package's code, so `govulncheck` was always green — but scanners that report on presence rather than reachability were not, and consumers run those. (#38) + +[Chores] +- `gomega` moved to v1.42.1, which depends on the maintained `go.yaml.in/yaml/v3` instead of the archived `gopkg.in/yaml.v3`. The `.modrotignore` exemption that covered the archived module recorded exactly this as its removal condition, and is now gone — the archived-dependency gate runs with no exemptions at all. +- The `copyloopvar` and `intrange` linters now run. They were already enabled in `.golangci.yml` but golangci-lint disabled them at runtime for being newer than the project's go directive. +- Added `make fuzz`, a fuzz target over the `VCAP_APPLICATION` and `VCAP_SERVICES` parsing that `New` performs. Both documents come from the platform rather than the app, so they are the package's untrusted boundary. (#39) diff --git a/fuzz_test.go b/fuzz_test.go new file mode 100644 index 0000000..652fef5 --- /dev/null +++ b/fuzz_test.go @@ -0,0 +1,59 @@ +package cfenv_test + +import ( + "testing" + + cfenv "github.com/cloudfoundry-community/go-cfenv" +) + +// FuzzNew exercises the two documents New parses. Their contents originate +// with the platform and the service brokers bound to an app, not with the +// app itself, so this package is the boundary that has to survive whatever +// arrives. New is expected to return an app or an error for any input, and +// never to panic. +func FuzzNew(f *testing.F) { + f.Add(`{}`, `{}`) + f.Add( + `{"instance_id":"abc","name":"app","limits":{"mem":512,"disk":1024,"fds":16384}}`, + `{"redis":[{"name":"r","label":"redis","tags":["t"],"plan":"free","credentials":{"port":"6379","ports":{"6379/tcp":"32843"}}}]}`, + ) + f.Add(`{}`, `{"user-provided":[{"name":"ups","syslog_drain_url":"syslog://x:514","credentials":{"a":{"b":{"c":1}}}}]}`) + f.Add(`{}`, `{"nfs":[{"volume_mounts":[{"container_dir":"/d","mode":"rw"}]}]}`) + f.Add(`{"limits":null}`, `{"x":[]}`) + f.Add(``, ``) + + f.Fuzz(func(t *testing.T, application, services string) { + app, err := cfenv.New(map[string]string{ + "VCAP_APPLICATION": application, + "VCAP_SERVICES": services, + }) + if err != nil { + if app != nil { + t.Fatalf("returned both an app and an error: %v", err) + } + + return + } + + if app == nil { + t.Fatal("returned neither an app nor an error") + } + + // Whatever decoded, the accessors have to stay total: they may find + // nothing, but they must not panic on any shape that got this far. + for _, instances := range app.Services { + for i := range instances { + service := instances[i] + service.CredentialString("uri") + service.Credential("a", "b", "c") + service.CredentialPath("a.b.c") + } + } + + app.Services.WithTag("t") + app.Services.WithLabel("redis") + app.Services.WithName("r") + app.Services.WithNameUsingPattern("r.*") + app.Services.WithTagUsingPattern("t.*") + }) +} diff --git a/go.mod b/go.mod index cc89a4b..897a1b6 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,16 @@ module github.com/cloudfoundry-community/go-cfenv -go 1.18 +go 1.25.0 require ( github.com/go-viper/mapstructure/v2 v2.5.0 - github.com/onsi/gomega v1.27.10 + github.com/onsi/gomega v1.42.1 github.com/sclevine/spec v1.4.0 ) require ( - github.com/google/go-cmp v0.5.9 // indirect - golang.org/x/net v0.35.0 // indirect - golang.org/x/text v0.22.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + github.com/google/go-cmp v0.7.0 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/net v0.57.0 // indirect + golang.org/x/text v0.40.0 // indirect ) diff --git a/go.sum b/go.sum index a19d2dc..7eec615 100644 --- a/go.sum +++ b/go.sum @@ -1,22 +1,14 @@ -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= -github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I= +github.com/onsi/gomega v1.42.1/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg= github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8= github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=