[buildbucket] Add a BuildBucket wrapper library
IN-990 #comment
TEST: go test ./..
Change-Id: Ic3f7b2d8ec0fd74e0b6b3d37ed89747d83d92752
diff --git a/buildbucket/build.go b/buildbucket/build.go
new file mode 100644
index 0000000..f7fce73
--- /dev/null
+++ b/buildbucket/build.go
@@ -0,0 +1,28 @@
+// Copyright 2019 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 buildbucket
+
+import (
+ buildbucketpb "go.chromium.org/luci/buildbucket/proto"
+)
+
+// Build is a helper for reading Buildbucket Build information.
+type Build buildbucketpb.Build
+
+// GitilesCommit returns the Gitiles commit that triggered this build, or nil if this
+// build was not triggered by a Gitiltes commit.
+func (b Build) GitilesCommit() *buildbucketpb.GitilesCommit {
+ return b.Input.GitilesCommit
+}
+
+// Property reads a specific input Property from this builder. Returns false with a nil
+// Property if not found.
+func (b Build) Property(name string) (*Property, bool) {
+ prop, ok := b.Input.Properties.Fields[name]
+ if !ok {
+ return nil, false
+ }
+ return &Property{name: name, value: prop}, true
+}
diff --git a/buildbucket/builder.go b/buildbucket/builder.go
new file mode 100644
index 0000000..5e5c09b
--- /dev/null
+++ b/buildbucket/builder.go
@@ -0,0 +1,45 @@
+// Copyright 2019 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 buildbucket
+
+import (
+ "flag"
+ "fmt"
+ "strings"
+
+ buildbucketpb "go.chromium.org/luci/buildbucket/proto"
+)
+
+// builderIDFlag identifies a Builder. This is a convenience type for reading a builder id
+// from command line flags.
+type builderIDFlag buildbucketpb.BuilderID
+
+func (b builderIDFlag) String() string {
+ return fmt.Sprintf("%s/%s/%s", b.Project, b.Bucket, b.Builder)
+}
+
+// Set implements flag.Value
+func (b *builderIDFlag) Set(input string) error {
+ parts := strings.SplitN(input, "/", 3)
+ if len(parts) != 3 {
+ return fmt.Errorf("invalid builder: %s must have form 'project/bucket/builder'", input)
+ }
+
+ b.Project = parts[0]
+ b.Bucket = parts[1]
+ b.Builder = parts[2]
+ return nil
+}
+
+// Get returns the parsed flag value. The output can be cast as a buildbucketpb.BuilderID.
+func (b builderIDFlag) Get() interface{} {
+ return buildbucketpb.BuilderID(b)
+}
+
+// BuilderID returns a flag.Value for reading a builder ID from a string. The format of
+// the input is project/bucket/build.
+func BuilderID() flag.Getter {
+ return &builderIDFlag{}
+}
diff --git a/buildbucket/builder_test.go b/buildbucket/builder_test.go
new file mode 100644
index 0000000..41664a1
--- /dev/null
+++ b/buildbucket/builder_test.go
@@ -0,0 +1,135 @@
+// Copyright 2019 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 buildbucket
+
+import (
+ "flag"
+ "reflect"
+ "testing"
+
+ buildbucketpb "go.chromium.org/luci/buildbucket/proto"
+)
+
+func TestBuilderIDGetter(t *testing.T) {
+ tests := []struct {
+ // The name of this test case
+ name string
+
+ // The string to parse into a BuilderID
+ input string
+
+ // The expected result.
+ output buildbucketpb.BuilderID
+
+ // Whether to expect an error
+ expectErr bool
+ }{
+ {
+ name: "should parse an input string into a BuilderID",
+ input: "project/bucket/builder",
+ output: buildbucketpb.BuilderID{
+ Project: "project",
+ Bucket: "bucket",
+ Builder: "builder",
+ },
+ }, {
+ name: "should err when the input contains < 2 fields",
+ expectErr: true,
+ output: buildbucketpb.BuilderID{},
+ }, {
+ name: "should err when the input is empty",
+ expectErr: true,
+ output: buildbucketpb.BuilderID{},
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ value := BuilderID()
+ err := value.Set(tt.input)
+ if err != nil != tt.expectErr {
+ if tt.expectErr {
+ t.Error("expected an err but got nil")
+ } else {
+ t.Errorf("wanted %v but got an err: %v", tt.output, err)
+ }
+ }
+
+ // Compare flag values directly rather than using reflect.DeepEqual to prove
+ // that `Get` returns a valid object.
+ builderID := value.Get().(buildbucketpb.BuilderID)
+ if !reflect.DeepEqual(builderID, tt.output) {
+ t.Errorf("got\n%v\nbut wanted:\n%v", builderID, tt.output)
+ }
+ })
+ }
+}
+
+func TestBuilderIDString(t *testing.T) {
+ tests := []struct {
+ // The name of this test case
+ name string
+
+ // The input BuilderID
+ input flag.Value
+
+ // The expected string.
+ output string
+
+ // Whether to expect an error
+ expectErr bool
+ }{
+ {
+ name: "should format the ID as a string",
+ input: &builderIDFlag{
+ Project: "project",
+ Bucket: "bucket",
+ Builder: "builder",
+ },
+ output: "project/bucket/builder",
+ }, {
+ name: "when the ID is empty",
+ input: &builderIDFlag{
+ Project: "",
+ Bucket: "",
+ Builder: "",
+ },
+ output: "//",
+ }, {
+ name: "when Project is empty",
+ input: &builderIDFlag{
+ Project: "",
+ Bucket: "bucket",
+ Builder: "builder",
+ },
+ output: "/bucket/builder",
+ }, {
+ name: "when Bucket is empty",
+ input: &builderIDFlag{
+ Project: "project",
+ Bucket: "",
+ Builder: "builder",
+ },
+ output: "project//builder",
+ }, {
+ name: "when Builder is empty",
+ input: &builderIDFlag{
+ Project: "project",
+ Bucket: "bucket",
+ Builder: "",
+ },
+ output: "project/bucket/",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ output := tt.input.String()
+ if output != tt.output {
+ t.Errorf("got %q but wanted %q", output, tt.output)
+ }
+ })
+ }
+}
diff --git a/buildbucket/builds.go b/buildbucket/builds.go
new file mode 100644
index 0000000..5952e14
--- /dev/null
+++ b/buildbucket/builds.go
@@ -0,0 +1,31 @@
+// Copyright 2019 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 buildbucket
+
+import (
+ "context"
+ "fmt"
+
+ "go.chromium.org/luci/auth"
+ buildbucketpb "go.chromium.org/luci/buildbucket/proto"
+ "go.chromium.org/luci/grpc/prpc"
+)
+
+// DefaultHost is the default Buildbucket server.
+const DefaultHost = "cr-buildbucket.appspot.com"
+
+// NewBuildsClient returns a new BuildsClient.
+func NewBuildsClient(ctx context.Context, host string, opts auth.Options) (buildbucketpb.BuildsClient, error) {
+ authenticator := auth.NewAuthenticator(ctx, auth.SilentLogin, opts)
+ httpClient, err := authenticator.Client()
+ if err != nil {
+ return nil, fmt.Errorf("failed to get authenticated http client: %v", err)
+ }
+
+ return buildbucketpb.NewBuildsPRPCClient(&prpc.Client{
+ C: httpClient,
+ Host: host,
+ }), nil
+}
diff --git a/buildbucket/property.go b/buildbucket/property.go
new file mode 100644
index 0000000..395b365
--- /dev/null
+++ b/buildbucket/property.go
@@ -0,0 +1,28 @@
+// Copyright 2019 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 buildbucket
+
+import (
+ structpb "github.com/golang/protobuf/ptypes/struct"
+)
+
+// Property is a Build input property.
+//
+// Implement different "TypedValue" methods on this type as needed.
+type Property struct {
+ name string
+ value *structpb.Value
+}
+
+// Name returns the name of this property.
+func (p Property) Name() string {
+ return p.name
+}
+
+// StringValue returns the value of this property as a string. Returns the empty string if
+// the value is unset or is not a string.
+func (p Property) StringValue() string {
+ return p.value.GetStringValue()
+}
diff --git a/go.mod b/go.mod
index 391052d..f3aaa7b 100644
--- a/go.mod
+++ b/go.mod
@@ -9,9 +9,10 @@
github.com/googleapis/gax-go v2.0.2+incompatible // indirect
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
github.com/jtolds/gls v4.2.1+incompatible // indirect
+ github.com/julienschmidt/httprouter v1.2.0 // indirect
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 // indirect
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
- go.chromium.org/luci v0.0.0-20181218015242-20acb618582d
+ go.chromium.org/luci v0.0.0-20181004001148-1bfb80352368
go.opencensus.io v0.19.0 // indirect
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9
golang.org/x/net v0.0.0-20181217023233-e147a9138326
diff --git a/go.sum b/go.sum
index 6c28b65..a87a6ef 100644
--- a/go.sum
+++ b/go.sum
@@ -1,4 +1,5 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
git.apache.org/thrift.git v0.0.0-20181218151757-9b75e4fe745a/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
@@ -22,6 +23,8 @@
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
@@ -36,6 +39,8 @@
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
+go.chromium.org/luci v0.0.0-20181004001148-1bfb80352368 h1:ijyFyFRPye+it8+bvyo09R1zrqgThy8/8PJxuWquPMg=
+go.chromium.org/luci v0.0.0-20181004001148-1bfb80352368/go.mod h1:MIQewVTLvOvc0UioV0JNqTNO/RspKFS0XEeoKrOxsdM=
go.chromium.org/luci v0.0.0-20181218015242-20acb618582d h1:WWlp6PQtC8FyaxytRO5UBYFBDcPOYy6+o7JmcvgLMuU=
go.chromium.org/luci v0.0.0-20181218015242-20acb618582d/go.mod h1:MIQewVTLvOvc0UioV0JNqTNO/RspKFS0XEeoKrOxsdM=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
@@ -54,6 +59,7 @@
golang.org/x/net v0.0.0-20181217023233-e147a9138326 h1:iCzOf0xz39Tstp+Tu/WwyGjUXCk34QhQORRxBeXXTA4=
golang.org/x/net v0.0.0-20181217023233-e147a9138326/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890 h1:uESlIz09WIHT2I+pasSXcpLYqYK8wHcdCetU3VuMBJE=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -67,6 +73,7 @@
golang.org/x/tools v0.0.0-20181219222714-6e267b5cc78e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181220000619-583d854617af/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
+google.golang.org/api v0.1.0 h1:K6z2u68e86TPdSdefXdzvXgR1zEMa+459vBSfWYAZkI=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=