diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 69f457d..caf29db 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 ``` diff --git a/README.md b/README.md index 99fa857..aaadcc4 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/deploy/docker-compose.cluster.yml b/deploy/docker-compose.cluster.yml index 386d5af..64ea27c 100644 --- a/deploy/docker-compose.cluster.yml +++ b/deploy/docker-compose.cluster.yml @@ -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) diff --git a/deploy/docker-compose.dev.yml b/deploy/docker-compose.dev.yml index b7b7194..befccbf 100644 --- a/deploy/docker-compose.dev.yml +++ b/deploy/docker-compose.dev.yml @@ -1,8 +1,8 @@ services: # The Application app: - build: - context: . + build: + context: .. dockerfile: Dockerfile container_name: uptop-dev ports: diff --git a/deploy/docker-compose.probe.yml b/deploy/docker-compose.probe.yml index cf4b2e9..28e4ecd 100644 --- a/deploy/docker-compose.probe.yml +++ b/deploy/docker-compose.probe.yml @@ -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 diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index b1ecf52..8131c93 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -1,8 +1,6 @@ services: app: - build: - context: . - dockerfile: Dockerfile + image: lerkolabs/uptop:latest container_name: uptop restart: unless-stopped read_only: true diff --git a/internal/config/apply.go b/internal/config/apply.go index e66247b..d47d112 100644 --- a/internal/config/apply.go +++ b/internal/config/apply.go @@ -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 diff --git a/internal/config/apply_test.go b/internal/config/apply_test.go index 635adc2..6e27b2b 100644 --- a/internal/config/apply_test.go +++ b/internal/config/apply_test.go @@ -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"})