From bd8aa91a803a70f3ac833482dfe1d8c7a5deee3e Mon Sep 17 00:00:00 2001 From: noah Date: Mon, 11 Apr 2022 22:11:30 +0900 Subject: [PATCH 1/4] Add the `DynamicPayload` param --- pkg/api/deployment.go | 7 ++++--- pkg/api/deployment_test.go | 11 ++++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/pkg/api/deployment.go b/pkg/api/deployment.go index 7efd47eb..cce64b08 100644 --- a/pkg/api/deployment.go +++ b/pkg/api/deployment.go @@ -21,9 +21,10 @@ type ( } DeploymentCreateRequest struct { - Type string `json:"type"` - Ref string `json:"ref"` - Env string `json:"env"` + Type string `json:"type"` + Ref string `json:"ref"` + Env string `json:"env"` + DynamicPayload map[string]interface{} `json:"dynamic_payload"` } ) diff --git a/pkg/api/deployment_test.go b/pkg/api/deployment_test.go index df553bf2..e6695e14 100644 --- a/pkg/api/deployment_test.go +++ b/pkg/api/deployment_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "io/ioutil" "net/http" + "reflect" "testing" "github.com/gitploy-io/gitploy/model/ent" @@ -61,7 +62,12 @@ func TestDeploymentService_Create(t *testing.T) { } // Verify the fields of the body. - return b.Type == "branch" && b.Env == "production" && b.Ref == "main", nil + return b.Type == "branch" && + b.Env == "production" && + b.Ref == "main" && + reflect.DeepEqual(b.DynamicPayload, map[string]interface{}{ + "foo": "bar", + }), nil }). Reply(201). JSON(d) @@ -72,6 +78,9 @@ func TestDeploymentService_Create(t *testing.T) { Type: "branch", Env: "production", Ref: "main", + DynamicPayload: map[string]interface{}{ + "foo": "bar", + }, }) if err != nil { t.Fatalf("Create returns an error: %s", err) From ea29a84c969b7395dc11882f221ded631d341b7d Mon Sep 17 00:00:00 2001 From: noah Date: Mon, 11 Apr 2022 23:26:51 +0900 Subject: [PATCH 2/4] Add the 'field' for dynamic payload. --- cmd/cli/deployment_create.go | 94 ++++++++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 3 deletions(-) diff --git a/cmd/cli/deployment_create.go b/cmd/cli/deployment_create.go index d21c4a74..f065808a 100644 --- a/cmd/cli/deployment_create.go +++ b/cmd/cli/deployment_create.go @@ -1,8 +1,13 @@ package main import ( + "fmt" + "strconv" + "strings" + "github.com/urfave/cli/v2" + "github.com/gitploy-io/gitploy/model/extent" "github.com/gitploy-io/gitploy/pkg/api" ) @@ -26,6 +31,11 @@ var deploymentCreateCommand = &cli.Command{ Usage: "The specific ref. It can be any named branch, tag, or SHA.", Required: true, }, + &cli.StringSliceFlag{ + Name: "field", + Aliases: []string{"f"}, + Usage: "The pair of key and value to add to the payload. The format must be =.", + }, }, Action: func(cli *cli.Context) error { ns, n, err := splitFullName(cli.Args().First()) @@ -34,10 +44,26 @@ var deploymentCreateCommand = &cli.Command{ } c := buildClient(cli) + + config, err := c.Config.Get(cli.Context, ns, n) + if err != nil { + return err + } + + // If the 'dynamic_payload' field is enabled, + // it creates a payload and pass it as a parameter. + var payload map[string]interface{} + if env := config.GetEnv(cli.String("env")); env.IsDynamicPayloadEnabled() { + if payload, err = buildDyanmicPayload(cli.StringSlice("field"), env); err != nil { + return err + } + } + d, err := c.Deployment.Create(cli.Context, ns, n, &api.DeploymentCreateRequest{ - Type: cli.String("type"), - Ref: cli.String("ref"), - Env: cli.String("env"), + Type: cli.String("type"), + Ref: cli.String("ref"), + Env: cli.String("env"), + DynamicPayload: payload, }) if err != nil { return err @@ -46,3 +72,65 @@ var deploymentCreateCommand = &cli.Command{ return printJson(cli, d) }, } + +func buildDyanmicPayload(fields []string, env *extent.Env) (map[string]interface{}, error) { + values := make(map[string]string) + + for _, f := range fields { + keyAndValue := strings.SplitN(f, "=", 2) + if len(keyAndValue) != 2 { + return nil, fmt.Errorf("The field must be = format") + } + + values[keyAndValue[0]] = keyAndValue[1] + } + + payload := make(map[string]interface{}) + + for key, input := range env.DynamicPayload.Inputs { + val, ok := values[key] + // Set the default value if the value doesn't exist. + if !ok { + if input.Default != nil { + payload[key] = *input.Default + continue + } + } + + parsed, err := parseValue(input, val) + if err != nil { + return nil, fmt.Errorf("The value of the '%s' field is not %s", key, input.Type) + } + + payload[key] = parsed + } + + return payload, nil +} + +func parseValue(input extent.Input, s string) (interface{}, error) { + switch input.Type { + case extent.InputTypeSelect: + return s, nil + + case extent.InputTypeString: + return s, nil + + case extent.InputTypeNumber: + if val, err := strconv.ParseFloat(s, 64); err != nil { + return nil, err + } else { + return val, nil + } + + case extent.InputTypeBoolean: + if val, err := strconv.ParseBool(s); err != nil { + return nil, err + } else { + return val, nil + } + + default: + return nil, fmt.Errorf("%s is unsupported type.", input.Type) + } +} From bd4c62a0b1f6a8a93a99de2a12890f134bdaedc5 Mon Sep 17 00:00:00 2001 From: noah Date: Sun, 17 Apr 2022 11:40:09 +0900 Subject: [PATCH 3/4] Add validation for required field --- cmd/cli/deployment_create.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cmd/cli/deployment_create.go b/cmd/cli/deployment_create.go index f065808a..10f6b777 100644 --- a/cmd/cli/deployment_create.go +++ b/cmd/cli/deployment_create.go @@ -87,6 +87,7 @@ func buildDyanmicPayload(fields []string, env *extent.Env) (map[string]interface payload := make(map[string]interface{}) + // Build the payload, and use the default value if there is no value. for key, input := range env.DynamicPayload.Inputs { val, ok := values[key] // Set the default value if the value doesn't exist. @@ -105,6 +106,17 @@ func buildDyanmicPayload(fields []string, env *extent.Env) (map[string]interface payload[key] = parsed } + // Check that the required values are present. + for key, input := range env.DynamicPayload.Inputs { + if !(input.Required != nil && *input.Required) { + continue + } + + if _, ok := payload[key]; !ok { + return nil, fmt.Errorf("The value of the '%s' field is required", key) + } + } + return payload, nil } From e7201ca5d400cd11223229743c910b64784bfe24 Mon Sep 17 00:00:00 2001 From: noah Date: Sun, 17 Apr 2022 12:47:16 +0900 Subject: [PATCH 4/4] Add unit tests --- cmd/cli/deployment_create.go | 3 +- cmd/cli/deployment_create_test.go | 53 +++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 cmd/cli/deployment_create_test.go diff --git a/cmd/cli/deployment_create.go b/cmd/cli/deployment_create.go index 10f6b777..f9adee59 100644 --- a/cmd/cli/deployment_create.go +++ b/cmd/cli/deployment_create.go @@ -94,8 +94,9 @@ func buildDyanmicPayload(fields []string, env *extent.Env) (map[string]interface if !ok { if input.Default != nil { payload[key] = *input.Default - continue } + + continue } parsed, err := parseValue(input, val) diff --git a/cmd/cli/deployment_create_test.go b/cmd/cli/deployment_create_test.go new file mode 100644 index 00000000..a80aada0 --- /dev/null +++ b/cmd/cli/deployment_create_test.go @@ -0,0 +1,53 @@ +package main + +import ( + "reflect" + "testing" + + "github.com/gitploy-io/gitploy/model/extent" +) + +func Test_buildDyanmicPayload(t *testing.T) { + t.Run("Return an error when syntax is invalid.", func(t *testing.T) { + _, err := buildDyanmicPayload([]string{ + "foo", + }, &extent.Env{ + DynamicPayload: &extent.DynamicPayload{ + Enabled: true, + }, + }) + + if err == nil { + t.Fatalf("buildDyanmicPayload dosen't return an error") + } + }) + + t.Run("Return a payload with default values.", func(t *testing.T) { + var qux interface{} = "qux" + + payload, err := buildDyanmicPayload([]string{}, &extent.Env{ + DynamicPayload: &extent.DynamicPayload{ + Enabled: true, + Inputs: map[string]extent.Input{ + "foo": { + Type: extent.InputTypeString, + }, + "baz": { + Type: extent.InputTypeString, + Default: &qux, + }, + }, + }, + }) + + if err != nil { + t.Fatalf("buildDyanmicPayload returns an error") + } + + if expected := map[string]interface{}{ + "baz": "qux", + }; !reflect.DeepEqual(payload, expected) { + t.Fatalf("buildDyanmicPayload = %v, wanted %v", payload, expected) + } + }) +}