Commit 0307d7ba authored by Clayton Coleman's avatar Clayton Coleman

Set a default user agent on all client.Client calls

Form: kube-controller-manager/v0.10.0 (linux/amd64) kubernetes/550b98eb <basename(os.Argv)>/<gitVersion> (<GOOS>/<GOARCH>) kubernetes/<shortGitCommit> Can be set by other clients
parent 550b98eb
...@@ -21,8 +21,10 @@ import ( ...@@ -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 { ...@@ -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 { ...@@ -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 ...@@ -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 { ...@@ -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 ...@@ -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) { ...@@ -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) { ...@@ -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 ( ...@@ -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) { ...@@ -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. ...@@ -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()
......
/*
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)
}
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment