blob: 6bf01614d7c0d0828bb1eb258ed24664327fa5b6 [file] [log] [blame]
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +04001package installer
2
3import (
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +04004 "fmt"
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +04005 "io/fs"
Giorgi Lekveishvili03ee5852023-05-30 13:20:10 +04006 "io/ioutil"
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +04007 "net"
8 "time"
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +04009
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040010 "golang.org/x/crypto/ssh"
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040011 "golang.org/x/exp/slices"
12
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040013 "github.com/go-git/go-billy/v5/util"
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040014 "github.com/go-git/go-git/v5"
15 "github.com/go-git/go-git/v5/plumbing/object"
16 gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040017 "sigs.k8s.io/yaml"
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040018)
19
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040020const appDirName = "apps"
21const configFileName = "config.yaml"
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040022const kustomizationFileName = "kustomization.yaml"
23
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040024type AppManager struct {
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040025 repo *git.Repository
26 signer ssh.Signer
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040027}
28
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040029func NewAppManager(repo *git.Repository, signer ssh.Signer) (*AppManager, error) {
30 return &AppManager{
31 repo,
32 signer,
33 }, nil
34}
35
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040036func (m *AppManager) Config() (Config, error) {
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040037 wt, err := m.repo.Worktree()
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040038 if err != nil {
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040039 return Config{}, err
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040040 }
41 configF, err := wt.Filesystem.Open(configFileName)
42 if err != nil {
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040043 return Config{}, err
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040044 }
45 defer configF.Close()
46 config, err := ReadConfig(configF)
47 if err != nil {
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040048 return Config{}, err
49 }
50 return config, nil
51}
52
Giorgi Lekveishvili03ee5852023-05-30 13:20:10 +040053func (m *AppManager) AppConfig(name string) (map[string]any, error) {
54 wt, err := m.repo.Worktree()
55 if err != nil {
56 return nil, err
57 }
58 configF, err := wt.Filesystem.Open(wt.Filesystem.Join(appDirName, name, configFileName))
59 if err != nil {
60 return nil, err
61 }
62 defer configF.Close()
63 var cfg map[string]any
64 contents, err := ioutil.ReadAll(configF)
65 if err != nil {
66 return cfg, err
67 }
68 err = yaml.UnmarshalStrict(contents, &cfg)
69 return cfg, err
70}
71
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040072func (m *AppManager) Install(app App, config map[string]any) error {
Giorgi Lekveishvili938b0732023-06-09 13:14:01 +040073 if err := m.repo.Fetch(&git.FetchOptions{
74 RemoteName: "origin",
75 Auth: auth(m.signer),
76 Force: true,
77 }); err != nil {
78 return err
79 }
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040080 wt, err := m.repo.Worktree()
81 if err != nil {
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040082 return err
83 }
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040084 globalConfig, err := m.Config()
85 if err != nil {
86 return err
87 }
88 all := map[string]any{
89 "Global": globalConfig.Values,
90 "Values": config,
91 }
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040092 appsRoot, err := wt.Filesystem.Chroot(appDirName)
93 if err != nil {
94 return err
95 }
96 rootKustF, err := appsRoot.Open(kustomizationFileName)
97 if err != nil {
98 return err
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040099 }
100 defer rootKustF.Close()
101 rootKust, err := ReadKustomization(rootKustF)
102 if err != nil {
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400103 return err
104 }
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400105 appRoot, err := appsRoot.Chroot(app.Name)
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400106 if err != nil {
107 return err
108 }
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400109 if err := util.RemoveAll(appRoot, app.Name); err != nil {
110 return err
111 }
112 if err := appRoot.MkdirAll(app.Name, fs.ModePerm); err != nil {
113 return nil
114 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400115 appKust := NewKustomization()
116 for _, t := range app.Templates {
117 out, err := appRoot.Create(t.Name())
118 if err != nil {
119 return err
120 }
121 defer out.Close()
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400122 if err := t.Execute(out, all); err != nil {
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400123 return err
124 }
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400125 appKust.AddResources(t.Name())
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400126 }
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400127 {
128 out, err := appRoot.Create(configFileName)
129 if err != nil {
130 return err
131 }
132 defer out.Close()
133 configBytes, err := yaml.Marshal(config)
134 if err != nil {
135 return err
136 }
137 if _, err := out.Write(configBytes); err != nil {
138 return err
139 }
140 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400141 appKustF, err := appRoot.Create(kustomizationFileName)
142 if err != nil {
143 return err
144 }
145 defer appKustF.Close()
146 if err := appKust.Write(appKustF); err != nil {
147 return err
148 }
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400149 if !slices.Contains(rootKust.Resources, app.Name) {
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400150 rootKust.AddResources(app.Name)
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400151 rootKustFW, err := appsRoot.Create(kustomizationFileName)
152 if err != nil {
153 return err
154 }
155 defer rootKustFW.Close()
156 if err := rootKust.Write(rootKustFW); err != nil {
157 return err
158 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400159 }
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400160 // Commit and push
161 if err := wt.AddGlob("*"); err != nil {
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400162 return err
163 }
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400164 if _, err := wt.Commit(fmt.Sprintf("install: %s", app.Name), &git.CommitOptions{
165 Author: &object.Signature{
166 Name: "pcloud-appmanager",
167 When: time.Now(),
168 },
169 }); err != nil {
170 return err
171 }
172 return m.repo.Push(&git.PushOptions{
173 RemoteName: "origin",
174 Auth: auth(m.signer),
175 })
176}
177
178func auth(signer ssh.Signer) *gitssh.PublicKeys {
179 return &gitssh.PublicKeys{
180 Signer: signer,
181 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
182 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
183 // TODO(giolekva): verify server public key
184 // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
185 return nil
186 },
187 },
188 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400189}