Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions charts/examples/kubernetes/adapter-task-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ resources:
post:
payloads:
- name: "clusterStatusPayload"
when:
expression: "!adapter.resourcesSkipped"
build:
adapter: "{{ .adapter.name }}"
conditions:
Expand Down Expand Up @@ -163,6 +165,38 @@ post:
expression: "generation"
observed_time: "{{ now | date \"2006-01-02T15:04:05Z07:00\" }}"

# Skipped path: preconditions not met — report simplified status without
# evaluating resources.* (which may not exist in this context)
- name: "skippedStatusPayload"
when:
expression: "adapter.resourcesSkipped"
Comment on lines +168 to +172

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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C3 \
  '^[[:space:]]*preconditions:|ResourcesSkipped|SetSkipped|PreconditionFailed|ResourceNotFound|skippedStatusPayload|reportClusterStatusSkipped' \
  internal/executor charts/examples/kubernetes/adapter-task-config.yaml

Repository: openshift-hyperfleet/hyperfleet-adapter

Length of output: 38076


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,260p' charts/examples/kubernetes/adapter-task-config.yaml

Repository: openshift-hyperfleet/hyperfleet-adapter

Length of output: 8256


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path('charts/examples/kubernetes/adapter-task-config.yaml')
for start, end in [(1, 260)]:
    lines = p.read_text().splitlines()
    for i in range(start-1, min(end, len(lines))):
        print(f"{i+1:4d}: {lines[i]}")
PY

Repository: openshift-hyperfleet/hyperfleet-adapter

Length of output: 9582


🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -t f -a . charts/examples | sed -n '1,120p'

Repository: openshift-hyperfleet/hyperfleet-adapter

Length of output: 1769


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C2 'preconditions:|resourcesSkipped|skippedStatusPayload|reportClusterStatusSkipped' charts/examples

Repository: openshift-hyperfleet/hyperfleet-adapter

Length of output: 11862


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' charts/examples/kubernetes/README.md

Repository: openshift-hyperfleet/hyperfleet-adapter

Length of output: 3968


Add a real skip path to the Kubernetes example (CWE-561). charts/examples/kubernetes/adapter-task-config.yaml documents skippedStatusPayload, but this example never defines any preconditions: or other skip-triggering branch, so adapter.resourcesSkipped cannot drive that payload/post-action. Wire checkClusterState into a concrete precondition with both match and no-match cases, or drop the skipped branch from the example.

🤖 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 `@charts/examples/kubernetes/adapter-task-config.yaml` around lines 168 - 172,
The Kubernetes example defines skippedStatusPayload without any precondition or
skip-triggering flow. Update the task configuration around checkClusterState and
skippedStatusPayload to add a concrete precondition with explicit match and
no-match handling that sets adapter.resourcesSkipped when resources are skipped,
or remove the skipped status payload branch if this example should not support
skipping.

Source: Path instructions

build:
adapter: "{{ .adapter.name }}"
conditions:
- type: "Applied"
status: "False"
reason: "PreconditionsNotMet"
message:
expression: |
"Resources skipped: " + adapter.?skipReason.orValue("preconditions not met")
- type: "Available"
status: "False"
reason: "PreconditionsNotMet"
message:
expression: |
"Resources not available: " + adapter.?skipReason.orValue("preconditions not met")
- type: "Health"
status: "True"
reason: "NoErrors"
message: "Adapter is healthy, no work performed due to preconditions"
Comment on lines +171 to +191

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not classify every resourcesSkipped outcome as a healthy precondition skip (CWE-754).

This payload hardcodes Health=True, NoErrors, and PreconditionsNotMet, but resourcesSkipped also represents failed precondition evaluation and resource-not-found handling. A failed precondition can therefore be persisted through the new PUT action as a healthy status, while a not-found case is mislabeled as a precondition skip. (raw.githubusercontent.com)

Use distinct outcome metadata: select this payload only for a successful PreconditionNotMet business outcome, and add a separate failure-reporting path for PreconditionFailed and other skip causes.

As per path instructions: “Prioritize Critical and Major severity issues” and validate cross-layer contracts.

🤖 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 `@charts/examples/kubernetes/adapter-task-config.yaml` around lines 171 - 191,
Update the resourcesSkipped handling in the adapter build configuration so this
payload is selected only for the successful PreconditionNotMet outcome, rather
than every skipped-resource case. Add a separate failure-reporting path for
PreconditionFailed and other skip causes, preserving accurate outcome-specific
condition type, status, reason, and message metadata instead of hardcoding
Health=True, NoErrors, and PreconditionsNotMet.

Source: Path instructions

- type: "Finalized"
status: "False"
reason: ""
message: ""
observed_generation:
expression: "generation"
observed_time: "{{ now | date \"2006-01-02T15:04:05Z07:00\" }}"

post_actions:
- name: "reportClusterStatus"
when:
Expand All @@ -174,3 +208,14 @@ post:
- name: "Content-Type"
value: "application/json"
body: "{{ .clusterStatusPayload }}"

- name: "reportClusterStatusSkipped"
when:
expression: "adapter.resourcesSkipped"
api_call:
method: "PUT"
url: "/clusters/{{ .clusterId }}/statuses"
headers:
- name: "Content-Type"
value: "application/json"
body: "{{ .skippedStatusPayload }}"
32 changes: 32 additions & 0 deletions docs/adapter-authoring-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1227,6 +1227,38 @@ post:

The `when` expression has access to the full execution context: all `adapter.*` metadata, extracted params, and `resources.*`. If `when` is omitted, the payload is always built (existing behavior). If the expression fails to parse or evaluate, the payload build is marked as **failed**. Evaluation order: payload `when` → build → post-action `when` → execute. Both gates are independent.

### Common `when` patterns

| Pattern | Expression | Use case |
|---------|-----------|----------|
| Run when work was done | `!adapter.resourcesSkipped` | Most common — gate status reporting on whether resources were actually applied |
| Success-only | `adapter.?executionStatus.orValue('') == 'success'` | Run only when all phases succeeded |
| Failure-only | `adapter.?executionStatus.orValue('') != 'success'` | Send a different status report on failure |
| Deletion path | `is_deleting` | Run only during cluster deletion (requires `is_deleting` param) |
| Resource exists | `resources.?myResource.hasValue()` | Gate on whether a specific resource was discovered |

#### Combining `when` on payloads and post-actions

You can use `when` at both levels. A post-action that references a skipped payload is automatically skipped, so adding `when` to the post-action is redundant in that case. However, using both makes intent explicit in the config and avoids relying on the implicit auto-skip behavior:

```yaml
post:
payloads:
- name: "statusPayload"
when:
expression: "!adapter.resourcesSkipped" # prevents CEL evaluation of missing resources.*
build: { ... }

post_actions:
- name: "reportClusterStatus"
when:
expression: "!adapter.resourcesSkipped" # explicit gate — also auto-skipped via payload
api_call:
body: "{{ .statusPayload }}"
```

> For a complete working example of conditional payloads and post-actions, see the `adapter1` configuration in [hyperfleet-infra](https://github.com/openshift-hyperfleet/hyperfleet-infra/tree/main/helmfile/configs/base/adapters/adapter1/adapter-task-config.yaml).

### Building payloads

A payload is a JSON structure built from CEL expressions and Go Templates. Each field can be specified in three ways:
Expand Down