Merge pull request #2335 from weaveworks/2191-improve-weave-net-errors

Improve error reporting when invoking weave script
This commit is contained in:
Alfonso Acosta
2017-03-21 17:21:31 +01:00
committed by GitHub
17 changed files with 411 additions and 122 deletions

View File

@@ -4,10 +4,12 @@ import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
realexec "os/exec"
"regexp"
"strconv"
@@ -164,7 +166,11 @@ func (c *client) AddDNSEntry(fqdn, containerID string, ip net.IP) error {
func (c *client) PS() (map[string]PSEntry, error) {
cmd := weaveCommand("--local", "ps")
out, err := cmd.StdoutPipe()
stdOut, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
stdErr, err := cmd.StderrPipe()
if err != nil {
return nil, err
}
@@ -173,7 +179,7 @@ func (c *client) PS() (map[string]PSEntry, error) {
}
psEntriesByPrefix := map[string]PSEntry{}
scanner := bufio.NewScanner(out)
scanner := bufio.NewScanner(stdOut)
for scanner.Scan() {
line := scanner.Text()
groups := weavePsMatch.FindStringSubmatch(line)
@@ -191,9 +197,10 @@ func (c *client) PS() (map[string]PSEntry, error) {
}
}
scannerErr := scanner.Err()
slurp, _ := ioutil.ReadAll(stdErr)
cmdErr := cmd.Wait()
if cmdErr != nil {
return nil, cmdErr
return nil, fmt.Errorf("%s: %q", cmdErr, slurp)
}
if scannerErr != nil {
return nil, scannerErr
@@ -202,17 +209,27 @@ func (c *client) PS() (map[string]PSEntry, error) {
}
func (c *client) Expose() error {
output, err := weaveCommand("--local", "ps", "weave:expose").Output()
cmd := weaveCommand("--local", "ps", "weave:expose")
output, err := cmd.Output()
if err != nil {
return err
stdErr := []byte{}
if exitErr, ok := err.(*realexec.ExitError); ok {
stdErr = exitErr.Stderr
}
return fmt.Errorf("Error running weave ps: %s: %q", err, stdErr)
}
ips := ipMatch.FindAllSubmatch(output, -1)
if ips != nil {
// Alread exposed!
return nil
}
if err := weaveCommand("--local", "expose").Run(); err != nil {
return fmt.Errorf("Error running weave expose: %v", err)
cmd = weaveCommand("--local", "expose")
if _, err := cmd.Output(); err != nil {
stdErr := []byte{}
if exitErr, ok := err.(*realexec.ExitError); ok {
stdErr = exitErr.Stderr
}
return fmt.Errorf("Error running weave expose: %s: %q", err, stdErr)
}
return nil
}

View File

@@ -273,7 +273,6 @@ func appMain(flags appFlags) {
if flags.logHTTP {
handler = middleware.Log{
LogRequestHeaders: flags.logHTTPHeaders,
LogSuccess: false,
}.Wrap(handler)
}

6
vendor/github.com/weaveworks/common/errors/error.go generated vendored Normal file
View File

@@ -0,0 +1,6 @@
package errors
// Error see https://dave.cheney.net/2016/04/07/constant-errors.
type Error string
func (e Error) Error() string { return string(e) }

View File

@@ -65,18 +65,27 @@ type Client struct {
conn *grpc.ClientConn
}
// NewClient makes a new Client, given a kubernetes service address. Expects
// an address of the form <service>.<namespace>:<port>
func NewClient(address string) (*Client, error) {
// ParseKubernetesAddress splits up an address of the form <service>(.<namespace>):<port>
// into its consistuent parts. Namespace will be "default" if missing.
func ParseKubernetesAddress(address string) (service, namespace, port string, err error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
return "", "", "", err
}
parts := strings.SplitN(host, ".", 2)
service, namespace := parts[0], "default"
service, namespace = parts[0], "default"
if len(parts) == 2 {
namespace = parts[1]
}
return service, namespace, port, nil
}
// NewClient makes a new Client, given a kubernetes service address.
func NewClient(address string) (*Client, error) {
service, namespace, port, err := ParseKubernetesAddress(address)
if err != nil {
return nil, err
}
return &Client{
service: service,
namespace: namespace,

View File

@@ -22,7 +22,7 @@ func ErrorCode(err error) string {
// "500". It will also emit an OpenTracing span if you have a global tracer configured.
//
// If you want more complicated logic for translating errors into statuses,
// use 'TimeRequestStatus'.
// use 'TimeRequestHistogramStatus'.
func TimeRequestHistogram(ctx context.Context, method string, metric *prometheus.HistogramVec, f func(context.Context) error) error {
return TimeRequestHistogramStatus(ctx, method, metric, ErrorCode, f)
}

View File

@@ -1,58 +1,28 @@
package middleware
import (
"fmt"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"github.com/weaveworks/common/user"
)
// ClientUserHeaderInterceptor propagates the user ID from the context to gRPC metadata, which eventually ends up as a HTTP2 header.
func ClientUserHeaderInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
userID, err := user.GetID(ctx)
ctx, err := user.InjectIntoGRPCRequest(ctx)
if err != nil {
return err
}
md, ok := metadata.FromContext(ctx)
if !ok {
md = metadata.New(map[string]string{})
}
newCtx := ctx
if userIDs, ok := md[user.LowerOrgIDHeaderName]; ok {
switch len(userIDs) {
case 1:
if userIDs[0] != userID {
return fmt.Errorf("wrong user ID found")
}
default:
return fmt.Errorf("multiple user IDs found")
}
} else {
md = md.Copy()
md[user.LowerOrgIDHeaderName] = []string{userID}
newCtx = metadata.NewContext(ctx, md)
}
return invoker(newCtx, method, req, reply, cc, opts...)
return invoker(ctx, method, req, reply, cc, opts...)
}
// ServerUserHeaderInterceptor propagates the user ID from the gRPC metadata back to our context.
func ServerUserHeaderInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
md, ok := metadata.FromContext(ctx)
if !ok {
return nil, fmt.Errorf("no metadata")
_, ctx, err := user.ExtractFromGRPCRequest(ctx)
if err != nil {
return nil, err
}
userIDs, ok := md[user.LowerOrgIDHeaderName]
if !ok || len(userIDs) != 1 {
return nil, fmt.Errorf("no user id")
}
newCtx := user.WithID(ctx, userIDs[0])
return handler(newCtx, req)
return handler(ctx, req)
}

View File

@@ -11,15 +11,13 @@ import (
const gRPC = "gRPC"
// ServerLoggingInterceptor logs gRPC requests, errors and latency.
func ServerLoggingInterceptor(logSuccess bool) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
begin := time.Now()
resp, err := handler(ctx, req)
if err != nil {
log.Errorf("%s %s (%v) %s", gRPC, info.FullMethod, err, time.Since(begin))
} else if logSuccess {
log.Infof("%s %s (success) %s", gRPC, info.FullMethod, time.Since(begin))
}
return resp, err
var ServerLoggingInterceptor = func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
begin := time.Now()
resp, err := handler(ctx, req)
if err != nil {
log.Warnf("%s %s (%v) %s", gRPC, info.FullMethod, err, time.Since(begin))
} else {
log.Debugf("%s %s (success) %s", gRPC, info.FullMethod, time.Since(begin))
}
return resp, err
}

View File

@@ -0,0 +1,19 @@
package middleware
import (
"net/http"
"github.com/weaveworks/common/user"
)
// AuthenticateUser propagates the user ID from HTTP headers back to the request's context.
var AuthenticateUser = Func(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, ctx, err := user.ExtractFromHTTPRequest(r)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r.WithContext(ctx))
})
})

View File

@@ -67,7 +67,7 @@ func (i Instrument) getRouteName(r *http.Request) string {
if name := routeMatch.Route.GetName(); name != "" {
return name
}
if tmpl, err := routeMatch.Route.GetPathTemplate(); err != nil {
if tmpl, err := routeMatch.Route.GetPathTemplate(); err == nil {
return MakeLabelValue(tmpl)
}
}

View File

@@ -13,7 +13,6 @@ import (
// Log middleware logs http requests
type Log struct {
LogSuccess bool // LogSuccess true -> log successful queries; false -> only log failed queries
LogRequestHeaders bool // LogRequestHeaders true -> dump http headers at debug log level
}
@@ -33,24 +32,17 @@ func (l Log) Wrap(next http.Handler) http.Handler {
}
i := &interceptor{ResponseWriter: w, statusCode: http.StatusOK}
next.ServeHTTP(i, r)
if l.LogSuccess || !(100 <= i.statusCode && i.statusCode < 400) {
log.Infof("%s %s (%d) %s", r.Method, uri, i.statusCode, time.Since(begin))
if 100 <= i.statusCode && i.statusCode < 400 {
log.Debugf("%s %s (%d) %s", r.Method, uri, i.statusCode, time.Since(begin))
} else {
log.Warnf("%s %s (%d) %s", r.Method, uri, i.statusCode, time.Since(begin))
}
})
}
// Logging middleware logs each HTTP request method, path, response code and
// duration for all HTTP requests.
var Logging = Log{
LogSuccess: true,
LogRequestHeaders: false,
}
// LogFailed middleware logs each HTTP request method, path, response code and
// duration for non-2xx HTTP requests.
var LogFailed = Log{
LogSuccess: false,
}
var Logging = Log{}
// interceptor implements WriteHeader to intercept status codes. WriteHeader
// may not be called on success, so initialize statusCode with the status you

172
vendor/github.com/weaveworks/common/server/server.go generated vendored Normal file
View File

@@ -0,0 +1,172 @@
package server
import (
"flag"
"fmt"
"net"
"net/http"
_ "net/http/pprof" // anonymous import to get the pprof handler registered
"time"
log "github.com/Sirupsen/logrus"
"github.com/gorilla/mux"
"github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc"
"github.com/mwitkow/go-grpc-middleware"
"github.com/opentracing-contrib/go-stdlib/nethttp"
"github.com/opentracing/opentracing-go"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/net/context"
"google.golang.org/grpc"
"github.com/weaveworks-experiments/loki/pkg/client"
"github.com/weaveworks/common/httpgrpc"
"github.com/weaveworks/common/middleware"
"github.com/weaveworks/common/signals"
)
func init() {
tracer, err := loki.NewTracer()
if err != nil {
panic(fmt.Sprintf("Failed to create tracer: %v", err))
} else {
opentracing.InitGlobalTracer(tracer)
}
}
// Config for a Server
type Config struct {
MetricsNamespace string
HTTPListenPort int
GRPCListenPort int
ServerGracefulShutdownTimeout time.Duration
HTTPServerReadTimeout time.Duration
HTTPServerWriteTimeout time.Duration
HTTPServerIdleTimeout time.Duration
GRPCMiddleware []grpc.UnaryServerInterceptor
HTTPMiddleware []middleware.Interface
}
// RegisterFlags adds the flags required to config this to the given FlagSet
func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
f.IntVar(&cfg.HTTPListenPort, "server.http-listen-port", 80, "HTTP server listen port.")
f.IntVar(&cfg.GRPCListenPort, "server.grpc-listen-port", 9095, "gRPC server listen port.")
f.DurationVar(&cfg.ServerGracefulShutdownTimeout, "server.graceful-shutdown-timeout", 5*time.Second, "Timeout for graceful shutdowns")
f.DurationVar(&cfg.HTTPServerReadTimeout, "server.http-read-timeout", 5*time.Second, "Read timeout for HTTP server")
f.DurationVar(&cfg.HTTPServerWriteTimeout, "server.http-write-timeout", 5*time.Second, "Write timeout for HTTP server")
f.DurationVar(&cfg.HTTPServerIdleTimeout, "server.http-idle-timeout", 120*time.Second, "Idle timeout for HTTP server")
}
// Server wraps a HTTP and gRPC server, and some common initialization.
//
// Servers will be automatically instrumented for Prometheus metrics
// and Loki tracing. HTTP over gRPC
type Server struct {
cfg Config
handler *signals.Handler
httpListener net.Listener
grpcListener net.Listener
httpServer *http.Server
HTTP *mux.Router
GRPC *grpc.Server
}
// New makes a new Server
func New(cfg Config) (*Server, error) {
// Setup listeners first, so we can fail early if the port is in use.
httpListener, err := net.Listen("tcp", fmt.Sprintf(":%d", cfg.HTTPListenPort))
if err != nil {
return nil, err
}
grpcListener, err := net.Listen("tcp", fmt.Sprintf(":%d", cfg.GRPCListenPort))
if err != nil {
return nil, err
}
// Prometheus histograms for requests.
requestDuration := prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: cfg.MetricsNamespace,
Name: "request_duration_seconds",
Help: "Time (in seconds) spent serving HTTP requests.",
Buckets: prometheus.DefBuckets,
}, []string{"method", "route", "status_code", "ws"})
prometheus.MustRegister(requestDuration)
// Setup gRPC server
grpcMiddleware := []grpc.UnaryServerInterceptor{
middleware.ServerLoggingInterceptor,
middleware.ServerInstrumentInterceptor(requestDuration),
otgrpc.OpenTracingServerInterceptor(opentracing.GlobalTracer()),
}
grpcMiddleware = append(grpcMiddleware, cfg.GRPCMiddleware...)
grpcServer := grpc.NewServer(
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
grpcMiddleware...,
)),
)
// Setup HTTP server
router := mux.NewRouter()
router.Handle("/metrics", prometheus.Handler())
router.Handle("/traces", loki.Handler())
router.PathPrefix("/debug/pprof").Handler(http.DefaultServeMux)
httpMiddleware := []middleware.Interface{
middleware.Log{},
middleware.Instrument{
Duration: requestDuration,
RouteMatcher: router,
},
middleware.Func(func(handler http.Handler) http.Handler {
return nethttp.Middleware(opentracing.GlobalTracer(), handler)
}),
}
httpMiddleware = append(httpMiddleware, cfg.HTTPMiddleware...)
httpServer := &http.Server{
ReadTimeout: cfg.HTTPServerReadTimeout,
WriteTimeout: cfg.HTTPServerWriteTimeout,
IdleTimeout: cfg.HTTPServerIdleTimeout,
Handler: middleware.Merge(httpMiddleware...).Wrap(router),
}
return &Server{
cfg: cfg,
httpListener: httpListener,
grpcListener: grpcListener,
httpServer: httpServer,
handler: signals.NewHandler(log.StandardLogger()),
HTTP: router,
GRPC: grpcServer,
}, nil
}
// Run the server; blocks until SIGTERM is received.
func (s *Server) Run() {
go s.httpServer.Serve(s.httpListener)
// Setup gRPC server
// for HTTP over gRPC, ensure we don't double-count the middleware
httpgrpc.RegisterHTTPServer(s.GRPC, httpgrpc.NewServer(s.HTTP))
go s.GRPC.Serve(s.grpcListener)
defer s.GRPC.GracefulStop()
// Wait for a signal
s.handler.Loop()
}
// Stop unblocks Run().
func (s *Server) Stop() {
s.handler.Stop()
}
// Shutdown the server, gracefully. Should be defered after New().
func (s *Server) Shutdown() {
ctx, cancel := context.WithTimeout(context.Background(), s.cfg.ServerGracefulShutdownTimeout)
defer cancel() // releases resources if httpServer.Churdown completes before timeout elapses
s.httpServer.Shutdown(ctx)
s.GRPC.Stop()
}

View File

@@ -18,24 +18,58 @@ type Logger interface {
Infof(format string, args ...interface{})
}
// SignalHandlerLoop blocks until it receives a SIGINT, SIGTERM or SIGQUIT.
// For SIGINT and SIGTERM, it exits; for SIGQUIT is print a goroutine stack
// dump.
func SignalHandlerLoop(log Logger, ss ...SignalReceiver) {
// Handler handles signals, can be interrupted.
// On SIGINT or SIGTERM it will exit, on SIGQUIT it
// will dump goroutine stacks to the Logger.
type Handler struct {
log Logger
receivers []SignalReceiver
quit chan struct{}
}
// NewHandler makes a new Handler.
func NewHandler(log Logger, receivers ...SignalReceiver) *Handler {
return &Handler{
log: log,
receivers: receivers,
quit: make(chan struct{}),
}
}
// Stop the handler
func (h *Handler) Stop() {
close(h.quit)
}
// Loop handles signals.
func (h *Handler) Loop() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
buf := make([]byte, 1<<20)
for {
switch <-sigs {
case syscall.SIGINT, syscall.SIGTERM:
log.Infof("=== received SIGINT/SIGTERM ===\n*** exiting")
for _, subsystem := range ss {
subsystem.Stop()
}
select {
case <-h.quit:
h.log.Infof("=== Handler.Stop()'d ===")
return
case syscall.SIGQUIT:
stacklen := runtime.Stack(buf, true)
log.Infof("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end", buf[:stacklen])
case sig := <-sigs:
switch sig {
case syscall.SIGINT, syscall.SIGTERM:
h.log.Infof("=== received SIGINT/SIGTERM ===\n*** exiting")
for _, subsystem := range h.receivers {
subsystem.Stop()
}
return
case syscall.SIGQUIT:
stacklen := runtime.Stack(buf, true)
h.log.Infof("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end", buf[:stacklen])
}
}
}
}
// SignalHandlerLoop blocks until it receives a SIGINT, SIGTERM or SIGQUIT.
// For SIGINT and SIGTERM, it exits; for SIGQUIT is print a goroutine stack
// dump.
func SignalHandlerLoop(log Logger, ss ...SignalReceiver) {
NewHandler(log, ss...).Loop()
}

View File

@@ -10,11 +10,6 @@ import (
type mockCmd struct {
io.ReadCloser
quit chan struct{}
}
type blockingReader struct {
quit chan struct{}
}
// NewMockCmdString creates a new mock Cmd which has s on its stdout pipe
@@ -27,7 +22,6 @@ func NewMockCmdString(s string) exec.Cmd {
bytes.NewBufferString(s),
ioutil.NopCloser(nil),
},
quit: make(chan struct{}),
}
}
@@ -35,7 +29,6 @@ func NewMockCmdString(s string) exec.Cmd {
func NewMockCmd(rc io.ReadCloser) exec.Cmd {
return &mockCmd{
ReadCloser: rc,
quit: make(chan struct{}),
}
}
@@ -52,11 +45,10 @@ func (c *mockCmd) StdoutPipe() (io.ReadCloser, error) {
}
func (c *mockCmd) StderrPipe() (io.ReadCloser, error) {
return &blockingReader{c.quit}, nil
return ioutil.NopCloser(bytes.NewReader(nil)), nil
}
func (c *mockCmd) Kill() error {
close(c.quit)
return nil
}
@@ -69,13 +61,3 @@ func (c *mockCmd) Run() error {
}
func (c *mockCmd) SetEnv([]string) {}
func (b *blockingReader) Read(p []byte) (n int, err error) {
<-b.quit
return 0, nil
}
func (b *blockingReader) Close() error {
<-b.quit
return nil
}

51
vendor/github.com/weaveworks/common/user/grpc.go generated vendored Normal file
View File

@@ -0,0 +1,51 @@
package user
import (
"golang.org/x/net/context"
"google.golang.org/grpc/metadata"
)
// ExtractFromGRPCRequest extracts the user ID from the request metadata and returns
// the user ID and a context with the user ID injected.
func ExtractFromGRPCRequest(ctx context.Context) (string, context.Context, error) {
md, ok := metadata.FromContext(ctx)
if !ok {
return "", ctx, ErrNoUserID
}
userIDs, ok := md[lowerOrgIDHeaderName]
if !ok || len(userIDs) != 1 {
return "", ctx, ErrNoUserID
}
return userIDs[0], Inject(ctx, userIDs[0]), nil
}
// InjectIntoGRPCRequest injects the userID from the context into the request metadata.
func InjectIntoGRPCRequest(ctx context.Context) (context.Context, error) {
userID, err := Extract(ctx)
if err != nil {
return ctx, err
}
md, ok := metadata.FromContext(ctx)
if !ok {
md = metadata.New(map[string]string{})
}
newCtx := ctx
if userIDs, ok := md[lowerOrgIDHeaderName]; ok {
if len(userIDs) == 1 {
if userIDs[0] != userID {
return ctx, ErrDifferentIDPresent
}
} else {
return ctx, ErrTooManyUserIDs
}
} else {
md = md.Copy()
md[lowerOrgIDHeaderName] = []string{userID}
newCtx = metadata.NewContext(ctx, md)
}
return newCtx, nil
}

31
vendor/github.com/weaveworks/common/user/http.go generated vendored Normal file
View File

@@ -0,0 +1,31 @@
package user
import (
"net/http"
"golang.org/x/net/context"
)
// ExtractFromHTTPRequest extracts the user ID from the request headers and returns
// the user ID and a context with the user ID embbedded.
func ExtractFromHTTPRequest(r *http.Request) (string, context.Context, error) {
userID := r.Header.Get(orgIDHeaderName)
if userID == "" {
return "", r.Context(), ErrNoUserID
}
return userID, Inject(r.Context(), userID), nil
}
// InjectIntoHTTPRequest injects the userID from the context into the request headers.
func InjectIntoHTTPRequest(ctx context.Context, r *http.Request) error {
userID, err := Extract(ctx)
if err != nil {
return err
}
existingID := r.Header.Get(orgIDHeaderName)
if existingID != "" && existingID != userID {
return ErrDifferentIDPresent
}
r.Header.Set(orgIDHeaderName, userID)
return nil
}

View File

@@ -1,32 +1,41 @@
package user
import (
"fmt"
"golang.org/x/net/context"
"github.com/weaveworks/common/errors"
)
// UserIDContextKey is the key used in contexts to find the userid
type contextKey int
const userIDContextKey contextKey = 0
const (
// UserIDContextKey is the key used in contexts to find the userid
userIDContextKey contextKey = 0
// OrgIDHeaderName is a legacy from scope as a service.
const OrgIDHeaderName = "X-Scope-OrgID"
// orgIDHeaderName is a legacy from scope as a service.
orgIDHeaderName = "X-Scope-OrgID"
// LowerOrgIDHeaderName as gRPC / HTTP2.0 headers are lowercased.
const LowerOrgIDHeaderName = "x-scope-orgid"
// LowerOrgIDHeaderName as gRPC / HTTP2.0 headers are lowercased.
lowerOrgIDHeaderName = "x-scope-orgid"
)
// GetID returns the user
func GetID(ctx context.Context) (string, error) {
userid, ok := ctx.Value(userIDContextKey).(string)
// Errors that we return
const (
ErrNoUserID = errors.Error("no user id")
ErrDifferentIDPresent = errors.Error("different user ID already present")
ErrTooManyUserIDs = errors.Error("multiple user IDs present")
)
// Extract gets the user ID from the context
func Extract(ctx context.Context) (string, error) {
userID, ok := ctx.Value(userIDContextKey).(string)
if !ok {
return "", fmt.Errorf("no user id")
return "", ErrNoUserID
}
return userid, nil
return userID, nil
}
// WithID returns a derived context containing the user ID.
func WithID(ctx context.Context, userID string) context.Context {
// Inject returns a derived context containing the user ID.
func Inject(ctx context.Context, userID string) context.Context {
return context.WithValue(ctx, interface{}(userIDContextKey), userID)
}

2
vendor/manifest vendored
View File

@@ -1414,7 +1414,7 @@
"importpath": "github.com/weaveworks/common",
"repository": "https://github.com/weaveworks/common",
"vcs": "git",
"revision": "8da456c848efd2a13ffb5a3f9f4507c52c3f52e1",
"revision": "f94043b3da140c7a735b1f2f286d72d19014b200",
"branch": "master",
"notests": true
},