Browse code

Introducing client login and setup - 'osc login'

fabianofranz authored on 2015/02/12 04:50:44
Showing 22 changed files
... ...
@@ -45,7 +45,8 @@ TEMP_DIR=${USE_TEMP:-$(mktemp -d /tmp/openshift-cmd.XXXX)}
45 45
 ETCD_DATA_DIR="${TEMP_DIR}/etcd"
46 46
 VOLUME_DIR="${TEMP_DIR}/volumes"
47 47
 CERT_DIR="${TEMP_DIR}/certs"
48
-mkdir -p "${ETCD_DATA_DIR}" "${VOLUME_DIR}" "${CERT_DIR}"
48
+CONFIG_DIR="${TEMP_DIR}/configs"
49
+mkdir -p "${ETCD_DATA_DIR}" "${VOLUME_DIR}" "${CERT_DIR}" "${CONFIG_DIR}"
49 50
 
50 51
 # handle profiling defaults
51 52
 profile="${OPENSHIFT_PROFILE-}"
... ...
@@ -83,14 +84,6 @@ wait_for_url "http://${API_HOST}:${KUBELET_PORT}/healthz" "kubelet: " 0.25 80
83 83
 wait_for_url "${API_SCHEME}://${API_HOST}:${API_PORT}/healthz" "apiserver: " 0.25 80
84 84
 wait_for_url "${API_SCHEME}://${API_HOST}:${API_PORT}/api/v1beta1/minions/127.0.0.1" "apiserver(minions): " 0.25 80
85 85
 
86
-# Set KUBERNETES_MASTER for osc
87
-export KUBERNETES_MASTER="${API_SCHEME}://${API_HOST}:${API_PORT}"
88
-if [[ "${API_SCHEME}" == "https" ]]; then
89
-	# Make osc use ${CERT_DIR}/admin/.kubeconfig, and ignore anything in the running user's $HOME dir
90
-	export HOME="${CERT_DIR}/admin"
91
-	export KUBECONFIG="${CERT_DIR}/admin/.kubeconfig"
92
-fi
93
-
94 86
 # profile the cli commands
95 87
 export OPENSHIFT_PROFILE="${CLI_PROFILE-}"
96 88
 
... ...
@@ -98,6 +91,49 @@ export OPENSHIFT_PROFILE="${CLI_PROFILE-}"
98 98
 # Begin tests
99 99
 #
100 100
 
101
+# test client not configured
102
+[ "$(osc get services 2>&1 | grep 'no server found')" ]
103
+
104
+# Set KUBERNETES_MASTER for osc from now on
105
+export KUBERNETES_MASTER="${API_SCHEME}://${API_HOST}:${API_PORT}"
106
+
107
+# Set certificates for osc from now on
108
+if [[ "${API_SCHEME}" == "https" ]]; then
109
+    # test bad certificate
110
+    [ "$(osc get services 2>&1 | grep 'certificate signed by unknown authority')" ]
111
+
112
+    # ignore anything in the running user's $HOME dir
113
+    export HOME="${CERT_DIR}/admin"
114
+fi
115
+
116
+# test config files from the --config flag
117
+osc get services --config="${CERT_DIR}/admin/.kubeconfig"
118
+
119
+# test config files from env vars
120
+OPENSHIFTCONFIG="${CERT_DIR}/admin/.kubeconfig" osc get services
121
+KUBECONFIG="${CERT_DIR}/admin/.kubeconfig" osc get services
122
+
123
+# test config files in the current directory
124
+TEMP_PWD=`pwd` 
125
+pushd ${CONFIG_DIR} >/dev/null
126
+    cp ${CERT_DIR}/admin/.kubeconfig .openshiftconfig
127
+    ${TEMP_PWD}/${GO_OUT}/osc get services
128
+    mv .openshiftconfig .kubeconfig 
129
+    ${TEMP_PWD}/${GO_OUT}/osc get services
130
+popd 
131
+
132
+# test config files in the home directory
133
+mv ${CONFIG_DIR} ${HOME}/.kube
134
+osc get services
135
+mkdir -p ${HOME}/.config
136
+mv ${HOME}/.kube ${HOME}/.config/openshift
137
+mv ${HOME}/.config/openshift/.kubeconfig ${HOME}/.config/openshift/.config
138
+osc get services
139
+echo "config files: ok"
140
+export OPENSHIFTCONFIG="${HOME}/.config/openshift/.config"
141
+
142
+# from this point every command will use config from the OPENSHIFTCONFIG env var
143
+
101 144
 osc get templates
102 145
 osc create -f examples/sample-app/application-template-dockerbuild.json
103 146
 osc get templates
... ...
@@ -273,14 +309,14 @@ osc describe policybinding master -n recreated-project | grep anypassword:create
273 273
 echo "ex new-project: ok"
274 274
 
275 275
 [ ! "$(openshift ex router | grep 'does not exist')"]
276
-[ "$(openshift ex router -o yaml --credentials="${KUBECONFIG}" | grep 'openshift/origin-haproxy-')" ]
277
-openshift ex router --create --credentials="${KUBECONFIG}"
276
+[ "$(openshift ex router -o yaml --credentials="${OPENSHIFTCONFIG}" | grep 'openshift/origin-haproxy-')" ]
277
+openshift ex router --create --credentials="${OPENSHIFTCONFIG}"
278 278
 [ "$(openshift ex router | grep 'service exists')" ]
279 279
 echo "ex router: ok"
280 280
 
281 281
 [ ! "$(openshift ex registry | grep 'does not exist')"]
282
-[ "$(openshift ex registry -o yaml --credentials="${KUBECONFIG}" | grep 'openshift/origin-docker-registry')" ]
283
-openshift ex registry --create --credentials="${KUBECONFIG}"
282
+[ "$(openshift ex registry -o yaml --credentials="${OPENSHIFTCONFIG}" | grep 'openshift/origin-docker-registry')" ]
283
+openshift ex registry --create --credentials="${OPENSHIFTCONFIG}"
284 284
 [ "$(openshift ex registry | grep 'service exists')" ]
285 285
 echo "ex registry: ok"
286 286
 
... ...
@@ -56,11 +56,13 @@ func NewCommandCLI(name, fullName string) *cobra.Command {
56 56
 	}
57 57
 
58 58
 	f := clientcmd.New(cmds.PersistentFlags())
59
+	in := os.Stdin
59 60
 	out := os.Stdout
60 61
 
61 62
 	cmds.SetUsageTemplate(templates.CliUsageTemplate())
62 63
 	cmds.SetHelpTemplate(templates.CliHelpTemplate())
63 64
 
65
+	cmds.AddCommand(cmd.NewCmdLogin(f, in, out))
64 66
 	cmds.AddCommand(cmd.NewCmdNewApplication(fullName, f, out))
65 67
 	cmds.AddCommand(cmd.NewCmdStartBuild(fullName, f, out))
66 68
 	cmds.AddCommand(cmd.NewCmdCancelBuild(fullName, f, out))
... ...
@@ -76,6 +78,7 @@ func NewCommandCLI(name, fullName string) *cobra.Command {
76 76
 	cmds.AddCommand(cmd.NewCmdLog(fullName, f, out))
77 77
 	cmds.AddCommand(f.NewCmdProxy(out))
78 78
 	cmds.AddCommand(kubecmd.NewCmdNamespace(out))
79
+	cmds.AddCommand(cmd.NewCmdProject(f, out))
79 80
 	cmds.AddCommand(cmd.NewCmdOptions(f, out))
80 81
 	cmds.AddCommand(version.NewVersionCommand(fullName))
81 82
 
82 83
new file mode 100644
... ...
@@ -0,0 +1,56 @@
0
+package cmd
1
+
2
+import (
3
+	"fmt"
4
+	"io"
5
+
6
+	"github.com/spf13/cobra"
7
+
8
+	cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util"
9
+	"github.com/openshift/origin/pkg/cmd/cli/config"
10
+	osclientcmd "github.com/openshift/origin/pkg/cmd/util/clientcmd"
11
+)
12
+
13
+const longDescription = `Logs in to the OpenShift server and save the session
14
+information to a config file that will be used by every subsequent command.
15
+
16
+First-time users of the OpenShift client must run this command to configure the server,
17
+establish a session against it and save it to a configuration file, usually in the
18
+user's home directory.
19
+
20
+The information required to login, like username and password or a session token, and 
21
+the server details, can be provided through flags. If not provided, the command will
22
+prompt for user input if needed.
23
+`
24
+
25
+func NewCmdLogin(f *osclientcmd.Factory, reader io.Reader, out io.Writer) *cobra.Command {
26
+	options := &LoginOptions{}
27
+
28
+	cmds := &cobra.Command{
29
+		Use:   "login [--username=<username>] [--password=<password>] [--server=<server>] [--context=<context>] [--certificate-authority=<path>]",
30
+		Short: "Logs in and save the configuration",
31
+		Long:  longDescription,
32
+		Run: func(cmd *cobra.Command, args []string) {
33
+			options.Reader = reader
34
+			options.ClientConfig = f.OpenShiftClientConfig
35
+
36
+			checkErr(options.GatherInfo())
37
+
38
+			forcePath := cmdutil.GetFlagString(cmd, config.OpenShiftConfigFlagName)
39
+			options.PathToSaveConfig = forcePath
40
+
41
+			newFileCreated, err := options.SaveConfig()
42
+			checkErr(err)
43
+
44
+			if newFileCreated {
45
+				fmt.Println("Welcome to OpenShift v3! Use 'osc --help' for a list of commands available.")
46
+			}
47
+		},
48
+	}
49
+
50
+	// TODO flags below should be DE-REGISTERED from the persistent flags and kept only here.
51
+	// Login is the only command that can negotiate a session token against the auth server.
52
+	cmds.Flags().StringVarP(&options.Username, "username", "u", "", "Username, will prompt if not provided")
53
+	cmds.Flags().StringVarP(&options.Password, "password", "p", "", "Password, will prompt if not provided")
54
+	return cmds
55
+}
0 56
new file mode 100644
... ...
@@ -0,0 +1,334 @@
0
+package cmd
1
+
2
+import (
3
+	"fmt"
4
+	"io"
5
+	"os"
6
+	"sort"
7
+	"strings"
8
+
9
+	kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
10
+	kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
11
+	kclientcmd "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
12
+	clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
13
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
14
+
15
+	"github.com/openshift/origin/pkg/client"
16
+	"github.com/openshift/origin/pkg/cmd/cli/config"
17
+	"github.com/openshift/origin/pkg/cmd/util"
18
+	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
19
+	"github.com/openshift/origin/pkg/cmd/util/tokencmd"
20
+	"github.com/openshift/origin/pkg/user/api"
21
+)
22
+
23
+const defaultClusterURL = "https://localhost:8443"
24
+
25
+// Helper for the login and setup process, gathers all information required for a
26
+// successful login and eventual update of config files.
27
+// Depending on the Reader present it can be interactive, asking for terminal input in
28
+// case of any missing information.
29
+// Notice that some methods mutate this object so it should not be reused. The Config
30
+// provided as a pointer will also mutate (handle new auth tokens, etc).
31
+type LoginOptions struct {
32
+	// flags and printing helpers
33
+	Username string
34
+	Password string
35
+	Project  string
36
+
37
+	// infra
38
+	ClientConfig kclientcmd.ClientConfig
39
+	Config       *kclient.Config
40
+	Reader       io.Reader
41
+
42
+	// flow controllers
43
+	gatherServerInfo  bool
44
+	gatherAuthInfo    bool
45
+	gatherProjectInfo bool
46
+
47
+	// Optional, if provided will only try to save in it
48
+	PathToSaveConfig string
49
+}
50
+
51
+// Gather all required information in a comprehensive order.
52
+func (o *LoginOptions) GatherInfo() error {
53
+	if err := o.GatherServerInfo(); err != nil {
54
+		return err
55
+	}
56
+	if err := o.GatherAuthInfo(); err != nil {
57
+		return err
58
+	}
59
+	if err := o.GatherProjectInfo(); err != nil {
60
+		return err
61
+	}
62
+	return nil
63
+}
64
+
65
+// Makes sure it has all the needed information about the server we are connecting to,
66
+// particularly the host address and certificate information. For every information not
67
+// present ask for interactive user input. Will also ping the server to make sure we can
68
+// connect to it, and if any problem is found (e.g. certificate issues), ask the user about
69
+// connecting insecurely.
70
+func (o *LoginOptions) GatherServerInfo() error {
71
+	// we need to have a server to talk to
72
+
73
+	if util.IsTerminal(o.Reader) {
74
+		for !o.serverProvided() {
75
+			defaultServer := defaultClusterURL
76
+			promptMsg := fmt.Sprintf("Please provide the server URL or just <enter> to use '%v': ", defaultServer)
77
+
78
+			server := util.PromptForStringWithDefault(o.Reader, defaultServer, promptMsg)
79
+			kclientcmd.DefaultCluster = clientcmdapi.Cluster{Server: server}
80
+		}
81
+	}
82
+
83
+	// we know the server we are expected to use
84
+
85
+	clientCfg, err := o.ClientConfig.ClientConfig()
86
+	if err != nil {
87
+		return err
88
+	}
89
+
90
+	// ping to check if server is reachable
91
+
92
+	osClient, err := client.New(clientCfg)
93
+	if err != nil {
94
+		return err
95
+	}
96
+
97
+	result := osClient.Get().AbsPath("/osapi").Do()
98
+	if result.Error() != nil {
99
+		// certificate issue, prompt user for insecure connection
100
+
101
+		if clientcmd.IsCertificateAuthorityUnknown(result.Error()) {
102
+			fmt.Println("The server uses a certificate signed by unknown authority. You can bypass the certificate check but it will make all connections insecure.")
103
+
104
+			clientCfg.Insecure = util.PromptForBool(os.Stdin, "Use insecure connections (strongly discouraged)? [y/N] ")
105
+			if !clientCfg.Insecure {
106
+				return fmt.Errorf(clientcmd.GetPrettyMessageFor(result.Error()))
107
+			}
108
+		}
109
+	}
110
+
111
+	// we have all info we need, now we can have a proper Config
112
+
113
+	o.Config = clientCfg
114
+
115
+	o.gatherServerInfo = true
116
+	return nil
117
+}
118
+
119
+// Negotiate a bearer token with the auth server, or try to reuse one based on the
120
+// information already present. In case of any missing information, ask for user input
121
+// (usually username and password, interactive depending on the Reader).
122
+func (o *LoginOptions) GatherAuthInfo() error {
123
+	if err := o.assertGatheredServerInfo(); err != nil {
124
+		return err
125
+	}
126
+
127
+	if me, err := o.Whoami(); err == nil && (!o.usernameProvided() || (o.usernameProvided() && o.Username == usernameFromUser(me))) {
128
+		o.Username = usernameFromUser(me)
129
+		fmt.Printf("Already logged into '%v' as '%v'.\n", o.Config.Host, o.Username)
130
+
131
+	} else {
132
+		// if not, we need to log in again
133
+
134
+		o.Config.BearerToken = ""
135
+		token, err := tokencmd.RequestToken(o.Config, o.Reader, o.Username, o.Password)
136
+		if err != nil {
137
+			return err
138
+		}
139
+		o.Config.BearerToken = token
140
+
141
+		me, err := o.Whoami()
142
+		if err != nil {
143
+			return err
144
+		}
145
+		o.Username = usernameFromUser(me)
146
+		fmt.Printf("Logged into '%v' as '%v'.\n", o.Config.Host, o.Username)
147
+	}
148
+
149
+	// TODO investigate about the safety and intent of the proposal below
150
+	// if trying to log in an user that's not the currently logged in, try to reuse an existing token
151
+
152
+	// if o.usernameProvided() {
153
+	// 	glog.V(5).Infof("Checking existing authentication info for '%v'...\n", o.Username)
154
+
155
+	// 	for _, ctx := range rawCfg.Contexts {
156
+	// 		authInfo := rawCfg.AuthInfos[ctx.AuthInfo]
157
+	// 		clusterInfo := rawCfg.Clusters[ctx.Cluster]
158
+
159
+	// 		if ctx.AuthInfo == o.Username && clusterInfo.Server == o.Server && len(authInfo.Token) > 0 { // only token for now
160
+	// 			glog.V(5).Infof("Authentication exists for '%v' on '%v', trying to use it...\n", o.Server, o.Username)
161
+
162
+	// 			o.Config.BearerToken = authInfo.Token
163
+
164
+	// 			if me, err := whoami(o.Config); err == nil && usernameFromUser(me) == o.Username {
165
+	// 				o.Username = usernameFromUser(me)
166
+	// 				return nil
167
+	// 			}
168
+
169
+	// 			glog.V(5).Infof("Token %v no longer valid for '%v', can't use it\n", authInfo.Token, o.Username)
170
+	// 		}
171
+	// 	}
172
+	// }
173
+
174
+	o.gatherAuthInfo = true
175
+	return nil
176
+}
177
+
178
+// Discover the projects available for the stabilished session and take one to use. It
179
+// fails in case of no existing projects, and print out useful information in case of
180
+// multiple projects.
181
+func (o *LoginOptions) GatherProjectInfo() error {
182
+	if err := o.assertGatheredAuthInfo(); err != nil {
183
+		return err
184
+	}
185
+
186
+	oClient, err := client.New(o.Config)
187
+	if err != nil {
188
+		return err
189
+	}
190
+
191
+	projects, err := oClient.Projects().List(labels.Everything(), labels.Everything())
192
+	if err != nil {
193
+		return err
194
+	}
195
+
196
+	projectsItems := projects.Items
197
+
198
+	switch len(projectsItems) {
199
+	case 0:
200
+		if me, err := o.Whoami(); err == nil {
201
+			// TODO most users will not be allowed to run the suggested commands below, so we should check it and/or
202
+			// have a server endpoint that allows an admin to describe to users how to request projects
203
+			return fmt.Errorf(`You don't have any project.
204
+To create a new project, run 'openshift ex new-project <projectname> --admin=%s'.
205
+To be added as an admin to an existing project, run 'openshift ex policy add-user admin %s -n <projectname>'.`, me.Name, me.Name)
206
+		}
207
+		return err
208
+
209
+	case 1:
210
+		o.Project = projectsItems[0].Name
211
+
212
+	default:
213
+		projects := []string{}
214
+
215
+		for _, project := range projectsItems {
216
+			projects = append(projects, project.Name)
217
+		}
218
+
219
+		namespace, err := o.ClientConfig.Namespace()
220
+		if err != nil {
221
+			return err
222
+		}
223
+
224
+		if current, err := oClient.Projects().Get(namespace); err != nil {
225
+			if kerrors.IsNotFound(err) || kerrors.IsForbidden(err) {
226
+				o.Project = projects[0]
227
+			} else {
228
+				return err
229
+			}
230
+		} else {
231
+			o.Project = current.Name
232
+		}
233
+
234
+		if n := len(projects); n > 10 {
235
+			projects = projects[:10]
236
+			fmt.Printf("You have %d projects, displaying only the first 10. To view all your projects run 'osc get projects'.\n", n)
237
+		}
238
+		var sortedProjects sort.StringSlice = projects
239
+		sortedProjects.Sort()
240
+		fmt.Printf("Your projects are: %v. You can switch between them at any time using 'osc project <project-name>'.\n", strings.Join(projects, ", "))
241
+	}
242
+
243
+	fmt.Printf("Using project '%v'.\n", o.Project)
244
+
245
+	o.gatherProjectInfo = true
246
+	return nil
247
+}
248
+
249
+// Save all the information present in this helper to a config file. An explicit config
250
+// file path can be provided, if not use the established conventions about config
251
+// loading rules. Will create a new config file if one can't be found at all. Will only
252
+// succeed if all required info is present.
253
+func (o *LoginOptions) SaveConfig() (created bool, err error) {
254
+	if len(o.Project) == 0 || len(o.Username) == 0 {
255
+		return false, fmt.Errorf("Insufficient data to merge configuration.")
256
+	}
257
+
258
+	var configStore *config.ConfigStore
259
+
260
+	if len(o.PathToSaveConfig) > 0 {
261
+		configStore, err = config.LoadFrom(o.PathToSaveConfig)
262
+		if err != nil {
263
+			return created, err
264
+		}
265
+	} else {
266
+		configStore, err = config.LoadWithLoadingRules()
267
+		if err != nil {
268
+			configStore, err = config.CreateEmpty()
269
+			if err != nil {
270
+				return created, err
271
+			}
272
+			created = true
273
+		}
274
+	}
275
+
276
+	rawCfg, err := o.ClientConfig.RawConfig()
277
+	if err != nil {
278
+		return created, err
279
+	}
280
+	return created, configStore.SaveToFile(o.Username, o.Project, o.Config, rawCfg)
281
+}
282
+
283
+func (o *LoginOptions) Whoami() (*api.User, error) {
284
+	oClient, err := client.New(o.Config)
285
+	if err != nil {
286
+		return nil, err
287
+	}
288
+
289
+	me, err := oClient.Users().Get("~")
290
+	if err != nil {
291
+		return nil, err
292
+	}
293
+
294
+	return me, nil
295
+}
296
+
297
+func (o *LoginOptions) assertGatheredServerInfo() error {
298
+	if !o.gatherServerInfo {
299
+		return fmt.Errorf("Must gather server info first.")
300
+	}
301
+	return nil
302
+}
303
+
304
+func (o *LoginOptions) assertGatheredAuthInfo() error {
305
+	if !o.gatherAuthInfo {
306
+		return fmt.Errorf("Must gather auth info first.")
307
+	}
308
+	return nil
309
+}
310
+
311
+func (o *LoginOptions) assertGatheredProjectInfo() error {
312
+	if !o.gatherProjectInfo {
313
+		return fmt.Errorf("Must gather project info first.")
314
+	}
315
+	return nil
316
+}
317
+
318
+func (o *LoginOptions) usernameProvided() bool {
319
+	return len(o.Username) > 0
320
+}
321
+
322
+func (o *LoginOptions) passwordProvided() bool {
323
+	return len(o.Password) > 0
324
+}
325
+
326
+func (o *LoginOptions) serverProvided() bool {
327
+	_, err := o.ClientConfig.ClientConfig()
328
+	return err == nil || !clientcmd.IsNoServerFound(err)
329
+}
330
+
331
+func usernameFromUser(user *api.User) string {
332
+	return strings.Split(user.Name, ":")[1]
333
+}
... ...
@@ -64,7 +64,12 @@ func NewCmdProject(f *clientcmd.Factory, out io.Writer) *cobra.Command {
64 64
 			}
65 65
 
66 66
 			pathFromFlag := cmdutil.GetFlagString(cmd, config.OpenShiftConfigFlagName)
67
-			configStore, err := config.Load(clientCfg, pathFromFlag)
67
+
68
+			configStore, err := config.LoadFrom(pathFromFlag)
69
+			if err != nil {
70
+				configStore, err = config.LoadWithLoadingRules()
71
+				checkErr(err)
72
+			}
68 73
 			checkErr(err)
69 74
 
70 75
 			config := configStore.Config
71 76
new file mode 100644
... ...
@@ -0,0 +1,54 @@
0
+package config
1
+
2
+import (
3
+	"os"
4
+	"path"
5
+
6
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
7
+)
8
+
9
+const (
10
+	OpenShiftConfigPathEnvVar      = "OPENSHIFTCONFIG"
11
+	OpenShiftConfigFlagName        = "config"
12
+	OpenShiftConfigFileName        = ".openshiftconfig"
13
+	OpenShiftConfigHomeDir         = ".config/openshift"
14
+	OpenShiftConfigHomeFileName    = ".config"
15
+	OpenShiftConfigHomeDirFileName = OpenShiftConfigHomeDir + "/" + OpenShiftConfigHomeFileName
16
+
17
+	KubeConfigPathEnvVar = clientcmd.RecommendedConfigPathEnvVar
18
+	KubeConfigFileName   = ".kubeconfig"
19
+	KubeConfigHomeDir    = ".kube"
20
+)
21
+
22
+// Set up the rules and priorities for loading config files.
23
+func NewOpenShiftClientConfigLoadingRules() *clientcmd.ClientConfigLoadingRules {
24
+	return clientcmd.NewClientConfigLoadingRules(FullClientConfigFilePriority())
25
+}
26
+
27
+// File priority loading rules for OpenShift.
28
+// 1. OPENSHIFTCONFIG env var
29
+// 2. .openshiftconfig file in current directory
30
+// 3. ~/.config/openshift/.config file
31
+func OpenShiftClientConfigFilePriority() []string {
32
+	return []string{
33
+		os.Getenv(OpenShiftConfigPathEnvVar),
34
+		OpenShiftConfigFileName,
35
+		path.Join(os.Getenv("HOME"), OpenShiftConfigHomeDirFileName),
36
+	}
37
+}
38
+
39
+// File priority loading rules for Kube.
40
+// 1. KUBECONFIG env var
41
+// 2. .kubeconfig file in current directory
42
+// 3. ~/.kube/.kubeconfig file
43
+func KubeClientConfigFilePriority() []string {
44
+	return []string{
45
+		os.Getenv(KubeConfigPathEnvVar),
46
+		KubeConfigFileName,
47
+		path.Join(os.Getenv("HOME"), KubeConfigHomeDir, KubeConfigFileName),
48
+	}
49
+}
50
+
51
+func FullClientConfigFilePriority() []string {
52
+	return append(OpenShiftClientConfigFilePriority(), KubeClientConfigFilePriority()...)
53
+}
0 54
new file mode 100644
... ...
@@ -0,0 +1,157 @@
0
+package config
1
+
2
+import (
3
+	"fmt"
4
+	"reflect"
5
+
6
+	clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
7
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
8
+)
9
+
10
+// MergeConfig takes a haystack to look for existing stanzas in (probably the merged config), a config object to modify (probably
11
+// either the local or envvar config), and the new additions to merge in.  It tries to find equivalents for the addition inside of the
12
+// haystack and uses the mapping to avoid creating additional stanzas with duplicate information.  It either locates or original
13
+// stanzas or creates new ones for clusters and users.  Then it uses the mapped names to build the correct contexts
14
+func MergeConfig(haystack, toModify, addition clientcmdapi.Config) (*clientcmdapi.Config, error) {
15
+	ret := toModify
16
+
17
+	requestedClusterNamesToActualClusterNames := map[string]string{}
18
+	existingClusterNames, err := getMapKeys(reflect.ValueOf(haystack.Clusters))
19
+	if err != nil {
20
+		return nil, err
21
+	}
22
+	for requestedKey, needle := range addition.Clusters {
23
+		if existingName := FindExistingClusterName(haystack, needle); len(existingName) > 0 {
24
+			requestedClusterNamesToActualClusterNames[requestedKey] = existingName
25
+			continue
26
+		}
27
+
28
+		uniqueName := getUniqueName(requestedKey, existingClusterNames)
29
+		requestedClusterNamesToActualClusterNames[requestedKey] = uniqueName
30
+		ret.Clusters[uniqueName] = needle
31
+	}
32
+
33
+	requestedAuthInfoNamesToActualAuthInfoNames := map[string]string{}
34
+	existingAuthInfoNames, err := getMapKeys(reflect.ValueOf(haystack.AuthInfos))
35
+	if err != nil {
36
+		return nil, err
37
+	}
38
+	for requestedKey, needle := range addition.AuthInfos {
39
+		if existingName := FindExistingAuthInfoName(haystack, needle); len(existingName) > 0 {
40
+			requestedAuthInfoNamesToActualAuthInfoNames[requestedKey] = existingName
41
+			continue
42
+		}
43
+
44
+		uniqueName := getUniqueName(requestedKey, existingAuthInfoNames)
45
+		requestedAuthInfoNamesToActualAuthInfoNames[requestedKey] = uniqueName
46
+		ret.AuthInfos[uniqueName] = needle
47
+	}
48
+
49
+	requestedContextNamesToActualContextNames := map[string]string{}
50
+	existingContextNames, err := getMapKeys(reflect.ValueOf(haystack.Contexts))
51
+	if err != nil {
52
+		return nil, err
53
+	}
54
+	for requestedKey, needle := range addition.Contexts {
55
+		exists := false
56
+
57
+		actualContext := clientcmdapi.NewContext()
58
+		actualContext.AuthInfo, exists = requestedAuthInfoNamesToActualAuthInfoNames[needle.AuthInfo]
59
+		if !exists {
60
+			actualContext.AuthInfo = needle.AuthInfo
61
+		}
62
+		actualContext.Cluster, exists = requestedClusterNamesToActualClusterNames[needle.Cluster]
63
+		if !exists {
64
+			actualContext.Cluster = needle.Cluster
65
+		}
66
+		actualContext.Namespace = needle.Namespace
67
+		actualContext.Extensions = needle.Extensions
68
+
69
+		if existingName := FindExistingContextName(haystack, *actualContext); len(existingName) > 0 {
70
+			// if this already exists, just move to the next, our job is done
71
+			requestedContextNamesToActualContextNames[requestedKey] = existingName
72
+			continue
73
+		}
74
+
75
+		uniqueName := getUniqueName(actualContext.Cluster+"-"+actualContext.AuthInfo, existingContextNames)
76
+		requestedContextNamesToActualContextNames[requestedKey] = uniqueName
77
+		ret.Contexts[uniqueName] = *actualContext
78
+	}
79
+
80
+	if len(addition.CurrentContext) > 0 {
81
+		if newCurrentContext, exists := requestedContextNamesToActualContextNames[addition.CurrentContext]; exists {
82
+			ret.CurrentContext = newCurrentContext
83
+		} else {
84
+			ret.CurrentContext = addition.CurrentContext
85
+		}
86
+	}
87
+
88
+	return &ret, nil
89
+}
90
+
91
+// FindExistingClusterName finds the nickname for the passed cluster config
92
+func FindExistingClusterName(haystack clientcmdapi.Config, needle clientcmdapi.Cluster) string {
93
+	for key, cluster := range haystack.Clusters {
94
+		if reflect.DeepEqual(cluster, needle) {
95
+			return key
96
+		}
97
+	}
98
+
99
+	return ""
100
+}
101
+
102
+// FindExistingAuthInfoName finds the nickname for the passed auth info
103
+func FindExistingAuthInfoName(haystack clientcmdapi.Config, needle clientcmdapi.AuthInfo) string {
104
+	for key, authInfo := range haystack.AuthInfos {
105
+		if reflect.DeepEqual(authInfo, needle) {
106
+			return key
107
+		}
108
+	}
109
+
110
+	return ""
111
+}
112
+
113
+// FindExistingContextName finds the nickname for the passed context
114
+func FindExistingContextName(haystack clientcmdapi.Config, needle clientcmdapi.Context) string {
115
+	for key, context := range haystack.Contexts {
116
+		if reflect.DeepEqual(context, needle) {
117
+			return key
118
+		}
119
+	}
120
+
121
+	return ""
122
+}
123
+
124
+func getMapKeys(theMap reflect.Value) (*util.StringSet, error) {
125
+	if theMap.Kind() != reflect.Map {
126
+		return nil, fmt.Errorf("theMap must be of type %v, not %v", reflect.Map, theMap.Kind())
127
+	}
128
+
129
+	ret := &util.StringSet{}
130
+
131
+	switch theMap.Kind() {
132
+	case reflect.Map:
133
+		for _, keyValue := range theMap.MapKeys() {
134
+			ret.Insert(keyValue.String())
135
+		}
136
+
137
+	}
138
+
139
+	return ret, nil
140
+
141
+}
142
+
143
+func getUniqueName(basename string, existingNames *util.StringSet) string {
144
+	if !existingNames.Has(basename) {
145
+		return basename
146
+	}
147
+
148
+	for i := 0; i < 100; i++ {
149
+		trialName := fmt.Sprintf("%v-%d", basename, i)
150
+		if !existingNames.Has(trialName) {
151
+			return trialName
152
+		}
153
+	}
154
+
155
+	return string(util.NewUUID())
156
+}
0 157
new file mode 100644
... ...
@@ -0,0 +1,159 @@
0
+package config
1
+
2
+import (
3
+	"fmt"
4
+	"io/ioutil"
5
+	"os"
6
+
7
+	"github.com/golang/glog"
8
+
9
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
10
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
11
+	clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
12
+
13
+	"github.com/openshift/origin/pkg/cmd/flagtypes"
14
+)
15
+
16
+const (
17
+	fromKube      = "fromkube"
18
+	fromOpenShift = "fromopenshift"
19
+)
20
+
21
+// A ConfigStore is the representation of a config from one individual config file. Can be used
22
+// to persist configs by being explicit about the file to save.
23
+type ConfigStore struct {
24
+	Config         *clientcmdapi.Config
25
+	Path           string
26
+	providerEngine string
27
+}
28
+
29
+func (c *ConfigStore) FromOpenShift() bool {
30
+	return c.providerEngine == fromOpenShift
31
+}
32
+
33
+func (c *ConfigStore) FromKube() bool {
34
+	return c.providerEngine == fromKube
35
+}
36
+
37
+// Load a ConfigStore from the explicit path to a config file provided as argument
38
+// Error if not found.
39
+func LoadFrom(path string) (*ConfigStore, error) {
40
+	data, err := ioutil.ReadFile(path)
41
+	if err == nil {
42
+		config, err := clientcmd.Load(data)
43
+		if err != nil {
44
+			return nil, err
45
+		}
46
+		return &ConfigStore{config, path, fromOpenShift}, nil
47
+	}
48
+
49
+	return nil, fmt.Errorf("Unable to load config file from '%v': %v", path, err.Error())
50
+}
51
+
52
+// Load a ConfigStore using the priority conventions declared by the ClientConfigLoadingRules.
53
+// Error if none can be found.
54
+func LoadWithLoadingRules() (store *ConfigStore, err error) {
55
+	loadingRules := map[string][]string{
56
+		fromOpenShift: OpenShiftClientConfigFilePriority(),
57
+		fromKube:      KubeClientConfigFilePriority(),
58
+	}
59
+
60
+	for source, priorities := range loadingRules {
61
+		for _, path := range priorities {
62
+			data, err := ioutil.ReadFile(path)
63
+			if err != nil && !os.IsNotExist(err) {
64
+				return nil, fmt.Errorf("Unable to load config file from %v: %v", path, err.Error())
65
+			}
66
+			if err == nil {
67
+				config, err := clientcmd.Load(data)
68
+				if err != nil {
69
+					return store, err
70
+				}
71
+				return &ConfigStore{config, path, source}, nil
72
+			}
73
+		}
74
+	}
75
+
76
+	return nil, fmt.Errorf("Unable to load a config file from any of the expected locations.")
77
+}
78
+
79
+// Create a new file to store configs and returns the ConfigStore that represents it.
80
+func CreateEmpty() (*ConfigStore, error) {
81
+	configPathToCreateIfNotFound := fmt.Sprintf("%v/%v", os.Getenv("HOME"), OpenShiftConfigHomeDirFileName)
82
+
83
+	glog.V(3).Infof("A new config will be created at: %v ", configPathToCreateIfNotFound)
84
+
85
+	newConfig := clientcmdapi.NewConfig()
86
+
87
+	if err := os.MkdirAll(fmt.Sprintf("%v/%v", os.Getenv("HOME"), OpenShiftConfigHomeDir), 0755); err != nil {
88
+		return nil, fmt.Errorf("Tried to create a new config file but failed while creating directory %v: %v", OpenShiftConfigHomeDirFileName, err)
89
+	}
90
+	glog.V(5).Infof("Created directory %v", "~/"+OpenShiftConfigHomeDir)
91
+
92
+	if err := clientcmd.WriteToFile(*newConfig, configPathToCreateIfNotFound); err != nil {
93
+		return nil, fmt.Errorf("Tried to create a new config file but failed with: %v", err)
94
+	}
95
+	glog.V(5).Infof("Created file %v", configPathToCreateIfNotFound)
96
+
97
+	data, err := ioutil.ReadFile(configPathToCreateIfNotFound)
98
+	if err != nil {
99
+		return nil, err
100
+	}
101
+
102
+	config, err := clientcmd.Load(data)
103
+	if err != nil {
104
+		return nil, err
105
+	}
106
+
107
+	return &ConfigStore{config, configPathToCreateIfNotFound, fromOpenShift}, nil
108
+}
109
+
110
+// Save the provided config attributes to this ConfigStore.
111
+func (c *ConfigStore) SaveToFile(credentialsName string, namespace string, clientCfg *client.Config, rawCfg clientcmdapi.Config) error {
112
+	glog.V(4).Infof("Trying to merge and update %v config to '%v'...", c.providerEngine, c.Path)
113
+
114
+	config := clientcmdapi.NewConfig()
115
+
116
+	credentials := clientcmdapi.NewAuthInfo()
117
+	credentials.Token = clientCfg.BearerToken
118
+	credentials.ClientCertificate = clientCfg.TLSClientConfig.CertFile
119
+	credentials.ClientCertificateData = clientCfg.TLSClientConfig.CertData
120
+	credentials.ClientKey = clientCfg.TLSClientConfig.KeyFile
121
+	credentials.ClientKeyData = clientCfg.TLSClientConfig.KeyData
122
+	if len(credentialsName) == 0 {
123
+		credentialsName = "osc-login"
124
+	}
125
+	config.AuthInfos[credentialsName] = *credentials
126
+
127
+	serverAddr := flagtypes.Addr{Value: clientCfg.Host}.Default()
128
+	clusterName := fmt.Sprintf("%v:%v", serverAddr.Host, serverAddr.Port)
129
+	cluster := clientcmdapi.NewCluster()
130
+	cluster.Server = clientCfg.Host
131
+	cluster.CertificateAuthority = clientCfg.CAFile
132
+	cluster.CertificateAuthorityData = clientCfg.CAData
133
+	cluster.InsecureSkipTLSVerify = clientCfg.Insecure
134
+	config.Clusters[clusterName] = *cluster
135
+
136
+	contextName := clusterName + "-" + credentialsName
137
+	context := clientcmdapi.NewContext()
138
+	context.Cluster = clusterName
139
+	context.AuthInfo = credentialsName
140
+	context.Namespace = namespace
141
+	config.Contexts[contextName] = *context
142
+	config.CurrentContext = contextName
143
+
144
+	configToModify := c.Config
145
+
146
+	configToWrite, err := MergeConfig(rawCfg, *configToModify, *config)
147
+	if err != nil {
148
+		return err
149
+	}
150
+
151
+	// TODO need to handle file not writable (probably create a copy)
152
+	err = clientcmd.WriteToFile(*configToWrite, c.Path)
153
+	if err != nil {
154
+		return err
155
+	}
156
+
157
+	return nil
158
+}
0 159
deleted file mode 100644
... ...
@@ -1,178 +0,0 @@
1
-package login
2
-
3
-import (
4
-	"fmt"
5
-	"os"
6
-
7
-	"github.com/golang/glog"
8
-	"github.com/spf13/cobra"
9
-
10
-	kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
11
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
12
-	clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
13
-	kcmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util"
14
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
15
-
16
-	"github.com/openshift/origin/pkg/client"
17
-	"github.com/openshift/origin/pkg/cmd/flagtypes"
18
-	osclientcmd "github.com/openshift/origin/pkg/cmd/util/clientcmd"
19
-	"github.com/openshift/origin/pkg/cmd/util/tokencmd"
20
-)
21
-
22
-func NewCmdLogin(f *osclientcmd.Factory, parentName, name string) *cobra.Command {
23
-	cmds := &cobra.Command{
24
-		Use:   name,
25
-		Short: "Logs in and returns a session token",
26
-		Long: `Logs in to the OpenShift server and prints out a session token.
27
-
28
-Username and password can be provided through flags, the command will
29
-prompt for user input if not provided.
30
-`,
31
-		Run: func(cmd *cobra.Command, args []string) {
32
-			clientCfg, err := f.OpenShiftClientConfig.ClientConfig()
33
-			if err != nil {
34
-				glog.Fatalf("%v\n", err)
35
-			}
36
-
37
-			username := ""
38
-
39
-			// check to see if we're already signed in.  If so, simply make sure that .kubeconfig has that information
40
-			if userFullName, err := whoami(clientCfg); err == nil {
41
-				if err := updateKubeconfigFile(userFullName, clientCfg.BearerToken, f.OpenShiftClientConfig); err != nil {
42
-					glog.Fatalf("%v\n", err)
43
-				}
44
-				username = userFullName
45
-
46
-			} else {
47
-				usernameFlag := kcmdutil.GetFlagString(cmd, "username")
48
-				passwordFlag := kcmdutil.GetFlagString(cmd, "password")
49
-
50
-				accessToken, err := tokencmd.RequestToken(clientCfg, os.Stdin, usernameFlag, passwordFlag)
51
-				if err != nil {
52
-					glog.Fatalf("%v\n", err)
53
-				}
54
-
55
-				clientCfg.BearerToken = accessToken
56
-
57
-				if userFullName, err := whoami(clientCfg); err == nil {
58
-					err = updateKubeconfigFile(userFullName, accessToken, f.OpenShiftClientConfig)
59
-					if err != nil {
60
-						glog.Fatalf("%v\n", err)
61
-					} else {
62
-						username = userFullName
63
-					}
64
-				} else {
65
-					glog.Fatalf("%v\n", err)
66
-				}
67
-			}
68
-
69
-			fmt.Printf("Logged into %v as %v\n", clientCfg.Host, username)
70
-		},
71
-	}
72
-
73
-	cmds.Flags().StringP("username", "u", "", "Username, will prompt if not provided")
74
-	cmds.Flags().StringP("password", "p", "", "Password, will prompt if not provided")
75
-	return cmds
76
-}
77
-
78
-func whoami(clientCfg *kclient.Config) (string, error) {
79
-	osClient, err := client.New(clientCfg)
80
-	if err != nil {
81
-		return "", err
82
-	}
83
-
84
-	me, err := osClient.Users().Get("~")
85
-	if err != nil {
86
-		return "", err
87
-	}
88
-
89
-	return me.FullName, nil
90
-}
91
-
92
-func updateKubeconfigFile(username, token string, clientCfg clientcmd.ClientConfig) error {
93
-	rawMergedConfig, err := clientCfg.RawConfig()
94
-	if err != nil {
95
-		return err
96
-	}
97
-	clientConfig, err := clientCfg.ClientConfig()
98
-	if err != nil {
99
-		return err
100
-	}
101
-	namespace, err := clientCfg.Namespace()
102
-	if err != nil {
103
-		return err
104
-	}
105
-
106
-	config := clientcmdapi.NewConfig()
107
-
108
-	credentialsName := username
109
-	if len(credentialsName) == 0 {
110
-		credentialsName = "osc-login"
111
-	}
112
-	credentials := clientcmdapi.NewAuthInfo()
113
-	credentials.Token = token
114
-	config.AuthInfos[credentialsName] = *credentials
115
-
116
-	serverAddr := flagtypes.Addr{Value: clientConfig.Host}.Default()
117
-	clusterName := fmt.Sprintf("%v:%v", serverAddr.Host, serverAddr.Port)
118
-	cluster := clientcmdapi.NewCluster()
119
-	cluster.Server = clientConfig.Host
120
-	cluster.InsecureSkipTLSVerify = clientConfig.Insecure
121
-	cluster.CertificateAuthority = clientConfig.CAFile
122
-	config.Clusters[clusterName] = *cluster
123
-
124
-	contextName := clusterName + "-" + credentialsName
125
-	context := clientcmdapi.NewContext()
126
-	context.Cluster = clusterName
127
-	context.AuthInfo = credentialsName
128
-	context.Namespace = namespace
129
-	config.Contexts[contextName] = *context
130
-
131
-	config.CurrentContext = contextName
132
-
133
-	configToModify, err := getConfigFromFile(".kubeconfig")
134
-	if err != nil {
135
-		return err
136
-	}
137
-
138
-	configToWrite, err := MergeConfig(rawMergedConfig, *configToModify, *config)
139
-	if err != nil {
140
-		return err
141
-	}
142
-	err = clientcmd.WriteToFile(*configToWrite, ".kubeconfig")
143
-	if err != nil {
144
-		return err
145
-	}
146
-
147
-	return nil
148
-
149
-}
150
-
151
-func getConfigFromFile(filename string) (*clientcmdapi.Config, error) {
152
-	var err error
153
-	config, err := clientcmd.LoadFromFile(filename)
154
-	if err != nil && !os.IsNotExist(err) {
155
-		return nil, err
156
-	}
157
-
158
-	if config == nil {
159
-		config = clientcmdapi.NewConfig()
160
-	}
161
-
162
-	return config, nil
163
-}
164
-
165
-func getUniqueName(basename string, existingNames *util.StringSet) string {
166
-	if !existingNames.Has(basename) {
167
-		return basename
168
-	}
169
-
170
-	for i := 0; i < 100; i++ {
171
-		trialName := fmt.Sprintf("%v-%d", basename, i)
172
-		if !existingNames.Has(trialName) {
173
-			return trialName
174
-		}
175
-	}
176
-
177
-	return string(util.NewUUID())
178
-}
179 1
deleted file mode 100644
... ...
@@ -1,142 +0,0 @@
1
-package login
2
-
3
-import (
4
-	"fmt"
5
-	"reflect"
6
-
7
-	clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
8
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
9
-)
10
-
11
-// MergeConfig takes a haystack to look for existing stanzas in (probably the merged config), a config object to modify (probably
12
-// either the local or envvar config), and the new additions to merge in.  It tries to find equivalents for the addition inside of the
13
-// haystack and uses the mapping to avoid creating additional stanzas with duplicate information.  It either locates or original
14
-// stanzas or creates new ones for clusters and users.  Then it uses the mapped names to build the correct contexts
15
-func MergeConfig(haystack, toModify, addition clientcmdapi.Config) (*clientcmdapi.Config, error) {
16
-	ret := toModify
17
-
18
-	requestedClusterNamesToActualClusterNames := map[string]string{}
19
-	existingClusterNames, err := getMapKeys(reflect.ValueOf(haystack.Clusters))
20
-	if err != nil {
21
-		return nil, err
22
-	}
23
-	for requestedKey, needle := range addition.Clusters {
24
-		if existingName := FindExistingClusterName(haystack, needle); len(existingName) > 0 {
25
-			requestedClusterNamesToActualClusterNames[requestedKey] = existingName
26
-			continue
27
-		}
28
-
29
-		uniqueName := getUniqueName(requestedKey, existingClusterNames)
30
-		requestedClusterNamesToActualClusterNames[requestedKey] = uniqueName
31
-		ret.Clusters[uniqueName] = needle
32
-	}
33
-
34
-	requestedAuthInfoNamesToActualAuthInfoNames := map[string]string{}
35
-	existingAuthInfoNames, err := getMapKeys(reflect.ValueOf(haystack.AuthInfos))
36
-	if err != nil {
37
-		return nil, err
38
-	}
39
-	for requestedKey, needle := range addition.AuthInfos {
40
-		if existingName := FindExistingAuthInfoName(haystack, needle); len(existingName) > 0 {
41
-			requestedAuthInfoNamesToActualAuthInfoNames[requestedKey] = existingName
42
-			continue
43
-		}
44
-
45
-		uniqueName := getUniqueName(requestedKey, existingAuthInfoNames)
46
-		requestedAuthInfoNamesToActualAuthInfoNames[requestedKey] = uniqueName
47
-		ret.AuthInfos[uniqueName] = needle
48
-	}
49
-
50
-	requestedContextNamesToActualContextNames := map[string]string{}
51
-	existingContextNames, err := getMapKeys(reflect.ValueOf(haystack.Contexts))
52
-	if err != nil {
53
-		return nil, err
54
-	}
55
-	for requestedKey, needle := range addition.Contexts {
56
-		exists := false
57
-
58
-		actualContext := clientcmdapi.NewContext()
59
-		actualContext.AuthInfo, exists = requestedAuthInfoNamesToActualAuthInfoNames[needle.AuthInfo]
60
-		if !exists {
61
-			actualContext.AuthInfo = needle.AuthInfo
62
-		}
63
-		actualContext.Cluster, exists = requestedClusterNamesToActualClusterNames[needle.Cluster]
64
-		if !exists {
65
-			actualContext.Cluster = needle.Cluster
66
-		}
67
-		actualContext.Namespace = needle.Namespace
68
-		actualContext.Extensions = needle.Extensions
69
-
70
-		if existingName := FindExistingContextName(haystack, *actualContext); len(existingName) > 0 {
71
-			// if this already exists, just move to the next, our job is done
72
-			requestedContextNamesToActualContextNames[requestedKey] = existingName
73
-			continue
74
-		}
75
-
76
-		uniqueName := getUniqueName(actualContext.Cluster+"-"+actualContext.AuthInfo, existingContextNames)
77
-		requestedContextNamesToActualContextNames[requestedKey] = uniqueName
78
-		ret.Contexts[uniqueName] = *actualContext
79
-	}
80
-
81
-	if len(addition.CurrentContext) > 0 {
82
-		if newCurrentContext, exists := requestedContextNamesToActualContextNames[addition.CurrentContext]; exists {
83
-			ret.CurrentContext = newCurrentContext
84
-		} else {
85
-			ret.CurrentContext = addition.CurrentContext
86
-		}
87
-	}
88
-
89
-	return &ret, nil
90
-}
91
-
92
-// FindExistingClusterName finds the nickname for the passed cluster config
93
-func FindExistingClusterName(haystack clientcmdapi.Config, needle clientcmdapi.Cluster) string {
94
-	for key, cluster := range haystack.Clusters {
95
-		if reflect.DeepEqual(cluster, needle) {
96
-			return key
97
-		}
98
-	}
99
-
100
-	return ""
101
-}
102
-
103
-// FindExistingAuthInfoName finds the nickname for the passed auth info
104
-func FindExistingAuthInfoName(haystack clientcmdapi.Config, needle clientcmdapi.AuthInfo) string {
105
-	for key, authInfo := range haystack.AuthInfos {
106
-		if reflect.DeepEqual(authInfo, needle) {
107
-			return key
108
-		}
109
-	}
110
-
111
-	return ""
112
-}
113
-
114
-// FindExistingContextName finds the nickname for the passed context
115
-func FindExistingContextName(haystack clientcmdapi.Config, needle clientcmdapi.Context) string {
116
-	for key, context := range haystack.Contexts {
117
-		if reflect.DeepEqual(context, needle) {
118
-			return key
119
-		}
120
-	}
121
-
122
-	return ""
123
-}
124
-
125
-func getMapKeys(theMap reflect.Value) (*util.StringSet, error) {
126
-	if theMap.Kind() != reflect.Map {
127
-		return nil, fmt.Errorf("theMap must be of type %v, not %v", reflect.Map, theMap.Kind())
128
-	}
129
-
130
-	ret := &util.StringSet{}
131
-
132
-	switch theMap.Kind() {
133
-	case reflect.Map:
134
-		for _, keyValue := range theMap.MapKeys() {
135
-			ret.Insert(keyValue.String())
136
-		}
137
-
138
-	}
139
-
140
-	return ret, nil
141
-
142
-}
... ...
@@ -157,7 +157,8 @@ func NewCmdRegistry(f *clientcmd.Factory, parentName, name string, out io.Writer
157 157
 				if len(cfg.Credentials) == 0 {
158 158
 					glog.Fatalf("You must specify a .kubeconfig file path containing credentials for connecting the registry to the master with --credentials")
159 159
 				}
160
-				credentials, err := (&kclientcmd.ClientConfigLoadingRules{CommandLinePath: cfg.Credentials}).Load()
160
+				clientConfigLoadingRules := &kclientcmd.ClientConfigLoadingRules{cfg.Credentials, []string{}}
161
+				credentials, err := clientConfigLoadingRules.Load()
161 162
 				if err != nil {
162 163
 					glog.Fatalf("The provided credentials %q could not be loaded: %v", cfg.Credentials, err)
163 164
 				}
... ...
@@ -21,7 +21,6 @@ import (
21 21
 	configcmd "github.com/openshift/origin/pkg/config/cmd"
22 22
 	dapi "github.com/openshift/origin/pkg/deploy/api"
23 23
 	"github.com/openshift/origin/pkg/generate/app"
24
-	//imageapi "github.com/openshift/origin/pkg/image/api"
25 24
 )
26 25
 
27 26
 const longDesc = `
... ...
@@ -148,7 +147,9 @@ func NewCmdRouter(f *clientcmd.Factory, parentName, name string, out io.Writer)
148 148
 				if len(cfg.Credentials) == 0 {
149 149
 					glog.Fatalf("You must specify a .kubeconfig file path containing credentials for connecting the router to the master with --credentials")
150 150
 				}
151
-				credentials, err := (&kclientcmd.ClientConfigLoadingRules{CommandLinePath: cfg.Credentials}).Load()
151
+
152
+				clientConfigLoadingRules := &kclientcmd.ClientConfigLoadingRules{cfg.Credentials, []string{}}
153
+				credentials, err := clientConfigLoadingRules.Load()
152 154
 				if err != nil {
153 155
 					glog.Fatalf("The provided credentials %q could not be loaded: %v", cfg.Credentials, err)
154 156
 				}
... ...
@@ -9,7 +9,6 @@ import (
9 9
 	"github.com/openshift/origin/pkg/cmd/cli"
10 10
 	"github.com/openshift/origin/pkg/cmd/experimental/config"
11 11
 	"github.com/openshift/origin/pkg/cmd/experimental/generate"
12
-	"github.com/openshift/origin/pkg/cmd/experimental/login"
13 12
 	"github.com/openshift/origin/pkg/cmd/experimental/policy"
14 13
 	"github.com/openshift/origin/pkg/cmd/experimental/project"
15 14
 	exregistry "github.com/openshift/origin/pkg/cmd/experimental/registry"
... ...
@@ -122,7 +121,6 @@ func newExperimentalCommand(parentName, name string) *cobra.Command {
122 122
 	experimental.AddCommand(tokens.NewCmdTokens(f, subName, "tokens"))
123 123
 	experimental.AddCommand(policy.NewCommandPolicy(f, subName, "policy"))
124 124
 	experimental.AddCommand(generate.NewCmdGenerate(f, subName, "generate"))
125
-	experimental.AddCommand(login.NewCmdLogin(f, subName, "login"))
126 125
 	experimental.AddCommand(exrouter.NewCmdRouter(f, subName, "router", os.Stdout))
127 126
 	experimental.AddCommand(exregistry.NewCmdRegistry(f, subName, "registry", os.Stdout))
128 127
 	return experimental
... ...
@@ -14,7 +14,6 @@ import (
14 14
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/client/record"
15 15
 	"github.com/coreos/go-systemd/daemon"
16 16
 	"github.com/golang/glog"
17
-
18 17
 	"github.com/openshift/origin/pkg/cmd/server/etcd"
19 18
 	"github.com/openshift/origin/pkg/cmd/server/kubernetes"
20 19
 	"github.com/openshift/origin/pkg/cmd/server/origin"
21 20
new file mode 100644
... ...
@@ -0,0 +1,29 @@
0
+package clientcmd
1
+
2
+import (
3
+	"net/http"
4
+
5
+	"github.com/golang/glog"
6
+)
7
+
8
+const (
9
+	unauthorizedErrorMessage = `Your session has expired. Use the following command to log in again:
10
+  osc login`
11
+)
12
+
13
+type statusHandlerClient struct {
14
+	delegate *http.Client
15
+}
16
+
17
+func (client *statusHandlerClient) Do(req *http.Request) (*http.Response, error) {
18
+	resp, err := client.delegate.Do(req)
19
+	if err != nil {
20
+		return nil, err
21
+	}
22
+
23
+	if resp.StatusCode == http.StatusUnauthorized {
24
+		glog.Fatal(unauthorizedErrorMessage)
25
+	}
26
+
27
+	return resp, err
28
+}
0 29
new file mode 100644
... ...
@@ -0,0 +1,54 @@
0
+package clientcmd
1
+
2
+import "strings"
3
+
4
+const (
5
+	unknownReason                     = 0
6
+	noServerFoundReason               = 1
7
+	certificateAuthorityUnknownReason = 2
8
+
9
+	certificateAuthorityUnknownMsg = "The server uses a certificate signed by unknown authority. You may need to use the --certificate-authority flag to provide the path to a certificate file for the certificate authority, or --insecure-skip-tls-verify to bypass the certificate check and use insecure connections."
10
+	notConfiguredMsg               = `OpenShift is not configured. You need to run the login command in order to create a default config for your server and credentials:
11
+  osc login
12
+You can also run this command again providing the path to a config file directly, either through the --config flag of the OPENSHIFTCONFIG environment variable.
13
+`
14
+)
15
+
16
+func GetPrettyMessageFor(err error) string {
17
+	if err == nil {
18
+		return ""
19
+	}
20
+
21
+	reason := detectReason(err)
22
+
23
+	switch reason {
24
+	case noServerFoundReason:
25
+		return notConfiguredMsg
26
+
27
+	case certificateAuthorityUnknownReason:
28
+		return certificateAuthorityUnknownMsg
29
+	}
30
+
31
+	return err.Error()
32
+}
33
+
34
+func IsNoServerFound(err error) bool {
35
+	return detectReason(err) == noServerFoundReason
36
+}
37
+
38
+func IsCertificateAuthorityUnknown(err error) bool {
39
+	return detectReason(err) == certificateAuthorityUnknownReason
40
+}
41
+
42
+func detectReason(err error) int {
43
+	if err != nil {
44
+		switch {
45
+		case strings.Contains(err.Error(), "certificate signed by unknown authority"):
46
+			return certificateAuthorityUnknownReason
47
+
48
+		case strings.Contains(err.Error(), "no server found for"):
49
+			return noServerFoundReason
50
+		}
51
+	}
52
+	return unknownReason
53
+}
... ...
@@ -2,7 +2,7 @@ package clientcmd
2 2
 
3 3
 import (
4 4
 	"fmt"
5
-	"os"
5
+	"net/http"
6 6
 
7 7
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
8 8
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
... ...
@@ -17,34 +17,39 @@ import (
17 17
 
18 18
 	"github.com/openshift/origin/pkg/api/latest"
19 19
 	"github.com/openshift/origin/pkg/client"
20
+	"github.com/openshift/origin/pkg/cmd/cli/config"
20 21
 	"github.com/openshift/origin/pkg/cmd/cli/describe"
21 22
 )
22 23
 
23
-const defaultClusterURL = "https://localhost:8443"
24
-
25 24
 // NewFactory creates a default Factory for commands that should share identical server
26 25
 // connection behavior. Most commands should use this method to get a factory.
27 26
 func New(flags *pflag.FlagSet) *Factory {
28
-	// Override global default to https and port 8443
29
-	clientcmd.DefaultCluster.Server = defaultClusterURL
27
+	// Override global default to "" so we force the client to ask for user input
28
+	// TODO refactor this usptream:
29
+	// DefaultCluster should not be a global
30
+	// A call to ClientConfig() should always return the best clientCfg possible
31
+	// even if an error was returned, and let the caller decide what to do
32
+	clientcmd.DefaultCluster.Server = ""
30 33
 
31 34
 	// TODO: there should be two client configs, one for OpenShift, and one for Kubernetes
32 35
 	clientConfig := DefaultClientConfig(flags)
33 36
 	f := NewFactory(clientConfig)
34 37
 	f.BindFlags(flags)
38
+
35 39
 	return f
36 40
 }
37 41
 
38 42
 // Copy of kubectl/cmd/DefaultClientConfig, using NewNonInteractiveDeferredLoadingClientConfig
39 43
 func DefaultClientConfig(flags *pflag.FlagSet) clientcmd.ClientConfig {
40
-	loadingRules := clientcmd.NewClientConfigLoadingRules()
41
-	loadingRules.EnvVarPath = os.Getenv(clientcmd.RecommendedConfigPathEnvVar)
42
-	flags.StringVar(&loadingRules.CommandLinePath, "kubeconfig", "", "Path to the kubeconfig file to use for CLI requests.")
44
+	loadingRules := config.NewOpenShiftClientConfigLoadingRules()
45
+
46
+	flags.StringVar(&loadingRules.CommandLinePath, config.OpenShiftConfigFlagName, "", "Path to the config file to use for CLI requests.")
43 47
 
44 48
 	overrides := &clientcmd.ConfigOverrides{}
45 49
 	overrideFlags := clientcmd.RecommendedConfigOverrideFlags("")
46 50
 	overrideFlags.ContextOverrideFlags.NamespaceShort = "n"
47 51
 	clientcmd.BindOverrideFlags(overrides, flags, overrideFlags)
52
+
48 53
 	clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)
49 54
 
50 55
 	return clientConfig
... ...
@@ -66,70 +71,79 @@ func NewFactory(clientConfig clientcmd.ClientConfig) *Factory {
66 66
 		return mapper, api.Scheme
67 67
 	}
68 68
 
69
-	// Save original RESTClient function
70
-	kRESTClientFunc := w.Factory.RESTClient
71 69
 	w.RESTClient = func(cmd *cobra.Command, mapping *meta.RESTMapping) (resource.RESTClient, error) {
70
+		oClient, kClient, err := w.Clients(cmd)
71
+		if err != nil {
72
+			return nil, fmt.Errorf("unable to create client %s: %v", mapping.Kind, err)
73
+		}
74
+
72 75
 		if latest.OriginKind(mapping.Kind, mapping.APIVersion) {
73
-			cfg, err := w.OpenShiftClientConfig.ClientConfig()
74
-			if err != nil {
75
-				return nil, fmt.Errorf("unable to find client config %s: %v", mapping.Kind, err)
76
-			}
77
-			cli, err := client.New(cfg)
78
-			if err != nil {
79
-				return nil, fmt.Errorf("unable to create client %s: %v", mapping.Kind, err)
80
-			}
81
-			return cli.RESTClient, nil
76
+			return oClient.RESTClient, nil
77
+		} else {
78
+			return kClient.RESTClient, nil
82 79
 		}
83
-		return kRESTClientFunc(cmd, mapping)
84 80
 	}
85 81
 
86
-	// Save original Describer function
87
-	kDescriberFunc := w.Factory.Describer
88 82
 	w.Describer = func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.Describer, error) {
83
+		oClient, kClient, err := w.Clients(cmd)
84
+		if err != nil {
85
+			return nil, fmt.Errorf("unable to create client %s: %v", mapping.Kind, err)
86
+		}
87
+
88
+		cfg, err := w.OpenShiftClientConfig.ClientConfig()
89
+		if err != nil {
90
+			return nil, fmt.Errorf("unable to describe %s: %v", mapping.Kind, err)
91
+		}
92
+
89 93
 		if latest.OriginKind(mapping.Kind, mapping.APIVersion) {
90
-			cfg, err := w.OpenShiftClientConfig.ClientConfig()
91
-			if err != nil {
92
-				return nil, fmt.Errorf("unable to describe %s: %v", mapping.Kind, err)
93
-			}
94
-			cli, err := client.New(cfg)
95
-			if err != nil {
96
-				return nil, fmt.Errorf("unable to describe %s: %v", mapping.Kind, err)
97
-			}
98
-			kubeClient, err := kclient.New(cfg)
99
-			if err != nil {
100
-				return nil, fmt.Errorf("unable to describe %s: %v", mapping.Kind, err)
101
-			}
102
-			describer, ok := describe.DescriberFor(mapping.Kind, cli, kubeClient, cfg.Host)
94
+			describer, ok := describe.DescriberFor(mapping.Kind, oClient, kClient, cfg.Host)
103 95
 			if !ok {
104 96
 				return nil, fmt.Errorf("no description has been implemented for %q", mapping.Kind)
105 97
 			}
106 98
 			return describer, nil
107 99
 		}
108
-		return kDescriberFunc(cmd, mapping)
100
+		return w.Factory.Describer(cmd, mapping)
109 101
 	}
110 102
 
111 103
 	w.Printer = func(cmd *cobra.Command, mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error) {
112 104
 		return describe.NewHumanReadablePrinter(noHeaders), nil
113 105
 	}
114 106
 
107
+	w.DefaultNamespace = func(cmd *cobra.Command) (string, error) {
108
+		return w.OpenShiftClientConfig.Namespace()
109
+	}
110
+
115 111
 	return w
116 112
 }
117 113
 
118 114
 // Clients returns an OpenShift and Kubernetes client.
119 115
 func (f *Factory) Clients(cmd *cobra.Command) (*client.Client, *kclient.Client, error) {
120
-	os, err := f.OpenShiftClientConfig.ClientConfig()
116
+	cfg, err := f.OpenShiftClientConfig.ClientConfig()
117
+	if err != nil {
118
+		return nil, nil, err
119
+	}
120
+
121
+	transport, err := kclient.TransportFor(cfg)
121 122
 	if err != nil {
122 123
 		return nil, nil, err
123 124
 	}
124
-	oc, err := client.New(os)
125
+	httpClient := &http.Client{
126
+		Transport: transport,
127
+	}
128
+
129
+	oClient, err := client.New(cfg)
125 130
 	if err != nil {
126 131
 		return nil, nil, err
127 132
 	}
128
-	kc, err := f.Client(cmd)
133
+	kClient, err := kclient.New(cfg)
129 134
 	if err != nil {
130 135
 		return nil, nil, err
131 136
 	}
132
-	return oc, kc, nil
137
+
138
+	oClient.Client = &statusHandlerClient{httpClient}
139
+	kClient.Client = &statusHandlerClient{httpClient}
140
+
141
+	return oClient, kClient, nil
133 142
 }
134 143
 
135 144
 // ShortcutExpander is a RESTMapper that can be used for OpenShift resources.
... ...
@@ -1,18 +1,16 @@
1 1
 package util
2 2
 
3 3
 import (
4
-	"os"
5
-
6 4
 	"github.com/spf13/pflag"
7 5
 
8 6
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
7
+	"github.com/openshift/origin/pkg/cmd/cli/config"
9 8
 )
10 9
 
11
-// Copy of kubectl/cmd/DefaultClientConfig, using NewNonInteractiveDeferredLoadingClientConfig
12 10
 func DefaultClientConfig(flags *pflag.FlagSet) clientcmd.ClientConfig {
13
-	loadingRules := clientcmd.NewClientConfigLoadingRules()
14
-	loadingRules.EnvVarPath = os.Getenv(clientcmd.RecommendedConfigPathEnvVar)
15
-	flags.StringVar(&loadingRules.CommandLinePath, "kubeconfig", "", "Path to the kubeconfig file to use for CLI requests.")
11
+	loadingRules := config.NewOpenShiftClientConfigLoadingRules()
12
+
13
+	flags.StringVar(&loadingRules.CommandLinePath, config.OpenShiftConfigFlagName, "", "Path to the config file to use for CLI requests.")
16 14
 
17 15
 	overrides := &clientcmd.ConfigOverrides{}
18 16
 	overrideFlags := clientcmd.RecommendedConfigOverrideFlags("")
... ...
@@ -1,20 +1,23 @@
1 1
 package util
2 2
 
3 3
 import (
4
+	"bufio"
4 5
 	"fmt"
5 6
 	"io"
6 7
 	"os"
8
+	"strings"
7 9
 
8 10
 	"github.com/docker/docker/pkg/term"
9 11
 	"github.com/golang/glog"
10 12
 )
11 13
 
14
+// Takes an io.Reader and prompt for user input if it's a terminal, returning the result.
12 15
 func PromptForString(r io.Reader, format string, a ...interface{}) string {
13 16
 	fmt.Printf(format, a...)
14 17
 	return readInput(r)
15 18
 }
16 19
 
17
-// TODO not tested on other platforms
20
+// Prompt for user input by disabling echo in terminal, useful for password prompt.
18 21
 func PromptForPasswordString(r io.Reader, format string, a ...interface{}) string {
19 22
 	if file, ok := r.(*os.File); ok {
20 23
 		inFd := file.Fd()
... ...
@@ -37,18 +40,50 @@ func PromptForPasswordString(r io.Reader, format string, a ...interface{}) strin
37 37
 			fmt.Printf("\n")
38 38
 
39 39
 			return input
40
-		} else {
41
-			glog.V(3).Infof("Stdin is not a terminal")
42
-			return PromptForString(r, format, a...)
43 40
 		}
44
-	} else {
45
-		glog.V(3).Infof("Unable to use a TTY")
41
+		glog.V(3).Infof("Stdin is not a terminal")
46 42
 		return PromptForString(r, format, a...)
47 43
 	}
44
+	return PromptForString(r, format, a...)
45
+}
46
+
47
+// Prompt for user input of a boolean value. The accepted values are:
48
+//   yes, y, true, 	t, 1 (not case sensitive)
49
+//   no, 	n, false, f, 0 (not case sensitive)
50
+// A valid answer is mandatory so it will keep asking until an answer is provided.
51
+func PromptForBool(r io.Reader, format string, a ...interface{}) bool {
52
+	str := PromptForString(r, format, a...)
53
+	switch strings.ToLower(str) {
54
+	case "1", "t", "true", "y", "yes":
55
+		return true
56
+	case "0", "f", "false", "n", "no":
57
+		return false
58
+	}
59
+	fmt.Println("Please enter 'yes' or 'no'.")
60
+	return PromptForBool(r, format, a...)
61
+}
62
+
63
+// Prompt for user input but take a default in case nothing is provided.
64
+func PromptForStringWithDefault(r io.Reader, def string, format string, a ...interface{}) string {
65
+	s := PromptForString(r, format, a...)
66
+	if len(s) == 0 {
67
+		return def
68
+	}
69
+	return s
48 70
 }
49 71
 
50 72
 func readInput(r io.Reader) string {
73
+	if IsTerminal(r) {
74
+		reader := bufio.NewReader(r)
75
+		result, _ := reader.ReadString('\n')
76
+		return strings.TrimSuffix(result, "\n")
77
+	}
51 78
 	var result string
52 79
 	fmt.Fscan(r, &result)
53 80
 	return result
54 81
 }
82
+
83
+func IsTerminal(r io.Reader) bool {
84
+	file, ok := r.(*os.File)
85
+	return ok && term.IsTerminal(file.Fd())
86
+}
... ...
@@ -40,7 +40,7 @@ func (client *challengingClient) Do(req *http.Request) (*http.Response, error) {
40 40
 			missingUsername := len(username) == 0
41 41
 			missingPassword := len(password) == 0
42 42
 
43
-			if (missingUsername || missingPassword) && (client.reader != nil) {
43
+			if (missingUsername || missingPassword) && client.reader != nil {
44 44
 				fmt.Printf("Authenticate for \"%v\"\n", realm)
45 45
 				if missingUsername {
46 46
 					username = util.PromptForString(client.reader, "Username: ")
... ...
@@ -7,10 +7,10 @@ import (
7 7
 	"regexp"
8 8
 
9 9
 	kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
10
-	"github.com/golang/glog"
11
-
12
-	"github.com/openshift/origin/pkg/auth/server/tokenrequest"
13 10
 	"github.com/openshift/origin/pkg/client"
11
+	"github.com/openshift/origin/pkg/oauth/server/osinserver"
12
+
13
+	server "github.com/openshift/origin/pkg/cmd/server/origin"
14 14
 )
15 15
 
16 16
 const accessTokenRedirectPattern = `#access_token=([\w]+)&`
... ...
@@ -45,15 +45,10 @@ func RequestToken(clientCfg *kclient.Config, reader io.Reader, defaultUsername s
45 45
 
46 46
 	osClient.Client = &challengingClient{httpClient, reader, defaultUsername, defaultPassword}
47 47
 
48
-	result := osClient.Get().AbsPath("oauth", "authorize").Param("response_type", "token").Param("client_id", "openshift-challenging-client").Do()
48
+	result := osClient.Get().AbsPath(server.OpenShiftOAuthAPIPrefix, osinserver.AuthorizePath).Param("response_type", "token").Param("client_id", "openshift-challenging-client").Do()
49 49
 
50 50
 	if len(tokenGetter.accessToken) == 0 {
51
-		if result.Error() != nil {
52
-			glog.Errorf("Error making server request: %v", result.Error())
53
-		}
54
-
55
-		requestTokenURL := clientCfg.Host + "/oauth" /* clean up after auth.go dies */ + tokenrequest.RequestTokenEndpoint
56
-		return "", errors.New("Unable to get token.  Try visiting " + requestTokenURL + " for a new token.")
51
+		return "", result.Error()
57 52
 	}
58 53
 
59 54
 	return tokenGetter.accessToken, nil
60 55
new file mode 100644
... ...
@@ -0,0 +1,147 @@
0
+// +build integration,!no-etcd
1
+
2
+package integration
3
+
4
+import (
5
+	"os"
6
+	"testing"
7
+
8
+	kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
9
+	"github.com/openshift/origin/pkg/client"
10
+	"github.com/openshift/origin/pkg/cmd/cli/cmd"
11
+	newproject "github.com/openshift/origin/pkg/cmd/experimental/project"
12
+	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
13
+	"github.com/openshift/origin/pkg/user/api"
14
+	"github.com/spf13/pflag"
15
+)
16
+
17
+func init() {
18
+	requireEtcd()
19
+}
20
+
21
+func TestLogin(t *testing.T) {
22
+	startConfig, err := StartTestMaster()
23
+	if err != nil {
24
+		t.Fatalf("unexpected error: %v", err)
25
+	}
26
+
27
+	openshiftClient, openshiftClientConfig, err := startConfig.GetOpenshiftClient()
28
+	if err != nil {
29
+		t.Fatalf("unexpected error: %v", err)
30
+	}
31
+
32
+	// empty config, should display message
33
+	loginOptions := newLoginOptions("", "", "", "", false)
34
+	err = loginOptions.GatherServerInfo()
35
+	if err == nil {
36
+		t.Errorf("Raw login should error out")
37
+	}
38
+
39
+	username := "joe"
40
+	password := "pass"
41
+	project := "the-singularity-is-near"
42
+	server := openshiftClientConfig.Host
43
+
44
+	loginOptions = newLoginOptions(server, username, password, "", true)
45
+
46
+	if err = loginOptions.GatherServerInfo(); err != nil {
47
+		t.Fatalf("Error trying to determine server info: ", err)
48
+	}
49
+
50
+	if err = loginOptions.GatherAuthInfo(); err != nil {
51
+		t.Fatalf("Error trying to determine auth info: ", err)
52
+	}
53
+
54
+	me, err := loginOptions.Whoami()
55
+	if err != nil {
56
+		t.Errorf("unexpected error: ", err)
57
+	}
58
+	if me.Name != "anypassword:"+username {
59
+		t.Fatalf("Unexpected user after authentication: %v", me.Name)
60
+	}
61
+
62
+	newProjectOptions := &newproject.NewProjectOptions{
63
+		Client:                openshiftClient,
64
+		ProjectName:           project,
65
+		AdminRole:             "admin",
66
+		MasterPolicyNamespace: "master",
67
+		AdminUser:             "anypassword:" + username,
68
+	}
69
+	if err := newProjectOptions.Run(); err != nil {
70
+		t.Fatalf("unexpected error, a project is required to continue: ", err)
71
+	}
72
+
73
+	oClient, _ := client.New(loginOptions.Config)
74
+	p, err := oClient.Projects().Get(project)
75
+	if err != nil {
76
+		t.Errorf("unexpected error: ", err)
77
+	}
78
+
79
+	if p.Name != project {
80
+		t.Fatalf("Got the unexpected project: %v", p.Name)
81
+	}
82
+
83
+	// TODO Commented because of incorrectly hitting cache when listing projects.
84
+	// Should be enabled again when cache eviction is properly fixed.
85
+
86
+	// err = loginOptions.GatherProjectInfo()
87
+	// if err != nil {
88
+	// 	t.Fatalf("unexpected error: ", err)
89
+	// }
90
+
91
+	// if loginOptions.Project != project {
92
+	// 	t.Fatalf("Expected project %v but got %v", project, loginOptions.Project)
93
+	// }
94
+
95
+	// configFile, err := ioutil.TempFile("", "openshiftconfig")
96
+	// if err != nil {
97
+	// 	t.Fatalf("unexpected error: %v", err)
98
+	// }
99
+	// defer os.Remove(configFile.Name())
100
+
101
+	// if _, err = loginOptions.SaveConfig(configFile.Name()); err != nil {
102
+	// 	t.Fatalf("unexpected error: ", err)
103
+	// }
104
+}
105
+
106
+func newLoginOptions(server string, username string, password string, context string, insecure bool) *cmd.LoginOptions {
107
+	flagset := pflag.NewFlagSet("test-flags", pflag.ContinueOnError)
108
+	factory := clientcmd.New(flagset)
109
+
110
+	flags := []string{}
111
+
112
+	if len(server) > 0 {
113
+		flags = append(flags, "--server="+server)
114
+	}
115
+	if len(context) > 0 {
116
+		flags = append(flags, "--context="+context)
117
+	}
118
+	if insecure {
119
+		flags = append(flags, "--insecure-skip-tls-verify")
120
+	}
121
+
122
+	flagset.Parse(flags)
123
+
124
+	loginOptions := &cmd.LoginOptions{
125
+		ClientConfig: factory.OpenShiftClientConfig,
126
+		Reader:       os.Stdin,
127
+		Username:     username,
128
+		Password:     password,
129
+	}
130
+
131
+	return loginOptions
132
+}
133
+
134
+func whoami(clientCfg *kclient.Config) (*api.User, error) {
135
+	oClient, err := client.New(clientCfg)
136
+	if err != nil {
137
+		return nil, err
138
+	}
139
+
140
+	me, err := oClient.Users().Get("~")
141
+	if err != nil {
142
+		return nil, err
143
+	}
144
+
145
+	return me, nil
146
+}