Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(instance): import file for cloud-init #1525

Merged
merged 10 commits into from
May 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ ARGS:
[security-group-id] The security group ID it use for this server
[placement-group-id] The placement group ID in witch the server has to be created
[bootscript-id] The bootscript ID to use, if empty the local boot will be used
[cloud-init] The cloud-init script to use
[cloud-init] The cloud-init script to use (Support file loading with @/path/to/file)
[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)
[project-id] Project ID to use. If none is passed the default project ID will be used
[zone=fr-par-1] Zone to target. If none is passed will use default zone from the config
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ ARGS:
server-id UUID of the server
[name] Name of the server
[ip] IP that should be attached to the server (use ip=none to detach)
[cloud-init] The cloud-init script to use
[cloud-init] The cloud-init script to use (Support file loading with @/path/to/file)
[boot-type] (local | bootscript | rescue)
[tags.{index}] Tags of the server
[bootscript]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ USAGE:
ARGS:
server-id UUID of the server
key Key of the user data to set
content Content of the user data
content Content of the user data (Support file loading with @/path/to/file)
[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)

FLAGS:
Expand Down
56 changes: 56 additions & 0 deletions internal/core/arg_file_content.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package core

import (
"bytes"
"fmt"
"io"
"io/ioutil"
"reflect"
"strings"

"github.com/scaleway/scaleway-sdk-go/strcase"
)

// loadArgsFileContent will hydrate args with default values.
func loadArgsFileContent(cmd *Command, cmdArgs interface{}) error {
for _, argSpec := range cmd.ArgSpecs {
if !argSpec.CanLoadFile {
continue
}

fieldName := strcase.ToPublicGoName(argSpec.Name)
fieldValues, err := getValuesForFieldByName(reflect.ValueOf(cmdArgs), strings.Split(fieldName, "."))
if err != nil {
continue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should return an error and not fail silently ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validate.go line 48 et 78 also execute getValuesForFieldByName, log an info and continue.
It would not be too much to log the same info many times ?

}

for _, v := range fieldValues {
switch i := v.Interface().(type) {
case io.Reader:
b, err := ioutil.ReadAll(i)
if err != nil {
return fmt.Errorf("could not read argument: %s", err)
}

if strings.HasPrefix(string(b), "@") {
content, err := ioutil.ReadFile(string(b)[1:])
if err != nil {
return fmt.Errorf("could not open requested file: %s", err)
}
test := bytes.NewBuffer(content)
v.Set(reflect.ValueOf(test))
}
case *string:
if strings.HasPrefix(*i, "@") {
content, err := ioutil.ReadFile((*i)[1:])
if err != nil {
return fmt.Errorf("could not open requested file: %s", err)
}
v.SetString(string(content))
}
}
}
}

return nil
}
3 changes: 3 additions & 0 deletions internal/core/arg_specs.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ type ArgSpec struct {
// Deprecated is used to flag an argument as deprecated.
// Use the short field to indicate migration tips for users.
Deprecated bool

// CanLoadFile allow to use @ prefix to load a file as content
CanLoadFile bool
}

func (a *ArgSpec) Prefix() string {
Expand Down
6 changes: 5 additions & 1 deletion internal/core/cobra_usage_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,19 @@ func buildUsageArgs(ctx context.Context, cmd *Command, deprecated bool) string {
func _buildUsageArgs(ctx context.Context, w io.Writer, argSpecs ArgSpecs) error {
for _, argSpec := range argSpecs {
argSpecUsageLeftPart := argSpec.Name
argSpecUsageRightPart := _buildArgShort(argSpec)
if argSpec.Default != nil {
_, doc := argSpec.Default(ctx)
argSpecUsageLeftPart = fmt.Sprintf("%s=%s", argSpecUsageLeftPart, doc)
}
if !argSpec.Required && !argSpec.Positional {
argSpecUsageLeftPart = fmt.Sprintf("[%s]", argSpecUsageLeftPart)
}
if argSpec.CanLoadFile {
argSpecUsageRightPart += " (Support file loading with @/path/to/file)"
}

_, err := fmt.Fprintf(w, " %s\t%s\n", argSpecUsageLeftPart, _buildArgShort(argSpec))
_, err := fmt.Fprintf(w, " %s\t%s\n", argSpecUsageLeftPart, argSpecUsageRightPart)
if err != nil {
return err
}
Expand Down
6 changes: 6 additions & 0 deletions internal/core/cobra_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ func run(ctx context.Context, cobraCmd *cobra.Command, cmd *Command, rawArgs []s
return nil, err
}

// Load args file imports.
err = loadArgsFileContent(cmd, cmdArgs)
if err != nil {
return nil, err
}

// PreValidate hook.
if cmd.PreValidateFunc != nil {
err = cmd.PreValidateFunc(ctx, cmdArgs)
Expand Down
5 changes: 3 additions & 2 deletions internal/namespaces/instance/v1/custom_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,9 @@ func serverUpdateBuilder(c *core.Command) *core.Command {
Short: `IP that should be attached to the server (use ip=none to detach)`,
})
c.ArgSpecs.AddBefore("boot-type", &core.ArgSpec{
Name: "cloud-init",
Short: "The cloud-init script to use",
Name: "cloud-init",
Short: "The cloud-init script to use",
CanLoadFile: true,
})

c.Run = func(ctx context.Context, argsI interface{}) (i interface{}, e error) {
Expand Down
5 changes: 3 additions & 2 deletions internal/namespaces/instance/v1/custom_server_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,9 @@ func serverCreateCommand() *core.Command {
Short: "The bootscript ID to use, if empty the local boot will be used",
},
{
Name: "cloud-init",
Short: "The cloud-init script to use",
Name: "cloud-init",
Short: "The cloud-init script to use",
CanLoadFile: true,
},
{
Name: "boot-type",
Expand Down
7 changes: 4 additions & 3 deletions internal/namespaces/instance/v1/custom_user_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ func userDataDeleteBuilder(c *core.Command) *core.Command {

func userDataSetBuilder(c *core.Command) *core.Command {
*c.ArgSpecs.GetByName("content.name") = core.ArgSpec{
Name: "content",
Short: "Content of the user data",
Required: true,
Name: "content",
Short: "Content of the user data",
Required: true,
CanLoadFile: true,
}

c.ArgSpecs.DeleteByName("content.content-type")
Expand Down
52 changes: 52 additions & 0 deletions internal/namespaces/instance/v1/custom_user_data_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package instance

import (
"io/ioutil"
"os"
"testing"

"github.com/scaleway/scaleway-cli/internal/core"
Expand Down Expand Up @@ -49,3 +51,53 @@ func Test_UserDataList(t *testing.T) {
),
}))
}

func Test_UserDataFileUpload(t *testing.T) {
content := "cloud-init file content"

t.Run("on-cloud-init", core.Test(&core.TestConfig{
Commands: GetCommands(),
BeforeFunc: core.BeforeFuncCombine(
core.ExecStoreBeforeCmd("Server", "scw instance server create stopped=true image=ubuntu-bionic"),
func(ctx *core.BeforeFuncCtx) error {
file, _ := ioutil.TempFile("", "test")
_, _ = file.WriteString(content)
ctx.Meta["filePath"] = file.Name()
return nil
},
),
Cmd: `scw instance user-data set key=cloud-init server-id={{ .Server.ID }} content=@{{ .filePath }}`,
Check: core.TestCheckCombine(
core.TestCheckGolden(),
),
AfterFunc: core.AfterFuncCombine(
func(ctx *core.AfterFuncCtx) error {
_ = os.RemoveAll(ctx.Meta["filePath"].(string))
return nil
},
),
}))

t.Run("on-random-key", core.Test(&core.TestConfig{
Commands: GetCommands(),
BeforeFunc: core.BeforeFuncCombine(
core.ExecStoreBeforeCmd("Server", "scw instance server create stopped=true image=ubuntu-bionic"),
func(ctx *core.BeforeFuncCtx) error {
file, _ := ioutil.TempFile("", "test")
_, _ = file.WriteString(content)
ctx.Meta["filePath"] = file.Name()
return nil
},
),
Cmd: `scw instance user-data set key=foobar server-id={{ .Server.ID }} content=@{{ .filePath }}`,
Check: core.TestCheckCombine(
core.TestCheckGolden(),
),
AfterFunc: core.AfterFuncCombine(
func(ctx *core.AfterFuncCtx) error {
_ = os.RemoveAll(ctx.Meta["filePath"].(string))
return nil
},
),
}))
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
🎲🎲🎲 EXIT CODE: 0 🎲🎲🎲
🟩🟩🟩 STDOUT️ 🟩🟩🟩️
✅ Success.
🟩🟩🟩 JSON STDOUT 🟩🟩🟩
{
"message": "Success",
"details": ""
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
🎲🎲🎲 EXIT CODE: 0 🎲🎲🎲
🟩🟩🟩 STDOUT️ 🟩🟩🟩️
✅ Success.
🟩🟩🟩 JSON STDOUT 🟩🟩🟩
{
"message": "Success",
"details": ""
}