This fix tries to address the issue raised in 28581 and 28927
where it is not possible to create a secret from a file (only
through STDIN).
This fix add a flag `--file` to `docker secret create` so that
it is possible to create a secret from a file with:
```
docker secret create --file secret.in secret.name
```
or
```
echo TEST | docker secret create --file - secret.name
```
Related docs has been updated.
An integration test has been added to cover the changes.
This fix fixes 28581.
This fix is related to 28927.
Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
| ... | ... |
@@ -2,13 +2,14 @@ package secret |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 |
+ "io" |
|
| 5 | 6 |
"io/ioutil" |
| 6 |
- "os" |
|
| 7 | 7 |
|
| 8 | 8 |
"github.com/docker/docker/api/types/swarm" |
| 9 | 9 |
"github.com/docker/docker/cli" |
| 10 | 10 |
"github.com/docker/docker/cli/command" |
| 11 | 11 |
"github.com/docker/docker/opts" |
| 12 |
+ "github.com/docker/docker/pkg/system" |
|
| 12 | 13 |
runconfigopts "github.com/docker/docker/runconfig/opts" |
| 13 | 14 |
"github.com/spf13/cobra" |
| 14 | 15 |
"golang.org/x/net/context" |
| ... | ... |
@@ -16,6 +17,7 @@ import ( |
| 16 | 16 |
|
| 17 | 17 |
type createOptions struct {
|
| 18 | 18 |
name string |
| 19 |
+ file string |
|
| 19 | 20 |
labels opts.ListOpts |
| 20 | 21 |
} |
| 21 | 22 |
|
| ... | ... |
@@ -26,7 +28,7 @@ func newSecretCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 26 | 26 |
|
| 27 | 27 |
cmd := &cobra.Command{
|
| 28 | 28 |
Use: "create [OPTIONS] SECRET", |
| 29 |
- Short: "Create a secret using stdin as content", |
|
| 29 |
+ Short: "Create a secret from a file or STDIN as content", |
|
| 30 | 30 |
Args: cli.ExactArgs(1), |
| 31 | 31 |
RunE: func(cmd *cobra.Command, args []string) error {
|
| 32 | 32 |
createOpts.name = args[0] |
| ... | ... |
@@ -35,6 +37,7 @@ func newSecretCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 35 | 35 |
} |
| 36 | 36 |
flags := cmd.Flags() |
| 37 | 37 |
flags.VarP(&createOpts.labels, "label", "l", "Secret labels") |
| 38 |
+ flags.StringVarP(&createOpts.file, "file", "f", "", "Read from a file or STDIN ('-')")
|
|
| 38 | 39 |
|
| 39 | 40 |
return cmd |
| 40 | 41 |
} |
| ... | ... |
@@ -43,9 +46,23 @@ func runSecretCreate(dockerCli *command.DockerCli, options createOptions) error |
| 43 | 43 |
client := dockerCli.Client() |
| 44 | 44 |
ctx := context.Background() |
| 45 | 45 |
|
| 46 |
- secretData, err := ioutil.ReadAll(os.Stdin) |
|
| 46 |
+ if options.file == "" {
|
|
| 47 |
+ return fmt.Errorf("Please specify either a file name or STDIN ('-') with --file")
|
|
| 48 |
+ } |
|
| 49 |
+ |
|
| 50 |
+ var in io.Reader = dockerCli.In() |
|
| 51 |
+ if options.file != "-" {
|
|
| 52 |
+ file, err := system.OpenSequential(options.file) |
|
| 53 |
+ if err != nil {
|
|
| 54 |
+ return err |
|
| 55 |
+ } |
|
| 56 |
+ in = file |
|
| 57 |
+ defer file.Close() |
|
| 58 |
+ } |
|
| 59 |
+ |
|
| 60 |
+ secretData, err := ioutil.ReadAll(in) |
|
| 47 | 61 |
if err != nil {
|
| 48 |
- return fmt.Errorf("Error reading content from STDIN: %v", err)
|
|
| 62 |
+ return fmt.Errorf("Error reading content from %q: %v", options.file, err)
|
|
| 49 | 63 |
} |
| 50 | 64 |
|
| 51 | 65 |
spec := swarm.SecretSpec{
|
| ... | ... |
@@ -16,15 +16,17 @@ keywords: ["secret, create"] |
| 16 | 16 |
# secret create |
| 17 | 17 |
|
| 18 | 18 |
```Markdown |
| 19 |
-Usage: docker secret create [OPTIONS] SECRET |
|
| 19 |
+Usage: docker secret create [OPTIONS] SECRET |
|
| 20 |
+ |
|
| 21 |
+Create a secret from a file or STDIN as content |
|
| 20 | 22 |
|
| 21 |
-Create a secret using stdin as content |
|
| 22 | 23 |
Options: |
| 23 |
- --help Print usage |
|
| 24 |
- -l, --label list Secret labels (default []) |
|
| 24 |
+ -f, --file string Read from a file or STDIN ('-')
|
|
| 25 |
+ --help Print usage |
|
| 26 |
+ -l, --label list Secret labels (default []) |
|
| 25 | 27 |
``` |
| 26 | 28 |
|
| 27 |
-Creates a secret using standard input for the secret content. You must run this |
|
| 29 |
+Creates a secret using standard input or from a file for the secret content. You must run this |
|
| 28 | 30 |
command on a manager node. |
| 29 | 31 |
|
| 30 | 32 |
## Examples |
| ... | ... |
@@ -32,7 +34,18 @@ command on a manager node. |
| 32 | 32 |
### Create a secret |
| 33 | 33 |
|
| 34 | 34 |
```bash |
| 35 |
-$ cat secret.json | docker secret create secret.json |
|
| 35 |
+$ cat secret.json | docker secret create -f - secret.json |
|
| 36 |
+mhv17xfe3gh6xc4rij5orpfds |
|
| 37 |
+ |
|
| 38 |
+$ docker secret ls |
|
| 39 |
+ID NAME CREATED UPDATED SIZE |
|
| 40 |
+mhv17xfe3gh6xc4rij5orpfds secret.json 2016-10-27 23:25:43.909181089 +0000 UTC 2016-10-27 23:25:43.909181089 +0000 UTC 1679 |
|
| 41 |
+``` |
|
| 42 |
+ |
|
| 43 |
+### Create a secret with a file |
|
| 44 |
+ |
|
| 45 |
+```bash |
|
| 46 |
+$ docker secret create --file secret.in secret.json |
|
| 36 | 47 |
mhv17xfe3gh6xc4rij5orpfds |
| 37 | 48 |
|
| 38 | 49 |
$ docker secret ls |
| ... | ... |
@@ -43,7 +56,7 @@ mhv17xfe3gh6xc4rij5orpfds secret.json 2016-10-27 23:25:43.90918108 |
| 43 | 43 |
### Create a secret with labels |
| 44 | 44 |
|
| 45 | 45 |
```bash |
| 46 |
-$ cat secret.json | docker secret create secret.json --label env=dev --label rev=20161102 |
|
| 46 |
+$ cat secret.json | docker secret create secret.json -f - --label env=dev --label rev=20161102 |
|
| 47 | 47 |
jtn7g6aukl5ky7nr9gvwafoxh |
| 48 | 48 |
|
| 49 | 49 |
$ docker secret inspect secret.json |
| ... | ... |
@@ -3,6 +3,10 @@ |
| 3 | 3 |
package main |
| 4 | 4 |
|
| 5 | 5 |
import ( |
| 6 |
+ "io/ioutil" |
|
| 7 |
+ "os" |
|
| 8 |
+ "strings" |
|
| 9 |
+ |
|
| 6 | 10 |
"github.com/docker/docker/api/types/swarm" |
| 7 | 11 |
"github.com/docker/docker/pkg/integration/checker" |
| 8 | 12 |
"github.com/go-check/check" |
| ... | ... |
@@ -104,3 +108,33 @@ func (s *DockerSwarmSuite) TestSecretCreateResolve(c *check.C) {
|
| 104 | 104 |
c.Assert(out, checker.Not(checker.Contains), id) |
| 105 | 105 |
c.Assert(out, checker.Not(checker.Contains), fake) |
| 106 | 106 |
} |
| 107 |
+ |
|
| 108 |
+func (s *DockerSwarmSuite) TestSecretCreateWithFile(c *check.C) {
|
|
| 109 |
+ d := s.AddDaemon(c, true, true) |
|
| 110 |
+ |
|
| 111 |
+ testFile, err := ioutil.TempFile("", "secretCreateTest")
|
|
| 112 |
+ c.Assert(err, checker.IsNil, check.Commentf("failed to create temporary file"))
|
|
| 113 |
+ defer os.Remove(testFile.Name()) |
|
| 114 |
+ |
|
| 115 |
+ testData := "TESTINGDATA" |
|
| 116 |
+ _, err = testFile.Write([]byte(testData)) |
|
| 117 |
+ c.Assert(err, checker.IsNil, check.Commentf("failed to write to temporary file"))
|
|
| 118 |
+ |
|
| 119 |
+ testName := "test_secret" |
|
| 120 |
+ out, err := d.Cmd("secret", "create", "--file", testFile.Name(), testName)
|
|
| 121 |
+ c.Assert(err, checker.IsNil) |
|
| 122 |
+ c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "", check.Commentf(out)) |
|
| 123 |
+ |
|
| 124 |
+ id := strings.TrimSpace(out) |
|
| 125 |
+ secret := d.GetSecret(c, id) |
|
| 126 |
+ c.Assert(secret.Spec.Name, checker.Equals, testName) |
|
| 127 |
+ |
|
| 128 |
+ testName = "test_secret_2" |
|
| 129 |
+ out, err = d.Cmd("secret", "create", testName, "-f", testFile.Name())
|
|
| 130 |
+ c.Assert(err, checker.IsNil) |
|
| 131 |
+ c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "", check.Commentf(out)) |
|
| 132 |
+ |
|
| 133 |
+ id = strings.TrimSpace(out) |
|
| 134 |
+ secret = d.GetSecret(c, id) |
|
| 135 |
+ c.Assert(secret.Spec.Name, checker.Equals, testName) |
|
| 136 |
+} |