From 0cf160ae9082bc119892a3290541903f33837ee6 Mon Sep 17 00:00:00 2001 From: Radu Tarean Date: Thu, 11 Jul 2024 13:14:41 +0300 Subject: [PATCH] feat: do not collate sunset paths --- Makefile | 2 +- internal/storage/collator.go | 23 +++++ internal/storage/collator_test.go | 165 ++++++++++++++++++++++++++++++ 3 files changed, 189 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7e4e183a..13c88320 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ APP:=vervet GO_BIN=$(shell pwd)/.bin/go -SHELL:=env PATH=$(GO_BIN):$(PATH) $(SHELL) +SHELL:=env PATH="$(GO_BIN):$(PATH)" $(SHELL) GOCI_LINT_V?=v1.54.2 diff --git a/internal/storage/collator.go b/internal/storage/collator.go index 5df1fbae..a671ee92 100644 --- a/internal/storage/collator.go +++ b/internal/storage/collator.go @@ -4,6 +4,7 @@ import ( "fmt" "net/url" "sort" + "time" "github.com/getkin/kin-openapi/openapi3" "github.com/rs/zerolog/log" @@ -99,6 +100,23 @@ func (c *Collator) Add(service string, revision ContentRevision) { } } +func isPathSunsetEligible(pathItem *openapi3.PathItem) bool { + currentDate := time.Now() + for _, operation := range pathItem.Operations() { + if sunsetDateStr, ok := operation.Extensions["x-snyk-sunset-eligible"]; ok { + sunsetDate, err := time.Parse("2006-01-02", sunsetDateStr.(string)[:10]) + if err != nil { + log.Error().Err(err).Msg("invalid sunset date format") + continue + } + if currentDate.After(sunsetDate) { + return true + } + } + } + return false +} + // Collate processes added service revisions to collate unified versions and OpenAPI specs for each version. func (c *Collator) Collate() (map[vervet.Version]openapi3.T, error) { specs := make(map[vervet.Version]openapi3.T) @@ -126,6 +144,11 @@ func (c *Collator) Collate() (map[vervet.Version]openapi3.T, error) { collatorMergeError.WithLabelValues(version.String()).Inc() return nil, err } + for path, pathItem := range spec.Paths { + if isPathSunsetEligible(pathItem) { + delete(spec.Paths, path) + } + } if err := vervet.RemoveElements(spec, c.excludePatterns); err != nil { log.Error().Err(err).Msgf("could not merge revision for version %s", version) collatorMergeError.WithLabelValues(version.String()).Inc() diff --git a/internal/storage/collator_test.go b/internal/storage/collator_test.go index 9d31ddf3..4e68b1c2 100644 --- a/internal/storage/collator_test.go +++ b/internal/storage/collator_test.go @@ -106,6 +106,171 @@ paths: description: Get OpenAPI at version ` +const serviceDSpecWithMixedSunset = ` +openapi: 3.0.0 +info: + title: ServiceD API + version: 0.0.0 +tags: + - name: example + description: service d example +paths: + /sunset: + get: + x-snyk-sunset-eligible: 2023-01-01 + summary: Sunset endpoint + responses: + '200': + description: An empty response + /notsunset: + get: + summary: Not Sunset endpoint + responses: + '200': + description: An empty response + /latersunset: + get: + x-snyk-sunset-eligible: 2025-01-01 + summary: Later Sunset endpoint + responses: + '200': + description: An empty response +` + +func TestCollator_Collate_MixedSunset(t *testing.T) { + c := qt.New(t) + + v20230101_ga := vervet.Version{ + Date: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + Stability: vervet.StabilityGA, + } + + collator, err := storage.NewCollator() + c.Assert(err, qt.IsNil) + + collator.Add("service-d", storage.ContentRevision{ + Version: v20230101_ga, + Blob: []byte(serviceDSpecWithMixedSunset), + }) + + specs, err := collator.Collate() + c.Assert(err, qt.IsNil) + + // Assert that the sunset endpoint is not present in the collated specs + _, exists := specs[v20230101_ga].Paths["/sunset"] + c.Assert(exists, qt.IsFalse) + + // Assert that the not sunset endpoint is still present + _, exists = specs[v20230101_ga].Paths["/notsunset"] + c.Assert(exists, qt.IsTrue) + + // Assert that the later sunset endpoint is still present + _, exists = specs[v20230101_ga].Paths["/latersunset"] + c.Assert(exists, qt.IsTrue) +} + +const serviceDSpecAllSunset = ` +openapi: 3.0.0 +info: + title: ServiceD API + version: 0.0.0 +tags: + - name: example + description: service d example +paths: + /sunset1: + get: + x-snyk-sunset-eligible: 2023-01-01 + summary: Sunset endpoint 1 + responses: + '200': + description: An empty response + /sunset2: + get: + x-snyk-sunset-eligible: 2022-12-31 + summary: Sunset endpoint 2 + responses: + '200': + description: An empty response +` + +func TestCollator_Collate_AllSunset(t *testing.T) { + c := qt.New(t) + + v20230101_ga := vervet.Version{ + Date: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + Stability: vervet.StabilityGA, + } + + collator, err := storage.NewCollator() + c.Assert(err, qt.IsNil) + + collator.Add("service-d", storage.ContentRevision{ + Version: v20230101_ga, + Blob: []byte(serviceDSpecAllSunset), + }) + + specs, err := collator.Collate() + c.Assert(err, qt.IsNil) + + // Assert that all sunset endpoints are not present in the collated specs + _, exists := specs[v20230101_ga].Paths["/sunset1"] + c.Assert(exists, qt.IsFalse) + + _, exists = specs[v20230101_ga].Paths["/sunset2"] + c.Assert(exists, qt.IsFalse) +} + +const serviceDSpecNoSunset = ` +openapi: 3.0.0 +info: + title: ServiceD API + version: 0.0.0 +tags: + - name: example + description: service d example +paths: + /active1: + get: + summary: Active endpoint 1 + responses: + '200': + description: An empty response + /active2: + get: + summary: Active endpoint 2 + responses: + '200': + description: An empty response +` + +func TestCollator_Collate_NoSunset(t *testing.T) { + c := qt.New(t) + + v20230101_ga := vervet.Version{ + Date: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + Stability: vervet.StabilityGA, + } + + collator, err := storage.NewCollator() + c.Assert(err, qt.IsNil) + + collator.Add("service-d", storage.ContentRevision{ + Version: v20230101_ga, + Blob: []byte(serviceDSpecNoSunset), + }) + + specs, err := collator.Collate() + c.Assert(err, qt.IsNil) + + // Assert that all active endpoints are still present in the collated specs + _, exists := specs[v20230101_ga].Paths["/active1"] + c.Assert(exists, qt.IsTrue) + + _, exists = specs[v20230101_ga].Paths["/active2"] + c.Assert(exists, qt.IsTrue) +} + func TestCollator_Collate(t *testing.T) { c := qt.New(t)