blob: dd5bbfe117a6e6d734bff76df8d9857ab04400e7 [file] [log] [blame]
giolekva8aa73e82022-07-09 11:34:39 +04001package soft
2
3import (
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +04004 "errors"
giolekva8aa73e82022-07-09 11:34:39 +04005 "fmt"
giolekva8aa73e82022-07-09 11:34:39 +04006 "log"
7 "net"
8 "os"
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +04009 "regexp"
gio266c04f2024-07-03 14:18:45 +040010 "slices"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040011 "strings"
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +040012 "time"
giolekva8aa73e82022-07-09 11:34:39 +040013
Davit Tabidzea5ea5092024-08-01 15:28:09 +040014 "golang.org/x/crypto/ssh"
15
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +040016 "github.com/cenkalti/backoff/v4"
giolekva8aa73e82022-07-09 11:34:39 +040017 "github.com/go-git/go-billy/v5/memfs"
18 "github.com/go-git/go-git/v5"
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040019 "github.com/go-git/go-git/v5/plumbing/transport"
giolekva8aa73e82022-07-09 11:34:39 +040020 gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
21 "github.com/go-git/go-git/v5/storage/memory"
giolekva8aa73e82022-07-09 11:34:39 +040022)
23
gio0eaf2712024-04-14 13:08:46 +040024var ErrorAlreadyExists = errors.New("already exists")
25
gioe72b54f2024-04-22 10:44:41 +040026type Client interface {
27 Address() string
28 Signer() ssh.Signer
29 GetPublicKeys() ([]string, error)
gio33059762024-07-05 13:19:07 +040030 RepoExists(name string) (bool, error)
gioe72b54f2024-04-22 10:44:41 +040031 GetRepo(name string) (RepoIO, error)
giocafd4e62024-07-31 10:53:40 +040032 GetAllRepos() ([]string, error)
gioe72b54f2024-04-22 10:44:41 +040033 GetRepoAddress(name string) string
34 AddRepository(name string) error
gio33059762024-07-05 13:19:07 +040035 UserExists(name string) (bool, error)
36 FindUser(pubKey string) (string, error)
Davit Tabidzea5ea5092024-08-01 15:28:09 +040037 GetAllUsers() ([]string, error)
gioe72b54f2024-04-22 10:44:41 +040038 AddUser(name, pubKey string) error
Davit Tabidzea5ea5092024-08-01 15:28:09 +040039 RemoveUser(user string) error
gioe72b54f2024-04-22 10:44:41 +040040 AddPublicKey(user string, pubKey string) error
41 RemovePublicKey(user string, pubKey string) error
Davit Tabidzea5ea5092024-08-01 15:28:09 +040042 GetUserPublicKeys(user string) ([]string, error)
gioe72b54f2024-04-22 10:44:41 +040043 MakeUserAdmin(name string) error
44 AddReadWriteCollaborator(repo, user string) error
45 AddReadOnlyCollaborator(repo, user string) error
gio0eaf2712024-04-14 13:08:46 +040046 AddWebhook(repo, url string, opts ...string) error
gioe72b54f2024-04-22 10:44:41 +040047}
48
49type realClient struct {
50 addr string
51 signer ssh.Signer
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040052 log *log.Logger
53 pemBytes []byte
giolekva8aa73e82022-07-09 11:34:39 +040054}
55
gioe72b54f2024-04-22 10:44:41 +040056func NewClient(addr string, clientPrivateKey []byte, log *log.Logger) (Client, error) {
giolekva8aa73e82022-07-09 11:34:39 +040057 signer, err := ssh.ParsePrivateKey(clientPrivateKey)
58 if err != nil {
59 return nil, err
60 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040061 log.SetPrefix("SOFT-SERVE: ")
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040062 log.Printf("Created signer")
gioe72b54f2024-04-22 10:44:41 +040063 return &realClient{
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +040064 addr,
giolekva8aa73e82022-07-09 11:34:39 +040065 signer,
66 log,
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040067 clientPrivateKey,
giolekva8aa73e82022-07-09 11:34:39 +040068 }, nil
69}
70
gioe72b54f2024-04-22 10:44:41 +040071type ClientGetter interface {
72 Get(addr string, clientPrivateKey []byte, log *log.Logger) (Client, error)
73}
74
75type RealClientGetter struct{}
76
77func (c RealClientGetter) Get(addr string, clientPrivateKey []byte, log *log.Logger) (Client, error) {
78 var client Client
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +040079 err := backoff.RetryNotify(func() error {
80 var err error
81 client, err = NewClient(addr, clientPrivateKey, log)
82 if err != nil {
83 return err
84 }
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +040085 if _, err := client.GetPublicKeys(); err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +040086 return err
87 }
88 return nil
89 }, backoff.NewConstantBackOff(5*time.Second), func(err error, _ time.Duration) {
90 log.Printf("Failed to create client: %s\n", err.Error())
91 })
92 return client, err
93}
94
gioe72b54f2024-04-22 10:44:41 +040095func (ss *realClient) Address() string {
96 return ss.addr
97}
98
99func (ss *realClient) Signer() ssh.Signer {
100 return ss.signer
101}
102
Davit Tabidzea5ea5092024-08-01 15:28:09 +0400103func (ss *realClient) GetAllUsers() ([]string, error) {
104 log.Printf("Getting all users")
105 out, err := ss.RunCommand("user", "list")
106 if err != nil {
107 return nil, err
108 }
109 return strings.Fields(out), nil
110}
111
gio33059762024-07-05 13:19:07 +0400112func (ss *realClient) UserExists(name string) (bool, error) {
gio11617ac2024-07-15 16:09:04 +0400113 log.Printf("Checking user exists %s", name)
gio33059762024-07-05 13:19:07 +0400114 out, err := ss.RunCommand("user", "list")
115 if err != nil {
116 return false, err
117 }
118 return slices.Contains(strings.Fields(out), name), nil
119}
120
121func (ss *realClient) FindUser(pubKey string) (string, error) {
122 log.Printf("Finding user %s", pubKey)
123 pk := strings.Join(strings.Fields(pubKey)[:2], " ")
124 out, err := ss.RunCommand("user", "list")
125 if err != nil {
126 return "", err
127 }
128 for _, user := range strings.Fields(out) {
129 info, err := ss.RunCommand("user", "info", user)
130 if err != nil {
131 return "", err
132 }
133 if strings.Contains(info, pk) {
134 return user, nil
135 }
136 }
137 return "", nil
138}
139
gioe72b54f2024-04-22 10:44:41 +0400140func (ss *realClient) AddUser(name, pubKey string) error {
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400141 log.Printf("Adding user %s", name)
gio266c04f2024-07-03 14:18:45 +0400142 if _, err := ss.RunCommand("user", "create", name); err != nil {
giolekva8aa73e82022-07-09 11:34:39 +0400143 return err
144 }
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400145 return ss.AddPublicKey(name, pubKey)
giolekva8aa73e82022-07-09 11:34:39 +0400146}
147
Davit Tabidzea5ea5092024-08-01 15:28:09 +0400148func (ss *realClient) RemoveUser(user string) error {
149 log.Printf("Removing user: %s\n", user)
150 _, err := ss.RunCommand("user", "delete", user)
151 return err
152}
153
gioe72b54f2024-04-22 10:44:41 +0400154func (ss *realClient) MakeUserAdmin(name string) error {
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400155 log.Printf("Making user %s admin", name)
gio266c04f2024-07-03 14:18:45 +0400156 _, err := ss.RunCommand("user", "set-admin", name, "true")
157 return err
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400158}
159
gioe72b54f2024-04-22 10:44:41 +0400160func (ss *realClient) AddPublicKey(user string, pubKey string) error {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400161 log.Printf("Adding public key: %s %s\n", user, pubKey)
gio266c04f2024-07-03 14:18:45 +0400162 _, err := ss.RunCommand("user", "add-pubkey", user, pubKey)
163 return err
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400164}
165
gioe72b54f2024-04-22 10:44:41 +0400166func (ss *realClient) RemovePublicKey(user string, pubKey string) error {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400167 log.Printf("Removing public key: %s %s\n", user, pubKey)
gio266c04f2024-07-03 14:18:45 +0400168 _, err := ss.RunCommand("user", "remove-pubkey", user, pubKey)
169 return err
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400170}
171
Davit Tabidzea5ea5092024-08-01 15:28:09 +0400172func (ss *realClient) GetUserPublicKeys(user string) ([]string, error) {
173 log.Printf("Getting public keys for user: %s\n", user)
174 out, err := ss.RunCommand("user", "info", user)
175 if err != nil {
176 return nil, err
177 }
178 return extractPublicKeys(out), nil
179}
180
181func extractPublicKeys(userInfo string) []string {
182 var keys []string
183 lines := strings.Split(userInfo, "\n")
184 gettingKeys := false
185 for _, line := range lines {
186 if strings.HasPrefix(line, "Public keys:") {
187 gettingKeys = true
188 continue
189 }
190 if gettingKeys {
Davit Tabidze4aaa27b2024-08-05 20:23:50 +0400191 if key := CleanKey(line); key != "" {
192 keys = append(keys, key)
193 }
Davit Tabidzea5ea5092024-08-01 15:28:09 +0400194 }
195 }
196 return keys
197}
198
Davit Tabidze4aaa27b2024-08-05 20:23:50 +0400199func CleanKey(key string) string {
200 k := strings.TrimSpace(key)
201 fields := strings.Fields(k)
202 if len(fields) < 2 {
203 return k
204 }
205 return fields[0] + " " + fields[1]
206}
207
gio266c04f2024-07-03 14:18:45 +0400208func (ss *realClient) RunCommand(args ...string) (string, error) {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400209 cmd := strings.Join(args, " ")
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400210 log.Printf("Running command %s", cmd)
gioe72b54f2024-04-22 10:44:41 +0400211 client, err := ssh.Dial("tcp", ss.addr, ss.sshClientConfig())
giolekva8aa73e82022-07-09 11:34:39 +0400212 if err != nil {
gio266c04f2024-07-03 14:18:45 +0400213 return "", err
giolekva8aa73e82022-07-09 11:34:39 +0400214 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400215 defer client.Close()
giolekva8aa73e82022-07-09 11:34:39 +0400216 session, err := client.NewSession()
217 if err != nil {
gio266c04f2024-07-03 14:18:45 +0400218 return "", err
giolekva8aa73e82022-07-09 11:34:39 +0400219 }
220 defer session.Close()
gio266c04f2024-07-03 14:18:45 +0400221 var buf strings.Builder
222 session.Stdout = &buf
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400223 session.Stderr = os.Stderr
gio266c04f2024-07-03 14:18:45 +0400224 err = session.Run(cmd)
225 return buf.String(), err
226}
227
gio33059762024-07-05 13:19:07 +0400228func (ss *realClient) RepoExists(name string) (bool, error) {
gio266c04f2024-07-03 14:18:45 +0400229 out, err := ss.RunCommand("repo", "list")
230 if err != nil {
231 return false, err
232 }
233 return slices.Contains(strings.Fields(out), name), nil
giolekva8aa73e82022-07-09 11:34:39 +0400234}
235
gioe72b54f2024-04-22 10:44:41 +0400236func (ss *realClient) AddRepository(name string) error {
giolekva8aa73e82022-07-09 11:34:39 +0400237 log.Printf("Adding repository %s", name)
gio33059762024-07-05 13:19:07 +0400238 if ok, err := ss.RepoExists(name); ok {
gio0eaf2712024-04-14 13:08:46 +0400239 return ErrorAlreadyExists
gio266c04f2024-07-03 14:18:45 +0400240 } else if err != nil {
241 return err
gio0eaf2712024-04-14 13:08:46 +0400242 }
gio266c04f2024-07-03 14:18:45 +0400243 _, err := ss.RunCommand("repo", "create", name)
244 return err
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400245}
246
gioe72b54f2024-04-22 10:44:41 +0400247func (ss *realClient) AddReadWriteCollaborator(repo, user string) error {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400248 log.Printf("Adding read-write collaborator %s %s", repo, user)
gio266c04f2024-07-03 14:18:45 +0400249 _, err := ss.RunCommand("repo", "collab", "add", repo, user, "read-write")
250 return err
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400251}
252
gioe72b54f2024-04-22 10:44:41 +0400253func (ss *realClient) AddReadOnlyCollaborator(repo, user string) error {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400254 log.Printf("Adding read-only collaborator %s %s", repo, user)
gio266c04f2024-07-03 14:18:45 +0400255 _, err := ss.RunCommand("repo", "collab", "add", repo, user, "read-only")
256 return err
giolekva8aa73e82022-07-09 11:34:39 +0400257}
258
gio0eaf2712024-04-14 13:08:46 +0400259func (ss *realClient) AddWebhook(repo, url string, opts ...string) error {
260 log.Printf("Adding webhook %s %s", repo, url)
gio266c04f2024-07-03 14:18:45 +0400261 _, err := ss.RunCommand(append(
gio0eaf2712024-04-14 13:08:46 +0400262 []string{"repo", "webhook", "create", repo, url},
263 opts...,
264 )...)
gio266c04f2024-07-03 14:18:45 +0400265 return err
gio0eaf2712024-04-14 13:08:46 +0400266}
267
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +0400268type Repository struct {
269 *git.Repository
270 Addr RepositoryAddress
271}
272
gioe72b54f2024-04-22 10:44:41 +0400273func (ss *realClient) GetRepo(name string) (RepoIO, error) {
274 r, err := CloneRepository(RepositoryAddress{ss.addr, name}, ss.signer)
275 if err != nil {
276 return nil, err
277 }
278 return NewRepoIO(r, ss.signer)
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +0400279}
280
giocafd4e62024-07-31 10:53:40 +0400281func (ss *realClient) GetAllRepos() ([]string, error) {
282 log.Printf("Getting all repos")
283 out, err := ss.RunCommand("repo", "list")
284 if err != nil {
285 return nil, err
286 }
287 return strings.Fields(out), nil
288}
289
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +0400290type RepositoryAddress struct {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400291 Addr string
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +0400292 Name string
293}
294
295func ParseRepositoryAddress(addr string) (RepositoryAddress, error) {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400296 items := regexp.MustCompile(`ssh://(.*)/(.*)`).FindStringSubmatch(addr)
297 if len(items) != 3 {
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +0400298 return RepositoryAddress{}, fmt.Errorf("Invalid address")
299 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400300 return RepositoryAddress{items[1], items[2]}, nil
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +0400301}
302
303func (r RepositoryAddress) FullAddress() string {
304 return fmt.Sprintf("ssh://%s/%s", r.Addr, r.Name)
305}
306
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400307func CloneRepository(addr RepositoryAddress, signer ssh.Signer) (*Repository, error) {
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400308 fmt.Printf("Cloning repository: %s %s\n", addr.Addr, addr.Name)
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +0400309 c, err := git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
310 URL: addr.FullAddress(),
311 Auth: &gitssh.PublicKeys{
312 User: "git",
313 Signer: signer,
314 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
315 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
316 // TODO(giolekva): verify server public key
317 fmt.Printf("--- %+v\n", ssh.MarshalAuthorizedKey(key))
318 return nil
319 },
320 },
321 },
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +0400322 RemoteName: "origin",
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400323 ReferenceName: "refs/heads/master",
giof5ffedb2024-06-19 14:14:43 +0400324 SingleBranch: true,
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400325 Depth: 1,
326 InsecureSkipTLS: true,
327 Progress: os.Stdout,
328 })
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400329 if err != nil && !errors.Is(err, transport.ErrEmptyRemoteRepository) {
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +0400330 return nil, err
331 }
giof5ffedb2024-06-19 14:14:43 +0400332 wt, err := c.Worktree()
333 if err != nil {
334 return nil, err
335 }
336 sb, err := wt.Submodules()
337 if err != nil {
338 return nil, err
339 }
340 if err := sb.Init(); err != nil {
341 return nil, err
342 }
343 if err := sb.Update(&git.SubmoduleUpdateOptions{
344 Depth: 1,
345 }); err != nil {
346 return nil, err
347 }
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +0400348 return &Repository{
349 Repository: c,
350 Addr: addr,
351 }, nil
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400352}
353
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +0400354// TODO(giolekva): dead code
gioe72b54f2024-04-22 10:44:41 +0400355func (ss *realClient) authSSH() gitssh.AuthMethod {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400356 a, err := gitssh.NewPublicKeys("git", ss.pemBytes, "")
giolekva8aa73e82022-07-09 11:34:39 +0400357 if err != nil {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400358 panic(err)
giolekva8aa73e82022-07-09 11:34:39 +0400359 }
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400360 a.HostKeyCallback = func(hostname string, remote net.Addr, key ssh.PublicKey) error {
361 // TODO(giolekva): verify server public key
362 ss.log.Printf("--- %+v\n", ssh.MarshalAuthorizedKey(key))
363 return nil
giolekva8aa73e82022-07-09 11:34:39 +0400364 }
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400365 return a
366 // return &gitssh.PublicKeys{
367 // User: "git",
368 // Signer: ss.Signer,
369 // HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
370 // HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
371 // // TODO(giolekva): verify server public key
372 // ss.log.Printf("--- %+v\n", ssh.MarshalAuthorizedKey(key))
373 // return nil
374 // },
375 // },
376 // }
giolekva8aa73e82022-07-09 11:34:39 +0400377}
378
gioe72b54f2024-04-22 10:44:41 +0400379func (ss *realClient) authGit() *gitssh.PublicKeys {
giolekva8aa73e82022-07-09 11:34:39 +0400380 return &gitssh.PublicKeys{
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400381 User: "git",
gioe72b54f2024-04-22 10:44:41 +0400382 Signer: ss.signer,
giolekva8aa73e82022-07-09 11:34:39 +0400383 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
384 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
385 // TODO(giolekva): verify server public key
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400386 ss.log.Printf("--- %+v\n", ssh.MarshalAuthorizedKey(key))
giolekva8aa73e82022-07-09 11:34:39 +0400387 return nil
388 },
389 },
390 }
391}
392
gioe72b54f2024-04-22 10:44:41 +0400393func (ss *realClient) GetPublicKeys() ([]string, error) {
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400394 var ret []string
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400395 config := &ssh.ClientConfig{
396 Auth: []ssh.AuthMethod{
gioe72b54f2024-04-22 10:44:41 +0400397 ssh.PublicKeys(ss.signer),
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400398 },
399 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400400 ret = append(ret, string(ssh.MarshalAuthorizedKey(key)))
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400401 return nil
402 },
403 }
gioe72b54f2024-04-22 10:44:41 +0400404 client, err := ssh.Dial("tcp", ss.addr, config)
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400405 if err != nil {
406 return nil, err
407 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400408 defer client.Close()
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400409 return ret, nil
410}
411
gioe72b54f2024-04-22 10:44:41 +0400412func (ss *realClient) sshClientConfig() *ssh.ClientConfig {
giolekva8aa73e82022-07-09 11:34:39 +0400413 return &ssh.ClientConfig{
414 Auth: []ssh.AuthMethod{
gioe72b54f2024-04-22 10:44:41 +0400415 ssh.PublicKeys(ss.signer),
giolekva8aa73e82022-07-09 11:34:39 +0400416 },
417 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
418 // TODO(giolekva): verify server public key
419 // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400420 fmt.Printf("%s %s %s", hostname, remote, ssh.MarshalAuthorizedKey(key))
giolekva8aa73e82022-07-09 11:34:39 +0400421 return nil
422 },
423 }
424}
425
gioe72b54f2024-04-22 10:44:41 +0400426func (ss *realClient) GetRepoAddress(name string) string {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400427 return fmt.Sprintf("%s/%s", ss.addressGit(), name)
428}
429
gioe72b54f2024-04-22 10:44:41 +0400430func (ss *realClient) addressGit() string {
431 return fmt.Sprintf("ssh://%s", ss.addr)
giolekva8aa73e82022-07-09 11:34:39 +0400432}