diff --git a/google/resource_endpoints_service.go b/google/resource_endpoints_service.go index 358b87f8..ccbee59e 100644 --- a/google/resource_endpoints_service.go +++ b/google/resource_endpoints_service.go @@ -14,6 +14,11 @@ func resourceEndpointsService() *schema.Resource { Read: resourceEndpointsServiceRead, Delete: resourceEndpointsServiceDelete, Update: resourceEndpointsServiceUpdate, + + // Migrates protoc_output -> protoc_output_base64. + SchemaVersion: 1, + MigrateState: migrateEndpointsService, + Schema: map[string]*schema.Schema{ "service_name": &schema.Schema{ Type: schema.TypeString, @@ -23,13 +28,18 @@ func resourceEndpointsService() *schema.Resource { "openapi_config": &schema.Schema{ Type: schema.TypeString, Optional: true, - ConflictsWith: []string{"grpc_config", "protoc_output"}, + ConflictsWith: []string{"grpc_config", "protoc_output_base64"}, }, "grpc_config": &schema.Schema{ Type: schema.TypeString, Optional: true, }, "protoc_output": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Deprecated: "Please use protoc_output_base64 instead.", + }, + "protoc_output_base64": &schema.Schema{ Type: schema.TypeString, Optional: true, }, @@ -134,7 +144,7 @@ func getGRPCConfigSource(serviceConfig, protoConfig string) servicemanagement.Co FilePath: "heredoc.yaml", } protoConfigfile := servicemanagement.ConfigFile{ - FileContents: base64.StdEncoding.EncodeToString([]byte(protoConfig)), + FileContents: protoConfig, FileType: "FILE_DESCRIPTOR_SET_PROTO", FilePath: "api_def.pb", } @@ -193,11 +203,20 @@ func resourceEndpointsServiceUpdate(d *schema.ResourceData, meta interface{}) er source = getOpenAPIConfigSource(openapiConfig.(string)) } else { grpcConfig, gok := d.GetOk("grpc_config") - protocOutput, pok := d.GetOk("protoc_output") + protocOutput, pok := d.GetOk("protoc_output_base64") + + // Support conversion from raw file -> base64 until the field is totally removed. + if !pok { + protocOutput, pok = d.GetOk("protoc_output") + if pok { + protocOutput = base64.StdEncoding.EncodeToString([]byte(protocOutput.(string))) + } + } + if gok && pok { source = getGRPCConfigSource(grpcConfig.(string), protocOutput.(string)) } else { - return errors.New("Could not decypher config - please either set openapi_config or set both grpc_config and protoc_output.") + return errors.New("Could not decypher config - please either set openapi_config or set both grpc_config and protoc_output_base64.") } } diff --git a/google/resource_endpoints_service_migration.go b/google/resource_endpoints_service_migration.go new file mode 100644 index 00000000..341f67a9 --- /dev/null +++ b/google/resource_endpoints_service_migration.go @@ -0,0 +1,23 @@ +package google + +import ( + "encoding/base64" + "fmt" + "github.com/hashicorp/terraform/terraform" + "log" +) + +func migrateEndpointsService(v int, is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) { + switch v { + case 0: + if is.Attributes["protoc_output"] == "" { + log.Println("[DEBUG] Nothing to migrate to V1.") + return is, nil + } + is.Attributes["protoc_output_base64"] = base64.StdEncoding.EncodeToString([]byte(is.Attributes["protoc_output"])) + is.Attributes["protoc_output"] = "" + return is, nil + default: + return nil, fmt.Errorf("Unexpected schema version: %d", v) + } +} diff --git a/google/resource_endpoints_service_test.go b/google/resource_endpoints_service_test.go index c48705d7..4df87cb9 100644 --- a/google/resource_endpoints_service_test.go +++ b/google/resource_endpoints_service_test.go @@ -1,6 +1,7 @@ package google import ( + "reflect" "testing" "fmt" @@ -42,6 +43,58 @@ func TestAccEndpointsService_grpc(t *testing.T) { }) } +func TestEndpointsService_grpcMigrateState(t *testing.T) { + cases := map[string]struct { + StateVersion int + Attributes map[string]string + ExpectedAttributes map[string]string + Meta interface{} + }{ + "update from protoc_output to protoc_output_base64": { + StateVersion: 0, + Attributes: map[string]string{ + "protoc_output": "123456789", + "name": "testcase", + }, + ExpectedAttributes: map[string]string{ + "protoc_output_base64": "MTIzNDU2Nzg5", + "protoc_output": "", + "name": "testcase", + }, + Meta: &Config{Project: "gcp-project", Region: "us-central1"}, + }, + "update from non-protoc_output": { + StateVersion: 0, + Attributes: map[string]string{ + "openapi_config": "foo bar baz", + "name": "testcase-2", + }, + ExpectedAttributes: map[string]string{ + "openapi_config": "foo bar baz", + "name": "testcase-2", + }, + Meta: &Config{Project: "gcp-project", Region: "us-central1"}, + }, + } + + for tn, tc := range cases { + is := &terraform.InstanceState{ + ID: tc.Attributes["name"], + Attributes: tc.Attributes, + } + + is, err := migrateEndpointsService(tc.StateVersion, is, tc.Meta) + + if err != nil { + t.Fatalf("bad: %s, err: %#v", tn, err) + } + + if !reflect.DeepEqual(is.Attributes, tc.ExpectedAttributes) { + t.Fatalf("Attributes should be `%s` but are `%s`", tc.ExpectedAttributes, is.Attributes) + } + } +} + func testAccEndpointsService_basic(random_name string) string { return fmt.Sprintf(`resource "google_endpoints_service" "endpoints_service" { service_name = "%s.endpoints.%s.cloud.goog" @@ -103,7 +156,7 @@ usage: - selector: endpoints.examples.bookstore.Bookstore.ListShelves allow_unregistered_calls: true EOF - protoc_output = "${file("test-fixtures/test_api_descriptor.pb")}" + protoc_output_base64 = "${base64encode(file("test-fixtures/test_api_descriptor.pb"))}" }`, random_name, getTestProjectFromEnv(), getTestProjectFromEnv(), random_name, getTestProjectFromEnv()) } diff --git a/website/docs/r/endpoints_service.html.markdown b/website/docs/r/endpoints_service.html.markdown index 504cc9d5..ed742049 100644 --- a/website/docs/r/endpoints_service.html.markdown +++ b/website/docs/r/endpoints_service.html.markdown @@ -35,7 +35,8 @@ The following arguments are supported: * `service_name`: (Required) The name of the service. Usually of the form `$apiname.endpoints.$projectid.cloud.goog`. * `openapi_config`: (Optional) The full text of the OpenAPI YAML configuration as described [here](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md). Either this, or *both* of `grpc_config` and `protoc_output` must be specified. * `grpc_config`: (Optional) The full text of the Service Config YAML file (Example located [here](https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/endpoints/bookstore-grpc/api_config.yaml)). If provided, must also provide `protoc_output`. `open_api` config must *not* be provided. -* `protoc_output`: (Optional) The full contents of the Service Descriptor File generated by protoc. This should be a compiled .pb file. +* `protoc_output_base64`: (Optional) The full contents of the Service Descriptor File generated by protoc. This should be a compiled .pb file, base64-encoded. +* `protoc_output`: (Deprecated) The full contents of the Service Descriptor File generated by protoc. This should be a compiled .pb file. Use `protoc_output_base64` instead to prevent a permanent diff from the statefile's munging of non-UTF8 bytes. * `project`: (Optional) The project ID that the service belongs to. If not provided, provider project is used. ## Attributes Reference