github.com/akamai/AkamaiOPEN-edgegrid-golang/v4@v4.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 Debug bool `ini:"debug"` 52 53 file string 54 section string 55 env bool 56 } 57 58 // Option defines a configuration option 59 Option func(*Config) 60 ) 61 62 // New returns new configuration with the specified options 63 func New(opts ...Option) (*Config, error) { 64 c := &Config{ 65 section: DefaultSection, 66 env: false, 67 } 68 69 for _, opt := range opts { 70 opt(c) 71 } 72 73 if c.env { 74 if err := c.FromEnv(c.section); err == nil { 75 return c, nil 76 } else if !errors.Is(err, ErrRequiredOptionEnv) { 77 return nil, err 78 } 79 } 80 81 if c.file != "" { 82 if err := c.FromFile(c.file, c.section); err != nil { 83 return c, fmt.Errorf("unable to load config from environment or .edgerc file: %w", err) 84 } 85 } 86 87 return c, nil 88 } 89 90 // Must will panic if the new method returns an error 91 func Must(config *Config, err error) *Config { 92 if err != nil { 93 panic(err) 94 } 95 return config 96 } 97 98 // WithFile sets the config file path 99 func WithFile(file string) Option { 100 return func(c *Config) { 101 c.file = file 102 } 103 } 104 105 // WithSection sets the section in the config 106 func WithSection(section string) Option { 107 return func(c *Config) { 108 c.section = section 109 } 110 } 111 112 // WithEnv sets the option to try to the environment vars to populate the config 113 // If loading from the env fails, will fallback to .edgerc 114 func WithEnv(env bool) Option { 115 return func(c *Config) { 116 c.env = env 117 } 118 } 119 120 // FromFile creates a config the configuration in standard INI format 121 func (c *Config) FromFile(file string, section string) error { 122 var ( 123 requiredOptions = []string{"host", "client_token", "client_secret", "access_token"} 124 ) 125 126 path, err := homedir.Expand(file) 127 if err != nil { 128 return fmt.Errorf("invalid path: %w", err) 129 } 130 131 edgerc, err := ini.Load(path) 132 if err != nil { 133 return fmt.Errorf("%w: %s", ErrLoadingFile, err) 134 } 135 136 sec, err := edgerc.GetSection(section) 137 if err != nil { 138 return fmt.Errorf("%w: %s", ErrSectionDoesNotExist, err) 139 } 140 141 err = sec.MapTo(&c) 142 if err != nil { 143 return err 144 } 145 146 for _, opt := range requiredOptions { 147 if !(edgerc.Section(section).HasKey(opt)) { 148 return fmt.Errorf("%w: %q", ErrRequiredOptionEdgerc, opt) 149 } 150 } 151 152 if c.MaxBody == 0 { 153 c.MaxBody = MaxBodySize 154 } 155 156 return nil 157 } 158 159 // FromEnv creates a new config using the Environment (ENV) 160 // 161 // By default, it uses AKAMAI_HOST, AKAMAI_CLIENT_TOKEN, AKAMAI_CLIENT_SECRET, 162 // AKAMAI_ACCESS_TOKEN, and AKAMAI_MAX_BODY variables. 163 // 164 // You can define multiple configurations by prefixing with the section name specified, e.g. 165 // passing "ccu" will cause it to look for AKAMAI_CCU_HOST, etc. 166 // 167 // If AKAMAI_{SECTION} does not exist, it will fall back to just AKAMAI_. 168 func (c *Config) FromEnv(section string) error { 169 var ( 170 requiredOptions = []string{"HOST", "CLIENT_TOKEN", "CLIENT_SECRET", "ACCESS_TOKEN"} 171 prefix string 172 ) 173 174 prefix = "AKAMAI" 175 176 if section != DefaultSection { 177 prefix = "AKAMAI_" + strings.ToUpper(section) 178 } 179 180 for _, opt := range requiredOptions { 181 optKey := fmt.Sprintf("%s_%s", prefix, opt) 182 183 val, ok := os.LookupEnv(optKey) 184 if !ok { 185 return fmt.Errorf("%w: %q", ErrRequiredOptionEnv, optKey) 186 } 187 switch { 188 case opt == "HOST": 189 c.Host = val 190 case opt == "CLIENT_TOKEN": 191 c.ClientToken = val 192 case opt == "CLIENT_SECRET": 193 c.ClientSecret = val 194 case opt == "ACCESS_TOKEN": 195 c.AccessToken = val 196 } 197 } 198 199 c.MaxBody = 0 200 201 val := os.Getenv(fmt.Sprintf("%s_%s", prefix, "MAX_BODY")) 202 if i, err := strconv.Atoi(val); err == nil { 203 c.MaxBody = i 204 } 205 206 if c.MaxBody <= 0 { 207 c.MaxBody = MaxBodySize 208 } 209 210 val, ok := os.LookupEnv(fmt.Sprintf("%s_%s", prefix, "ACCOUNT_KEY")) 211 if ok { 212 c.AccountKey = val 213 } 214 215 return nil 216 } 217 218 // Timestamp returns an edgegrid timestamp from the time 219 func Timestamp(t time.Time) string { 220 local := time.FixedZone("GMT", 0) 221 t = t.In(local) 222 return t.Format("20060102T15:04:05-0700") 223 } 224 225 // Validate verifies that the host is not ending with the slash character 226 func (c *Config) Validate() error { 227 if strings.HasSuffix(c.Host, "/") { 228 return fmt.Errorf("%w: %q", ErrHostContainsSlashAtTheEnd, c.Host) 229 } 230 return nil 231 }