Skip to content

Add a linter check for environment selectors.#2619

Merged
openshift-merge-bot[bot] merged 1 commit intoopenshift:masterfrom
bergmannf:add-linter-clusterselection
Mar 10, 2026
Merged

Add a linter check for environment selectors.#2619
openshift-merge-bot[bot] merged 1 commit intoopenshift:masterfrom
bergmannf:add-linter-clusterselection

Conversation

@bergmannf
Copy link
Copy Markdown
Contributor

@bergmannf bergmannf commented Dec 23, 2025

This should prevent putting an invalid environment selector like 'stg' or 'int' into this repo.

What type of PR is this?

feature

What this PR does / why we need it?

Additional checks when using environment selectors.

Which Jira/Github issue(s) this PR fixes?

Special notes for your reviewer:

Pre-checks (if applicable):

  • Tested latest changes against a cluster

  • Included documentation changes with PR

  • If this is a new object that is not intended for the FedRAMP environment (if unsure, please reach out to team FedRAMP), please exclude it with:

    matchExpressions:
    - key: api.openshift.com/fedramp
      operator: NotIn
      values: ["true"]

Summary by CodeRabbit

  • Improvements
    • Enhanced environment validation to prevent invalid configurations and provide better error handling.
    • Updated build infrastructure to use a newer Python runtime (3.12) for improved performance and compatibility.

@openshift-ci openshift-ci Bot requested review from apahim and ravitri December 23, 2025 12:20
@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Dec 23, 2025
@bergmannf bergmannf force-pushed the add-linter-clusterselection branch 2 times, most recently from b87e6c0 to 7abefbd Compare December 23, 2025 14:15
@joshbranham
Copy link
Copy Markdown
Contributor

/retest

Comment thread deploy/osd-fedramp-machineconfig/stg/pre-4.15/config.yaml
@bergmannf bergmannf force-pushed the add-linter-clusterselection branch from 37d0b45 to cb0ad92 Compare January 12, 2026 15:34
@joshbranham
Copy link
Copy Markdown
Contributor

Should we rebase and get this merged still @bergmannf ?

This should prevent putting an invalid environment selector like 'stage'
or 'int' into this repo.

A workaround exists for syncsets for FedRAMP as those allow 'stage'
until https://issues.redhat.com/browse/HCMSEC-2597 is implemented.

Also updated to ubi9 & python3.12 to allow match expressions.
@bergmannf bergmannf force-pushed the add-linter-clusterselection branch from cb0ad92 to 7e45439 Compare March 10, 2026 07:00
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 10, 2026

Walkthrough

The pull request upgrades the container image in the Makefile from ubi8/python-39 to ubi9/python-312 for template generation workflows. Additionally, scripts/generate_template.py adds environment validation logic, including a new utility function to evaluate expressions and constants to validate environment selectors against known environments.

Changes

Cohort / File(s) Summary
Container Image Upgrade
Makefile
Updated generate-hive-templates workflow to use ubi9/python-312 instead of ubi8/python-39 for both PolicyGenerator and template generation runs.
Environment Validation Logic
scripts/generate_template.py
Added expression_is_true() utility function to evaluate expression objects; introduced new constants (environment_selector, fedramp_selector, valid_environments); added environment validation to verify that matchExpressions reference only known environments with dynamic extension based on fedramp-controlled expressions.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title directly and concisely describes the main change: adding a linter check for environment selectors, which is supported by the validation logic added to scripts/generate_template.py and the Makefile update.
Stable And Deterministic Test Names ✅ Passed This PR modifies only Makefile and a Python script; no Ginkgo test files are present in the repository.
Test Structure And Quality ✅ Passed This PR contains no Ginkgo test code. The repository is a Python/Kubernetes configuration project with changes only to a Makefile and Python script, making the Ginkgo-specific check not applicable.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Mar 10, 2026

@bergmannf: all tests passed!

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/generate_template.py`:
- Around line 104-114: The helper expression_is_true improperly assumes
expression["values"] is a list of strings; normalize the incoming values first
(in expression_is_true) by coercing scalar strings or non-list inputs into a
list and then map each element to a lowercase string (e.g., values =
[str(v).lower() for v in <normalized list>]) before evaluating operators "In",
"NotIn", and "Eq" so selectors like values: "true" are handled correctly and the
FedRAMP selector logic adds the proper stage to valid_envs.
- Around line 179-203: The linter only validates
selectorSyncSet.matchExpressions but misses selectorSyncSet.matchLabels, so
extend the validation to also read sss.get("matchLabels", {}) and verify any
label with key environment_selector has a value present in valid_envs (after you
apply the fedramp_selector workaround via expression_is_true as currently done);
if the label value is not in valid_envs raise the same RuntimeError (use dirpath
and valid_environments in the message) and treat label values as strings
(matchLabels are scalar), referencing config["selectorSyncSet"], sss,
matchExpressions, matchLabels, environment_selector, fedramp_selector,
expression_is_true, valid_environments and add_sss_for() to locate the related
logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 80e14ad4-36e2-4407-8269-d7398fa1d467

📥 Commits

Reviewing files that changed from the base of the PR and between 5b45436 and 7e45439.

📒 Files selected for processing (2)
  • Makefile
  • scripts/generate_template.py

Comment on lines +104 to +114
def expression_is_true(expression: dict[str, Any]) -> bool:
values = [v.lower() for v in expression["values"]]
match expression["operator"]:
case "In":
return "true" in values
case "NotIn":
return "false" in values
case "Eq":
return "true" in values
case _:
return False
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Normalize values before evaluating FedRAMP selectors.

expression_is_true() assumes expression["values"] is already a list of strings, but Line 192 explicitly accepts scalar strings too. With values: "true", this helper lowercases characters instead of the selector value, so the FedRAMP workaround never adds stage to valid_envs and valid configs get rejected.

Suggested fix
 def expression_is_true(expression: dict[str, Any]) -> bool:
-    values = [v.lower() for v in expression["values"]]
+    raw_values = expression.get("values", [])
+    if isinstance(raw_values, list):
+        values = [str(v).lower() for v in raw_values]
+    else:
+        values = [str(raw_values).lower()]
     match expression["operator"]:
         case "In":
             return "true" in values
         case "NotIn":
             return "false" in values
         case "Eq":
             return "true" in values
         case _:
             return False
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def expression_is_true(expression: dict[str, Any]) -> bool:
values = [v.lower() for v in expression["values"]]
match expression["operator"]:
case "In":
return "true" in values
case "NotIn":
return "false" in values
case "Eq":
return "true" in values
case _:
return False
def expression_is_true(expression: dict[str, Any]) -> bool:
raw_values = expression.get("values", [])
if isinstance(raw_values, list):
values = [str(v).lower() for v in raw_values]
else:
values = [str(raw_values).lower()]
match expression["operator"]:
case "In":
return "true" in values
case "NotIn":
return "false" in values
case "Eq":
return "true" in values
case _:
return False
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/generate_template.py` around lines 104 - 114, The helper
expression_is_true improperly assumes expression["values"] is a list of strings;
normalize the incoming values first (in expression_is_true) by coercing scalar
strings or non-list inputs into a list and then map each element to a lowercase
string (e.g., values = [str(v).lower() for v in <normalized list>]) before
evaluating operators "In", "NotIn", and "Eq" so selectors like values: "true"
are handled correctly and the FedRAMP selector logic adds the proper stage to
valid_envs.

Comment on lines +179 to +203
# Verify that environment selectors make sense
sss = config["selectorSyncSet"]
valid_envs = valid_environments.copy()
expressions: list[dict[str, Any]] = sss.get("matchExpressions", []) if sss else []
# FIXME: This is a workaround till https://issues.redhat.com/browse/HCMSEC-2597 is fixed.
# Until then 'stage' is a valid environment for fedramp
for expression in expressions:
if expression["key"] == fedramp_selector:
if expression_is_true(expression):
valid_envs.append('stage')
for expression in expressions:
if not expression["key"] == environment_selector:
continue
values: str|list[str] = expression["values"]
match values:
case list(x):
for value in x:
if value not in valid_envs:
raise RuntimeError(f"The environment value {value} for {dirpath} does not match a known environment: must be one of {valid_environments}")
case str(x):
if x not in valid_envs:
raise RuntimeError(f"The environment value {values} for {dirpath} does not match a known environment: must be one of {valid_environments}")
case _:
raise RuntimeError(f"Received invalid values {values} for {dirpath} for key: {environment_selector}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

matchLabels still bypasses the new environment lint.

The new check only walks selectorSyncSet.matchExpressions, but add_sss_for() also merges selectorSyncSet.matchLabels into the generated selector. A config that sets api.openshift.com/environment via matchLabels can still introduce an invalid value and pass this linter.

Suggested fix
             sss = config["selectorSyncSet"]
             valid_envs = valid_environments.copy()
+            match_labels: dict[str, Any] = sss.get("matchLabels", {}) if sss else {}
             expressions: list[dict[str, Any]] = sss.get("matchExpressions", []) if sss else []
             # FIXME: This is a workaround till https://issues.redhat.com/browse/HCMSEC-2597 is fixed.
             # Until then 'stage' is a valid environment for fedramp
             for expression in expressions:
                 if expression["key"] == fedramp_selector:
                     if expression_is_true(expression):
                         valid_envs.append('stage')
+
+            env_label = match_labels.get(environment_selector)
+            if env_label is not None and env_label not in valid_envs:
+                raise RuntimeError(
+                    f"The environment value {env_label} for {dirpath} does not match a known environment: must be one of {valid_envs}"
+                )
+
             for expression in expressions:
                 if not expression["key"] == environment_selector:
                     continue
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Verify that environment selectors make sense
sss = config["selectorSyncSet"]
valid_envs = valid_environments.copy()
expressions: list[dict[str, Any]] = sss.get("matchExpressions", []) if sss else []
# FIXME: This is a workaround till https://issues.redhat.com/browse/HCMSEC-2597 is fixed.
# Until then 'stage' is a valid environment for fedramp
for expression in expressions:
if expression["key"] == fedramp_selector:
if expression_is_true(expression):
valid_envs.append('stage')
for expression in expressions:
if not expression["key"] == environment_selector:
continue
values: str|list[str] = expression["values"]
match values:
case list(x):
for value in x:
if value not in valid_envs:
raise RuntimeError(f"The environment value {value} for {dirpath} does not match a known environment: must be one of {valid_environments}")
case str(x):
if x not in valid_envs:
raise RuntimeError(f"The environment value {values} for {dirpath} does not match a known environment: must be one of {valid_environments}")
case _:
raise RuntimeError(f"Received invalid values {values} for {dirpath} for key: {environment_selector}")
# Verify that environment selectors make sense
sss = config["selectorSyncSet"]
valid_envs = valid_environments.copy()
match_labels: dict[str, Any] = sss.get("matchLabels", {}) if sss else {}
expressions: list[dict[str, Any]] = sss.get("matchExpressions", []) if sss else []
# FIXME: This is a workaround till https://issues.redhat.com/browse/HCMSEC-2597 is fixed.
# Until then 'stage' is a valid environment for fedramp
for expression in expressions:
if expression["key"] == fedramp_selector:
if expression_is_true(expression):
valid_envs.append('stage')
env_label = match_labels.get(environment_selector)
if env_label is not None and env_label not in valid_envs:
raise RuntimeError(
f"The environment value {env_label} for {dirpath} does not match a known environment: must be one of {valid_envs}"
)
for expression in expressions:
if not expression["key"] == environment_selector:
continue
values: str|list[str] = expression["values"]
match values:
case list(x):
for value in x:
if value not in valid_envs:
raise RuntimeError(f"The environment value {value} for {dirpath} does not match a known environment: must be one of {valid_environments}")
case str(x):
if x not in valid_envs:
raise RuntimeError(f"The environment value {values} for {dirpath} does not match a known environment: must be one of {valid_environments}")
case _:
raise RuntimeError(f"Received invalid values {values} for {dirpath} for key: {environment_selector}")
🧰 Tools
🪛 Ruff (0.15.5)

[warning] 197-197: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 200-200: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 202-202: Avoid specifying long messages outside the exception class

(TRY003)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/generate_template.py` around lines 179 - 203, The linter only
validates selectorSyncSet.matchExpressions but misses
selectorSyncSet.matchLabels, so extend the validation to also read
sss.get("matchLabels", {}) and verify any label with key environment_selector
has a value present in valid_envs (after you apply the fedramp_selector
workaround via expression_is_true as currently done); if the label value is not
in valid_envs raise the same RuntimeError (use dirpath and valid_environments in
the message) and treat label values as strings (matchLabels are scalar),
referencing config["selectorSyncSet"], sss, matchExpressions, matchLabels,
environment_selector, fedramp_selector, expression_is_true, valid_environments
and add_sss_for() to locate the related logic.

@joshbranham
Copy link
Copy Markdown
Contributor

/lgtm

@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Mar 10, 2026
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Mar 10, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: bergmannf, joshbranham

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-merge-bot openshift-merge-bot Bot merged commit 70ec8df into openshift:master Mar 10, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. lgtm Indicates that a PR is ready to be merged.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants