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