blob: 0803e64bd6373f42aaf43b65062bc0528eee4097 [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
37type fakeZoneStatusFetcher struct {
38 t *testing.T
39}
40
41func (f fakeZoneStatusFetcher) Fetch(addr string) (string, error) {
42 f.t.Logf("Fetching status: %s", addr)
43 return addr, nil
44}
45
46type mockRepoIO struct {
47 soft.RepoFS
48 addr string
49 t *testing.T
50 l sync.Locker
51}
52
53func (r mockRepoIO) FullAddress() string {
54 return r.addr
55}
56
57func (r mockRepoIO) Pull() error {
58 r.t.Logf("Pull: %s", r.addr)
59 return nil
60}
61
gio0eaf2712024-04-14 13:08:46 +040062func (r mockRepoIO) CommitAndPush(message string, opts ...soft.PushOption) error {
gioe72b54f2024-04-22 10:44:41 +040063 r.t.Logf("Commit and push: %s", message)
64 return nil
65}
66
67func (r mockRepoIO) Do(op soft.DoFn, _ ...soft.DoOption) error {
68 r.l.Lock()
69 defer r.l.Unlock()
70 msg, err := op(r)
71 if err != nil {
72 return err
73 }
74 return r.CommitAndPush(msg)
75}
76
77type fakeSoftServeClient struct {
78 t *testing.T
79 envFS billy.Filesystem
80}
81
82func (f fakeSoftServeClient) Address() string {
83 return ""
84}
85
86func (f fakeSoftServeClient) Signer() ssh.Signer {
87 return nil
88}
89
90func (f fakeSoftServeClient) GetPublicKeys() ([]string, error) {
91 return []string{}, nil
92}
93
94func (f fakeSoftServeClient) GetRepo(name string) (soft.RepoIO, error) {
95 var l sync.Mutex
96 return mockRepoIO{soft.NewBillyRepoFS(f.envFS), "foo.bar", f.t, &l}, nil
97}
98
99func (f fakeSoftServeClient) GetRepoAddress(name string) string {
100 return ""
101}
102
103func (f fakeSoftServeClient) AddRepository(name string) error {
104 return nil
105}
106
107func (f fakeSoftServeClient) AddUser(name, pubKey string) error {
108 return nil
109}
110
111func (f fakeSoftServeClient) AddPublicKey(user string, pubKey string) error {
112 return nil
113}
114
115func (f fakeSoftServeClient) RemovePublicKey(user string, pubKey string) error {
116 return nil
117}
118
119func (f fakeSoftServeClient) MakeUserAdmin(name string) error {
120 return nil
121}
122
123func (f fakeSoftServeClient) AddReadWriteCollaborator(repo, user string) error {
124 return nil
125}
126
127func (f fakeSoftServeClient) AddReadOnlyCollaborator(repo, user string) error {
128 return nil
129}
130
gio0eaf2712024-04-14 13:08:46 +0400131func (f fakeSoftServeClient) AddWebhook(repo, url string, opts ...string) error {
132 return nil
133}
134
gioe72b54f2024-04-22 10:44:41 +0400135type fakeClientGetter struct {
136 t *testing.T
137 envFS billy.Filesystem
138}
139
140func (f fakeClientGetter) Get(addr string, clientPrivateKey []byte, log *log.Logger) (soft.Client, error) {
141 return fakeSoftServeClient{f.t, f.envFS}, nil
142}
143
144const infraConfig = `
145infraAdminPublicKey: Zm9vYmFyCg==
146namespacePrefix: infra-
147pcloudEnvName: infra
148publicIP:
149- 1.1.1.1
150- 2.2.2.2
151`
152
153const envCidrs = ``
154
155type fixedNameGenerator struct{}
156
157func (f fixedNameGenerator) Generate() (string, error) {
158 return "test", nil
159}
160
161type fakeHttpClient struct {
162 t *testing.T
163 counts map[string]int
164}
165
166func (f fakeHttpClient) Get(addr string) (*http.Response, error) {
167 f.t.Logf("HTTP GET: %s", addr)
168 cnt, ok := f.counts[addr]
169 if !ok {
170 cnt = 0
171 }
172 f.counts[addr] = cnt + 1
173 return &http.Response{
174 Status: "200 OK",
175 StatusCode: http.StatusOK,
176 Proto: "HTTP/1.0",
177 ProtoMajor: 1,
178 ProtoMinor: 0,
179 Body: io.NopCloser(strings.NewReader("ok")),
180 }, nil
181}
182
183type fakeDnsClient struct {
184 t *testing.T
185 counts map[string]int
186}
187
188func (f fakeDnsClient) Lookup(host string) ([]net.IP, error) {
189 f.t.Logf("HTTP GET: %s", host)
190 return []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("2.2.2.2")}, nil
191}
192
giod9c398e2024-06-06 13:33:03 +0400193type onDoneTaskMap struct {
194 m tasks.TaskManager
195 onDone tasks.TaskDoneListener
196}
197
198func (m *onDoneTaskMap) Add(name string, task tasks.Task) error {
199 if err := m.m.Add(name, task); err != nil {
200 return err
201 } else {
202 task.OnDone(m.onDone)
203 return nil
204 }
205}
206
207func (m *onDoneTaskMap) Get(name string) (tasks.Task, error) {
208 return m.m.Get(name)
209}
210
gioe72b54f2024-04-22 10:44:41 +0400211func TestCreateNewEnv(t *testing.T) {
212 apps := installer.NewInMemoryAppRepository(installer.CreateAllApps())
213 infraFS := memfs.New()
214 envFS := memfs.New()
215 nsCreator := fakeNSCreator{t}
216 infraRepo := mockRepoIO{soft.NewBillyRepoFS(infraFS), "foo.bar", t, &sync.Mutex{}}
217 infraMgr, err := installer.NewInfraAppManager(infraRepo, nsCreator)
218 if err != nil {
219 t.Fatal(err)
220 }
221 if err := util.WriteFile(infraFS, "config.yaml", []byte(infraConfig), fs.ModePerm); err != nil {
222 t.Fatal(err)
223 }
224 if err := util.WriteFile(infraFS, "env-cidrs.yaml", []byte(envCidrs), fs.ModePerm); err != nil {
225 t.Fatal(err)
226 }
227 {
228 app, err := installer.FindInfraApp(apps, "dns-gateway")
229 if err != nil {
230 t.Fatal(err)
231 }
gio778577f2024-04-29 09:44:38 +0400232 if _, err := infraMgr.Install(app, "/infrastructure/dns-gateway", "dns-gateway", map[string]any{
gioe72b54f2024-04-22 10:44:41 +0400233 "servers": []installer.EnvDNS{},
234 }); err != nil {
235 t.Fatal(err)
236 }
237 }
238 cg := fakeClientGetter{t, envFS}
239 httpClient := fakeHttpClient{t, make(map[string]int)}
240 dnsClient := fakeDnsClient{t, make(map[string]int)}
giod9c398e2024-06-06 13:33:03 +0400241 var done sync.WaitGroup
242 done.Add(1)
243 var taskErr error
244 tm := &onDoneTaskMap{
245 tasks.NewTaskMap(),
246 func(err error) {
247 taskErr = err
248 done.Done()
249 },
250 }
gioe72b54f2024-04-22 10:44:41 +0400251 s := NewEnvServer(
252 8181,
253 fakeSoftServeClient{t, envFS},
254 infraRepo,
255 cg,
256 nsCreator,
257 fakeZoneStatusFetcher{t},
258 fixedNameGenerator{},
259 httpClient,
260 dnsClient,
giod9c398e2024-06-06 13:33:03 +0400261 tm,
gioe72b54f2024-04-22 10:44:41 +0400262 )
263 go s.Start()
giod9c398e2024-06-06 13:33:03 +0400264 time.Sleep(1 * time.Second) // Let server start
gioe72b54f2024-04-22 10:44:41 +0400265 req := createEnvReq{
266 Name: "test",
267 ContactEmail: "test@test.t",
268 Domain: "test.t",
269 AdminPublicKey: "test",
270 SecretToken: "test",
271 }
272 var buf bytes.Buffer
273 if err := json.NewEncoder(&buf).Encode(req); err != nil {
274 t.Fatal(err)
275 }
276 resp, err := http.Post("http://localhost:8181/", "application/json", &buf)
gioe72b54f2024-04-22 10:44:41 +0400277 if err != nil {
278 t.Fatal(err)
279 }
280 if resp.StatusCode != http.StatusOK {
281 var buf bytes.Buffer
282 io.Copy(&buf, resp.Body)
283 t.Fatal(buf.String())
284 }
285 done.Wait()
286 http.Get("http://localhost:8181/env/test")
287 debugFS(infraFS, t, "/infrastructure/dns-gateway/resources/coredns.yaml")
288 debugFS(envFS, t)
289 if taskErr != nil {
290 t.Fatal(taskErr)
291 }
292 expected := []string{
293 "https://accounts-ui.test.t",
294 "https://welcome.test.t",
295 "https://memberships.p.test.t",
gio09a3e5b2024-04-26 14:11:06 +0400296 "https://launcher.test.t",
gioe72b54f2024-04-22 10:44:41 +0400297 "https://headscale.test.t/apple",
298 }
299 for _, e := range expected {
300 if cnt, ok := httpClient.counts[e]; !ok || cnt != 1 {
301 t.Fatal(httpClient.counts)
302 }
303 }
gio09a3e5b2024-04-26 14:11:06 +0400304 if len(httpClient.counts) != 5 {
gioe72b54f2024-04-22 10:44:41 +0400305 t.Fatal(httpClient.counts)
306 }
307}
308
309func debugFS(bfs billy.Filesystem, t *testing.T, files ...string) {
310 f := map[string]struct{}{}
311 for _, i := range files {
312 f[i] = struct{}{}
313 }
314 t.Log("----- START ------")
315 err := util.Walk(bfs, "/", func(path string, info fs.FileInfo, err error) error {
316 t.Logf("%s %t\n", path, info.IsDir())
317 if _, ok := f[path]; ok && !info.IsDir() {
318 contents, err := util.ReadFile(bfs, path)
319 if err != nil {
320 return err
321 }
322 t.Log(string(contents))
323 }
324 return nil
325 })
326 if err != nil {
327 t.Fatal(err)
328 }
329 t.Log("----- END ------")
330}