service.go
115 lines1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package scaffold
import (
"cmp"
"fmt"
"log"
"strings"
"congo.gg/pkg/platform"
)
func startService(server *platform.Server, name string, spec *ServiceSpec, imageName string, serverVolumes []VolumeSpec) error {
log.Printf(" Starting %s...", name)
// Build volume name → mount path lookup for $volume-name expansion
volMounts := make(map[string]string, len(serverVolumes))
for _, v := range serverVolumes {
volMounts[v.Name] = v.Mount
}
expandRef := func(s string) string {
if !strings.HasPrefix(s, "$") {
return s
}
ref := s[1:]
rest := ""
if idx := strings.Index(ref, "/"); idx >= 0 {
rest = ref[idx:]
ref = ref[:idx]
}
if mount, ok := volMounts[ref]; ok {
return mount + rest
}
return s
}
svc := &platform.Service{
Name: name,
Image: cmp.Or(imageName, spec.Image),
Network: spec.Network,
Restart: "always",
Privileged: spec.Privileged,
}
if len(spec.Command) > 0 {
svc.Command = spec.Command
}
for _, p := range spec.Ports {
svc.Ports = append(svc.Ports, platform.Port{
Host: p.Host,
Container: p.Container,
Bind: p.Bind,
})
}
for _, v := range spec.Volumes {
svc.Volumes = append(svc.Volumes, platform.Mount{Source: expandRef(v.Source), Target: v.Target})
}
svc.Env = make(map[string]string)
for k, v := range spec.Env {
svc.Env[k] = expandRef(v)
}
for envName, filePath := range spec.EnvFiles {
if !SafePath(filePath) {
log.Printf(" Warning: skipping env_file %s — invalid path %q", envName, filePath)
continue
}
value, err := server.SSH("cat", filePath)
if err != nil {
log.Printf(" Warning: could not read %s from %s", envName, filePath)
continue
}
svc.Env[envName] = strings.TrimSpace(value)
}
svc.Logging = &platform.LogConfig{
Driver: "json-file",
Options: map[string]string{
"max-size": "50m",
"max-file": "3",
},
}
var healthCmd string
if spec.Healthcheck != "" {
healthCmd = spec.Healthcheck
} else if len(spec.Ports) > 0 {
healthCmd = fmt.Sprintf("curl -sf http://localhost:%d/health || exit 1", spec.Ports[0].Container)
}
if healthCmd != "" {
svc.Healthcheck = &platform.Healthcheck{
Cmd: healthCmd,
Interval: "10s",
Timeout: "5s",
Retries: 3,
StartPeriod: "30s",
}
}
if err := server.Start(svc); err != nil {
return err
}
if svc.Healthcheck != nil {
if err := server.WaitForHealthy(name, 60); err != nil {
log.Printf(" Health check failed, rolling back...")
server.Rollback(name)
return fmt.Errorf("health check failed: %w", err)
}
}
return nil
}