blob: 68964d3e5c79d57c5a8a97dcc6d4a7051379498f [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/object"
15 gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
16 "github.com/go-git/go-git/v5/storage/memory"
17 "golang.org/x/crypto/ssh"
18 "sigs.k8s.io/yaml"
19)
20
21type Client struct {
22 ip string
23 port int
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040024 Signer ssh.Signer
giolekva8aa73e82022-07-09 11:34:39 +040025 log *log.Logger
26}
27
28func NewClient(ip string, port int, clientPrivateKey []byte, log *log.Logger) (*Client, error) {
29 signer, err := ssh.ParsePrivateKey(clientPrivateKey)
30 if err != nil {
31 return nil, err
32 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040033 log.SetPrefix("SOFT-SERVE: ")
giolekva8aa73e82022-07-09 11:34:39 +040034 return &Client{
35 ip,
36 port,
37 signer,
38 log,
39 }, nil
40}
41
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040042func (ss *Client) AddUser(name, pubKey string) error {
43 log.Printf("Adding user %s", name)
44 if err := ss.RunCommand(fmt.Sprintf("user create %s", name)); err != nil {
giolekva8aa73e82022-07-09 11:34:39 +040045 return err
46 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040047 return ss.RunCommand(fmt.Sprintf("user add-pubkey %s %s", name, pubKey))
giolekva8aa73e82022-07-09 11:34:39 +040048}
49
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040050func (ss *Client) MakeUserAdmin(name string) error {
51 log.Printf("Making user %s admin", name)
52 return ss.RunCommand(fmt.Sprintf("user set-admin %s true", name))
53}
54
55func (ss *Client) RunCommand(cmd string) error {
56 log.Printf("Running command %s", cmd)
giolekva8aa73e82022-07-09 11:34:39 +040057 client, err := ssh.Dial("tcp", ss.addressSSH(), ss.sshClientConfig())
58 if err != nil {
59 return err
60 }
61 session, err := client.NewSession()
62 if err != nil {
63 return err
64 }
65 defer session.Close()
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040066 return session.Run(cmd)
giolekva8aa73e82022-07-09 11:34:39 +040067}
68
69func (ss *Client) AddRepository(name, readme string) error {
70 log.Printf("Adding repository %s", name)
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040071 return ss.RunCommand(fmt.Sprintf("repo create %s -d \"%s\"", name, readme))
72}
73
74func (ss *Client) AddCollaborator(repo, user string) error {
75 log.Printf("Adding collaborator %s %s", repo, user)
76 return ss.RunCommand(fmt.Sprintf("repo collab add %s %s", repo, user))
giolekva8aa73e82022-07-09 11:34:39 +040077}
78
79func (ss *Client) CreateRepository(name string) error {
80 log.Printf("Creating repository %s", name)
81 configRepo, err := ss.getConfigRepo()
82 if err != nil {
83 return err
84 }
85 wt, err := configRepo.Worktree()
86 if err != nil {
87 return err
88 }
giolekva8aa73e82022-07-09 11:34:39 +040089 if err = wt.Checkout(&git.CheckoutOptions{
90 Branch: "refs/heads/master",
91 }); err != nil {
92 return err
93 }
giolekva8aa73e82022-07-09 11:34:39 +040094 f, err := wt.Filesystem.Open("config.yaml")
95 if err != nil {
96 return err
97 }
98 defer f.Close()
99 configBytes, err := ioutil.ReadAll(f)
100 if err != nil {
101 return err
102 }
103 config := make(map[string]interface{})
104 if err := yaml.Unmarshal(configBytes, &config); err != nil {
105 return err
106 }
107 repos := config["repos"].([]interface{})
108 repos = append(repos, map[string]interface{}{
109 "name": name,
110 "repo": name,
111 "private": true,
112 "note": fmt.Sprintf("PCloud env for %s", name),
113 })
114 config["repos"] = repos
115 configBytes, err = yaml.Marshal(config)
116 if err != nil {
117 return err
118 }
119 if err := ss.writeFile(wt, "config.yaml", string(configBytes)); err != nil {
120 return err
121 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400122 if err := ss.Commit(wt, fmt.Sprintf("add-repo: %s", name)); err != nil {
giolekva8aa73e82022-07-09 11:34:39 +0400123 return err
124 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400125 return ss.Push(configRepo)
giolekva8aa73e82022-07-09 11:34:39 +0400126}
127
128func (ss *Client) getConfigRepo() (*git.Repository, error) {
129 return git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
130 URL: ss.addressGit(),
131 Auth: ss.authGit(),
132 RemoteName: "soft",
133 ReferenceName: "refs/heads/master",
134 Depth: 1,
135 InsecureSkipTLS: true,
136 Progress: os.Stdout,
137 })
138}
139
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400140func (ss *Client) GetRepo(name string) (*git.Repository, error) {
141 return git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
142 URL: fmt.Sprintf("%s/%s", ss.addressGit(), name),
143 Auth: ss.authGit(),
144 RemoteName: "soft",
145 ReferenceName: "refs/heads/master",
146 Depth: 1,
147 InsecureSkipTLS: true,
148 Progress: os.Stdout,
149 })
150}
151
giolekva8aa73e82022-07-09 11:34:39 +0400152func (ss *Client) repoPathByName(name string) string {
153 return fmt.Sprintf("%s/%s", ss.addressGit(), name)
154}
155
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400156func (ss *Client) Commit(wt *git.Worktree, message string) error {
giolekva8aa73e82022-07-09 11:34:39 +0400157 _, err := wt.Commit(message, &git.CommitOptions{
158 Author: &object.Signature{
159 Name: "pcloud",
160 Email: "pcloud@installer",
161 When: time.Now(),
162 },
163 })
164 return err
165}
166
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400167func (ss *Client) Push(repo *git.Repository) error {
giolekva8aa73e82022-07-09 11:34:39 +0400168 return repo.Push(&git.PushOptions{
169 RemoteName: "soft",
170 Auth: ss.authGit(),
171 })
172}
173
174func (ss *Client) writeFile(wt *git.Worktree, path, contents string) error {
175 f, err := wt.Filesystem.Create(path)
176 if err != nil {
177 return err
178 }
179 defer f.Close()
180 if _, err = f.Write([]byte(contents)); err != nil {
181 return err
182 }
183 _, err = wt.Add(path)
184 return err
185}
186
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400187func (ss *Client) CloneRepository(name string) (*git.Repository, error) {
giolekva8aa73e82022-07-09 11:34:39 +0400188 return git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
189 URL: ss.repoPathByName(name),
190 Auth: ss.authGit(),
191 RemoteName: "soft",
192 InsecureSkipTLS: true,
193 })
194}
195
196func (ss *Client) authGit() *gitssh.PublicKeys {
197 return &gitssh.PublicKeys{
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400198 Signer: ss.Signer,
giolekva8aa73e82022-07-09 11:34:39 +0400199 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
200 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
201 // TODO(giolekva): verify server public key
202 // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
203 return nil
204 },
205 },
206 }
207}
208
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400209func (ss *Client) GetPublicKey() ([]byte, error) {
210 var ret []byte
211 config := &ssh.ClientConfig{
212 Auth: []ssh.AuthMethod{
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400213 ssh.PublicKeys(ss.Signer),
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400214 },
215 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
216 ret = ssh.MarshalAuthorizedKey(key)
217 return nil
218 },
219 }
220 _, err := ssh.Dial("tcp", ss.addressSSH(), config)
221 if err != nil {
222 return nil, err
223 }
224 return ret, nil
225}
226
giolekva8aa73e82022-07-09 11:34:39 +0400227func (ss *Client) sshClientConfig() *ssh.ClientConfig {
228 return &ssh.ClientConfig{
229 Auth: []ssh.AuthMethod{
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400230 ssh.PublicKeys(ss.Signer),
giolekva8aa73e82022-07-09 11:34:39 +0400231 },
232 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
233 // TODO(giolekva): verify server public key
234 // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400235 fmt.Printf("%s %s %s", hostname, remote, ssh.MarshalAuthorizedKey(key))
giolekva8aa73e82022-07-09 11:34:39 +0400236 return nil
237 },
238 }
239}
240
241func (ss *Client) addressGit() string {
242 return fmt.Sprintf("ssh://%s:%d", ss.ip, ss.port)
243}
244
245func (ss *Client) addressSSH() string {
246 return fmt.Sprintf("%s:%d", ss.ip, ss.port)
247}