Skip to content

Commit 4db0823

Browse files
feat(init): add support for ed25519 ssh key (#1453)
1 parent f27e279 commit 4db0823

File tree

4 files changed

+231
-14
lines changed

4 files changed

+231
-14
lines changed

internal/namespaces/account/v2alpha1/custom.go

+15-10
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,23 @@ func initCommand() *core.Command {
3939

4040
func InitRun(ctx context.Context, argsI interface{}) (i interface{}, e error) {
4141
// Get default local SSH key
42-
relativePath := path.Join(".ssh", "id_rsa.pub")
43-
filename := path.Join(core.ExtractUserHomeDir(ctx), relativePath)
44-
shortenedFilename := "~/" + relativePath
45-
localSSHKeyContent, err := ioutil.ReadFile(filename)
46-
42+
var shortenedFilename string
43+
var err error
44+
var localSSHKeyContent []byte
45+
for _, keyName := range [3]string{"id_ecdsa.pub", "id_ed25519.pub", "id_rsa.pub"} {
46+
// element is the element from someSlice for where we are
47+
relativePath := path.Join(".ssh", keyName)
48+
filename := path.Join(core.ExtractUserHomeDir(ctx), relativePath)
49+
shortenedFilename = "~/" + relativePath
50+
localSSHKeyContent, err = ioutil.ReadFile(filename)
51+
// If we managed to load an ssh key, let's stop there
52+
if err == nil {
53+
break
54+
}
55+
}
4756
addKeyInstructions := `scw account ssh-key add name=my-key key="$(cat path/to/my/key.pub)"`
48-
49-
// Early exit if key is not present locally
50-
if os.IsNotExist(err) {
57+
if err != nil && os.IsNotExist(err) {
5158
return nil, sshKeyNotFound(shortenedFilename, addKeyInstructions)
52-
} else if err != nil {
53-
return nil, err
5459
}
5560

5661
// Get all SSH keys from Scaleway

internal/namespaces/init/custom_init_ssh_test.go

+19-4
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import (
1313
"github.com/scaleway/scaleway-sdk-go/scw"
1414
)
1515

16-
func setUpSSHKeyLocally(key string) core.BeforeFunc {
16+
func setUpSSHKeyLocallyWithKeyName(key string, name string) core.BeforeFunc {
1717
return func(ctx *core.BeforeFuncCtx) error {
1818
homeDir := ctx.OverrideEnv["HOME"]
1919
// TODO we persist the key as ~/.ssh/id_rsa.pub regardless of the type of key it is (rsa, ed25519)
20-
keyPath := path.Join(homeDir, ".ssh", "id_rsa.pub")
20+
keyPath := path.Join(homeDir, ".ssh", name)
2121
ctx.Logger.Info("public key path set to: ", keyPath)
2222

2323
// Ensure the subfolders for the configuration files are all created
@@ -85,7 +85,7 @@ func Test_InitSSH(t *testing.T) {
8585
Commands: cmds,
8686
BeforeFunc: core.BeforeFuncCombine(
8787
baseBeforeFunc(),
88-
setUpSSHKeyLocally(dummySSHKey),
88+
setUpSSHKeyLocallyWithKeyName(dummySSHKey, "id_rsa.pub"),
8989
addSSHKeyToAccount("key", "test-cli-KeyRegistered", dummySSHKey),
9090
),
9191
Cmd: appendArgs("scw init with-ssh-key=true", defaultSettings),
@@ -101,7 +101,7 @@ func Test_InitSSH(t *testing.T) {
101101
Commands: cmds,
102102
BeforeFunc: core.BeforeFuncCombine(
103103
baseBeforeFunc(),
104-
setUpSSHKeyLocally(dummySSHKey),
104+
setUpSSHKeyLocallyWithKeyName(dummySSHKey, "id_rsa.pub"),
105105
),
106106
Cmd: appendArgs("scw init with-ssh-key=true", defaultSettings),
107107
Check: core.TestCheckGolden(),
@@ -117,4 +117,19 @@ func Test_InitSSH(t *testing.T) {
117117
Check: core.TestCheckGolden(),
118118
TmpHomeDir: true,
119119
}))
120+
121+
t.Run("WithLocalEd25519Key", func(t *testing.T) {
122+
dummySSHKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJb42xh9l10D/u9imDFfLZ+U6KrZmr/F/qBClnmijCFF/qEehPJxK/3thmMiZg foobaz@foobaz"
123+
core.Test(&core.TestConfig{
124+
Commands: cmds,
125+
BeforeFunc: core.BeforeFuncCombine(
126+
baseBeforeFunc(),
127+
setUpSSHKeyLocallyWithKeyName(dummySSHKey, "id_ed25519.pub"),
128+
),
129+
Cmd: appendArgs("scw init with-ssh-key=true", defaultSettings),
130+
Check: core.TestCheckGolden(),
131+
TmpHomeDir: true,
132+
AfterFunc: removeSSHKeyFromAccount(dummySSHKey),
133+
})(t)
134+
})
120135
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
---
2+
version: 1
3+
interactions:
4+
- request:
5+
body: ""
6+
form: {}
7+
headers: {}
8+
url: https://account.scaleway.com/tokens/11111111-1111-1111-1111-111111111111
9+
method: GET
10+
response:
11+
body: '{"token": {"access_key": "SCW8XT5JRAV4B0WQSHPE", "user_id": "721f2fde-1a01-4854-822b-fcca5638d73e",
12+
"expires": null, "creation_date": "2020-06-23T14:06:56.763352+00:00", "creation_ip":
13+
"195.154.229.35", "category": "user_created", "deletion_date": null, "inherits_user_perms":
14+
true, "roles": {"role": null, "organization": null}, "description": "sieben-macbook",
15+
"organization_id": "951df375-e094-4d26-97c1-ba548eeb9c42", "project_id": "951df375-e094-4d26-97c1-ba548eeb9c42",
16+
"use_role_key": false}}'
17+
headers:
18+
Connection:
19+
- keep-alive
20+
Content-Length:
21+
- "445"
22+
Content-Type:
23+
- application/json
24+
Date:
25+
- Tue, 23 Jun 2020 14:33:35 GMT
26+
Server:
27+
- nginx
28+
Strict-Transport-Security:
29+
- max-age=15552000
30+
status: 200 OK
31+
code: 200
32+
duration: ""
33+
- request:
34+
body: ""
35+
form: {}
36+
headers:
37+
User-Agent:
38+
- scaleway-sdk-go/v1.0.0-beta.6+dev (go1.14.4; darwin; amd64) cli-e2e-test
39+
url: https://api.scaleway.com/account/v2alpha1/ssh-keys?order_by=created_at_asc&page=1
40+
method: GET
41+
response:
42+
body: '{"ssh_keys":[{"id":"9bf6c340-687c-4f31-8aad-6a525761d8cd","name":"sieben-macbook","public_key":"ssh-rsa
43+
AAAAB3NzaC1yc2EAAAADAQABAAACAQDZrUk+hFs8hYZVu6PVzXIZH83glJfiqZU7gzxw9zSkinCNHZnJ0KViZmol/XUEvGPu0toWOnM5t9MUVRq3xMldo6TKOZ7tvqBDoYD/hMHSBZb+b5f12l7i43wV7dunypy+nKtlb+rv9YJdMdbVaXy29K5EPaQujYTZTvXFEB9b8P0NXt0jA+qsUEf6/TK7dONdh2PFrUFiLXtNxkw21n/q6JGfR78etQ3mIaDKffjoVYHKgMv2edGWpjAkZeM5eBg6U6kb7CMMstpxPfOPADkXu4hbWhMGG5TC4tuizfq8YAeE8t2nyfB+xq8MtQe5jTPLK1/B+NVuKvp+3WLDSrai6dbsMpFsZDXJL34BkoY7XsRf8FeaPiy5Z3ScD6YLTl986rztAPyLCKJrfPSSc2tAOgne9cGFVUPS+tehqtW+VPeD4qu00h7H9hX/x+hefqYL5cfS3h/qgpcUFw+69rVK6r1zcBwJyJAI9gUQ08628iirYku3KkUQ0VdlavrYYfB9wyStQ4VBUYpXKMhKgk5gCRRkEuJ2gETffj+nLZAqL13yFPm8lCOgRztp06S+K9Xq/cYVvAnDh8R2IsBGbs4KmpKMM7PA7mYf3XMI1KzAWXqc6RCv1a+PUwMK/AIKqnk040n2Q0yxuJSOufcVE6mWxVywHag9Ucww1FuSR54i7w==
44+
[email protected]","fingerprint":"4096 MD5:43:ba:7c:ec:66:6b:cc:fe:02:91:04:29:32:19:30:bf
45+
[email protected] (ssh-rsa)","created_at":"2019-05-02T08:26:01.304954Z","updated_at":"2020-05-13T22:32:46.471097Z","creation_info":{"address":"195.154.228.158","user_agent":"ansible
46+
2.7.0.dev0 Python 2.7.16","country_code":"FR"},"organization_id":"951df375-e094-4d26-97c1-ba548eeb9c42"},{"id":"cffa1bb1-57f0-4ad8-be20-f335df743b27","name":"juliencastets-macbook","public_key":"ssh-rsa
47+
AAAAB3NzaC1yc2EAAAADAQABAAABAQDMiV5DEMsYihbdS3kCJwzSBLgGO7L2j5z2/9+HVbJbzDvAz0HcUQ+sKOlvzEUzDgtNKNP+zs7C9sMcDKKUPsVzx2jpZgGN0hP9taDzWuMX+A4B4CBftTQHtZBV8dg+EVeI5Xv6HmY/WtAWC1Xfb8mvz5z+a/8D0u9IVURqiZy/n8moQwhjpK9+m634GorgMwduMZX1pPskSVL1m2ocaP8NFq8oV9L6zf9kSi7/Jwgci1TTsGYGsRSbpc665nP7MgyxZqpP0oQK8f6VW1ndFjsSgE1fT83/1WH8ro2Ou0Esh/FTs0Wxt2A5vIShYJIO+o680rNjbIhoWHLg5tU9+ip1
48+
macbook","fingerprint":"2048 MD5:00:43:dc:7f:26:64:70:a2:b9:cd:06:77:35:f2:f6:f6
49+
macbook (ssh-rsa)","created_at":"2020-02-26T12:46:58.241993Z","updated_at":"2020-02-26T12:46:58.241993Z","creation_info":{"address":"195.154.229.35","user_agent":"Mozilla/5.0
50+
(Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130
51+
Safari/537.36","country_code":"FR"},"organization_id":"951df375-e094-4d26-97c1-ba548eeb9c42"},{"id":"d4eb532f-b59a-4f9d-b079-e6082bc46016","name":"oss_exporter","public_key":"ssh-rsa
52+
AAAAB3NzaC1yc2EAAAADAQABAAABgQDHuJH34xHb/1LtKgMlCXN2x6SYc8TQKkevOTGItFfrOf+MCck9Y7Visl7uBD9uQO6e2JBkpG9LWg9HVd5TDMiTxai81CiuuQh9dHq0/UP7VQQMyDS3zWx2gaWX6uuvPuLbza4+yfahFN9LAM8mw/uGplJBDljJbvK1TEgP9M2/1hpWUn+qAWLFiNg943ahlVJBPyNIi3EbXJ8cy9GYAhA4GR/8vtH2CdFWokL7AUTF63G+DKC/5DfCqzEsF5PpraIUObZHJ/K7m0otCu3K7kJG0rltyAdxew3FOPAvVZyKyi0ES2avsBCrPtSU89GzqVvdSL09h0W0TzX8agraHwB0Fp21pQW7+o+TWmJ0RToAQA+s4xFH4OFvyc0S3iMB1/A1219ep6CM7EggIhLDn0KJfO92Vhtkl0Z8Uw5qqRVoIsrhriReD5csK6kwUA0fmJAANudjIJgTOCE27462OCeUZK/p0CNgqnkieHdCeFOnIkn5yiGOhV9AkjPHmmLlujs=
53+
oss-exporter","fingerprint":"3072 MD5:d8:53:fc:ef:8e:ee:dd:65:3b:fc:e3:a7:96:05:f0:ac
54+
oss-exporter (ssh-rsa)","created_at":"2020-05-18T14:06:54.887451Z","updated_at":"2020-05-18T14:12:35.090751Z","creation_info":{"address":"","user_agent":"","country_code":""},"organization_id":"32f707c5-df07-43c7-8b57-6e09b687dd03"}],"total_count":3}'
55+
headers:
56+
Content-Length:
57+
- "3090"
58+
Content-Security-Policy:
59+
- default-src 'none'; frame-ancestors 'none'
60+
Content-Type:
61+
- application/json
62+
Date:
63+
- Tue, 23 Jun 2020 14:33:35 GMT
64+
Server:
65+
- agw_listener_public_vip
66+
Strict-Transport-Security:
67+
- max-age=63072000
68+
X-Content-Type-Options:
69+
- nosniff
70+
X-Frame-Options:
71+
- DENY
72+
X-Request-Id:
73+
- 86c1516c-6356-45ca-95d2-f49b32e3cf74
74+
status: 200 OK
75+
code: 200
76+
duration: ""
77+
- request:
78+
body: '{"name":"","public_key":"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJb42xh9l10D/u9imDFfLZ+U6KrZmr/F/qBClnmijCFF/qEehPJxK/3thmMiZg
79+
foobaz@foobaz","organization_id":"951df375-e094-4d26-97c1-ba548eeb9c42"}'
80+
form: {}
81+
headers:
82+
Content-Type:
83+
- application/json
84+
User-Agent:
85+
- scaleway-sdk-go/v1.0.0-beta.6+dev (go1.14.4; darwin; amd64) cli-e2e-test
86+
url: https://api.scaleway.com/account/v2alpha1/ssh-keys
87+
method: POST
88+
response:
89+
body: '{"id":"255abc8e-4628-4d53-a497-e1bd23c06757","name":"","public_key":"ssh-ed25519
90+
AAAAC3NzaC1lZDI1NTE5AAAAIJb42xh9l10D/u9imDFfLZ+U6KrZmr/F/qBClnmijCFF/qEehPJxK/3thmMiZg foobaz@foobaz","fingerprint":"256
91+
MD5:ea:c1:a1:82:4b:41:2b:65:26:9c:18:27:da:0d:bd:91 foobar@foobar (ssh-ed25519)","created_at":"2020-06-10T13:03:44.732785Z","updated_at":"2020-06-23T14:33:35.687332Z","creation_info":{"address":"","user_agent":"","country_code":""},"organization_id":"951df375-e094-4d26-97c1-ba548eeb9c42"}'
92+
headers:
93+
Content-Length:
94+
- "473"
95+
Content-Security-Policy:
96+
- default-src 'none'; frame-ancestors 'none'
97+
Content-Type:
98+
- application/json
99+
Date:
100+
- Tue, 23 Jun 2020 14:33:35 GMT
101+
Server:
102+
- agw_listener_public_vip
103+
Strict-Transport-Security:
104+
- max-age=63072000
105+
X-Content-Type-Options:
106+
- nosniff
107+
X-Frame-Options:
108+
- DENY
109+
X-Request-Id:
110+
- ceba1f28-d81c-424b-beb6-18c4fd4060f6
111+
status: 200 OK
112+
code: 200
113+
duration: ""
114+
- request:
115+
body: ""
116+
form: {}
117+
headers:
118+
User-Agent:
119+
- scaleway-sdk-go/v1.0.0-beta.6+dev (go1.14.4; darwin; amd64) cli-e2e-test
120+
url: https://api.scaleway.com/account/v2alpha1/ssh-keys?order_by=created_at_asc&page=1
121+
method: GET
122+
response:
123+
body: '{"ssh_keys":[{"id":"9bf6c340-687c-4f31-8aad-6a525761d8cd","name":"sieben-macbook","public_key":"ssh-rsa
124+
AAAAB3NzaC1yc2EAAAADAQABAAACAQDZrUk+hFs8hYZVu6PVzXIZH83glJfiqZU7gzxw9zSkinCNHZnJ0KViZmol/XUEvGPu0toWOnM5t9MUVRq3xMldo6TKOZ7tvqBDoYD/hMHSBZb+b5f12l7i43wV7dunypy+nKtlb+rv9YJdMdbVaXy29K5EPaQujYTZTvXFEB9b8P0NXt0jA+qsUEf6/TK7dONdh2PFrUFiLXtNxkw21n/q6JGfR78etQ3mIaDKffjoVYHKgMv2edGWpjAkZeM5eBg6U6kb7CMMstpxPfOPADkXu4hbWhMGG5TC4tuizfq8YAeE8t2nyfB+xq8MtQe5jTPLK1/B+NVuKvp+3WLDSrai6dbsMpFsZDXJL34BkoY7XsRf8FeaPiy5Z3ScD6YLTl986rztAPyLCKJrfPSSc2tAOgne9cGFVUPS+tehqtW+VPeD4qu00h7H9hX/x+hefqYL5cfS3h/qgpcUFw+69rVK6r1zcBwJyJAI9gUQ08628iirYku3KkUQ0VdlavrYYfB9wyStQ4VBUYpXKMhKgk5gCRRkEuJ2gETffj+nLZAqL13yFPm8lCOgRztp06S+K9Xq/cYVvAnDh8R2IsBGbs4KmpKMM7PA7mYf3XMI1KzAWXqc6RCv1a+PUwMK/AIKqnk040n2Q0yxuJSOufcVE6mWxVywHag9Ucww1FuSR54i7w==
125+
[email protected]","fingerprint":"4096 MD5:43:ba:7c:ec:66:6b:cc:fe:02:91:04:29:32:19:30:bf
126+
[email protected] (ssh-rsa)","created_at":"2019-05-02T08:26:01.304954Z","updated_at":"2020-05-13T22:32:46.471097Z","creation_info":{"address":"195.154.228.158","user_agent":"ansible
127+
2.7.0.dev0 Python 2.7.16","country_code":"FR"},"organization_id":"951df375-e094-4d26-97c1-ba548eeb9c42"},{"id":"cffa1bb1-57f0-4ad8-be20-f335df743b27","name":"juliencastets-macbook","public_key":"ssh-rsa
128+
AAAAB3NzaC1yc2EAAAADAQABAAABAQDMiV5DEMsYihbdS3kCJwzSBLgGO7L2j5z2/9+HVbJbzDvAz0HcUQ+sKOlvzEUzDgtNKNP+zs7C9sMcDKKUPsVzx2jpZgGN0hP9taDzWuMX+A4B4CBftTQHtZBV8dg+EVeI5Xv6HmY/WtAWC1Xfb8mvz5z+a/8D0u9IVURqiZy/n8moQwhjpK9+m634GorgMwduMZX1pPskSVL1m2ocaP8NFq8oV9L6zf9kSi7/Jwgci1TTsGYGsRSbpc665nP7MgyxZqpP0oQK8f6VW1ndFjsSgE1fT83/1WH8ro2Ou0Esh/FTs0Wxt2A5vIShYJIO+o680rNjbIhoWHLg5tU9+ip1
129+
macbook","fingerprint":"2048 MD5:00:43:dc:7f:26:64:70:a2:b9:cd:06:77:35:f2:f6:f6
130+
macbook (ssh-rsa)","created_at":"2020-02-26T12:46:58.241993Z","updated_at":"2020-02-26T12:46:58.241993Z","creation_info":{"address":"195.154.229.35","user_agent":"Mozilla/5.0
131+
(Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130
132+
Safari/537.36","country_code":"FR"},"organization_id":"951df375-e094-4d26-97c1-ba548eeb9c42"},{"id":"d4eb532f-b59a-4f9d-b079-e6082bc46016","name":"oss_exporter","public_key":"ssh-rsa
133+
AAAAB3NzaC1yc2EAAAADAQABAAABgQDHuJH34xHb/1LtKgMlCXN2x6SYc8TQKkevOTGItFfrOf+MCck9Y7Visl7uBD9uQO6e2JBkpG9LWg9HVd5TDMiTxai81CiuuQh9dHq0/UP7VQQMyDS3zWx2gaWX6uuvPuLbza4+yfahFN9LAM8mw/uGplJBDljJbvK1TEgP9M2/1hpWUn+qAWLFiNg943ahlVJBPyNIi3EbXJ8cy9GYAhA4GR/8vtH2CdFWokL7AUTF63G+DKC/5DfCqzEsF5PpraIUObZHJ/K7m0otCu3K7kJG0rltyAdxew3FOPAvVZyKyi0ES2avsBCrPtSU89GzqVvdSL09h0W0TzX8agraHwB0Fp21pQW7+o+TWmJ0RToAQA+s4xFH4OFvyc0S3iMB1/A1219ep6CM7EggIhLDn0KJfO92Vhtkl0Z8Uw5qqRVoIsrhriReD5csK6kwUA0fmJAANudjIJgTOCE27462OCeUZK/p0CNgqnkieHdCeFOnIkn5yiGOhV9AkjPHmmLlujs=
134+
oss-exporter","fingerprint":"3072 MD5:d8:53:fc:ef:8e:ee:dd:65:3b:fc:e3:a7:96:05:f0:ac
135+
oss-exporter (ssh-rsa)","created_at":"2020-05-18T14:06:54.887451Z","updated_at":"2020-05-18T14:12:35.090751Z","creation_info":{"address":"","user_agent":"","country_code":""},"organization_id":"32f707c5-df07-43c7-8b57-6e09b687dd03"},{"id":"255abc8e-4628-4d53-a497-e1bd23c06757","name":"","public_key":"ssh-ed25519
136+
AAAAC3NzaC1lZDI1NTE5AAAAIIQE67HxSRicWd4ol7ntM2jdeD/qEehPJxK/3thmMiZg foobar@foobar","fingerprint":"256
137+
MD5:ea:c1:a1:82:4b:41:2b:65:26:9c:18:27:da:0d:bd:91 foobar@foobar (ssh-ed25519)","created_at":"2020-06-10T13:03:44.732785Z","updated_at":"2020-06-23T14:33:35.687332Z","creation_info":{"address":"","user_agent":"","country_code":""},"organization_id":"951df375-e094-4d26-97c1-ba548eeb9c42"}],"total_count":4}'
138+
headers:
139+
Content-Length:
140+
- "3564"
141+
Content-Security-Policy:
142+
- default-src 'none'; frame-ancestors 'none'
143+
Content-Type:
144+
- application/json
145+
Date:
146+
- Tue, 23 Jun 2020 14:33:35 GMT
147+
Server:
148+
- agw_listener_public_vip
149+
Strict-Transport-Security:
150+
- max-age=63072000
151+
X-Content-Type-Options:
152+
- nosniff
153+
X-Frame-Options:
154+
- DENY
155+
X-Request-Id:
156+
- 000ed79a-4d23-491b-832c-0097c472c46f
157+
status: 200 OK
158+
code: 200
159+
duration: ""
160+
- request:
161+
body: ""
162+
form: {}
163+
headers:
164+
User-Agent:
165+
- scaleway-sdk-go/v1.0.0-beta.6+dev (go1.14.4; darwin; amd64) cli-e2e-test
166+
url: https://api.scaleway.com/account/v2alpha1/ssh-key/255abc8e-4628-4d53-a497-e1bd23c06757
167+
method: DELETE
168+
response:
169+
body: ""
170+
headers:
171+
Content-Security-Policy:
172+
- default-src 'none'; frame-ancestors 'none'
173+
Content-Type:
174+
- application/json
175+
Date:
176+
- Tue, 23 Jun 2020 14:33:35 GMT
177+
Server:
178+
- agw_listener_public_vip
179+
Strict-Transport-Security:
180+
- max-age=63072000
181+
X-Content-Type-Options:
182+
- nosniff
183+
X-Frame-Options:
184+
- DENY
185+
X-Request-Id:
186+
- 9f1fc349-7996-4343-a432-5bdb34cfeed3
187+
status: 204 No Content
188+
code: 204
189+
duration: ""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
🎲🎲🎲 EXIT CODE: 0 🎲🎲🎲
2+
🟩🟩🟩 STDOUT️ 🟩🟩🟩️
3+
✅ Initialization completed with success.
4+
🟩🟩🟩 JSON STDOUT 🟩🟩🟩
5+
{
6+
"message": "Initialization completed with success",
7+
"details": ""
8+
}

0 commit comments

Comments
 (0)