Browse code

Remove kubecfg, expose kubectl in its place

Now that kubectl upstream has deprecated kubecfg, do the same for
OpenShift. This updates `openshift kube` to point to kubectl, exposed
directly as-is from upstream. It includes the new resize, run-container,
and rollingupdate commands.

Clean up and reorganize the "experimental" commands option to be a new,
visible sub-command `openshift ex`, which has tokens and config under
it for now. Remove the openshift-experimental binary.

When printing help directly, use os.Stdout.

Add more tests to hack/test-cmd.sh, and print slightly more logging.

Clayton Coleman authored on 2015/02/02 10:45:28
Showing 19 changed files
... ...
@@ -51,7 +51,6 @@ readonly OPENSHIFT_BINARY_SYMLINKS=(
51 51
   openshift-sti-build
52 52
   openshift-docker-build
53 53
   osc
54
-  openshift-experimental
55 54
 )
56 55
 readonly OPENSHIFT_BINARY_COPY=(
57 56
   osc
... ...
@@ -10,6 +10,8 @@ set -o pipefail
10 10
 OS_ROOT=$(dirname "${BASH_SOURCE}")/..
11 11
 source "${OS_ROOT}/hack/util.sh"
12 12
 
13
+os::log::install_errexit
14
+
13 15
 function cleanup()
14 16
 {
15 17
     out=$?
... ...
@@ -39,9 +41,11 @@ API_HOST=${API_HOST:-127.0.0.1}
39 39
 KUBELET_SCHEME=${KUBELET_SCHEME:-http}
40 40
 KUBELET_PORT=${KUBELET_PORT:-10250}
41 41
 
42
-ETCD_DATA_DIR=$(mktemp -d /tmp/openshift.local.etcd.XXXX)
43
-VOLUME_DIR=$(mktemp -d /tmp/openshift.local.volumes.XXXX)
44
-CERT_DIR="${CERT_DIR:-$(mktemp -d /tmp/openshift.local.certificates.XXXX)}"
42
+TEMP_DIR=$(mktemp -d /tmp/openshift-cmd.XXXX)
43
+ETCD_DATA_DIR="${TEMP_DIR}/etcd"
44
+VOLUME_DIR="${TEMP_DIR}/volumes"
45
+CERT_DIR="${TEMP_DIR}/certs"
46
+mkdir -p "${ETCD_DATA_DIR}" "${VOLUME_DIR}" "${CERT_DIR}"
45 47
 
46 48
 # handle profiling defaults
47 49
 profile="${OPENSHIFT_PROFILE-}"
... ...
@@ -91,6 +95,14 @@ export OPENSHIFT_PROFILE="${CLI_PROFILE-}"
91 91
 # Begin tests
92 92
 #
93 93
 
94
+# verify some default commands
95
+[ "$(openshift cli)" ]
96
+[ "$(openshift ex)" ]
97
+[ "$(openshift ex config 2>&1)" ]
98
+[ "$(openshift ex tokens)" ]
99
+[ "$(openshift kubectl)" ]
100
+[ "$(openshift kube 2>&1)" ]
101
+
94 102
 osc get pods --match-server-version
95 103
 osc create -f examples/hello-openshift/hello-pod.json
96 104
 osc delete pods hello-openshift
... ...
@@ -138,6 +150,10 @@ echo "deploymentConfigs: ok"
138 138
 osc process -f examples/guestbook/template.json | osc apply -f -
139 139
 echo "template+config: ok"
140 140
 
141
+openshift kube resize --replicas=2 rc guestbook
142
+osc get pods
143
+echo "resize: ok"
144
+
141 145
 osc process -f examples/sample-app/application-template-dockerbuild.json | osc apply -f -
142 146
 echo "buildConfig: ok"
143 147
 
... ...
@@ -188,3 +188,125 @@ function time_now()
188 188
 {
189 189
   echo $(($(date +'%s * 1000 + %-N / 1000000')))
190 190
 }
191
+
192
+# Handler for when we exit automatically on an error.
193
+# Borrowed from https://gist.github.com/ahendrix/7030300
194
+os::log::errexit() {
195
+  local err="${PIPESTATUS[@]}"
196
+
197
+  # If the shell we are in doesn't have errexit set (common in subshells) then
198
+  # don't dump stacks.
199
+  set +o | grep -qe "-o errexit" || return
200
+
201
+  set +o xtrace
202
+  local code="${1:-1}"
203
+  os::log::error_exit "'${BASH_COMMAND}' exited with status $err" "${1:-1}" 1
204
+}
205
+
206
+os::log::install_errexit() {
207
+  # trap ERR to provide an error handler whenever a command exits nonzero  this
208
+  # is a more verbose version of set -o errexit
209
+  trap 'os::log::errexit' ERR
210
+
211
+  # setting errtrace allows our ERR trap handler to be propagated to functions,
212
+  # expansions and subshells
213
+  set -o errtrace
214
+}
215
+
216
+# Print out the stack trace
217
+#
218
+# Args:
219
+#   $1 The number of stack frames to skip when printing.
220
+os::log::stack() {
221
+  local stack_skip=${1:-0}
222
+  stack_skip=$((stack_skip + 1))
223
+  if [[ ${#FUNCNAME[@]} -gt $stack_skip ]]; then
224
+    echo "Call stack:" >&2
225
+    local i
226
+    for ((i=1 ; i <= ${#FUNCNAME[@]} - $stack_skip ; i++))
227
+    do
228
+      local frame_no=$((i - 1 + stack_skip))
229
+      local source_file=${BASH_SOURCE[$frame_no]}
230
+      local source_lineno=${BASH_LINENO[$((frame_no - 1))]}
231
+      local funcname=${FUNCNAME[$frame_no]}
232
+      echo "  $i: ${source_file}:${source_lineno} ${funcname}(...)" >&2
233
+    done
234
+  fi
235
+}
236
+
237
+# Log an error and exit.
238
+# Args:
239
+#   $1 Message to log with the error
240
+#   $2 The error code to return
241
+#   $3 The number of stack frames to skip when printing.
242
+os::log::error_exit() {
243
+  local message="${1:-}"
244
+  local code="${2:-1}"
245
+  local stack_skip="${3:-0}"
246
+  stack_skip=$((stack_skip + 1))
247
+
248
+  local source_file=${BASH_SOURCE[$stack_skip]}
249
+  local source_line=${BASH_LINENO[$((stack_skip - 1))]}
250
+  echo "!!! Error in ${source_file}:${source_line}" >&2
251
+  [[ -z ${1-} ]] || {
252
+    echo "  ${1}" >&2
253
+  }
254
+
255
+  os::log::stack $stack_skip
256
+
257
+  echo "Exiting with status ${code}" >&2
258
+  exit "${code}"
259
+}
260
+
261
+# Log an error but keep going.  Don't dump the stack or exit.
262
+os::log::error() {
263
+  echo "!!! ${1-}" >&2
264
+  shift
265
+  for message; do
266
+    echo "    $message" >&2
267
+  done
268
+}
269
+
270
+# Print an usage message to stderr.  The arguments are printed directly.
271
+os::log::usage() {
272
+  echo >&2
273
+  local message
274
+  for message; do
275
+    echo "$message" >&2
276
+  done
277
+  echo >&2
278
+}
279
+
280
+os::log::usage_from_stdin() {
281
+  local messages=()
282
+  while read -r line; do
283
+    messages+=$line
284
+  done
285
+
286
+  os::log::usage "${messages[@]}"
287
+}
288
+
289
+# Print out some info that isn't a top level status line
290
+os::log::info() {
291
+  for message; do
292
+    echo "$message"
293
+  done
294
+}
295
+
296
+os::log::info_from_stdin() {
297
+  local messages=()
298
+  while read -r line; do
299
+    messages+=$line
300
+  done
301
+
302
+  os::log::info "${messages[@]}"
303
+}
304
+
305
+# Print a status line.  Formatted to show up in a stream of output.
306
+os::log::status() {
307
+  echo "+++ $1"
308
+  shift
309
+  for message; do
310
+    echo "    $message"
311
+  done
312
+}
... ...
@@ -10,7 +10,6 @@ RUN mkdir -p /var/lib/openshift
10 10
 
11 11
 ADD bin/openshift        /usr/bin/openshift
12 12
 RUN ln -s /usr/bin/openshift /usr/bin/osc && \
13
-    ln -s /usr/bin/openshift /usr/bin/openshift-experimental && \
14 13
     ln -s /usr/bin/openshift /usr/bin/openshift-deploy && \
15 14
     ln -s /usr/bin/openshift /usr/bin/openshift-docker-build && \
16 15
     ln -s /usr/bin/openshift /usr/bin/openshift-sti-build && \
... ...
@@ -27,21 +27,23 @@ on all resources.  Some commands you can try:
27 27
 Note: This is an alpha release of OpenShift and will change significantly.  See
28 28
     https://github.com/openshift/origin for the latest information on OpenShift.
29 29
 `
30
+const defaultClusterURL = "https://localhost:8443"
30 31
 
31
-func NewCommandCLI(name string) *cobra.Command {
32
+func NewCommandCLI(name, fullName string) *cobra.Command {
32 33
 	// Main command
33 34
 	cmds := &cobra.Command{
34 35
 		Use:     name,
35 36
 		Aliases: []string{"kubectl"},
36 37
 		Short:   "Client tools for OpenShift",
37
-		Long:    fmt.Sprintf(longDesc, name),
38
+		Long:    fmt.Sprintf(longDesc, fullName),
38 39
 		Run: func(c *cobra.Command, args []string) {
40
+			c.SetOutput(os.Stdout)
39 41
 			c.Help()
40 42
 		},
41 43
 	}
42 44
 
43 45
 	// Override global default to https and port 8443
44
-	clientcmd.DefaultCluster.Server = "https://localhost:8443"
46
+	clientcmd.DefaultCluster.Server = defaultClusterURL
45 47
 
46 48
 	// TODO: there should be two client configs, one for OpenShift, and one for Kubernetes
47 49
 	clientConfig := defaultClientConfig(cmds.PersistentFlags())
... ...
@@ -51,10 +53,8 @@ func NewCommandCLI(name string) *cobra.Command {
51 51
 	// Kubernetes CRUD commands
52 52
 	cmds.AddCommand(f.NewCmdGet(out))
53 53
 	cmds.AddCommand(f.NewCmdDescribe(out))
54
-	cmds.AddCommand(
55
-		// Deprecate 'osc apply' with 'osc create' command.
56
-		applyToCreate(f.NewCmdCreate(out)),
57
-	)
54
+	// Deprecate 'osc apply' with 'osc create' command.
55
+	cmds.AddCommand(applyToCreate(f.NewCmdCreate(out)))
58 56
 	cmds.AddCommand(f.NewCmdUpdate(out))
59 57
 	cmds.AddCommand(f.NewCmdDelete(out))
60 58
 	cmds.AddCommand(kubecmd.NewCmdNamespace(out))
... ...
@@ -76,6 +76,23 @@ func NewCommandCLI(name string) *cobra.Command {
76 76
 	return cmds
77 77
 }
78 78
 
79
+// NewCmdKubectl provides exactly the functionality from Kubernetes,
80
+// but with support for OpenShift resources
81
+func NewCmdKubectl(name string) *cobra.Command {
82
+	flags := pflag.NewFlagSet("", pflag.ContinueOnError)
83
+	clientcmd.DefaultCluster.Server = defaultClusterURL
84
+	clientConfig := defaultClientConfig(flags)
85
+	f := cmd.NewFactory(clientConfig)
86
+	cmd := f.NewKubectlCommand(os.Stdout)
87
+	cmd.Use = name
88
+	cmd.Short = "Kubernetes cluster management via kubectl"
89
+	cmd.Long = cmd.Long + "\n\nThis command is provided for direct management of the Kubernetes cluster OpenShift runs on."
90
+	flags.VisitAll(func(flag *pflag.Flag) {
91
+		cmd.PersistentFlags().AddFlag(flag)
92
+	})
93
+	return cmd
94
+}
95
+
79 96
 // applyToCreate injects the deprecation notice about for 'apply' command into
80 97
 // 'create' command.
81 98
 // TODO: Remove this once we get rid of 'apply' in all documentation/etc.
82 99
deleted file mode 100644
... ...
@@ -1,23 +0,0 @@
1
-package api
2
-
3
-import (
4
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
5
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
6
-)
7
-
8
-// RESTClient provides REST interface compatible with Kubernetes RESTClient
9
-type RESTClient interface {
10
-	Verb(verb string) *client.Request
11
-	Put() *client.Request
12
-	Post() *client.Request
13
-	Delete() *client.Request
14
-	Get() *client.Request
15
-}
16
-
17
-// ClientMappings stores mapping between kind and client and codec
18
-// TODO: This struct is now obsoleted by kubectl
19
-type ClientMappings map[string]struct {
20
-	Kind   string
21
-	Client RESTClient
22
-	Codec  runtime.Codec
23
-}
24 1
deleted file mode 100644
... ...
@@ -1,49 +0,0 @@
1
-package build
2
-
3
-import (
4
-	"fmt"
5
-	"io"
6
-
7
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/kubecfg"
8
-	"github.com/openshift/origin/pkg/build/api"
9
-)
10
-
11
-var buildColumns = []string{"Name", "Type", "Status", "Pod Name"}
12
-var buildConfigColumns = []string{"Name", "Type", "SourceURI"}
13
-
14
-// RegisterPrintHandlers registers HumanReadablePrinter handlers
15
-// for build and buildConfig resources.
16
-func RegisterPrintHandlers(printer *kubecfg.HumanReadablePrinter) {
17
-	printer.Handler(buildColumns, printBuild)
18
-	printer.Handler(buildColumns, printBuildList)
19
-	printer.Handler(buildConfigColumns, printBuildConfig)
20
-	printer.Handler(buildConfigColumns, printBuildConfigList)
21
-}
22
-
23
-func printBuild(build *api.Build, w io.Writer) error {
24
-	_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", build.Name, build.Parameters.Strategy.Type, build.Status, build.PodName)
25
-	return err
26
-}
27
-
28
-func printBuildList(buildList *api.BuildList, w io.Writer) error {
29
-	for _, build := range buildList.Items {
30
-		if err := printBuild(&build, w); err != nil {
31
-			return err
32
-		}
33
-	}
34
-	return nil
35
-}
36
-
37
-func printBuildConfig(bc *api.BuildConfig, w io.Writer) error {
38
-	_, err := fmt.Fprintf(w, "%s\t%v\t%s\n", bc.Name, bc.Parameters.Strategy.Type, bc.Parameters.Source.Git.URI)
39
-	return err
40
-}
41
-
42
-func printBuildConfigList(buildList *api.BuildConfigList, w io.Writer) error {
43
-	for _, buildConfig := range buildList.Items {
44
-		if err := printBuildConfig(&buildConfig, w); err != nil {
45
-			return err
46
-		}
47
-	}
48
-	return nil
49
-}
50 1
deleted file mode 100644
... ...
@@ -1,62 +0,0 @@
1
-package client
2
-
3
-import (
4
-	"os"
5
-	"time"
6
-
7
-	"github.com/spf13/cobra"
8
-)
9
-
10
-// DEPRECATED, use NewCommandCLI instead
11
-func NewCommandKubecfg(name string) *cobra.Command {
12
-	cfg := &KubeConfig{}
13
-	cmd := &cobra.Command{
14
-		Use:   name,
15
-		Short: "DEPRECATED, use 'cli' instead",
16
-		Long: `
17
-DEPRECATED, use 'cli' instead
18
-
19
-Kubernetes Command Line - kubecfg
20
-
21
-OpenShift currently embeds the kubecfg command line for prototyping and debugging.
22
-` + usage(name),
23
-		Run: func(c *cobra.Command, args []string) {
24
-			if len(args) < 1 {
25
-				c.Help()
26
-				os.Exit(1)
27
-			}
28
-			cfg.Args = args
29
-			cfg.Run()
30
-		},
31
-	}
32
-	flag := cmd.Flags()
33
-	flag.BoolVar(&cfg.ServerVersion, "server_version", false, "Print the server's version number.")
34
-	flag.BoolVar(&cfg.PreventSkew, "expect_version_match", false, "Fail if server's version doesn't match own version.")
35
-	flag.StringVar(&cfg.ClientConfig.Host, "host", "", "The host to connect to.")
36
-	flag.StringVarP(&cfg.Config, "config", "c", "", "Path or URL to the config file, or '-' to read from STDIN")
37
-	flag.StringVarP(&cfg.Selector, "label", "l", "", "Selector (label query) to use for listing")
38
-	flag.DurationVarP(&cfg.UpdatePeriod, "update", "u", 60*time.Second, "Update interval period")
39
-	flag.StringVarP(&cfg.PortSpec, "port", "p", "", "The port spec, comma-separated list of <external>:<internal>,...")
40
-	flag.IntVarP(&cfg.ServicePort, "service", "s", -1, "If positive, create and run a corresponding service on this port, only used with 'run'")
41
-	flag.StringVar(&cfg.AuthConfig, "auth", os.Getenv("HOME")+"/.kubernetes_auth", "Path to the auth info file.  If missing, prompt the user.  Only used if doing https.")
42
-	flag.BoolVar(&cfg.JSON, "json", false, "If true, print raw JSON for responses")
43
-	flag.BoolVar(&cfg.YAML, "yaml", false, "If true, print raw YAML for responses")
44
-	flag.BoolVar(&cfg.Verbose, "verbose", false, "If true, print extra information")
45
-	flag.BoolVar(&cfg.Proxy, "proxy", false, "If true, run a proxy to the api server")
46
-	flag.StringVar(&cfg.WWW, "www", "", "If -proxy is true, use this directory to serve static files")
47
-	flag.StringVar(&cfg.TemplateFile, "template_file", "", "If present, load this file as a golang template and use it for output printing")
48
-	flag.StringVar(&cfg.TemplateStr, "template", "", "If present, parse this string as a golang template and use it for output printing")
49
-	flag.StringVar(&cfg.ClientConfig.CAFile, "certificate_authority", "", "Path to a cert. file for the certificate authority")
50
-	flag.StringVar(&cfg.ClientConfig.CertFile, "client_certificate", "", "Path to a client certificate for TLS.")
51
-	flag.StringVar(&cfg.ClientConfig.KeyFile, "client_key", "", "Path to a client key file for TLS.")
52
-	flag.BoolVar(&cfg.ClientConfig.Insecure, "insecure_skip_tls_verify", false, "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure.")
53
-	flag.StringVar(&cfg.ImageName, "image", "", "Image used when updating a replicationController.  Will apply to the first container in the pod template.")
54
-	flag.StringVar(&cfg.ID, "id", "", "Specifies ID of requested resource.")
55
-	flag.StringVar(&cfg.ns, "ns", "", "If present, the namespace scope for this request.")
56
-	flag.StringVar(&cfg.nsFile, "ns_file", os.Getenv("HOME")+"/.kubernetes_ns", "Path to the namespace file")
57
-	flag.StringVar(&cfg.BuildConfigID, "from-build-cfg", "", "Specifies BuildConfigID from which the build should be created.")
58
-	// this is handy for end to end testing
59
-	flag.StringVar(&cfg.ClientConfig.BearerToken, "token", "", "If present, the bearer token for this request.")
60
-
61
-	return cmd
62
-}
63 1
deleted file mode 100644
... ...
@@ -1,4 +0,0 @@
1
-// Package client contains the implementation of the client side communication with the
2
-// Kubernetes master. OpenShift currently embeds the Kubernetes's kubecfg command line
3
-// for prototyping and debugging.
4
-package client
5 1
deleted file mode 100644
... ...
@@ -1,57 +0,0 @@
1
-package image
2
-
3
-import (
4
-	"fmt"
5
-	"io"
6
-	"strings"
7
-
8
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/kubecfg"
9
-	"github.com/openshift/origin/pkg/image/api"
10
-)
11
-
12
-var imageColumns = []string{"Name", "Docker Ref"}
13
-var imageRepositoryColumns = []string{"Name", "Docker Repo", "Tags"}
14
-
15
-// RegisterPrintHandlers registers HumanReadablePrinter handlers for image and image repository resources.
16
-func RegisterPrintHandlers(printer *kubecfg.HumanReadablePrinter) {
17
-	printer.Handler(imageColumns, printImage)
18
-	printer.Handler(imageColumns, printImageList)
19
-	printer.Handler(imageRepositoryColumns, printImageRepository)
20
-	printer.Handler(imageRepositoryColumns, printImageRepositoryList)
21
-}
22
-
23
-func printImage(image *api.Image, w io.Writer) error {
24
-	_, err := fmt.Fprintf(w, "%s\t%s\n", image.Name, image.DockerImageReference)
25
-	return err
26
-}
27
-
28
-func printImageList(images *api.ImageList, w io.Writer) error {
29
-	for _, image := range images.Items {
30
-		if err := printImage(&image, w); err != nil {
31
-			return err
32
-		}
33
-	}
34
-	return nil
35
-}
36
-
37
-func printImageRepository(repo *api.ImageRepository, w io.Writer) error {
38
-	tags := ""
39
-	if len(repo.Tags) > 0 {
40
-		var t []string
41
-		for tag := range repo.Tags {
42
-			t = append(t, tag)
43
-		}
44
-		tags = strings.Join(t, ",")
45
-	}
46
-	_, err := fmt.Fprintf(w, "%s\t%s\t%s\n", repo.Name, repo.DockerImageRepository, tags)
47
-	return err
48
-}
49
-
50
-func printImageRepositoryList(repos *api.ImageRepositoryList, w io.Writer) error {
51
-	for _, repo := range repos.Items {
52
-		if err := printImageRepository(&repo, w); err != nil {
53
-			return err
54
-		}
55
-	}
56
-	return nil
57
-}
58 1
deleted file mode 100644
... ...
@@ -1,26 +0,0 @@
1
-package client
2
-
3
-import (
4
-	"encoding/json"
5
-	"fmt"
6
-	"io"
7
-)
8
-
9
-// JSONPrinter is an implementation of ResourcePrinter which parsess raw JSON, and
10
-// re-formats as indented, human-readable JSON.
11
-type JSONPrinter struct{}
12
-
13
-// Print parses the data as JSON, re-formats as JSON and prints the indented
14
-// JSON.
15
-func (y *JSONPrinter) Print(data []byte, w io.Writer) error {
16
-	var obj interface{}
17
-	if err := json.Unmarshal(data, &obj); err != nil {
18
-		return err
19
-	}
20
-	output, err := json.MarshalIndent(obj, "", "  ")
21
-	if err != nil {
22
-		return err
23
-	}
24
-	_, err = fmt.Fprint(w, string(output))
25
-	return err
26
-}
27 1
deleted file mode 100644
... ...
@@ -1,629 +0,0 @@
1
-package client
2
-
3
-import (
4
-	"fmt"
5
-	"io"
6
-	"io/ioutil"
7
-	"net/http"
8
-	"net/url"
9
-	"os"
10
-	"reflect"
11
-	"sort"
12
-	"strconv"
13
-	"strings"
14
-	"time"
15
-
16
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
17
-	klatest "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
18
-	kmeta "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
19
-	kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
20
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/kubecfg"
21
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
22
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
23
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
24
-	"github.com/golang/glog"
25
-	"github.com/openshift/origin/pkg/api/latest"
26
-	buildapi "github.com/openshift/origin/pkg/build/api"
27
-	buildutil "github.com/openshift/origin/pkg/build/util"
28
-	osclient "github.com/openshift/origin/pkg/client"
29
-	. "github.com/openshift/origin/pkg/cmd/client/api"
30
-	"github.com/openshift/origin/pkg/cmd/client/build"
31
-	"github.com/openshift/origin/pkg/cmd/client/image"
32
-	"github.com/openshift/origin/pkg/cmd/client/project"
33
-	"github.com/openshift/origin/pkg/cmd/client/route"
34
-	configapi "github.com/openshift/origin/pkg/config/api"
35
-	deployapi "github.com/openshift/origin/pkg/deploy/api"
36
-	deployclient "github.com/openshift/origin/pkg/deploy/client"
37
-	imageapi "github.com/openshift/origin/pkg/image/api"
38
-	projectapi "github.com/openshift/origin/pkg/project/api"
39
-	routeapi "github.com/openshift/origin/pkg/route/api"
40
-)
41
-
42
-type KubeConfig struct {
43
-	ClientConfig   kclient.Config
44
-	ServerVersion  bool
45
-	PreventSkew    bool
46
-	Config         string
47
-	TemplateConfig string
48
-	Selector       string
49
-	UpdatePeriod   time.Duration
50
-	PortSpec       string
51
-	ServicePort    int
52
-	AuthConfig     string
53
-	JSON           bool
54
-	YAML           bool
55
-	Verbose        bool
56
-	Proxy          bool
57
-	WWW            string
58
-	TemplateFile   string
59
-	TemplateStr    string
60
-	ID             string
61
-	Namespace      string
62
-	BuildConfigID  string
63
-
64
-	ImageName string
65
-
66
-	APIVersion   string
67
-	OSAPIVersion string
68
-
69
-	Args   []string
70
-	ns     string
71
-	nsFile string
72
-}
73
-
74
-func (c *KubeConfig) Arg(index int) string {
75
-	if index >= len(c.Args) {
76
-		return ""
77
-	}
78
-	return c.Args[index]
79
-}
80
-
81
-func usage(name string) string {
82
-	return fmt.Sprintf(`
83
-  Kubernetes REST API:
84
-  %[1]s [OPTIONS] get|list|create|delete|update <%[2]s>[/<id>]
85
-
86
-  Manage replication controllers:
87
-
88
-  %[1]s [OPTIONS] stop|rm <controller>
89
-  %[1]s [OPTIONS] [-u <time>] [-image <image>] rollingupdate <controller>
90
-  %[1]s [OPTIONS] resize <controller> <replicas>
91
-
92
-  Launch a simple ReplicationController with a single container based
93
-  on the given image:
94
-
95
-  %[1]s [OPTIONS] [-p <port spec>] run <image> <replicas> <controller>
96
-
97
-  Manage namespace:
98
-  %[1]s [OPTIONS] ns [<namespace>]
99
-
100
-  Perform bulk operations on groups of Kubernetes resources:
101
-  %[1]s [OPTIONS] apply -c config.json
102
-
103
-  Process template into config:
104
-  %[1]s [OPTIONS] process -c template.json
105
-
106
-  Retrieve build logs:
107
-  %[1]s [OPTIONS] buildLogs --id="buildID"
108
-`, name, prettyWireStorage())
109
-}
110
-
111
-var parser = kubecfg.NewParser(map[string]runtime.Object{
112
-	"pods":                    &api.Pod{},
113
-	"services":                &api.Service{},
114
-	"replicationControllers":  &api.ReplicationController{},
115
-	"minions":                 &api.Node{},
116
-	"nodes":                   &api.Node{},
117
-	"builds":                  &buildapi.Build{},
118
-	"buildConfigs":            &buildapi.BuildConfig{},
119
-	"images":                  &imageapi.Image{},
120
-	"imageRepositories":       &imageapi.ImageRepository{},
121
-	"imageRepositoryMappings": &imageapi.ImageRepositoryMapping{},
122
-	"config":                  &configapi.Config{},
123
-	"deployments":             &deployapi.Deployment{},
124
-	"deploymentConfigs":       &deployapi.DeploymentConfig{},
125
-	"routes":                  &routeapi.Route{},
126
-	"projects":                &projectapi.Project{},
127
-})
128
-
129
-func prettyWireStorage() string {
130
-	types := parser.SupportedWireStorage()
131
-	sort.Strings(types)
132
-	return strings.Join(types, "|")
133
-}
134
-
135
-// readConfigData reads the bytes from the specified filesytem or network location associated with the *config flag
136
-func (c *KubeConfig) readConfigData() []byte {
137
-	// read from STDIN
138
-	if c.Config == "-" {
139
-		data, err := ioutil.ReadAll(os.Stdin)
140
-		if err != nil {
141
-			glog.Fatalf("Unable to read from STDIN: %v\n", err)
142
-		}
143
-		return data
144
-	}
145
-
146
-	// we look for http:// or https:// to determine if valid URL, otherwise do normal file IO
147
-	if url, err := url.Parse(c.Config); err == nil && (url.Scheme == "http" || url.Scheme == "https") {
148
-		resp, err := http.Get(url.String())
149
-		if err != nil {
150
-			glog.Fatalf("Unable to access URL %v: %v\n", c.Config, err)
151
-		}
152
-		defer resp.Body.Close()
153
-		if resp.StatusCode != 200 {
154
-			glog.Fatalf("Unable to read URL, server reported %d %s", resp.StatusCode, resp.Status)
155
-		}
156
-		data, err := ioutil.ReadAll(resp.Body)
157
-		if err != nil {
158
-			glog.Fatalf("Unable to read URL %v: %v\n", c.Config, err)
159
-		}
160
-		return data
161
-	}
162
-
163
-	data, err := ioutil.ReadFile(c.Config)
164
-	if err != nil {
165
-		glog.Fatalf("Unable to read %v: %v\n", c.Config, err)
166
-	}
167
-	return data
168
-}
169
-
170
-// readConfig reads and parses pod, replicationController, and service
171
-// configuration files. If any errors log and exit non-zero.
172
-func (c *KubeConfig) readConfig(storage string, serverCodec runtime.Codec) []byte {
173
-	if len(c.Config) == 0 {
174
-		glog.Fatal("Need config file (-c)")
175
-	}
176
-
177
-	data, err := parser.ToWireFormat(c.readConfigData(), storage, latest.Codec, serverCodec)
178
-	if err != nil {
179
-		glog.Fatalf("Error parsing %v as an object for %v: %v", c.Config, storage, err)
180
-	}
181
-	if c.Verbose {
182
-		glog.Infof("Parsed config file successfully; sending:\n%v", string(data))
183
-	}
184
-	return data
185
-}
186
-
187
-// getNamespace returns the effective namespace for this invocation based on the first of:
188
-// 1.  The --ns argument
189
-// 2.  The contents of the nsFile
190
-// 3.  Uses the default namespace
191
-func (c *KubeConfig) getNamespace() string {
192
-	// Load namespace information for requests
193
-	nsInfo, err := kubecfg.LoadNamespaceInfo(c.nsFile)
194
-	if err != nil {
195
-		glog.Fatalf("Error loading current namespace: %v", err)
196
-	}
197
-	ret := nsInfo.Namespace
198
-
199
-	// Check if the namespace was overriden by the -ns argument
200
-	if len(c.ns) > 0 {
201
-		ret = c.ns
202
-	}
203
-
204
-	return ret
205
-}
206
-
207
-func (c *KubeConfig) Run() {
208
-	util.InitLogs()
209
-	defer util.FlushLogs()
210
-
211
-	clientConfig := c.ClientConfig
212
-	// Initialize the client
213
-	if clientConfig.Host == "" {
214
-		clientConfig.Host = os.Getenv("KUBERNETES_MASTER")
215
-	}
216
-	if clientConfig.Host == "" {
217
-		// TODO: eventually apiserver should start on 443 and be secure by default
218
-		clientConfig.Host = "http://localhost:8080"
219
-	}
220
-	hosts := strings.SplitN(clientConfig.Host, ",", 2)
221
-	for i := range hosts {
222
-		hosts[i] = strings.TrimRight(hosts[i], "/")
223
-	}
224
-	clientConfig.Host = hosts[0]
225
-
226
-	if kclient.IsConfigTransportTLS(clientConfig) {
227
-		auth, err := kubecfg.LoadClientAuthInfoOrPrompt(c.AuthConfig, os.Stdin)
228
-		if err != nil {
229
-			glog.Fatalf("Error loading auth: %v", err)
230
-		}
231
-		clientConfig.Username = auth.User
232
-		clientConfig.Password = auth.Password
233
-		if auth.CAFile != "" {
234
-			clientConfig.CAFile = auth.CAFile
235
-		}
236
-		if auth.CertFile != "" {
237
-			clientConfig.CertFile = auth.CertFile
238
-		}
239
-		if auth.KeyFile != "" {
240
-			clientConfig.KeyFile = auth.KeyFile
241
-		}
242
-		if len(clientConfig.BearerToken) == 0 && auth.BearerToken != "" {
243
-			clientConfig.BearerToken = auth.BearerToken
244
-		}
245
-		if auth.Insecure != nil {
246
-			clientConfig.Insecure = *auth.Insecure
247
-		}
248
-	}
249
-	clientConfig.Version = c.APIVersion
250
-	kubeClient, err := kclient.New(&clientConfig)
251
-	if err != nil {
252
-		glog.Fatalf("Unable to set up the Kubernetes API client: %v", err)
253
-	}
254
-
255
-	if len(hosts) > 1 {
256
-		clientConfig.Host = hosts[1]
257
-	}
258
-	clientConfig.Version = c.OSAPIVersion
259
-	client, err := osclient.New(&clientConfig)
260
-	if err != nil {
261
-		glog.Fatalf("Unable to set up the OpenShift API client: %v", err)
262
-	}
263
-
264
-	// check the kubernetes server version
265
-	if c.ServerVersion {
266
-		got, err := kubeClient.ServerVersion()
267
-		if err != nil {
268
-			fmt.Printf("Couldn't read version from server: %v", err)
269
-			os.Exit(1)
270
-		}
271
-		fmt.Printf("Server Version: %#v", got)
272
-		os.Exit(0)
273
-	}
274
-
275
-	if c.PreventSkew {
276
-		got, err := kubeClient.ServerVersion()
277
-		if err != nil {
278
-			fmt.Printf("Couldn't read version from server: %v", err)
279
-			os.Exit(1)
280
-		}
281
-		if c, s := version.Get(), *got; !reflect.DeepEqual(c, s) {
282
-			fmt.Printf("Server version (%#v) differs from client version (%#v)!", s, c)
283
-			os.Exit(1)
284
-		}
285
-	}
286
-
287
-	if c.Proxy {
288
-		glog.Info("Starting to serve on localhost:8001")
289
-		server, err := kubecfg.NewProxyServer(c.WWW, &clientConfig)
290
-		if err != nil {
291
-			glog.Fatalf("Unable to initialize proxy server %v", err)
292
-		}
293
-		glog.Fatal(server.Serve())
294
-	}
295
-
296
-	method := c.Arg(0)
297
-	clients := ClientMappings{
298
-		"minions":                 {"Minion", kubeClient.RESTClient, klatest.Codec},
299
-		"pods":                    {"Pod", kubeClient.RESTClient, klatest.Codec},
300
-		"services":                {"Service", kubeClient.RESTClient, klatest.Codec},
301
-		"replicationControllers":  {"ReplicationController", kubeClient.RESTClient, klatest.Codec},
302
-		"builds":                  {"Build", client.RESTClient, latest.Codec},
303
-		"buildConfigs":            {"BuildConfig", client.RESTClient, latest.Codec},
304
-		"images":                  {"Image", client.RESTClient, latest.Codec},
305
-		"imageRepositories":       {"ImageRepository", client.RESTClient, latest.Codec},
306
-		"imageRepositoryMappings": {"ImageRepositoryMapping", client.RESTClient, latest.Codec},
307
-		"deployments":             {"Deployment", client.RESTClient, latest.Codec},
308
-		"deploymentConfigs":       {"DeploymentConfig", client.RESTClient, latest.Codec},
309
-		"routes":                  {"Route", client.RESTClient, latest.Codec},
310
-		"projects":                {"Project", client.RESTClient, latest.Codec},
311
-	}
312
-
313
-	matchFound := c.executeConfigRequest(method, clients) || c.executeBuildRequest(method, client) || c.executeTemplateRequest(method, client) || c.executeBuildLogRequest(method, client) || c.executeControllerRequest(method, kubeClient) || c.executeNamespaceRequest(method) || c.executeAPIRequest(method, clients)
314
-	if matchFound == false {
315
-		glog.Fatalf("Unknown command %s", method)
316
-	}
317
-}
318
-
319
-// storagePathFromArg normalizes a path and breaks out the first segment if available
320
-func storagePathFromArg(arg string) (storage, path string, hasSuffix bool) {
321
-	path = strings.Trim(arg, "/")
322
-	segments := strings.SplitN(path, "/", 2)
323
-	storage = segments[0]
324
-	if len(segments) > 1 && segments[1] != "" {
325
-		hasSuffix = true
326
-	}
327
-	return storage, path, hasSuffix
328
-}
329
-
330
-//checkStorage returns true if the provided storage is valid
331
-func checkStorage(storage string) bool {
332
-	for _, allowed := range parser.SupportedWireStorage() {
333
-		if allowed == storage {
334
-			return true
335
-		}
336
-	}
337
-	return false
338
-}
339
-
340
-func (c *KubeConfig) executeAPIRequest(method string, clients ClientMappings) bool {
341
-	storage, path, hasSuffix := storagePathFromArg(c.Arg(1))
342
-	validStorage := checkStorage(storage)
343
-	client, ok := clients[storage]
344
-	if !ok {
345
-		glog.Fatalf("Unsupported storage type %s", storage)
346
-	}
347
-
348
-	verb := ""
349
-	setBody := false
350
-	var version string
351
-	switch method {
352
-	case "get":
353
-		verb = "GET"
354
-		if !validStorage || !hasSuffix {
355
-			glog.Fatalf("usage: kubecfg [OPTIONS] %s <%s>[/<id>]", method, prettyWireStorage())
356
-		}
357
-	case "list":
358
-		verb = "GET"
359
-		if !validStorage || hasSuffix {
360
-			glog.Fatalf("usage: kubecfg [OPTIONS] %s <%s>", method, prettyWireStorage())
361
-		}
362
-	case "delete":
363
-		verb = "DELETE"
364
-		if !validStorage || !hasSuffix {
365
-			glog.Fatalf("usage: kubecfg [OPTIONS] %s <%s>/<id>", method, prettyWireStorage())
366
-		}
367
-	case "create":
368
-		verb = "POST"
369
-		setBody = true
370
-		if !validStorage || hasSuffix {
371
-			glog.Fatalf("usage: kubecfg [OPTIONS] %s <%s>", method, prettyWireStorage())
372
-		}
373
-	case "update":
374
-		obj, err := client.Client.Verb("GET").Resource(path).Do().Get()
375
-		if err != nil {
376
-			glog.Fatalf("error obtaining resource version for update: %v", err)
377
-		}
378
-		typeMeta, err := kmeta.Accessor(obj)
379
-		if err != nil {
380
-			glog.Fatalf("error finding json base for update: %v", err)
381
-		}
382
-		version = typeMeta.ResourceVersion()
383
-		verb = "PUT"
384
-		setBody = true
385
-		if !validStorage || !hasSuffix {
386
-			glog.Fatalf("usage: kubecfg [OPTIONS] %s <%s>/<id>", method, prettyWireStorage())
387
-		}
388
-	default:
389
-		return false
390
-	}
391
-
392
-	r := client.Client.Verb(verb).
393
-		Namespace(c.getNamespace()).
394
-		Resource(path).
395
-		ParseSelectorParam("labels", c.Selector)
396
-	if setBody {
397
-		if len(version) != 0 {
398
-			data := c.readConfig(storage, client.Codec)
399
-			obj, err := latest.Codec.Decode(data)
400
-			if err != nil {
401
-				glog.Fatalf("error setting resource version: %v", err)
402
-			}
403
-			typeMeta, err := kmeta.Accessor(obj)
404
-			if err != nil {
405
-				glog.Fatalf("error setting resource version: %v", err)
406
-			}
407
-			typeMeta.SetResourceVersion(version)
408
-			data, err = client.Codec.Encode(obj)
409
-			if err != nil {
410
-				glog.Fatalf("error setting resource version: %v", err)
411
-			}
412
-			r.Body(data)
413
-		} else {
414
-			r.Body(c.readConfig(storage, client.Codec))
415
-		}
416
-	}
417
-	result := r.Do()
418
-	obj, err := result.Get()
419
-	if err != nil {
420
-		glog.Fatalf("Got request error: %v", err)
421
-		return false
422
-	}
423
-
424
-	var printer kubecfg.ResourcePrinter
425
-	switch {
426
-	case c.JSON:
427
-		printer = &kubecfg.IdentityPrinter{}
428
-	case c.YAML:
429
-		printer = &kubecfg.YAMLPrinter{}
430
-	case len(c.TemplateFile) > 0 || len(c.TemplateStr) > 0:
431
-		var data []byte
432
-		if len(c.TemplateFile) > 0 {
433
-			var err error
434
-			data, err = ioutil.ReadFile(c.TemplateFile)
435
-			if err != nil {
436
-				glog.Fatalf("Error reading template %s, %v", c.TemplateFile, err)
437
-				return false
438
-			}
439
-		} else {
440
-			data = []byte(c.TemplateStr)
441
-		}
442
-		if printer, err = kubecfg.NewTemplatePrinter(data); err != nil {
443
-			glog.Fatalf("Failed to create printer %v", err)
444
-		}
445
-	default:
446
-		printer = humanReadablePrinter()
447
-	}
448
-
449
-	if err = printer.PrintObj(obj, os.Stdout); err != nil {
450
-		body, _ := result.Raw()
451
-		glog.Fatalf("Failed to print: %v\nRaw received object:\n%#v\n\nBody received: %v", err, obj, string(body))
452
-	}
453
-	fmt.Print("\n")
454
-
455
-	return true
456
-}
457
-
458
-func (c *KubeConfig) executeControllerRequest(method string, client *kclient.Client) bool {
459
-	parseController := func() string {
460
-		if len(c.Args) != 2 {
461
-			glog.Fatal("usage: kubecfg [OPTIONS] stop|rm|rollingupdate|run|resize <controller>")
462
-		}
463
-		return c.Arg(1)
464
-	}
465
-
466
-	ctx := api.WithNamespace(api.NewContext(), c.getNamespace())
467
-	var err error
468
-	switch method {
469
-	case "stop":
470
-		err = kubecfg.StopController(ctx, parseController(), client)
471
-	case "rm":
472
-		err = kubecfg.DeleteController(ctx, parseController(), client)
473
-	case "rollingupdate":
474
-		err = kubecfg.Update(ctx, parseController(), client, c.UpdatePeriod, c.ImageName)
475
-	case "run":
476
-		if len(c.Args) != 4 {
477
-			glog.Fatal("usage: kubecfg [OPTIONS] run <image> <replicas> <controller>")
478
-		}
479
-		image := c.Arg(1)
480
-		replicas, err := strconv.Atoi(c.Arg(2))
481
-		name := c.Arg(3)
482
-		if err != nil {
483
-			glog.Fatalf("Error parsing replicas: %v", err)
484
-		}
485
-		err = kubecfg.RunController(ctx, image, name, replicas, client, c.PortSpec, c.ServicePort)
486
-	case "resize":
487
-		args := c.Args
488
-		if len(args) < 3 {
489
-			glog.Fatal("usage: kubecfg resize <controller> <replicas>")
490
-		}
491
-		name := args[1]
492
-		replicas, err := strconv.Atoi(args[2])
493
-		if err != nil {
494
-			glog.Fatalf("Error parsing replicas: %v", err)
495
-		}
496
-		err = kubecfg.ResizeController(ctx, name, replicas, client)
497
-	default:
498
-		return false
499
-	}
500
-	if err != nil {
501
-		glog.Fatalf("Error: %v", err)
502
-	}
503
-	return true
504
-}
505
-
506
-// executeBuildRequest will re-ran specified build or create a new one from specified buildConfig.
507
-// To re-run the build specify the buildID, from which the build parameters will be extracted.
508
-// E.g: openshift kube create builds --id="buildID"
509
-// To create a build from a buildConfig specify it's ID.
510
-// E.g: openshift kube create builds --from-build-cfg="buildConfigID"
511
-func (c *KubeConfig) executeBuildRequest(method string, client *osclient.Client) bool {
512
-	if method != "create" || c.Arg(1) != "builds" || (len(c.ID) == 0 && len(c.BuildConfigID) == 0) {
513
-		return false
514
-	}
515
-	build := &buildapi.Build{}
516
-	if len(c.ID) != 0 {
517
-		oldBuild := &buildapi.Build{}
518
-		request := client.Get().Namespace(c.getNamespace()).Resource("/builds").Name(c.ID)
519
-		err := request.Do().Into(oldBuild)
520
-		if err != nil {
521
-			glog.Fatalf("failed to trigger build manually: %v", err)
522
-		}
523
-		build = buildutil.GenerateBuildFromBuild(oldBuild)
524
-	} else {
525
-		buildConfig := &buildapi.BuildConfig{}
526
-		request := client.Get().Namespace(c.getNamespace()).Resource("/buildConfigs").Name(c.BuildConfigID)
527
-		err := request.Do().Into(buildConfig)
528
-		if err != nil {
529
-			glog.Fatalf("failed to trigger build manually: %v", err)
530
-		}
531
-		build = buildutil.GenerateBuildFromConfig(buildConfig, buildConfig.Parameters.Revision, nil)
532
-	}
533
-	request := client.Post().Namespace(c.getNamespace()).Resource("/builds").Body(build)
534
-	if err := request.Do().Error(); err != nil {
535
-		glog.Fatalf("failed to trigger build manually: %v", err)
536
-	}
537
-	return true
538
-}
539
-
540
-// executeBuildLogRequest retrieves the logs from builder container
541
-func (c *KubeConfig) executeBuildLogRequest(method string, client *osclient.Client) bool {
542
-	if method != "buildLogs" {
543
-		return false
544
-	}
545
-	if len(c.ID) == 0 {
546
-		glog.Fatal("Build ID required")
547
-	}
548
-	request := client.Verb("GET").Namespace(c.getNamespace()).Prefix("redirect").Resource("buildLogs").Name(c.ID)
549
-	readCloser, err := request.Stream()
550
-	if err != nil {
551
-		glog.Fatalf("Error: %v", err)
552
-	}
553
-	defer readCloser.Close()
554
-	if _, err := io.Copy(os.Stdout, readCloser); err != nil {
555
-		glog.Fatalf("Error: %v", err)
556
-	}
557
-	return true
558
-}
559
-
560
-// executeTemplateRequest transform the JSON file with Config template into a
561
-// valid Config JSON.
562
-//
563
-// TODO: Print the output for each resource on success, as "create" method
564
-//       does in the executeAPIRequest().
565
-func (c *KubeConfig) executeTemplateRequest(method string, client *osclient.Client) bool {
566
-	if method != "process" {
567
-		return false
568
-	}
569
-	if len(c.Config) == 0 {
570
-		glog.Fatal("Need template file (-c)")
571
-	}
572
-	data, err := ioutil.ReadFile(c.Config)
573
-	if err != nil {
574
-		glog.Fatalf("error reading template file: %v", err)
575
-	}
576
-	request := client.Verb("POST").Namespace(c.getNamespace()).Resource("/templateConfigs").Body(data)
577
-	result := request.Do()
578
-	body, err := result.Raw()
579
-	if err != nil {
580
-		glog.Fatalf("failed to process template: %v", err)
581
-	}
582
-	printer := JSONPrinter{}
583
-	if err := printer.Print(body, os.Stdout); err != nil {
584
-		glog.Fatalf("unable to pretty print config JSON: %v [%s]", err, string(body))
585
-	}
586
-	return true
587
-}
588
-
589
-func (c *KubeConfig) executeConfigRequest(method string, clients ClientMappings) bool {
590
-	glog.Fatalf("DEPRECATED: The 'apply' is deprecated, use 'osc create' command instead.")
591
-	return false
592
-}
593
-
594
-func humanReadablePrinter() *kubecfg.HumanReadablePrinter {
595
-	printer := kubecfg.NewHumanReadablePrinter()
596
-
597
-	// Add Handler calls here to support additional types
598
-	build.RegisterPrintHandlers(printer)
599
-	image.RegisterPrintHandlers(printer)
600
-	deployclient.RegisterPrintHandlers(printer)
601
-	route.RegisterPrintHandlers(printer)
602
-	project.RegisterPrintHandlers(printer)
603
-
604
-	return printer
605
-}
606
-
607
-func (c *KubeConfig) executeNamespaceRequest(method string) bool {
608
-	var err error
609
-	var ns *kubecfg.NamespaceInfo
610
-	switch method {
611
-	case "ns":
612
-		switch len(c.Args) {
613
-		case 1:
614
-			ns, err = kubecfg.LoadNamespaceInfo(c.nsFile)
615
-		case 2:
616
-			ns = &kubecfg.NamespaceInfo{Namespace: c.Args[1]}
617
-			err = kubecfg.SaveNamespaceInfo(c.nsFile, ns)
618
-		default:
619
-			glog.Fatalf("usage: kubecfg ns [<namespace>]")
620
-		}
621
-	default:
622
-		return false
623
-	}
624
-	if err != nil {
625
-		glog.Fatalf("Error: %v", err)
626
-	}
627
-	fmt.Printf("Using namespace %s\n", ns.Namespace)
628
-	return true
629
-}
630 1
deleted file mode 100644
... ...
@@ -1,31 +0,0 @@
1
-package project
2
-
3
-import (
4
-	"fmt"
5
-	"io"
6
-
7
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/kubecfg"
8
-	"github.com/openshift/origin/pkg/project/api"
9
-)
10
-
11
-var projectColumns = []string{"Name", "Namespace", "Display Name"}
12
-
13
-// RegisterPrintHandlers registers HumanReadablePrinter handlers for project resources.
14
-func RegisterPrintHandlers(printer *kubecfg.HumanReadablePrinter) {
15
-	printer.Handler(projectColumns, printProject)
16
-	printer.Handler(projectColumns, printProjectList)
17
-}
18
-
19
-func printProject(project *api.Project, w io.Writer) error {
20
-	_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", project.Name, project.Namespace, project.DisplayName)
21
-	return err
22
-}
23
-
24
-func printProjectList(projects *api.ProjectList, w io.Writer) error {
25
-	for _, project := range projects.Items {
26
-		if err := printProject(&project, w); err != nil {
27
-			return err
28
-		}
29
-	}
30
-	return nil
31
-}
32 1
deleted file mode 100644
... ...
@@ -1,33 +0,0 @@
1
-package route
2
-
3
-import (
4
-	"fmt"
5
-	"io"
6
-
7
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/kubecfg"
8
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
9
-
10
-	"github.com/openshift/origin/pkg/route/api"
11
-)
12
-
13
-var routeColumns = []string{"Name", "Host/Port", "Path", "Service", "Labels"}
14
-
15
-// RegisterPrintHandlers registers HumanReadablePrinter handlers
16
-func RegisterPrintHandlers(printer *kubecfg.HumanReadablePrinter) {
17
-	printer.Handler(routeColumns, printRoute)
18
-	printer.Handler(routeColumns, printRouteList)
19
-}
20
-
21
-func printRoute(route *api.Route, w io.Writer) error {
22
-	_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", route.Name, route.Host, route.Path, route.ServiceName, labels.Set(route.Labels))
23
-	return err
24
-}
25
-
26
-func printRouteList(routeList *api.RouteList, w io.Writer) error {
27
-	for _, route := range routeList.Items {
28
-		if err := printRoute(&route, w); err != nil {
29
-			return err
30
-		}
31
-	}
32
-	return nil
33
-}
34 1
new file mode 100644
... ...
@@ -0,0 +1,22 @@
0
+package config
1
+
2
+import (
3
+	"fmt"
4
+	"os"
5
+
6
+	"github.com/spf13/cobra"
7
+
8
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/config"
9
+)
10
+
11
+func NewCmdConfig(parentName, name string) *cobra.Command {
12
+	cmd := config.NewCmdConfig(os.Stdout)
13
+	cmd.Long = fmt.Sprintf(`Manages .kubeconfig files using subcommands like:
14
+
15
+%[1]s %[2]s use-context my-context
16
+%[1]s %[2]s set preferences.some true
17
+
18
+Reference: https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/kubeconfig-file.md`, parentName, name)
19
+
20
+	return cmd
21
+}
... ...
@@ -1,6 +1,8 @@
1 1
 package tokens
2 2
 
3 3
 import (
4
+	"os"
5
+
4 6
 	"github.com/golang/glog"
5 7
 	"github.com/spf13/cobra"
6 8
 
... ...
@@ -13,13 +15,16 @@ const (
13 13
 	TOKEN_FILE_PARAM = "token-file"
14 14
 )
15 15
 
16
-func NewCommandTokens(name string) *cobra.Command {
16
+func NewCmdTokens(name string) *cobra.Command {
17 17
 	// Parent command to which all subcommands are added.
18 18
 	cmds := &cobra.Command{
19 19
 		Use:   name,
20 20
 		Short: "manage authentication tokens",
21 21
 		Long:  `manage authentication tokens`,
22
-		Run:   runHelp,
22
+		Run: func(c *cobra.Command, args []string) {
23
+			c.SetOutput(os.Stdout)
24
+			c.Help()
25
+		},
23 26
 	}
24 27
 
25 28
 	// copied out of kubernetes kubectl so that I'll be ready when that and osc finally merge in
... ...
@@ -37,10 +42,6 @@ func NewCommandTokens(name string) *cobra.Command {
37 37
 	return cmds
38 38
 }
39 39
 
40
-func runHelp(cmd *cobra.Command, args []string) {
41
-	cmd.Help()
42
-}
43
-
44 40
 func getFlagString(cmd *cobra.Command, flag string) string {
45 41
 	f := cmd.Flags().Lookup(flag)
46 42
 	if f == nil {
47 43
deleted file mode 100644
... ...
@@ -1,35 +0,0 @@
1
-package experimental
2
-
3
-import (
4
-	"fmt"
5
-	"os"
6
-
7
-	"github.com/spf13/cobra"
8
-
9
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/config"
10
-	"github.com/openshift/origin/pkg/cmd/experimental/tokens"
11
-)
12
-
13
-func NewCommandExperimental(name string) *cobra.Command {
14
-	cmd := &cobra.Command{
15
-		Use: name,
16
-		Run: func(c *cobra.Command, args []string) {
17
-			c.Help()
18
-		},
19
-	}
20
-
21
-	out := os.Stdout
22
-
23
-	cmd.AddCommand(tokens.NewCommandTokens("tokens"))
24
-
25
-	cmdconfig := config.NewCmdConfig(out)
26
-	cmdconfig.Long = fmt.Sprintf(`Manages .kubeconfig files using subcommands like:
27
-
28
-%[1]s config use-context my-context
29
-%[1]s config set preferences.some true
30
-
31
-Reference: https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/kubeconfig-file.md`, name)
32
-	cmd.AddCommand(cmdconfig)
33
-
34
-	return cmd
35
-}
... ...
@@ -2,16 +2,17 @@ package openshift
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"os"
5 6
 
6 7
 	kubeversion "github.com/GoogleCloudPlatform/kubernetes/pkg/version"
7 8
 	"github.com/spf13/cobra"
8 9
 
9 10
 	"github.com/openshift/origin/pkg/cmd/cli"
10
-	"github.com/openshift/origin/pkg/cmd/client"
11
+	"github.com/openshift/origin/pkg/cmd/experimental/config"
12
+	"github.com/openshift/origin/pkg/cmd/experimental/tokens"
11 13
 	"github.com/openshift/origin/pkg/cmd/flagtypes"
12 14
 	"github.com/openshift/origin/pkg/cmd/infra/builder"
13 15
 	"github.com/openshift/origin/pkg/cmd/infra/deployer"
14
-	"github.com/openshift/origin/pkg/cmd/infra/experimental"
15 16
 	"github.com/openshift/origin/pkg/cmd/infra/router"
16 17
 	"github.com/openshift/origin/pkg/cmd/server"
17 18
 	"github.com/openshift/origin/pkg/version"
... ...
@@ -49,9 +50,7 @@ func CommandFor(basename string) *cobra.Command {
49 49
 	case "openshift-docker-build":
50 50
 		cmd = builder.NewCommandDockerBuilder(basename)
51 51
 	case "osc":
52
-		cmd = cli.NewCommandCLI(basename)
53
-	case "openshift-experimental":
54
-		cmd = experimental.NewCommandExperimental(basename)
52
+		cmd = cli.NewCommandCLI(basename, basename)
55 53
 	default:
56 54
 		cmd = NewCommandOpenShift()
57 55
 	}
... ...
@@ -66,13 +65,15 @@ func NewCommandOpenShift() *cobra.Command {
66 66
 		Short: "OpenShift helps you build, deploy, and manage your cloud applications",
67 67
 		Long:  longDescription,
68 68
 		Run: func(c *cobra.Command, args []string) {
69
+			c.SetOutput(os.Stdout)
69 70
 			c.Help()
70 71
 		},
71 72
 	}
72 73
 
73 74
 	root.AddCommand(server.NewCommandStartServer("start"))
74
-	root.AddCommand(client.NewCommandKubecfg("kube")) // DEPRECATED, use cli instead
75
-	root.AddCommand(cli.NewCommandCLI("cli"))
75
+	root.AddCommand(cli.NewCommandCLI("cli", "openshift cli"))
76
+	root.AddCommand(cli.NewCmdKubectl("kube"))
77
+	root.AddCommand(newExperimentalCommand("openshift", "ex"))
76 78
 	root.AddCommand(newVersionCommand("version"))
77 79
 
78 80
 	// infra commands are those that are bundled with the binary but not displayed to end users
... ...
@@ -85,17 +86,31 @@ func NewCommandOpenShift() *cobra.Command {
85 85
 		deployer.NewCommandDeployer("deploy"),
86 86
 		builder.NewCommandSTIBuilder("sti-build"),
87 87
 		builder.NewCommandDockerBuilder("docker-build"),
88
-		experimental.NewCommandExperimental("experimental"),
89 88
 	)
90 89
 	root.AddCommand(infra)
91 90
 
92 91
 	return root
93 92
 }
94 93
 
94
+func newExperimentalCommand(parentName, name string) *cobra.Command {
95
+	experimental := &cobra.Command{
96
+		Use:   name,
97
+		Short: "Experimental commands under active development",
98
+		Long:  "The commands grouped here are under development and may change without notice.",
99
+		Run: func(c *cobra.Command, args []string) {
100
+			c.SetOutput(os.Stdout)
101
+			c.Help()
102
+		},
103
+	}
104
+	experimental.AddCommand(config.NewCmdConfig(fmt.Sprintf("%s %s", parentName, name), "config"))
105
+	experimental.AddCommand(tokens.NewCmdTokens("tokens"))
106
+	return experimental
107
+}
108
+
95 109
 // newVersionCommand creates a command for displaying the version of this binary
96
-func newVersionCommand(use string) *cobra.Command {
110
+func newVersionCommand(name string) *cobra.Command {
97 111
 	return &cobra.Command{
98
-		Use:   use,
112
+		Use:   name,
99 113
 		Short: "Display version",
100 114
 		Run: func(c *cobra.Command, args []string) {
101 115
 			fmt.Printf("openshift %v\n", version.Get())
... ...
@@ -92,6 +92,9 @@ var (
92 92
 // Do watches for unauthorized challenges.  If we know to respond, we respond to the challenge
93 93
 func (client *challengingClient) Do(req *http.Request) (*http.Response, error) {
94 94
 	resp, err := client.delegate.Do(req)
95
+	if err != nil {
96
+		return nil, err
97
+	}
95 98
 	if resp.StatusCode == http.StatusUnauthorized {
96 99
 		if wantsBasicAuth, realm := isBasicAuthChallenge(resp); wantsBasicAuth {
97 100
 			fmt.Printf("Authenticate for \"%v\"\n", realm)