config.go raw

   1  package linodego
   2  
   3  import (
   4  	"fmt"
   5  	"os"
   6  	"strings"
   7  
   8  	"gopkg.in/ini.v1"
   9  )
  10  
  11  const (
  12  	DefaultConfigProfile = "default"
  13  )
  14  
  15  var DefaultConfigPaths = []string{
  16  	"%s/.config/linode",
  17  	"%s/.config/linode-cli",
  18  }
  19  
  20  type ConfigProfile struct {
  21  	APIToken   string `ini:"token"`
  22  	APIVersion string `ini:"api_version"`
  23  	APIURL     string `ini:"api_url"`
  24  }
  25  
  26  type LoadConfigOptions struct {
  27  	Path            string
  28  	Profile         string
  29  	SkipLoadProfile bool
  30  }
  31  
  32  // LoadConfig loads a Linode config according to the option's argument.
  33  // If no options are specified, the following defaults will be used:
  34  // Path: ~/.config/linode
  35  // Profile: default
  36  func (c *Client) LoadConfig(options *LoadConfigOptions) error {
  37  	path, err := resolveValidConfigPath()
  38  	if err != nil {
  39  		return err
  40  	}
  41  
  42  	profileOption := DefaultConfigProfile
  43  
  44  	if options != nil {
  45  		if options.Path != "" {
  46  			path = options.Path
  47  		}
  48  
  49  		if options.Profile != "" {
  50  			profileOption = options.Profile
  51  		}
  52  	}
  53  
  54  	cfg, err := ini.Load(path)
  55  	if err != nil {
  56  		return err
  57  	}
  58  
  59  	defaultConfig := ConfigProfile{
  60  		APIToken:   "",
  61  		APIURL:     APIHost,
  62  		APIVersion: APIVersion,
  63  	}
  64  
  65  	if cfg.HasSection("default") {
  66  		err := cfg.Section("default").MapTo(&defaultConfig)
  67  		if err != nil {
  68  			return fmt.Errorf("failed to map default profile: %w", err)
  69  		}
  70  	}
  71  
  72  	result := make(map[string]ConfigProfile)
  73  
  74  	for _, profile := range cfg.Sections() {
  75  		name := strings.ToLower(profile.Name())
  76  
  77  		f := defaultConfig
  78  		if err := profile.MapTo(&f); err != nil {
  79  			return fmt.Errorf("failed to map values: %w", err)
  80  		}
  81  
  82  		result[name] = f
  83  	}
  84  
  85  	c.configProfiles = result
  86  
  87  	if !options.SkipLoadProfile {
  88  		if err := c.UseProfile(profileOption); err != nil {
  89  			return fmt.Errorf("unable to use profile %s: %w", profileOption, err)
  90  		}
  91  	}
  92  
  93  	return nil
  94  }
  95  
  96  // UseProfile switches client to use the specified profile.
  97  // The specified profile must be already be loaded using client.LoadConfig(...)
  98  func (c *Client) UseProfile(name string) error {
  99  	name = strings.ToLower(name)
 100  
 101  	profile, ok := c.configProfiles[name]
 102  	if !ok {
 103  		return fmt.Errorf("profile %s does not exist", name)
 104  	}
 105  
 106  	if profile.APIToken == "" {
 107  		return fmt.Errorf("unable to resolve linode_token for profile %s", name)
 108  	}
 109  
 110  	if profile.APIURL == "" {
 111  		return fmt.Errorf("unable to resolve linode_api_url for profile %s", name)
 112  	}
 113  
 114  	if profile.APIVersion == "" {
 115  		return fmt.Errorf("unable to resolve linode_api_version for profile %s", name)
 116  	}
 117  
 118  	c.SetToken(profile.APIToken)
 119  	c.SetBaseURL(profile.APIURL)
 120  	c.SetAPIVersion(profile.APIVersion)
 121  	c.selectedProfile = name
 122  	c.loadedProfile = name
 123  
 124  	return nil
 125  }
 126  
 127  func FormatConfigPath(path string) (string, error) {
 128  	homeDir, err := os.UserHomeDir()
 129  	if err != nil {
 130  		return "", err
 131  	}
 132  
 133  	return fmt.Sprintf(path, homeDir), nil
 134  }
 135  
 136  func resolveValidConfigPath() (string, error) {
 137  	for _, cfg := range DefaultConfigPaths {
 138  		p, err := FormatConfigPath(cfg)
 139  		if err != nil {
 140  			return "", err
 141  		}
 142  
 143  		if _, err = os.Stat(p); err != nil {
 144  			continue
 145  		}
 146  
 147  		return p, err
 148  	}
 149  
 150  	// An empty result may not be an error
 151  	return "", nil
 152  }
 153