blob: de2f75a73d459a5bd03ca5f48f4170ea498f739d [file] [log] [blame]
gioe72b54f2024-04-22 10:44:41 +04001package soft
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +04002
3import (
gio308105e2024-04-19 13:12:13 +04004 "encoding/json"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +04005 "errors"
gio0eaf2712024-04-14 13:08:46 +04006 "fmt"
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +04007 "io"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +04008 "io/fs"
Giorgi Lekveishvili76951482023-06-30 23:25:09 +04009 "io/ioutil"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040010 "net"
gio0eaf2712024-04-14 13:08:46 +040011 "os"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040012 "path/filepath"
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040013 "sync"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040014 "time"
15
gioe72b54f2024-04-22 10:44:41 +040016 pio "github.com/giolekva/pcloud/core/installer/io"
17
gio3af43942024-04-16 08:13:50 +040018 "github.com/go-git/go-billy/v5"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040019 "github.com/go-git/go-billy/v5/util"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040020 "github.com/go-git/go-git/v5"
gio0eaf2712024-04-14 13:08:46 +040021 "github.com/go-git/go-git/v5/config"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040022 "github.com/go-git/go-git/v5/plumbing/object"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040023 gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040024 "golang.org/x/crypto/ssh"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040025 "sigs.k8s.io/yaml"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040026)
27
gio3af43942024-04-16 08:13:50 +040028type RepoFS interface {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040029 Reader(path string) (io.ReadCloser, error)
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +040030 Writer(path string) (io.WriteCloser, error)
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040031 CreateDir(path string) error
giof6ad2982024-08-23 17:42:49 +040032 RemoveAll(path string) error
giof8843412024-05-22 16:38:05 +040033 ListDir(path string) ([]os.FileInfo, error)
gio3af43942024-04-16 08:13:50 +040034}
35
gio308105e2024-04-19 13:12:13 +040036type DoFn func(r RepoFS) (string, error)
37
38type doOptions struct {
giof71a0832024-06-27 14:45:45 +040039 NoPull bool
gio308105e2024-04-19 13:12:13 +040040 NoCommit bool
gio0eaf2712024-04-14 13:08:46 +040041 Force bool
42 ToBranch string
gio9d66f322024-07-06 13:45:10 +040043 NoLock bool
gio308105e2024-04-19 13:12:13 +040044}
45
46type DoOption func(*doOptions)
47
gio9d66f322024-07-06 13:45:10 +040048func WithNoLock() DoOption {
49 return func(o *doOptions) {
50 o.NoLock = true
51 }
52}
53
giof71a0832024-06-27 14:45:45 +040054func WithNoPull() DoOption {
55 return func(o *doOptions) {
56 o.NoPull = true
57 }
58}
59
gio308105e2024-04-19 13:12:13 +040060func WithNoCommit() DoOption {
61 return func(o *doOptions) {
62 o.NoCommit = true
63 }
64}
gio3af43942024-04-16 08:13:50 +040065
gio0eaf2712024-04-14 13:08:46 +040066func WithForce() DoOption {
67 return func(o *doOptions) {
68 o.Force = true
69 }
70}
71
72func WithCommitToBranch(branch string) DoOption {
73 return func(o *doOptions) {
74 o.ToBranch = branch
75 }
76}
77
78type pushOptions struct {
79 ToBranch string
80 Force bool
81}
82
83type PushOption func(*pushOptions)
84
85func WithToBranch(branch string) PushOption {
86 return func(o *pushOptions) {
87 o.ToBranch = branch
88 }
89}
90
91func PushWithForce() PushOption {
92 return func(o *pushOptions) {
93 o.Force = true
94 }
95}
96
gio3af43942024-04-16 08:13:50 +040097type RepoIO interface {
98 RepoFS
99 FullAddress() string
100 Pull() error
giob4a3a192024-08-19 09:55:47 +0400101 CommitAndPush(message string, opts ...PushOption) (string, error)
102 Do(op DoFn, opts ...DoOption) (string, error)
gio3af43942024-04-16 08:13:50 +0400103}
104
105type repoFS struct {
106 fs billy.Filesystem
107}
108
gioe72b54f2024-04-22 10:44:41 +0400109func NewBillyRepoFS(fs billy.Filesystem) RepoFS {
110 return &repoFS{fs}
111}
112
gio3af43942024-04-16 08:13:50 +0400113func (r *repoFS) Reader(path string) (io.ReadCloser, error) {
114 return r.fs.Open(path)
115}
116
117func (r *repoFS) Writer(path string) (io.WriteCloser, error) {
gio9d66f322024-07-06 13:45:10 +0400118 if err := r.CreateDir(filepath.Dir(path)); err != nil {
gio3af43942024-04-16 08:13:50 +0400119 return nil, err
120 }
121 return r.fs.Create(path)
122}
123
124func (r *repoFS) CreateDir(path string) error {
125 return r.fs.MkdirAll(path, fs.ModePerm)
126}
127
giof6ad2982024-08-23 17:42:49 +0400128func (r *repoFS) RemoveAll(path string) error {
gio3af43942024-04-16 08:13:50 +0400129 if err := util.RemoveAll(r.fs, path); err != nil {
130 if errors.Is(err, fs.ErrNotExist) {
131 return nil
132 }
133 return err
134 }
135 return nil
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400136}
137
giof8843412024-05-22 16:38:05 +0400138func (r *repoFS) ListDir(path string) ([]os.FileInfo, error) {
139 return r.fs.ReadDir(path)
140}
141
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400142type repoIO struct {
gio3af43942024-04-16 08:13:50 +0400143 *repoFS
gioe72b54f2024-04-22 10:44:41 +0400144 repo *Repository
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400145 signer ssh.Signer
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400146 l sync.Locker
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400147}
148
gioe72b54f2024-04-22 10:44:41 +0400149func NewRepoIO(repo *Repository, signer ssh.Signer) (RepoIO, error) {
gio3af43942024-04-16 08:13:50 +0400150 wt, err := repo.Worktree()
151 if err != nil {
152 return nil, err
153 }
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400154 return &repoIO{
gio3af43942024-04-16 08:13:50 +0400155 &repoFS{wt.Filesystem},
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400156 repo,
157 signer,
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400158 &sync.Mutex{},
gio3af43942024-04-16 08:13:50 +0400159 }, nil
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400160}
161
gio3af43942024-04-16 08:13:50 +0400162func (r *repoIO) FullAddress() string {
163 return r.repo.Addr.FullAddress()
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +0400164}
165
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +0400166func (r *repoIO) Pull() error {
167 r.l.Lock()
168 defer r.l.Unlock()
169 return r.pullWithoutLock()
170}
171
172func (r *repoIO) pullWithoutLock() error {
173 wt, err := r.repo.Worktree()
174 if err != nil {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400175 return nil
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400176 }
gio3cdee592024-04-17 10:15:56 +0400177 err = wt.Pull(&git.PullOptions{
gio0eaf2712024-04-14 13:08:46 +0400178 Auth: auth(r.signer),
179 Force: true,
180 Progress: os.Stdout,
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +0400181 })
gio3cdee592024-04-17 10:15:56 +0400182 if err == nil {
183 return nil
184 }
185 if errors.Is(err, git.NoErrAlreadyUpToDate) {
186 return nil
187 }
188 // TODO(gio): check `remote repository is empty`
giof5ffedb2024-06-19 14:14:43 +0400189 fmt.Printf("-- GIT PULL: %s\n", err.Error())
gio3cdee592024-04-17 10:15:56 +0400190 return nil
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400191}
192
giob4a3a192024-08-19 09:55:47 +0400193func (r *repoIO) CommitAndPush(message string, opts ...PushOption) (string, error) {
gio0eaf2712024-04-14 13:08:46 +0400194 var o pushOptions
195 for _, i := range opts {
196 i(&o)
197 }
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400198 wt, err := r.repo.Worktree()
199 if err != nil {
giob4a3a192024-08-19 09:55:47 +0400200 return "", err
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400201 }
202 if err := wt.AddGlob("*"); err != nil {
giob4a3a192024-08-19 09:55:47 +0400203 return "", err
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400204 }
gio03fd0c72024-06-18 12:31:42 +0400205 st, err := wt.Status()
206 if err != nil {
giob4a3a192024-08-19 09:55:47 +0400207 return "", err
gio03fd0c72024-06-18 12:31:42 +0400208 }
209 if len(st) == 0 {
giob4a3a192024-08-19 09:55:47 +0400210 return "", nil // TODO(gio): maybe return ErrorNothingToCommit
gio03fd0c72024-06-18 12:31:42 +0400211 }
gio7fbd4ad2024-08-27 10:06:39 +0400212 fmt.Printf("@@@ %+v\n", st)
giob4a3a192024-08-19 09:55:47 +0400213 hash, err := wt.Commit(message, &git.CommitOptions{
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400214 Author: &object.Signature{
215 Name: "pcloud-installer",
216 When: time.Now(),
217 },
giob4a3a192024-08-19 09:55:47 +0400218 })
219 if err != nil {
220 return "", err
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400221 }
gio0eaf2712024-04-14 13:08:46 +0400222 gopts := &git.PushOptions{
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +0400223 RemoteName: "origin",
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400224 Auth: auth(r.signer),
gio0eaf2712024-04-14 13:08:46 +0400225 }
226 if o.ToBranch != "" {
gio7fbd4ad2024-08-27 10:06:39 +0400227 gopts.RefSpecs = []config.RefSpec{config.RefSpec(fmt.Sprintf("%s:refs/heads/%s", r.repo.Ref, o.ToBranch))}
gio0eaf2712024-04-14 13:08:46 +0400228 }
229 if o.Force {
230 gopts.Force = true
231 }
gio7fbd4ad2024-08-27 10:06:39 +0400232 fmt.Println(3333)
giob4a3a192024-08-19 09:55:47 +0400233 return hash.String(), r.repo.Push(gopts)
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400234}
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400235
giob4a3a192024-08-19 09:55:47 +0400236func (r *repoIO) Do(op DoFn, opts ...DoOption) (string, error) {
gio308105e2024-04-19 13:12:13 +0400237 o := &doOptions{}
238 for _, i := range opts {
239 i(o)
240 }
gio9d66f322024-07-06 13:45:10 +0400241 if o.NoLock {
242 r.l.Lock()
243 defer r.l.Unlock()
244 }
giof71a0832024-06-27 14:45:45 +0400245 if !o.NoPull {
246 if err := r.pullWithoutLock(); err != nil {
giob4a3a192024-08-19 09:55:47 +0400247 return "", err
giof71a0832024-06-27 14:45:45 +0400248 }
249 }
gio9d66f322024-07-06 13:45:10 +0400250 msg, err := op(r)
251 if err != nil {
giob4a3a192024-08-19 09:55:47 +0400252 return "", err
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400253 }
gio9d66f322024-07-06 13:45:10 +0400254 if o.NoCommit {
giob4a3a192024-08-19 09:55:47 +0400255 return "", nil
gio9d66f322024-07-06 13:45:10 +0400256 }
257 popts := []PushOption{}
258 if o.Force {
259 popts = append(popts, PushWithForce())
260 }
261 if o.ToBranch != "" {
262 popts = append(popts, WithToBranch(o.ToBranch))
263 }
gio7fbd4ad2024-08-27 10:06:39 +0400264 fmt.Println(2222)
gio9d66f322024-07-06 13:45:10 +0400265 return r.CommitAndPush(msg, popts...)
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400266}
267
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400268func auth(signer ssh.Signer) *gitssh.PublicKeys {
269 return &gitssh.PublicKeys{
270 Signer: signer,
271 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
272 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
273 // TODO(giolekva): verify server public key
274 // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
275 return nil
276 },
277 },
278 }
279}
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400280
gio3af43942024-04-16 08:13:50 +0400281func ReadYaml[T any](repo RepoFS, path string, o *T) error {
282 r, err := repo.Reader(path)
283 if err != nil {
284 return err
285 }
286 defer r.Close()
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400287 if contents, err := ioutil.ReadAll(r); err != nil {
288 return err
289 } else {
290 return yaml.UnmarshalStrict(contents, o)
291 }
292}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400293
gio3af43942024-04-16 08:13:50 +0400294func WriteYaml(repo RepoFS, path string, data any) error {
gioe72b54f2024-04-22 10:44:41 +0400295 if d, ok := data.(*pio.Kustomization); ok {
gio3af43942024-04-16 08:13:50 +0400296 data = d
297 }
298 out, err := repo.Writer(path)
299 if err != nil {
300 return err
301 }
gio94904702024-07-26 16:58:34 +0400302 defer out.Close()
gio3af43942024-04-16 08:13:50 +0400303 serialized, err := yaml.Marshal(data)
304 if err != nil {
305 return err
306 }
307 if _, err := out.Write(serialized); err != nil {
308 return err
309 }
310 return nil
311}
312
gio308105e2024-04-19 13:12:13 +0400313func ReadJson[T any](repo RepoFS, path string, o *T) error {
314 r, err := repo.Reader(path)
315 if err != nil {
316 return err
317 }
318 defer r.Close()
319 return json.NewDecoder(r).Decode(o)
320}
321
322func WriteJson(repo RepoFS, path string, data any) error {
gioe72b54f2024-04-22 10:44:41 +0400323 if d, ok := data.(*pio.Kustomization); ok {
gio308105e2024-04-19 13:12:13 +0400324 data = d
325 }
326 w, err := repo.Writer(path)
327 if err != nil {
328 return err
329 }
330 e := json.NewEncoder(w)
331 e.SetIndent("", "\t")
332 return e.Encode(data)
333}
334
gioe72b54f2024-04-22 10:44:41 +0400335func ReadKustomization(repo RepoFS, path string) (*pio.Kustomization, error) {
336 ret := &pio.Kustomization{}
gio3af43942024-04-16 08:13:50 +0400337 if err := ReadYaml(repo, path, &ret); err != nil {
338 return nil, err
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400339 }
340 return ret, nil
341}
gio0eaf2712024-04-14 13:08:46 +0400342
343func ReadFile(repo RepoFS, path string) ([]byte, error) {
344 r, err := repo.Reader(path)
345 if err != nil {
346 return nil, err
347 }
348 defer r.Close()
349 return io.ReadAll(r)
350}
gio5e49bb62024-07-20 10:43:19 +0400351
352func WriteFile(repo RepoFS, path, contents string) error {
353 w, err := repo.Writer(path)
354 if err != nil {
355 return err
356 }
357 defer w.Close()
358 _, err = io.WriteString(w, contents)
359 return err
360}