Skip to content

feat: optional Kubernetes pod metadata for activation jobs#1520

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

feat: optional Kubernetes pod metadata for activation jobs#1520
amasolov wants to merge 8 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); 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 names, labels, and annotations.
  • Tests

    • New and updated unit/integration tests for validation, CSV export, and pod template behavior.

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: three fields (k8s_pod_service_account_name, k8s_pod_labels, k8s_pod_annotations) are added across the model, migration, serializers, validators, utilities, container engine pod creation, analytics export, 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 three Activation model fields: k8s_pod_service_account_name (nullable TextField), k8s_pod_labels (JSONField, default {}), k8s_pod_annotations (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: validate_k8s_pod_labels, validate_k8s_pod_annotations with Kubernetes-style qualified-name rules and limits; wrapper validators and a service-account name checker added (validation gated by deployment type).
API Serializers
src/aap_eda/api/serializers/activation.py
Adds k8s_pod_service_account_name, k8s_pod_labels, k8s_pod_annotations to Activation serializers (list/create/update/read/post). Introduces _activation_k8s_pod_metadata_payload and _normalize_activation_k8s_pod_fields, integrates normalization/validation into create/update/reactivate flows and representation.
Container Engine
src/aap_eda/services/activation/engine/common.py, src/aap_eda/services/activation/engine/kubernetes.py
Extends ContainerRequest with k8s pod fields. Kubernetes engine merges provided labels with defaults (app="eda", job-name=<job_name>), conditionally attaches annotations, trims/applies serviceAccountName to pod spec when provided, and refactors pod spec assembly to a unified dict.
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 columns.
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 validation and normalization; integration test asserting pod metadata applied to created Job; updates analytics test header and minor test mocking cleanup.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Serializer as Activation Serializer
    participant Validator as Validation Layer
    participant Model as Activation Model
    participant Engine as Kubernetes Engine
    participant K8s as Kubernetes API

    Client->>Serializer: POST activation with k8s_pod_* fields
    Serializer->>Validator: normalize & validate service account, labels, annotations
    Validator-->>Serializer: validation result / errors
    Serializer->>Model: save Activation with k8s_pod_* fields
    Model-->>Serializer: Activation persisted
    Serializer-->>Client: response with stored metadata

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

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.10% 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 and concisely describes the main feature added: optional Kubernetes pod metadata for activation jobs.
Description check ✅ Passed The description covers all required template sections: summary, what changed, why, how to test, breaking changes, and links to the issue.
Linked Issues check ✅ Passed All objectives from issue #1521 are met: model fields added, Kubernetes engine applies metadata, serializers updated, validation implemented, and tests provided.
Out of Scope Changes check ✅ Passed All changes align with the linked issue objective. Model fields, serializers, validators, engine implementation, migrations, analytics export, and tests are all directly related to adding Kubernetes pod metadata support.

✏️ 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

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 10, 2026

Codecov Report

❌ Patch coverage is 97.24138% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.99%. Comparing base (bf5aca0) to head (affbb7b).

Files with missing lines Patch % Lines
src/aap_eda/core/utils/k8s_pod_metadata.py 93.44% 4 Missing ⚠️
@@            Coverage Diff             @@
##             main    #1520      +/-   ##
==========================================
+ Coverage   91.92%   91.99%   +0.07%     
==========================================
  Files         239      241       +2     
  Lines       10796    10936     +140     
==========================================
+ Hits         9924    10061     +137     
- Misses        872      875       +3     
Flag Coverage Δ
unit-int-tests-3.11 91.99% <97.24%> (+0.07%) ⬆️
unit-int-tests-3.12 91.99% <97.24%> (+0.07%) ⬆️

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.32% <100.00%> (+0.25%) ⬆️
...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 93.86% <100.00%> (+0.53%) ⬆️
src/aap_eda/services/activation/engine/common.py 83.95% <100.00%> (+0.30%) ⬆️
...c/aap_eda/services/activation/engine/kubernetes.py 82.51% <100.00%> (+0.92%) ⬆️
src/aap_eda/core/utils/k8s_pod_metadata.py 93.44% <93.44%> (ø)
🚀 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
@sonarqubecloud
Copy link
Copy Markdown

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)

2 participants