blob: 671bfb8c8d0424ec9c22f0677628811d766dc76e [file] [log] [blame]
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +04001package installer
2
3import (
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +04004 "errors"
5 "fmt"
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +04006 "io"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +04007 "io/fs"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +04008 "net"
9 "path"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040010 "path/filepath"
11 "time"
12
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040013 "github.com/go-git/go-billy/v5/util"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040014 "github.com/go-git/go-git/v5"
15 "github.com/go-git/go-git/v5/plumbing/object"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040016 gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040017 "golang.org/x/crypto/ssh"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040018 "sigs.k8s.io/yaml"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040019)
20
21type RepoIO interface {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040022 Fetch() error
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +040023 ReadConfig() (Config, error)
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040024 ReadKustomization(path string) (*Kustomization, error)
25 WriteKustomization(path string, kust Kustomization) error
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040026 WriteYaml(path string, data any) error
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040027 CommitAndPush(message string) error
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040028 Reader(path string) (io.ReadCloser, error)
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +040029 Writer(path string) (io.WriteCloser, error)
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040030 CreateDir(path string) error
31 RemoveDir(path string) error
32 InstallApp(app App, path string, values map[string]any) error
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040033}
34
35type repoIO struct {
36 repo *git.Repository
37 signer ssh.Signer
38}
39
40func NewRepoIO(repo *git.Repository, signer ssh.Signer) RepoIO {
41 return &repoIO{
42 repo,
43 signer,
44 }
45}
46
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040047func (r *repoIO) Fetch() error {
48 err := r.repo.Fetch(&git.FetchOptions{
49 RemoteName: "origin",
50 Auth: auth(r.signer),
51 Force: true,
52 })
53 if err == nil || err == git.NoErrAlreadyUpToDate {
54 return nil
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040055 }
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040056 return err
57}
58
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +040059func (r *repoIO) ReadConfig() (Config, error) {
60 configF, err := r.Reader(configFileName)
61 if err != nil {
62 return Config{}, err
63 }
64 defer configF.Close()
65 return ReadConfig(configF)
66}
67
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040068func (r *repoIO) ReadKustomization(path string) (*Kustomization, error) {
69 inp, err := r.Reader(path)
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040070 if err != nil {
71 return nil, err
72 }
73 defer inp.Close()
74 return ReadKustomization(inp)
75}
76
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040077func (r *repoIO) Reader(path string) (io.ReadCloser, error) {
78 wt, err := r.repo.Worktree()
79 if err != nil {
80 return nil, err
81 }
82 return wt.Filesystem.Open(path)
83}
84
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +040085func (r *repoIO) Writer(path string) (io.WriteCloser, error) {
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040086 wt, err := r.repo.Worktree()
87 if err != nil {
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +040088 return nil, err
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040089 }
90 if err := wt.Filesystem.MkdirAll(filepath.Dir(path), fs.ModePerm); err != nil {
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +040091 return nil, err
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040092 }
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +040093 return wt.Filesystem.Create(path)
94}
95
96func (r *repoIO) WriteKustomization(path string, kust Kustomization) error {
97 out, err := r.Writer(path)
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040098 if err != nil {
99 return err
100 }
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400101 return kust.Write(out)
102}
103
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400104func (r *repoIO) WriteYaml(path string, data any) error {
105 out, err := r.Writer(path)
106 if err != nil {
107 return err
108 }
109 serialized, err := yaml.Marshal(data)
110 if err != nil {
111 return err
112 }
113 if _, err := out.Write(serialized); err != nil {
114 return err
115 }
116 return nil
117}
118
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400119func (r *repoIO) CommitAndPush(message string) error {
120 wt, err := r.repo.Worktree()
121 if err != nil {
122 return err
123 }
124 if err := wt.AddGlob("*"); err != nil {
125 return err
126 }
127 if _, err := wt.Commit(message, &git.CommitOptions{
128 Author: &object.Signature{
129 Name: "pcloud-installer",
130 When: time.Now(),
131 },
132 }); err != nil {
133 return err
134 }
135 return r.repo.Push(&git.PushOptions{
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +0400136 RemoteName: "origin",
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400137 Auth: auth(r.signer),
138 })
139}
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400140
141func (r *repoIO) CreateDir(path string) error {
142 wt, err := r.repo.Worktree()
143 if err != nil {
144 return err
145 }
146 return wt.Filesystem.MkdirAll(path, fs.ModePerm)
147}
148
149func (r *repoIO) RemoveDir(path string) error {
150 wt, err := r.repo.Worktree()
151 if err != nil {
152 return err
153 }
154 err = util.RemoveAll(wt.Filesystem, path)
155 if err == nil || errors.Is(err, fs.ErrNotExist) {
156 return nil
157 }
158 return err
159}
160
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400161func (r *repoIO) InstallApp(app App, appRootDir string, values map[string]any) error {
162 if !filepath.IsAbs(appRootDir) {
163 return fmt.Errorf("Expected absolute path: %s", appRootDir)
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400164 }
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400165 appRootDir = filepath.Clean(appRootDir)
166 for p := appRootDir; p != "/"; {
167 parent, child := filepath.Split(p)
168 kustPath := filepath.Join(parent, "kustomization.yaml")
169 kust, err := r.ReadKustomization(kustPath)
170 if err != nil {
171 if errors.Is(err, fs.ErrNotExist) {
172 k := NewKustomization()
173 kust = &k
174 } else {
175 return err
176 }
177 }
178 kust.AddResources(child)
179 if err := r.WriteKustomization(kustPath, *kust); err != nil {
180 return err
181 }
182 p = filepath.Clean(parent)
183 }
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400184 {
185 if err := r.RemoveDir(appRootDir); err != nil {
186 return err
187 }
188 if err := r.CreateDir(appRootDir); err != nil {
189 return err
190 }
191 if err := r.WriteYaml(path.Join(appRootDir, configFileName), values); err != nil {
192 return err
193 }
194 }
195 {
196 appKust := NewKustomization()
197 for _, t := range app.Templates {
198 appKust.AddResources(t.Name())
199 out, err := r.Writer(path.Join(appRootDir, t.Name()))
200 if err != nil {
201 return err
202 }
203 defer out.Close()
204 if err := t.Execute(out, values); err != nil {
205 return err
206 }
207 }
208 if err := r.WriteKustomization(path.Join(appRootDir, "kustomization.yaml"), appKust); err != nil {
209 return err
210 }
211 }
212 return r.CommitAndPush(fmt.Sprintf("install: %s", app.Name))
213}
214
215func auth(signer ssh.Signer) *gitssh.PublicKeys {
216 return &gitssh.PublicKeys{
217 Signer: signer,
218 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
219 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
220 // TODO(giolekva): verify server public key
221 // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
222 return nil
223 },
224 },
225 }
226}