diff --git a/staging/src/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators/status.go b/staging/src/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators/status.go index adcaabc5a6e..2213a99442c 100644 --- a/staging/src/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators/status.go +++ b/staging/src/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators/status.go @@ -20,6 +20,7 @@ import ( "fmt" "io" "path" + "regexp" "strconv" "strings" @@ -364,6 +365,10 @@ func (g *genPreleaseLifecycle) Imports(c *generator.Context) (imports []string) return importLines } +var ( + isGAVersionRegex = regexp.MustCompile(`^v\d+$`) +) + func (g *genPreleaseLifecycle) argsFromType(c *generator.Context, t *types.Type) (generator.Args, error) { a := generator.Args{ "type": t, @@ -372,37 +377,53 @@ func (g *genPreleaseLifecycle) argsFromType(c *generator.Context, t *types.Type) if err != nil { return nil, err } + + // Take version from package last segment. + // Use heuristic to determine whether the package is GA or prerelease. + // If the package is GA, the version matches the format vN where N is a number. + version := path.Base(t.Name.Package) + isGAVersion := isGAVersionRegex.MatchString(version) + a = a. With("introducedMajor", introducedMajor). With("introducedMinor", introducedMinor) // compute based on our policy + hasDeprecated := tagExists(deprecatedTagName, t) + hasRemoved := tagExists(removedTagName, t) + deprecatedMajor := introducedMajor deprecatedMinor := introducedMinor + 3 // if someone intentionally override the deprecation release - if tagExists(deprecatedTagName, t) { + if hasDeprecated { _, deprecatedMajor, deprecatedMinor, err = extractDeprecatedTag(t) if err != nil { return nil, err } } - a = a. - With("deprecatedMajor", deprecatedMajor). - With("deprecatedMinor", deprecatedMinor) + + if !isGAVersion || hasDeprecated { + a = a. + With("deprecatedMajor", deprecatedMajor). + With("deprecatedMinor", deprecatedMinor) + } // compute based on our policy removedMajor := deprecatedMajor removedMinor := deprecatedMinor + 3 // if someone intentionally override the removed release - if tagExists(removedTagName, t) { + if hasRemoved { _, removedMajor, removedMinor, err = extractRemovedTag(t) if err != nil { return nil, err } } - a = a. - With("removedMajor", removedMajor). - With("removedMinor", removedMinor) + + if !isGAVersion || hasRemoved { + a = a. + With("removedMajor", removedMajor). + With("removedMinor", removedMinor) + } replacementGroup, replacementVersion, replacementKind, hasReplacement, err := extractReplacementTag(t) if err != nil { @@ -441,13 +462,17 @@ func (g *genPreleaseLifecycle) GenerateType(c *generator.Context, t *types.Type, sw.Do(" return $.introducedMajor$, $.introducedMinor$\n", args) sw.Do("}\n\n", nil) } - if versionedMethodOrDie("APILifecycleDeprecated", t) == nil { - sw.Do("// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison.\n", args) - sw.Do("// It is controlled by \""+deprecatedTagName+"\" tags in types.go or \""+introducedTagName+"\" plus three minor.\n", args) - sw.Do("func (in *$.type|intrapackage$) APILifecycleDeprecated() (major, minor int) {\n", args) - sw.Do(" return $.deprecatedMajor$, $.deprecatedMinor$\n", args) - sw.Do("}\n\n", nil) + + if _, hasDeprecated := args["deprecatedMajor"]; hasDeprecated { + if versionedMethodOrDie("APILifecycleDeprecated", t) == nil { + sw.Do("// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison.\n", args) + sw.Do("// It is controlled by \""+deprecatedTagName+"\" tags in types.go or \""+introducedTagName+"\" plus three minor.\n", args) + sw.Do("func (in *$.type|intrapackage$) APILifecycleDeprecated() (major, minor int) {\n", args) + sw.Do(" return $.deprecatedMajor$, $.deprecatedMinor$\n", args) + sw.Do("}\n\n", nil) + } } + if _, hasReplacement := args["replacementKind"]; hasReplacement { if versionedMethodOrDie("APILifecycleReplacement", t) == nil { sw.Do("// APILifecycleReplacement is an autogenerated function, returning the group, version, and kind that should be used instead of this deprecated type.\n", args) @@ -457,12 +482,15 @@ func (g *genPreleaseLifecycle) GenerateType(c *generator.Context, t *types.Type, sw.Do("}\n\n", nil) } } - if versionedMethodOrDie("APILifecycleRemoved", t) == nil { - sw.Do("// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison.\n", args) - sw.Do("// It is controlled by \""+removedTagName+"\" tags in types.go or \""+deprecatedTagName+"\" plus three minor.\n", args) - sw.Do("func (in *$.type|intrapackage$) APILifecycleRemoved() (major, minor int) {\n", args) - sw.Do(" return $.removedMajor$, $.removedMinor$\n", args) - sw.Do("}\n\n", nil) + + if _, hasRemoved := args["removedMajor"]; hasRemoved { + if versionedMethodOrDie("APILifecycleRemoved", t) == nil { + sw.Do("// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison.\n", args) + sw.Do("// It is controlled by \""+removedTagName+"\" tags in types.go or \""+deprecatedTagName+"\" plus three minor.\n", args) + sw.Do("func (in *$.type|intrapackage$) APILifecycleRemoved() (major, minor int) {\n", args) + sw.Do(" return $.removedMajor$, $.removedMinor$\n", args) + sw.Do("}\n\n", nil) + } } return sw.Error() diff --git a/staging/src/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators/status_test.go b/staging/src/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators/status_test.go index f9fc952d9c7..96e392047eb 100644 --- a/staging/src/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators/status_test.go +++ b/staging/src/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators/status_test.go @@ -20,8 +20,11 @@ import ( "fmt" "reflect" "strconv" + "strings" "testing" + "github.com/google/go-cmp/cmp" + "k8s.io/gengo/v2/generator" "k8s.io/gengo/v2/types" "k8s.io/klog/v2" ) @@ -34,6 +37,248 @@ var mockType = &types.Type{ SecondClosestCommentLines: []string{}, } +func TestArgsFromType(t *testing.T) { + type testcase struct { + name string + t *types.Type + expected generator.Args + expectedError string + } + + tests := []testcase{ + { + name: "no comments", + t: &types.Type{ + Name: types.Name{ + Name: "Simple", + Package: "k8s.io/apis/core/v1", + }, + }, + expectedError: `missing`, + }, + { + name: "GA type", + t: &types.Type{ + Name: types.Name{ + Name: "Simple", + Package: "k8s.io/apis/core/v1", + }, + CommentLines: []string{ + "+k8s:prerelease-lifecycle-gen:introduced=1.5", + }, + }, + expected: generator.Args{ + "introducedMajor": 1, + "introducedMinor": 5, + }, + }, + { + name: "GA type v2", + t: &types.Type{ + Name: types.Name{ + Name: "Simple", + Package: "k8s.io/apis/core/v2", + }, + CommentLines: []string{ + "+k8s:prerelease-lifecycle-gen:introduced=1.5", + }, + }, + expected: generator.Args{ + "introducedMajor": 1, + "introducedMinor": 5, + }, + }, + { + name: "GA type - explicit deprecated", + t: &types.Type{ + Name: types.Name{ + Name: "Simple", + Package: "k8s.io/apis/core/v1", + }, + CommentLines: []string{ + "+k8s:prerelease-lifecycle-gen:introduced=1.5", + "+k8s:prerelease-lifecycle-gen:deprecated=1.7", + }, + }, + expected: generator.Args{ + "introducedMajor": 1, + "introducedMinor": 5, + "deprecatedMajor": 1, + "deprecatedMinor": 7, + }, + }, + { + name: "GA type - explicit removed", + t: &types.Type{ + Name: types.Name{ + Name: "Simple", + Package: "k8s.io/apis/core/v1", + }, + CommentLines: []string{ + "+k8s:prerelease-lifecycle-gen:introduced=1.5", + "+k8s:prerelease-lifecycle-gen:removed=1.9", + }, + }, + expected: generator.Args{ + "introducedMajor": 1, + "introducedMinor": 5, + "removedMajor": 1, + "removedMinor": 9, + }, + }, + { + name: "GA type - explicit", + t: &types.Type{ + Name: types.Name{ + Name: "Simple", + Package: "k8s.io/apis/core/v1", + }, + CommentLines: []string{ + "+k8s:prerelease-lifecycle-gen:introduced=1.5", + "+k8s:prerelease-lifecycle-gen:deprecated=1.7", + "+k8s:prerelease-lifecycle-gen:removed=1.9", + }, + }, + expected: generator.Args{ + "introducedMajor": 1, + "introducedMinor": 5, + "deprecatedMajor": 1, + "deprecatedMinor": 7, + "removedMajor": 1, + "removedMinor": 9, + }, + }, + { + name: "beta type - defaulted", + t: &types.Type{ + Name: types.Name{ + Name: "Simple", + Package: "k8s.io/apis/core/v1beta1", + }, + CommentLines: []string{ + "+k8s:prerelease-lifecycle-gen:introduced=1.5", + }, + }, + expected: generator.Args{ + "introducedMajor": 1, + "introducedMinor": 5, + "deprecatedMajor": 1, + "deprecatedMinor": 8, + "removedMajor": 1, + "removedMinor": 11, + }, + }, + { + name: "beta type - explicit", + t: &types.Type{ + Name: types.Name{ + Name: "Simple", + Package: "k8s.io/apis/core/v1beta1", + }, + CommentLines: []string{ + "+k8s:prerelease-lifecycle-gen:introduced=1.5", + "+k8s:prerelease-lifecycle-gen:deprecated=1.7", + "+k8s:prerelease-lifecycle-gen:removed=1.9", + }, + }, + expected: generator.Args{ + "introducedMajor": 1, + "introducedMinor": 5, + "deprecatedMajor": 1, + "deprecatedMinor": 7, + "removedMajor": 1, + "removedMinor": 9, + }, + }, + { + name: "beta type - explicit deprecated only", + t: &types.Type{ + Name: types.Name{ + Name: "Simple", + Package: "k8s.io/apis/core/v1beta1", + }, + CommentLines: []string{ + "+k8s:prerelease-lifecycle-gen:introduced=1.5", + "+k8s:prerelease-lifecycle-gen:deprecated=1.7", + }, + }, + expected: generator.Args{ + "introducedMajor": 1, + "introducedMinor": 5, + "deprecatedMajor": 1, + "deprecatedMinor": 7, + "removedMajor": 1, + "removedMinor": 10, + }, + }, + { + name: "beta type - explicit removed only", + t: &types.Type{ + Name: types.Name{ + Name: "Simple", + Package: "k8s.io/apis/core/v1beta1", + }, + CommentLines: []string{ + "+k8s:prerelease-lifecycle-gen:introduced=1.5", + "+k8s:prerelease-lifecycle-gen:removed=1.9", + }, + }, + expected: generator.Args{ + "introducedMajor": 1, + "introducedMinor": 5, + "deprecatedMajor": 1, + "deprecatedMinor": 8, + "removedMajor": 1, + "removedMinor": 9, + }, + }, + { + name: "alpha type - defaulted", + t: &types.Type{ + Name: types.Name{ + Name: "Simple", + Package: "k8s.io/apis/core/v1alpha1", + }, + CommentLines: []string{ + "+k8s:prerelease-lifecycle-gen:introduced=1.5", + }, + }, + expected: generator.Args{ + "introducedMajor": 1, + "introducedMinor": 5, + "deprecatedMajor": 1, + "deprecatedMinor": 8, + "removedMajor": 1, + "removedMinor": 11, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if test.expected != nil { + test.expected["type"] = test.t + } + gen := genPreleaseLifecycle{} + args, err := gen.argsFromType(nil, test.t) + if test.expectedError != "" { + if err == nil { + t.Errorf("expected error, got none") + } else if !strings.Contains(err.Error(), test.expectedError) { + t.Errorf("expected error %q, got %q", test.expectedError, err.Error()) + } + return + } + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if diff := cmp.Diff(test.expected, args); diff != "" { + t.Error(diff) + } + }) + } +} + func Test_extractKubeVersionTag(t *testing.T) { oldKlogOsExit := klog.OsExit defer func() {