carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package hostenv
2
3import (
4 "context"
5 "errors"
6 "flag"
7 "fmt"
8 "io"
9 "os"
10 "path/filepath"
11)
12
13// carlos system host-env write|seed (region HA spec 2 §5 step 2).
14//
15// - write is the start-up script's step: as root, it assembles host.env
16// from the deployment's parameters and writes it 0600. A missing
17// required value stops it, and the file is left as it was.
18// - seed is the operator's: it writes a box's existing host.env into
19// the parameter layout, once per region.
20
21const hostEnvUsage = `usage: carlos system host-env write --deployment <name> --region <code> [--aws-region <region>]
22 carlos system host-env seed --deployment <name> --region <code> --kms-key <key> [--aws-region <region>] [--from <path>]
23
24 write build host.env from the deployment's parameters (root)
25 seed copy a box's host.env into the deployment's parameters
26`
27
28// hostEnvRoot is the root write's fixed path is under: "/" on a box.
29// Tests point it at a directory of their own; there is no flag.
30var hostEnvRoot = "/"
31
32// openParams is the parameter store: the AWS adapter's (awsssm.go).
33// kmsKey is the key seed encrypts with; write passes "".
34var openParams = openSSMParams
35
36// RunSystemHostEnv is `carlos system host-env`.
37func RunSystemHostEnv(args []string) {
38 if err := runHostEnv(context.Background(), args, os.Stdout, os.Geteuid(), openParams); err != nil {
39 fmt.Fprintln(os.Stderr, err)
40 os.Exit(1)
41 }
42}
43
44func runHostEnv(ctx context.Context, args []string, out io.Writer, euid int, open func(ctx context.Context, awsRegion, kmsKey string) (Params, error)) error {
45 if len(args) == 0 || (args[0] != "write" && args[0] != "seed") {
46 return usageError()
47 }
48 verb := args[0]
49 fs := flag.NewFlagSet("system host-env "+verb, flag.ContinueOnError)
50 fs.SetOutput(io.Discard)
51 deployment := fs.String("deployment", "", "")
52 region := fs.String("region", "", "")
53 awsRegion := fs.String("aws-region", "", "")
54 kmsKey := fs.String("kms-key", "", "")
55 from := fs.String("from", Path, "")
56 if err := fs.Parse(args[1:]); err != nil || fs.NArg() != 0 || *deployment == "" || *region == "" {
57 return usageError()
58 }
59 switch verb {
60 case "write":
61 if euid != 0 {
62 return errors.New("carlos system host-env write runs as root: it writes a root-owned file")
63 }
64 p, err := open(ctx, *awsRegion, "")
65 if err != nil {
66 return err
67 }
68 body, err := Fetch(ctx, p, *deployment, *region)
69 if err != nil {
70 return fmt.Errorf("host.env was not written: %w", err)
71 }
72 // The path is fixed, /etc/carlos/host.env, and the file is written
73 // only through directories proved to be root's alone, then
74 // replaced whole (writeHostEnv).
75 if err := writeHostEnv(hostEnvRoot, body); err != nil {
76 return fmt.Errorf("host.env was not written: %w", err)
77 }
78 fmt.Fprintln(out, wroteFileLine(filepath.Join(hostEnvRoot, Path), *region))
79 return nil
80 default:
81 if *kmsKey == "" {
82 return usageError()
83 }
84 body, err := os.ReadFile(*from)
85 if err != nil {
86 return err
87 }
88 env, err := Parse(body)
89 if err != nil {
90 return fmt.Errorf("%s: %w", *from, err)
91 }
92 p, err := open(ctx, *awsRegion, *kmsKey)
93 if err != nil {
94 return err
95 }
96 s, err := Seed(ctx, p, *deployment, *region, env)
97 if err != nil {
98 return fmt.Errorf("the parameters were not all written: %w", err)
99 }
100 fmt.Fprintln(out, seededLine(s))
101 return nil
102 }
103}
104
105func usageError() error { return errors.New(hostEnvUsage) }
106
107// wroteFileLine is what write says once host.env is in place.
108func wroteFileLine(path, region string) string {
109 return fmt.Sprintf("wrote %s from the parameters for %s", path, region)
110}
111
112// seededLine is what seed says once the parameters are written.
113func seededLine(s Seeded) string {
114 return fmt.Sprintf("wrote %d parameters under %s and %d under %s", s.Regional, s.RegionPrefix, s.Shared, s.SharedPrefix)
115}
116