Pull-based GitOps-style Docker Compose deployer: polls a (private) Git repo, detects changed stacks and reconciles only the affected
1package stacks
2
3import (
4 "fmt"
5 "log"
6 "os"
7 "os/exec"
8 "path/filepath"
9 "sync"
10)
11
12func DeployStacks(repoPath string, stacks []string, concurrency int) {
13 semaphore := make(chan struct{}, concurrency)
14 var wg sync.WaitGroup
15
16 for _, stack := range stacks {
17 wg.Add(1)
18 go func(stack string) {
19 defer wg.Done()
20
21 semaphore <- struct{}{}
22 defer func() { <-semaphore }()
23
24 composePath := filepath.Join(repoPath, "stacks", stack, "compose.yml")
25 if _, err := os.Stat(composePath); os.IsNotExist(err) {
26 composePath = filepath.Join(repoPath, "stacks", stack, "compose.yaml")
27 }
28
29 fmt.Printf("Deploying stack: %s\n", stack)
30 if err := deployStack(composePath); err != nil {
31 log.Printf("Failed to deploy stack %s: %v", stack, err)
32 return
33 }
34 fmt.Printf("Successfully deployed stack: %s\n", stack)
35 }(stack)
36 }
37 wg.Wait()
38}
39
40func deployStack(composePath string) error {
41 if _, err := os.Stat(composePath); os.IsNotExist(err) {
42 return fmt.Errorf("compose file does not exist: %s", composePath)
43 }
44
45 composeDir := filepath.Dir(composePath)
46
47 cmd := exec.Command("docker", "compose", "-f", composePath, "up", "-d")
48 cmd.Dir = composeDir
49 output, err := cmd.CombinedOutput()
50 if err != nil {
51 return fmt.Errorf("docker compose up failed: %s, %w", string(output), err)
52 }
53
54 return nil
55}