blob: 01d79c7366e66f988fa838b59128f1a2b878b257 [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"
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040013 // "github.com/go-git/go-git/v5/config"
giolekva8aa73e82022-07-09 11:34:39 +040014 "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 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040034 log.SetPrefix("SOFT-SERVE: ")
giolekva8aa73e82022-07-09 11:34:39 +040035 return &Client{
36 ip,
37 port,
38 signer,
39 log,
40 }, nil
41}
42
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040043func (ss *Client) AddUser(name, pubKey string) error {
44 log.Printf("Adding user %s", name)
45 if err := ss.RunCommand(fmt.Sprintf("user create %s", name)); err != nil {
giolekva8aa73e82022-07-09 11:34:39 +040046 return err
47 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040048 return ss.RunCommand(fmt.Sprintf("user add-pubkey %s %s", name, pubKey))
giolekva8aa73e82022-07-09 11:34:39 +040049}
50
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040051func (ss *Client) MakeUserAdmin(name string) error {
52 log.Printf("Making user %s admin", name)
53 return ss.RunCommand(fmt.Sprintf("user set-admin %s true", name))
54}
55
56func (ss *Client) RunCommand(cmd string) error {
57 log.Printf("Running command %s", cmd)
giolekva8aa73e82022-07-09 11:34:39 +040058 client, err := ssh.Dial("tcp", ss.addressSSH(), ss.sshClientConfig())
59 if err != nil {
60 return err
61 }
62 session, err := client.NewSession()
63 if err != nil {
64 return err
65 }
66 defer session.Close()
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040067 return session.Run(cmd)
giolekva8aa73e82022-07-09 11:34:39 +040068}
69
70func (ss *Client) AddRepository(name, readme string) error {
71 log.Printf("Adding repository %s", name)
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040072 return ss.RunCommand(fmt.Sprintf("repo create %s -d \"%s\"", name, readme))
73}
74
75func (ss *Client) AddCollaborator(repo, user string) error {
76 log.Printf("Adding collaborator %s %s", repo, user)
77 return ss.RunCommand(fmt.Sprintf("repo collab add %s %s", repo, user))
giolekva8aa73e82022-07-09 11:34:39 +040078}
79
80func (ss *Client) CreateRepository(name string) error {
81 log.Printf("Creating repository %s", name)
82 configRepo, err := ss.getConfigRepo()
83 if err != nil {
84 return err
85 }
86 wt, err := configRepo.Worktree()
87 if err != nil {
88 return err
89 }
90 fmt.Println("aaaa")
91 b, _ := configRepo.Branches()
92 b.ForEach(func(r *plumbing.Reference) error {
93 fmt.Println(r.Name())
94 return nil
95 })
96 if err = wt.Checkout(&git.CheckoutOptions{
97 Branch: "refs/heads/master",
98 }); err != nil {
99 return err
100 }
101 fmt.Println("bbb")
102 f, err := wt.Filesystem.Open("config.yaml")
103 if err != nil {
104 return err
105 }
106 defer f.Close()
107 configBytes, err := ioutil.ReadAll(f)
108 if err != nil {
109 return err
110 }
111 config := make(map[string]interface{})
112 if err := yaml.Unmarshal(configBytes, &config); err != nil {
113 return err
114 }
115 repos := config["repos"].([]interface{})
116 repos = append(repos, map[string]interface{}{
117 "name": name,
118 "repo": name,
119 "private": true,
120 "note": fmt.Sprintf("PCloud env for %s", name),
121 })
122 config["repos"] = repos
123 configBytes, err = yaml.Marshal(config)
124 if err != nil {
125 return err
126 }
127 if err := ss.writeFile(wt, "config.yaml", string(configBytes)); err != nil {
128 return err
129 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400130 if err := ss.Commit(wt, fmt.Sprintf("add-repo: %s", name)); err != nil {
giolekva8aa73e82022-07-09 11:34:39 +0400131 return err
132 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400133 return ss.Push(configRepo)
giolekva8aa73e82022-07-09 11:34:39 +0400134}
135
136func (ss *Client) getConfigRepo() (*git.Repository, error) {
137 return git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
138 URL: ss.addressGit(),
139 Auth: ss.authGit(),
140 RemoteName: "soft",
141 ReferenceName: "refs/heads/master",
142 Depth: 1,
143 InsecureSkipTLS: true,
144 Progress: os.Stdout,
145 })
146}
147
148func (ss *Client) repoPathByName(name string) string {
149 return fmt.Sprintf("%s/%s", ss.addressGit(), name)
150}
151
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400152func (ss *Client) Commit(wt *git.Worktree, message string) error {
giolekva8aa73e82022-07-09 11:34:39 +0400153 _, err := wt.Commit(message, &git.CommitOptions{
154 Author: &object.Signature{
155 Name: "pcloud",
156 Email: "pcloud@installer",
157 When: time.Now(),
158 },
159 })
160 return err
161}
162
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400163func (ss *Client) Push(repo *git.Repository) error {
giolekva8aa73e82022-07-09 11:34:39 +0400164 return repo.Push(&git.PushOptions{
165 RemoteName: "soft",
166 Auth: ss.authGit(),
167 })
168}
169
170func (ss *Client) writeFile(wt *git.Worktree, path, contents string) error {
171 f, err := wt.Filesystem.Create(path)
172 if err != nil {
173 return err
174 }
175 defer f.Close()
176 if _, err = f.Write([]byte(contents)); err != nil {
177 return err
178 }
179 _, err = wt.Add(path)
180 return err
181}
182
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400183func (ss *Client) CloneRepository(name string) (*git.Repository, error) {
giolekva8aa73e82022-07-09 11:34:39 +0400184 return git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
185 URL: ss.repoPathByName(name),
186 Auth: ss.authGit(),
187 RemoteName: "soft",
188 InsecureSkipTLS: true,
189 })
190}
191
192func (ss *Client) authGit() *gitssh.PublicKeys {
193 return &gitssh.PublicKeys{
194 Signer: ss.signer,
195 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
196 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
197 // TODO(giolekva): verify server public key
198 // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
199 return nil
200 },
201 },
202 }
203}
204
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400205func (ss *Client) GetPublicKey() ([]byte, error) {
206 var ret []byte
207 config := &ssh.ClientConfig{
208 Auth: []ssh.AuthMethod{
209 ssh.PublicKeys(ss.signer),
210 },
211 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
212 ret = ssh.MarshalAuthorizedKey(key)
213 return nil
214 },
215 }
216 _, err := ssh.Dial("tcp", ss.addressSSH(), config)
217 if err != nil {
218 return nil, err
219 }
220 return ret, nil
221}
222
giolekva8aa73e82022-07-09 11:34:39 +0400223func (ss *Client) sshClientConfig() *ssh.ClientConfig {
224 return &ssh.ClientConfig{
225 Auth: []ssh.AuthMethod{
226 ssh.PublicKeys(ss.signer),
227 },
228 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
229 // TODO(giolekva): verify server public key
230 // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400231 fmt.Printf("%s %s %s", hostname, remote, ssh.MarshalAuthorizedKey(key))
giolekva8aa73e82022-07-09 11:34:39 +0400232 return nil
233 },
234 }
235}
236
237func (ss *Client) addressGit() string {
238 return fmt.Sprintf("ssh://%s:%d", ss.ip, ss.port)
239}
240
241func (ss *Client) addressSSH() string {
242 return fmt.Sprintf("%s:%d", ss.ip, ss.port)
243}