Skip to content

feat: deletion order#2

Merged
patrickleet merged 3 commits intomainfrom
feat/deletion-ordering
Feb 25, 2026
Merged

feat: deletion order#2
patrickleet merged 3 commits intomainfrom
feat/deletion-ordering

Conversation

@patrickleet
Copy link
Contributor

@patrickleet patrickleet commented Feb 25, 2026

Summary by CodeRabbit

  • Documentation

    • Updated README to clarify resource creation and deletion ordering, including deployment summary and phase-based teardown sequence.
  • Chores

    • Updated CI/CD workflow references to the latest versions.

@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

Warning

Rate limit exceeded

@patrickleet has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 22 minutes and 37 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between cc05644 and 3c07530.

📒 Files selected for processing (5)
  • .github/workflows/on-pr.yaml
  • README.md
  • functions/render/200-kube-prometheus-stack.yaml.gotmpl
  • functions/render/210-loki.yaml.gotmpl
  • functions/render/220-tempo.yaml.gotmpl
📝 Walkthrough

Walkthrough

The changes remove k8s-monitoring-dependent deletion protection from Loki and Tempo templates, consolidate kube-prometheus-stack and grafana-operator protection into a single Usage resource, update GitHub Actions workflow references to v2.15.0 (with one e2e reference to a feature branch), and restructure README documentation to emphasize creation order over deletion order.

Changes

Cohort / File(s) Summary
Crossplane Protection Consolidation
functions/render/200-kube-prometheus-stack.yaml.gotmpl
Consolidated two separate Usage resources into a single conditional Usage protecting kube-prometheus-stack deletion until grafana-operator is deleted; removed all k8s-monitoring-related protection logic and references.
Crossplane Protection Removal
functions/render/210-loki.yaml.gotmpl, functions/render/220-tempo.yaml.gotmpl
Removed entire crossplane Usage protection blocks that previously guarded Loki and Tempo deletion until k8s-monitoring was deleted; eliminated deletion-order enforcement logic from both templates.
Workflow Version Updates
.github/workflows/on-pr.yaml, .github/workflows/on-push-main.yaml, .github/workflows/on-version-tagged.yaml
Updated workflow action versions from v2.14.2 to v2.15.0 across validate, test, and publish jobs; one e2e reference in on-pr.yaml points to feat/more-usage-debugging branch instead.
Documentation Restructuring
README.md
Shifted narrative focus from deletion ordering to creation ordering; updated composed resource counts (5 Usage protections down from 7, 6 Kubernetes Objects up from 4); replaced deletion-oriented visuals with creation sequence diagram; added phase-based deletion order section.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 We simplified the chains today,
Removed the k8s-monitoring way,
Grafana guards the teardown door,
Creation flows, dependencies soar,
One Usage shields what once took four! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'feat: deletion order' is vague and does not clearly convey the specific changes made in the pull request. Consider a more descriptive title that clarifies the main change, such as 'feat: update deletion protection to use only grafana-operator dependency' or 'feat: simplify crossplane usage protections by removing k8s-monitoring dependency'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/deletion-ordering

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.

@github-actions
Copy link

github-actions bot commented Feb 25, 2026

Published Crossplane Package

The following Crossplane package was published as part of this PR:

Package: ghcr.io/hops-ops/stack-observe:pr-2-446ce57af2935a77e6fd64ece1c096b5be96b038

View Package

Copy link

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@functions/render/260-grafana-dashboards.yaml.gotmpl`:
- Around line 79-215: The Usage blocks currently gate on readiness (e.g.
conditions like and $state.observed.dashboardOpencostOverview.ready
$state.observed.datasourcePrometheus.ready), which causes the Usage to be
omitted while resources exist but are not "ready"; update each conditional to
gate on resource existence instead (for example replace checks of
$state.observed.dashboardOpencostOverview.ready and
$state.observed.datasourcePrometheus.ready with the corresponding existence
flags such as $state.observed.dashboardOpencostOverview.exists and
$state.observed.datasourcePrometheus.exists or another boolean that reflects
presence), applying the same change to all six blocks (overview/namespace ×
prometheus/loki/tempo) so the Usage objects (names like {{ $state.name
}}-delete-dashboard-overview-before-ds-prometheus, annotations
setResourceNameAnnotation "usage-dsprom-dash-overview", and similar) are
rendered when the resources exist regardless of ready state.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 97940de and 6851e34.

📒 Files selected for processing (5)
  • functions/render/010-state-status.yaml.gotmpl
  • functions/render/200-kube-prometheus-stack.yaml.gotmpl
  • functions/render/210-loki.yaml.gotmpl
  • functions/render/220-tempo.yaml.gotmpl
  • functions/render/260-grafana-dashboards.yaml.gotmpl
💤 Files with no reviewable changes (3)
  • functions/render/200-kube-prometheus-stack.yaml.gotmpl
  • functions/render/210-loki.yaml.gotmpl
  • functions/render/220-tempo.yaml.gotmpl

Comment on lines +79 to +215
{{- if and $state.observed.dashboardOpencostOverview.ready $state.observed.datasourcePrometheus.ready }}
---
apiVersion: protection.crossplane.io/v1beta1
kind: Usage
metadata:
name: {{ $state.name }}-delete-dashboard-overview-before-ds-prometheus
annotations:
{{ setResourceNameAnnotation "usage-dsprom-dash-overview" }}
labels: {{ $state.labels | toJson }}
spec:
replayDeletion: true
of:
apiVersion: kubernetes.m.crossplane.io/v1alpha1
kind: Object
resourceRef:
name: {{ $state.name }}-datasource-prometheus
by:
apiVersion: kubernetes.m.crossplane.io/v1alpha1
kind: Object
resourceRef:
name: {{ $state.name }}-dashboard-opencost-overview
{{- end }}

{{- if and $state.observed.dashboardOpencostOverview.ready $state.observed.datasourceLoki.ready }}
---
apiVersion: protection.crossplane.io/v1beta1
kind: Usage
metadata:
name: {{ $state.name }}-delete-dashboard-overview-before-ds-loki
annotations:
{{ setResourceNameAnnotation "usage-dsloki-dash-overview" }}
labels: {{ $state.labels | toJson }}
spec:
replayDeletion: true
of:
apiVersion: kubernetes.m.crossplane.io/v1alpha1
kind: Object
resourceRef:
name: {{ $state.name }}-datasource-loki
by:
apiVersion: kubernetes.m.crossplane.io/v1alpha1
kind: Object
resourceRef:
name: {{ $state.name }}-dashboard-opencost-overview
{{- end }}

{{- if and $state.observed.dashboardOpencostOverview.ready $state.observed.datasourceTempo.ready }}
---
apiVersion: protection.crossplane.io/v1beta1
kind: Usage
metadata:
name: {{ $state.name }}-delete-dashboard-overview-before-ds-tempo
annotations:
{{ setResourceNameAnnotation "usage-dstempo-dash-overview" }}
labels: {{ $state.labels | toJson }}
spec:
replayDeletion: true
of:
apiVersion: kubernetes.m.crossplane.io/v1alpha1
kind: Object
resourceRef:
name: {{ $state.name }}-datasource-tempo
by:
apiVersion: kubernetes.m.crossplane.io/v1alpha1
kind: Object
resourceRef:
name: {{ $state.name }}-dashboard-opencost-overview
{{- end }}

{{- if and $state.observed.dashboardOpencostNamespace.ready $state.observed.datasourcePrometheus.ready }}
---
apiVersion: protection.crossplane.io/v1beta1
kind: Usage
metadata:
name: {{ $state.name }}-delete-dashboard-namespace-before-ds-prometheus
annotations:
{{ setResourceNameAnnotation "usage-dsprom-dash-namespace" }}
labels: {{ $state.labels | toJson }}
spec:
replayDeletion: true
of:
apiVersion: kubernetes.m.crossplane.io/v1alpha1
kind: Object
resourceRef:
name: {{ $state.name }}-datasource-prometheus
by:
apiVersion: kubernetes.m.crossplane.io/v1alpha1
kind: Object
resourceRef:
name: {{ $state.name }}-dashboard-opencost-namespace
{{- end }}

{{- if and $state.observed.dashboardOpencostNamespace.ready $state.observed.datasourceLoki.ready }}
---
apiVersion: protection.crossplane.io/v1beta1
kind: Usage
metadata:
name: {{ $state.name }}-delete-dashboard-namespace-before-ds-loki
annotations:
{{ setResourceNameAnnotation "usage-dsloki-dash-namespace" }}
labels: {{ $state.labels | toJson }}
spec:
replayDeletion: true
of:
apiVersion: kubernetes.m.crossplane.io/v1alpha1
kind: Object
resourceRef:
name: {{ $state.name }}-datasource-loki
by:
apiVersion: kubernetes.m.crossplane.io/v1alpha1
kind: Object
resourceRef:
name: {{ $state.name }}-dashboard-opencost-namespace
{{- end }}

{{- if and $state.observed.dashboardOpencostNamespace.ready $state.observed.datasourceTempo.ready }}
---
apiVersion: protection.crossplane.io/v1beta1
kind: Usage
metadata:
name: {{ $state.name }}-delete-dashboard-namespace-before-ds-tempo
annotations:
{{ setResourceNameAnnotation "usage-dstempo-dash-namespace" }}
labels: {{ $state.labels | toJson }}
spec:
replayDeletion: true
of:
apiVersion: kubernetes.m.crossplane.io/v1alpha1
kind: Object
resourceRef:
name: {{ $state.name }}-datasource-tempo
by:
apiVersion: kubernetes.m.crossplane.io/v1alpha1
kind: Object
resourceRef:
name: {{ $state.name }}-dashboard-opencost-namespace
{{- end }}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Crossplane protection.crossplane.io v1beta1 Usage deletion protection Ready status

💡 Result:

In Crossplane, deletion protection using protection.crossplane.io/v1beta1 is done with a Usage (namespaced) or ClusterUsage (cluster-scoped) object. A Usage declares that some resource is “in use”, and Crossplane will reject deletes of the protected (“of”) resource while that usage exists. (docs.crossplane.io)

Minimal deletion-protection example (namespaced Usage)

apiVersion: protection.crossplane.io/v1beta1
kind: Usage
metadata:
  namespace: default
  name: protect-production-database
spec:
  of:
    apiVersion: rds.aws.m.upbound.io/v1beta1
    kind: Instance
    resourceRef:
      name: my-database
  reason: "Production Database - should never be deleted!"

Crossplane also supports a “dependency ordering” form by setting spec.by (the “using” resource). (docs.crossplane.io)

What actually blocks deletion

When the Usage is active, Crossplane marks the referenced resource as in-use (e.g., via an crossplane.io/in-use: "true" label) and a validating webhook denies DELETE requests for resources marked in-use. (deepwiki.com)

What READY means on a Usage

kubectl get usage ... shows a READY column that reflects .status.conditions[type=="Ready"].status. (pkg.go.dev)

Practically:

  • Ready=True: Crossplane successfully resolved the spec.of (and optional spec.by) reference/selector and applied the protection mechanism (i.e., the “of” resource is considered protected/in-use). (docs.crossplane.io)
  • Ready=False or Unknown: Crossplane can’t make the usage effective yet (most commonly it can’t resolve the referenced resource(s), or can’t update/label them due to RBAC). Use kubectl describe usage ... to see the condition Reason/Message. (Crossplane troubleshooting guidance: inspect status.conditions and Events.) (docs.crossplane.io)

Extra fields that matter

  • spec.replayDeletion: true re-triggers a previously-blocked deletion of the protected resource after the Usage is removed (useful to avoid long GC backoff delays). (docs.crossplane.io)
  • Use ClusterUsage to protect cluster-scoped resources (like CRDs). (docs.crossplane.io)

Citations:


🏁 Script executed:

# Check the broader context of this file to understand the pattern
head -100 functions/render/260-grafana-dashboards.yaml.gotmpl

# Also check if there are other similar Usage patterns elsewhere
find . -type f -name "*.gotmpl" -o -name "*.yaml" | head -20

Repository: hops-ops/stack-observe

Length of output: 4547


🏁 Script executed:

# Search for how $state.observed is structured in the codebase
rg 'state\.observed\.' functions/render/260-grafana-dashboards.yaml.gotmpl | head -30

# Look for other patterns that check existence vs readiness
rg 'hasKey' functions/ | head -20

# Check the beginning of the file to understand the state structure
sed -n '1,50p' functions/render/260-grafana-dashboards.yaml.gotmpl

# Search for any existing existence-based checks
rg 'if.*observed' functions/render/260-grafana-dashboards.yaml.gotmpl | head -20

Repository: hops-ops/stack-observe

Length of output: 3121


🏁 Script executed:

# Search for the definition of observed resources and their structure
rg 'observed.*resources' functions/ | head -10

# Look for hasKey usage to see if it's a pattern used in this codebase
rg 'hasKey' . --type yaml --type gotmpl

# Check for any resource registration or observed key definitions
rg 'dashboard-opencost-overview|datasource-prometheus' functions/

# Search for how the state/observed structure is defined in the composition
grep -r "observed" apis/ --include="*.yaml" | head -20

Repository: hops-ops/stack-observe

Length of output: 2338


🏁 Script executed:

# Look at the 010-state-status file to understand the observed structure better
cat functions/render/010-state-status.yaml.gotmpl | grep -A 20 "observed.resources"

# Check if hasKey is valid Sprig function
rg 'hasKey' functions/ --no-filename -A 2 -B 2

Repository: hops-ops/stack-observe

Length of output: 1112


🏁 Script executed:

# Check the complete state-status file to see the full structure
cat functions/render/010-state-status.yaml.gotmpl

# Search for the complete state object definition
rg 'state.*:=.*dict' functions/render/ -A 10 | head -40

Repository: hops-ops/stack-observe

Length of output: 3328


🏁 Script executed:

# Verify: when does a resource appear in $.observed.resources?
# Check the composition definition to understand resource observation
head -200 apis/observes/composition.yaml | grep -A 50 "observed"

Repository: hops-ops/stack-observe

Length of output: 48


🏁 Script executed:

# Check if resources appear in observed.resources only when they are created
rg 'observed\.resources' apis/ functions/ | head -20

Repository: hops-ops/stack-observe

Length of output: 167


Gate Usage protection on resource existence, not readiness.

Lines 79–215 gate each Usage on both resources being ready. During transient non-ready states—even when both resources exist and the dependency is active—the Usage is omitted from the rendered template. On the next composition reconcile, the absent Usage is deleted from the cluster, immediately dropping deletion protection while the dashboard still references the datasource.

For deletion ordering, protect based on resource existence in the cluster, not readiness status.

🔧 Proposed direction (apply to all six blocks)
-{{- if and $state.observed.dashboardOpencostOverview.ready $state.observed.datasourcePrometheus.ready }}
+{{- if and (hasKey $.observed.resources "dashboard-opencost-overview") (hasKey $.observed.resources "datasource-prometheus") }}
---
apiVersion: protection.crossplane.io/v1beta1
kind: Usage
...
{{- end }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@functions/render/260-grafana-dashboards.yaml.gotmpl` around lines 79 - 215,
The Usage blocks currently gate on readiness (e.g. conditions like and
$state.observed.dashboardOpencostOverview.ready
$state.observed.datasourcePrometheus.ready), which causes the Usage to be
omitted while resources exist but are not "ready"; update each conditional to
gate on resource existence instead (for example replace checks of
$state.observed.dashboardOpencostOverview.ready and
$state.observed.datasourcePrometheus.ready with the corresponding existence
flags such as $state.observed.dashboardOpencostOverview.exists and
$state.observed.datasourcePrometheus.exists or another boolean that reflects
presence), applying the same change to all six blocks (overview/namespace ×
prometheus/loki/tempo) so the Usage objects (names like {{ $state.name
}}-delete-dashboard-overview-before-ds-prometheus, annotations
setResourceNameAnnotation "usage-dsprom-dash-overview", and similar) are
rendered when the resources exist regardless of ready state.

Copy link

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/on-pr.yaml:
- Line 47: The workflow is referencing a mutable feature branch in the uses:
directive
(unbounded-tech/workflows-crossplane/.github/workflows/e2e.yaml@feat/more-usage-debugging);
replace that branch ref with an immutable identifier (a released tag like
`@vX.Y.Z` or a commit SHA) so the PR e2e job points to a stable, reproducible
workflow; update the uses: value accordingly while keeping the same path to
.github/workflows/e2e.yaml.

In @.github/workflows/on-push-main.yaml:
- Line 26: Update the three workflow usage references that point to
unbounded-tech/workflows-crossplane/.github/workflows/validate.yaml@2.15.0 so
they match the annotated Git tag by changing the suffix to `@v2.15.0` (replace
occurrences of "@2.15.0" with "@v2.15.0"); alternatively, for stronger
supply-chain security you can pin those usages to the commit SHA by using
"@bfaaaf9" and leave a trailing comment mentioning "v2.15.0" for readability.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6851e34 and dd685da.

📒 Files selected for processing (3)
  • .github/workflows/on-pr.yaml
  • .github/workflows/on-push-main.yaml
  • .github/workflows/on-version-tagged.yaml


e2e:
uses: unbounded-tech/workflows-crossplane/.github/workflows/e2e.yaml@v2.14.2
uses: unbounded-tech/workflows-crossplane/.github/workflows/e2e.yaml@feat/more-usage-debugging
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# First, check the actual file content
echo "=== Checking .github/workflows/on-pr.yaml line 47 ==="
if [ -f ".github/workflows/on-pr.yaml" ]; then
  sed -n '45,50p' ".github/workflows/on-pr.yaml"
else
  echo "File not found at .github/workflows/on-pr.yaml"
fi

echo ""
echo "=== Verifying upstream refs ==="
repo="https://github.com/unbounded-tech/workflows-crossplane.git"

echo "Checking feature branch ref:"
git ls-remote --refs "$repo" "refs/heads/feat/more-usage-debugging" || echo "Feature branch not found"

echo ""
echo "Checking stable tag ref:"
git ls-remote --refs "$repo" "refs/tags/2.15.0" || echo "Tag not found"

echo ""
echo "List recent tags for reference:"
git ls-remote --tags "$repo" | grep -E '2\.[0-9]+\.[0-9]+' | tail -10

Repository: hops-ops/stack-observe

Length of output: 1029


Avoid feature-branch refs in PR e2e workflow.

Line 47 points to a mutable feature branch, which makes CI non-reproducible and brittle (branch force-push/deletion risk). Use a stable release tag or immutable commit SHA.

Suggested fix
-    uses: unbounded-tech/workflows-crossplane/.github/workflows/e2e.yaml@feat/more-usage-debugging
+    uses: unbounded-tech/workflows-crossplane/.github/workflows/e2e.yaml@v2.15.0
📝 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
uses: unbounded-tech/workflows-crossplane/.github/workflows/e2e.yaml@feat/more-usage-debugging
uses: unbounded-tech/workflows-crossplane/.github/workflows/e2e.yaml@v2.15.0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/on-pr.yaml at line 47, The workflow is referencing a
mutable feature branch in the uses: directive
(unbounded-tech/workflows-crossplane/.github/workflows/e2e.yaml@feat/more-usage-debugging);
replace that branch ref with an immutable identifier (a released tag like
`@vX.Y.Z` or a commit SHA) so the PR e2e job points to a stable, reproducible
workflow; update the uses: value accordingly while keeping the same path to
.github/workflows/e2e.yaml.

Copy link

@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

Caution

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

⚠️ Outside diff range comments (1)
functions/render/200-kube-prometheus-stack.yaml.gotmpl (1)

102-123: ⚠️ Potential issue | 🟡 Minor

Usage render condition drops protection transiently if kubePrometheusStack becomes not-ready

The guard {{- if and $state.observed.grafanaOperator.ready $state.observed.kubePrometheusStack.ready }} means the Usage is removed from desired state whenever either resource is not observed-and-ready (e.g., during a Helm upgrade of kps itself). Crossplane will garbage-collect the Usage, leaving kps unprotected until the next reconciliation cycle restores readiness.

replayDeletion: true is designed for the case where a deletion is already in flight — it replays the deletion right after the using resource disappears, significantly reducing wait time. But that doesn't cover the scenario where the Usage is transiently absent and a deletion request arrives in that window.

Consider guarding only on grafanaOperator.ready (the by resource whose ref must exist), rather than both, since kps being the of resource doesn't need to be ready for the Usage to be valid:

🛡️ Proposed tighter guard
-{{- if and $state.observed.grafanaOperator.ready $state.observed.kubePrometheusStack.ready }}
+{{- if $state.observed.grafanaOperator.ready }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@functions/render/200-kube-prometheus-stack.yaml.gotmpl` around lines 102 -
123, The Usage resource is being removed whenever either GrafanaOperator or
kubePrometheusStack is not-ready because of the guard "{{- if and
$state.observed.grafanaOperator.ready $state.observed.kubePrometheusStack.ready
}}", which transiently drops protection; change the conditional to only require
the "grafanaOperator" readiness (i.e., check
$state.observed.grafanaOperator.ready) so the Usage (kind: Usage, metadata name
using setResourceNameAnnotation "usage-kps-grafop", spec.replayDeletion: true,
spec.of.resourceRef.name = {{ $kps.name }}, spec.by.resourceRef.name = {{
$state.grafanaOperator.name }}) remains present even when kubePrometheusStack is
temporarily not-ready.
♻️ Duplicate comments (1)
.github/workflows/on-pr.yaml (1)

47-47: ⚠️ Potential issue | 🟠 Major

e2e still points to a mutable feature branch — use a stable tag or SHA.

feat/more-usage-debugging can be force-pushed or deleted, making CI non-reproducible and fragile. This was not addressed from the previous review cycle.

🔧 Proposed fix
-    uses: unbounded-tech/workflows-crossplane/.github/workflows/e2e.yaml@feat/more-usage-debugging
+    uses: unbounded-tech/workflows-crossplane/.github/workflows/e2e.yaml@v2.15.0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/on-pr.yaml at line 47, Replace the mutable branch ref used
in the workflow invocation — update the uses reference
"unbounded-tech/workflows-crossplane/.github/workflows/e2e.yaml@feat/more-usage-debugging"
to point at a stable tag or a commit SHA (e.g., vX.Y.Z or <commit-sha>) instead
of the feature branch so the on-pr workflow becomes reproducible and resilient
to force-pushes or branch deletion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Around line 202-217: The deletion graph currently shows k8smon, loki, and
tempo as free resources but omits the two Phase 1 dashboards (dash-overview and
dash-ns), causing inconsistency; update the mermaid graph by adding nodes for
dash-overview and dash-ns (e.g., dash-overview[dash-overview], dash-ns[dash-ns])
and connect them as free/unblocked like k8smon/loki/tempo (use the same -.-
freeX styling and style rules) so the diagram matches the Phase 1 table.
- Line 227: Update the README line describing the "grafana chain" to accurately
state that the grafana-operator Helm release (not kube-prometheus-stack/kps)
installs Grafana CRDs (e.g., GrafanaInstance, GrafanaDatasource), and clarify
the chain's purpose: enforce deletion ordering so GrafanaInstance and related
CRs are removed before the grafana-operator is uninstalled to avoid
finalizer/dangling resource issues; replace the inaccurate phrase
"CRDs...installed by kps" with this corrected description referencing the
grafana-operator and GrafanaInstance/GrafanaDatasource CRs.

---

Outside diff comments:
In `@functions/render/200-kube-prometheus-stack.yaml.gotmpl`:
- Around line 102-123: The Usage resource is being removed whenever either
GrafanaOperator or kubePrometheusStack is not-ready because of the guard "{{- if
and $state.observed.grafanaOperator.ready
$state.observed.kubePrometheusStack.ready }}", which transiently drops
protection; change the conditional to only require the "grafanaOperator"
readiness (i.e., check $state.observed.grafanaOperator.ready) so the Usage
(kind: Usage, metadata name using setResourceNameAnnotation "usage-kps-grafop",
spec.replayDeletion: true, spec.of.resourceRef.name = {{ $kps.name }},
spec.by.resourceRef.name = {{ $state.grafanaOperator.name }}) remains present
even when kubePrometheusStack is temporarily not-ready.

---

Duplicate comments:
In @.github/workflows/on-pr.yaml:
- Line 47: Replace the mutable branch ref used in the workflow invocation —
update the uses reference
"unbounded-tech/workflows-crossplane/.github/workflows/e2e.yaml@feat/more-usage-debugging"
to point at a stable tag or a commit SHA (e.g., vX.Y.Z or <commit-sha>) instead
of the feature branch so the on-pr workflow becomes reproducible and resilient
to force-pushes or branch deletion.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd685da and cc05644.

📒 Files selected for processing (5)
  • .github/workflows/on-pr.yaml
  • .github/workflows/on-push-main.yaml
  • .github/workflows/on-version-tagged.yaml
  • README.md
  • functions/render/200-kube-prometheus-stack.yaml.gotmpl
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/on-push-main.yaml

| 4 | grafana-operator | grafana-instance gone |
| 5 | kube-prometheus-stack | grafana-operator gone |

The grafana chain ensures CRDs (managed by grafana-operator, installed by kps) stay alive until all CRs are cleaned up.
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's see the context around line 227 in README.md
sed -n '220,235p' README.md

Repository: hops-ops/stack-observe

Length of output: 658


🏁 Script executed:

# Search for CustomResourceDefinition files in the repository
find . -type f \( -name "*.yaml" -o -name "*.yml" \) -exec grep -l "kind: CustomResourceDefinition" {} \; | head -20

Repository: hops-ops/stack-observe

Length of output: 48


🏁 Script executed:

# Get an overview of what grafana-related CRDs might exist in this repo
rg "kind: CustomResourceDefinition" -A 5 -B 1 | head -50

Repository: hops-ops/stack-observe

Length of output: 48


🏁 Script executed:

# Check if this is a Helm chart and look at the structure
ls -la | head -20

Repository: hops-ops/stack-observe

Length of output: 914


🏁 Script executed:

# Look for Chart.yaml or similar
find . -maxdepth 2 -name "Chart.yaml" -o -name "values*.yaml" | head -10

Repository: hops-ops/stack-observe

Length of output: 48


🏁 Script executed:

# Search for grafana-related configuration or comments
rg "grafana" -i --type yaml -C 3 | head -60

Repository: hops-ops/stack-observe

Length of output: 3979


🏁 Script executed:

# Look for the full context around line 227 and the chain table
sed -n '200,250p' README.md

Repository: hops-ops/stack-observe

Length of output: 1723


🏁 Script executed:

# Search for deletion, finalizers, or chain-related comments in composition/definition files
rg "deletion|finalizer|chain" -i --type yaml -C 2

Repository: hops-ops/stack-observe

Length of output: 341


🏁 Script executed:

# Look at the composition definition to understand the dependency model
cat apis/observes/definition.yaml | head -100

Repository: hops-ops/stack-observe

Length of output: 4382


🏁 Script executed:

# Look for composition resources that manage the deletion chain/finalizers
find . -name "*.yaml" -path "*/apis/*" -o -path "*/functions/*" | xargs grep -l "finalizer\|dependsOn" | head -5

Repository: hops-ops/stack-observe

Length of output: 106


🏁 Script executed:

# Check if there are composition resources that manage the grafana resources
cat apis/observes/composition.yaml 2>/dev/null | head -150

Repository: hops-ops/stack-observe

Length of output: 489


Clarify the purpose of the grafana deletion chain

The phrase "CRDs...installed by kps" is inaccurate. The grafana-operator Helm release installs grafana-operator's CRDs (like GrafanaInstance, GrafanaDatasource), not kube-prometheus-stack.

The grafana chain's actual purpose is ensuring cleanup ordering: grafana-instance custom resources must be deleted before the grafana-operator is removed, preventing issues with finalizers and dangling resources. The comment should reflect this accurately instead of implying kps manages the CRDs.

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

In `@README.md` at line 227, Update the README line describing the "grafana chain"
to accurately state that the grafana-operator Helm release (not
kube-prometheus-stack/kps) installs Grafana CRDs (e.g., GrafanaInstance,
GrafanaDatasource), and clarify the chain's purpose: enforce deletion ordering
so GrafanaInstance and related CRs are removed before the grafana-operator is
uninstalled to avoid finalizer/dangling resource issues; replace the inaccurate
phrase "CRDs...installed by kps" with this corrected description referencing the
grafana-operator and GrafanaInstance/GrafanaDatasource CRs.

@patrickleet patrickleet force-pushed the feat/deletion-ordering branch from cc05644 to 3c07530 Compare February 25, 2026 19:25
@patrickleet patrickleet merged commit 80a8c48 into main Feb 25, 2026
11 checks passed
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.

1 participant