[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}
 }