Skip to content

feat: Add global OTEL extension, backend service, and release automation to Argo template - #53

Merged
yesterdaysrebel merged 35 commits into
mainfrom
feature/global-otel-backend
Sep 11, 2026
Merged

feat: Add global OTEL extension, backend service, and release automation to Argo template#53
yesterdaysrebel merged 35 commits into
mainfrom
feature/global-otel-backend

Conversation

@yesterdaysrebel

@yesterdaysrebel yesterdaysrebel commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

This PR enables the ArgoCD OTEL UI extension globally, deploys a purpose-built in-cluster backend service (otel-extension-api) that the extension calls, and adds release automation to publish the backend container image to GHCR.

Changes Made

argocd.yaml.tpl

  • Enables ArgoCD extension proxy (server.enable.proxy.extension)
  • Adds YAML-valid commented placeholders for OTEL extension config, RBAC policies, server extension list, and backend extraObjects

main.tf

  • Adds otel_enabled, otel_extension_version, otel_backend_tag, and tempo_base_url input variables (OTEL opt-in defaults to false)
  • Renders OTEL extension config, RBAC, server extensions, and backend Deployment/Service into the template when otel_enabled = true
  • Adds output precondition guards for OTEL inputs with whitespace trimming
  • Adds required_version = ">= 1.2.0" Terraform constraint

backend/

  • src/server.js — Node.js/Express server proxying /prometheus/*PROMETHEUS_BASE_URL and /tempo/*TEMPO_BASE_URL, with a /healthz health check
  • package.json — Express + http-proxy-middleware dependencies
  • Dockerfile — Node 20 Alpine container image
  • README.md — Documents all endpoints and environment variables

.github/workflows/release.yml

  • Builds and pushes ghcr.io/glueops/argocd-otel-extension-api to GHCR on every published release using docker/build-push-action

README.md

  • Documents OTEL Terraform inputs and manual install guidance for non-Terraform users
  • Updates Terraform usage example with all required inputs and correct output reference

Copilot AI review requested due to automatic review settings June 15, 2026 05:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This pull request updates the Argo CD Helm values template and accompanying documentation to enable a global Argo CD UI extension (OTEL) and deploy an in-cluster backend service that the extension calls.

Changes:

  • Enables Argo CD’s proxying for extension backends and adds extension configuration in configs.cm.
  • Adds Argo CD RBAC rules to allow invoking the otel-extension.
  • Deploys an otel-extension-api Deployment and Service via extraObjects, and documents the new placeholders in README.md.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
README.md Documents new placeholders and explains that the OTEL extension is configured globally.
argocd.yaml.tpl Enables the extension feature, configures the extension + RBAC, and adds Kubernetes resources for the extension backend API.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI force-pushed the feature/global-otel-backend branch from a3a1322 to 4ba2510 Compare June 15, 2026 05:10
@yesterdaysrebel yesterdaysrebel changed the title Add global OTEL extension and backend service to Argo template feat: Add global OTEL extension and backend service to Argo template Jun 15, 2026
Copilot AI review requested due to automatic review settings June 15, 2026 05:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

Comment thread README.md Outdated
Comment thread argocd.yaml.tpl Outdated
Comment thread argocd.yaml.tpl Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

Comment thread argocd.yaml.tpl Outdated
Comment thread main.tf Outdated
Comment thread README.md Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

Comment thread argocd.yaml.tpl Outdated
Comment thread argocd.yaml.tpl Outdated
Comment thread argocd.yaml.tpl Outdated
Comment thread argocd.yaml.tpl Outdated
Comment thread README.md Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

Comment thread main.tf Outdated
Comment thread main.tf Outdated
Comment thread main.tf Outdated
Comment thread main.tf Outdated
@GlueOps GlueOps deleted a comment from Copilot AI Jun 19, 2026
Copilot AI review requested due to automatic review settings June 23, 2026 05:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 11 changed files in this pull request and generated 8 comments.

Files not reviewed (1)
  • backend/package-lock.json: Generated file

Comment thread main.tf Outdated
Comment thread main.tf Outdated
Comment thread main.tf Outdated
Comment thread main.tf Outdated
Comment thread main.tf Outdated
Comment thread main.tf Outdated
Comment thread main.tf Outdated
Comment thread main.tf Outdated
yesterdaysrebel and others added 14 commits June 23, 2026 11:21
Makes the Argo CD OTEL UI extension unconditional and removes the duplicate
backend this module was deploying.

Always on: drops var.otel_enabled. Safe because the frontend renders nothing
when it has no links -- a cluster whose backend is not up shows no panel at
all rather than an error box. That behaviour only exists from v0.1.3 onward,
so the version default must never go below it.

Backend ownership: platform-helm-chart-platform owns the backend and deploys
it into glueops-core-argocd-extension-backend. This module was deploying a
second Deployment/Service of the same image into glueops-core, so enabling
otel would have run two copies. Removed, along with the now-unused
otel_backend_tag and tempo_base_url.

Backend URL: was glueops-core, which does not resolve (verified NXDOMAIN
in-cluster). Both the Service name and its namespace are hardcoded constants
in the platform chart, so this URL is identical on every cluster -- there is
deliberately nothing per-cluster to substitute.

Installer image: the chart defaults it to quay.io directly, unlike every
other image on the platform. It runs as an initContainer on argocd-server,
so a failed pull takes the Argo CD UI down rather than just disabling the
extension. Pinned to the gpkg mirror.

Verified by rendering the module output and parsing it: valid YAML, no
unreplaced placeholders, tenant RBAC policies preserved (otel policies are
appended, not substituted), and no backend objects in extraObjects.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The previous comment said 'do NOT pin below v0.1.3' while the default was
v0.1.3-rc1, which sorts below v0.1.3 -- the rule contradicted the value it
was guarding. State the actual constraint (a release containing PR #25's
hide-when-empty behaviour) and why a prerelease is pinned for now.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
rc1 blanked the panel behind "Loading links..." on every Argo CD reconcile:
Argo CD remounts status-panel extensions when the Application object updates,
and with timeout.reconciliation: 10s that is roughly every 10 seconds. Each
remount reset the panel and refetched /api/links, once per reconcile per open
application.

rc2 caches the links per application and seeds state from that cache, so a
remount renders immediately with no flash and no refetch. It keeps rc1's
render-nothing-when-empty behaviour, so it is still safe to ship to every
cluster.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Prepares this branch for merge. As it stood, merging would have started
publishing a second, unused backend image on every release, forever.

.github/workflows/release.yml triggered on `release: [published]` and pushed
ghcr.io/glueops/argocd-otel-extension-api, built from backend/ in this repo.
That image is a duplicate: the real backend is argocd-extension-backend-api,
built from GlueOps/argo-cd-extention-backend and deployed by the platform chart
as the glueops-argocd-extension-backend Application. main.tf here says as much
-- "This module must never deploy a second copy of it." The duplicate is
referenced nowhere in the delivery path, and clusters run
argocd-extension-backend-api. Once release-please starts cutting releases from
main, that workflow would fire on each one.

Removes the workflow and backend/. Leaves .github/release.yml alone -- a
different file, org-managed via internal-GlueOps/github-shared-files-sync and
present on main too.

README documented an interface that no longer exists: otel_enabled ("The
default is false, so tenants must opt in explicitly" -- the opposite of current
behaviour), otel_backend_tag, tempo_base_url, and a placeholder_otel_enabled
that is not in the template. The example also omitted argocd_rbac_policies and
carried a credential-shaped client_secret. Rewritten against the eight
variables main.tf actually declares.

No rendering logic changed: main.tf and argocd.yaml.tpl are untouched and the
module output is byte-identical (13256 bytes, sha 57f5c2ab).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…g them in HCL

The extension is unconditionally installed, so the three injected blocks
carried no interpolation at all -- they were static YAML assembled as HCL
string lists and pushed in with replace(). Writing them directly in the
template removes ~56 lines of main.tf and, more importantly, removes the
failure mode: the anchors were *comment lines* at 4, 2 and 6 spaces, and
because rendering is plain string replacement the indentation of a comment
decided whether the injected YAML landed in the right place.

argocd.yaml.tpl now parses as valid YAML as downloaded (verified with yq),
so the documented "wget it and use it with helm install" path works without
substituting anything structural. Only placeholder_otel_extension_version
and placeholder_otel_extension_semver remain, and both are plain scalars.

Verified byte-for-byte: rendering the module before and after produces
identical YAML content (the only added lines are the explanatory comments,
which moved from HCL into the template and now ship in the rendered file),
and yq reports the two documents semantically identical. Zero unsubstituted
placeholders in the output.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The locals chain this PR introduced was justified when OTEL needed five
substitutions on top of the seven already there -- twelve levels of nested
replace() is unreadable. Moving the static blocks into argocd.yaml.tpl cut
that to two, so the nest is nine instead of seven and the rewrite no longer
pays for itself. It also meant this PR touched how every unrelated
placeholder renders, which is review surface the change does not need.

main.tf now keeps the nested-replace shape already on main and appends two
calls for the extension version and semver.

The two version checks move from output preconditions to variable validation
blocks. That is where they belong -- they validate an input, not an output --
and it leaves the output block the same shape as main's. Verified they still
fire: passing "  " fails with "otel_extension_version must be non-empty".

Rendered output is unchanged from before this PR's cleanup: YAML content
byte-identical with comments stripped, and yq reports the documents
semantically identical. Zero unsubstituted placeholders.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
tofu fmt had normalised the provider block's alignment, and the comment above
otel_extension_version still described v0.1.3-rc1 as unreleased and told the
reader to bump the default once PR #25 merged -- both done. Neither line is
what this PR is about.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Argo CD denies extension invocation unless a policy allows it, and the policy
named otel-extension specifically. Adding a second extension would have meant
an RBAC edit reaching every cluster -- exactly the per-cluster churn we are
removing elsewhere. The installed set is controlled by server.extensionList in
this same file, so the wildcard does not widen what can be invoked, only what
can be invoked without another edit.

The explanation sits above policy.csv as a YAML comment rather than inside the
block scalar: everything under policy.csv is Casbin policy text, and while
Casbin does skip "#" lines, RBAC is not worth resting on that. Verified with
yq that the rendered policy value contains only policy lines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
v0.1.3 is the first non-prerelease of the panel and the first build that
renders the links it fetches. Every earlier build gated each category on
status === 'ok', and deployment-config is effectively the only category the
backend ever marks ok, so the panel showed a single Config Repo button and
hid working Loki, Tempo, Prometheus and dashboard links.

Verified end to end: the release tarball downloads, contains
resources/otel-extension/extensions.js (7725 bytes), and the shipped bundle
carries the fix and no longer carries the removed gate. Rendering this module
produces the matching EXTENSION_URL, with zero unsubstituted placeholders.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Security and correctness hardening, no visible change to the panel: safeHref()
gates every rendered href (javascript:/data: XSS, and //host, /\host and
tab/newline open-redirect smuggling), requestTimeoutMs rejects NaN and
fractional values that make setTimeout fire immediately, categories filter to
renderable links before deciding whether to render, React keys cannot collide,
and the theme MutationObserver no longer watches document.body's subtree.

Verified: the release URL returns HTTP 200, the shipped bundle carries the
hardening markers (8058 bytes, up from 7725 in v0.1.3) and still carries the
links cache from #69, and rendering this module produces the matching
EXTENSION_URL with zero unsubstituted placeholders.

Unlike v0.1.3, this tag is an ancestor of main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The status panel no longer appears on clusters without the extension backend.
It previously always rendered its bordered panel with the GlueOps logo, and
usually without even an explanatory line, because a transport failure resolved
as an empty category list and took the success path. v0.1.5 returns null unless
there are links to show, and stops calling /api/links once a failure has marked
the backend absent.

This is what makes the always-on rollout safe: the extension can ship
fleet-wide ahead of, or without, its backend and simply not appear.

Behaviour change: an application whose categories are all empty now shows no
panel at all, where earlier releases showed a logo box.

Verified: the release URL returns HTTP 200 and the shipped bundle no longer
contains "Loading links", "Observability unavailable" or "Application context
not available" -- all three present in v0.1.4 -- while retaining the links
cache and the safeHref/timeout hardening. Rendering this module produces the
matching EXTENSION_URL with zero unsubstituted placeholders.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread argocd.yaml.tpl Outdated
Comment thread argocd.yaml.tpl
Answers both review questions on #53 with evidence rather than assertion.

The role cannot be wildcarded. Argo CD's Casbin matcher resolves the subject
with g(r.sub, p.sub) -- a group lookup -- while resource/action/object go
through globMatch, so `*` in the subject position is a literal name matching
nobody. Verified with `argocd admin settings rbac can` against v3.2.12: with
`p, role:*, ...` both role:readonly and role:admin answer No; with a bare
`p, *, ...` a real user also answers No; with the current two lines both answer
Yes, as does a user mapped in via `g,`. The lines stay as they are.

A bad EXTENSION_URL does NOT break the deployment, which is the opposite of
what the comment here claimed. Tested against argocd-extension-installer:v0.0.9
in a throwaway pod: a nonexistent domain (curl exit 6) and a 404 from a bad tag
(curl exit 22) both leave the initContainer exiting 0, the pod Succeeded, and
the main container starting with an empty /tmp/extensions. The installer's EXIT
trap runs `rm -rf` on its temp dir and then reads $?, which reports the rm
rather than the curl, masking the real code.

The old comment conflated two failure modes. An IMAGE PULL failure genuinely is
fatal -- the initContainer never starts, so argocd-server never starts -- which
is what the gpkg mirror pin protects against. A DOWNLOAD failure is not, and
nothing alerts on it: the extension is just silently absent.

Rendered output is unchanged apart from these comments; policy.csv still
contains only policy lines and there are zero unsubstituted placeholders.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread .gitignore Outdated
Irfan Paraniya and others added 5 commits September 10, 2026 09:46
Reverts the object wildcard. Adding an extension should be a conscious RBAC
decision rather than something a wildcard grants silently, so a second extension
now needs a line here.

Only Argo CD built-in roles are referenced, deliberately. This template is
identical on every cluster while the roles above it come from each tenant's own
argocd_rbac_policies -- venus defines a single group->admin mapping, another
tenant may define several -- so naming a custom role such as role:developers
would leave a dangling reference wherever that tenant does not define it.

The consequence is that a user holding neither built-in role cannot invoke the
extension, and since v0.1.3 the panel renders nothing rather than an error, so
nothing on screen points at RBAC. The agreed fix is `policy.default:
role:readonly`, rolled out with the OTel stack. Verified with `argocd admin
settings rbac can --default-role role:readonly` on v3.2.12: an arbitrary user
goes No -> Yes for the extension and for `get applications`, and stays No for
`delete applications`. Worth being explicit that this widens read access to Argo
CD generally, not only to this panel -- on venus today a non-super_admin has no
access at all.

Also recorded that the role itself cannot be wildcarded: Argo CD resolves the
subject with g(r.sub, p.sub) while resource/action/object use globMatch, so
`p, role:*` and a bare `p, *` both answer No for every subject tested.

Rendered output verified: policy.csv contains only policy lines, zero
unsubstituted placeholders.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
It was introduced by this branch solely to ignore backend/node_modules/, and
backend/ was removed by the same PR when the duplicate backend publisher was
dropped -- the real backend lives in GlueOps/argo-cd-extention-backend and is
deployed by the platform chart.

That leaves a file whose only line ignores a path that exists on neither this
branch nor main. The repo is Terraform plus a template and has nothing to
ignore, and main never carried a .gitignore, so removing it restores that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
argocd-extension-installer:v0.0.9 assigns ext_version once at line 100 of
install.sh and never references it again -- across all 124 lines that is the
only mention. The tarball URL determines everything.

Confirmed empirically rather than by reading: installed the real v0.1.5 tarball
in a throwaway pod with the variable omitted and got the same file at the same
path, md5 56b8f0b9f5e53cad2088624abcd4e763, 8038 bytes -- byte-identical to the
released bundle and to what is running on venus -- with exit 0 and no warning.
The log shows `ext_version=` empty and no different code path taken.

Removing it also removes the second placeholder. main.tf now has one OTEL
substitution instead of two, and the otel_extension_semver local and its
trimprefix are gone. The two placeholders only ever differed by the leading "v":
the URL needs the literal GitHub tag (v0.1.5), EXTENSION_VERSION took the bare
semver (0.1.5).

Rendered output verified: the diff against the previous render is exactly the
two deleted lines, nothing else, and zero unsubstituted placeholders remain.

The finding is recorded next to the env var, including the caveat that a newer
installer could start using it -- the image is pinned in the same block, so
whoever bumps it should re-check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The manual-install note still told readers that both
placeholder_otel_extension_version and placeholder_otel_extension_semver are
substituted. The semver placeholder was removed with EXTENSION_VERSION, so the
template now carries eight placeholders and only one of them is OTEL's.

The version floor said "v0.1.3 or newer", which understates it now that the
extension is installed on every cluster unconditionally. v0.1.3 and v0.1.4 draw
an empty bordered panel on a cluster with no backend; only v0.1.5 renders
nothing. Since always-on is the design, v0.1.5 is the real floor, and the note
now says so and explains what each earlier build does instead.

Verified: all eight placeholders in argocd.yaml.tpl are substituted exactly once
by main.tf, none orphaned, and the rendered output is unchanged by this commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The comment-to-code ratio on the new OTEL blocks was too high. Cut the
long explanatory blocks to a line or two each, or dropped them where the
key name already says it:

  - server.enable.proxy.extension and EXTENSION_URL: dropped
  - extension.config, RBAC policy, installer image pin: reduced
  - otel_extension_version: dropped the version history, since v0.1.5 is
    the first release anyone will use

Only comment lines and the otel_extension_version description string
changed; no behaviour change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@yesterdaysrebel
yesterdaysrebel merged commit 45a0911 into main Sep 11, 2026
2 checks passed
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