From c8b83ec9f2a66623d72e90ad3e9fe51c1c4a4f91 Mon Sep 17 00:00:00 2001 From: guido9j Date: Wed, 14 Jun 2023 16:58:00 -0400 Subject: [PATCH] Spinnaker Application Enhancements (#7) (#8) Enhancements to Spinnaker application resource: - Add support for cloud providers - Add support for permissions - Implemented Update method - Improved error messages for Spinnaker application Co-authored-by: James Guido --- README.md | 2 +- docs/resources/spinnaker_application.md | 18 ++++++ go.mod | 2 +- main.go | 2 +- spinnaker/api/application.go | 60 +++++++++++++++---- spinnaker/api/pipeline.go | 17 ++---- spinnaker/api/util.go | 15 +++++ spinnaker/provider.go | 10 ++-- spinnaker/provider_test.go | 8 +-- spinnaker/resource_application.go | 54 +++++++++++++---- spinnaker/resource_application_test.go | 2 +- spinnaker/resource_pipeline.go | 4 +- spinnaker/resource_pipeline_template.go | 4 +- .../resource_pipeline_template_config.go | 4 +- 14 files changed, 149 insertions(+), 53 deletions(-) create mode 100644 spinnaker/api/util.go diff --git a/README.md b/README.md index 7548a27..8067e90 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ https://spinnaker.io/concepts/ $ git clone git@github.com:homedepot/terraform-provider-spinnaker.git $ cd terraform-provider-spinnaker/ $ go build -$ go test./... +$ go test ./... ``` ## Using the provider diff --git a/docs/resources/spinnaker_application.md b/docs/resources/spinnaker_application.md index 159c4f2..45dfa0e 100644 --- a/docs/resources/spinnaker_application.md +++ b/docs/resources/spinnaker_application.md @@ -15,6 +15,22 @@ resource "spinnaker_application" "terraformtest" { } ``` +```hcl +resource "spinnaker_application" "terraformtest" { + application = "terraformtest" + email = "user@example.com" + cloud_providers = [ + "kubernetes", + "appengine" + ] + permissions { + read = [ "group1", "group2", "group3" ] + write = [ "group1" ] + execute = [ "group1", "group2" ] + } +} +``` + ## Argument Reference - `application` - (Required) Spinnaker application name. @@ -22,5 +38,7 @@ resource "spinnaker_application" "terraformtest" { - `description` - (Optional) Description. (Default: `""`) - `platform_health_only` - (Optional) Consider only cloud provider health when executing tasks. (Default: `false`) - `platform_health_only_show_override` - (Optional) Show health override option for each operation. (Default: `false`) +- `cloud_providers` - (Optional) Array list of cloud providers. +- `permissions` - (Optional) Array of permissions in key/value pairs. Valid permission keys are `read`, `write` and `execute`. Permission value is array list of groups. ## Attribute Reference diff --git a/go.mod b/go.mod index 61aaf0f..253ef54 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/guido9j/terraform-provider-spinnaker +module terraform-provider-spinnaker go 1.18 diff --git a/main.go b/main.go index 4fd3930..9e2f544 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" - "github.com/guido9j/terraform-provider-spinnaker/spinnaker" + "terraform-provider-spinnaker/spinnaker" ) func main() { diff --git a/spinnaker/api/application.go b/spinnaker/api/application.go index 790fcd4..4450481 100644 --- a/spinnaker/api/application.go +++ b/spinnaker/api/application.go @@ -6,10 +6,12 @@ import ( "strings" "time" - "github.com/antihax/optional" + "github.com/antihax/optional" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/mitchellh/mapstructure" + gate "github.com/spinnaker/spin/cmd/gateclient" - gateapi "github.com/spinnaker/spin/gateapi" + gateapi "github.com/spinnaker/spin/gateapi" ) func GetApplication(client *gate.GatewayClient, applicationName string, dest interface{}) error { @@ -23,7 +25,7 @@ func GetApplication(client *gate.GatewayClient, applicationName string, dest int } if err != nil { - return err + return FormatAPIErrorMessage ("ApplicationControllerApi.GetApplicationUsingGET", err) } if err := mapstructure.Decode(app, dest); err != nil { @@ -33,10 +35,19 @@ func GetApplication(client *gate.GatewayClient, applicationName string, dest int return nil } -func CreateApplication(client *gate.GatewayClient, applicationName, email, - applicationDescription string, platformHealthOnly, platformHealthOnlyShowOverride bool) error { +func CreateOrUpdateApplication(client *gate.GatewayClient, applicationName, email, + applicationDescription string, platformHealthOnly, platformHealthOnlyShowOverride bool, + cloud_providers []interface{}, permissions *schema.Set) error { - app := map[string]interface{}{ + jobType := "createApplication" + jobDescription := fmt.Sprintf("Create Application: %s", applicationName) + app, resp, err := client.ApplicationControllerApi.GetApplicationUsingGET(client.Context, applicationName, &gateapi.ApplicationControllerApiGetApplicationUsingGETOpts{Expand: optional.NewBool(false)}) + if resp != nil && resp.StatusCode == http.StatusOK && err == nil { + jobType = "updateApplication" + jobDescription = fmt.Sprintf("Update Application: %s", applicationName) + } + + app = map[string]interface{}{ "instancePort": 80, "name": applicationName, "email": email, @@ -45,15 +56,42 @@ func CreateApplication(client *gate.GatewayClient, applicationName, email, "description": applicationDescription, } + if len(cloud_providers) > 0 { + providers_csv := "" + for i := range cloud_providers { + if (i > 0) { providers_csv += "," } + providers_csv += cloud_providers[i].(string) + } + app["cloudProviders"] = providers_csv + } + + if permissions.Len() == 1 { + permissions_object := make(map[string]interface{}) + list := permissions.List() + for k, value := range list[0].(map[string]interface{}) { + switch key := k; key { + case "read": + permissions_object["READ"] = value + case "execute": + permissions_object["EXECUTE"] = value + case "write": + permissions_object["WRITE"] = value + default: + return fmt.Errorf("invalid permissions type of %s", key) + } + } + app["permissions"] = permissions_object + } + createAppTask := map[string]interface{}{ - "job": []interface{}{map[string]interface{}{"type": "createApplication", "application": app}}, + "job": []interface{}{map[string]interface{}{"type": jobType, "application": app}}, "application": applicationName, - "description": fmt.Sprintf("Create Application: %s", applicationName), + "description": jobDescription, } ref, _, err := client.TaskControllerApi.TaskUsingPOST1(client.Context, createAppTask) if err != nil { - return err + return FormatAPIErrorMessage ("TaskControllerApi.TaskUsingPOST1", err) } toks := strings.Split(ref["ref"].(string), "/") @@ -71,7 +109,7 @@ func CreateApplication(client *gate.GatewayClient, applicationName, email, } if err != nil { - return err + return FormatAPIErrorMessage ("TaskControllerApi.GetTaskUsingGET1", err) } if resp.StatusCode < 200 || resp.StatusCode > 299 { return fmt.Errorf("Encountered an error saving application, status code: %d\n", resp.StatusCode) @@ -100,7 +138,7 @@ func DeleteAppliation(client *gate.GatewayClient, applicationName string) error _, resp, err := client.TaskControllerApi.TaskUsingPOST1(client.Context, deleteAppTask) if err != nil { - return err + return FormatAPIErrorMessage ("TaskControllerApi.TaskUsingPOST1", err) } if resp.StatusCode != http.StatusOK { diff --git a/spinnaker/api/pipeline.go b/spinnaker/api/pipeline.go index 6798b9a..c376a52 100644 --- a/spinnaker/api/pipeline.go +++ b/spinnaker/api/pipeline.go @@ -6,22 +6,13 @@ import ( "github.com/mitchellh/mapstructure" gate "github.com/spinnaker/spin/cmd/gateclient" - gate_swagger "github.com/spinnaker/spin/gateapi" ) -func FormatAPIErrorMessage(function_name string, err error) error { - switch err_sw := err.(type) { - case gate_swagger.GenericSwaggerError: - return fmt.Errorf("%s: %s() response payload: %s", err_sw.Error(), function_name, err_sw.Body()) - } - return fmt.Errorf("%s: From %s()", err, function_name) -} - func CreatePipeline(client *gate.GatewayClient, pipeline interface{}) error { resp, err := client.PipelineControllerApi.SavePipelineUsingPOST(client.Context, pipeline, nil) if err != nil { - return FormatAPIErrorMessage ("PipelineControllerApi.SavePipelineUsingPOST", err) + return FormatAPIErrorMessage ("PipelineControllerApi.SavePipelineUsingPOST", err) } if resp.StatusCode != http.StatusOK { @@ -42,7 +33,7 @@ func GetPipeline(client *gate.GatewayClient, applicationName, pipelineName strin } return jsonMap, fmt.Errorf("Encountered an error getting pipeline %s, %s\n", pipelineName, - FormatAPIErrorMessage ("PipelineControllerApi.GetPipelineConfigUsingGET", err)) + FormatAPIErrorMessage ("PipelineControllerApi.GetPipelineConfigUsingGET", err)) } if resp.StatusCode != http.StatusOK { @@ -67,7 +58,7 @@ func UpdatePipeline(client *gate.GatewayClient, pipelineID string, pipeline inte _, resp, err := client.PipelineControllerApi.UpdatePipelineUsingPUT(client.Context, pipelineID, pipeline) if err != nil { - return FormatAPIErrorMessage ("PipelineControllerApi.UpdatePipelineUsingPUT", err) + return FormatAPIErrorMessage ("PipelineControllerApi.UpdatePipelineUsingPUT", err) } if resp.StatusCode != http.StatusOK { @@ -81,7 +72,7 @@ func DeletePipeline(client *gate.GatewayClient, applicationName, pipelineName st resp, err := client.PipelineControllerApi.DeletePipelineUsingDELETE(client.Context, applicationName, pipelineName) if err != nil { - return FormatAPIErrorMessage ("PipelineControllerApi.DeletePipelineUsingDELETE", err) + return FormatAPIErrorMessage ("PipelineControllerApi.DeletePipelineUsingDELETE", err) } if resp.StatusCode != http.StatusOK { diff --git a/spinnaker/api/util.go b/spinnaker/api/util.go new file mode 100644 index 0000000..51c3161 --- /dev/null +++ b/spinnaker/api/util.go @@ -0,0 +1,15 @@ +package api + +import ( + "fmt" + gate_swagger "github.com/spinnaker/spin/gateapi" +) + +func FormatAPIErrorMessage(function_name string, err error) error { + switch err_sw := err.(type) { + case gate_swagger.GenericSwaggerError: + return fmt.Errorf("%s: %s() response payload: %s", err_sw.Error(), function_name, err_sw.Body()) + } + return fmt.Errorf("%s: From %s()", err, function_name) +} + diff --git a/spinnaker/provider.go b/spinnaker/provider.go index bd6eabc..02cabf3 100644 --- a/spinnaker/provider.go +++ b/spinnaker/provider.go @@ -1,12 +1,12 @@ package spinnaker import ( - "os" - "io/ioutil" + "os" + "io" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/spinnaker/spin/cmd/output" + "github.com/spinnaker/spin/cmd/output" gate "github.com/spinnaker/spin/cmd/gateclient" ) @@ -88,7 +88,7 @@ func providerConfigureFunc(data *schema.ResourceData) (interface{}, error) { if err != nil { return nil, err } - ui := output.NewUI(quiet, noColor, outputFormater, ioutil.Discard, ioutil.Discard) + ui := output.NewUI(quiet, noColor, outputFormater, io.Discard, io.Discard) client, err := gate.NewGateClient(ui, server, defaultHeaders, config, ignoreCertErrors) if err != nil { diff --git a/spinnaker/provider_test.go b/spinnaker/provider_test.go index 250b632..4502b23 100644 --- a/spinnaker/provider_test.go +++ b/spinnaker/provider_test.go @@ -2,12 +2,12 @@ package spinnaker import ( "os" - "fmt" + "fmt" "testing" - "context" + "context" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) var testAccProviders map[string]*schema.Provider diff --git a/spinnaker/resource_application.go b/spinnaker/resource_application.go index ac31fa6..8695fd5 100644 --- a/spinnaker/resource_application.go +++ b/spinnaker/resource_application.go @@ -3,9 +3,9 @@ package spinnaker import ( "strings" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/guido9j/terraform-provider-spinnaker/spinnaker/api" + "terraform-provider-spinnaker/spinnaker/api" ) func resourceApplication() *schema.Resource { @@ -35,10 +35,46 @@ func resourceApplication() *schema.Resource { Optional: true, Default: "", }, + "cloud_providers": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "permissions": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "read": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "execute": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "write": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, }, - Create: resourceApplicationCreate, + Create: resourceApplicationCreateOrUpdate, Read: resourceApplicationRead, - Update: resourceApplicationUpdate, + Update: resourceApplicationCreateOrUpdate, Delete: resourceApplicationDelete, Exists: resourceApplicationExists, } @@ -51,7 +87,7 @@ type applicationRead struct { } `json:"attributes"` } -func resourceApplicationCreate(data *schema.ResourceData, meta interface{}) error { +func resourceApplicationCreateOrUpdate(data *schema.ResourceData, meta interface{}) error { clientConfig := meta.(gateConfig) client := clientConfig.client application := data.Get("application").(string) @@ -59,8 +95,10 @@ func resourceApplicationCreate(data *schema.ResourceData, meta interface{}) erro description := data.Get("description").(string) platform_health_only := data.Get("platform_health_only").(bool) platform_health_only_show_override := data.Get("platform_health_only_show_override").(bool) + cloud_providers := data.Get("cloud_providers").([]interface{}) + permissions := data.Get("permissions").(*schema.Set) - if err := api.CreateApplication(client, application, email, description, platform_health_only, platform_health_only_show_override); err != nil { + if err := api.CreateOrUpdateApplication(client, application, email, description, platform_health_only, platform_health_only_show_override, cloud_providers, permissions); err != nil { return err } @@ -79,10 +117,6 @@ func resourceApplicationRead(data *schema.ResourceData, meta interface{}) error return readApplication(data, app) } -func resourceApplicationUpdate(data *schema.ResourceData, meta interface{}) error { - return nil -} - func resourceApplicationDelete(data *schema.ResourceData, meta interface{}) error { clientConfig := meta.(gateConfig) client := clientConfig.client diff --git a/spinnaker/resource_application_test.go b/spinnaker/resource_application_test.go index 24a89bd..0c01e72 100644 --- a/spinnaker/resource_application_test.go +++ b/spinnaker/resource_application_test.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) func TestAccSpinnakerApplication_basic(t *testing.T) { diff --git a/spinnaker/resource_pipeline.go b/spinnaker/resource_pipeline.go index 5c071ac..0dd9898 100644 --- a/spinnaker/resource_pipeline.go +++ b/spinnaker/resource_pipeline.go @@ -5,9 +5,9 @@ import ( "fmt" "strings" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/guido9j/terraform-provider-spinnaker/spinnaker/api" + "terraform-provider-spinnaker/spinnaker/api" ) func resourcePipeline() *schema.Resource { diff --git a/spinnaker/resource_pipeline_template.go b/spinnaker/resource_pipeline_template.go index acbfe35..b451207 100644 --- a/spinnaker/resource_pipeline_template.go +++ b/spinnaker/resource_pipeline_template.go @@ -7,9 +7,9 @@ import ( "log" "reflect" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/guido9j/terraform-provider-spinnaker/spinnaker/api" + "terraform-provider-spinnaker/spinnaker/api" "github.com/ghodss/yaml" ) diff --git a/spinnaker/resource_pipeline_template_config.go b/spinnaker/resource_pipeline_template_config.go index 2a44911..bf7b67b 100644 --- a/spinnaker/resource_pipeline_template_config.go +++ b/spinnaker/resource_pipeline_template_config.go @@ -6,9 +6,9 @@ import ( "fmt" "log" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/guido9j/terraform-provider-spinnaker/spinnaker/api" + "terraform-provider-spinnaker/spinnaker/api" "github.com/ghodss/yaml" )