From 4193c7269ef1a058010cc275257855c903209ed3 Mon Sep 17 00:00:00 2001 From: zambien Date: Mon, 26 Apr 2021 12:15:06 -0400 Subject: [PATCH 1/6] update to current release in readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ae91944..29db4e5 100644 --- a/README.md +++ b/README.md @@ -20,13 +20,13 @@ An example of how to do this would be: `mkdir -p ~/terraform-providers` 2. Download plugin for linux into your home directory -`curl -L https://github.com/zambien/terraform-provider-apigee/releases/download/v0.0.7/terraform-provider-apigee-v0.0.7-linux64 -o ~/terraform-providers/terraform-provider-apigee-v0.0.7-linux64` +`curl -L https://github.com/zambien/terraform-provider-apigee/releases/download/v0.0.23/terraform-provider-apigee-v0.0.23-linux64 -o ~/terraform-providers/terraform-provider-apigee-v0.0.23-linux64` 3. Add the providers clause if you don't already have one. Warning, this command will overwrite your .terraformrc! ``` cat << EOF > ~/.terraformrc providers { - apigee = "$HOME/terraform-providers/terraform-provider-apigee-v0.0.7-linux64" + apigee = "$HOME/terraform-providers/terraform-provider-apigee-v0.0.23-linux64" } EOF ``` From bdd93464def7fb664446687b0374d9cdd5b6f9d1 Mon Sep 17 00:00:00 2001 From: zambien Date: Mon, 26 Apr 2021 13:12:23 -0400 Subject: [PATCH 2/6] resolve #68 --- apigee/resource_api_proxy_deployment.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apigee/resource_api_proxy_deployment.go b/apigee/resource_api_proxy_deployment.go index c21dc91..a8a1021 100644 --- a/apigee/resource_api_proxy_deployment.go +++ b/apigee/resource_api_proxy_deployment.go @@ -149,7 +149,7 @@ func resourceApiProxyDeploymentCreate(d *schema.ResourceData, meta interface{}) rev_int, err := getLatestRevision(client, proxy_name) rev = apigee.Revision(rev_int) if err != nil { - return fmt.Errorf("[ERROR] resourceApiProxyDeploymentUpdate error getting latest revision: %v", err) + return fmt.Errorf("[ERROR] resourceApiProxyDeploymentCreate error getting latest revision: %v", err) } } @@ -210,7 +210,7 @@ func resourceApiProxyDeploymentUpdate(d *schema.ResourceData, meta interface{}) if err != nil { log.Printf("[ERROR] resourceApiProxyDeploymentUpdate error redeploying: %s", err.Error()) - if strings.Contains(err.Error(), " is already deployed into environment ") { + if strings.Contains(err.Error(), " is already deployed ") { return resourceApiProxyDeploymentRead(d, meta) } return fmt.Errorf("[ERROR] resourceApiProxyDeploymentUpdate error redeploying: %s", err.Error()) From 013d04534c392846e2cba2bbb125e6bd5a387490 Mon Sep 17 00:00:00 2001 From: zambien Date: Mon, 26 Apr 2021 15:34:34 -0400 Subject: [PATCH 3/6] actually see latest changes --- apigee/resource_api_proxy_deployment.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/apigee/resource_api_proxy_deployment.go b/apigee/resource_api_proxy_deployment.go index a8a1021..84c44d6 100644 --- a/apigee/resource_api_proxy_deployment.go +++ b/apigee/resource_api_proxy_deployment.go @@ -119,9 +119,30 @@ func resourceApiProxyDeploymentRead(d *schema.ResourceData, meta interface{}) (e if found { if d.Get("revision").(string) == "latest" { + + log.Printf("[DEBUG] resourceApiProxyDeploymentRead doing latest check") + + // Get the latest revision and make sure we show it as different if it is + rev_int := 0 + rev := apigee.Revision(rev_int) + + rev_int, err := getLatestRevision(client, d.Get("proxy_name").(string)) + rev = apigee.Revision(rev_int) + if err != nil { + return fmt.Errorf("[ERROR] resourceApiProxyDeploymentUpdate error getting latest revision: %v", err) + } + + log.Printf("[DEBUG] resourceApiProxyDeploymentRead found latest revision: %#v\n", rev) + + if matchedRevision != strconv.Itoa(rev_int) { + log.Printf("[DEBUG] resourceApiProxyDeploymentRead latest deployed revision: %#v did not match actual latest revision: %#v. Updating revision to latest available. \n", matchedRevision, strconv.Itoa(rev_int)) + d.Set("revision", strconv.Itoa(rev_int)) + } + d.SetId(matchedRevision) } else { d.Set("revision", matchedRevision) + d.Set("deployed_revision", matchedRevision) } log.Printf("[DEBUG] resourceApiProxyDeploymentRead - deployment found. Revision is: %#v", d.Get("revision").(string)) } else { From 11c4d20b02ec09a0d3bc4385f8378603d204a9ff Mon Sep 17 00:00:00 2001 From: zambien Date: Tue, 27 Apr 2021 08:55:47 -0400 Subject: [PATCH 4/6] fix latest so it sees changes --- apigee/resource_api_proxy_deployment.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/apigee/resource_api_proxy_deployment.go b/apigee/resource_api_proxy_deployment.go index 48e121f..d1422a5 100644 --- a/apigee/resource_api_proxy_deployment.go +++ b/apigee/resource_api_proxy_deployment.go @@ -138,11 +138,8 @@ func resourceApiProxyDeploymentRead(d *schema.ResourceData, meta interface{}) (e log.Printf("[DEBUG] resourceApiProxyDeploymentRead latest deployed revision: %#v did not match actual latest revision: %#v. Updating revision to latest available. \n", matchedRevision, strconv.Itoa(rev_int)) d.Set("revision", strconv.Itoa(rev_int)) } - - d.SetId(matchedRevision) } else { d.Set("revision", matchedRevision) - d.Set("deployed_revision", matchedRevision) } log.Printf("[DEBUG] resourceApiProxyDeploymentRead - deployment found. Revision is: %#v", d.Get("revision").(string)) } else { From b52c51b898136219c9967d2a004d53ae550f4755 Mon Sep 17 00:00:00 2001 From: zambien Date: Tue, 27 Apr 2021 09:15:16 -0400 Subject: [PATCH 5/6] update readme with new provider installation, latest for deployments, table of contents, mention double-apply --- README.md | 65 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 29db4e5..6004a6e 100644 --- a/README.md +++ b/README.md @@ -6,31 +6,17 @@ Allows Terraform deployments and management of Apigee API proxies, deployments, https://registry.terraform.io/providers/zambien/apigee/ -## Installation - -Download the appropriate release for your system: https://github.com/zambien/terraform-provider-apigee/releases - -See here for info on how to install the plugin: - -https://www.terraform.io/docs/plugins/basics.html - -An example of how to do this would be: - -1. Make a terraform providers folder in home -`mkdir -p ~/terraform-providers` - -2. Download plugin for linux into your home directory -`curl -L https://github.com/zambien/terraform-provider-apigee/releases/download/v0.0.23/terraform-provider-apigee-v0.0.23-linux64 -o ~/terraform-providers/terraform-provider-apigee-v0.0.23-linux64` - -3. Add the providers clause if you don't already have one. Warning, this command will overwrite your .terraformrc! -``` -cat << EOF > ~/.terraformrc -providers { - apigee = "$HOME/terraform-providers/terraform-provider-apigee-v0.0.23-linux64" -} -EOF -``` - +- [terraform-provider-apigee](#terraform-provider-apigee) + * [TFVARS for provider](#tfvars-for-provider) + * [Simple Example](#simple-example) + * [Contributions](#contributions) + * [Building](#building) + * [Testing](#testing) + - [Set env vars for test using username/password:](#set-env-vars-for-test-using-username-password-) + - [Set env vars for test using access token:](#set-env-vars-for-test-using-access-token-) + * [Releasing](#releasing) + * [Known issues](#known-issues) + ## TFVARS for provider ``` @@ -49,6 +35,25 @@ APIGEE_ACCESS_TOKEN="my-access-token" ``` +# note, to test and build the plugin locally uncomment the lines below or do something like it +# provider_version=0.0.x +# mkdir -p ~/.terraform.d/plugins/local/zambien/apigee/${provider_version}/linux_amd64/ +# mv terraform-provider-apigee-v${provider_version}-linux64 ~/.terraform.d/plugins/local/zambien/apigee/${provider_version}/linux_amd64/terraform-provider-apigee_v${provider_version} + +terraform { + required_version = ">= 0.14" + + required_providers { + apigee = { + # pull from registry + source = "zambien/apigee" + # test locally built plugin + # source = "local/zambien/apigee" + version = "~> 0.0.23" + } + } +} + variable "org" { default = "my-really-cool-apigee-org-name" } variable "env" { default = "test" } @@ -109,7 +114,8 @@ resource "apigee_api_proxy_deployment" "helloworld_proxy_deployment" { # NOTE: revision = "latest" # will deploy the latest revision of the api proxy - revision = "${apigee_api_proxy.helloworld_proxy.revision}" + revision = "latest" + # OR revision = "1" # for specific revision } # A target server @@ -206,7 +212,8 @@ resource "apigee_shared_flow_deployment" "helloworld_shared_flow_deployment" { # NOTE: revision = "latest" # will deploy the latest revision of the shared flow - revision = "${apigee_shared_flow.helloworld_shared_flow.revision}" + revision = "latest" + # OR revision = "1" # for specific revision } ``` @@ -264,3 +271,7 @@ goreleaser # actually create the release You can read more about goreleaser here: https://goreleaser.com/ + +## Known issues + +* You will often find the need to run apply twice when updating a proxy. This has to do with how terraform handles state. This plugin will be rewritten to combine proxies and proxy deployments to resolve this issue in the future. From 307352cb36e2b49422f988277faaeffbc42fda8b Mon Sep 17 00:00:00 2001 From: zambien Date: Wed, 28 Apr 2021 09:24:37 -0400 Subject: [PATCH 6/6] remove non-working credential object, add simple credentials outputs, fix issue with already created proxy_deployment on create --- apigee/helpers.go | 31 +++++++++--- apigee/resource_api_proxy_deployment.go | 6 ++- apigee/resource_company_app.go | 62 +++++++---------------- apigee/resource_developer_app.go | 65 ++++++++++++++++++++++--- go.mod | 1 + 5 files changed, 104 insertions(+), 61 deletions(-) diff --git a/apigee/helpers.go b/apigee/helpers.go index e1a4f21..9e06b67 100644 --- a/apigee/helpers.go +++ b/apigee/helpers.go @@ -1,7 +1,6 @@ package apigee import ( - "github.com/17media/structs" "github.com/hashicorp/terraform/helper/schema" "github.com/zambien/go-apigee-edge" "reflect" @@ -60,16 +59,34 @@ func attributesFromMap(attributes map[string]interface{}) []apigee.Attribute { return result } -func mapFromCredentials(credentials []apigee.Credential) []interface{} { +func flattenCredentials(in []apigee.Credential) []interface{} { - result := make([]interface{}, 0, len(credentials)) + if in != nil { - for _, elem := range credentials { - credentialMap := structs.Map(elem) - result = append(result, credentialMap) + out := make([]interface{}, len(in), len(in)) + + for i, elem := range in { + + m := make(map[string]interface{}) + + m["consumer_key"] = elem.ConsumerKey + m["consumer_secret"] = elem.ConsumerSecret + m["issued_at"] = elem.IssuedAt + m["expired_at"] = elem.ExpiresAt + + + /* + if len(elem.ApiProducts) > 0 { + m["port_mapping"] = flattenDockerPortMappings(elem.PortMappings) + }*/ + + out[i] = m + } + + return out } - return result + return make([]interface{}, 0) } func arraySortedEqual(a, b []string) bool { diff --git a/apigee/resource_api_proxy_deployment.go b/apigee/resource_api_proxy_deployment.go index d1422a5..52d08f1 100644 --- a/apigee/resource_api_proxy_deployment.go +++ b/apigee/resource_api_proxy_deployment.go @@ -174,8 +174,10 @@ func resourceApiProxyDeploymentCreate(d *schema.ResourceData, meta interface{}) proxyDep, _, err := client.Proxies.Deploy(proxy_name, env, rev, delay, override) if err != nil { - - if strings.Contains(err.Error(), "conflicts with existing deployment path") { + if strings.Contains(err.Error(), " is already deployed ") { + log.Printf("[ERROR] resourceApiProxyDeploymentCreate error deploying. We will read into state: %s", err.Error()) + resourceApiProxyDeploymentUpdate(d, meta) + } else if strings.Contains(err.Error(), "conflicts with existing deployment path") { //create, fail, update log.Printf("[ERROR] resourceApiProxyDeploymentCreate error deploying: %s", err.Error()) log.Print("[DEBUG] resourceApiProxyDeploymentCreate something got out of sync... maybe someone messing around in apigee directly. Terraform OVERRIDE!!!") diff --git a/apigee/resource_company_app.go b/apigee/resource_company_app.go index cf3828b..948ed87 100644 --- a/apigee/resource_company_app.go +++ b/apigee/resource_company_app.go @@ -40,12 +40,6 @@ func resourceCompanyApp() *schema.Resource { Type: schema.TypeString, Computed: true, }, - /* - "credentials": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeMap}, - },*/ "credentials": { Type: schema.TypeList, Computed: true, @@ -104,7 +98,7 @@ func resourceCompanyApp() *schema.Resource { }, "scopes": { Type: schema.TypeList, - Computed: true, + Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, "callback_url": { @@ -119,6 +113,14 @@ func resourceCompanyApp() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "consumer_key": { + Type: schema.TypeString, + Computed: true, + }, + "consumer_secret": { + Type: schema.TypeString, + Computed: true, + }, }, } } @@ -170,28 +172,17 @@ func resourceCompanyAppRead(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] resourceCompanyAppRead CompanyAppData: %+v\n", CompanyAppData) //Scopes and apiProducts are tricky. These actually result in an array which will always have - //one element unless an outside API is called. Since using terraform we assume you do everything there - //you might only ever have one credential... we'll see. - scopes := flattenStringList(CompanyAppData.Credentials[0].Scopes) - - credentials := mapFromCredentials(CompanyAppData.Credentials) - - //credentials_again - if CompanyAppData.Credentials != nil { + //one element unless an outside API is called. + //Get the most recent scopes from the last credentials set + scopes := flattenStringList(CompanyAppData.Credentials[len(CompanyAppData.Credentials)-1].Scopes) - log.Print("[DEBUG] resourceCompanyAppRead credentials ConsumerKey: ", CompanyAppData.Credentials[0].ConsumerKey) - log.Print("[DEBUG] resourceCompanyAppRead credentials ConsumerSecret: ", CompanyAppData.Credentials[0].ConsumerSecret) - - d.Set("credentials.0.consumer_key", CompanyAppData.Credentials[0].ConsumerKey) - d.Set("credentials.0.consumer_secret", CompanyAppData.Credentials[0].ConsumerSecret) - - log.Print("[DEBUG] resourceCompanyAppRead credentials: ", d.Get("credentials.0")) - log.Print("[DEBUG] resourceCompanyAppRead credentials consumer key: ", d.Get("credentials.0.consumer_key")) - } + //TBD: credentials complex list. See comments in resource_developer_app.go + d.Set("credentials", make([]interface{}, 0)) //Apigee does not return products in the order you send them + //Get the most recent api products from the last credentials set oldApiProducts := getStringList("api_products", d) - newApiProducts := apiProductsListFromCredentials(CompanyAppData.Credentials[0].ApiProducts) + newApiProducts := apiProductsListFromCredentials(CompanyAppData.Credentials[len(CompanyAppData.Credentials)-1].ApiProducts) if !arraySortedEqual(oldApiProducts, newApiProducts) { d.Set("api_products", newApiProducts) @@ -202,14 +193,13 @@ func resourceCompanyAppRead(d *schema.ResourceData, meta interface{}) error { d.Set("test","tester") d.Set("name", CompanyAppData.Name) d.Set("attributes", CompanyAppData.Attributes) - d.Set("credentials", CompanyAppData.Credentials) d.Set("scopes", scopes) d.Set("callback_url", CompanyAppData.CallbackUrl) d.Set("app_id", CompanyAppData.AppId) d.Set("company_name", CompanyAppData.CompanyName) d.Set("status", CompanyAppData.Status) - - log.Print("[DEBUG] resourceCompanyAppRead credentials: ", credentials) + d.Set("consumer_key", CompanyAppData.Credentials[len(CompanyAppData.Credentials)-1].ConsumerKey) + d.Set("consumer_secret", CompanyAppData.Credentials[len(CompanyAppData.Credentials)-1].ConsumerSecret) return nil } @@ -259,21 +249,6 @@ func setCompanyAppData(d *schema.ResourceData) (apigee.CompanyApp, error) { apiProducts = getStringList("api_products", d) } - log.Print("[DEBUG] setCompanyAppData credentials: ", d.Get("credentials")) - var credentials []apigee.Credential - if d.Get("credentials") != nil { - - credentialsMap := d.Get("credentials").([]interface{}) - - for elem := range credentialsMap { - - - log.Printf("[DEBUG] setCompanyAppData credentialsMap element: %v", elem) - - //credentials = append(result, t) - } - } - scopes := []string{""} if d.Get("scopes") != nil { scopes = getStringList("scopes", d) @@ -290,7 +265,6 @@ func setCompanyAppData(d *schema.ResourceData) (apigee.CompanyApp, error) { Attributes: attributes, ApiProducts: apiProducts, Scopes: scopes, - Credentials: credentials, CallbackUrl: d.Get("callback_url").(string), } diff --git a/apigee/resource_developer_app.go b/apigee/resource_developer_app.go index 19e5fb1..0121ee7 100644 --- a/apigee/resource_developer_app.go +++ b/apigee/resource_developer_app.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/gofrs/uuid" "github.com/hashicorp/terraform/helper/schema" + //"github.com/mitchellh/mapstructure" "github.com/zambien/go-apigee-edge" "log" "strings" @@ -43,7 +44,31 @@ func resourceDeveloperApp() *schema.Resource { "credentials": { Type: schema.TypeList, Computed: true, - Elem: &schema.Schema{Type: schema.TypeMap}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "consumer_key": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "consumer_secret": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "issued_at": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "expires_at": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + /* + "api_products": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + },*/ + }, + }, }, "scopes": { Type: schema.TypeList, @@ -66,6 +91,14 @@ func resourceDeveloperApp() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "consumer_key": { + Type: schema.TypeString, + Computed: true, + }, + "consumer_secret": { + Type: schema.TypeString, + Computed: true, + }, }, } } @@ -117,15 +150,14 @@ func resourceDeveloperAppRead(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] resourceDeveloperAppRead DeveloperAppData: %+v\n", DeveloperAppData) //Scopes and apiProducts are tricky. These actually result in an array which will always have - //one element unless an outside API is called. Since using terraform we assume you do everything there - //you might only ever have one credential... we'll see. - scopes := flattenStringList(DeveloperAppData.Credentials[0].Scopes) - - credentials := mapFromCredentials(DeveloperAppData.Credentials) + //one element unless an outside API is called. + //Get the most recent scopes from the last credentials set + scopes := flattenStringList(DeveloperAppData.Credentials[len(DeveloperAppData.Credentials)-1].Scopes) //Apigee does not return products in the order you send them + //Get the most recent api products from the last credentials set oldApiProducts := getStringList("api_products", d) - newApiProducts := apiProductsListFromCredentials(DeveloperAppData.Credentials[0].ApiProducts) + newApiProducts := apiProductsListFromCredentials(DeveloperAppData.Credentials[len(DeveloperAppData.Credentials)-1].ApiProducts) if !arraySortedEqual(oldApiProducts, newApiProducts) { d.Set("api_products", newApiProducts) @@ -133,15 +165,32 @@ func resourceDeveloperAppRead(d *schema.ResourceData, meta interface{}) error { d.Set("api_products", oldApiProducts) } + d.Set("name", DeveloperAppData.Name) d.Set("attributes", DeveloperAppData.Attributes) - d.Set("credentials", credentials) d.Set("scopes", scopes) d.Set("callback_url", DeveloperAppData.CallbackUrl) d.Set("app_id", DeveloperAppData.AppId) d.Set("developer_id", DeveloperAppData.DeveloperId) d.Set("status", DeveloperAppData.Status) + + //For some reason this is not ever set and there are no errors. I have followed the syntax here: + // https://stackoverflow.com/questions/54033185/writing-a-terraform-provider-with-nested-map + //and here: https://learn.hashicorp.com/tutorials/terraform/provider-complex-read + //to no avail. We may need to update to lastest plugin sdk. + //For now just set the last consumer key and secret as simple strings. + /* + credentials := flattenCredentials(DeveloperAppData.Credentials) + if cred_err := d.Set("credentials", credentials); err != nil { + return fmt.Errorf("[ERROR] resourceDeveloperAppRead error setting credentials: %s", cred_err.Error()) + }*/ + + + d.Set("credentials", make([]interface{}, 0)) + d.Set("consumer_key", DeveloperAppData.Credentials[len(DeveloperAppData.Credentials)-1].ConsumerKey) + d.Set("consumer_secret", DeveloperAppData.Credentials[len(DeveloperAppData.Credentials)-1].ConsumerSecret) + return nil } diff --git a/go.mod b/go.mod index 594d8ef..5ff3ed2 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/17media/structs v0.0.0-20200317074636-7872972ebe57 github.com/gofrs/uuid v3.2.0+incompatible github.com/hashicorp/terraform v0.12.13 + github.com/mitchellh/mapstructure v1.1.2 // indirect github.com/sethgrid/pester v0.0.0-20190127155807-68a33a018ad0 // indirect github.com/zambien/go-apigee-edge v0.0.0-20191101145538-e45257f96262 )