Skip to content

CherryPicked: [v2.11] feat(copyoffload): add storage_secret_extra for array-specific Secret keys - #540

Merged
myakove merged 2 commits into
v2.11from
CherryPicked-feat/copyoffload-storage-secret-extra-j8yZj
Jun 9, 2026
Merged

myakove merged 2 commits into
v2.11from
CherryPicked-feat/copyoffload-storage-secret-extra-j8yZj

Conversation

@manage-repositories-app

Copy link
Copy Markdown

Cherry-pick from main branch, original PR: #535, PR owner: prabinovRedhat

… keys (#535)

* feat(copyoffload): add storage_secret_extra for array-specific Secret keys

Allow extra Forklift stringData keys via providers JSON and
COPYOFFLOAD_STORAGE_SECRET_EXTRA env override. Includes generate wizard
prompts and copy-offload guide documentation.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(copyoffload): address PR review on storage_secret_extra

Add missing docstring sections, use TypeError for type validation,
mask wizard secret extra values in prompts and config summary.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
@redhat-qe-bot

Copy link
Copy Markdown

Report bugs in Issues

Welcome! 🎉

This pull request will be automatically processed with the following features:

Note: You are an auto-verified user. Your PRs will be automatically verified and may be auto-merged when all requirements are met.

🔄 Automatic Actions

  • Reviewer Assignment: Reviewers are automatically assigned based on the OWNERS file in the repository root
  • Size Labeling: PR size labels (XS, S, M, L, XL, XXL) are automatically applied based on changes
  • Issue Creation: Disabled for this repository
  • Branch Labeling: Branch-specific labels are applied to track the target branch
  • Auto-verification: Auto-verified users have their PRs automatically marked as verified
  • Labels: All label categories are enabled (default configuration)

📋 Available Commands

PR Status Management

  • /wip - Mark PR as work in progress (adds WIP: prefix to title)
  • /wip cancel - Remove work in progress status
  • /hold - Block PR merging (approvers only)
  • /hold cancel - Unblock PR merging
  • /verified - Mark PR as verified
  • /verified cancel - Remove verification status
  • /reprocess - Trigger complete PR workflow reprocessing (useful if webhook failed or configuration changed)
  • /regenerate-welcome - Regenerate this welcome message

Review & Approval

  • /lgtm - Approve changes (looks good to me)
  • /approve - Approve PR (approvers only)
  • /automerge - Enable automatic merging when all requirements are met (maintainers and approvers only)
  • /assign-reviewers - Assign reviewers based on OWNERS file
  • /assign-reviewer @username - Assign specific reviewer
  • /check-can-merge - Check if PR meets merge requirements

Testing & Validation

  • /retest tox - Run Python test suite with tox
  • /retest build-container - Rebuild and test container image
  • /retest conventional-title - Validate commit message format
  • /retest all - Run all available tests

Container Operations

  • /build-and-push-container - Build and push container image (tagged with PR number)
    • Supports additional build arguments: /build-and-push-container --build-arg KEY=value

Cherry-pick Operations

  • /cherry-pick <branch> - Schedule cherry-pick to target branch when PR is merged
    • Multiple branches: /cherry-pick branch1 branch2 branch3

Label Management

  • /<label-name> - Add a label to the PR
  • /<label-name> cancel - Remove a label from the PR

✅ Merge Requirements

This PR will be automatically approved when the following conditions are met:

  1. Approval: /approve from at least one approver
  2. Status Checks: All required status checks must pass
  3. No Blockers: No wip, hold, has-conflicts labels and PR must be mergeable (no conflicts)
  4. Verified: PR must be marked as verified

📊 Review Process

Approvers and Reviewers

Approvers:

  • myakove
  • solenoci

Reviewers:

  • krcmarik
  • myakove
  • solenoci
Available Labels
  • hold
  • verified
  • wip
  • lgtm
  • approve
  • automerge
AI Features
  • Conventional Title: Mode: fix (claude/claude-opus-4-6[1m])
  • Cherry-Pick Conflict Resolution: Enabled (claude/claude-opus-4-6[1m])
  • Test Oracle: Triggers: approved (cursor/gpt-5.4-xhigh-fast); /test-oracle can be used anytime

💡 Tips

  • WIP Status: Use /wip when your PR is not ready for review
  • Verification: The verified label is removed on new commits unless the push is detected as a clean rebase
  • Cherry-picking: Cherry-pick labels are processed when the PR is merged
  • Container Builds: Container images are automatically tagged with the PR number
  • Permission Levels: Some commands require approver permissions
  • Auto-verified Users: Certain users have automatic verification and merge privileges

For more information, please refer to the project documentation or contact the maintainers.

@prabinovRedhat

Copy link
Copy Markdown
Collaborator

/verified

@prabinovRedhat

Copy link
Copy Markdown
Collaborator

@myakove I can’t rebase this branch because I don’t have permissions. If you can do it, that would be really helpful. We need this PR merged for our partners, who are waiting for this fix.

cc: @rgolangh

@qodo-code-review

qodo-code-review Bot commented Jun 9, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (1)

Grey Divider


Action required

1. Manual edit under docs/ 📘 Rule violation ⚙ Maintainability
Description
The PR directly modifies a file under docs/, which is disallowed because docs must remain
generator-owned to avoid drift. This can cause the generated documentation output to diverge from
the source-of-truth.
Code

docs/copyoffload/how-to-run-copyoffload-tests.md[R220-250]

+### Extra storage secret keys (`storage_secret_extra`)
+
+Use `storage_secret_extra` when the populator needs additional Kubernetes Secret keys that are not
+mapped by the built-in vendor fields (for example Dell PowerMax `POWERMAX_PORT_GROUP_NAME`).
+
+Keys in `storage_secret_extra` must match Forklift **Secret `stringData` names** (uppercase). Values are
+merged into the storage secret the test suite creates for copy-offload. They override vendor-mapped keys
+when the same Secret key is specified.
+
+Use quoted strings for values (for example `"true"`). If you use JSON booleans in
+`COPYOFFLOAD_STORAGE_SECRET_EXTRA` or in `.providers.json`, they are normalized to lowercase `"true"` /
+`"false"` for Secret `stringData`. An empty object or `null` for `storage_secret_extra` is allowed; env-only
+configuration still works.
+
+**Example (PowerMax):**
+
+```json
+"storage_secret_extra": {
+  "POWERMAX_PORT_GROUP_NAME": "mtv_group_pg",
+  "STORAGE_SKIP_SSL_VERIFICATION": "true"
+}
+```
+
+**Environment override** (takes precedence over `.providers.json`):
+
+```bash
+export COPYOFFLOAD_STORAGE_SECRET_EXTRA='{"POWERMAX_PORT_GROUP_NAME":"mtv_group_pg","STORAGE_SKIP_SSL_VERIFICATION":"true"}'
+```
+
+The `mtv-api-tests generate` wizard can prompt for these keys interactively after vendor-specific fields.
+
Evidence
PR Compliance ID 2 forbids any manual modifications under the docs/ directory. The diff shows new
documentation content added in docs/copyoffload/how-to-run-copyoffload-tests.md in the specified
line range.

CLAUDE.md: Do Not Manually Edit Auto-Generated docs/ Files
docs/copyoffload/how-to-run-copyoffload-tests.md[220-250]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
This PR manually edits a file under `docs/`, which is prohibited by compliance policy. Documentation under `docs/` must only change via the documentation generator/source-of-truth process.

## Issue Context
The PR adds a new section about `storage_secret_extra` directly into `docs/copyoffload/how-to-run-copyoffload-tests.md`.

## Fix Focus Areas
- docs/copyoffload/how-to-run-copyoffload-tests.md[220-250]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Extra values get altered 🐞 Bug ≡ Correctness
Description
_storage_secret_extra_value_as_string() uses str(value).strip(), which removes leading/trailing
whitespace/newlines from extra Secret values and can corrupt whitespace-sensitive data (e.g.,
PEM/cert blobs or tokens) before writing to the Kubernetes Secret. It also coerces non-scalar JSON
values (lists/objects) into Python string forms (e.g., dicts become strings with single quotes),
silently producing unexpected Secret contents rather than failing fast.
Code

utilities/copyoffload_migration.py[R60-99]

+def _storage_secret_extra_value_as_string(value: Any) -> str:
+    """Convert a config/env value to Kubernetes Secret stringData text.
+
+    JSON booleans must become lowercase ``true``/``false``, not Python ``True``/``False``.
+
+    Args:
+        value: Raw value from JSON config or environment.
+
+    Returns:
+        str: Normalized Secret ``stringData`` text.
+    """
+    if isinstance(value, bool):
+        return "true" if value else "false"
+    return str(value).strip()
+
+
+def _secret_extra_entries_from_mapping(mapping: dict[Any, Any]) -> dict[str, str]:
+    """Normalize a mapping to non-empty Secret stringData key/value pairs.
+
+    Args:
+        mapping: Raw key/value mapping from JSON config or environment.
+
+    Returns:
+        dict[str, str]: Normalized Secret keys and values.
+
+    Raises:
+        ValueError: If any key is empty after stripping.
+    """
+    result: dict[str, str] = {}
+    for secret_key, value in mapping.items():
+        if value is None:
+            continue
+        text = _storage_secret_extra_value_as_string(value)
+        if not text:
+            continue
+        key = str(secret_key).strip()
+        if not key:
+            raise ValueError("storage_secret_extra keys must be non-empty strings")
+        result[key] = text
+    return result
Evidence
The new conversion strips all non-boolean values and accepts any type via str(...). Those
normalized values are then merged into secret_data and passed directly as Secret.string_data, so
any trimming/coercion affects the Secret created in the cluster.

utilities/copyoffload_migration.py[60-74]
utilities/copyoffload_migration.py[76-99]
tests/copyoffload/conftest.py[281-291]
docs/copyoffload/how-to-run-copyoffload-tests.md[229-233]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`storage_secret_extra` values are normalized via `str(value).strip()`, which can modify intended values by trimming whitespace/newlines. Additionally, complex JSON types (objects/arrays) are stringified implicitly, yielding surprising non-JSON text.

### Issue Context
These values are merged into the `Secret.stringData` used by copy-offload, so any normalization issues become real cluster Secret data.

### Fix Focus Areas
- utilities/copyoffload_migration.py[60-99]

### Suggested fix
- Change `_storage_secret_extra_value_as_string` so that:
 - `bool` -> "true"/"false" (keep)
 - `str` -> return as-is (do not strip)
 - `int`/`float` -> `str(value)` (no strip)
 - `dict`/`list`/other complex types -> raise `TypeError` with a clear message like: "storage_secret_extra values must be strings (or JSON booleans)"
- In `_secret_extra_entries_from_mapping`, decide emptiness without mutating values, e.g. skip only when `value is None` or (isinstance(value, str) and value.strip() == "").

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Extra key casing unvalidated 🐞 Bug ⚙ Maintainability
Description
Docs require storage_secret_extra keys to match Forklift Secret stringData names (uppercase),
but _secret_extra_entries_from_mapping() only strips keys and accepts any casing. Mis-cased keys
will be written under the wrong name (Kubernetes Secret keys are case-sensitive), so the expected
populator key will be absent and failures will surface later during copy-offload execution.
Code

utilities/copyoffload_migration.py[R76-99]

+def _secret_extra_entries_from_mapping(mapping: dict[Any, Any]) -> dict[str, str]:
+    """Normalize a mapping to non-empty Secret stringData key/value pairs.
+
+    Args:
+        mapping: Raw key/value mapping from JSON config or environment.
+
+    Returns:
+        dict[str, str]: Normalized Secret keys and values.
+
+    Raises:
+        ValueError: If any key is empty after stripping.
+    """
+    result: dict[str, str] = {}
+    for secret_key, value in mapping.items():
+        if value is None:
+            continue
+        text = _storage_secret_extra_value_as_string(value)
+        if not text:
+            continue
+        key = str(secret_key).strip()
+        if not key:
+            raise ValueError("storage_secret_extra keys must be non-empty strings")
+        result[key] = text
+    return result
Evidence
Repository docs and the example providers file state keys must be uppercase, but the implementation
only strips whitespace and does not validate uppercase before adding keys to the mapping.

docs/copyoffload/how-to-run-copyoffload-tests.md[220-227]
.providers.json.example[89-95]
utilities/copyoffload_migration.py[76-99]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`storage_secret_extra` keys are documented as uppercase Forklift Secret `stringData` names, but the parser accepts any casing without validation. This makes configuration mistakes easy and failures delayed.

### Issue Context
The docs and example config explicitly state the uppercase requirement.

### Fix Focus Areas
- utilities/copyoffload_migration.py[76-99]
- docs/copyoffload/how-to-run-copyoffload-tests.md[220-227]
- .providers.json.example[89-95]

### Suggested fix
- Enforce the documented contract in `_secret_extra_entries_from_mapping`:
 - After `key = str(secret_key).strip()`, add a check like `if key.upper() != key: raise ValueError("storage_secret_extra keys must be uppercase Secret stringData names")`.
 - Alternatively, normalize with `key = key.upper()` and log a warning that the key was normalized (but raising is stricter and matches the docs).
- Keep the existing non-empty key check.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@myakove

myakove commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

/lgtm
/approve

@myakove
myakove enabled auto-merge (squash) June 9, 2026 07:39
@redhat-qe-bot2

Copy link
Copy Markdown

Clean rebase detected — no code changes compared to previous head (47e097a).
The following labels were preserved: verified.

@myakove
myakove merged commit cbace59 into v2.11 Jun 9, 2026
6 checks passed
@myakove
myakove deleted the CherryPicked-feat/copyoffload-storage-secret-extra-j8yZj branch June 9, 2026 07:41
Comment on lines +220 to +250
### Extra storage secret keys (`storage_secret_extra`)

Use `storage_secret_extra` when the populator needs additional Kubernetes Secret keys that are not
mapped by the built-in vendor fields (for example Dell PowerMax `POWERMAX_PORT_GROUP_NAME`).

Keys in `storage_secret_extra` must match Forklift **Secret `stringData` names** (uppercase). Values are
merged into the storage secret the test suite creates for copy-offload. They override vendor-mapped keys
when the same Secret key is specified.

Use quoted strings for values (for example `"true"`). If you use JSON booleans in
`COPYOFFLOAD_STORAGE_SECRET_EXTRA` or in `.providers.json`, they are normalized to lowercase `"true"` /
`"false"` for Secret `stringData`. An empty object or `null` for `storage_secret_extra` is allowed; env-only
configuration still works.

**Example (PowerMax):**

```json
"storage_secret_extra": {
"POWERMAX_PORT_GROUP_NAME": "mtv_group_pg",
"STORAGE_SKIP_SSL_VERIFICATION": "true"
}
```

**Environment override** (takes precedence over `.providers.json`):

```bash
export COPYOFFLOAD_STORAGE_SECRET_EXTRA='{"POWERMAX_PORT_GROUP_NAME":"mtv_group_pg","STORAGE_SKIP_SSL_VERIFICATION":"true"}'
```

The `mtv-api-tests generate` wizard can prompt for these keys interactively after vendor-specific fields.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Manual edit under docs/ 📘 Rule violation ⚙ Maintainability

The PR directly modifies a file under docs/, which is disallowed because docs must remain
generator-owned to avoid drift. This can cause the generated documentation output to diverge from
the source-of-truth.
Agent Prompt
## Issue description
This PR manually edits a file under `docs/`, which is prohibited by compliance policy. Documentation under `docs/` must only change via the documentation generator/source-of-truth process.

## Issue Context
The PR adds a new section about `storage_secret_extra` directly into `docs/copyoffload/how-to-run-copyoffload-tests.md`.

## Fix Focus Areas
- docs/copyoffload/how-to-run-copyoffload-tests.md[220-250]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +60 to +99
def _storage_secret_extra_value_as_string(value: Any) -> str:
"""Convert a config/env value to Kubernetes Secret stringData text.

JSON booleans must become lowercase ``true``/``false``, not Python ``True``/``False``.

Args:
value: Raw value from JSON config or environment.

Returns:
str: Normalized Secret ``stringData`` text.
"""
if isinstance(value, bool):
return "true" if value else "false"
return str(value).strip()


def _secret_extra_entries_from_mapping(mapping: dict[Any, Any]) -> dict[str, str]:
"""Normalize a mapping to non-empty Secret stringData key/value pairs.

Args:
mapping: Raw key/value mapping from JSON config or environment.

Returns:
dict[str, str]: Normalized Secret keys and values.

Raises:
ValueError: If any key is empty after stripping.
"""
result: dict[str, str] = {}
for secret_key, value in mapping.items():
if value is None:
continue
text = _storage_secret_extra_value_as_string(value)
if not text:
continue
key = str(secret_key).strip()
if not key:
raise ValueError("storage_secret_extra keys must be non-empty strings")
result[key] = text
return result

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Extra values get altered 🐞 Bug ≡ Correctness

_storage_secret_extra_value_as_string() uses str(value).strip(), which removes leading/trailing
whitespace/newlines from extra Secret values and can corrupt whitespace-sensitive data (e.g.,
PEM/cert blobs or tokens) before writing to the Kubernetes Secret. It also coerces non-scalar JSON
values (lists/objects) into Python string forms (e.g., dicts become strings with single quotes),
silently producing unexpected Secret contents rather than failing fast.
Agent Prompt
### Issue description
`storage_secret_extra` values are normalized via `str(value).strip()`, which can modify intended values by trimming whitespace/newlines. Additionally, complex JSON types (objects/arrays) are stringified implicitly, yielding surprising non-JSON text.

### Issue Context
These values are merged into the `Secret.stringData` used by copy-offload, so any normalization issues become real cluster Secret data.

### Fix Focus Areas
- utilities/copyoffload_migration.py[60-99]

### Suggested fix
- Change `_storage_secret_extra_value_as_string` so that:
  - `bool` -> "true"/"false" (keep)
  - `str` -> return as-is (do not strip)
  - `int`/`float` -> `str(value)` (no strip)
  - `dict`/`list`/other complex types -> raise `TypeError` with a clear message like: "storage_secret_extra values must be strings (or JSON booleans)"
- In `_secret_extra_entries_from_mapping`, decide emptiness without mutating values, e.g. skip only when `value is None` or (isinstance(value, str) and value.strip() == "").

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +76 to +99
def _secret_extra_entries_from_mapping(mapping: dict[Any, Any]) -> dict[str, str]:
"""Normalize a mapping to non-empty Secret stringData key/value pairs.

Args:
mapping: Raw key/value mapping from JSON config or environment.

Returns:
dict[str, str]: Normalized Secret keys and values.

Raises:
ValueError: If any key is empty after stripping.
"""
result: dict[str, str] = {}
for secret_key, value in mapping.items():
if value is None:
continue
text = _storage_secret_extra_value_as_string(value)
if not text:
continue
key = str(secret_key).strip()
if not key:
raise ValueError("storage_secret_extra keys must be non-empty strings")
result[key] = text
return result

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

3. Extra key casing unvalidated 🐞 Bug ⚙ Maintainability

Docs require storage_secret_extra keys to match Forklift Secret stringData names (uppercase),
but _secret_extra_entries_from_mapping() only strips keys and accepts any casing. Mis-cased keys
will be written under the wrong name (Kubernetes Secret keys are case-sensitive), so the expected
populator key will be absent and failures will surface later during copy-offload execution.
Agent Prompt
### Issue description
`storage_secret_extra` keys are documented as uppercase Forklift Secret `stringData` names, but the parser accepts any casing without validation. This makes configuration mistakes easy and failures delayed.

### Issue Context
The docs and example config explicitly state the uppercase requirement.

### Fix Focus Areas
- utilities/copyoffload_migration.py[76-99]
- docs/copyoffload/how-to-run-copyoffload-tests.md[220-227]
- .providers.json.example[89-95]

### Suggested fix
- Enforce the documented contract in `_secret_extra_entries_from_mapping`:
  - After `key = str(secret_key).strip()`, add a check like `if key.upper() != key: raise ValueError("storage_secret_extra keys must be uppercase Secret stringData names")`.
  - Alternatively, normalize with `key = key.upper()` and log a warning that the key was normalized (but raising is stricter and matches the docs).
- Keep the existing non-empty key check.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@redhat-qe-bot2

Copy link
Copy Markdown

New container for ghcr.io/redhatqe/mtv-api-tests:v2.11 published

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants