Skip to content

WIP: etcd backups - #2952

Draft
bhperry wants to merge 1 commit into
openshift:masterfrom
bhperry:etcd-backups
Draft

WIP: etcd backups#2952
bhperry wants to merge 1 commit into
openshift:masterfrom
bhperry:etcd-backups

Conversation

@bhperry

@bhperry bhperry commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jul 24, 2026
@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: LGTM mode

@openshift-ci

openshift-ci Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@openshift-ci

openshift-ci Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Hello @bhperry! Some important instructions when contributing to openshift/api:
API design plays an important part in the user experience of OpenShift and as such API PRs are subject to a high level of scrutiny to ensure they follow our best practices. If you haven't already done so, please review the OpenShift API Conventions and ensure that your proposed changes are compliant. Following these conventions will help expedite the api review process for your PR.

@openshift-ci openshift-ci Bot added the size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. label Jul 24, 2026
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 7e812239-7ad8-4d56-83d4-373f60a94b1a

📥 Commits

Reviewing files that changed from the base of the PR and between 4ea684c and 3bad281.

⛔ Files ignored due to path filters (16)
  • config/v1alpha1/zz_generated.crd-manifests/0000_10_config-operator_01_backups.crd.yaml is excluded by !**/zz_generated.crd-manifests/*
  • config/v1alpha1/zz_generated.deepcopy.go is excluded by !**/zz_generated*
  • config/v1alpha1/zz_generated.featuregated-crd-manifests.yaml is excluded by !**/zz_generated*
  • config/v1alpha1/zz_generated.featuregated-crd-manifests/backups.config.openshift.io/AutomatedEtcdBackup.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • config/v1alpha1/zz_generated.model_name.go is excluded by !**/zz_generated*
  • config/v1alpha1/zz_generated.swagger_doc_generated.go is excluded by !**/zz_generated*
  • openapi/generated_openapi/zz_generated.openapi.go is excluded by !openapi/**, !**/zz_generated*
  • openapi/openapi.json is excluded by !openapi/**
  • operator/v1alpha1/zz_generated.crd-manifests/0000_10_etcd_01_etcdbackuppolicies.crd.yaml is excluded by !**/zz_generated.crd-manifests/*
  • operator/v1alpha1/zz_generated.crd-manifests/0000_10_etcd_01_etcdbackups.crd.yaml is excluded by !**/zz_generated.crd-manifests/*
  • operator/v1alpha1/zz_generated.deepcopy.go is excluded by !**/zz_generated*
  • operator/v1alpha1/zz_generated.featuregated-crd-manifests.yaml is excluded by !**/zz_generated*
  • operator/v1alpha1/zz_generated.featuregated-crd-manifests/etcdbackuppolicies.operator.openshift.io/AutomatedEtcdBackup.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • operator/v1alpha1/zz_generated.featuregated-crd-manifests/etcdbackups.operator.openshift.io/AutomatedEtcdBackup.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • operator/v1alpha1/zz_generated.model_name.go is excluded by !**/zz_generated*
  • operator/v1alpha1/zz_generated.swagger_doc_generated.go is excluded by !**/zz_generated*
📒 Files selected for processing (9)
  • config/v1alpha1/register.go
  • config/v1alpha1/types_backup.go
  • hack/update-payload-crds.sh
  • operator/v1alpha1/register.go
  • operator/v1alpha1/types_etcdbackup.go
  • operator/v1alpha1/types_etcdbackuppolicy.go
  • payload-manifests/crds/0000_10_config-operator_01_backups.crd.yaml
  • payload-manifests/crds/0000_10_etcd_01_etcdbackuppolicies.crd.yaml
  • payload-manifests/crds/0000_10_etcd_01_etcdbackups.crd.yaml
💤 Files with no reviewable changes (3)
  • payload-manifests/crds/0000_10_config-operator_01_backups.crd.yaml
  • config/v1alpha1/types_backup.go
  • config/v1alpha1/register.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • hack/update-payload-crds.sh
  • payload-manifests/crds/0000_10_etcd_01_etcdbackups.crd.yaml
  • operator/v1alpha1/types_etcdbackuppolicy.go
  • payload-manifests/crds/0000_10_etcd_01_etcdbackuppolicies.crd.yaml
  • operator/v1alpha1/types_etcdbackup.go

📝 Walkthrough

Walkthrough

The change replaces EtcdBackup PVC-only configuration with selectable PVC or local storage, adds node and backup result status fields, and introduces typed job references and condition reasons. It adds the cluster-scoped EtcdBackupPolicy API with scheduling, node selection, storage, retention, and status fields. Matching CRD schemas and scheme registrations are updated, while legacy Backup types are removed from config API registration and CRI-O credential provider types are registered.

🚥 Pre-merge checks | ✅ 13 | ❌ 2

❌ Failed checks (2 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is related to the PR, but it is too vague to convey the main change. Rename it to describe the primary change, such as introducing etcd backup policy and backup API updates.
Description check ❓ Inconclusive No pull request description was provided, so there is no meaningful description to evaluate. Add a brief description of the changes and their purpose.
✅ Passed checks (13 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Stable And Deterministic Test Names ✅ Passed No test files or Ginkgo titles were added/changed in this PR; only API types, CRDs, and generated artifacts changed.
Test Structure And Quality ✅ Passed No Ginkgo test files were modified; the PR only changes API types/manifests, so the test-quality check is not applicable.
Microshift Test Compatibility ✅ Passed No new Ginkgo/e2e tests were added; the diff only changes API types/CRDs and generated manifests, so MicroShift compatibility isn’t implicated.
Single Node Openshift (Sno) Test Compatibility ✅ Passed No new Ginkgo e2e tests were added; the PR changes API/CRD/codegen files only, with no *_test.go files or It/Describe/Context/When test bodies.
Topology-Aware Scheduling Compatibility ✅ Passed Only API/CRD schema and scheme-registration changes; no deployments, pods, affinity, spread constraints, or controller scheduling logic were added.
Ote Binary Stdout Contract ✅ Passed Touched files are API types/registers only; no main/init/TestMain/BeforeSuite/RunSpecs or stdout logging writes were added.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No new Ginkgo/e2e tests were added; the diff is API/CRD/schema only, so IPv4/disconnected-network compatibility is not applicable.
No-Weak-Crypto ✅ Passed Touched files only add/adjust CRD types and scheme registration; imports are k8s resource/metav1 only, with no crypto algorithms, custom crypto, or secret comparisons.
Container-Privileges ✅ Passed PR only updates CRDs/API types; no privileged/hostPID/hostNetwork/hostIPC/SYS_ADMIN/allowPrivilegeEscalation settings were found.
No-Sensitive-Data-In-Logs ✅ Passed No new logging statements were added in the changed source files; the only sensitive-term hit was a comment about an encryption-secret ID.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

Error: build linters: unable to load custom analyzer "kubeapilinter": tools/_output/bin/kube-api-linter.so, plugin: not implemented
The command is terminated due to an error: build linters: unable to load custom analyzer "kubeapilinter": tools/_output/bin/kube-api-linter.so, plugin: not implemented


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.

@openshift-ci

openshift-ci Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign joelspeed for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found 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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
operator/v1alpha1/types_etcdbackuppolicy.go (1)

34-44: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

No format validation for Schedule (cron) or TimeZone, unlike the legacy Backup CRD it supersedes.

The pre-existing config.openshift.io Backup.spec.etcd.schedule/timeZone fields enforced cron and IANA-timezone patterns; the new EtcdBackupPolicySpec fields have no equivalent, so invalid cron expressions can be persisted.
[recommended_refactor_low_effort_note]

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@operator/v1alpha1/types_etcdbackuppolicy.go` around lines 34 - 44, Add
equivalent validation markers to EtcdBackupPolicySpec.Schedule and TimeZone,
enforcing valid cron expressions for Schedule and valid IANA time-zone names for
TimeZone as the superseded Backup CRD did. Preserve Schedule as required and
TimeZone as optional while ensuring invalid values are rejected during CRD
validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@operator/v1alpha1/types_etcdbackup.go`:
- Around line 90-94: Update the kubebuilder XValidation message on the Path
field to say “path is immutable once set” instead of referring to “name”; leave
the immutability rule and other field metadata unchanged.

In `@operator/v1alpha1/types_etcdbackuppolicy.go`:
- Around line 52-55: Add non-negative validation for both NodeCount and
MaxQuantity using the appropriate Kubernetes validation markers, while
preserving NodeCount’s zero behavior for selecting all nodes. Mark NodeCount
with +optional consistently with its sibling fields, and update the
corresponding field declarations/documentation so generated CRD schemas reject
negative values.
- Around line 70-95: Update EtcdBackupPolicyRetentionRule to make the
discriminator enum accept both MaxQuantity and MaxSize. Add the corresponding
+unionMember tags to MaxQuantity and MaxSize, and add conditional XValidation
rules matching the EtcdBackupStorage union pattern so the field selected by Type
is required while the other is unset.

In `@payload-manifests/crds/0000_10_etcd_01_etcdbackups.crd.yaml`:
- Around line 88-94: Regenerate this CRD after correcting the kubebuilder
validation marker for the Path field in the EtcdBackup Go type, so the generated
validation message identifies “path” rather than “name.” Do not edit the
generated YAML directly.

---

Nitpick comments:
In `@operator/v1alpha1/types_etcdbackuppolicy.go`:
- Around line 34-44: Add equivalent validation markers to
EtcdBackupPolicySpec.Schedule and TimeZone, enforcing valid cron expressions for
Schedule and valid IANA time-zone names for TimeZone as the superseded Backup
CRD did. Preserve Schedule as required and TimeZone as optional while ensuring
invalid values are rejected during CRD validation.
🪄 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: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: b85840b1-9249-43dc-8ef7-89d067ce8593

📥 Commits

Reviewing files that changed from the base of the PR and between 967cc4c and f0c10e6.

⛔ Files ignored due to path filters (16)
  • config/v1alpha1/zz_generated.crd-manifests/0000_10_config-operator_01_backups.crd.yaml is excluded by !**/zz_generated.crd-manifests/*
  • config/v1alpha1/zz_generated.deepcopy.go is excluded by !**/zz_generated*
  • config/v1alpha1/zz_generated.featuregated-crd-manifests.yaml is excluded by !**/zz_generated*
  • config/v1alpha1/zz_generated.featuregated-crd-manifests/backups.config.openshift.io/AutomatedEtcdBackup.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • config/v1alpha1/zz_generated.model_name.go is excluded by !**/zz_generated*
  • config/v1alpha1/zz_generated.swagger_doc_generated.go is excluded by !**/zz_generated*
  • openapi/generated_openapi/zz_generated.openapi.go is excluded by !openapi/**, !**/zz_generated*
  • openapi/openapi.json is excluded by !openapi/**
  • operator/v1alpha1/zz_generated.crd-manifests/0000_10_etcd_01_etcdbackuppolicies.crd.yaml is excluded by !**/zz_generated.crd-manifests/*
  • operator/v1alpha1/zz_generated.crd-manifests/0000_10_etcd_01_etcdbackups.crd.yaml is excluded by !**/zz_generated.crd-manifests/*
  • operator/v1alpha1/zz_generated.deepcopy.go is excluded by !**/zz_generated*
  • operator/v1alpha1/zz_generated.featuregated-crd-manifests.yaml is excluded by !**/zz_generated*
  • operator/v1alpha1/zz_generated.featuregated-crd-manifests/etcdbackuppolicies.operator.openshift.io/AutomatedEtcdBackup.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • operator/v1alpha1/zz_generated.featuregated-crd-manifests/etcdbackups.operator.openshift.io/AutomatedEtcdBackup.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • operator/v1alpha1/zz_generated.model_name.go is excluded by !**/zz_generated*
  • operator/v1alpha1/zz_generated.swagger_doc_generated.go is excluded by !**/zz_generated*
📒 Files selected for processing (7)
  • config/v1alpha1/register.go
  • config/v1alpha1/types_backup.go
  • operator/v1alpha1/register.go
  • operator/v1alpha1/types_etcdbackup.go
  • operator/v1alpha1/types_etcdbackuppolicy.go
  • payload-manifests/crds/0000_10_config-operator_01_backups.crd.yaml
  • payload-manifests/crds/0000_10_etcd_01_etcdbackups.crd.yaml
💤 Files with no reviewable changes (2)
  • config/v1alpha1/types_backup.go
  • config/v1alpha1/register.go

Comment thread operator/v1alpha1/types_etcdbackup.go
Comment thread operator/v1alpha1/types_etcdbackuppolicy.go
Comment thread operator/v1alpha1/types_etcdbackuppolicy.go
Comment thread payload-manifests/crds/0000_10_etcd_01_etcdbackups.crd.yaml
@bhperry
bhperry force-pushed the etcd-backups branch 3 times, most recently from 8887fba to 4ea684c Compare July 29, 2026 21:56

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@operator/v1alpha1/types_etcdbackup.go`:
- Around line 128-131: Change the FileSize field in the backup status type from
resource.Quantity to *resource.Quantity so an unset optional value is omitted by
json:"fileSize,omitempty". Preserve its existing JSON tag, validation markers,
and file-size semantics.
- Around line 106-111: The optional Conditions field in the etcd backup status
currently lacks omission behavior for nil slices. Update Conditions to use the
existing JSON tag with omitempty, while preserving its list validation markers
and type.
🪄 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: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: b2e25909-3b06-43f8-817c-6c83ab7d986b

📥 Commits

Reviewing files that changed from the base of the PR and between 8887fba and 4ea684c.

⛔ Files ignored due to path filters (16)
  • config/v1alpha1/zz_generated.crd-manifests/0000_10_config-operator_01_backups.crd.yaml is excluded by !**/zz_generated.crd-manifests/*
  • config/v1alpha1/zz_generated.deepcopy.go is excluded by !**/zz_generated*
  • config/v1alpha1/zz_generated.featuregated-crd-manifests.yaml is excluded by !**/zz_generated*
  • config/v1alpha1/zz_generated.featuregated-crd-manifests/backups.config.openshift.io/AutomatedEtcdBackup.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • config/v1alpha1/zz_generated.model_name.go is excluded by !**/zz_generated*
  • config/v1alpha1/zz_generated.swagger_doc_generated.go is excluded by !**/zz_generated*
  • openapi/generated_openapi/zz_generated.openapi.go is excluded by !openapi/**, !**/zz_generated*
  • openapi/openapi.json is excluded by !openapi/**
  • operator/v1alpha1/zz_generated.crd-manifests/0000_10_etcd_01_etcdbackuppolicies.crd.yaml is excluded by !**/zz_generated.crd-manifests/*
  • operator/v1alpha1/zz_generated.crd-manifests/0000_10_etcd_01_etcdbackups.crd.yaml is excluded by !**/zz_generated.crd-manifests/*
  • operator/v1alpha1/zz_generated.deepcopy.go is excluded by !**/zz_generated*
  • operator/v1alpha1/zz_generated.featuregated-crd-manifests.yaml is excluded by !**/zz_generated*
  • operator/v1alpha1/zz_generated.featuregated-crd-manifests/etcdbackuppolicies.operator.openshift.io/AutomatedEtcdBackup.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • operator/v1alpha1/zz_generated.featuregated-crd-manifests/etcdbackups.operator.openshift.io/AutomatedEtcdBackup.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • operator/v1alpha1/zz_generated.model_name.go is excluded by !**/zz_generated*
  • operator/v1alpha1/zz_generated.swagger_doc_generated.go is excluded by !**/zz_generated*
📒 Files selected for processing (9)
  • config/v1alpha1/register.go
  • config/v1alpha1/types_backup.go
  • hack/update-payload-crds.sh
  • operator/v1alpha1/register.go
  • operator/v1alpha1/types_etcdbackup.go
  • operator/v1alpha1/types_etcdbackuppolicy.go
  • payload-manifests/crds/0000_10_config-operator_01_backups.crd.yaml
  • payload-manifests/crds/0000_10_etcd_01_etcdbackuppolicies.crd.yaml
  • payload-manifests/crds/0000_10_etcd_01_etcdbackups.crd.yaml
💤 Files with no reviewable changes (3)
  • config/v1alpha1/register.go
  • payload-manifests/crds/0000_10_config-operator_01_backups.crd.yaml
  • config/v1alpha1/types_backup.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • payload-manifests/crds/0000_10_etcd_01_etcdbackups.crd.yaml
  • operator/v1alpha1/register.go
  • operator/v1alpha1/types_etcdbackuppolicy.go

Comment on lines 106 to +111
// conditions provide details on the status of the etcd backup job.
// +kubebuilder:validation:Optional
// +listType=map
// +listMapKey=type
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
Conditions []metav1.Condition `json:"conditions"`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify convention: other Condition slice fields in this repo keep omitempty despite listType=map.
rg -n -B2 'Conditions \[\]metav1.Condition' --type go

Repository: openshift/api

Length of output: 151


🏁 Script executed:

#!/bin/bash
set -u

echo "## candidate files"
git ls-files | rg '(^|/)types_etcdbackup\.go$|types.*etcd|etcdbackup' | head -100

echo
echo "## etcdbackup types outline/content around Conditions"
while IFS= read -r f; do
  echo "--- $f"
  wc -l "$f"
  sed -n '1,180p' "$f" | cat -n
done < <(git ls-files | rg '(^|/)types_etcdbackup\.go$')

echo
echo "## Condition slice declarations with omitempty status"
python3 - <<'PY'
import pathlib, re
for path in pathlib.Path('.').rglob('*.go'):
    if '.git' in path.parts: continue
    text=path.read_text()
    for i,line in enumerate(text.splitlines(),1):
        if re.search(r'(?m)^\s*Conditions\s+\[\]metav1\.Condition', line):
            omitempty='omitempty' in line.split('`',1)[1] if '`' in line else False
            print(f"{path}:{i}: omitempty={omitempty}: {line.strip()}")
PY

echo
echo "## JSON marshaling behavior for nil vs empty []metav1.Condition-like slices"
python3 - <<'PY'
import json
for value in [None, []]:
    print(json.dumps({"conditions": value}))
PY

Repository: openshift/api

Length of output: 13261


🏁 Script executed:

#!/bin/bash
set -u

echo "## operator/v1alpha1/types_etcdbackup.go lines 1-140"
sed -n '1,140p' operator/v1alpha1/types_etcdbackup.go | cat -n

echo
echo "## CRD openAPIV3Schema for EtcdBackup.conditions"
python3 - <<'PY'
import pathlib, yaml
for p in pathlib.Path('operator/v1alpha1/zz_generated.crd-manifests', 'payload-manifests/crds').glob('*etcdbackups*.yaml'):
    text=p.read_text()
    docs=text.split('---')
    print(f'--- {p} ---')
    for doc in docs:
        if not doc.strip(): continue
        data=yaml.safe_load(doc)
        if not data or data.get('apiVersion') != 'apiextensions.k8s.io/v1': continue
        name = (data.get('metadata') or {}).get('name', '')
        if 'etcdbackups' in name:
            try:
                schema=data['spec']['versions'][0]['schema']['openAPIV3Schema']
            except Exception as e:
                print("schema access failed:", e)
                continue
            # print relevant schema paths
            import json
            paths=['definitions','$defs','properties','status','properties','conditions','x-kubernetes-list-map-keys','x-kubernetes-list-type']
            print(name)
            j=json.dumps(schema, sort_keys=True, indent=2)
            for term in ['properties', 'status', 'conditions', 'type', 'items', 'title', 'nullable']:
                if term in j:
                    print(term)
PY

Repository: openshift/api

Length of output: 6732


🏁 Script executed:

#!/bin/bash
set -u

echo "## CRD files for EtcdBackup conditions"
python3 - <<'PY'
import pathlib
for p in [
    pathlib.Path('operator/v1alpha1/zz_generated.crd-manifests/0000_10_etcd_01_etcdbackups.crd.yaml'),
    pathlib.Path('payload-manifests/crds/0000_10_etcd_01_etcdbackups.crd.yaml'),
]:
    text=p.read_text()
    print(f'--- {p} ---')
    lines=text.splitlines()
    start=None
    in_conds=False
    for i,l in enumerate(lines,1):
        if "  conditions:" in l or "    conditions:" in l:
            in_conds=True
            start=max(1,i-10)
        if in_conds and i <= start+80:
            print(f"{i:4}: {l}")
        if in_conds and i > 200:
            break
PY

echo
echo "## generated CRD schema nullability hints for conditions"
rg -n -C 4 'conditions:' operator/v1alpha1/zz_generated.crd-manifests/0000_10_etcd_01_etcdbackups.crd.yaml payload-manifests/crds/0000_10_etcd_01_etcdbackups.crd.yaml

Repository: openshift/api

Length of output: 10683


🌐 Web query:

Kubernetes CRD structural schema arrays omit null listType map x-kubernetes-list-type x-kubernetes-list-map-keys

💡 Result:

In Kubernetes Custom Resource Definition (CRD) structural schemas, the x-kubernetes-list-type and x-kubernetes-list-map-keys extensions are used to define the behavior and topology of arrays, particularly for Server-Side Apply [1][2]. x-kubernetes-list-type This extension defines the topology of a list and must be one of three values: 1. map: The list elements act like map entries indexed by one or more keys [3][1]. Order is preserved, and unique keys ensure correct merging [2]. This must only be used on lists of objects [3][4]. 2. set: The list acts as a set where all elements must be unique [3][2]. Elements must be scalars, or objects/arrays marked with x-kubernetes-map-type or x-kubernetes-list-type as "atomic" [3][4]. 3. atomic: The entire list is treated as a single entity (like a scalar). Updates replace the entire list [3][1]. If not specified, this is the default behavior for arrays [2][4]. x-kubernetes-list-map-keys This extension is required when x-kubernetes-list-type is set to "map" [3][4]. It specifies an array of strings representing the fields within the list's child objects that serve as the unique keys [5][1]. These key fields must be scalar types and cannot be nested [3][2]. For reliable identification of list items, the specified key properties should either be marked as required or have a default value [2][4]. Null values in arrays Regarding null values in arrays, CRD structural schemas support the nullable: true attribute for array items [6][7]. While early implementations or related utilities (such as older versions of json-patch) experienced issues with dropping nulls, modern Kubernetes versions support nullability, allowing null values to be represented and preserved in lists [6][8]. To allow an explicit "null" as a valid value, nullable: true must be applied to the item schema [9][8]. Summary of Key Constraints: - x-kubernetes-list-map-keys can only be used when x-kubernetes-list-type is "map" [5][4]. - Keys for "map" lists must be scalar types [3][2]. - Atomic is the default if no list type is defined [2][4]. - Structural schemas require that all properties be explicitly defined and conform to structural rules (no logical junctors like anyOf/oneOf at the top level of schema branches) [10][7].

Citations:


Keep omitempty on the optional conditions field.

The field is marked +optional, but json:"conditions" serializes a nil slice as null. The generated CRD schema has type: array without nullable, so this makes the optional field non-null; keep the existing generated convention for list-typed optional conditions with json:"conditions,omitempty".

🐛 Proposed fix
 	// conditions provide Details on the status of the etcd backup job.
 	// +kubebuilder:validation:Optional
 	// +listType=map
 	// +listMapKey=type
 	// +optional
-	Conditions []metav1.Condition `json:"conditions"`
+	Conditions []metav1.Condition `json:"conditions,omitempty"`
📝 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
// conditions provide details on the status of the etcd backup job.
// +kubebuilder:validation:Optional
// +listType=map
// +listMapKey=type
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
Conditions []metav1.Condition `json:"conditions"`
// conditions provide details on the status of the etcd backup job.
// +kubebuilder:validation:Optional
// +listType=map
// +listMapKey=type
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@operator/v1alpha1/types_etcdbackup.go` around lines 106 - 111, The optional
Conditions field in the etcd backup status currently lacks omission behavior for
nil slices. Update Conditions to use the existing JSON tag with omitempty, while
preserving its list validation markers and type.

Comment on lines +128 to +131
// fileSize is the size of the backup file on the storage backend.
// +kubebuilder:validation:Optional
// +optional
FileSize resource.Quantity `json:"fileSize,omitempty"`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check existing convention for optional resource.Quantity fields in this repo (pointer vs value).
rg -n '\*resource\.Quantity' --type go
rg -n 'resource\.Quantity `json:"[a-zA-Z]+,omitempty"`' --type go

Repository: openshift/api

Length of output: 151


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the target type and nearby Quantity fields without executing repo code.
if [ -f operator/v1alpha1/types_etcdbackup.go ]; then
  echo "== target file line count =="
  wc -l operator/v1alpha1/types_etcdbackup.go
  echo "== target file context =="
  sed -n '100,155p' operator/v1alpha1/types_etcdbackup.go | nl -ba -v100
else
  echo "target file not found"
  fd -a 'types_etcdbackup\.go$' .
fi

echo "== all Quantity field declarations in repo =="
rg -n 'resource\.Quantity `json:"[A-Za-z]+,omitempty"`|`json:"[A-Za-z]+,omitempty"[\s\S]*resource\.Quantity' --type go || true

echo "== pointer Quantity declarations in repo =="
rg -n '\*resource\.Quantity' --type go || true

echo "== go json omitempty behavior for non-pointer Quantity =="
go env GOPATH GOROOT 2>/dev/null || true
cat >/tmp/quantity_omitempty_probe.go <<'EOF'
package main

import (
	"encoding/json"
	"fmt"

	"k8s.io/apimachinery/pkg/api/resource"
)

type FileSizeValue struct {
	FileSize resource.Quantity `json:"fileSize,omitempty"`
}

type FileSizePointer struct {
	FileSize *resource.Quantity `json:"fileSize,omitempty"`
}

func mustMarshal(v any) string {
	b, err := json.Marshal(v)
	if err != nil {
		panic(err)
	}
	return string(b)
}

func main() {
	q := resource.Quantity{}
	fmt.Println(mustMarshal(FileSizeValue{}))
	fmt.Println(mustMarshal(FileSizePointer{}))
	ptr := &q
	fmt.Println(mustMarshal(FileSizePointer{FileSize: ptr}))
}
EOF
go run /tmp/quantity_omitempty_probe.go

Repository: openshift/api

Length of output: 288


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target file context =="
sed -n '100,155p' operator/v1alpha1/types_etcdbackup.go || true

echo "== all Quantity field declarations using omitempty =="
rg -n 'resource\.Quantity `json:"[A-Za-z]+,omitempty"`|`json:"[A-Za-z]+,omitempty"[\s\S]*resource\.Quantity' --type go || true

echo "== pointer Quantity declarations in repo =="
rg -n '\*resource\.Quantity' --type go || true

echo "== go availability =="
command -v go || true
go env GOPATH GOROOT 2>/dev/null || true

echo "== behavior probe =="
cat >/tmp/quantity_omitempty_probe.go <<'EOF'
package main

import (
	"encoding/json"
	"fmt"

	"k8s.io/apimachinery/pkg/api/resource"
)

type FileSizeValue struct {
	FileSize resource.Quantity `json:"fileSize,omitempty"`
}

type FileSizePointer struct {
	FileSize *resource.Quantity `json:"fileSize,omitempty"`
}

func mustMarshal(v any) string {
	b, err := json.Marshal(v)
	if err != nil {
		panic(err)
	}
	return string(b)
}

func main() {
	fmt.Println("zero-valued value:", mustMarshal(FileSizeValue{}))
	fmt.Println("zero-valued pointer:", mustMarshal(FileSizePointer{}))
	val := resource.Quantity{}
	fmt.Println("non-nil zero-valued pointer:", mustMarshal(FileSizePointer{FileSize: &val}))
}
EOF
if command -v go >/dev/null 2>&1; then
  go run /tmp/quantity_omitempty_probe.go
else
  echo "go not available"
fi

Repository: openshift/api

Length of output: 2005


Make FileSize a pointer so omitempty works for an optional quantity.

resource.Quantity is a non-pointer struct, so json:"fileSize,omitempty" won’t omit the zero value (it still serializes "0"). Use *resource.Quantity for this optional status field so it is omitted when unset.

🐛 Proposed fix
 	// fileSize is the size of the backup file on the storage backend.
 	// +kubebuilder:validation:Optional
 	// +optional
-	FileSize resource.Quantity `json:"fileSize,omitempty"`
+	FileSize *resource.Quantity `json:"fileSize,omitempty"`
📝 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
// fileSize is the size of the backup file on the storage backend.
// +kubebuilder:validation:Optional
// +optional
FileSize resource.Quantity `json:"fileSize,omitempty"`
// fileSize is the size of the backup file on the storage backend.
// +kubebuilder:validation:Optional
// +optional
FileSize *resource.Quantity `json:"fileSize,omitempty"`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@operator/v1alpha1/types_etcdbackup.go` around lines 128 - 131, Change the
FileSize field in the backup status type from resource.Quantity to
*resource.Quantity so an unset optional value is omitted by
json:"fileSize,omitempty". Preserve its existing JSON tag, validation markers,
and file-size semantics.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant