blob: 35e968d72c6efd41f6123aadb22065b347d8f924 [file] [log] [blame]
gioe72b54f2024-04-22 10:44:41 +04001package welcome
2
3import (
4 "bytes"
5 "encoding/json"
6 "golang.org/x/crypto/ssh"
7 "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
17 "github.com/go-git/go-billy/v5"
18 "github.com/go-git/go-billy/v5/memfs"
19 "github.com/go-git/go-billy/v5/util"
20 // "github.com/go-git/go-git/v5"
21 // "github.com/go-git/go-git/v5/storage/memory"
22
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
gio0eaf2712024-04-14 13:08:46 +040080func (r mockRepoIO) CommitAndPush(message string, opts ...soft.PushOption) error {
gioe72b54f2024-04-22 10:44:41 +040081 r.t.Logf("Commit and push: %s", message)
82 return nil
83}
84
85func (r mockRepoIO) Do(op soft.DoFn, _ ...soft.DoOption) error {
86 r.l.Lock()
87 defer r.l.Unlock()
88 msg, err := op(r)
89 if err != nil {
90 return err
91 }
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
112func (f fakeSoftServeClient) GetRepo(name string) (soft.RepoIO, error) {
113 var l sync.Mutex
114 return mockRepoIO{soft.NewBillyRepoFS(f.envFS), "foo.bar", f.t, &l}, nil
115}
116
117func (f fakeSoftServeClient) GetRepoAddress(name string) string {
118 return ""
119}
120
121func (f fakeSoftServeClient) AddRepository(name string) error {
122 return nil
123}
124
125func (f fakeSoftServeClient) AddUser(name, pubKey string) error {
126 return nil
127}
128
129func (f fakeSoftServeClient) AddPublicKey(user string, pubKey string) error {
130 return nil
131}
132
133func (f fakeSoftServeClient) RemovePublicKey(user string, pubKey string) error {
134 return nil
135}
136
137func (f fakeSoftServeClient) MakeUserAdmin(name string) error {
138 return nil
139}
140
141func (f fakeSoftServeClient) AddReadWriteCollaborator(repo, user string) error {
142 return nil
143}
144
145func (f fakeSoftServeClient) AddReadOnlyCollaborator(repo, user string) error {
146 return nil
147}
148
gio0eaf2712024-04-14 13:08:46 +0400149func (f fakeSoftServeClient) AddWebhook(repo, url string, opts ...string) error {
150 return nil
151}
152
gioe72b54f2024-04-22 10:44:41 +0400153type fakeClientGetter struct {
154 t *testing.T
155 envFS billy.Filesystem
156}
157
158func (f fakeClientGetter) Get(addr string, clientPrivateKey []byte, log *log.Logger) (soft.Client, error) {
159 return fakeSoftServeClient{f.t, f.envFS}, nil
160}
161
162const infraConfig = `
163infraAdminPublicKey: Zm9vYmFyCg==
164namespacePrefix: infra-
165pcloudEnvName: infra
166publicIP:
167- 1.1.1.1
168- 2.2.2.2
169`
170
171const envCidrs = ``
172
173type fixedNameGenerator struct{}
174
175func (f fixedNameGenerator) Generate() (string, error) {
176 return "test", nil
177}
178
179type fakeHttpClient struct {
180 t *testing.T
181 counts map[string]int
182}
183
184func (f fakeHttpClient) Get(addr string) (*http.Response, error) {
185 f.t.Logf("HTTP GET: %s", addr)
186 cnt, ok := f.counts[addr]
187 if !ok {
188 cnt = 0
189 }
190 f.counts[addr] = cnt + 1
191 return &http.Response{
192 Status: "200 OK",
193 StatusCode: http.StatusOK,
194 Proto: "HTTP/1.0",
195 ProtoMajor: 1,
196 ProtoMinor: 0,
197 Body: io.NopCloser(strings.NewReader("ok")),
198 }, nil
199}
200
201type fakeDnsClient struct {
202 t *testing.T
203 counts map[string]int
204}
205
206func (f fakeDnsClient) Lookup(host string) ([]net.IP, error) {
207 f.t.Logf("HTTP GET: %s", host)
208 return []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("2.2.2.2")}, nil
209}
210
giod9c398e2024-06-06 13:33:03 +0400211type onDoneTaskMap struct {
212 m tasks.TaskManager
213 onDone tasks.TaskDoneListener
214}
215
216func (m *onDoneTaskMap) Add(name string, task tasks.Task) error {
217 if err := m.m.Add(name, task); err != nil {
218 return err
219 } else {
220 task.OnDone(m.onDone)
221 return nil
222 }
223}
224
225func (m *onDoneTaskMap) Get(name string) (tasks.Task, error) {
226 return m.m.Get(name)
227}
228
gioe72b54f2024-04-22 10:44:41 +0400229func TestCreateNewEnv(t *testing.T) {
230 apps := installer.NewInMemoryAppRepository(installer.CreateAllApps())
231 infraFS := memfs.New()
232 envFS := memfs.New()
233 nsCreator := fakeNSCreator{t}
giof8843412024-05-22 16:38:05 +0400234 jc := fakeJobCreator{t}
235 hf := fakeHelmFetcher{t}
236 lg := installer.GitRepositoryLocalChartGenerator{"foo", "bar"}
gioe72b54f2024-04-22 10:44:41 +0400237 infraRepo := mockRepoIO{soft.NewBillyRepoFS(infraFS), "foo.bar", t, &sync.Mutex{}}
giof8843412024-05-22 16:38:05 +0400238 infraMgr, err := installer.NewInfraAppManager(infraRepo, nsCreator, hf, lg)
gioe72b54f2024-04-22 10:44:41 +0400239 if err != nil {
240 t.Fatal(err)
241 }
242 if err := util.WriteFile(infraFS, "config.yaml", []byte(infraConfig), fs.ModePerm); err != nil {
243 t.Fatal(err)
244 }
245 if err := util.WriteFile(infraFS, "env-cidrs.yaml", []byte(envCidrs), fs.ModePerm); err != nil {
246 t.Fatal(err)
247 }
248 {
249 app, err := installer.FindInfraApp(apps, "dns-gateway")
250 if err != nil {
251 t.Fatal(err)
252 }
gio778577f2024-04-29 09:44:38 +0400253 if _, err := infraMgr.Install(app, "/infrastructure/dns-gateway", "dns-gateway", map[string]any{
gioe72b54f2024-04-22 10:44:41 +0400254 "servers": []installer.EnvDNS{},
255 }); err != nil {
256 t.Fatal(err)
257 }
258 }
259 cg := fakeClientGetter{t, envFS}
260 httpClient := fakeHttpClient{t, make(map[string]int)}
261 dnsClient := fakeDnsClient{t, make(map[string]int)}
giod9c398e2024-06-06 13:33:03 +0400262 var done sync.WaitGroup
263 done.Add(1)
264 var taskErr error
265 tm := &onDoneTaskMap{
266 tasks.NewTaskMap(),
267 func(err error) {
268 taskErr = err
269 done.Done()
270 },
271 }
gioe72b54f2024-04-22 10:44:41 +0400272 s := NewEnvServer(
273 8181,
274 fakeSoftServeClient{t, envFS},
275 infraRepo,
276 cg,
277 nsCreator,
giof8843412024-05-22 16:38:05 +0400278 jc,
279 hf,
gioe72b54f2024-04-22 10:44:41 +0400280 fakeZoneStatusFetcher{t},
281 fixedNameGenerator{},
282 httpClient,
283 dnsClient,
giod9c398e2024-06-06 13:33:03 +0400284 tm,
gioe72b54f2024-04-22 10:44:41 +0400285 )
286 go s.Start()
giod9c398e2024-06-06 13:33:03 +0400287 time.Sleep(1 * time.Second) // Let server start
gioe72b54f2024-04-22 10:44:41 +0400288 req := createEnvReq{
289 Name: "test",
290 ContactEmail: "test@test.t",
291 Domain: "test.t",
292 AdminPublicKey: "test",
293 SecretToken: "test",
294 }
295 var buf bytes.Buffer
296 if err := json.NewEncoder(&buf).Encode(req); err != nil {
297 t.Fatal(err)
298 }
299 resp, err := http.Post("http://localhost:8181/", "application/json", &buf)
gioe72b54f2024-04-22 10:44:41 +0400300 if err != nil {
301 t.Fatal(err)
302 }
303 if resp.StatusCode != http.StatusOK {
304 var buf bytes.Buffer
305 io.Copy(&buf, resp.Body)
306 t.Fatal(buf.String())
307 }
308 done.Wait()
309 http.Get("http://localhost:8181/env/test")
310 debugFS(infraFS, t, "/infrastructure/dns-gateway/resources/coredns.yaml")
311 debugFS(envFS, t)
312 if taskErr != nil {
313 t.Fatal(taskErr)
314 }
315 expected := []string{
316 "https://accounts-ui.test.t",
317 "https://welcome.test.t",
318 "https://memberships.p.test.t",
gio09a3e5b2024-04-26 14:11:06 +0400319 "https://launcher.test.t",
gioe72b54f2024-04-22 10:44:41 +0400320 "https://headscale.test.t/apple",
321 }
322 for _, e := range expected {
323 if cnt, ok := httpClient.counts[e]; !ok || cnt != 1 {
324 t.Fatal(httpClient.counts)
325 }
326 }
gio09a3e5b2024-04-26 14:11:06 +0400327 if len(httpClient.counts) != 5 {
gioe72b54f2024-04-22 10:44:41 +0400328 t.Fatal(httpClient.counts)
329 }
330}
331
332func debugFS(bfs billy.Filesystem, t *testing.T, files ...string) {
333 f := map[string]struct{}{}
334 for _, i := range files {
335 f[i] = struct{}{}
336 }
337 t.Log("----- START ------")
338 err := util.Walk(bfs, "/", func(path string, info fs.FileInfo, err error) error {
339 t.Logf("%s %t\n", path, info.IsDir())
340 if _, ok := f[path]; ok && !info.IsDir() {
341 contents, err := util.ReadFile(bfs, path)
342 if err != nil {
343 return err
344 }
345 t.Log(string(contents))
346 }
347 return nil
348 })
349 if err != nil {
350 t.Fatal(err)
351 }
352 t.Log("----- END ------")
353}