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
28 changes: 20 additions & 8 deletions scripts/setup-github-datasource.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,22 @@
set -euo pipefail

GRAFANA_URL="${GRAFANA_URL:-http://localhost:3000}"
[ -f .env ] && { set -a; . ./.env; set +a; }
[ -f .env ] && . ./.env
: "${GITHUB_TOKEN:?Set GITHUB_TOKEN (a read-only fine-grained PAT) in the environment or .env}"
: "${GRAFANA_ADMIN_PASSWORD:?Set GRAFANA_ADMIN_PASSWORD in the environment or .env}"
AUTH="admin:${GRAFANA_ADMIN_PASSWORD}"

TMP_DIR="$(mktemp -d)"
NETRC_FILE="$TMP_DIR/netrc"
trap 'rm -rf "$TMP_DIR"' EXIT
GRAFANA_HOSTPORT="${GRAFANA_URL#*://}"
GRAFANA_HOSTPORT="${GRAFANA_HOSTPORT%%/*}"
GRAFANA_HOST="${GRAFANA_HOSTPORT%%:*}"
printf 'machine %s login %s password %s\n' "$GRAFANA_HOST" admin "$GRAFANA_ADMIN_PASSWORD" >"$NETRC_FILE"
chmod 600 "$NETRC_FILE"

grafana_curl() {
env -u GRAFANA_ADMIN_PASSWORD -u GITHUB_TOKEN curl -sf --netrc-file "$NETRC_FILE" "$@"
}

payload() {
cat <<JSON
Expand All @@ -34,16 +46,16 @@ JSON
}

# Idempotent: update in place if a datasource with uid "github" already exists, else create it.
if curl -sf -u "$AUTH" "$GRAFANA_URL/api/datasources/uid/github" >/dev/null 2>&1; then
if grafana_curl "$GRAFANA_URL/api/datasources/uid/github" >/dev/null 2>&1; then
echo "Updating existing GitHub data source…"
curl -sf -u "$AUTH" -H 'content-type: application/json' -X PUT \
"$GRAFANA_URL/api/datasources/uid/github" -d "$(payload)" >/dev/null
payload | grafana_curl -H 'content-type: application/json' -X PUT \
"$GRAFANA_URL/api/datasources/uid/github" --data-binary @- >/dev/null
else
echo "Creating GitHub data source…"
curl -sf -u "$AUTH" -H 'content-type: application/json' -X POST \
"$GRAFANA_URL/api/datasources" -d "$(payload)" >/dev/null
payload | grafana_curl -H 'content-type: application/json' -X POST \
"$GRAFANA_URL/api/datasources" --data-binary @- >/dev/null
fi

echo "Done. Verifying health…"
curl -sf -u "$AUTH" -X POST "$GRAFANA_URL/api/datasources/uid/github/health" 2>/dev/null \
grafana_curl -X POST "$GRAFANA_URL/api/datasources/uid/github/health" 2>/dev/null \
| grep -q '"status":"OK"' && echo "✓ GitHub data source healthy" || echo "⚠ Added, but health check did not return OK — verify the token scopes."
28 changes: 20 additions & 8 deletions scripts/setup-sentry-datasource.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,26 @@
set -euo pipefail

GRAFANA_URL="${GRAFANA_URL:-http://localhost:3000}"
[ -f .env ] && { set -a; . ./.env; set +a; }
[ -f .env ] && . ./.env
: "${SENTRY_API_TOKEN:?Set SENTRY_API_TOKEN (a Sentry Internal Integration token, NOT your SENTRY_DSN) in the environment or .env}"
: "${SENTRY_ORG_SLUG:?Set SENTRY_ORG_SLUG (your Sentry organization slug) in the environment or .env}"
: "${GRAFANA_ADMIN_PASSWORD:?Set GRAFANA_ADMIN_PASSWORD in the environment or .env}"
AUTH="admin:${GRAFANA_ADMIN_PASSWORD}"
# https://sentry.io for Sentry SaaS; override for a self-hosted Sentry instance.
SENTRY_API_URL="${SENTRY_API_URL:-https://sentry.io}"

TMP_DIR="$(mktemp -d)"
NETRC_FILE="$TMP_DIR/netrc"
trap 'rm -rf "$TMP_DIR"' EXIT
GRAFANA_HOSTPORT="${GRAFANA_URL#*://}"
GRAFANA_HOSTPORT="${GRAFANA_HOSTPORT%%/*}"
GRAFANA_HOST="${GRAFANA_HOSTPORT%%:*}"
printf 'machine %s login %s password %s\n' "$GRAFANA_HOST" admin "$GRAFANA_ADMIN_PASSWORD" >"$NETRC_FILE"
chmod 600 "$NETRC_FILE"

grafana_curl() {
env -u GRAFANA_ADMIN_PASSWORD -u SENTRY_API_TOKEN curl -sf --netrc-file "$NETRC_FILE" "$@"
}

payload() {
cat <<JSON
{ "name": "Sentry", "type": "grafana-sentry-datasource", "uid": "sentry", "access": "proxy",
Expand All @@ -44,16 +56,16 @@ JSON
}

# Idempotent: update in place if a datasource with uid "sentry" already exists, else create it.
if curl -sf -u "$AUTH" "$GRAFANA_URL/api/datasources/uid/sentry" >/dev/null 2>&1; then
if grafana_curl "$GRAFANA_URL/api/datasources/uid/sentry" >/dev/null 2>&1; then
echo "Updating existing Sentry data source…"
curl -sf -u "$AUTH" -H 'content-type: application/json' -X PUT \
"$GRAFANA_URL/api/datasources/uid/sentry" -d "$(payload)" >/dev/null
payload | grafana_curl -H 'content-type: application/json' -X PUT \
"$GRAFANA_URL/api/datasources/uid/sentry" --data-binary @- >/dev/null
else
echo "Creating Sentry data source…"
curl -sf -u "$AUTH" -H 'content-type: application/json' -X POST \
"$GRAFANA_URL/api/datasources" -d "$(payload)" >/dev/null
payload | grafana_curl -H 'content-type: application/json' -X POST \
"$GRAFANA_URL/api/datasources" --data-binary @- >/dev/null
fi

echo "Done. Verifying health…"
curl -sf -u "$AUTH" -X POST "$GRAFANA_URL/api/datasources/uid/sentry/health" 2>/dev/null \
grafana_curl -X POST "$GRAFANA_URL/api/datasources/uid/sentry/health" 2>/dev/null \
| grep -q '"status":"OK"' && echo "✓ Sentry data source healthy" || echo "⚠ Added, but health check did not return OK — verify SENTRY_API_TOKEN's scopes and SENTRY_ORG_SLUG."
43 changes: 43 additions & 0 deletions test/unit/selfhost-grafana-github-datasource.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { readFileSync, statSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";

// scripts/setup-github-datasource.sh had no dedicated coverage before this (only referenced in passing by
// selfhost-grafana-sentry-datasource.test.ts's own comparison assertions). Mirrors that file's credential-safety
// test: this script shares the exact same GRAFANA_ADMIN_PASSWORD-via-curl-argv/env leak this PR fixes.
describe("scripts/setup-github-datasource.sh", () => {
function readScript(): string {
return readFileSync(join(process.cwd(), "scripts/setup-github-datasource.sh"), "utf8");
}

it("is idempotent (update-vs-create) and ships a health check, matching setup-sentry-datasource.sh's own shape", () => {
const script = readScript();

expect(script).toContain("GITHUB_TOKEN");
expect(script).toContain("grafana-github-datasource");
expect(script).toContain("api/datasources/uid/github");
expect(script).toMatch(/-X PUT/);
expect(script).toMatch(/-X POST/);
expect(script).toContain("secureJsonData");
expect(script).toContain("accessToken");
expect(script).toContain("/health");
});

it("keeps GitHub and Grafana credentials out of curl argv and child environments", () => {
const script = readScript();

expect(script).not.toContain("set -a");
expect(script).not.toContain('AUTH="admin:${GRAFANA_ADMIN_PASSWORD}"');
expect(script).not.toContain('-u "$AUTH"');
expect(script).not.toContain('-d "$(payload)"');
expect(script).toContain('--netrc-file "$NETRC_FILE"');
expect(script).toContain('--data-binary @-');
expect(script).toMatch(/env -u GRAFANA_ADMIN_PASSWORD -u GITHUB_TOKEN curl/);
});

it("is executable", () => {
const mode = statSync(join(process.cwd(), "scripts/setup-github-datasource.sh")).mode;
// Owner-execute bit (0o100).
expect(mode & 0o100).not.toBe(0);
});
});
12 changes: 12 additions & 0 deletions test/unit/selfhost-grafana-sentry-datasource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@ describe("Grafana Sentry data source (#5369)", () => {
expect(script).toContain("/health");
});

it("keeps Sentry and Grafana credentials out of curl argv and child environments", () => {
const script = readFileSync(join(process.cwd(), "scripts/setup-sentry-datasource.sh"), "utf8");

expect(script).not.toContain("set -a");
expect(script).not.toContain('AUTH="admin:${GRAFANA_ADMIN_PASSWORD}"');
expect(script).not.toContain('-u "$AUTH"');
expect(script).not.toContain('-d "$(payload)"');
expect(script).toContain('--netrc-file "$NETRC_FILE"');
expect(script).toContain('--data-binary @-');
expect(script).toMatch(/env -u GRAFANA_ADMIN_PASSWORD -u SENTRY_API_TOKEN curl/);
});

it("setup-sentry-datasource.sh is executable, matching setup-github-datasource.sh's own mode", () => {
const mode = statSync(join(process.cwd(), "scripts/setup-sentry-datasource.sh")).mode;
// Owner-execute bit (0o100).
Expand Down
Loading