github.com/leg100/ots@v0.0.7-0.20210919080622-034055ced4bd/cmd/ots/credentials_store.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "net/url" 8 "os" 9 "path/filepath" 10 ) 11 12 const ( 13 CredentialsPath = ".terraform.d/credentials.tfrc.json" 14 ) 15 16 type CredentialsConfig struct { 17 Credentials map[string]TokenConfig `json:"credentials"` 18 } 19 20 type TokenConfig struct { 21 Token string `json:"token"` 22 } 23 24 // CredentialsStore is a JSON file in a user's home dir that stores tokens for 25 // one or more TFE-type hosts 26 type CredentialsStore string 27 28 // NewCredentialsStore is a contructor for CredentialsStore 29 func NewCredentialsStore(dirs Directories) (CredentialsStore, error) { 30 // Construct full path to creds config 31 home, err := dirs.UserHomeDir() 32 if err != nil { 33 return "", err 34 } 35 path := filepath.Join(home, CredentialsPath) 36 37 return CredentialsStore(path), nil 38 } 39 40 // Load retrieves the token for hostname 41 func (c CredentialsStore) Load(hostname string) (string, error) { 42 hostname, err := c.sanitizeHostname(hostname) 43 if err != nil { 44 return "", err 45 } 46 47 config, err := c.read() 48 if err != nil { 49 return "", err 50 } 51 52 tokenConfig, ok := config.Credentials[hostname] 53 if !ok { 54 return "", fmt.Errorf("credentials for %s not found in %s", hostname, c) 55 } 56 57 return tokenConfig.Token, nil 58 } 59 60 // Save saves the token for the given hostname to the store, overwriting any 61 // existing tokens for the hostname. 62 func (c CredentialsStore) Save(hostname, token string) error { 63 hostname, err := c.sanitizeHostname(hostname) 64 if err != nil { 65 return err 66 } 67 68 config, err := c.read() 69 if err != nil { 70 return err 71 } 72 73 config.Credentials[hostname] = TokenConfig{ 74 Token: token, 75 } 76 77 if err := c.write(config); err != nil { 78 return err 79 } 80 81 return nil 82 } 83 84 func (c CredentialsStore) read() (*CredentialsConfig, error) { 85 // Construct credentials config obj 86 config := CredentialsConfig{Credentials: make(map[string]TokenConfig)} 87 88 // Read any existing file contents 89 data, err := os.ReadFile(string(c)) 90 if err == nil { 91 if err := json.Unmarshal(data, &config); err != nil { 92 return nil, err 93 } 94 } else if !errors.Is(err, os.ErrNotExist) { 95 return nil, err 96 } 97 98 return &config, nil 99 } 100 101 func (c CredentialsStore) write(config *CredentialsConfig) error { 102 data, err := json.MarshalIndent(&config, "", " ") 103 if err != nil { 104 return err 105 } 106 107 // Ensure all parent directories of config file exist 108 if err := os.MkdirAll(filepath.Dir(string(c)), 0775); err != nil { 109 return err 110 } 111 112 if err := os.WriteFile(string(c), data, 0600); err != nil { 113 return err 114 } 115 116 return nil 117 } 118 119 // Ensure hostname is in the format <host>:<port> 120 func (c CredentialsStore) sanitizeHostname(hostname string) (string, error) { 121 u, err := url.ParseRequestURI(hostname) 122 if err != nil || u.Host == "" { 123 u, er := url.ParseRequestURI("https://" + hostname) 124 if er != nil { 125 return "", fmt.Errorf("could not parse hostname: %w", err) 126 } 127 return u.Host, nil 128 } 129 130 return u.Host, nil 131 }