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 ## Development
```sh ```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 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 ## Quick start
```bash ```bash
go run ./cmd/uptop UPTOP_ADMIN_KEY="$(cat ~/.ssh/id_ed25519.pub)" go run ./cmd/uptop
ssh -p 23234 localhost ssh -p 23234 localhost
``` ```
Want some data to look at first: Want some data to look at first:
```bash ```bash
go run ./cmd/uptop -demo UPTOP_ADMIN_KEY="$(cat ~/.ssh/id_ed25519.pub)" go run ./cmd/uptop -demo
``` ```
## Install ## Install
+2 -2
View File
@@ -3,7 +3,7 @@ services:
# LEADER NODE # LEADER NODE
# ------------------------- # -------------------------
leader: leader:
build: . image: lerkolabs/uptop:latest
container_name: uptop-leader container_name: uptop-leader
ports: ports:
- "23234:23234" # SSH - "23234:23234" # SSH
@@ -38,7 +38,7 @@ services:
# FOLLOWER NODE # FOLLOWER NODE
# ------------------------- # -------------------------
follower: follower:
build: . image: lerkolabs/uptop:latest
container_name: uptop-follower container_name: uptop-follower
ports: ports:
- "23233:23234" # SSH (Mapped to different host port) - "23233:23234" # SSH (Mapped to different host port)
+1 -1
View File
@@ -2,7 +2,7 @@ services:
# The Application # The Application
app: app:
build: build:
context: . context: ..
dockerfile: Dockerfile dockerfile: Dockerfile
container_name: uptop-dev container_name: uptop-dev
ports: ports:
+3 -3
View File
@@ -1,6 +1,6 @@
services: services:
leader: leader:
build: . image: lerkolabs/uptop:latest
environment: environment:
- UPTOP_CLUSTER_MODE=leader - UPTOP_CLUSTER_MODE=leader
- UPTOP_CLUSTER_SECRET=changeme # EXAMPLE ONLY — rotate before use - UPTOP_CLUSTER_SECRET=changeme # EXAMPLE ONLY — rotate before use
@@ -11,7 +11,7 @@ services:
- "23234:23234" - "23234:23234"
probe-us-east: probe-us-east:
build: . image: lerkolabs/uptop:latest
environment: environment:
- UPTOP_CLUSTER_MODE=probe - UPTOP_CLUSTER_MODE=probe
- UPTOP_NODE_ID=us-east-1 - UPTOP_NODE_ID=us-east-1
@@ -23,7 +23,7 @@ services:
- leader - leader
probe-eu-west: probe-eu-west:
build: . image: lerkolabs/uptop:latest
environment: environment:
- UPTOP_CLUSTER_MODE=probe - UPTOP_CLUSTER_MODE=probe
- UPTOP_NODE_ID=eu-west-1 - UPTOP_NODE_ID=eu-west-1
+1 -3
View File
@@ -1,8 +1,6 @@
services: services:
app: app:
build: image: lerkolabs/uptop:latest
context: .
dockerfile: Dockerfile
container_name: uptop container_name: uptop
restart: unless-stopped restart: unless-stopped
read_only: true 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 alertMap[ea.Name] = ea.ID
} }
nextPlaceholderID := -1
desiredAlertNames := make(map[string]bool, len(f.Alerts)) desiredAlertNames := make(map[string]bool, len(f.Alerts))
for _, a := range f.Alerts { for _, a := range f.Alerts {
desiredAlertNames[a.Name] = true 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) return changes, fmt.Errorf("create alert %q: %w", a.Name, err)
} }
alertMap[a.Name] = id alertMap[a.Name] = id
} else {
alertMap[a.Name] = nextPlaceholderID
nextPlaceholderID--
} }
} else { } else {
alertMap[a.Name] = existing.ID 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) return changes, fmt.Errorf("create group %q: %w", g.Name, err)
} }
groupMap[g.Name] = id groupMap[g.Name] = id
} else {
groupMap[g.Name] = nextPlaceholderID
nextPlaceholderID--
} }
} else { } else {
groupMap[g.Name] = existing.ID 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) { func TestApplyExistingAlertReference(t *testing.T) {
s := newTestStore(t) s := newTestStore(t)
s.AddAlert(context.Background(), "Existing", "webhook", map[string]string{"url": "https://example.com"}) s.AddAlert(context.Background(), "Existing", "webhook", map[string]string{"url": "https://example.com"})