New datasource: service account and service account key (#1535)

This commit is contained in:
Jonathan Pentecost 2018-06-01 01:31:45 +01:00 committed by Nathan McKinley
parent c71ab3f62f
commit 8f31fec857
12 changed files with 423 additions and 8 deletions

View File

@ -0,0 +1,70 @@
package google
import (
"fmt"
"strings"
"github.com/hashicorp/terraform/helper/schema"
)
func dataSourceGoogleServiceAccount() *schema.Resource {
return &schema.Resource{
Read: dataSourceGoogleServiceAccountRead,
Schema: map[string]*schema.Schema{
"account_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateRFC1035Name(6, 30),
},
"project": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"email": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"unique_id": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"display_name": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}
func dataSourceGoogleServiceAccountRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
// Get the project from the resource or fallback to the project
// in the provider configuration
project, err := getProject(d, config)
if err != nil {
return err
}
// Get the service account as a fully qualified name
serviceAccountName := serviceAccountFQN(d.Get("account_id").(string), project)
sa, err := config.clientIAM.Projects.ServiceAccounts.Get(serviceAccountName).Do()
if err != nil {
return handleNotFoundError(err, d, fmt.Sprintf("Service Account %q", serviceAccountName))
}
d.SetId(sa.Name)
d.Set("email", sa.Email)
d.Set("unique_id", sa.UniqueId)
d.Set("project", sa.ProjectId)
d.Set("account_id", strings.Split(sa.Email, "@")[0])
d.Set("name", sa.Name)
d.Set("display_name", sa.DisplayName)
return nil
}

View File

@ -0,0 +1,74 @@
package google
import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
)
func dataSourceGoogleServiceAccountKey() *schema.Resource {
return &schema.Resource{
Read: dataSourceGoogleServiceAccountKeyRead,
Schema: map[string]*schema.Schema{
"service_account_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"public_key_type": &schema.Schema{
Type: schema.TypeString,
Default: "TYPE_X509_PEM_FILE",
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"TYPE_NONE", "TYPE_X509_PEM_FILE", "TYPE_RAW_PUBLIC_KEY"}, false),
},
"project": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
// Computed
"name": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"key_algorithm": {
Type: schema.TypeString,
Computed: true,
},
"public_key": {
Type: schema.TypeString,
Computed: true,
},
},
}
}
func dataSourceGoogleServiceAccountKeyRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
// Get the project from the resource or fallback to the project
// in the provider configuration
project, err := getProject(d, config)
if err != nil {
return err
}
// Get the service account as the fully qualified name
serviceAccountName := serviceAccountFQN(d.Get("service_account_id").(string), project)
publicKeyType := d.Get("public_key_type").(string)
// Confirm the service account key exists
sak, err := config.clientIAM.Projects.ServiceAccounts.Keys.Get(serviceAccountName).PublicKeyType(publicKeyType).Do()
if err != nil {
return handleNotFoundError(err, d, fmt.Sprintf("Service Account Key %q", serviceAccountName))
}
d.SetId(sak.Name)
d.Set("name", sak.Name)
d.Set("key_algorithm", sak.KeyAlgorithm)
d.Set("public_key", sak.PublicKeyData)
return nil
}

View File

@ -0,0 +1,56 @@
package google
import (
"fmt"
"regexp"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccDatasourceGoogleServiceAccountKey_basic(t *testing.T) {
t.Parallel()
resourceName := "data.google_service_account_key.acceptance"
account := acctest.RandomWithPrefix("tf-test")
serviceAccountName := fmt.Sprintf(
"projects/%s/serviceAccounts/%s@%s.iam.gserviceaccount.com",
getTestProjectFromEnv(),
account,
getTestProjectFromEnv(),
)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDatasourceGoogleServiceAccountKey(account),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleServiceAccountKeyExists(resourceName),
// Check that the 'name' starts with the service account name
resource.TestMatchResourceAttr(resourceName, "name", regexp.MustCompile(serviceAccountName)),
resource.TestCheckResourceAttrSet(resourceName, "key_algorithm"),
resource.TestCheckResourceAttrSet(resourceName, "public_key"),
),
},
},
})
}
func testAccDatasourceGoogleServiceAccountKey(account string) string {
return fmt.Sprintf(`
resource "google_service_account" "acceptance" {
account_id = "%s"
}
resource "google_service_account_key" "acceptance" {
service_account_id = "${google_service_account.acceptance.name}"
public_key_type = "TYPE_X509_PEM_FILE"
}
data "google_service_account_key" "acceptance" {
service_account_id = "${google_service_account_key.acceptance.id}"
}`, account)
}

View File

@ -0,0 +1,48 @@
package google
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccDatasourceGoogleServiceAccount_basic(t *testing.T) {
t.Parallel()
resourceName := "data.google_service_account.acceptance"
account := acctest.RandomWithPrefix("tf-test")
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCheckGoogleServiceAccount_basic(account),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleServiceAccountExists(resourceName),
resource.TestCheckResourceAttr(
resourceName, "id", fmt.Sprintf("projects/%s/serviceAccounts/%s@%s.iam.gserviceaccount.com", getTestProjectFromEnv(), account, getTestProjectFromEnv())),
resource.TestCheckResourceAttrSet(resourceName, "email"),
resource.TestCheckResourceAttrSet(resourceName, "unique_id"),
resource.TestCheckResourceAttrSet(resourceName, "name"),
resource.TestCheckResourceAttrSet(resourceName, "display_name"),
),
},
},
})
}
func testAccCheckGoogleServiceAccount_basic(account string) string {
return fmt.Sprintf(`
resource "google_service_account" "acceptance" {
account_id = "%s"
display_name = "Testing Account"
}
data "google_service_account" "acceptance" {
account_id = "${google_service_account.acceptance.account_id}"
}
`, account)
}

View File

@ -88,6 +88,8 @@ func Provider() terraform.ResourceProvider {
"google_kms_secret": dataSourceGoogleKmsSecret(),
"google_folder": dataSourceGoogleFolder(),
"google_organization": dataSourceGoogleOrganization(),
"google_service_account": dataSourceGoogleServiceAccount(),
"google_service_account_key": dataSourceGoogleServiceAccountKey(),
"google_storage_object_signed_url": dataSourceGoogleSignedUrl(),
"google_storage_project_service_account": dataSourceGoogleStorageProjectServiceAccount(),
"google_compute_backend_service": dataSourceGoogleComputeBackendService(),

View File

@ -2,7 +2,6 @@ package google
import (
"fmt"
"strings"
"github.com/hashicorp/terraform/helper/encryption"
"github.com/hashicorp/terraform/helper/schema"
@ -88,17 +87,21 @@ func resourceGoogleServiceAccountKey() *schema.Resource {
func resourceGoogleServiceAccountKeyCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
serviceAccount := d.Get("service_account_id").(string)
if !strings.HasPrefix(serviceAccount, "projects/") {
serviceAccount = "projects/-/serviceAccounts/" + serviceAccount
// Get the project from the resource or fallback to the project
// in the provider configuration
project, err := getProject(d, config)
if err != nil {
return err
}
serviceAccountName := serviceAccountFQN(d.Get("service_account_id").(string), project)
r := &iam.CreateServiceAccountKeyRequest{
KeyAlgorithm: d.Get("key_algorithm").(string),
PrivateKeyType: d.Get("private_key_type").(string),
}
sak, err := config.clientIAM.Projects.ServiceAccounts.Keys.Create(serviceAccount, r).Do()
sak, err := config.clientIAM.Projects.ServiceAccounts.Keys.Create(serviceAccountName, r).Do()
if err != nil {
return fmt.Errorf("Error creating service account key: %s", err)
}

View File

@ -360,3 +360,19 @@ func lockedCall(lockKey string, f func() error) error {
return f()
}
// serviceAccountFQN will attempt to generate the fully qualified name in the format of:
// "projects/(-|<project_name>)/serviceAccounts/<service_account_id>@<project_name>.iam.gserviceaccount.com"
func serviceAccountFQN(serviceAccount, project string) string {
// If the service account id isn't already the fully qualified name
if !strings.HasPrefix(serviceAccount, "projects/") {
// If the service account id is an email
if strings.Contains(serviceAccount, "@") {
serviceAccount = "projects/-/serviceAccounts/" + serviceAccount
} else {
// If the service account id doesn't contain the email, build it
serviceAccount = fmt.Sprintf("projects/-/serviceAccounts/%s@%s.iam.gserviceaccount.com", serviceAccount, project)
}
}
return serviceAccount
}

View File

@ -444,3 +444,30 @@ func TestEmptyOrDefaultStringSuppress(t *testing.T) {
}
}
}
func TestServiceAccountFQN(t *testing.T) {
// Every test case should produce this fully qualified service account name
serviceAccountExpected := "projects/-/serviceAccounts/test-service-account@test-project.iam.gserviceaccount.com"
cases := map[string]struct {
serviceAccount string
project string
}{
"service account fully qualified name from account id": {
serviceAccount: "test-service-account",
project: "test-project",
},
"service account fully qualified name from account email": {
serviceAccount: "test-service-account@test-project.iam.gserviceaccount.com",
},
"service account fully qualified name from account name": {
serviceAccount: "projects/-/serviceAccounts/test-service-account@test-project.iam.gserviceaccount.com",
},
}
for tn, tc := range cases {
serviceAccountName := serviceAccountFQN(tc.serviceAccount, tc.project)
if serviceAccountName != serviceAccountExpected {
t.Errorf("bad: %s, expected '%s' but returned '%s", tn, serviceAccountExpected, serviceAccountName)
}
}
}

View File

@ -2,12 +2,13 @@ package google
import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
"net"
"regexp"
"strconv"
"strings"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
)
const (

View File

@ -0,0 +1,63 @@
---
layout: "google"
page_title: "Google: google_service_account"
sidebar_current: "docs-google-datasource-service-account"
description: |-
Get the service account from a project.
---
# google\_service\_account
Get the service account from a project. For more information see
the official [API](https://cloud.google.com/compute/docs/access/service-accounts) documentation.
## Example Usage
```hcl
data "google_service_account" "object_viewer" {
account_id = "object-viewer"
}
```
## Example Usage, save key in Kubernetes secret
```hcl
data "google_service_account" "myaccount" {
account_id = "myaccount-id"
}
resource "google_service_account_key" "mykey" {
service_account_id = "${data.google_service_account.myaccount.name}"
}
resource "kubernetes_secret" "google-application-credentials" {
metadata {
name = "google-application-credentials"
}
data {
credentials.json = "${base64decode(google_service_account_key.mykey.private_key)}"
}
```
## Argument Reference
The following arguments are supported:
* `account_id` - (Required) The Service account id.
* `project` - (Optional) The ID of the project that the service account will be created in.
Defaults to the provider project configuration.
## Attributes Reference
In addition to the arguments listed above, the following computed attributes are
exported:
* `email` - The e-mail address of the service account. This value
should be referenced from any `google_iam_policy` data sources
that would grant the service account privileges.
* `unique_id` - The unique id of the service account.
* `name` - The fully-qualified name of the service account.
* `display_name` - The display name for the service account.

View File

@ -0,0 +1,50 @@
---
layout: "google"
page_title: "Google: google_service_account_key"
sidebar_current: "docs-google-datasource-service-account-key"
description: |-
Get a Google Cloud Platform service account Public Key
---
# google\_service\_account\_key
Get service account public key. For more information, see [the official documentation](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) and [API](https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts.keys/get).
## Example Usage
```hcl
data "google_service_account" "myaccount" {
account_id = "myaccount"
}
data "google_service_account_key" "mykey" {
service_account_id = "${data.google_service_account.myaccount.name}"
public_key_type = "TYPE_X509_PEM_FILE"
}
output "mykey_public_key" {
value = "${data.google_service_account_key.mykey.public_key}"
}
```
## Argument Reference
The following arguments are supported:
* `service_account_id` - (Required) The Service account id of the Key Pair. This can be a string in the format
`{ACCOUNT}` or `projects/{PROJECT_ID}/serviceAccounts/{ACCOUNT}`, where `{ACCOUNT}` is the email address or
unique id of the service account. If the `{ACCOUNT}` syntax is used, the project will be inferred from the account.
* `project` - (Optional) The ID of the project that the service account will be created in.
Defaults to the provider project configuration.
* `public_key_type` (Optional) The output format of the public key requested. X509_PEM is the default output format.
## Attributes Reference
The following attributes are exported in addition to the arguments listed above:
* `name` - The name used for this key pair
* `public_key` - The public key, base64 encoded

View File

@ -40,7 +40,6 @@
<li<%= sidebar_current("docs-google-datasource-compute-network") %>>
<a href="/docs/providers/google/d/datasource_compute_network.html">google_compute_network</a>
</li>
</li>
<li<%= sidebar_current("docs-google-datasource-project") %>>
<a href="/docs/providers/google/d/google_project.html">google_project</a>
</li>
@ -98,6 +97,12 @@
<li<%= sidebar_current("docs-google-datasource-folder") %>>
<a href="/docs/providers/google/d/google_folder.html">google_folder</a>
</li>
<li<%= sidebar_current("docs-google-datasource-service-account") %>>
<a href="/docs/providers/google/d/datasource_google_service_account.html">google_service_account</a>
</li>
<li<%= sidebar_current("docs-google-datasource-service-account-key") %>>
<a href="/docs/providers/google/d/datasource_google_service_account_key.html">google_service_account_key</a>
</li>
<li<%= sidebar_current("docs-google-datasource-signed_url") %>>
<a href="/docs/providers/google/d/signed_url.html">google_storage_object_signed_url</a>
</li>