From c375b4e6dd01f2cc9b6ba36198086b9110c5f616 Mon Sep 17 00:00:00 2001 From: Linus Arver Date: Tue, 3 Jan 2023 11:11:35 -0800 Subject: [PATCH] gangway Go client and cloud endpoints integration This adds all the necessary bits from the Prow side to let developers programmatically access the Prow API (gangway). Currently there is 1 FIXME in the example/main.go file, which is to poll gangway for reading the job execution status. This is a FIXME because we haven't actually implemented the method on the server side (yet). That part should be done as a separate follow-up item. For all future extensions to the API, we should also modify the example/main.go we have here to illustrate actual API usage. --- hack/make-rules/update/codegen.sh | 14 +++ prow/gangway/api_descriptor.pb | Bin 0 -> 80324 bytes prow/gangway/example/main.go | 101 +++++++++++++++++++ prow/gangway/google/client.go | 160 ++++++++++++++++++++++++++++++ 4 files changed, 275 insertions(+) create mode 100644 prow/gangway/api_descriptor.pb create mode 100644 prow/gangway/example/main.go create mode 100644 prow/gangway/google/client.go diff --git a/hack/make-rules/update/codegen.sh b/hack/make-rules/update/codegen.sh index 1810e2492475e..81f44690f7e2f 100755 --- a/hack/make-rules/update/codegen.sh +++ b/hack/make-rules/update/codegen.sh @@ -298,6 +298,19 @@ gen-all-proto-stubs(){ -print0 | sort -z) } +gen-gangway-apidescriptorpb-for-cloud-endpoints(){ + echo >&2 "Generating self-describing proto stub (api_descriptor.pb) for gangway.proto" + + "${REPO_ROOT}/_bin/protoc/bin/protoc" \ + "--proto_path=${REPO_ROOT}/_bin/protoc/include/google/protobuf" \ + "--proto_path=${REPO_ROOT}/_bin/protoc/include/googleapis" \ + "--proto_path=${REPO_ROOT}/prow/gangway" \ + --include_imports \ + --include_source_info \ + --descriptor_set_out "${REPO_ROOT}/prow/gangway/api_descriptor.pb" \ + gangway.proto +} + gen-prow-config-documented export GO111MODULE=off @@ -320,3 +333,4 @@ gen-prowjob-crd export GO111MODULE=on gen-all-proto-stubs +gen-gangway-apidescriptorpb-for-cloud-endpoints diff --git a/prow/gangway/api_descriptor.pb b/prow/gangway/api_descriptor.pb new file mode 100644 index 0000000000000000000000000000000000000000..031da2f9ecbc7ca9c72b160a23bdcb9c62936f97 GIT binary patch literal 80324 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 literal 0 HcmV?d00001 diff --git a/prow/gangway/example/main.go b/prow/gangway/example/main.go new file mode 100644 index 0000000000000..1b1bdb080e4ad --- /dev/null +++ b/prow/gangway/example/main.go @@ -0,0 +1,101 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "flag" + "log" + + "google.golang.org/protobuf/encoding/prototext" + pb "k8s.io/test-infra/prow/gangway" + gangwayGoogleClient "k8s.io/test-infra/prow/gangway/google" +) + +var ( + addr = flag.String("addr", "127.0.0.1:50051", "Address of grpc server.") + apiKey = flag.String("api-key", "", "API key.") + audience = flag.String("audience", "", "Audience.") + keyFile = flag.String("key-file", "", "Path to a Google service account key file.") + clientPem = flag.String("client-pem", "", "Path to a client.pem file.") +) + +// Use like this: +// +// go run main.go --key-file=key.json \ +// --audience=SERVICE_NAME.endpoints.PROJECT_NAME.cloud.goog \ +// --addr=12.34.56.78:443 --api-key=API_KEY --client-pem=client.pem \ +// +// +// where is the protobuf (in textpb format) message +// you want to send over. For example, if you want to run the periodic job named +// "foo", use: +// +// 'job_name: "foo", job_execution_type: 1' +// +// as the . The "1" here for "job_execution_type" +// denotes the periodic job type (because this field is an enum, not a string). + +func main() { + flag.Parse() + + jobName := "some-job" + jobExecutionType := pb.JobExecutionType_PERIODIC + + // Set default values. + cjer := pb.CreateJobExecutionRequest{ + JobName: jobName, + JobExecutionType: jobExecutionType, + } + + // Read in string version of a CreateJobExecutionRequest. + if len(flag.Args()) > 0 { + textpb := flag.Arg(0) + if err := prototext.Unmarshal([]byte(textpb), &cjer); err != nil { + log.Fatalf("could not unmarshal textpb %q: %v", textpb, err) + } + } + + log.Printf("creating job execution with %v", &cjer) + + // Create a Prow API gRPC client that's able to authenticate to Gangway (the + // Prow API Server). + prowClient, err := gangwayGoogleClient.NewFromFile(*keyFile, *audience, *apiKey, *addr, *clientPem) + if err != nil { + log.Fatalf("Prow API client creation failed: %v", err) + } + + defer prowClient.Close() + + // Create a Context that has credentials injected inside it. + ctx, err := prowClient.EmbedCredentials(context.Background()) + if err != nil { + log.Fatalf("could not create a context with embedded credentials: %v", err) + } + + // Trigger job! Because this is gRPC it's just a function call. + jobExecution, err := prowClient.GRPC.CreateJobExecution(ctx, &cjer) + if err != nil { + log.Fatalf("could not trigger job: %v", err) + } + + log.Printf("triggered job: %v", jobExecution) + + // FIXME (listx): At this point we have the jobExecution object and it has an `Id` + // field. In order to get the job status, we have to poll + // prowClient.GRPC.GetJobExecution() to get the current job status. +} diff --git a/prow/gangway/google/client.go b/prow/gangway/google/client.go new file mode 100644 index 0000000000000..b980bf6551f2c --- /dev/null +++ b/prow/gangway/google/client.go @@ -0,0 +1,160 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package google + +import ( + "context" + "errors" + "fmt" + "io/ioutil" + + "golang.org/x/oauth2" + googleOAuth "golang.org/x/oauth2/google" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/metadata" + + pb "k8s.io/test-infra/prow/gangway" +) + +// This is the client library for Go clients that need to access to the Prow +// API, aka Gangway, when Gangway is deployed in a GKE cluster and integrated +// with Cloud Endpoints. +// +// Go clients need to always append 2 things to the metadata of a gRPC call: +// +// 1. The JWT (authentication) token (to make the call identify itself as +// an allowlisted client in our api_config_auth.yaml configuration for Cloud +// Endpoints [1]), and +// +// 2. The API key (that is generated by the client's GCP Project). +// +// The JWT token is generated from a GCP Service Account key file (JSON). The +// API key is generated from the GCP user interface, like this: +// https://cloud.google.com/docs/authentication/api-keys#create. +// +// For us, the clients must supply the service account JSON key file, API key, +// audience, and finally the address where Gangway is being served. An example +// client application using this library is provided in the Prow codebase under +// prow/gangway/example/main.go. +// +// [1]: https://github.com/GoogleCloudPlatform/golang-samples/blob/e888c56cb843f475db4f79b391be999518e63db4/endpoints/getting-started-grpc/README.md#configuring-authentication-and-authenticating-requests + +type Client struct { + // JWT token-based authentication and GCP Project identification. + keyBytes []byte + audience string + tokenSource oauth2.TokenSource + + // apiKey identifies the GCP Project. + apiKey string + + addr string + conn *grpc.ClientConn + + // GRPC is the auto-generated gRPC client interface for gangway. + GRPC pb.ProwClient +} + +// NewFromFile creates a Gangway client from a JSON service account key file and an audience string. +func NewFromFile(keyFile, audience, apiKey, addr, clientPem string) (*Client, error) { + keyBytes, err := ioutil.ReadFile(keyFile) + if err != nil { + return nil, fmt.Errorf("Unable to read service account key file %s: %v", keyFile, err) + } + + return New(keyBytes, audience, apiKey, addr, clientPem) +} + +// New creates a new gRPC client. It does most of the work in NewFromFile(). +func New(keyBytes []byte, audience, apiKey, addr, clientPem string) (*Client, error) { + c := Client{} + + creds, err := credentials.NewClientTLSFromFile(clientPem, "") + if err != nil { + return nil, fmt.Errorf("could not process clientPem credentials: %v", err) + } + + c.addr = addr + + conn, err := grpc.Dial(c.addr, grpc.WithTransportCredentials(creds)) + if err != nil { + return nil, fmt.Errorf("could not connect to %q: %v", c.addr, err) + } + c.conn = conn + c.GRPC = pb.NewProwClient(c.conn) + + if len(audience) == 0 { + return nil, errors.New("audience cannot be empty") + } + + c.audience = audience + + if len(apiKey) == 0 { + return nil, errors.New("apiKey cannot be empty") + } + + c.apiKey = apiKey + + if len(keyBytes) == 0 { + return nil, errors.New("keyBytes cannot be empty") + } + + c.keyBytes = keyBytes + + tokenSource, err := googleOAuth.JWTAccessTokenSourceFromJSON(c.keyBytes, c.audience) + if err != nil { + return nil, fmt.Errorf("could not create tokenSource: %v", err) + } + + c.tokenSource = tokenSource + + return &c, nil +} + +// MkToken generates a new JWT token with a 1h TTL. This is apparently a +// cheap operation, according to +// https://github.com/GoogleCloudPlatform/golang-samples/blob/e7a5459d85661a35c5eb4f0b5759b7b30ac6ff90/endpoints/getting-started-grpc/client/main.go#L81-L88. +func (c *Client) MkToken() (string, error) { + jwt, err := c.tokenSource.Token() + if err != nil { + return "", fmt.Errorf("could not generate JSON Web Token: %v", err) + } + + return jwt.AccessToken, nil +} + +// EmbedCredentials is used to modify a provided context so that it has the the +// necessary token and apiKey attached to it in the metadata. +func (c *Client) EmbedCredentials(ctx context.Context) (context.Context, error) { + ctxWithCreds := metadata.AppendToOutgoingContext(ctx, "x-api-key", c.apiKey) + + token, err := c.MkToken() + if err != nil { + return ctxWithCreds, err + } + + fmt.Printf("using token %q\n", token) + + ctxWithCreds = metadata.AppendToOutgoingContext(ctxWithCreds, "Authorization", fmt.Sprintf("Bearer %s", token)) + + return ctxWithCreds, nil +} + +func (c *Client) Close() { + c.conn.Close() +}