зеркало из https://github.com/golang/build.git
cmd/coordinator,cmd/retrybuilds: add wipe API to coordinator
As part of our migration to combine codebases of the Build Dashboard and the Coordinator, the first step is to start calling a Coordinator API for wiping release status of failed builds. This adds a gRPC API to the coordinator, listening on the same port as the HTTPS listeners. The Coordinator API in this implementation simply validates and forwards a request to the dashboard API. This change also updates cmd/retrybuilds to optionally use the Coordinator gRPC API for wiping. Tested locally using the live Dashboard API. Updates golang/go#34744 Change-Id: I4b34b064625193eb11a280565d701605064a8443 Reviewed-on: https://go-review.googlesource.com/c/build/+/219120 Run-TryBot: Alexander Rakoczy <alex@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Carlos Amedee <carlos@golang.org>
This commit is contained in:
Родитель
3693b7b9d8
Коммит
b077d0cce9
|
@ -45,7 +45,9 @@ import (
|
|||
"unicode"
|
||||
|
||||
"go4.org/syncutil"
|
||||
grpc "grpc.go4.org"
|
||||
"golang.org/x/build/cmd/coordinator/protos"
|
||||
"google.golang.org/grpc"
|
||||
grpc4 "grpc.go4.org"
|
||||
|
||||
"cloud.google.com/go/errorreporting"
|
||||
"cloud.google.com/go/storage"
|
||||
|
@ -162,8 +164,9 @@ func listenAndServeTLS() {
|
|||
}
|
||||
|
||||
func serveTLS(ln net.Listener) {
|
||||
// Support HTTP/2 for gRPC handlers.
|
||||
config := &tls.Config{
|
||||
NextProtos: []string{"http/1.1"},
|
||||
NextProtos: []string{"http/1.1", "h2"},
|
||||
}
|
||||
|
||||
if autocertManager != nil {
|
||||
|
@ -240,6 +243,9 @@ func (fn loggerFunc) CreateSpan(event string, optText ...string) spanlog.Span {
|
|||
// autocertManager is non-nil if LetsEncrypt is in use.
|
||||
var autocertManager *autocert.Manager
|
||||
|
||||
// grpcServer is a shared gRPC server. It is global, as it needs to be used in places that aren't factored otherwise.
|
||||
var grpcServer = grpc.NewServer()
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
|
@ -293,12 +299,14 @@ func main() {
|
|||
|
||||
addHealthCheckers(context.Background())
|
||||
|
||||
cc, err := grpc.NewClient(http.DefaultClient, "https://maintner.golang.org")
|
||||
cc, err := grpc4.NewClient(http.DefaultClient, "https://maintner.golang.org")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
maintnerClient = apipb.NewMaintnerServiceClient(cc)
|
||||
|
||||
gs := &gRPCServer{dashboardURL: "https://build.golang.org"}
|
||||
protos.RegisterCoordinatorServer(grpcServer, gs)
|
||||
http.HandleFunc("/", handleStatus)
|
||||
http.HandleFunc("/debug/goroutines", handleDebugGoroutines)
|
||||
http.HandleFunc("/debug/watcher/", handleDebugWatcher)
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: coordinator.proto
|
||||
|
||||
package protos
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
// ClearResultsRequest specifies the data needed to clear a result.
|
||||
type ClearResultsRequest struct {
|
||||
// builder is the builder to clear results.
|
||||
Builder string `protobuf:"bytes,1,opt,name=builder,proto3" json:"builder,omitempty"`
|
||||
// hash is the commit hash to clear results.
|
||||
Hash string `protobuf:"bytes,2,opt,name=hash,proto3" json:"hash,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *ClearResultsRequest) Reset() { *m = ClearResultsRequest{} }
|
||||
func (m *ClearResultsRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*ClearResultsRequest) ProtoMessage() {}
|
||||
func (*ClearResultsRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_99e779eb11ceee19, []int{0}
|
||||
}
|
||||
|
||||
func (m *ClearResultsRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_ClearResultsRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *ClearResultsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_ClearResultsRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *ClearResultsRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ClearResultsRequest.Merge(m, src)
|
||||
}
|
||||
func (m *ClearResultsRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_ClearResultsRequest.Size(m)
|
||||
}
|
||||
func (m *ClearResultsRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_ClearResultsRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_ClearResultsRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *ClearResultsRequest) GetBuilder() string {
|
||||
if m != nil {
|
||||
return m.Builder
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *ClearResultsRequest) GetHash() string {
|
||||
if m != nil {
|
||||
return m.Hash
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ClearResultsResponse struct {
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *ClearResultsResponse) Reset() { *m = ClearResultsResponse{} }
|
||||
func (m *ClearResultsResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*ClearResultsResponse) ProtoMessage() {}
|
||||
func (*ClearResultsResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_99e779eb11ceee19, []int{1}
|
||||
}
|
||||
|
||||
func (m *ClearResultsResponse) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_ClearResultsResponse.Unmarshal(m, b)
|
||||
}
|
||||
func (m *ClearResultsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_ClearResultsResponse.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *ClearResultsResponse) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ClearResultsResponse.Merge(m, src)
|
||||
}
|
||||
func (m *ClearResultsResponse) XXX_Size() int {
|
||||
return xxx_messageInfo_ClearResultsResponse.Size(m)
|
||||
}
|
||||
func (m *ClearResultsResponse) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_ClearResultsResponse.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_ClearResultsResponse proto.InternalMessageInfo
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*ClearResultsRequest)(nil), "protos.ClearResultsRequest")
|
||||
proto.RegisterType((*ClearResultsResponse)(nil), "protos.ClearResultsResponse")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("coordinator.proto", fileDescriptor_99e779eb11ceee19) }
|
||||
|
||||
var fileDescriptor_99e779eb11ceee19 = []byte{
|
||||
// 151 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4c, 0xce, 0xcf, 0x2f,
|
||||
0x4a, 0xc9, 0xcc, 0x4b, 0x2c, 0xc9, 0x2f, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x03,
|
||||
0x53, 0xc5, 0x4a, 0xce, 0x5c, 0xc2, 0xce, 0x39, 0xa9, 0x89, 0x45, 0x41, 0xa9, 0xc5, 0xa5, 0x39,
|
||||
0x25, 0xc5, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x12, 0x5c, 0xec, 0x49, 0xa5, 0x99,
|
||||
0x39, 0x29, 0xa9, 0x45, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x30, 0xae, 0x90, 0x10, 0x17,
|
||||
0x4b, 0x46, 0x62, 0x71, 0x86, 0x04, 0x13, 0x58, 0x18, 0xcc, 0x56, 0x12, 0xe3, 0x12, 0x41, 0x35,
|
||||
0xa4, 0xb8, 0x20, 0x3f, 0xaf, 0x38, 0xd5, 0x28, 0x8a, 0x8b, 0xdb, 0x19, 0x61, 0xb3, 0x90, 0x37,
|
||||
0x17, 0x0f, 0xb2, 0x32, 0x21, 0x69, 0x88, 0x5b, 0x8a, 0xf5, 0xb0, 0xb8, 0x40, 0x4a, 0x06, 0xbb,
|
||||
0x24, 0xc4, 0x64, 0x25, 0x86, 0x24, 0x88, 0x07, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x10,
|
||||
0xa0, 0xb6, 0xbb, 0xdc, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConnInterface
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion6
|
||||
|
||||
// CoordinatorClient is the client API for Coordinator service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
|
||||
type CoordinatorClient interface {
|
||||
// ClearResults clears build failures from the coordinator to force them to rebuild.
|
||||
ClearResults(ctx context.Context, in *ClearResultsRequest, opts ...grpc.CallOption) (*ClearResultsResponse, error)
|
||||
}
|
||||
|
||||
type coordinatorClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewCoordinatorClient(cc grpc.ClientConnInterface) CoordinatorClient {
|
||||
return &coordinatorClient{cc}
|
||||
}
|
||||
|
||||
func (c *coordinatorClient) ClearResults(ctx context.Context, in *ClearResultsRequest, opts ...grpc.CallOption) (*ClearResultsResponse, error) {
|
||||
out := new(ClearResultsResponse)
|
||||
err := c.cc.Invoke(ctx, "/protos.Coordinator/ClearResults", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// CoordinatorServer is the server API for Coordinator service.
|
||||
type CoordinatorServer interface {
|
||||
// ClearResults clears build failures from the coordinator to force them to rebuild.
|
||||
ClearResults(context.Context, *ClearResultsRequest) (*ClearResultsResponse, error)
|
||||
}
|
||||
|
||||
// UnimplementedCoordinatorServer can be embedded to have forward compatible implementations.
|
||||
type UnimplementedCoordinatorServer struct {
|
||||
}
|
||||
|
||||
func (*UnimplementedCoordinatorServer) ClearResults(ctx context.Context, req *ClearResultsRequest) (*ClearResultsResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ClearResults not implemented")
|
||||
}
|
||||
|
||||
func RegisterCoordinatorServer(s *grpc.Server, srv CoordinatorServer) {
|
||||
s.RegisterService(&_Coordinator_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _Coordinator_ClearResults_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ClearResultsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(CoordinatorServer).ClearResults(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/protos.Coordinator/ClearResults",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(CoordinatorServer).ClearResults(ctx, req.(*ClearResultsRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _Coordinator_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "protos.Coordinator",
|
||||
HandlerType: (*CoordinatorServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "ClearResults",
|
||||
Handler: _Coordinator_ClearResults_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "coordinator.proto",
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package protos;
|
||||
|
||||
service Coordinator {
|
||||
// ClearResults clears build failures from the coordinator to force them to rebuild.
|
||||
rpc ClearResults(ClearResultsRequest) returns (ClearResultsResponse) {}
|
||||
}
|
||||
|
||||
// ClearResultsRequest specifies the data needed to clear a result.
|
||||
message ClearResultsRequest {
|
||||
// builder is the builder to clear results.
|
||||
string builder = 1;
|
||||
// hash is the commit hash to clear results.
|
||||
string hash = 2;
|
||||
}
|
||||
|
||||
message ClearResultsResponse {}
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright 2020 The Go 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 protos
|
||||
|
||||
// Run "go generate" in this directory to update. You need to have:
|
||||
//
|
||||
// - a protoc binary (see https://github.com/golang/protobuf#installation)
|
||||
// - go get -u github.com/golang/protobuf/protoc-gen-go
|
||||
|
||||
//go:generate protoc --proto_path=$GOPATH/src:. --go_out=plugins=grpc:. coordinator.proto
|
|
@ -0,0 +1,155 @@
|
|||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.13
|
||||
// +build linux darwin
|
||||
|
||||
// Code related to the Build Results API.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/build/cmd/coordinator/protos"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
grpcstatus "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type gRPCServer struct {
|
||||
// embed an UnimplementedCoordinatorServer to avoid errors when adding new RPCs to the proto.
|
||||
*protos.UnimplementedCoordinatorServer
|
||||
|
||||
// dashboardURL is the base URL of the Dashboard service (https://build.golang.org)
|
||||
dashboardURL string
|
||||
}
|
||||
|
||||
// ClearResults implements the ClearResults RPC call from the CoordinatorService.
|
||||
//
|
||||
// It currently hits the build Dashboard service to clear a result.
|
||||
// TODO(golang.org/issue/34744) - Change to wipe build status from the Coordinator itself after findWork
|
||||
// starts using maintner.
|
||||
func (g *gRPCServer) ClearResults(ctx context.Context, req *protos.ClearResultsRequest) (*protos.ClearResultsResponse, error) {
|
||||
key, err := keyFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.GetBuilder() == "" || req.GetHash() == "" {
|
||||
return nil, grpcstatus.Error(codes.InvalidArgument, "Builder and Hash must be provided")
|
||||
}
|
||||
if err := g.clearFromDashboard(ctx, req.GetBuilder(), req.GetHash(), key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &protos.ClearResultsResponse{}, nil
|
||||
}
|
||||
|
||||
// clearFromDashboard calls the dashboard API to remove a build.
|
||||
// TODO(golang.org/issue/34744) - Remove after switching to wiping in the Coordinator.
|
||||
func (g *gRPCServer) clearFromDashboard(ctx context.Context, builder, hash, key string) error {
|
||||
u, err := url.Parse(g.dashboardURL)
|
||||
if err != nil {
|
||||
log.Printf("gRPCServer.ClearResults: Error parsing dashboardURL %q: %v", g.dashboardURL, err)
|
||||
return grpcstatus.Error(codes.Internal, codes.Internal.String())
|
||||
}
|
||||
u.Path = "/clear-results"
|
||||
form := url.Values{
|
||||
"builder": {builder},
|
||||
"hash": {hash},
|
||||
"key": {key},
|
||||
}
|
||||
u.RawQuery = form.Encode() // The Dashboard API does not read the POST body.
|
||||
clearReq, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), nil)
|
||||
if err != nil {
|
||||
log.Printf("gRPCServer.ClearResults: error creating http request: %v", err)
|
||||
return grpcstatus.Error(codes.Internal, codes.Internal.String())
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(clearReq)
|
||||
if err != nil {
|
||||
log.Printf("gRPCServer.ClearResults: error performing wipe for %q/%q: %v", builder, hash, err)
|
||||
return grpcstatus.Error(codes.Internal, codes.Internal.String())
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
log.Printf("gRPCServer.ClearResults: error reading response body for %q/%q: %v", builder, hash, err)
|
||||
return grpcstatus.Error(codes.Internal, codes.Internal.String())
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Printf("gRPCServer.ClearResults: bad status from dashboard: %v (%q)", resp.StatusCode, resp.Status)
|
||||
code, ok := statusToCode[resp.StatusCode]
|
||||
if !ok {
|
||||
code = codes.Internal
|
||||
}
|
||||
return grpcstatus.Error(code, code.String())
|
||||
}
|
||||
if len(body) == 0 {
|
||||
return nil
|
||||
}
|
||||
dr := new(dashboardResponse)
|
||||
if err := json.Unmarshal(body, dr); err != nil {
|
||||
log.Printf("gRPCServer.ClearResults: error parsing response body for %q/%q: %v", builder, hash, err)
|
||||
return grpcstatus.Error(codes.Internal, codes.Internal.String())
|
||||
}
|
||||
if dr.Error == "datastore: concurrent transaction" {
|
||||
return grpcstatus.Error(codes.Aborted, dr.Error)
|
||||
}
|
||||
if dr.Error != "" {
|
||||
return grpcstatus.Error(codes.FailedPrecondition, dr.Error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// dashboardResponse mimics the dashResponse struct from app/appengine.
|
||||
// TODO(golang.org/issue/34744) - Remove after switching to wiping in the Coordinator.
|
||||
type dashboardResponse struct {
|
||||
// Error is an error string describing the API response. The dashboard API semantics are to always return a
|
||||
// 200, and populate this field with details.
|
||||
Error string `json:"Error"`
|
||||
// Response a human friendly response from the API. It is not populated for build status clear responses.
|
||||
Response string `json:"Response"`
|
||||
}
|
||||
|
||||
// statusToCode maps HTTP status codes to gRPC codes. It purposefully only contains statuses we care to map.
|
||||
// TODO(golang.org/issue/34744) - Move to shared file or library.
|
||||
var statusToCode = map[int]codes.Code{
|
||||
http.StatusOK: codes.OK,
|
||||
http.StatusBadRequest: codes.InvalidArgument,
|
||||
http.StatusUnauthorized: codes.Unauthenticated,
|
||||
http.StatusForbidden: codes.PermissionDenied,
|
||||
http.StatusNotFound: codes.NotFound,
|
||||
http.StatusConflict: codes.Aborted,
|
||||
http.StatusGone: codes.DataLoss,
|
||||
http.StatusTooManyRequests: codes.ResourceExhausted,
|
||||
http.StatusInternalServerError: codes.Internal,
|
||||
http.StatusNotImplemented: codes.Unimplemented,
|
||||
http.StatusServiceUnavailable: codes.Unavailable,
|
||||
http.StatusGatewayTimeout: codes.DeadlineExceeded,
|
||||
}
|
||||
|
||||
// keyFromContext loads a builder key from request metadata.
|
||||
//
|
||||
// The metadata format is prefixed with "builder " to avoid collisions with OAuth:
|
||||
// authorization: builder MYKEY
|
||||
//
|
||||
// TODO(golang.org/issue/34744) - Move to shared file or library. This would make a nice UnaryServerInterceptor.
|
||||
// TODO(golang.org/issue/34744) - Currently allows the Build Dashboard to validate tokens, but we should validate here.
|
||||
func keyFromContext(ctx context.Context) (string, error) {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return "", grpcstatus.Error(codes.Internal, codes.Internal.String())
|
||||
}
|
||||
auth := md.Get("authorization")
|
||||
if len(auth) == 0 || len(auth[0]) < 9 || !strings.HasPrefix(auth[0], "builder ") {
|
||||
return "", grpcstatus.Error(codes.Unauthenticated, codes.Unauthenticated.String())
|
||||
}
|
||||
key := auth[0][8:len(auth[0])]
|
||||
return key, nil
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.13
|
||||
// +build linux darwin
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/build/cmd/coordinator/protos"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
grpcstatus "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// fakeDashboard implements a fake version of the Build Dashboard API for testing.
|
||||
// TODO(golang.org/issue/34744) - Remove with build dashboard API client removal.
|
||||
type fakeDashboard struct {
|
||||
returnBody string
|
||||
returnStatus int
|
||||
}
|
||||
|
||||
func (f *fakeDashboard) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
http.Error(rw, "method must be POST", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if r.URL.Path != "/clear-results" {
|
||||
http.NotFound(rw, r)
|
||||
return
|
||||
}
|
||||
if f.returnStatus != 0 && f.returnStatus != http.StatusOK {
|
||||
http.Error(rw, `{"Error": "`+http.StatusText(f.returnStatus)+`"}`, f.returnStatus)
|
||||
return
|
||||
}
|
||||
r.ParseForm()
|
||||
if r.FormValue("builder") == "" || r.FormValue("hash") == "" || r.FormValue("key") == "" {
|
||||
http.Error(rw, `{"Error": "missing builder, hash, or key"}`, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if f.returnBody == "" {
|
||||
rw.Write([]byte("{}"))
|
||||
return
|
||||
}
|
||||
rw.Write([]byte(f.returnBody))
|
||||
return
|
||||
}
|
||||
|
||||
func TestClearResults(t *testing.T) {
|
||||
req := &protos.ClearResultsRequest{Builder: "somebuilder", Hash: "somehash"}
|
||||
fd := new(fakeDashboard)
|
||||
s := httptest.NewServer(fd)
|
||||
defer s.Close()
|
||||
|
||||
md := metadata.New(map[string]string{"authorization": "builder mykey"})
|
||||
ctx := metadata.NewIncomingContext(context.Background(), md)
|
||||
gs := &gRPCServer{dashboardURL: s.URL}
|
||||
_, err := gs.ClearResults(ctx, req)
|
||||
if err != nil {
|
||||
t.Errorf("cli.ClearResults(%v, %v) = _, %v, wanted no error", ctx, req, err)
|
||||
}
|
||||
|
||||
if grpcstatus.Code(err) != codes.OK {
|
||||
t.Errorf("cli.ClearResults(%v, %v) = _, %v, wanted %v", ctx, req, err, codes.OK)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearResultsErrors(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
key string
|
||||
req *protos.ClearResultsRequest
|
||||
apiCode int
|
||||
apiResp string
|
||||
wantCode codes.Code
|
||||
}{
|
||||
{
|
||||
desc: "missing key",
|
||||
req: &protos.ClearResultsRequest{
|
||||
Builder: "local",
|
||||
Hash: "ABCDEF1234567890",
|
||||
},
|
||||
wantCode: codes.Unauthenticated,
|
||||
},
|
||||
{
|
||||
desc: "missing builder",
|
||||
key: "somekey",
|
||||
req: &protos.ClearResultsRequest{
|
||||
Hash: "ABCDEF1234567890",
|
||||
},
|
||||
wantCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
desc: "missing hash",
|
||||
key: "somekey",
|
||||
req: &protos.ClearResultsRequest{
|
||||
Builder: "local",
|
||||
},
|
||||
wantCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
desc: "dashboard API error",
|
||||
key: "somekey",
|
||||
req: &protos.ClearResultsRequest{
|
||||
Builder: "local",
|
||||
Hash: "ABCDEF1234567890",
|
||||
},
|
||||
apiCode: http.StatusBadRequest,
|
||||
wantCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
desc: "dashboard API unknown status",
|
||||
key: "somekey",
|
||||
req: &protos.ClearResultsRequest{
|
||||
Builder: "local",
|
||||
Hash: "ABCDEF1234567890",
|
||||
},
|
||||
apiCode: http.StatusPermanentRedirect,
|
||||
wantCode: codes.Internal,
|
||||
},
|
||||
{
|
||||
desc: "dashboard API retryable error",
|
||||
key: "somekey",
|
||||
req: &protos.ClearResultsRequest{
|
||||
Builder: "local",
|
||||
Hash: "ABCDEF1234567890",
|
||||
},
|
||||
apiCode: http.StatusOK,
|
||||
apiResp: `{"Error": "datastore: concurrent transaction"}`,
|
||||
wantCode: codes.Aborted,
|
||||
},
|
||||
{
|
||||
desc: "dashboard API other error",
|
||||
key: "somekey",
|
||||
req: &protos.ClearResultsRequest{
|
||||
Builder: "local",
|
||||
Hash: "ABCDEF1234567890",
|
||||
},
|
||||
apiCode: http.StatusOK,
|
||||
apiResp: `{"Error": "no matching builder found"}`,
|
||||
wantCode: codes.FailedPrecondition,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
fd := &fakeDashboard{returnStatus: c.apiCode, returnBody: c.apiResp}
|
||||
s := httptest.NewServer(fd)
|
||||
defer s.Close()
|
||||
|
||||
md := metadata.New(map[string]string{"authorization": "builder " + c.key})
|
||||
ctx := metadata.NewIncomingContext(context.Background(), md)
|
||||
gs := &gRPCServer{dashboardURL: s.URL}
|
||||
_, err := gs.ClearResults(ctx, c.req)
|
||||
|
||||
if grpcstatus.Code(err) != c.wantCode {
|
||||
t.Errorf("cli.ClearResults(%v, %v) = _, %v, wanted %v", ctx, c.req, err, c.wantCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -556,6 +556,12 @@ func healthCheckerHandler(hc *healthChecker) http.Handler {
|
|||
func uptime() time.Duration { return time.Since(processStartTime).Round(time.Second) }
|
||||
|
||||
func handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||
// Support gRPC handlers. handleStatus is our toplevel ("/") handler, so reroute to the gRPC server for
|
||||
// matching requests.
|
||||
if r.ProtoMajor == 2 && strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc") {
|
||||
grpcServer.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
if r.URL.Path != "/" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
|
|
|
@ -18,8 +18,10 @@ package main
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
@ -32,6 +34,13 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/build/cmd/coordinator/protos"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -45,6 +54,8 @@ var (
|
|||
sendMasterKey = flag.Bool("sendmaster", false, "send the master key in request instead of a builder-specific key; allows overriding actions of revoked keys")
|
||||
branch = flag.String("branch", "master", "branch to find flakes from (for use with -redo-flaky)")
|
||||
substr = flag.String("substr", "", "if non-empty, redoes all build failures whose failure logs contain this substring")
|
||||
// TODO(golang.org/issue/34744) - remove after gRPC API for ClearResults is deployed
|
||||
grpcHost = flag.String("grpc-host", "", "(EXPERIMENTAL) use gRPC for communicating with the API.")
|
||||
)
|
||||
|
||||
type Failure struct {
|
||||
|
@ -56,11 +67,20 @@ type Failure struct {
|
|||
func main() {
|
||||
flag.Parse()
|
||||
*builderPrefix = strings.TrimSuffix(*builderPrefix, "/")
|
||||
cl := client{}
|
||||
if *grpcHost != "" {
|
||||
tc := &tls.Config{InsecureSkipVerify: strings.HasPrefix(*grpcHost, "localhost:")}
|
||||
cc, err := grpc.DialContext(context.Background(), *grpcHost, grpc.WithTransportCredentials(credentials.NewTLS(tc)))
|
||||
if err != nil {
|
||||
log.Fatalf("grpc.DialContext(_, %q, _) = %v, wanted no error", *grpcHost, err)
|
||||
}
|
||||
cl.coordinator = protos.NewCoordinatorClient(cc)
|
||||
}
|
||||
if *logHash != "" {
|
||||
substr := "/log/" + *logHash
|
||||
for _, f := range failures() {
|
||||
if strings.Contains(f.LogURL, substr) {
|
||||
wipe(f.Builder, f.Hash)
|
||||
cl.wipe(f.Builder, f.Hash)
|
||||
}
|
||||
}
|
||||
return
|
||||
|
@ -69,7 +89,7 @@ func main() {
|
|||
foreachFailure(func(f Failure, failLog string) {
|
||||
if strings.Contains(failLog, *substr) {
|
||||
log.Printf("Restarting %+v", f)
|
||||
wipe(f.Builder, f.Hash)
|
||||
cl.wipe(f.Builder, f.Hash)
|
||||
}
|
||||
})
|
||||
return
|
||||
|
@ -78,7 +98,7 @@ func main() {
|
|||
foreachFailure(func(f Failure, failLog string) {
|
||||
if isFlaky(failLog) {
|
||||
log.Printf("Restarting flaky %+v", f)
|
||||
wipe(f.Builder, f.Hash)
|
||||
cl.wipe(f.Builder, f.Hash)
|
||||
}
|
||||
})
|
||||
return
|
||||
|
@ -91,11 +111,11 @@ func main() {
|
|||
if f.Builder != *builder {
|
||||
continue
|
||||
}
|
||||
wipe(f.Builder, f.Hash)
|
||||
cl.wipe(f.Builder, f.Hash)
|
||||
}
|
||||
return
|
||||
}
|
||||
wipe(*builder, fullHash(*hash))
|
||||
cl.wipe(*builder, fullHash(*hash))
|
||||
}
|
||||
|
||||
func foreachFailure(fn func(f Failure, failLog string)) {
|
||||
|
@ -198,9 +218,49 @@ func fullHash(h string) string {
|
|||
panic("unreachable")
|
||||
}
|
||||
|
||||
type client struct {
|
||||
coordinator protos.CoordinatorClient
|
||||
}
|
||||
|
||||
// grpcWipe wipes a git hash failure for the provided builder and hash.
|
||||
// Only the main Go repo is currently supported.
|
||||
// TODO(golang.org/issue/34744) - replace HTTP wipe with this after gRPC API for ClearResults is deployed
|
||||
func (c *client) grpcWipe(builder, hash string) {
|
||||
md := metadata.New(map[string]string{"authorization": "builder " + builderKey(builder)})
|
||||
for i := 0; i < 10; i++ {
|
||||
ctx, cancel := context.WithTimeout(metadata.NewOutgoingContext(context.Background(), md), time.Minute)
|
||||
resp, err := c.coordinator.ClearResults(ctx, &protos.ClearResultsRequest{
|
||||
Builder: builder,
|
||||
Hash: hash,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
s, _ := status.FromError(err)
|
||||
switch s.Code() {
|
||||
case codes.Aborted:
|
||||
log.Printf("Concurrent datastore transaction wiping %v %v: retrying in 1 second", builder, hash)
|
||||
time.Sleep(time.Second)
|
||||
case codes.DeadlineExceeded:
|
||||
log.Printf("Timeout wiping %v %v: retrying", builder, hash)
|
||||
default:
|
||||
log.Fatalln(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
log.Printf("cl.ClearResults(%q, %q) = %v: resp: %v", builder, hash, status.Code(err), resp)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// wipe wipes the git hash failure for the provided failure.
|
||||
// Only the main go repo is currently supported.
|
||||
func wipe(builder, hash string) {
|
||||
func (c *client) wipe(builder, hash string) {
|
||||
if *grpcHost != "" {
|
||||
// TODO(golang.org/issue/34744) - Remove HTTP logic after gRPC API for ClearResults is deployed
|
||||
// to the Coordinator.
|
||||
c.grpcWipe(builder, hash)
|
||||
return
|
||||
}
|
||||
vals := url.Values{
|
||||
"builder": {builder},
|
||||
"hash": {hash},
|
||||
|
|
12
go.mod
12
go.mod
|
@ -14,7 +14,7 @@ require (
|
|||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||
github.com/gliderlabs/ssh v0.1.1
|
||||
github.com/golang/protobuf v1.3.2
|
||||
github.com/golang/protobuf v1.3.3
|
||||
github.com/google/go-cmp v0.4.0
|
||||
github.com/google/go-github v17.0.0+incompatible
|
||||
github.com/google/go-querystring v1.0.0 // indirect
|
||||
|
@ -25,16 +25,16 @@ require (
|
|||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5
|
||||
golang.org/x/text v0.3.2
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
|
||||
google.golang.org/api v0.15.0
|
||||
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b
|
||||
google.golang.org/grpc v1.26.0
|
||||
google.golang.org/api v0.17.0
|
||||
google.golang.org/genproto v0.0.0-20200207204624-4f3edf09f4f6
|
||||
google.golang.org/grpc v1.27.1
|
||||
gopkg.in/inf.v0 v0.9.1
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919
|
||||
)
|
||||
|
|
37
go.sum
37
go.sum
|
@ -27,6 +27,7 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYU
|
|||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625 h1:ckJgFhFWywOx+YLEMIJsTb+NV6NexWICk5+AMSuz3ss=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
|
@ -57,13 +58,18 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
|||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
|
||||
|
@ -105,6 +111,7 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
|
@ -125,6 +132,8 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB
|
|||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4 h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -140,6 +149,8 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx
|
|||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f h1:hX65Cu3JDlGH3uEdK7I99Ii+9kjD6mvnnpfLdEAH0x4=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE=
|
||||
|
@ -158,13 +169,18 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -187,11 +203,14 @@ golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1 h1:gZpLHxUX5BdYLA08Lj4YCJNN/jk7KtquiArPoeX0WvA=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
|
@ -207,6 +226,8 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3
|
|||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
|
@ -221,12 +242,15 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0 h1:KKgc1aqhV8wDPbDzlDtpvyjZFY3vjz85FP7p4wcQUyI=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.15.0 h1:yzlyyDW/J0w8yNFJIhiAJy4kq74S+1DOLdawELNxFMA=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0 h1:0q95w+VuFtv4PAx4PZVQdBMmYbaCHbnfKaEiDIcVyag=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
|
@ -239,17 +263,24 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn
|
|||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b h1:c8OBoXP3kTbDWWB/oVE3FkR851p4iZ3MPadz7zXEIPU=
|
||||
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200207204624-4f3edf09f4f6 h1:tirixpud1WdjE3/NrL9ar4ot0ADfwls8sOcIf1ivRDw=
|
||||
google.golang.org/genproto v0.0.0-20200207204624-4f3edf09f4f6/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@ -263,6 +294,8 @@ grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJd
|
|||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
|
|
Загрузка…
Ссылка в новой задаче