in main.go [722:874]
func editRun(cmd *cobra.Command, args []string) error {
ctx, client, err := clientWithContext(cmd.Context())
if err != nil {
return misuseError(err)
}
// Find the editor
var editor string
for _, e := range []string{"VISUAL", "EDITOR"} {
if v := os.Getenv(e); v != "" {
editor = v
break
}
}
if editor == "" {
err := fmt.Errorf("no editor is set - set VISUAL or EDITOR")
return apiError(err)
}
ref, err := parseRef(args[0])
if err != nil {
return misuseError(err)
}
var originalSecret *berglas.Secret
// Get the existing secret
switch t := ref.Type(); t {
case berglas.ReferenceTypeSecretManager:
originalSecret, err = client.Read(ctx, &berglas.SecretManagerReadRequest{
Project: ref.Project(),
Name: ref.Name(),
Version: ref.Version(),
})
case berglas.ReferenceTypeStorage:
originalSecret, err = client.Read(ctx, &berglas.StorageReadRequest{
Bucket: ref.Bucket(),
Object: ref.Object(),
Generation: ref.Generation(),
})
default:
return misuseError(fmt.Errorf("unknown type %T", t))
}
if err != nil {
return apiError(err)
}
// Create the tempfile
f, err := os.CreateTemp("", "berglas-")
if err != nil {
err = fmt.Errorf("failed to create tempfile for secret: %w", err)
return apiError(err)
}
defer func() {
if err := os.Remove(f.Name()); err != nil {
fmt.Fprintf(stderr, "failed to cleanup tempfile %s: %s\n", f.Name(), err)
}
}()
// Write contents to the original file
if _, err := f.Write(originalSecret.Plaintext); err != nil {
err = fmt.Errorf("failed to write tempfile for secret: %w", err)
return apiError(err)
}
if err := f.Sync(); err != nil {
err = fmt.Errorf("failed to sync tempfile for secret: %w", err)
return apiError(err)
}
if err := f.Close(); err != nil {
err = fmt.Errorf("failed to close tempfile for secret: %w", err)
return apiError(err)
}
// Spawn editor
editorSplit := strings.Split(editor, " ")
editorCmd, editorArgs := editorSplit[0], editorSplit[1:]
editorArgs = append(editorArgs, f.Name())
externalCmd := exec.CommandContext(ctx, editorCmd, editorArgs...)
externalCmd.Stdin = stdin
externalCmd.Stdout = stdout
externalCmd.Stderr = stderr
if err := externalCmd.Start(); err != nil {
err = fmt.Errorf("failed to start editor: %w", err)
return misuseError(err)
}
if err := externalCmd.Wait(); err != nil {
if terr, ok := err.(*exec.ExitError); ok && terr.ProcessState != nil {
code := terr.ProcessState.ExitCode()
return exitWithCode(code, fmt.Errorf("editor did not exit 0: %w", err))
}
err = fmt.Errorf("unknown failure in running editor: %w", err)
return misuseError(err)
}
// Read the new secret value
newPlaintext, err := os.ReadFile(f.Name())
if err != nil {
err = fmt.Errorf("failed to read secret tempfile: %w", err)
return misuseError(err)
}
// Error if the secret is empty
if len(newPlaintext) == 0 {
err := fmt.Errorf("secret is empty")
return misuseError(err)
}
if bytes.Equal(newPlaintext, originalSecret.Plaintext) {
err := fmt.Errorf("secret unchanged - not going to update")
return misuseError(err)
}
// Update the secret
switch t := ref.Type(); t {
case berglas.ReferenceTypeSecretManager:
updatedSecret, err := client.Update(ctx, &berglas.SecretManagerUpdateRequest{
Project: ref.Project(),
Name: ref.Name(),
Plaintext: newPlaintext,
})
if err != nil {
err = fmt.Errorf("failed to update secret: %w", err)
return misuseError(err)
}
fmt.Fprintf(stdout, "Successfully updated secret [%s] to version [%s]\n",
updatedSecret.Name, updatedSecret.Version)
case berglas.ReferenceTypeStorage:
updatedSecret, err := client.Update(ctx, &berglas.StorageUpdateRequest{
Bucket: ref.Bucket(),
Object: ref.Object(),
Generation: originalSecret.Generation,
Key: originalSecret.KMSKey,
Metageneration: originalSecret.Metageneration,
Plaintext: newPlaintext,
})
if err != nil {
err = fmt.Errorf("failed to update secret: %w", err)
return misuseError(err)
}
fmt.Fprintf(stdout, "Successfully updated secret [%s] with generation [%d]\n",
updatedSecret.Name, updatedSecret.Generation)
default:
return misuseError(fmt.Errorf("unknown type %T", t))
}
return nil
}