-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.go
164 lines (134 loc) · 4.11 KB
/
main.go
1
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
package main
import (
"context"
"fmt"
"log/slog"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"syscall"
)
var logLevel = new(slog.LevelVar)
func main() {
ctx := context.Background()
logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: logLevel}))
slog.SetDefault(logger)
if v, err := strconv.ParseBool(os.Getenv("DEBUG_BOOTSTRAP")); err == nil && v {
logLevel.Set(slog.LevelDebug)
}
cfg := NewFromEnv()
logger = logger.With(
slog.String("env", cfg.Environment),
slog.String("service", cfg.Service),
slog.String("product", cfg.Product),
slog.String("region", cfg.Region),
slog.String("program", cfg.Program),
)
slog.SetDefault(logger)
if len(os.Args) < 2 {
logger.ErrorContext(ctx, "Missing command")
os.Exit(1)
}
env, err := loadEnvVars(ctx, cfg, logger)
if err != nil {
logger.ErrorContext(ctx, "Could not load environment variables", "error", err)
os.Exit(1)
}
pwd, err := os.Getwd()
if err != nil {
logger.WarnContext(ctx, "Cannot determine PWD", "error", err)
}
env.Add("PWD", pwd)
env.Add("AWS_REGION", cfg.Region)
env.Add("SERVICE_ENV", cfg.Environment)
env.Add("PROCESSOR_COUNT", strconv.Itoa(runtime.NumCPU()))
if err := validate(ctx, cfg, env, logger); err != nil {
logger.ErrorContext(ctx, "Missing dependencies", "error", err)
os.Exit(4)
}
cmd := os.Args[1]
args := os.Args[2:]
if cmd == "yarn" || cmd == "npm" {
logger.WarnContext(ctx, cmd+" is not recommended. You might see unexpected behavior. Use node instead.")
}
logger.DebugContext(ctx, "Running command", "command", cmd, "args", args)
if err := run(cmd, args, env.Environ()); err != nil {
logger.ErrorContext(ctx, "Could not run command", "error", err)
os.Exit(1)
}
}
func loadEnvVars(ctx context.Context, cfg *Config, l *slog.Logger) (*EnvMap, error) {
env := NewEnvMap()
if addr := os.Getenv("CONSUL_ADDR"); addr != "" {
c, err := loadConsul(ctx, addr, cfg, l)
if err != nil {
return env, fmt.Errorf("could not load values from Consul: %w", err)
}
env.Merge(c)
} else {
l.WarnContext(ctx, "Not loading values from Consul. CONSUL_ADDR is not set")
}
if addr := os.Getenv("VAULT_ADDR"); addr != "" {
v, err := loadVault(ctx, addr, cfg, l)
if err != nil {
return env, fmt.Errorf("could not load values from Vault: %w", err)
}
env.Merge(v)
} else {
l.WarnContext(ctx, "Not loading values from Vault. VAULT_ADDR is not set")
}
return env, nil
}
func loadConsul(ctx context.Context, addr string, c *Config, l *slog.Logger) (Dict, error) {
l.DebugContext(ctx, "Loading values from Consul")
client, err := NewConsul(addr)
if err != nil {
return nil, serror(fmt.Errorf("Could not connect to Consul: %w", err), "addr", addr)
}
paths := c.ConsulPaths()
if p := os.Getenv("CONSUL_PATHS"); p != "" {
paths = append(paths, strings.Split(p, ",")...)
}
return loadValues(ctx, client, l, paths)
}
func loadVault(ctx context.Context, addr string, c *Config, l *slog.Logger) (Dict, error) {
l.DebugContext(ctx, "Loading values from Vault")
client, err := NewVault(addr, c.Region)
if err != nil {
return nil, serror(fmt.Errorf("Could not connect to Vault: %w", err), "addr", addr)
}
role := os.Getenv("VAULT_ROLE")
if role == "" {
role = c.Service
}
token := os.Getenv("VAULT_TOKEN")
auth, err := client.Authenticate(ctx, token, role)
if err != nil {
return nil, fmt.Errorf("Could not authenticate Vault: %w", err)
}
if auth == "" {
l.WarnContext(ctx, "Not loading values from Vault. Unable to authenticate Vault")
return make(Dict), nil
}
paths := c.VaultPaths()
if p := os.Getenv("VAULT_PATHS"); p != "" {
paths = append(paths, strings.Split(p, ",")...)
}
values, err := loadValues(ctx, client, l, paths)
values["VAULT_TOKEN"] = auth
return values, err
}
func run(name string, args, env []string) error {
bin, err := exec.LookPath(name)
if err != nil {
return fmt.Errorf("could not find %s: %w", name, err)
}
env = append(os.Environ(), env...)
args = append([]string{name}, args...)
if err := syscall.Exec(bin, args, env); err != nil {
return fmt.Errorf("could not execute %s %s: %w", name, strings.Join(args, " "), err)
}
return nil
}