Browse code

add secrets subcommand to osc

deads2k authored on 2015/06/06 02:10:46
Showing 28 changed files
... ...
@@ -320,6 +320,20 @@ echo "templates: ok"
320 320
 [ "$(openshift admin TYPO; echo $? | grep '1')" ]
321 321
 [ "$(openshift cli TYPO; echo $? | grep '1')" ]
322 322
 [ "$(oc policy TYPO; echo $? | grep '1')" ]
323
+[ "$(oc secrets TYPO; echo $? | grep '1')" ]
324
+
325
+
326
+oc secrets new-dockercfg dockercfg --docker-username=sample-user --docker-password=sample-password --docker-email=fake@example.org
327
+# can't use a go template here because the output needs to be base64 decoded.  base64 isn't installed by default in all distros
328
+oc describe secrets/dockercfg | grep "dockercfg:" | awk '{print $2}' > ${HOME}/.dockercfg
329
+oc secrets new from-file ${HOME}/.dockercfg
330
+# check to make sure the type was correctly auto-detected
331
+[ "$(oc get secret/from-file -t "{{ .type }}" | grep "kubernetes.io/dockercfg")" ]
332
+# make sure the -o works correctly
333
+[ "$(oc secrets new-dockercfg dockercfg --docker-username=sample-user --docker-password=sample-password --docker-email=fake@example.org -o yaml | grep "kubernetes.io/dockercfg")" ]
334
+[ "$(oc secrets new from-file ${HOME}/.dockercfg -o yaml | grep "kubernetes.io/dockercfg")" ]
335
+echo "secrets: ok"
336
+
323 337
 
324 338
 oc get pods --match-server-version
325 339
 oc create -f examples/hello-openshift/hello-pod.json
... ...
@@ -42,7 +42,7 @@ func NewCommandAdmin(name, fullName string, out io.Writer) *cobra.Command {
42 42
 	f := clientcmd.New(cmds.PersistentFlags())
43 43
 
44 44
 	cmds.AddCommand(project.NewCmdNewProject(project.NewProjectRecommendedName, fullName+" "+project.NewProjectRecommendedName, f, out))
45
-	cmds.AddCommand(policy.NewCommandPolicy(policy.PolicyRecommendedName, fullName+" "+policy.PolicyRecommendedName, f, out))
45
+	cmds.AddCommand(policy.NewCmdPolicy(policy.PolicyRecommendedName, fullName+" "+policy.PolicyRecommendedName, f, out))
46 46
 	cmds.AddCommand(exipfailover.NewCmdIPFailoverConfig(f, fullName, "ipfailover", out))
47 47
 	cmds.AddCommand(router.NewCmdRouter(f, fullName, "router", out))
48 48
 	cmds.AddCommand(registry.NewCmdRegistry(f, fullName, "registry", out))
... ...
@@ -20,8 +20,8 @@ import (
20 20
 
21 21
 const PolicyRecommendedName = "policy"
22 22
 
23
-// NewCommandPolicy implements the OpenShift cli policy command
24
-func NewCommandPolicy(name, fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command {
23
+// NewCmdPolicy implements the OpenShift cli policy command
24
+func NewCmdPolicy(name, fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command {
25 25
 	// Parent command to which all subcommands are added.
26 26
 	cmds := &cobra.Command{
27 27
 		Use:   name,
... ...
@@ -13,6 +13,7 @@ import (
13 13
 
14 14
 	"github.com/openshift/origin/pkg/cmd/cli/cmd"
15 15
 	"github.com/openshift/origin/pkg/cmd/cli/policy"
16
+	"github.com/openshift/origin/pkg/cmd/cli/secrets"
16 17
 	"github.com/openshift/origin/pkg/cmd/templates"
17 18
 	cmdutil "github.com/openshift/origin/pkg/cmd/util"
18 19
 	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
... ...
@@ -117,7 +118,8 @@ func NewCommandCLI(name, fullName string) *cobra.Command {
117 117
 				cmd.NewCmdUpdate(fullName, f, out),
118 118
 				cmd.NewCmdProcess(fullName, f, out),
119 119
 				cmd.NewCmdExport(fullName, f, os.Stdin, out),
120
-				policy.NewCommandPolicy(policy.PolicyRecommendedName, fullName+" "+policy.PolicyRecommendedName, f, out),
120
+				policy.NewCmdPolicy(policy.PolicyRecommendedName, fullName+" "+policy.PolicyRecommendedName, f, out),
121
+				secrets.NewCmdSecrets(secrets.SecretsRecommendedName, fullName+" "+secrets.SecretsRecommendedName, f, out, fullName+" edit"),
121 122
 			},
122 123
 		},
123 124
 		{
... ...
@@ -2,7 +2,6 @@ package policy
2 2
 
3 3
 import (
4 4
 	"io"
5
-	"os"
6 5
 
7 6
 	"github.com/spf13/cobra"
8 7
 
... ...
@@ -13,7 +12,7 @@ import (
13 13
 
14 14
 const PolicyRecommendedName = "policy"
15 15
 
16
-func NewCommandPolicy(name, fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command {
16
+func NewCmdPolicy(name, fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command {
17 17
 	// Parent command to which all subcommands are added.
18 18
 	cmds := &cobra.Command{
19 19
 		Use:   name,
... ...
@@ -33,10 +32,3 @@ func NewCommandPolicy(name, fullName string, f *clientcmd.Factory, out io.Writer
33 33
 
34 34
 	return cmds
35 35
 }
36
-
37
-func runHelp(cmd *cobra.Command, args []string) {
38
-	cmd.Help()
39
-
40
-	// make sure that we exit with non-zero status so that typoed commands don't look like they were successful
41
-	os.Exit(1)
42
-}
43 36
new file mode 120000
... ...
@@ -0,0 +1 @@
0
+.
0 1
\ No newline at end of file
1 2
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+this is a test file
0 1
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+this is a file in a directory
0 1
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+this filename is invalid DNS Subdomain
0 1
new file mode 100644
1 2
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+this is a multiple file named www.google.com 
0 1
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+this is a valid file
0 1
new file mode 100644
... ...
@@ -0,0 +1,125 @@
0
+package secrets
1
+
2
+import (
3
+	"io/ioutil"
4
+	"testing"
5
+
6
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
7
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
8
+)
9
+
10
+func TestValidate(t *testing.T) {
11
+	tests := []struct {
12
+		testName string
13
+		args     []string
14
+		expErr   bool
15
+	}{
16
+		{
17
+			testName: "validArgs",
18
+			args:     []string{"testSecret", "./bsFixtures/www.google.com"},
19
+		},
20
+		{
21
+			testName: "noName",
22
+			args:     []string{"./bsFixtures/www.google.com"},
23
+			expErr:   true, //"Secret name is required"
24
+		},
25
+		{
26
+			testName: "noFilesPassed",
27
+			args:     []string{"testSecret"},
28
+			expErr:   true, //"At least one source file or directory must be specified"
29
+		},
30
+	}
31
+
32
+	for _, test := range tests {
33
+		options := NewCreateSecretOptions()
34
+		options.Complete(test.args, nil)
35
+		err := options.Validate()
36
+		if err != nil && !test.expErr {
37
+			t.Errorf("%s: unexpected error: %v", test.testName, err)
38
+		}
39
+	}
40
+}
41
+
42
+func TestCreateSecret(t *testing.T) {
43
+	tests := []struct {
44
+		testName string
45
+		args     []string
46
+		expErr   bool
47
+		quiet    bool
48
+	}{
49
+		{
50
+			testName: "validSources",
51
+			args:     []string{"testSecret", "./bsFixtures/www.google.com", "./bsFixtures/dirNoSubdir"},
52
+		},
53
+		{
54
+			testName: "invalidDNS",
55
+			args:     []string{"testSecret", "./bsFixtures/invalid/invalid-DNS"},
56
+			expErr:   true, // "/bsFixtures/invalid-DNS cannot be used as a key in a secret"
57
+		},
58
+		{
59
+			testName: "leadingDotsAllowed",
60
+			args:     []string{"testSecret", "./bsFixtures/leadingdot/.dockercfg"},
61
+		},
62
+		{
63
+			testName: "filesSameName",
64
+			args:     []string{"testSecret", "./bsFixtures/www.google.com", "./bsFixtures/multiple/www.google.com"},
65
+			expErr:   true, // "Multiple files with the same name (www.google.com) cannot be included a secret"
66
+		},
67
+		{
68
+			testName: "testQuietTrue",
69
+			args:     []string{"testSecret", "./bsFixtures/dir"},
70
+			quiet:    true,
71
+		},
72
+		{
73
+			testName: "testQuietFalse",
74
+			args:     []string{"testSecret", "./bsFixtures/dir"},
75
+			expErr:   true, // "Skipping resource <resource path>"
76
+		},
77
+	}
78
+	for _, test := range tests {
79
+		options := NewCreateSecretOptions()
80
+		options.Complete(test.args, nil)
81
+		options.Quiet = test.quiet
82
+
83
+		err := options.Validate()
84
+		if err != nil {
85
+			t.Errorf("unexpected error")
86
+		}
87
+		_, err = options.BundleSecret()
88
+		if err != nil && !test.expErr {
89
+			t.Errorf("%s: unexpected error: %s", test.testName, err)
90
+		}
91
+	}
92
+}
93
+
94
+func TestSecretTypeSpecified(t *testing.T) {
95
+	options := CreateSecretOptions{
96
+		Name:           "any",
97
+		SecretTypeName: string(kapi.SecretTypeDockercfg),
98
+		Sources:        util.StringList([]string{"./bsFixtures/www.google.com"}),
99
+		Stderr:         ioutil.Discard,
100
+	}
101
+
102
+	secret, err := options.BundleSecret()
103
+	if err != nil {
104
+		t.Errorf("unexpected error: %v", err)
105
+	}
106
+	if secret.Type != kapi.SecretTypeDockercfg {
107
+		t.Errorf("expected %v, got %v", kapi.SecretTypeDockercfg, secret.Type)
108
+	}
109
+}
110
+func TestSecretTypeDiscovered(t *testing.T) {
111
+	options := CreateSecretOptions{
112
+		Name:    "any",
113
+		Sources: util.StringList([]string{"./bsFixtures/leadingdot/.dockercfg"}),
114
+		Stderr:  ioutil.Discard,
115
+	}
116
+
117
+	secret, err := options.BundleSecret()
118
+	if err != nil {
119
+		t.Errorf("unexpected error: %v", err)
120
+	}
121
+	if secret.Type != kapi.SecretTypeDockercfg {
122
+		t.Errorf("expected %v, got %v", kapi.SecretTypeDockercfg, secret.Type)
123
+	}
124
+}
0 125
new file mode 100644
... ...
@@ -0,0 +1,197 @@
0
+package secrets
1
+
2
+import (
3
+	"encoding/json"
4
+	"errors"
5
+	"fmt"
6
+	"io"
7
+	"io/ioutil"
8
+	"strings"
9
+
10
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
11
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
12
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/credentialprovider"
13
+	cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util"
14
+
15
+	"github.com/spf13/cobra"
16
+)
17
+
18
+const (
19
+	CreateDockerConfigSecretRecommendedName = "new-dockercfg"
20
+
21
+	createDockercfgLong = `Create a new dockercfg secret
22
+
23
+Dockercfg secrets are used to authenticate against Docker registries.
24
+
25
+When using the Docker command line to push images, you can authenticate to a given registry by running 
26
+  'docker login DOCKER_REGISTRY_SERVER --username=DOCKER_USER --password=DOCKER_PASSWORD --email=DOCKER_EMAIL'.
27
+That produces a ~/.dockercfg file that is used by subsequent 'docker push' and 'docker pull' commands to authenticate to the registry.
28
+
29
+When using OpenShift, you may have a Docker registry that requires authentication.  In order for the nodes to pull images on your behalf, they have to have the credentials.  You can provide this information by creating a dockercfg secret and attaching it to your service account.
30
+
31
+If you don't already have a .dockercfg file, you can create a dockercfg secret directly by using:
32
+
33
+  $ %s SECRET_NAME --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL
34
+
35
+If you do already have a .dockercfg file, you can create a dockercfg secret by using:
36
+
37
+  $ %s SECRET_NAME path/to/.dockercfg
38
+
39
+You can then use '%s SERVICE_ACCOUNT' to add the new secret to 'imagePullSecrets' for the node to use or 'secrets' for builds to use.
40
+`
41
+)
42
+
43
+type CreateDockerConfigOptions struct {
44
+	SecretNamespace  string
45
+	SecretName       string
46
+	RegistryLocation string
47
+	Username         string
48
+	Password         string
49
+	EmailAddress     string
50
+
51
+	SecretsInterface client.SecretsInterface
52
+
53
+	Out io.Writer
54
+}
55
+
56
+// NewCmdCreateDockerConfigSecret creates a command object for making a dockercfg secret
57
+func NewCmdCreateDockerConfigSecret(name, fullName string, f *cmdutil.Factory, out io.Writer, newSecretFullName, ocEditFullName string) *cobra.Command {
58
+	o := &CreateDockerConfigOptions{Out: out}
59
+
60
+	cmd := &cobra.Command{
61
+		Use:   fmt.Sprintf("%s SECRET_NAME --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL", name),
62
+		Short: "Create a new dockercfg secret",
63
+		Long:  fmt.Sprintf(createDockercfgLong, fullName, newSecretFullName, ocEditFullName),
64
+		Run: func(c *cobra.Command, args []string) {
65
+			if err := o.Complete(f, args); err != nil {
66
+				cmdutil.CheckErr(err)
67
+			}
68
+
69
+			if err := o.Validate(); err != nil {
70
+				cmdutil.CheckErr(err)
71
+			}
72
+
73
+			if len(cmdutil.GetFlagString(c, "output")) != 0 {
74
+				secret, err := o.MakeDockerSecret()
75
+				cmdutil.CheckErr(err)
76
+
77
+				cmdutil.CheckErr(f.PrintObject(c, secret, out))
78
+				return
79
+			}
80
+
81
+			if err := o.CreateDockerSecret(); err != nil {
82
+				cmdutil.CheckErr(err)
83
+			}
84
+
85
+		},
86
+	}
87
+
88
+	cmd.Flags().StringVar(&o.Username, "docker-username", "", "username for Docker registry authentication")
89
+	cmd.Flags().StringVar(&o.Password, "docker-password", "", "password for Docker registry authentication")
90
+	cmd.Flags().StringVar(&o.EmailAddress, "docker-email", "", "email for Docker registry")
91
+	cmd.Flags().StringVar(&o.RegistryLocation, "docker-server", "https://index.docker.io/v1/", "server location for Docker registry")
92
+	cmdutil.AddPrinterFlags(cmd)
93
+
94
+	return cmd
95
+}
96
+
97
+func (o CreateDockerConfigOptions) CreateDockerSecret() error {
98
+	secret, err := o.MakeDockerSecret()
99
+	if err != nil {
100
+		return err
101
+	}
102
+
103
+	if _, err := o.SecretsInterface.Create(secret); err != nil {
104
+		return err
105
+	}
106
+
107
+	fmt.Fprintf(o.GetOut(), "secret/%s\n", secret.Name)
108
+
109
+	return nil
110
+}
111
+
112
+func (o CreateDockerConfigOptions) MakeDockerSecret() (*api.Secret, error) {
113
+	if err := o.Validate(); err != nil {
114
+		return nil, err
115
+	}
116
+
117
+	dockercfgAuth := credentialprovider.DockerConfigEntry{
118
+		Username: o.Username,
119
+		Password: o.Password,
120
+		Email:    o.EmailAddress,
121
+	}
122
+
123
+	dockerCfg := map[string]credentialprovider.DockerConfigEntry{o.RegistryLocation: dockercfgAuth}
124
+
125
+	dockercfgContent, err := json.Marshal(dockerCfg)
126
+	if err != nil {
127
+		return nil, err
128
+	}
129
+
130
+	secret := &api.Secret{}
131
+	secret.Namespace = o.SecretNamespace
132
+	secret.Name = o.SecretName
133
+	secret.Type = api.SecretTypeDockercfg
134
+	secret.Data = map[string][]byte{}
135
+	secret.Data[api.DockerConfigKey] = dockercfgContent
136
+
137
+	return secret, nil
138
+}
139
+
140
+func (o *CreateDockerConfigOptions) Complete(f *cmdutil.Factory, args []string) error {
141
+	if len(args) != 1 {
142
+		return errors.New("must have exactly one argument: secret name")
143
+	}
144
+	o.SecretName = args[0]
145
+
146
+	client, err := f.Client()
147
+	if err != nil {
148
+		return err
149
+	}
150
+	o.SecretNamespace, err = f.DefaultNamespace()
151
+	if err != nil {
152
+		return err
153
+	}
154
+
155
+	o.SecretsInterface = client.Secrets(o.SecretNamespace)
156
+
157
+	return nil
158
+}
159
+
160
+func (o CreateDockerConfigOptions) Validate() error {
161
+	if len(o.SecretNamespace) == 0 {
162
+		return errors.New("SecretNamespace must be present")
163
+	}
164
+	if len(o.SecretName) == 0 {
165
+		return errors.New("secret name must be present")
166
+	}
167
+	if len(o.RegistryLocation) == 0 {
168
+		return errors.New("docker-server must be present")
169
+	}
170
+	if len(o.Username) == 0 {
171
+		return errors.New("docker-username must be present")
172
+	}
173
+	if len(o.Password) == 0 {
174
+		return errors.New("docker-password must be present")
175
+	}
176
+	if len(o.EmailAddress) == 0 {
177
+		return errors.New("docker-email must be present")
178
+	}
179
+	if o.SecretsInterface == nil {
180
+		return errors.New("SecretsInterface must be present")
181
+	}
182
+
183
+	if strings.Contains(o.Username, ":") {
184
+		return fmt.Errorf("username '%v' is illegal because it contains a ':'", o.Username)
185
+	}
186
+
187
+	return nil
188
+}
189
+
190
+func (o CreateDockerConfigOptions) GetOut() io.Writer {
191
+	if o.Out == nil {
192
+		return ioutil.Discard
193
+	}
194
+
195
+	return o.Out
196
+}
0 197
new file mode 100644
... ...
@@ -0,0 +1,28 @@
0
+package secrets
1
+
2
+import (
3
+	"reflect"
4
+
5
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
6
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
7
+)
8
+
9
+type KnownSecretType struct {
10
+	Type             kapi.SecretType
11
+	RequiredContents util.StringSet
12
+}
13
+
14
+func (ks KnownSecretType) Matches(secretContent map[string][]byte) bool {
15
+	if secretContent == nil {
16
+		return false
17
+	}
18
+
19
+	secretKeys := util.KeySet(reflect.ValueOf(secretContent))
20
+	return reflect.DeepEqual(ks.RequiredContents.List(), secretKeys.List())
21
+}
22
+
23
+var (
24
+	KnownSecretTypes = []KnownSecretType{
25
+		{kapi.SecretTypeDockercfg, util.NewStringSet(kapi.DockerConfigKey)},
26
+	}
27
+)
0 28
new file mode 100644
... ...
@@ -0,0 +1,230 @@
0
+package secrets
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+	"io"
6
+	"io/ioutil"
7
+	"os"
8
+	"path"
9
+
10
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
11
+	kvalidation "github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
12
+	kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
13
+	cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util"
14
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
15
+
16
+	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
17
+	"github.com/spf13/cobra"
18
+)
19
+
20
+const NewSecretRecommendedCommandName = "new"
21
+
22
+type CreateSecretOptions struct {
23
+	// Name of the resulting secret
24
+	Name string
25
+
26
+	// SecretTypeName is the type to use when creating the secret.  It is checked against known types.
27
+	SecretTypeName string
28
+
29
+	// Files/Directories to read from.
30
+	// Directory sources are listed and any direct file children included (but subfolders are not traversed)
31
+	Sources util.StringList
32
+
33
+	SecretsInterface kclient.SecretsInterface
34
+
35
+	// Writer to write warnings to
36
+	Stderr io.Writer
37
+
38
+	Out io.Writer
39
+
40
+	// Controls whether to output warnings
41
+	Quiet bool
42
+}
43
+
44
+func NewCmdCreateSecret(name, fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command {
45
+	options := NewCreateSecretOptions()
46
+	options.Out = out
47
+
48
+	cmd := &cobra.Command{
49
+		Use:   fmt.Sprintf("%s NAME SOURCE [SOURCE ...]", name),
50
+		Short: "Create a new secret based on a file or files within a directory",
51
+		Long: fmt.Sprintf(`Create a new secret based on a file or files within a directory.
52
+
53
+  $ %s <secret-name> <source> [<source>...]
54
+		`, fullName),
55
+		Run: func(c *cobra.Command, args []string) {
56
+			cmdutil.CheckErr(options.Complete(args, f))
57
+
58
+			cmdutil.CheckErr(options.Validate())
59
+
60
+			if len(cmdutil.GetFlagString(c, "output")) != 0 {
61
+				secret, err := options.BundleSecret()
62
+				cmdutil.CheckErr(err)
63
+
64
+				cmdutil.CheckErr(f.Factory.PrintObject(c, secret, out))
65
+				return
66
+			}
67
+			_, err := options.CreateSecret()
68
+			cmdutil.CheckErr(err)
69
+		},
70
+	}
71
+
72
+	cmd.Flags().BoolVarP(&options.Quiet, "quiet", "q", options.Quiet, "Suppress warnings")
73
+	cmd.Flags().VarP(&options.Sources, "source", "f", "List of filenames or directories to populate the data elements in a secret")
74
+	cmd.Flags().StringVar(&options.SecretTypeName, "type", "", "The type of secret")
75
+	cmdutil.AddPrinterFlags(cmd)
76
+
77
+	return cmd
78
+}
79
+
80
+func NewCreateSecretOptions() *CreateSecretOptions {
81
+	return &CreateSecretOptions{
82
+		Stderr:  os.Stderr,
83
+		Sources: util.StringList{},
84
+	}
85
+}
86
+
87
+func (o *CreateSecretOptions) Complete(args []string, f *clientcmd.Factory) error {
88
+	// Fill name from args[0]
89
+	if len(args) > 0 {
90
+		o.Name = args[0]
91
+	}
92
+
93
+	// Add sources from args[1:...] in addition to -f
94
+	if len(args) > 1 {
95
+		o.Sources = append(o.Sources, args[1:]...)
96
+	}
97
+
98
+	if f != nil {
99
+		_, kubeClient, err := f.Clients()
100
+		if err != nil {
101
+			return err
102
+		}
103
+		namespace, err := f.Factory.DefaultNamespace()
104
+		if err != nil {
105
+			return err
106
+		}
107
+		o.SecretsInterface = kubeClient.Secrets(namespace)
108
+	}
109
+
110
+	return nil
111
+}
112
+
113
+func (o *CreateSecretOptions) Validate() error {
114
+	if len(o.Name) == 0 {
115
+		return errors.New("Secret name is required")
116
+	}
117
+	if len(o.Sources) == 0 {
118
+		return errors.New("At least one source file or directory must be specified")
119
+	}
120
+
121
+nameCheck:
122
+	switch o.SecretTypeName {
123
+	case string(kapi.SecretTypeOpaque), "":
124
+		// this is ok
125
+	default:
126
+		// TODO this probably isn't a good idea.  It limits the power of this command.  Maybe allow unknown names with a force?
127
+		for _, secretType := range KnownSecretTypes {
128
+			if o.SecretTypeName == string(secretType.Type) {
129
+				break nameCheck
130
+			}
131
+		}
132
+		return fmt.Errorf("unknown secret type: %v", o.SecretTypeName)
133
+	}
134
+
135
+	return nil
136
+}
137
+
138
+func (o *CreateSecretOptions) CreateSecret() (*kapi.Secret, error) {
139
+	secret, err := o.BundleSecret()
140
+	if err != nil {
141
+		return nil, err
142
+	}
143
+
144
+	persistedSecret, err := o.SecretsInterface.Create(secret)
145
+	if err == nil {
146
+		fmt.Fprintf(o.Out, "secret/%s\n", persistedSecret.Name)
147
+	}
148
+
149
+	return persistedSecret, err
150
+}
151
+
152
+func (o *CreateSecretOptions) BundleSecret() (*kapi.Secret, error) {
153
+	secretData := make(map[string][]byte)
154
+
155
+	for _, source := range o.Sources {
156
+		info, err := os.Stat(source)
157
+		if err != nil {
158
+			switch err := err.(type) {
159
+			case *os.PathError:
160
+				return nil, fmt.Errorf("Error reading %s: %v", source, err.Err)
161
+			default:
162
+				return nil, fmt.Errorf("Error reading %s: %v", source, err)
163
+			}
164
+		}
165
+
166
+		if info.IsDir() {
167
+			fileList, err := ioutil.ReadDir(source)
168
+			if err != nil {
169
+				return nil, fmt.Errorf("Error listing files in %s: %v", source, err)
170
+			}
171
+
172
+			for _, item := range fileList {
173
+				itemPath := path.Join(source, item.Name())
174
+				if !item.Mode().IsRegular() {
175
+					if o.Stderr != nil && o.Quiet != true {
176
+						fmt.Fprintf(o.Stderr, "Skipping resource %s\n", itemPath)
177
+					}
178
+				} else {
179
+					if err := readFile(itemPath, secretData); err != nil {
180
+						return nil, err
181
+					}
182
+				}
183
+			}
184
+		} else if err := readFile(source, secretData); err != nil {
185
+			return nil, err
186
+		}
187
+	}
188
+
189
+	if len(secretData) == 0 {
190
+		return nil, errors.New("No files selected")
191
+	}
192
+
193
+	// if the secret type isn't specified, attempt to auto-detect likely hit
194
+	secretType := kapi.SecretType(o.SecretTypeName)
195
+	if len(o.SecretTypeName) == 0 {
196
+		secretType = kapi.SecretTypeOpaque
197
+
198
+		for _, knownSecretType := range KnownSecretTypes {
199
+			if knownSecretType.Matches(secretData) {
200
+				secretType = knownSecretType.Type
201
+			}
202
+		}
203
+	}
204
+
205
+	secret := &kapi.Secret{
206
+		ObjectMeta: kapi.ObjectMeta{Name: o.Name},
207
+		Type:       secretType,
208
+		Data:       secretData,
209
+	}
210
+
211
+	return secret, nil
212
+}
213
+
214
+func readFile(filePath string, dataMap map[string][]byte) error {
215
+	fileName := path.Base(filePath)
216
+	if !kvalidation.IsSecretKey(fileName) {
217
+		return fmt.Errorf("%s cannot be used as a key in a secret", filePath)
218
+	}
219
+	if _, exists := dataMap[fileName]; exists {
220
+		return fmt.Errorf("Multiple files with the same name (%s) cannot be included in a secret", fileName)
221
+	}
222
+
223
+	data, err := ioutil.ReadFile(filePath)
224
+	if err != nil {
225
+		return err
226
+	}
227
+	dataMap[fileName] = data
228
+	return nil
229
+}
0 230
new file mode 100644
... ...
@@ -0,0 +1,36 @@
0
+package secrets
1
+
2
+import (
3
+	"io"
4
+
5
+	"github.com/spf13/cobra"
6
+
7
+	cmdutil "github.com/openshift/origin/pkg/cmd/util"
8
+	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
9
+)
10
+
11
+const SecretsRecommendedName = "secrets"
12
+
13
+const (
14
+	secretsLong = `Manage secrets in your project.
15
+
16
+Secrets are used to store confidential information that should not be contained inside of an image.
17
+They are commonly used to hold things like keys for authentication to other internal systems like 
18
+Docker registries.`
19
+)
20
+
21
+func NewCmdSecrets(name, fullName string, f *clientcmd.Factory, out io.Writer, ocEditFullName string) *cobra.Command {
22
+	// Parent command to which all subcommands are added.
23
+	cmds := &cobra.Command{
24
+		Use:   name,
25
+		Short: "Manage secrets",
26
+		Long:  secretsLong,
27
+		Run:   cmdutil.DefaultSubCommandRun(out),
28
+	}
29
+
30
+	newSecretFullName := fullName + " " + NewSecretRecommendedCommandName
31
+	cmds.AddCommand(NewCmdCreateSecret(NewSecretRecommendedCommandName, newSecretFullName, f, out))
32
+	cmds.AddCommand(NewCmdCreateDockerConfigSecret(CreateDockerConfigSecretRecommendedName, fullName+" "+CreateDockerConfigSecretRecommendedName, f.Factory, out, newSecretFullName, ocEditFullName))
33
+
34
+	return cmds
35
+}
0 36
deleted file mode 120000
... ...
@@ -1 +0,0 @@
1
-.
2 1
\ No newline at end of file
3 2
deleted file mode 100644
... ...
@@ -1 +0,0 @@
1
-this is a test file
2 1
deleted file mode 100644
... ...
@@ -1 +0,0 @@
1
-this is a file in a directory
2 1
deleted file mode 100644
... ...
@@ -1 +0,0 @@
1
-this filename is invalid DNS Subdomain
2 1
deleted file mode 100644
3 2
deleted file mode 100644
... ...
@@ -1 +0,0 @@
1
-this is a multiple file named www.google.com 
2 1
deleted file mode 100644
... ...
@@ -1 +0,0 @@
1
-this is a valid file
2 1
deleted file mode 100644
... ...
@@ -1,205 +0,0 @@
1
-package bundlesecret
2
-
3
-import (
4
-	"errors"
5
-	"fmt"
6
-	"io"
7
-	"io/ioutil"
8
-	"os"
9
-	"path"
10
-
11
-	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
12
-	kvalidation "github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
13
-	cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util"
14
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
15
-
16
-	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
17
-	"github.com/spf13/cobra"
18
-)
19
-
20
-type CreateSecretOptions struct {
21
-	// Name of the resulting secret
22
-	Name string
23
-
24
-	// SecretTypeName is the type to use when creating the secret.  It is checked against known types.
25
-	SecretTypeName string
26
-
27
-	// Files/Directories to read from.
28
-	// Directory sources are listed and any direct file children included (but subfolders are not traversed)
29
-	Sources util.StringList
30
-
31
-	// Writer to write warnings to
32
-	Stderr io.Writer
33
-	// Controls whether to output warnings
34
-	Quiet bool
35
-}
36
-
37
-func NewCmdBundleSecret(f *clientcmd.Factory, parentName, name string, out io.Writer) *cobra.Command {
38
-
39
-	options := NewDefaultOptions()
40
-
41
-	cmd := &cobra.Command{
42
-		Use:   fmt.Sprintf("%s NAME SOURCE [SOURCE ...]", name),
43
-		Short: "Bundle files (or files within directories) into a Kubernetes secret",
44
-		Long: fmt.Sprintf(`Bundle files (or files within directories) into a Kubernetes secret.
45
-
46
-  $ %s %s <secret-name> <source> [<source>...]
47
-		`, parentName, name),
48
-		Run: func(c *cobra.Command, args []string) {
49
-			cmdutil.CheckErr(options.Complete(args))
50
-
51
-			err := options.Validate()
52
-			if err != nil {
53
-				fmt.Fprintf(c.Out(), "Error: %v\n\n", err.Error())
54
-				c.Help()
55
-				return
56
-			}
57
-
58
-			secret, err := options.CreateSecret()
59
-			if err != nil {
60
-				cmdutil.CheckErr(err)
61
-			}
62
-
63
-			err = f.Factory.PrintObject(c, secret, out)
64
-			if err != nil {
65
-				cmdutil.CheckErr(err)
66
-			}
67
-		},
68
-	}
69
-
70
-	cmd.Flags().BoolVarP(&options.Quiet, "quiet", "q", options.Quiet, "Suppress warnings")
71
-	cmd.Flags().VarP(&options.Sources, "source", "f", "List of filenames or directories to use as sources of Kubernetes Secret.Data")
72
-	cmd.Flags().StringVar(&options.SecretTypeName, "type", "", "The type of secret")
73
-	cmdutil.AddPrinterFlags(cmd)
74
-
75
-	// Default to JSON
76
-	if flag := cmd.Flags().Lookup("output"); flag != nil {
77
-		flag.Value.Set("json")
78
-	}
79
-
80
-	return cmd
81
-}
82
-
83
-func NewDefaultOptions() *CreateSecretOptions {
84
-	return &CreateSecretOptions{
85
-		Stderr:  os.Stderr,
86
-		Sources: util.StringList{},
87
-	}
88
-}
89
-
90
-func (o *CreateSecretOptions) Complete(args []string) error {
91
-	// Fill name from args[0]
92
-	if len(args) > 0 {
93
-		o.Name = args[0]
94
-	}
95
-
96
-	// Add sources from args[1:...] in addition to -f
97
-	if len(args) > 1 {
98
-		o.Sources = append(o.Sources, args[1:]...)
99
-	}
100
-
101
-	return nil
102
-}
103
-
104
-func (o *CreateSecretOptions) Validate() error {
105
-	if len(o.Name) == 0 {
106
-		return errors.New("Secret name is required")
107
-	}
108
-	if len(o.Sources) == 0 {
109
-		return errors.New("At least one source file or directory must be specified")
110
-	}
111
-
112
-nameCheck:
113
-	switch o.SecretTypeName {
114
-	case string(kapi.SecretTypeOpaque), "":
115
-		// this is ok
116
-	default:
117
-		for _, secretType := range KnownSecretTypes {
118
-			if o.SecretTypeName == string(secretType.Type) {
119
-				break nameCheck
120
-			}
121
-		}
122
-		return fmt.Errorf("unknown secret type: %v", o.SecretTypeName)
123
-	}
124
-
125
-	return nil
126
-}
127
-
128
-func (o *CreateSecretOptions) CreateSecret() (*kapi.Secret, error) {
129
-	secretData := make(map[string][]byte)
130
-
131
-	for _, source := range o.Sources {
132
-		info, err := os.Stat(source)
133
-		if err != nil {
134
-			switch err := err.(type) {
135
-			case *os.PathError:
136
-				return nil, fmt.Errorf("Error reading %s: %v", source, err.Err)
137
-			default:
138
-				return nil, fmt.Errorf("Error reading %s: %v", source, err)
139
-			}
140
-		}
141
-
142
-		if info.IsDir() {
143
-			fileList, err := ioutil.ReadDir(source)
144
-			if err != nil {
145
-				return nil, fmt.Errorf("Error listing files in %s: %v", source, err)
146
-			}
147
-
148
-			for _, item := range fileList {
149
-				itemPath := path.Join(source, item.Name())
150
-				if !item.Mode().IsRegular() {
151
-					if o.Stderr != nil && o.Quiet != true {
152
-						fmt.Fprintf(o.Stderr, "Skipping resource %s\n", itemPath)
153
-					}
154
-				} else {
155
-					if err := readFile(itemPath, secretData); err != nil {
156
-						return nil, err
157
-					}
158
-				}
159
-			}
160
-		} else if err := readFile(source, secretData); err != nil {
161
-			return nil, err
162
-		}
163
-	}
164
-
165
-	if len(secretData) == 0 {
166
-		return nil, errors.New("No files selected")
167
-	}
168
-
169
-	// if the secret type isn't specified, attempt to auto-detect likely hit
170
-	secretType := kapi.SecretType(o.SecretTypeName)
171
-	if len(o.SecretTypeName) == 0 {
172
-		secretType = kapi.SecretTypeOpaque
173
-
174
-		for _, knownSecretType := range KnownSecretTypes {
175
-			if knownSecretType.Matches(secretData) {
176
-				secretType = knownSecretType.Type
177
-			}
178
-		}
179
-	}
180
-
181
-	secret := &kapi.Secret{
182
-		ObjectMeta: kapi.ObjectMeta{Name: o.Name},
183
-		Type:       secretType,
184
-		Data:       secretData,
185
-	}
186
-
187
-	return secret, nil
188
-}
189
-
190
-func readFile(filePath string, dataMap map[string][]byte) error {
191
-	fileName := path.Base(filePath)
192
-	if !kvalidation.IsSecretKey(fileName) {
193
-		return fmt.Errorf("%s cannot be used as a key in a secret", filePath)
194
-	}
195
-	if _, exists := dataMap[fileName]; exists {
196
-		return fmt.Errorf("Multiple files with the same name (%s) cannot be included in a secret", fileName)
197
-	}
198
-
199
-	data, err := ioutil.ReadFile(filePath)
200
-	if err != nil {
201
-		return err
202
-	}
203
-	dataMap[fileName] = data
204
-	return nil
205
-}
206 1
deleted file mode 100644
... ...
@@ -1,127 +0,0 @@
1
-package bundlesecret
2
-
3
-import (
4
-	"io/ioutil"
5
-	"testing"
6
-
7
-	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
8
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
9
-)
10
-
11
-func TestValidate(t *testing.T) {
12
-
13
-	tests := []struct {
14
-		testName string
15
-		args     []string
16
-		expErr   bool
17
-	}{
18
-		{
19
-			testName: "validArgs",
20
-			args:     []string{"testSecret", "./bsFixtures/www.google.com"},
21
-		},
22
-		{
23
-			testName: "noName",
24
-			args:     []string{"./bsFixtures/www.google.com"},
25
-			expErr:   true, //"Secret name is required"
26
-		},
27
-		{
28
-			testName: "noFilesPassed",
29
-			args:     []string{"testSecret"},
30
-			expErr:   true, //"At least one source file or directory must be specified"
31
-		},
32
-	}
33
-
34
-	for _, test := range tests {
35
-		options := NewDefaultOptions()
36
-		options.Complete(test.args)
37
-		err := options.Validate()
38
-		if err != nil && !test.expErr {
39
-			t.Errorf("%s: unexpected error: %v", test.testName, err)
40
-		}
41
-	}
42
-}
43
-
44
-func TestCreateSecret(t *testing.T) {
45
-
46
-	tests := []struct {
47
-		testName string
48
-		args     []string
49
-		expErr   bool
50
-		quiet    bool
51
-	}{
52
-		{
53
-			testName: "validSources",
54
-			args:     []string{"testSecret", "./bsFixtures/www.google.com", "./bsFixtures/dirNoSubdir"},
55
-		},
56
-		{
57
-			testName: "invalidDNS",
58
-			args:     []string{"testSecret", "./bsFixtures/invalid/invalid-DNS"},
59
-			expErr:   true, // "/bsFixtures/invalid-DNS cannot be used as a key in a secret"
60
-		},
61
-		{
62
-			testName: "leadingDotsAllowed",
63
-			args:     []string{"testSecret", "./bsFixtures/leadingdot/.dockercfg"},
64
-		},
65
-		{
66
-			testName: "filesSameName",
67
-			args:     []string{"testSecret", "./bsFixtures/www.google.com", "./bsFixtures/multiple/www.google.com"},
68
-			expErr:   true, // "Multiple files with the same name (www.google.com) cannot be included a secret"
69
-		},
70
-		{
71
-			testName: "testQuietTrue",
72
-			args:     []string{"testSecret", "./bsFixtures/dir"},
73
-			quiet:    true,
74
-		},
75
-		{
76
-			testName: "testQuietFalse",
77
-			args:     []string{"testSecret", "./bsFixtures/dir"},
78
-			expErr:   true, // "Skipping resource <resource path>"
79
-		},
80
-	}
81
-	for _, test := range tests {
82
-		options := NewDefaultOptions()
83
-		options.Complete(test.args)
84
-		options.Quiet = test.quiet
85
-
86
-		err := options.Validate()
87
-		if err != nil {
88
-			t.Errorf("unexpected error")
89
-		}
90
-		_, err = options.CreateSecret()
91
-		if err != nil && !test.expErr {
92
-			t.Errorf("%s: unexpected error: %s", test.testName, err)
93
-		}
94
-	}
95
-}
96
-
97
-func TestSecretTypeSpecified(t *testing.T) {
98
-	options := CreateSecretOptions{
99
-		Name:           "any",
100
-		SecretTypeName: string(kapi.SecretTypeDockercfg),
101
-		Sources:        util.StringList([]string{"./bsFixtures/www.google.com"}),
102
-		Stderr:         ioutil.Discard,
103
-	}
104
-
105
-	secret, err := options.CreateSecret()
106
-	if err != nil {
107
-		t.Errorf("unexpected error: %v", err)
108
-	}
109
-	if secret.Type != kapi.SecretTypeDockercfg {
110
-		t.Errorf("expected %v, got %v", kapi.SecretTypeDockercfg, secret.Type)
111
-	}
112
-}
113
-func TestSecretTypeDiscovered(t *testing.T) {
114
-	options := CreateSecretOptions{
115
-		Name:    "any",
116
-		Sources: util.StringList([]string{"./bsFixtures/leadingdot/.dockercfg"}),
117
-		Stderr:  ioutil.Discard,
118
-	}
119
-
120
-	secret, err := options.CreateSecret()
121
-	if err != nil {
122
-		t.Errorf("unexpected error: %v", err)
123
-	}
124
-	if secret.Type != kapi.SecretTypeDockercfg {
125
-		t.Errorf("expected %v, got %v", kapi.SecretTypeDockercfg, secret.Type)
126
-	}
127
-}
128 1
deleted file mode 100644
... ...
@@ -1,28 +0,0 @@
1
-package bundlesecret
2
-
3
-import (
4
-	"reflect"
5
-
6
-	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
7
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
8
-)
9
-
10
-type KnownSecretType struct {
11
-	Type             kapi.SecretType
12
-	RequiredContents util.StringSet
13
-}
14
-
15
-func (ks KnownSecretType) Matches(secretContent map[string][]byte) bool {
16
-	if secretContent == nil {
17
-		return false
18
-	}
19
-
20
-	secretKeys := util.KeySet(reflect.ValueOf(secretContent))
21
-	return reflect.DeepEqual(ks.RequiredContents.List(), secretKeys.List())
22
-}
23
-
24
-var (
25
-	KnownSecretTypes = []KnownSecretType{
26
-		{kapi.SecretTypeDockercfg, util.NewStringSet(kapi.DockerConfigKey)},
27
-	}
28
-)
... ...
@@ -11,7 +11,6 @@ import (
11 11
 	"github.com/openshift/origin/pkg/cmd/cli"
12 12
 	"github.com/openshift/origin/pkg/cmd/cli/cmd"
13 13
 	"github.com/openshift/origin/pkg/cmd/experimental/buildchain"
14
-	"github.com/openshift/origin/pkg/cmd/experimental/bundlesecret"
15 14
 	exipfailover "github.com/openshift/origin/pkg/cmd/experimental/ipfailover"
16 15
 	"github.com/openshift/origin/pkg/cmd/experimental/tokens"
17 16
 	"github.com/openshift/origin/pkg/cmd/flagtypes"
... ...
@@ -152,7 +151,6 @@ func newExperimentalCommand(name, fullName string) *cobra.Command {
152 152
 	experimental.AddCommand(tokens.NewCmdTokens(tokens.TokenRecommendedCommandName, fullName+" "+tokens.TokenRecommendedCommandName, f, out))
153 153
 	experimental.AddCommand(exipfailover.NewCmdIPFailoverConfig(f, fullName, "ipfailover", out))
154 154
 	experimental.AddCommand(buildchain.NewCmdBuildChain(f, fullName, "build-chain"))
155
-	experimental.AddCommand(bundlesecret.NewCmdBundleSecret(f, fullName, "bundle-secret", out))
156 155
 	experimental.AddCommand(cmd.NewCmdOptions(out))
157 156
 	return experimental
158 157
 }