blob: 6a5097a357fba2b50a4a0c406d7dec2424a0fd6c [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"
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
gioe72b54f2024-04-22 10:44:41 +040014 pio "github.com/giolekva/pcloud/core/installer/io"
15
gio3af43942024-04-16 08:13:50 +040016 "github.com/go-git/go-billy/v5"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040017 "github.com/go-git/go-billy/v5/util"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040018 "github.com/go-git/go-git/v5"
19 "github.com/go-git/go-git/v5/plumbing/object"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040020 gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040021 "golang.org/x/crypto/ssh"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040022 "sigs.k8s.io/yaml"
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
gioe72b54f2024-04-22 10:44:41 +040058func NewBillyRepoFS(fs billy.Filesystem) RepoFS {
59 return &repoFS{fs}
60}
61
gio3af43942024-04-16 08:13:50 +040062func (r *repoFS) Reader(path string) (io.ReadCloser, error) {
63 return r.fs.Open(path)
64}
65
66func (r *repoFS) Writer(path string) (io.WriteCloser, error) {
67 if err := r.fs.MkdirAll(filepath.Dir(path), fs.ModePerm); err != nil {
68 return nil, err
69 }
70 return r.fs.Create(path)
71}
72
73func (r *repoFS) CreateDir(path string) error {
74 return r.fs.MkdirAll(path, fs.ModePerm)
75}
76
77func (r *repoFS) RemoveDir(path string) error {
78 if err := util.RemoveAll(r.fs, path); err != nil {
79 if errors.Is(err, fs.ErrNotExist) {
80 return nil
81 }
82 return err
83 }
84 return nil
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040085}
86
87type repoIO struct {
gio3af43942024-04-16 08:13:50 +040088 *repoFS
gioe72b54f2024-04-22 10:44:41 +040089 repo *Repository
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040090 signer ssh.Signer
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040091 l sync.Locker
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040092}
93
gioe72b54f2024-04-22 10:44:41 +040094func NewRepoIO(repo *Repository, signer ssh.Signer) (RepoIO, error) {
gio3af43942024-04-16 08:13:50 +040095 wt, err := repo.Worktree()
96 if err != nil {
97 return nil, err
98 }
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040099 return &repoIO{
gio3af43942024-04-16 08:13:50 +0400100 &repoFS{wt.Filesystem},
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400101 repo,
102 signer,
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400103 &sync.Mutex{},
gio3af43942024-04-16 08:13:50 +0400104 }, nil
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400105}
106
gio3af43942024-04-16 08:13:50 +0400107func (r *repoIO) FullAddress() string {
108 return r.repo.Addr.FullAddress()
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +0400109}
110
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +0400111func (r *repoIO) Pull() error {
112 r.l.Lock()
113 defer r.l.Unlock()
114 return r.pullWithoutLock()
115}
116
117func (r *repoIO) pullWithoutLock() error {
118 wt, err := r.repo.Worktree()
119 if err != nil {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400120 return nil
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400121 }
gio3cdee592024-04-17 10:15:56 +0400122 err = wt.Pull(&git.PullOptions{
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +0400123 Auth: auth(r.signer),
124 Force: true,
125 })
gio3cdee592024-04-17 10:15:56 +0400126 if err == nil {
127 return nil
128 }
129 if errors.Is(err, git.NoErrAlreadyUpToDate) {
130 return nil
131 }
132 // TODO(gio): check `remote repository is empty`
133 return nil
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400134}
135
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400136func (r *repoIO) CommitAndPush(message string) error {
137 wt, err := r.repo.Worktree()
138 if err != nil {
139 return err
140 }
141 if err := wt.AddGlob("*"); err != nil {
142 return err
143 }
144 if _, err := wt.Commit(message, &git.CommitOptions{
145 Author: &object.Signature{
146 Name: "pcloud-installer",
147 When: time.Now(),
148 },
149 }); err != nil {
150 return err
151 }
152 return r.repo.Push(&git.PushOptions{
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +0400153 RemoteName: "origin",
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400154 Auth: auth(r.signer),
155 })
156}
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400157
gio308105e2024-04-19 13:12:13 +0400158func (r *repoIO) Do(op DoFn, opts ...DoOption) error {
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400159 r.l.Lock()
160 defer r.l.Unlock()
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +0400161 if err := r.pullWithoutLock(); err != nil {
162 return err
163 }
gio308105e2024-04-19 13:12:13 +0400164 o := &doOptions{}
165 for _, i := range opts {
166 i(o)
167 }
gio3af43942024-04-16 08:13:50 +0400168 if msg, err := op(r); err != nil {
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400169 return err
gio3af43942024-04-16 08:13:50 +0400170 } else {
gio308105e2024-04-19 13:12:13 +0400171 if !o.NoCommit {
172 return r.CommitAndPush(msg)
173 }
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400174 }
gio308105e2024-04-19 13:12:13 +0400175 return nil
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400176}
177
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400178func auth(signer ssh.Signer) *gitssh.PublicKeys {
179 return &gitssh.PublicKeys{
180 Signer: signer,
181 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
182 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
183 // TODO(giolekva): verify server public key
184 // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
185 return nil
186 },
187 },
188 }
189}
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400190
gio3af43942024-04-16 08:13:50 +0400191func ReadYaml[T any](repo RepoFS, path string, o *T) error {
192 r, err := repo.Reader(path)
193 if err != nil {
194 return err
195 }
196 defer r.Close()
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400197 if contents, err := ioutil.ReadAll(r); err != nil {
198 return err
199 } else {
200 return yaml.UnmarshalStrict(contents, o)
201 }
202}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400203
gio3af43942024-04-16 08:13:50 +0400204func WriteYaml(repo RepoFS, path string, data any) error {
gioe72b54f2024-04-22 10:44:41 +0400205 if d, ok := data.(*pio.Kustomization); ok {
gio3af43942024-04-16 08:13:50 +0400206 data = d
207 }
208 out, err := repo.Writer(path)
209 if err != nil {
210 return err
211 }
212 serialized, err := yaml.Marshal(data)
213 if err != nil {
214 return err
215 }
216 if _, err := out.Write(serialized); err != nil {
217 return err
218 }
219 return nil
220}
221
gio308105e2024-04-19 13:12:13 +0400222func ReadJson[T any](repo RepoFS, path string, o *T) error {
223 r, err := repo.Reader(path)
224 if err != nil {
225 return err
226 }
227 defer r.Close()
228 return json.NewDecoder(r).Decode(o)
229}
230
231func WriteJson(repo RepoFS, path string, data any) error {
gioe72b54f2024-04-22 10:44:41 +0400232 if d, ok := data.(*pio.Kustomization); ok {
gio308105e2024-04-19 13:12:13 +0400233 data = d
234 }
235 w, err := repo.Writer(path)
236 if err != nil {
237 return err
238 }
239 e := json.NewEncoder(w)
240 e.SetIndent("", "\t")
241 return e.Encode(data)
242}
243
gioe72b54f2024-04-22 10:44:41 +0400244func ReadKustomization(repo RepoFS, path string) (*pio.Kustomization, error) {
245 ret := &pio.Kustomization{}
gio3af43942024-04-16 08:13:50 +0400246 if err := ReadYaml(repo, path, &ret); err != nil {
247 return nil, err
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400248 }
249 return ret, nil
250}