mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-02 00:07:50 +00:00
Merge pull request #4129 from smarterclayton/add_user_agent
Add user agent string to all client requests
This commit is contained in:
commit
63c07ad58b
@ -19,6 +19,7 @@
|
|||||||
kube::etcd::start() {
|
kube::etcd::start() {
|
||||||
local host=${ETCD_HOST:-127.0.0.1}
|
local host=${ETCD_HOST:-127.0.0.1}
|
||||||
local port=${ETCD_PORT:-4001}
|
local port=${ETCD_PORT:-4001}
|
||||||
|
local testhost=${ETCD_PUBLIC_HOST:-localhost}
|
||||||
|
|
||||||
which etcd >/dev/null || {
|
which etcd >/dev/null || {
|
||||||
kube::log::usage "etcd must be in your PATH"
|
kube::log::usage "etcd must be in your PATH"
|
||||||
@ -38,18 +39,13 @@ kube::etcd::start() {
|
|||||||
|
|
||||||
# Start etcd
|
# Start etcd
|
||||||
ETCD_DIR=$(mktemp -d -t test-etcd.XXXXXX)
|
ETCD_DIR=$(mktemp -d -t test-etcd.XXXXXX)
|
||||||
kube::log::usage "etcd -data-dir ${ETCD_DIR} -addr ${host}:${port} >/dev/null 2>/dev/null"
|
kube::log::usage "etcd -data-dir ${ETCD_DIR} --bind-addr ${host}:${port} >/dev/null 2>/dev/null"
|
||||||
etcd -data-dir ${ETCD_DIR} -addr ${host}:${port} >/dev/null 2>/dev/null &
|
etcd -data-dir ${ETCD_DIR} -addr ${host}:${port} >/dev/null 2>/dev/null &
|
||||||
ETCD_PID=$!
|
ETCD_PID=$!
|
||||||
|
|
||||||
echo "Waiting for etcd to come up."
|
|
||||||
while true; do
|
|
||||||
if curl -L http://127.0.0.1:4001/v2/keys/test -XPUT -d value="test"; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
kube::util::wait_for_url "http://${host}:${port}/v2/keys/test" "etcd: "
|
echo "Waiting for etcd to come up."
|
||||||
|
kube::util::wait_for_url "http://${testhost}:${port}/v2/machines" "etcd: " 0.25 80
|
||||||
|
curl -X PUT "http://${testhost}:${port}/v2/keys/_test"
|
||||||
}
|
}
|
||||||
|
|
||||||
kube::etcd::cleanup() {
|
kube::etcd::cleanup() {
|
||||||
|
@ -93,7 +93,7 @@ sudo "${GO_OUT}/kube-apiserver" \
|
|||||||
--address="${API_HOST}" \
|
--address="${API_HOST}" \
|
||||||
--port="${API_PORT}" \
|
--port="${API_PORT}" \
|
||||||
--runtime_config=api/v1beta3 \
|
--runtime_config=api/v1beta3 \
|
||||||
--etcd_servers="http://127.0.0.1:4001" \
|
--etcd_servers="http://localhost:4001" \
|
||||||
--portal_net="10.0.0.0/24" \
|
--portal_net="10.0.0.0/24" \
|
||||||
--cors_allowed_origins="${API_CORS_ALLOWED_ORIGINS}" >"${APISERVER_LOG}" 2>&1 &
|
--cors_allowed_origins="${API_CORS_ALLOWED_ORIGINS}" >"${APISERVER_LOG}" 2>&1 &
|
||||||
APISERVER_PID=$!
|
APISERVER_PID=$!
|
||||||
|
@ -21,8 +21,10 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
gruntime "runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||||
@ -68,6 +70,9 @@ type Config struct {
|
|||||||
// certificate. For testing only.
|
// certificate. For testing only.
|
||||||
Insecure bool
|
Insecure bool
|
||||||
|
|
||||||
|
// UserAgent is an optional field that specifies the caller of this request.
|
||||||
|
UserAgent string
|
||||||
|
|
||||||
// Transport may be used for custom HTTP behavior. This attribute may not
|
// Transport may be used for custom HTTP behavior. This attribute may not
|
||||||
// be specified with the TLS client certificate options.
|
// be specified with the TLS client certificate options.
|
||||||
Transport http.RoundTripper
|
Transport http.RoundTripper
|
||||||
@ -151,6 +156,9 @@ func SetKubernetesDefaults(config *Config) error {
|
|||||||
if config.Prefix == "" {
|
if config.Prefix == "" {
|
||||||
config.Prefix = "/api"
|
config.Prefix = "/api"
|
||||||
}
|
}
|
||||||
|
if len(config.UserAgent) == 0 {
|
||||||
|
config.UserAgent = DefaultKubernetesUserAgent()
|
||||||
|
}
|
||||||
if len(config.Version) == 0 {
|
if len(config.Version) == 0 {
|
||||||
config.Version = defaultVersionFor(config)
|
config.Version = defaultVersionFor(config)
|
||||||
}
|
}
|
||||||
@ -252,6 +260,9 @@ func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTrip
|
|||||||
case hasBasicAuth:
|
case hasBasicAuth:
|
||||||
rt = NewBasicAuthRoundTripper(config.Username, config.Password, rt)
|
rt = NewBasicAuthRoundTripper(config.Username, config.Password, rt)
|
||||||
}
|
}
|
||||||
|
if len(config.UserAgent) > 0 {
|
||||||
|
rt = NewUserAgentRoundTripper(config.UserAgent, rt)
|
||||||
|
}
|
||||||
return rt, nil
|
return rt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,3 +364,18 @@ func defaultVersionFor(config *Config) string {
|
|||||||
}
|
}
|
||||||
return version
|
return version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultKubernetesUserAgent returns the default user agent that clients can use.
|
||||||
|
func DefaultKubernetesUserAgent() string {
|
||||||
|
commit := version.Get().GitCommit
|
||||||
|
if len(commit) > 7 {
|
||||||
|
commit = commit[:7]
|
||||||
|
}
|
||||||
|
if len(commit) == 0 {
|
||||||
|
commit = "unknown"
|
||||||
|
}
|
||||||
|
version := version.Get().GitVersion
|
||||||
|
seg := strings.SplitN(version, "-", 2)
|
||||||
|
version = seg[0]
|
||||||
|
return fmt.Sprintf("%s/%s (%s/%s) kubernetes/%s", path.Base(os.Args[0]), version, gruntime.GOOS, gruntime.GOARCH, commit)
|
||||||
|
}
|
||||||
|
@ -18,7 +18,11 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -255,3 +259,57 @@ func TestIsConfigTransportTLS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetKubernetesDefaults(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
Config Config
|
||||||
|
After Config
|
||||||
|
Err bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Config{},
|
||||||
|
Config{
|
||||||
|
Prefix: "/api",
|
||||||
|
Version: latest.Version,
|
||||||
|
Codec: latest.Codec,
|
||||||
|
LegacyBehavior: (latest.Version == "v1beta1" || latest.Version == "v1beta2"),
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config{
|
||||||
|
Version: "not_an_api",
|
||||||
|
},
|
||||||
|
Config{},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
val := &testCase.Config
|
||||||
|
err := SetKubernetesDefaults(val)
|
||||||
|
val.UserAgent = ""
|
||||||
|
switch {
|
||||||
|
case err == nil && testCase.Err:
|
||||||
|
t.Errorf("expected error but was nil")
|
||||||
|
continue
|
||||||
|
case err != nil && !testCase.Err:
|
||||||
|
t.Errorf("unexpected error %v", err)
|
||||||
|
continue
|
||||||
|
case err != nil:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(*val, testCase.After) {
|
||||||
|
t.Errorf("unexpected result object: %#v", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetKubernetesDefaultsUserAgent(t *testing.T) {
|
||||||
|
config := &Config{}
|
||||||
|
if err := SetKubernetesDefaults(config); err != nil {
|
||||||
|
t.Errorf("unexpected error: %v")
|
||||||
|
}
|
||||||
|
if !strings.Contains(config.UserAgent, "kubernetes/") {
|
||||||
|
t.Errorf("no user agent set: %#v", config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -65,49 +65,6 @@ func TestSetsCodec(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetDefaults(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
Config Config
|
|
||||||
After Config
|
|
||||||
Err bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Config{},
|
|
||||||
Config{
|
|
||||||
Prefix: "/api",
|
|
||||||
Version: latest.Version,
|
|
||||||
Codec: latest.Codec,
|
|
||||||
LegacyBehavior: (latest.Version == "v1beta1" || latest.Version == "v1beta2"),
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Config{
|
|
||||||
Version: "not_an_api",
|
|
||||||
},
|
|
||||||
Config{},
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
val := &testCase.Config
|
|
||||||
err := SetKubernetesDefaults(val)
|
|
||||||
switch {
|
|
||||||
case err == nil && testCase.Err:
|
|
||||||
t.Errorf("expected error but was nil")
|
|
||||||
continue
|
|
||||||
case err != nil && !testCase.Err:
|
|
||||||
t.Errorf("unexpected error %v", err)
|
|
||||||
continue
|
|
||||||
case err != nil:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(*val, testCase.After) {
|
|
||||||
t.Errorf("unexpected result object: %#v", val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRESTClientRequires(t *testing.T) {
|
func TestRESTClientRequires(t *testing.T) {
|
||||||
if _, err := RESTClientFor(&Config{Host: "127.0.0.1", Version: "", Codec: testapi.Codec()}); err == nil {
|
if _, err := RESTClientFor(&Config{Host: "127.0.0.1", Version: "", Codec: testapi.Codec()}); err == nil {
|
||||||
t.Errorf("unexpected non-error")
|
t.Errorf("unexpected non-error")
|
||||||
|
@ -23,6 +23,24 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type userAgentRoundTripper struct {
|
||||||
|
agent string
|
||||||
|
rt http.RoundTripper
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserAgentRoundTripper(agent string, rt http.RoundTripper) http.RoundTripper {
|
||||||
|
return &userAgentRoundTripper{agent, rt}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *userAgentRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
if len(req.Header.Get("User-Agent")) != 0 {
|
||||||
|
return rt.rt.RoundTrip(req)
|
||||||
|
}
|
||||||
|
req = cloneRequest(req)
|
||||||
|
req.Header.Set("User-Agent", rt.agent)
|
||||||
|
return rt.rt.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
type basicAuthRoundTripper struct {
|
type basicAuthRoundTripper struct {
|
||||||
username string
|
username string
|
||||||
password string
|
password string
|
||||||
|
@ -69,3 +69,33 @@ func TestBasicAuthRoundTripper(t *testing.T) {
|
|||||||
t.Errorf("unexpected authorization header: %#v", rt.Request)
|
t.Errorf("unexpected authorization header: %#v", rt.Request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUserAgentRoundTripper(t *testing.T) {
|
||||||
|
rt := &testRoundTripper{}
|
||||||
|
req := &http.Request{
|
||||||
|
Header: make(http.Header),
|
||||||
|
}
|
||||||
|
req.Header.Set("User-Agent", "other")
|
||||||
|
NewUserAgentRoundTripper("test", rt).RoundTrip(req)
|
||||||
|
if rt.Request == nil {
|
||||||
|
t.Fatalf("unexpected nil request: %v", rt)
|
||||||
|
}
|
||||||
|
if rt.Request != req {
|
||||||
|
t.Fatalf("round tripper should not have copied request object: %#v", rt.Request)
|
||||||
|
}
|
||||||
|
if rt.Request.Header.Get("User-Agent") != "other" {
|
||||||
|
t.Errorf("unexpected user agent header: %#v", rt.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
req = &http.Request{}
|
||||||
|
NewUserAgentRoundTripper("test", rt).RoundTrip(req)
|
||||||
|
if rt.Request == nil {
|
||||||
|
t.Fatalf("unexpected nil request: %v", rt)
|
||||||
|
}
|
||||||
|
if rt.Request == req {
|
||||||
|
t.Fatalf("round tripper should have copied request object: %#v", rt.Request)
|
||||||
|
}
|
||||||
|
if rt.Request.Header.Get("User-Agent") != "test" {
|
||||||
|
t.Errorf("unexpected user agent header: %#v", rt.Request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -18,79 +18,21 @@ limitations under the License.
|
|||||||
package kubectl
|
package kubectl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var apiVersionToUse = "v1beta1"
|
var apiVersionToUse = "v1beta1"
|
||||||
|
|
||||||
const kubectlAnnotationPrefix = "kubectl.kubernetes.io/"
|
const kubectlAnnotationPrefix = "kubectl.kubernetes.io/"
|
||||||
|
|
||||||
func GetKubeClient(config *client.Config, matchVersion bool) (*client.Client, error) {
|
|
||||||
// TODO: get the namespace context when kubectl ns is completed
|
|
||||||
c, err := client.New(config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if matchVersion {
|
|
||||||
clientVersion := version.Get()
|
|
||||||
serverVersion, err := c.ServerVersion()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't read version from server: %v\n", err)
|
|
||||||
}
|
|
||||||
if s := *serverVersion; !reflect.DeepEqual(clientVersion, s) {
|
|
||||||
return nil, fmt.Errorf("server version (%#v) differs from client version (%#v)!\n", s, clientVersion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type NamespaceInfo struct {
|
type NamespaceInfo struct {
|
||||||
Namespace string
|
Namespace string
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadNamespaceInfo parses a NamespaceInfo object from a file path. It creates a file at the specified path if it doesn't exist with the default namespace.
|
|
||||||
func LoadNamespaceInfo(path string) (*NamespaceInfo, error) {
|
|
||||||
var ns NamespaceInfo
|
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
||||||
ns.Namespace = api.NamespaceDefault
|
|
||||||
err = SaveNamespaceInfo(path, &ns)
|
|
||||||
return &ns, err
|
|
||||||
}
|
|
||||||
data, err := ioutil.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(data, &ns)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &ns, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveNamespaceInfo saves a NamespaceInfo object at the specified file path.
|
|
||||||
func SaveNamespaceInfo(path string, ns *NamespaceInfo) error {
|
|
||||||
if !util.IsDNSLabel(ns.Namespace) {
|
|
||||||
return fmt.Errorf("namespace %s is not a valid DNS Label", ns.Namespace)
|
|
||||||
}
|
|
||||||
data, err := json.Marshal(ns)
|
|
||||||
err = ioutil.WriteFile(path, data, 0600)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Move to labels package.
|
// TODO Move to labels package.
|
||||||
func formatLabels(labelMap map[string]string) string {
|
func formatLabels(labelMap map[string]string) string {
|
||||||
l := labels.Set(labelMap).String()
|
l := labels.Set(labelMap).String()
|
||||||
|
@ -1,84 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2014 Google Inc. All rights reserved.
|
|
||||||
|
|
||||||
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 kubectl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
func validateAction(expectedAction, actualAction client.FakeAction, t *testing.T) {
|
|
||||||
if !reflect.DeepEqual(expectedAction, actualAction) {
|
|
||||||
t.Errorf("Unexpected Action: %#v, expected: %#v", actualAction, expectedAction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadNamespaceInfo(t *testing.T) {
|
|
||||||
loadNamespaceInfoTests := []struct {
|
|
||||||
nsData string
|
|
||||||
nsInfo *NamespaceInfo
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
`{"Namespace":"test"}`,
|
|
||||||
&NamespaceInfo{Namespace: "test"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"", nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"missing",
|
|
||||||
&NamespaceInfo{Namespace: "default"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, loadNamespaceInfoTest := range loadNamespaceInfoTests {
|
|
||||||
tt := loadNamespaceInfoTest
|
|
||||||
nsfile, err := ioutil.TempFile("", "testNamespaceInfo")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if tt.nsData != "missing" {
|
|
||||||
defer os.Remove(nsfile.Name())
|
|
||||||
defer nsfile.Close()
|
|
||||||
_, err := nsfile.WriteString(tt.nsData)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
nsfile.Close()
|
|
||||||
os.Remove(nsfile.Name())
|
|
||||||
}
|
|
||||||
nsInfo, err := LoadNamespaceInfo(nsfile.Name())
|
|
||||||
if len(tt.nsData) == 0 && tt.nsData != "missing" {
|
|
||||||
if err == nil {
|
|
||||||
t.Error("LoadNamespaceInfo didn't fail on an empty file")
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if tt.nsData != "missing" {
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unexpected error: %v, %v", tt.nsData, err)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(nsInfo, tt.nsInfo) {
|
|
||||||
t.Errorf("Expected %v, got %v", tt.nsInfo, nsInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user