github.com/webonyx/up@v0.7.4-0.20180808230834-91b94e551323/config/config.go (about)

     1  package config
     2  
     3  import (
     4  	"encoding/json"
     5  	"io/ioutil"
     6  	"os"
     7  
     8  	"github.com/apex/log"
     9  	"github.com/pkg/errors"
    10  
    11  	"github.com/apex/up/internal/header"
    12  	"github.com/apex/up/internal/inject"
    13  	"github.com/apex/up/internal/redirect"
    14  	"github.com/apex/up/internal/validate"
    15  	"github.com/apex/up/platform/aws/regions"
    16  	"github.com/aws/aws-sdk-go/aws/session"
    17  )
    18  
    19  // defaulter is the interface that provides config defaulting.
    20  type defaulter interface {
    21  	Default() error
    22  }
    23  
    24  // validator is the interface that provides config validation.
    25  type validator interface {
    26  	Validate() error
    27  }
    28  
    29  // Config for the project.
    30  type Config struct {
    31  	Name        string         `json:"name"`
    32  	Description string         `json:"description"`
    33  	Type        string         `json:"type"`
    34  	Headers     header.Rules   `json:"headers"`
    35  	Redirects   redirect.Rules `json:"redirects"`
    36  	Hooks       Hooks          `json:"hooks"`
    37  	Environment Environment    `json:"environment"`
    38  	Regions     []string       `json:"regions"`
    39  	Profile     string         `json:"profile"`
    40  	Inject      inject.Rules   `json:"inject"`
    41  	Lambda      Lambda         `json:"lambda"`
    42  	CORS        *CORS          `json:"cors"`
    43  	ErrorPages  ErrorPages     `json:"error_pages"`
    44  	Proxy       Relay          `json:"proxy"`
    45  	Static      Static         `json:"static"`
    46  	Logs        Logs           `json:"logs"`
    47  	Stages      Stages         `json:"stages"`
    48  	DNS         DNS            `json:"dns"`
    49  	Alerting
    50  }
    51  
    52  // Validate implementation.
    53  func (c *Config) Validate() error {
    54  	if err := validate.RequiredString(c.Name); err != nil {
    55  		return errors.Wrap(err, ".name")
    56  	}
    57  
    58  	if err := validate.Name(c.Name); err != nil {
    59  		return errors.Wrapf(err, ".name %q", c.Name)
    60  	}
    61  
    62  	if err := validate.List(c.Type, []string{"static", "server"}); err != nil {
    63  		return errors.Wrap(err, ".type")
    64  	}
    65  
    66  	if err := validate.Lists(c.Regions, regions.IDs); err != nil {
    67  		return errors.Wrap(err, ".regions")
    68  	}
    69  
    70  	if err := c.DNS.Validate(); err != nil {
    71  		return errors.Wrap(err, ".dns")
    72  	}
    73  
    74  	if err := c.Static.Validate(); err != nil {
    75  		return errors.Wrap(err, ".static")
    76  	}
    77  
    78  	if err := c.Inject.Validate(); err != nil {
    79  		return errors.Wrap(err, ".inject")
    80  	}
    81  
    82  	if err := c.Lambda.Validate(); err != nil {
    83  		return errors.Wrap(err, ".lambda")
    84  	}
    85  
    86  	if err := c.Proxy.Validate(); err != nil {
    87  		return errors.Wrap(err, ".proxy")
    88  	}
    89  
    90  	if err := c.Stages.Validate(); err != nil {
    91  		return errors.Wrap(err, ".stages")
    92  	}
    93  
    94  	if err := c.Alerting.Validate(); err != nil {
    95  		return err
    96  	}
    97  
    98  	if len(c.Regions) > 1 {
    99  		return errors.New("multiple regions is not yet supported, see https://github.com/apex/up/issues/134")
   100  	}
   101  
   102  	return nil
   103  }
   104  
   105  // Default implementation.
   106  func (c *Config) Default() error {
   107  	if c.Stages == nil {
   108  		c.Stages = make(Stages)
   109  	}
   110  
   111  	// we default stages here before others simply to
   112  	// initialize the default stages such as "development"
   113  	// allowing runtime inference to default values.
   114  	if err := c.Stages.Default(); err != nil {
   115  		return errors.Wrap(err, ".stages")
   116  	}
   117  
   118  	// TODO: hack, move to the instantiation of aws clients
   119  	if c.Profile != "" {
   120  		setProfile(c.Profile)
   121  	}
   122  
   123  	// default type to server
   124  	if c.Type == "" {
   125  		c.Type = "server"
   126  	}
   127  
   128  	// runtime defaults
   129  	if c.Type != "static" {
   130  		runtime := inferRuntime()
   131  		log.WithField("type", runtime).Debug("inferred runtime")
   132  
   133  		if err := runtimeConfig(runtime, c); err != nil {
   134  			return errors.Wrap(err, "runtime")
   135  		}
   136  	}
   137  
   138  	// default .regions
   139  	if err := c.defaultRegions(); err != nil {
   140  		return errors.Wrap(err, ".region")
   141  	}
   142  
   143  	// region globbing
   144  	c.Regions = regions.Match(c.Regions)
   145  
   146  	// default .proxy
   147  	if err := c.Proxy.Default(); err != nil {
   148  		return errors.Wrap(err, ".proxy")
   149  	}
   150  
   151  	// default .lambda
   152  	if err := c.Lambda.Default(); err != nil {
   153  		return errors.Wrap(err, ".lambda")
   154  	}
   155  
   156  	// default .dns
   157  	if err := c.DNS.Default(); err != nil {
   158  		return errors.Wrap(err, ".dns")
   159  	}
   160  
   161  	// default .logs
   162  	if err := c.Logs.Default(); err != nil {
   163  		return errors.Wrap(err, ".logs")
   164  	}
   165  
   166  	// default .inject
   167  	if err := c.Inject.Default(); err != nil {
   168  		return errors.Wrap(err, ".inject")
   169  	}
   170  
   171  	// default .error_pages
   172  	if err := c.ErrorPages.Default(); err != nil {
   173  		return errors.Wrap(err, ".error_pages")
   174  	}
   175  
   176  	// default .stages
   177  	if err := c.Stages.Default(); err != nil {
   178  		return errors.Wrap(err, ".stages")
   179  	}
   180  
   181  	// default .alerting
   182  	if err := c.Alerting.Default(); err != nil {
   183  		return err
   184  	}
   185  
   186  	return nil
   187  }
   188  
   189  // Override with stage config if present, and re-validate.
   190  func (c *Config) Override(stage string) error {
   191  	s := c.Stages.GetByName(stage)
   192  	if s == nil {
   193  		return nil
   194  	}
   195  
   196  	s.Override(c)
   197  
   198  	if err := c.Lambda.Validate(); err != nil {
   199  		return errors.Wrap(err, ".lambda")
   200  	}
   201  
   202  	return nil
   203  }
   204  
   205  // defaultRegions checks AWS_REGION and falls back on us-west-2.
   206  func (c *Config) defaultRegions() error {
   207  	if len(c.Regions) != 0 {
   208  		log.Debugf("%d regions from config", len(c.Regions))
   209  		return nil
   210  	}
   211  
   212  	s, err := session.NewSessionWithOptions(session.Options{
   213  		SharedConfigState: session.SharedConfigEnable,
   214  	})
   215  
   216  	if err != nil {
   217  		return errors.Wrap(err, "creating session")
   218  	}
   219  
   220  	if r := *s.Config.Region; r != "" {
   221  		log.Debugf("region from aws shared config %q", r)
   222  		c.Regions = append(c.Regions, r)
   223  		return nil
   224  	}
   225  
   226  	r := "us-west-2"
   227  	log.Debugf("region defaulted to %q", r)
   228  	c.Regions = append(c.Regions, r)
   229  	return nil
   230  }
   231  
   232  // ParseConfig returns config from JSON bytes.
   233  func ParseConfig(b []byte) (*Config, error) {
   234  	c := &Config{}
   235  
   236  	if err := json.Unmarshal(b, c); err != nil {
   237  		return nil, errors.Wrap(err, "parsing json")
   238  	}
   239  
   240  	if err := c.Default(); err != nil {
   241  		return nil, errors.Wrap(err, "defaulting")
   242  	}
   243  
   244  	if err := c.Validate(); err != nil {
   245  		return nil, errors.Wrap(err, "validating")
   246  	}
   247  
   248  	return c, nil
   249  }
   250  
   251  // ParseConfigString returns config from JSON string.
   252  func ParseConfigString(s string) (*Config, error) {
   253  	return ParseConfig([]byte(s))
   254  }
   255  
   256  // MustParseConfigString returns config from JSON string.
   257  func MustParseConfigString(s string) *Config {
   258  	c, err := ParseConfigString(s)
   259  	if err != nil {
   260  		panic(err)
   261  	}
   262  
   263  	return c
   264  }
   265  
   266  // ReadConfig reads the configuration from `path`.
   267  func ReadConfig(path string) (*Config, error) {
   268  	b, err := ioutil.ReadFile(path)
   269  	if err != nil {
   270  		return nil, err
   271  	}
   272  
   273  	return ParseConfig(b)
   274  }
   275  
   276  // setProfile sets the AWS_PROFILE.
   277  func setProfile(name string) {
   278  	os.Setenv("AWS_PROFILE", name)
   279  }