blob: c5e80c6dc10ff7f7d04596faa892bf4b252a8e8a [file] [log] [blame]
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +04001package installer
2
3import (
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +04004 "errors"
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +04005 "io"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +04006 "io/fs"
Giorgi Lekveishvili76951482023-06-30 23:25:09 +04007 "io/ioutil"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +04008 "net"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +04009 "path/filepath"
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040010 "sync"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040011 "time"
12
gio3af43942024-04-16 08:13:50 +040013 "github.com/go-git/go-billy/v5"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040014 "github.com/go-git/go-billy/v5/util"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040015 "github.com/go-git/go-git/v5"
16 "github.com/go-git/go-git/v5/plumbing/object"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040017 gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040018 "golang.org/x/crypto/ssh"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040019 "sigs.k8s.io/yaml"
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +040020
21 "github.com/giolekva/pcloud/core/installer/soft"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040022)
23
gio3af43942024-04-16 08:13:50 +040024type RepoFS interface {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040025 Reader(path string) (io.ReadCloser, error)
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +040026 Writer(path string) (io.WriteCloser, error)
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040027 CreateDir(path string) error
28 RemoveDir(path string) error
gio3af43942024-04-16 08:13:50 +040029}
30
31type AtomicOp func(r RepoFS) (string, error)
32
33type RepoIO interface {
34 RepoFS
35 FullAddress() string
36 Pull() error
37 CommitAndPush(message string) error
38 Atomic(op AtomicOp) error
39}
40
41type repoFS struct {
42 fs billy.Filesystem
43}
44
45func (r *repoFS) Reader(path string) (io.ReadCloser, error) {
46 return r.fs.Open(path)
47}
48
49func (r *repoFS) Writer(path string) (io.WriteCloser, error) {
50 if err := r.fs.MkdirAll(filepath.Dir(path), fs.ModePerm); err != nil {
51 return nil, err
52 }
53 return r.fs.Create(path)
54}
55
56func (r *repoFS) CreateDir(path string) error {
57 return r.fs.MkdirAll(path, fs.ModePerm)
58}
59
60func (r *repoFS) RemoveDir(path string) error {
61 if err := util.RemoveAll(r.fs, path); err != nil {
62 if errors.Is(err, fs.ErrNotExist) {
63 return nil
64 }
65 return err
66 }
67 return nil
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040068}
69
70type repoIO struct {
gio3af43942024-04-16 08:13:50 +040071 *repoFS
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +040072 repo *soft.Repository
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040073 signer ssh.Signer
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040074 l sync.Locker
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040075}
76
gio3af43942024-04-16 08:13:50 +040077func NewRepoIO(repo *soft.Repository, signer ssh.Signer) (RepoIO, error) {
78 wt, err := repo.Worktree()
79 if err != nil {
80 return nil, err
81 }
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040082 return &repoIO{
gio3af43942024-04-16 08:13:50 +040083 &repoFS{wt.Filesystem},
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040084 repo,
85 signer,
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +040086 &sync.Mutex{},
gio3af43942024-04-16 08:13:50 +040087 }, nil
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040088}
89
gio3af43942024-04-16 08:13:50 +040090func (r *repoIO) FullAddress() string {
91 return r.repo.Addr.FullAddress()
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +040092}
93
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +040094func (r *repoIO) Pull() error {
95 r.l.Lock()
96 defer r.l.Unlock()
97 return r.pullWithoutLock()
98}
99
100func (r *repoIO) pullWithoutLock() error {
101 wt, err := r.repo.Worktree()
102 if err != nil {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400103 return nil
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400104 }
gio3cdee592024-04-17 10:15:56 +0400105 err = wt.Pull(&git.PullOptions{
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +0400106 Auth: auth(r.signer),
107 Force: true,
108 })
gio3cdee592024-04-17 10:15:56 +0400109 if err == nil {
110 return nil
111 }
112 if errors.Is(err, git.NoErrAlreadyUpToDate) {
113 return nil
114 }
115 // TODO(gio): check `remote repository is empty`
116 return nil
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400117}
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
gio3af43942024-04-16 08:13:50 +0400141func (r *repoIO) Atomic(op AtomicOp) error {
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400142 r.l.Lock()
143 defer r.l.Unlock()
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +0400144 if err := r.pullWithoutLock(); err != nil {
145 return err
146 }
gio3af43942024-04-16 08:13:50 +0400147 if msg, err := op(r); err != nil {
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400148 return err
gio3af43942024-04-16 08:13:50 +0400149 } else {
150 return r.CommitAndPush(msg)
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400151 }
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400152}
153
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400154func auth(signer ssh.Signer) *gitssh.PublicKeys {
155 return &gitssh.PublicKeys{
156 Signer: signer,
157 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
158 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
159 // TODO(giolekva): verify server public key
160 // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
161 return nil
162 },
163 },
164 }
165}
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400166
gio3af43942024-04-16 08:13:50 +0400167func ReadYaml[T any](repo RepoFS, path string, o *T) error {
168 r, err := repo.Reader(path)
169 if err != nil {
170 return err
171 }
172 defer r.Close()
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400173 if contents, err := ioutil.ReadAll(r); err != nil {
174 return err
175 } else {
176 return yaml.UnmarshalStrict(contents, o)
177 }
178}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400179
gio3af43942024-04-16 08:13:50 +0400180func WriteYaml(repo RepoFS, path string, data any) error {
181 if d, ok := data.(*Kustomization); ok {
182 data = d
183 }
184 out, err := repo.Writer(path)
185 if err != nil {
186 return err
187 }
188 serialized, err := yaml.Marshal(data)
189 if err != nil {
190 return err
191 }
192 if _, err := out.Write(serialized); err != nil {
193 return err
194 }
195 return nil
196}
197
198func ReadKustomization(repo RepoFS, path string) (*Kustomization, error) {
199 ret := &Kustomization{}
200 if err := ReadYaml(repo, path, &ret); err != nil {
201 return nil, err
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400202 }
203 return ret, nil
204}