blob: e7e75a80f8bf893e74e862ddc206c1b8354ac824 [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
gioe72b54f2024-04-22 10:44:41 +040018 "github.com/go-git/go-billy/v5"
19 "github.com/go-git/go-billy/v5/memfs"
20 "github.com/go-git/go-billy/v5/util"
gioe72b54f2024-04-22 10:44:41 +040021
22 "github.com/giolekva/pcloud/core/installer"
23 "github.com/giolekva/pcloud/core/installer/soft"
giod9c398e2024-06-06 13:33:03 +040024 "github.com/giolekva/pcloud/core/installer/tasks"
gioe72b54f2024-04-22 10:44:41 +040025)
26
27type fakeNSCreator struct {
28 t *testing.T
29}
30
31func (f fakeNSCreator) Create(name string) error {
32 f.t.Logf("Create namespace: %s", name)
33 return nil
34}
35
giof8843412024-05-22 16:38:05 +040036type fakeJobCreator struct {
37 t *testing.T
38}
39
40func (f fakeJobCreator) Create(name, namespace string, image string, cmd []string) error {
41 f.t.Logf("Create job: %s/%s %s \"%s\"", namespace, name, image, strings.Join(cmd, " "))
42 return nil
43}
44
45type fakeHelmFetcher struct {
46 t *testing.T
47}
48
49func (f fakeHelmFetcher) Pull(chart installer.HelmChartGitRepo, rfs soft.RepoFS, root string) error {
50 f.t.Logf("Helm pull: %+v", chart)
51 return nil
52}
53
gioe72b54f2024-04-22 10:44:41 +040054type fakeZoneStatusFetcher struct {
55 t *testing.T
56}
57
58func (f fakeZoneStatusFetcher) Fetch(addr string) (string, error) {
59 f.t.Logf("Fetching status: %s", addr)
60 return addr, nil
61}
62
63type mockRepoIO struct {
64 soft.RepoFS
65 addr string
66 t *testing.T
67 l sync.Locker
68}
69
70func (r mockRepoIO) FullAddress() string {
71 return r.addr
72}
73
74func (r mockRepoIO) Pull() error {
75 r.t.Logf("Pull: %s", r.addr)
76 return nil
77}
78
giob4a3a192024-08-19 09:55:47 +040079func (r mockRepoIO) CommitAndPush(message string, opts ...soft.PushOption) (string, error) {
gioe72b54f2024-04-22 10:44:41 +040080 r.t.Logf("Commit and push: %s", message)
giob4a3a192024-08-19 09:55:47 +040081 return "", nil
gioe72b54f2024-04-22 10:44:41 +040082}
83
giob4a3a192024-08-19 09:55:47 +040084func (r mockRepoIO) Do(op soft.DoFn, _ ...soft.DoOption) (string, error) {
gioe72b54f2024-04-22 10:44:41 +040085 r.l.Lock()
86 defer r.l.Unlock()
87 msg, err := op(r)
88 if err != nil {
giob4a3a192024-08-19 09:55:47 +040089 return "", err
gioe72b54f2024-04-22 10:44:41 +040090 }
91 return r.CommitAndPush(msg)
92}
93
94type fakeSoftServeClient struct {
95 t *testing.T
96 envFS billy.Filesystem
97}
98
99func (f fakeSoftServeClient) Address() string {
100 return ""
101}
102
103func (f fakeSoftServeClient) Signer() ssh.Signer {
104 return nil
105}
106
107func (f fakeSoftServeClient) GetPublicKeys() ([]string, error) {
108 return []string{}, nil
109}
110
gio33059762024-07-05 13:19:07 +0400111func (f fakeSoftServeClient) RepoExists(name string) (bool, error) {
112 return false, nil
113}
114
gioe72b54f2024-04-22 10:44:41 +0400115func (f fakeSoftServeClient) GetRepo(name string) (soft.RepoIO, error) {
116 var l sync.Mutex
117 return mockRepoIO{soft.NewBillyRepoFS(f.envFS), "foo.bar", f.t, &l}, nil
118}
119
giocafd4e62024-07-31 10:53:40 +0400120func (f fakeSoftServeClient) GetAllRepos() ([]string, error) {
121 return []string{}, nil
122}
123
gioe72b54f2024-04-22 10:44:41 +0400124func (f fakeSoftServeClient) GetRepoAddress(name string) string {
125 return ""
126}
127
128func (f fakeSoftServeClient) AddRepository(name string) error {
129 return nil
130}
131
gio33059762024-07-05 13:19:07 +0400132func (f fakeSoftServeClient) UserExists(name string) (bool, error) {
133 return false, nil
134}
135func (f fakeSoftServeClient) FindUser(pubKey string) (string, error) {
136 return "", nil
137}
138
Davit Tabidzea5ea5092024-08-01 15:28:09 +0400139func (f fakeSoftServeClient) GetAllUsers() ([]string, error) {
140 return []string{}, nil
141}
142
gioe72b54f2024-04-22 10:44:41 +0400143func (f fakeSoftServeClient) AddUser(name, pubKey string) error {
144 return nil
145}
146
Davit Tabidzea5ea5092024-08-01 15:28:09 +0400147func (f fakeSoftServeClient) RemoveUser(name string) error {
148 return nil
149}
150
gioe72b54f2024-04-22 10:44:41 +0400151func (f fakeSoftServeClient) AddPublicKey(user string, pubKey string) error {
152 return nil
153}
154
Davit Tabidzea5ea5092024-08-01 15:28:09 +0400155func (f fakeSoftServeClient) GetUserPublicKeys(username string) ([]string, error) {
156 return []string{}, nil
157}
158
gioe72b54f2024-04-22 10:44:41 +0400159func (f fakeSoftServeClient) RemovePublicKey(user string, pubKey string) error {
160 return nil
161}
162
163func (f fakeSoftServeClient) MakeUserAdmin(name string) error {
164 return nil
165}
166
167func (f fakeSoftServeClient) AddReadWriteCollaborator(repo, user string) error {
168 return nil
169}
170
171func (f fakeSoftServeClient) AddReadOnlyCollaborator(repo, user string) error {
172 return nil
173}
174
gio0eaf2712024-04-14 13:08:46 +0400175func (f fakeSoftServeClient) AddWebhook(repo, url string, opts ...string) error {
176 return nil
177}
178
gioe72b54f2024-04-22 10:44:41 +0400179type fakeClientGetter struct {
180 t *testing.T
181 envFS billy.Filesystem
182}
183
184func (f fakeClientGetter) Get(addr string, clientPrivateKey []byte, log *log.Logger) (soft.Client, error) {
185 return fakeSoftServeClient{f.t, f.envFS}, nil
186}
187
188const infraConfig = `
189infraAdminPublicKey: Zm9vYmFyCg==
190namespacePrefix: infra-
191pcloudEnvName: infra
192publicIP:
193- 1.1.1.1
194- 2.2.2.2
195`
196
197const envCidrs = ``
198
199type fixedNameGenerator struct{}
200
201func (f fixedNameGenerator) Generate() (string, error) {
202 return "test", nil
203}
204
205type fakeHttpClient struct {
206 t *testing.T
207 counts map[string]int
208}
209
210func (f fakeHttpClient) Get(addr string) (*http.Response, error) {
211 f.t.Logf("HTTP GET: %s", addr)
212 cnt, ok := f.counts[addr]
213 if !ok {
214 cnt = 0
215 }
216 f.counts[addr] = cnt + 1
217 return &http.Response{
218 Status: "200 OK",
219 StatusCode: http.StatusOK,
220 Proto: "HTTP/1.0",
221 ProtoMajor: 1,
222 ProtoMinor: 0,
223 Body: io.NopCloser(strings.NewReader("ok")),
224 }, nil
225}
226
227type fakeDnsClient struct {
228 t *testing.T
229 counts map[string]int
230}
231
232func (f fakeDnsClient) Lookup(host string) ([]net.IP, error) {
233 f.t.Logf("HTTP GET: %s", host)
234 return []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("2.2.2.2")}, nil
235}
236
giod9c398e2024-06-06 13:33:03 +0400237type onDoneTaskMap struct {
238 m tasks.TaskManager
239 onDone tasks.TaskDoneListener
240}
241
242func (m *onDoneTaskMap) Add(name string, task tasks.Task) error {
243 if err := m.m.Add(name, task); err != nil {
244 return err
245 } else {
246 task.OnDone(m.onDone)
247 return nil
248 }
249}
250
251func (m *onDoneTaskMap) Get(name string) (tasks.Task, error) {
252 return m.m.Get(name)
253}
254
gioe72b54f2024-04-22 10:44:41 +0400255func TestCreateNewEnv(t *testing.T) {
256 apps := installer.NewInMemoryAppRepository(installer.CreateAllApps())
257 infraFS := memfs.New()
258 envFS := memfs.New()
259 nsCreator := fakeNSCreator{t}
giof8843412024-05-22 16:38:05 +0400260 jc := fakeJobCreator{t}
261 hf := fakeHelmFetcher{t}
262 lg := installer.GitRepositoryLocalChartGenerator{"foo", "bar"}
gioe72b54f2024-04-22 10:44:41 +0400263 infraRepo := mockRepoIO{soft.NewBillyRepoFS(infraFS), "foo.bar", t, &sync.Mutex{}}
giof8843412024-05-22 16:38:05 +0400264 infraMgr, err := installer.NewInfraAppManager(infraRepo, nsCreator, hf, lg)
gioe72b54f2024-04-22 10:44:41 +0400265 if err != nil {
266 t.Fatal(err)
267 }
268 if err := util.WriteFile(infraFS, "config.yaml", []byte(infraConfig), fs.ModePerm); err != nil {
269 t.Fatal(err)
270 }
271 if err := util.WriteFile(infraFS, "env-cidrs.yaml", []byte(envCidrs), fs.ModePerm); err != nil {
272 t.Fatal(err)
273 }
274 {
275 app, err := installer.FindInfraApp(apps, "dns-gateway")
276 if err != nil {
277 t.Fatal(err)
278 }
gio778577f2024-04-29 09:44:38 +0400279 if _, err := infraMgr.Install(app, "/infrastructure/dns-gateway", "dns-gateway", map[string]any{
gioe72b54f2024-04-22 10:44:41 +0400280 "servers": []installer.EnvDNS{},
281 }); err != nil {
282 t.Fatal(err)
283 }
284 }
285 cg := fakeClientGetter{t, envFS}
286 httpClient := fakeHttpClient{t, make(map[string]int)}
287 dnsClient := fakeDnsClient{t, make(map[string]int)}
giod9c398e2024-06-06 13:33:03 +0400288 var done sync.WaitGroup
289 done.Add(1)
290 var taskErr error
291 tm := &onDoneTaskMap{
292 tasks.NewTaskMap(),
293 func(err error) {
294 taskErr = err
295 done.Done()
296 },
297 }
gioe72b54f2024-04-22 10:44:41 +0400298 s := NewEnvServer(
299 8181,
300 fakeSoftServeClient{t, envFS},
301 infraRepo,
302 cg,
303 nsCreator,
giof8843412024-05-22 16:38:05 +0400304 jc,
305 hf,
gioe72b54f2024-04-22 10:44:41 +0400306 fakeZoneStatusFetcher{t},
307 fixedNameGenerator{},
308 httpClient,
309 dnsClient,
giod9c398e2024-06-06 13:33:03 +0400310 tm,
gioe72b54f2024-04-22 10:44:41 +0400311 )
312 go s.Start()
giod9c398e2024-06-06 13:33:03 +0400313 time.Sleep(1 * time.Second) // Let server start
gioe72b54f2024-04-22 10:44:41 +0400314 req := createEnvReq{
gio7841f4f2024-07-26 19:53:49 +0400315 Name: "test",
316 ContactEmail: "test@test.t",
317 Domain: "test.t",
318 PrivateNetworkSubdomain: "p",
319 AdminPublicKey: "test",
320 SecretToken: "test",
gioe72b54f2024-04-22 10:44:41 +0400321 }
322 var buf bytes.Buffer
323 if err := json.NewEncoder(&buf).Encode(req); err != nil {
324 t.Fatal(err)
325 }
326 resp, err := http.Post("http://localhost:8181/", "application/json", &buf)
gioe72b54f2024-04-22 10:44:41 +0400327 if err != nil {
328 t.Fatal(err)
329 }
330 if resp.StatusCode != http.StatusOK {
331 var buf bytes.Buffer
332 io.Copy(&buf, resp.Body)
333 t.Fatal(buf.String())
334 }
335 done.Wait()
336 http.Get("http://localhost:8181/env/test")
337 debugFS(infraFS, t, "/infrastructure/dns-gateway/resources/coredns.yaml")
338 debugFS(envFS, t)
339 if taskErr != nil {
340 t.Fatal(taskErr)
341 }
342 expected := []string{
giob79db3a2024-08-01 14:20:42 +0400343 "https://apps.p.test.t",
gioe72b54f2024-04-22 10:44:41 +0400344 "https://accounts-ui.test.t",
345 "https://welcome.test.t",
346 "https://memberships.p.test.t",
gio09a3e5b2024-04-26 14:11:06 +0400347 "https://launcher.test.t",
gioe72b54f2024-04-22 10:44:41 +0400348 "https://headscale.test.t/apple",
349 }
350 for _, e := range expected {
351 if cnt, ok := httpClient.counts[e]; !ok || cnt != 1 {
352 t.Fatal(httpClient.counts)
353 }
354 }
giob79db3a2024-08-01 14:20:42 +0400355 if len(httpClient.counts) != 6 {
gioe72b54f2024-04-22 10:44:41 +0400356 t.Fatal(httpClient.counts)
357 }
358}
359
360func debugFS(bfs billy.Filesystem, t *testing.T, files ...string) {
361 f := map[string]struct{}{}
362 for _, i := range files {
363 f[i] = struct{}{}
364 }
365 t.Log("----- START ------")
366 err := util.Walk(bfs, "/", func(path string, info fs.FileInfo, err error) error {
gioe72b54f2024-04-22 10:44:41 +0400367 if _, ok := f[path]; ok && !info.IsDir() {
368 contents, err := util.ReadFile(bfs, path)
369 if err != nil {
370 return err
371 }
372 t.Log(string(contents))
373 }
374 return nil
375 })
376 if err != nil {
377 t.Fatal(err)
378 }
379 t.Log("----- END ------")
380}