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  }