carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package main
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "log"
8 "net/http"
9 "net/url"
10 "os"
11
12 "github.com/carlosframework/platform/internal/host"
13)
14
15// carlos system box role <box> <role> and carlos system group role
16// <region> <role> (region HA spec 2 §7, A11-1). A box's role says whether
17// placement may choose it: legacy and platform boxes take no new
18// instances and keep the ones they have. A group's role is every box it
19// has enrolled and every box it enrolls later. Operators only, and only on
20// a console with region HA on.
21
22// rolePost is the one console call a role change makes.
23type rolePost func(ctx context.Context, path string, in any) (int, []byte, error)
24
25// boxRoleLine is what the CLI says once a box has its role.
26func boxRoleLine(box, role string) string { return fmt.Sprintf("%s now has the %s role", box, role) }
27
28// groupRoleLine is what it says once a group has its role.
29func groupRoleLine(region, role string) string {
30 return fmt.Sprintf("the group in %s now has the %s role", region, role)
31}
32
33// setBoxRole asks the console to give box the role, and returns the line
34// to print.
35func setBoxRole(ctx context.Context, post rolePost, box, role string) (string, error) {
36 status, body, err := post(ctx, "/api/cli/system/boxes/"+url.PathEscape(box)+"/role", map[string]string{"role": role})
37 if err != nil {
38 return "", err
39 }
40 if status != http.StatusOK {
41 return "", apiErrorFromBody(status, body)
42 }
43 var out struct {
44 Box string `json:"box"`
45 Role string `json:"role"`
46 }
47 if err := json.Unmarshal(body, &out); err != nil {
48 return "", fmt.Errorf("unreadable response: %w", err)
49 }
50 return boxRoleLine(out.Box, out.Role), nil
51}
52
53// setGroupRole asks the console to give region's group the role, and
54// returns the lines to print: the group's, then one per box it set.
55func setGroupRole(ctx context.Context, post rolePost, region, role string) ([]string, error) {
56 status, body, err := post(ctx, "/api/cli/system/groups/"+url.PathEscape(region)+"/role", map[string]string{"role": role})
57 if err != nil {
58 return nil, err
59 }
60 if status != http.StatusOK {
61 return nil, apiErrorFromBody(status, body)
62 }
63 var out struct {
64 Region string `json:"region"`
65 Role string `json:"role"`
66 Boxes []string `json:"boxes"`
67 }
68 if err := json.Unmarshal(body, &out); err != nil {
69 return nil, fmt.Errorf("unreadable response: %w", err)
70 }
71 lines := []string{groupRoleLine(out.Region, out.Role)}
72 for _, b := range out.Boxes {
73 lines = append(lines, boxRoleLine(b, out.Role))
74 }
75 return lines, nil
76}
77
78// roleArgs reads `role <name> <role>` with an optional --console flag
79// after them, and exits with the usage on anything else.
80func roleArgs(rest []string) (name, role, console string) {
81 if len(rest) < 3 || rest[0] != "role" {
82 systemUsage()
83 os.Exit(2)
84 }
85 name, role = rest[1], rest[2]
86 tail := rest[3:]
87 switch {
88 case len(tail) == 0:
89 case len(tail) == 2 && (tail[0] == "--console" || tail[0] == "-console"):
90 console = tail[1]
91 default:
92 systemUsage()
93 os.Exit(2)
94 }
95 return name, role, console
96}
97
98func runSystemBoxVerb(rest []string) {
99 if len(rest) > 0 && rest[0] == "publish-record" {
100 // On the box itself, not through the console (internal/host
101 // boxpublish.go).
102 host.RunSystemBoxPublishRecord(rest[1:])
103 return
104 }
105 box, role, consoleFlag := roleArgs(rest)
106 sess := requireDeployAPISession(consoleFlag)
107 printUsing(stdout, sess)
108 line, err := setBoxRole(context.Background(), sess.postJSON, box, role)
109 if err != nil {
110 log.Fatalf("carlos system box role: %v", err)
111 }
112 fmt.Fprintln(stdout, line)
113}
114
115func runSystemGroupVerb(rest []string) {
116 region, role, consoleFlag := roleArgs(rest)
117 sess := requireDeployAPISession(consoleFlag)
118 printUsing(stdout, sess)
119 lines, err := setGroupRole(context.Background(), sess.postJSON, region, role)
120 if err != nil {
121 log.Fatalf("carlos system group role: %v", err)
122 }
123 for _, l := range lines {
124 fmt.Fprintln(stdout, l)
125 }
126}
127