mirror of https://github.com/k3s-io/k3s
Merge pull request #4291 from jlowdermilk/delete-kubecfg
Remove kubecfg, cleanup a few stray references.pull/6/head
commit
20f7cbb87b
|
@ -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
|
||||
|
|
|
@ -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[@]}}" "${@}"
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -1,6 +1,6 @@
|
|||
# Install and configure kubecfg
|
||||
# Install and configure kubectl
|
||||
|
||||
## Download the kubecfg CLI tool
|
||||
## Download the kubectl CLI tool
|
||||
|
||||
### Darwin
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue