blob: 97bf9b6cb75f7335ce0e3c3569f5c6088eb0e0d5 [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 Lekveishvili3550b432023-06-09 19:37:51 +040023 ReadKustomization(path string) (*Kustomization, error)
24 WriteKustomization(path string, kust Kustomization) error
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040025 WriteYaml(path string, data any) error
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040026 CommitAndPush(message string) error
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040027 Reader(path string) (io.ReadCloser, error)
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +040028 Writer(path string) (io.WriteCloser, error)
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040029 CreateDir(path string) error
30 RemoveDir(path string) error
31 InstallApp(app App, path string, values map[string]any) error
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040032}
33
34type repoIO struct {
35 repo *git.Repository
36 signer ssh.Signer
37}
38
39func NewRepoIO(repo *git.Repository, signer ssh.Signer) RepoIO {
40 return &repoIO{
41 repo,
42 signer,
43 }
44}
45
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040046func (r *repoIO) Fetch() error {
47 err := r.repo.Fetch(&git.FetchOptions{
48 RemoteName: "origin",
49 Auth: auth(r.signer),
50 Force: true,
51 })
52 if err == nil || err == git.NoErrAlreadyUpToDate {
53 return nil
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040054 }
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040055 return err
56}
57
58func (r *repoIO) ReadKustomization(path string) (*Kustomization, error) {
59 inp, err := r.Reader(path)
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040060 if err != nil {
61 return nil, err
62 }
63 defer inp.Close()
64 return ReadKustomization(inp)
65}
66
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040067func (r *repoIO) Reader(path string) (io.ReadCloser, error) {
68 wt, err := r.repo.Worktree()
69 if err != nil {
70 return nil, err
71 }
72 return wt.Filesystem.Open(path)
73}
74
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +040075func (r *repoIO) Writer(path string) (io.WriteCloser, error) {
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040076 wt, err := r.repo.Worktree()
77 if err != nil {
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +040078 return nil, err
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040079 }
80 if err := wt.Filesystem.MkdirAll(filepath.Dir(path), fs.ModePerm); err != nil {
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +040081 return nil, err
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040082 }
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +040083 return wt.Filesystem.Create(path)
84}
85
86func (r *repoIO) WriteKustomization(path string, kust Kustomization) error {
87 out, err := r.Writer(path)
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040088 if err != nil {
89 return err
90 }
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040091 return kust.Write(out)
92}
93
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040094func (r *repoIO) WriteYaml(path string, data any) error {
95 out, err := r.Writer(path)
96 if err != nil {
97 return err
98 }
99 serialized, err := yaml.Marshal(data)
100 if err != nil {
101 return err
102 }
103 if _, err := out.Write(serialized); err != nil {
104 return err
105 }
106 return nil
107}
108
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400109func (r *repoIO) CommitAndPush(message string) error {
110 wt, err := r.repo.Worktree()
111 if err != nil {
112 return err
113 }
114 if err := wt.AddGlob("*"); err != nil {
115 return err
116 }
117 if _, err := wt.Commit(message, &git.CommitOptions{
118 Author: &object.Signature{
119 Name: "pcloud-installer",
120 When: time.Now(),
121 },
122 }); err != nil {
123 return err
124 }
125 return r.repo.Push(&git.PushOptions{
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +0400126 RemoteName: "origin",
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400127 Auth: auth(r.signer),
128 })
129}
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400130
131func (r *repoIO) CreateDir(path string) error {
132 wt, err := r.repo.Worktree()
133 if err != nil {
134 return err
135 }
136 return wt.Filesystem.MkdirAll(path, fs.ModePerm)
137}
138
139func (r *repoIO) RemoveDir(path string) error {
140 wt, err := r.repo.Worktree()
141 if err != nil {
142 return err
143 }
144 err = util.RemoveAll(wt.Filesystem, path)
145 if err == nil || errors.Is(err, fs.ErrNotExist) {
146 return nil
147 }
148 return err
149}
150
151func (r *repoIO) InstallApp(app App, root string, values map[string]any) error {
152 {
153 appsKustPath := path.Join(root, "kustomization.yaml")
154 appsKust, err := r.ReadKustomization(appsKustPath)
155 if err != nil {
156 return err
157 }
158 appsKust.AddResources(app.Name)
159 if err := r.WriteKustomization(appsKustPath, *appsKust); err != nil {
160 return err
161 }
162 }
163 appRootDir := path.Join(root, app.Name)
164 {
165 if err := r.RemoveDir(appRootDir); err != nil {
166 return err
167 }
168 if err := r.CreateDir(appRootDir); err != nil {
169 return err
170 }
171 if err := r.WriteYaml(path.Join(appRootDir, configFileName), values); err != nil {
172 return err
173 }
174 }
175 {
176 appKust := NewKustomization()
177 for _, t := range app.Templates {
178 appKust.AddResources(t.Name())
179 out, err := r.Writer(path.Join(appRootDir, t.Name()))
180 if err != nil {
181 return err
182 }
183 defer out.Close()
184 if err := t.Execute(out, values); err != nil {
185 return err
186 }
187 }
188 if err := r.WriteKustomization(path.Join(appRootDir, "kustomization.yaml"), appKust); err != nil {
189 return err
190 }
191 }
192 return r.CommitAndPush(fmt.Sprintf("install: %s", app.Name))
193}
194
195func auth(signer ssh.Signer) *gitssh.PublicKeys {
196 return &gitssh.PublicKeys{
197 Signer: signer,
198 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
199 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
200 // TODO(giolekva): verify server public key
201 // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
202 return nil
203 },
204 },
205 }
206}