blob: 944a0a275f63102cb28fa1db2b8463f47a349f53 [file] [log] [blame]
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +04001package installer
2
3import (
gio308105e2024-04-19 13:12:13 +04004 "encoding/json"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +04005 "errors"
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +04006 "io"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +04007 "io/fs"
Giorgi Lekveishvili76951482023-06-30 23:25:09 +04008 "io/ioutil"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +04009 "net"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040010 "path/filepath"
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040011 "sync"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040012 "time"
13
gio3af43942024-04-16 08:13:50 +040014 "github.com/go-git/go-billy/v5"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040015 "github.com/go-git/go-billy/v5/util"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040016 "github.com/go-git/go-git/v5"
17 "github.com/go-git/go-git/v5/plumbing/object"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040018 gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040019 "golang.org/x/crypto/ssh"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040020 "sigs.k8s.io/yaml"
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +040021
22 "github.com/giolekva/pcloud/core/installer/soft"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040023)
24
gio3af43942024-04-16 08:13:50 +040025type RepoFS interface {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040026 Reader(path string) (io.ReadCloser, error)
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +040027 Writer(path string) (io.WriteCloser, error)
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040028 CreateDir(path string) error
29 RemoveDir(path string) error
gio3af43942024-04-16 08:13:50 +040030}
31
gio308105e2024-04-19 13:12:13 +040032type DoFn func(r RepoFS) (string, error)
33
34type doOptions struct {
35 NoCommit bool
36}
37
38type DoOption func(*doOptions)
39
40func WithNoCommit() DoOption {
41 return func(o *doOptions) {
42 o.NoCommit = true
43 }
44}
gio3af43942024-04-16 08:13:50 +040045
46type RepoIO interface {
47 RepoFS
48 FullAddress() string
49 Pull() error
50 CommitAndPush(message string) error
gio308105e2024-04-19 13:12:13 +040051 Do(op DoFn, opts ...DoOption) error
gio3af43942024-04-16 08:13:50 +040052}
53
54type repoFS struct {
55 fs billy.Filesystem
56}
57
58func (r *repoFS) Reader(path string) (io.ReadCloser, error) {
59 return r.fs.Open(path)
60}
61
62func (r *repoFS) Writer(path string) (io.WriteCloser, error) {
63 if err := r.fs.MkdirAll(filepath.Dir(path), fs.ModePerm); err != nil {
64 return nil, err
65 }
66 return r.fs.Create(path)
67}
68
69func (r *repoFS) CreateDir(path string) error {
70 return r.fs.MkdirAll(path, fs.ModePerm)
71}
72
73func (r *repoFS) RemoveDir(path string) error {
74 if err := util.RemoveAll(r.fs, path); err != nil {
75 if errors.Is(err, fs.ErrNotExist) {
76 return nil
77 }
78 return err
79 }
80 return nil
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040081}
82
83type repoIO struct {
gio3af43942024-04-16 08:13:50 +040084 *repoFS
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +040085 repo *soft.Repository
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040086 signer ssh.Signer
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040087 l sync.Locker
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040088}
89
gio3af43942024-04-16 08:13:50 +040090func NewRepoIO(repo *soft.Repository, signer ssh.Signer) (RepoIO, error) {
91 wt, err := repo.Worktree()
92 if err != nil {
93 return nil, err
94 }
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040095 return &repoIO{
gio3af43942024-04-16 08:13:50 +040096 &repoFS{wt.Filesystem},
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040097 repo,
98 signer,
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040099 &sync.Mutex{},
gio3af43942024-04-16 08:13:50 +0400100 }, nil
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400101}
102
gio3af43942024-04-16 08:13:50 +0400103func (r *repoIO) FullAddress() string {
104 return r.repo.Addr.FullAddress()
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +0400105}
106
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +0400107func (r *repoIO) Pull() error {
108 r.l.Lock()
109 defer r.l.Unlock()
110 return r.pullWithoutLock()
111}
112
113func (r *repoIO) pullWithoutLock() error {
114 wt, err := r.repo.Worktree()
115 if err != nil {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400116 return nil
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400117 }
gio3cdee592024-04-17 10:15:56 +0400118 err = wt.Pull(&git.PullOptions{
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +0400119 Auth: auth(r.signer),
120 Force: true,
121 })
gio3cdee592024-04-17 10:15:56 +0400122 if err == nil {
123 return nil
124 }
125 if errors.Is(err, git.NoErrAlreadyUpToDate) {
126 return nil
127 }
128 // TODO(gio): check `remote repository is empty`
129 return nil
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400130}
131
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400132func (r *repoIO) CommitAndPush(message string) error {
133 wt, err := r.repo.Worktree()
134 if err != nil {
135 return err
136 }
137 if err := wt.AddGlob("*"); err != nil {
138 return err
139 }
140 if _, err := wt.Commit(message, &git.CommitOptions{
141 Author: &object.Signature{
142 Name: "pcloud-installer",
143 When: time.Now(),
144 },
145 }); err != nil {
146 return err
147 }
148 return r.repo.Push(&git.PushOptions{
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +0400149 RemoteName: "origin",
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400150 Auth: auth(r.signer),
151 })
152}
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400153
gio308105e2024-04-19 13:12:13 +0400154func (r *repoIO) Do(op DoFn, opts ...DoOption) error {
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400155 r.l.Lock()
156 defer r.l.Unlock()
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +0400157 if err := r.pullWithoutLock(); err != nil {
158 return err
159 }
gio308105e2024-04-19 13:12:13 +0400160 o := &doOptions{}
161 for _, i := range opts {
162 i(o)
163 }
gio3af43942024-04-16 08:13:50 +0400164 if msg, err := op(r); err != nil {
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400165 return err
gio3af43942024-04-16 08:13:50 +0400166 } else {
gio308105e2024-04-19 13:12:13 +0400167 if !o.NoCommit {
168 return r.CommitAndPush(msg)
169 }
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400170 }
gio308105e2024-04-19 13:12:13 +0400171 return nil
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400172}
173
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400174func auth(signer ssh.Signer) *gitssh.PublicKeys {
175 return &gitssh.PublicKeys{
176 Signer: signer,
177 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
178 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
179 // TODO(giolekva): verify server public key
180 // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
181 return nil
182 },
183 },
184 }
185}
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400186
gio3af43942024-04-16 08:13:50 +0400187func ReadYaml[T any](repo RepoFS, path string, o *T) error {
188 r, err := repo.Reader(path)
189 if err != nil {
190 return err
191 }
192 defer r.Close()
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400193 if contents, err := ioutil.ReadAll(r); err != nil {
194 return err
195 } else {
196 return yaml.UnmarshalStrict(contents, o)
197 }
198}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400199
gio3af43942024-04-16 08:13:50 +0400200func WriteYaml(repo RepoFS, path string, data any) error {
201 if d, ok := data.(*Kustomization); ok {
202 data = d
203 }
204 out, err := repo.Writer(path)
205 if err != nil {
206 return err
207 }
208 serialized, err := yaml.Marshal(data)
209 if err != nil {
210 return err
211 }
212 if _, err := out.Write(serialized); err != nil {
213 return err
214 }
215 return nil
216}
217
gio308105e2024-04-19 13:12:13 +0400218func ReadJson[T any](repo RepoFS, path string, o *T) error {
219 r, err := repo.Reader(path)
220 if err != nil {
221 return err
222 }
223 defer r.Close()
224 return json.NewDecoder(r).Decode(o)
225}
226
227func WriteJson(repo RepoFS, path string, data any) error {
228 if d, ok := data.(*Kustomization); ok {
229 data = d
230 }
231 w, err := repo.Writer(path)
232 if err != nil {
233 return err
234 }
235 e := json.NewEncoder(w)
236 e.SetIndent("", "\t")
237 return e.Encode(data)
238}
239
gio3af43942024-04-16 08:13:50 +0400240func ReadKustomization(repo RepoFS, path string) (*Kustomization, error) {
241 ret := &Kustomization{}
242 if err := ReadYaml(repo, path, &ret); err != nil {
243 return nil, err
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400244 }
245 return ret, nil
246}