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
1 change: 1 addition & 0 deletions deploy/console/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ services:
ALERTS_FROM: ${ALERTS_FROM:-}
PLATFORM_APP_CLIENT_ID: ${PLATFORM_APP_CLIENT_ID:-}
PLATFORM_APP_PRIVATE_KEY: ${PLATFORM_APP_PRIVATE_KEY:-}
DEPLOY_REPORT_SECRET: ${DEPLOY_REPORT_SECRET:-}
privileged: true
pid: host
volumes: [/var/run/docker.sock:/var/run/docker.sock, data:/data, logs:/logs]
Expand Down
34 changes: 34 additions & 0 deletions deploy/graphite-hub/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
services:
app:
extends: {file: ../../shared/app.yaml, service: app}
image: ghcr.io/project-graphite/graphite-hub/site:${IMAGE_TAG:?}
environment:
REDIS_URL: redis://:${REDIS_PASSWORD:?}@redis:6379
SESSION_SECRET: ${SESSION_SECRET:?}
GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID:?}
GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET:?}
GITHUB_WEBHOOK_SECRET: ${GITHUB_WEBHOOK_SECRET:?}
DEPLOY_REPORT_SECRET: ${DEPLOY_REPORT_SECRET:?}
PLATFORM_APP_CLIENT_ID: ${PLATFORM_APP_CLIENT_ID:?}
PLATFORM_APP_PRIVATE_KEY: ${PLATFORM_APP_PRIVATE_KEY:?}
SMTP_URL: ${SMTP_URL:-}
ALERTS_EMAIL: ${ALERTS_EMAIL:-}
ALERTS_FROM: ${ALERTS_FROM:-}
volumes: [data:/data]
labels: {graphite.backup.volumes: data}
healthcheck:
test: [CMD, node, -e, "fetch('http://127.0.0.1:3000/health/ready').then((r) => process.exit(r.ok ? 0 : 1), () => process.exit(1))"]
start_period: 20s
stop_grace_period: 10s
depends_on:
redis: {condition: service_healthy}

redis:
extends: {file: ../../shared/redis-password.yaml, service: redis}

volumes:
data: {}
redis-data: {}

networks:
edge: {external: true}
8 changes: 8 additions & 0 deletions deploy/graphite-hub/values
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
IMAGE_TAG=sha-004b86c
PROXIED=true
APP_MEMORY=1024M
APP_RESERVE=512M
APP_CPUS=1.5
REDIS_MEMORY=128M
REDIS_MAXMEMORY=96mb
REDIS_POLICY=noeviction
ENABLED=false
3 changes: 2 additions & 1 deletion host/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ id deploy >/dev/null 2>&1 || useradd --system --create-home --shell /usr/sbin/no

install -d -o deploy -g deploy /srv/graphite /srv/graphite/bin /srv/graphite/edge /srv/graphite/edge/routes
curl -fsSL -o /srv/graphite/bin/graphite-up "$source/host/graphite-up"
curl -fsSL -o /srv/graphite/bin/graphite-report "$source/host/graphite-report"
curl -fsSL -o /srv/graphite/bin/agent.py "$source/host/agent.py"
curl -fsSL -o /srv/graphite/edge/compose.yaml "$source/edge/compose.yaml"
curl -fsSL -o /srv/graphite/edge/traefik.yml "$source/edge/traefik.yml"
curl -fsSL -o /srv/graphite/edge/routes/edge.yml "$source/edge/agent.yml"
curl -fsSL -o /etc/systemd/system/graphite-agent.service "$source/host/graphite-agent.service"
printf 'CF_DNS_API_TOKEN=%s\n' "$CF_DNS_API_TOKEN" > /srv/graphite/edge/secrets.env
chmod 755 /srv/graphite/bin/graphite-up
chmod 755 /srv/graphite/bin/graphite-up /srv/graphite/bin/graphite-report
chown -R deploy:deploy /srv/graphite
chmod 600 /srv/graphite/edge/secrets.env

Expand Down
97 changes: 97 additions & 0 deletions host/graphite-report
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/usr/bin/env python3
import datetime
import hashlib
import hmac
import json
import pathlib
import re
import subprocess
import sys
import time
import urllib.error
import urllib.request

BASE = pathlib.Path("/srv/graphite")
AGENT_ENV = pathlib.Path("/etc/graphite/agent.env")
HUB = "graphite-hub"
LINE = re.compile(r"""^([A-Z_][A-Z0-9_]*)=(?:'([^']*)'|"((?:[^"\\]|\\.)*)"|(.*))$""")
ESCAPES = {"n": "\n", "r": "\r", "t": "\t"}


def settings(path):
found = {}
for line in path.read_text(encoding="utf-8").splitlines() if path.exists() else []:
match = LINE.match(line)
if match:
single, double, plain = match.group(2, 3, 4)
found[match[1]] = single if single is not None else re.sub(
r"\\(.)", lambda escape: ESCAPES.get(escape[1], escape[1]), double
) if double is not None else plain.strip()
return found


def docker(*args):
return subprocess.run(["docker", *args], capture_output=True, text=True, timeout=30).stdout.strip()


def hub_address():
container = docker(
"ps", "-q",
"--filter", f"label=com.docker.compose.project={HUB}",
"--filter", "label=com.docker.compose.service=app",
).split("\n")[0]
if not container:
return None
return docker("inspect", "-f", '{{with index .NetworkSettings.Networks "edge"}}{{.IPAddress}}{{end}}', container) or None


def health(host):
started = time.monotonic()
try:
with urllib.request.urlopen(f"https://{host}/", timeout=10) as response:
status = response.status
except urllib.error.HTTPError as error:
status = error.code
except (urllib.error.URLError, OSError):
status = None
return status, round((time.monotonic() - started) * 1000)


def main(name, started, exit_status):
project = settings(BASE / name / "values")
if "HOST" not in project:
print(f"graphite-report: {name} has no host to report")
return 0
secret = settings(AGENT_ENV).get("DEPLOY_REPORT_SECRET")
address = hub_address()
if not secret or not address:
print(f"graphite-report: nothing reported for {name}")
return 0
status, ms = health(project["HOST"])
body = json.dumps({
"project": name,
"tag": project.get("IMAGE_TAG", "unknown"),
"startedAt": started,
"finishedAt": datetime.datetime.now(datetime.UTC).strftime("%Y-%m-%dT%H:%M:%SZ"),
"ok": exit_status == "0",
**({"healthStatus": status} if status is not None else {}),
"healthMs": ms,
}).encode()
stamp = str(int(time.time()))
request = urllib.request.Request(
f"http://{address}:3000/internal/deploys",
data=body,
method="POST",
headers={
"Content-Type": "application/json",
"X-Graphite-Timestamp": stamp,
"X-Graphite-Signature": hmac.new(secret.encode(), stamp.encode() + b"." + body, hashlib.sha256).hexdigest(),
},
)
with urllib.request.urlopen(request, timeout=10) as response:
print(f"graphite-report: {name} reported to the hub ({response.status})")
return 0


if __name__ == "__main__":
sys.exit(main(*sys.argv[1:4]))
2 changes: 2 additions & 0 deletions host/graphite-up
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
set -eu

name=$1
started=$(date -u +%Y-%m-%dT%H:%M:%SZ)
trap 'status=$?; /srv/graphite/bin/graphite-report "$name" "$started" "$status" || echo "graphite-up: rollout report failed" >&2; exit $status' EXIT
cd "/srv/graphite/$name"
env=/etc/graphite/$name.env
if [ ! -f "$env" ]; then
Expand Down
6 changes: 1 addition & 5 deletions projects/graphite-hub.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,5 @@ status: live
listed: false

deploy:
kind: pages
kind: compose
host: project-graphite.com
source:
directory: site
output: dist
command: npm run build
Loading