@@ -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+
429597func 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+
487752func 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