| ... | ... |
@@ -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 |
-} |
| 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 |
+} |
| 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 |
} |