[tilo][resultstore] Add code to connect to ResultStore
IN-699 #comment
Change-Id: Ia324e83a8d0dfb044b4facb5420507036e5c9311
diff --git a/tilo/resultstore/resultstore.go b/tilo/resultstore/resultstore.go
new file mode 100644
index 0000000..1243411
--- /dev/null
+++ b/tilo/resultstore/resultstore.go
@@ -0,0 +1,78 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package resultstore
+
+import (
+ "context"
+ "crypto/x509"
+ "fmt"
+
+ "go.chromium.org/luci/auth"
+ api "google.golang.org/genproto/googleapis/devtools/resultstore/v2"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/credentials"
+ "google.golang.org/grpc/credentials/oauth"
+)
+
+// Connect returns a new Service connected to the ResultStore backend at the given host.
+func Connect(ctx context.Context, host string) (Service, error) {
+ conn, err := connectToGRPCHost(ctx, host)
+ if err != nil {
+ return nil, err
+ }
+ return NewService(api.NewResultStoreUploadClient(conn)), nil
+}
+
+// AuthMode specifies how to authenticate with ResultStore.
+type AuthMode string
+
+// AuthMode constants.
+const (
+ // LUCIAuth uses LUCI auth with SilentLogin. See the docs at
+ // go.chromium.org/luci/common/auth#SilentLogin for more details. This mode should
+ // always be used in production.
+ LUCIAuth AuthMode = "luci"
+
+ // GAEDefaultAuth uses the Google application default credentials, which are read from
+ // the environment variable GOOGLE_APPLICATION_CREDENTIALS. This is useful for local
+ // debugging and testing.
+ GAEDefaultAuth AuthMode = "gae_default"
+)
+
+const (
+ // Google Cloud API scope required to use ResultStore Upload API.
+ scope = "https://www.googleapis.com/auth/cloud-platform"
+)
+
+func connectToGRPCHost(ctx context.Context, host string) (*grpc.ClientConn, error) {
+ // TODO(IN-699): AuthMode and Options should be initialized by command-line flags.
+ var authOpts auth.Options
+ perRPC, err := perRPCCredentials(ctx, GAEDefaultAuth, authOpts)
+ if err != nil {
+ return nil, err
+ }
+
+ pool, err := x509.SystemCertPool()
+ if err != nil {
+ return nil, fmt.Errorf("failed to create cert pool: %v", err)
+ }
+ transportCreds := credentials.NewClientTLSFromCert(pool, "")
+ return grpc.Dial(host,
+ grpc.WithTransportCredentials(transportCreds),
+ grpc.WithPerRPCCredentials(perRPC),
+ )
+}
+
+func perRPCCredentials(ctx context.Context, authMode AuthMode, authOpts auth.Options) (credentials.PerRPCCredentials, error) {
+ switch authMode {
+ case LUCIAuth:
+ authenticator := auth.NewAuthenticator(ctx, auth.SilentLogin, authOpts)
+ return authenticator.PerRPCCredentials()
+ case GAEDefaultAuth:
+ return oauth.NewApplicationDefault(ctx, scope)
+ default:
+ return nil, fmt.Errorf("invalid authenticatation mode: %v", authMode)
+ }
+}
diff --git a/tilo/resultstore/service.go b/tilo/resultstore/service.go
index 702050c..5743e45 100644
--- a/tilo/resultstore/service.go
+++ b/tilo/resultstore/service.go
@@ -69,6 +69,8 @@
// NewService creates a new Service. Set useFakeUUIDs to true to use fake UUIDs for
// testing. authToken is the authorization token to use for the invocation. The same
// token must be used in all requests to modify a single invocation.
+//
+// Visible for testing only. Use `Connect` instead.
func NewService(client api.ResultStoreUploadClient) Service {
return &service{client: client}
}