Skip to content

Commit cd7e8e8

Browse files
jtherinremyleone
andauthored
feat(instance): import file for cloud-init (#1525)
* feat(instance): import file for cloud-init * fix: add io.Reader support for @file Co-authored-by: Rémy Léone <[email protected]>
1 parent c62c15f commit cd7e8e8

15 files changed

+573
-11
lines changed

cmd/scw/testdata/test-all-usage-instance-server-create-usage.golden

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ ARGS:
3535
[security-group-id] The security group ID it use for this server
3636
[placement-group-id] The placement group ID in witch the server has to be created
3737
[bootscript-id] The bootscript ID to use, if empty the local boot will be used
38-
[cloud-init] The cloud-init script to use
38+
[cloud-init] The cloud-init script to use (Support file loading with @/path/to/file)
3939
[boot-type=local] The boot type to use, if empty the local boot will be used. Will be overwritten to bootscript if bootscript-id is set. (local | bootscript | rescue)
4040
[project-id] Project ID to use. If none is passed the default project ID will be used
4141
[zone=fr-par-1] Zone to target. If none is passed will use default zone from the config

cmd/scw/testdata/test-all-usage-instance-server-update-usage.golden

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ ARGS:
2828
server-id UUID of the server
2929
[name] Name of the server
3030
[ip] IP that should be attached to the server (use ip=none to detach)
31-
[cloud-init] The cloud-init script to use
31+
[cloud-init] The cloud-init script to use (Support file loading with @/path/to/file)
3232
[boot-type] (local | bootscript | rescue)
3333
[tags.{index}] Tags of the server
3434
[bootscript]

cmd/scw/testdata/test-all-usage-instance-user-data-set-usage.golden

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ USAGE:
88
ARGS:
99
server-id UUID of the server
1010
key Key of the user data to set
11-
content Content of the user data
11+
content Content of the user data (Support file loading with @/path/to/file)
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 | nl-ams-1 | pl-waw-1)
1313

1414
FLAGS:

internal/core/arg_file_content.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package core
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"io"
7+
"io/ioutil"
8+
"reflect"
9+
"strings"
10+
11+
"github.com/scaleway/scaleway-sdk-go/strcase"
12+
)
13+
14+
// loadArgsFileContent will hydrate args with default values.
15+
func loadArgsFileContent(cmd *Command, cmdArgs interface{}) error {
16+
for _, argSpec := range cmd.ArgSpecs {
17+
if !argSpec.CanLoadFile {
18+
continue
19+
}
20+
21+
fieldName := strcase.ToPublicGoName(argSpec.Name)
22+
fieldValues, err := getValuesForFieldByName(reflect.ValueOf(cmdArgs), strings.Split(fieldName, "."))
23+
if err != nil {
24+
continue
25+
}
26+
27+
for _, v := range fieldValues {
28+
switch i := v.Interface().(type) {
29+
case io.Reader:
30+
b, err := ioutil.ReadAll(i)
31+
if err != nil {
32+
return fmt.Errorf("could not read argument: %s", err)
33+
}
34+
35+
if strings.HasPrefix(string(b), "@") {
36+
content, err := ioutil.ReadFile(string(b)[1:])
37+
if err != nil {
38+
return fmt.Errorf("could not open requested file: %s", err)
39+
}
40+
test := bytes.NewBuffer(content)
41+
v.Set(reflect.ValueOf(test))
42+
}
43+
case *string:
44+
if strings.HasPrefix(*i, "@") {
45+
content, err := ioutil.ReadFile((*i)[1:])
46+
if err != nil {
47+
return fmt.Errorf("could not open requested file: %s", err)
48+
}
49+
v.SetString(string(content))
50+
}
51+
}
52+
}
53+
}
54+
55+
return nil
56+
}

internal/core/arg_specs.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ type ArgSpec struct {
9999
// Deprecated is used to flag an argument as deprecated.
100100
// Use the short field to indicate migration tips for users.
101101
Deprecated bool
102+
103+
// CanLoadFile allow to use @ prefix to load a file as content
104+
CanLoadFile bool
102105
}
103106

104107
func (a *ArgSpec) Prefix() string {

internal/core/cobra_usage_builder.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,19 @@ func buildUsageArgs(ctx context.Context, cmd *Command, deprecated bool) string {
4545
func _buildUsageArgs(ctx context.Context, w io.Writer, argSpecs ArgSpecs) error {
4646
for _, argSpec := range argSpecs {
4747
argSpecUsageLeftPart := argSpec.Name
48+
argSpecUsageRightPart := _buildArgShort(argSpec)
4849
if argSpec.Default != nil {
4950
_, doc := argSpec.Default(ctx)
5051
argSpecUsageLeftPart = fmt.Sprintf("%s=%s", argSpecUsageLeftPart, doc)
5152
}
5253
if !argSpec.Required && !argSpec.Positional {
5354
argSpecUsageLeftPart = fmt.Sprintf("[%s]", argSpecUsageLeftPart)
5455
}
56+
if argSpec.CanLoadFile {
57+
argSpecUsageRightPart += " (Support file loading with @/path/to/file)"
58+
}
5559

56-
_, err := fmt.Fprintf(w, " %s\t%s\n", argSpecUsageLeftPart, _buildArgShort(argSpec))
60+
_, err := fmt.Fprintf(w, " %s\t%s\n", argSpecUsageLeftPart, argSpecUsageRightPart)
5761
if err != nil {
5862
return err
5963
}

internal/core/cobra_utils.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,12 @@ func run(ctx context.Context, cobraCmd *cobra.Command, cmd *Command, rawArgs []s
115115
return nil, err
116116
}
117117

118+
// Load args file imports.
119+
err = loadArgsFileContent(cmd, cmdArgs)
120+
if err != nil {
121+
return nil, err
122+
}
123+
118124
// PreValidate hook.
119125
if cmd.PreValidateFunc != nil {
120126
err = cmd.PreValidateFunc(ctx, cmdArgs)

internal/namespaces/instance/v1/custom_server.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,9 @@ func serverUpdateBuilder(c *core.Command) *core.Command {
202202
Short: `IP that should be attached to the server (use ip=none to detach)`,
203203
})
204204
c.ArgSpecs.AddBefore("boot-type", &core.ArgSpec{
205-
Name: "cloud-init",
206-
Short: "The cloud-init script to use",
205+
Name: "cloud-init",
206+
Short: "The cloud-init script to use",
207+
CanLoadFile: true,
207208
})
208209

209210
c.Run = func(ctx context.Context, argsI interface{}) (i interface{}, e error) {

internal/namespaces/instance/v1/custom_server_create.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,9 @@ func serverCreateCommand() *core.Command {
121121
Short: "The bootscript ID to use, if empty the local boot will be used",
122122
},
123123
{
124-
Name: "cloud-init",
125-
Short: "The cloud-init script to use",
124+
Name: "cloud-init",
125+
Short: "The cloud-init script to use",
126+
CanLoadFile: true,
126127
},
127128
{
128129
Name: "boot-type",

internal/namespaces/instance/v1/custom_user_data.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ func userDataDeleteBuilder(c *core.Command) *core.Command {
2323

2424
func userDataSetBuilder(c *core.Command) *core.Command {
2525
*c.ArgSpecs.GetByName("content.name") = core.ArgSpec{
26-
Name: "content",
27-
Short: "Content of the user data",
28-
Required: true,
26+
Name: "content",
27+
Short: "Content of the user data",
28+
Required: true,
29+
CanLoadFile: true,
2930
}
3031

3132
c.ArgSpecs.DeleteByName("content.content-type")

0 commit comments

Comments
 (0)