fix: resolve 4 tag-blocking issues for v0.1.0
CI / test (pull_request) Successful in 1m43s
CI / lint (pull_request) Successful in 1m11s
CI / vulncheck (pull_request) Successful in 51s

- README/CONTRIBUTING quick start: add UPTOP_ADMIN_KEY so SSH works on
  fresh DB, fix single-file go run path that doesn't compile
- apply --dry-run: assign placeholder IDs for new alerts and groups so
  resolveAlertID succeeds when monitors reference not-yet-created alerts
- deploy/*.yml: switch user-facing compose files from broken build
  context to image: lerkolabs/uptop:latest, fix dev context to ..
This commit is contained in:
2026-06-16 20:32:41 -04:00
parent 2e07e16b45
commit c2bfa5ad82
8 changed files with 86 additions and 13 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
## Development
```sh
go run cmd/uptop/main.go -demo # starts with sample data
go run ./cmd/uptop -demo # starts with sample data
ssh -p 23234 localhost # connect to TUI
```
+2 -2
View File
@@ -53,14 +53,14 @@ Canonical repo: [gitea.lerkolabs.com/lerkolabs/uptop](https://gitea.lerkolabs.co
## Quick start
```bash
go run ./cmd/uptop
UPTOP_ADMIN_KEY="$(cat ~/.ssh/id_ed25519.pub)" go run ./cmd/uptop
ssh -p 23234 localhost
```
Want some data to look at first:
```bash
go run ./cmd/uptop -demo
UPTOP_ADMIN_KEY="$(cat ~/.ssh/id_ed25519.pub)" go run ./cmd/uptop -demo
```
## Install
+2 -2
View File
@@ -3,7 +3,7 @@ services:
# LEADER NODE
# -------------------------
leader:
build: .
image: lerkolabs/uptop:latest
container_name: uptop-leader
ports:
- "23234:23234" # SSH
@@ -38,7 +38,7 @@ services:
# FOLLOWER NODE
# -------------------------
follower:
build: .
image: lerkolabs/uptop:latest
container_name: uptop-follower
ports:
- "23233:23234" # SSH (Mapped to different host port)
+2 -2
View File
@@ -1,8 +1,8 @@
services:
# The Application
app:
build:
context: .
build:
context: ..
dockerfile: Dockerfile
container_name: uptop-dev
ports:
+3 -3
View File
@@ -1,6 +1,6 @@
services:
leader:
build: .
image: lerkolabs/uptop:latest
environment:
- UPTOP_CLUSTER_MODE=leader
- UPTOP_CLUSTER_SECRET=changeme # EXAMPLE ONLY — rotate before use
@@ -11,7 +11,7 @@ services:
- "23234:23234"
probe-us-east:
build: .
image: lerkolabs/uptop:latest
environment:
- UPTOP_CLUSTER_MODE=probe
- UPTOP_NODE_ID=us-east-1
@@ -23,7 +23,7 @@ services:
- leader
probe-eu-west:
build: .
image: lerkolabs/uptop:latest
environment:
- UPTOP_CLUSTER_MODE=probe
- UPTOP_NODE_ID=eu-west-1
+1 -3
View File
@@ -1,8 +1,6 @@
services:
app:
build:
context: .
dockerfile: Dockerfile
image: lerkolabs/uptop:latest
container_name: uptop
restart: unless-stopped
read_only: true
+7
View File
@@ -54,6 +54,7 @@ func Apply(ctx context.Context, s store.Store, f *File, opts ApplyOpts) ([]Chang
alertMap[ea.Name] = ea.ID
}
nextPlaceholderID := -1
desiredAlertNames := make(map[string]bool, len(f.Alerts))
for _, a := range f.Alerts {
desiredAlertNames[a.Name] = true
@@ -66,6 +67,9 @@ func Apply(ctx context.Context, s store.Store, f *File, opts ApplyOpts) ([]Chang
return changes, fmt.Errorf("create alert %q: %w", a.Name, err)
}
alertMap[a.Name] = id
} else {
alertMap[a.Name] = nextPlaceholderID
nextPlaceholderID--
}
} else {
alertMap[a.Name] = existing.ID
@@ -109,6 +113,9 @@ func Apply(ctx context.Context, s store.Store, f *File, opts ApplyOpts) ([]Chang
return changes, fmt.Errorf("create group %q: %w", g.Name, err)
}
groupMap[g.Name] = id
} else {
groupMap[g.Name] = nextPlaceholderID
nextPlaceholderID--
}
} else {
groupMap[g.Name] = existing.ID
+68
View File
@@ -266,6 +266,74 @@ func TestApplyDuplicateNames(t *testing.T) {
}
}
func TestApplyDryRunNewAlertAndMonitor(t *testing.T) {
s := newTestStore(t)
f := &File{
Alerts: []Alert{
{Name: "Discord", Type: "discord", Settings: map[string]string{"url": "https://example.com"}},
},
Monitors: []Monitor{
{Name: "Web", Type: "http", URL: "https://example.com", Interval: 30, Alert: "Discord"},
},
}
changes, err := Apply(context.Background(), s, f, ApplyOpts{DryRun: true})
if err != nil {
t.Fatalf("dry-run with new alert+monitor should not error: %v", err)
}
creates := 0
for _, c := range changes {
if c.Action == "create" {
creates++
}
}
if creates != 2 {
t.Fatalf("expected 2 creates (alert+monitor), got %d: %+v", creates, changes)
}
sites, _ := s.GetSites(context.Background())
alerts, _ := s.GetAllAlerts(context.Background())
if len(sites) != 0 {
t.Fatalf("dry-run should not persist sites, got %d", len(sites))
}
if len(alerts) != 0 {
t.Fatalf("dry-run should not persist alerts, got %d", len(alerts))
}
}
func TestApplyDryRunNewGroupWithChildren(t *testing.T) {
s := newTestStore(t)
f := &File{
Alerts: []Alert{
{Name: "Slack", Type: "slack", Settings: map[string]string{"url": "https://hooks.example.com"}},
},
Monitors: []Monitor{
{
Name: "Prod", Type: "group", Alert: "Slack",
Monitors: []Monitor{
{Name: "API", Type: "http", URL: "https://api.example.com", Interval: 15, Alert: "Slack"},
},
},
},
}
changes, err := Apply(context.Background(), s, f, ApplyOpts{DryRun: true})
if err != nil {
t.Fatalf("dry-run with new group+alert should not error: %v", err)
}
creates := 0
for _, c := range changes {
if c.Action == "create" {
creates++
}
}
if creates != 3 {
t.Fatalf("expected 3 creates (alert+group+child), got %d: %+v", creates, changes)
}
}
func TestApplyExistingAlertReference(t *testing.T) {
s := newTestStore(t)
s.AddAlert(context.Background(), "Existing", "webhook", map[string]string{"url": "https://example.com"})