-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(alloydbomni): add service_account_credentials validation
- Loading branch information
Showing
6 changed files
with
231 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1072,28 +1072,57 @@ func TestAccAivenServiceAlloyDBOmni_service_account_credentials(t *testing.T) { | |
project := os.Getenv("AIVEN_PROJECT_NAME") | ||
resourceName := "aiven_alloydbomni.foo" | ||
serviceName := fmt.Sprintf("test-acc-sr-%s", acc.RandStr()) | ||
|
||
// Service account credentials are managed by its own API | ||
// When Terraform fails to create a service because of this field, | ||
// the whole resource is tainted, and must be replaced | ||
serviceAccountCredentialsInvalid := testAccAivenServiceAlloyDBOmniServiceAccountCredentials( | ||
project, serviceName, | ||
`{ | ||
"private_key": "-----BEGIN PRIVATE KEY--.........----END PRIVATE KEY-----\n", | ||
"client_email": "[email protected]", | ||
"client_id": "example_user_id", | ||
"type": "service_account", | ||
"project_id": "example_project_id" | ||
}`, | ||
) | ||
serviceAccountCredentialsEmpty := testAccAivenServiceAlloyDBOmniServiceAccountCredentials( | ||
project, serviceName, "", | ||
) | ||
serviceAccountCredentialsValid := testAccAivenServiceAlloyDBOmniServiceAccountCredentials( | ||
project, serviceName, getTestServiceAccountCredentials("foo"), | ||
) | ||
serviceAccountCredentialsValidModified := testAccAivenServiceAlloyDBOmniServiceAccountCredentials( | ||
project, serviceName, getTestServiceAccountCredentials("bar"), | ||
) | ||
|
||
resource.ParallelTest(t, resource.TestCase{ | ||
PreCheck: func() { acc.TestAccPreCheck(t) }, | ||
ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories, | ||
CheckDestroy: acc.TestAccCheckAivenServiceResourceDestroy, | ||
Steps: []resource.TestStep{ | ||
{ | ||
// 0. Invalid credentials | ||
Config: serviceAccountCredentialsInvalid, | ||
ExpectError: regexp.MustCompile(`private_key_id is required`), | ||
}, | ||
{ | ||
// 1. No credential initially | ||
Config: testAccAivenServiceAlloyDBOmniServiceAccountCredentials(project, serviceName, ""), | ||
Config: serviceAccountCredentialsEmpty, | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckNoResourceAttr(resourceName, "service_account_credentials"), | ||
), | ||
}, | ||
{ | ||
// 2. Credentials are set | ||
Config: testAccAivenServiceAlloyDBOmniServiceAccountCredentials(project, serviceName, "foo"), | ||
Config: serviceAccountCredentialsValid, | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttrSet(resourceName, "service_account_credentials"), | ||
), | ||
}, | ||
{ | ||
// 3. Updates the credentials | ||
Config: testAccAivenServiceAlloyDBOmniServiceAccountCredentials(project, serviceName, "bar"), | ||
Config: serviceAccountCredentialsValidModified, | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttrSet(resourceName, "service_account_credentials"), | ||
// Validates they key has been updated | ||
|
@@ -1111,7 +1140,7 @@ func TestAccAivenServiceAlloyDBOmni_service_account_credentials(t *testing.T) { | |
}, | ||
{ | ||
// 4. Removes the credentials | ||
Config: testAccAivenServiceAlloyDBOmniServiceAccountCredentials(project, serviceName, ""), | ||
Config: serviceAccountCredentialsEmpty, | ||
Check: resource.ComposeTestCheckFunc( | ||
// It looks like TF can't unset an attribute, when it was set. | ||
// So I can't use TestCheckNoResourceAttr here. | ||
|
@@ -1131,14 +1160,14 @@ func TestAccAivenServiceAlloyDBOmni_service_account_credentials(t *testing.T) { | |
}, | ||
{ | ||
// 5. Re-applies the credential, so we can check unexpected remote state changes in the next step | ||
Config: testAccAivenServiceAlloyDBOmniServiceAccountCredentials(project, serviceName, "bar"), | ||
Config: serviceAccountCredentialsValidModified, | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttrSet(resourceName, "service_account_credentials"), | ||
), | ||
}, | ||
{ | ||
// 6. Same config. Modifies the remove state, expects non-empty plan | ||
Config: testAccAivenServiceAlloyDBOmniServiceAccountCredentials(project, serviceName, "bar"), | ||
Config: serviceAccountCredentialsValidModified, | ||
PlanOnly: true, | ||
ExpectNonEmptyPlan: true, | ||
PreConfig: func() { | ||
|
@@ -1158,23 +1187,22 @@ func TestAccAivenServiceAlloyDBOmni_service_account_credentials(t *testing.T) { | |
|
||
func getTestServiceAccountCredentials(privateKeyID string) string { | ||
return fmt.Sprintf(`{ | ||
"private_key_id": %q, | ||
"private_key": "-----BEGIN PRIVATE KEY--.........----END PRIVATE KEY-----\n", | ||
"client_email": "[email protected]", | ||
"client_id": "example_user_id", | ||
"type": "service_account", | ||
"project_id": "example_project_id" | ||
}`, privateKeyID) | ||
"private_key_id": %q, | ||
"private_key": "-----BEGIN PRIVATE KEY--.........----END PRIVATE KEY-----\n", | ||
"client_email": "[email protected]", | ||
"client_id": "example_user_id", | ||
"type": "service_account", | ||
"project_id": "example_project_id" | ||
}`, privateKeyID) | ||
} | ||
|
||
// testAccAivenServiceAlloyDBOmniServiceAccountCredentials adds service_account_credentials when privateKeyID is not empty | ||
func testAccAivenServiceAlloyDBOmniServiceAccountCredentials(project, name, privateKeyID string) string { | ||
func testAccAivenServiceAlloyDBOmniServiceAccountCredentials(project, name, privateKey string) string { | ||
var p string | ||
if privateKeyID != "" { | ||
if privateKey != "" { | ||
p = fmt.Sprintf(` | ||
service_account_credentials = <<EOF | ||
%s | ||
EOF`, getTestServiceAccountCredentials(privateKeyID)) | ||
EOF`, privateKey) | ||
} | ||
|
||
return fmt.Sprintf(` | ||
|
106 changes: 106 additions & 0 deletions
106
internal/sdkprovider/service/alloydbomni/service_account_credentials_validator.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package alloydbomni | ||
|
||
import ( | ||
"github.com/hashicorp/go-cty/cty" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
"github.com/xeipuuv/gojsonschema" | ||
) | ||
|
||
var _ schema.SchemaValidateDiagFunc = validateServiceAccountCredentials | ||
|
||
func validateServiceAccountCredentials(i interface{}, p cty.Path) diag.Diagnostics { | ||
s, ok := i.(string) | ||
if !ok { | ||
return diag.Errorf("expected type of %q to be string", p) | ||
} | ||
|
||
r, err := gojsonschema.Validate( | ||
gojsonschema.NewStringLoader(serviceAccountCredentialsSchema), | ||
gojsonschema.NewStringLoader(s), | ||
) | ||
|
||
if err != nil { | ||
return diag.FromErr(err) | ||
} | ||
|
||
var diags diag.Diagnostics | ||
for _, e := range r.Errors() { | ||
diags = append(diags, diag.Diagnostic{ | ||
Severity: diag.Error, | ||
Summary: e.String(), | ||
AttributePath: p, | ||
}) | ||
} | ||
return diags | ||
} | ||
|
||
const serviceAccountCredentialsSchema = `{ | ||
"title": "Google service account credentials map", | ||
"type": "object", | ||
"properties": { | ||
"type": { | ||
"type": "string", | ||
"title": "Credentials type", | ||
"description": "Always service_account for credentials created in Gcloud console or CLI", | ||
"example": "service_account" | ||
}, | ||
"project_id": { | ||
"type": "string", | ||
"title": "Gcloud project id", | ||
"example": "some-my-project" | ||
}, | ||
"private_key_id": { | ||
"type": "string", | ||
"title": "Hexadecimal ID number of your private key", | ||
"example": "5fdeb02a11ddf081930ac3ac60bf376a0aef8fad" | ||
}, | ||
"private_key": { | ||
"type": "string", | ||
"title": "PEM-encoded private key", | ||
"example": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n" | ||
}, | ||
"client_email": { | ||
"type": "string", | ||
"title": "Email of the service account", | ||
"example": "[email protected]" | ||
}, | ||
"client_id": { | ||
"type": "string", | ||
"title": "Numeric client id for this service account", | ||
"example": "103654484443722885992" | ||
}, | ||
"auth_uri": { | ||
"type": "string", | ||
"title": "The authentication endpoint of Google", | ||
"example": "https://accounts.google.com/o/oauth2/auth" | ||
}, | ||
"token_uri": { | ||
"type": "string", | ||
"title": "The token lease endpoint of Google", | ||
"example": "https://accounts.google.com/o/oauth2/token" | ||
}, | ||
"auth_provider_x509_cert_url": { | ||
"type": "string", | ||
"title": "The certificate service of Google", | ||
"example": "https://www.googleapis.com/oauth2/v1/certs" | ||
}, | ||
"client_x509_cert_url": { | ||
"type": "string", | ||
"title": "Certificate URL for your service account", | ||
"example": "https://www.googleapis.com/robot/v1/metadata/x509/my-service-account%40some-my-project.iam.gserviceaccount.com" | ||
}, | ||
"universe_domain": { | ||
"type": "string", | ||
"title": "The universe domain", | ||
"description": "The universe domain. The default universe domain is googleapis.com." | ||
} | ||
}, | ||
"required": [ | ||
"private_key_id", | ||
"private_key", | ||
"client_email", | ||
"client_id" | ||
], | ||
"additionalProperties": false | ||
}` |
66 changes: 66 additions & 0 deletions
66
internal/sdkprovider/service/alloydbomni/service_account_credentials_validator_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package alloydbomni | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestValidateServiceAccountCredentials(t *testing.T) { | ||
cases := []struct { | ||
name string | ||
input string | ||
expected diag.Diagnostics | ||
}{ | ||
{ | ||
name: "valid", | ||
input: `{ | ||
"private_key_id": "0", | ||
"private_key": "1", | ||
"client_email": "2", | ||
"client_id": "3" | ||
}`, | ||
expected: nil, | ||
}, | ||
{ | ||
name: "invalid, empty", | ||
input: `{}`, | ||
expected: diag.Diagnostics{ | ||
{Summary: "(root): private_key_id is required"}, | ||
{Summary: "(root): private_key is required"}, | ||
{Summary: "(root): client_email is required"}, | ||
{Summary: "(root): client_id is required"}, | ||
}, | ||
}, | ||
{ | ||
name: "missing private_key_id", | ||
input: `{ | ||
"private_key": "1", | ||
"client_email": "2", | ||
"client_id": "3" | ||
}`, | ||
expected: diag.Diagnostics{{Summary: "(root): private_key_id is required"}}, | ||
}, | ||
{ | ||
name: "invalid type client_id", | ||
input: `{ | ||
"private_key_id": "0", | ||
"private_key": "1", | ||
"client_email": "2", | ||
"client_id": 3 | ||
}`, | ||
expected: diag.Diagnostics{{Summary: "client_id: Invalid type. Expected: string, given: integer"}}, | ||
}, | ||
} | ||
|
||
for _, tc := range cases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
actual := validateServiceAccountCredentials(tc.input, nil) | ||
if diff := cmp.Diff(tc.expected, actual); diff != "" { | ||
assert.Empty(t, diff) | ||
} | ||
}) | ||
} | ||
} |