Skip to content

Commit

Permalink
Print to stderr and exit instead of panicking
Browse files Browse the repository at this point in the history
  • Loading branch information
slarwise committed Aug 4, 2024
1 parent f5761bf commit 7f2a438
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 47 deletions.
90 changes: 44 additions & 46 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,33 @@ import (
"encoding/json"
"fmt"
"io"
"log"
"log/slog"
"net/http"
"os"
"strings"
"sync"
)

func envOrPanic(name string) string {
func mustGetEnv(name string) string {
value, found := os.LookupEnv(name)
if !found {
panic(fmt.Sprintf("Environment variable %s must be set", name))
fatal(fmt.Sprintf("Environment variable %s must be set", name))
}
return value
}

func fatal(format string, args ...any) {
format = format + "\n"
fmt.Fprintf(os.Stderr, format, args...)
func fatal(msg string, args ...any) {
slog.Error(msg, args...)
os.Exit(1)
}

func main() {
log.SetFlags(0) // Disable the timestamp
vault := VaultClient{
Addr: envOrPanic("VAULT_ADDR"),
Token: envOrPanic("VAULT_TOKEN"),
Mount: envOrPanic("VAULT_MOUNT"),
Addr: mustGetEnv("VAULT_ADDR"),
Token: mustGetEnv("VAULT_TOKEN"),
Mount: mustGetEnv("VAULT_MOUNT"),
}
if len(os.Args) < 2 {
fatal("Must provide subcommand `tree` or `get`")
Expand All @@ -50,21 +52,24 @@ func main() {
fmt.Println(key)
}
} else if subcommand == "get" {
if len(os.Args) < 2 {
fatal("Must provide the name of the secret")
if len(os.Args) < 3 {
fatal("Must provide the name of the secret, e.g. ./pole3 get my-secret")
}
key := os.Args[2]
if !strings.HasPrefix(key, "/") {
key = "/" + key
}
secret := vault.getSecret(key)
secret, err := vault.getSecret(key)
if err != nil {
fatal("Failed to get secret", "key", key, "err", err)
}
output, err := json.MarshalIndent(secret, "", " ")
if err != nil {
fatal("Could not marshal secret as json: %s", err)
fatal("Could not marshal secret as json", "error", err)
}
fmt.Println(string(output))
} else {
fatal("Subcommand must be one of `tree` or `get`, got %s", subcommand)
fatal(fmt.Sprintf("Subcommand must be one of `tree` or `get`, got %s", subcommand))
}
}

Expand Down Expand Up @@ -93,7 +98,7 @@ func recurse(recv chan string, vault VaultClient, entry DirEnt) {
}
relativeEntries, err := vault.listDir(entry.Name)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to list directory %s: %s\n", entry.Name, err.Error())
slog.Error("Failed to list directory", "directory", entry.Name, "err", err.Error())
return
}
entries := []DirEnt{}
Expand Down Expand Up @@ -124,29 +129,32 @@ func (v VaultClient) listDir(name string) ([]DirEnt, error) {
url := fmt.Sprintf("%s/v1/%s/metadata%s?list=true", v.Addr, v.Mount, name)
request, err := http.NewRequest("GET", url, nil)
if err != nil {
panic(err)
return []DirEnt{}, fmt.Errorf("Failed to create request: %s", err)
}
request.Header.Set("X-Vault-Token", v.Token)
request.Header.Set("Accept", "application/json")
response, err := http.DefaultClient.Do(request)
if err != nil {
panic(err)
return []DirEnt{}, fmt.Errorf("Failed to perform request: %s", err)
}
if response.StatusCode != 200 {
return []DirEnt{}, fmt.Errorf("Error listing entries on url %s: %s", url, response.Status)
if response.StatusCode == 403 {
slog.Info("Forbidden to list dir", "dir", name, "url", url)
return []DirEnt{}, nil
} else if response.StatusCode != 200 {
return []DirEnt{}, fmt.Errorf("Got %s on url %s", response.Status, url)
}
defer response.Body.Close()
body, err := io.ReadAll(response.Body)
if err != nil {
panic(err)
return []DirEnt{}, fmt.Errorf("Failed to read response body: %s", err)
}
listResponse := struct {
Data struct {
Keys []string
}
}{}
if err := json.Unmarshal(body, &listResponse); err != nil {
panic(err)
return []DirEnt{}, fmt.Errorf("Failed to parse response body %s: %s", string(body), err)
}
entries := []DirEnt{}
for _, key := range listResponse.Data.Keys {
Expand All @@ -166,43 +174,33 @@ type Secret struct {
} `json:"data"`
}

func (v VaultClient) getSecret(name string) Secret {
func (v VaultClient) getSecret(name string) (Secret, error) {
url := fmt.Sprintf("%s/v1/%s/data%s", v.Addr, v.Mount, name)
request, err := http.NewRequest("GET", url, nil)
if err != nil {
panic(err)
return Secret{}, fmt.Errorf("Failed to create request: %s", err)
}
request.Header.Set("X-Vault-Token", v.Token)
request.Header.Set("Accept", "application/json")
response, err := http.DefaultClient.Do(request)
if err != nil {
panic(err)
return Secret{}, fmt.Errorf("Failed to perform request: %s", err)
}
defer response.Body.Close()
body, err := io.ReadAll(response.Body)
if response.StatusCode != 200 {
// 404 can mean that the secret has been deleted, but it will still
// be listed. Supposedly all status codes above 400 return an
// error body. This is not true in this case. I guess we can look
// at the body and see if it has errors, if not the response is
// still valid and we can show the data.
// https://developer.hashicorp.com/vault/api-docs#error-response
var secret Secret
if err := json.Unmarshal(body, &secret); err != nil {
panic(fmt.Sprintf("Failed to unmarshal secret response body: %s, %s", err.Error(), string(body)))
}
if secret.Data.Data == nil && secret.Data.Metadata == nil {
// We got an error for real for real
panic(fmt.Sprintf("Error getting secret on url %s: %s\n%s", url, response.Status, body))
}
return secret
}
if err != nil {
panic(err)
}
var secret Secret
if err := json.Unmarshal(body, &secret); err != nil {
panic(fmt.Sprintf("Failed to unmarshal secret response body: %s, %s", err.Error(), string(body)))
}
return secret
return Secret{}, fmt.Errorf("Failed to unmarshal response body %s: %s", string(body), err.Error())
}
// 404 can mean that the secret has been deleted, but it will still
// be listed. Supposedly all status codes above 400 return an
// error body. This is not true in this case. I guess we can look
// at the body and see if it has errors, if not the response is
// still valid and we can show the data.
// https://developer.hashicorp.com/vault/api-docs#error-response
isErrorForRealForReal := secret.Data.Data == nil && secret.Data.Metadata == nil
if response.StatusCode != 200 && isErrorForRealForReal {
return Secret{}, fmt.Errorf("Got %s on url %s", response.Status, url)
}
return secret, nil
}
5 changes: 4 additions & 1 deletion main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ func TestGetSecret(t *testing.T) {
Token: token,
Mount: "secret",
}
secret := vault.getSecret("/bar/baz")
secret, err := vault.getSecret("/bar/baz")
if err != nil {
t.Fatalf("Got unexpected error: %s", err)
}
data, found := secret.Data.Data["c"]
if !found || data != "d" {
t.Fatalf("Expected secret to have data `c=d`, got %v", secret.Data.Data)
Expand Down

0 comments on commit 7f2a438

Please sign in to comment.