diff --git a/google-beta/config.go b/google-beta/config.go index f608745967d..5d432d3902e 100644 --- a/google-beta/config.go +++ b/google-beta/config.go @@ -35,6 +35,7 @@ import ( "google.golang.org/api/dns/v1" dnsBeta "google.golang.org/api/dns/v1beta2" file "google.golang.org/api/file/v1beta1" + healthcare "google.golang.org/api/healthcare/v1beta1" "google.golang.org/api/iam/v1" iamcredentials "google.golang.org/api/iamcredentials/v1" iap "google.golang.org/api/iap/v1beta1" @@ -153,6 +154,9 @@ type Config struct { IAMBasePath string clientIAM *iam.Service + HealthcareBasePath string + clientHealthcare *healthcare.Service + IAPBasePath string clientIAP *iap.Service @@ -534,6 +538,22 @@ func (c *Config) LoadAndValidate() error { c.clientStorageTransfer.UserAgent = userAgent c.clientStorageTransfer.BasePath = storageTransferClientBasePath + log.Printf("[INFO] Instantiating IAP Client...") + c.clientHealthcare, err = healthcare.NewService(context, option.WithHTTPClient(client)) + if err != nil { + return err + } + c.clientHealthcare.UserAgent = userAgent + + healthcareClientBasePath := removeBasePathVersion(c.HealthcareBasePath) + log.Printf("[INFO] Instantiating Google Cloud Healthcare client for path %s", healthcareClientBasePath) + c.clientHealthcare, err = healthcare.NewService(context, option.WithHTTPClient(client)) + if err != nil { + return err + } + c.clientHealthcare.UserAgent = userAgent + c.clientHealthcare.BasePath = healthcareClientBasePath + return nil } diff --git a/google-beta/healthcare_utils.go b/google-beta/healthcare_utils.go new file mode 100644 index 00000000000..ada0f626fed --- /dev/null +++ b/google-beta/healthcare_utils.go @@ -0,0 +1,235 @@ +package google + +import ( + "fmt" + "regexp" + "strings" +) + +type healthcareDatasetId struct { + Project string + Location string + Name string +} + +func (s *healthcareDatasetId) datasetId() string { + return fmt.Sprintf("projects/%s/locations/%s/datasets/%s", s.Project, s.Location, s.Name) +} + +func (s *healthcareDatasetId) terraformId() string { + return fmt.Sprintf("%s/%s/%s", s.Project, s.Location, s.Name) +} + +func parseHealthcareDatasetId(id string, config *Config) (*healthcareDatasetId, error) { + parts := strings.Split(id, "/") + + datasetIdRegex := regexp.MustCompile("^(" + ProjectRegex + ")/([a-z0-9-])+/([a-zA-Z0-9_-]{1,256})$") + datasetIdWithoutProjectRegex := regexp.MustCompile("^([a-z0-9-])+/([a-zA-Z0-9_-]{1,256})$") + datasetRelativeLinkRegex := regexp.MustCompile("^projects/(" + ProjectRegex + ")/locations/([a-z0-9-]+)/datasets/([a-zA-Z0-9_-]{1,256})$") + + if datasetIdRegex.MatchString(id) { + return &healthcareDatasetId{ + Project: parts[0], + Location: parts[1], + Name: parts[2], + }, nil + } + + if datasetIdWithoutProjectRegex.MatchString(id) { + if config.Project == "" { + return nil, fmt.Errorf("The default project for the provider must be set when using the `{location}/{datasetName}` id format.") + } + + return &healthcareDatasetId{ + Project: config.Project, + Location: parts[0], + Name: parts[1], + }, nil + } + + if parts := datasetRelativeLinkRegex.FindStringSubmatch(id); parts != nil { + return &healthcareDatasetId{ + Project: parts[1], + Location: parts[2], + Name: parts[3], + }, nil + } + return nil, fmt.Errorf("Invalid Dataset id format, expecting `{projectId}/{locationId}/{datasetName}` or `{locationId}/{datasetName}.`") +} + +type healthcareFhirStoreId struct { + DatasetId healthcareDatasetId + Name string +} + +func (s *healthcareFhirStoreId) fhirStoreId() string { + return fmt.Sprintf("%s/fhirStores/%s", s.DatasetId.datasetId(), s.Name) +} + +func (s *healthcareFhirStoreId) terraformId() string { + return fmt.Sprintf("%s/%s", s.DatasetId.terraformId(), s.Name) +} + +func parseHealthcareFhirStoreId(id string, config *Config) (*healthcareFhirStoreId, error) { + parts := strings.Split(id, "/") + + fhirStoreIdRegex := regexp.MustCompile("^(" + ProjectRegex + ")/([a-z0-9-])+/([a-zA-Z0-9_-]{1,256})/([a-zA-Z0-9_-]{1,256})$") + fhirStoreIdWithoutProjectRegex := regexp.MustCompile("^([a-z0-9-])+/([a-zA-Z0-9_-]{1,256})/([a-zA-Z0-9_-]{1,256})$") + fhirStoreRelativeLinkRegex := regexp.MustCompile("^projects/(" + ProjectRegex + ")/locations/([a-z0-9-]+)/datasets/([a-zA-Z0-9_-]{1,256})/fhirStores/([a-zA-Z0-9_-]{1,256})$") + + if fhirStoreIdRegex.MatchString(id) { + return &healthcareFhirStoreId{ + DatasetId: healthcareDatasetId{ + Project: parts[0], + Location: parts[1], + Name: parts[2], + }, + Name: parts[3], + }, nil + } + + if fhirStoreIdWithoutProjectRegex.MatchString(id) { + if config.Project == "" { + return nil, fmt.Errorf("The default project for the provider must be set when using the `{location}/{datasetName}/{fhirStoreName}` id format.") + } + + return &healthcareFhirStoreId{ + DatasetId: healthcareDatasetId{ + Project: config.Project, + Location: parts[0], + Name: parts[1], + }, + Name: parts[2], + }, nil + } + + if parts := fhirStoreRelativeLinkRegex.FindStringSubmatch(id); parts != nil { + return &healthcareFhirStoreId{ + DatasetId: healthcareDatasetId{ + Project: parts[1], + Location: parts[2], + Name: parts[3], + }, + Name: parts[4], + }, nil + } + return nil, fmt.Errorf("Invalid FhirStore id format, expecting `{projectId}/{locationId}/{datasetName}/{fhirStoreName}` or `{locationId}/{datasetName}/{fhirStoreName}.`") +} + +type healthcareHl7V2StoreId struct { + DatasetId healthcareDatasetId + Name string +} + +func (s *healthcareHl7V2StoreId) hl7V2StoreId() string { + return fmt.Sprintf("%s/hl7V2Stores/%s", s.DatasetId.datasetId(), s.Name) +} + +func (s *healthcareHl7V2StoreId) terraformId() string { + return fmt.Sprintf("%s/%s", s.DatasetId.terraformId(), s.Name) +} + +func parseHealthcareHl7V2StoreId(id string, config *Config) (*healthcareHl7V2StoreId, error) { + parts := strings.Split(id, "/") + + hl7V2StoreIdRegex := regexp.MustCompile("^(" + ProjectRegex + ")/([a-z0-9-])+/([a-zA-Z0-9_-]{1,256})/([a-zA-Z0-9_-]{1,256})$") + hl7V2StoreIdWithoutProjectRegex := regexp.MustCompile("^([a-z0-9-])+/([a-zA-Z0-9_-]{1,256})/([a-zA-Z0-9_-]{1,256})$") + hl7V2StoreRelativeLinkRegex := regexp.MustCompile("^projects/(" + ProjectRegex + ")/locations/([a-z0-9-]+)/datasets/([a-zA-Z0-9_-]{1,256})/hl7V2Stores/([a-zA-Z0-9_-]{1,256})$") + + if hl7V2StoreIdRegex.MatchString(id) { + return &healthcareHl7V2StoreId{ + DatasetId: healthcareDatasetId{ + Project: parts[0], + Location: parts[1], + Name: parts[2], + }, + Name: parts[3], + }, nil + } + + if hl7V2StoreIdWithoutProjectRegex.MatchString(id) { + if config.Project == "" { + return nil, fmt.Errorf("The default project for the provider must be set when using the `{location}/{datasetName}/{hl7V2StoreName}` id format.") + } + + return &healthcareHl7V2StoreId{ + DatasetId: healthcareDatasetId{ + Project: config.Project, + Location: parts[0], + Name: parts[1], + }, + Name: parts[2], + }, nil + } + + if parts := hl7V2StoreRelativeLinkRegex.FindStringSubmatch(id); parts != nil { + return &healthcareHl7V2StoreId{ + DatasetId: healthcareDatasetId{ + Project: parts[1], + Location: parts[2], + Name: parts[3], + }, + Name: parts[4], + }, nil + } + return nil, fmt.Errorf("Invalid Hl7V2Store id format, expecting `{projectId}/{locationId}/{datasetName}/{hl7V2StoreName}` or `{locationId}/{datasetName}/{hl7V2StoreName}.`") +} + +type healthcareDicomStoreId struct { + DatasetId healthcareDatasetId + Name string +} + +func (s *healthcareDicomStoreId) dicomStoreId() string { + return fmt.Sprintf("%s/dicomStores/%s", s.DatasetId.datasetId(), s.Name) +} + +func (s *healthcareDicomStoreId) terraformId() string { + return fmt.Sprintf("%s/%s", s.DatasetId.terraformId(), s.Name) +} + +func parseHealthcareDicomStoreId(id string, config *Config) (*healthcareDicomStoreId, error) { + parts := strings.Split(id, "/") + + dicomStoreIdRegex := regexp.MustCompile("^(" + ProjectRegex + ")/([a-z0-9-])+/([a-zA-Z0-9_-]{1,256})/([a-zA-Z0-9_-]{1,256})$") + dicomStoreIdWithoutProjectRegex := regexp.MustCompile("^([a-z0-9-])+/([a-zA-Z0-9_-]{1,256})/([a-zA-Z0-9_-]{1,256})$") + dicomStoreRelativeLinkRegex := regexp.MustCompile("^projects/(" + ProjectRegex + ")/locations/([a-z0-9-]+)/datasets/([a-zA-Z0-9_-]{1,256})/dicomStores/([a-zA-Z0-9_-]{1,256})$") + + if dicomStoreIdRegex.MatchString(id) { + return &healthcareDicomStoreId{ + DatasetId: healthcareDatasetId{ + Project: parts[0], + Location: parts[1], + Name: parts[2], + }, + Name: parts[3], + }, nil + } + + if dicomStoreIdWithoutProjectRegex.MatchString(id) { + if config.Project == "" { + return nil, fmt.Errorf("The default project for the provider must be set when using the `{location}/{datasetName}/{dicomStoreName}` id format.") + } + + return &healthcareDicomStoreId{ + DatasetId: healthcareDatasetId{ + Project: config.Project, + Location: parts[0], + Name: parts[1], + }, + Name: parts[2], + }, nil + } + + if parts := dicomStoreRelativeLinkRegex.FindStringSubmatch(id); parts != nil { + return &healthcareDicomStoreId{ + DatasetId: healthcareDatasetId{ + Project: parts[1], + Location: parts[2], + Name: parts[3], + }, + Name: parts[4], + }, nil + } + return nil, fmt.Errorf("Invalid DicomStore id format, expecting `{projectId}/{locationId}/{datasetName}/{dicomStoreName}` or `{locationId}/{datasetName}/{dicomStoreName}.`") +} diff --git a/google-beta/iam_healthcare_dataset.go b/google-beta/iam_healthcare_dataset.go new file mode 100644 index 00000000000..abe09f5b2c1 --- /dev/null +++ b/google-beta/iam_healthcare_dataset.go @@ -0,0 +1,113 @@ +package google + +import ( + "fmt" + + healthcare "google.golang.org/api/healthcare/v1beta1" + + "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/cloudresourcemanager/v1" +) + +var IamHealthcareDatasetSchema = map[string]*schema.Schema{ + "dataset_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, +} + +type HealthcareDatasetIamUpdater struct { + resourceId string + Config *Config +} + +func NewHealthcareDatasetIamUpdater(d *schema.ResourceData, config *Config) (ResourceIamUpdater, error) { + dataset := d.Get("dataset_id").(string) + datasetId, err := parseHealthcareDatasetId(dataset, config) + + if err != nil { + return nil, errwrap.Wrapf(fmt.Sprintf("Error parsing resource ID for for %s: {{err}}", dataset), err) + } + + return &HealthcareDatasetIamUpdater{ + resourceId: datasetId.datasetId(), + Config: config, + }, nil +} + +func DatasetIdParseFunc(d *schema.ResourceData, config *Config) error { + datasetId, err := parseHealthcareDatasetId(d.Id(), config) + if err != nil { + return err + } + + d.Set("dataset_id", datasetId.datasetId()) + d.SetId(datasetId.datasetId()) + return nil +} + +func (u *HealthcareDatasetIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) { + p, err := u.Config.clientHealthcare.Projects.Locations.Datasets.GetIamPolicy(u.resourceId).Do() + + if err != nil { + return nil, errwrap.Wrapf(fmt.Sprintf("Error retrieving IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + cloudResourcePolicy, err := healthcareToResourceManagerPolicy(p) + + if err != nil { + return nil, errwrap.Wrapf(fmt.Sprintf("Invalid IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + return cloudResourcePolicy, nil +} + +func (u *HealthcareDatasetIamUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error { + healthcarePolicy, err := resourceManagerToHealthcarePolicy(policy) + + if err != nil { + return errwrap.Wrapf(fmt.Sprintf("Invalid IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + _, err = u.Config.clientHealthcare.Projects.Locations.Datasets.SetIamPolicy(u.resourceId, &healthcare.SetIamPolicyRequest{ + Policy: healthcarePolicy, + }).Do() + + if err != nil { + return errwrap.Wrapf(fmt.Sprintf("Error setting IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + return nil +} + +func (u *HealthcareDatasetIamUpdater) GetResourceId() string { + return u.resourceId +} + +func (u *HealthcareDatasetIamUpdater) GetMutexKey() string { + return u.resourceId +} + +func (u *HealthcareDatasetIamUpdater) DescribeResource() string { + return fmt.Sprintf("Healthcare Dataset %q", u.resourceId) +} + +func resourceManagerToHealthcarePolicy(p *cloudresourcemanager.Policy) (*healthcare.Policy, error) { + out := &healthcare.Policy{} + err := Convert(p, out) + if err != nil { + return nil, errwrap.Wrapf("Cannot convert a v1 policy to a healthcare policy: {{err}}", err) + } + return out, nil +} + +func healthcareToResourceManagerPolicy(p *healthcare.Policy) (*cloudresourcemanager.Policy, error) { + out := &cloudresourcemanager.Policy{} + err := Convert(p, out) + if err != nil { + return nil, errwrap.Wrapf("Cannot convert a healthcare policy to a v1 policy: {{err}}", err) + } + return out, nil +} diff --git a/google-beta/iam_healthcare_dicom_store.go b/google-beta/iam_healthcare_dicom_store.go new file mode 100644 index 00000000000..19c3ab63ca1 --- /dev/null +++ b/google-beta/iam_healthcare_dicom_store.go @@ -0,0 +1,94 @@ +package google + +import ( + "fmt" + + healthcare "google.golang.org/api/healthcare/v1beta1" + + "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/cloudresourcemanager/v1" +) + +var IamHealthcareDicomStoreSchema = map[string]*schema.Schema{ + "dicom_store_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, +} + +type HealthcareDicomStoreIamUpdater struct { + resourceId string + Config *Config +} + +func NewHealthcareDicomStoreIamUpdater(d *schema.ResourceData, config *Config) (ResourceIamUpdater, error) { + dicomStore := d.Get("dicom_store_id").(string) + dicomStoreId, err := parseHealthcareDicomStoreId(dicomStore, config) + + if err != nil { + return nil, errwrap.Wrapf(fmt.Sprintf("Error parsing resource ID for for %s: {{err}}", dicomStore), err) + } + + return &HealthcareDicomStoreIamUpdater{ + resourceId: dicomStoreId.dicomStoreId(), + Config: config, + }, nil +} + +func DicomStoreIdParseFunc(d *schema.ResourceData, config *Config) error { + dicomStoreId, err := parseHealthcareDicomStoreId(d.Id(), config) + if err != nil { + return err + } + d.Set("dicom_store_id", dicomStoreId.dicomStoreId()) + d.SetId(dicomStoreId.dicomStoreId()) + return nil +} + +func (u *HealthcareDicomStoreIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) { + p, err := u.Config.clientHealthcare.Projects.Locations.Datasets.DicomStores.GetIamPolicy(u.resourceId).Do() + + if err != nil { + return nil, errwrap.Wrapf(fmt.Sprintf("Error retrieving IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + cloudResourcePolicy, err := healthcareToResourceManagerPolicy(p) + + if err != nil { + return nil, errwrap.Wrapf(fmt.Sprintf("Invalid IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + return cloudResourcePolicy, nil +} + +func (u *HealthcareDicomStoreIamUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error { + healthcarePolicy, err := resourceManagerToHealthcarePolicy(policy) + + if err != nil { + return errwrap.Wrapf(fmt.Sprintf("Invalid IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + _, err = u.Config.clientHealthcare.Projects.Locations.Datasets.DicomStores.SetIamPolicy(u.resourceId, &healthcare.SetIamPolicyRequest{ + Policy: healthcarePolicy, + }).Do() + + if err != nil { + return errwrap.Wrapf(fmt.Sprintf("Error setting IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + return nil +} + +func (u *HealthcareDicomStoreIamUpdater) GetResourceId() string { + return u.resourceId +} + +func (u *HealthcareDicomStoreIamUpdater) GetMutexKey() string { + return u.resourceId +} + +func (u *HealthcareDicomStoreIamUpdater) DescribeResource() string { + return fmt.Sprintf("Healthcare DicomStore %q", u.resourceId) +} diff --git a/google-beta/iam_healthcare_fhir_store.go b/google-beta/iam_healthcare_fhir_store.go new file mode 100644 index 00000000000..9f1f2e3667f --- /dev/null +++ b/google-beta/iam_healthcare_fhir_store.go @@ -0,0 +1,94 @@ +package google + +import ( + "fmt" + + healthcare "google.golang.org/api/healthcare/v1beta1" + + "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/cloudresourcemanager/v1" +) + +var IamHealthcareFhirStoreSchema = map[string]*schema.Schema{ + "fhir_store_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, +} + +type HealthcareFhirStoreIamUpdater struct { + resourceId string + Config *Config +} + +func NewHealthcareFhirStoreIamUpdater(d *schema.ResourceData, config *Config) (ResourceIamUpdater, error) { + fhirStore := d.Get("fhir_store_id").(string) + fhirStoreId, err := parseHealthcareFhirStoreId(fhirStore, config) + + if err != nil { + return nil, errwrap.Wrapf(fmt.Sprintf("Error parsing resource ID for for %s: {{err}}", fhirStore), err) + } + + return &HealthcareFhirStoreIamUpdater{ + resourceId: fhirStoreId.fhirStoreId(), + Config: config, + }, nil +} + +func FhirStoreIdParseFunc(d *schema.ResourceData, config *Config) error { + fhirStoreId, err := parseHealthcareFhirStoreId(d.Id(), config) + if err != nil { + return err + } + d.Set("fhir_store_id", fhirStoreId.fhirStoreId()) + d.SetId(fhirStoreId.fhirStoreId()) + return nil +} + +func (u *HealthcareFhirStoreIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) { + p, err := u.Config.clientHealthcare.Projects.Locations.Datasets.FhirStores.GetIamPolicy(u.resourceId).Do() + + if err != nil { + return nil, errwrap.Wrapf(fmt.Sprintf("Error retrieving IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + cloudResourcePolicy, err := healthcareToResourceManagerPolicy(p) + + if err != nil { + return nil, errwrap.Wrapf(fmt.Sprintf("Invalid IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + return cloudResourcePolicy, nil +} + +func (u *HealthcareFhirStoreIamUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error { + healthcarePolicy, err := resourceManagerToHealthcarePolicy(policy) + + if err != nil { + return errwrap.Wrapf(fmt.Sprintf("Invalid IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + _, err = u.Config.clientHealthcare.Projects.Locations.Datasets.FhirStores.SetIamPolicy(u.resourceId, &healthcare.SetIamPolicyRequest{ + Policy: healthcarePolicy, + }).Do() + + if err != nil { + return errwrap.Wrapf(fmt.Sprintf("Error setting IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + return nil +} + +func (u *HealthcareFhirStoreIamUpdater) GetResourceId() string { + return u.resourceId +} + +func (u *HealthcareFhirStoreIamUpdater) GetMutexKey() string { + return u.resourceId +} + +func (u *HealthcareFhirStoreIamUpdater) DescribeResource() string { + return fmt.Sprintf("Healthcare FhirStore %q", u.resourceId) +} diff --git a/google-beta/iam_healthcare_hl7_v2_store.go b/google-beta/iam_healthcare_hl7_v2_store.go new file mode 100644 index 00000000000..a92544e7557 --- /dev/null +++ b/google-beta/iam_healthcare_hl7_v2_store.go @@ -0,0 +1,94 @@ +package google + +import ( + "fmt" + + healthcare "google.golang.org/api/healthcare/v1beta1" + + "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/cloudresourcemanager/v1" +) + +var IamHealthcareHl7V2StoreSchema = map[string]*schema.Schema{ + "hl7_v2_store_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, +} + +type HealthcareHl7V2StoreIamUpdater struct { + resourceId string + Config *Config +} + +func NewHealthcareHl7V2StoreIamUpdater(d *schema.ResourceData, config *Config) (ResourceIamUpdater, error) { + hl7V2Store := d.Get("hl7_v2_store_id").(string) + hl7V2StoreId, err := parseHealthcareHl7V2StoreId(hl7V2Store, config) + + if err != nil { + return nil, errwrap.Wrapf(fmt.Sprintf("Error parsing resource ID for for %s: {{err}}", hl7V2Store), err) + } + + return &HealthcareHl7V2StoreIamUpdater{ + resourceId: hl7V2StoreId.hl7V2StoreId(), + Config: config, + }, nil +} + +func Hl7V2StoreIdParseFunc(d *schema.ResourceData, config *Config) error { + hl7V2StoreId, err := parseHealthcareHl7V2StoreId(d.Id(), config) + if err != nil { + return err + } + d.Set("hl7_v2_store_id", hl7V2StoreId.hl7V2StoreId()) + d.SetId(hl7V2StoreId.hl7V2StoreId()) + return nil +} + +func (u *HealthcareHl7V2StoreIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) { + p, err := u.Config.clientHealthcare.Projects.Locations.Datasets.Hl7V2Stores.GetIamPolicy(u.resourceId).Do() + + if err != nil { + return nil, errwrap.Wrapf(fmt.Sprintf("Error retrieving IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + cloudResourcePolicy, err := healthcareToResourceManagerPolicy(p) + + if err != nil { + return nil, errwrap.Wrapf(fmt.Sprintf("Invalid IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + return cloudResourcePolicy, nil +} + +func (u *HealthcareHl7V2StoreIamUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error { + healthcarePolicy, err := resourceManagerToHealthcarePolicy(policy) + + if err != nil { + return errwrap.Wrapf(fmt.Sprintf("Invalid IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + _, err = u.Config.clientHealthcare.Projects.Locations.Datasets.Hl7V2Stores.SetIamPolicy(u.resourceId, &healthcare.SetIamPolicyRequest{ + Policy: healthcarePolicy, + }).Do() + + if err != nil { + return errwrap.Wrapf(fmt.Sprintf("Error setting IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + return nil +} + +func (u *HealthcareHl7V2StoreIamUpdater) GetResourceId() string { + return u.resourceId +} + +func (u *HealthcareHl7V2StoreIamUpdater) GetMutexKey() string { + return u.resourceId +} + +func (u *HealthcareHl7V2StoreIamUpdater) DescribeResource() string { + return fmt.Sprintf("Healthcare Hl7V2Store %q", u.resourceId) +} diff --git a/google-beta/provider.go b/google-beta/provider.go index 51dbc096a13..d295d0145f2 100644 --- a/google-beta/provider.go +++ b/google-beta/provider.go @@ -103,7 +103,8 @@ func Provider() terraform.ResourceProvider { // Handwritten Products / Versioned / Atypical Entries // start beta-only products - IAPCustomEndpointEntryKey: IAPCustomEndpointEntry, + HealthcareCustomEndpointEntryKey: HealthcareCustomEndpointEntry, + IAPCustomEndpointEntryKey: IAPCustomEndpointEntry, // end beta-only products CloudBillingCustomEndpointEntryKey: CloudBillingCustomEndpointEntry, ComposerCustomEndpointEntryKey: ComposerCustomEndpointEntry, @@ -198,6 +199,7 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { GeneratedBinaryAuthorizationResourcesMap, GeneratedContainerAnalysisResourcesMap, GeneratedSecurityScannerResourcesMap, + GeneratedHealthcareResourcesMap, // end beta-only products GeneratedAccessContextManagerResourcesMap, GeneratedAppEngineResourcesMap, @@ -271,6 +273,18 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_folder_iam_member": ResourceIamMemberWithImport(IamFolderSchema, NewFolderIamUpdater, FolderIdParseFunc), "google_folder_iam_policy": ResourceIamPolicyWithImport(IamFolderSchema, NewFolderIamUpdater, FolderIdParseFunc), "google_folder_organization_policy": resourceGoogleFolderOrganizationPolicy(), + "google_healthcare_dataset_iam_binding": ResourceIamBindingWithImport(IamHealthcareDatasetSchema, NewHealthcareDatasetIamUpdater, DatasetIdParseFunc), + "google_healthcare_dataset_iam_member": ResourceIamMemberWithImport(IamHealthcareDatasetSchema, NewHealthcareDatasetIamUpdater, DatasetIdParseFunc), + "google_healthcare_dataset_iam_policy": ResourceIamPolicyWithImport(IamHealthcareDatasetSchema, NewHealthcareDatasetIamUpdater, DatasetIdParseFunc), + "google_healthcare_dicom_store_iam_binding": ResourceIamBindingWithImport(IamHealthcareDicomStoreSchema, NewHealthcareDicomStoreIamUpdater, DicomStoreIdParseFunc), + "google_healthcare_dicom_store_iam_member": ResourceIamMemberWithImport(IamHealthcareDicomStoreSchema, NewHealthcareDicomStoreIamUpdater, DicomStoreIdParseFunc), + "google_healthcare_dicom_store_iam_policy": ResourceIamPolicyWithImport(IamHealthcareDicomStoreSchema, NewHealthcareDicomStoreIamUpdater, DicomStoreIdParseFunc), + "google_healthcare_fhir_store_iam_binding": ResourceIamBindingWithImport(IamHealthcareFhirStoreSchema, NewHealthcareFhirStoreIamUpdater, FhirStoreIdParseFunc), + "google_healthcare_fhir_store_iam_member": ResourceIamMemberWithImport(IamHealthcareFhirStoreSchema, NewHealthcareFhirStoreIamUpdater, FhirStoreIdParseFunc), + "google_healthcare_fhir_store_iam_policy": ResourceIamPolicyWithImport(IamHealthcareFhirStoreSchema, NewHealthcareFhirStoreIamUpdater, FhirStoreIdParseFunc), + "google_healthcare_hl7_v2_store_iam_binding": ResourceIamBindingWithImport(IamHealthcareHl7V2StoreSchema, NewHealthcareHl7V2StoreIamUpdater, Hl7V2StoreIdParseFunc), + "google_healthcare_hl7_v2_store_iam_member": ResourceIamMemberWithImport(IamHealthcareHl7V2StoreSchema, NewHealthcareHl7V2StoreIamUpdater, Hl7V2StoreIdParseFunc), + "google_healthcare_hl7_v2_store_iam_policy": ResourceIamPolicyWithImport(IamHealthcareHl7V2StoreSchema, NewHealthcareHl7V2StoreIamUpdater, Hl7V2StoreIdParseFunc), "google_iap_tunnel_instance_iam_binding": ResourceIamBindingWithImport(IamIapTunnelInstanceSchema, NewIapTunnelInstanceIamUpdater, IapTunnelInstanceIdParseFunc), "google_iap_tunnel_instance_iam_member": ResourceIamMemberWithImport(IamIapTunnelInstanceSchema, NewIapTunnelInstanceIamUpdater, IapTunnelInstanceIdParseFunc), "google_iap_tunnel_instance_iam_policy": ResourceIamPolicyWithImport(IamIapTunnelInstanceSchema, NewIapTunnelInstanceIamUpdater, IapTunnelInstanceIdParseFunc), @@ -389,6 +403,7 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { config.StorageBasePath = d.Get(StorageCustomEndpointEntryKey).(string) config.TpuBasePath = d.Get(TpuCustomEndpointEntryKey).(string) + config.HealthcareBasePath = d.Get(HealthcareCustomEndpointEntryKey).(string) config.IAPBasePath = d.Get(IAPCustomEndpointEntryKey).(string) config.CloudBillingBasePath = d.Get(CloudBillingCustomEndpointEntryKey).(string) config.ComposerBasePath = d.Get(ComposerCustomEndpointEntryKey).(string) diff --git a/google-beta/provider_healthcare_gen.go b/google-beta/provider_healthcare_gen.go new file mode 100644 index 00000000000..9da42d501b8 --- /dev/null +++ b/google-beta/provider_healthcare_gen.go @@ -0,0 +1,37 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import "github.com/hashicorp/terraform/helper/schema" + +// If the base path has changed as a result of your PR, make sure to update +// the provider_reference page! +var HealthcareDefaultBasePath = "https://healthcare.googleapis.com/v1beta1/" +var HealthcareCustomEndpointEntryKey = "healthcare_custom_endpoint" +var HealthcareCustomEndpointEntry = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateCustomEndpoint, + DefaultFunc: schema.MultiEnvDefaultFunc([]string{ + "GOOGLE_HEALTHCARE_CUSTOM_ENDPOINT", + }, HealthcareDefaultBasePath), +} + +var GeneratedHealthcareResourcesMap = map[string]*schema.Resource{ + "google_healthcare_dataset": resourceHealthcareDataset(), + "google_healthcare_dicom_store": resourceHealthcareDicomStore(), + "google_healthcare_fhir_store": resourceHealthcareFhirStore(), + "google_healthcare_hl7_v2_store": resourceHealthcareHl7V2Store(), +} diff --git a/google-beta/resource_healthcare_dataset.go b/google-beta/resource_healthcare_dataset.go new file mode 100644 index 00000000000..e9b48d5bf67 --- /dev/null +++ b/google-beta/resource_healthcare_dataset.go @@ -0,0 +1,247 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "log" + "reflect" + "strings" + "time" + + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceHealthcareDataset() *schema.Resource { + return &schema.Resource{ + Create: resourceHealthcareDatasetCreate, + Read: resourceHealthcareDatasetRead, + Update: resourceHealthcareDatasetUpdate, + Delete: resourceHealthcareDatasetDelete, + + Importer: &schema.ResourceImporter{ + State: resourceHealthcareDatasetImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(240 * time.Second), + Update: schema.DefaultTimeout(240 * time.Second), + Delete: schema.DefaultTimeout(240 * time.Second), + }, + + Schema: map[string]*schema.Schema{ + "location": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "time_zone": { + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + "self_link": { + Type: schema.TypeString, + Computed: true, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + } +} + +func resourceHealthcareDatasetCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + nameProp, err := expandHealthcareDatasetName(d.Get("name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("name"); !isEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) { + obj["name"] = nameProp + } + timeZoneProp, err := expandHealthcareDatasetTimeZone(d.Get("time_zone"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("time_zone"); !isEmptyValue(reflect.ValueOf(timeZoneProp)) && (ok || !reflect.DeepEqual(v, timeZoneProp)) { + obj["timeZone"] = timeZoneProp + } + + url, err := replaceVars(d, config, "{{HealthcareBasePath}}projects/{{project}}/locations/{{location}}/datasets?datasetId={{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new Dataset: %#v", obj) + res, err := sendRequestWithTimeout(config, "POST", url, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating Dataset: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "projects/{{project}}/locations/{{location}}/datasets/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating Dataset %q: %#v", d.Id(), res) + + return resourceHealthcareDatasetRead(d, meta) +} + +func resourceHealthcareDatasetRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{HealthcareBasePath}}projects/{{project}}/locations/{{location}}/datasets/{{name}}") + if err != nil { + return err + } + + res, err := sendRequest(config, "GET", url, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("HealthcareDataset %q", d.Id())) + } + + res, err = resourceHealthcareDatasetDecoder(d, meta, res) + if err != nil { + return err + } + + project, err := getProject(d, config) + if err != nil { + return err + } + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading Dataset: %s", err) + } + + if err := d.Set("name", flattenHealthcareDatasetName(res["name"], d)); err != nil { + return fmt.Errorf("Error reading Dataset: %s", err) + } + if err := d.Set("time_zone", flattenHealthcareDatasetTimeZone(res["timeZone"], d)); err != nil { + return fmt.Errorf("Error reading Dataset: %s", err) + } + + return nil +} + +func resourceHealthcareDatasetUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + timeZoneProp, err := expandHealthcareDatasetTimeZone(d.Get("time_zone"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("time_zone"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, timeZoneProp)) { + obj["timeZone"] = timeZoneProp + } + + url, err := replaceVars(d, config, "{{HealthcareBasePath}}projects/{{project}}/locations/{{location}}/datasets/{{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating Dataset %q: %#v", d.Id(), obj) + updateMask := []string{} + + if d.HasChange("time_zone") { + updateMask = append(updateMask, "timeZone") + } + // updateMask is a URL parameter but not present in the schema, so replaceVars + // won't set it + url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) + if err != nil { + return err + } + _, err = sendRequestWithTimeout(config, "PATCH", url, obj, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("Error updating Dataset %q: %s", d.Id(), err) + } + + return resourceHealthcareDatasetRead(d, meta) +} + +func resourceHealthcareDatasetDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{HealthcareBasePath}}projects/{{project}}/locations/{{location}}/datasets/{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting Dataset %q", d.Id()) + res, err := sendRequestWithTimeout(config, "DELETE", url, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "Dataset") + } + + log.Printf("[DEBUG] Finished deleting Dataset %q: %#v", d.Id(), res) + return nil +} + +func resourceHealthcareDatasetImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{"projects/(?P[^/]+)/locations/(?P[^/]+)/datasets/(?P[^/]+)", "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", "(?P[^/]+)/(?P[^/]+)"}, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "projects/{{project}}/locations/{{location}}/datasets/{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenHealthcareDatasetName(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenHealthcareDatasetTimeZone(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func expandHealthcareDatasetName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandHealthcareDatasetTimeZone(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func resourceHealthcareDatasetDecoder(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) { + // Take the returned long form of the name and use it as `self_link`. + // Then modify the name to be the user specified form. + // We can't just ignore_read on `name` as the linter will + // complain that the returned `res` is never used afterwards. + // Some field needs to be actually set, and we chose `name`. + d.Set("self_link", res["name"].(string)) + res["name"] = d.Get("name").(string) + return res, nil +} diff --git a/google-beta/resource_healthcare_dataset_iam_test.go b/google-beta/resource_healthcare_dataset_iam_test.go new file mode 100644 index 00000000000..a8629cf386b --- /dev/null +++ b/google-beta/resource_healthcare_dataset_iam_test.go @@ -0,0 +1,254 @@ +package google + +import ( + "fmt" + "reflect" + "sort" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +const DEFAULT_HEALTHCARE_TEST_LOCATION = "us-central1" + +func TestAccHealthcareDatasetIamBinding(t *testing.T) { + t.Parallel() + + projectId := getTestProjectFromEnv() + account := acctest.RandomWithPrefix("tf-test") + roleId := "roles/healthcare.datasetAdmin" + datasetName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + datasetId := &healthcareDatasetId{ + Project: projectId, + Location: DEFAULT_HEALTHCARE_TEST_LOCATION, + Name: datasetName, + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + // Test Iam Binding creation + Config: testAccHealthcareDatasetIamBinding_basic(account, datasetName, roleId), + Check: testAccCheckGoogleHealthcareDatasetIam(datasetId.datasetId(), roleId, []string{ + fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, projectId), + }), + }, + { + ResourceName: "google_healthcare_dataset_iam_binding.foo", + ImportStateId: fmt.Sprintf("%s %s", datasetId.terraformId(), roleId), + ImportState: true, + ImportStateVerify: true, + }, + { + // Test Iam Binding update + Config: testAccHealthcareDatasetIamBinding_update(account, datasetName, roleId), + Check: testAccCheckGoogleHealthcareDatasetIam(datasetId.datasetId(), roleId, []string{ + fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, projectId), + fmt.Sprintf("serviceAccount:%s-2@%s.iam.gserviceaccount.com", account, projectId), + }), + }, + { + ResourceName: "google_healthcare_dataset_iam_binding.foo", + ImportStateId: fmt.Sprintf("%s %s", datasetId.terraformId(), roleId), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccHealthcareDatasetIamMember(t *testing.T) { + t.Parallel() + + projectId := getTestProjectFromEnv() + account := acctest.RandomWithPrefix("tf-test") + roleId := "roles/healthcare.datasetViewer" + datasetName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + datasetId := &healthcareDatasetId{ + Project: projectId, + Location: DEFAULT_HEALTHCARE_TEST_LOCATION, + Name: datasetName, + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + // Test Iam Member creation (no update for member, no need to test) + Config: testAccHealthcareDatasetIamMember_basic(account, datasetName, roleId), + Check: testAccCheckGoogleHealthcareDatasetIam(datasetId.datasetId(), roleId, []string{ + fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, projectId), + }), + }, + { + ResourceName: "google_healthcare_dataset_iam_member.foo", + ImportStateId: fmt.Sprintf("%s %s serviceAccount:%s@%s.iam.gserviceaccount.com", datasetId.terraformId(), roleId, account, projectId), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccHealthcareDatasetIamPolicy(t *testing.T) { + t.Parallel() + + projectId := getTestProjectFromEnv() + account := acctest.RandomWithPrefix("tf-test") + roleId := "roles/healthcare.datasetAdmin" + datasetName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + datasetId := &healthcareDatasetId{ + Project: projectId, + Location: DEFAULT_HEALTHCARE_TEST_LOCATION, + Name: datasetName, + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccHealthcareDatasetIamPolicy_basic(account, datasetName, roleId), + Check: testAccCheckGoogleHealthcareDatasetIam(datasetId.datasetId(), roleId, []string{ + fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, projectId), + }), + }, + { + ResourceName: "google_healthcare_dataset_iam_policy.foo", + ImportStateId: datasetId.terraformId(), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckGoogleHealthcareDatasetIam(datasetId, role string, members []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + p, err := config.clientHealthcare.Projects.Locations.Datasets.GetIamPolicy(datasetId).Do() + if err != nil { + return err + } + + for _, binding := range p.Bindings { + if binding.Role == role { + sort.Strings(members) + sort.Strings(binding.Members) + + if reflect.DeepEqual(members, binding.Members) { + return nil + } + + return fmt.Errorf("Binding found but expected members is %v, got %v", members, binding.Members) + } + } + + return fmt.Errorf("No binding for role %q", role) + } +} + +// We are using a custom role since iam_binding is authoritative on the member list and +// we want to avoid removing members from an existing role to prevent unwanted side effects. +func testAccHealthcareDatasetIamBinding_basic(account, datasetName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_healthcare_dataset" "dataset" { + location = "us-central1" + name = "%s" +} + +resource "google_healthcare_dataset_iam_binding" "foo" { + dataset_id = "${google_healthcare_dataset.dataset.id}" + role = "%s" + members = ["serviceAccount:${google_service_account.test_account.email}"] +} +`, account, datasetName, roleId) +} + +func testAccHealthcareDatasetIamBinding_update(account, datasetName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_service_account" "test_account_2" { + account_id = "%s-2" + display_name = "Iam Testing Account" +} + +resource "google_healthcare_dataset" "dataset" { + location = "%s" + name = "%s" +} + +resource "google_healthcare_dataset_iam_binding" "foo" { + dataset_id = "${google_healthcare_dataset.dataset.id}" + role = "%s" + members = [ + "serviceAccount:${google_service_account.test_account.email}", + "serviceAccount:${google_service_account.test_account_2.email}" + ] +} +`, account, account, DEFAULT_HEALTHCARE_TEST_LOCATION, datasetName, roleId) +} + +func testAccHealthcareDatasetIamMember_basic(account, datasetName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_healthcare_dataset" "dataset" { + location = "%s" + name = "%s" +} + +resource "google_healthcare_dataset_iam_member" "foo" { + dataset_id = "${google_healthcare_dataset.dataset.id}" + role = "%s" + member = "serviceAccount:${google_service_account.test_account.email}" +} +`, account, DEFAULT_HEALTHCARE_TEST_LOCATION, datasetName, roleId) +} + +func testAccHealthcareDatasetIamPolicy_basic(account, datasetName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_healthcare_dataset" "dataset" { + location = "%s" + name = "%s" +} + +data "google_iam_policy" "foo" { + binding { + role = "%s" + + members = ["serviceAccount:${google_service_account.test_account.email}"] + } +} + +resource "google_healthcare_dataset_iam_policy" "foo" { + dataset_id = "${google_healthcare_dataset.dataset.id}" + policy_data = "${data.google_iam_policy.foo.policy_data}" +} +`, account, DEFAULT_HEALTHCARE_TEST_LOCATION, datasetName, roleId) +} diff --git a/google-beta/resource_healthcare_dataset_test.go b/google-beta/resource_healthcare_dataset_test.go new file mode 100644 index 00000000000..53a1253aba8 --- /dev/null +++ b/google-beta/resource_healthcare_dataset_test.go @@ -0,0 +1,179 @@ +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccHealthcareDatasetIdParsing(t *testing.T) { + t.Parallel() + + cases := map[string]struct { + ImportId string + ExpectedError bool + ExpectedTerraformId string + ExpectedDatasetId string + Config *Config + }{ + "id is in project/location/datasetName format": { + ImportId: "test-project/us-central1/test-dataset", + ExpectedError: false, + ExpectedTerraformId: "test-project/us-central1/test-dataset", + ExpectedDatasetId: "projects/test-project/locations/us-central1/datasets/test-dataset", + }, + "id is in domain:project/location/datasetName format": { + ImportId: "example.com:test-project/us-central1/test-dataset", + ExpectedError: false, + ExpectedTerraformId: "example.com:test-project/us-central1/test-dataset", + ExpectedDatasetId: "projects/example.com:test-project/locations/us-central1/datasets/test-dataset", + }, + "id is in location/datasetName format": { + ImportId: "us-central1/test-dataset", + ExpectedError: false, + ExpectedTerraformId: "test-project/us-central1/test-dataset", + ExpectedDatasetId: "projects/test-project/locations/us-central1/datasets/test-dataset", + Config: &Config{Project: "test-project"}, + }, + "id is in location/datasetName format without project in config": { + ImportId: "us-central1/test-dataset", + ExpectedError: true, + Config: &Config{Project: ""}, + }, + } + + for tn, tc := range cases { + datasetId, err := parseHealthcareDatasetId(tc.ImportId, tc.Config) + + if tc.ExpectedError && err == nil { + t.Fatalf("bad: %s, expected an error", tn) + } + + if err != nil { + if tc.ExpectedError { + continue + } + t.Fatalf("bad: %s, err: %#v", tn, err) + } + + if datasetId.terraformId() != tc.ExpectedTerraformId { + t.Fatalf("bad: %s, expected Terraform ID to be `%s` but is `%s`", tn, tc.ExpectedTerraformId, datasetId.terraformId()) + } + + if datasetId.datasetId() != tc.ExpectedDatasetId { + t.Fatalf("bad: %s, expected Dataset ID to be `%s` but is `%s`", tn, tc.ExpectedDatasetId, datasetId.datasetId()) + } + } +} + +func TestAccHealthcareDataset_basic(t *testing.T) { + t.Parallel() + + location := "us-central1" + datasetName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + timeZone := "America/New_York" + resourceName := "google_healthcare_dataset.dataset" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckHealthcareDatasetDestroy, + Steps: []resource.TestStep{ + { + Config: testGoogleHealthcareDataset_basic(datasetName, location), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testGoogleHealthcareDataset_update(datasetName, location, timeZone), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleHealthcareDatasetUpdate(timeZone), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckHealthcareDatasetDestroy(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_healthcare_dataset" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := testAccProvider.Meta().(*Config) + + url, err := replaceVarsForTest(config, rs, "{{HealthcareBasePath}}projects/{{project}}/locations/{{location}}/datasets/{{name}}") + if err != nil { + return err + } + + _, err = sendRequest(config, "GET", url, nil) + if err == nil { + return fmt.Errorf("HealthcareDataset still exists at %s", url) + } + } + + return nil +} + +func testAccCheckGoogleHealthcareDatasetUpdate(timeZone string) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_healthcare_dataset" { + continue + } + + config := testAccProvider.Meta().(*Config) + + gcpResourceUri, err := replaceVarsForTest(config, rs, "projects/{{project}}/locations/{{location}}/datasets/{{name}}") + if err != nil { + return err + } + + response, err := config.clientHealthcare.Projects.Locations.Datasets.Get(gcpResourceUri).Do() + if err != nil { + return fmt.Errorf("Unexpected failure while verifying 'updated' dataset: %s", err) + } + + if response.TimeZone != timeZone { + return fmt.Errorf("Dataset timeZone was not set to '%s' as expected: %s", timeZone, gcpResourceUri) + } + } + + return nil + } +} + +func testGoogleHealthcareDataset_basic(datasetName, location string) string { + return fmt.Sprintf(` +resource "google_healthcare_dataset" "dataset" { + name = "%s" + location = "%s" +} + `, datasetName, location) +} + +func testGoogleHealthcareDataset_update(datasetName, location, timeZone string) string { + return fmt.Sprintf(` +resource "google_healthcare_dataset" "dataset" { + name = "%s" + location = "%s" + time_zone = "%s" +} + `, datasetName, location, timeZone) +} diff --git a/google-beta/resource_healthcare_dicom_store.go b/google-beta/resource_healthcare_dicom_store.go new file mode 100644 index 00000000000..3e94b3d7c01 --- /dev/null +++ b/google-beta/resource_healthcare_dicom_store.go @@ -0,0 +1,312 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "log" + "reflect" + "strings" + "time" + + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceHealthcareDicomStore() *schema.Resource { + return &schema.Resource{ + Create: resourceHealthcareDicomStoreCreate, + Read: resourceHealthcareDicomStoreRead, + Update: resourceHealthcareDicomStoreUpdate, + Delete: resourceHealthcareDicomStoreDelete, + + Importer: &schema.ResourceImporter{ + State: resourceHealthcareDicomStoreImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(240 * time.Second), + Update: schema.DefaultTimeout(240 * time.Second), + Delete: schema.DefaultTimeout(240 * time.Second), + }, + + Schema: map[string]*schema.Schema{ + "dataset": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: compareSelfLinkOrResourceName, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "notification_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "pubsub_topic": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "self_link": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceHealthcareDicomStoreCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + nameProp, err := expandHealthcareDicomStoreName(d.Get("name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("name"); !isEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) { + obj["name"] = nameProp + } + labelsProp, err := expandHealthcareDicomStoreLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(labelsProp)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + notificationConfigProp, err := expandHealthcareDicomStoreNotificationConfig(d.Get("notification_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("notification_config"); !isEmptyValue(reflect.ValueOf(notificationConfigProp)) && (ok || !reflect.DeepEqual(v, notificationConfigProp)) { + obj["notificationConfig"] = notificationConfigProp + } + + url, err := replaceVars(d, config, "{{HealthcareBasePath}}{{dataset}}/dicomStores?dicomStoreId={{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new DicomStore: %#v", obj) + res, err := sendRequestWithTimeout(config, "POST", url, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating DicomStore: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "{{dataset}}/dicomStores/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating DicomStore %q: %#v", d.Id(), res) + + return resourceHealthcareDicomStoreRead(d, meta) +} + +func resourceHealthcareDicomStoreRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{HealthcareBasePath}}{{dataset}}/dicomStores/{{name}}") + if err != nil { + return err + } + + res, err := sendRequest(config, "GET", url, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("HealthcareDicomStore %q", d.Id())) + } + + res, err = resourceHealthcareDicomStoreDecoder(d, meta, res) + if err != nil { + return err + } + + if err := d.Set("name", flattenHealthcareDicomStoreName(res["name"], d)); err != nil { + return fmt.Errorf("Error reading DicomStore: %s", err) + } + if err := d.Set("labels", flattenHealthcareDicomStoreLabels(res["labels"], d)); err != nil { + return fmt.Errorf("Error reading DicomStore: %s", err) + } + if err := d.Set("notification_config", flattenHealthcareDicomStoreNotificationConfig(res["notificationConfig"], d)); err != nil { + return fmt.Errorf("Error reading DicomStore: %s", err) + } + + return nil +} + +func resourceHealthcareDicomStoreUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + labelsProp, err := expandHealthcareDicomStoreLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + notificationConfigProp, err := expandHealthcareDicomStoreNotificationConfig(d.Get("notification_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("notification_config"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, notificationConfigProp)) { + obj["notificationConfig"] = notificationConfigProp + } + + url, err := replaceVars(d, config, "{{HealthcareBasePath}}{{dataset}}/dicomStores/{{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating DicomStore %q: %#v", d.Id(), obj) + updateMask := []string{} + + if d.HasChange("labels") { + updateMask = append(updateMask, "labels") + } + + if d.HasChange("notification_config") { + updateMask = append(updateMask, "notificationConfig") + } + // updateMask is a URL parameter but not present in the schema, so replaceVars + // won't set it + url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) + if err != nil { + return err + } + _, err = sendRequestWithTimeout(config, "PATCH", url, obj, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("Error updating DicomStore %q: %s", d.Id(), err) + } + + return resourceHealthcareDicomStoreRead(d, meta) +} + +func resourceHealthcareDicomStoreDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{HealthcareBasePath}}{{dataset}}/dicomStores/{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting DicomStore %q", d.Id()) + res, err := sendRequestWithTimeout(config, "DELETE", url, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "DicomStore") + } + + log.Printf("[DEBUG] Finished deleting DicomStore %q: %#v", d.Id(), res) + return nil +} + +func resourceHealthcareDicomStoreImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + + config := meta.(*Config) + + dicomStoreId, err := parseHealthcareDicomStoreId(d.Id(), config) + if err != nil { + return nil, err + } + + d.Set("dataset", dicomStoreId.DatasetId.datasetId()) + d.Set("name", dicomStoreId.Name) + + return []*schema.ResourceData{d}, nil +} + +func flattenHealthcareDicomStoreName(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenHealthcareDicomStoreLabels(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenHealthcareDicomStoreNotificationConfig(v interface{}, d *schema.ResourceData) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["pubsub_topic"] = + flattenHealthcareDicomStoreNotificationConfigPubsubTopic(original["pubsubTopic"], d) + return []interface{}{transformed} +} +func flattenHealthcareDicomStoreNotificationConfigPubsubTopic(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func expandHealthcareDicomStoreName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandHealthcareDicomStoreLabels(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) { + if v == nil { + return map[string]string{}, nil + } + m := make(map[string]string) + for k, val := range v.(map[string]interface{}) { + m[k] = val.(string) + } + return m, nil +} + +func expandHealthcareDicomStoreNotificationConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedPubsubTopic, err := expandHealthcareDicomStoreNotificationConfigPubsubTopic(original["pubsub_topic"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPubsubTopic); val.IsValid() && !isEmptyValue(val) { + transformed["pubsubTopic"] = transformedPubsubTopic + } + + return transformed, nil +} + +func expandHealthcareDicomStoreNotificationConfigPubsubTopic(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func resourceHealthcareDicomStoreDecoder(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) { + // Take the returned long form of the name and use it as `self_link`. + // Then modify the name to be the user specified form. + // We can't just ignore_read on `name` as the linter will + // complain that the returned `res` is never used afterwards. + // Some field needs to be actually set, and we chose `name`. + d.Set("self_link", res["name"].(string)) + res["name"] = d.Get("name").(string) + return res, nil +} diff --git a/google-beta/resource_healthcare_dicom_store_iam_test.go b/google-beta/resource_healthcare_dicom_store_iam_test.go new file mode 100644 index 00000000000..0b3c0e9f939 --- /dev/null +++ b/google-beta/resource_healthcare_dicom_store_iam_test.go @@ -0,0 +1,352 @@ +package google + +import ( + "fmt" + "reflect" + "sort" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccHealthcareDicomStoreIamBinding(t *testing.T) { + t.Parallel() + + projectId := getTestProjectFromEnv() + account := acctest.RandomWithPrefix("tf-test") + roleId := "roles/healthcare.dicomStoreAdmin" + datasetName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + datasetId := &healthcareDatasetId{ + Project: projectId, + Location: DEFAULT_HEALTHCARE_TEST_LOCATION, + Name: datasetName, + } + dicomStoreName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + // Test Iam Binding creation + Config: testAccHealthcareDicomStoreIamBinding_basic(account, datasetName, dicomStoreName, roleId), + Check: testAccCheckGoogleHealthcareDicomStoreIamBindingExists("foo", roleId, []string{ + fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, projectId), + }), + }, + { + ResourceName: "google_healthcare_dicom_store_iam_binding.foo", + ImportStateId: fmt.Sprintf("%s/%s %s", datasetId.terraformId(), dicomStoreName, roleId), + ImportState: true, + ImportStateVerify: true, + }, + { + // Test Iam Binding update + Config: testAccHealthcareDicomStoreIamBinding_update(account, datasetName, dicomStoreName, roleId), + Check: testAccCheckGoogleHealthcareDicomStoreIamBindingExists("foo", roleId, []string{ + fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, projectId), + fmt.Sprintf("serviceAccount:%s-2@%s.iam.gserviceaccount.com", account, projectId), + }), + }, + { + ResourceName: "google_healthcare_dicom_store_iam_binding.foo", + ImportStateId: fmt.Sprintf("%s/%s %s", datasetId.terraformId(), dicomStoreName, roleId), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccHealthcareDicomStoreIamMember(t *testing.T) { + t.Parallel() + + projectId := getTestProjectFromEnv() + account := acctest.RandomWithPrefix("tf-test") + roleId := "roles/healthcare.dicomEditor" + datasetName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + datasetId := &healthcareDatasetId{ + Project: projectId, + Location: DEFAULT_HEALTHCARE_TEST_LOCATION, + Name: datasetName, + } + dicomStoreName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + // Test Iam Member creation (no update for member, no need to test) + Config: testAccHealthcareDicomStoreIamMember_basic(account, datasetName, dicomStoreName, roleId), + Check: testAccCheckGoogleHealthcareDicomStoreIamMemberExists("foo", roleId, + fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, projectId), + ), + }, + { + ResourceName: "google_healthcare_dicom_store_iam_member.foo", + ImportStateId: fmt.Sprintf("%s/%s %s serviceAccount:%s@%s.iam.gserviceaccount.com", datasetId.terraformId(), dicomStoreName, roleId, account, projectId), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccHealthcareDicomStoreIamPolicy(t *testing.T) { + t.Parallel() + + projectId := getTestProjectFromEnv() + account := acctest.RandomWithPrefix("tf-test") + roleId := "roles/healthcare.dicomViewer" + datasetName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + datasetId := &healthcareDatasetId{ + Project: projectId, + Location: DEFAULT_HEALTHCARE_TEST_LOCATION, + Name: datasetName, + } + dicomStoreName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + // Test Iam Policy creation (no update for policy, no need to test) + Config: testAccHealthcareDicomStoreIamPolicy_basic(account, datasetName, dicomStoreName, roleId), + Check: testAccCheckGoogleHealthcareDicomStoreIamPolicyExists("foo", roleId, + fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, projectId), + ), + }, + { + ResourceName: "google_healthcare_dicom_store_iam_policy.foo", + ImportStateId: fmt.Sprintf("%s/%s", datasetId.terraformId(), dicomStoreName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckGoogleHealthcareDicomStoreIamBindingExists(bindingResourceName, roleId string, members []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + bindingRs, ok := s.RootModule().Resources[fmt.Sprintf("google_healthcare_dicom_store_iam_binding.%s", bindingResourceName)] + if !ok { + return fmt.Errorf("Not found: %s", bindingResourceName) + } + + config := testAccProvider.Meta().(*Config) + dicomStoreId, err := parseHealthcareDicomStoreId(bindingRs.Primary.Attributes["dicom_store_id"], config) + + if err != nil { + return err + } + + p, err := config.clientHealthcare.Projects.Locations.Datasets.DicomStores.GetIamPolicy(dicomStoreId.dicomStoreId()).Do() + if err != nil { + return err + } + + for _, binding := range p.Bindings { + if binding.Role == roleId { + sort.Strings(members) + sort.Strings(binding.Members) + + if reflect.DeepEqual(members, binding.Members) { + return nil + } + + return fmt.Errorf("Binding found but expected members is %v, got %v", members, binding.Members) + } + } + + return fmt.Errorf("No binding for role %q", roleId) + } +} + +func testAccCheckGoogleHealthcareDicomStoreIamMemberExists(n, role, member string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources["google_healthcare_dicom_store_iam_member."+n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + config := testAccProvider.Meta().(*Config) + dicomStoreId, err := parseHealthcareDicomStoreId(rs.Primary.Attributes["dicom_store_id"], config) + + if err != nil { + return err + } + + p, err := config.clientHealthcare.Projects.Locations.Datasets.DicomStores.GetIamPolicy(dicomStoreId.dicomStoreId()).Do() + if err != nil { + return err + } + + for _, binding := range p.Bindings { + if binding.Role == role { + for _, m := range binding.Members { + if m == member { + return nil + } + } + + return fmt.Errorf("Missing member %q, got %v", member, binding.Members) + } + } + + return fmt.Errorf("No binding for role %q", role) + } +} + +func testAccCheckGoogleHealthcareDicomStoreIamPolicyExists(n, role, policy string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources["google_healthcare_dicom_store_iam_policy."+n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + config := testAccProvider.Meta().(*Config) + dicomStoreId, err := parseHealthcareDicomStoreId(rs.Primary.Attributes["dicom_store_id"], config) + + if err != nil { + return err + } + + p, err := config.clientHealthcare.Projects.Locations.Datasets.DicomStores.GetIamPolicy(dicomStoreId.dicomStoreId()).Do() + if err != nil { + return err + } + + for _, binding := range p.Bindings { + if binding.Role == role { + for _, m := range binding.Members { + if m == policy { + return nil + } + } + + return fmt.Errorf("Missing policy %q, got %v", policy, binding.Members) + } + } + + return fmt.Errorf("No binding for role %q", role) + } +} + +// We are using a custom role since iam_binding is authoritative on the member list and +// we want to avoid removing members from an existing role to prevent unwanted side effects. +func testAccHealthcareDicomStoreIamBinding_basic(account, datasetName, dicomStoreName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_healthcare_dataset" "dataset" { + location = "us-central1" + name = "%s" +} + +resource "google_healthcare_dicom_store" "dicom_store" { + dataset = "${google_healthcare_dataset.dataset.id}" + name = "%s" +} + +resource "google_healthcare_dicom_store_iam_binding" "foo" { + dicom_store_id = "${google_healthcare_dicom_store.dicom_store.id}" + role = "%s" + members = ["serviceAccount:${google_service_account.test_account.email}"] +} +`, account, datasetName, dicomStoreName, roleId) +} + +func testAccHealthcareDicomStoreIamBinding_update(account, datasetName, dicomStoreName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_service_account" "test_account_2" { + account_id = "%s-2" + display_name = "Iam Testing Account" +} + +resource "google_healthcare_dataset" "dataset" { + location = "us-central1" + name = "%s" +} + +resource "google_healthcare_dicom_store" "dicom_store" { + dataset = "${google_healthcare_dataset.dataset.id}" + name = "%s" +} + +resource "google_healthcare_dicom_store_iam_binding" "foo" { + dicom_store_id = "${google_healthcare_dicom_store.dicom_store.id}" + role = "%s" + members = [ + "serviceAccount:${google_service_account.test_account.email}", + "serviceAccount:${google_service_account.test_account_2.email}" + ] +} +`, account, account, datasetName, dicomStoreName, roleId) +} + +func testAccHealthcareDicomStoreIamMember_basic(account, datasetName, dicomStoreName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_healthcare_dataset" "dataset" { + location = "us-central1" + name = "%s" +} +resource "google_healthcare_dicom_store" "dicom_store" { + dataset = "${google_healthcare_dataset.dataset.id}" + name = "%s" +} + +resource "google_healthcare_dicom_store_iam_member" "foo" { + dicom_store_id = "${google_healthcare_dicom_store.dicom_store.id}" + role = "%s" + member = "serviceAccount:${google_service_account.test_account.email}" +} +`, account, datasetName, dicomStoreName, roleId) +} + +func testAccHealthcareDicomStoreIamPolicy_basic(account, datasetName, dicomStoreName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_healthcare_dataset" "dataset" { + location = "us-central1" + name = "%s" +} +resource "google_healthcare_dicom_store" "dicom_store" { + dataset = "${google_healthcare_dataset.dataset.id}" + name = "%s" +} + +data "google_iam_policy" "foo" { + binding { + role = "%s" + + members = ["serviceAccount:${google_service_account.test_account.email}"] + } +} + +resource "google_healthcare_dicom_store_iam_policy" "foo" { + dicom_store_id = "${google_healthcare_dicom_store.dicom_store.id}" + policy_data = "${data.google_iam_policy.foo.policy_data}" +} +`, account, datasetName, dicomStoreName, roleId) +} diff --git a/google-beta/resource_healthcare_dicom_store_test.go b/google-beta/resource_healthcare_dicom_store_test.go new file mode 100644 index 00000000000..efcdd9cca77 --- /dev/null +++ b/google-beta/resource_healthcare_dicom_store_test.go @@ -0,0 +1,212 @@ +package google + +import ( + "fmt" + "path" + "strings" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccHealthcareDicomStoreIdParsing(t *testing.T) { + t.Parallel() + + cases := map[string]struct { + ImportId string + ExpectedError bool + ExpectedTerraformId string + ExpectedDicomStoreId string + Config *Config + }{ + "id is in project/location/datasetName/dicomStoreName format": { + ImportId: "test-project/us-central1/test-dataset/test-store-name", + ExpectedError: false, + ExpectedTerraformId: "test-project/us-central1/test-dataset/test-store-name", + ExpectedDicomStoreId: "projects/test-project/locations/us-central1/datasets/test-dataset/dicomStores/test-store-name", + }, + "id is in domain:project/location/datasetName/dicomStoreName format": { + ImportId: "example.com:test-project/us-central1/test-dataset/test-store-name", + ExpectedError: false, + ExpectedTerraformId: "example.com:test-project/us-central1/test-dataset/test-store-name", + ExpectedDicomStoreId: "projects/example.com:test-project/locations/us-central1/datasets/test-dataset/dicomStores/test-store-name", + }, + "id is in location/datasetName/dicomStoreName format": { + ImportId: "us-central1/test-dataset/test-store-name", + ExpectedError: false, + ExpectedTerraformId: "test-project/us-central1/test-dataset/test-store-name", + ExpectedDicomStoreId: "projects/test-project/locations/us-central1/datasets/test-dataset/dicomStores/test-store-name", + Config: &Config{Project: "test-project"}, + }, + "id is in location/datasetName/dicomStoreName format without project in config": { + ImportId: "us-central1/test-dataset/test-store-name", + ExpectedError: true, + Config: &Config{Project: ""}, + }, + } + + for tn, tc := range cases { + dicomStoreId, err := parseHealthcareDicomStoreId(tc.ImportId, tc.Config) + + if tc.ExpectedError && err == nil { + t.Fatalf("bad: %s, expected an error", tn) + } + + if err != nil { + if tc.ExpectedError { + continue + } + t.Fatalf("bad: %s, err: %#v", tn, err) + } + + if dicomStoreId.terraformId() != tc.ExpectedTerraformId { + t.Fatalf("bad: %s, expected Terraform ID to be `%s` but is `%s`", tn, tc.ExpectedTerraformId, dicomStoreId.terraformId()) + } + + if dicomStoreId.dicomStoreId() != tc.ExpectedDicomStoreId { + t.Fatalf("bad: %s, expected DicomStore ID to be `%s` but is `%s`", tn, tc.ExpectedDicomStoreId, dicomStoreId.dicomStoreId()) + } + } +} + +func TestAccHealthcareDicomStore_basic(t *testing.T) { + t.Parallel() + + datasetName := fmt.Sprintf("tf-test-dataset-%s", acctest.RandString(10)) + dicomStoreName := fmt.Sprintf("tf-test-dicom-store-%s", acctest.RandString(10)) + pubsubTopic := fmt.Sprintf("tf-test-topic-%s", acctest.RandString(10)) + resourceName := "google_healthcare_dicom_store.default" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckHealthcareDicomStoreDestroy, + Steps: []resource.TestStep{ + { + Config: testGoogleHealthcareDicomStore_basic(dicomStoreName, datasetName), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testGoogleHealthcareDicomStore_update(dicomStoreName, datasetName, pubsubTopic), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleHealthcareDicomStoreUpdate(pubsubTopic), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testGoogleHealthcareDicomStore_basic(dicomStoreName, datasetName string) string { + return fmt.Sprintf(` +resource "google_healthcare_dicom_store" "default" { + name = "%s" + dataset = "${google_healthcare_dataset.dataset.id}" +} + +resource "google_healthcare_dataset" "dataset" { + name = "%s" + location = "us-central1" +} +`, dicomStoreName, datasetName) +} + +func testGoogleHealthcareDicomStore_update(dicomStoreName, datasetName, pubsubTopic string) string { + return fmt.Sprintf(` +resource "google_healthcare_dicom_store" "default" { + name = "%s" + dataset = "${google_healthcare_dataset.dataset.id}" + + notification_config { + pubsub_topic = "${google_pubsub_topic.topic.id}" + } + + labels = { + label1 = "labelvalue1" + } +} + +resource "google_healthcare_dataset" "dataset" { + name = "%s" + location = "us-central1" +} + +resource "google_pubsub_topic" "topic" { + name = "%s" +} + +`, dicomStoreName, datasetName, pubsubTopic) +} + +func testAccCheckHealthcareDicomStoreDestroy(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_healthcare_dicom_store" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := testAccProvider.Meta().(*Config) + + url, err := replaceVarsForTest(config, rs, "{{HealthcareBasePath}}{{dataset}}/dicomStores/{{name}}") + if err != nil { + return err + } + + _, err = sendRequest(config, "GET", url, nil) + if err == nil { + return fmt.Errorf("HealthcareDicomStore still exists at %s", url) + } + } + + return nil +} + +func testAccCheckGoogleHealthcareDicomStoreUpdate(pubsubTopic string) resource.TestCheckFunc { + return func(s *terraform.State) error { + var foundResource = false + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_healthcare_dicom_store" { + continue + } + foundResource = true + + config := testAccProvider.Meta().(*Config) + + gcpResourceUri, err := replaceVarsForTest(config, rs, "{{dataset}}/dicomStores/{{name}}") + if err != nil { + return err + } + + response, err := config.clientHealthcare.Projects.Locations.Datasets.DicomStores.Get(gcpResourceUri).Do() + if err != nil { + return fmt.Errorf("Unexpected failure while verifying 'updated' dataset: %s", err) + } + + if len(response.Labels) == 0 || response.Labels["label1"] != "labelvalue1" { + return fmt.Errorf("dicomStore labels not updated: %s", gcpResourceUri) + } + + topicName := path.Base(response.NotificationConfig.PubsubTopic) + if topicName != pubsubTopic { + return fmt.Errorf("dicomStore 'NotificationConfig' not updated ('%s' != '%s'): %s", topicName, pubsubTopic, gcpResourceUri) + } + } + + if !foundResource { + return fmt.Errorf("google_healthcare_dicom_store resource was missing") + } + return nil + } +} diff --git a/google-beta/resource_healthcare_fhir_store.go b/google-beta/resource_healthcare_fhir_store.go new file mode 100644 index 00000000000..6d3341ba554 --- /dev/null +++ b/google-beta/resource_healthcare_fhir_store.go @@ -0,0 +1,409 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "log" + "reflect" + "strings" + "time" + + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceHealthcareFhirStore() *schema.Resource { + return &schema.Resource{ + Create: resourceHealthcareFhirStoreCreate, + Read: resourceHealthcareFhirStoreRead, + Update: resourceHealthcareFhirStoreUpdate, + Delete: resourceHealthcareFhirStoreDelete, + + Importer: &schema.ResourceImporter{ + State: resourceHealthcareFhirStoreImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(240 * time.Second), + Update: schema.DefaultTimeout(240 * time.Second), + Delete: schema.DefaultTimeout(240 * time.Second), + }, + + Schema: map[string]*schema.Schema{ + "dataset": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: compareSelfLinkOrResourceName, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "disable_referential_integrity": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "disable_resource_versioning": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "enable_history_import": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "enable_update_create": { + Type: schema.TypeBool, + Optional: true, + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "notification_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "pubsub_topic": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "self_link": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceHealthcareFhirStoreCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + nameProp, err := expandHealthcareFhirStoreName(d.Get("name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("name"); !isEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) { + obj["name"] = nameProp + } + enableUpdateCreateProp, err := expandHealthcareFhirStoreEnableUpdateCreate(d.Get("enable_update_create"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("enable_update_create"); !isEmptyValue(reflect.ValueOf(enableUpdateCreateProp)) && (ok || !reflect.DeepEqual(v, enableUpdateCreateProp)) { + obj["enableUpdateCreate"] = enableUpdateCreateProp + } + disableReferentialIntegrityProp, err := expandHealthcareFhirStoreDisableReferentialIntegrity(d.Get("disable_referential_integrity"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("disable_referential_integrity"); !isEmptyValue(reflect.ValueOf(disableReferentialIntegrityProp)) && (ok || !reflect.DeepEqual(v, disableReferentialIntegrityProp)) { + obj["disableReferentialIntegrity"] = disableReferentialIntegrityProp + } + disableResourceVersioningProp, err := expandHealthcareFhirStoreDisableResourceVersioning(d.Get("disable_resource_versioning"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("disable_resource_versioning"); !isEmptyValue(reflect.ValueOf(disableResourceVersioningProp)) && (ok || !reflect.DeepEqual(v, disableResourceVersioningProp)) { + obj["disableResourceVersioning"] = disableResourceVersioningProp + } + enableHistoryImportProp, err := expandHealthcareFhirStoreEnableHistoryImport(d.Get("enable_history_import"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("enable_history_import"); !isEmptyValue(reflect.ValueOf(enableHistoryImportProp)) && (ok || !reflect.DeepEqual(v, enableHistoryImportProp)) { + obj["enableHistoryImport"] = enableHistoryImportProp + } + labelsProp, err := expandHealthcareFhirStoreLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(labelsProp)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + notificationConfigProp, err := expandHealthcareFhirStoreNotificationConfig(d.Get("notification_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("notification_config"); !isEmptyValue(reflect.ValueOf(notificationConfigProp)) && (ok || !reflect.DeepEqual(v, notificationConfigProp)) { + obj["notificationConfig"] = notificationConfigProp + } + + url, err := replaceVars(d, config, "{{HealthcareBasePath}}{{dataset}}/fhirStores?fhirStoreId={{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new FhirStore: %#v", obj) + res, err := sendRequestWithTimeout(config, "POST", url, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating FhirStore: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "{{dataset}}/fhirStores/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating FhirStore %q: %#v", d.Id(), res) + + return resourceHealthcareFhirStoreRead(d, meta) +} + +func resourceHealthcareFhirStoreRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{HealthcareBasePath}}{{dataset}}/fhirStores/{{name}}") + if err != nil { + return err + } + + res, err := sendRequest(config, "GET", url, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("HealthcareFhirStore %q", d.Id())) + } + + res, err = resourceHealthcareFhirStoreDecoder(d, meta, res) + if err != nil { + return err + } + + if err := d.Set("name", flattenHealthcareFhirStoreName(res["name"], d)); err != nil { + return fmt.Errorf("Error reading FhirStore: %s", err) + } + if err := d.Set("enable_update_create", flattenHealthcareFhirStoreEnableUpdateCreate(res["enableUpdateCreate"], d)); err != nil { + return fmt.Errorf("Error reading FhirStore: %s", err) + } + if err := d.Set("disable_referential_integrity", flattenHealthcareFhirStoreDisableReferentialIntegrity(res["disableReferentialIntegrity"], d)); err != nil { + return fmt.Errorf("Error reading FhirStore: %s", err) + } + if err := d.Set("disable_resource_versioning", flattenHealthcareFhirStoreDisableResourceVersioning(res["disableResourceVersioning"], d)); err != nil { + return fmt.Errorf("Error reading FhirStore: %s", err) + } + if err := d.Set("enable_history_import", flattenHealthcareFhirStoreEnableHistoryImport(res["enableHistoryImport"], d)); err != nil { + return fmt.Errorf("Error reading FhirStore: %s", err) + } + if err := d.Set("labels", flattenHealthcareFhirStoreLabels(res["labels"], d)); err != nil { + return fmt.Errorf("Error reading FhirStore: %s", err) + } + if err := d.Set("notification_config", flattenHealthcareFhirStoreNotificationConfig(res["notificationConfig"], d)); err != nil { + return fmt.Errorf("Error reading FhirStore: %s", err) + } + + return nil +} + +func resourceHealthcareFhirStoreUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + enableUpdateCreateProp, err := expandHealthcareFhirStoreEnableUpdateCreate(d.Get("enable_update_create"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("enable_update_create"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, enableUpdateCreateProp)) { + obj["enableUpdateCreate"] = enableUpdateCreateProp + } + labelsProp, err := expandHealthcareFhirStoreLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + notificationConfigProp, err := expandHealthcareFhirStoreNotificationConfig(d.Get("notification_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("notification_config"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, notificationConfigProp)) { + obj["notificationConfig"] = notificationConfigProp + } + + url, err := replaceVars(d, config, "{{HealthcareBasePath}}{{dataset}}/fhirStores/{{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating FhirStore %q: %#v", d.Id(), obj) + updateMask := []string{} + + if d.HasChange("enable_update_create") { + updateMask = append(updateMask, "enableUpdateCreate") + } + + if d.HasChange("labels") { + updateMask = append(updateMask, "labels") + } + + if d.HasChange("notification_config") { + updateMask = append(updateMask, "notificationConfig") + } + // updateMask is a URL parameter but not present in the schema, so replaceVars + // won't set it + url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) + if err != nil { + return err + } + _, err = sendRequestWithTimeout(config, "PATCH", url, obj, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("Error updating FhirStore %q: %s", d.Id(), err) + } + + return resourceHealthcareFhirStoreRead(d, meta) +} + +func resourceHealthcareFhirStoreDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{HealthcareBasePath}}{{dataset}}/fhirStores/{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting FhirStore %q", d.Id()) + res, err := sendRequestWithTimeout(config, "DELETE", url, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "FhirStore") + } + + log.Printf("[DEBUG] Finished deleting FhirStore %q: %#v", d.Id(), res) + return nil +} + +func resourceHealthcareFhirStoreImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + + config := meta.(*Config) + + fhirStoreId, err := parseHealthcareFhirStoreId(d.Id(), config) + if err != nil { + return nil, err + } + + d.Set("dataset", fhirStoreId.DatasetId.datasetId()) + d.Set("name", fhirStoreId.Name) + + return []*schema.ResourceData{d}, nil +} + +func flattenHealthcareFhirStoreName(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenHealthcareFhirStoreEnableUpdateCreate(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenHealthcareFhirStoreDisableReferentialIntegrity(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenHealthcareFhirStoreDisableResourceVersioning(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenHealthcareFhirStoreEnableHistoryImport(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenHealthcareFhirStoreLabels(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenHealthcareFhirStoreNotificationConfig(v interface{}, d *schema.ResourceData) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["pubsub_topic"] = + flattenHealthcareFhirStoreNotificationConfigPubsubTopic(original["pubsubTopic"], d) + return []interface{}{transformed} +} +func flattenHealthcareFhirStoreNotificationConfigPubsubTopic(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func expandHealthcareFhirStoreName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandHealthcareFhirStoreEnableUpdateCreate(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandHealthcareFhirStoreDisableReferentialIntegrity(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandHealthcareFhirStoreDisableResourceVersioning(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandHealthcareFhirStoreEnableHistoryImport(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandHealthcareFhirStoreLabels(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) { + if v == nil { + return map[string]string{}, nil + } + m := make(map[string]string) + for k, val := range v.(map[string]interface{}) { + m[k] = val.(string) + } + return m, nil +} + +func expandHealthcareFhirStoreNotificationConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedPubsubTopic, err := expandHealthcareFhirStoreNotificationConfigPubsubTopic(original["pubsub_topic"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPubsubTopic); val.IsValid() && !isEmptyValue(val) { + transformed["pubsubTopic"] = transformedPubsubTopic + } + + return transformed, nil +} + +func expandHealthcareFhirStoreNotificationConfigPubsubTopic(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func resourceHealthcareFhirStoreDecoder(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) { + // Take the returned long form of the name and use it as `self_link`. + // Then modify the name to be the user specified form. + // We can't just ignore_read on `name` as the linter will + // complain that the returned `res` is never used afterwards. + // Some field needs to be actually set, and we chose `name`. + d.Set("self_link", res["name"].(string)) + res["name"] = d.Get("name").(string) + return res, nil +} diff --git a/google-beta/resource_healthcare_fhir_store_iam_test.go b/google-beta/resource_healthcare_fhir_store_iam_test.go new file mode 100644 index 00000000000..410e1b60747 --- /dev/null +++ b/google-beta/resource_healthcare_fhir_store_iam_test.go @@ -0,0 +1,352 @@ +package google + +import ( + "fmt" + "reflect" + "sort" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccHealthcareFhirStoreIamBinding(t *testing.T) { + t.Parallel() + + projectId := getTestProjectFromEnv() + account := acctest.RandomWithPrefix("tf-test") + roleId := "roles/healthcare.fhirStoreAdmin" + datasetName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + datasetId := &healthcareDatasetId{ + Project: projectId, + Location: DEFAULT_HEALTHCARE_TEST_LOCATION, + Name: datasetName, + } + fhirStoreName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + // Test Iam Binding creation + Config: testAccHealthcareFhirStoreIamBinding_basic(account, datasetName, fhirStoreName, roleId), + Check: testAccCheckGoogleHealthcareFhirStoreIamBindingExists("foo", roleId, []string{ + fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, projectId), + }), + }, + { + ResourceName: "google_healthcare_fhir_store_iam_binding.foo", + ImportStateId: fmt.Sprintf("%s/%s %s", datasetId.terraformId(), fhirStoreName, roleId), + ImportState: true, + ImportStateVerify: true, + }, + { + // Test Iam Binding update + Config: testAccHealthcareFhirStoreIamBinding_update(account, datasetName, fhirStoreName, roleId), + Check: testAccCheckGoogleHealthcareFhirStoreIamBindingExists("foo", roleId, []string{ + fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, projectId), + fmt.Sprintf("serviceAccount:%s-2@%s.iam.gserviceaccount.com", account, projectId), + }), + }, + { + ResourceName: "google_healthcare_fhir_store_iam_binding.foo", + ImportStateId: fmt.Sprintf("%s/%s %s", datasetId.terraformId(), fhirStoreName, roleId), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccHealthcareFhirStoreIamMember(t *testing.T) { + t.Parallel() + + projectId := getTestProjectFromEnv() + account := acctest.RandomWithPrefix("tf-test") + roleId := "roles/healthcare.fhirResourceEditor" + datasetName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + datasetId := &healthcareDatasetId{ + Project: projectId, + Location: DEFAULT_HEALTHCARE_TEST_LOCATION, + Name: datasetName, + } + fhirStoreName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + // Test Iam Member creation (no update for member, no need to test) + Config: testAccHealthcareFhirStoreIamMember_basic(account, datasetName, fhirStoreName, roleId), + Check: testAccCheckGoogleHealthcareFhirStoreIamMemberExists("foo", roleId, + fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, projectId), + ), + }, + { + ResourceName: "google_healthcare_fhir_store_iam_member.foo", + ImportStateId: fmt.Sprintf("%s/%s %s serviceAccount:%s@%s.iam.gserviceaccount.com", datasetId.terraformId(), fhirStoreName, roleId, account, projectId), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccHealthcareFhirStoreIamPolicy(t *testing.T) { + t.Parallel() + + projectId := getTestProjectFromEnv() + account := acctest.RandomWithPrefix("tf-test") + roleId := "roles/healthcare.fhirResourceEditor" + datasetName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + datasetId := &healthcareDatasetId{ + Project: projectId, + Location: DEFAULT_HEALTHCARE_TEST_LOCATION, + Name: datasetName, + } + fhirStoreName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + // Test Iam Policy creation (no update for policy, no need to test) + Config: testAccHealthcareFhirStoreIamPolicy_basic(account, datasetName, fhirStoreName, roleId), + Check: testAccCheckGoogleHealthcareFhirStoreIamPolicyExists("foo", roleId, + fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, projectId), + ), + }, + { + ResourceName: "google_healthcare_fhir_store_iam_policy.foo", + ImportStateId: fmt.Sprintf("%s/%s", datasetId.terraformId(), fhirStoreName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckGoogleHealthcareFhirStoreIamBindingExists(bindingResourceName, roleId string, members []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + bindingRs, ok := s.RootModule().Resources[fmt.Sprintf("google_healthcare_fhir_store_iam_binding.%s", bindingResourceName)] + if !ok { + return fmt.Errorf("Not found: %s", bindingResourceName) + } + + config := testAccProvider.Meta().(*Config) + fhirStoreId, err := parseHealthcareFhirStoreId(bindingRs.Primary.Attributes["fhir_store_id"], config) + + if err != nil { + return err + } + + p, err := config.clientHealthcare.Projects.Locations.Datasets.FhirStores.GetIamPolicy(fhirStoreId.fhirStoreId()).Do() + if err != nil { + return err + } + + for _, binding := range p.Bindings { + if binding.Role == roleId { + sort.Strings(members) + sort.Strings(binding.Members) + + if reflect.DeepEqual(members, binding.Members) { + return nil + } + + return fmt.Errorf("Binding found but expected members is %v, got %v", members, binding.Members) + } + } + + return fmt.Errorf("No binding for role %q", roleId) + } +} + +func testAccCheckGoogleHealthcareFhirStoreIamMemberExists(n, role, member string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources["google_healthcare_fhir_store_iam_member."+n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + config := testAccProvider.Meta().(*Config) + fhirStoreId, err := parseHealthcareFhirStoreId(rs.Primary.Attributes["fhir_store_id"], config) + + if err != nil { + return err + } + + p, err := config.clientHealthcare.Projects.Locations.Datasets.FhirStores.GetIamPolicy(fhirStoreId.fhirStoreId()).Do() + if err != nil { + return err + } + + for _, binding := range p.Bindings { + if binding.Role == role { + for _, m := range binding.Members { + if m == member { + return nil + } + } + + return fmt.Errorf("Missing member %q, got %v", member, binding.Members) + } + } + + return fmt.Errorf("No binding for role %q", role) + } +} + +func testAccCheckGoogleHealthcareFhirStoreIamPolicyExists(n, role, policy string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources["google_healthcare_fhir_store_iam_policy."+n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + config := testAccProvider.Meta().(*Config) + fhirStoreId, err := parseHealthcareFhirStoreId(rs.Primary.Attributes["fhir_store_id"], config) + + if err != nil { + return err + } + + p, err := config.clientHealthcare.Projects.Locations.Datasets.FhirStores.GetIamPolicy(fhirStoreId.fhirStoreId()).Do() + if err != nil { + return err + } + + for _, binding := range p.Bindings { + if binding.Role == role { + for _, m := range binding.Members { + if m == policy { + return nil + } + } + + return fmt.Errorf("Missing policy %q, got %v", policy, binding.Members) + } + } + + return fmt.Errorf("No binding for role %q", role) + } +} + +// We are using a custom role since iam_binding is authoritative on the member list and +// we want to avoid removing members from an existing role to prevent unwanted side effects. +func testAccHealthcareFhirStoreIamBinding_basic(account, datasetName, fhirStoreName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_healthcare_dataset" "dataset" { + location = "us-central1" + name = "%s" +} + +resource "google_healthcare_fhir_store" "fhir_store" { + dataset = "${google_healthcare_dataset.dataset.id}" + name = "%s" +} + +resource "google_healthcare_fhir_store_iam_binding" "foo" { + fhir_store_id = "${google_healthcare_fhir_store.fhir_store.id}" + role = "%s" + members = ["serviceAccount:${google_service_account.test_account.email}"] +} +`, account, datasetName, fhirStoreName, roleId) +} + +func testAccHealthcareFhirStoreIamBinding_update(account, datasetName, fhirStoreName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_service_account" "test_account_2" { + account_id = "%s-2" + display_name = "Iam Testing Account" +} + +resource "google_healthcare_dataset" "dataset" { + location = "us-central1" + name = "%s" +} + +resource "google_healthcare_fhir_store" "fhir_store" { + dataset = "${google_healthcare_dataset.dataset.id}" + name = "%s" +} + +resource "google_healthcare_fhir_store_iam_binding" "foo" { + fhir_store_id = "${google_healthcare_fhir_store.fhir_store.id}" + role = "%s" + members = [ + "serviceAccount:${google_service_account.test_account.email}", + "serviceAccount:${google_service_account.test_account_2.email}" + ] +} +`, account, account, datasetName, fhirStoreName, roleId) +} + +func testAccHealthcareFhirStoreIamMember_basic(account, datasetName, fhirStoreName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_healthcare_dataset" "dataset" { + location = "us-central1" + name = "%s" +} +resource "google_healthcare_fhir_store" "fhir_store" { + dataset = "${google_healthcare_dataset.dataset.id}" + name = "%s" +} + +resource "google_healthcare_fhir_store_iam_member" "foo" { + fhir_store_id = "${google_healthcare_fhir_store.fhir_store.id}" + role = "%s" + member = "serviceAccount:${google_service_account.test_account.email}" +} +`, account, datasetName, fhirStoreName, roleId) +} + +func testAccHealthcareFhirStoreIamPolicy_basic(account, datasetName, fhirStoreName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_healthcare_dataset" "dataset" { + location = "us-central1" + name = "%s" +} +resource "google_healthcare_fhir_store" "fhir_store" { + dataset = "${google_healthcare_dataset.dataset.id}" + name = "%s" +} + +data "google_iam_policy" "foo" { + binding { + role = "%s" + + members = ["serviceAccount:${google_service_account.test_account.email}"] + } +} + +resource "google_healthcare_fhir_store_iam_policy" "foo" { + fhir_store_id = "${google_healthcare_fhir_store.fhir_store.id}" + policy_data = "${data.google_iam_policy.foo.policy_data}" +} +`, account, datasetName, fhirStoreName, roleId) +} diff --git a/google-beta/resource_healthcare_fhir_store_test.go b/google-beta/resource_healthcare_fhir_store_test.go new file mode 100644 index 00000000000..2780b035520 --- /dev/null +++ b/google-beta/resource_healthcare_fhir_store_test.go @@ -0,0 +1,230 @@ +package google + +import ( + "fmt" + "path" + "strings" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccHealthcareFhirStoreIdParsing(t *testing.T) { + t.Parallel() + + cases := map[string]struct { + ImportId string + ExpectedError bool + ExpectedTerraformId string + ExpectedFhirStoreId string + Config *Config + }{ + "id is in project/location/datasetName/fhirStoreName format": { + ImportId: "test-project/us-central1/test-dataset/test-store-name", + ExpectedError: false, + ExpectedTerraformId: "test-project/us-central1/test-dataset/test-store-name", + ExpectedFhirStoreId: "projects/test-project/locations/us-central1/datasets/test-dataset/fhirStores/test-store-name", + }, + "id is in domain:project/location/datasetName/fhirStoreName format": { + ImportId: "example.com:test-project/us-central1/test-dataset/test-store-name", + ExpectedError: false, + ExpectedTerraformId: "example.com:test-project/us-central1/test-dataset/test-store-name", + ExpectedFhirStoreId: "projects/example.com:test-project/locations/us-central1/datasets/test-dataset/fhirStores/test-store-name", + }, + "id is in location/datasetName/fhirStoreName format": { + ImportId: "us-central1/test-dataset/test-store-name", + ExpectedError: false, + ExpectedTerraformId: "test-project/us-central1/test-dataset/test-store-name", + ExpectedFhirStoreId: "projects/test-project/locations/us-central1/datasets/test-dataset/fhirStores/test-store-name", + Config: &Config{Project: "test-project"}, + }, + "id is in location/datasetName/fhirStoreName format without project in config": { + ImportId: "us-central1/test-dataset/test-store-name", + ExpectedError: true, + Config: &Config{Project: ""}, + }, + } + + for tn, tc := range cases { + fhirStoreId, err := parseHealthcareFhirStoreId(tc.ImportId, tc.Config) + + if tc.ExpectedError && err == nil { + t.Fatalf("bad: %s, expected an error", tn) + } + + if err != nil { + if tc.ExpectedError { + continue + } + t.Fatalf("bad: %s, err: %#v", tn, err) + } + + if fhirStoreId.terraformId() != tc.ExpectedTerraformId { + t.Fatalf("bad: %s, expected Terraform ID to be `%s` but is `%s`", tn, tc.ExpectedTerraformId, fhirStoreId.terraformId()) + } + + if fhirStoreId.fhirStoreId() != tc.ExpectedFhirStoreId { + t.Fatalf("bad: %s, expected FhirStore ID to be `%s` but is `%s`", tn, tc.ExpectedFhirStoreId, fhirStoreId.fhirStoreId()) + } + } +} + +func TestAccHealthcareFhirStore_basic(t *testing.T) { + t.Parallel() + + datasetName := fmt.Sprintf("tf-test-dataset-%s", acctest.RandString(10)) + fhirStoreName := fmt.Sprintf("tf-test-fhir-store-%s", acctest.RandString(10)) + pubsubTopic := fmt.Sprintf("tf-test-topic-%s", acctest.RandString(10)) + resourceName := "google_healthcare_fhir_store.default" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckHealthcareFhirStoreDestroy, + Steps: []resource.TestStep{ + { + Config: testGoogleHealthcareFhirStore_basic(fhirStoreName, datasetName), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testGoogleHealthcareFhirStore_update(fhirStoreName, datasetName, pubsubTopic), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleHealthcareFhirStoreUpdate(pubsubTopic), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testGoogleHealthcareFhirStore_basic(fhirStoreName, datasetName string) string { + return fmt.Sprintf(` +resource "google_healthcare_fhir_store" "default" { + name = "%s" + dataset = "${google_healthcare_dataset.dataset.id}" + + enable_update_create = false + disable_referential_integrity = false + disable_resource_versioning = false + enable_history_import = false +} + +resource "google_healthcare_dataset" "dataset" { + name = "%s" + location = "us-central1" +} +`, fhirStoreName, datasetName) +} + +func testGoogleHealthcareFhirStore_update(fhirStoreName, datasetName, pubsubTopic string) string { + return fmt.Sprintf(` +resource "google_healthcare_fhir_store" "default" { + name = "%s" + dataset = "${google_healthcare_dataset.dataset.id}" + + enable_update_create = true + + notification_config { + pubsub_topic = "${google_pubsub_topic.topic.id}" + } + + labels = { + label1 = "labelvalue1" + } +} + +resource "google_healthcare_dataset" "dataset" { + name = "%s" + location = "us-central1" +} + +resource "google_pubsub_topic" "topic" { + name = "%s" +} + +`, fhirStoreName, datasetName, pubsubTopic) +} + +func testAccCheckHealthcareFhirStoreDestroy(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_healthcare_fhir_store" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := testAccProvider.Meta().(*Config) + + url, err := replaceVarsForTest(config, rs, "{{HealthcareBasePath}}{{dataset}}/fhirStores/{{name}}") + if err != nil { + return err + } + + _, err = sendRequest(config, "GET", url, nil) + if err == nil { + return fmt.Errorf("HealthcareFhirStore still exists at %s", url) + } + } + + return nil +} + +func testAccCheckGoogleHealthcareFhirStoreUpdate(pubsubTopic string) resource.TestCheckFunc { + return func(s *terraform.State) error { + var foundResource = false + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_healthcare_fhir_store" { + continue + } + foundResource = true + + config := testAccProvider.Meta().(*Config) + + gcpResourceUri, err := replaceVarsForTest(config, rs, "{{dataset}}/fhirStores/{{name}}") + if err != nil { + return err + } + + response, err := config.clientHealthcare.Projects.Locations.Datasets.FhirStores.Get(gcpResourceUri).Do() + if err != nil { + return fmt.Errorf("Unexpected failure while verifying 'updated' dataset: %s", err) + } + + if !response.EnableUpdateCreate { + return fmt.Errorf("fhirStore 'EnableUpdateCreate' not updated: %s", gcpResourceUri) + } + + // because the GET for the FHIR store resource does not return the "enableHistoryImport" flag, this value + // will always be false and cannot be relied upon + + //if !response.EnableHistoryImport { + // return fmt.Errorf("fhirStore 'EnableHistoryImport' not updated: %s", gcpResourceUri) + //} + + if len(response.Labels) == 0 || response.Labels["label1"] != "labelvalue1" { + return fmt.Errorf("fhirStore labels not updated: %s", gcpResourceUri) + } + + topicName := path.Base(response.NotificationConfig.PubsubTopic) + if topicName != pubsubTopic { + return fmt.Errorf("fhirStore 'NotificationConfig' not updated ('%s' != '%s'): %s", topicName, pubsubTopic, gcpResourceUri) + } + } + + if !foundResource { + return fmt.Errorf("google_healthcare_fhir_store resource was missing") + } + return nil + } +} diff --git a/google-beta/resource_healthcare_hl7_v2_store.go b/google-beta/resource_healthcare_hl7_v2_store.go new file mode 100644 index 00000000000..338d789673c --- /dev/null +++ b/google-beta/resource_healthcare_hl7_v2_store.go @@ -0,0 +1,405 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "log" + "reflect" + "strings" + "time" + + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceHealthcareHl7V2Store() *schema.Resource { + return &schema.Resource{ + Create: resourceHealthcareHl7V2StoreCreate, + Read: resourceHealthcareHl7V2StoreRead, + Update: resourceHealthcareHl7V2StoreUpdate, + Delete: resourceHealthcareHl7V2StoreDelete, + + Importer: &schema.ResourceImporter{ + State: resourceHealthcareHl7V2StoreImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(240 * time.Second), + Update: schema.DefaultTimeout(240 * time.Second), + Delete: schema.DefaultTimeout(240 * time.Second), + }, + + Schema: map[string]*schema.Schema{ + "dataset": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: compareSelfLinkOrResourceName, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "notification_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "pubsub_topic": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "parser_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "allow_null_header": { + Type: schema.TypeBool, + Optional: true, + }, + "segment_terminator": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "self_link": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceHealthcareHl7V2StoreCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + nameProp, err := expandHealthcareHl7V2StoreName(d.Get("name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("name"); !isEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) { + obj["name"] = nameProp + } + parserConfigProp, err := expandHealthcareHl7V2StoreParserConfig(d.Get("parser_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("parser_config"); !isEmptyValue(reflect.ValueOf(parserConfigProp)) && (ok || !reflect.DeepEqual(v, parserConfigProp)) { + obj["parserConfig"] = parserConfigProp + } + labelsProp, err := expandHealthcareHl7V2StoreLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(labelsProp)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + notificationConfigProp, err := expandHealthcareHl7V2StoreNotificationConfig(d.Get("notification_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("notification_config"); !isEmptyValue(reflect.ValueOf(notificationConfigProp)) && (ok || !reflect.DeepEqual(v, notificationConfigProp)) { + obj["notificationConfig"] = notificationConfigProp + } + + url, err := replaceVars(d, config, "{{HealthcareBasePath}}{{dataset}}/hl7V2Stores?hl7V2StoreId={{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new Hl7V2Store: %#v", obj) + res, err := sendRequestWithTimeout(config, "POST", url, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating Hl7V2Store: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "{{dataset}}/hl7V2Stores/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating Hl7V2Store %q: %#v", d.Id(), res) + + return resourceHealthcareHl7V2StoreRead(d, meta) +} + +func resourceHealthcareHl7V2StoreRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{HealthcareBasePath}}{{dataset}}/hl7V2Stores/{{name}}") + if err != nil { + return err + } + + res, err := sendRequest(config, "GET", url, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("HealthcareHl7V2Store %q", d.Id())) + } + + res, err = resourceHealthcareHl7V2StoreDecoder(d, meta, res) + if err != nil { + return err + } + + if err := d.Set("name", flattenHealthcareHl7V2StoreName(res["name"], d)); err != nil { + return fmt.Errorf("Error reading Hl7V2Store: %s", err) + } + if err := d.Set("parser_config", flattenHealthcareHl7V2StoreParserConfig(res["parserConfig"], d)); err != nil { + return fmt.Errorf("Error reading Hl7V2Store: %s", err) + } + if err := d.Set("labels", flattenHealthcareHl7V2StoreLabels(res["labels"], d)); err != nil { + return fmt.Errorf("Error reading Hl7V2Store: %s", err) + } + if err := d.Set("notification_config", flattenHealthcareHl7V2StoreNotificationConfig(res["notificationConfig"], d)); err != nil { + return fmt.Errorf("Error reading Hl7V2Store: %s", err) + } + + return nil +} + +func resourceHealthcareHl7V2StoreUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + parserConfigProp, err := expandHealthcareHl7V2StoreParserConfig(d.Get("parser_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("parser_config"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, parserConfigProp)) { + obj["parserConfig"] = parserConfigProp + } + labelsProp, err := expandHealthcareHl7V2StoreLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + notificationConfigProp, err := expandHealthcareHl7V2StoreNotificationConfig(d.Get("notification_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("notification_config"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, notificationConfigProp)) { + obj["notificationConfig"] = notificationConfigProp + } + + url, err := replaceVars(d, config, "{{HealthcareBasePath}}{{dataset}}/hl7V2Stores/{{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating Hl7V2Store %q: %#v", d.Id(), obj) + updateMask := []string{} + + if d.HasChange("parser_config") { + updateMask = append(updateMask, "parserConfig") + } + + if d.HasChange("labels") { + updateMask = append(updateMask, "labels") + } + + if d.HasChange("notification_config") { + updateMask = append(updateMask, "notificationConfig") + } + // updateMask is a URL parameter but not present in the schema, so replaceVars + // won't set it + url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) + if err != nil { + return err + } + _, err = sendRequestWithTimeout(config, "PATCH", url, obj, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("Error updating Hl7V2Store %q: %s", d.Id(), err) + } + + return resourceHealthcareHl7V2StoreRead(d, meta) +} + +func resourceHealthcareHl7V2StoreDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{HealthcareBasePath}}{{dataset}}/hl7V2Stores/{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting Hl7V2Store %q", d.Id()) + res, err := sendRequestWithTimeout(config, "DELETE", url, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "Hl7V2Store") + } + + log.Printf("[DEBUG] Finished deleting Hl7V2Store %q: %#v", d.Id(), res) + return nil +} + +func resourceHealthcareHl7V2StoreImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + + config := meta.(*Config) + + hl7v2StoreId, err := parseHealthcareHl7V2StoreId(d.Id(), config) + if err != nil { + return nil, err + } + + d.Set("dataset", hl7v2StoreId.DatasetId.datasetId()) + d.Set("name", hl7v2StoreId.Name) + + return []*schema.ResourceData{d}, nil +} + +func flattenHealthcareHl7V2StoreName(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenHealthcareHl7V2StoreParserConfig(v interface{}, d *schema.ResourceData) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["allow_null_header"] = + flattenHealthcareHl7V2StoreParserConfigAllowNullHeader(original["allowNullHeader"], d) + transformed["segment_terminator"] = + flattenHealthcareHl7V2StoreParserConfigSegmentTerminator(original["segmentTerminator"], d) + return []interface{}{transformed} +} +func flattenHealthcareHl7V2StoreParserConfigAllowNullHeader(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenHealthcareHl7V2StoreParserConfigSegmentTerminator(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenHealthcareHl7V2StoreLabels(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenHealthcareHl7V2StoreNotificationConfig(v interface{}, d *schema.ResourceData) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["pubsub_topic"] = + flattenHealthcareHl7V2StoreNotificationConfigPubsubTopic(original["pubsubTopic"], d) + return []interface{}{transformed} +} +func flattenHealthcareHl7V2StoreNotificationConfigPubsubTopic(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func expandHealthcareHl7V2StoreName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandHealthcareHl7V2StoreParserConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedAllowNullHeader, err := expandHealthcareHl7V2StoreParserConfigAllowNullHeader(original["allow_null_header"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedAllowNullHeader); val.IsValid() && !isEmptyValue(val) { + transformed["allowNullHeader"] = transformedAllowNullHeader + } + + transformedSegmentTerminator, err := expandHealthcareHl7V2StoreParserConfigSegmentTerminator(original["segment_terminator"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSegmentTerminator); val.IsValid() && !isEmptyValue(val) { + transformed["segmentTerminator"] = transformedSegmentTerminator + } + + return transformed, nil +} + +func expandHealthcareHl7V2StoreParserConfigAllowNullHeader(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandHealthcareHl7V2StoreParserConfigSegmentTerminator(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandHealthcareHl7V2StoreLabels(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) { + if v == nil { + return map[string]string{}, nil + } + m := make(map[string]string) + for k, val := range v.(map[string]interface{}) { + m[k] = val.(string) + } + return m, nil +} + +func expandHealthcareHl7V2StoreNotificationConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedPubsubTopic, err := expandHealthcareHl7V2StoreNotificationConfigPubsubTopic(original["pubsub_topic"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPubsubTopic); val.IsValid() && !isEmptyValue(val) { + transformed["pubsubTopic"] = transformedPubsubTopic + } + + return transformed, nil +} + +func expandHealthcareHl7V2StoreNotificationConfigPubsubTopic(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func resourceHealthcareHl7V2StoreDecoder(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) { + // Take the returned long form of the name and use it as `self_link`. + // Then modify the name to be the user specified form. + // We can't just ignore_read on `name` as the linter will + // complain that the returned `res` is never used afterwards. + // Some field needs to be actually set, and we chose `name`. + d.Set("self_link", res["name"].(string)) + res["name"] = d.Get("name").(string) + return res, nil +} diff --git a/google-beta/resource_healthcare_hl7_v2_store_iam_test.go b/google-beta/resource_healthcare_hl7_v2_store_iam_test.go new file mode 100644 index 00000000000..3718f46f643 --- /dev/null +++ b/google-beta/resource_healthcare_hl7_v2_store_iam_test.go @@ -0,0 +1,352 @@ +package google + +import ( + "fmt" + "reflect" + "sort" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccHealthcareHl7V2StoreIamBinding(t *testing.T) { + t.Parallel() + + projectId := getTestProjectFromEnv() + account := acctest.RandomWithPrefix("tf-test") + roleId := "roles/healthcare.hl7V2StoreAdmin" + datasetName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + datasetId := &healthcareDatasetId{ + Project: projectId, + Location: DEFAULT_HEALTHCARE_TEST_LOCATION, + Name: datasetName, + } + hl7V2StoreName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + // Test Iam Binding creation + Config: testAccHealthcareHl7V2StoreIamBinding_basic(account, datasetName, hl7V2StoreName, roleId), + Check: testAccCheckGoogleHealthcareHl7V2StoreIamBindingExists("foo", roleId, []string{ + fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, projectId), + }), + }, + { + ResourceName: "google_healthcare_hl7_v2_store_iam_binding.foo", + ImportStateId: fmt.Sprintf("%s/%s %s", datasetId.terraformId(), hl7V2StoreName, roleId), + ImportState: true, + ImportStateVerify: true, + }, + { + // Test Iam Binding update + Config: testAccHealthcareHl7V2StoreIamBinding_update(account, datasetName, hl7V2StoreName, roleId), + Check: testAccCheckGoogleHealthcareHl7V2StoreIamBindingExists("foo", roleId, []string{ + fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, projectId), + fmt.Sprintf("serviceAccount:%s-2@%s.iam.gserviceaccount.com", account, projectId), + }), + }, + { + ResourceName: "google_healthcare_hl7_v2_store_iam_binding.foo", + ImportStateId: fmt.Sprintf("%s/%s %s", datasetId.terraformId(), hl7V2StoreName, roleId), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccHealthcareHl7V2StoreIamMember(t *testing.T) { + t.Parallel() + + projectId := getTestProjectFromEnv() + account := acctest.RandomWithPrefix("tf-test") + roleId := "roles/healthcare.hl7V2Editor" + datasetName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + datasetId := &healthcareDatasetId{ + Project: projectId, + Location: DEFAULT_HEALTHCARE_TEST_LOCATION, + Name: datasetName, + } + hl7V2StoreName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + // Test Iam Member creation (no update for member, no need to test) + Config: testAccHealthcareHl7V2StoreIamMember_basic(account, datasetName, hl7V2StoreName, roleId), + Check: testAccCheckGoogleHealthcareHl7V2StoreIamMemberExists("foo", roleId, + fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, projectId), + ), + }, + { + ResourceName: "google_healthcare_hl7_v2_store_iam_member.foo", + ImportStateId: fmt.Sprintf("%s/%s %s serviceAccount:%s@%s.iam.gserviceaccount.com", datasetId.terraformId(), hl7V2StoreName, roleId, account, projectId), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccHealthcareHl7V2StoreIamPolicy(t *testing.T) { + t.Parallel() + + projectId := getTestProjectFromEnv() + account := acctest.RandomWithPrefix("tf-test") + roleId := "roles/healthcare.hl7V2Consumer" + datasetName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + datasetId := &healthcareDatasetId{ + Project: projectId, + Location: DEFAULT_HEALTHCARE_TEST_LOCATION, + Name: datasetName, + } + hl7V2StoreName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + // Test Iam Policy creation (no update for policy, no need to test) + Config: testAccHealthcareHl7V2StoreIamPolicy_basic(account, datasetName, hl7V2StoreName, roleId), + Check: testAccCheckGoogleHealthcareHl7V2StoreIamPolicyExists("foo", roleId, + fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, projectId), + ), + }, + { + ResourceName: "google_healthcare_hl7_v2_store_iam_policy.foo", + ImportStateId: fmt.Sprintf("%s/%s", datasetId.terraformId(), hl7V2StoreName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckGoogleHealthcareHl7V2StoreIamBindingExists(bindingResourceName, roleId string, members []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + bindingRs, ok := s.RootModule().Resources[fmt.Sprintf("google_healthcare_hl7_v2_store_iam_binding.%s", bindingResourceName)] + if !ok { + return fmt.Errorf("Not found: %s", bindingResourceName) + } + + config := testAccProvider.Meta().(*Config) + hl7V2StoreId, err := parseHealthcareHl7V2StoreId(bindingRs.Primary.Attributes["hl7_v2_store_id"], config) + + if err != nil { + return err + } + + p, err := config.clientHealthcare.Projects.Locations.Datasets.Hl7V2Stores.GetIamPolicy(hl7V2StoreId.hl7V2StoreId()).Do() + if err != nil { + return err + } + + for _, binding := range p.Bindings { + if binding.Role == roleId { + sort.Strings(members) + sort.Strings(binding.Members) + + if reflect.DeepEqual(members, binding.Members) { + return nil + } + + return fmt.Errorf("Binding found but expected members is %v, got %v", members, binding.Members) + } + } + + return fmt.Errorf("No binding for role %q", roleId) + } +} + +func testAccCheckGoogleHealthcareHl7V2StoreIamMemberExists(n, role, member string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources["google_healthcare_hl7_v2_store_iam_member."+n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + config := testAccProvider.Meta().(*Config) + hl7V2StoreId, err := parseHealthcareHl7V2StoreId(rs.Primary.Attributes["hl7_v2_store_id"], config) + + if err != nil { + return err + } + + p, err := config.clientHealthcare.Projects.Locations.Datasets.Hl7V2Stores.GetIamPolicy(hl7V2StoreId.hl7V2StoreId()).Do() + if err != nil { + return err + } + + for _, binding := range p.Bindings { + if binding.Role == role { + for _, m := range binding.Members { + if m == member { + return nil + } + } + + return fmt.Errorf("Missing member %q, got %v", member, binding.Members) + } + } + + return fmt.Errorf("No binding for role %q", role) + } +} + +func testAccCheckGoogleHealthcareHl7V2StoreIamPolicyExists(n, role, policy string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources["google_healthcare_hl7_v2_store_iam_policy."+n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + config := testAccProvider.Meta().(*Config) + hl7V2StoreId, err := parseHealthcareHl7V2StoreId(rs.Primary.Attributes["hl7_v2_store_id"], config) + + if err != nil { + return err + } + + p, err := config.clientHealthcare.Projects.Locations.Datasets.Hl7V2Stores.GetIamPolicy(hl7V2StoreId.hl7V2StoreId()).Do() + if err != nil { + return err + } + + for _, binding := range p.Bindings { + if binding.Role == role { + for _, m := range binding.Members { + if m == policy { + return nil + } + } + + return fmt.Errorf("Missing policy %q, got %v", policy, binding.Members) + } + } + + return fmt.Errorf("No binding for role %q", role) + } +} + +// We are using a custom role since iam_binding is authoritative on the member list and +// we want to avoid removing members from an existing role to prevent unwanted side effects. +func testAccHealthcareHl7V2StoreIamBinding_basic(account, datasetName, hl7V2StoreName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_healthcare_dataset" "dataset" { + location = "us-central1" + name = "%s" +} + +resource "google_healthcare_hl7_v2_store" "hl7_v2_store" { + dataset = "${google_healthcare_dataset.dataset.id}" + name = "%s" +} + +resource "google_healthcare_hl7_v2_store_iam_binding" "foo" { + hl7_v2_store_id = "${google_healthcare_hl7_v2_store.hl7_v2_store.id}" + role = "%s" + members = ["serviceAccount:${google_service_account.test_account.email}"] +} +`, account, datasetName, hl7V2StoreName, roleId) +} + +func testAccHealthcareHl7V2StoreIamBinding_update(account, datasetName, hl7V2StoreName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_service_account" "test_account_2" { + account_id = "%s-2" + display_name = "Iam Testing Account" +} + +resource "google_healthcare_dataset" "dataset" { + location = "us-central1" + name = "%s" +} + +resource "google_healthcare_hl7_v2_store" "hl7_v2_store" { + dataset = "${google_healthcare_dataset.dataset.id}" + name = "%s" +} + +resource "google_healthcare_hl7_v2_store_iam_binding" "foo" { + hl7_v2_store_id = "${google_healthcare_hl7_v2_store.hl7_v2_store.id}" + role = "%s" + members = [ + "serviceAccount:${google_service_account.test_account.email}", + "serviceAccount:${google_service_account.test_account_2.email}" + ] +} +`, account, account, datasetName, hl7V2StoreName, roleId) +} + +func testAccHealthcareHl7V2StoreIamMember_basic(account, datasetName, hl7V2StoreName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_healthcare_dataset" "dataset" { + location = "us-central1" + name = "%s" +} +resource "google_healthcare_hl7_v2_store" "hl7_v2_store" { + dataset = "${google_healthcare_dataset.dataset.id}" + name = "%s" +} + +resource "google_healthcare_hl7_v2_store_iam_member" "foo" { + hl7_v2_store_id = "${google_healthcare_hl7_v2_store.hl7_v2_store.id}" + role = "%s" + member = "serviceAccount:${google_service_account.test_account.email}" +} +`, account, datasetName, hl7V2StoreName, roleId) +} + +func testAccHealthcareHl7V2StoreIamPolicy_basic(account, datasetName, hl7V2StoreName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_healthcare_dataset" "dataset" { + location = "us-central1" + name = "%s" +} +resource "google_healthcare_hl7_v2_store" "hl7_v2_store" { + dataset = "${google_healthcare_dataset.dataset.id}" + name = "%s" +} + +data "google_iam_policy" "foo" { + binding { + role = "%s" + + members = ["serviceAccount:${google_service_account.test_account.email}"] + } +} + +resource "google_healthcare_hl7_v2_store_iam_policy" "foo" { + hl7_v2_store_id = "${google_healthcare_hl7_v2_store.hl7_v2_store.id}" + policy_data = "${data.google_iam_policy.foo.policy_data}" +} +`, account, datasetName, hl7V2StoreName, roleId) +} diff --git a/google-beta/resource_healthcare_hl7_v2_store_test.go b/google-beta/resource_healthcare_hl7_v2_store_test.go new file mode 100644 index 00000000000..d69ebbeb7ff --- /dev/null +++ b/google-beta/resource_healthcare_hl7_v2_store_test.go @@ -0,0 +1,229 @@ +package google + +import ( + "fmt" + "path" + "strings" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccHealthcareHl7V2StoreIdParsing(t *testing.T) { + t.Parallel() + + cases := map[string]struct { + ImportId string + ExpectedError bool + ExpectedTerraformId string + ExpectedHl7V2StoreId string + Config *Config + }{ + "id is in project/location/datasetName/hl7V2StoreName format": { + ImportId: "test-project/us-central1/test-dataset/test-store-name", + ExpectedError: false, + ExpectedTerraformId: "test-project/us-central1/test-dataset/test-store-name", + ExpectedHl7V2StoreId: "projects/test-project/locations/us-central1/datasets/test-dataset/hl7V2Stores/test-store-name", + }, + "id is in domain:project/location/datasetName/hl7V2StoreName format": { + ImportId: "example.com:test-project/us-central1/test-dataset/test-store-name", + ExpectedError: false, + ExpectedTerraformId: "example.com:test-project/us-central1/test-dataset/test-store-name", + ExpectedHl7V2StoreId: "projects/example.com:test-project/locations/us-central1/datasets/test-dataset/hl7V2Stores/test-store-name", + }, + "id is in location/datasetName/hl7V2StoreName format": { + ImportId: "us-central1/test-dataset/test-store-name", + ExpectedError: false, + ExpectedTerraformId: "test-project/us-central1/test-dataset/test-store-name", + ExpectedHl7V2StoreId: "projects/test-project/locations/us-central1/datasets/test-dataset/hl7V2Stores/test-store-name", + Config: &Config{Project: "test-project"}, + }, + "id is in location/datasetName/hl7V2StoreName format without project in config": { + ImportId: "us-central1/test-dataset/test-store-name", + ExpectedError: true, + Config: &Config{Project: ""}, + }, + } + + for tn, tc := range cases { + hl7V2StoreId, err := parseHealthcareHl7V2StoreId(tc.ImportId, tc.Config) + + if tc.ExpectedError && err == nil { + t.Fatalf("bad: %s, expected an error", tn) + } + + if err != nil { + if tc.ExpectedError { + continue + } + t.Fatalf("bad: %s, err: %#v", tn, err) + } + + if hl7V2StoreId.terraformId() != tc.ExpectedTerraformId { + t.Fatalf("bad: %s, expected Terraform ID to be `%s` but is `%s`", tn, tc.ExpectedTerraformId, hl7V2StoreId.terraformId()) + } + + if hl7V2StoreId.hl7V2StoreId() != tc.ExpectedHl7V2StoreId { + t.Fatalf("bad: %s, expected Hl7V2Store ID to be `%s` but is `%s`", tn, tc.ExpectedHl7V2StoreId, hl7V2StoreId.hl7V2StoreId()) + } + } +} + +func TestAccHealthcareHl7V2Store_basic(t *testing.T) { + t.Parallel() + + datasetName := fmt.Sprintf("tf-test-dataset-%s", acctest.RandString(10)) + hl7_v2StoreName := fmt.Sprintf("tf-test-hl7_v2-store-%s", acctest.RandString(10)) + pubsubTopic := fmt.Sprintf("tf-test-topic-%s", acctest.RandString(10)) + resourceName := "google_healthcare_hl7_v2_store.default" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckHealthcareHl7V2StoreDestroy, + Steps: []resource.TestStep{ + { + Config: testGoogleHealthcareHl7V2Store_basic(hl7_v2StoreName, datasetName), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testGoogleHealthcareHl7V2Store_update(hl7_v2StoreName, datasetName, pubsubTopic), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleHealthcareHl7V2StoreUpdate(pubsubTopic), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testGoogleHealthcareHl7V2Store_basic(hl7_v2StoreName, datasetName string) string { + return fmt.Sprintf(` +resource "google_healthcare_hl7_v2_store" "default" { + name = "%s" + dataset = "${google_healthcare_dataset.dataset.id}" +} + +resource "google_healthcare_dataset" "dataset" { + name = "%s" + location = "us-central1" +} +`, hl7_v2StoreName, datasetName) +} + +func testGoogleHealthcareHl7V2Store_update(hl7_v2StoreName, datasetName, pubsubTopic string) string { + return fmt.Sprintf(` +resource "google_healthcare_hl7_v2_store" "default" { + name = "%s" + dataset = "${google_healthcare_dataset.dataset.id}" + + parser_config { + allow_null_header = true + segment_terminator = "Jw==" + } + + notification_config { + pubsub_topic = "${google_pubsub_topic.topic.id}" + } + + labels = { + label1 = "labelvalue1" + } +} + +resource "google_healthcare_dataset" "dataset" { + name = "%s" + location = "us-central1" +} + +resource "google_pubsub_topic" "topic" { + name = "%s" +} + +`, hl7_v2StoreName, datasetName, pubsubTopic) +} + +func testAccCheckHealthcareHl7V2StoreDestroy(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_healthcare_hl7_v2_store" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := testAccProvider.Meta().(*Config) + + url, err := replaceVarsForTest(config, rs, "{{HealthcareBasePath}}{{dataset}}/hl7V2Stores/{{name}}") + if err != nil { + return err + } + + _, err = sendRequest(config, "GET", url, nil) + if err == nil { + return fmt.Errorf("HealthcareHl7V2Store still exists at %s", url) + } + } + + return nil +} + +func testAccCheckGoogleHealthcareHl7V2StoreUpdate(pubsubTopic string) resource.TestCheckFunc { + return func(s *terraform.State) error { + var foundResource = false + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_healthcare_hl7_v2_store" { + continue + } + foundResource = true + + config := testAccProvider.Meta().(*Config) + + gcpResourceUri, err := replaceVarsForTest(config, rs, "{{dataset}}/hl7V2Stores/{{name}}") + if err != nil { + return err + } + + response, err := config.clientHealthcare.Projects.Locations.Datasets.Hl7V2Stores.Get(gcpResourceUri).Do() + if err != nil { + return fmt.Errorf("Unexpected failure while verifying 'updated' dataset: %s", err) + } + + if response.ParserConfig == nil { + return fmt.Errorf("hl7_v2_store had no parser config: %s", gcpResourceUri) + } + + if !response.ParserConfig.AllowNullHeader { + return fmt.Errorf("hl7_v2_store allowNullHeader not changed to true: %s", gcpResourceUri) + } + + if response.ParserConfig.SegmentTerminator != "Jw==" { + return fmt.Errorf("hl7_v2_store segmentTerminator was not changed to 'JW==' as was expected: %s", gcpResourceUri) + } + + if len(response.Labels) == 0 || response.Labels["label1"] != "labelvalue1" { + return fmt.Errorf("hl7_v2_store labels not updated: %s", gcpResourceUri) + } + + topicName := path.Base(response.NotificationConfig.PubsubTopic) + if topicName != pubsubTopic { + return fmt.Errorf("hl7_v2_store 'NotificationConfig' not updated ('%s' != '%s'): %s", topicName, pubsubTopic, gcpResourceUri) + } + } + + if !foundResource { + return fmt.Errorf("google_healthcare_hl7_v2_store resource was missing") + } + return nil + } +} diff --git a/website/docs/r/healthcare_dataset.html.markdown b/website/docs/r/healthcare_dataset.html.markdown new file mode 100644 index 00000000000..26dcebd6a37 --- /dev/null +++ b/website/docs/r/healthcare_dataset.html.markdown @@ -0,0 +1,103 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by Magic Modules and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in +# .github/CONTRIBUTING.md. +# +# ---------------------------------------------------------------------------- +layout: "google" +page_title: "Google: google_healthcare_dataset" +sidebar_current: "docs-google-healthcare-dataset" +description: |- + A Healthcare `Dataset` is a toplevel logical grouping of `dicomStores`, `fhirStores` and `hl7V2Stores`. +--- + +# google\_healthcare\_dataset + +A Healthcare `Dataset` is a toplevel logical grouping of `dicomStores`, `fhirStores` and `hl7V2Stores`. + +~> **Warning:** This resource is in beta, and should be used with the terraform-provider-google-beta provider. +See [Provider Versions](https://terraform.io/docs/providers/google/provider_versions.html) for more details on beta resources. + +To get more information about Dataset, see: + +* [API documentation](https://cloud.google.com/healthcare/docs/reference/rest/v1beta1/projects.locations.datasets) +* How-to Guides + * [Creating a dataset](https://cloud.google.com/healthcare/docs/how-tos/datasets) + +## Example Usage - Healthcare Dataset Basic + + +```hcl +resource "google_healthcare_dataset" "default" { + name = "example-dataset" + location = "us-central1" + time_zone = "UTC" + provider = "google-beta" +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `name` - + (Required) + The resource name for the Dataset. + +* `location` - + (Required) + The location for the Dataset. + + +- - - + + +* `time_zone` - + (Optional) + The default timezone used by this dataset. Must be a either a valid IANA time zone name such as + "America/New_York" or empty, which defaults to UTC. This is used for parsing times in resources + (e.g., HL7 messages) where no explicit timezone is specified. + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the provider project is used. + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + + +* `self_link` - + The fully qualifed name of this dataset + + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 4 minutes. +- `update` - Default is 4 minutes. +- `delete` - Default is 4 minutes. + +## Import + +Dataset can be imported using any of these accepted formats: + +``` +$ terraform import -provider=google-beta google_healthcare_dataset.default projects/{{project}}/locations/{{location}}/datasets/{{name}} +$ terraform import -provider=google-beta google_healthcare_dataset.default {{project}}/{{location}}/{{name}} +$ terraform import -provider=google-beta google_healthcare_dataset.default {{location}}/{{name}} +``` + +-> If you're importing a resource with beta features, make sure to include `-provider=google-beta` +as an argument so that Terraform uses the correct provider to import your resource. diff --git a/website/docs/r/healthcare_dataset_iam.html.markdown b/website/docs/r/healthcare_dataset_iam.html.markdown new file mode 100644 index 00000000000..ce625e52f58 --- /dev/null +++ b/website/docs/r/healthcare_dataset_iam.html.markdown @@ -0,0 +1,116 @@ +--- +layout: "google" +page_title: "Google: google_healthcare_dataset_iam" +sidebar_current: "docs-google-healthcare-dataset-iam" +description: |- + Collection of resources to manage IAM policy for a Google Cloud Healthcare dataset. +--- + +# IAM policy for Google Cloud Healthcare dataset + +~> **Warning:** These resources are in beta, and should be used with the terraform-provider-google-beta provider. +See [Provider Versions](https://terraform.io/docs/providers/google/provider_versions.html) for more details on beta resources. + +Three different resources help you manage your IAM policy for Healthcare dataset. Each of these resources serves a different use case: + +* `google_healthcare_dataset_iam_policy`: Authoritative. Sets the IAM policy for the dataset and replaces any existing policy already attached. +* `google_healthcare_dataset_iam_binding`: Authoritative for a given role. Updates the IAM policy to grant a role to a list of members. Other roles within the IAM policy for the dataset are preserved. +* `google_healthcare_dataset_iam_member`: Non-authoritative. Updates the IAM policy to grant a role to a new member. Other members for the role for the dataset are preserved. + +~> **Note:** `google_healthcare_dataset_iam_policy` **cannot** be used in conjunction with `google_healthcare_dataset_iam_binding` and `google_healthcare_dataset_iam_member` or they will fight over what your policy should be. + +~> **Note:** `google_healthcare_dataset_iam_binding` resources **can be** used in conjunction with `google_healthcare_dataset_iam_member` resources **only if** they do not grant privilege to the same role. + +## google\_healthcare\_dataset\_iam\_policy + +```hcl +data "google_iam_policy" "admin" { + binding { + role = "roles/editor" + + members = [ + "user:jane@example.com", + ] + } +} + +resource "google_healthcare_dataset_iam_policy" "dataset" { + dataset_id = "your-dataset-id" + policy_data = "${data.google_iam_policy.admin.policy_data}" +} +``` + +## google\_healthcare\_dataset\_iam\_binding + +```hcl +resource "google_healthcare_dataset_iam_binding" "dataset" { + dataset_id = "your-dataset-id" + role = "roles/editor" + + members = [ + "user:jane@example.com", + ] +} +``` + +## google\_healthcare\_dataset\_iam\_member + +```hcl +resource "google_healthcare_dataset_iam_member" "dataset" { + dataset_id = "your-dataset-id" + role = "roles/editor" + member = "user:jane@example.com" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `dataset_id` - (Required) The dataset ID, in the form + `{project_id}/{location_name}/{dataset_name}` or + `{location_name}/{dataset_name}`. In the second form, the provider's + project setting will be used as a fallback. + +* `member/members` - (Required) Identities that will be granted the privilege in `role`. + Each entry can have one of the following values: + * **allUsers**: A special identifier that represents anyone who is on the internet; with or without a Google account. + * **allAuthenticatedUsers**: A special identifier that represents anyone who is authenticated with a Google account or a service account. + * **user:{emailid}**: An email address that represents a specific Google account. For example, alice@gmail.com or joe@example.com. + * **serviceAccount:{emailid}**: An email address that represents a service account. For example, my-other-app@appspot.gserviceaccount.com. + * **group:{emailid}**: An email address that represents a Google group. For example, admins@example.com. + * **domain:{domain}**: A G Suite domain (primary, instead of alias) name that represents all the users of that domain. For example, google.com or example.com. + +* `role` - (Required) The role that should be applied. Only one + `google_healthcare_dataset_iam_binding` can be used per role. Note that custom roles must be of the format + `[projects|organizations]/{parent-name}/roles/{role-name}`. + +* `policy_data` - (Required only by `google_healthcare_dataset_iam_policy`) The policy data generated by + a `google_iam_policy` data source. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are +exported: + +* `etag` - (Computed) The etag of the dataset's IAM policy. + +## Import + +IAM member imports use space-delimited identifiers; the resource in question, the role, and the account. This member resource can be imported using the `dataset_id`, role, and account e.g. + +``` +$ terraform import google_healthcare_dataset_iam_member.dataset_iam "your-project-id/location-name/dataset-name roles/viewer user:foo@example.com" +``` + +IAM binding imports use space-delimited identifiers; the resource in question and the role. This binding resource can be imported using the `dataset_id` and role, e.g. + +``` +$ terraform import google_healthcare_dataset_iam_binding.dataset_iam "your-project-id/location-name/dataset-name roles/viewer" +``` + +IAM policy imports use the identifier of the resource in question. This policy resource can be imported using the `dataset_id`, role, and account e.g. + +``` +$ terraform import google_healthcare_dataset_iam_policy.dataset_iam your-project-id/location-name/dataset-name +``` diff --git a/website/docs/r/healthcare_dicom_store.html.markdown b/website/docs/r/healthcare_dicom_store.html.markdown new file mode 100644 index 00000000000..1470562ea5c --- /dev/null +++ b/website/docs/r/healthcare_dicom_store.html.markdown @@ -0,0 +1,141 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by Magic Modules and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in +# .github/CONTRIBUTING.md. +# +# ---------------------------------------------------------------------------- +layout: "google" +page_title: "Google: google_healthcare_dicom_store" +sidebar_current: "docs-google-healthcare-dicom-store" +description: |- + A DicomStore is a datastore inside a Healthcare dataset that conforms to the DICOM + (https://www. +--- + +# google\_healthcare\_dicom\_store + +A DicomStore is a datastore inside a Healthcare dataset that conforms to the DICOM +(https://www.dicomstandard.org/about/) standard for Healthcare information exchange + +~> **Warning:** This resource is in beta, and should be used with the terraform-provider-google-beta provider. +See [Provider Versions](https://terraform.io/docs/providers/google/provider_versions.html) for more details on beta resources. + +To get more information about DicomStore, see: + +* [API documentation](https://cloud.google.com/healthcare/docs/reference/rest/v1beta1/projects.locations.datasets.dicomStores) +* How-to Guides + * [Creating a DICOM store](https://cloud.google.com/healthcare/docs/how-tos/dicom) + +## Example Usage - Healthcare Dicom Store Basic + + +```hcl +resource "google_healthcare_dicom_store" "default" { + name = "example-dicom-store" + dataset = "${google_healthcare_dataset.dataset.id}" + + notification_config { + pubsub_topic = "${google_pubsub_topic.topic.id}" + } + + labels = { + label1 = "labelvalue1" + } + provider = "google-beta" +} + +resource "google_pubsub_topic" "topic" { + name = "dicom-notifications" + provider = "google-beta" +} + +resource "google_healthcare_dataset" "dataset" { + name = "example-dataset" + location = "us-central1" + provider = "google-beta" +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `name` - + (Required) + The resource name for the DicomStore. + ** Changing this property may recreate the Dicom store (removing all data) ** + +* `dataset` - + (Required) + Identifies the dataset addressed by this request. Must be in the format + 'projects/{project}/locations/{location}/datasets/{dataset}' + + +- - - + + +* `labels` - + (Optional) + User-supplied key-value pairs used to organize DICOM stores. + Label keys must be between 1 and 63 characters long, have a UTF-8 encoding of maximum 128 bytes, and must + conform to the following PCRE regular expression: [\p{Ll}\p{Lo}][\p{Ll}\p{Lo}\p{N}_-]{0,62} + Label values are optional, must be between 1 and 63 characters long, have a UTF-8 encoding of maximum 128 + bytes, and must conform to the following PCRE regular expression: [\p{Ll}\p{Lo}\p{N}_-]{0,63} + No more than 64 labels can be associated with a given store. + An object containing a list of "key": value pairs. + Example: { "name": "wrench", "mass": "1.3kg", "count": "3" }. + +* `notification_config` - + (Optional) + A nested object resource Structure is documented below. + + +The `notification_config` block supports: + +* `pubsub_topic` - + (Required) + The Cloud Pub/Sub topic that notifications of changes are published on. Supplied by the client. + PubsubMessage.Data will contain the resource name. PubsubMessage.MessageId is the ID of this message. + It is guaranteed to be unique within the topic. PubsubMessage.PublishTime is the time at which the message + was published. Notifications are only sent if the topic is non-empty. Topic names must be scoped to a + project. cloud-healthcare@system.gserviceaccount.com must have publisher permissions on the given + Cloud Pub/Sub topic. Not having adequate permissions will cause the calls that send notifications to fail. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + + +* `self_link` - + The fully qualifed name of this dataset + + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 4 minutes. +- `update` - Default is 4 minutes. +- `delete` - Default is 4 minutes. + +## Import + +DicomStore can be imported using any of these accepted formats: + +``` +$ terraform import -provider=google-beta google_healthcare_dicom_store.default {{dataset}}/dicomStores/{{name}} +$ terraform import -provider=google-beta google_healthcare_dicom_store.default {{dataset}}/{{name}} +``` + +-> If you're importing a resource with beta features, make sure to include `-provider=google-beta` +as an argument so that Terraform uses the correct provider to import your resource. diff --git a/website/docs/r/healthcare_dicom_store_iam.html.markdown b/website/docs/r/healthcare_dicom_store_iam.html.markdown new file mode 100644 index 00000000000..b73dc58a1ae --- /dev/null +++ b/website/docs/r/healthcare_dicom_store_iam.html.markdown @@ -0,0 +1,116 @@ +--- +layout: "google" +page_title: "Google: google_healthcare_dicom_store_iam" +sidebar_current: "docs-google-healthcare-dicom-store-iam" +description: |- + Collection of resources to manage IAM policy for a Google Cloud Healthcare DICOM store. +--- + +# IAM policy for Google Cloud Healthcare DICOM store + +~> **Warning:** These resources are in beta, and should be used with the terraform-provider-google-beta provider. +See [Provider Versions](https://terraform.io/docs/providers/google/provider_versions.html) for more details on beta resources. + +Three different resources help you manage your IAM policy for Healthcare DICOM store. Each of these resources serves a different use case: + +* `google_healthcare_dicom_store_iam_policy`: Authoritative. Sets the IAM policy for the DICOM store and replaces any existing policy already attached. +* `google_healthcare_dicom_store_iam_binding`: Authoritative for a given role. Updates the IAM policy to grant a role to a list of members. Other roles within the IAM policy for the DICOM store are preserved. +* `google_healthcare_dicom_store_iam_member`: Non-authoritative. Updates the IAM policy to grant a role to a new member. Other members for the role for the DICOM store are preserved. + +~> **Note:** `google_healthcare_dicom_store_iam_policy` **cannot** be used in conjunction with `google_healthcare_dicom_store_iam_binding` and `google_healthcare_dicom_store_iam_member` or they will fight over what your policy should be. + +~> **Note:** `google_healthcare_dicom_store_iam_binding` resources **can be** used in conjunction with `google_healthcare_dicom_store_iam_member` resources **only if** they do not grant privilege to the same role. + +## google\_healthcare\_dicom\_store\_iam\_policy + +```hcl +data "google_iam_policy" "admin" { + binding { + role = "roles/editor" + + members = [ + "user:jane@example.com", + ] + } +} + +resource "google_healthcare_dicom_store_iam_policy" "dicom_store" { + dicom_store_id = "your-dicom-store-id" + policy_data = "${data.google_iam_policy.admin.policy_data}" +} +``` + +## google\_healthcare\_dicom\_store\_iam\_binding + +```hcl +resource "google_healthcare_dicom_store_iam_binding" "dicom_store" { + dicom_store_id = "your-dicom-store-id" + role = "roles/editor" + + members = [ + "user:jane@example.com", + ] +} +``` + +## google\_healthcare\_dicom\_store\_iam\_member + +```hcl +resource "google_healthcare_dicom_store_iam_member" "dicom_store" { + dicom_store_id = "your-dicom-store-id" + role = "roles/editor" + member = "user:jane@example.com" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `dicom_store_id` - (Required) The DICOM store ID, in the form + `{project_id}/{location_name}/{dataset_name}/{dicom_store_name}` or + `{location_name}/{dataset_name}/{dicom_store_name}`. In the second form, the provider's + project setting will be used as a fallback. + +* `member/members` - (Required) Identities that will be granted the privilege in `role`. + Each entry can have one of the following values: + * **allUsers**: A special identifier that represents anyone who is on the internet; with or without a Google account. + * **allAuthenticatedUsers**: A special identifier that represents anyone who is authenticated with a Google account or a service account. + * **user:{emailid}**: An email address that represents a specific Google account. For example, alice@gmail.com or joe@example.com. + * **serviceAccount:{emailid}**: An email address that represents a service account. For example, my-other-app@appspot.gserviceaccount.com. + * **group:{emailid}**: An email address that represents a Google group. For example, admins@example.com. + * **domain:{domain}**: A G Suite domain (primary, instead of alias) name that represents all the users of that domain. For example, google.com or example.com. + +* `role` - (Required) The role that should be applied. Only one + `google_healthcare_dicom_store_iam_binding` can be used per role. Note that custom roles must be of the format + `[projects|organizations]/{parent-name}/roles/{role-name}`. + +* `policy_data` - (Required only by `google_healthcare_dicom_store_iam_policy`) The policy data generated by + a `google_iam_policy` data source. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are +exported: + +* `etag` - (Computed) The etag of the DICOM store's IAM policy. + +## Import + +IAM member imports use space-delimited identifiers; the resource in question, the role, and the account. This member resource can be imported using the `dicom_store_id`, role, and account e.g. + +``` +$ terraform import google_healthcare_dicom_store_iam_member.dicom_store_iam "your-project-id/location-name/dataset-name/dicom-store-name roles/viewer user:foo@example.com" +``` + +IAM binding imports use space-delimited identifiers; the resource in question and the role. This binding resource can be imported using the `dicom_store_id` and role, e.g. + +``` +$ terraform import google_healthcare_dicom_store_iam_binding.dicom_store_iam "your-project-id/location-name/dataset-name/dicom-store-name roles/viewer" +``` + +IAM policy imports use the identifier of the resource in question. This policy resource can be imported using the `dicom_store_id`, role, and account e.g. + +``` +$ terraform import google_healthcare_dicom_store_iam_policy.dicom_store_iam your-project-id/location-name/dataset-name/dicom-store-name +``` diff --git a/website/docs/r/healthcare_fhir_store.html.markdown b/website/docs/r/healthcare_fhir_store.html.markdown new file mode 100644 index 00000000000..6d6f0f198c1 --- /dev/null +++ b/website/docs/r/healthcare_fhir_store.html.markdown @@ -0,0 +1,181 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by Magic Modules and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in +# .github/CONTRIBUTING.md. +# +# ---------------------------------------------------------------------------- +layout: "google" +page_title: "Google: google_healthcare_fhir_store" +sidebar_current: "docs-google-healthcare-fhir-store" +description: |- + A FhirStore is a datastore inside a Healthcare dataset that conforms to the FHIR (https://www. +--- + +# google\_healthcare\_fhir\_store + +A FhirStore is a datastore inside a Healthcare dataset that conforms to the FHIR (https://www.hl7.org/fhir/STU3/) +standard for Healthcare information exchange + +~> **Warning:** This resource is in beta, and should be used with the terraform-provider-google-beta provider. +See [Provider Versions](https://terraform.io/docs/providers/google/provider_versions.html) for more details on beta resources. + +To get more information about FhirStore, see: + +* [API documentation](https://cloud.google.com/healthcare/docs/reference/rest/v1beta1/projects.locations.datasets.fhirStores) +* How-to Guides + * [Creating a FHIR store](https://cloud.google.com/healthcare/docs/how-tos/fhir) + +## Example Usage - Healthcare Fhir Store Basic + + +```hcl +resource "google_healthcare_fhir_store" "default" { + name = "example-fhir-store" + dataset = "${google_healthcare_dataset.dataset.id}" + + enable_update_create = false + disable_referential_integrity = false + disable_resource_versioning = false + enable_history_import = false + + notification_config { + pubsub_topic = "${google_pubsub_topic.topic.id}" + } + + labels = { + label1 = "labelvalue1" + } + provider = "google-beta" +} + +resource "google_pubsub_topic" "topic" { + name = "fhir-notifications" + provider = "google-beta" +} + +resource "google_healthcare_dataset" "dataset" { + name = "example-dataset" + location = "us-central1" + provider = "google-beta" +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `name` - + (Required) + The resource name for the FhirStore. + ** Changing this property may recreate the FHIR store (removing all data) ** + +* `dataset` - + (Required) + Identifies the dataset addressed by this request. Must be in the format + 'projects/{project}/locations/{location}/datasets/{dataset}' + + +- - - + + +* `enable_update_create` - + (Optional) + Whether this FHIR store has the updateCreate capability. This determines if the client can use an Update + operation to create a new resource with a client-specified ID. If false, all IDs are server-assigned through + the Create operation and attempts to Update a non-existent resource will return errors. Please treat the audit + logs with appropriate levels of care if client-specified resource IDs contain sensitive data such as patient + identifiers, those IDs will be part of the FHIR resource path recorded in Cloud audit logs and Cloud Pub/Sub + notifications. + +* `disable_referential_integrity` - + (Optional) + Whether to disable referential integrity in this FHIR store. This field is immutable after FHIR store + creation. The default value is false, meaning that the API will enforce referential integrity and fail the + requests that will result in inconsistent state in the FHIR store. When this field is set to true, the API + will skip referential integrity check. Consequently, operations that rely on references, such as + Patient.get$everything, will not return all the results if broken references exist. + ** Changing this property may recreate the FHIR store (removing all data) ** + +* `disable_resource_versioning` - + (Optional) + Whether to disable resource versioning for this FHIR store. This field can not be changed after the creation + of FHIR store. If set to false, which is the default behavior, all write operations will cause historical + versions to be recorded automatically. The historical versions can be fetched through the history APIs, but + cannot be updated. If set to true, no historical versions will be kept. The server will send back errors for + attempts to read the historical versions. + ** Changing this property may recreate the FHIR store (removing all data) ** + +* `enable_history_import` - + (Optional) + Whether to allow the bulk import API to accept history bundles and directly insert historical resource + versions into the FHIR store. Importing resource histories creates resource interactions that appear to have + occurred in the past, which clients may not want to allow. If set to false, history bundles within an import + will fail with an error. + ** Changing this property may recreate the FHIR store (removing all data) ** + ** This property can be changed manually in the Google Cloud Healthcare admin console without recreating the FHIR store ** + +* `labels` - + (Optional) + User-supplied key-value pairs used to organize FHIR stores. + Label keys must be between 1 and 63 characters long, have a UTF-8 encoding of maximum 128 bytes, and must + conform to the following PCRE regular expression: [\p{Ll}\p{Lo}][\p{Ll}\p{Lo}\p{N}_-]{0,62} + Label values are optional, must be between 1 and 63 characters long, have a UTF-8 encoding of maximum 128 + bytes, and must conform to the following PCRE regular expression: [\p{Ll}\p{Lo}\p{N}_-]{0,63} + No more than 64 labels can be associated with a given store. + An object containing a list of "key": value pairs. + Example: { "name": "wrench", "mass": "1.3kg", "count": "3" }. + +* `notification_config` - + (Optional) + A nested object resource Structure is documented below. + + +The `notification_config` block supports: + +* `pubsub_topic` - + (Required) + The Cloud Pub/Sub topic that notifications of changes are published on. Supplied by the client. + PubsubMessage.Data will contain the resource name. PubsubMessage.MessageId is the ID of this message. + It is guaranteed to be unique within the topic. PubsubMessage.PublishTime is the time at which the message + was published. Notifications are only sent if the topic is non-empty. Topic names must be scoped to a + project. cloud-healthcare@system.gserviceaccount.com must have publisher permissions on the given + Cloud Pub/Sub topic. Not having adequate permissions will cause the calls that send notifications to fail. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + + +* `self_link` - + The fully qualifed name of this dataset + + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 4 minutes. +- `update` - Default is 4 minutes. +- `delete` - Default is 4 minutes. + +## Import + +FhirStore can be imported using any of these accepted formats: + +``` +$ terraform import -provider=google-beta google_healthcare_fhir_store.default {{dataset}}/fhirStores/{{name}} +$ terraform import -provider=google-beta google_healthcare_fhir_store.default {{dataset}}/{{name}} +``` + +-> If you're importing a resource with beta features, make sure to include `-provider=google-beta` +as an argument so that Terraform uses the correct provider to import your resource. diff --git a/website/docs/r/healthcare_fhir_store_iam.html.markdown b/website/docs/r/healthcare_fhir_store_iam.html.markdown new file mode 100644 index 00000000000..ba90acab0f4 --- /dev/null +++ b/website/docs/r/healthcare_fhir_store_iam.html.markdown @@ -0,0 +1,116 @@ +--- +layout: "google" +page_title: "Google: google_healthcare_fhir_store_iam" +sidebar_current: "docs-google-healthcare-fhir-store-iam" +description: |- + Collection of resources to manage IAM policy for a Google Cloud Healthcare FHIR store. +--- + +# IAM policy for Google Cloud Healthcare FHIR store + +~> **Warning:** These resources are in beta, and should be used with the terraform-provider-google-beta provider. +See [Provider Versions](https://terraform.io/docs/providers/google/provider_versions.html) for more details on beta resources. + +Three different resources help you manage your IAM policy for Healthcare FHIR store. Each of these resources serves a different use case: + +* `google_healthcare_fhir_store_iam_policy`: Authoritative. Sets the IAM policy for the FHIR store and replaces any existing policy already attached. +* `google_healthcare_fhir_store_iam_binding`: Authoritative for a given role. Updates the IAM policy to grant a role to a list of members. Other roles within the IAM policy for the FHIR store are preserved. +* `google_healthcare_fhir_store_iam_member`: Non-authoritative. Updates the IAM policy to grant a role to a new member. Other members for the role for the FHIR store are preserved. + +~> **Note:** `google_healthcare_fhir_store_iam_policy` **cannot** be used in conjunction with `google_healthcare_fhir_store_iam_binding` and `google_healthcare_fhir_store_iam_member` or they will fight over what your policy should be. + +~> **Note:** `google_healthcare_fhir_store_iam_binding` resources **can be** used in conjunction with `google_healthcare_fhir_store_iam_member` resources **only if** they do not grant privilege to the same role. + +## google\_healthcare\_fhir\_store\_iam\_policy + +```hcl +data "google_iam_policy" "admin" { + binding { + role = "roles/editor" + + members = [ + "user:jane@example.com", + ] + } +} + +resource "google_healthcare_fhir_store_iam_policy" "fhir_store" { + fhir_store_id = "your-fhir-store-id" + policy_data = "${data.google_iam_policy.admin.policy_data}" +} +``` + +## google\_healthcare\_fhir\_store\_iam\_binding + +```hcl +resource "google_healthcare_fhir_store_iam_binding" "fhir_store" { + fhir_store_id = "your-fhir-store-id" + role = "roles/editor" + + members = [ + "user:jane@example.com", + ] +} +``` + +## google\_healthcare\_fhir\_store\_iam\_member + +```hcl +resource "google_healthcare_fhir_store_iam_member" "fhir_store" { + fhir_store_id = "your-fhir-store-id" + role = "roles/editor" + member = "user:jane@example.com" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `fhir_store_id` - (Required) The FHIR store ID, in the form + `{project_id}/{location_name}/{dataset_name}/{fhir_store_name}` or + `{location_name}/{dataset_name}/{fhir_store_name}`. In the second form, the provider's + project setting will be used as a fallback. + +* `member/members` - (Required) Identities that will be granted the privilege in `role`. + Each entry can have one of the following values: + * **allUsers**: A special identifier that represents anyone who is on the internet; with or without a Google account. + * **allAuthenticatedUsers**: A special identifier that represents anyone who is authenticated with a Google account or a service account. + * **user:{emailid}**: An email address that represents a specific Google account. For example, alice@gmail.com or joe@example.com. + * **serviceAccount:{emailid}**: An email address that represents a service account. For example, my-other-app@appspot.gserviceaccount.com. + * **group:{emailid}**: An email address that represents a Google group. For example, admins@example.com. + * **domain:{domain}**: A G Suite domain (primary, instead of alias) name that represents all the users of that domain. For example, google.com or example.com. + +* `role` - (Required) The role that should be applied. Only one + `google_healthcare_fhir_store_iam_binding` can be used per role. Note that custom roles must be of the format + `[projects|organizations]/{parent-name}/roles/{role-name}`. + +* `policy_data` - (Required only by `google_healthcare_fhir_store_iam_policy`) The policy data generated by + a `google_iam_policy` data source. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are +exported: + +* `etag` - (Computed) The etag of the FHIR store's IAM policy. + +## Import + +IAM member imports use space-delimited identifiers; the resource in question, the role, and the account. This member resource can be imported using the `fhir_store_id`, role, and account e.g. + +``` +$ terraform import google_healthcare_fhir_store_iam_member.fhir_store_iam "your-project-id/location-name/dataset-name/fhir-store-name roles/viewer user:foo@example.com" +``` + +IAM binding imports use space-delimited identifiers; the resource in question and the role. This binding resource can be imported using the `fhir_store_id` and role, e.g. + +``` +$ terraform import google_healthcare_fhir_store_iam_binding.fhir_store_iam "your-project-id/location-name/dataset-name/fhir-store-name roles/viewer" +``` + +IAM policy imports use the identifier of the resource in question. This policy resource can be imported using the `fhir_store_id`, role, and account e.g. + +``` +$ terraform import google_healthcare_fhir_store_iam_policy.fhir_store_iam your-project-id/location-name/dataset-name/fhir-store-name +``` diff --git a/website/docs/r/healthcare_hl7_v2_store.html.markdown b/website/docs/r/healthcare_hl7_v2_store.html.markdown new file mode 100644 index 00000000000..b73af1e576a --- /dev/null +++ b/website/docs/r/healthcare_hl7_v2_store.html.markdown @@ -0,0 +1,160 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by Magic Modules and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in +# .github/CONTRIBUTING.md. +# +# ---------------------------------------------------------------------------- +layout: "google" +page_title: "Google: google_healthcare_hl7_v2_store" +sidebar_current: "docs-google-healthcare-hl7-v2-store" +description: |- + A Hl7V2Store is a datastore inside a Healthcare dataset that conforms to the FHIR (https://www. +--- + +# google\_healthcare\_hl7\_v2\_store + +A Hl7V2Store is a datastore inside a Healthcare dataset that conforms to the FHIR (https://www.hl7.org/hl7V2/STU3/) +standard for Healthcare information exchange + +~> **Warning:** This resource is in beta, and should be used with the terraform-provider-google-beta provider. +See [Provider Versions](https://terraform.io/docs/providers/google/provider_versions.html) for more details on beta resources. + +To get more information about Hl7V2Store, see: + +* [API documentation](https://cloud.google.com/healthcare/docs/reference/rest/v1beta1/projects.locations.datasets.hl7V2Stores) +* How-to Guides + * [Creating a HL7v2 Store](https://cloud.google.com/healthcare/docs/how-tos/hl7v2) + +## Example Usage - Healthcare Hl7 V2 Store Basic + + +```hcl +resource "google_healthcare_hl7_v2_store" "default" { + name = "example-hl7-v2-store" + dataset = "${google_healthcare_dataset.dataset.id}" + + parser_config { + allow_null_header = false + segment_terminator = "Jw==" + } + + notification_config { + pubsub_topic = "${google_pubsub_topic.topic.id}" + } + + labels = { + label1 = "labelvalue1" + } + provider = "google-beta" +} + +resource "google_pubsub_topic" "topic" { + name = "hl7-v2-notifications" + provider = "google-beta" +} + +resource "google_healthcare_dataset" "dataset" { + name = "example-dataset" + location = "us-central1" + provider = "google-beta" +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `name` - + (Required) + The resource name for the Hl7V2Store. + ** Changing this property may recreate the Hl7v2 store (removing all data) ** + +* `dataset` - + (Required) + Identifies the dataset addressed by this request. Must be in the format + 'projects/{project}/locations/{location}/datasets/{dataset}' + + +- - - + + +* `parser_config` - + (Optional) + A nested object resource Structure is documented below. + +* `labels` - + (Optional) + User-supplied key-value pairs used to organize HL7v2 stores. + Label keys must be between 1 and 63 characters long, have a UTF-8 encoding of maximum 128 bytes, and must + conform to the following PCRE regular expression: [\p{Ll}\p{Lo}][\p{Ll}\p{Lo}\p{N}_-]{0,62} + Label values are optional, must be between 1 and 63 characters long, have a UTF-8 encoding of maximum 128 + bytes, and must conform to the following PCRE regular expression: [\p{Ll}\p{Lo}\p{N}_-]{0,63} + No more than 64 labels can be associated with a given store. + An object containing a list of "key": value pairs. + Example: { "name": "wrench", "mass": "1.3kg", "count": "3" }. + +* `notification_config` - + (Optional) + A nested object resource Structure is documented below. + + +The `parser_config` block supports: + +* `allow_null_header` - + (Optional) + Determines whether messages with no header are allowed. + +* `segment_terminator` - + (Optional) + Byte(s) to be used as the segment terminator. If this is unset, '\r' will be used as segment terminator. + A base64-encoded string. + +The `notification_config` block supports: + +* `pubsub_topic` - + (Required) + The Cloud Pub/Sub topic that notifications of changes are published on. Supplied by the client. + PubsubMessage.Data will contain the resource name. PubsubMessage.MessageId is the ID of this message. + It is guaranteed to be unique within the topic. PubsubMessage.PublishTime is the time at which the message + was published. Notifications are only sent if the topic is non-empty. Topic names must be scoped to a + project. cloud-healthcare@system.gserviceaccount.com must have publisher permissions on the given + Cloud Pub/Sub topic. Not having adequate permissions will cause the calls that send notifications to fail. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + + +* `self_link` - + The fully qualifed name of this dataset + + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 4 minutes. +- `update` - Default is 4 minutes. +- `delete` - Default is 4 minutes. + +## Import + +Hl7V2Store can be imported using any of these accepted formats: + +``` +$ terraform import -provider=google-beta google_healthcare_hl7_v2_store.default {{dataset}}/hl7V2Stores/{{name}} +$ terraform import -provider=google-beta google_healthcare_hl7_v2_store.default {{dataset}}/{{name}} +``` + +-> If you're importing a resource with beta features, make sure to include `-provider=google-beta` +as an argument so that Terraform uses the correct provider to import your resource. diff --git a/website/docs/r/healthcare_hl7_v2_store_iam.html.markdown b/website/docs/r/healthcare_hl7_v2_store_iam.html.markdown new file mode 100644 index 00000000000..20402ef4e36 --- /dev/null +++ b/website/docs/r/healthcare_hl7_v2_store_iam.html.markdown @@ -0,0 +1,116 @@ +--- +layout: "google" +page_title: "Google: google_healthcare_hl7_v2_store_iam" +sidebar_current: "docs-google-healthcare-hl7-v2-store-iam" +description: |- + Collection of resources to manage IAM policy for a Google Cloud Healthcare HL7v2 store. +--- + +# IAM policy for Google Cloud Healthcare HL7v2 store + +~> **Warning:** These resources are in beta, and should be used with the terraform-provider-google-beta provider. +See [Provider Versions](https://terraform.io/docs/providers/google/provider_versions.html) for more details on beta resources. + +Three different resources help you manage your IAM policy for Healthcare HL7v2 store. Each of these resources serves a different use case: + +* `google_healthcare_hl7_v2_store_iam_policy`: Authoritative. Sets the IAM policy for the HL7v2 store and replaces any existing policy already attached. +* `google_healthcare_hl7_v2_store_iam_binding`: Authoritative for a given role. Updates the IAM policy to grant a role to a list of members. Other roles within the IAM policy for the HL7v2 store are preserved. +* `google_healthcare_hl7_v2_store_iam_member`: Non-authoritative. Updates the IAM policy to grant a role to a new member. Other members for the role for the HL7v2 store are preserved. + +~> **Note:** `google_healthcare_hl7_v2_store_iam_policy` **cannot** be used in conjunction with `google_healthcare_hl7_v2_store_iam_binding` and `google_healthcare_hl7_v2_store_iam_member` or they will fight over what your policy should be. + +~> **Note:** `google_healthcare_hl7_v2_store_iam_binding` resources **can be** used in conjunction with `google_healthcare_hl7_v2_store_iam_member` resources **only if** they do not grant privilege to the same role. + +## google\_healthcare\_hl7\_v2\_store\_iam\_policy + +```hcl +data "google_iam_policy" "admin" { + binding { + role = "roles/editor" + + members = [ + "user:jane@example.com", + ] + } +} + +resource "google_healthcare_hl7_v2_store_iam_policy" "hl7_v2_store" { + hl7_v2_store_id = "your-hl7-v2-store-id" + policy_data = "${data.google_iam_policy.admin.policy_data}" +} +``` + +## google\_healthcare\_hl7\_v2\_store\_iam\_binding + +```hcl +resource "google_healthcare_hl7_v2_store_iam_binding" "hl7_v2_store" { + hl7_v2_store_id = "your-hl7-v2-store-id" + role = "roles/editor" + + members = [ + "user:jane@example.com", + ] +} +``` + +## google\_healthcare\_hl7\_v2\_store\_iam\_member + +```hcl +resource "google_healthcare_hl7_v2_store_iam_member" "hl7_v2_store" { + hl7_v2_store_id = "your-hl7-v2-store-id" + role = "roles/editor" + member = "user:jane@example.com" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `hl7_v2_store_id` - (Required) The HL7v2 store ID, in the form + `{project_id}/{location_name}/{dataset_name}/{hl7_v2_store_name}` or + `{location_name}/{dataset_name}/{hl7_v2_store_name}`. In the second form, the provider's + project setting will be used as a fallback. + +* `member/members` - (Required) Identities that will be granted the privilege in `role`. + Each entry can have one of the following values: + * **allUsers**: A special identifier that represents anyone who is on the internet; with or without a Google account. + * **allAuthenticatedUsers**: A special identifier that represents anyone who is authenticated with a Google account or a service account. + * **user:{emailid}**: An email address that represents a specific Google account. For example, alice@gmail.com or joe@example.com. + * **serviceAccount:{emailid}**: An email address that represents a service account. For example, my-other-app@appspot.gserviceaccount.com. + * **group:{emailid}**: An email address that represents a Google group. For example, admins@example.com. + * **domain:{domain}**: A G Suite domain (primary, instead of alias) name that represents all the users of that domain. For example, google.com or example.com. + +* `role` - (Required) The role that should be applied. Only one + `google_healthcare_hl7_v2_store_iam_binding` can be used per role. Note that custom roles must be of the format + `[projects|organizations]/{parent-name}/roles/{role-name}`. + +* `policy_data` - (Required only by `google_healthcare_hl7_v2_store_iam_policy`) The policy data generated by + a `google_iam_policy` data source. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are +exported: + +* `etag` - (Computed) The etag of the HL7v2 store's IAM policy. + +## Import + +IAM member imports use space-delimited identifiers; the resource in question, the role, and the account. This member resource can be imported using the `hl7_v2_store_id`, role, and account e.g. + +``` +$ terraform import google_healthcare_hl7_v2_store_iam_member.hl7_v2_store_iam "your-project-id/location-name/dataset-name/hl7-v2-store-name roles/viewer user:foo@example.com" +``` + +IAM binding imports use space-delimited identifiers; the resource in question and the role. This binding resource can be imported using the `hl7_v2_store_id` and role, e.g. + +``` +$ terraform import google_healthcare_hl7_v2_store_iam_binding.hl7_v2_store_iam "your-project-id/location-name/dataset-name/hl7-v2-store-name roles/viewer" +``` + +IAM policy imports use the identifier of the resource in question. This policy resource can be imported using the `hl7_v2_store_id`, role, and account e.g. + +``` +$ terraform import google_healthcare_hl7_v2_store_iam_policy.hl7_v2_store_iam your-project-id/location-name/dataset-name/hl7-v2-store-name +``` diff --git a/website/google.erb b/website/google.erb index 2f81b674ac7..ef8b9683908 100644 --- a/website/google.erb +++ b/website/google.erb @@ -761,6 +761,59 @@ + > + Google Healthcare Resources + + > Google IAP Resources