Skip to content

feat: optional Kubernetes pod metadata for activation jobs#1520

Open
amasolov wants to merge 12 commits intoansible:mainfrom
amasolov:feature/activation-k8s-pod-metadata
Open

feat: optional Kubernetes pod metadata for activation jobs#1520
amasolov wants to merge 12 commits intoansible:mainfrom
amasolov:feature/activation-k8s-pod-metadata

Conversation

@amasolov
Copy link
Copy Markdown
Contributor

@amasolov amasolov commented Apr 10, 2026

Summary

Adds optional Kubernetes pod template settings on Activation so rulebook activation Jobs can use a chosen ServiceAccount and custom labels and annotations (for example workload identity patterns).

What changed

  • New model fields: k8s_pod_service_account_name, k8s_pod_labels, k8s_pod_annotations (migration 0071).
  • kubernetes.Engine applies them to the activation Job pod template. Labels app and job-name remain under EDA control; user cannot set those keys.
  • API serializers (create, update, list, read, copy, post-activation validation), analytics activation export, and tests.

Why

Operators need per-activation pod identity (named ServiceAccount) and provider-specific annotations without post-create mutation.

How to test

poetry install -E all
poetry run pytest tests/unit/test_k8s_pod_metadata.py \
  tests/integration/services/activation/engine/test_kubernetes.py::test_engine_start_applies_k8s_pod_metadata \
  tests/integration/analytics/test_analytics_collectors.py -q

Full CI: black, isort, ruff, flake8, OpenAPI generation (see .github/workflows/ci.yaml).

Breaking changes / dependencies

None. Fields are optional; existing activations default to empty / null behaviour unchanged.

Notes

  • The target ServiceAccount must exist in the namespace; cluster RBAC and admission are outside EDA.
  • Feature requests are often tracked at https://github.com/ansible/aap-eda/issues ; this PR implements the controller side for kubernetes deployments.

Fixes #1521

Summary by CodeRabbit

  • New Features

    • Activations can include Kubernetes pod metadata: service account, labels, annotations, and node selector; pod templates apply service account, merge labels (app/job-name) and attach annotations at deploy time.
  • Data / Schema

    • Activation records now persist pod metadata fields via a DB migration.
  • Validation

    • Input normalization and strict validation for service account, labels, annotations, and node selector; configurable allowed service accounts.
  • Tests

    • New and updated unit/integration tests for validation, CSV export, and pod template behavior (CSV includes new pod metadata columns).

Add k8s_pod_service_account_name, k8s_pod_labels, and k8s_pod_annotations on Activation; apply to rulebook activation Job pod templates on Kubernetes. Validate labels and annotations; reserve EDA selector labels app and job-name.

Includes migration, API serializers, analytics export, and tests.

Made-with: Cursor
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds optional Kubernetes pod metadata to activations: four fields (k8s_pod_service_account_name, k8s_pod_labels, k8s_pod_annotations, k8s_pod_node_selector) are added across model, migration, serializers, validators, utilities, container engine pod creation, analytics export, settings, and tests.

Changes

Cohort / File(s) Summary
Model & Migration
src/aap_eda/core/models/activation.py, src/aap_eda/core/migrations/0071_activation_k8s_pod_metadata.py
Adds four Activation model fields: k8s_pod_service_account_name (nullable TextField), k8s_pod_labels (JSONField, default {}), k8s_pod_annotations (JSONField, default {}), k8s_pod_node_selector (JSONField, default {}). Migration creates corresponding DB columns.
Validation & Utilities
src/aap_eda/core/utils/k8s_pod_metadata.py, src/aap_eda/core/validators.py
New validators for pod labels and annotations with Kubernetes-qualified-key rules and size limits; wrapper validators for labels/annotations/nodeSelector and a service-account name checker (gated by deployment type and ALLOWED_SERVICE_ACCOUNTS).
API Serializers
src/aap_eda/api/serializers/activation.py
Adds read/write support and normalization for k8s_pod_service_account_name, k8s_pod_labels, k8s_pod_annotations, k8s_pod_node_selector; integrates _normalize_activation_k8s_pod_fields, validation, payload representation, and refill behavior during updates/reactivations.
Container Engine
src/aap_eda/services/activation/engine/common.py, src/aap_eda/services/activation/engine/kubernetes.py
ContainerRequest extended with k8s pod fields. Kubernetes engine builds pod metadata by merging request labels with defaults (app="eda", job-name), conditionally attaches annotations, applies trimmed serviceAccountName when present, supports nodeSelector, and refactors pod spec assembly.
Analytics
src/aap_eda/analytics/analytics_collectors.py
CSV export (activations_table) updated to include k8s_pod_service_account_name, k8s_pod_labels, k8s_pod_annotations, k8s_pod_node_selector columns.
Settings
src/aap_eda/settings/defaults.py
Adds ALLOWED_SERVICE_ACCOUNTS setting (env: EDA_ALLOWED_SERVICE_ACCOUNTS) to optionally restrict allowed ServiceAccount names; default is empty list (no restriction).
Tests
tests/unit/test_k8s_pod_metadata.py, tests/integration/services/activation/engine/test_kubernetes.py, tests/integration/analytics/test_analytics_collectors.py
Adds comprehensive unit tests for label/annotation/nodeSelector validation and normalization; integration test asserting engine propagates pod metadata into created Job; updates analytics test to expect new CSV columns and minor test refactorings.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Serializer
    participant Validator
    participant Model
    participant Engine
    participant K8s
    participant Settings

    Client->>Serializer: POST/PUT activation with k8s_pod_* payload
    Serializer->>Validator: _normalize_activation_k8s_pod_fields -> validate k8s fields
    Validator->>Settings: check DEPLOYMENT_TYPE / ALLOWED_SERVICE_ACCOUNTS
    Validator-->>Serializer: validation result (errors or normalized data)
    Serializer->>Model: save Activation with k8s_pod_* fields
    Model-->>Serializer: persisted Activation
    Serializer-->>Client: response including _activation_k8s_pod_metadata_payload

    activate Engine
    Engine->>Model: load Activation (contains k8s_pod_* fields)
    Engine->>Engine: build ContainerRequest including k8s_pod_* fields
    Engine->>Engine: merge labels with defaults (app="eda", job-name=...)
    Engine->>Engine: assemble pod metadata & spec (annotations, serviceAccountName, nodeSelector)
    Engine->>K8s: create_namespaced_job(pod_template)
    K8s-->>Engine: Job created
    deactivate Engine
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.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
Title check ✅ Passed The title clearly summarizes the main change: adding optional Kubernetes pod metadata support for activation jobs.
Description check ✅ Passed The description covers all required sections: summary, what changed, why, testing instructions, breaking changes, and notes. All mandatory information is present.
Linked Issues check ✅ Passed The PR fully implements all objectives from issue #1521: pod metadata fields (service account, labels, annotations, node selector), validation, serializers, engine integration, tests, and allowlist setting.
Out of Scope Changes check ✅ Passed All changes are in-scope: model fields, migrations, serializers, validators, engine implementation, tests, and settings directly address issue #1521 requirements. No unrelated modifications present.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

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

🧹 Nitpick comments (2)
tests/integration/services/activation/engine/test_kubernetes.py (1)

317-323: Add an assertion for the reserved job-name pod label.

This test already verifies user metadata merge; asserting job-name too will protect selector compatibility regressions.

Suggested test addition
     labels = created_body.spec.template.metadata.labels
     assert labels["app"] == "eda"
+    assert labels["job-name"] == engine.job_name
     assert labels["cost-centre"] == "cba"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration/services/activation/engine/test_kubernetes.py` around lines
317 - 323, Add an assertion that the reserved pod label "job-name" exists and
has the expected value on the merged pod template labels: after reading
created_body.spec.template.metadata.labels (used above), assert
labels["job-name"] == "<expected-job-name>" (replace with the correct job name
used in the test setup) to ensure the reserved selector label is preserved
during metadata merging.
tests/unit/test_k8s_pod_metadata.py (1)

24-46: Validator tests are a good start; add boundary-path coverage.

Please add cases for non-object payloads and max-length violations (label value 63+, annotation value 256KiB+) so key validation constraints stay protected.

As per coding guidelines, "Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity."

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

In `@tests/unit/test_k8s_pod_metadata.py` around lines 24 - 46, Add boundary-path
unit tests to tests/unit/test_k8s_pod_metadata.py covering non-object payloads
and max-length violations: call validate_k8s_pod_labels and
validate_k8s_pod_annotations with non-dict inputs (e.g., string, list, None) and
assert serializers.ValidationError is raised, and add tests that pass a label
value of length >=63 characters to validate_k8s_pod_labels and an annotation
value exceeding 256KiB (262144 bytes) to validate_k8s_pod_annotations and assert
they raise serializers.ValidationError; reference the existing test functions
test_validate_k8s_pod_labels_ok/test_validate_k8s_pod_annotations_ok to mirror
style and error-assertion patterns.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/aap_eda/api/serializers/activation.py`:
- Around line 610-613: Trim whitespace from service account strings before
persisting: in each serializer validate method that currently does `sa =
data.get("k8s_pod_service_account_name"); if sa is not None and str(sa).strip()
== "": data["k8s_pod_service_account_name"] = None`, instead normalize by
computing trimmed = str(sa).strip() and then set
`data["k8s_pod_service_account_name"] = trimmed if trimmed != "" else None`;
apply the same trimming-and-None logic to the other two analogous validate
methods/locations that currently only map blank strings to None (also update any
similar handling for k8s_pod_labels), so non-empty values are stored trimmed
rather than with surrounding whitespace.

---

Nitpick comments:
In `@tests/integration/services/activation/engine/test_kubernetes.py`:
- Around line 317-323: Add an assertion that the reserved pod label "job-name"
exists and has the expected value on the merged pod template labels: after
reading created_body.spec.template.metadata.labels (used above), assert
labels["job-name"] == "<expected-job-name>" (replace with the correct job name
used in the test setup) to ensure the reserved selector label is preserved
during metadata merging.

In `@tests/unit/test_k8s_pod_metadata.py`:
- Around line 24-46: Add boundary-path unit tests to
tests/unit/test_k8s_pod_metadata.py covering non-object payloads and max-length
violations: call validate_k8s_pod_labels and validate_k8s_pod_annotations with
non-dict inputs (e.g., string, list, None) and assert
serializers.ValidationError is raised, and add tests that pass a label value of
length >=63 characters to validate_k8s_pod_labels and an annotation value
exceeding 256KiB (262144 bytes) to validate_k8s_pod_annotations and assert they
raise serializers.ValidationError; reference the existing test functions
test_validate_k8s_pod_labels_ok/test_validate_k8s_pod_annotations_ok to mirror
style and error-assertion patterns.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 51d0d033-cb9d-46e1-bfa7-e673fffa1ce2

📥 Commits

Reviewing files that changed from the base of the PR and between bf5aca0 and 0140b69.

📒 Files selected for processing (11)
  • src/aap_eda/analytics/analytics_collectors.py
  • src/aap_eda/api/serializers/activation.py
  • src/aap_eda/core/migrations/0071_activation_k8s_pod_metadata.py
  • src/aap_eda/core/models/activation.py
  • src/aap_eda/core/utils/k8s_pod_metadata.py
  • src/aap_eda/core/validators.py
  • src/aap_eda/services/activation/engine/common.py
  • src/aap_eda/services/activation/engine/kubernetes.py
  • tests/integration/analytics/test_analytics_collectors.py
  • tests/integration/services/activation/engine/test_kubernetes.py
  • tests/unit/test_k8s_pod_metadata.py

Comment thread src/aap_eda/api/serializers/activation.py Outdated
Comment thread src/aap_eda/core/utils/k8s_pod_metadata.py Outdated
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 10, 2026

Codecov Report

❌ Patch coverage is 98.80952% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.04%. Comparing base (bf5aca0) to head (5aec94e).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
src/aap_eda/core/utils/k8s_pod_metadata.py 96.61% 2 Missing ⚠️
@@            Coverage Diff             @@
##             main    #1520      +/-   ##
==========================================
+ Coverage   91.92%   92.04%   +0.12%     
==========================================
  Files         239      241       +2     
  Lines       10796    10973     +177     
==========================================
+ Hits         9924    10100     +176     
- Misses        872      873       +1     
Flag Coverage Δ
unit-int-tests-3.11 92.04% <98.80%> (+0.12%) ⬆️
unit-int-tests-3.12 92.04% <98.80%> (+0.12%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/aap_eda/analytics/analytics_collectors.py 98.38% <ø> (ø)
src/aap_eda/api/serializers/activation.py 96.35% <100.00%> (+0.28%) ⬆️
...ore/migrations/0071_activation_k8s_pod_metadata.py 100.00% <100.00%> (ø)
src/aap_eda/core/models/activation.py 100.00% <100.00%> (ø)
src/aap_eda/core/validators.py 94.20% <100.00%> (+0.86%) ⬆️
src/aap_eda/services/activation/engine/common.py 84.04% <100.00%> (+0.40%) ⬆️
...c/aap_eda/services/activation/engine/kubernetes.py 82.69% <100.00%> (+1.11%) ⬆️
src/aap_eda/settings/defaults.py 100.00% <100.00%> (ø)
src/aap_eda/core/utils/k8s_pod_metadata.py 96.61% <96.61%> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

- Trim ServiceAccount and label/annotation strings before persist
- Stricter qualified-name validation for K8s keys (prefix/name, lengths)
- Add integration assert for job-name label; expand unit boundary tests
- Add validator docstrings for CodeRabbit docstring coverage

Made-with: Cursor
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.

🧹 Nitpick comments (2)
src/aap_eda/api/serializers/activation.py (2)

120-154: Add focused tests for the normalization branches.

This helper now owns whitespace trimming, {} coercion, and empty-key failures for all three write/validate paths, but this file still has uncovered changed lines. A few serializer tests around None, whitespace-only ServiceAccount names, trimmed keys/values, and empty-key rejection would reduce regression risk.

Also applies to: 621-636, 812-827, 1312-1342

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

In `@src/aap_eda/api/serializers/activation.py` around lines 120 - 154, Add
focused serializer tests that exercise _normalize_activation_k8s_pod_fields
behavior: cover k8s_pod_service_account_name being None and whitespace-only
(should coerce to None after strip), k8s_pod_labels and k8s_pod_annotations
being None (should coerce to {}), labels/annotations with string keys/values
containing surrounding whitespace (keys/values should be trimmed) and non-string
values preserved, and cases with empty/whitespace-only keys that must raise
serializers.ValidationError; create tests that call the serializer/path that
triggers _normalize_activation_k8s_pod_fields (the same serializer functions
referenced in the diff) and assert normalized outputs or raised errors for each
branch.

540-545: Extract a shared pod-metadata payload helper.

These same three assignments now live in five places. Centralizing them would reduce drift risk the next time these fields change.

♻️ Possible cleanup
+def _activation_k8s_pod_metadata_payload(
+    activation: models.Activation,
+) -> dict:
+    return {
+        "k8s_pod_service_account_name": (
+            activation.k8s_pod_service_account_name
+        ),
+        "k8s_pod_labels": activation.k8s_pod_labels or {},
+        "k8s_pod_annotations": activation.k8s_pod_annotations or {},
+    }
...
-            "k8s_pod_service_account_name": (
-                activation.k8s_pod_service_account_name
-            ),
-            "k8s_pod_labels": activation.k8s_pod_labels or {},
-            "k8s_pod_annotations": activation.k8s_pod_annotations or {},
+            **_activation_k8s_pod_metadata_payload(activation),

As per coding guidelines, "Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity."

Also applies to: 726-730, 978-982, 1263-1267, 1398-1402

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

In `@src/aap_eda/api/serializers/activation.py` around lines 540 - 545, Extract a
shared helper that builds the pod-metadata dict from an Activation-like object
(readers see fields activation.k8s_pod_service_account_name,
activation.k8s_pod_labels, activation.k8s_pod_annotations) and return
{"k8s_pod_service_account_name": ..., "k8s_pod_labels": ... or {},
"k8s_pod_annotations": ... or {}}; replace the five inline occurrences (the
blocks assigning those three keys) with a call to this helper (e.g.,
build_pod_metadata(activation) or Activation.to_pod_metadata()) so all
serializers reuse the same implementation and avoid duplication.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/aap_eda/api/serializers/activation.py`:
- Around line 120-154: Add focused serializer tests that exercise
_normalize_activation_k8s_pod_fields behavior: cover
k8s_pod_service_account_name being None and whitespace-only (should coerce to
None after strip), k8s_pod_labels and k8s_pod_annotations being None (should
coerce to {}), labels/annotations with string keys/values containing surrounding
whitespace (keys/values should be trimmed) and non-string values preserved, and
cases with empty/whitespace-only keys that must raise
serializers.ValidationError; create tests that call the serializer/path that
triggers _normalize_activation_k8s_pod_fields (the same serializer functions
referenced in the diff) and assert normalized outputs or raised errors for each
branch.
- Around line 540-545: Extract a shared helper that builds the pod-metadata dict
from an Activation-like object (readers see fields
activation.k8s_pod_service_account_name, activation.k8s_pod_labels,
activation.k8s_pod_annotations) and return {"k8s_pod_service_account_name": ...,
"k8s_pod_labels": ... or {}, "k8s_pod_annotations": ... or {}}; replace the five
inline occurrences (the blocks assigning those three keys) with a call to this
helper (e.g., build_pod_metadata(activation) or Activation.to_pod_metadata()) so
all serializers reuse the same implementation and avoid duplication.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4eabf054-e86c-4a6e-afc5-c01e87952f09

📥 Commits

Reviewing files that changed from the base of the PR and between f5f36a3 and 1cfe41e.

📒 Files selected for processing (2)
  • src/aap_eda/api/serializers/activation.py
  • src/aap_eda/services/activation/engine/common.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/aap_eda/services/activation/engine/common.py

- Extract _activation_k8s_pod_metadata_payload helper to replace five
  identical pod-metadata blocks in serializers (duplication 9% -> ~2%).
- Add comprehensive tests for validators.py DEPLOYMENT_TYPE gating,
  k8s_pod_metadata.py qualified-key edge cases, _normalize helper
  (trim, None coercion, empty-key rejection), and the payload helper.

Made-with: Cursor
Move repeated serializer field declarations for k8s_pod_service_account_name,
k8s_pod_labels, and k8s_pod_annotations into _K8sPodMetadataReadFields and
_K8sPodMetadataWriteFields mixins. Five serializer classes now inherit them
instead of declaring them inline, targeting SonarCloud duplication < 3%.

Made-with: Cursor
- _normalize_activation_k8s_pod_fields: extract _trim_string_dict helper
  to reduce cognitive complexity from 22 to ~8 (S3776, threshold 15).
- _validate_qualified_metadata_key: extract _validate_prefixed_key and
  _is_valid_dns_subdomain helpers to reduce complexity from 18 to ~6.
- Replace _DNS_SUBDOMAIN regex with per-label matching via _DNS_LABEL to
  eliminate backtracking risk flagged as security hotspot (S5852).

Made-with: Cursor
@ttuffin
Copy link
Copy Markdown
Contributor

ttuffin commented Apr 16, 2026

/run-e2e

@github-actions github-actions Bot deployed to e2e-tests April 16, 2026 13:28 Active
ttuffin
ttuffin previously approved these changes Apr 16, 2026
Copy link
Copy Markdown
Contributor

@ttuffin ttuffin left a comment

Choose a reason for hiding this comment

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

I tested this in a local kind-based deployment and it looks good. The unit test coverage is sufficient and existing eda-qa tests are passing. Thanks for the contribution @amasolov

@ttuffin ttuffin requested a review from a team April 16, 2026 15:54
class _K8sPodMetadataWriteFields:
"""Writable k8s pod-metadata field declarations with validators."""

k8s_pod_service_account_name = serializers.CharField(
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.

@amasolov Can we add Node Selectors to this list too so the user can decide which node will run the Activation. They can enter a dict with all the fields that they would need to identify the node.
When you have a service_account_name do we need credentials to go with it.
Also the UI would have to change to add support for these new fields. The collection would also have to change.
These fields are only needed if the deployment type is k8s. It should get ignored for podman deployment type

@mkanoor
Copy link
Copy Markdown
Contributor

mkanoor commented Apr 17, 2026

@amasolov The use of SA accounts can lead to privilege escalation e.g

POST /api/activations {
    "k8s_pod_service_account_name": "cluster-admin",
    "rulebook_id": 123
  }

With cluster-admin the Rulebook Activation creator can access secrets and anything else that they need.
I think we should use a select few EDA_ALLOWED_SERVICE_ACCOUNTS in the settings which will only allow certain sa names to be used, if we dont put constraints on this we could be running Activations with elevated privileges.

@mkanoor
Copy link
Copy Markdown
Contributor

mkanoor commented Apr 17, 2026

@amasolov We would have to schedule a UI and collection work related to this once the back end has been changed. But overall these are nice things to have in the product.

- New `k8s_pod_node_selector` JSONField on Activation model (migration
  0071) allows users to schedule activation job pods onto specific nodes
  via Kubernetes nodeSelector. Addresses AAPRFE-2840.
- New `ALLOWED_SERVICE_ACCOUNTS` setting (env: EDA_ALLOWED_SERVICE_ACCOUNTS)
  restricts which ServiceAccount names can be specified on activations,
  preventing privilege escalation via arbitrary SA names.
- Kubernetes engine applies nodeSelector to the pod template spec.
- Podman deployments: all k8s_pod_* fields are silently ignored (validators
  skip when DEPLOYMENT_TYPE != "k8s", podman engine never reads them).
- Serializers, analytics export, and tests updated for both features.

Made-with: Cursor
@amasolov
Copy link
Copy Markdown
Contributor Author

Thanks @mkanoor, all addressed in e030241:

  • Node selectors: Added k8s_pod_node_selector (JSONField). Applied as nodeSelector on the Job pod spec for k8s deployments. Covers AAPRFE-2840.
  • SA allowlist: New ALLOWED_SERVICE_ACCOUNTS setting (env: EDA_ALLOWED_SERVICE_ACCOUNTS). Non-empty list restricts which SA names are permitted. Empty (default) allows any valid name.
  • SA credentials: Not needed. The SA must exist in the namespace; EDA just references it by name. Cloud identity (IRSA etc.) is configured on the SA itself.
  • Podman: All k8s_pod_* fields silently ignored for podman deployments.
  • UI/collection: Agreed, will need follow-up work.

Tests that patch aap_eda.core.validators.settings use a MagicMock,
so ALLOWED_SERVICE_ACCOUNTS resolves to a truthy MagicMock object
instead of the default empty list. Explicitly set it to [] in tests
that are not exercising the allowlist logic.

Signed-off-by: Artem Masolov <amasolov@redhat.com>
Made-with: Cursor
mkanoor
mkanoor previously approved these changes Apr 20, 2026
Copy link
Copy Markdown
Contributor

@mkanoor mkanoor left a comment

Choose a reason for hiding this comment

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

LGTM

@mkanoor
Copy link
Copy Markdown
Contributor

mkanoor commented Apr 20, 2026

@amasolov Do you want to set the resources.limits.memory since we are not doing that for k8s but only for podman currently. We can add a similar one for k8s.

@amasolov
Copy link
Copy Markdown
Contributor Author

Good shout. Happy to tackle that as a follow-up PR to keep this one focused on pod metadata. I'll open an issue for it.

@amasolov
Copy link
Copy Markdown
Contributor Author

Good shout. The k8s engine doesn't set any resource requests/limits today, so I've added K8S_MEM_LIMIT and K8S_CPU_LIMIT settings (env: EDA_K8S_MEM_LIMIT, EDA_K8S_CPU_LIMIT) to bring parity with PODMAN_MEM_LIMIT. Tackled as a separate PR to keep this one focused on pod metadata:

- Annotation validation: enforce total size (256 KiB) across all keys
  and values combined instead of per-value limit, matching Kubernetes
  apimachinery behaviour.
- Remove blanket 253-char cap on qualified metadata keys. Prefixed keys
  (prefix/name) can be up to 253+1+63=317 chars; validation now relies
  on prefix(<=253) and name(<=63) checks individually.
- NodeSelector validation: validate keys as Kubernetes qualified names
  and values as label values (<=63 chars, name segment regex), not just
  string type checks.
- Update tests accordingly.

Signed-off-by: Artem Masolov <amasolov@redhat.com>
Made-with: Cursor
A 253-char prefix must be a valid DNS subdomain (labels <=63 chars
separated by dots). The previous test used 253 'a' chars without
dots, which fails the per-label length check.

Signed-off-by: Artem Masolov <amasolov@redhat.com>
Made-with: Cursor
@sonarqubecloud
Copy link
Copy Markdown

@ttuffin
Copy link
Copy Markdown
Contributor

ttuffin commented Apr 23, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/aap_eda/api/serializers/activation.py (1)

735-776: ⚠️ Potential issue | 🟠 Major

Validate copied pod metadata against the current allowlist.

copy() now persists the source activation’s k8s_pod_* values directly through ModelSerializer.create(), bypassing the new write-field validators. If ALLOWED_SERVICE_ACCOUNTS is tightened after an activation was created, copying that activation can still create a new one with a now-disallowed ServiceAccount.

Proposed fix
     def copy(self) -> dict:
         activation: models.Activation = self.instance
+        pod_metadata = _activation_k8s_pod_metadata_payload(activation)
+        _normalize_activation_k8s_pod_fields(pod_metadata)
+        validators.check_if_k8s_pod_service_account_name_valid(
+            pod_metadata["k8s_pod_service_account_name"]
+        )
+        validators.check_if_k8s_pod_labels_valid(
+            pod_metadata["k8s_pod_labels"]
+        )
+        validators.check_if_k8s_pod_annotations_valid(
+            pod_metadata["k8s_pod_annotations"]
+        )
+        validators.check_if_k8s_pod_node_selector_valid(
+            pod_metadata["k8s_pod_node_selector"]
+        )
         copied_data = {
             "name": self.validated_data["name"],
             "description": activation.description,
@@
             "enable_persistence": activation.enable_persistence,
             "rule_engine_credential_id": activation.rule_engine_credential_id,
-            **_activation_k8s_pod_metadata_payload(activation),
+            **pod_metadata,
         }

As per coding guidelines, "Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity."

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

In `@src/aap_eda/api/serializers/activation.py` around lines 735 - 776, The copy()
method currently bypasses write-field validators by passing activation k8s pod
metadata straight into super().create(copied_data); before calling
super().create, run the serializer field validators for any pod-related fields
(e.g., "k8s_service_account", "k8s_service_name", and any other k8s_* fields
produced by _activation_k8s_pod_metadata_payload) and reject or normalize values
that fail the allowlist; implement this by iterating the relevant field names,
calling self.fields[field_name].run_validation(copied_data[field_name]) (or
raising serializers.ValidationError) and replacing copied_data[field_name] with
the validated value, then call super().create(copied_data).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/aap_eda/api/serializers/activation.py`:
- Around line 735-776: The copy() method currently bypasses write-field
validators by passing activation k8s pod metadata straight into
super().create(copied_data); before calling super().create, run the serializer
field validators for any pod-related fields (e.g., "k8s_service_account",
"k8s_service_name", and any other k8s_* fields produced by
_activation_k8s_pod_metadata_payload) and reject or normalize values that fail
the allowlist; implement this by iterating the relevant field names, calling
self.fields[field_name].run_validation(copied_data[field_name]) (or raising
serializers.ValidationError) and replacing copied_data[field_name] with the
validated value, then call super().create(copied_data).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3729a7b9-fcb4-440d-b514-2a796a6f036d

📥 Commits

Reviewing files that changed from the base of the PR and between 89890ec and 5aec94e.

📒 Files selected for processing (12)
  • src/aap_eda/analytics/analytics_collectors.py
  • src/aap_eda/api/serializers/activation.py
  • src/aap_eda/core/migrations/0071_activation_k8s_pod_metadata.py
  • src/aap_eda/core/models/activation.py
  • src/aap_eda/core/utils/k8s_pod_metadata.py
  • src/aap_eda/core/validators.py
  • src/aap_eda/services/activation/engine/common.py
  • src/aap_eda/services/activation/engine/kubernetes.py
  • src/aap_eda/settings/defaults.py
  • tests/integration/analytics/test_analytics_collectors.py
  • tests/integration/services/activation/engine/test_kubernetes.py
  • tests/unit/test_k8s_pod_metadata.py
✅ Files skipped from review due to trivial changes (3)
  • tests/integration/analytics/test_analytics_collectors.py
  • src/aap_eda/services/activation/engine/common.py
  • src/aap_eda/core/utils/k8s_pod_metadata.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/aap_eda/analytics/analytics_collectors.py
  • src/aap_eda/core/models/activation.py
  • tests/integration/services/activation/engine/test_kubernetes.py
  • tests/unit/test_k8s_pod_metadata.py

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.

Feature: optional Kubernetes pod metadata per rulebook activation (EDA controller)

4 participants