[resultstore] Organize connection and environment code
* Put environment code in environment.go
* Connect() -> NewUploadClient()
* Add Flags and Options for parsing conn info from the
command line.
Change-Id: I83c4da307a847c1024633c410dd87c6696c3a559
diff --git a/resultstore/resultstore.go b/resultstore/environment.go
similarity index 61%
rename from resultstore/resultstore.go
rename to resultstore/environment.go
index e083ab1..d0c12d3 100644
--- a/resultstore/resultstore.go
+++ b/resultstore/environment.go
@@ -4,42 +4,7 @@
package resultstore
-import (
- "context"
- "crypto/x509"
- "fmt"
-
- api "google.golang.org/genproto/googleapis/devtools/resultstore/v2"
- "google.golang.org/grpc"
- "google.golang.org/grpc/credentials"
-)
-
-var (
- // Google Cloud API scope required to use ResultStore Upload API.
- RequiredScopes = []string{
- "https://www.googleapis.com/auth/cloud-platform",
- }
-)
-
-// Connect returns a new UploadClient connected to the ResultStore backend at the given host.
-func Connect(ctx context.Context, environment Environment, creds credentials.PerRPCCredentials) (*UploadClient, error) {
- pool, err := x509.SystemCertPool()
- if err != nil {
- return nil, fmt.Errorf("failed to create cert pool: %v", err)
- }
-
- transportCreds := credentials.NewClientTLSFromCert(pool, "")
-
- conn, err := grpc.Dial(
- environment.GRPCServiceAddress(),
- grpc.WithTransportCredentials(transportCreds),
- grpc.WithPerRPCCredentials(creds),
- )
- if err != nil {
- return nil, err
- }
- return NewUploadClient(api.NewResultStoreUploadClient(conn)), nil
-}
+import "fmt"
// Environment describes which ResultStore environment to use.
type Environment string
diff --git a/resultstore/upload_client.go b/resultstore/upload_client.go
index 43bf5a2..02709bd 100644
--- a/resultstore/upload_client.go
+++ b/resultstore/upload_client.go
@@ -6,16 +6,107 @@
import (
"context"
+ "crypto/x509"
+ "flag"
+ "fmt"
"log"
"github.com/google/uuid"
+ "go.chromium.org/luci/auth"
+ "go.chromium.org/luci/auth/client/authcli"
+ "go.chromium.org/luci/hardcoded/chromeinfra"
api "google.golang.org/genproto/googleapis/devtools/resultstore/v2"
"google.golang.org/genproto/protobuf/field_mask"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/credentials"
)
-// NewUploadClient creates a new UploadClient. This is visible for testing; Use `Connect` instead.
-func NewUploadClient(client api.ResultStoreUploadClient) *UploadClient {
- return &UploadClient{client: client}
+// Returns the list of Google Cloud API scopes required to use the ResultStore Upload API.
+func requiredScopes() []string {
+ return []string{"https://www.googleapis.com/auth/cloud-platform"}
+}
+
+// UploadClientFlags are used to parse commnad-line options for creating a new ResultStore
+// UploadClient. The options are, in turn, used to generate UploadClientOptions. Example:
+//
+// var cf UploadClientFlags
+//
+// func init() {
+// cf.Register(flag.CommandLine)
+// }
+//
+// func Main(ctx context.Background()) {
+// flag.Parse()
+// opts, err := cf.Options(ctx)
+// // check err ...
+// client, err := NewClient(ctx, opts)
+// // check err ...
+// }
+type UploadClientFlags struct {
+ authFlags authcli.Flags
+ environ Environment
+}
+
+// Register sets these UploadClient flags on the given flag.FlagSet.
+func (f *UploadClientFlags) Register(in *flag.FlagSet) {
+ // LUCI auth flags
+ defaultAuthOpts := chromeinfra.DefaultAuthOptions()
+ defaultAuthOpts.Scopes = append(defaultAuthOpts.Scopes, requiredScopes()...)
+ f.authFlags.Register(in, defaultAuthOpts)
+
+ // ResultStore flags.
+ environs := []Environment{Production, Staging}
+ in.Var(&f.environ, "environment", fmt.Sprintf("ResultStore environment: %v", environs))
+}
+
+// Options returns UploadClientOptions created from this UploadClientFlags' inputs.
+func (f *UploadClientFlags) Options() (*UploadClientOptions, error) {
+ authOpts, err := f.authFlags.Options()
+ if err != nil {
+ return nil, fmt.Errorf("failed to create LUCI auth options: %v", err)
+ }
+ return &UploadClientOptions{
+ Environ: f.environ,
+ AuthOpts: authOpts,
+ }, nil
+}
+
+// UploadClientOptions are used to create new UploadClients. See UploadClientFlags for
+// example usage.
+type UploadClientOptions struct {
+ Environ Environment
+ AuthOpts auth.Options
+}
+
+// NewClient returns a new UploadClient connected to a ResultStore backend.
+func NewClient(ctx context.Context, opts UploadClientOptions) (*UploadClient, error) {
+ // Generate transport credentials.
+ pool, err := x509.SystemCertPool()
+ if err != nil {
+ return nil, fmt.Errorf("failed to create cert pool: %v", err)
+ }
+ tcreds, err := credentials.NewClientTLSFromCert(pool, ""), nil
+ if err != nil {
+ return nil, err
+ }
+ // Generate per RPC credentials.
+ authenticator := auth.NewAuthenticator(ctx, auth.SilentLogin, opts.AuthOpts)
+ pcreds, err := authenticator.PerRPCCredentials()
+ if err != nil {
+ return nil, err
+ }
+ conn, err := grpc.Dial(
+ opts.Environ.GRPCServiceAddress(),
+ grpc.WithTransportCredentials(tcreds),
+ grpc.WithPerRPCCredentials(pcreds),
+ )
+ if err != nil {
+ return nil, err
+ }
+ return &UploadClient{
+ client: api.NewResultStoreUploadClient(conn),
+ conn: conn,
+ }, nil
}
// UploadClient wraps the ResultStoreUpload client libraries.
@@ -24,10 +115,18 @@
// Context object contains a non-empty string value for TestUUIDKey, that value is
// used instead. This is done by calling `SetTestUUID(ctx, "uuid")`.
//
-// UploadClient requires an Invocation's authorization token to be set in the provided Context.
-// This can be done by calling: `SetAuthToken(ctx, "auth-token")`.
+// UploadClient requires an Invocation's authorization token to be set in the provided
+// Context. This can be done by calling: `SetAuthToken(ctx, "auth-token")`.
+//
+// The user should Close() the client when finished.
type UploadClient struct {
client api.ResultStoreUploadClient
+ conn *grpc.ClientConn
+}
+
+// Close closes this UploadClient's connection to ResultStore.
+func (c *UploadClient) Close() error {
+ return c.conn.Close()
}
// CreateInvocation creates an Invocation in ResultStore. This must be called before
diff --git a/resultstore/upload_client_test.go b/resultstore/upload_client_test.go
index 2e72f77..8e831c5 100644
--- a/resultstore/upload_client_test.go
+++ b/resultstore/upload_client_test.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-package resultstore_test
+package resultstore
import (
"context"
@@ -10,10 +10,8 @@
"testing"
"time"
- "fuchsia.googlesource.com/tools/resultstore"
"fuchsia.googlesource.com/tools/resultstore/mocks"
"github.com/golang/mock/gomock"
-
api "google.golang.org/genproto/googleapis/devtools/resultstore/v2"
"google.golang.org/genproto/protobuf/field_mask"
)
@@ -37,7 +35,7 @@
)
type tester struct {
- client *resultstore.UploadClient
+ client *UploadClient
mock *mocks.MockResultStoreUploadClient
}
@@ -58,13 +56,13 @@
{
method: "CreateConfiguration",
description: "should rmake an RPC to create a Configuration",
- output: &resultstore.Configuration{
+ output: &Configuration{
Name: "resultstore_configuration_name",
ID: "configuration_id",
InvocationID: "invocation_id",
},
execute: func(ctx context.Context, tester *tester) (interface{}, error) {
- input := &resultstore.Configuration{
+ input := &Configuration{
ID: "configuration_id",
InvocationID: "invocation_id",
Properties: map[string]string{"key": "value"},
@@ -94,24 +92,24 @@
{
method: "CreateConfiguredTarget",
description: "should make an RPC to create a ConfiguredTarget",
- output: &resultstore.ConfiguredTarget{
+ output: &ConfiguredTarget{
Name: "resultstore_configured_target_name",
- ID: &resultstore.ConfiguredTargetID{
+ ID: &ConfiguredTargetID{
InvocationID: "invocation_id",
ConfigID: "configuration_id",
TargetID: "target_id",
},
},
execute: func(ctx context.Context, tester *tester) (interface{}, error) {
- input := &resultstore.ConfiguredTarget{
- ID: &resultstore.ConfiguredTargetID{
+ input := &ConfiguredTarget{
+ ID: &ConfiguredTargetID{
InvocationID: "invocation_id",
TargetID: "target_id",
ConfigID: "configuration_id",
},
Properties: map[string]string{"key": "value"},
StartTime: may18_1993,
- Status: resultstore.Passed,
+ Status: Passed,
}
response := &api.ConfiguredTarget{
@@ -139,12 +137,12 @@
{
method: "CreateInvocation",
description: "should make an RPC to create an Invocation",
- output: &resultstore.Invocation{
+ output: &Invocation{
Name: "resultstore_invocation_name",
ID: "invocation_id",
},
execute: func(ctx context.Context, tester *tester) (interface{}, error) {
- input := &resultstore.Invocation{
+ input := &Invocation{
ProjectID: "123456789",
ID: "invocation_id",
Users: []string{"user"},
@@ -152,7 +150,7 @@
Properties: map[string]string{"key": "value"},
LogURL: "http://test.log",
StartTime: may18_1993,
- Status: resultstore.Passed,
+ Status: Passed,
}
response := &api.Invocation{
@@ -177,21 +175,21 @@
{
method: "CreateTarget",
description: "should make an RPC to create a Target",
- output: &resultstore.Target{
+ output: &Target{
Name: "resultstore_target_name",
- ID: &resultstore.TargetID{
+ ID: &TargetID{
ID: "target_id",
InvocationID: "invocation_id",
},
},
execute: func(ctx context.Context, tester *tester) (interface{}, error) {
- input := &resultstore.Target{
- ID: &resultstore.TargetID{
+ input := &Target{
+ ID: &TargetID{
ID: "target_id",
},
Properties: map[string]string{"key": "value"},
StartTime: may18_1993,
- Status: resultstore.Passed,
+ Status: Passed,
}
response := &api.Target{
@@ -218,17 +216,17 @@
{
method: "CreateTestAction",
description: "should make an RPC to create a Test Action",
- output: &resultstore.TestAction{
+ output: &TestAction{
Name: "resultstore_action_name",
- ID: &resultstore.TestActionID{
+ ID: &TestActionID{
InvocationID: "invocation_id",
ConfigID: "configuration_id",
TargetID: "target_id",
},
},
execute: func(ctx context.Context, tester *tester) (interface{}, error) {
- input := &resultstore.TestAction{
- ID: &resultstore.TestActionID{
+ input := &TestAction{
+ ID: &TestActionID{
ID: "test",
InvocationID: "invocation_id",
TargetID: "target_id",
@@ -237,7 +235,7 @@
TestSuite: "test_suite",
TestLogURI: "http://test.log",
StartTime: may18_1993,
- Status: resultstore.Passed,
+ Status: Passed,
}
response := &api.Action{
@@ -308,23 +306,23 @@
{
method: "UpdateConfiguredTarget",
description: "should make an RPC to update a ConfiguredTarget",
- output: &resultstore.ConfiguredTarget{
+ output: &ConfiguredTarget{
Name: "resultstore_configured_target_name",
- ID: &resultstore.ConfiguredTargetID{
+ ID: &ConfiguredTargetID{
InvocationID: "invocation_id",
ConfigID: "configuration_id",
TargetID: "target_id",
},
},
execute: func(ctx context.Context, tester *tester) (interface{}, error) {
- input := &resultstore.ConfiguredTarget{
- ID: &resultstore.ConfiguredTargetID{
+ input := &ConfiguredTarget{
+ ID: &ConfiguredTargetID{
InvocationID: "invocation_id",
TargetID: "target_id",
ConfigID: "configuration_id",
},
Properties: map[string]string{"key": "value"},
- Status: resultstore.Passed,
+ Status: Passed,
StartTime: may18_1993,
Duration: time.Hour,
}
@@ -354,12 +352,12 @@
{
method: "UpdateInvocation",
description: "should make an RPC to update an Invocation",
- output: &resultstore.Invocation{
+ output: &Invocation{
Name: "resultstore_invocation_name",
ID: "invocation_id",
},
execute: func(ctx context.Context, tester *tester) (interface{}, error) {
- input := &resultstore.Invocation{
+ input := &Invocation{
ID: "invocation_id",
Properties: map[string]string{"key": "value"},
ProjectID: "project_id",
@@ -368,7 +366,7 @@
Users: []string{"users"},
Labels: []string{"label"},
LogURL: "url",
- Status: resultstore.Passed,
+ Status: Passed,
}
response := &api.Invocation{
@@ -394,23 +392,23 @@
{
method: "UpdateTarget",
description: "should make an RPC to update a Target",
- output: &resultstore.Target{
+ output: &Target{
Name: "resultstore_target_name",
- ID: &resultstore.TargetID{
+ ID: &TargetID{
ID: "target_id",
InvocationID: "invocation_id",
},
},
execute: func(ctx context.Context, tester *tester) (interface{}, error) {
- input := &resultstore.Target{
- ID: &resultstore.TargetID{
+ input := &Target{
+ ID: &TargetID{
ID: "target_id",
InvocationID: "invocation_id",
},
Properties: map[string]string{"key": "value"},
StartTime: may18_1993,
Duration: time.Hour,
- Status: resultstore.Passed,
+ Status: Passed,
}
response := &api.Target{
@@ -437,9 +435,9 @@
{
method: "UpdateTestAction",
description: "should make an RPC to update a Test Action",
- output: &resultstore.TestAction{
+ output: &TestAction{
Name: "resultstore_action_name",
- ID: &resultstore.TestActionID{
+ ID: &TestActionID{
ID: "action_id",
InvocationID: "invocation_id",
ConfigID: "configuration_id",
@@ -447,8 +445,8 @@
},
},
execute: func(ctx context.Context, tester *tester) (interface{}, error) {
- input := &resultstore.TestAction{
- ID: &resultstore.TestActionID{
+ input := &TestAction{
+ ID: &TestActionID{
ID: "test",
InvocationID: "invocation_id",
TargetID: "target_id",
@@ -458,7 +456,7 @@
TestLogURI: "http://test.log",
StartTime: may18_1993,
Duration: time.Hour,
- Status: resultstore.Passed,
+ Status: Passed,
}
response := &api.Action{
@@ -486,20 +484,20 @@
},
}
- setup := func(t *testing.T) (context.Context, *resultstore.UploadClient, *mocks.MockResultStoreUploadClient, *gomock.Controller) {
- ctx, err := resultstore.SetTestUUID(context.Background(), testUUID)
+ setup := func(t *testing.T) (context.Context, *UploadClient, *mocks.MockResultStoreUploadClient, *gomock.Controller) {
+ ctx, err := SetTestUUID(context.Background(), testUUID)
if err != nil {
t.Fatalf("failed to set test uuid: %v", err)
}
- ctx, err = resultstore.SetAuthToken(ctx, testAuthToken)
+ ctx, err = SetAuthToken(ctx, testAuthToken)
if err != nil {
t.Fatalf("failed to set test auth token: %v", err)
}
controller := gomock.NewController(t)
mock := mocks.NewMockResultStoreUploadClient(controller)
- client := resultstore.NewUploadClient(mock)
+ client := &UploadClient{client: mock}
return ctx, client, mock, controller
}