Skip to content

Commit 97c5eda

Browse files
authored
Merge branch 'master' into instance_server_get_pretty_volumes
2 parents e992a55 + 157a515 commit 97c5eda

File tree

10 files changed

+160
-85
lines changed

10 files changed

+160
-85
lines changed

.github/workflows/deploy-docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
run: |
3434
sudo apt update
3535
sudo apt install curl unzip -y
36-
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
36+
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.22.20.zip" -o "awscliv2.zip"
3737
unzip awscliv2.zip
3838
./aws/install
3939
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
🎲🎲🎲 EXIT CODE: 0 🎲🎲🎲
2+
🟥🟥🟥 STDERR️️ 🟥🟥🟥️
3+
Keys registered via the Scaleway Console will be propagated to the selected servers.
4+
The command 'ssh <server-ip> -t -l <username> scw-fetch-ssh-keys --upgrade' will be run on the servers matching the zone and project filters.
5+
Keep in mind that you need to be able to connect to your server with another key than the one you want to add.
6+
Keep in mind that SSH keys are scoped by project.
7+
8+
USAGE:
9+
scw instance ssh fetch-keys [arg=value ...]
10+
11+
ARGS:
12+
[project-id] Fetch the keys on all servers in the given Project
13+
[username=root] Username used for the SSH connection
14+
[zone=fr-par-1] Zone to target. If none is passed will use default zone from the config (fr-par-1 | fr-par-2 | fr-par-3 | nl-ams-1 | nl-ams-2 | nl-ams-3 | pl-waw-1 | pl-waw-2 | pl-waw-3)
15+
16+
FLAGS:
17+
-h, --help help for fetch-keys
18+
19+
GLOBAL FLAGS:
20+
-c, --config string The path to the config file
21+
-D, --debug Enable debug mode
22+
-o, --output string Output format: json or human, see 'scw help output' for more info (default "human")
23+
-p, --profile string The config profile to use
24+
--web open console page for the current ressource

cmd/scw/testdata/test-all-usage-instance-ssh-list-keys-usage.golden

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ USAGE:
88
scw instance ssh list-keys <server-id ...> [arg=value ...]
99

1010
ARGS:
11-
server-id Server to add your key to
11+
server-id Server which keys are to be listed
1212
[zone=fr-par-1] Zone to target. If none is passed will use default zone from the config (fr-par-1 | fr-par-2 | fr-par-3 | nl-ams-1 | nl-ams-2 | nl-ams-3 | pl-waw-1 | pl-waw-2 | pl-waw-3)
1313

1414
FLAGS:

cmd/scw/testdata/test-all-usage-instance-ssh-usage.golden

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ USAGE:
99

1010
UTILITY COMMANDS:
1111
add-key Add a public key to a server
12+
fetch-keys Fetch SSH keys from the console and install them on multiple servers
1213
list-keys List manually added public keys
1314
remove-key Remove a manually added public key from a server
1415

core/arg_specs.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,15 @@ var AllLocalities = "all"
1414

1515
type ArgSpecs []*ArgSpec
1616

17-
// GetPositionalArg returns the last positional argument from the arg specs.
17+
// GetPositionalArg if exist returns the positional argument from the arg specs.
18+
// Panics when more than one positional arg is found.
1819
func (s ArgSpecs) GetPositionalArg() *ArgSpec {
1920
var positionalArg *ArgSpec
2021
for _, argSpec := range s {
2122
if argSpec.Positional {
23+
if positionalArg != nil {
24+
panic(fmt.Errorf("more than one positional parameter detected: %s and %s are flagged as positional arg", positionalArg.Name, argSpec.Name))
25+
}
2226
positionalArg = argSpec
2327
}
2428
}

core/cobra_utils_test.go

Lines changed: 0 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,6 @@ type testType struct {
1717
Tag string
1818
}
1919

20-
type testTypeManyTags struct {
21-
NameID string
22-
Tags []string
23-
}
24-
2520
type testDate struct {
2621
Date *time.Time
2722
}
@@ -83,45 +78,6 @@ func testGetCommands() *core.Commands {
8378
return argsI, nil
8479
},
8580
},
86-
&core.Command{
87-
Namespace: "test",
88-
Resource: "many-positional",
89-
ArgSpecs: core.ArgSpecs{
90-
{
91-
Name: "name-id",
92-
Positional: true,
93-
},
94-
{
95-
Name: "tag",
96-
Positional: true,
97-
},
98-
},
99-
AllowAnonymousClient: true,
100-
ArgsType: reflect.TypeOf(testType{}),
101-
Run: func(_ context.Context, argsI interface{}) (i interface{}, e error) {
102-
return argsI, nil
103-
},
104-
},
105-
&core.Command{
106-
Namespace: "test",
107-
Resource: "many-multi-positional",
108-
ArgSpecs: core.ArgSpecs{
109-
{
110-
Name: "name-id",
111-
Positional: true,
112-
},
113-
{
114-
Name: "tags",
115-
Positional: true,
116-
},
117-
},
118-
AcceptMultiplePositionalArgs: true,
119-
AllowAnonymousClient: true,
120-
ArgsType: reflect.TypeOf(testTypeManyTags{}),
121-
Run: func(_ context.Context, argsI interface{}) (i interface{}, e error) {
122-
return argsI, nil
123-
},
124-
},
12581
&core.Command{
12682
Namespace: "test",
12783
Resource: "raw-args",
@@ -298,41 +254,6 @@ func Test_PositionalArg(t *testing.T) {
298254
core.TestCheckGolden(),
299255
),
300256
}))
301-
302-
t.Run("many positional", core.Test(&core.TestConfig{
303-
Commands: testGetCommands(),
304-
Cmd: "scw test many-positional tag1 name-id=plop",
305-
Check: core.TestCheckExitCode(0),
306-
}))
307-
308-
t.Run("many positional", core.Test(&core.TestConfig{
309-
Commands: testGetCommands(),
310-
Cmd: "scw test many-positional tag1 name-id=plop",
311-
Check: core.TestCheckCombine(
312-
core.TestCheckExitCode(0),
313-
func(t *testing.T, ctx *core.CheckFuncCtx) {
314-
t.Helper()
315-
res := ctx.Result.(*testType)
316-
assert.Equal(t, "plop", res.NameID)
317-
assert.Equal(t, "tag1", res.Tag)
318-
},
319-
),
320-
}))
321-
322-
t.Run("many multi-positional", core.Test(&core.TestConfig{
323-
Commands: testGetCommands(),
324-
Cmd: "scw test many-multi-positional pos1 pos2 name-id=plop",
325-
Check: core.TestCheckCombine(
326-
core.TestCheckExitCode(0),
327-
func(t *testing.T, ctx *core.CheckFuncCtx) {
328-
t.Helper()
329-
res := ctx.Result.(*testTypeManyTags)
330-
assert.Equal(t, "plop", res.NameID)
331-
assert.Equal(t, "pos1", res.Tags[0])
332-
assert.Equal(t, "pos2", res.Tags[1])
333-
},
334-
),
335-
}))
336257
}
337258

338259
func Test_MultiPositionalArg(t *testing.T) {

docs/commands/instance.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ This API allows you to manage your Instances.
8787
- [Wait for snapshot to reach a stable state](#wait-for-snapshot-to-reach-a-stable-state)
8888
- [SSH Utilities](#ssh-utilities)
8989
- [Add a public key to a server](#add-a-public-key-to-a-server)
90+
- [Fetch SSH keys from the console and install them on multiple servers](#fetch-ssh-keys-from-the-console-and-install-them-on-multiple-servers)
9091
- [Install a ssh config with all your servers as host
9192
It generate hosts for instance servers, baremetal, apple-silicon and bastions](#install-a-ssh-config-with-all-your-servers-as-host
9293
it-generate-hosts-for-instance-servers,-baremetal,-apple-silicon-and-bastions)
@@ -2769,6 +2770,30 @@ scw instance ssh add-key [arg=value ...]
27692770

27702771

27712772

2773+
### Fetch SSH keys from the console and install them on multiple servers
2774+
2775+
Keys registered via the Scaleway Console will be propagated to the selected servers.
2776+
The command 'ssh <server-ip> -t -l <username> scw-fetch-ssh-keys --upgrade' will be run on the servers matching the zone and project filters.
2777+
Keep in mind that you need to be able to connect to your server with another key than the one you want to add.
2778+
Keep in mind that SSH keys are scoped by project.
2779+
2780+
**Usage:**
2781+
2782+
```
2783+
scw instance ssh fetch-keys [arg=value ...]
2784+
```
2785+
2786+
2787+
**Args:**
2788+
2789+
| Name | | Description |
2790+
|------|---|-------------|
2791+
| project-id | | Fetch the keys on all servers in the given Project |
2792+
| username | Default: `root` | Username used for the SSH connection |
2793+
| zone | Default: `fr-par-1`<br />One of: `fr-par-1`, `fr-par-2`, `fr-par-3`, `nl-ams-1`, `nl-ams-2`, `nl-ams-3`, `pl-waw-1`, `pl-waw-2`, `pl-waw-3` | Zone to target. If none is passed will use default zone from the config |
2794+
2795+
2796+
27722797
### Install a ssh config with all your servers as host
27732798
It generate hosts for instance servers, baremetal, apple-silicon and bastions
27742799

@@ -2807,7 +2832,7 @@ scw instance ssh list-keys <server-id ...> [arg=value ...]
28072832

28082833
| Name | | Description |
28092834
|------|---|-------------|
2810-
| server-id | Required | Server to add your key to |
2835+
| server-id | Required | Server which keys are to be listed |
28112836
| zone | Default: `fr-par-1`<br />One of: `fr-par-1`, `fr-par-2`, `fr-par-3`, `nl-ams-1`, `nl-ams-2`, `nl-ams-3`, `pl-waw-1`, `pl-waw-2`, `pl-waw-3` | Zone to target. If none is passed will use default zone from the config |
28122837

28132838

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/scaleway/scaleway-cli/v2
22

3-
go 1.24
3+
go 1.24.0
44

55
require (
66
github.com/aws/aws-sdk-go-v2 v1.36.3

internal/namespaces/instance/v1/custom.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ func GetCommands() *core.Commands {
197197
instanceSSH(),
198198
sshAddKeyCommand(),
199199
sshConfigInstallCommand(),
200+
sshFetchKeysCommand(),
200201
sshListKeysCommand(),
201202
sshRemoveKeyCommand(),
202203
instanceServerGetRdpPassword(),

internal/namespaces/instance/v1/custom_ssh_key.go

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import (
77
"errors"
88
"fmt"
99
"hash/crc32"
10+
"os/exec"
1011
"reflect"
1112
"strings"
1213

1314
"github.com/scaleway/scaleway-cli/v2/core"
1415
"github.com/scaleway/scaleway-cli/v2/core/human"
16+
"github.com/scaleway/scaleway-cli/v2/internal/interactive"
1517
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
1618
"github.com/scaleway/scaleway-sdk-go/scw"
1719
)
@@ -132,6 +134,103 @@ Lookup /root/.ssh/authorized_keys on your server for more information`,
132134
}
133135
}
134136

137+
type sshFetchKeysRequest struct {
138+
Zone scw.Zone
139+
ProjectID string
140+
Username string
141+
}
142+
143+
func sshFetchKeysCommand() *core.Command {
144+
return &core.Command{
145+
Namespace: "instance",
146+
Resource: "ssh",
147+
Verb: "fetch-keys",
148+
Groups: []string{"utility"},
149+
Short: "Fetch SSH keys from the console and install them on multiple servers",
150+
Long: `Keys registered via the Scaleway Console will be propagated to the selected servers.
151+
The command 'ssh <server-ip> -t -l <username> scw-fetch-ssh-keys --upgrade' will be run on the servers matching the zone and project filters.
152+
Keep in mind that you need to be able to connect to your server with another key than the one you want to add.
153+
Keep in mind that SSH keys are scoped by project.`,
154+
ArgsType: reflect.TypeOf(sshFetchKeysRequest{}),
155+
ArgSpecs: core.ArgSpecs{
156+
{
157+
Name: "project-id",
158+
Short: "Fetch the keys on all servers in the given Project",
159+
Required: false,
160+
},
161+
{
162+
Name: "username",
163+
Short: "Username used for the SSH connection",
164+
Default: core.DefaultValueSetter("root"),
165+
},
166+
core.ZoneArgSpec(((*instance.API)(nil)).Zones()...),
167+
},
168+
Run: func(ctx context.Context, argsI interface{}) (interface{}, error) {
169+
args := argsI.(*sshFetchKeysRequest)
170+
api := instance.NewAPI(core.ExtractClient(ctx))
171+
172+
listServersRequest := &instance.ListServersRequest{
173+
Zone: args.Zone,
174+
}
175+
if args.ProjectID != "" {
176+
listServersRequest.Project = &args.ProjectID
177+
}
178+
servers, err := api.ListServers(listServersRequest, scw.WithAllPages(), scw.WithContext(ctx))
179+
if err != nil {
180+
return nil, fmt.Errorf("failed to fetch servers: %w", err)
181+
}
182+
183+
for i, server := range servers.Servers {
184+
msg := fmt.Sprintf("Loading SSH keys on server %q", server.Name)
185+
if i == 0 {
186+
_, _ = interactive.Println(">", msg)
187+
} else {
188+
_, _ = interactive.Println("\n>", msg)
189+
}
190+
191+
if server.State != instance.ServerStateRunning {
192+
_, _ = interactive.Printf("Failed: server %q is not running", server.Name)
193+
194+
continue
195+
}
196+
197+
if len(server.PublicIPs) == 0 {
198+
_, _ = interactive.Printf("Failed: server %q has no public IP", server.Name)
199+
200+
continue
201+
}
202+
203+
for _, publicIP := range server.PublicIPs {
204+
sshArgs := []string{
205+
publicIP.Address.String(),
206+
"-t",
207+
"-l", args.Username,
208+
"/usr/sbin/scw-fetch-ssh-keys",
209+
"--upgrade",
210+
}
211+
212+
sshCmd := exec.Command("ssh", sshArgs...)
213+
214+
_, _ = interactive.Println(sshCmd)
215+
216+
exitCode, err := core.ExecCmd(ctx, sshCmd)
217+
if err != nil || exitCode != 0 {
218+
if err != nil {
219+
_, _ = interactive.Println("Failed:", err)
220+
} else {
221+
_, _ = interactive.Println("Failed: ssh command failed with exit code", exitCode)
222+
}
223+
} else {
224+
_, _ = interactive.Println("Success")
225+
}
226+
}
227+
}
228+
229+
return &core.SuccessResult{Empty: true}, nil
230+
},
231+
}
232+
}
233+
135234
type sshListKeysRequest struct {
136235
Zone scw.Zone
137236
ServerID string
@@ -151,7 +250,7 @@ Lookup /root/.ssh/authorized_keys on your server for more information`,
151250
ArgSpecs: core.ArgSpecs{
152251
{
153252
Name: "server-id",
154-
Short: "Server to add your key to",
253+
Short: "Server which keys are to be listed",
155254
Positional: true,
156255
Required: true,
157256
},

0 commit comments

Comments
 (0)