file.go raw
1 package credentials
2
3 import (
4 "fmt"
5 "os"
6 "os/user"
7 "path"
8
9 "github.com/spf13/viper"
10 )
11
12 type FileOpt func(*FileProvider)
13
14 // FileOptWithFilename returns a FileOpt overriding the default filename.
15 func FileOptWithFilename(filename string) FileOpt {
16 return func(f *FileProvider) {
17 f.filename = filename
18 }
19 }
20
21 // FileOptWithAccount returns a FileOpt overriding the default account.
22 func FileOptWithAccount(account string) FileOpt {
23 return func(f *FileProvider) {
24 f.account = account
25 }
26 }
27
28 type FileProvider struct {
29 filename string
30 account string
31 retrieved bool
32
33 // TODO: export some fields from the config like: default zone...etc.
34 }
35
36 func NewFileCredentials(opts ...FileOpt) *Credentials {
37 fp := &FileProvider{}
38 for _, opt := range opts {
39 opt(fp)
40 }
41 return NewCredentials(fp)
42 }
43
44 func (f *FileProvider) Retrieve() (Value, error) {
45 f.retrieved = false
46
47 viperConf, err := f.retrieveViperConfig()
48 if err != nil {
49 return Value{}, err
50 }
51
52 if err := viperConf.ReadInConfig(); err != nil {
53 return Value{}, err
54 }
55
56 config := Config{}
57 if err := viperConf.Unmarshal(&config); err != nil {
58 return Value{}, fmt.Errorf("file provider: couldn't read config: %w", err)
59 }
60
61 if len(config.Accounts) == 0 {
62 return Value{}, fmt.Errorf("file provider: no accounts were found into %q", viper.ConfigFileUsed())
63 }
64
65 if f.account == "" && config.DefaultAccount == "" {
66 return Value{}, fmt.Errorf("file provider: no account defined")
67 }
68
69 accountName := config.DefaultAccount
70 if f.account != "" {
71 accountName = f.account
72 }
73
74 account := Account{}
75 for i, a := range config.Accounts {
76 if a.Name == accountName {
77 account = config.Accounts[i]
78 break
79 }
80 }
81
82 v := Value{
83 APIKey: account.Key,
84 APISecret: account.Secret,
85 }
86
87 if !v.IsSet() {
88 return Value{}, fmt.Errorf("file provider: account %q: %w", accountName, ErrMissingIncomplete)
89 }
90
91 f.retrieved = true
92
93 return v, nil
94 }
95
96 // IsExpired returns if the shared credentials have expired.
97 func (f *FileProvider) IsExpired() bool {
98 return !f.retrieved
99 }
100
101 type Account struct {
102 Name string
103 Account string
104 SosEndpoint string
105 Environment string
106 Key string
107 Secret string
108 SecretCommand []string
109 DefaultZone string
110 DefaultSSHKey string
111 DefaultTemplate string
112 DefaultOutputFormat string
113 ClientTimeout int
114 CustomHeaders map[string]string
115 }
116
117 type Config struct {
118 DefaultAccount string
119 DefaultOutputFormat string
120 Accounts []Account
121 }
122
123 func (f *FileProvider) retrieveViperConfig() (*viper.Viper, error) {
124 config := viper.New()
125
126 if f.filename != "" {
127 config.SetConfigFile(f.filename)
128 return config, nil
129 }
130
131 cfgdir, err := os.UserConfigDir()
132 if err != nil {
133 return nil, fmt.Errorf("could not find configuration directory: %s", err)
134 }
135
136 usr, err := user.Current()
137 if err != nil {
138 return nil, err
139 }
140
141 config.SetConfigName("exoscale")
142 config.SetConfigType("toml")
143 config.AddConfigPath(path.Join(cfgdir, "exoscale"))
144 config.AddConfigPath(path.Join(usr.HomeDir, ".exoscale"))
145 config.AddConfigPath(usr.HomeDir)
146 config.AddConfigPath(".")
147
148 return config, nil
149 }
150