From 8ab04ef4995df104a590af2a503c5a88a0697aca Mon Sep 17 00:00:00 2001 From: Shamim Mohamed Date: Wed, 24 Oct 2018 14:06:52 -0700 Subject: [PATCH] Send auth headers (#370) Mostly plumbing --- CHANGELOG.md | 4 +++ cmd/dosa/headers.go | 34 +++++++++++++++++++ cmd/dosa/main.go | 19 ++++++----- cmd/dosa/options.go | 27 ++++++++------- cmd/dosa/query.go | 2 +- cmd/dosa/schema.go | 8 ++--- connectors/memory/memory.go | 2 +- connectors/yarpc/helpers.go | 11 ++++-- connectors/yarpc/helpers_test.go | 13 ++++++++ connectors/yarpc/yarpc.go | 57 ++++++++++++++++++++------------ type.go | 2 +- 11 files changed, 127 insertions(+), 52 deletions(-) create mode 100644 cmd/dosa/headers.go diff --git a/CHANGELOG.md b/CHANGELOG.md index b6423baa..97094120 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v3.3.1 (unreleased) + - CLI: Send Auth header + - Library: send CallerName in header + ## v3.3.0 (2018-10-04) - Switch to gofrs/uuid (#361) - Cleanup EnsureValidRangeConditions (#364) diff --git a/cmd/dosa/headers.go b/cmd/dosa/headers.go new file mode 100644 index 00000000..40fdc54b --- /dev/null +++ b/cmd/dosa/headers.go @@ -0,0 +1,34 @@ +// Copyright (c) 2018 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package main + +import ( + "fmt" + "os" + "strings" +) + +// Returns the set of headers required for auth: currently only X-Auth-Params-Email. +func getAuthHeaders() map[string]string { + return map[string]string{ + "X-Auth-Params-Email": fmt.Sprintf("%s@uber.com", strings.ToLower(os.Getenv("USER"))), + } +} diff --git a/cmd/dosa/main.go b/cmd/dosa/main.go index f598dcd0..ba9a8533 100644 --- a/cmd/dosa/main.go +++ b/cmd/dosa/main.go @@ -23,10 +23,11 @@ package main import ( "bytes" "fmt" - "github.com/jessevdk/go-flags" - "github.com/uber-go/dosa" "os" "os/exec" + + "github.com/jessevdk/go-flags" + "github.com/uber-go/dosa" ) // for testing, we make exit an overridable routine @@ -40,11 +41,11 @@ type queryClientProvider func(opts GlobalOptions, scope, prefix, path, structNam // these are overridden at build-time w/ the -ldflags -X option var ( - version = "0.0.0" - githash = "master" - timestamp = "now" + version = "0.0.0" + githash = "master" + timestamp = "now" javaclientVersion = "1.1.0-beta" - javaclient = os.Getenv("HOME") + "/.m2/target/dependency/java-client-" + javaclientVersion + ".jar" + javaclient = os.Getenv("HOME") + "/.m2/target/dependency/java-client-" + javaclientVersion + ".jar" ) // BuildInfo reports information about the binary build environment @@ -70,7 +71,7 @@ type GlobalOptions struct { Host string `long:"host" default:"127.0.0.1" description:"The hostname or IP for the gateway."` Port string `short:"p" long:"port" default:"21300" description:"The hostname or IP for the gateway."` ServiceName string `short:"s" long:"service" default:"dosa-gateway" description:"The TChannel service name for the gateway."` - CallerName callerFlag `long:"caller" default:"dosacli-$USER" description:"Caller will override the default caller name (which is dosacli-$USER)."` + CallerName callerFlag `long:"caller" default:"dosacli-$USER" description:"The RPC Caller name."` Timeout timeFlag `long:"timeout" default:"60s" description:"The timeout for gateway requests. E.g., 100ms, 0.5s, 1s. If no unit is specified, milliseconds are assumed."` Version bool `long:"version" description:"Display version info"` } @@ -131,8 +132,8 @@ dosa manages your schema both in production and development scopes` func downloadJar() { fmt.Println("Downloading required dependencies... This may take some time...") - cmd := exec.Command( "mvn", "org.apache.maven.plugins:maven-dependency-plugin:RELEASE:copy", - "-Dartifact=com.uber.dosa:java-client:" + javaclientVersion, "-Dproject.basedir=" + os.Getenv("HOME") + "/.m2/") + cmd := exec.Command("mvn", "org.apache.maven.plugins:maven-dependency-plugin:RELEASE:copy", + "-Dartifact=com.uber.dosa:java-client:"+javaclientVersion, "-Dproject.basedir="+os.Getenv("HOME")+"/.m2/") var out bytes.Buffer var stderr bytes.Buffer cmd.Stdout = &out diff --git a/cmd/dosa/options.go b/cmd/dosa/options.go index 57281d39..c5747875 100644 --- a/cmd/dosa/options.go +++ b/cmd/dosa/options.go @@ -97,10 +97,11 @@ func provideAdminClient(opts GlobalOptions) (dosa.AdminClient, error) { } ycfg := yarpc.Config{ - Host: opts.Host, - Port: opts.Port, - CallerName: opts.CallerName.String(), - ServiceName: opts.ServiceName, + Host: opts.Host, + Port: opts.Port, + CallerName: opts.CallerName.String(), + ServiceName: opts.ServiceName, + ExtraHeaders: getAuthHeaders(), } conn, err := yarpc.NewConnector(ycfg) @@ -126,10 +127,11 @@ func provideMDClient(opts GlobalOptions) (c dosa.Client, err error) { Scope: "production", NamePrefix: "dosa_scopes_metadata", Yarpc: yarpc.Config{ - Host: opts.Host, - Port: opts.Port, - CallerName: opts.CallerName.String(), - ServiceName: opts.ServiceName, + Host: opts.Host, + Port: opts.Port, + CallerName: opts.CallerName.String(), + ServiceName: opts.ServiceName, + ExtraHeaders: getAuthHeaders(), }, } @@ -160,10 +162,11 @@ func provideShellQueryClient(opts GlobalOptions, scope, prefix, path, structName } ycfg := yarpc.Config{ - Host: opts.Host, - Port: opts.Port, - CallerName: opts.CallerName.String(), - ServiceName: opts.ServiceName, + Host: opts.Host, + Port: opts.Port, + CallerName: opts.CallerName.String(), + ServiceName: opts.ServiceName, + ExtraHeaders: getAuthHeaders(), } conn, err := yarpc.NewConnector(ycfg) diff --git a/cmd/dosa/query.go b/cmd/dosa/query.go index 557c6214..e27d0d24 100644 --- a/cmd/dosa/query.go +++ b/cmd/dosa/query.go @@ -57,7 +57,7 @@ type QueryCmd struct { Prefix string `short:"p" long:"prefix" description:"Name prefix for schema types." hidden:"true"` Path string `long:"path" description:"Path to source." required:"true"` JarPath string `short:"j" long:"jarpath" description:"Path of the jar. This jar contains schema entities."` - ClassNames []string `short:"c" long:"classnames" description:"Classes contain schema."` + ClassNames []string `short:"c" long:"classnames" description:"Classes contain schema."` provideClient queryClientProvider } diff --git a/cmd/dosa/schema.go b/cmd/dosa/schema.go index 0992be41..b32d6cb1 100644 --- a/cmd/dosa/schema.go +++ b/cmd/dosa/schema.go @@ -78,7 +78,7 @@ type SchemaCmd struct { NamePrefix string `short:"n" long:"namePrefix" description:"Name prefix for schema types."` Prefix string `short:"p" long:"prefix" description:"Name prefix for schema types." hidden:"true"` JarPath string `short:"j" long:"jarpath" description:"Path of the jar. This jar contains schema entities."` - ClassNames []string `short:"c" long:"classnames" description:"Classes contain schema."` + ClassNames []string `short:"c" long:"classnames" description:"Classes contain schema."` provideClient adminClientProvider } @@ -301,8 +301,8 @@ func (c *SchemaStatus) Execute(args []string) error { // SchemaDump contains data for executing the schema dump command type SchemaDump struct { *SchemaOptions - Format string `long:"format" short:"f" description:"output format" choice:"cql" choice:"uql" choice:"avro" default:"cql"` - JarPath string `short:"j" long:"jarpath" description:"Path of the jar. This jar contains schema entities."` + Format string `long:"format" short:"f" description:"output format" choice:"cql" choice:"uql" choice:"avro" default:"cql"` + JarPath string `short:"j" long:"jarpath" description:"Path of the jar. This jar contains schema entities."` ClassNames []string `short:"c" long:"classnames" description:"Classes contain schema."` Args struct { Paths []string `positional-arg-name:"paths"` @@ -388,7 +388,7 @@ func (c *SchemaDump) doSchemaDumpInJavaClient() { args = append(args, element) } } - + if c.Verbose { args = append(args, "-v") } diff --git a/connectors/memory/memory.go b/connectors/memory/memory.go index 38050c4a..5d6a106f 100644 --- a/connectors/memory/memory.go +++ b/connectors/memory/memory.go @@ -28,8 +28,8 @@ import ( "sync" "time" - "github.com/pkg/errors" "github.com/gofrs/uuid" + "github.com/pkg/errors" "github.com/uber-go/dosa" "github.com/uber-go/dosa/connectors/base" "github.com/uber-go/dosa/encoding" diff --git a/connectors/yarpc/helpers.go b/connectors/yarpc/helpers.go index cf4f4854..316b525f 100644 --- a/connectors/yarpc/helpers.go +++ b/connectors/yarpc/helpers.go @@ -415,7 +415,12 @@ func multiKeyValuesToRPCValues(keysSlice []map[string]dosa.FieldValue) ([]dosarp return rpcFieldsSlice, nil } -// VersionHeader returns the rpc style version header -func VersionHeader() rpc.CallOption { - return rpc.WithHeader(_version, dosa.VERSION) +// getHeaders converts the provided headers into rpc.CallOption values. A header for Version is also added. +func getHeaders(headers map[string]string) []rpc.CallOption { + hdrs := make([]rpc.CallOption, 0, len(headers)+1) + hdrs = append(hdrs, rpc.WithHeader(_version, dosa.VERSION)) + for h, v := range headers { + hdrs = append(hdrs, rpc.WithHeader(h, v)) + } + return hdrs } diff --git a/connectors/yarpc/helpers_test.go b/connectors/yarpc/helpers_test.go index e151ef6e..5b4d6b4c 100644 --- a/connectors/yarpc/helpers_test.go +++ b/connectors/yarpc/helpers_test.go @@ -21,6 +21,7 @@ package yarpc import ( + "reflect" "testing" "github.com/stretchr/testify/assert" @@ -234,3 +235,15 @@ func TestEncodeOperator(t *testing.T) { assert.Equal(t, test.rpcop, *encodeOperator(test.dop)) } } + +func TestGetHeaders(t *testing.T) { + headers := map[string]string{ + "Foo": "bar", + "Bar": "foo", + } + hdrs := getHeaders(headers) + assert.Equal(t, len(headers)+1, len(hdrs)) + for _, h := range hdrs { + assert.Equal(t, "yarpc.CallOption", reflect.TypeOf(h).String()) + } +} diff --git a/connectors/yarpc/yarpc.go b/connectors/yarpc/yarpc.go index 98586ac8..be0651b7 100644 --- a/connectors/yarpc/yarpc.go +++ b/connectors/yarpc/yarpc.go @@ -59,16 +59,18 @@ func ErrorIsConnectionRefused(err error) bool { // Config contains the YARPC connector parameters. type Config struct { - Host string `yaml:"host"` - Port string `yaml:"port"` - CallerName string `yaml:"callerName"` - ServiceName string `yaml:"serviceName"` + Host string `yaml:"host"` + Port string `yaml:"port"` + CallerName string `yaml:"callerName"` + ServiceName string `yaml:"serviceName"` + ExtraHeaders map[string]string } // Connector holds the client-side RPC interface and some schema information type Connector struct { client dosaclient.Interface dispatcher *rpc.Dispatcher + headers map[string]string } // NewConnector creates a new instance with user provided transport @@ -117,9 +119,22 @@ func NewConnector(config Config) (*Connector, error) { return &Connector{ dispatcher: dispatcher, client: client, + headers: checkHeaders(config.ExtraHeaders, config.CallerName), }, nil } +// checkHeaders ensures that X-Uber-Source is set. +func checkHeaders(headers map[string]string, caller string) map[string]string { + if headers == nil { + headers = map[string]string{} + } + // We don't just check to see if there's a value; it has to be non-empty. + if headers["X-Uber-Source"] == "" { + headers["X-Uber-Source"] = caller + } + return headers +} + // CreateIfNotExists ... func (c *Connector) CreateIfNotExists(ctx context.Context, ei *dosa.EntityInfo, values map[string]dosa.FieldValue) error { ev, err := fieldValueMapFromClientMap(values) @@ -138,7 +153,7 @@ func (c *Connector) CreateIfNotExists(ctx context.Context, ei *dosa.EntityInfo, TTL: &ttl, } - err = c.client.CreateIfNotExists(ctx, &createRequest, VersionHeader()) + err = c.client.CreateIfNotExists(ctx, &createRequest, getHeaders(c.headers)...) if err != nil { if be, ok := err.(*dosarpc.BadRequestError); ok { if be.ErrorCode != nil && *be.ErrorCode == errCodeAlreadyExists { @@ -171,7 +186,7 @@ func (c *Connector) Upsert(ctx context.Context, ei *dosa.EntityInfo, values map[ TTL: &ttl, } - err = c.client.Upsert(ctx, &upsertRequest, VersionHeader()) + err = c.client.Upsert(ctx, &upsertRequest, getHeaders(c.headers)...) if !dosarpc.Dosa_Upsert_Helper.IsException(err) { return errors.Wrap(err, "failed to Upsert due to network issue") @@ -199,7 +214,7 @@ func (c *Connector) MultiUpsert(ctx context.Context, ei *dosa.EntityInfo, multiV // TTL: &ttl, mgode@ has not yet committed origin/ttl-for-multi-upsert } - response, err := c.client.MultiUpsert(ctx, request, VersionHeader()) + response, err := c.client.MultiUpsert(ctx, request, getHeaders(c.headers)...) if err != nil { if !dosarpc.Dosa_MultiUpsert_Helper.IsException(err) { return nil, errors.Wrap(err, "failed to MultiUpsert due to network issue") @@ -251,7 +266,7 @@ func (c *Connector) Read(ctx context.Context, ei *dosa.EntityInfo, keys map[stri FieldsToRead: rpcMinimumFields, } - response, err := c.client.Read(ctx, readRequest, VersionHeader()) + response, err := c.client.Read(ctx, readRequest, getHeaders(c.headers)...) if err != nil { if be, ok := err.(*dosarpc.BadRequestError); ok { if be.ErrorCode != nil && *be.ErrorCode == errCodeNotFound { @@ -300,7 +315,7 @@ func (c *Connector) MultiRead(ctx context.Context, ei *dosa.EntityInfo, keys []m FieldsToRead: rpcMinimumFields, } - response, err := c.client.MultiRead(ctx, request, VersionHeader()) + response, err := c.client.MultiRead(ctx, request, getHeaders(c.headers)...) if err != nil { if !dosarpc.Dosa_MultiRead_Helper.IsException(err) { return nil, errors.Wrap(err, "failed to MultiRead due to network issue") @@ -343,7 +358,7 @@ func (c *Connector) Remove(ctx context.Context, ei *dosa.EntityInfo, keys map[st KeyValues: rpcFields, } - err = c.client.Remove(ctx, removeRequest, VersionHeader()) + err = c.client.Remove(ctx, removeRequest, getHeaders(c.headers)...) if err != nil { if !dosarpc.Dosa_Remove_Helper.IsException(err) { return errors.Wrap(err, "failed to Remove due to network issue") @@ -367,7 +382,7 @@ func (c *Connector) MultiRemove(ctx context.Context, ei *dosa.EntityInfo, multiK KeyValues: keyValues, } - response, err := c.client.MultiRemove(ctx, request, VersionHeader()) + response, err := c.client.MultiRemove(ctx, request, getHeaders(c.headers)...) if err != nil { if !dosarpc.Dosa_MultiRemove_Helper.IsException(err) { return nil, errors.Wrap(err, "failed to MultiRemove due to network issue") @@ -399,7 +414,7 @@ func (c *Connector) RemoveRange(ctx context.Context, ei *dosa.EntityInfo, column Conditions: rpcConditions, } - if err := c.client.RemoveRange(ctx, request, VersionHeader()); err != nil { + if err := c.client.RemoveRange(ctx, request, getHeaders(c.headers)...); err != nil { if !dosarpc.Dosa_RemoveRange_Helper.IsException(err) { return errors.Wrap(err, "failed to RemoveRange due to network issue") } @@ -423,7 +438,7 @@ func (c *Connector) Range(ctx context.Context, ei *dosa.EntityInfo, columnCondit Conditions: rpcConditions, FieldsToRead: rpcMinimumFields, } - response, err := c.client.Range(ctx, &rangeRequest, VersionHeader()) + response, err := c.client.Range(ctx, &rangeRequest, getHeaders(c.headers)...) if err != nil { if !dosarpc.Dosa_Range_Helper.IsException(err) { return nil, "", errors.Wrap(err, "failed to Range due to network issue") @@ -472,7 +487,7 @@ func (c *Connector) Scan(ctx context.Context, ei *dosa.EntityInfo, minimumFields Limit: &limit32, FieldsToRead: rpcMinimumFields, } - response, err := c.client.Scan(ctx, &scanRequest, VersionHeader()) + response, err := c.client.Scan(ctx, &scanRequest, getHeaders(c.headers)...) if err != nil { if !dosarpc.Dosa_Scan_Helper.IsException(err) { return nil, "", errors.Wrap(err, "failed to Scan due to network issue") @@ -497,7 +512,7 @@ func (c *Connector) CheckSchema(ctx context.Context, scope, namePrefix string, e Scope: &scope, NamePrefix: &namePrefix, } - response, err := c.client.CheckSchema(ctx, &csr, VersionHeader()) + response, err := c.client.CheckSchema(ctx, &csr, getHeaders(c.headers)...) if err != nil { if !dosarpc.Dosa_CheckSchema_Helper.IsException(err) { return dosa.InvalidVersion, errors.Wrap(err, "failed to CheckSchema due to network issue") @@ -520,7 +535,7 @@ func (c *Connector) CanUpsertSchema(ctx context.Context, scope, namePrefix strin Scope: &scope, NamePrefix: &namePrefix, } - response, err := c.client.CanUpsertSchema(ctx, &csr, VersionHeader()) + response, err := c.client.CanUpsertSchema(ctx, &csr, getHeaders(c.headers)...) if err != nil { if !dosarpc.Dosa_CanUpsertSchema_Helper.IsException(err) { return dosa.InvalidVersion, errors.Wrap(err, "failed to CanUpsertSchema due to network issue") @@ -540,7 +555,7 @@ func (c *Connector) UpsertSchema(ctx context.Context, scope, namePrefix string, EntityDefs: rpcEds, } - response, err := c.client.UpsertSchema(ctx, request, VersionHeader()) + response, err := c.client.UpsertSchema(ctx, request, getHeaders(c.headers)...) if err != nil { if !dosarpc.Dosa_UpsertSchema_Helper.IsException(err) { return nil, errors.Wrap(err, "failed to UpsertSchema due to network issue") @@ -566,7 +581,7 @@ func (c *Connector) UpsertSchema(ctx context.Context, scope, namePrefix string, // CheckSchemaStatus checks the status of specific version of schema func (c *Connector) CheckSchemaStatus(ctx context.Context, scope, namePrefix string, version int32) (*dosa.SchemaStatus, error) { request := dosarpc.CheckSchemaStatusRequest{Scope: &scope, NamePrefix: &namePrefix, Version: &version} - response, err := c.client.CheckSchemaStatus(ctx, &request, VersionHeader()) + response, err := c.client.CheckSchemaStatus(ctx, &request, getHeaders(c.headers)...) if err != nil { if !dosarpc.Dosa_CheckSchemaStatus_Helper.IsException(err) { @@ -612,7 +627,7 @@ func (c *Connector) CreateScope(ctx context.Context, md *dosa.ScopeMetadata) err Metadata: &mds, } - if err = c.client.CreateScope(ctx, request, VersionHeader()); err != nil { + if err = c.client.CreateScope(ctx, request, getHeaders(c.headers)...); err != nil { if !dosarpc.Dosa_CreateScope_Helper.IsException(err) { return errors.Wrap(err, "failed to CreateScope due to network issue") } @@ -632,7 +647,7 @@ func (c *Connector) TruncateScope(ctx context.Context, scope string) error { Requester: dosa.GetUsername(), } - if err := c.client.TruncateScope(ctx, request, VersionHeader()); err != nil { + if err := c.client.TruncateScope(ctx, request, getHeaders(c.headers)...); err != nil { if !dosarpc.Dosa_TruncateScope_Helper.IsException(err) { return errors.Wrap(err, "failed to TruncateScope due to network issue") } @@ -652,7 +667,7 @@ func (c *Connector) DropScope(ctx context.Context, scope string) error { Requester: dosa.GetUsername(), } - if err := c.client.DropScope(ctx, request, VersionHeader()); err != nil { + if err := c.client.DropScope(ctx, request, getHeaders(c.headers)...); err != nil { if !dosarpc.Dosa_DropScope_Helper.IsException(err) { return errors.Wrap(err, "failed to DropScope due to network issue") } diff --git a/type.go b/type.go index a18cddf9..46d34bd2 100644 --- a/type.go +++ b/type.go @@ -21,8 +21,8 @@ package dosa import ( - "github.com/pkg/errors" "github.com/gofrs/uuid" + "github.com/pkg/errors" ) //go:generate stringer -type=Type