From 11c8053bfd9211853f3f4185ee67f7549eea8fb8 Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Wed, 6 Oct 2021 12:47:24 +0100 Subject: [PATCH] Work around inconsistency for password and certificate credentials --- .../application_certificate_resource.go | 33 +++++++++++++++++ .../application_password_resource.go | 36 ++++++++++++++++++- .../service_principal_certificate_resource.go | 33 +++++++++++++++++ .../service_principal_password_resource.go | 34 ++++++++++++++++++ 4 files changed, 135 insertions(+), 1 deletion(-) diff --git a/internal/services/applications/application_certificate_resource.go b/internal/services/applications/application_certificate_resource.go index afe368ec7..a84d73483 100644 --- a/internal/services/applications/application_certificate_resource.go +++ b/internal/services/applications/application_certificate_resource.go @@ -10,6 +10,7 @@ import ( "time" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/manicminer/hamilton/msgraph" @@ -172,6 +173,38 @@ func applicationCertificateResourceCreate(ctx context.Context, d *schema.Resourc return tf.ErrorDiagF(err, "Adding certificate for application with object ID %q", id.ObjectId) } + // Wait for the credential to appear in the application manifest, this can take several minutes + timeout, _ := ctx.Deadline() + polledForCredential, err := (&resource.StateChangeConf{ + Pending: []string{"Waiting"}, + Target: []string{"Done"}, + Timeout: time.Until(timeout), + MinTimeout: 1 * time.Second, + ContinuousTargetOccurence: 5, + Refresh: func() (interface{}, string, error) { + app, _, err := client.Get(ctx, id.ObjectId, odata.Query{}) + if err != nil { + return nil, "Error", err + } + + if app.KeyCredentials != nil { + for _, cred := range *app.KeyCredentials { + if cred.KeyId != nil && strings.EqualFold(*cred.KeyId, id.KeyId) { + return &cred, "Done", nil + } + } + } + + return nil, "Waiting", nil + }, + }).WaitForStateContext(ctx) + + if err != nil { + return tf.ErrorDiagF(err, "Waiting for certificate credential for application with object ID %q", id.ObjectId) + } else if polledForCredential == nil { + return tf.ErrorDiagF(errors.New("certificate credential not found in application manifest"), "Waiting for certificate credential for application with object ID %q", id.ObjectId) + } + d.SetId(id.String()) return applicationCertificateResourceRead(ctx, d, meta) diff --git a/internal/services/applications/application_password_resource.go b/internal/services/applications/application_password_resource.go index 650ab1f5b..bb766cb51 100644 --- a/internal/services/applications/application_password_resource.go +++ b/internal/services/applications/application_password_resource.go @@ -10,6 +10,7 @@ import ( "time" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/manicminer/hamilton/msgraph" @@ -30,7 +31,7 @@ func applicationPasswordResource() *schema.Resource { DeleteContext: applicationPasswordResourceDelete, Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(5 * time.Minute), + Create: schema.DefaultTimeout(15 * time.Minute), Read: schema.DefaultTimeout(5 * time.Minute), Update: schema.DefaultTimeout(5 * time.Minute), Delete: schema.DefaultTimeout(5 * time.Minute), @@ -161,6 +162,39 @@ func applicationPasswordResourceCreate(ctx context.Context, d *schema.ResourceDa } id := parse.NewCredentialID(*app.ID, "password", *newCredential.KeyId) + + // Wait for the credential to appear in the application manifest, this can take several minutes + timeout, _ := ctx.Deadline() + polledForCredential, err := (&resource.StateChangeConf{ + Pending: []string{"Waiting"}, + Target: []string{"Done"}, + Timeout: time.Until(timeout), + MinTimeout: 1 * time.Second, + ContinuousTargetOccurence: 5, + Refresh: func() (interface{}, string, error) { + app, _, err := client.Get(ctx, id.ObjectId, odata.Query{}) + if err != nil { + return nil, "Error", err + } + + if app.PasswordCredentials != nil { + for _, cred := range *app.PasswordCredentials { + if cred.KeyId != nil && strings.EqualFold(*cred.KeyId, id.KeyId) { + return &cred, "Done", nil + } + } + } + + return nil, "Waiting", nil + }, + }).WaitForStateContext(ctx) + + if err != nil { + return tf.ErrorDiagF(err, "Waiting for password credential for application with object ID %q", id.ObjectId) + } else if polledForCredential == nil { + return tf.ErrorDiagF(errors.New("password credential not found in application manifest"), "Waiting for password credential for application with object ID %q", id.ObjectId) + } + d.SetId(id.String()) d.Set("value", newCredential.SecretText) diff --git a/internal/services/serviceprincipals/service_principal_certificate_resource.go b/internal/services/serviceprincipals/service_principal_certificate_resource.go index b9b82e05f..8ee8cee77 100644 --- a/internal/services/serviceprincipals/service_principal_certificate_resource.go +++ b/internal/services/serviceprincipals/service_principal_certificate_resource.go @@ -10,6 +10,7 @@ import ( "time" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/manicminer/hamilton/msgraph" @@ -172,6 +173,38 @@ func servicePrincipalCertificateResourceCreate(ctx context.Context, d *schema.Re return tf.ErrorDiagF(err, "Adding certificate for service principal with object ID %q", id.ObjectId) } + // Wait for the credential to appear in the service principal manifest, this can take several minutes + timeout, _ := ctx.Deadline() + polledForCredential, err := (&resource.StateChangeConf{ + Pending: []string{"Waiting"}, + Target: []string{"Done"}, + Timeout: time.Until(timeout), + MinTimeout: 1 * time.Second, + ContinuousTargetOccurence: 5, + Refresh: func() (interface{}, string, error) { + servicePrincipal, _, err := client.Get(ctx, id.ObjectId, odata.Query{}) + if err != nil { + return nil, "Error", err + } + + if servicePrincipal.KeyCredentials != nil { + for _, cred := range *servicePrincipal.KeyCredentials { + if cred.KeyId != nil && strings.EqualFold(*cred.KeyId, id.KeyId) { + return &cred, "Done", nil + } + } + } + + return nil, "Waiting", nil + }, + }).WaitForStateContext(ctx) + + if err != nil { + return tf.ErrorDiagF(err, "Waiting for certificate credential for service principal with object ID %q", id.ObjectId) + } else if polledForCredential == nil { + return tf.ErrorDiagF(errors.New("certificate credential not found in service principal manifest"), "Waiting for certificate credential for service principal with object ID %q", id.ObjectId) + } + d.SetId(id.String()) return servicePrincipalCertificateResourceRead(ctx, d, meta) diff --git a/internal/services/serviceprincipals/service_principal_password_resource.go b/internal/services/serviceprincipals/service_principal_password_resource.go index b9b37fd82..fafb1ccab 100644 --- a/internal/services/serviceprincipals/service_principal_password_resource.go +++ b/internal/services/serviceprincipals/service_principal_password_resource.go @@ -10,6 +10,7 @@ import ( "time" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/manicminer/hamilton/msgraph" "github.com/manicminer/hamilton/odata" @@ -142,6 +143,39 @@ func servicePrincipalPasswordResourceCreate(ctx context.Context, d *schema.Resou } id := parse.NewCredentialID(*sp.ID, "password", *newCredential.KeyId) + + // Wait for the credential to appear in the service principal manifest, this can take several minutes + timeout, _ := ctx.Deadline() + polledForCredential, err := (&resource.StateChangeConf{ + Pending: []string{"Waiting"}, + Target: []string{"Done"}, + Timeout: time.Until(timeout), + MinTimeout: 1 * time.Second, + ContinuousTargetOccurence: 5, + Refresh: func() (interface{}, string, error) { + servicePrincipal, _, err := client.Get(ctx, id.ObjectId, odata.Query{}) + if err != nil { + return nil, "Error", err + } + + if servicePrincipal.PasswordCredentials != nil { + for _, cred := range *servicePrincipal.PasswordCredentials { + if cred.KeyId != nil && strings.EqualFold(*cred.KeyId, id.KeyId) { + return &cred, "Done", nil + } + } + } + + return nil, "Waiting", nil + }, + }).WaitForStateContext(ctx) + + if err != nil { + return tf.ErrorDiagF(err, "Waiting for password credential for service principal with object ID %q", id.ObjectId) + } else if polledForCredential == nil { + return tf.ErrorDiagF(errors.New("password credential not found in service principal manifest"), "Waiting for password credential for service principal with object ID %q", id.ObjectId) + } + d.SetId(id.String()) d.Set("value", newCredential.SecretText)