blob: 4574fee0ab8eb3213449a1146651c727add8d85a [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
161func (r *repoIO) InstallApp(app App, root string, values map[string]any) error {
162 {
163 appsKustPath := path.Join(root, "kustomization.yaml")
164 appsKust, err := r.ReadKustomization(appsKustPath)
165 if err != nil {
166 return err
167 }
168 appsKust.AddResources(app.Name)
169 if err := r.WriteKustomization(appsKustPath, *appsKust); err != nil {
170 return err
171 }
172 }
173 appRootDir := path.Join(root, app.Name)
174 {
175 if err := r.RemoveDir(appRootDir); err != nil {
176 return err
177 }
178 if err := r.CreateDir(appRootDir); err != nil {
179 return err
180 }
181 if err := r.WriteYaml(path.Join(appRootDir, configFileName), values); err != nil {
182 return err
183 }
184 }
185 {
186 appKust := NewKustomization()
187 for _, t := range app.Templates {
188 appKust.AddResources(t.Name())
189 out, err := r.Writer(path.Join(appRootDir, t.Name()))
190 if err != nil {
191 return err
192 }
193 defer out.Close()
194 if err := t.Execute(out, values); err != nil {
195 return err
196 }
197 }
198 if err := r.WriteKustomization(path.Join(appRootDir, "kustomization.yaml"), appKust); err != nil {
199 return err
200 }
201 }
202 return r.CommitAndPush(fmt.Sprintf("install: %s", app.Name))
203}
204
205func auth(signer ssh.Signer) *gitssh.PublicKeys {
206 return &gitssh.PublicKeys{
207 Signer: signer,
208 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
209 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
210 // TODO(giolekva): verify server public key
211 // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
212 return nil
213 },
214 },
215 }
216}