-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Exporting credentials is useful both for backup and management purposes (e.g. mass-moving a number of credentials to a different path). This command enables exporting by path matching, utilising the bulk export models to create an output that is reimportable. By default, the command exports all credentials to `stdout` in import compatible YAML. The `-p` flag can be used to restrict the paths exported; the `-f` flag to write the output to a file. Note that the credentials are held unencrypted in-memory during this process; as this seems true of all the code, this should not be seen as a problem; just a caveat.
- Loading branch information
Showing
3 changed files
with
244 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package commands | ||
|
||
import ( | ||
"fmt" | ||
"io/ioutil" | ||
|
||
"github.com/cloudfoundry-incubator/credhub-cli/config" | ||
"github.com/cloudfoundry-incubator/credhub-cli/credhub/credentials" | ||
"github.com/cloudfoundry-incubator/credhub-cli/models" | ||
) | ||
|
||
type ExportCommand struct { | ||
Path string `short:"p" long:"path" description:"Path of credentials to export" required:"false"` | ||
File string `short:"f" long:"file" description:"File in which to write credentials" required:"false"` | ||
} | ||
|
||
func (cmd ExportCommand) Execute([]string) error { | ||
allCredentials, err := getAllCredentialsForPath(cmd.Path) | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
exportCreds, err := models.ExportCredentials(allCredentials) | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
if cmd.File == "" { | ||
fmt.Printf("%s", exportCreds) | ||
|
||
return err | ||
} else { | ||
return ioutil.WriteFile(cmd.File, exportCreds.Bytes, 0644) | ||
} | ||
} | ||
|
||
func getAllCredentialsForPath(path string) ([]credentials.Credential, error) { | ||
cfg := config.ReadConfig() | ||
credhubClient, err := initializeCredhubClient(cfg) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
allPaths, err := credhubClient.FindByPath(path) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
credentials := make([]credentials.Credential, len(allPaths.Credentials)) | ||
for i, baseCred := range allPaths.Credentials { | ||
credential, err := credhubClient.GetLatestVersion(baseCred.Name) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
credentials[i] = credential | ||
} | ||
|
||
return credentials, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
package commands_test | ||
|
||
import ( | ||
"io/ioutil" | ||
"net/http" | ||
"os" | ||
|
||
"github.com/cloudfoundry-incubator/credhub-cli/config" | ||
"runtime" | ||
|
||
. "github.com/onsi/ginkgo" | ||
. "github.com/onsi/gomega" | ||
. "github.com/onsi/gomega/gbytes" | ||
. "github.com/onsi/gomega/gexec" | ||
. "github.com/onsi/gomega/ghttp" | ||
) | ||
|
||
func withTemporaryFile(wantingFile func(string)) error { | ||
f, err := ioutil.TempFile("", "credhub_tests_") | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
name := f.Name() | ||
|
||
f.Close() | ||
wantingFile(name) | ||
|
||
return os.Remove(name) | ||
} | ||
|
||
var _ = Describe("Export", func() { | ||
BeforeEach(func() { | ||
login() | ||
}) | ||
|
||
ItRequiresAuthentication("export") | ||
ItRequiresAnAPIToBeSet("export") | ||
ItAutomaticallyLogsIn("GET", "get_response.json", "/api/v1/data", "export") | ||
|
||
ItBehavesLikeHelp("export", "e", func(session *Session) { | ||
Expect(session.Err).To(Say("Usage")) | ||
if runtime.GOOS == "windows" { | ||
Expect(session.Err).To(Say("credhub-cli.exe \\[OPTIONS\\] export \\[export-OPTIONS\\]")) | ||
} else { | ||
Expect(session.Err).To(Say("credhub-cli \\[OPTIONS\\] export \\[export-OPTIONS\\]")) | ||
} | ||
}) | ||
|
||
Describe("Exporting", func() { | ||
It("queries for the most recent version of all credentials", func() { | ||
findJson := `{ | ||
"credentials": [ | ||
{ | ||
"version_created_at": "idc", | ||
"name": "/path/to/cred" | ||
}, | ||
{ | ||
"version_created_at": "idc", | ||
"name": "/path/to/another/cred" | ||
} | ||
] | ||
}` | ||
|
||
getJson := `{ | ||
"data": [{ | ||
"type":"value", | ||
"id":"some_uuid", | ||
"name":"/path/to/cred", | ||
"version_created_at":"idc", | ||
"value": "foo" | ||
}] | ||
}` | ||
|
||
responseTable := `credentials: | ||
- name: /path/to/cred | ||
type: value | ||
value: foo | ||
- name: /path/to/cred | ||
type: value | ||
value: foo` | ||
|
||
server.AppendHandlers( | ||
CombineHandlers( | ||
VerifyRequest("GET", "/api/v1/data", "path="), | ||
RespondWith(http.StatusOK, findJson), | ||
), | ||
CombineHandlers( | ||
VerifyRequest("GET", "/api/v1/data", "name=/path/to/cred¤t=true"), | ||
RespondWith(http.StatusOK, getJson), | ||
), | ||
CombineHandlers( | ||
VerifyRequest("GET", "/api/v1/data", "name=/path/to/another/cred¤t=true"), | ||
RespondWith(http.StatusOK, getJson), | ||
), | ||
) | ||
|
||
session := runCommand("export") | ||
|
||
Eventually(session).Should(Exit(0)) | ||
Eventually(session.Out).Should(Say(responseTable)) | ||
}) | ||
|
||
Context("when given a path", func() { | ||
It("queries for credentials matching that path", func() { | ||
noCredsJson := `{ "credentials" : [] }` | ||
|
||
server.AppendHandlers( | ||
CombineHandlers( | ||
VerifyRequest("GET", "/api/v1/data", "path=/some/path"), | ||
RespondWith(http.StatusOK, noCredsJson), | ||
), | ||
) | ||
|
||
session := runCommand("export", "-p", "/some/path") | ||
|
||
Eventually(session).Should(Exit(0)) | ||
}) | ||
}) | ||
|
||
Context("when given a file", func() { | ||
It("writes the YAML to that file", func() { | ||
withTemporaryFile(func(filename string) { | ||
noCredsJson := `{ "credentials" : [] }` | ||
noCredsYaml := `credentials: [] | ||
` | ||
|
||
server.AppendHandlers( | ||
CombineHandlers( | ||
VerifyRequest("GET", "/api/v1/data", "path="), | ||
RespondWith(http.StatusOK, noCredsJson), | ||
), | ||
) | ||
|
||
session := runCommand("export", "-f", filename) | ||
|
||
Eventually(session).Should(Exit(0)) | ||
|
||
Expect(filename).To(BeAnExistingFile()) | ||
|
||
fileContents, _ := ioutil.ReadFile(filename) | ||
|
||
Expect(string(fileContents)).To(Equal(noCredsYaml)) | ||
}) | ||
}) | ||
}) | ||
}) | ||
|
||
Describe("Errors", func() { | ||
It("prints an error when the network request fails", func() { | ||
cfg := config.ReadConfig() | ||
cfg.ApiURL = "mashed://potatoes" | ||
config.WriteConfig(cfg) | ||
|
||
session := runCommand("export") | ||
|
||
Eventually(session).Should(Exit(1)) | ||
Eventually(string(session.Err.Contents())).Should(ContainSubstring("Get mashed://potatoes/api/v1/data?path=: unsupported protocol scheme \"mashed\"")) | ||
}) | ||
|
||
It("prints an error if the specified output file cannot be opened", func() { | ||
noCredsJson := `{ "credentials" : [] }` | ||
|
||
server.AppendHandlers( | ||
CombineHandlers( | ||
VerifyRequest("GET", "/api/v1/data", "path="), | ||
RespondWith(http.StatusOK, noCredsJson), | ||
), | ||
) | ||
|
||
session := runCommand("export", "-f", "/this/should/not/exist/anywhere") | ||
|
||
Eventually(session).Should(Exit(1)) | ||
Eventually(string(session.Err.Contents())).Should(ContainSubstring("open /this/should/not/exist/anywhere: no such file or directory")) | ||
}) | ||
}) | ||
}) |