mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 05:27:21 +00:00
Kubectl command headers in requests: KEP 859
This commit is contained in:
parent
2f263b24a7
commit
211fc12b67
@ -8,6 +8,7 @@ require (
|
|||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
|
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
|
||||||
github.com/evanphx/json-patch v4.9.0+incompatible
|
github.com/evanphx/json-patch v4.9.0+incompatible
|
||||||
|
github.com/google/uuid v1.1.2
|
||||||
github.com/googleapis/gnostic v0.4.1
|
github.com/googleapis/gnostic v0.4.1
|
||||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de
|
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de
|
||||||
github.com/mailru/easyjson v0.7.0 // indirect
|
github.com/mailru/easyjson v0.7.0 // indirect
|
||||||
|
1
staging/src/k8s.io/cli-runtime/go.sum
generated
1
staging/src/k8s.io/cli-runtime/go.sum
generated
@ -148,6 +148,7 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
|
|||||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
|
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2021 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package genericclioptions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
kubectlCommandHeader = "X-Kubectl-Command"
|
||||||
|
kubectlSessionHeader = "X-Kubectl-Session"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CommandHeaderRoundTripper adds a layer around the standard
|
||||||
|
// round tripper to add Request headers before delegation. Implements
|
||||||
|
// the go standard library "http.RoundTripper" interface.
|
||||||
|
type CommandHeaderRoundTripper struct {
|
||||||
|
Delegate http.RoundTripper
|
||||||
|
Headers map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommandHeaderRoundTripper adds Request headers before delegating to standard
|
||||||
|
// round tripper. These headers are kubectl command headers which
|
||||||
|
// detail the kubectl command. See SIG CLI KEP 859:
|
||||||
|
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/859-kubectl-headers
|
||||||
|
func (c *CommandHeaderRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
for header, value := range c.Headers {
|
||||||
|
req.Header.Set(header, value)
|
||||||
|
}
|
||||||
|
return c.Delegate.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseCommandHeaders fills in a map of X-Headers into the CommandHeaderRoundTripper. These
|
||||||
|
// headers are then filled into each request. For details on X-Headers see:
|
||||||
|
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/859-kubectl-headers
|
||||||
|
// Each call overwrites the previously parsed command headers (not additive).
|
||||||
|
// TODO(seans3): Parse/add flags removing PII from flag values.
|
||||||
|
func (c *CommandHeaderRoundTripper) ParseCommandHeaders(cmd *cobra.Command, args []string) {
|
||||||
|
if cmd == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Overwrites previously parsed command headers (headers not additive).
|
||||||
|
c.Headers = map[string]string{}
|
||||||
|
// Session identifier to aggregate multiple Requests from single kubectl command.
|
||||||
|
uid := uuid.New().String()
|
||||||
|
c.Headers[kubectlSessionHeader] = uid
|
||||||
|
// Iterate up the hierarchy of commands from the leaf command to create
|
||||||
|
// the full command string. Example: kubectl create secret generic
|
||||||
|
cmdStrs := []string{}
|
||||||
|
for cmd.HasParent() {
|
||||||
|
parent := cmd.Parent()
|
||||||
|
currName := strings.TrimSpace(cmd.Name())
|
||||||
|
cmdStrs = append([]string{currName}, cmdStrs...)
|
||||||
|
cmd = parent
|
||||||
|
}
|
||||||
|
currName := strings.TrimSpace(cmd.Name())
|
||||||
|
cmdStrs = append([]string{currName}, cmdStrs...)
|
||||||
|
if len(cmdStrs) > 0 {
|
||||||
|
c.Headers[kubectlCommandHeader] = strings.Join(cmdStrs, " ")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2021 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package genericclioptions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var kubectlCmd = &cobra.Command{Use: "kubectl"}
|
||||||
|
var applyCmd = &cobra.Command{Use: "apply"}
|
||||||
|
var createCmd = &cobra.Command{Use: "create"}
|
||||||
|
var secretCmd = &cobra.Command{Use: "secret"}
|
||||||
|
var genericCmd = &cobra.Command{Use: "generic"}
|
||||||
|
var authCmd = &cobra.Command{Use: "auth"}
|
||||||
|
var reconcileCmd = &cobra.Command{Use: "reconcile"}
|
||||||
|
|
||||||
|
func TestParseCommandHeaders(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
// Ordering is important; each subsequent command is added as a subcommand
|
||||||
|
// of the previous command.
|
||||||
|
commands []*cobra.Command
|
||||||
|
// Headers which should be present; but other headers may exist
|
||||||
|
expectedHeaders map[string]string
|
||||||
|
}{
|
||||||
|
"Single kubectl command example": {
|
||||||
|
commands: []*cobra.Command{kubectlCmd},
|
||||||
|
expectedHeaders: map[string]string{
|
||||||
|
kubectlCommandHeader: "kubectl",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Simple kubectl apply example": {
|
||||||
|
commands: []*cobra.Command{kubectlCmd, applyCmd},
|
||||||
|
expectedHeaders: map[string]string{
|
||||||
|
kubectlCommandHeader: "kubectl apply",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Kubectl auth reconcile example": {
|
||||||
|
commands: []*cobra.Command{kubectlCmd, authCmd, reconcileCmd},
|
||||||
|
expectedHeaders: map[string]string{
|
||||||
|
kubectlCommandHeader: "kubectl auth reconcile",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Long kubectl create secret generic example": {
|
||||||
|
commands: []*cobra.Command{kubectlCmd, createCmd, secretCmd, genericCmd},
|
||||||
|
expectedHeaders: map[string]string{
|
||||||
|
kubectlCommandHeader: "kubectl create secret generic",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
rootCmd := buildCommandChain(tc.commands)
|
||||||
|
ch := &CommandHeaderRoundTripper{}
|
||||||
|
ch.ParseCommandHeaders(rootCmd, []string{})
|
||||||
|
// Unique session ID header should always be present.
|
||||||
|
if _, found := ch.Headers[kubectlSessionHeader]; !found {
|
||||||
|
t.Errorf("expected kubectl session header (%s) is missing", kubectlSessionHeader)
|
||||||
|
}
|
||||||
|
// All expected headers must be present; but there may be extras.
|
||||||
|
for key, expectedValue := range tc.expectedHeaders {
|
||||||
|
actualValue, found := ch.Headers[key]
|
||||||
|
if found {
|
||||||
|
if expectedValue != actualValue {
|
||||||
|
t.Errorf("expected header value (%s), got (%s)", expectedValue, actualValue)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("expected header (%s) not found", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builds a hierarchy of commands in order from the passed slice of commands,
|
||||||
|
// by adding each subsequent command as a child of the previous command,
|
||||||
|
// returning the last leaf command.
|
||||||
|
func buildCommandChain(commands []*cobra.Command) *cobra.Command {
|
||||||
|
var currCmd *cobra.Command
|
||||||
|
if len(commands) > 0 {
|
||||||
|
currCmd = commands[0]
|
||||||
|
}
|
||||||
|
for i := 1; i < len(commands); i++ {
|
||||||
|
cmd := commands[i]
|
||||||
|
currCmd.AddCommand(cmd)
|
||||||
|
currCmd = cmd
|
||||||
|
}
|
||||||
|
return currCmd
|
||||||
|
}
|
@ -98,6 +98,9 @@ type ConfigFlags struct {
|
|||||||
Username *string
|
Username *string
|
||||||
Password *string
|
Password *string
|
||||||
Timeout *string
|
Timeout *string
|
||||||
|
// If non-nil, wrap config function can transform the Config
|
||||||
|
// before it is returned in ToRESTConfig function.
|
||||||
|
WrapConfigFn func(*rest.Config) *rest.Config
|
||||||
|
|
||||||
clientConfig clientcmd.ClientConfig
|
clientConfig clientcmd.ClientConfig
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
@ -113,9 +116,17 @@ type ConfigFlags struct {
|
|||||||
// ToRESTConfig implements RESTClientGetter.
|
// ToRESTConfig implements RESTClientGetter.
|
||||||
// Returns a REST client configuration based on a provided path
|
// Returns a REST client configuration based on a provided path
|
||||||
// to a .kubeconfig file, loading rules, and config flag overrides.
|
// to a .kubeconfig file, loading rules, and config flag overrides.
|
||||||
// Expects the AddFlags method to have been called.
|
// Expects the AddFlags method to have been called. If WrapConfigFn
|
||||||
|
// is non-nil this function can transform config before return.
|
||||||
func (f *ConfigFlags) ToRESTConfig() (*rest.Config, error) {
|
func (f *ConfigFlags) ToRESTConfig() (*rest.Config, error) {
|
||||||
return f.ToRawKubeConfigLoader().ClientConfig()
|
c, err := f.ToRawKubeConfigLoader().ClientConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if f.WrapConfigFn != nil {
|
||||||
|
return f.WrapConfigFn(c), nil
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToRawKubeConfigLoader binds config flag values to config overrides
|
// ToRawKubeConfigLoader binds config flag values to config overrides
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -322,6 +323,8 @@ __kubectl_custom_func() {
|
|||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const kubectlCmdHeaders = "KUBECTL_COMMAND_HEADERS"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
bashCompletionFlags = map[string]string{
|
bashCompletionFlags = map[string]string{
|
||||||
"namespace": "__kubectl_get_resource_namespace",
|
"namespace": "__kubectl_get_resource_namespace",
|
||||||
@ -469,7 +472,6 @@ func HandlePluginCommand(pluginHandler PluginHandler, cmdArgs []string) error {
|
|||||||
func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command {
|
func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command {
|
||||||
warningHandler := rest.NewWarningWriter(err, rest.WarningWriterOptions{Deduplicate: true, Color: term.AllowsColorOutput(err)})
|
warningHandler := rest.NewWarningWriter(err, rest.WarningWriterOptions{Deduplicate: true, Color: term.AllowsColorOutput(err)})
|
||||||
warningsAsErrors := false
|
warningsAsErrors := false
|
||||||
|
|
||||||
// Parent command to which all subcommands are added.
|
// Parent command to which all subcommands are added.
|
||||||
cmds := &cobra.Command{
|
cmds := &cobra.Command{
|
||||||
Use: "kubectl",
|
Use: "kubectl",
|
||||||
@ -521,6 +523,8 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command {
|
|||||||
kubeConfigFlags.AddFlags(flags)
|
kubeConfigFlags.AddFlags(flags)
|
||||||
matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(kubeConfigFlags)
|
matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(kubeConfigFlags)
|
||||||
matchVersionKubeConfigFlags.AddFlags(cmds.PersistentFlags())
|
matchVersionKubeConfigFlags.AddFlags(cmds.PersistentFlags())
|
||||||
|
// Updates hooks to add kubectl command headers: SIG CLI KEP 859.
|
||||||
|
addCmdHeaderHooks(cmds, kubeConfigFlags)
|
||||||
|
|
||||||
cmds.PersistentFlags().AddGoFlagSet(flag.CommandLine)
|
cmds.PersistentFlags().AddGoFlagSet(flag.CommandLine)
|
||||||
|
|
||||||
@ -538,6 +542,12 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command {
|
|||||||
|
|
||||||
ioStreams := genericclioptions.IOStreams{In: in, Out: out, ErrOut: err}
|
ioStreams := genericclioptions.IOStreams{In: in, Out: out, ErrOut: err}
|
||||||
|
|
||||||
|
// Proxy command is incompatible with CommandHeaderRoundTripper, so
|
||||||
|
// clear the WrapConfigFn before running proxy command.
|
||||||
|
proxyCmd := proxy.NewCmdProxy(f, ioStreams)
|
||||||
|
proxyCmd.PreRun = func(cmd *cobra.Command, args []string) {
|
||||||
|
kubeConfigFlags.WrapConfigFn = nil
|
||||||
|
}
|
||||||
groups := templates.CommandGroups{
|
groups := templates.CommandGroups{
|
||||||
{
|
{
|
||||||
Message: "Basic Commands (Beginner):",
|
Message: "Basic Commands (Beginner):",
|
||||||
@ -585,7 +595,7 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command {
|
|||||||
attach.NewCmdAttach(f, ioStreams),
|
attach.NewCmdAttach(f, ioStreams),
|
||||||
cmdexec.NewCmdExec(f, ioStreams),
|
cmdexec.NewCmdExec(f, ioStreams),
|
||||||
portforward.NewCmdPortForward(f, ioStreams),
|
portforward.NewCmdPortForward(f, ioStreams),
|
||||||
proxy.NewCmdProxy(f, ioStreams),
|
proxyCmd,
|
||||||
cp.NewCmdCp(f, ioStreams),
|
cp.NewCmdCp(f, ioStreams),
|
||||||
auth.NewCmdAuth(f, ioStreams),
|
auth.NewCmdAuth(f, ioStreams),
|
||||||
debug.NewCmdDebug(f, ioStreams),
|
debug.NewCmdDebug(f, ioStreams),
|
||||||
@ -646,6 +656,38 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command {
|
|||||||
return cmds
|
return cmds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addCmdHeaderHooks performs updates on two hooks:
|
||||||
|
// 1) Modifies the passed "cmds" persistent pre-run function to parse command headers.
|
||||||
|
// These headers will be subsequently added as X-headers to every
|
||||||
|
// REST call.
|
||||||
|
// 2) Adds CommandHeaderRoundTripper as a wrapper around the standard
|
||||||
|
// RoundTripper. CommandHeaderRoundTripper adds X-Headers then delegates
|
||||||
|
// to standard RoundTripper.
|
||||||
|
// For alpha, these hooks are only updated if the KUBECTL_COMMAND_HEADERS
|
||||||
|
// environment variable is set.
|
||||||
|
// See SIG CLI KEP 859 for more information:
|
||||||
|
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/859-kubectl-headers
|
||||||
|
func addCmdHeaderHooks(cmds *cobra.Command, kubeConfigFlags *genericclioptions.ConfigFlags) {
|
||||||
|
if _, exists := os.LookupEnv(kubectlCmdHeaders); !exists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
crt := &genericclioptions.CommandHeaderRoundTripper{}
|
||||||
|
existingPreRunE := cmds.PersistentPreRunE
|
||||||
|
// Add command parsing to the existing persistent pre-run function.
|
||||||
|
cmds.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||||
|
crt.ParseCommandHeaders(cmd, args)
|
||||||
|
return existingPreRunE(cmd, args)
|
||||||
|
}
|
||||||
|
// Wraps CommandHeaderRoundTripper around standard RoundTripper.
|
||||||
|
kubeConfigFlags.WrapConfigFn = func(c *rest.Config) *rest.Config {
|
||||||
|
c.Wrap(func(rt http.RoundTripper) http.RoundTripper {
|
||||||
|
crt.Delegate = rt
|
||||||
|
return crt
|
||||||
|
})
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func runHelp(cmd *cobra.Command, args []string) {
|
func runHelp(cmd *cobra.Command, args []string) {
|
||||||
cmd.Help()
|
cmd.Help()
|
||||||
}
|
}
|
||||||
|
1
staging/src/k8s.io/sample-cli-plugin/go.sum
generated
1
staging/src/k8s.io/sample-cli-plugin/go.sum
generated
@ -148,6 +148,7 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
|
|||||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
|
Loading…
Reference in New Issue
Block a user