Merge pull request #4291 from jlowdermilk/delete-kubecfg

Remove kubecfg, cleanup a few stray references.
pull/6/head
Brian Grant 2015-02-11 11:18:59 -08:00
commit 20f7cbb87b
17 changed files with 4 additions and 2814 deletions

View File

@ -478,7 +478,6 @@ function kube::release::package_tarballs() {
# Clean out any old releases
rm -rf "${RELEASE_DIR}"
mkdir -p "${RELEASE_DIR}"
kube::release::package_client_tarballs
kube::release::package_server_tarballs
kube::release::package_salt_tarball
@ -489,7 +488,7 @@ function kube::release::package_tarballs() {
# Package up all of the cross compiled clients. Over time this should grow into
# a full SDK
function kube::release::package_client_tarballs() {
# Find all of the built kubecfg binaries
# Find all of the built client binaries
local platform platforms
platforms=($(cd "${LOCAL_OUTPUT_BINPATH}" ; echo */*))
for platform in "${platforms[@]}" ; do

View File

@ -1,109 +0,0 @@
#!/bin/bash
# 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.
set -o errexit
set -o nounset
set -o pipefail
KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
source "${KUBE_ROOT}/cluster/kube-env.sh"
source "${KUBE_ROOT}/cluster/${KUBERNETES_PROVIDER}/util.sh"
# Detect the OS name/arch so that we can find our binary
case "$(uname -s)" in
Darwin)
host_os=darwin
;;
Linux)
host_os=linux
;;
*)
echo "Unsupported host OS. Must be Linux or Mac OS X." >&2
exit 1
;;
esac
case "$(uname -m)" in
x86_64*)
host_arch=amd64
;;
i?86_64*)
host_arch=amd64
;;
amd64*)
host_arch=amd64
;;
arm*)
host_arch=arm
;;
i?86*)
host_arch=x86
;;
*)
echo "Unsupported host arch. Must be x86_64, 386 or arm." >&2
exit 1
;;
esac
# Gather up the list of likely places and use ls to find the latest one.
locations=(
"${KUBE_ROOT}/_output/dockerized/bin/${host_os}/${host_arch}/kubecfg"
"${KUBE_ROOT}/_output/local/bin/${host_os}/${host_arch}/kubecfg"
"${KUBE_ROOT}/platforms/${host_os}/${host_arch}/kubecfg"
)
kubecfg=$( (ls -t "${locations[@]}" 2>/dev/null || true) | head -1 )
if [[ ! -x "$kubecfg" ]]; then
{
echo "It looks as if you don't have a compiled kubecfg binary."
echo
echo "If you are running from a clone of the git repo, please run"
echo "'./build/run.sh hack/build-cross.sh'. Note that this requires having"
echo "Docker installed."
echo
echo "If you are running from a binary release tarball, something is wrong. "
echo "Look at http://kubernetes.io/ for information on how to contact the "
echo "development team for help."
} >&2
exit 1
fi
# When we are using vagrant it has hard coded auth. We repeat that here so that
# we don't clobber auth that might be used for a publicly facing cluster.
if [[ "$KUBERNETES_PROVIDER" == "vagrant" ]]; then
auth_config=(
"-auth" "$HOME/.kubernetes_vagrant_auth"
)
elif [[ "${KUBERNETES_PROVIDER}" == "gke" ]]; then
# While KUBECFG calls remain, we manually pass the auth and cert info.
detect-project &> /dev/null
cluster_config_dir="${GCLOUD_CONFIG_DIR}/${PROJECT}.${ZONE}.${CLUSTER_NAME}"
auth_config=(
"-certificate_authority=${cluster_config_dir}/ca.crt"
"-client_certificate=${cluster_config_dir}/kubecfg.crt"
"-client_key=${cluster_config_dir}/kubecfg.key"
"-auth=${cluster_config_dir}/kubernetes_auth"
)
else
auth_config=()
fi
detect-master > /dev/null
if [[ -n "${KUBE_MASTER_IP-}" && -z "${KUBERNETES_MASTER-}" ]]; then
export KUBERNETES_MASTER=https://${KUBE_MASTER_IP}
fi
"${kubecfg}" "${auth_config[@]:+${auth_config[@]}}" "${@}"

View File

@ -1,529 +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 main
import (
"flag"
"fmt"
"io/ioutil"
"net/http"
"os"
"reflect"
"sort"
"strconv"
"strings"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubecfg"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag"
"github.com/golang/glog"
"github.com/skratchdot/open-golang/open"
)
var (
serverVersion = verflag.Version("server_version", verflag.VersionFalse, "Print the server's version information and quit")
preventSkew = flag.Bool("expect_version_match", false, "Fail if server's version doesn't match own version.")
config = flag.String("c", "", "Path or URL to the config file, or '-' to read from STDIN")
selector = flag.String("l", "", "Selector (label query) to use for listing")
fieldSelector = flag.String("fields", "", "Selector (field query) to use for listing")
updatePeriod = flag.Duration("u", 60*time.Second, "Update interval period")
portSpec = flag.String("p", "", "The port spec, comma-separated list of <external>:<internal>,...")
servicePort = flag.Int("s", -1, "If positive, create and run a corresponding service on this port, only used with 'run'")
authConfig = flag.String("auth", os.Getenv("HOME")+"/.kubernetes_auth", "Path to the auth info file. If missing, prompt the user. Only used if doing https.")
json = flag.Bool("json", false, "If true, print raw JSON for responses")
yaml = flag.Bool("yaml", false, "If true, print raw YAML for responses")
verbose = flag.Bool("verbose", false, "If true, print extra information")
validate = flag.Bool("validate", false, "If true, try to validate the passed in object using a swagger schema on the api server")
proxy = flag.Bool("proxy", false, "If true, run a proxy to the api server")
www = flag.String("www", "", "If -proxy is true, use this directory to serve static files")
templateFile = flag.String("template_file", "", "If present, load this file as a golang template and use it for output printing")
templateStr = flag.String("template", "", "If present, parse this string as a golang template and use it for output printing")
imageName = flag.String("image", "", "Image used when updating a replicationController. Will apply to the first container in the pod template.")
clientConfig = &client.Config{}
openBrowser = flag.Bool("open_browser", true, "If true, and -proxy is specified, open a browser pointed at the Kubernetes UX. Default true.")
ns = flag.String("ns", "", "If present, the namespace scope for this request.")
nsFile = flag.String("ns_file", os.Getenv("HOME")+"/.kubernetes_ns", "Path to the namespace file")
)
func init() {
flag.StringVar(&clientConfig.Host, "h", "", "The host to connect to.")
flag.StringVar(&clientConfig.Version, "api_version", latest.Version, "The version of the API to use against this server.")
flag.StringVar(&clientConfig.CAFile, "certificate_authority", "", "Path to a cert. file for the certificate authority")
flag.StringVar(&clientConfig.CertFile, "client_certificate", "", "Path to a client certificate for TLS.")
flag.StringVar(&clientConfig.KeyFile, "client_key", "", "Path to a client key file for TLS.")
flag.BoolVar(&clientConfig.Insecure, "insecure_skip_tls_verify", clientConfig.Insecure, "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure.")
}
var parser = kubecfg.NewParser(map[string]runtime.Object{
"pods": &api.Pod{},
"services": &api.Service{},
"replicationControllers": &api.ReplicationController{},
"minions": &api.Node{},
"nodes": &api.Node{},
"events": &api.Event{},
})
func usage() {
fmt.Fprintf(os.Stderr, `Usage: kubecfg -h [-c config/file.json|url|-] <method>
Kubernetes REST API:
kubecfg [OPTIONS] get|list|create|delete|update <%s>[/<id>]
Manage replication controllers:
kubecfg [OPTIONS] stop|rm <controller>
kubecfg [OPTIONS] [-u <time>] [-image <image>] rollingupdate <controller>
kubecfg [OPTIONS] resize <controller> <replicas>
Launch a simple ReplicationController with a single container based
on the given image:
kubecfg [OPTIONS] [-p <port spec>] run <image> <replicas> <controller>
Manage namespace:
kubecfg [OPTIONS] ns [<namespace>]
Options:
`, prettyWireStorage())
flag.PrintDefaults()
}
func prettyWireStorage() string {
types := parser.SupportedWireStorage()
sort.Strings(types)
return strings.Join(types, "|")
}
// readConfigData reads the bytes from the specified filesytem or network location associated with the *config flag
func readConfigData() []byte {
// read from STDIN
if *config == "-" {
data, err := ioutil.ReadAll(os.Stdin)
if err != nil {
glog.Fatalf("Unable to read from STDIN: %v\n", err)
}
return data
}
// we look for http:// or https:// to determine if valid URL, otherwise do normal file IO
if strings.Index(*config, "http://") == 0 || strings.Index(*config, "https://") == 0 {
resp, err := http.Get(*config)
if err != nil {
glog.Fatalf("Unable to access URL %v: %v\n", *config, err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
glog.Fatalf("Unable to read URL, server reported %d %s", resp.StatusCode, resp.Status)
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
glog.Fatalf("Unable to read URL %v: %v\n", *config, err)
}
return data
}
data, err := ioutil.ReadFile(*config)
if err != nil {
glog.Fatalf("Unable to read %v: %v\n", *config, err)
}
return data
}
// readConfig reads and parses pod, replicationController, and service
// configuration files. If any errors log and exit non-zero.
func readConfig(storage string, c *client.Client) []byte {
serverCodec := c.RESTClient.Codec
if len(*config) == 0 {
glog.Fatal("Need config file (-c)")
}
dataInput := readConfigData()
if *validate {
err := kubecfg.ValidateObject(dataInput, c)
if err != nil {
glog.Fatalf("Error validating %v as an object for %v: %v\n", *config, storage, err)
}
}
data, err := parser.ToWireFormat(dataInput, storage, latest.Codec, serverCodec)
if err != nil {
glog.Fatalf("Error parsing %v as an object for %v: %v\n", *config, storage, err)
}
if *verbose {
glog.Infof("Parsed config file successfully; sending:\n%v\n", string(data))
}
return data
}
func main() {
flag.Usage = func() {
usage()
}
flag.Parse()
util.InitLogs()
defer util.FlushLogs()
verflag.PrintAndExitIfRequested()
// Initialize the client
if clientConfig.Host == "" {
clientConfig.Host = os.Getenv("KUBERNETES_MASTER")
}
// Load namespace information for requests
// Check if the namespace was overriden by the -ns argument
ctx := api.NewDefaultContext()
if len(*ns) > 0 {
ctx = api.WithNamespace(ctx, *ns)
} else {
nsInfo, err := kubecfg.LoadNamespaceInfo(*nsFile)
if err != nil {
glog.Fatalf("Error loading current namespace: %v", err)
}
ctx = api.WithNamespace(ctx, nsInfo.Namespace)
}
if clientConfig.Host == "" {
// TODO: eventually apiserver should start on 443 and be secure by default
// TODO: don't specify http or https in Host, and infer that from auth options.
clientConfig.Host = "http://localhost:8080"
}
if client.IsConfigTransportTLS(*clientConfig) {
auth, err := kubecfg.LoadClientAuthInfoOrPrompt(*authConfig, os.Stdin)
if err != nil {
glog.Fatalf("Error loading auth: %v", err)
}
clientConfig.Username = auth.User
clientConfig.Password = auth.Password
if auth.CAFile != "" {
clientConfig.CAFile = auth.CAFile
}
if auth.CertFile != "" {
clientConfig.CertFile = auth.CertFile
}
if auth.KeyFile != "" {
clientConfig.KeyFile = auth.KeyFile
}
if auth.BearerToken != "" {
clientConfig.BearerToken = auth.BearerToken
}
if auth.Insecure != nil {
clientConfig.Insecure = *auth.Insecure
}
}
kubeClient, err := client.New(clientConfig)
if err != nil {
glog.Fatalf("Can't configure client: %v", err)
}
if *serverVersion != verflag.VersionFalse {
got, err := kubeClient.ServerVersion()
if err != nil {
fmt.Printf("Couldn't read version from server: %v\n", err)
os.Exit(1)
}
if *serverVersion == verflag.VersionRaw {
fmt.Printf("%#v\n", *got)
os.Exit(0)
} else {
fmt.Printf("Server: Kubernetes %s\n", got)
os.Exit(0)
}
}
if *preventSkew {
got, err := kubeClient.ServerVersion()
if err != nil {
fmt.Printf("Couldn't read version from server: %v\n", err)
os.Exit(1)
}
if c, s := version.Get(), *got; !reflect.DeepEqual(c, s) {
fmt.Printf("Server version (%#v) differs from client version (%#v)!\n", s, c)
os.Exit(1)
}
}
if *proxy {
glog.Info("Starting to serve on localhost:8001")
if *openBrowser {
go func() {
time.Sleep(2 * time.Second)
open.Start("http://localhost:8001/static/")
}()
}
server, err := kubecfg.NewProxyServer(*www, clientConfig)
if err != nil {
glog.Fatalf("Error creating proxy server: %v", err)
}
glog.Fatal(server.Serve())
}
if len(flag.Args()) < 1 {
usage()
os.Exit(1)
}
method := flag.Arg(0)
matchFound := executeAPIRequest(ctx, method, kubeClient) || executeControllerRequest(ctx, method, kubeClient) || executeNamespaceRequest(method, kubeClient)
if matchFound == false {
glog.Fatalf("Unknown command %s", method)
}
}
// storagePathFromArg normalizes a path and breaks out the first segment if available
func storagePathFromArg(arg string) (storage, path string, hasSuffix bool) {
path = strings.Trim(arg, "/")
segments := strings.SplitN(path, "/", 2)
storage = segments[0]
if len(segments) > 1 && segments[1] != "" {
hasSuffix = true
}
return storage, path, hasSuffix
}
//checkStorage returns true if the provided storage is valid
func checkStorage(storage string) bool {
for _, allowed := range parser.SupportedWireStorage() {
if allowed == storage {
return true
}
}
return false
}
func getPrinter() kubecfg.ResourcePrinter {
var printer kubecfg.ResourcePrinter
switch {
case *json:
printer = &kubecfg.IdentityPrinter{}
case *yaml:
printer = &kubecfg.YAMLPrinter{}
case len(*templateFile) > 0 || len(*templateStr) > 0:
var data []byte
if len(*templateFile) > 0 {
var err error
data, err = ioutil.ReadFile(*templateFile)
if err != nil {
glog.Fatalf("Error reading template %s, %v\n", *templateFile, err)
return nil
}
} else {
data = []byte(*templateStr)
}
var err error
printer, err = kubecfg.NewTemplatePrinter(data)
if err != nil {
glog.Fatalf("Error '%v' parsing template:\n'%s'", err, string(data))
return nil
}
default:
printer = humanReadablePrinter()
}
return printer
}
func executeAPIRequest(ctx api.Context, method string, c *client.Client) bool {
storage, path, hasSuffix := storagePathFromArg(flag.Arg(1))
validStorage := checkStorage(storage)
verb := ""
setBody := false
var version string
printer := getPrinter()
switch method {
case "get":
verb = "GET"
if !validStorage || !hasSuffix {
glog.Fatalf("usage: kubecfg [OPTIONS] %s <%s>[/<id>]", method, prettyWireStorage())
}
case "list":
verb = "GET"
if !validStorage || hasSuffix {
glog.Fatalf("usage: kubecfg [OPTIONS] %s <%s>", method, prettyWireStorage())
}
case "delete":
verb = "DELETE"
if !validStorage || !hasSuffix {
glog.Fatalf("usage: kubecfg [OPTIONS] %s <%s>/<id>", method, prettyWireStorage())
}
case "create":
verb = "POST"
setBody = true
if !validStorage || hasSuffix {
glog.Fatalf("usage: kubecfg [OPTIONS] %s <%s>", method, prettyWireStorage())
}
case "update":
obj, err := c.Verb("GET").Namespace(api.NamespaceValue(ctx)).Suffix(path).Do().Get()
if err != nil {
glog.Fatalf("error obtaining resource version for update: %v", err)
}
meta, err := meta.Accessor(obj)
if err != nil {
glog.Fatalf("error finding json base for update: %v", err)
}
version = meta.ResourceVersion()
verb = "PUT"
setBody = true
if !validStorage || !hasSuffix {
glog.Fatalf("usage: kubecfg [OPTIONS] %s <%s>/<id>", method, prettyWireStorage())
}
case "print":
data := readConfig(storage, c)
obj, err := latest.Codec.Decode(data)
if err != nil {
glog.Fatalf("error setting resource version: %v", err)
}
printer.PrintObj(obj, os.Stdout)
return true
default:
return false
}
r := c.Verb(verb).Namespace(api.NamespaceValue(ctx)).Suffix(path)
if len(*selector) > 0 {
r.ParseSelectorParam("labels", *selector)
}
if len(*fieldSelector) > 0 {
r.ParseSelectorParam("fields", *fieldSelector)
}
if setBody {
if len(version) > 0 {
data := readConfig(storage, c)
obj, err := latest.Codec.Decode(data)
if err != nil {
glog.Fatalf("error setting resource version: %v", err)
}
jsonBase, err := meta.Accessor(obj)
if err != nil {
glog.Fatalf("error setting resource version: %v", err)
}
jsonBase.SetResourceVersion(version)
data, err = c.RESTClient.Codec.Encode(obj)
if err != nil {
glog.Fatalf("error setting resource version: %v", err)
}
r.Body(data)
} else {
r.Body(readConfig(storage, c))
}
}
result := r.Do()
obj, err := result.Get()
if err != nil {
glog.Fatalf("Got request error: %v\n", err)
return false
}
if err = printer.PrintObj(obj, os.Stdout); err != nil {
body, _ := result.Raw()
glog.Fatalf("Failed to print: %v\nRaw received object:\n%#v\n\nBody received: %v", err, obj, string(body))
}
fmt.Print("\n")
return true
}
func executeControllerRequest(ctx api.Context, method string, c *client.Client) bool {
parseController := func() string {
if len(flag.Args()) != 2 {
glog.Fatal("usage: kubecfg [OPTIONS] stop|rm|rollingupdate <controller>")
}
return flag.Arg(1)
}
var err error
switch method {
case "stop":
err = kubecfg.StopController(ctx, parseController(), c)
case "rm":
err = kubecfg.DeleteController(ctx, parseController(), c)
case "rollingupdate":
err = kubecfg.Update(ctx, parseController(), c, *updatePeriod, *imageName)
case "run":
if len(flag.Args()) != 4 {
glog.Fatal("usage: kubecfg [OPTIONS] run <image> <replicas> <controller>")
}
image := flag.Arg(1)
replicas, err2 := strconv.Atoi(flag.Arg(2))
if err2 != nil {
glog.Fatalf("Error parsing replicas: %v", err2)
}
name := flag.Arg(3)
err = kubecfg.RunController(ctx, image, name, replicas, c, *portSpec, *servicePort)
case "resize":
args := flag.Args()
if len(args) < 3 {
glog.Fatal("usage: kubecfg resize <controller> <replicas>")
}
name := args[1]
replicas, err2 := strconv.Atoi(args[2])
if err2 != nil {
glog.Fatalf("Error parsing replicas: %v", err2)
}
err = kubecfg.ResizeController(ctx, name, replicas, c)
default:
return false
}
if err != nil {
glog.Fatalf("Error: %v", err)
}
return true
}
// executeNamespaceRequest handles client operations for namespaces
func executeNamespaceRequest(method string, c *client.Client) bool {
var err error
var ns *kubecfg.NamespaceInfo
switch method {
case "ns":
args := flag.Args()
switch len(args) {
case 1:
ns, err = kubecfg.LoadNamespaceInfo(*nsFile)
case 2:
ns = &kubecfg.NamespaceInfo{Namespace: args[1]}
err = kubecfg.SaveNamespaceInfo(*nsFile, ns)
default:
glog.Fatalf("usage: kubecfg ns [<namespace>]")
}
default:
return false
}
if err != nil {
glog.Fatalf("Error: %v", err)
}
fmt.Printf("Using namespace %s\n", ns.Namespace)
return true
}
func humanReadablePrinter() *kubecfg.HumanReadablePrinter {
printer := kubecfg.NewHumanReadablePrinter()
// Add Handler calls here to support additional types
return printer
}

View File

@ -1,199 +0,0 @@
#!bash
#
# bash completion file for core kubecfg commands
#
# This script provides completion of non replication controller options
#
# To enable the completions either:
# - place this file in /etc/bash_completion.d
# or
# - copy this file and add the line below to your .bashrc after
# bash completion features are loaded
# . kubecfg
#
# Note:
# Currently, the completions will not work if the kube-apiserver daemon is not
# running on localhost on the standard port 8080
__contains_word () {
local w word=$1; shift
for w in "$@"; do
[[ $w = "$word" ]] && return
done
return 1
}
# This should be provided by the bash-completions, but give a really simple
# stoopid version just in case. It works most of the time.
if ! declare -F _get_comp_words_by_ref >/dev/null 2>&1; then
_get_comp_words_by_ref ()
{
while [ $# -gt 0 ]; do
case "$1" in
cur)
cur=${COMP_WORDS[COMP_CWORD]}
;;
prev)
prev=${COMP_WORDS[COMP_CWORD-1]}
;;
words)
words=("${COMP_WORDS[@]}")
;;
cword)
cword=$COMP_CWORD
;;
-n)
shift # we don't handle excludes
;;
esac
shift
done
}
fi
__has_service() {
local i
for ((i=0; i < cword; i++)); do
local word=${words[i]}
# strip everything after a / so things like pods/[id] match
word=${word%%/*}
if __contains_word "${word}" "${services[@]}" &&
! __contains_word "${words[i-1]}" "${opts[@]}"; then
return 0
fi
done
return 1
}
# call kubecfg list $1,
# exclude blank lines
# skip the header stuff kubecfg prints on the first 2 lines
# append $1/ to the first column and use that in compgen
__kubecfg_parse_list()
{
local kubecfg_output
if kubecfg_output=$(kubecfg list "$1" 2>/dev/null); then
out=($(echo "${kubecfg_output}" | awk -v prefix="$1" '/^$/ {next} NR > 2 {print prefix"/"$1}'))
COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) )
fi
}
_kubecfg_specific_service_match()
{
case "$cur" in
pods/*)
__kubecfg_parse_list pods
;;
minions/*)
__kubecfg_parse_list minions
;;
replicationControllers/*)
__kubecfg_parse_list replicationControllers
;;
services/*)
__kubecfg_parse_list services
;;
*)
if __has_service; then
return 0
fi
compopt -o nospace
COMPREPLY=( $( compgen -S / -W "${services[*]}" -- "$cur" ) )
;;
esac
}
_kubecfg_service_match()
{
if __has_service; then
return 0
fi
COMPREPLY=( $( compgen -W "${services[*]}" -- "$cur" ) )
}
_kubecfg()
{
local opts=(
-h
-c
)
local create_services=(pods replicationControllers services)
local update_services=(replicationControllers)
local all_services=(pods replicationControllers services minions)
local services=("${all_services[@]}")
local json_commands=(create update)
local all_commands=(create update get list delete stop rm rollingupdate resize)
local commands=("${all_commands[@]}")
COMPREPLY=()
local command
local cur prev words cword
_get_comp_words_by_ref -n : cur prev words cword
if __contains_word "$prev" "${opts[@]}"; then
case $prev in
-c)
_filedir '@(json|yml|yaml)'
return 0
;;
-h)
return 0
;;
esac
fi
if [[ "$cur" = -* ]]; then
COMPREPLY=( $(compgen -W "${opts[*]}" -- "$cur") )
return 0
fi
# if you passed -c, you are limited to create or update
if __contains_word "-c" "${words[@]}"; then
services=("${create_services[@]}" "${update_services[@]}")
commands=("${json_commands[@]}")
fi
# figure out which command they are running, remembering that arguments to
# options don't count as the command! So a hostname named 'create' won't
# trip things up
local i
for ((i=0; i < cword; i++)); do
if __contains_word "${words[i]}" "${commands[@]}" &&
! __contains_word "${words[i-1]}" "${opts[@]}"; then
command=${words[i]}
break
fi
done
# tell the list of possible commands
if [[ -z ${command} ]]; then
COMPREPLY=( $( compgen -W "${commands[*]}" -- "$cur" ) )
return 0
fi
# remove services which you can't update given your command
if [[ ${command} == "create" ]]; then
services=("${create_services[@]}")
elif [[ ${command} == "update" ]]; then
services=("${update_services[@]}")
fi
case $command in
create | list)
_kubecfg_service_match
;;
update | get | delete)
_kubecfg_specific_service_match
;;
*)
;;
esac
return 0
}
complete -F _kubecfg kubecfg
# ex: ts=4 sw=4 et filetype=sh

View File

@ -1,6 +1,6 @@
# Install and configure kubecfg
# Install and configure kubectl
## Download the kubecfg CLI tool
## Download the kubectl CLI tool
### Darwin

View File

@ -276,7 +276,7 @@ You probably have an incorrect ~/.kubernetes_vagrant_auth file for the cluster y
rm ~/.kubernetes_vagrant_auth
```
After using kubecfg.sh make sure that the correct credentials are set:
After using kubectl.sh make sure that the correct credentials are set:
```
cat ~/.kubernetes_vagrant_auth

View File

@ -35,7 +35,6 @@ readonly KUBE_SERVER_PLATFORMS=(
# The set of client targets that we are building for all platforms
readonly KUBE_CLIENT_TARGETS=(
cmd/kubecfg
cmd/kubectl
cmd/kubernetes
)

View File

@ -1,20 +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 kubecfg is a set of libraries that are used by the kubecfg command line tool.
// They are separated out into a library to support unit testing. Most functionality should
// be included in this package, and the main kubecfg should really just be an entry point.
package kubecfg

View File

@ -1,329 +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 kubecfg
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"strconv"
"strings"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/clientauth"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/wait"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
"github.com/ghodss/yaml"
"github.com/golang/glog"
)
func GetServerVersion(client *client.Client) (*version.Info, error) {
info, err := client.ServerVersion()
if err != nil {
return nil, err
}
return info, nil
}
func promptForString(field string, r io.Reader) string {
fmt.Printf("Please enter %s: ", field)
var result string
fmt.Fscan(r, &result)
return result
}
type NamespaceInfo struct {
Namespace string
}
// LoadClientAuthInfoOrPrompt parses a clientauth.Info object from a file path. It prompts user and creates file if it doesn't exist.
// Oddly, it returns a clientauth.Info even if there is an error.
func LoadClientAuthInfoOrPrompt(path string, r io.Reader) (*clientauth.Info, error) {
var auth clientauth.Info
// Prompt for user/pass and write a file if none exists.
if _, err := os.Stat(path); os.IsNotExist(err) {
auth.User = promptForString("Username", r)
auth.Password = promptForString("Password", r)
data, err := json.Marshal(auth)
if err != nil {
return &auth, err
}
err = ioutil.WriteFile(path, data, 0600)
return &auth, err
}
authPtr, err := clientauth.LoadFromFile(path)
if err != nil {
return nil, err
}
return authPtr, nil
}
// 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
}
// extracted for test speed
var (
updatePollInterval = 5 * time.Second
updatePollTimeout = 300 * time.Second
)
// Update performs a rolling update of a collection of pods.
// 'name' points to a replication controller.
// 'client' is used for updating pods.
// 'updatePeriod' is the time between pod updates.
// 'imageName' is the new image to update for the template. This will work
// with the first container in the pod. There is no support yet for
// updating more complex replication controllers. If this is blank then no
// update of the image is performed.
func Update(ctx api.Context, name string, client client.Interface, updatePeriod time.Duration, imageName string) error {
// TODO ctx is not needed as input to this function, should just be 'namespace'
controller, err := client.ReplicationControllers(api.NamespaceValue(ctx)).Get(name)
if err != nil {
return err
}
if len(imageName) != 0 {
controller.Spec.Template.Spec.Containers[0].Image = imageName
controller, err = client.ReplicationControllers(controller.Namespace).Update(controller)
if err != nil {
return err
}
}
s := labels.Set(controller.Spec.Selector).AsSelector()
podList, err := client.Pods(api.NamespaceValue(ctx)).List(s)
if err != nil {
return err
}
expected := len(podList.Items)
if expected == 0 {
return nil
}
for _, pod := range podList.Items {
// We delete the pod here, the controller will recreate it. This will result in pulling
// a new Docker image. This isn't a full "update" but it's what we support for now.
err = client.Pods(pod.Namespace).Delete(pod.Name)
if err != nil {
return err
}
time.Sleep(updatePeriod)
}
return wait.Poll(updatePollInterval, updatePollTimeout, func() (bool, error) {
podList, err := client.Pods(api.NamespaceValue(ctx)).List(s)
if err != nil {
return false, err
}
return len(podList.Items) == expected, nil
})
}
// StopController stops a controller named 'name' by setting replicas to zero.
func StopController(ctx api.Context, name string, client client.Interface) error {
return ResizeController(ctx, name, 0, client)
}
// ResizeController resizes a controller named 'name' by setting replicas to 'replicas'.
func ResizeController(ctx api.Context, name string, replicas int, client client.Interface) error {
// TODO ctx is not needed, and should just be a namespace
controller, err := client.ReplicationControllers(api.NamespaceValue(ctx)).Get(name)
if err != nil {
return err
}
controller.Spec.Replicas = replicas
controllerOut, err := client.ReplicationControllers(api.NamespaceValue(ctx)).Update(controller)
if err != nil {
return err
}
data, err := yaml.Marshal(controllerOut)
if err != nil {
return err
}
fmt.Print(string(data))
return nil
}
func portsFromString(spec string) ([]api.Port, error) {
if spec == "" {
return []api.Port{}, nil
}
parts := strings.Split(spec, ",")
var result []api.Port
for _, part := range parts {
pieces := strings.Split(part, ":")
if len(pieces) < 1 || len(pieces) > 2 {
glog.Infof("Bad port spec: %s", part)
return nil, fmt.Errorf("bad port spec: %s", part)
}
host := 0
container := 0
var err error
if len(pieces) == 1 {
container, err = strconv.Atoi(pieces[0])
if err != nil {
glog.Errorf("Container port is not integer: %s %v", pieces[0], err)
return nil, err
}
} else {
host, err = strconv.Atoi(pieces[0])
if err != nil {
glog.Errorf("Host port is not integer: %s %v", pieces[0], err)
return nil, err
}
container, err = strconv.Atoi(pieces[1])
if err != nil {
glog.Errorf("Container port is not integer: %s %v", pieces[1], err)
return nil, err
}
}
if container < 1 {
glog.Errorf("Container port is not valid: %d", container)
return nil, err
}
result = append(result, api.Port{ContainerPort: container, HostPort: host})
}
return result, nil
}
// RunController creates a new replication controller named 'name' which creates 'replicas' pods running 'image'.
func RunController(ctx api.Context, image, name string, replicas int, client client.Interface, portSpec string, servicePort int) error {
// TODO replace ctx with a namespace string
if servicePort > 0 && !util.IsDNSLabel(name) {
return fmt.Errorf("service creation requested, but an invalid name for a service was provided (%s). Service names must be valid DNS labels.", name)
}
ports, err := portsFromString(portSpec)
if err != nil {
return err
}
controller := &api.ReplicationController{
ObjectMeta: api.ObjectMeta{
Name: name,
},
Spec: api.ReplicationControllerSpec{
Replicas: replicas,
Selector: map[string]string{
"name": name,
},
Template: &api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{
"name": name,
},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: strings.ToLower(name),
Image: image,
Ports: ports,
},
},
},
},
},
}
controllerOut, err := client.ReplicationControllers(api.NamespaceValue(ctx)).Create(controller)
if err != nil {
return err
}
data, err := yaml.Marshal(controllerOut)
if err != nil {
return err
}
fmt.Print(string(data))
if servicePort > 0 {
svc, err := createService(ctx, name, servicePort, client)
if err != nil {
return err
}
data, err = yaml.Marshal(svc)
if err != nil {
return err
}
fmt.Printf(string(data))
}
return nil
}
func createService(ctx api.Context, name string, port int, client client.Interface) (*api.Service, error) {
// TODO remove context in favor of just namespace string
svc := &api.Service{
ObjectMeta: api.ObjectMeta{
Name: name,
Labels: map[string]string{
"name": name,
},
},
Spec: api.ServiceSpec{
Port: port,
Selector: map[string]string{
"name": name,
},
},
}
svc, err := client.Services(api.NamespaceValue(ctx)).Create(svc)
return svc, err
}
// DeleteController deletes a replication controller named 'name', requires that the controller
// already be stopped.
func DeleteController(ctx api.Context, name string, client client.Interface) error {
// TODO remove ctx in favor of just namespace string
controller, err := client.ReplicationControllers(api.NamespaceValue(ctx)).Get(name)
if err != nil {
return err
}
if controller.Spec.Replicas != 0 {
return fmt.Errorf("controller has non-zero replicas (%d), please stop it first", controller.Spec.Replicas)
}
return client.ReplicationControllers(api.NamespaceValue(ctx)).Delete(name)
}

View File

@ -1,393 +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 kubecfg
import (
"bytes"
"io"
"io/ioutil"
"os"
"reflect"
"testing"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/clientauth"
)
func validateAction(expectedAction, actualAction client.FakeAction, t *testing.T) {
if !reflect.DeepEqual(expectedAction, actualAction) {
t.Errorf("Unexpected Action: %#v, expected: %#v", actualAction, expectedAction)
}
}
func init() {
updatePollInterval = 1 * time.Millisecond
}
func TestUpdateWithPods(t *testing.T) {
fakeClient := client.Fake{
PodsList: api.PodList{
Items: []api.Pod{
{ObjectMeta: api.ObjectMeta{Name: "pod-1"}},
{ObjectMeta: api.ObjectMeta{Name: "pod-2"}},
},
},
}
Update(api.NewDefaultContext(), "foo", &fakeClient, 0, "")
if len(fakeClient.Actions) != 5 {
t.Fatalf("Unexpected action list %#v", fakeClient.Actions)
}
validateAction(client.FakeAction{Action: "get-controller", Value: "foo"}, fakeClient.Actions[0], t)
validateAction(client.FakeAction{Action: "list-pods"}, fakeClient.Actions[1], t)
// Update deletes the pods, it relies on the replication controller to replace them.
validateAction(client.FakeAction{Action: "delete-pod", Value: "pod-1"}, fakeClient.Actions[2], t)
validateAction(client.FakeAction{Action: "delete-pod", Value: "pod-2"}, fakeClient.Actions[3], t)
validateAction(client.FakeAction{Action: "list-pods"}, fakeClient.Actions[4], t)
}
func TestUpdateNoPods(t *testing.T) {
fakeClient := client.Fake{}
Update(api.NewDefaultContext(), "foo", &fakeClient, 0, "")
if len(fakeClient.Actions) != 2 {
t.Errorf("Unexpected action list %#v", fakeClient.Actions)
}
validateAction(client.FakeAction{Action: "get-controller", Value: "foo"}, fakeClient.Actions[0], t)
validateAction(client.FakeAction{Action: "list-pods"}, fakeClient.Actions[1], t)
}
func TestUpdateWithNewImage(t *testing.T) {
fakeClient := client.Fake{
PodsList: api.PodList{
Items: []api.Pod{
{ObjectMeta: api.ObjectMeta{Name: "pod-1"}},
{ObjectMeta: api.ObjectMeta{Name: "pod-2"}},
},
},
Ctrl: api.ReplicationController{
Spec: api.ReplicationControllerSpec{
Template: &api.PodTemplateSpec{
Spec: api.PodSpec{
Containers: []api.Container{
{Image: "fooImage:1"},
},
},
},
},
},
}
Update(api.NewDefaultContext(), "foo", &fakeClient, 0, "fooImage:2")
if len(fakeClient.Actions) != 6 {
t.Errorf("Unexpected action list %#v", fakeClient.Actions)
}
validateAction(client.FakeAction{Action: "get-controller", Value: "foo"}, fakeClient.Actions[0], t)
newCtrl := api.Scheme.CopyOrDie(&fakeClient.Ctrl).(*api.ReplicationController)
newCtrl.Spec.Template.Spec.Containers[0].Image = "fooImage:2"
validateAction(client.FakeAction{Action: "update-controller", Value: newCtrl}, fakeClient.Actions[1], t)
validateAction(client.FakeAction{Action: "list-pods"}, fakeClient.Actions[2], t)
// Update deletes the pods, it relies on the replication controller to replace them.
validateAction(client.FakeAction{Action: "delete-pod", Value: "pod-1"}, fakeClient.Actions[3], t)
validateAction(client.FakeAction{Action: "delete-pod", Value: "pod-2"}, fakeClient.Actions[4], t)
validateAction(client.FakeAction{Action: "list-pods"}, fakeClient.Actions[5], t)
}
func TestRunController(t *testing.T) {
fakeClient := client.Fake{}
name := "name"
image := "foo/bar"
replicas := 3
RunController(api.NewDefaultContext(), image, name, replicas, &fakeClient, "8080:80", -1)
if len(fakeClient.Actions) != 1 || fakeClient.Actions[0].Action != "create-controller" {
t.Errorf("Unexpected actions: %#v", fakeClient.Actions)
}
controller := fakeClient.Actions[0].Value.(*api.ReplicationController)
if controller.Name != name ||
controller.Spec.Replicas != replicas ||
controller.Spec.Template.Spec.Containers[0].Image != image {
t.Errorf("Unexpected controller: %#v", controller)
}
}
func TestRunControllerWithWrongArgs(t *testing.T) {
fakeClient := client.Fake{}
name := "name"
image := "foo/bar"
replicas := 3
err := RunController(api.NewDefaultContext(), image, name, replicas, &fakeClient, "8080:", -1)
if err == nil {
t.Errorf("Unexpected non-error: %#v", fakeClient.Actions)
}
RunController(api.NewDefaultContext(), image, name, replicas, &fakeClient, "8080:80", -1)
if len(fakeClient.Actions) != 1 || fakeClient.Actions[0].Action != "create-controller" {
t.Errorf("Unexpected actions: %#v", fakeClient.Actions)
}
controller := fakeClient.Actions[0].Value.(*api.ReplicationController)
if controller.Name != name ||
controller.Spec.Replicas != replicas ||
controller.Spec.Template.Spec.Containers[0].Image != image {
t.Errorf("Unexpected controller: %#v", controller)
}
}
func TestRunControllerWithService(t *testing.T) {
fakeClient := client.Fake{}
name := "name"
image := "foo/bar"
replicas := 3
RunController(api.NewDefaultContext(), image, name, replicas, &fakeClient, "", 8000)
if len(fakeClient.Actions) != 2 ||
fakeClient.Actions[0].Action != "create-controller" ||
fakeClient.Actions[1].Action != "create-service" {
t.Errorf("Unexpected actions: %#v", fakeClient.Actions)
}
controller := fakeClient.Actions[0].Value.(*api.ReplicationController)
if controller.Name != name ||
controller.Spec.Replicas != replicas ||
controller.Spec.Template.Spec.Containers[0].Image != image {
t.Errorf("Unexpected controller: %#v", controller)
}
}
func TestStopController(t *testing.T) {
fakeClient := client.Fake{}
name := "name"
StopController(api.NewDefaultContext(), name, &fakeClient)
if len(fakeClient.Actions) != 2 {
t.Errorf("Unexpected actions: %#v", fakeClient.Actions)
}
if fakeClient.Actions[0].Action != "get-controller" ||
fakeClient.Actions[0].Value.(string) != name {
t.Errorf("Unexpected Action: %#v", fakeClient.Actions[0])
}
controller := fakeClient.Actions[1].Value.(*api.ReplicationController)
if fakeClient.Actions[1].Action != "update-controller" ||
controller.Spec.Replicas != 0 {
t.Errorf("Unexpected Action: %#v", fakeClient.Actions[1])
}
}
func TestResizeController(t *testing.T) {
fakeClient := client.Fake{}
name := "name"
replicas := 17
ResizeController(api.NewDefaultContext(), name, replicas, &fakeClient)
if len(fakeClient.Actions) != 2 {
t.Errorf("Unexpected actions: %#v", fakeClient.Actions)
}
if fakeClient.Actions[0].Action != "get-controller" ||
fakeClient.Actions[0].Value.(string) != name {
t.Errorf("Unexpected Action: %#v", fakeClient.Actions[0])
}
controller := fakeClient.Actions[1].Value.(*api.ReplicationController)
if fakeClient.Actions[1].Action != "update-controller" ||
controller.Spec.Replicas != 17 {
t.Errorf("Unexpected Action: %#v", fakeClient.Actions[1])
}
}
func TestCloudCfgDeleteController(t *testing.T) {
fakeClient := client.Fake{}
name := "name"
err := DeleteController(api.NewDefaultContext(), name, &fakeClient)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if len(fakeClient.Actions) != 2 {
t.Errorf("Unexpected actions: %#v", fakeClient.Actions)
}
if fakeClient.Actions[0].Action != "get-controller" ||
fakeClient.Actions[0].Value.(string) != name {
t.Errorf("Unexpected Action: %#v", fakeClient.Actions[0])
}
if fakeClient.Actions[1].Action != "delete-controller" ||
fakeClient.Actions[1].Value.(string) != name {
t.Errorf("Unexpected Action: %#v", fakeClient.Actions[1])
}
}
func TestCloudCfgDeleteControllerWithReplicas(t *testing.T) {
fakeClient := client.Fake{
Ctrl: api.ReplicationController{
Spec: api.ReplicationControllerSpec{
Replicas: 2,
},
},
}
name := "name"
err := DeleteController(api.NewDefaultContext(), name, &fakeClient)
if len(fakeClient.Actions) != 1 {
t.Errorf("Unexpected actions: %#v", fakeClient.Actions)
}
if fakeClient.Actions[0].Action != "get-controller" ||
fakeClient.Actions[0].Value.(string) != name {
t.Errorf("Unexpected Action: %#v", fakeClient.Actions[0])
}
if err == nil {
t.Errorf("Unexpected non-error.")
}
}
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)
}
}
}
}
func TestLoadClientAuthInfoOrPrompt(t *testing.T) {
loadAuthInfoTests := []struct {
authData string
authInfo *clientauth.Info
r io.Reader
}{
{
`{"user": "user", "password": "pass"}`,
&clientauth.Info{User: "user", Password: "pass"},
nil,
},
{
"", nil, nil,
},
{
"missing",
&clientauth.Info{User: "user", Password: "pass"},
bytes.NewBufferString("user\npass"),
},
}
for _, loadAuthInfoTest := range loadAuthInfoTests {
tt := loadAuthInfoTest
aifile, err := ioutil.TempFile("", "testAuthInfo")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if tt.authData != "missing" {
defer os.Remove(aifile.Name())
defer aifile.Close()
_, err = aifile.WriteString(tt.authData)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
} else {
aifile.Close()
os.Remove(aifile.Name())
}
authInfo, err := LoadClientAuthInfoOrPrompt(aifile.Name(), tt.r)
if len(tt.authData) == 0 && tt.authData != "missing" {
if err == nil {
t.Error("LoadClientAuthInfoOrPrompt didn't fail on empty file")
}
continue
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if !reflect.DeepEqual(authInfo, tt.authInfo) {
t.Errorf("Expected %v, got %v", tt.authInfo, authInfo)
}
}
}
func TestMakePorts(t *testing.T) {
var successTestCases = []struct {
spec string
ports []api.Port
}{
{
"8080:80,8081:8081,443:444",
[]api.Port{
{HostPort: 8080, ContainerPort: 80},
{HostPort: 8081, ContainerPort: 8081},
{HostPort: 443, ContainerPort: 444},
},
},
{
"",
[]api.Port{},
},
}
for _, tt := range successTestCases {
ports, err := portsFromString(tt.spec)
if !reflect.DeepEqual(ports, tt.ports) {
t.Errorf("Expected %#v, got %#v", tt.ports, ports)
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
}
var failTestCases = []struct {
spec string
}{
{"8080:"},
{":80"},
{":"},
}
for _, tt := range failTestCases {
_, err := portsFromString(tt.spec)
if err == nil {
t.Errorf("Unexpected non-error")
}
}
}

View File

@ -1,61 +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 kubecfg
import (
"fmt"
"reflect"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
)
type Parser struct {
storageToType map[string]reflect.Type
}
// NewParser creates a new parser.
func NewParser(objectMap map[string]runtime.Object) *Parser {
typeMap := make(map[string]reflect.Type)
for name, obj := range objectMap {
typeMap[name] = reflect.TypeOf(obj).Elem()
}
return &Parser{typeMap}
}
// ToWireFormat takes input 'data' as either json or yaml, checks that it parses as the
// appropriate object type, and returns json for sending to the API or an error.
func (p *Parser) ToWireFormat(data []byte, storage string, decode runtime.Codec, encode runtime.Codec) ([]byte, error) {
prototypeType, found := p.storageToType[storage]
if !found {
return nil, fmt.Errorf("unknown storage type: %v", storage)
}
obj := reflect.New(prototypeType).Interface().(runtime.Object)
err := decode.DecodeInto(data, obj)
if err != nil {
return nil, err
}
return encode.Encode(obj)
}
func (p *Parser) SupportedWireStorage() []string {
types := []string{}
for k := range p.storageToType {
types = append(types, k)
}
return types
}

View File

@ -1,157 +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 kubecfg
import (
"encoding/json"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/ghodss/yaml"
)
func TestParseBadStorage(t *testing.T) {
p := NewParser(map[string]runtime.Object{})
_, err := p.ToWireFormat([]byte("{}"), "badstorage", latest.Codec, latest.Codec)
if err == nil {
t.Errorf("Expected error, received none")
}
}
func DoParseTest(t *testing.T, storage string, obj runtime.Object, codec runtime.Codec, p *Parser) {
jsonData, _ := codec.Encode(obj)
var tmp map[string]interface{}
json.Unmarshal(jsonData, &tmp)
yamlData, _ := yaml.Marshal(tmp)
t.Logf("Intermediate yaml:\n%v\n", string(yamlData))
t.Logf("Intermediate json:\n%v\n", string(jsonData))
jsonGot, jsonErr := p.ToWireFormat(jsonData, storage, latest.Codec, codec)
yamlGot, yamlErr := p.ToWireFormat(yamlData, storage, latest.Codec, codec)
if jsonErr != nil {
t.Errorf("json err: %#v", jsonErr)
}
if yamlErr != nil {
t.Errorf("yaml err: %#v", yamlErr)
}
if string(jsonGot) != string(jsonData) {
t.Errorf("json output didn't match:\nGot:\n%v\n\nWanted:\n%v\n",
string(jsonGot), string(jsonData))
}
if string(yamlGot) != string(jsonData) {
t.Errorf("yaml parsed output didn't match:\nGot:\n%v\n\nWanted:\n%v\n",
string(yamlGot), string(jsonData))
}
}
var testParser = NewParser(map[string]runtime.Object{
"pods": &api.Pod{},
"services": &api.Service{},
"replicationControllers": &api.ReplicationController{},
})
func TestParsePod(t *testing.T) {
DoParseTest(t, "pods", &api.Pod{
TypeMeta: api.TypeMeta{APIVersion: "v1beta1", Kind: "Pod"},
ObjectMeta: api.ObjectMeta{Name: "test pod"},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "my container",
ImagePullPolicy: api.PullIfNotPresent,
TerminationMessagePath: api.TerminationMessagePathDefault,
},
},
Volumes: []api.Volume{
{Name: "volume", Source: api.VolumeSource{EmptyDir: &api.EmptyDir{}}},
},
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
}, v1beta1.Codec, testParser)
}
func TestParseService(t *testing.T) {
DoParseTest(t, "services", &api.Service{
TypeMeta: api.TypeMeta{APIVersion: "v1beta1", Kind: "Service"},
ObjectMeta: api.ObjectMeta{
Name: "my service",
Labels: map[string]string{
"area": "staging",
},
},
Spec: api.ServiceSpec{
Port: 8080,
Selector: map[string]string{
"area": "staging",
},
Protocol: "TCP",
SessionAffinity: "None",
},
}, v1beta1.Codec, testParser)
}
func TestParseController(t *testing.T) {
DoParseTest(t, "replicationControllers", &api.ReplicationController{
TypeMeta: api.TypeMeta{APIVersion: "v1beta1", Kind: "ReplicationController"},
ObjectMeta: api.ObjectMeta{Name: "my controller"},
Spec: api.ReplicationControllerSpec{
Replicas: 9001,
Template: &api.PodTemplateSpec{
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "my container",
ImagePullPolicy: api.PullIfNotPresent,
TerminationMessagePath: api.TerminationMessagePathDefault,
},
},
Volumes: []api.Volume{
{Name: "volume", Source: api.VolumeSource{EmptyDir: &api.EmptyDir{}}},
},
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
},
},
}, v1beta1.Codec, testParser)
}
type TestParseType struct {
api.TypeMeta `json:",inline"`
api.ObjectMeta `json:"metadata"`
Data string `json:"data"`
}
func (*TestParseType) IsAnAPIObject() {}
func TestParseCustomType(t *testing.T) {
api.Scheme.AddKnownTypes("", &TestParseType{})
api.Scheme.AddKnownTypes("v1beta1", &TestParseType{})
api.Scheme.AddKnownTypes("v1beta2", &TestParseType{})
parser := NewParser(map[string]runtime.Object{
"custom": &TestParseType{},
})
DoParseTest(t, "custom", &TestParseType{
TypeMeta: api.TypeMeta{APIVersion: "", Kind: "TestParseType"},
ObjectMeta: api.ObjectMeta{Name: "my custom object"},
Data: "test data",
}, v1beta1.Codec, parser)
}

View File

@ -1,81 +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 kubecfg
import (
"net/http"
"net/http/httputil"
"net/url"
"strings"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
)
// ProxyServer is a http.Handler which proxies Kubernetes APIs to remote API server.
type ProxyServer struct {
httputil.ReverseProxy
}
// NewProxyServer creates and installs a new ProxyServer.
// It automatically registers the created ProxyServer to http.DefaultServeMux.
func NewProxyServer(filebase string, cfg *client.Config) (*ProxyServer, error) {
prefix := cfg.Prefix
if prefix == "" {
prefix = "/api"
}
target, err := url.Parse(singleJoiningSlash(cfg.Host, prefix))
if err != nil {
return nil, err
}
proxy := newProxyServer(target)
if proxy.Transport, err = client.TransportFor(cfg); err != nil {
return nil, err
}
http.Handle("/api/", http.StripPrefix("/api/", proxy))
http.Handle("/static/", newFileHandler("/static/", filebase))
return proxy, nil
}
// Serve starts the server (http.DefaultServeMux) on TCP port 8001, loops forever.
func (s *ProxyServer) Serve() error {
return http.ListenAndServe(":8001", nil)
}
func newProxyServer(target *url.URL) *ProxyServer {
director := func(req *http.Request) {
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
}
return &ProxyServer{ReverseProxy: httputil.ReverseProxy{Director: director}}
}
func newFileHandler(prefix, base string) http.Handler {
return http.StripPrefix(prefix, http.FileServer(http.Dir(base)))
}
func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
return a + b[1:]
case !aslash && !bslash:
return a + "/" + b
}
return a + b
}

View File

@ -1,106 +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 kubecfg
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"path/filepath"
"strings"
"testing"
)
func TestFileServing(t *testing.T) {
const (
fname = "test.txt"
data = "This is test data"
)
dir, err := ioutil.TempDir("", "data")
if err != nil {
t.Fatalf("error creating tmp dir: %v", err)
}
if err := ioutil.WriteFile(filepath.Join(dir, fname), []byte(data), 0755); err != nil {
t.Fatalf("error writing tmp file: %v", err)
}
const prefix = "/foo/"
handler := newFileHandler(prefix, dir)
server := httptest.NewServer(handler)
defer server.Close()
url := server.URL + prefix + fname
res, err := http.Get(url)
if err != nil {
t.Fatalf("http.Get(%q) error: %v", url, err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
t.Errorf("res.StatusCode = %d; want %d", res.StatusCode, http.StatusOK)
}
b, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatalf("error reading resp body: %v", err)
}
if string(b) != data {
t.Errorf("have %q; want %q", string(b), data)
}
}
func TestAPIRequests(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "%s %s %s", r.Method, r.RequestURI, string(b))
}))
defer ts.Close()
// httptest.NewServer should always generate a valid URL.
target, _ := url.Parse(ts.URL)
proxy := newProxyServer(target)
tests := []struct{ method, body string }{
{"GET", ""},
{"DELETE", ""},
{"POST", "test payload"},
{"PUT", "test payload"},
}
const path = "/api/test?fields=ID%3Dfoo&labels=key%3Dvalue"
for i, tt := range tests {
r, err := http.NewRequest(tt.method, path, strings.NewReader(tt.body))
if err != nil {
t.Errorf("error creating request: %v", err)
continue
}
w := httptest.NewRecorder()
proxy.ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Errorf("%d: proxy.ServeHTTP w.Code = %d; want %d", i, w.Code, http.StatusOK)
}
want := strings.Join([]string{tt.method, path, tt.body}, " ")
if w.Body.String() != want {
t.Errorf("%d: response body = %q; want %q", i, w.Body.String(), want)
}
}
}

View File

@ -1,460 +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 kubecfg
import (
"encoding/json"
"fmt"
"io"
"reflect"
"strings"
"text/tabwriter"
"text/template"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/ghodss/yaml"
"github.com/golang/glog"
)
// ResourcePrinter is an interface that knows how to print API resources.
type ResourcePrinter interface {
// Print receives an arbitrary JSON body, formats it and prints it to a writer.
Print([]byte, io.Writer) error
PrintObj(runtime.Object, io.Writer) error
}
// IdentityPrinter is an implementation of ResourcePrinter which simply copies the body out to the output stream.
type IdentityPrinter struct{}
// Print is an implementation of ResourcePrinter.Print which simply writes the data to the Writer.
func (i *IdentityPrinter) Print(data []byte, w io.Writer) error {
_, err := w.Write(data)
return err
}
// PrintObj is an implementation of ResourcePrinter.PrintObj which simply writes the object to the Writer.
func (i *IdentityPrinter) PrintObj(obj runtime.Object, output io.Writer) error {
data, err := latest.Codec.Encode(obj)
if err != nil {
return err
}
return i.Print(data, output)
}
// YAMLPrinter is an implementation of ResourcePrinter which parsess JSON, and re-formats as YAML.
type YAMLPrinter struct{}
// Print parses the data as JSON, re-formats as YAML and prints the YAML.
func (y *YAMLPrinter) Print(data []byte, w io.Writer) error {
var obj interface{}
if err := json.Unmarshal(data, &obj); err != nil {
return err
}
output, err := yaml.Marshal(obj)
if err != nil {
return err
}
_, err = fmt.Fprint(w, string(output))
return err
}
// PrintObj prints the data as YAML.
func (y *YAMLPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
output, err := yaml.Marshal(obj)
if err != nil {
return err
}
_, err = fmt.Fprint(w, string(output))
return err
}
type handlerEntry struct {
columns []string
printFunc reflect.Value
}
// HumanReadablePrinter is an implementation of ResourcePrinter which attempts to provide more elegant output.
type HumanReadablePrinter struct {
handlerMap map[reflect.Type]*handlerEntry
}
// NewHumanReadablePrinter creates a HumanReadablePrinter.
func NewHumanReadablePrinter() *HumanReadablePrinter {
printer := &HumanReadablePrinter{make(map[reflect.Type]*handlerEntry)}
printer.addDefaultHandlers()
return printer
}
// Handler adds a print handler with a given set of columns to HumanReadablePrinter instance.
// printFunc is the function that will be called to print an object.
// It must be of the following type:
// func printFunc(object ObjectType, w io.Writer) error
// where ObjectType is the type of the object that will be printed.
func (h *HumanReadablePrinter) Handler(columns []string, printFunc interface{}) error {
printFuncValue := reflect.ValueOf(printFunc)
if err := h.validatePrintHandlerFunc(printFuncValue); err != nil {
glog.Errorf("Unable to add print handler: %v", err)
return err
}
objType := printFuncValue.Type().In(0)
h.handlerMap[objType] = &handlerEntry{
columns: columns,
printFunc: printFuncValue,
}
return nil
}
func (h *HumanReadablePrinter) validatePrintHandlerFunc(printFunc reflect.Value) error {
if printFunc.Kind() != reflect.Func {
return fmt.Errorf("invalid print handler. %#v is not a function.", printFunc)
}
funcType := printFunc.Type()
if funcType.NumIn() != 2 || funcType.NumOut() != 1 {
return fmt.Errorf("invalid print handler." +
"Must accept 2 parameters and return 1 value.")
}
if funcType.In(1) != reflect.TypeOf((*io.Writer)(nil)).Elem() ||
funcType.Out(0) != reflect.TypeOf((*error)(nil)).Elem() {
return fmt.Errorf("invalid print handler. The expected signature is: "+
"func handler(obj %v, w io.Writer) error", funcType.In(0))
}
return nil
}
var podColumns = []string{"Name", "Image(s)", "Host", "Labels", "Status"}
var replicationControllerColumns = []string{"Name", "Image(s)", "Selector", "Replicas"}
var serviceColumns = []string{"Name", "Labels", "Selector", "IP", "Port"}
var minionColumns = []string{"Minion identifier", "Labels"}
var statusColumns = []string{"Status"}
var eventColumns = []string{"Name", "Kind", "Reason", "Message"}
// addDefaultHandlers adds print handlers for default Kubernetes types.
func (h *HumanReadablePrinter) addDefaultHandlers() {
h.Handler(podColumns, printPod)
h.Handler(podColumns, printPodList)
h.Handler(replicationControllerColumns, printReplicationController)
h.Handler(replicationControllerColumns, printReplicationControllerList)
h.Handler(serviceColumns, printService)
h.Handler(serviceColumns, printServiceList)
h.Handler(minionColumns, printMinion)
h.Handler(minionColumns, printMinionList)
h.Handler(statusColumns, printStatus)
h.Handler(eventColumns, printEvent)
h.Handler(eventColumns, printEventList)
}
func (h *HumanReadablePrinter) unknown(data []byte, w io.Writer) error {
_, err := fmt.Fprintf(w, "Unknown object: %s", string(data))
return err
}
func (h *HumanReadablePrinter) printHeader(columnNames []string, w io.Writer) error {
if _, err := fmt.Fprintf(w, "%s\n", strings.Join(columnNames, "\t")); err != nil {
return err
}
var lines []string
for _ = range columnNames {
lines = append(lines, "----------")
}
_, err := fmt.Fprintf(w, "%s\n", strings.Join(lines, "\t"))
return err
}
func makeImageList(manifest api.PodSpec) string {
var images []string
for _, container := range manifest.Containers {
images = append(images, container.Image)
}
return strings.Join(images, ",")
}
func makeImageListPodSpec(spec api.PodSpec) string {
var images []string
for _, container := range spec.Containers {
images = append(images, container.Image)
}
return strings.Join(images, ",")
}
func podHostString(host, ip string) string {
if host == "" && ip == "" {
return "<unassigned>"
}
return host + "/" + ip
}
func printPod(pod *api.Pod, w io.Writer) error {
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n",
pod.Name, makeImageList(pod.Spec),
podHostString(pod.Status.Host, pod.Status.HostIP),
labels.Set(pod.Labels), pod.Status.Phase)
return err
}
func printPodList(podList *api.PodList, w io.Writer) error {
for _, pod := range podList.Items {
if err := printPod(&pod, w); err != nil {
return err
}
}
return nil
}
func printReplicationController(controller *api.ReplicationController, w io.Writer) error {
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%d\n",
controller.Name, makeImageListPodSpec(controller.Spec.Template.Spec),
labels.Set(controller.Spec.Selector), controller.Spec.Replicas)
return err
}
func printReplicationControllerList(list *api.ReplicationControllerList, w io.Writer) error {
for _, controller := range list.Items {
if err := printReplicationController(&controller, w); err != nil {
return err
}
}
return nil
}
func printService(svc *api.Service, w io.Writer) error {
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\n", svc.Name, labels.Set(svc.Labels),
labels.Set(svc.Spec.Selector), svc.Spec.PortalIP, svc.Spec.Port)
return err
}
func printServiceList(list *api.ServiceList, w io.Writer) error {
for _, svc := range list.Items {
if err := printService(&svc, w); err != nil {
return err
}
}
return nil
}
func printMinion(minion *api.Node, w io.Writer) error {
_, err := fmt.Fprintf(w, "%s\t%s\n", minion.Name, labels.Set(minion.Labels))
return err
}
func printMinionList(list *api.NodeList, w io.Writer) error {
for _, minion := range list.Items {
if err := printMinion(&minion, w); err != nil {
return err
}
}
return nil
}
func printStatus(status *api.Status, w io.Writer) error {
_, err := fmt.Fprintf(w, "%v\n", status.Status)
return err
}
func printEvent(event *api.Event, w io.Writer) error {
_, err := fmt.Fprintf(
w, "%s\t%s\t%s\t%s\n",
event.InvolvedObject.Name,
event.InvolvedObject.Kind,
event.Reason,
event.Message,
)
return err
}
func printEventList(list *api.EventList, w io.Writer) error {
for i := range list.Items {
if err := printEvent(&list.Items[i], w); err != nil {
return err
}
}
return nil
}
// Print parses the data as JSON, then prints the parsed data in a human-friendly
// format according to the type of the data.
func (h *HumanReadablePrinter) Print(data []byte, output io.Writer) error {
var mapObj map[string]runtime.Object
if err := json.Unmarshal([]byte(data), &mapObj); err != nil {
return err
}
if _, contains := mapObj["kind"]; !contains {
return fmt.Errorf("unexpected object with no 'kind' field: %s", data)
}
obj, err := latest.Codec.Decode(data)
if err != nil {
return err
}
return h.PrintObj(obj, output)
}
// PrintObj prints the obj in a human-friendly format according to the type of the obj.
func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) error {
w := tabwriter.NewWriter(output, 20, 5, 3, ' ', 0)
defer w.Flush()
if handler := h.handlerMap[reflect.TypeOf(obj)]; handler != nil {
h.printHeader(handler.columns, w)
args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(w)}
resultValue := handler.printFunc.Call(args)[0]
if resultValue.IsNil() {
return nil
} else {
return resultValue.Interface().(error)
}
} else {
return fmt.Errorf("unknown type %#v", obj)
}
}
// TemplatePrinter is an implementation of ResourcePrinter which formats data with a Go Template.
type TemplatePrinter struct {
rawTemplate string
template *template.Template
}
func NewTemplatePrinter(tmpl []byte) (*TemplatePrinter, error) {
t, err := template.New("output").
Funcs(template.FuncMap{"exists": exists}).
Parse(string(tmpl))
if err != nil {
return nil, err
}
return &TemplatePrinter{string(tmpl), t}, nil
}
// Print parses the data as JSON, and re-formats it with the Go Template.
func (t *TemplatePrinter) Print(data []byte, w io.Writer) error {
out := map[string]interface{}{}
err := json.Unmarshal(data, &out)
if err != nil {
return err
}
if err := t.safeExecute(w, out); err != nil {
// It is way easier to debug this stuff when it shows up in
// stdout instead of just stdin. So in addition to returning
// a nice error, also print useful stuff with the writer.
fmt.Fprintf(w, "Error executing template: %v\n", err)
fmt.Fprintf(w, "template was:\n%v\n", t.rawTemplate)
fmt.Fprintf(w, "raw data was:\n%v\n", string(data))
fmt.Fprintf(w, "object given to template engine was:\n%+v\n", out)
return fmt.Errorf("error executing template '%v': '%v'\n----data----\n%#v\n", t.rawTemplate, err, out)
}
return nil
}
// PrintObj formats the obj with the Go Template.
func (t *TemplatePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
data, err := latest.Codec.Encode(obj)
if err != nil {
return err
}
return t.Print(data, w)
}
// safeExecute tries to execute the template, but catches panics and returns an error
// should the template engine panic.
func (p *TemplatePrinter) safeExecute(w io.Writer, obj interface{}) error {
var panicErr error
// Sorry for the double anonymous function. There's probably a clever way
// to do this that has the defer'd func setting the value to be returned, but
// that would be even less obvious.
retErr := func() error {
defer func() {
if x := recover(); x != nil {
panicErr = fmt.Errorf("caught panic: %+v", x)
}
}()
return p.template.Execute(w, obj)
}()
if panicErr != nil {
return panicErr
}
return retErr
}
// exists returns true if it would be possible to call the index function
// with these arguments.
//
// TODO: how to document this for users?
//
// index returns the result of indexing its first argument by the following
// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each
// indexed item must be a map, slice, or array.
func exists(item interface{}, indices ...interface{}) bool {
v := reflect.ValueOf(item)
for _, i := range indices {
index := reflect.ValueOf(i)
var isNil bool
if v, isNil = indirect(v); isNil {
return false
}
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.String:
var x int64
switch index.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
x = index.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
x = int64(index.Uint())
default:
return false
}
if x < 0 || x >= int64(v.Len()) {
return false
}
v = v.Index(int(x))
case reflect.Map:
if !index.IsValid() {
index = reflect.Zero(v.Type().Key())
}
if !index.Type().AssignableTo(v.Type().Key()) {
return false
}
if x := v.MapIndex(index); x.IsValid() {
v = x
} else {
v = reflect.Zero(v.Type().Elem())
}
default:
return false
}
}
if _, isNil := indirect(v); isNil {
return false
}
return true
}
// stolen from text/template
// indirect returns the item at the end of indirection, and a bool to indicate if it's nil.
// We indirect through pointers and empty interfaces (only) because
// non-empty interfaces have methods we might need.
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
if v.IsNil() {
return v, true
}
if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
break
}
}
return v, false
}

View File

@ -1,313 +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 kubecfg
import (
"bytes"
"encoding/json"
"fmt"
"io"
"reflect"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/ghodss/yaml"
)
func TestYAMLPrinterPrint(t *testing.T) {
type testStruct struct {
Key string `json:"Key"`
Map map[string]int `json:"Map"`
StringList []string `json:"StringList"`
IntList []int `json:"IntList"`
}
testData := testStruct{
"testValue",
map[string]int{"TestSubkey": 1},
[]string{"a", "b", "c"},
[]int{1, 2, 3},
}
printer := &YAMLPrinter{}
buf := bytes.NewBuffer([]byte{})
err := printer.Print([]byte("invalidJSON"), buf)
if err == nil {
t.Error("Error: didn't fail on invalid JSON data")
}
jTestData, err := json.Marshal(&testData)
if err != nil {
t.Fatal("Unexpected error: couldn't marshal test data")
}
err = printer.Print(jTestData, buf)
if err != nil {
t.Fatal(err)
}
var poutput testStruct
err = yaml.Unmarshal(buf.Bytes(), &poutput)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(testData, poutput) {
t.Errorf("Test data and unmarshaled data are not equal: %#v vs %#v", poutput, testData)
}
obj := &api.Pod{
ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
}
buf.Reset()
printer.PrintObj(obj, buf)
var objOut api.Pod
err = yaml.Unmarshal([]byte(buf.String()), &objOut)
if err != nil {
t.Errorf("Unexpected error: %#v", err)
}
if !reflect.DeepEqual(obj, &objOut) {
t.Errorf("Unexpected inequality: %#v vs %#v", obj, &objOut)
}
}
func TestIdentityPrinter(t *testing.T) {
printer := &IdentityPrinter{}
buff := bytes.NewBuffer([]byte{})
str := "this is a test string"
printer.Print([]byte(str), buff)
if buff.String() != str {
t.Errorf("Bytes are not equal: %s vs %s", str, buff.String())
}
obj := &api.Pod{
ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
}
buff.Reset()
printer.PrintObj(obj, buff)
objOut, err := latest.Codec.Decode([]byte(buff.String()))
if err != nil {
t.Errorf("Unexpected error: %#v", err)
}
if !reflect.DeepEqual(obj, objOut) {
t.Errorf("Unexpected inequality: %#v vs %#v", obj, objOut)
}
}
type TestPrintType struct {
Data string
}
func (*TestPrintType) IsAnAPIObject() {}
type TestUnknownType struct{}
func (*TestUnknownType) IsAnAPIObject() {}
func PrintCustomType(obj *TestPrintType, w io.Writer) error {
_, err := fmt.Fprintf(w, "%s", obj.Data)
return err
}
func ErrorPrintHandler(obj *TestPrintType, w io.Writer) error {
return fmt.Errorf("ErrorPrintHandler error")
}
func TestCustomTypePrinting(t *testing.T) {
columns := []string{"Data"}
printer := NewHumanReadablePrinter()
printer.Handler(columns, PrintCustomType)
obj := TestPrintType{"test object"}
buffer := &bytes.Buffer{}
err := printer.PrintObj(&obj, buffer)
if err != nil {
t.Errorf("An error occurred printing the custom type: %#v", err)
}
expectedOutput := "Data\n----------\ntest object"
if buffer.String() != expectedOutput {
t.Errorf("The data was not printed as expected. Expected:\n%s\nGot:\n%s", expectedOutput, buffer.String())
}
}
func TestPrintHandlerError(t *testing.T) {
columns := []string{"Data"}
printer := NewHumanReadablePrinter()
printer.Handler(columns, ErrorPrintHandler)
obj := TestPrintType{"test object"}
buffer := &bytes.Buffer{}
err := printer.PrintObj(&obj, buffer)
if err == nil || err.Error() != "ErrorPrintHandler error" {
t.Errorf("Did not get the expected error: %#v", err)
}
}
func TestUnknownTypePrinting(t *testing.T) {
printer := NewHumanReadablePrinter()
buffer := &bytes.Buffer{}
err := printer.PrintObj(&TestUnknownType{}, buffer)
if err == nil {
t.Errorf("An error was expected from printing unknown type")
}
}
func TestTemplateEmitsVersionedObjects(t *testing.T) {
// kind is always blank in memory and set on the wire
printer, err := NewTemplatePrinter([]byte(`{{.kind}}`))
if err != nil {
t.Fatalf("tmpl fail: %v", err)
}
buffer := &bytes.Buffer{}
err = printer.PrintObj(&api.Pod{}, buffer)
if err != nil {
t.Fatalf("print fail: %v", err)
}
if e, a := "Pod", string(buffer.Bytes()); e != a {
t.Errorf("Expected %v, got %v", e, a)
}
}
func TestTemplatePanic(t *testing.T) {
tmpl := `{{and ((index .currentState.info "foo").state.running.startedAt) .currentState.info.net.state.running.startedAt}}`
printer, err := NewTemplatePrinter([]byte(tmpl))
if err != nil {
t.Fatalf("tmpl fail: %v", err)
}
buffer := &bytes.Buffer{}
err = printer.PrintObj(&api.Pod{}, buffer)
if err == nil {
t.Fatalf("expected that template to crash")
}
if buffer.String() == "" {
t.Errorf("no debugging info was printed")
}
}
func TestTemplateStrings(t *testing.T) {
// This unit tests the "exists" function as well as the template from update.sh
table := map[string]struct {
pod api.Pod
expect string
}{
"nilInfo": {api.Pod{}, "false"},
"emptyInfo": {api.Pod{Status: api.PodStatus{Info: api.PodInfo{}}}, "false"},
"fooExists": {
api.Pod{
Status: api.PodStatus{
Info: api.PodInfo{"foo": api.ContainerStatus{}},
},
},
"false",
},
"barExists": {
api.Pod{
Status: api.PodStatus{
Info: api.PodInfo{"bar": api.ContainerStatus{}},
},
},
"false",
},
"bothExist": {
api.Pod{
Status: api.PodStatus{
Info: api.PodInfo{
"foo": api.ContainerStatus{},
"bar": api.ContainerStatus{},
},
},
},
"false",
},
"oneValid": {
api.Pod{
Status: api.PodStatus{
Info: api.PodInfo{
"foo": api.ContainerStatus{},
"bar": api.ContainerStatus{
State: api.ContainerState{
Running: &api.ContainerStateRunning{
StartedAt: util.Time{},
},
},
},
},
},
},
"false",
},
"bothValid": {
api.Pod{
Status: api.PodStatus{
Info: api.PodInfo{
"foo": api.ContainerStatus{
State: api.ContainerState{
Running: &api.ContainerStateRunning{
StartedAt: util.Time{},
},
},
},
"bar": api.ContainerStatus{
State: api.ContainerState{
Running: &api.ContainerStateRunning{
StartedAt: util.Time{},
},
},
},
},
},
},
"true",
},
}
// The point of this test is to verify that the below template works. If you change this
// template, you need to update hack/e2e-suite/update.sh.
tmpl :=
`{{and (exists . "currentState" "info" "foo" "state" "running") (exists . "currentState" "info" "bar" "state" "running")}}`
useThisToDebug := `
a: {{exists . "currentState"}}
b: {{exists . "currentState" "info"}}
c: {{exists . "currentState" "info" "foo"}}
d: {{exists . "currentState" "info" "foo" "state"}}
e: {{exists . "currentState" "info" "foo" "state" "running"}}
f: {{exists . "currentState" "info" "foo" "state" "running" "startedAt"}}`
_ = useThisToDebug // don't complain about unused var
printer, err := NewTemplatePrinter([]byte(tmpl))
if err != nil {
t.Fatalf("tmpl fail: %v", err)
}
for name, item := range table {
buffer := &bytes.Buffer{}
err = printer.PrintObj(&item.pod, buffer)
if err != nil {
t.Errorf("%v: unexpected err: %v", name, err)
continue
}
if e, a := item.expect, buffer.String(); e != a {
t.Errorf("%v: expected %v, got %v", name, e, a)
}
}
}

View File

@ -1,51 +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 kubecfg
import (
"encoding/json"
"fmt"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
)
func ValidateObject(data []byte, c *client.Client) error {
var obj interface{}
err := json.Unmarshal(data, &obj)
if err != nil {
return err
}
apiVersion, found := obj.(map[string]interface{})["apiVersion"]
if !found {
return fmt.Errorf("couldn't find apiVersion in object")
}
schemaData, err := c.RESTClient.Get().
AbsPath("/swaggerapi/api").
Prefix(apiVersion.(string)).
Do().
Raw()
if err != nil {
return err
}
schema, err := validation.NewSwaggerSchemaFromBytes(schemaData)
if err != nil {
return err
}
return schema.ValidateBytes(data)
}