diff --git a/cmd/dot_env.go b/cmd/dot_env.go index fd7c0625..bae7c961 100644 --- a/cmd/dot_env.go +++ b/cmd/dot_env.go @@ -47,6 +47,24 @@ func (c *DotEnvCommand) Run(args []string) int { } spinner := NewSpinner( + SpinnerCfg{ + Message: "Validating ansible-vault usability", + StopMessage: "ansible-vault is ready to use", + FailMessage: "ansible-vault is not ready to use", + }, + ) + + spinner.Start() + + if err := validateAnsibleVaultUsable(c.Trellis.Path); err != nil { + spinner.StopFail() + c.UI.Error(fmt.Sprintf("Error validating ansible-vault usability: %s", err)) + return 1 + } + + spinner.Stop() + + spinner = NewSpinner( SpinnerCfg{ Message: "Generating .env file", StopMessage: "Generated .env file", diff --git a/cmd/validate_ansible_vault_usable.go b/cmd/validate_ansible_vault_usable.go new file mode 100644 index 00000000..d79d111d --- /dev/null +++ b/cmd/validate_ansible_vault_usable.go @@ -0,0 +1,77 @@ +package cmd + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/roots/trellis-cli/command" + "github.com/roots/trellis-cli/trellis" +) + +func validateAnsibleVaultUsable(trellisPath string) (err error) { + _, err = command.Cmd("which", []string{"ansible-vault"}).CombinedOutput() + if err != nil { + return fmt.Errorf("command not found: ansible-vault") + } + + helpText := "see: https://docs.roots.io/trellis/master/vault/#storing-your-password" + + dummyString := "foo bar" + err = command.Cmd("ansible-vault", []string{"encrypt_string", dummyString}).Run() + if err != nil { + return fmt.Errorf("unable to encrypt_string. probably: vault_password_file not exists. %s", helpText) + } + + path := findFirstEncryptedFilePath(trellisPath) + if path == "" { + // No encrypted files found. Assume ansible-vault is working. + return nil + } + + err = command.Cmd("ansible-vault", []string{"decrypt", "--output", "-", path}).Run() + if err != nil { + return fmt.Errorf("unable to decrypt vault file %s. probably: incorrect vault pass. %s", path, helpText) + } + + return nil +} + +func findFirstEncryptedFilePath(trellisPath string) string { + defaultVaults := []string{ + "group_vars/development/vault.yml", // Start with development because it contains less important secrets. + "group_vars/staging/vault.yml", + "group_vars/all/vault.yml", + "group_vars/production/vault.yml", + } + for _, vault := range defaultVaults { + path := filepath.Join(trellisPath, vault) + + isEncrypted, _ := trellis.IsFileEncrypted(path) + if isEncrypted { + return path + } + } + + result := "" + filepath.Walk( + filepath.Join(trellisPath, "group_vars"), + func(path string, fi os.FileInfo, _ error) error { + if result != "" { + return nil + } + + if fi.IsDir() { + return nil + } + + isEncrypted, _ := trellis.IsFileEncrypted(path) + if isEncrypted { + result = path + } + + return nil + }) + + return result +}