Skip to content

Grafana: stock upstream server + lunar-dashboards deploy tool#76

Open
idelvall wants to merge 15 commits into
mainfrom
nacho/grafana-single-deploy
Open

Grafana: stock upstream server + lunar-dashboards deploy tool#76
idelvall wants to merge 15 commits into
mainfrom
nacho/grafana-single-deploy

Conversation

@idelvall

@idelvall idelvall commented Jul 3, 2026

Copy link
Copy Markdown
Member

Goal

Run stock upstream Grafana (grafana/grafana:13.1.0) instead of a Lunar-built/derived image, and install Lunar's plugins/datasources/dashboards over the Grafana HTTP API with a new lunar-dashboards deploy tool. This drops the AGPL and maintenance burden of shipping a modified Grafana, and lets the same tool target both the chart's own Grafana pod and a customer-owned/external Grafana.

Context and problem

The chart shipped a bespoke lunar-grafana image with plugins, dashboards and a theme baked in, plus an entrypoint wired to Postgres and the Hub. That couples us to a modified Grafana (AGPL implications, ongoing maintenance, larger image) and only ever works for the in-cluster pod. Companion to earthly/lunar#2007 (design: earthly/plans#58), which adds the Hub RPCs (GetGrafanaEndpoint, GetGrafanaConnectionString), the 02_grafana grafana_user migration, and the lunar-dashboards image. Docs follow-up: earthly/lunar#2027.

Changes

Stock Grafana pod

  • grafana.image -> grafana/grafana:13.1.0. The pod runs stock Grafana with only GF_SERVER_ROOT_URL, GF_SECURITY_ADMIN_* and GF_USERS_ALLOW_SIGN_UP set; dropped the baked plugins/dashboards/theme and the POSTGRES_* / LUNAR_HUB_* env the old entrypoint needed. The kiosk sidecar (browser view) is unchanged.

lunar-dashboards deploy tool (grafana.dashboardsDeploy)

  • A run-to-completion image (entrypoint deploy.sh) that installs plugins/datasources/dashboards over the Grafana API, resolving the target Grafana endpoint + a read-only DB connection from the Hub over gRPC (so it needs no Grafana/DB args in the chart).
  • Deploy hook Job (grafana-deploy-job.yaml): post-install/post-upgrade, gated on dashboardsDeploy.enabled. Targets the chart's own pod directly (in-cluster Service) or a customer-owned/external Grafana (grafana.enabled=false; endpoint + auth resolved from the Hub). An init container waits for the Hub's gRPC (and, for the chart pod, Grafana's API) so the deploy doesn't race the Hub becoming Ready on a fresh install. Non-blocking: a failed deploy is logged but never fails/rolls back the Hub upgrade.
  • Reconverge sidecar on the Grafana pod: re-applies content on every pod (re)start so the pod is stateless / self-healing (no PVC), deploying to localhost:3000.

Hub wiring for the deploy tool

  • Read-only grafana_user DB role for the datasource: its password is chart-generated - a pre-install/pre-upgrade hook secret weighted before the migrate Job (-10 < -5) with helm.sh/resource-policy: keep, so it exists before the migrate Job creates the role and stays stable across upgrades (BYO via dashboardsDeploy.dbPassword.secretName). The migrate Job creates the role WITH it on a fresh DB, and the Hub vends the connection via GetGrafanaConnectionString. It's internal (never user-facing), so no operator config is needed.
  • The Hub also vends the chart pod's Grafana admin login (HUB_GRAFANA_USER / HUB_GRAFANA_PASSWORD) via GetGrafanaEndpoint.
  • New helper grafanaDeployHubEnv (LUNAR_HUB_*) shared by the deploy Job and the reconverge sidecar.

Chart metadata

  • Chart.yaml 2.12.0 -> 2.13.0; CHANGELOG entry + upgrade note.

Requires / notes

  • A Hub image from earthly/lunar#2007 (the GetGrafanaEndpoint / GetGrafanaConnectionString RPCs + the 02_grafana grafana_user migration) and a published ghcr.io/earthly/lunar-dashboards at a matching tag (dashboardsDeploy.image.tag defaults to hub.image.tag).
  • The grafana_user DB password is chart-generated - no operator config required. For an external Grafana, set grafana.externalURL (-> HUB_GRAFANA_URL_BASE) and HUB_GRAFANA_TOKEN (or HUB_GRAFANA_USER + HUB_GRAFANA_PASSWORD) via hub.extraEnv, and ensure the external Grafana can reach the Hub's Postgres.
  • If you mirror images into a private registry, mirror grafana/grafana:13.1.0 and ghcr.io/earthly/lunar-dashboards too.

Test plan

  • helm lint.
  • helm template renders on: defaults (chart pod), grafana.enabled=false + dashboardsDeploy.enabled=true (external), dashboardsDeploy.enabled=false, and a BYO dashboardsDeploy.dbPassword.secretName.
  • Install/upgrade on a live cluster against a Hub built from earthly/lunar#2007.

Run stock grafana/grafana:13.0.3 instead of a Lunar-built/derived image; install
plugins/datasources/dashboards over the Grafana API via the renamed
lunar-dashboards deploy tool (post-install/upgrade hook Job + reconverge sidecar).

- values: grafana.image -> grafana/grafana:13.0.3; add grafana.dashboardsDeploy
  (enabled/blocking/reconvergeSidecar/backoffLimit/image/dbPassword) and
  grafana.persistence (optional PVC, off by default).
- grafana-deployment: stock server env only (GF_SERVER_ROOT_URL,
  GF_SECURITY_ADMIN_*, GF_USERS_ALLOW_SIGN_UP); reconverge sidecar; optional PVC.
- new grafana-deploy-job.yaml (non-blocking hook), grafana-db-secret.yaml
  (read-only grafana_user password), grafana-pvc.yaml.
- hub + migrate jobs: HUB_GRAFANA_USER/PASSWORD + HUB_GRAFANA_DB_PASSWORD.
- helpers: grafanaDBSecretName + grafanaDeployHubEnv.

Companion to earthly/lunar#2007. Chart 2.12.0 -> 2.13.0.

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

lunar-internal Bot commented Jul 3, 2026

Copy link
Copy Markdown

🌒 Earthly Lunar

✅ 16 Passing

The checks marked as (🔀 required) need to pass before merging is allowed.

  • max-severity sca-critical.max-severity - Ensures no findings at or above the configured severity threshold.
    Configure min_severity to set the threshold (critical, high, medium, low).
    (🔀 required)

  • branch-protection-enabled vcs.branch-protection-enabled - Requires branch protection rules to be enabled on the default branch.
    Branch protection is the foundation for all other VCS security controls.

  • build-tagged container.build-tagged - Requires container builds to use an explicit image tag via -t/--tag.
    Untagged builds produce anonymous images that cannot be tracked or deployed.

13 more...
  • changelog-exists changelog.changelog-exists - Verifies that a CHANGELOG file exists in the repository root. Detects
    common variants (CHANGELOG.md, CHANGELOG, CHANGES.md, HISTORY.md,
    RELEASES.md). Intended for repos that ship versioned releases — apply
    via lunar-config on: targeting (e.g. public-only) rather than blanketly.
    Reads from .repo.changelog.

  • codeowners-catchall repo-hygiene.codeowners-catchall - Requires a default catch-all rule (*) in CODEOWNERS so that every file
    in the repository has at least one owner.

  • codeowners-exists repo-hygiene.codeowners-exists - Requires a CODEOWNERS file to be present in the repository.
    Checks standard locations: root, .github/, or docs/.

  • codeowners-valid repo-hygiene.codeowners-valid - Validates that the CODEOWNERS file has correct syntax.
    Checks that all owner references use valid formats (@user, @org/team, or email).

  • disallow-force-push vcs.disallow-force-push - Prohibits force pushes to protected branches to preserve commit history.
    Force pushes can destroy audit trails and cause data loss.

  • executed sca.executed - Verifies that SCA scanning was executed on the component.
    Fails if no scanner has written to .sca.

  • instruction-file-exists ai.instruction-file-exists - Verifies that an agent instruction file exists at the repository root. Checks
    ai.instructions.all[] which is populated by the ai collector (AGENTS.md) and
    tool-specific collectors via array append (CLAUDE.md, CODEX.md, GEMINI.md).
    Passes if any entry exists.

  • max-severity sca-high.max-severity - Ensures no findings at or above the configured severity threshold.
    Configure min_severity to set the threshold (critical, high, medium, low).

  • no-latest container.no-latest - Prevents use of the :latest tag (explicit or implicit) in base images.
    Using :latest creates non-reproducible builds and makes debugging difficult.

  • readme-exists repo-hygiene.readme-exists - Verifies that a README file exists in the repository root.
    Every repository should have basic documentation for discoverability.

  • require-default-branch vcs.require-default-branch - Validates the default branch name matches the required name (default "main").
    Helps standardize branch naming across repositories.

  • require-pull-request vcs.require-pull-request - Requires all changes to go through pull requests before merging.
    Prevents direct pushes to protected branches without review.

  • stable-tags container.stable-tags - Requires base images to use stable tags: digests (sha256:...) or full semver (1.2.3).
    Partial versions like "node:20" can change unexpectedly and break builds.

More Details

idelvall and others added 2 commits July 3, 2026 17:49
Drop the optional/opt-in surface, keeping the essential deploy path:
- remove grafana.persistence + grafana-pvc.yaml (PVC volume/mount/fsGroup) —
  the reconverge sidecar keeps the pod stateless without it
- remove the blocking toggle (the deploy hook is always non-blocking)
- remove the reconvergeSidecar/backoffLimit knobs (sidecar always on when
  dashboardsDeploy.enabled; backoffLimit hardcoded)
- drop the external-Grafana chart branch (deploy Job gates on grafana.enabled +
  dashboardsDeploy.enabled)

Kept: stock grafana/grafana, deploy hook Job, reconverge sidecar, and the Hub +
grafana_user DB-role wiring.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@idelvall idelvall marked this pull request as ready for review July 3, 2026 18:50
@idelvall idelvall requested a review from dchw as a code owner July 3, 2026 18:50

@me-bender me-bender Bot 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.

Two issues before this can land, one blocking. The pre-install/pre-upgrade migrate hook now reads a chart-generated non-hook secret, so the default fresh install (and the first upgrade to 2.13.0) fail at the hook. Separately, the deploy Job and reconverge sidecar do not actually wait for the Hub, so dashboards can silently fail to install on first install. Details inline.

Comment thread charts/lunar/templates/hub-migrate-job.yaml
Comment thread charts/lunar/templates/grafana-provision-job.yaml
Addresses the charts#76 review:

- Blocking: the pre-install migrate hook read HUB_GRAFANA_DB_PASSWORD from the
  plain <release>-grafana-db secret, which Helm creates only after pre-install
  hooks complete — so a fresh install (and the first upgrade to 2.13.0) failed
  with CreateContainerConfigError -> DeadlineExceeded -> rollback. Drop the
  chart-generated grafana-db secret (+ grafanaDBSecretName helper + the
  dashboardsDeploy.dbPassword values) and supply the grafana_user password
  operator-side via hub.extraEnv, exactly like HUB_SQLAPI_PASSWORD. No chart
  secret means no hook-ordering dependency.

- Non-blocking: deploy.sh makes a single Hub gRPC call with no internal retry,
  and nothing waited for the Hub, so on a fresh install the deploy raced the hub
  becoming Ready and dashboards silently didn't install until the next upgrade.
  Add a wait-for-hub grpcurl loop (reflection list) to the deploy Job's init
  container and the reconverge sidecar, alongside wait-for-grafana; fix the
  misleading backoffLimit comment.

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

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@me-bender me-bender Bot 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.

Both concerns addressed. The chart-generated grafana-db secret is gone (HUB_GRAFANA_DB_PASSWORD now rides hub.extraEnv like HUB_SQLAPI_PASSWORD, so the pre-install migrate hook no longer references a not-yet-created secret), and the deploy Job init container plus the reconverge sidecar both wait for the Hub gRPC before deploy.sh runs. Renders clean on defaults. LGTM.

The chart comments/CHANGELOG referenced GetGrafanaDBConfig, which is not a real
Hub RPC — the deploy tool (and companion earthly/lunar#2007) call
GetGrafanaConnectionString (hubapi/hub.proto). Revert the never-applied rename
so the chart's docs match the actual Hub API.

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

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

Gate the content-deploy Job on dashboardsDeploy.enabled alone (was: also required
grafana.enabled), so it runs for a customer-owned/external Grafana as well as the
chart's own pod:

- Internal (grafana.enabled): unchanged "direct" route — GRAFANA_URL targets the
  in-cluster Service and the init container waits for Grafana's API.
- External (grafana.enabled=false): GRAFANA_URL is omitted and the in-cluster
  Grafana wait is skipped, so deploy.sh resolves the endpoint (URL + auth) from the
  Hub via GetGrafanaEndpoint. The operator configures the Hub for it
  (grafana.externalURL + HUB_GRAFANA_TOKEN or USER/PASSWORD + HUB_GRAFANA_DB_PASSWORD
  via hub.extraEnv).

The Hub-gRPC wait stays unconditional. Docs updated in values.yaml + CHANGELOG.

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

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

The grafana_user role is internal (the Hub vends its connection to the deploy tool;
never user-facing), so the chart should generate its password rather than require
HUB_GRAFANA_DB_PASSWORD via hub.extraEnv.

Ship the password as a pre-install/pre-upgrade hook secret weighted -10 (below the
migrate Job's -5) so it exists before that Job creates the role and before the hub
Deployment reads it — resolving the ordering bug that made a chart-generated plain
secret fail on fresh install. resource-policy: keep + a lookup guard create it once
and keep it stable (the password must stay in sync with the DB role, which the
content-hashed 02_grafana code package won't re-run to reset). Gated on
dashboardsDeploy.enabled so external Grafana gets it too; BYO via
dbPassword.secretName. Reverts the operator-supplied HUB_GRAFANA_DB_PASSWORD path.

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

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

…tions

HUB_SQLAPI_CONNECTION_OPTIONS and HUB_GRAFANA_DB_CONNECTION_OPTIONS default to
sslmode=disable in the Hub, but sqlapi_user and grafana_user connect to the same
Postgres as the hub. Set both from hub.db.connectionOptions (like the operator's
OPERATOR_DB_CONNECTION_OPTIONS) so all connections share one SSL mode — the
sqlapi option always, the grafana option under dashboardsDeploy.enabled.

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

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@dchw dchw 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.

We will need a version bump? Unsure how that fits with our new release process.

I think this update warrants a major version bump?

Comment thread charts/lunar/values.yaml Outdated
Comment thread charts/lunar/templates/grafana-deploy-job.yaml Outdated
Comment thread charts/lunar/templates/grafana-provision-job.yaml
Comment thread charts/lunar/templates/grafana-deploy-job.yaml Outdated
- name: kiosk-config
mountPath: /etc/nginx/conf.d
{{- if .Values.grafana.dashboardsDeploy.enabled }}
- name: dashboards-reconverge

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.

Wonder if its worth adding a readiness probe here to prevent grafana from getting traffic unless successfully provisioned?

@idelvall idelvall Jul 9, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

AI-generated (Opus 4.8) posted at request of @idelvall.

Going to pass on this one. The provisioning path is deliberately non-blocking — a failed provision is logged but never blocks or rolls back the upgrade. A readiness gate inverts that: a provisioning hiccup (or a Hub blip on a pod restart) would pull Grafana itself out of the Service, which is worse than briefly serving an un-provisioned Grafana — and that empty window is only until the post-install Job / reconverge sidecar finish (seconds). We are OK with a briefly-empty Grafana. Happy to revisit as an opt-in, first-provision-only gate if it ever actually bites.

Comment thread charts/lunar/templates/grafana-deploy-job.yaml Outdated
Comment thread charts/lunar/templates/grafana-deploy-job.yaml Outdated
Comment thread charts/lunar/templates/grafana-db-secret.yaml
…ning)

- Replace grafana.enabled + grafana.provisioning.enabled with a single
  grafana.mode = chart | external | off (infra renders in chart mode;
  dashboards provision when mode != off); fail fast on the removed keys.
- Unify grafana.externalURL -> grafana.url and grafana.admin -> grafana.auth
  (+ tokenKey). In external mode the chart wires url + auth into the Hub
  (HUB_GRAFANA_URL_BASE + HUB_GRAFANA_TOKEN or USER/PASSWORD) — no hub.extraEnv.
- Standardize naming on "provisioning" (value, Job, provision-reconverge sidecar, helper).
- Add securityContext + resources to the provision Job and reconverge sidecar;
  securityContext to the kiosk sidecar.
- DRY wait-for-hub/grafana and the image ref into helpers.
- provision Job backoffLimit 6 -> 2 (match migrate Job).
- Document why grafana_user's DB password is chart-generated.
- Chart 2.13.0 -> 3.0.0; update CHANGELOG + README.

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

cursor Bot commented Jul 9, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@idelvall

idelvall commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

AI-generated (Opus 4.8) posted at request of @idelvall.

Re: the version bump / "how it fits the new release process" — bumped the chart to 3.0.0 (major). It is a breaking values-shape change (grafana.enabled + grafana.provisioning.enabledgrafana.mode; grafana.externalURL/grafana.admingrafana.url/grafana.auth) on top of a coordinated Hub upgrade, so it does not ride the automated bump-and-pr flow (which only does minor + image-tag bumps on a lunar release) — the version is set by hand in this PR.

⚠️ One prerequisite before publish: the default hub.image.tag is still 2.6.0, which predates the companion Hub RPCs (GetGrafanaEndpoint / GetGrafanaConnectionString + the 02_grafana migration, earthly/lunar#2007). It needs pinning to the release that includes them, and the chart should not publish before those images (lunar-hub + ghcr.io/earthly/lunar-dashboards) exist — called out in the CHANGELOG Breaking section.

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

# Conflicts:
#	charts/lunar/CHANGELOG.md
#	charts/lunar/Chart.yaml
#	charts/lunar/values.yaml
@cursor

cursor Bot commented Jul 9, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@idelvall idelvall requested a review from dchw July 9, 2026 17:05
Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	charts/lunar/CHANGELOG.md
#	charts/lunar/Chart.yaml
#	charts/lunar/values.yaml
@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

The chart now runs stock upstream grafana/grafana, so the release bump
must not rewrite grafana.image.tag to a lunar version. Chart and hub
versions stay in lockstep — Chart.yaml version plus the
hub/operator/init/sidecar tags still track the release version; grafana
is left untouched.

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

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

Merging this branch must not change Chart.yaml's version, or chart-releaser
would publish 3.0.0 before the matching lunar-hub 3.0.0 / lunar-dashboards
images exist. The lunar-hub 3.0.0 release auto-dispatches bump-and-pr on
charts main, which sets the chart version (and image tags) to 3.0.0 in
lockstep — after the images are published. The CHANGELOG 3.0.0 entry stays
ready and becomes that release's notes.

Co-authored-by: Cursor <cursoragent@cursor.com>
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.

2 participants