Merge pull request #9946 from evandbrown/google-service-account-resource
providers/google: Create and delete Service Accounts
This commit is contained in:
commit
642dd87f34
|
@ -17,6 +17,7 @@ import (
|
||||||
"google.golang.org/api/compute/v1"
|
"google.golang.org/api/compute/v1"
|
||||||
"google.golang.org/api/container/v1"
|
"google.golang.org/api/container/v1"
|
||||||
"google.golang.org/api/dns/v1"
|
"google.golang.org/api/dns/v1"
|
||||||
|
"google.golang.org/api/iam/v1"
|
||||||
"google.golang.org/api/pubsub/v1"
|
"google.golang.org/api/pubsub/v1"
|
||||||
"google.golang.org/api/sqladmin/v1beta4"
|
"google.golang.org/api/sqladmin/v1beta4"
|
||||||
"google.golang.org/api/storage/v1"
|
"google.golang.org/api/storage/v1"
|
||||||
|
@ -36,6 +37,7 @@ type Config struct {
|
||||||
clientResourceManager *cloudresourcemanager.Service
|
clientResourceManager *cloudresourcemanager.Service
|
||||||
clientStorage *storage.Service
|
clientStorage *storage.Service
|
||||||
clientSqlAdmin *sqladmin.Service
|
clientSqlAdmin *sqladmin.Service
|
||||||
|
clientIAM *iam.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) loadAndValidate() error {
|
func (c *Config) loadAndValidate() error {
|
||||||
|
@ -135,12 +137,19 @@ func (c *Config) loadAndValidate() error {
|
||||||
}
|
}
|
||||||
c.clientPubsub.UserAgent = userAgent
|
c.clientPubsub.UserAgent = userAgent
|
||||||
|
|
||||||
log.Printf("[INFO] Instatiating Google CloudResourceManager Client...")
|
log.Printf("[INFO] Instatiating Google Cloud ResourceManager Client...")
|
||||||
c.clientResourceManager, err = cloudresourcemanager.New(client)
|
c.clientResourceManager, err = cloudresourcemanager.New(client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.clientPubsub.UserAgent = userAgent
|
c.clientResourceManager.UserAgent = userAgent
|
||||||
|
|
||||||
|
log.Printf("[INFO] Instatiating Google Cloud IAM Client...")
|
||||||
|
c.clientIAM, err = iam.New(client)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.clientIAM.UserAgent = userAgent
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,6 +96,7 @@ func Provider() terraform.ResourceProvider {
|
||||||
"google_project": resourceGoogleProject(),
|
"google_project": resourceGoogleProject(),
|
||||||
"google_pubsub_topic": resourcePubsubTopic(),
|
"google_pubsub_topic": resourcePubsubTopic(),
|
||||||
"google_pubsub_subscription": resourcePubsubSubscription(),
|
"google_pubsub_subscription": resourcePubsubSubscription(),
|
||||||
|
"google_service_account": resourceGoogleServiceAccount(),
|
||||||
"google_storage_bucket": resourceStorageBucket(),
|
"google_storage_bucket": resourceStorageBucket(),
|
||||||
"google_storage_bucket_acl": resourceStorageBucketAcl(),
|
"google_storage_bucket_acl": resourceStorageBucketAcl(),
|
||||||
"google_storage_bucket_object": resourceStorageBucketObject(),
|
"google_storage_bucket_object": resourceStorageBucketObject(),
|
||||||
|
|
|
@ -468,5 +468,4 @@ data "google_iam_policy" "admin" {
|
||||||
"user:evandbrown@gmail.com",
|
"user:evandbrown@gmail.com",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
}`
|
}`
|
||||||
|
|
|
@ -0,0 +1,320 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"google.golang.org/api/googleapi"
|
||||||
|
"google.golang.org/api/iam/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceGoogleServiceAccount() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceGoogleServiceAccountCreate,
|
||||||
|
Read: resourceGoogleServiceAccountRead,
|
||||||
|
Delete: resourceGoogleServiceAccountDelete,
|
||||||
|
Update: resourceGoogleServiceAccountUpdate,
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"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,
|
||||||
|
},
|
||||||
|
"account_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"display_name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"project": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"policy_data": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceGoogleServiceAccountCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
project, err := getProject(d, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
aid := d.Get("account_id").(string)
|
||||||
|
displayName := d.Get("display_name").(string)
|
||||||
|
|
||||||
|
sa := &iam.ServiceAccount{
|
||||||
|
DisplayName: displayName,
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &iam.CreateServiceAccountRequest{
|
||||||
|
AccountId: aid,
|
||||||
|
ServiceAccount: sa,
|
||||||
|
}
|
||||||
|
|
||||||
|
sa, err = config.clientIAM.Projects.ServiceAccounts.Create("projects/"+project, r).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating service account: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId(sa.Name)
|
||||||
|
|
||||||
|
// Apply the IAM policy if it is set
|
||||||
|
if pString, ok := d.GetOk("policy_data"); ok {
|
||||||
|
// The policy string is just a marshaled cloudresourcemanager.Policy.
|
||||||
|
// Unmarshal it to a struct.
|
||||||
|
var policy iam.Policy
|
||||||
|
if err = json.Unmarshal([]byte(pString.(string)), &policy); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve existing IAM policy from project. This will be merged
|
||||||
|
// with the policy defined here.
|
||||||
|
// TODO(evanbrown): Add an 'authoritative' flag that allows policy
|
||||||
|
// in manifest to overwrite existing policy.
|
||||||
|
p, err := getServiceAccountIamPolicy(sa.Name, config)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Could not find service account %q when applying IAM policy: %s", sa.Name, err)
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] Got existing bindings for service account: %#v", p.Bindings)
|
||||||
|
|
||||||
|
// Merge the existing policy bindings with those defined in this manifest.
|
||||||
|
p.Bindings = saMergeBindings(append(p.Bindings, policy.Bindings...))
|
||||||
|
|
||||||
|
// Apply the merged policy
|
||||||
|
log.Printf("[DEBUG] Setting new policy for service account: %#v", p)
|
||||||
|
_, err = config.clientIAM.Projects.ServiceAccounts.SetIamPolicy(sa.Name,
|
||||||
|
&iam.SetIamPolicyRequest{Policy: p}).Do()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error applying IAM policy for service account %q: %s", sa.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resourceGoogleServiceAccountRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceGoogleServiceAccountRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
// Confirm the service account exists
|
||||||
|
sa, err := config.clientIAM.Projects.ServiceAccounts.Get(d.Id()).Do()
|
||||||
|
if err != nil {
|
||||||
|
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
|
||||||
|
log.Printf("[WARN] Removing reference to service account %q because it no longer exists", d.Id())
|
||||||
|
saName := d.Id()
|
||||||
|
// The resource doesn't exist anymore
|
||||||
|
d.SetId("")
|
||||||
|
|
||||||
|
return fmt.Errorf("Error getting service account with name %q: %s", saName, err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Error reading service account %q: %q", d.Id(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("email", sa.Email)
|
||||||
|
d.Set("unique_id", sa.UniqueId)
|
||||||
|
d.Set("name", sa.Name)
|
||||||
|
d.Set("display_name", sa.DisplayName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceGoogleServiceAccountDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
name := d.Id()
|
||||||
|
_, err := config.clientIAM.Projects.ServiceAccounts.Delete(name).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceGoogleServiceAccountUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
var err error
|
||||||
|
if ok := d.HasChange("display_name"); ok {
|
||||||
|
sa, err := config.clientIAM.Projects.ServiceAccounts.Get(d.Id()).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error retrieving service account %q: %s", d.Id(), err)
|
||||||
|
}
|
||||||
|
_, err = config.clientIAM.Projects.ServiceAccounts.Update(d.Id(),
|
||||||
|
&iam.ServiceAccount{
|
||||||
|
DisplayName: d.Get("display_name").(string),
|
||||||
|
Etag: sa.Etag,
|
||||||
|
}).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error updating service account %q: %s", d.Id(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok := d.HasChange("policy_data"); ok {
|
||||||
|
// The policy string is just a marshaled cloudresourcemanager.Policy.
|
||||||
|
// Unmarshal it to a struct that contains the old and new policies
|
||||||
|
oldP, newP := d.GetChange("policy_data")
|
||||||
|
oldPString := oldP.(string)
|
||||||
|
newPString := newP.(string)
|
||||||
|
|
||||||
|
// JSON Unmarshaling would fail
|
||||||
|
if oldPString == "" {
|
||||||
|
oldPString = "{}"
|
||||||
|
}
|
||||||
|
if newPString == "" {
|
||||||
|
newPString = "{}"
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG]: Old policy: %q\nNew policy: %q", string(oldPString), string(newPString))
|
||||||
|
|
||||||
|
var oldPolicy, newPolicy iam.Policy
|
||||||
|
if err = json.Unmarshal([]byte(newPString), &newPolicy); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal([]byte(oldPString), &oldPolicy); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find any Roles and Members that were removed (i.e., those that are present
|
||||||
|
// in the old but absent in the new
|
||||||
|
oldMap := saRolesToMembersMap(oldPolicy.Bindings)
|
||||||
|
newMap := saRolesToMembersMap(newPolicy.Bindings)
|
||||||
|
deleted := make(map[string]map[string]bool)
|
||||||
|
|
||||||
|
// Get each role and its associated members in the old state
|
||||||
|
for role, members := range oldMap {
|
||||||
|
// Initialize map for role
|
||||||
|
if _, ok := deleted[role]; !ok {
|
||||||
|
deleted[role] = make(map[string]bool)
|
||||||
|
}
|
||||||
|
// The role exists in the new state
|
||||||
|
if _, ok := newMap[role]; ok {
|
||||||
|
// Check each memeber
|
||||||
|
for member, _ := range members {
|
||||||
|
// Member does not exist in new state, so it was deleted
|
||||||
|
if _, ok = newMap[role][member]; !ok {
|
||||||
|
deleted[role][member] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This indicates an entire role was deleted. Mark all members
|
||||||
|
// for delete.
|
||||||
|
for member, _ := range members {
|
||||||
|
deleted[role][member] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] Roles and Members to be deleted: %#v", deleted)
|
||||||
|
|
||||||
|
// Retrieve existing IAM policy from project. This will be merged
|
||||||
|
// with the policy in the current state
|
||||||
|
// TODO(evanbrown): Add an 'authoritative' flag that allows policy
|
||||||
|
// in manifest to overwrite existing policy.
|
||||||
|
p, err := getServiceAccountIamPolicy(d.Id(), config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] Got existing bindings from service account %q: %#v", d.Id(), p.Bindings)
|
||||||
|
|
||||||
|
// Merge existing policy with policy in the current state
|
||||||
|
log.Printf("[DEBUG] Merging new bindings from service account %q: %#v", d.Id(), newPolicy.Bindings)
|
||||||
|
mergedBindings := saMergeBindings(append(p.Bindings, newPolicy.Bindings...))
|
||||||
|
|
||||||
|
// Remove any roles and members that were explicitly deleted
|
||||||
|
mergedBindingsMap := saRolesToMembersMap(mergedBindings)
|
||||||
|
for role, members := range deleted {
|
||||||
|
for member, _ := range members {
|
||||||
|
delete(mergedBindingsMap[role], member)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Bindings = saRolesToMembersBinding(mergedBindingsMap)
|
||||||
|
log.Printf("[DEBUG] Setting new policy for project: %#v", p)
|
||||||
|
|
||||||
|
dump, _ := json.MarshalIndent(p.Bindings, " ", " ")
|
||||||
|
log.Printf(string(dump))
|
||||||
|
_, err = config.clientIAM.Projects.ServiceAccounts.SetIamPolicy(d.Id(),
|
||||||
|
&iam.SetIamPolicyRequest{Policy: p}).Do()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error applying IAM policy for service account %q: %s", d.Id(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the existing IAM Policy for a service account
|
||||||
|
func getServiceAccountIamPolicy(sa string, config *Config) (*iam.Policy, error) {
|
||||||
|
p, err := config.clientIAM.Projects.ServiceAccounts.GetIamPolicy(sa).Do()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error retrieving IAM policy for service account %q: %s", sa, err)
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a map of roles->members to a list of Binding
|
||||||
|
func saRolesToMembersBinding(m map[string]map[string]bool) []*iam.Binding {
|
||||||
|
bindings := make([]*iam.Binding, 0)
|
||||||
|
for role, members := range m {
|
||||||
|
b := iam.Binding{
|
||||||
|
Role: role,
|
||||||
|
Members: make([]string, 0),
|
||||||
|
}
|
||||||
|
for m, _ := range members {
|
||||||
|
b.Members = append(b.Members, m)
|
||||||
|
}
|
||||||
|
bindings = append(bindings, &b)
|
||||||
|
}
|
||||||
|
return bindings
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map a role to a map of members, allowing easy merging of multiple bindings.
|
||||||
|
func saRolesToMembersMap(bindings []*iam.Binding) map[string]map[string]bool {
|
||||||
|
bm := make(map[string]map[string]bool)
|
||||||
|
// Get each binding
|
||||||
|
for _, b := range bindings {
|
||||||
|
// Initialize members map
|
||||||
|
if _, ok := bm[b.Role]; !ok {
|
||||||
|
bm[b.Role] = make(map[string]bool)
|
||||||
|
}
|
||||||
|
// Get each member (user/principal) for the binding
|
||||||
|
for _, m := range b.Members {
|
||||||
|
// Add the member
|
||||||
|
bm[b.Role][m] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bm
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge multiple Bindings such that Bindings with the same Role result in
|
||||||
|
// a single Binding with combined Members
|
||||||
|
func saMergeBindings(bindings []*iam.Binding) []*iam.Binding {
|
||||||
|
bm := saRolesToMembersMap(bindings)
|
||||||
|
rb := make([]*iam.Binding, 0)
|
||||||
|
|
||||||
|
for role, members := range bm {
|
||||||
|
var b iam.Binding
|
||||||
|
b.Role = role
|
||||||
|
b.Members = make([]string, 0)
|
||||||
|
for m, _ := range members {
|
||||||
|
b.Members = append(b.Members, m)
|
||||||
|
}
|
||||||
|
rb = append(rb, &b)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rb
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/acctest"
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test that a service account resource can be created, updated, and destroyed
|
||||||
|
func TestAccGoogleServiceAccount_basic(t *testing.T) {
|
||||||
|
accountId := "a" + acctest.RandString(10)
|
||||||
|
displayName := "Terraform Test"
|
||||||
|
displayName2 := "Terraform Test Update"
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
// The first step creates a basic service account
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccGoogleServiceAccountBasic(accountId, displayName),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckGoogleServiceAccountExists("google_service_account.acceptance"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
// The second step updates the service account
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccGoogleServiceAccountBasic(accountId, displayName2),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckGoogleServiceAccountNameModified("google_service_account.acceptance", displayName2),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that a service account resource can be created with a policy, updated,
|
||||||
|
// and destroyed.
|
||||||
|
func TestAccGoogleServiceAccount_createPolicy(t *testing.T) {
|
||||||
|
accountId := "a" + acctest.RandString(10)
|
||||||
|
displayName := "Terraform Test"
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
// The first step creates a basic service account with an IAM policy
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccGoogleServiceAccountPolicy(accountId, projectId),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckGoogleServiceAccountPolicyCount("google_service_account.acceptance", 1),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
// The second step updates the service account with no IAM policy
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccGoogleServiceAccountBasic(accountId, displayName),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckGoogleServiceAccountPolicyCount("google_service_account.acceptance", 0),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
// The final step re-applies the IAM policy
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccGoogleServiceAccountPolicy(accountId, projectId),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckGoogleServiceAccountPolicyCount("google_service_account.acceptance", 1),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckGoogleServiceAccountPolicyCount(r string, n int) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
c := testAccProvider.Meta().(*Config)
|
||||||
|
p, err := getServiceAccountIamPolicy(s.RootModule().Resources[r].Primary.ID, c)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to retrieve IAM Policy for service account: %s", err)
|
||||||
|
}
|
||||||
|
if len(p.Bindings) != n {
|
||||||
|
return fmt.Errorf("The service account has %v bindings but %v were expected", len(p.Bindings), n)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckGoogleServiceAccountExists(r string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[r]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckGoogleServiceAccountNameModified(r, n string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[r]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.Attributes["display_name"] != n {
|
||||||
|
return fmt.Errorf("display_name is %q expected %q", rs.Primary.Attributes["display_name"], n)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccGoogleServiceAccountBasic(account, name string) string {
|
||||||
|
t := `resource "google_service_account" "acceptance" {
|
||||||
|
account_id = "%v"
|
||||||
|
display_name = "%v"
|
||||||
|
}`
|
||||||
|
return fmt.Sprintf(t, account, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccGoogleServiceAccountPolicy(account, name string) string {
|
||||||
|
|
||||||
|
t := `resource "google_service_account" "acceptance" {
|
||||||
|
account_id = "%v"
|
||||||
|
display_name = "%v"
|
||||||
|
policy_data = "${data.google_iam_policy.service_account.policy_data}"
|
||||||
|
}
|
||||||
|
|
||||||
|
data "google_iam_policy" "service_account" {
|
||||||
|
binding {
|
||||||
|
role = "roles/iam.serviceAccountActor"
|
||||||
|
members = [
|
||||||
|
"serviceAccount:%v@%v.iam.gserviceaccount.com",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
return fmt.Sprintf(t, account, name, account, projectId)
|
||||||
|
}
|
|
@ -19,9 +19,9 @@ project's existing policy. The policy is always specified in a
|
||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
```js
|
```js
|
||||||
resource "google_project" "my-project" {
|
resource "google_project" "my_project" {
|
||||||
id = "your-project-id"
|
id = "your-project-id"
|
||||||
policy_data = "${data.google_iam_policy.admin.policy}"
|
policy_data = "${data.google_iam_policy.admin.policy_data}"
|
||||||
}
|
}
|
||||||
|
|
||||||
data "google_iam_policy" "admin" {
|
data "google_iam_policy" "admin" {
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
---
|
||||||
|
layout: "google"
|
||||||
|
page_title: "Google: google_service_account"
|
||||||
|
sidebar_current: "docs-google-service-account"
|
||||||
|
description: |-
|
||||||
|
Allows management of a Google Cloud Platform service account.
|
||||||
|
---
|
||||||
|
|
||||||
|
# google\_service\_account
|
||||||
|
|
||||||
|
Allows management of a [Google Cloud Platform service account](https://cloud.google.com/compute/docs/access/service-accounts)
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
This snippet creates a service account, then gives it objectViewer
|
||||||
|
permission in a project.
|
||||||
|
|
||||||
|
```js
|
||||||
|
resource "google_service_account" "object_viewer" {
|
||||||
|
account_id = "object-viewer"
|
||||||
|
display_name = "Object viewer"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_project" "my_project" {
|
||||||
|
id = "your-project-id"
|
||||||
|
policy_data = "${data.google_iam_policy.admin.policy_data}"
|
||||||
|
}
|
||||||
|
|
||||||
|
data "google_iam_policy" "admin" {
|
||||||
|
binding {
|
||||||
|
role = "roles/storage.objectViewer"
|
||||||
|
members = [
|
||||||
|
"serviceAccount:${google_service_account.object_viewer.email}",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `account_id` - (Required) The service account ID.
|
||||||
|
Changing this forces a new service account to be created.
|
||||||
|
|
||||||
|
* `display_name` - (Optional) The display name for the service account.
|
||||||
|
Can be updated without creating a new resource.
|
||||||
|
|
||||||
|
* `project` - (Optional) The project that the service account will be created in.
|
||||||
|
Defaults to the provider project configuration.
|
||||||
|
|
||||||
|
* `policy_data` - (Optional) The `google_iam_policy` data source that represents
|
||||||
|
the IAM policy that will be applied to the service account. The policy will be
|
||||||
|
merged with any existing policy.
|
||||||
|
|
||||||
|
Changing this updates the policy.
|
||||||
|
|
||||||
|
Deleting this removes the policy declared in Terraform. Any policy bindings
|
||||||
|
associated with the project before Terraform was used are not deleted.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
* `name` - The fully-qualified name of the service account.
|
||||||
|
|
||||||
|
* `unique_id` - The unique id of the service account.
|
|
@ -24,7 +24,10 @@
|
||||||
<ul class="nav nav-visible">
|
<ul class="nav nav-visible">
|
||||||
<li<%= sidebar_current("docs-google-project") %>>
|
<li<%= sidebar_current("docs-google-project") %>>
|
||||||
<a href="/docs/providers/google/r/google_project.html">google_project</a>
|
<a href="/docs/providers/google/r/google_project.html">google_project</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-google-service-account") %>>
|
||||||
|
<a href="/docs/providers/google/r/google_service_account.html">google_service_account</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue