blob: 448f22193f8e3be40cc4573dd1c986590f5ca47e [file] [log] [blame]
gioe72b54f2024-04-22 10:44:41 +04001package welcome
2
3import (
4 "bytes"
5 "encoding/json"
giof6ad2982024-08-23 17:42:49 +04006 "fmt"
gioe72b54f2024-04-22 10:44:41 +04007 "io"
8 "io/fs"
9 "log"
10 "net"
11 "net/http"
12 "strings"
13 "sync"
14 "testing"
giod9c398e2024-06-06 13:33:03 +040015 "time"
gioe72b54f2024-04-22 10:44:41 +040016
Davit Tabidzea5ea5092024-08-01 15:28:09 +040017 "golang.org/x/crypto/ssh"
18
gio7fbd4ad2024-08-27 10:06:39 +040019 "cuelang.org/go/cue/errors"
gioe72b54f2024-04-22 10:44:41 +040020 "github.com/go-git/go-billy/v5"
21 "github.com/go-git/go-billy/v5/memfs"
22 "github.com/go-git/go-billy/v5/util"
gioe72b54f2024-04-22 10:44:41 +040023
24 "github.com/giolekva/pcloud/core/installer"
25 "github.com/giolekva/pcloud/core/installer/soft"
giod9c398e2024-06-06 13:33:03 +040026 "github.com/giolekva/pcloud/core/installer/tasks"
gioe72b54f2024-04-22 10:44:41 +040027)
28
29type fakeNSCreator struct {
30 t *testing.T
31}
32
33func (f fakeNSCreator) Create(name string) error {
34 f.t.Logf("Create namespace: %s", name)
35 return nil
36}
37
giof8843412024-05-22 16:38:05 +040038type fakeJobCreator struct {
39 t *testing.T
40}
41
42func (f fakeJobCreator) Create(name, namespace string, image string, cmd []string) error {
43 f.t.Logf("Create job: %s/%s %s \"%s\"", namespace, name, image, strings.Join(cmd, " "))
44 return nil
45}
46
47type fakeHelmFetcher struct {
48 t *testing.T
49}
50
51func (f fakeHelmFetcher) Pull(chart installer.HelmChartGitRepo, rfs soft.RepoFS, root string) error {
52 f.t.Logf("Helm pull: %+v", chart)
53 return nil
54}
55
gioe72b54f2024-04-22 10:44:41 +040056type fakeZoneStatusFetcher struct {
57 t *testing.T
58}
59
60func (f fakeZoneStatusFetcher) Fetch(addr string) (string, error) {
61 f.t.Logf("Fetching status: %s", addr)
62 return addr, nil
63}
64
65type mockRepoIO struct {
66 soft.RepoFS
67 addr string
68 t *testing.T
69 l sync.Locker
70}
71
72func (r mockRepoIO) FullAddress() string {
73 return r.addr
74}
75
76func (r mockRepoIO) Pull() error {
77 r.t.Logf("Pull: %s", r.addr)
78 return nil
79}
80
giob4a3a192024-08-19 09:55:47 +040081func (r mockRepoIO) CommitAndPush(message string, opts ...soft.PushOption) (string, error) {
gioe72b54f2024-04-22 10:44:41 +040082 r.t.Logf("Commit and push: %s", message)
giob4a3a192024-08-19 09:55:47 +040083 return "", nil
gioe72b54f2024-04-22 10:44:41 +040084}
85
giob4a3a192024-08-19 09:55:47 +040086func (r mockRepoIO) Do(op soft.DoFn, _ ...soft.DoOption) (string, error) {
gioe72b54f2024-04-22 10:44:41 +040087 r.l.Lock()
88 defer r.l.Unlock()
89 msg, err := op(r)
90 if err != nil {
giob4a3a192024-08-19 09:55:47 +040091 return "", err
gioe72b54f2024-04-22 10:44:41 +040092 }
93 return r.CommitAndPush(msg)
94}
95
96type fakeSoftServeClient struct {
97 t *testing.T
98 envFS billy.Filesystem
99}
100
101func (f fakeSoftServeClient) Address() string {
102 return ""
103}
104
105func (f fakeSoftServeClient) Signer() ssh.Signer {
106 return nil
107}
108
109func (f fakeSoftServeClient) GetPublicKeys() ([]string, error) {
110 return []string{}, nil
111}
112
gio33059762024-07-05 13:19:07 +0400113func (f fakeSoftServeClient) RepoExists(name string) (bool, error) {
114 return false, nil
115}
116
gioe72b54f2024-04-22 10:44:41 +0400117func (f fakeSoftServeClient) GetRepo(name string) (soft.RepoIO, error) {
118 var l sync.Mutex
119 return mockRepoIO{soft.NewBillyRepoFS(f.envFS), "foo.bar", f.t, &l}, nil
120}
121
gio7fbd4ad2024-08-27 10:06:39 +0400122func (f fakeSoftServeClient) GetRepoBranch(name, branch string) (soft.RepoIO, error) {
123 return f.GetRepo(name)
124}
125
giocafd4e62024-07-31 10:53:40 +0400126func (f fakeSoftServeClient) GetAllRepos() ([]string, error) {
127 return []string{}, nil
128}
129
gioe72b54f2024-04-22 10:44:41 +0400130func (f fakeSoftServeClient) GetRepoAddress(name string) string {
131 return ""
132}
133
134func (f fakeSoftServeClient) AddRepository(name string) error {
135 return nil
136}
137
gio33059762024-07-05 13:19:07 +0400138func (f fakeSoftServeClient) UserExists(name string) (bool, error) {
139 return false, nil
140}
141func (f fakeSoftServeClient) FindUser(pubKey string) (string, error) {
142 return "", nil
143}
144
Davit Tabidzea5ea5092024-08-01 15:28:09 +0400145func (f fakeSoftServeClient) GetAllUsers() ([]string, error) {
146 return []string{}, nil
147}
148
gioe72b54f2024-04-22 10:44:41 +0400149func (f fakeSoftServeClient) AddUser(name, pubKey string) error {
150 return nil
151}
152
Davit Tabidzea5ea5092024-08-01 15:28:09 +0400153func (f fakeSoftServeClient) RemoveUser(name string) error {
154 return nil
155}
156
gioe72b54f2024-04-22 10:44:41 +0400157func (f fakeSoftServeClient) AddPublicKey(user string, pubKey string) error {
158 return nil
159}
160
Davit Tabidzea5ea5092024-08-01 15:28:09 +0400161func (f fakeSoftServeClient) GetUserPublicKeys(username string) ([]string, error) {
162 return []string{}, nil
163}
164
gioe72b54f2024-04-22 10:44:41 +0400165func (f fakeSoftServeClient) RemovePublicKey(user string, pubKey string) error {
166 return nil
167}
168
169func (f fakeSoftServeClient) MakeUserAdmin(name string) error {
170 return nil
171}
172
173func (f fakeSoftServeClient) AddReadWriteCollaborator(repo, user string) error {
174 return nil
175}
176
177func (f fakeSoftServeClient) AddReadOnlyCollaborator(repo, user string) error {
178 return nil
179}
180
gio0eaf2712024-04-14 13:08:46 +0400181func (f fakeSoftServeClient) AddWebhook(repo, url string, opts ...string) error {
182 return nil
183}
184
gio7fbd4ad2024-08-27 10:06:39 +0400185func (f fakeSoftServeClient) DisableAnonAccess() error {
186 return nil
187}
188
189func (f fakeSoftServeClient) DisableKeyless() error {
190 return nil
191}
192
gio5887caa2024-10-03 15:07:23 +0400193func (f fakeSoftServeClient) DeleteRepoBranch(_, _ string) error {
194 return nil
195}
196
197func (f fakeSoftServeClient) DeleteRepo(_ string) error {
198 return nil
199}
200
gioe72b54f2024-04-22 10:44:41 +0400201type fakeClientGetter struct {
202 t *testing.T
203 envFS billy.Filesystem
204}
205
206func (f fakeClientGetter) Get(addr string, clientPrivateKey []byte, log *log.Logger) (soft.Client, error) {
207 return fakeSoftServeClient{f.t, f.envFS}, nil
208}
209
210const infraConfig = `
211infraAdminPublicKey: Zm9vYmFyCg==
212namespacePrefix: infra-
213pcloudEnvName: infra
214publicIP:
215- 1.1.1.1
216- 2.2.2.2
217`
218
219const envCidrs = ``
220
221type fixedNameGenerator struct{}
222
223func (f fixedNameGenerator) Generate() (string, error) {
224 return "test", nil
225}
226
227type fakeHttpClient struct {
228 t *testing.T
229 counts map[string]int
230}
231
232func (f fakeHttpClient) Get(addr string) (*http.Response, error) {
233 f.t.Logf("HTTP GET: %s", addr)
234 cnt, ok := f.counts[addr]
235 if !ok {
236 cnt = 0
237 }
238 f.counts[addr] = cnt + 1
239 return &http.Response{
240 Status: "200 OK",
241 StatusCode: http.StatusOK,
242 Proto: "HTTP/1.0",
243 ProtoMajor: 1,
244 ProtoMinor: 0,
245 Body: io.NopCloser(strings.NewReader("ok")),
246 }, nil
247}
248
249type fakeDnsClient struct {
250 t *testing.T
251 counts map[string]int
252}
253
254func (f fakeDnsClient) Lookup(host string) ([]net.IP, error) {
255 f.t.Logf("HTTP GET: %s", host)
256 return []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("2.2.2.2")}, nil
257}
258
giod9c398e2024-06-06 13:33:03 +0400259type onDoneTaskMap struct {
260 m tasks.TaskManager
261 onDone tasks.TaskDoneListener
262}
263
264func (m *onDoneTaskMap) Add(name string, task tasks.Task) error {
265 if err := m.m.Add(name, task); err != nil {
266 return err
267 } else {
giof6ad2982024-08-23 17:42:49 +0400268 task.OnDone(func(err error) {
269 if err == nil {
270 m.onDone(nil)
271 } else {
272 m.onDone(fmt.Errorf("%s: %s", name, err))
273 }
274 })
giod9c398e2024-06-06 13:33:03 +0400275 return nil
276 }
277}
278
279func (m *onDoneTaskMap) Get(name string) (tasks.Task, error) {
280 return m.m.Get(name)
281}
282
gioe72b54f2024-04-22 10:44:41 +0400283func TestCreateNewEnv(t *testing.T) {
284 apps := installer.NewInMemoryAppRepository(installer.CreateAllApps())
285 infraFS := memfs.New()
286 envFS := memfs.New()
287 nsCreator := fakeNSCreator{t}
giof8843412024-05-22 16:38:05 +0400288 jc := fakeJobCreator{t}
289 hf := fakeHelmFetcher{t}
290 lg := installer.GitRepositoryLocalChartGenerator{"foo", "bar"}
gioc76baed2024-08-19 22:04:57 +0400291 infraRepo := soft.NewMockRepoIO(soft.NewBillyRepoFS(infraFS), "foo.bar", t)
giof8843412024-05-22 16:38:05 +0400292 infraMgr, err := installer.NewInfraAppManager(infraRepo, nsCreator, hf, lg)
gioe72b54f2024-04-22 10:44:41 +0400293 if err != nil {
294 t.Fatal(err)
295 }
296 if err := util.WriteFile(infraFS, "config.yaml", []byte(infraConfig), fs.ModePerm); err != nil {
297 t.Fatal(err)
298 }
299 if err := util.WriteFile(infraFS, "env-cidrs.yaml", []byte(envCidrs), fs.ModePerm); err != nil {
300 t.Fatal(err)
301 }
302 {
303 app, err := installer.FindInfraApp(apps, "dns-gateway")
304 if err != nil {
305 t.Fatal(err)
306 }
gio778577f2024-04-29 09:44:38 +0400307 if _, err := infraMgr.Install(app, "/infrastructure/dns-gateway", "dns-gateway", map[string]any{
gioe72b54f2024-04-22 10:44:41 +0400308 "servers": []installer.EnvDNS{},
309 }); err != nil {
gio7fbd4ad2024-08-27 10:06:39 +0400310 for _, e := range errors.Errors(err) {
311 t.Log(e)
312 }
gioe72b54f2024-04-22 10:44:41 +0400313 t.Fatal(err)
314 }
315 }
316 cg := fakeClientGetter{t, envFS}
317 httpClient := fakeHttpClient{t, make(map[string]int)}
318 dnsClient := fakeDnsClient{t, make(map[string]int)}
giod9c398e2024-06-06 13:33:03 +0400319 var done sync.WaitGroup
320 done.Add(1)
321 var taskErr error
322 tm := &onDoneTaskMap{
323 tasks.NewTaskMap(),
324 func(err error) {
325 taskErr = err
326 done.Done()
327 },
328 }
gioe72b54f2024-04-22 10:44:41 +0400329 s := NewEnvServer(
330 8181,
331 fakeSoftServeClient{t, envFS},
332 infraRepo,
333 cg,
334 nsCreator,
giof8843412024-05-22 16:38:05 +0400335 jc,
336 hf,
gioe72b54f2024-04-22 10:44:41 +0400337 fakeZoneStatusFetcher{t},
338 fixedNameGenerator{},
339 httpClient,
340 dnsClient,
giod9c398e2024-06-06 13:33:03 +0400341 tm,
gioe72b54f2024-04-22 10:44:41 +0400342 )
343 go s.Start()
giod9c398e2024-06-06 13:33:03 +0400344 time.Sleep(1 * time.Second) // Let server start
gioe72b54f2024-04-22 10:44:41 +0400345 req := createEnvReq{
gio7841f4f2024-07-26 19:53:49 +0400346 Name: "test",
347 ContactEmail: "test@test.t",
348 Domain: "test.t",
349 PrivateNetworkSubdomain: "p",
350 AdminPublicKey: "test",
351 SecretToken: "test",
gioe72b54f2024-04-22 10:44:41 +0400352 }
353 var buf bytes.Buffer
354 if err := json.NewEncoder(&buf).Encode(req); err != nil {
355 t.Fatal(err)
356 }
357 resp, err := http.Post("http://localhost:8181/", "application/json", &buf)
gioe72b54f2024-04-22 10:44:41 +0400358 if err != nil {
359 t.Fatal(err)
360 }
361 if resp.StatusCode != http.StatusOK {
362 var buf bytes.Buffer
363 io.Copy(&buf, resp.Body)
364 t.Fatal(buf.String())
365 }
366 done.Wait()
367 http.Get("http://localhost:8181/env/test")
368 debugFS(infraFS, t, "/infrastructure/dns-gateway/resources/coredns.yaml")
369 debugFS(envFS, t)
370 if taskErr != nil {
371 t.Fatal(taskErr)
372 }
373 expected := []string{
giob79db3a2024-08-01 14:20:42 +0400374 "https://apps.p.test.t",
gioe72b54f2024-04-22 10:44:41 +0400375 "https://accounts-ui.test.t",
376 "https://welcome.test.t",
377 "https://memberships.p.test.t",
gio09a3e5b2024-04-26 14:11:06 +0400378 "https://launcher.test.t",
gioe72b54f2024-04-22 10:44:41 +0400379 "https://headscale.test.t/apple",
380 }
381 for _, e := range expected {
382 if cnt, ok := httpClient.counts[e]; !ok || cnt != 1 {
383 t.Fatal(httpClient.counts)
384 }
385 }
giob79db3a2024-08-01 14:20:42 +0400386 if len(httpClient.counts) != 6 {
gioe72b54f2024-04-22 10:44:41 +0400387 t.Fatal(httpClient.counts)
388 }
389}
390
391func debugFS(bfs billy.Filesystem, t *testing.T, files ...string) {
392 f := map[string]struct{}{}
393 for _, i := range files {
394 f[i] = struct{}{}
395 }
396 t.Log("----- START ------")
397 err := util.Walk(bfs, "/", func(path string, info fs.FileInfo, err error) error {
gioe72b54f2024-04-22 10:44:41 +0400398 if _, ok := f[path]; ok && !info.IsDir() {
399 contents, err := util.ReadFile(bfs, path)
400 if err != nil {
401 return err
402 }
403 t.Log(string(contents))
404 }
405 return nil
406 })
407 if err != nil {
408 t.Fatal(err)
409 }
410 t.Log("----- END ------")
411}