github.com/akamai/AkamaiOPEN-edgegrid-golang/v2@v2.17.0/pkg/edgegrid/config.go (about) 1 package edgegrid 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "strconv" 8 "strings" 9 "time" 10 11 "github.com/mitchellh/go-homedir" 12 "gopkg.in/ini.v1" 13 ) 14 15 const ( 16 // DefaultConfigFile is the default configuration file path 17 DefaultConfigFile = "~/.edgerc" 18 19 // DefaultSection is the .edgerc ini default section 20 DefaultSection = "default" 21 22 // MaxBodySize is the max payload size for client requests 23 MaxBodySize = 131072 24 ) 25 26 var ( 27 // ErrRequiredOptionEnv is returned when a required ENV variable is not found 28 ErrRequiredOptionEnv = errors.New("required option is missing from env") 29 // ErrRequiredOptionEdgerc is returned when a required value is not found in edgerc file 30 ErrRequiredOptionEdgerc = errors.New("required option is missing from edgerc") 31 // ErrLoadingFile indicates problem with loading configuration file 32 ErrLoadingFile = errors.New("loading config file") 33 // ErrSectionDoesNotExist is returned when a section with provided name does not exist in edgerc 34 ErrSectionDoesNotExist = errors.New("provided config section does not exist") 35 // ErrHostContainsSlashAtTheEnd is returned when host has unnecessary '/' at the end 36 ErrHostContainsSlashAtTheEnd = errors.New("host must not contain '/' at the end") 37 ) 38 39 type ( 40 // Config struct provides all the necessary fields to 41 // create authorization header, debug is optional 42 Config struct { 43 Host string `ini:"host"` 44 ClientToken string `ini:"client_token"` 45 ClientSecret string `ini:"client_secret"` 46 AccessToken string `ini:"access_token"` 47 AccountKey string `ini:"account_key"` 48 HeaderToSign []string `ini:"headers_to_sign"` 49 MaxBody int `ini:"max_body"` 50 Debug bool `ini:"debug"` 51 52 file string 53 section string 54 env bool 55 } 56 57 // Option defines a configuration option 58 Option func(*Config) 59 ) 60 61 // New returns new configuration with the specified options 62 func New(opts ...Option) (*Config, error) { 63 c := &Config{ 64 section: DefaultSection, 65 env: false, 66 } 67 68 for _, opt := range opts { 69 opt(c) 70 } 71 72 if c.env { 73 if err := c.FromEnv(c.section); err == nil { 74 return c, nil 75 } else if !errors.Is(err, ErrRequiredOptionEnv) { 76 return nil, err 77 } 78 } 79 80 if c.file != "" { 81 if err := c.FromFile(c.file, c.section); err != nil { 82 return c, fmt.Errorf("unable to load config from environment or .edgerc file: %w", err) 83 } 84 } 85 86 return c, nil 87 } 88 89 // Must will panic if the new method returns an error 90 func Must(config *Config, err error) *Config { 91 if err != nil { 92 panic(err) 93 } 94 return config 95 } 96 97 // WithFile sets the config file path 98 func WithFile(file string) Option { 99 return func(c *Config) { 100 c.file = file 101 } 102 } 103 104 // WithSection sets the section in the config 105 func WithSection(section string) Option { 106 return func(c *Config) { 107 c.section = section 108 } 109 } 110 111 // WithEnv sets the option to try to the environment vars to populate the config 112 // If loading from the env fails, will fallback to .edgerc 113 func WithEnv(env bool) Option { 114 return func(c *Config) { 115 c.env = env 116 } 117 } 118 119 // FromFile creates a config the configuration in standard INI format 120 func (c *Config) FromFile(file string, section string) error { 121 var ( 122 requiredOptions = []string{"host", "client_token", "client_secret", "access_token"} 123 ) 124 125 path, err := homedir.Expand(file) 126 if err != nil { 127 return fmt.Errorf("invalid path: %w", err) 128 } 129 130 edgerc, err := ini.Load(path) 131 if err != nil { 132 return fmt.Errorf("%w: %s", ErrLoadingFile, err) 133 } 134 135 sec, err := edgerc.GetSection(section) 136 if err != nil { 137 return fmt.Errorf("%w: %s", ErrSectionDoesNotExist, err) 138 } 139 140 err = sec.MapTo(&c) 141 if err != nil { 142 return err 143 } 144 145 for _, opt := range requiredOptions { 146 if !(edgerc.Section(section).HasKey(opt)) { 147 return fmt.Errorf("%w: %q", ErrRequiredOptionEdgerc, opt) 148 } 149 } 150 151 if c.MaxBody == 0 { 152 c.MaxBody = MaxBodySize 153 } 154 155 return nil 156 } 157 158 // FromEnv creates a new config using the Environment (ENV) 159 // 160 // By default, it uses AKAMAI_HOST, AKAMAI_CLIENT_TOKEN, AKAMAI_CLIENT_SECRET, 161 // AKAMAI_ACCESS_TOKEN, and AKAMAI_MAX_BODY variables. 162 // 163 // You can define multiple configurations by prefixing with the section name specified, e.g. 164 // passing "ccu" will cause it to look for AKAMAI_CCU_HOST, etc. 165 // 166 // If AKAMAI_{SECTION} does not exist, it will fall back to just AKAMAI_. 167 func (c *Config) FromEnv(section string) error { 168 var ( 169 requiredOptions = []string{"HOST", "CLIENT_TOKEN", "CLIENT_SECRET", "ACCESS_TOKEN"} 170 prefix string 171 ) 172 173 prefix = "AKAMAI" 174 175 if section != DefaultSection { 176 prefix = "AKAMAI_" + strings.ToUpper(section) 177 } 178 179 for _, opt := range requiredOptions { 180 optKey := fmt.Sprintf("%s_%s", prefix, opt) 181 182 val, ok := os.LookupEnv(optKey) 183 if !ok { 184 return fmt.Errorf("%w: %q", ErrRequiredOptionEnv, optKey) 185 } 186 switch { 187 case opt == "HOST": 188 c.Host = val 189 case opt == "CLIENT_TOKEN": 190 c.ClientToken = val 191 case opt == "CLIENT_SECRET": 192 c.ClientSecret = val 193 case opt == "ACCESS_TOKEN": 194 c.AccessToken = val 195 } 196 } 197 198 c.MaxBody = 0 199 200 val := os.Getenv(fmt.Sprintf("%s_%s", prefix, "MAX_BODY")) 201 if i, err := strconv.Atoi(val); err == nil { 202 c.MaxBody = i 203 } 204 205 if c.MaxBody <= 0 { 206 c.MaxBody = MaxBodySize 207 } 208 209 val, ok := os.LookupEnv(fmt.Sprintf("%s_%s", prefix, "ACCOUNT_KEY")) 210 if ok { 211 c.AccountKey = val 212 } 213 214 return nil 215 } 216 217 // Timestamp returns an edgegrid timestamp from the time 218 func Timestamp(t time.Time) string { 219 local := time.FixedZone("GMT", 0) 220 t = t.In(local) 221 return t.Format("20060102T15:04:05-0700") 222 } 223 224 // Validate verifies that the host is not ending with the slash character 225 func (c *Config) Validate() error { 226 if strings.HasSuffix(c.Host, "/") { 227 return fmt.Errorf("%w: %q", ErrHostContainsSlashAtTheEnd, c.Host) 228 } 229 return nil 230 }