-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathmain.go
More file actions
174 lines (145 loc) · 4.17 KB
/
Copy pathmain.go
File metadata and controls
174 lines (145 loc) · 4.17 KB
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
165
166
167
168
169
170
171
172
173
174
package main
import (
"context"
"errors"
"fmt"
"log"
"log/slog"
"os"
"os/signal"
"slices"
"strings"
"gopkg.in/yaml.v3"
)
func main() {
ctx := context.Background()
ctx, stop := signal.NotifyContext(ctx, os.Interrupt)
defer stop()
defer context.AfterFunc(ctx, stop)()
if err := run(ctx); err != nil {
log.Println(err)
os.Exit(1)
}
}
type workspace struct {
IDHash string `yaml:"idHash"`
Users []string `yaml:"users"`
}
func run(ctx context.Context) error {
var want struct {
Workspaces []workspace `yaml:"workspaces"`
}
err := yaml.NewDecoder(os.Stdin).Decode(&want)
if err != nil {
return fmt.Errorf("failed parsing workspace specification: %w", err)
}
harborURL := os.Getenv("SGS_HARBOR_URL")
if harborURL == "" {
return errors.New("SGS_HARBOR_URL is not set")
}
harborUsername := os.Getenv("SGS_HARBOR_USERNAME")
if harborUsername == "" {
return errors.New("SGS_HARBOR_USERNAME is not set")
}
harborPassword := os.Getenv("SGS_HARBOR_PASSWORD")
if harborPassword == "" {
return errors.New("SGS_HARBOR_PASSWORD is not set")
}
hapi, err := loadHarbor(harborURL, harborUsername, harborPassword)
if err != nil {
return fmt.Errorf("failed loading harbor client: %w", err)
}
return runSync(ctx, hapi, want.Workspaces)
}
func runSync(ctx context.Context, hapi harborIface, want []workspace) error {
var outErr error
projects, err := hapi.listProjects(ctx)
if err != nil {
return fmt.Errorf("failed listing projects: %w", err)
}
for _, p := range projects {
if !strings.HasPrefix(p, "ws-") {
// ignore non-workspace projects
continue
}
ind := slices.IndexFunc(want, func(w workspace) bool {
return fmt.Sprintf("ws-%s", w.IDHash) == p
})
if ind == -1 {
// not found, delete project
slog.InfoContext(ctx, fmt.Sprintf("deleting project %q", p))
if err := hapi.deleteProject(ctx, p); err != nil {
err = fmt.Errorf("failed deleting project %q: %w", p, err)
slog.WarnContext(ctx, err.Error())
outErr = errors.Join(outErr, err)
}
continue
}
slog.InfoContext(ctx, fmt.Sprintf("syncing workspace %q", p))
if err := syncWorkspace(ctx, hapi, want[ind]); err != nil {
err = fmt.Errorf("failed syncing workspace %q: %w", p, err)
slog.WarnContext(ctx, err.Error())
outErr = errors.Join(outErr, err)
}
// remove from reference slice
want = slices.Delete(want, ind, ind+1)
}
// create remaining workspaces
for _, w := range want {
slog.InfoContext(ctx, fmt.Sprintf("creating workspace %q", w.IDHash))
if err := createWorkspace(ctx, hapi, w); err != nil {
err = fmt.Errorf("failed creating workspace %q: %w", w.IDHash, err)
slog.WarnContext(ctx, err.Error())
outErr = errors.Join(outErr, err)
}
}
return outErr
}
func syncWorkspace(ctx context.Context, hapi harborIface, w workspace) error {
project := fmt.Sprintf("ws-%s", w.IDHash)
members, err := hapi.listMembers(ctx, project)
if err != nil {
return fmt.Errorf("failed listing members: %w", err)
}
for _, m := range members {
if m.name == "admin" {
continue
}
ind := slices.Index(w.Users, m.name)
if ind == -1 {
// not found, delete member
slog.InfoContext(ctx, fmt.Sprintf("deleting user %q from project %q", m.name, project))
if err := hapi.deleteMember(ctx, project, m.id); err != nil {
return err
}
continue
}
w.Users = slices.Delete(w.Users, ind, ind+1)
}
for _, u := range w.Users {
slog.InfoContext(ctx, fmt.Sprintf("adding user %q to project %q", u, project))
if err := hapi.createMember(ctx, project, u); err != nil {
return err
}
}
return nil
}
func createWorkspace(ctx context.Context, hapi harborIface, w workspace) error {
project := fmt.Sprintf("ws-%s", w.IDHash)
if err := hapi.createProject(ctx, project); err != nil {
return err
}
for _, u := range w.Users {
slog.InfoContext(ctx, fmt.Sprintf("adding user %q to project %q", u, project))
if err := hapi.createMember(ctx, project, u); err != nil {
return err
}
}
slog.InfoContext(ctx, fmt.Sprintf("creating robot for project %q", project))
// TODO: It would be nice to actually sync the robot if not exists on cluster
// but that's hard.
if err := hapi.createRobot(ctx, project); err != nil {
return err
}
return nil
}