Skip to content

feat: adding k8s version metadata to catalog api - #4662

Open
IndulekhaPrathapan wants to merge 16 commits into
open-policy-agent:masterfrom
IndulekhaPrathapan:ip/k8s-metadata-catalog-api
Open

feat: adding k8s version metadata to catalog api#4662
IndulekhaPrathapan wants to merge 16 commits into
open-policy-agent:masterfrom
IndulekhaPrathapan:ip/k8s-metadata-catalog-api

Conversation

@IndulekhaPrathapan

@IndulekhaPrathapan IndulekhaPrathapan commented Jul 8, 2026

Copy link
Copy Markdown

What this PR does / why we need it:
This PR add a minimum and maximum k8s version to policy. It also updates gator install/upgrade to match k8s version before installing policies.

Which issue(s) this PR fixes (optional, using fixes #(, fixes #<issue_number>, ...) format, will close the issue(s) when the PR gets merged):
Fixes #4383

Special notes for your reviewer:

Add a min and max k8s version to the policy - the versions are derived from annotations in the template
Added a check for k8s version match in gator install and upgrade with a force flag to enable install even if k8s version is outside the range
Added tests

Copilot AI review requested due to automatic review settings July 8, 2026 05:10
@linux-foundation-easycla

linux-foundation-easycla Bot commented Jul 8, 2026

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ login: IndulekhaPrathapan / name: Indulekha Prathapan (741f434)

@IndulekhaPrathapan
IndulekhaPrathapan marked this pull request as ready for review July 8, 2026 05:13
@IndulekhaPrathapan
IndulekhaPrathapan requested a review from a team as a code owner July 8, 2026 05:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds Kubernetes version compatibility metadata (min/max) to the gator policy catalog and enforces it during gator policy install / upgrade. Version bounds are sourced from metadata.gatekeeper.sh/{min,max}KubernetesVersion annotations on ConstraintTemplates, with a fallback that derives bounds from the API lifecycle metadata of the built-in resources a policy's sample constraints target. A --force flag bypasses the gate, and incompatible policies are surfaced as skips (not failures) in output and exit codes.

Changes:

  • Add MinKubernetesVersion/MaxKubernetesVersion to the catalog Policy, SearchResult, and JSON/table output; validate them and render a human-readable range.
  • Add a cluster version compatibility gate to install/upgrade (resolved via a new discovery-backed ServerVersion client method, resolved once per batch), with --force override and partial-success signaling.
  • Add catalog/lifecycle.go to derive version bounds from k8s.io/api lifecycle metadata as a fallback, plus refactors (normalizeVersion, lenient parseVersion, findConstraintFile).

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated no comments.

Show a summary per file
File Description
pkg/gator/policy/catalog/types.go Adds min/max k8s version fields to Policy with json/yaml tags
pkg/gator/policy/catalog/generator.go Parses/normalizes/validates version bounds; adds K8sVersionInRange/FormatK8sVersionRange; lenient semver parsing
pkg/gator/policy/catalog/lifecycle.go New: derives version range from built-in API lifecycle metadata
pkg/gator/policy/catalog/lifecycle_test.go New tests for derivation, match-kind parsing, end-to-end fallback
pkg/gator/policy/catalog/generator_test.go Tests for annotations, schema validation, range formatting/checking
pkg/gator/policy/client/client.go Adds ServerVersion + discovery client; dedups constructor logic
pkg/gator/policy/client/install.go Adds compatibility gate, IncompatibleEntry, and anyPolicyHasVersionBounds
pkg/gator/policy/client/upgrade.go Batch version resolution; surfaces incompatible as skip vs failure
pkg/gator/policy/client/client_test.go Fake client ServerVersion + gate/upgrade tests
pkg/gator/policy/output/output.go Adds SkippedEntry and Incompatible result fields
pkg/gator/policy/output/table.go Adds K8S VERSION column and incompatible rendering
pkg/gator/policy/output/output_test.go Tests for new column and incompatible output
cmd/gator/policy/install.go --force flag, wiring, dryRun ServerVersion, partial-success message
cmd/gator/policy/upgrade.go --force flag, wiring, dry-run cluster-access messaging
cmd/gator/policy/search.go Populates new version fields in search results
go.mod Promotes blang/semver/v4 from indirect to direct dependency

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.

Comment thread pkg/gator/policy/catalog/generator.go Outdated
@JaydipGabani

Copy link
Copy Markdown
Contributor

@IndulekhaPrathapan can you fix easycla?

@IndulekhaPrathapan
IndulekhaPrathapan force-pushed the ip/k8s-metadata-catalog-api branch 2 times, most recently from d2fb126 to 741f434 Compare July 8, 2026 18:15
@IndulekhaPrathapan

Copy link
Copy Markdown
Author

@IndulekhaPrathapan can you fix easycla?

Fixed now, conflict between two linked emails.

Copilot AI review requested due to automatic review settings July 9, 2026 22:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.

Comment thread pkg/gator/policy/catalog/generator.go Outdated
Comment thread pkg/gator/policy/catalog/generator.go Outdated
Comment thread pkg/gator/policy/client/upgrade.go Outdated
@IndulekhaPrathapan
IndulekhaPrathapan force-pushed the ip/k8s-metadata-catalog-api branch from a4c0fa0 to 94213ec Compare July 14, 2026 22:11
Copilot AI review requested due to automatic review settings July 14, 2026 22:11
@IndulekhaPrathapan
IndulekhaPrathapan force-pushed the ip/k8s-metadata-catalog-api branch from 1b6150d to 4301036 Compare July 14, 2026 22:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.

Comment thread pkg/gator/policy/catalog/generator.go Outdated
Comment thread go.mod Outdated
Comment thread pkg/gator/policy/client/install.go Outdated
Comment thread pkg/gator/policy/client/install.go Outdated
Copilot AI review requested due to automatic review settings July 14, 2026 22:19
@IndulekhaPrathapan
IndulekhaPrathapan force-pushed the ip/k8s-metadata-catalog-api branch from 699a766 to c966917 Compare July 14, 2026 22:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.

Comment thread pkg/gator/policy/catalog/generator.go
Comment thread pkg/gator/policy/catalog/generator.go Outdated
Comment thread pkg/gator/policy/client/client.go Outdated
Comment thread pkg/gator/policy/catalog/types.go
Comment thread pkg/gator/policy/client/install.go
Copilot AI review requested due to automatic review settings July 14, 2026 22:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.

Comment thread pkg/gator/policy/catalog/generator.go Outdated
Comment thread pkg/gator/policy/catalog/lifecycle.go Outdated
Comment thread pkg/gator/policy/client/install.go Outdated
Copilot AI review requested due to automatic review settings July 15, 2026 06:51
@IndulekhaPrathapan
IndulekhaPrathapan force-pushed the ip/k8s-metadata-catalog-api branch from 4c9a813 to 0140044 Compare July 15, 2026 06:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.

Comment thread pkg/gator/policy/client/install.go
Signed-off-by: Indulekha Prathapan <indulekhamp@gmail.com>
Copilot AI review requested due to automatic review settings July 16, 2026 00:30
@IndulekhaPrathapan
IndulekhaPrathapan force-pushed the ip/k8s-metadata-catalog-api branch from 0a3f86b to 96b1465 Compare July 16, 2026 00:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

@IndulekhaPrathapan

Copy link
Copy Markdown
Author

@JaydipGabani Can you PTAL - fixed issues from copilot review

@JaydipGabani

Copy link
Copy Markdown
Contributor

@IndulekhaPrathapan thanks for working on this, this pr is in my review list I will try to get to this pr by EOW.

@IndulekhaPrathapan

Copy link
Copy Markdown
Author

@IndulekhaPrathapan thanks for working on this, this pr is in my review list I will try to get to this pr by EOW.

Just want to make sure this is in your radar.

@JaydipGabani JaydipGabani left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this pr!

Comment thread cmd/gator/policy/upgrade.go Outdated
Comment on lines +99 to +101
if upgradeDryRun {
return fmt.Errorf("creating Kubernetes client: %w (upgrade --dry-run still requires cluster access to read installed policy versions)", err)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this being added? the flag description already clarifies that cluster access is needed. I dont think we need this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

Comment thread pkg/gator/policy/catalog/generator.go Outdated
Comment on lines +322 to +327
var constraintFilenames = []string{"constraint.yaml", "constraint.yml"}

// findConstraintFile returns the path to the constraint file in dir, or "" if
// none exists.
func findConstraintFile(dir string) string {
for _, name := range constraintFilenames {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these filenames are fixed and only used by findConstraintFile, could we keep them local instead of introducing a mutable package-level variable?

Suggested change
var constraintFilenames = []string{"constraint.yaml", "constraint.yml"}
// findConstraintFile returns the path to the constraint file in dir, or "" if
// none exists.
func findConstraintFile(dir string) string {
for _, name := range constraintFilenames {
func findConstraintFile(dir string) string {
for _, name := range [...]string{"constraint.yaml", "constraint.yml"} {

This keeps the candidate list close to its only use and communicates that it is a small, fixed set.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +526 to +537
func VersionRangeContradicts(minVersion, maxVersion string) bool {
if minVersion == "" || maxVersion == "" {
return false
}
inRange, err := K8sVersionInRange(minVersion, "", maxVersion)
if err != nil {
// An unparseable bound is treated as non-contradictory; catalog schema
// validation flags bad version strings separately, up front.
return false
}
return !inRange
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing minVersion as the serverVersion argument works, but it makes the contradiction check difficult to understand. Could we extract the maximum-bound comparison into a helper and use it from both VersionRangeContradicts and K8sVersionInRange?

For example:

func exceedsMaxVersion(candidate, max *version.Version) bool {
	if len(max.Components()) >= 3 {
		return candidate.GreaterThan(max)
	}
	return candidate.Major() > max.Major() ||
		(candidate.Major() == max.Major() && candidate.Minor() > max.Minor())
}

Then VersionRangeContradicts can parse both bounds and call exceedsMaxVersion(min, max) directly. This avoids treating the minimum as a synthetic server version while keeping the whole-minor versus exact-patch semantics shared with the runtime compatibility check.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +953 to +965
{"invalid server version", "notaversion", "v1.21.0", "", false, true},
{"invalid min", "v1.25.0", "bogus", "", false, true},
{"invalid max", "v1.25.0", "", "bogus", false, true},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := K8sVersionInRange(tt.serverVersion, tt.minVer, tt.maxVer)
if tt.wantErr {
if err == nil {
t.Fatalf("expected error, got nil")
}
return

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could these error cases also assert which input failed to parse? Checking only err != nil could let a regression pass if the function returns an error from the wrong validation branch. For example, assert that the errors contain server version, minKubernetesVersion, and maxKubernetesVersion for the corresponding cases.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +112 to +193
func TestK8sClient_ServerVersion_Discovery(t *testing.T) {
newDiscoveryK8sClient := func(t *testing.T, server *httptest.Server) *K8sClient {
t.Helper()
discoveryClient, err := discovery.NewDiscoveryClientForConfig(&rest.Config{Host: server.URL})
require.NoError(t, err)
return &K8sClient{discoveryClient: discoveryClient}
}

t.Run("successful gitVersion response", func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/version", r.URL.Path)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"gitVersion":"v1.30.2"}`))
}))
defer server.Close()

client := newDiscoveryK8sClient(t, server)
version, err := client.ServerVersion(context.Background())
require.NoError(t, err)
assert.Equal(t, "v1.30.2", version)
})

t.Run("server error response is a genuine error", func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))
defer server.Close()

client := newDiscoveryK8sClient(t, server)
_, err := client.ServerVersion(context.Background())
require.Error(t, err)
assert.Contains(t, err.Error(), "getting server version")
})

t.Run("malformed response body is a genuine error", func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte("not json"))
}))
defer server.Close()

client := newDiscoveryK8sClient(t, server)
_, err := client.ServerVersion(context.Background())
require.Error(t, err)
assert.Contains(t, err.Error(), "parsing server version")
})

t.Run("request error (connection refused) is a genuine error", func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
addr := server.URL
server.Close() // nothing is listening at addr anymore

discoveryClient, err := discovery.NewDiscoveryClientForConfig(&rest.Config{Host: addr})
require.NoError(t, err)
client := &K8sClient{discoveryClient: discoveryClient}

_, err = client.ServerVersion(context.Background())
require.Error(t, err)
assert.Contains(t, err.Error(), "getting server version")
})

t.Run("context cancellation aborts a slow request instead of blocking forever", func(t *testing.T) {
release := make(chan struct{})
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
<-release
}))
defer server.Close()
defer close(release)

client := newDiscoveryK8sClient(t, server)

ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()

start := time.Now()
_, err := client.ServerVersion(ctx)
elapsed := time.Since(start)

require.Error(t, err)
assert.Less(t, elapsed, 5*time.Second, "ServerVersion must honor context cancellation via Do(ctx) instead of blocking on the handler")
})
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a case where /version returns valid JSON without gitVersion, such as {}? The current implementation would decode this successfully and return ("", nil). Since callers rely on a non-empty version for compatibility checks, either ServerVersion should reject a missing/empty gitVersion, or the test should explicitly document that returning an empty version is intentional.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

existing, err := k8sClient.GetTemplate(ctx, policy.Name)
if err == nil {
// Template exists - check if managed by gator
if !labels.IsManagedByGator(existing) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

installPolicy checks ownership before compatibility. If an out-of-range policy has an unmanaged same-name ConstraintTemplate, it is recorded as Failed with ConflictError, and fail-fast can prevent later compatible policies from installing. This contradicts the intended behavior that incompatible policies are skipped before write-related failures.

Please defer the unmanaged-template conflict until after the compatibility gate.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@IndulekhaPrathapan
IndulekhaPrathapan force-pushed the ip/k8s-metadata-catalog-api branch from dd7bf5e to c464c17 Compare August 14, 2026 23:04
Signed-off-by: Indulekha Prathapan <indulekhamp@gmail.com>
@IndulekhaPrathapan

Copy link
Copy Markdown
Author

@JaydipGabani Addressed the comments , do you mind taking another look pls?

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

Suppressed comments (1)

docs/design/gator-policy.md:200

  • This blanket statement is inaccurate for gator policy install --dry-run: the command intentionally uses an offline client and skips the compatibility gate, so an out-of-range policy is previewed as installable. Clarify this exception so users do not rely on dry-run as a compatibility check.
`gator policy install` and `gator policy upgrade` skip policies outside this range; pass `--force` to bypass the compatibility check.

Comment thread cmd/gator/policy/install.go
Comment thread pkg/gator/policy/client/upgrade.go
Comment thread pkg/gator/policy/client/install.go Outdated
IndulekhaPrathapan and others added 3 commits August 26, 2026 15:18
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Indulekha Prathapan <indulekhamp@gmail.com>
Signed-off-by: Indulekha Prathapan <indulekhamp@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add k8s version metadata in catalog api for gator

3 participants