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