mirror of
https://github.com/letic/terraform-provider-google.git
synced 2024-07-05 17:52:38 +00:00
Add cloud endpoints resource (#933)
OpenAPI & gRPC Endpoints on Compute Engine. New Resource: - Endpoints Service Create/Read/Delete - Example terraform config
This commit is contained in:
parent
fec2f0bc02
commit
9d3e64cdaf
3
examples/endpoints-on-compute-engine/.gitignore
vendored
Normal file
3
examples/endpoints-on-compute-engine/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
terraform.tfstate
|
||||||
|
terraform.tfstate.backup
|
||||||
|
terraform.tfvars
|
136
examples/endpoints-on-compute-engine/main.tf
Normal file
136
examples/endpoints-on-compute-engine/main.tf
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
provider "google" {
|
||||||
|
region = "${var.region}"
|
||||||
|
credentials = "${file("${var.credentials_file_path}")}"
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "random" {}
|
||||||
|
|
||||||
|
resource "random_id" "project_name" {
|
||||||
|
byte_length = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_project" "endpoints_project" {
|
||||||
|
name = "Endpoints Project"
|
||||||
|
project_id = "tf-ep-${random_id.project_name.hex}"
|
||||||
|
org_id = "${var.org_id}"
|
||||||
|
billing_account = "${var.billing_account_id}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_project_service" "endpoints_project" {
|
||||||
|
project = "${google_project.endpoints_project.project_id}"
|
||||||
|
service = "compute.googleapis.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_project_service" "endpoints_project_sm" {
|
||||||
|
project = "${google_project.endpoints_project.project_id}"
|
||||||
|
service = "servicemanagement.googleapis.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_endpoints_service" "endpoints_service" {
|
||||||
|
service_name = "echo-api.endpoints.${google_project.endpoints_project.project_id}.cloud.goog"
|
||||||
|
project = "${google_project.endpoints_project.project_id}"
|
||||||
|
|
||||||
|
config_text = <<EOF
|
||||||
|
swagger: "2.0"
|
||||||
|
info:
|
||||||
|
description: "A simple Google Cloud Endpoints API example."
|
||||||
|
title: "Endpoints Example"
|
||||||
|
version: "1.0.0"
|
||||||
|
host: "echo-api.endpoints.${google_project.endpoints_project.project_id}.cloud.goog"
|
||||||
|
basePath: "/"
|
||||||
|
consumes:
|
||||||
|
- "application/json"
|
||||||
|
produces:
|
||||||
|
- "application/json"
|
||||||
|
schemes:
|
||||||
|
- "https"
|
||||||
|
paths:
|
||||||
|
"/echo":
|
||||||
|
post:
|
||||||
|
description: "Echo back a given message."
|
||||||
|
operationId: "echo"
|
||||||
|
produces:
|
||||||
|
- "application/json"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "Echo"
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/echoMessage"
|
||||||
|
parameters:
|
||||||
|
- description: "Message to echo"
|
||||||
|
in: body
|
||||||
|
name: message
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/echoMessage"
|
||||||
|
security:
|
||||||
|
- api_key: []
|
||||||
|
definitions:
|
||||||
|
echoMessage:
|
||||||
|
properties:
|
||||||
|
message:
|
||||||
|
type: "string"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
depends_on = ["google_project_service.endpoints_project_sm"]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_compute_network" "network" {
|
||||||
|
name = "ep-network"
|
||||||
|
auto_create_subnetworks = "true"
|
||||||
|
project = "${google_project.endpoints_project.project_id}"
|
||||||
|
depends_on = ["google_project_service.endpoints_project"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Allow the hosted network to be hit over ICMP, SSH, and HTTP.
|
||||||
|
resource "google_compute_firewall" "network" {
|
||||||
|
name = "allow-ssh-and-icmp"
|
||||||
|
network = "${google_compute_network.network.self_link}"
|
||||||
|
project = "${google_compute_network.network.project}"
|
||||||
|
|
||||||
|
allow {
|
||||||
|
protocol = "icmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
allow {
|
||||||
|
protocol = "tcp"
|
||||||
|
ports = ["22", "80"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_compute_instance" "project_1_vm" {
|
||||||
|
name = "tf-ep-vm"
|
||||||
|
project = "${google_project.endpoints_project.project_id}"
|
||||||
|
machine_type = "f1-micro"
|
||||||
|
zone = "${var.region_zone}"
|
||||||
|
|
||||||
|
boot_disk {
|
||||||
|
initialize_params {
|
||||||
|
image = "projects/debian-cloud/global/images/family/debian-8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata {
|
||||||
|
endpoints-service-name = "${google_endpoints_service.endpoints_service.service_name}"
|
||||||
|
endpoints-service-config-id = "${google_endpoints_service.endpoints_service.config_id}"
|
||||||
|
startup-script = "${file("scripts/install-vm.sh")}"
|
||||||
|
}
|
||||||
|
|
||||||
|
network_interface {
|
||||||
|
network = "${google_compute_firewall.network.network}"
|
||||||
|
|
||||||
|
access_config {
|
||||||
|
// Ephemeral IP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
service_account {
|
||||||
|
scopes = ["https://www.googleapis.com/auth/cloud-platform"]
|
||||||
|
}
|
||||||
|
|
||||||
|
depends_on = ["google_project_service.endpoints_project_sm", "google_project_service.endpoints_project"]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "ip" {
|
||||||
|
value = "${google_compute_instance.project_1_vm.network_interface.0.access_config.0.assigned_nat_ip}"
|
||||||
|
}
|
14
examples/endpoints-on-compute-engine/scripts/install-vm.sh
Normal file
14
examples/endpoints-on-compute-engine/scripts/install-vm.sh
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
sudo apt-get install git -y
|
||||||
|
curl https://storage.googleapis.com/golang/go1.7.3.linux-amd64.tar.gz | tar xzf -
|
||||||
|
wget https://raw.githubusercontent.com/GoogleCloudPlatform/golang-samples/master/endpoints/getting-started/app.go
|
||||||
|
GOPATH=$PWD GOROOT=$PWD/go go/bin/go get ./... 2> /tmp/goget_log.txt > /tmp/goget_outlog.txt
|
||||||
|
echo "deb http://packages.cloud.google.com/apt google-cloud-endpoints-jessie main" | sudo tee /etc/apt/sources.list.d/google-cloud-endpoints.list
|
||||||
|
curl --silent https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
|
||||||
|
sudo apt-get update && sudo apt-get install google-cloud-sdk
|
||||||
|
sudo apt-get install endpoints-runtime
|
||||||
|
sudo echo "PORT=80" >> /etc/default/nginx
|
||||||
|
PORT=8081 GOPATH=$PWD GOROOT=$PWD/go go/bin/go run app.go > /tmp/gorun_outlog.txt 2> /tmp/gorun_log.txt &
|
||||||
|
sleep 10
|
||||||
|
sudo service nginx restart
|
20
examples/endpoints-on-compute-engine/variables.tf
Normal file
20
examples/endpoints-on-compute-engine/variables.tf
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
variable "region" {
|
||||||
|
default = "us-central1"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "region_zone" {
|
||||||
|
default = "us-central1-f"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "org_id" {
|
||||||
|
description = "The ID of the Google Cloud Organization."
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "billing_account_id" {
|
||||||
|
description = "The ID of the associated billing account (optional)."
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "credentials_file_path" {
|
||||||
|
description = "Location of the credentials to use."
|
||||||
|
default = "~/.gcloud/Terraform.json"
|
||||||
|
}
|
|
@ -139,6 +139,7 @@ func Provider() terraform.ResourceProvider {
|
||||||
"google_dataproc_job": resourceDataprocJob(),
|
"google_dataproc_job": resourceDataprocJob(),
|
||||||
"google_dns_managed_zone": resourceDnsManagedZone(),
|
"google_dns_managed_zone": resourceDnsManagedZone(),
|
||||||
"google_dns_record_set": resourceDnsRecordSet(),
|
"google_dns_record_set": resourceDnsRecordSet(),
|
||||||
|
"google_endpoints_service": resourceEndpointsService(),
|
||||||
"google_folder": resourceGoogleFolder(),
|
"google_folder": resourceGoogleFolder(),
|
||||||
"google_folder_iam_policy": ResourceIamPolicyWithImport(IamFolderSchema, NewFolderIamUpdater, FolderIdParseFunc),
|
"google_folder_iam_policy": ResourceIamPolicyWithImport(IamFolderSchema, NewFolderIamUpdater, FolderIdParseFunc),
|
||||||
"google_folder_organization_policy": resourceGoogleFolderOrganizationPolicy(),
|
"google_folder_organization_policy": resourceGoogleFolderOrganizationPolicy(),
|
||||||
|
|
302
google/resource_endpoints_service.go
Normal file
302
google/resource_endpoints_service.go
Normal file
|
@ -0,0 +1,302 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"google.golang.org/api/servicemanagement/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceEndpointsService() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceEndpointsServiceCreate,
|
||||||
|
Read: resourceEndpointsServiceRead,
|
||||||
|
Delete: resourceEndpointsServiceDelete,
|
||||||
|
Update: resourceEndpointsServiceUpdate,
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"service_name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"openapi_config": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ConflictsWith: []string{"grpc_config", "protoc_output"},
|
||||||
|
},
|
||||||
|
"grpc_config": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"protoc_output": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"project": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"config_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"apis": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Computed: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"syntax": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"version": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"methods": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Computed: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"syntax": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"request_type": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"response_type": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"dns_address": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"endpoints": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Computed: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"address": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOpenAPIConfigSource(configText string) servicemanagement.ConfigSource {
|
||||||
|
// We need to provide a ConfigSource object to the API whenever submitting a
|
||||||
|
// new config. A ConfigSource contains a ConfigFile which contains the b64
|
||||||
|
// encoded contents of the file. OpenAPI requires only one file.
|
||||||
|
configfile := servicemanagement.ConfigFile{
|
||||||
|
FileContents: base64.StdEncoding.EncodeToString([]byte(configText)),
|
||||||
|
FileType: "OPEN_API_YAML",
|
||||||
|
FilePath: "heredoc.yaml",
|
||||||
|
}
|
||||||
|
return servicemanagement.ConfigSource{
|
||||||
|
Files: []*servicemanagement.ConfigFile{&configfile},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGRPCConfigSource(serviceConfig, protoConfig string) servicemanagement.ConfigSource {
|
||||||
|
// gRPC requires both the file specifying the service and the compiled protobuf,
|
||||||
|
// but they can be in any order.
|
||||||
|
ymlConfigfile := servicemanagement.ConfigFile{
|
||||||
|
FileContents: base64.StdEncoding.EncodeToString([]byte(serviceConfig)),
|
||||||
|
FileType: "SERVICE_CONFIG_YAML",
|
||||||
|
FilePath: "heredoc.yaml",
|
||||||
|
}
|
||||||
|
protoConfigfile := servicemanagement.ConfigFile{
|
||||||
|
FileContents: base64.StdEncoding.EncodeToString([]byte(protoConfig)),
|
||||||
|
FileType: "FILE_DESCRIPTOR_SET_PROTO",
|
||||||
|
FilePath: "api_def.pb",
|
||||||
|
}
|
||||||
|
return servicemanagement.ConfigSource{
|
||||||
|
Files: []*servicemanagement.ConfigFile{&ymlConfigfile, &protoConfigfile},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceEndpointsServiceCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
project, err := getProject(d, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// If the service doesn't exist, we'll need to create it, but if it does, it
|
||||||
|
// will be reused. This is unusual for Terraform, but it causes the behavior
|
||||||
|
// that users will want and accept. Users of Endpoints are not thinking in
|
||||||
|
// terms of services, configs, and rollouts - they just want the setup declared
|
||||||
|
// in their config to happen. The fact that a service may need to be created
|
||||||
|
// is not interesting to them. Consequently, we create this service if necessary
|
||||||
|
// so that we can perform the rollout without further disruption, which is the
|
||||||
|
// action that a user running `terraform apply` is going to want.
|
||||||
|
serviceName := d.Get("service_name").(string)
|
||||||
|
servicesService := servicemanagement.NewServicesService(config.clientServiceMan)
|
||||||
|
_, err = servicesService.Get(serviceName).Do()
|
||||||
|
if err != nil {
|
||||||
|
_, err = servicesService.Create(&servicemanagement.ManagedService{ProducerProjectId: project, ServiceName: serviceName}).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Do a rollout using the update mechanism.
|
||||||
|
err = resourceEndpointsServiceUpdate(d, meta)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId(serviceName)
|
||||||
|
return resourceEndpointsServiceRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceEndpointsServiceUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
// This update is not quite standard for a terraform resource. Instead of
|
||||||
|
// using the go client library to send an HTTP request to update something
|
||||||
|
// serverside, we have to push a new configuration, wait for it to be
|
||||||
|
// parsed and loaded, then create and push a rollout and wait for that
|
||||||
|
// rollout to be completed.
|
||||||
|
// There's a lot of moving parts there, and all of them have knobs that can
|
||||||
|
// be tweaked if the user is using gcloud. In the interest of simplicity,
|
||||||
|
// we currently only support full rollouts - anyone trying to do incremental
|
||||||
|
// rollouts or A/B testing is going to need a more precise tool than this resource.
|
||||||
|
config := meta.(*Config)
|
||||||
|
serviceName := d.Get("service_name").(string)
|
||||||
|
var source servicemanagement.ConfigSource
|
||||||
|
if openapiConfig, ok := d.GetOk("openapi_config"); ok {
|
||||||
|
source = getOpenAPIConfigSource(openapiConfig.(string))
|
||||||
|
} else {
|
||||||
|
grpcConfig, gok := d.GetOk("grpc_config")
|
||||||
|
protocOutput, pok := d.GetOk("protoc_output")
|
||||||
|
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.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configService := servicemanagement.NewServicesConfigsService(config.clientServiceMan)
|
||||||
|
// The difference between "submit" and "create" is that submit parses the config
|
||||||
|
// you provide, where "create" requires the config in a pre-parsed format.
|
||||||
|
// "submit" will be a lot more flexible for users and will always be up-to-date
|
||||||
|
// with any new features that arise - this is why you provide a YAML config
|
||||||
|
// instead of providing the config in HCL.
|
||||||
|
op, err := configService.Submit(serviceName, &servicemanagement.SubmitConfigSourceRequest{ConfigSource: &source}).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s, err := serviceManagementOperationWait(config, op, "Submitting service config.")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var serviceConfig servicemanagement.SubmitConfigSourceResponse
|
||||||
|
json.Unmarshal(s, &serviceConfig)
|
||||||
|
|
||||||
|
// Next, we create a new rollout with the new config value, and wait for it to complete.
|
||||||
|
rolloutService := servicemanagement.NewServicesRolloutsService(config.clientServiceMan)
|
||||||
|
rollout := servicemanagement.Rollout{
|
||||||
|
ServiceName: serviceName,
|
||||||
|
TrafficPercentStrategy: &servicemanagement.TrafficPercentStrategy{
|
||||||
|
Percentages: map[string]float64{serviceConfig.ServiceConfig.Id: 100.0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
op, err = rolloutService.Create(serviceName, &rollout).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = serviceManagementOperationWait(config, op, "Performing service rollout.")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return resourceEndpointsServiceRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceEndpointsServiceDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
servicesService := servicemanagement.NewServicesService(config.clientServiceMan)
|
||||||
|
op, err := servicesService.Delete(d.Get("service_name").(string)).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = serviceManagementOperationWait(config, op, "Deleting service.")
|
||||||
|
d.SetId("")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceEndpointsServiceRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
servicesService := servicemanagement.NewServicesService(config.clientServiceMan)
|
||||||
|
service, err := servicesService.GetConfig(d.Get("service_name").(string)).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.Set("config_id", service.Id)
|
||||||
|
d.Set("dns_address", service.Name)
|
||||||
|
d.Set("apis", flattenServiceManagementAPIs(service.Apis))
|
||||||
|
d.Set("endpoints", flattenServiceManagementEndpoints(service.Endpoints))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func flattenServiceManagementAPIs(apis []*servicemanagement.Api) []map[string]interface{} {
|
||||||
|
flattened := make([]map[string]interface{}, len(apis))
|
||||||
|
for i, a := range apis {
|
||||||
|
flattened[i] = map[string]interface{}{
|
||||||
|
"name": a.Name,
|
||||||
|
"version": a.Version,
|
||||||
|
"syntax": a.Syntax,
|
||||||
|
"methods": flattenServiceManagementMethods(a.Methods),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flattened
|
||||||
|
}
|
||||||
|
|
||||||
|
func flattenServiceManagementMethods(methods []*servicemanagement.Method) []map[string]interface{} {
|
||||||
|
flattened := make([]map[string]interface{}, len(methods))
|
||||||
|
for i, m := range methods {
|
||||||
|
flattened[i] = map[string]interface{}{
|
||||||
|
"name": m.Name,
|
||||||
|
"syntax": m.Syntax,
|
||||||
|
"request_type": m.RequestTypeUrl,
|
||||||
|
"response_type": m.ResponseTypeUrl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flattened
|
||||||
|
}
|
||||||
|
|
||||||
|
func flattenServiceManagementEndpoints(endpoints []*servicemanagement.Endpoint) []map[string]interface{} {
|
||||||
|
flattened := make([]map[string]interface{}, len(endpoints))
|
||||||
|
for i, e := range endpoints {
|
||||||
|
flattened[i] = map[string]interface{}{
|
||||||
|
"name": e.Name,
|
||||||
|
"address": e.Target,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flattened
|
||||||
|
}
|
124
google/resource_endpoints_service_test.go
Normal file
124
google/resource_endpoints_service_test.go
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
"github.com/hashicorp/terraform/helper/acctest"
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"google.golang.org/api/servicemanagement/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccEndpointsService_basic(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
random_name := "t-" + acctest.RandString(10)
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccEndpointsService_basic(random_name),
|
||||||
|
Check: testAccCheckEndpointExistsByName(random_name),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccEndpointsService_grpc(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
random_name := "t-" + acctest.RandString(10)
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccEndpointsService_grpc(random_name),
|
||||||
|
Check: testAccCheckEndpointExistsByName(random_name),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccEndpointsService_basic(random_name string) string {
|
||||||
|
return fmt.Sprintf(`resource "google_endpoints_service" "endpoints_service" {
|
||||||
|
service_name = "%s.endpoints.%s.cloud.goog"
|
||||||
|
project = "%s"
|
||||||
|
openapi_config = <<EOF
|
||||||
|
swagger: "2.0"
|
||||||
|
info:
|
||||||
|
description: "A simple Google Cloud Endpoints API example."
|
||||||
|
title: "Endpoints Example"
|
||||||
|
version: "1.0.0"
|
||||||
|
host: "%s.endpoints.%s.cloud.goog"
|
||||||
|
basePath: "/"
|
||||||
|
consumes:
|
||||||
|
- "application/json"
|
||||||
|
produces:
|
||||||
|
- "application/json"
|
||||||
|
schemes:
|
||||||
|
- "https"
|
||||||
|
paths:
|
||||||
|
"/echo":
|
||||||
|
post:
|
||||||
|
description: "Echo back a given message."
|
||||||
|
operationId: "echo"
|
||||||
|
produces:
|
||||||
|
- "application/json"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "Echo"
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/echoMessage"
|
||||||
|
parameters:
|
||||||
|
- description: "Message to echo"
|
||||||
|
in: body
|
||||||
|
name: message
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/echoMessage"
|
||||||
|
security:
|
||||||
|
- api_key: []
|
||||||
|
definitions:
|
||||||
|
echoMessage:
|
||||||
|
properties:
|
||||||
|
message:
|
||||||
|
type: "string"
|
||||||
|
EOF
|
||||||
|
}`, random_name, getTestProjectFromEnv(), getTestProjectFromEnv(), random_name, getTestProjectFromEnv())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccEndpointsService_grpc(random_name string) string {
|
||||||
|
return fmt.Sprintf(`resource "google_endpoints_service" "endpoints_service" {
|
||||||
|
service_name = "%s.endpoints.%s.cloud.goog"
|
||||||
|
project = "%s"
|
||||||
|
grpc_config = <<EOF
|
||||||
|
type: google.api.Service
|
||||||
|
config_version: 3
|
||||||
|
name: %s.endpoints.%s.cloud.goog
|
||||||
|
usage:
|
||||||
|
rules:
|
||||||
|
- selector: endpoints.examples.bookstore.Bookstore.ListShelves
|
||||||
|
allow_unregistered_calls: true
|
||||||
|
EOF
|
||||||
|
protoc_output = "${file("test-fixtures/test_api_descriptor.pb")}"
|
||||||
|
}`, random_name, getTestProjectFromEnv(), getTestProjectFromEnv(), random_name, getTestProjectFromEnv())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckEndpointExistsByName(random_name string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
servicesService := servicemanagement.NewServicesService(config.clientServiceMan)
|
||||||
|
service, err := servicesService.GetConfig(fmt.Sprintf("%s.endpoints.%s.cloud.goog", random_name, config.Project)).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if service != nil {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Service %s.endpoints.%s.cloud.goog does not seem to exist.", random_name, config.Project)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -191,7 +191,7 @@ func enableService(s, pid string, config *Config) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
waitErr := serviceManagementOperationWait(config, sop, "api to enable")
|
_, waitErr := serviceManagementOperationWait(config, sop, "api to enable")
|
||||||
if waitErr != nil {
|
if waitErr != nil {
|
||||||
return waitErr
|
return waitErr
|
||||||
}
|
}
|
||||||
|
@ -211,7 +211,7 @@ func disableService(s, pid string, config *Config) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Wait for the operation to complete
|
// Wait for the operation to complete
|
||||||
waitErr := serviceManagementOperationWait(config, sop, "api to disable")
|
_, waitErr := serviceManagementOperationWait(config, sop, "api to disable")
|
||||||
if waitErr != nil {
|
if waitErr != nil {
|
||||||
return waitErr
|
return waitErr
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"google.golang.org/api/googleapi"
|
||||||
"google.golang.org/api/servicemanagement/v1"
|
"google.golang.org/api/servicemanagement/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -39,11 +40,11 @@ func (w *ServiceManagementOperationWaiter) Conf() *resource.StateChangeConf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func serviceManagementOperationWait(config *Config, op *servicemanagement.Operation, activity string) error {
|
func serviceManagementOperationWait(config *Config, op *servicemanagement.Operation, activity string) (googleapi.RawMessage, error) {
|
||||||
return serviceManagementOperationWaitTime(config, op, activity, 10)
|
return serviceManagementOperationWaitTime(config, op, activity, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
func serviceManagementOperationWaitTime(config *Config, op *servicemanagement.Operation, activity string, timeoutMin int) error {
|
func serviceManagementOperationWaitTime(config *Config, op *servicemanagement.Operation, activity string, timeoutMin int) (googleapi.RawMessage, error) {
|
||||||
w := &ServiceManagementOperationWaiter{
|
w := &ServiceManagementOperationWaiter{
|
||||||
Service: config.clientServiceMan,
|
Service: config.clientServiceMan,
|
||||||
Op: op,
|
Op: op,
|
||||||
|
@ -55,13 +56,13 @@ func serviceManagementOperationWaitTime(config *Config, op *servicemanagement.Op
|
||||||
state.MinTimeout = 2 * time.Second
|
state.MinTimeout = 2 * time.Second
|
||||||
opRaw, err := state.WaitForState()
|
opRaw, err := state.WaitForState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error waiting for %s: %s", activity, err)
|
return nil, fmt.Errorf("Error waiting for %s: %s", activity, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
op = opRaw.(*servicemanagement.Operation)
|
op = opRaw.(*servicemanagement.Operation)
|
||||||
if op.Error != nil {
|
if op.Error != nil {
|
||||||
return fmt.Errorf("Error code %v, message: %s", op.Error.Code, op.Error.Message)
|
return nil, fmt.Errorf("Error code %v, message: %s", op.Error.Code, op.Error.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return op.Response, nil
|
||||||
}
|
}
|
||||||
|
|
BIN
google/test-fixtures/test_api_descriptor.pb
Normal file
BIN
google/test-fixtures/test_api_descriptor.pb
Normal file
Binary file not shown.
63
website/docs/r/endpoints_service.html.markdown
Normal file
63
website/docs/r/endpoints_service.html.markdown
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
---
|
||||||
|
layout: "google"
|
||||||
|
page_title: "Google: google_endpoints_service"
|
||||||
|
sidebar_current: "docs-google-endpoints-service"
|
||||||
|
description: |-
|
||||||
|
Creates and rolls out a Google Endpoints service.
|
||||||
|
---
|
||||||
|
|
||||||
|
# google_endpoints_service
|
||||||
|
|
||||||
|
This resource creates and rolls out a Cloud Endpoints service using OpenAPI or gRPC. View the relevant docs for [OpenAPI](https://cloud.google.com/endpoints/docs/openapi/) and [gRPC](https://cloud.google.com/endpoints/docs/grpc/).
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
resource "google_endpoints_service" "openapi_service" {
|
||||||
|
service_name = "api-name.endpoints.project-id.cloud.goog"
|
||||||
|
project = "project-id"
|
||||||
|
openapi_config = "${file("openapi_spec.yml")}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_endpoints_service" "grpc_service" {
|
||||||
|
service_name = "api-name.endpoints.project-id.cloud.goog"
|
||||||
|
project = "project-id"
|
||||||
|
grpc_config = "${file("service_spec.yml")}"
|
||||||
|
protoc_output = "${file("compiled_descriptor_file.pb")}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The example in `examples/endpoints_on_compute_engine` shows the API from the quickstart running on a Compute Engine VM and reachable through Cloud Endpoints, which may also be useful.
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
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.
|
||||||
|
* `project`: (Optional) The project ID that the service belongs to. If not provided, provider project is used.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
In addition to the arguments, the following attributes are available:
|
||||||
|
* `config_id`: The autogenerated ID for the configuration that is rolled out as part of the creation of this resource. Must be provided to compute engine instances as a tag.
|
||||||
|
* `dns_address`: The address at which the service can be found - usually the same as the service name.
|
||||||
|
* `apis`: A list of API objects; structure is documented below.
|
||||||
|
* `endpoints`: A list of Endpoint objects; structure is documented below.
|
||||||
|
|
||||||
|
- - -
|
||||||
|
### API Object Structure
|
||||||
|
* `name`: The FQDN of the API as described in the provided config.
|
||||||
|
* `syntax`: `SYNTAX_PROTO2` or `SYNTAX_PROTO3`.
|
||||||
|
* `version`: A version string for this api. If specified, will have the form major-version.minor-version, e.g. `1.10`.
|
||||||
|
* `methods`: A list of Method objects; structure is documented below.
|
||||||
|
|
||||||
|
### Method Object Structure
|
||||||
|
* `name`: The simple name of this method as described in the provided config.
|
||||||
|
* `syntax`: `SYNTAX_PROTO2` or `SYNTAX_PROTO3`.
|
||||||
|
* `request_type`: The type URL for the request to this API.
|
||||||
|
* `response_type`: The type URL for the response from this API.
|
||||||
|
|
||||||
|
### Endpoint Object Structure
|
||||||
|
* `name`: The simple name of the endpoint as described in the config.
|
||||||
|
* `address`: The FQDN of the endpoint as described in the config.
|
|
@ -396,6 +396,15 @@
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-google-endpoints") %>>
|
||||||
|
<a href="#">Google Endpoints Resources</a>
|
||||||
|
<ul class="nav nav-visible">
|
||||||
|
<li<%= sidebar_current("docs-google-endpoints-service") %>>
|
||||||
|
<a href="/docs/providers/google/r/endpoints_service.html">google_endpoints_service</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-google-pubsub") %>>
|
<li<%= sidebar_current("docs-google-pubsub") %>>
|
||||||
<a href="#">Google PubSub Resources</a>
|
<a href="#">Google PubSub Resources</a>
|
||||||
<ul class="nav nav-visible">
|
<ul class="nav nav-visible">
|
||||||
|
|
Loading…
Reference in New Issue
Block a user