blob: ffb407837477a99c0d3b54faf1b68900ef715648 [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 }
gio3af43942024-04-16 08:13:50 +0400105 return wt.Pull(&git.PullOptions{
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +0400106 Auth: auth(r.signer),
107 Force: true,
108 })
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400109}
110
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400111func (r *repoIO) CommitAndPush(message string) error {
112 wt, err := r.repo.Worktree()
113 if err != nil {
114 return err
115 }
116 if err := wt.AddGlob("*"); err != nil {
117 return err
118 }
119 if _, err := wt.Commit(message, &git.CommitOptions{
120 Author: &object.Signature{
121 Name: "pcloud-installer",
122 When: time.Now(),
123 },
124 }); err != nil {
125 return err
126 }
127 return r.repo.Push(&git.PushOptions{
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +0400128 RemoteName: "origin",
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400129 Auth: auth(r.signer),
130 })
131}
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400132
gio3af43942024-04-16 08:13:50 +0400133func (r *repoIO) Atomic(op AtomicOp) error {
Giorgi Lekveishvili378ea882023-12-12 13:59:18 +0400134 r.l.Lock()
135 defer r.l.Unlock()
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +0400136 if err := r.pullWithoutLock(); err != nil {
137 return err
138 }
gio3af43942024-04-16 08:13:50 +0400139 if msg, err := op(r); err != nil {
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400140 return err
gio3af43942024-04-16 08:13:50 +0400141 } else {
142 return r.CommitAndPush(msg)
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400143 }
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400144}
145
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400146func auth(signer ssh.Signer) *gitssh.PublicKeys {
147 return &gitssh.PublicKeys{
148 Signer: signer,
149 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
150 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
151 // TODO(giolekva): verify server public key
152 // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
153 return nil
154 },
155 },
156 }
157}
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400158
gio3af43942024-04-16 08:13:50 +0400159func ReadYaml[T any](repo RepoFS, path string, o *T) error {
160 r, err := repo.Reader(path)
161 if err != nil {
162 return err
163 }
164 defer r.Close()
Giorgi Lekveishvili76951482023-06-30 23:25:09 +0400165 if contents, err := ioutil.ReadAll(r); err != nil {
166 return err
167 } else {
168 return yaml.UnmarshalStrict(contents, o)
169 }
170}
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400171
gio3af43942024-04-16 08:13:50 +0400172func WriteYaml(repo RepoFS, path string, data any) error {
173 if d, ok := data.(*Kustomization); ok {
174 data = d
175 }
176 out, err := repo.Writer(path)
177 if err != nil {
178 return err
179 }
180 serialized, err := yaml.Marshal(data)
181 if err != nil {
182 return err
183 }
184 if _, err := out.Write(serialized); err != nil {
185 return err
186 }
187 return nil
188}
189
190func ReadKustomization(repo RepoFS, path string) (*Kustomization, error) {
191 ret := &Kustomization{}
192 if err := ReadYaml(repo, path, &ret); err != nil {
193 return nil, err
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400194 }
195 return ret, nil
196}