Skip to content
Closed
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."
34 changes: 34 additions & 0 deletions test/unit/selfhost-grafana-github-datasource.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { readFileSync, statSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";

describe("Grafana GitHub data source", () => {
it("keeps GITHUB_TOKEN and GRAFANA_ADMIN_PASSWORD out of curl argv and child environments", () => {
const script = readFileSync(join(process.cwd(), "scripts/setup-github-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 GITHUB_TOKEN curl/);
});

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

it("remains idempotent (update-vs-create) and preserves the health check after the credential-handling rewrite", () => {
const script = readFileSync(join(process.cwd(), "scripts/setup-github-datasource.sh"), "utf8");

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");
});
});