Skip to content

Commit 2447c32

Browse files
FHIR store streaming config (#3611) (#2145)
* FHIR store streaming config * make recursiveStructureDepth required Signed-off-by: Modular Magician <[email protected]>
1 parent a34f99a commit 2447c32

File tree

4 files changed

+446
-0
lines changed

4 files changed

+446
-0
lines changed

.changelog/3611.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
`healthcare`: Added support for `streaming_configs` to `google_healthcare_fhir_store`
3+
```

google-beta/resource_healthcare_fhir_store.go

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"fmt"
1919
"log"
2020
"reflect"
21+
"strconv"
2122
"strings"
2223
"time"
2324

@@ -143,6 +144,76 @@ Cloud Pub/Sub topic. Not having adequate permissions will cause the calls that s
143144
},
144145
},
145146
},
147+
"stream_configs": {
148+
Type: schema.TypeList,
149+
Optional: true,
150+
Description: `A list of streaming configs that configure the destinations of streaming export for every resource mutation in
151+
this FHIR store. Each store is allowed to have up to 10 streaming configs. After a new config is added, the next
152+
resource mutation is streamed to the new location in addition to the existing ones. When a location is removed
153+
from the list, the server stops streaming to that location. Before adding a new config, you must add the required
154+
bigquery.dataEditor role to your project's Cloud Healthcare Service Agent service account. Some lag (typically on
155+
the order of dozens of seconds) is expected before the results show up in the streaming destination.`,
156+
Elem: &schema.Resource{
157+
Schema: map[string]*schema.Schema{
158+
"bigquery_destination": {
159+
Type: schema.TypeList,
160+
Required: true,
161+
Description: `The destination BigQuery structure that contains both the dataset location and corresponding schema config.
162+
The output is organized in one table per resource type. The server reuses the existing tables (if any) that
163+
are named after the resource types, e.g. "Patient", "Observation". When there is no existing table for a given
164+
resource type, the server attempts to create one.
165+
See the [streaming config reference](https://cloud.google.com/healthcare/docs/reference/rest/v1beta1/projects.locations.datasets.fhirStores#streamconfig) for more details.`,
166+
MaxItems: 1,
167+
Elem: &schema.Resource{
168+
Schema: map[string]*schema.Schema{
169+
"dataset_uri": {
170+
Type: schema.TypeString,
171+
Required: true,
172+
Description: `BigQuery URI to a dataset, up to 2000 characters long, in the format bq://projectId.bqDatasetId`,
173+
},
174+
"schema_config": {
175+
Type: schema.TypeList,
176+
Required: true,
177+
Description: `The configuration for the exported BigQuery schema.`,
178+
MaxItems: 1,
179+
Elem: &schema.Resource{
180+
Schema: map[string]*schema.Schema{
181+
"recursive_structure_depth": {
182+
Type: schema.TypeInt,
183+
Required: true,
184+
Description: `The depth for all recursive structures in the output analytics schema. For example, concept in the CodeSystem
185+
resource is a recursive structure; when the depth is 2, the CodeSystem table will have a column called
186+
concept.concept but not concept.concept.concept. If not specified or set to 0, the server will use the default
187+
value 2. The maximum depth allowed is 5.`,
188+
},
189+
"schema_type": {
190+
Type: schema.TypeString,
191+
Optional: true,
192+
ValidateFunc: validation.StringInSlice([]string{"ANALYTICS", ""}, false),
193+
Description: `Specifies the output schema type. Only ANALYTICS is supported at this time.
194+
* ANALYTICS: Analytics schema defined by the FHIR community.
195+
See https://github.com/FHIR/sql-on-fhir/blob/master/sql-on-fhir.md. Default value: "ANALYTICS" Possible values: ["ANALYTICS"]`,
196+
Default: "ANALYTICS",
197+
},
198+
},
199+
},
200+
},
201+
},
202+
},
203+
},
204+
"resource_types": {
205+
Type: schema.TypeList,
206+
Optional: true,
207+
Description: `Supply a FHIR resource type (such as "Patient" or "Observation"). See
208+
https://www.hl7.org/fhir/valueset-resource-types.html for a list of all FHIR resource types. The server treats
209+
an empty list as an intent to stream all the supported resource types in this FHIR store.`,
210+
Elem: &schema.Schema{
211+
Type: schema.TypeString,
212+
},
213+
},
214+
},
215+
},
216+
},
146217
"version": {
147218
Type: schema.TypeString,
148219
Optional: true,
@@ -212,6 +283,12 @@ func resourceHealthcareFhirStoreCreate(d *schema.ResourceData, meta interface{})
212283
} else if v, ok := d.GetOkExists("notification_config"); !isEmptyValue(reflect.ValueOf(notificationConfigProp)) && (ok || !reflect.DeepEqual(v, notificationConfigProp)) {
213284
obj["notificationConfig"] = notificationConfigProp
214285
}
286+
streamConfigsProp, err := expandHealthcareFhirStoreStreamConfigs(d.Get("stream_configs"), d, config)
287+
if err != nil {
288+
return err
289+
} else if v, ok := d.GetOkExists("stream_configs"); !isEmptyValue(reflect.ValueOf(streamConfigsProp)) && (ok || !reflect.DeepEqual(v, streamConfigsProp)) {
290+
obj["streamConfigs"] = streamConfigsProp
291+
}
215292

216293
url, err := replaceVars(d, config, "{{HealthcareBasePath}}{{dataset}}/fhirStores?fhirStoreId={{name}}")
217294
if err != nil {
@@ -285,6 +362,9 @@ func resourceHealthcareFhirStoreRead(d *schema.ResourceData, meta interface{}) e
285362
if err := d.Set("notification_config", flattenHealthcareFhirStoreNotificationConfig(res["notificationConfig"], d, config)); err != nil {
286363
return fmt.Errorf("Error reading FhirStore: %s", err)
287364
}
365+
if err := d.Set("stream_configs", flattenHealthcareFhirStoreStreamConfigs(res["streamConfigs"], d, config)); err != nil {
366+
return fmt.Errorf("Error reading FhirStore: %s", err)
367+
}
288368

289369
return nil
290370
}
@@ -311,6 +391,12 @@ func resourceHealthcareFhirStoreUpdate(d *schema.ResourceData, meta interface{})
311391
} else if v, ok := d.GetOkExists("notification_config"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, notificationConfigProp)) {
312392
obj["notificationConfig"] = notificationConfigProp
313393
}
394+
streamConfigsProp, err := expandHealthcareFhirStoreStreamConfigs(d.Get("stream_configs"), d, config)
395+
if err != nil {
396+
return err
397+
} else if v, ok := d.GetOkExists("stream_configs"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, streamConfigsProp)) {
398+
obj["streamConfigs"] = streamConfigsProp
399+
}
314400

315401
url, err := replaceVars(d, config, "{{HealthcareBasePath}}{{dataset}}/fhirStores/{{name}}")
316402
if err != nil {
@@ -331,6 +417,10 @@ func resourceHealthcareFhirStoreUpdate(d *schema.ResourceData, meta interface{})
331417
if d.HasChange("notification_config") {
332418
updateMask = append(updateMask, "notificationConfig")
333419
}
420+
421+
if d.HasChange("stream_configs") {
422+
updateMask = append(updateMask, "streamConfigs")
423+
}
334424
// updateMask is a URL parameter but not present in the schema, so replaceVars
335425
// won't set it
336426
url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")})
@@ -426,6 +516,84 @@ func flattenHealthcareFhirStoreNotificationConfigPubsubTopic(v interface{}, d *s
426516
return v
427517
}
428518

519+
func flattenHealthcareFhirStoreStreamConfigs(v interface{}, d *schema.ResourceData, config *Config) interface{} {
520+
if v == nil {
521+
return v
522+
}
523+
l := v.([]interface{})
524+
transformed := make([]interface{}, 0, len(l))
525+
for _, raw := range l {
526+
original := raw.(map[string]interface{})
527+
if len(original) < 1 {
528+
// Do not include empty json objects coming back from the api
529+
continue
530+
}
531+
transformed = append(transformed, map[string]interface{}{
532+
"resource_types": flattenHealthcareFhirStoreStreamConfigsResourceTypes(original["resourceTypes"], d, config),
533+
"bigquery_destination": flattenHealthcareFhirStoreStreamConfigsBigqueryDestination(original["bigqueryDestination"], d, config),
534+
})
535+
}
536+
return transformed
537+
}
538+
func flattenHealthcareFhirStoreStreamConfigsResourceTypes(v interface{}, d *schema.ResourceData, config *Config) interface{} {
539+
return v
540+
}
541+
542+
func flattenHealthcareFhirStoreStreamConfigsBigqueryDestination(v interface{}, d *schema.ResourceData, config *Config) interface{} {
543+
if v == nil {
544+
return nil
545+
}
546+
original := v.(map[string]interface{})
547+
if len(original) == 0 {
548+
return nil
549+
}
550+
transformed := make(map[string]interface{})
551+
transformed["dataset_uri"] =
552+
flattenHealthcareFhirStoreStreamConfigsBigqueryDestinationDatasetUri(original["datasetUri"], d, config)
553+
transformed["schema_config"] =
554+
flattenHealthcareFhirStoreStreamConfigsBigqueryDestinationSchemaConfig(original["schemaConfig"], d, config)
555+
return []interface{}{transformed}
556+
}
557+
func flattenHealthcareFhirStoreStreamConfigsBigqueryDestinationDatasetUri(v interface{}, d *schema.ResourceData, config *Config) interface{} {
558+
return v
559+
}
560+
561+
func flattenHealthcareFhirStoreStreamConfigsBigqueryDestinationSchemaConfig(v interface{}, d *schema.ResourceData, config *Config) interface{} {
562+
if v == nil {
563+
return nil
564+
}
565+
original := v.(map[string]interface{})
566+
if len(original) == 0 {
567+
return nil
568+
}
569+
transformed := make(map[string]interface{})
570+
transformed["schema_type"] =
571+
flattenHealthcareFhirStoreStreamConfigsBigqueryDestinationSchemaConfigSchemaType(original["schemaType"], d, config)
572+
transformed["recursive_structure_depth"] =
573+
flattenHealthcareFhirStoreStreamConfigsBigqueryDestinationSchemaConfigRecursiveStructureDepth(original["recursiveStructureDepth"], d, config)
574+
return []interface{}{transformed}
575+
}
576+
func flattenHealthcareFhirStoreStreamConfigsBigqueryDestinationSchemaConfigSchemaType(v interface{}, d *schema.ResourceData, config *Config) interface{} {
577+
return v
578+
}
579+
580+
func flattenHealthcareFhirStoreStreamConfigsBigqueryDestinationSchemaConfigRecursiveStructureDepth(v interface{}, d *schema.ResourceData, config *Config) interface{} {
581+
// Handles the string fixed64 format
582+
if strVal, ok := v.(string); ok {
583+
if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil {
584+
return intVal
585+
}
586+
}
587+
588+
// number values are represented as float64
589+
if floatVal, ok := v.(float64); ok {
590+
intVal := int(floatVal)
591+
return intVal
592+
}
593+
594+
return v // let terraform core handle it otherwise
595+
}
596+
429597
func expandHealthcareFhirStoreName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
430598
return v, nil
431599
}
@@ -484,6 +652,103 @@ func expandHealthcareFhirStoreNotificationConfigPubsubTopic(v interface{}, d Ter
484652
return v, nil
485653
}
486654

655+
func expandHealthcareFhirStoreStreamConfigs(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
656+
l := v.([]interface{})
657+
req := make([]interface{}, 0, len(l))
658+
for _, raw := range l {
659+
if raw == nil {
660+
continue
661+
}
662+
original := raw.(map[string]interface{})
663+
transformed := make(map[string]interface{})
664+
665+
transformedResourceTypes, err := expandHealthcareFhirStoreStreamConfigsResourceTypes(original["resource_types"], d, config)
666+
if err != nil {
667+
return nil, err
668+
} else if val := reflect.ValueOf(transformedResourceTypes); val.IsValid() && !isEmptyValue(val) {
669+
transformed["resourceTypes"] = transformedResourceTypes
670+
}
671+
672+
transformedBigqueryDestination, err := expandHealthcareFhirStoreStreamConfigsBigqueryDestination(original["bigquery_destination"], d, config)
673+
if err != nil {
674+
return nil, err
675+
} else if val := reflect.ValueOf(transformedBigqueryDestination); val.IsValid() && !isEmptyValue(val) {
676+
transformed["bigqueryDestination"] = transformedBigqueryDestination
677+
}
678+
679+
req = append(req, transformed)
680+
}
681+
return req, nil
682+
}
683+
684+
func expandHealthcareFhirStoreStreamConfigsResourceTypes(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
685+
return v, nil
686+
}
687+
688+
func expandHealthcareFhirStoreStreamConfigsBigqueryDestination(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
689+
l := v.([]interface{})
690+
if len(l) == 0 || l[0] == nil {
691+
return nil, nil
692+
}
693+
raw := l[0]
694+
original := raw.(map[string]interface{})
695+
transformed := make(map[string]interface{})
696+
697+
transformedDatasetUri, err := expandHealthcareFhirStoreStreamConfigsBigqueryDestinationDatasetUri(original["dataset_uri"], d, config)
698+
if err != nil {
699+
return nil, err
700+
} else if val := reflect.ValueOf(transformedDatasetUri); val.IsValid() && !isEmptyValue(val) {
701+
transformed["datasetUri"] = transformedDatasetUri
702+
}
703+
704+
transformedSchemaConfig, err := expandHealthcareFhirStoreStreamConfigsBigqueryDestinationSchemaConfig(original["schema_config"], d, config)
705+
if err != nil {
706+
return nil, err
707+
} else if val := reflect.ValueOf(transformedSchemaConfig); val.IsValid() && !isEmptyValue(val) {
708+
transformed["schemaConfig"] = transformedSchemaConfig
709+
}
710+
711+
return transformed, nil
712+
}
713+
714+
func expandHealthcareFhirStoreStreamConfigsBigqueryDestinationDatasetUri(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
715+
return v, nil
716+
}
717+
718+
func expandHealthcareFhirStoreStreamConfigsBigqueryDestinationSchemaConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
719+
l := v.([]interface{})
720+
if len(l) == 0 || l[0] == nil {
721+
return nil, nil
722+
}
723+
raw := l[0]
724+
original := raw.(map[string]interface{})
725+
transformed := make(map[string]interface{})
726+
727+
transformedSchemaType, err := expandHealthcareFhirStoreStreamConfigsBigqueryDestinationSchemaConfigSchemaType(original["schema_type"], d, config)
728+
if err != nil {
729+
return nil, err
730+
} else if val := reflect.ValueOf(transformedSchemaType); val.IsValid() && !isEmptyValue(val) {
731+
transformed["schemaType"] = transformedSchemaType
732+
}
733+
734+
transformedRecursiveStructureDepth, err := expandHealthcareFhirStoreStreamConfigsBigqueryDestinationSchemaConfigRecursiveStructureDepth(original["recursive_structure_depth"], d, config)
735+
if err != nil {
736+
return nil, err
737+
} else if val := reflect.ValueOf(transformedRecursiveStructureDepth); val.IsValid() && !isEmptyValue(val) {
738+
transformed["recursiveStructureDepth"] = transformedRecursiveStructureDepth
739+
}
740+
741+
return transformed, nil
742+
}
743+
744+
func expandHealthcareFhirStoreStreamConfigsBigqueryDestinationSchemaConfigSchemaType(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
745+
return v, nil
746+
}
747+
748+
func expandHealthcareFhirStoreStreamConfigsBigqueryDestinationSchemaConfigRecursiveStructureDepth(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
749+
return v, nil
750+
}
751+
487752
func resourceHealthcareFhirStoreDecoder(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) {
488753
// Take the returned long form of the name and use it as `self_link`.
489754
// Then modify the name to be the user specified form.

0 commit comments

Comments
 (0)