blob: 1d0bf7f61b8f42b26d66fdf94059406321118e99 [file] [log] [blame]
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +04001package installer
2
3import (
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +04004 "bytes"
5 "encoding/json"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +04006 "errors"
7 "fmt"
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +04008 "io"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +04009 "io/fs"
Giorgi Lekveishvili76951482023-06-30 23:25:09 +040010 "io/ioutil"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040011 "net"
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040012 "net/http"
13 "os"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040014 "path"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040015 "path/filepath"
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040016 "sync"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040017 "time"
18
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040019 "github.com/go-git/go-billy/v5/util"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040020 "github.com/go-git/go-git/v5"
21 "github.com/go-git/go-git/v5/plumbing/object"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040022 gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040023 "golang.org/x/crypto/ssh"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040024 "sigs.k8s.io/yaml"
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +040025
26 "github.com/giolekva/pcloud/core/installer/soft"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040027)
28
29type RepoIO interface {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +040030 Addr() string
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +040031 Pull() error
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +040032 ReadConfig() (Config, error)
Giorgi Lekveishvili76951482023-06-30 23:25:09 +040033 ReadAppConfig(path string) (AppConfig, error)
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040034 ReadKustomization(path string) (*Kustomization, error)
35 WriteKustomization(path string, kust Kustomization) error
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040036 ReadYaml(path string) (map[string]any, error)
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040037 WriteYaml(path string, data any) error
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040038 CommitAndPush(message string) error
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +040039 WriteCommitAndPush(path, contents, message string) error
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040040 Reader(path string) (io.ReadCloser, error)
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +040041 Writer(path string) (io.WriteCloser, error)
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040042 CreateDir(path string) error
43 RemoveDir(path string) error
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040044 InstallApp(app App, path string, values map[string]any, derived Derived) error
45 RemoveApp(path string) error
46 FindAllInstances(root string, appId string) ([]AppConfig, error)
47 FindInstance(root string, id string) (AppConfig, error)
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040048}
49
50type repoIO struct {
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +040051 repo *soft.Repository
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040052 signer ssh.Signer
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040053 l sync.Locker
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040054}
55
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +040056func NewRepoIO(repo *soft.Repository, signer ssh.Signer) RepoIO {
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040057 return &repoIO{
58 repo,
59 signer,
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040060 &sync.Mutex{},
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040061 }
62}
63
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +040064func (r *repoIO) Addr() string {
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +040065 return r.repo.Addr.Addr
66}
67
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +040068func (r *repoIO) Pull() error {
69 r.l.Lock()
70 defer r.l.Unlock()
71 return r.pullWithoutLock()
72}
73
74func (r *repoIO) pullWithoutLock() error {
75 wt, err := r.repo.Worktree()
76 if err != nil {
77 fmt.Printf("EEEER wt: %s\b", err)
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040078 return nil
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040079 }
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +040080 err = wt.Pull(&git.PullOptions{
81 Auth: auth(r.signer),
82 Force: true,
83 })
84 // TODO(gio): propagate error
85 if err != nil {
86 fmt.Printf("EEEER: %s\b", err)
87 }
88 return nil
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040089}
90
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +040091func (r *repoIO) ReadConfig() (Config, error) {
92 configF, err := r.Reader(configFileName)
93 if err != nil {
94 return Config{}, err
95 }
96 defer configF.Close()
Giorgi Lekveishvili76951482023-06-30 23:25:09 +040097 var cfg Config
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +040098 if err := ReadYaml(configF, &cfg); err != nil {
Giorgi Lekveishvili76951482023-06-30 23:25:09 +040099 return Config{}, err
100 } else {
101 return cfg, nil
102 }
103}
104
105func (r *repoIO) ReadAppConfig(path string) (AppConfig, error) {
106 configF, err := r.Reader(path)
107 if err != nil {
108 return AppConfig{}, err
109 }
110 defer configF.Close()
111 var cfg AppConfig
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400112 if err := ReadYaml(configF, &cfg); err != nil {
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400113 return AppConfig{}, err
114 } else {
115 return cfg, nil
116 }
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400117}
118
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400119func (r *repoIO) ReadKustomization(path string) (*Kustomization, error) {
120 inp, err := r.Reader(path)
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400121 if err != nil {
122 return nil, err
123 }
124 defer inp.Close()
125 return ReadKustomization(inp)
126}
127
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400128func (r *repoIO) Reader(path string) (io.ReadCloser, error) {
129 wt, err := r.repo.Worktree()
130 if err != nil {
131 return nil, err
132 }
133 return wt.Filesystem.Open(path)
134}
135
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +0400136func (r *repoIO) Writer(path string) (io.WriteCloser, error) {
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400137 wt, err := r.repo.Worktree()
138 if err != nil {
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +0400139 return nil, err
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400140 }
141 if err := wt.Filesystem.MkdirAll(filepath.Dir(path), fs.ModePerm); err != nil {
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +0400142 return nil, err
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400143 }
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +0400144 return wt.Filesystem.Create(path)
145}
146
147func (r *repoIO) WriteKustomization(path string, kust Kustomization) error {
148 out, err := r.Writer(path)
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400149 if err != nil {
150 return err
151 }
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400152 return kust.Write(out)
153}
154
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400155func (r *repoIO) WriteYaml(path string, data any) error {
156 out, err := r.Writer(path)
157 if err != nil {
158 return err
159 }
160 serialized, err := yaml.Marshal(data)
161 if err != nil {
162 return err
163 }
164 if _, err := out.Write(serialized); err != nil {
165 return err
166 }
167 return nil
168}
169
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400170func (r *repoIO) ReadYaml(path string) (map[string]any, error) {
Giorgi Lekveishvili1506a4f2023-07-11 11:49:02 +0400171 inp, err := r.Reader(path)
172 if err != nil {
173 return nil, err
174 }
175 data := make(map[string]any)
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400176 if err := ReadYaml(inp, &data); err != nil {
Giorgi Lekveishvili1506a4f2023-07-11 11:49:02 +0400177 return nil, err
178 }
179 return data, err
180}
181
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400182func (r *repoIO) WriteCommitAndPush(path, contents, message string) error {
183 w, err := r.Writer(path)
184 if err != nil {
185 return err
186 }
187 defer w.Close()
188 if _, err := io.WriteString(w, contents); err != nil {
189 return err
190 }
191 return r.CommitAndPush(message)
192}
193
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400194func (r *repoIO) CommitAndPush(message string) error {
195 wt, err := r.repo.Worktree()
196 if err != nil {
197 return err
198 }
199 if err := wt.AddGlob("*"); err != nil {
200 return err
201 }
202 if _, err := wt.Commit(message, &git.CommitOptions{
203 Author: &object.Signature{
204 Name: "pcloud-installer",
205 When: time.Now(),
206 },
207 }); err != nil {
208 return err
209 }
210 return r.repo.Push(&git.PushOptions{
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +0400211 RemoteName: "origin",
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400212 Auth: auth(r.signer),
213 })
214}
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400215
216func (r *repoIO) CreateDir(path string) error {
217 wt, err := r.repo.Worktree()
218 if err != nil {
219 return err
220 }
221 return wt.Filesystem.MkdirAll(path, fs.ModePerm)
222}
223
224func (r *repoIO) RemoveDir(path string) error {
225 wt, err := r.repo.Worktree()
226 if err != nil {
227 return err
228 }
229 err = util.RemoveAll(wt.Filesystem, path)
230 if err == nil || errors.Is(err, fs.ErrNotExist) {
231 return nil
232 }
233 return err
234}
235
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400236type Release struct {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400237 Namespace string `json:"namespace"`
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400238 RepoAddr string `json:"repoAddr"`
239 AppDir string `json:"appDir"`
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400240}
241
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400242type Derived struct {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400243 Release Release `json:"release"`
244 Global Values `json:"global"`
245 Values map[string]any `json:"input"` // TODO(gio): rename to input
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400246}
247
248type AppConfig struct {
249 Id string `json:"id"`
250 AppId string `json:"appId"`
251 Config map[string]any `json:"config"`
252 Derived Derived `json:"derived"`
253}
254
gio5c44e6c2024-04-12 16:52:59 +0400255func (a AppConfig) Input(schema Schema) map[string]any {
256 ret, err := derivedToConfig(a.Derived.Values, schema)
257 if err != nil {
258 panic(err) // TODO(gio): handle
259 }
260 return ret
261}
262
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400263type allocatePortReq struct {
264 Protocol string `json:"protocol"`
265 SourcePort int `json:"sourcePort"`
266 TargetService string `json:"targetService"`
267 TargetPort int `json:"targetPort"`
268}
269
270// TODO(gio): most of this logic should move to AppManager
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400271func (r *repoIO) InstallApp(app App, appRootDir string, values map[string]any, derived Derived) error {
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400272 r.l.Lock()
273 defer r.l.Unlock()
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +0400274 if err := r.pullWithoutLock(); err != nil {
275 return err
276 }
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400277 if !filepath.IsAbs(appRootDir) {
278 return fmt.Errorf("Expected absolute path: %s", appRootDir)
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400279 }
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400280 derived.Release.RepoAddr = r.repo.Addr.FullAddress()
281 // TODO(gio): maybe client should populate this?
282 derived.Release.AppDir = appRootDir
283 rendered, err := app.Render(derived)
284 if err != nil {
285 return err
286 }
287 for _, p := range rendered.Ports {
288 var buf bytes.Buffer
289 req := allocatePortReq{
290 Protocol: p.Protocol,
291 SourcePort: p.SourcePort,
292 TargetService: p.TargetService,
293 TargetPort: p.TargetPort,
294 }
295 fmt.Printf("%+v\n", req)
296 if err := json.NewEncoder(&buf).Encode(req); err != nil {
297 return err
298 }
299 resp, err := http.Post(p.Allocator, "application/json", &buf)
300 if err != nil {
301 return err
302 }
303 if resp.StatusCode != http.StatusOK {
304 io.Copy(os.Stdout, resp.Body)
305 return fmt.Errorf("Could not allocate port %d, status code: %d", p.SourcePort, resp.StatusCode)
306 }
307 }
308 if err := r.pullWithoutLock(); err != nil {
309 return err
310 }
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400311 appRootDir = filepath.Clean(appRootDir)
312 for p := appRootDir; p != "/"; {
313 parent, child := filepath.Split(p)
314 kustPath := filepath.Join(parent, "kustomization.yaml")
315 kust, err := r.ReadKustomization(kustPath)
316 if err != nil {
317 if errors.Is(err, fs.ErrNotExist) {
318 k := NewKustomization()
319 kust = &k
320 } else {
321 return err
322 }
323 }
324 kust.AddResources(child)
325 if err := r.WriteKustomization(kustPath, *kust); err != nil {
326 return err
327 }
328 p = filepath.Clean(parent)
329 }
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400330 {
331 if err := r.RemoveDir(appRootDir); err != nil {
332 return err
333 }
334 if err := r.CreateDir(appRootDir); err != nil {
335 return err
336 }
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400337 cfg := AppConfig{
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400338 AppId: app.Name(),
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400339 Config: values,
340 Derived: derived,
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400341 }
342 if err := r.WriteYaml(path.Join(appRootDir, configFileName), cfg); err != nil {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400343 return err
344 }
345 }
346 {
347 appKust := NewKustomization()
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400348 for name, contents := range rendered.Resources {
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +0400349 appKust.AddResources(name)
350 out, err := r.Writer(path.Join(appRootDir, name))
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400351 if err != nil {
352 return err
353 }
354 defer out.Close()
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +0400355 if _, err := out.Write(contents); err != nil {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400356 return err
357 }
358 }
359 if err := r.WriteKustomization(path.Join(appRootDir, "kustomization.yaml"), appKust); err != nil {
360 return err
361 }
362 }
Giorgi Lekveishvili7c037392024-03-11 14:40:24 +0400363 return r.CommitAndPush(fmt.Sprintf("install: %s", app.Name()))
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400364}
365
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400366func (r *repoIO) RemoveApp(appRootDir string) error {
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400367 r.l.Lock()
368 defer r.l.Unlock()
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400369 r.RemoveDir(appRootDir)
370 parent, child := filepath.Split(appRootDir)
371 kustPath := filepath.Join(parent, "kustomization.yaml")
372 kust, err := r.ReadKustomization(kustPath)
373 if err != nil {
374 return err
375 }
376 kust.RemoveResources(child)
377 r.WriteKustomization(kustPath, *kust)
378 return r.CommitAndPush(fmt.Sprintf("uninstall: %s", child))
379}
380
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400381func (r *repoIO) FindAllInstances(root string, name string) ([]AppConfig, error) {
382 if !filepath.IsAbs(root) {
383 return nil, fmt.Errorf("Expected absolute path: %s", root)
384 }
385 kust, err := r.ReadKustomization(filepath.Join(root, "kustomization.yaml"))
386 if err != nil {
387 return nil, err
388 }
389 ret := make([]AppConfig, 0)
390 for _, app := range kust.Resources {
391 cfg, err := r.ReadAppConfig(filepath.Join(root, app, "config.yaml"))
392 if err != nil {
393 return nil, err
394 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400395 cfg.Id = app
396 if cfg.AppId == name {
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400397 ret = append(ret, cfg)
398 }
399 }
400 return ret, nil
401}
402
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400403func (r *repoIO) FindInstance(root string, id string) (AppConfig, error) {
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400404 if !filepath.IsAbs(root) {
405 return AppConfig{}, fmt.Errorf("Expected absolute path: %s", root)
406 }
407 kust, err := r.ReadKustomization(filepath.Join(root, "kustomization.yaml"))
408 if err != nil {
409 return AppConfig{}, err
410 }
411 for _, app := range kust.Resources {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400412 if app == id {
413 cfg, err := r.ReadAppConfig(filepath.Join(root, app, "config.yaml"))
414 if err != nil {
415 return AppConfig{}, err
416 }
417 cfg.Id = id
418 return cfg, nil
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400419 }
420 }
421 return AppConfig{}, nil
422}
423
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400424func auth(signer ssh.Signer) *gitssh.PublicKeys {
425 return &gitssh.PublicKeys{
426 Signer: signer,
427 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
428 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
429 // TODO(giolekva): verify server public key
430 // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
431 return nil
432 },
433 },
434 }
435}
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400436
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400437func ReadYaml[T any](r io.Reader, o *T) error {
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400438 if contents, err := ioutil.ReadAll(r); err != nil {
439 return err
440 } else {
441 return yaml.UnmarshalStrict(contents, o)
442 }
443}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400444
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400445func deriveValues(values any, schema Schema, networks []Network) (map[string]any, error) {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400446 ret := make(map[string]any)
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400447 for k, def := range schema.Fields() {
448 // TODO(gio): validate that it is map
449 v, ok := values.(map[string]any)[k]
450 // TODO(gio): if missing use default value
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400451 if !ok {
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400452 if def.Kind() == KindSSHKey {
453 key, err := NewECDSASSHKeyPair("tmp")
454 if err != nil {
455 return nil, err
456 }
457 ret[k] = map[string]string{
458 "public": string(key.RawAuthorizedKey()),
459 "private": string(key.RawPrivateKey()),
460 }
461 }
462 continue
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400463 }
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400464 switch def.Kind() {
465 case KindBoolean:
Giorgi Lekveishvili0ba5e402024-03-20 15:56:30 +0400466 ret[k] = v
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400467 case KindString:
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400468 ret[k] = v
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400469 case KindInt:
470 ret[k] = v
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400471 case KindNetwork:
472 n, err := findNetwork(networks, v.(string)) // TODO(giolekva): validate
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400473 if err != nil {
474 return nil, err
475 }
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400476 ret[k] = n
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400477 case KindAuth:
478 r, err := deriveValues(v, AuthSchema, networks)
479 if err != nil {
480 return nil, err
481 }
482 ret[k] = r
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400483 case KindSSHKey:
484 r, err := deriveValues(v, SSHKeySchema, networks)
485 if err != nil {
486 return nil, err
487 }
488 ret[k] = r
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400489 case KindStruct:
490 r, err := deriveValues(v, def, networks)
491 if err != nil {
492 return nil, err
493 }
494 ret[k] = r
495 default:
496 return nil, fmt.Errorf("Should not reach!")
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400497 }
498 }
499 return ret, nil
500}
501
gio5c44e6c2024-04-12 16:52:59 +0400502func derivedToConfig(derived map[string]any, schema Schema) (map[string]any, error) {
503 ret := make(map[string]any)
504 for k, def := range schema.Fields() {
505 v, ok := derived[k]
506 // TODO(gio): if missing use default value
507 if !ok {
508 continue
509 }
510 switch def.Kind() {
511 case KindBoolean:
512 ret[k] = v
513 case KindString:
514 ret[k] = v
515 case KindInt:
516 ret[k] = v
517 case KindNetwork:
518 vm, ok := v.(map[string]any)
519 if !ok {
520 return nil, fmt.Errorf("expected map")
521 }
522 name, ok := vm["name"]
523 if !ok {
524 return nil, fmt.Errorf("expected network name")
525 }
526 ret[k] = name
527 case KindAuth:
528 vm, ok := v.(map[string]any)
529 if !ok {
530 return nil, fmt.Errorf("expected map")
531 }
532 r, err := derivedToConfig(vm, AuthSchema)
533 if err != nil {
534 return nil, err
535 }
536 ret[k] = r
537 case KindSSHKey:
538 vm, ok := v.(map[string]any)
539 if !ok {
540 return nil, fmt.Errorf("expected map")
541 }
542 r, err := derivedToConfig(vm, SSHKeySchema)
543 if err != nil {
544 return nil, err
545 }
546 ret[k] = r
547 case KindStruct:
548 vm, ok := v.(map[string]any)
549 if !ok {
550 return nil, fmt.Errorf("expected map")
551 }
552 r, err := derivedToConfig(vm, def)
553 if err != nil {
554 return nil, err
555 }
556 ret[k] = r
557 default:
558 return nil, fmt.Errorf("Should not reach!")
559 }
560 }
561 return ret, nil
562}
563
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400564func findNetwork(networks []Network, name string) (Network, error) {
565 for _, n := range networks {
566 if n.Name == name {
567 return n, nil
568 }
569 }
570 return Network{}, fmt.Errorf("Network not found: %s", name)
571}
572
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400573type Network struct {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400574 Name string `json:"name,omitempty"`
575 IngressClass string `json:"ingressClass,omitempty"`
576 CertificateIssuer string `json:"certificateIssuer,omitempty"`
577 Domain string `json:"domain,omitempty"`
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400578 AllocatePortAddr string `json:"allocatePortAddr,omitempty"`
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400579}