blob: fbf69d13620f710c372e9d7c20216526cac4ea5c [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 +040029// func NewAppManager(repo *git.Repository, fs billy.Filesystem, config Config, appRepo AppRepository) (*AppManager, error) {
30func NewAppManager(repo *git.Repository, signer ssh.Signer) (*AppManager, error) {
31 return &AppManager{
32 repo,
33 signer,
34 }, nil
35}
36
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040037func (m *AppManager) Config() (Config, error) {
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040038 wt, err := m.repo.Worktree()
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040039 if err != nil {
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040040 return Config{}, err
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040041 }
42 configF, err := wt.Filesystem.Open(configFileName)
43 if err != nil {
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040044 return Config{}, err
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040045 }
46 defer configF.Close()
47 config, err := ReadConfig(configF)
48 if err != nil {
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040049 return Config{}, err
50 }
51 return config, nil
52}
53
Giorgi Lekveishvili03ee5852023-05-30 13:20:10 +040054func (m *AppManager) AppConfig(name string) (map[string]any, error) {
55 wt, err := m.repo.Worktree()
56 if err != nil {
57 return nil, err
58 }
59 configF, err := wt.Filesystem.Open(wt.Filesystem.Join(appDirName, name, configFileName))
60 if err != nil {
61 return nil, err
62 }
63 defer configF.Close()
64 var cfg map[string]any
65 contents, err := ioutil.ReadAll(configF)
66 if err != nil {
67 return cfg, err
68 }
69 err = yaml.UnmarshalStrict(contents, &cfg)
70 return cfg, err
71}
72
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040073func (m *AppManager) Install(app App, config map[string]any) error {
74 wt, err := m.repo.Worktree()
75 if err != nil {
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040076 return err
77 }
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040078 globalConfig, err := m.Config()
79 if err != nil {
80 return err
81 }
82 all := map[string]any{
83 "Global": globalConfig.Values,
84 "Values": config,
85 }
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040086 appsRoot, err := wt.Filesystem.Chroot(appDirName)
87 if err != nil {
88 return err
89 }
90 rootKustF, err := appsRoot.Open(kustomizationFileName)
91 if err != nil {
92 return err
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040093 }
94 defer rootKustF.Close()
95 rootKust, err := ReadKustomization(rootKustF)
96 if err != nil {
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040097 return err
98 }
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040099 appRoot, err := appsRoot.Chroot(app.Name)
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400100 if err != nil {
101 return err
102 }
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400103 if err := util.RemoveAll(appRoot, app.Name); err != nil {
104 return err
105 }
106 if err := appRoot.MkdirAll(app.Name, fs.ModePerm); err != nil {
107 return nil
108 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400109 appKust := NewKustomization()
110 for _, t := range app.Templates {
111 out, err := appRoot.Create(t.Name())
112 if err != nil {
113 return err
114 }
115 defer out.Close()
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400116 if err := t.Execute(out, all); err != nil {
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400117 return err
118 }
119 appKust.Resources = append(appKust.Resources, t.Name())
120 }
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400121 {
122 out, err := appRoot.Create(configFileName)
123 if err != nil {
124 return err
125 }
126 defer out.Close()
127 configBytes, err := yaml.Marshal(config)
128 if err != nil {
129 return err
130 }
131 if _, err := out.Write(configBytes); err != nil {
132 return err
133 }
134 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400135 appKustF, err := appRoot.Create(kustomizationFileName)
136 if err != nil {
137 return err
138 }
139 defer appKustF.Close()
140 if err := appKust.Write(appKustF); err != nil {
141 return err
142 }
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400143 if !slices.Contains(rootKust.Resources, app.Name) {
144 rootKust.Resources = append(rootKust.Resources, app.Name)
145 rootKustFW, err := appsRoot.Create(kustomizationFileName)
146 if err != nil {
147 return err
148 }
149 defer rootKustFW.Close()
150 if err := rootKust.Write(rootKustFW); err != nil {
151 return err
152 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400153 }
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400154 // Commit and push
155 if err := wt.AddGlob("*"); err != nil {
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400156 return err
157 }
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400158 if _, err := wt.Commit(fmt.Sprintf("install: %s", app.Name), &git.CommitOptions{
159 Author: &object.Signature{
160 Name: "pcloud-appmanager",
161 When: time.Now(),
162 },
163 }); err != nil {
164 return err
165 }
166 return m.repo.Push(&git.PushOptions{
167 RemoteName: "origin",
168 Auth: auth(m.signer),
169 })
170}
171
172func auth(signer ssh.Signer) *gitssh.PublicKeys {
173 return &gitssh.PublicKeys{
174 Signer: signer,
175 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
176 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
177 // TODO(giolekva): verify server public key
178 // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
179 return nil
180 },
181 },
182 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400183}