blob: ee80e4ef35164b4765f4f0151e0b29b038309007 [file] [log] [blame]
gioe72b54f2024-04-22 10:44:41 +04001package welcome
2
3import (
4 "bytes"
5 "encoding/json"
gioe72b54f2024-04-22 10:44:41 +04006 "io"
7 "io/fs"
8 "log"
9 "net"
10 "net/http"
11 "strings"
12 "sync"
13 "testing"
giod9c398e2024-06-06 13:33:03 +040014 "time"
gioe72b54f2024-04-22 10:44:41 +040015
Davit Tabidzea5ea5092024-08-01 15:28:09 +040016 "golang.org/x/crypto/ssh"
17
gio7fbd4ad2024-08-27 10:06:39 +040018 "cuelang.org/go/cue/errors"
gioe72b54f2024-04-22 10:44:41 +040019 "github.com/go-git/go-billy/v5"
20 "github.com/go-git/go-billy/v5/memfs"
21 "github.com/go-git/go-billy/v5/util"
gioe72b54f2024-04-22 10:44:41 +040022
23 "github.com/giolekva/pcloud/core/installer"
24 "github.com/giolekva/pcloud/core/installer/soft"
giod9c398e2024-06-06 13:33:03 +040025 "github.com/giolekva/pcloud/core/installer/tasks"
gioe72b54f2024-04-22 10:44:41 +040026)
27
28type fakeNSCreator struct {
29 t *testing.T
30}
31
32func (f fakeNSCreator) Create(name string) error {
33 f.t.Logf("Create namespace: %s", name)
34 return nil
35}
36
giof8843412024-05-22 16:38:05 +040037type fakeJobCreator struct {
38 t *testing.T
39}
40
41func (f fakeJobCreator) Create(name, namespace string, image string, cmd []string) error {
42 f.t.Logf("Create job: %s/%s %s \"%s\"", namespace, name, image, strings.Join(cmd, " "))
43 return nil
44}
45
46type fakeHelmFetcher struct {
47 t *testing.T
48}
49
50func (f fakeHelmFetcher) Pull(chart installer.HelmChartGitRepo, rfs soft.RepoFS, root string) error {
51 f.t.Logf("Helm pull: %+v", chart)
52 return nil
53}
54
gioe72b54f2024-04-22 10:44:41 +040055type fakeZoneStatusFetcher struct {
56 t *testing.T
57}
58
59func (f fakeZoneStatusFetcher) Fetch(addr string) (string, error) {
60 f.t.Logf("Fetching status: %s", addr)
61 return addr, nil
62}
63
64type mockRepoIO struct {
65 soft.RepoFS
66 addr string
67 t *testing.T
68 l sync.Locker
69}
70
71func (r mockRepoIO) FullAddress() string {
72 return r.addr
73}
74
75func (r mockRepoIO) Pull() error {
76 r.t.Logf("Pull: %s", r.addr)
77 return nil
78}
79
giob4a3a192024-08-19 09:55:47 +040080func (r mockRepoIO) CommitAndPush(message string, opts ...soft.PushOption) (string, error) {
gioe72b54f2024-04-22 10:44:41 +040081 r.t.Logf("Commit and push: %s", message)
giob4a3a192024-08-19 09:55:47 +040082 return "", nil
gioe72b54f2024-04-22 10:44:41 +040083}
84
giob4a3a192024-08-19 09:55:47 +040085func (r mockRepoIO) Do(op soft.DoFn, _ ...soft.DoOption) (string, error) {
gioe72b54f2024-04-22 10:44:41 +040086 r.l.Lock()
87 defer r.l.Unlock()
88 msg, err := op(r)
89 if err != nil {
giob4a3a192024-08-19 09:55:47 +040090 return "", err
gioe72b54f2024-04-22 10:44:41 +040091 }
92 return r.CommitAndPush(msg)
93}
94
95type fakeSoftServeClient struct {
96 t *testing.T
97 envFS billy.Filesystem
98}
99
100func (f fakeSoftServeClient) Address() string {
101 return ""
102}
103
104func (f fakeSoftServeClient) Signer() ssh.Signer {
105 return nil
106}
107
108func (f fakeSoftServeClient) GetPublicKeys() ([]string, error) {
109 return []string{}, nil
110}
111
gio33059762024-07-05 13:19:07 +0400112func (f fakeSoftServeClient) RepoExists(name string) (bool, error) {
113 return false, nil
114}
115
gioe72b54f2024-04-22 10:44:41 +0400116func (f fakeSoftServeClient) GetRepo(name string) (soft.RepoIO, error) {
117 var l sync.Mutex
118 return mockRepoIO{soft.NewBillyRepoFS(f.envFS), "foo.bar", f.t, &l}, nil
119}
120
gio7fbd4ad2024-08-27 10:06:39 +0400121func (f fakeSoftServeClient) GetRepoBranch(name, branch string) (soft.RepoIO, error) {
122 return f.GetRepo(name)
123}
124
giocafd4e62024-07-31 10:53:40 +0400125func (f fakeSoftServeClient) GetAllRepos() ([]string, error) {
126 return []string{}, nil
127}
128
gioe72b54f2024-04-22 10:44:41 +0400129func (f fakeSoftServeClient) GetRepoAddress(name string) string {
130 return ""
131}
132
133func (f fakeSoftServeClient) AddRepository(name string) error {
134 return nil
135}
136
gio33059762024-07-05 13:19:07 +0400137func (f fakeSoftServeClient) UserExists(name string) (bool, error) {
138 return false, nil
139}
140func (f fakeSoftServeClient) FindUser(pubKey string) (string, error) {
141 return "", nil
142}
143
Davit Tabidzea5ea5092024-08-01 15:28:09 +0400144func (f fakeSoftServeClient) GetAllUsers() ([]string, error) {
145 return []string{}, nil
146}
147
gioe72b54f2024-04-22 10:44:41 +0400148func (f fakeSoftServeClient) AddUser(name, pubKey string) error {
149 return nil
150}
151
Davit Tabidzea5ea5092024-08-01 15:28:09 +0400152func (f fakeSoftServeClient) RemoveUser(name string) error {
153 return nil
154}
155
gioe72b54f2024-04-22 10:44:41 +0400156func (f fakeSoftServeClient) AddPublicKey(user string, pubKey string) error {
157 return nil
158}
159
Davit Tabidzea5ea5092024-08-01 15:28:09 +0400160func (f fakeSoftServeClient) GetUserPublicKeys(username string) ([]string, error) {
161 return []string{}, nil
162}
163
gioe72b54f2024-04-22 10:44:41 +0400164func (f fakeSoftServeClient) RemovePublicKey(user string, pubKey string) error {
165 return nil
166}
167
168func (f fakeSoftServeClient) MakeUserAdmin(name string) error {
169 return nil
170}
171
172func (f fakeSoftServeClient) AddReadWriteCollaborator(repo, user string) error {
173 return nil
174}
175
176func (f fakeSoftServeClient) AddReadOnlyCollaborator(repo, user string) error {
177 return nil
178}
179
gio0eaf2712024-04-14 13:08:46 +0400180func (f fakeSoftServeClient) AddWebhook(repo, url string, opts ...string) error {
181 return nil
182}
183
gio7fbd4ad2024-08-27 10:06:39 +0400184func (f fakeSoftServeClient) DisableAnonAccess() error {
185 return nil
186}
187
188func (f fakeSoftServeClient) DisableKeyless() error {
189 return nil
190}
191
gioe72b54f2024-04-22 10:44:41 +0400192type fakeClientGetter struct {
193 t *testing.T
194 envFS billy.Filesystem
195}
196
197func (f fakeClientGetter) Get(addr string, clientPrivateKey []byte, log *log.Logger) (soft.Client, error) {
198 return fakeSoftServeClient{f.t, f.envFS}, nil
199}
200
201const infraConfig = `
202infraAdminPublicKey: Zm9vYmFyCg==
203namespacePrefix: infra-
204pcloudEnvName: infra
205publicIP:
206- 1.1.1.1
207- 2.2.2.2
208`
209
210const envCidrs = ``
211
212type fixedNameGenerator struct{}
213
214func (f fixedNameGenerator) Generate() (string, error) {
215 return "test", nil
216}
217
218type fakeHttpClient struct {
219 t *testing.T
220 counts map[string]int
221}
222
223func (f fakeHttpClient) Get(addr string) (*http.Response, error) {
224 f.t.Logf("HTTP GET: %s", addr)
225 cnt, ok := f.counts[addr]
226 if !ok {
227 cnt = 0
228 }
229 f.counts[addr] = cnt + 1
230 return &http.Response{
231 Status: "200 OK",
232 StatusCode: http.StatusOK,
233 Proto: "HTTP/1.0",
234 ProtoMajor: 1,
235 ProtoMinor: 0,
236 Body: io.NopCloser(strings.NewReader("ok")),
237 }, nil
238}
239
240type fakeDnsClient struct {
241 t *testing.T
242 counts map[string]int
243}
244
245func (f fakeDnsClient) Lookup(host string) ([]net.IP, error) {
246 f.t.Logf("HTTP GET: %s", host)
247 return []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("2.2.2.2")}, nil
248}
249
giod9c398e2024-06-06 13:33:03 +0400250type onDoneTaskMap struct {
251 m tasks.TaskManager
252 onDone tasks.TaskDoneListener
253}
254
255func (m *onDoneTaskMap) Add(name string, task tasks.Task) error {
256 if err := m.m.Add(name, task); err != nil {
257 return err
258 } else {
259 task.OnDone(m.onDone)
260 return nil
261 }
262}
263
264func (m *onDoneTaskMap) Get(name string) (tasks.Task, error) {
265 return m.m.Get(name)
266}
267
gioe72b54f2024-04-22 10:44:41 +0400268func TestCreateNewEnv(t *testing.T) {
269 apps := installer.NewInMemoryAppRepository(installer.CreateAllApps())
270 infraFS := memfs.New()
271 envFS := memfs.New()
272 nsCreator := fakeNSCreator{t}
giof8843412024-05-22 16:38:05 +0400273 jc := fakeJobCreator{t}
274 hf := fakeHelmFetcher{t}
275 lg := installer.GitRepositoryLocalChartGenerator{"foo", "bar"}
gioc76baed2024-08-19 22:04:57 +0400276 infraRepo := soft.NewMockRepoIO(soft.NewBillyRepoFS(infraFS), "foo.bar", t)
giof8843412024-05-22 16:38:05 +0400277 infraMgr, err := installer.NewInfraAppManager(infraRepo, nsCreator, hf, lg)
gioe72b54f2024-04-22 10:44:41 +0400278 if err != nil {
279 t.Fatal(err)
280 }
281 if err := util.WriteFile(infraFS, "config.yaml", []byte(infraConfig), fs.ModePerm); err != nil {
282 t.Fatal(err)
283 }
284 if err := util.WriteFile(infraFS, "env-cidrs.yaml", []byte(envCidrs), fs.ModePerm); err != nil {
285 t.Fatal(err)
286 }
287 {
288 app, err := installer.FindInfraApp(apps, "dns-gateway")
289 if err != nil {
290 t.Fatal(err)
291 }
gio778577f2024-04-29 09:44:38 +0400292 if _, err := infraMgr.Install(app, "/infrastructure/dns-gateway", "dns-gateway", map[string]any{
gioe72b54f2024-04-22 10:44:41 +0400293 "servers": []installer.EnvDNS{},
294 }); err != nil {
gio7fbd4ad2024-08-27 10:06:39 +0400295 for _, e := range errors.Errors(err) {
296 t.Log(e)
297 }
gioe72b54f2024-04-22 10:44:41 +0400298 t.Fatal(err)
299 }
300 }
301 cg := fakeClientGetter{t, envFS}
302 httpClient := fakeHttpClient{t, make(map[string]int)}
303 dnsClient := fakeDnsClient{t, make(map[string]int)}
giod9c398e2024-06-06 13:33:03 +0400304 var done sync.WaitGroup
305 done.Add(1)
306 var taskErr error
307 tm := &onDoneTaskMap{
308 tasks.NewTaskMap(),
309 func(err error) {
310 taskErr = err
311 done.Done()
312 },
313 }
gioe72b54f2024-04-22 10:44:41 +0400314 s := NewEnvServer(
315 8181,
316 fakeSoftServeClient{t, envFS},
317 infraRepo,
318 cg,
319 nsCreator,
giof8843412024-05-22 16:38:05 +0400320 jc,
321 hf,
gioe72b54f2024-04-22 10:44:41 +0400322 fakeZoneStatusFetcher{t},
323 fixedNameGenerator{},
324 httpClient,
325 dnsClient,
giod9c398e2024-06-06 13:33:03 +0400326 tm,
gioe72b54f2024-04-22 10:44:41 +0400327 )
328 go s.Start()
giod9c398e2024-06-06 13:33:03 +0400329 time.Sleep(1 * time.Second) // Let server start
gioe72b54f2024-04-22 10:44:41 +0400330 req := createEnvReq{
gio7841f4f2024-07-26 19:53:49 +0400331 Name: "test",
332 ContactEmail: "test@test.t",
333 Domain: "test.t",
334 PrivateNetworkSubdomain: "p",
335 AdminPublicKey: "test",
336 SecretToken: "test",
gioe72b54f2024-04-22 10:44:41 +0400337 }
338 var buf bytes.Buffer
339 if err := json.NewEncoder(&buf).Encode(req); err != nil {
340 t.Fatal(err)
341 }
342 resp, err := http.Post("http://localhost:8181/", "application/json", &buf)
gioe72b54f2024-04-22 10:44:41 +0400343 if err != nil {
344 t.Fatal(err)
345 }
346 if resp.StatusCode != http.StatusOK {
347 var buf bytes.Buffer
348 io.Copy(&buf, resp.Body)
349 t.Fatal(buf.String())
350 }
351 done.Wait()
352 http.Get("http://localhost:8181/env/test")
353 debugFS(infraFS, t, "/infrastructure/dns-gateway/resources/coredns.yaml")
354 debugFS(envFS, t)
355 if taskErr != nil {
356 t.Fatal(taskErr)
357 }
358 expected := []string{
giob79db3a2024-08-01 14:20:42 +0400359 "https://apps.p.test.t",
gioe72b54f2024-04-22 10:44:41 +0400360 "https://accounts-ui.test.t",
361 "https://welcome.test.t",
362 "https://memberships.p.test.t",
gio09a3e5b2024-04-26 14:11:06 +0400363 "https://launcher.test.t",
gioe72b54f2024-04-22 10:44:41 +0400364 "https://headscale.test.t/apple",
365 }
366 for _, e := range expected {
367 if cnt, ok := httpClient.counts[e]; !ok || cnt != 1 {
368 t.Fatal(httpClient.counts)
369 }
370 }
giob79db3a2024-08-01 14:20:42 +0400371 if len(httpClient.counts) != 6 {
gioe72b54f2024-04-22 10:44:41 +0400372 t.Fatal(httpClient.counts)
373 }
374}
375
376func debugFS(bfs billy.Filesystem, t *testing.T, files ...string) {
377 f := map[string]struct{}{}
378 for _, i := range files {
379 f[i] = struct{}{}
380 }
381 t.Log("----- START ------")
382 err := util.Walk(bfs, "/", func(path string, info fs.FileInfo, err error) error {
gioe72b54f2024-04-22 10:44:41 +0400383 if _, ok := f[path]; ok && !info.IsDir() {
384 contents, err := util.ReadFile(bfs, path)
385 if err != nil {
386 return err
387 }
388 t.Log(string(contents))
389 }
390 return nil
391 })
392 if err != nil {
393 t.Fatal(err)
394 }
395 t.Log("----- END ------")
396}