blob: 582b21666a161c186f33096c25318db19fc382bc [file] [log] [blame]
giolekva8aa73e82022-07-09 11:34:39 +04001package soft
2
3import (
4 "fmt"
5 "io/ioutil"
6 "log"
7 "net"
8 "os"
9 "time"
10
11 "github.com/go-git/go-billy/v5/memfs"
12 "github.com/go-git/go-git/v5"
13 "github.com/go-git/go-git/v5/config"
14 "github.com/go-git/go-git/v5/plumbing"
15 "github.com/go-git/go-git/v5/plumbing/object"
16 gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
17 "github.com/go-git/go-git/v5/storage/memory"
18 "golang.org/x/crypto/ssh"
19 "sigs.k8s.io/yaml"
20)
21
22type Client struct {
23 ip string
24 port int
25 signer ssh.Signer
26 log *log.Logger
27}
28
29func NewClient(ip string, port int, clientPrivateKey []byte, log *log.Logger) (*Client, error) {
30 signer, err := ssh.ParsePrivateKey(clientPrivateKey)
31 if err != nil {
32 return nil, err
33 }
34 log.SetPrefix("SOFT-SERVE")
35 return &Client{
36 ip,
37 port,
38 signer,
39 log,
40 }, nil
41}
42
43func (ss *Client) UpdateConfig(config Config, reason string) error {
44 log.Print("Updating configuration")
45 configBytes, err := yaml.Marshal(config)
46 if err != nil {
47 return err
48 }
49 repo, err := ss.cloneRepository("config")
50 if err != nil {
51 return err
52 }
53 wt, err := repo.Worktree()
54 if err != nil {
55 return err
56 }
57 if err := ss.writeFile(wt, "config.yaml", string(configBytes)); err != nil {
58 return err
59 }
60 if err := ss.commit(wt, reason); err != nil {
61 return nil
62 }
63 return ss.push(repo)
64}
65
66func (ss *Client) ReloadConfig() error {
67 log.Print("Reloading configuration")
68 client, err := ssh.Dial("tcp", ss.addressSSH(), ss.sshClientConfig())
69 if err != nil {
70 return err
71 }
72 session, err := client.NewSession()
73 if err != nil {
74 return err
75 }
76 defer session.Close()
77 return session.Run("reload")
78}
79
80func (ss *Client) AddRepository(name, readme string) error {
81 log.Printf("Adding repository %s", name)
82 repo, err := git.Init(memory.NewStorage(), memfs.New())
83 if err != nil {
84 return err
85 }
86 wt, err := repo.Worktree()
87 if err != nil {
88 return err
89 }
90 if err := ss.writeFile(wt, "README.md", readme); err != nil {
91 return err
92 }
93 if err := ss.commit(wt, "init"); err != nil {
94 return err
95 }
96 if _, err := repo.CreateRemote(&config.RemoteConfig{
97 Name: "soft",
98 URLs: []string{ss.repoPathByName(name)},
99 }); err != nil {
100 return err
101 }
102 return ss.push(repo)
103}
104
105func (ss *Client) CreateRepository(name string) error {
106 log.Printf("Creating repository %s", name)
107 configRepo, err := ss.getConfigRepo()
108 if err != nil {
109 return err
110 }
111 wt, err := configRepo.Worktree()
112 if err != nil {
113 return err
114 }
115 fmt.Println("aaaa")
116 b, _ := configRepo.Branches()
117 b.ForEach(func(r *plumbing.Reference) error {
118 fmt.Println(r.Name())
119 return nil
120 })
121 if err = wt.Checkout(&git.CheckoutOptions{
122 Branch: "refs/heads/master",
123 }); err != nil {
124 return err
125 }
126 fmt.Println("bbb")
127 f, err := wt.Filesystem.Open("config.yaml")
128 if err != nil {
129 return err
130 }
131 defer f.Close()
132 configBytes, err := ioutil.ReadAll(f)
133 if err != nil {
134 return err
135 }
136 config := make(map[string]interface{})
137 if err := yaml.Unmarshal(configBytes, &config); err != nil {
138 return err
139 }
140 repos := config["repos"].([]interface{})
141 repos = append(repos, map[string]interface{}{
142 "name": name,
143 "repo": name,
144 "private": true,
145 "note": fmt.Sprintf("PCloud env for %s", name),
146 })
147 config["repos"] = repos
148 configBytes, err = yaml.Marshal(config)
149 if err != nil {
150 return err
151 }
152 if err := ss.writeFile(wt, "config.yaml", string(configBytes)); err != nil {
153 return err
154 }
155 if err := ss.commit(wt, fmt.Sprintf("add-repo: %s", name)); err != nil {
156 return err
157 }
158 return ss.push(configRepo)
159}
160
161func (ss *Client) getConfigRepo() (*git.Repository, error) {
162 return git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
163 URL: ss.addressGit(),
164 Auth: ss.authGit(),
165 RemoteName: "soft",
166 ReferenceName: "refs/heads/master",
167 Depth: 1,
168 InsecureSkipTLS: true,
169 Progress: os.Stdout,
170 })
171}
172
173func (ss *Client) repoPathByName(name string) string {
174 return fmt.Sprintf("%s/%s", ss.addressGit(), name)
175}
176
177func (ss *Client) commit(wt *git.Worktree, message string) error {
178 _, err := wt.Commit(message, &git.CommitOptions{
179 Author: &object.Signature{
180 Name: "pcloud",
181 Email: "pcloud@installer",
182 When: time.Now(),
183 },
184 })
185 return err
186}
187
188func (ss *Client) push(repo *git.Repository) error {
189 return repo.Push(&git.PushOptions{
190 RemoteName: "soft",
191 Auth: ss.authGit(),
192 })
193}
194
195func (ss *Client) writeFile(wt *git.Worktree, path, contents string) error {
196 f, err := wt.Filesystem.Create(path)
197 if err != nil {
198 return err
199 }
200 defer f.Close()
201 if _, err = f.Write([]byte(contents)); err != nil {
202 return err
203 }
204 _, err = wt.Add(path)
205 return err
206}
207
208func (ss *Client) cloneRepository(name string) (*git.Repository, error) {
209 return git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
210 URL: ss.repoPathByName(name),
211 Auth: ss.authGit(),
212 RemoteName: "soft",
213 InsecureSkipTLS: true,
214 })
215}
216
217func (ss *Client) authGit() *gitssh.PublicKeys {
218 return &gitssh.PublicKeys{
219 Signer: ss.signer,
220 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
221 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
222 // TODO(giolekva): verify server public key
223 // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
224 return nil
225 },
226 },
227 }
228}
229
230func (ss *Client) sshClientConfig() *ssh.ClientConfig {
231 return &ssh.ClientConfig{
232 Auth: []ssh.AuthMethod{
233 ssh.PublicKeys(ss.signer),
234 },
235 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
236 // TODO(giolekva): verify server public key
237 // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
238 return nil
239 },
240 }
241}
242
243func (ss *Client) addressGit() string {
244 return fmt.Sprintf("ssh://%s:%d", ss.ip, ss.port)
245}
246
247func (ss *Client) addressSSH() string {
248 return fmt.Sprintf("%s:%d", ss.ip, ss.port)
249}