github.com/jalateras/up@v0.1.5/config.go (about) 1 package up 2 3 import ( 4 "encoding/json" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 9 "github.com/apex/log" 10 "github.com/pkg/errors" 11 12 "github.com/apex/up/config" 13 "github.com/apex/up/internal/header" 14 "github.com/apex/up/internal/inject" 15 "github.com/apex/up/internal/redirect" 16 "github.com/apex/up/internal/util" 17 "github.com/apex/up/internal/validate" 18 "github.com/apex/up/platform/lambda/regions" 19 ) 20 21 // TODO: refactor defaulting / validation with slices 22 23 // defaulter is the interface that provides config defaulting. 24 type defaulter interface { 25 Default() error 26 } 27 28 // validator is the interface that provides config validation. 29 type validator interface { 30 Validate() error 31 } 32 33 // Config for the project. 34 type Config struct { 35 // Name of the project. 36 Name string `json:"name"` 37 38 // Description of the project. 39 Description string `json:"description"` 40 41 // Type of project. 42 Type string `json:"type"` 43 44 // Headers injection rules. 45 Headers header.Rules `json:"headers"` 46 47 // Redirects redirection rules. 48 Redirects redirect.Rules `json:"redirects"` 49 50 // Hooks defined for the project. 51 Hooks config.Hooks `json:"hooks"` 52 53 // Environment variables. 54 Environment config.Environment `json:"environment"` 55 56 // Regions is a list of regions to deploy to. 57 Regions []string `json:"regions"` 58 59 // Profile is the AWS profile name to reference for credentials. 60 Profile string `json:"profile"` 61 62 // Inject rules. 63 Inject inject.Rules `json:"inject"` 64 65 // Lambda provider configuration. 66 Lambda config.Lambda `json:"lambda"` 67 68 // CORS config. 69 CORS *config.CORS `json:"cors"` 70 71 // ErrorPages config. 72 ErrorPages config.ErrorPages `json:"error_pages"` 73 74 // Proxy config. 75 Proxy config.Relay `json:"proxy"` 76 77 // Static config. 78 Static config.Static `json:"static"` 79 80 // Logs config. 81 Logs config.Logs `json:"logs"` 82 83 // Certs config. 84 Certs config.Certs `json:"certs"` 85 86 // DNS config. 87 DNS config.DNS `json:"dns"` 88 } 89 90 // Validate implementation. 91 func (c *Config) Validate() error { 92 if err := validate.Name(c.Name); err != nil { 93 return errors.Wrapf(err, ".name %q", c.Name) 94 } 95 96 if err := validate.List(c.Type, []string{"static", "server"}); err != nil { 97 return errors.Wrap(err, ".type") 98 } 99 100 if err := validate.Lists(c.Regions, regions.All); err != nil { 101 return errors.Wrap(err, ".regions") 102 } 103 104 if err := c.Certs.Validate(); err != nil { 105 return errors.Wrap(err, ".certs") 106 } 107 108 if err := c.DNS.Validate(); err != nil { 109 return errors.Wrap(err, ".dns") 110 } 111 112 if err := c.Static.Validate(); err != nil { 113 return errors.Wrap(err, ".static") 114 } 115 116 if err := c.Inject.Validate(); err != nil { 117 return errors.Wrap(err, ".inject") 118 } 119 120 return nil 121 } 122 123 // Default implementation. 124 func (c *Config) Default() error { 125 // TODO: hack, move to the instantiation of aws clients 126 if c.Profile != "" { 127 os.Setenv("AWS_PROFILE", c.Profile) 128 } 129 130 // default type to server 131 if c.Type == "" { 132 c.Type = "server" 133 } 134 135 // runtime defaults 136 switch { 137 case util.Exists("main.go"): 138 golang(c) 139 case util.Exists("main.cr"): 140 crystal(c) 141 case util.Exists("package.json"): 142 if err := nodejs(c); err != nil { 143 return err 144 } 145 case util.Exists("app.js"): 146 c.Proxy.Command = "node app.js" 147 case util.Exists("app.py"): 148 c.Proxy.Command = "python app.py" 149 case util.Exists("index.html"): 150 c.Type = "static" 151 } 152 153 // default .name 154 if err := c.defaultName(); err != nil { 155 return errors.Wrap(err, ".name") 156 } 157 158 // default .regions 159 if err := c.defaultRegions(); err != nil { 160 return errors.Wrap(err, ".region") 161 } 162 163 // region globbing 164 c.Regions = regions.Match(c.Regions) 165 166 if err := c.Proxy.Default(); err != nil { 167 return errors.Wrap(err, ".proxy") 168 } 169 170 // default .lambda 171 if err := c.Lambda.Default(); err != nil { 172 return errors.Wrap(err, ".lambda") 173 } 174 175 // default .dns 176 if err := c.DNS.Default(); err != nil { 177 return errors.Wrap(err, ".dns") 178 } 179 180 // default .inject 181 if err := c.Inject.Default(); err != nil { 182 return errors.Wrap(err, ".inject") 183 } 184 185 // default .static 186 if err := c.Static.Default(); err != nil { 187 return errors.Wrap(err, ".static") 188 } 189 190 // default .error_pages 191 if err := c.ErrorPages.Default(); err != nil { 192 return errors.Wrap(err, ".error_pages") 193 } 194 195 return nil 196 } 197 198 // defaultName infers the name from the CWD if it's not set. 199 func (c *Config) defaultName() error { 200 if c.Name != "" { 201 return nil 202 } 203 204 dir, err := os.Getwd() 205 if err != nil { 206 return err 207 } 208 209 c.Name = filepath.Base(dir) 210 log.Debugf("infer name from current working directory %q", c.Name) 211 return nil 212 } 213 214 // defaultRegions checks AWS_REGION and falls back on us-west-2. 215 func (c *Config) defaultRegions() error { 216 if len(c.Regions) != 0 { 217 log.Debugf("%d regions from config", len(c.Regions)) 218 return nil 219 } 220 221 if s := os.Getenv("AWS_REGION"); s != "" { 222 log.Debugf("region from AWS_REGION %q", s) 223 c.Regions = append(c.Regions, s) 224 return nil 225 } 226 227 s := "us-west-2" 228 log.Debugf("region defaulted to %q", s) 229 c.Regions = append(c.Regions, s) 230 return nil 231 } 232 233 // ParseConfig returns config from JSON bytes. 234 func ParseConfig(b []byte) (*Config, error) { 235 c := &Config{} 236 237 if err := json.Unmarshal(b, c); err != nil { 238 return nil, errors.Wrap(err, "parsing json") 239 } 240 241 if err := c.Default(); err != nil { 242 return nil, errors.Wrap(err, "defaulting") 243 } 244 245 if err := c.Validate(); err != nil { 246 return nil, errors.Wrap(err, "validating") 247 } 248 249 return c, nil 250 } 251 252 // ParseConfigString returns config from JSON string. 253 func ParseConfigString(s string) (*Config, error) { 254 return ParseConfig([]byte(s)) 255 } 256 257 // MustParseConfigString returns config from JSON string. 258 func MustParseConfigString(s string) *Config { 259 c, err := ParseConfigString(s) 260 if err != nil { 261 panic(err) 262 } 263 264 return c 265 } 266 267 // ReadConfig reads the configuration from `path`. 268 func ReadConfig(path string) (*Config, error) { 269 b, err := ioutil.ReadFile(path) 270 271 if os.IsNotExist(err) { 272 c := &Config{} 273 274 if err := c.Default(); err != nil { 275 return nil, errors.Wrap(err, "defaulting") 276 } 277 278 if err := c.Validate(); err != nil { 279 return nil, errors.Wrap(err, "validating") 280 } 281 282 return c, nil 283 } 284 285 if err != nil { 286 return nil, errors.Wrap(err, "reading file") 287 } 288 289 return ParseConfig(b) 290 } 291 292 // golang config. 293 func golang(c *Config) { 294 if c.Hooks.Build == "" { 295 c.Hooks.Build = `GOOS=linux GOARCH=amd64 go build -o server *.go` 296 } 297 298 if c.Hooks.Clean == "" { 299 c.Hooks.Clean = `rm server` 300 } 301 } 302 303 // crystal config. 304 func crystal(c *Config) { 305 if c.Hooks.Build == "" { 306 c.Hooks.Build = `docker run --rm -v $(PWD):/src -w /src tjholowaychuk/up-crystal crystal build --link-flags -static -o server main.cr` 307 } 308 309 if c.Hooks.Clean == "" { 310 c.Hooks.Clean = `rm server` 311 } 312 } 313 314 // nodejs config. 315 func nodejs(c *Config) error { 316 var pkg struct { 317 Scripts struct { 318 Start string `json:"start"` 319 Build string `json:"build"` 320 } `json:"scripts"` 321 } 322 323 if err := util.ReadFileJSON("package.json", &pkg); err != nil { 324 return err 325 } 326 327 // start script 328 if s := pkg.Scripts.Start; s != "" { 329 c.Proxy.Command = s 330 } else { 331 return errors.New(`the "start" script must be present in package.json`) 332 } 333 334 // build script 335 if c.Hooks.Build == "" { 336 c.Hooks.Build = pkg.Scripts.Build 337 } 338 339 return nil 340 }