blob: a48fa3b51c7735c276f28311c95232bb8d2571dc [file] [log] [blame]
// Copyright 2018 The Goma Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/*
Binary goma_grpc_client is a simple gRPC client of goma api.
$ goma_grpc_client [-api_key <key>] <addr> <service>.<method> <request>
*/
package main
import (
"context"
"crypto/tls"
"crypto/x509"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/proto"
"go.chromium.org/goma/server/auth/account"
"go.chromium.org/goma/server/auth/enduser"
"go.chromium.org/goma/server/log"
gomapb "go.chromium.org/goma/server/proto/api"
execpb "go.chromium.org/goma/server/proto/exec"
execlogpb "go.chromium.org/goma/server/proto/execlog"
filepb "go.chromium.org/goma/server/proto/file"
)
var (
// https://cloud.google.com/endpoints/docs/grpc/restricting-api-access-with-api-keys-grpc
apiKey = flag.String("api_key", "", "api_key")
enduserEmail = flag.String("enduser_email", "", "enduser email")
enduserGroup = flag.String("enduser_group", "", "enduser group")
enduserSAJSON = flag.String("enduser_service_account_json", "", "enduser service account json file")
tlsVerify = flag.Bool("tls_verify", true, "verifies the server's certificate chain and hostname.")
insecure = flag.Bool("insecure", false, "insecure connection, i.e. no TLS")
verbose = flag.Bool("v", false, "verbose flag")
)
type desc struct {
method func(context.Context, proto.Message) (proto.Message, error)
req proto.Message
}
func protoDesc(ctx context.Context, conn *grpc.ClientConn, servMethod string) (desc, error) {
// TODO: use proto descriptor?
switch servMethod {
case "devtools_goma.ExecService.Exec":
client := execpb.NewExecServiceClient(conn)
return desc{
method: func(ctx context.Context, req proto.Message) (proto.Message, error) {
return client.Exec(ctx, req.(*gomapb.ExecReq))
},
req: &gomapb.ExecReq{},
}, nil
case "devtools_goma.FileService.StoreFile":
client := filepb.NewFileServiceClient(conn)
return desc{
method: func(ctx context.Context, req proto.Message) (proto.Message, error) {
return client.StoreFile(ctx, req.(*gomapb.StoreFileReq))
},
req: &gomapb.StoreFileReq{},
}, nil
case "devtools_goma.FileService.LookupFile":
client := filepb.NewFileServiceClient(conn)
return desc{
method: func(ctx context.Context, req proto.Message) (proto.Message, error) {
return client.LookupFile(ctx, req.(*gomapb.LookupFileReq))
},
req: &gomapb.LookupFileReq{},
}, nil
case "devtools_goma.LogService.SaveLog":
client := execlogpb.NewLogServiceClient(conn)
return desc{
method: func(ctx context.Context, req proto.Message) (proto.Message, error) {
return client.SaveLog(ctx, req.(*gomapb.SaveLogReq))
},
req: &gomapb.SaveLogReq{},
}, nil
default:
return desc{}, fmt.Errorf("unknown service: %s", servMethod)
}
}
func setEnduser(ctx context.Context) context.Context {
if *enduserEmail == "" || *enduserGroup == "" || *enduserSAJSON == "" {
return ctx
}
logger := log.FromContext(ctx)
p := account.JSONDir{
Dir: filepath.Dir(*enduserSAJSON),
Scopes: []string{
"https://www.googleapis.com/auth/cloud-build-service",
},
}
name := filepath.Base(*enduserSAJSON)
name = strings.TrimRight(name, ".json")
a, err := p.New(name)
if err != nil {
logger.Errorf("service account %s: %v", name, err)
return ctx
}
token, err := a.Token(ctx)
if err != nil {
logger.Errorf("token %s: %v", name, err)
return ctx
}
logger.Infof("enduser email=%q group=%q token=%v", *enduserEmail, *enduserGroup, token)
return enduser.NewContext(ctx, enduser.New(*enduserEmail, *enduserGroup, token))
}
func main() {
flag.Usage = func() {
w := flag.CommandLine.Output()
fmt.Fprintf(w, "Usage of %s:\n", os.Args[0])
fmt.Fprintf(w, "%s <address> <service>.<method> <request>\n", os.Args[0])
fmt.Fprintf(w, " <address>; host:port\n")
fmt.Fprintf(w, " <service>; exported service name\n")
fmt.Fprintf(w, " ; e.g. devtools_goma.ExecService\n")
fmt.Fprintf(w, " <method> ; method name. e.g Exec\n")
fmt.Fprintf(w, " <request>; text protobuffer for request\n")
flag.PrintDefaults()
}
flag.Parse()
ctx := context.Background()
if !*verbose {
log.SetZapLogger(zap.NewNop())
}
logger := log.FromContext(ctx)
fatalf := func(format string, args ...interface{}) {
if !*verbose {
// logger.Fatalf won't print if we set zap.NewNop...
fmt.Fprintf(os.Stderr, format+"\n", args...)
}
logger.Fatalf(format, args...)
}
if flag.NArg() < 3 {
flag.Usage()
os.Exit(2)
}
address := flag.Arg(0)
servMethod := flag.Arg(1)
requestMsg := flag.Arg(2)
certPool, err := x509.SystemCertPool()
if err != nil {
fatalf("system cert pool: %v", err)
}
if certPath := os.Getenv("GOMA_SSL_EXTRA_CERT"); certPath != "" {
buf, err := ioutil.ReadFile(certPath)
if err != nil {
fatalf("cert file %q: %v", certPath, err)
}
ok := certPool.AppendCertsFromPEM(buf)
if !ok {
fatalf("set cert from $GOMA_SSL_EXTRA_CERT")
}
}
if certData := os.Getenv("GOMA_SSL_EXTRA_CERT_DATA"); certData != "" {
logger.Infof("using GOMA_SSL_EXTRA_CERT_DATA:\n%s", certData)
ok := certPool.AppendCertsFromPEM([]byte(certData))
if !ok {
fatalf("set cert from $GOMA_SSL_EXTRA_CERT_DATA")
}
}
insecureSkipVerify := !*tlsVerify
if insecureSkipVerify {
logger.Warnf("insecure skip verify. accepts any certificate")
}
opts := []grpc.DialOption{
grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
RootCAs: certPool,
InsecureSkipVerify: insecureSkipVerify,
})),
}
if *insecure {
logger.Warnf("insecure connection")
opts[0] = grpc.WithInsecure()
}
conn, err := grpc.DialContext(ctx, address, opts...)
if err != nil {
fatalf("dial: %v", err)
}
defer conn.Close()
desc, err := protoDesc(ctx, conn, servMethod)
if err != nil {
fatalf("desc: %v", err)
}
err = prototext.Unmarshal([]byte(requestMsg), desc.req)
if err != nil {
fatalf("request: %v", err)
}
if *apiKey != "" {
logger.Infof("Using api_key: %s", *apiKey)
ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs("x-api-key", *apiKey))
}
ctx = setEnduser(ctx)
md, _ := metadata.FromOutgoingContext(ctx)
logger.Debugf("outgoing metatada: %v", md)
resp, err := desc.method(ctx, desc.req)
if err != nil {
fatalf("call: %v", err)
}
fmt.Println("response:\n", prototext.Format(resp))
}