diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 545466e932..455ab4a5cc 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -68,6 +68,10 @@ jobs: needs: [prepare-e2e] runs-on: ubuntu-latest environment: test + env: + GHCR_REPO: ghcr.io/mongodb/mongodb-atlas-kubernetes-operator-prerelease + GHCR_BUNDLES_REPO: ghcr.io/mongodb/mongodb-atlas-kubernetes-bundles-prerelease + AKO_DEPRECATION_WARNINGS: true strategy: fail-fast: false matrix: @@ -230,3 +234,35 @@ jobs: with: name: logs path: output/** + gather-deprecations: + name: Gather Deprecation Warnings + environment: release + needs: [e2e] + runs-on: ubuntu-latest + steps: + - name: Parse logs from e2e tests + run: | + devbox run -- 'make tools/scandeprecation/scandeprecation' + LOGS=$(gh run view ${{ github.run_id }} --log | grep "javaMethod" | ./tools/scandeprecation/scandeprecations) + echo "LOGS=$LOGS" >> $GITHUB_ENV` + - name: Generate GitHub App Token + id: generate_token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.AKO_RELEASER_APP_ID }} + private-key: ${{ secrets.AKO_RELEASER_RSA_KEY }} + owner: ${{ github.repository_owner }} + repositories: | + mongodb-atlas-kubernetes + - name: Create comment from data + if: ${{ ! env.LOGS == '' }} + uses: actions/github-script@v7 + with: + github-token: ${{ steps.generate_token.outputs.token }} + script: | + github.rest.issues.createComment({ + owner: context.repo.owner, + issue_number: context.issue.number, + repo: context.repo.repo, + body: process.env.LOGS + }) diff --git a/.gitignore b/.gitignore index 981ef2cfb1..30e9d581a6 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,7 @@ deploy/ tools/clean/clean tools/makejwt/makejwt tools/metrics/metrics +tools/scandeprecation/scandeprecations # ignore the ako.siging key local copy ako.pem diff --git a/Makefile b/Makefile index 27304b696b..291ccf9198 100644 --- a/Makefile +++ b/Makefile @@ -644,3 +644,6 @@ install-ako-helm: --set subobjectDeletionProtection=false \ --namespace=$(HELM_AKO_NAMESPACE) --create-namespace kubectl get crds + +tools/scandeprecation/scandeprecation: tools/scandeprecation/*.go + cd tools/scandeprecation && go build . \ No newline at end of file diff --git a/internal/controller/atlas/provider.go b/internal/controller/atlas/provider.go index 98631e9176..f4b66af7ee 100644 --- a/internal/controller/atlas/provider.go +++ b/internal/controller/atlas/provider.go @@ -19,6 +19,7 @@ import ( "fmt" "net/http" "net/url" + "os" "runtime" "strings" @@ -29,6 +30,7 @@ import ( "github.com/mongodb/mongodb-atlas-kubernetes/v2/api" akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/deprecation" "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/dryrun" "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/httputil" "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/version" @@ -126,7 +128,7 @@ func (p *ProductionProvider) Client(ctx context.Context, creds *Credentials, log httputil.LoggingTransport(log), } - transport := p.newDryRunTransport(http.DefaultTransport) + transport := p.newTransport(http.DefaultTransport, log) httpClient, err := httputil.DecorateClient(&http.Client{Transport: transport}, clientCfg...) if err != nil { return nil, err @@ -139,7 +141,7 @@ func (p *ProductionProvider) Client(ctx context.Context, creds *Credentials, log func (p *ProductionProvider) SdkClientSet(ctx context.Context, creds *Credentials, log *zap.SugaredLogger) (*ClientSet, error) { var transport http.RoundTripper = digest.NewTransport(creds.APIKeys.PublicKey, creds.APIKeys.PrivateKey) - transport = p.newDryRunTransport(transport) + transport = p.newTransport(transport, log) transport = httputil.NewLoggingTransport(log, false, transport) httpClient := &http.Client{Transport: transport} @@ -157,7 +159,11 @@ func (p *ProductionProvider) SdkClientSet(ctx context.Context, creds *Credential }, nil } -func (p *ProductionProvider) newDryRunTransport(delegate http.RoundTripper) http.RoundTripper { +func (p *ProductionProvider) newTransport(delegate http.RoundTripper, log *zap.SugaredLogger) http.RoundTripper { + if os.Getenv("AKO_DEPRECATION_WARNINGS") != "" { + return deprecation.NewLoggingTransport(delegate, log.Desugar()) + } + if p.dryRun { return dryrun.NewDryRunTransport(delegate) } diff --git a/internal/deprecation/transport.go b/internal/deprecation/transport.go new file mode 100644 index 0000000000..68ce42ea9e --- /dev/null +++ b/internal/deprecation/transport.go @@ -0,0 +1,51 @@ +// Copyright 2025 MongoDB Inc +// +// 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 deprecation + +import ( + "net/http" + + "go.uber.org/zap" +) + +type Transport struct { + delegate http.RoundTripper + logger *zap.Logger +} + +func NewLoggingTransport(delegate http.RoundTripper, logger *zap.Logger) *Transport { + return &Transport{ + delegate: delegate, + logger: logger.Named("deprecated"), + } +} + +func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { + resp, err := t.delegate.RoundTrip(req) + if resp != nil { + javaMethod := resp.Header.Get("X-Java-Method") + deprecation := resp.Header.Get("Deprecation") + sunset := resp.Header.Get("Sunset") + + if deprecation != "" { + t.logger.Warn("deprecation", zap.String("type", "deprecation"), zap.String("date", deprecation), zap.String("javaMethod", javaMethod), zap.String("path", req.URL.Path), zap.String("method", req.Method)) + } + + if sunset != "" { + t.logger.Warn("sunset", zap.String("type", "sunset"), zap.String("date", sunset), zap.String("javaMethod", javaMethod), zap.String("path", req.URL.Path), zap.String("method", req.Method)) + } + } + return resp, err +} diff --git a/tools/scandeprecation/go.mod b/tools/scandeprecation/go.mod new file mode 100644 index 0000000000..1b71739ae2 --- /dev/null +++ b/tools/scandeprecation/go.mod @@ -0,0 +1,7 @@ +module tools/scandeprecations + +go 1.24.4 + +require go.uber.org/zap v1.27.0 + +require go.uber.org/multierr v1.10.0 // indirect diff --git a/tools/scandeprecation/go.sum b/tools/scandeprecation/go.sum new file mode 100644 index 0000000000..2d29b57e61 --- /dev/null +++ b/tools/scandeprecation/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tools/scandeprecation/scan_deprecations.go b/tools/scandeprecation/scan_deprecations.go new file mode 100644 index 0000000000..b975e8ad9d --- /dev/null +++ b/tools/scandeprecation/scan_deprecations.go @@ -0,0 +1,90 @@ +// Copyright 2025 MongoDB Inc +// +// 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 main + +import ( + "bufio" + "cmp" + "encoding/json" + "fmt" + "os" + "slices" + "strings" + + "go.uber.org/zap" +) + +type DeprecationResponse struct { + Type string `json:"type"` + Date string `json:"date"` + JavaMethod string `json:"javaMethod"` +} + +func main() { + l, err := zap.NewProduction() + if err != nil { + panic(err) + } + log := l.Sugar() + + // Take input from stdin (pipe from GitHub CLI) + scanner := bufio.NewScanner(os.Stdin) + var responses []DeprecationResponse + var out strings.Builder + // Markdown table header for GH Comment + out.WriteString("|Type|Java Method|Date|\n|------|------|------|\n") + + for scanner.Scan() { + example := scanner.Text() + // Non-JSON logs mean we split by tabs + split := strings.Split(example, "\t") + + // Last element is the JSON "body" of the log line + example = split[len(split)-1] + + res := DeprecationResponse{} + err = json.Unmarshal([]byte(example), &res) + if err != nil { + log.Warn("failed to unmarshal JSON", zap.Error(err)) + continue + } + responses = append(responses, res) + } + + // Quit out if there is no deprecations logged + if len(responses) == 0 { + os.Exit(0) + } + + // Sort & Compact to remove duplicates + slices.SortFunc(responses, func(a, b DeprecationResponse) int { + if a.JavaMethod != b.JavaMethod { + return cmp.Compare(a.JavaMethod, b.JavaMethod) + } + if a.Type != b.Type { + return cmp.Compare(a.Type, b.Type) + } + return cmp.Compare(a.Date, b.Date) + }) + responses = slices.Compact(responses) + + // Build the Markdown table + for _, res := range responses { + out.WriteString(res.Type + "|" + res.JavaMethod + "|" + res.Date + "|\n") + } + + // Print to stdout + fmt.Println(out.String()) +}