parent
8c48057382
commit
43a62a87b7
4 changed files with 304 additions and 88 deletions
@ -0,0 +1,164 @@ |
|||||||
|
# Service Infrastructure Example API |
||||||
|
|
||||||
|
This is an example API that demonstrates how to design a networked API following |
||||||
|
[API Design Guide](https://cloud.google.com/apis/design), implement the API |
||||||
|
as an [App Engine](https://cloud.google.com/app-engine) app, |
||||||
|
and integrate the API with Google Cloud's |
||||||
|
[Service Infrastructure](https://cloud.google.com/service-infrastructure). |
||||||
|
It is intended for service providers who want to build services using |
||||||
|
Google Cloud. |
||||||
|
|
||||||
|
NOTE: This API is also used for Service Infrastructure reference documentation. |
||||||
|
|
||||||
|
## Glossary |
||||||
|
|
||||||
|
For terminology used in this example, see |
||||||
|
[API Glossary](https://cloud.google.com/apis/design/glossary). |
||||||
|
|
||||||
|
## Installation |
||||||
|
|
||||||
|
You need to install [`gcloud`](https://cloud.google.com/sdk/docs), |
||||||
|
[`go`](https://golang.org/doc/install), |
||||||
|
[`protoc`](https://grpc.io/docs/protoc-installation/), |
||||||
|
and [`oauth2l`](https://github.com/google/oauth2l) tools on your local machine |
||||||
|
in order to build this example. |
||||||
|
|
||||||
|
Run the following commands to install additional Go packages that will be used |
||||||
|
in this example API: |
||||||
|
|
||||||
|
```shell |
||||||
|
go get golang.org/x/oauth2/google |
||||||
|
go get google.golang.org/api/servicecontrol/v2 |
||||||
|
``` |
||||||
|
|
||||||
|
## Introduction |
||||||
|
|
||||||
|
This API uses a proto file to define the API surface, and a YAML file to |
||||||
|
configure the API service. The API backend is built as an App Engine app |
||||||
|
written in Go, and uses the |
||||||
|
[Service Control API](https://cloud.google.com/service-control) for |
||||||
|
deeper integration with Service Infrastructure and Google Cloud. |
||||||
|
|
||||||
|
This example API uses project id `endpointsapis` and service name |
||||||
|
`endpointsapis.appspot.com`. The name `endpointsapis` has no special meaning. |
||||||
|
If you want to build this example API by yourself, you must use your own |
||||||
|
project id and service name. For how to create a project, see |
||||||
|
[Getting Started](https://cloud.google.com/apis/docs/getting-started#creating_a_google_project). |
||||||
|
|
||||||
|
WARNING: Some features used in this example are private, and you need to join |
||||||
|
private preview programs to use such features. |
||||||
|
|
||||||
|
### Design |
||||||
|
|
||||||
|
This example API design follows Google's |
||||||
|
[API Design Guide](https://cloud.google.com/apis/design). It uses a proto |
||||||
|
file, `./v1/workspace.proto`, to define the gRPC API surface, and uses |
||||||
|
[`google.api.http`](https://aip.dev/127) annotation to define the REST |
||||||
|
API mapping. |
||||||
|
|
||||||
|
For more information, see the proto definition and |
||||||
|
[API Design Guide](https://cloud.google.com/apis/design). |
||||||
|
|
||||||
|
### Configuration |
||||||
|
|
||||||
|
To build a networked API and expose it to consumers, you need to configure it |
||||||
|
as an API service. The configuration includes the API title, the network |
||||||
|
addresses, the authentication requirements, and many other aspects. |
||||||
|
|
||||||
|
If you create an API service using Google Cloud's Service Infrastructure, |
||||||
|
you need to create a servie configuration using the |
||||||
|
[`google.api.Service`](../../api/service.proto) schema. The service |
||||||
|
configuration is the yaml representation of `google.api.Service`, |
||||||
|
see `./endpointsapis.yaml`. |
||||||
|
|
||||||
|
```yaml |
||||||
|
type: google.api.Service # The schema to use; always "google.api.Service". |
||||||
|
config_version: 3 # The config version; always 3. |
||||||
|
name: endpointsapis.appspot.com |
||||||
|
title: Endpoints APIs |
||||||
|
producer_project_id: endpointsapis |
||||||
|
apis: # The supported API interfaces. |
||||||
|
- name: google.example.endpointsapis.v1.Workspaces |
||||||
|
``` |
||||||
|
|
||||||
|
After you have created your API definition and service configuration, you need |
||||||
|
to push them to the Service Management API, which will distribute the |
||||||
|
information to Google Cloud infrastructure backends. |
||||||
|
|
||||||
|
Run the following commands to generate proto descriptor and push the |
||||||
|
descriptor and service configuration to the Service Management API. |
||||||
|
|
||||||
|
```shell |
||||||
|
# Generate proto descriptors from the proto files. |
||||||
|
protoc --include_source_info --include_imports --descriptor_set_out=service.descriptors v1/workspace.proto |
||||||
|
|
||||||
|
# Push the proto descriptors and yaml files to the Service Management API. |
||||||
|
gcloud endpoints services deploy service.descriptors endpointsapis.yaml |
||||||
|
``` |
||||||
|
|
||||||
|
For more information, see |
||||||
|
[Manage Service Configurations](https://cloud.google.com/service-infrastructure/docs/manage-config). |
||||||
|
|
||||||
|
### Implementation |
||||||
|
|
||||||
|
Create an App Engine app that implements your API and deploy the app, see |
||||||
|
[Building a Go App on App Engine](https://cloud.google.com/appengine/docs/standard/go/building-app). |
||||||
|
The implementation of this code is in `goapp/main.go`. Run the following |
||||||
|
command to deploy the code: |
||||||
|
|
||||||
|
```shell |
||||||
|
gcloud app deploy |
||||||
|
``` |
||||||
|
|
||||||
|
### Integration |
||||||
|
|
||||||
|
The Service Control API is the frontend of System Infrastructure control plane. |
||||||
|
It provides admission control and telemetry reporting functionality to any |
||||||
|
service that is integrated with Service Infrastructure, specifically: |
||||||
|
|
||||||
|
* For each request, the service calls the `Check` method for admission |
||||||
|
control. |
||||||
|
* For each response, the service calls the `Report` method for telemetry |
||||||
|
reporting. |
||||||
|
|
||||||
|
For more information, see |
||||||
|
[Admission Control](https://cloud.google.com/service-infrastructure/docs/admission-control) |
||||||
|
and |
||||||
|
[Telemetry Reporting](https://cloud.google.com/service-infrastructure/docs/telemetry-reporting). |
||||||
|
|
||||||
|
In this example API, the app calls the Service Control API v2 for each request |
||||||
|
and response, see `goapp/main.go`. You need to make sure the App Engine |
||||||
|
service account has the necessary permissions to run the service. |
||||||
|
For more information, see |
||||||
|
[Access Control](https://cloud.google.com/service-infrastructure/docs/service-control/access-control). |
||||||
|
|
||||||
|
NOTE: The telemetry reporting feature is not yet available as of July 2020. |
||||||
|
|
||||||
|
### Verification |
||||||
|
|
||||||
|
Follow [Getting Started](https://cloud.google.com/apis/getting-started) to set |
||||||
|
up a client project for calling your API service. If you want to run your |
||||||
|
test locally, you need to create an application credential in your client |
||||||
|
project and download it to your local machine. The credential can be either |
||||||
|
an OAuth client or a service account key in JSON format. |
||||||
|
|
||||||
|
Use [`oauth2l`](https://github.com/google/oauth2l) to send a test request to |
||||||
|
your App Engine app using your application credentials. `oauth2l` supports |
||||||
|
different credential types in different environments. |
||||||
|
|
||||||
|
```shell |
||||||
|
curl -H "$(oauth2l header --credentials creds.json --scope cloud-platform,userinfo.email)" https://endpointsapis.uc.r.appspot.com/v1/projects/1020789478714/locations/1/workspaces |
||||||
|
``` |
||||||
|
|
||||||
|
You should see the admission control results in the HTTP response |
||||||
|
and the telemetry reporting results in Google Cloud Console. The producer |
||||||
|
metrics are shown on the Endpoints page. The consumer metrics are shown on |
||||||
|
the APIs & Services page. |
||||||
|
|
||||||
|
NOTE: The telemetry reporting feature is not yet available as of July 2020. |
||||||
|
|
||||||
|
## Summary |
||||||
|
|
||||||
|
This example shows how to build a simple API service using Google Cloud |
||||||
|
infrastructure. If you have any suggestions, please send us |
||||||
|
[pull requests](https://github.com/googleapis/googleapis/pulls) directly. |
@ -0,0 +1,130 @@ |
|||||||
|
// The goapp command implements a simple App Engine app to demonstrate how to
|
||||||
|
// use the Service Control API v2 for admission control. For more information,
|
||||||
|
// see https://cloud.google.com/service-infrastructure/docs/admission-control.
|
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"log" |
||||||
|
"net/http" |
||||||
|
"os" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
|
||||||
|
// WARNING:`go get google.golang.org/api/servicecontrol/v2" may take
|
||||||
|
// 30 minutes or longer, depending on your network speed.
|
||||||
|
"google.golang.org/api/servicecontrol/v2" |
||||||
|
) |
||||||
|
|
||||||
|
// Check calls Service Control API v2 for admission control.
|
||||||
|
// Name specifies the target resource name. Permission specifies
|
||||||
|
// the required permission on the target resource.
|
||||||
|
func check(w http.ResponseWriter, r *http.Request, name string, permission string) (string, error) { |
||||||
|
client, err := servicecontrol.NewService(r.Context()) |
||||||
|
if err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
// Construct CheckRequest from the incoming HTTP request.
|
||||||
|
// The code assumes the incoming request processed by App Engine ingress.
|
||||||
|
checkRequest := &servicecontrol.CheckRequest{ |
||||||
|
ServiceConfigId: "latest", |
||||||
|
Attributes: &servicecontrol.AttributeContext{ |
||||||
|
Origin: &servicecontrol.Peer{ |
||||||
|
Ip: r.Header.Get("x-appengine-user-ip"), |
||||||
|
}, |
||||||
|
Api: &servicecontrol.Api{ |
||||||
|
Service: "endpointsapis.appspot.com", |
||||||
|
Operation: "google.example.endpointsapis.v1.Workspaces.GetWorkspace", |
||||||
|
Version: "v1", |
||||||
|
Protocol: r.Header.Get("x-forwarded-proto"), |
||||||
|
}, |
||||||
|
Request: &servicecontrol.Request{ |
||||||
|
Id: r.Header.Get("x-appengine-request-log-id"), |
||||||
|
Time: time.Now().UTC().Format(time.RFC3339), |
||||||
|
Method: r.Method, |
||||||
|
Scheme: r.Header.Get("x-forwarded-proto"), |
||||||
|
Host: r.Host, |
||||||
|
Path: r.URL.Path, |
||||||
|
Headers: map[string]string{ |
||||||
|
"authorization": r.Header.Get("authorization"), |
||||||
|
"user-agent": r.Header.Get("user-agent"), |
||||||
|
"origin": r.Header.Get("origin"), |
||||||
|
"referer": r.Header.Get("referer"), |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
Resources: []*servicecontrol.ResourceInfo{ |
||||||
|
{ |
||||||
|
Name: name, |
||||||
|
Type: "endpointsapis.appspot.com/Workspace", |
||||||
|
Permission: permission, |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
response, err := client.Services.Check("endpointsapis.appspot.com", checkRequest).Do() |
||||||
|
if err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
responseJSON, err := response.MarshalJSON() |
||||||
|
if err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
return string(responseJSON), nil |
||||||
|
} |
||||||
|
|
||||||
|
func admission(w http.ResponseWriter, r *http.Request) (string, error) { |
||||||
|
// Split the request path.
|
||||||
|
segments := strings.Split(r.URL.Path, "/") |
||||||
|
|
||||||
|
// The request path must match "/v1/projects/*/locations/*/workspaces/*" or
|
||||||
|
// "/v1/projects/*/locations/*/workspaces". They correspond to the
|
||||||
|
// GetWorkspace() and ListWorkspaces() methods defined in ../v1/workspace.proto.
|
||||||
|
if segments[0] != "" || segments[1] != "v1" || segments[2] != "projects" || segments[4] != "locations" || segments[6] != "workspaces" || len(segments) > 8 { |
||||||
|
return "", errors.New("Resource '" + r.URL.Path + "' not found.") |
||||||
|
} |
||||||
|
// Skip prefix "/v1/".
|
||||||
|
resource := r.URL.Path[4:] |
||||||
|
permission := "endpointsapis.appspot.com/workspaces.list" |
||||||
|
if len(segments) == 8 { |
||||||
|
permission = "endpointsapis.appspot.com/workspaces.get" |
||||||
|
} |
||||||
|
return check(w, r, resource, permission) |
||||||
|
} |
||||||
|
|
||||||
|
func indexHandler(w http.ResponseWriter, r *http.Request) { |
||||||
|
// Perform admission control.
|
||||||
|
result, err := admission(w, r) |
||||||
|
|
||||||
|
// Print the admission control result.
|
||||||
|
if err != nil { |
||||||
|
fmt.Fprintln(w, "Error:") |
||||||
|
fmt.Fprintln(w, err.Error()) |
||||||
|
} else { |
||||||
|
fmt.Fprintln(w, "CheckResponse:") |
||||||
|
fmt.Fprintln(w, result) |
||||||
|
} |
||||||
|
|
||||||
|
// Print all environment variables.
|
||||||
|
fmt.Fprintln(w, "Environments:") |
||||||
|
fmt.Fprintln(w, strings.Join(os.Environ(), "\n")) |
||||||
|
|
||||||
|
// Print all request headers.
|
||||||
|
fmt.Fprintln(w, "Headers:") |
||||||
|
for key, values := range r.Header { |
||||||
|
for _, value := range values { |
||||||
|
fmt.Fprintf(w, "%v: %v\n", key, value) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func main() { |
||||||
|
http.HandleFunc("/", indexHandler) |
||||||
|
|
||||||
|
port := os.Getenv("PORT") |
||||||
|
|
||||||
|
log.Printf("Listen and serve on port %s", port) |
||||||
|
if err := http.ListenAndServe(":"+port, nil); err != nil { |
||||||
|
log.Fatal(err) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue