Repository: apex/up
Branch: master
Commit: 66dbf6d5e836
Files: 230
Total size: 618.2 KB
Directory structure:
gitextract_3gd4es3i/
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE.md
│ └── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .goreleaser.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── History.md
├── LICENSE
├── Makefile
├── Readme.md
├── cmd/
│ ├── up/
│ │ └── main.go
│ └── up-proxy/
│ └── main.go
├── config/
│ ├── backoff.go
│ ├── backoff_test.go
│ ├── config.go
│ ├── config_test.go
│ ├── cors.go
│ ├── dns.go
│ ├── dns_test.go
│ ├── doc.go
│ ├── duration.go
│ ├── duration_test.go
│ ├── environment.go
│ ├── errorpages.go
│ ├── errorpages_test.go
│ ├── hooks.go
│ ├── hooks_test.go
│ ├── lambda.go
│ ├── lambda_test.go
│ ├── logs.go
│ ├── relay.go
│ ├── runtimes.go
│ ├── stages.go
│ ├── stages_test.go
│ ├── static.go
│ └── static_test.go
├── docs/
│ ├── 00-introduction.md
│ ├── 01-installation.md
│ ├── 02-aws-credentials.md
│ ├── 03-getting-started.md
│ ├── 04-configuration.md
│ ├── 05-runtimes.md
│ ├── 06-commands.md
│ ├── 07-guides.md
│ ├── 08-troubleshooting.md
│ ├── 09-faq.md
│ └── 10-links.md
├── go.mod
├── go.sum
├── handler/
│ ├── handler.go
│ ├── handler_test.go
│ └── testdata/
│ ├── node/
│ │ ├── app.js
│ │ └── up.json
│ ├── node-pkg/
│ │ ├── app.js
│ │ ├── package.json
│ │ └── up.json
│ ├── node-pkg-start/
│ │ ├── index.js
│ │ ├── package.json
│ │ └── up.json
│ ├── spa/
│ │ ├── app.js
│ │ ├── css/
│ │ │ ├── bar.css
│ │ │ └── foo.css
│ │ ├── index.html
│ │ └── up.json
│ ├── static/
│ │ ├── index.html
│ │ ├── style.css
│ │ └── up.json
│ ├── static-redirects/
│ │ ├── help/
│ │ │ └── ping/
│ │ │ └── alerts/
│ │ │ └── index.html
│ │ ├── index.html
│ │ └── up.json
│ └── static-rewrites/
│ ├── help/
│ │ └── ping/
│ │ └── alerts.html
│ ├── index.html
│ └── up.json
├── http/
│ ├── cors/
│ │ ├── cors.go
│ │ └── cors_test.go
│ ├── errorpages/
│ │ ├── errorpages.go
│ │ ├── errorpages_test.go
│ │ └── testdata/
│ │ ├── defaults/
│ │ │ ├── index.html
│ │ │ └── up.json
│ │ └── templates/
│ │ ├── 404.html
│ │ ├── 5xx.html
│ │ ├── index.html
│ │ └── up.json
│ ├── gzip/
│ │ ├── gzip.go
│ │ └── gzip_test.go
│ ├── headers/
│ │ ├── headers.go
│ │ ├── headers_test.go
│ │ └── testdata/
│ │ ├── _headers
│ │ ├── index.html
│ │ ├── style.css
│ │ └── up.json
│ ├── inject/
│ │ ├── inject.go
│ │ ├── inject_test.go
│ │ └── testdata/
│ │ ├── 404.html
│ │ ├── index.html
│ │ ├── style.css
│ │ └── up.json
│ ├── logs/
│ │ ├── logs.go
│ │ ├── logs_test.go
│ │ └── testdata/
│ │ ├── index.html
│ │ └── up.json
│ ├── poweredby/
│ │ ├── poweredby.go
│ │ ├── poweredby_test.go
│ │ └── testdata/
│ │ ├── index.html
│ │ └── up.json
│ ├── redirects/
│ │ ├── redirects.go
│ │ └── redirects_test.go
│ ├── relay/
│ │ ├── relay.go
│ │ ├── relay_test.go
│ │ └── testdata/
│ │ ├── basic/
│ │ │ ├── app.js
│ │ │ └── up.json
│ │ └── node/
│ │ ├── package.json
│ │ ├── server.js
│ │ └── up.json
│ ├── robots/
│ │ ├── robots.go
│ │ ├── robots_test.go
│ │ └── testdata/
│ │ ├── index.html
│ │ └── up.json
│ └── static/
│ ├── static.go
│ ├── static_test.go
│ └── testdata/
│ ├── dynamic/
│ │ ├── app.js
│ │ ├── public/
│ │ │ └── css/
│ │ │ └── style.css
│ │ └── up.json
│ └── static/
│ ├── index.html
│ ├── style.css
│ └── up.json
├── install.sh
├── internal/
│ ├── account/
│ │ ├── account.go
│ │ └── cards.go
│ ├── cli/
│ │ ├── app/
│ │ │ └── app.go
│ │ ├── build/
│ │ │ └── build.go
│ │ ├── config/
│ │ │ └── config.go
│ │ ├── deploy/
│ │ │ └── deploy.go
│ │ ├── disable-stats/
│ │ │ └── disable-stats.go
│ │ ├── docs/
│ │ │ └── docs.go
│ │ ├── domains/
│ │ │ └── domains.go
│ │ ├── logs/
│ │ │ └── logs.go
│ │ ├── metrics/
│ │ │ └── metrics.go
│ │ ├── prune/
│ │ │ └── prune.go
│ │ ├── root/
│ │ │ └── root.go
│ │ ├── run/
│ │ │ └── run.go
│ │ ├── stack/
│ │ │ └── stack.go
│ │ ├── start/
│ │ │ └── start.go
│ │ ├── team/
│ │ │ └── team.go
│ │ ├── upgrade/
│ │ │ └── upgrade.go
│ │ ├── url/
│ │ │ └── url.go
│ │ └── version/
│ │ └── version.go
│ ├── colors/
│ │ └── colors.go
│ ├── errorpage/
│ │ ├── errorpage.go
│ │ ├── errorpage_test.go
│ │ ├── template.go
│ │ └── testdata/
│ │ ├── 200.html
│ │ ├── 404.html
│ │ ├── 4xx.html
│ │ ├── 500.html
│ │ ├── error.html
│ │ ├── other.html
│ │ └── somedir/
│ │ └── test.html
│ ├── header/
│ │ ├── header.go
│ │ └── header_test.go
│ ├── inject/
│ │ ├── inject.go
│ │ └── inject_test.go
│ ├── logs/
│ │ ├── logs.go
│ │ ├── parser/
│ │ │ ├── ast/
│ │ │ │ └── ast.go
│ │ │ ├── grammar.peg
│ │ │ ├── grammar.peg.go
│ │ │ ├── parser.go
│ │ │ └── parser_test.go
│ │ ├── text/
│ │ │ ├── text.go
│ │ │ └── text_test.go
│ │ └── writer/
│ │ ├── writer.go
│ │ └── writer_test.go
│ ├── metrics/
│ │ └── metrics.go
│ ├── progressreader/
│ │ └── progressreader.go
│ ├── proxy/
│ │ ├── bin/
│ │ │ └── bin.go
│ │ ├── event.go
│ │ ├── event_test.go
│ │ ├── lambda.go
│ │ ├── request.go
│ │ ├── request_test.go
│ │ ├── response.go
│ │ └── response_test.go
│ ├── redirect/
│ │ ├── redirect.go
│ │ └── redirect_test.go
│ ├── setup/
│ │ └── setup.go
│ ├── shim/
│ │ ├── index.js
│ │ └── shim.go
│ ├── signal/
│ │ └── signal.go
│ ├── stats/
│ │ └── stats.go
│ ├── userconfig/
│ │ ├── userconfig.go
│ │ └── userconfig_test.go
│ ├── util/
│ │ ├── util.go
│ │ └── util_test.go
│ ├── validate/
│ │ └── validate.go
│ └── zip/
│ ├── testdata/
│ │ ├── .file
│ │ ├── .upignore
│ │ ├── Readme.md
│ │ ├── bar.js
│ │ ├── foo.js
│ │ └── index.js
│ ├── zip.go
│ └── zip_test.go
├── platform/
│ ├── aws/
│ │ ├── cost/
│ │ │ ├── cost.go
│ │ │ ├── cost_test.go
│ │ │ └── domains.go
│ │ ├── domains/
│ │ │ └── domains.go
│ │ ├── logs/
│ │ │ └── logs.go
│ │ ├── regions/
│ │ │ ├── regions.go
│ │ │ └── regions_test.go
│ │ └── runtime/
│ │ └── runtime.go
│ ├── event/
│ │ └── event.go
│ └── lambda/
│ ├── lambda.go
│ ├── lambda_test.go
│ ├── metrics.go
│ ├── prune.go
│ ├── reporter/
│ │ └── reporter.go
│ └── stack/
│ ├── resources/
│ │ ├── resources.go
│ │ └── resources_test.go
│ ├── stack.go
│ ├── stack_test.go
│ ├── status.go
│ └── status_test.go
├── platform.go
├── reporter/
│ ├── discard/
│ │ └── discard.go
│ ├── plain/
│ │ └── plain.go
│ ├── reporter.go
│ └── text/
│ └── text.go
└── up.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
internal/proxy/bin/bin_assets.go filter=lfs diff=lfs merge=lfs -text
================================================
FILE: .github/FUNDING.yml
================================================
github: tj
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
## Prerequisites
* [ ] I am running the latest version. (`up upgrade`)
* [ ] I searched to see if the issue already exists.
* [ ] I inspected the verbose debug output with the `-v, --verbose` flag.
* [ ] Are you an Up Pro subscriber?
## Description
Describe the bug or feature.
## Steps to Reproduce
Describe the steps required to reproduce the issue if applicable.
## Slack
Join us on Slack https://chat.apex.sh/
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
Open an issue and discuss changes before spending time on them, unless the change is trivial or an issue already exists.
Use "VERB some thing here. Closes #n" to close the relevant issue, where VERB is one of:
- add
- remove
- change
- refactor
If the change is documentation related prefix with "docs: ", as these are filtered from the changelog.
docs: add ~/.aws/config
Run `dep ensure` if you introduce any new `import`'s so they're included in the ./vendor dir.
================================================
FILE: .gitignore
================================================
.envrc
node_modules/
.shards/
lib
vendor/
testing
up-proxy
!cmd/up-proxy
dist
.idea
.vscode
.DS_Store
internal/proxy/bin/bin_assets.go
internal/shim/bindata.go
================================================
FILE: .goreleaser.yml
================================================
build:
main: cmd/up/main.go
binary: up
goos:
- darwin
- linux
- windows
- freebsd
- netbsd
- openbsd
goarch:
- amd64
- 386
ignore:
- goos: darwin
goarch: 386
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^refactor'
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at tj@apex.sh. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
Before contributing to Up you'll need a few things:
- Install [Golang 1.11](https://golang.org/dl/) for that Go thing if you don't have it
The following are optional:
- Install [pointlander/peg](https://github.com/pointlander/peg) if you're working on the log grammar
- Install [shuLhan/go-bindata](https://github.com/shuLhan/go-bindata) if you need to bake `up-proxy` into `up`
## Setup
Grab Up:
```
$ go get github.com/apex/up
```
Change into the project:
```
$ cd $GOPATH/src/github.com/apex/up
```
## Testing
```
$ make test
```
## Layout
Although Up is not provided as a library it is structured as if it was, for organizational purposes. The project layout is loosely:
- *.go – Primary API
- [reporter](reporter) – Event based CLI reporting
- [platform](platform) – Platform specifics (AWS Lambda, Azure, Google, etc)
- [internal](internal) – Internal utilities and lower level tooling
- [http](http) – HTTP middleware for up-proxy
- [handler](handler) – HTTP middleware aggregate, effectively the entire proxy
- [docs](docs) – Documentation used to generate the static site
- [config](config) – Configuration structures and validation for `up.json`
- [cmd](cmd) – Commands, where `up` is the CLI and `up-proxy` is serving requests in production
Note that this is just a first pass, and the code / layout will be refactored. View [Godoc](http://godoc.org/github.com/apex/up) for more details of the internals.
## Proxy
One oddity is that the `up-proxy` is baked into `up`. Yes there's a binary within the binary :) – this is so `up` can inject the proxy before deploying your function to Lambda.
The proxy accepts AWS Lambda events from API Gateway, translates them to HTTP, and sends a request to your application, then translates it back to an event that API Gateway understands.
Reverse proxy features such as URL rewriting, gzip compression, script injection, error pages and others are also provided in `up-proxy`.
## Roadmap
Up uses GitHub issue tracking and milestones for its loose roadmap. I highly recommend installing Zenhub (https://www.zenhub.com/) as well, however I primarily organize by milestones and labels for now.
## Releases
Notes for myself:
- Run `make clean build` if necessary to re-build the proxy
- Run `git changelog`
- Run `git release`
- Run `make release`
- Re-build documentation
- Notes about any backwards compat issues, migration, IAM policy changes
- Adjust schemastore JSON schema if necessary
================================================
FILE: History.md
================================================
v1.7.1 / 2021-09-27
===================
* fix Lambda state issue by waiting for an Active state. Closes #833
v1.7.0-pro / 2020-10-07
=======================
* add support for tagging resources
v1.7.0 / 2020-10-07
===================
* add `lambda.timeout` back, defaulting to 60s. Closes #814
* change LICENSE, commercial use requires a subscription
v1.6.2 / 2020-09-23
===================
* Rebuild to decrease the binary filesize bloat
v1.6.2-pro / 2020-09-23
=======================
* Rebuild to decrease the binary filesize bloat
v1.6.1-pro / 2020-09-23
=======================
* Rebuild the proxy to include X-Up-Timeout
v1.6.0-pro / 2020-09-23
=======================
* Rebase
v1.6.0 / 2020-09-23
===================
* add support for X-Up-Timeout header field. Closes #815
* change id field to request_id
v1.5.2 / 2020-06-08
===================
* add Hong Kong region. Closes #804
* fix `up stack` panic due to missing res.DistributionDomainName. Closes #809
v1.5.1-pro / 2019-12-17
=======================
* Rebase
v1.5.1 / 2019-12-17
===================
* fix overriding of `lambda.runtime`
v1.5.0-pro / 2019-11-21
=======================
* Rebase
v1.5.0 / 2019-11-21
===================
* change error_pages to be disabled by default, use `enable: true` to add them
* fix stack delete behavior to not attempt to delete configured lambda roles. (Closes #787) (#788)
v1.4.1-pro / 2019-10-23
=======================
* fix: add blacklisting of up-env.json so it cannot be .upignored
v1.4.0-pro / 2019-10-23
=======================
* change warming functions to nodejs10.x, existing ones will be fine, as AWS doesn't
actually stop these functions, they just discontinue updating/creation
* Rebase
v1.4.0 / 2019-10-23
===================
* refactor nodejs shim to work on node8 or node10
* change default runtime to nodejs10.x (potentially breaking change, depends on your application). Closes #784
v1.3.0-pro / 2019-05-30
=======================
* add wrapping of env var logs with `logs.disable` check
v1.3.0 / 2019-05-30
===================
* add ./vendor to excluded directories by default
* refactor: regenerate parser with updated peg
* remove discount message, it didn't work
v1.2.0 / 2019-04-20
===================
* add 60% coupon
v1.2.0-pro / 2019-04-05
=======================
* add regional DNS with latency-based routing
* add `--region` flag for every command to override region id
v1.1.3-pro / 2019-04-02
=======================
* Rebase
v1.1.3 / 2019-04-02
===================
* update tj/aws dependency for duplicate logs fix
v1.1.2-pro / 2019-03-29
=======================
* Rebase
v1.1.2 / 2019-03-29
===================
* fix: update tj/aws dependency for ThrottlingException logs exception
v1.1.1-pro / 2019-03-23
=======================
* update warming function to nodejs8.10 to prevent EOL warning from AWS
v1.1.0-pro / 2019-03-04
===================
* add file based environment variables, removing the size restrictions
v1.0.0-pro / 2019-02-26
===================
* add regional endpoint support
v0.9.1-pro / 2019-01-21
=======================
* add sorting of env vars. Closes #750
v0.9.0-pro / 2018-12-13
=======================
* add Lambda layer support. Closes #743
v0.8.1-pro / 2018-12-11
=======================
* improve `up env export` performance, no longer linear time
* fix `up deploys` error when the stage is not deployed. Closes #716
* Rebase
v0.8.1 / 2018-12-11
===================
* update tj/aws for bug preventing all logs from being returned. Closes #733
* add deploy --no-build flag for skipping build hooks. Closes #730
* Release v0.8.0-pro
v0.8.0-pro / 2018-12-06
=======================
* improve `up env export` performance, no longer linear time
* fix `up deploys` error when the stage is not deployed. Closes #716
v0.8.0 / 2018-12-04
===================
* add endpoint URL to the deployment output
* add deploy stage to the deployment output
* add msg about Up Pro
* remove "not info" log example. Closes #724
* fix typo in deploy example. Closes #718
* fix: use crystallang/crystal for Crystal builds (#713)
v0.7.8-pro / 2018-09-24
=======================
* add `up env export` command for exposing env vars to shell scripts
v0.7.7-pro / 2018-09-17
=======================
* Rebase
v0.7.7 / 2018-09-17
===================
* update go-update dependency for copy regression
v0.7.5-pro / 2018-09-17
=======================
* Rebase
v0.7.5 / 2018-09-17
===================
* update go-update dependency for rename() to copy replacement
v0.7.4-pro / 2018-09-16
=======================
* add baked in env vars from SSM, env vars are no longer loaded at runtime. Closes #547
v0.7.4 / 2018-09-16
===================
* add request id to proxy errors
* fix missing lambda configurations costs. (#703)
* fix order relay errors so timeouts are returned first (leads to better error messages)
v0.7.3-pro / 2018-08-08
=======================
* Rebase
v0.7.3 / 2018-08-08
===================
* fix crash recovery in lambda, bug was introduced in v0.7.0
* update cors middleware for security when using allow-origin * and allow-credentials
v0.7.2-pro / 2018-07-23
=======================
* Rebase
v0.7.2 / 2018-07-23
===================
* add vpc stage override support. Closes #689
v0.7.1-pro / 2018-07-12
=======================
* Rebase
v0.7.1 / 2018-07-12
===================
* fix initial IAM role creation waiting due to error response change
v0.7.0-pro / 2018-07-11
=======================
* Rebase
v0.7.0 / 2018-07-11
===================
* add in-flight request timeouts.
* remove retries
* refactor crash recovery to be more robust
v0.6.8-pro / 2018-06-07
=======================
* Rebase
v0.6.8 / 2018-06-07
===================
* fix multiple set-cookie API Gateway limitation for real (previous had a bug)
v0.6.7-pro / 2018-06-07
=======================
* fix s3 acceleration update with existing S3 buckets
* Rebase
v0.6.7 / 2018-06-07
===================
* add striping of @owner/repo@ portion of Lerna tags. Closes #670
* fix multiple set-cookies API Gateway limitation with casing hack
* fix deployment with empty Git repo
* update AWS SDK versions for assuming roles. (#668)
v0.6.6-pro / 2018-05-24
=======================
* Rebase
v0.6.6 / 2018-05-24
===================
* add vpc support. Closes #281
* fix Crystal build on Linux: PWD => pwd (#664)
v0.6.5-pro / 2018-05-16
=======================
* Rebase
v0.6.5 / 2018-05-16
===================
* add hidden disable stats command (#659)
* add X-Context header field. Closes #657
* fix CORS header fields from being clobbered by error pages. Closes #661
v0.6.4-pro / 2018-05-09
=======================
* add deployment size to `up deploys` output
* add asterisk to denote current version in `up deploys` due to rollbacks
* Rebase
v0.6.4 / 2018-05-09
===================
* add support for customizing the Lambda function IAM role policy. Closes #539
* add support for specifying dns zone, and disabling it. Closes #536
* add support for updating the role policy upon deploy
* change default prune retention to 30 versions
v0.6.3-pro / 2018-05-02
=======================
* add deployment size to `up deploys` output
* add asterisk to denote current version in `up deploys` due to rollbacks
* Rebase
v0.6.3 / 2018-05-02
===================
* add `--stage` flag to `up build`
* add `--stage` flag to `up run`
* change logs, metrics, and url commands to use `-s` flag for stage. Closes #371 (BREAKING)
v0.6.2-pro / 2018-04-25
=======================
* Rebase
v0.6.2 / 2018-04-25
===================
* add up prune `--stage` flag. Closes #647
* add `up` to ignore whitelist by default
* remove retries on 5xx. Closes #485
* fix login bug preventing `--email` from overriding the active team email
v0.6.1-pro / 2018-04-16
=======================
* Rebase
v0.6.1 / 2018-04-16
===================
* add guard against `up stack plan` before `up`
* add `prune` command to remove old releases from S3. Closes #322
v0.6.0-pro / 2018-04-10
=======================
* Rebase
v0.6.0 / 2018-04-10
===================
* add annual plan subscription option
v0.5.17-pro / 2018-04-09
========================
* Rebase
v0.5.14 / 2018-04-09
====================
* add start command --stage flag. Closes #639
* fix scenario where JSON logs have invalid .level values
* refactor: add note about running `up upgrade` after subscribing
v0.5.16-pro / 2018-04-07
========================
* Rebase
v0.5.13 / 2018-04-07
====================
* fix "Error: fetching git commit: " error when Git is missing from the system
v0.5.15-pro / 2018-04-03
========================
* Rebase
v0.5.12 / 2018-04-03
====================
* add support for defining `lambda.runtime`
* add robots middleware (#627)
* change default runtime to nodejs 8.10
* refactor: remove redundant wrapping of "deploying" message
v0.5.11 / 2018-03-19
====================
* fix: update tj/go for Git signer fix
v0.5.12-pro / 2018-03-19
========================
* Rebase
v0.5.10 / 2018-03-19
====================
* fix: update tj/go for Git subject fix
v0.5.11-pro / 2018-03-16
========================
* refactor: add mapping of Alarm and Subscription for `up stack plan` output
* refactor: add .duration to Deploys track call
* Rebase
v0.5.9 / 2018-03-16
===================
* add support for serving static files with dynamic applications. Closes #174
v0.5.10-pro / 2018-03-15
===================
* add nicer `up rollback` failure message when version does not exist
* add git sha and tag support to `up rollback`
* add `up deploys` for listing deployments and versions
* fix log filter relational and equality operators with strings
v0.5.8 / 2018-03-15
===================
* fix log filter relational and equality operators with strings
v0.5.7 / 2018-03-15
===================
* add git versioning, used for Pro rollbacks and deployment changelog. Closes #100
v0.5.9-pro / 2018-03-09
=======================
* add stage overrides for lambda warming. Closes #615
v0.5.8-pro / 2018-03-05
=======================
* Rebase
v0.5.6 / 2018-03-05
===================
* add support for upgrading in-place up(1). Closes #607
* add CI specific upgrade to avoid progress bar
* fix: remove IsNotFound error check, masks the real issue
v0.5.7-pro / 2018-03-03
=======================
* Rebase
v0.5.5 / 2018-03-03
===================
* fix: improve idempotency of stack deletion
* docs: add sns to policy (necesary for Pro's alerting)
v0.5.6-pro / 2018-03-02
=======================
* add support for `=` delimited env vars ("FOO=bar")
* add support for passing multiple env vars to `up env set`
* add support for overriding envs for `up start` (`$ URL=xxx up start`)
v0.5.5-pro / 2018-03-01
=======================
* Rebase
v0.5.4 / 2018-03-01
===================
* add default `up start` command for Go and Crystal. Closes #581
* add log stage field to all logs, not just request-level
* add owner to `up team` output
* fix `up metrics` output, should be stage-specific, not global
* refactor: add humanized error when the stack (app) does not exist
* refactor: add stage name to beginning of log line instead of as a field
* refactor: add os/arch to debug logs to aid in support
* refactor: add alias upserts when updating (merged from pro)
* refactor: remove a redundant "deploying" error wrap
* refactor: tweak some error messages
* refactor: change perms of up.json to 0644. Closes #601
v0.5.4-pro / 2018-02-23
=======================
* Rebase
v0.5.3 / 2018-02-23
===================
* fix log flushing, make it synchronous. Closes #545
* docs: add changelog link
* docs: add mention of BINDIR
v0.5.3-pro / 2018-02-22
=======================
* add 1s sleep to /_ping endpoint for improved warming concurrency accuracy
* add `up env get` command for fetching a value
* Rebase
v0.5.2 / 2018-02-22
===================
* remove unsetting of `AWS_*` vars for now, reverts #590 fix
v0.5.1 / 2018-02-22
===================
* add function version to `up stack` output
* change `up team ci` to output base64 encoded config
* change UP_CONFIG to attempt base64-decode when not JSON (#594)
* fix proxy.command overrides. Closes #597
* fix .profile precedence. Closes #590
v0.5.2-pro / 2018-02-12
=======================
* add active warming support
* Rebase
v0.5.1-pro / 2018-02-08
=======================
* add `up env` --decrypt flag for emergencies when you need to list
v0.5.0-pro / 2018-02-08
=======================
* add nicer env var logging with masking
* add custom stage support to `up env`
* add message for `up env` when no vars are defined
* fix rollbacks using -previous aliases
* Rebase
v0.5.0 / 2018-02-08
===================
* add custom stage support. Closes #326
* add customer feedback option when unsubscribing
* add `up team card change` command for updating the CC
* remove sourcing of .gitignore. Closes #557
* remove development as a remote stage (now local only). Closes #563
* refactor: add separator to make log message more obvious
* refactor: add hiding of cursor when verifying email
* refactor retry labels below s3 uploads (improves performance)
* refactor: add nicer output when using `up url -c`
v0.4.12-pro / 2018-02-01
========================
* Rebase
v0.4.12 / 2018-02-01
====================
* add -o, --open to `up start` for opening in the browser
* add `logs.{stdout,stderr}` for configuring log levels. Closes #565
* add `-c, --command` flag to `up start`. Closes #564
* fix panic when .domain is missing from a stage, as it is now optional. Closes #567
* docs: add example .upignore for static sites
* docs: fix team members rm example. Closes #562
* docs: add "Unable to associate certificate error" to troubleshooting
* docs: add gin example
v0.4.11-pro / 2018-01-29
========================
* Rebase
v0.4.11 / 2018-01-29
====================
* add development config overrides to `up start`
* add the ability to override .proxy.command at the stage level
* docs: mention that the WHOIS contact emails are used
* docs: fix link for acm validation
* docs: tweak
* docs: add guide for hot reloading
* docs: remove old "Local Environment Variables" guide section
* docs: add gin example for dev command
v0.4.10-pro / 2018-01-25
========================
* Rebase
v0.4.10 / 2018-01-25
====================
* refactor to use a single account/region level S3 bucket, not per-project. Closes #550
* fix base64 encoded json when params are provided
v0.4.9-pro / 2018-01-24
=======================
* Rebase
v0.4.9 / 2018-01-24
===================
* revert tj/go-update, causing permission issues
v0.4.8-pro / 2018-01-24
=======================
* fix validating after overrides
v0.4.8 / 2018-01-24
===================
* update tj/go-update for copy instead of rename. Closes #329
* update api client for RemoveMember() json body change
* docs: add missing ssm to policy
* docs: add note about 404s
v0.4.7-pro / 2018-01-19
=======================
* add rollback support
* fix upgrade deduplication due to version having -pro suffix
v0.4.7 / 2018-01-19
===================
* add optimization of ACM certificate creation. Closes #452
* add `development` Lambda alias. Closes #542
* add start of stage overrides for config. Closes #314
* add support for upgrading to a specific version of Up. Closes #387
* update go-cli-analytics for disabled segment cli logging
* refactor handler.New() to accept an http.Handler
* refactor logging configuration, delegate isatty check etc
* refactor: move internal logs to tj/aws
* refactor platform integration quickly
v0.4.6-pro / 2018-01-03
=======================
* add rollback support
v0.4.5-pro / 2018-01-03
=======================
* add s3 acceleration
* fix a log call in runtime
v0.4.6 / 2018-01-03
===================
* add support for Clojure with Leiningen (#522)
* add coupon price adjustment to `up team` output. Closes #516
* add support for overriding NODE_ENV. Closes #505
* add error for multiple regions, until the feature is complete
* add Paris region
* change `error_pages` to be enabled by default for text/html requests
* refactor `handler.New()` to accept config
* refactor signal handling
* refactor: update api client
* refactor: remove unnecessary code (#517)
* refactor login and provide a non-error when you are already signed in
* fix s3 buckets, should be scoped to region
* fix output flickering before build output
* fix: add a ! in front of build.gradle for forced inclusion (#518)
v0.4.4-pro / 2017-12-22
=======================
* Rebase
v0.4.5 / 2017-12-22
===================
* add new subscribe workflow
* add team CRUD and rename `up account` to `up team`. Re #410
* refactor: replace `kingpin.CmdClause` with `kingpin.Cmd`
* refactor: use `time.Since` for time difference (#509)
* refactor: add "ci" to stats so we can see how often CI is used
* refactor: simplify start of plain reporter (#508)
* refactor: a typo fix in http/relay (#507)
* refactor: drop unnecessary `fmt.Sprintf` in reporter/text (#506)
* refactor: simplify personal team check (#500)
v0.4.3-pro / 2017-12-19
=======================
* Rebase
v0.4.4 / 2017-12-19
===================
* fix `up stack status` scenario before a domain is mapped
* refactor: config, simplify unmarshal json of dns. Closes #497
v0.4.2-pro / 2017-12-19
=======================
* Rebase
v0.4.3 / 2017-12-19
===================
* refactor: shorten s3 bucket name
v0.4.1-pro / 2017-12-19
=======================
* remove 0.0.0 hack for pro upgrade
* Rebase
v0.4.2 / 2017-12-19
===================
* change to disallow uppercase characters in .name. Closes #498
* refactor: add humanized string for the current version
* refactor: add config/backoff.go
v0.4.1 / 2017-12-18
===================
* fix upgrades to pro when version matches
v0.4.0-pro / 2017-12-18
=======================
* add slack `gif` option
* add slack alert support
* add initialization of env vars for builds. Closes #458
* add initialization of env vars for deployments. Closes #458
* add initialization of env vars for `up start`. Closes #458
* add `{alerts,actions}_count` to Deploy track
* change missing default to `notBreaching`
* refactor: add title casing to `up env` output
v0.4.0 / 2017-12-18
===================
* add unquoted string literals for log queries
* add log string sans-quote literal. Closes #461
* add log message field equality short-hand. Closes #372
* add CI=true check for plain text output. Re #422
* add --format=plain for CI. Closes #422
* add setup workflow for creating up.json and doing the initial deploy. Closes #482, #386
* add `NODE_ENV` population by default
* add env vars to `up start`
* add s3 deployments. Closes #272
* add cloudfront endpoint to `up stack` output. Closes #459
* change logs to purple (match everything else)
* change how expanded log mode looks
* remove `--region` flag
* fix upgrade messages for OSS -> Pro
* fix clearing state in text reporter
v0.3.0-pro / 2017-12-03
=======================
* add sms alerting support
v0.2.0-pro / 2017-12-03
=======================
* add hosted email alerting for nicer formatting
* change alert default `period` to 1m
v0.1.11-pro / 2017-11-30
========================
* add support for listing secrets without last modified user name
* fix secrets listing when user ARN is not present. Closes #433
* refactor alerting into new resources sub-pkg
* Rebase
v0.3.8 / 2017-11-30
===================
* add {pre,post}{build,deploy} hooks
* add flushing of logs after [re]start. See #359
* add "w" for week to `ParseDuration()`
* refactor: fix Map for now
* refactor: use effective domain for CFN id
* refactor: add test for existing zone and apex domain
* refactor: add test for existing zone
* refactor: add test coverage for CFN resources
* fix hosted zones for sub-domains. Closes #447
* fix `.type` precedence when runtime files are detected. Closes #436
v0.3.7 / 2017-11-24
===================
* add date formatting for older logs
* remove project init from `up account login`
* fix timestamps for lambda plain text logs
v0.1.10-pro / 2017-11-23
========================
* add support for listing secrets without last modified user name
* fix secrets listing when user ARN is not present. Closes #433
* Rebase
v0.3.6 / 2017-11-22
===================
* fix subscription without coupon
v0.1.9-pro / 2017-11-21
=======================
* Rebase
v0.3.5 / 2017-11-21
===================
* add `stage` field to all log contexts (fixes log filtering against `production`)
* fix DNS record logical id collision. Closes #420
* refactor `up stack` output
v0.1.8-pro / 2017-11-20
=======================
* add TreatMissingData as ignore by default
v0.1.7-pro / 2017-11-20
=======================
* fix email alerting
v0.1.6-pro / 2017-11-20
=======================
* add initial alerting support
v0.1.5-pro / 2017-11-20
=======================
* fix "development" env support for `up env`
* Rebase
v0.3.4 / 2017-11-20
===================
* add `up accounts ci` and --copy to help with setting up UP_CONFIG for CI
* fix domain verification for ssl certificates. Closes #425
* update tj/kingpin for arg output formatting fix
v0.1.4-pro / 2017-11-18
=======================
* Rebase
v0.3.3 / 2017-11-18
===================
* fix zip paths on Windows. Closes #418
v0.1.3-pro / 2017-11-18
=======================
* Rebase
v0.3.2 / 2017-11-18
===================
* add support for UP_CONFIG from environment
* add `up docs` command back for opening documentation in the browser
* change logs `--since` default to 1 day
* fix intermittent metrics failure. Closes #414
v0.3.1 / 2017-11-15
===================
* add `up account` and sub-commands
* add extended duration parsing for `--since` flags. Closes #401
* add log expansion. Closes #399
* add Content-Length request header
* add request logs
* add pom.xml and build.grade to whitelist which cannot be ignored
* change metrics `--since` default to 1 month
* refactor: remove .size defaulting of 0
* refactor progress bar with diffing, making it more responsive
* fix missing logs when json does not take the shape of a log. Closes #411
v0.1.2-pro / 2017-11-15
=======================
* fix missing logs when json does not take the shape of a log. Closes #411
v0.1.0-pro / 2017-11-15
=======================
* add `env` command
v0.3.0 / 2017-10-19
===================
* add listing of NS records in `up stack` output
* add changelog exclusion of docs: for goreleaser
* add nicer domain registration form
* update tj/survey for color changes
* update dependencies
* refactor: add more properties to deploy track
* refactor: tweak cert email output
* refactor: exclude Makefile from todo target (#382)
* refactor: add stack to ResourceType mapping
* refactor reporting for aws types
* fix install.deps target
* fix case where improper cert is created due to second-level domain (.co.uk). Closes #350
* fix hosted zone regression introduced by e8a33a3
* fix permission issues for static file serving. Closes #385
* docs: add domains command
* docs: move policy behind a details element for collapsing
* docs: tweak for domain changes
v0.2.10 / 2017-10-13
====================
* add flushing of proxy logs after response. Closes #370
* add periodic flushing of proxy logs for `up start`. Closes #369
* add internal text handler to `up start`
v0.2.9 / 2017-10-10
===================
* fix: disable relay keep alive conns, they interact poorly with suspension (#365)
v0.2.8 / 2017-10-09
===================
* fix missing body regression
v0.2.7 / 2017-10-09
===================
* update go-apex dep
* update lambda shim with concurrency support
* fix: implement proxy GetBody to allow for re-reading request bodies. Closes #363
* remove .lambda.timeout, replace with .proxy.timeout
v0.2.6 / 2017-09-29
===================
* add `proxy.retry` option defaulting to `true`
* add UP_STAGE to `up start`
* add stage `.path` basepath support
* fix install script for Yosemite. Closes #345
v0.2.5 / 2017-09-20
===================
* add more relay logs
* docs: refactor
* add .proxy.timeout for requests and retries. Closes #335
* refactor: remove a duplicate test
* add retrying of 5xx errors for idempotent requests. Closes #214
* docs: change chown to bin only. Closes #337
* docs: add deletion info
* docs: add more stage info
* docs: add guide for full app
* docs: add note about CF provisioning
* docs: add stage section
* docs: refactor dns section
* docs: remove references to `certs`
* docs: remove "coming soon"
* docs: tweak faq
* docs: add vendor mention
* update Bowery/prompt dep and fix spacing
* fix 404 checksum not found (#331)
* docs: add missing package comments
* docs: add missing package comments
* docs: add note about omitting proxy bin changes
v0.2.4 / 2017-09-15
===================
* add custom domain support
* add Up version to the -v debug output
* add support for JSON log lines, captured and translated to the internal format
* add support for indented log lines to be captured as a single message
* add sub-process cleanup and grace period. Closes #311
* add `ssm:GetParametersByPath` to the function policy
* add UP_STAGE env var. Closes #200
* change default `proxy.listen_timeout` to 15
* fix gzip handling when previously compressed. Closes #328
* fix ignoring of .pypath
v0.2.3 / 2017-09-05
===================
* fix rewrite content-type. Closes #304
v0.2.2 / 2017-09-05
===================
* add logging of log query for debugging
* add stage shorthands to log grammar. Closes #286
* add bytes / duration units to logging grammar. Closes #283
* add humanization of .size field in logs. Closes #252
* add support for checking domain availability and registration. Closes #159
* add support for multiple hook commands with arrays. Closes #127
* add forced inclusion of ./server
* add eu-west-2 to the regions list. Closes #280
* fix ignoring of node_modules dotfiles (removed .bin by accident etc)
* fix stage validation, move before building zip
* fix support for other authentication schemes. Closes #287
* fix dns record .ttl default
* rename .proxy.timeout to .proxy.listen_timeout (BREAKING)
* remove `docs` command
* remove omission of stage from logs
v0.2.1 / 2017-08-25
===================
* fix missing param in Infof log call, outputting `MISSING`
v0.2.0 / 2017-08-25
===================
* add hiding of cursor for stack delete and apply
* add support for configuring proxy timeout (#273)
* add cost to metrics output. Closes #204
* add: ignore dotfiles by default
* add nicer formatting for numeric metrics
* add build command. Closes #257
* add validation of stage name to `url` and `deploy`. Closes #261
* remove .npmignore support. Closes #270
v0.1.12 / 2017-08-23
=====================
* add some basic formatting to `up stack plan`
* rename `up stack show` to `up stack status`
* fix hard-coded versions for stack updates
v0.1.11 / 2017-08-22
====================
* add support for regions defined in `~/.aws/config`
* add `up stack plan` and `up stack apply` support. Closes #115
* add environment variables to hooks when performing builds etc
* fix support for implicit `app.js` when `package.json` is present without a `start` script defined
v0.1.10 / 2017-08-15
====================
* add default of ./server back for when source is omitted (main.go for example)
* add `**` .upignore support
* add forced inclusion of Up's required files
* add support for omitting `node_modules` when using Browserify or Webpack
* update go-archive for gitignore parity improvements
v0.1.9 / 2017-08-14
===================
* add -modtime 0
* add smaller progress bar for initial stack
* revert "add error when a dir does not look like a valid project. Closes #197"
* caused an issue if you ignore *.go for example, not robust enough
v0.1.8 / 2017-08-14
===================
* add error when a dir does not look like a valid project. Closes #197
* add convenience make targets `install` and `install.deps`
* add note about AWS_PROFILE in getting started. Closes #230
* add python projects with a requirements.txt
* add install.sh
* fix greedy default error page, add option to explicitly enable. Closes #233
* fix exec bit on windows. Closes #225
* fix python overriding of custom command
* remove default of ./server
* remove "-api" suffix from IAM role (breaking change)
* refactor NewLogs() to properly delegate the error instead of panic
v0.1.7 / 2017-08-12
===================
* add size of code/zip before attempting deploy. Closes #222
* add better description for --force
* change default timeout to 15s from 5s
* change default memory from 128 to 512 (Node.js require() is slow)
* fix relay timeout (lack of an error)
================================================
FILE: LICENSE
================================================
The MIT License
Copyright (c) 2020 TJ Holowaychuk tj@tjholowaychuk.com
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: Makefile
================================================
GO ?= go
# Build all files.
build:
@echo "==> Building"
@$(GO) generate ./...
.PHONY: build
# Install from source.
install:
@echo "==> Installing up ${GOPATH}/bin/up"
@$(GO) install ./...
.PHONY: install
# Run all tests.
test: internal/proxy/bin/bin_assets.go
@$(GO) test -timeout 2m ./... && echo "\n==>\033[32m Ok\033[m\n"
.PHONY: test
# Run all tests in CI.
test.ci: internal/proxy/bin/bin_assets.go
@$(GO) test -v -timeout 5m ./... && echo "\n==>\033[32m Ok\033[m\n"
.PHONY: test.ci
internal/proxy/bin/bin_assets.go:
@$(GO) generate ./...
# Show source statistics.
cloc:
@cloc -exclude-dir=vendor,node_modules .
.PHONY: cloc
# Release binaries to GitHub.
release: build
@echo "==> Releasing"
@goreleaser -p 1 --rm-dist --config .goreleaser.yml
@echo "==> Complete"
.PHONY: release
# Show to-do items per file.
todo:
@rg TODO:
.PHONY: todo
# Show size of imports.
size:
@curl -sL https://gist.githubusercontent.com/tj/04e0965e23da00ca33f101e5b2ed4ed4/raw/9aa16698b2bc606cf911219ea540972edef05c4b/gistfile1.txt | bash
.PHONY: size
# Clean.
clean:
@rm -fr \
dist \
internal/proxy/bin/bin_assets.go \
internal/shim/bindata.go
.PHONY: clean
================================================
FILE: Readme.md
================================================

Up deploys infinitely scalable serverless apps, APIs, and static websites in seconds, so you can get back to working on what makes your product unique.
With Up there's no need to worry about managing or scaling machines, paying for idle servers, worrying about logging infrastructure or alerting. Just deploy your app with `$ up` and you're done!
Use the free OSS version, or subscribe to [Up Pro](#pro-features) for a small monthly fee for unlimited use within your company, there is no additional cost per team-member or application. Deploy dozens or even hundreds of applications for pennies thanks to AWS Lambda's cost effective nature.
## About
Up focuses on deploying "vanilla" HTTP servers so there's nothing new to learn, just develop with your favorite existing frameworks such as Express, Koa, Django, Golang net/http or others.
Up currently supports Node.js, Golang, Python, Java, Crystal, Clojure and static sites out of the box. Up is platform-agnostic, supporting AWS Lambda and API Gateway as the first targets. You can think of Up as self-hosted Heroku style user experience for a fraction of the price, with the security, isolation, flexibility, and scalability of AWS.
Check out the [documentation](https://up.docs.apex.sh/) for more instructions and links, or try one of the [examples](https://github.com/apex/up-examples), or chat with us in [Slack](https://chat.apex.sh/).

## OSS Features
Features of the free open-source edition.

## Pro Features
Up Pro provides additional features for production-ready applications such as encrypted environment variables, error alerting, unlimited team members, unlimited applications, priority [email support](mailto:support@apex.sh), and global deployments for **$19.99/mo USD**. Visit [Subscribing to Up Pro](https://apex.sh/docs/up/guides/#subscribing_to_up_pro) to get started.

[](https://apex.sh/docs/up/guides/#subscribing_to_up_pro)
## Quick Start
Install Up:
```
$ curl -sf https://up.apex.sh/install | sh
```
Create an `app.js` file:
```js
require('http').createServer((req, res) => {
res.end('Hello World\n')
}).listen(process.env.PORT)
```
Deploy the app:
```
$ up
```
Open it in the browser, or copy the url to your clipboard:
```
$ up url -o
$ up url -c
```
================================================
FILE: cmd/up/main.go
================================================
package main
import (
"errors"
"os"
"runtime"
"github.com/stripe/stripe-go"
"github.com/tj/go/env"
"github.com/tj/go/term"
// commands
_ "github.com/apex/up/internal/cli/build"
_ "github.com/apex/up/internal/cli/config"
_ "github.com/apex/up/internal/cli/deploy"
_ "github.com/apex/up/internal/cli/disable-stats"
_ "github.com/apex/up/internal/cli/docs"
_ "github.com/apex/up/internal/cli/domains"
_ "github.com/apex/up/internal/cli/logs"
_ "github.com/apex/up/internal/cli/metrics"
_ "github.com/apex/up/internal/cli/prune"
_ "github.com/apex/up/internal/cli/run"
_ "github.com/apex/up/internal/cli/stack"
_ "github.com/apex/up/internal/cli/start"
_ "github.com/apex/up/internal/cli/team"
_ "github.com/apex/up/internal/cli/upgrade"
_ "github.com/apex/up/internal/cli/url"
_ "github.com/apex/up/internal/cli/version"
"github.com/apex/up/internal/cli/app"
"github.com/apex/up/internal/signal"
"github.com/apex/up/internal/stats"
"github.com/apex/up/internal/util"
)
var version = "master"
func main() {
signal.Add(reset)
stripe.Key = env.GetDefault("STRIPE_KEY", "pk_live_23pGrHcZ2QpfX525XYmiyzmx")
stripe.LogLevel = 0
err := run()
if err == nil {
return
}
term.ShowCursor()
switch {
case util.IsNoCredentials(err):
util.Fatal(errors.New("Cannot find credentials, visit https://apex.sh/docs/up/credentials/ for help."))
default:
util.Fatal(err)
}
}
// run the cli.
func run() error {
stats.SetProperties(map[string]interface{}{
"os": runtime.GOOS,
"arch": runtime.GOARCH,
"version": version,
"ci": os.Getenv("CI") == "true" || os.Getenv("CI") == "1",
})
return app.Run(version)
}
// reset cursor.
func reset() error {
term.ShowCursor()
println()
return nil
}
================================================
FILE: cmd/up-proxy/main.go
================================================
package main
import (
"os"
"time"
"github.com/apex/go-apex"
"github.com/apex/log"
"github.com/apex/log/handlers/json"
"github.com/apex/up"
"github.com/apex/up/handler"
"github.com/apex/up/internal/logs"
"github.com/apex/up/internal/proxy"
"github.com/apex/up/internal/util"
"github.com/apex/up/platform/aws/runtime"
)
func main() {
start := time.Now()
stage := os.Getenv("UP_STAGE")
// setup logging
log.SetHandler(json.Default)
if s := os.Getenv("LOG_LEVEL"); s != "" {
log.SetLevelFromString(s)
}
log.Log = log.WithFields(logs.Fields())
log.Info("initializing")
// read config
c, err := up.ReadConfig("up.json")
if err != nil {
log.Fatalf("error reading config: %s", err)
}
ctx := log.WithFields(log.Fields{
"name": c.Name,
"type": c.Type,
})
// init project
p := runtime.New(c)
// init runtime
if err := p.Init(stage); err != nil {
ctx.Fatalf("error initializing: %s", err)
}
// overrides
if err := c.Override(stage); err != nil {
ctx.Fatalf("error overriding: %s", err)
}
// create handler
h, err := handler.FromConfig(c)
if err != nil {
ctx.Fatalf("error creating handler: %s", err)
}
// init handler
h, err = handler.New(c, h)
if err != nil {
ctx.Fatalf("error initializing handler: %s", err)
}
// serve
log.WithField("duration", util.MillisecondsSince(start)).Info("initialized")
apex.Handle(proxy.NewHandler(h))
}
================================================
FILE: config/backoff.go
================================================
package config
import (
"time"
"github.com/tj/backoff"
)
// Backoff config.
type Backoff struct {
// Min time in milliseconds.
Min int `json:"min"`
// Max time in milliseconds.
Max int `json:"max"`
// Factor applied for every attempt.
Factor float64 `json:"factor"`
// Attempts performed before failing.
Attempts int `json:"attempts"`
// Jitter is applied when true.
Jitter bool `json:"jitter"`
}
// Default implementation.
func (b *Backoff) Default() error {
if b.Min == 0 {
b.Min = 100
}
if b.Max == 0 {
b.Max = 500
}
if b.Factor == 0 {
b.Factor = 2
}
if b.Attempts == 0 {
b.Attempts = 3
}
return nil
}
// Backoff returns the backoff from config.
func (b *Backoff) Backoff() *backoff.Backoff {
return &backoff.Backoff{
Min: time.Duration(b.Min) * time.Millisecond,
Max: time.Duration(b.Max) * time.Millisecond,
Factor: b.Factor,
Jitter: b.Jitter,
}
}
================================================
FILE: config/backoff_test.go
================================================
package config
import (
"testing"
"time"
"github.com/tj/assert"
)
func TestBackoff_Default(t *testing.T) {
a := &Backoff{}
assert.NoError(t, a.Default(), "default")
b := &Backoff{
Min: 100,
Max: 500,
Factor: 2,
Attempts: 3,
}
assert.Equal(t, b, a)
}
func TestBackoff_Backoff(t *testing.T) {
a := &Backoff{}
assert.NoError(t, a.Default(), "default")
b := a.Backoff()
assert.Equal(t, time.Millisecond*100, b.Min)
assert.Equal(t, time.Millisecond*500, b.Max)
}
================================================
FILE: config/config.go
================================================
package config
import (
"encoding/json"
"io/ioutil"
"os"
"github.com/apex/log"
"github.com/pkg/errors"
"github.com/apex/up/internal/header"
"github.com/apex/up/internal/inject"
"github.com/apex/up/internal/redirect"
"github.com/apex/up/internal/validate"
"github.com/apex/up/platform/aws/regions"
"github.com/aws/aws-sdk-go/aws/session"
)
// defaulter is the interface that provides config defaulting.
type defaulter interface {
Default() error
}
// validator is the interface that provides config validation.
type validator interface {
Validate() error
}
// Config for the project.
type Config struct {
Name string `json:"name"`
Description string `json:"description"`
Type string `json:"type"`
Headers header.Rules `json:"headers"`
Redirects redirect.Rules `json:"redirects"`
Hooks Hooks `json:"hooks"`
Environment Environment `json:"environment"`
Regions []string `json:"regions"`
Profile string `json:"profile"`
Inject inject.Rules `json:"inject"`
Lambda Lambda `json:"lambda"`
CORS *CORS `json:"cors"`
ErrorPages ErrorPages `json:"error_pages"`
Proxy Relay `json:"proxy"`
Static Static `json:"static"`
Logs Logs `json:"logs"`
Stages Stages `json:"stages"`
DNS DNS `json:"dns"`
}
// Validate implementation.
func (c *Config) Validate() error {
if err := validate.RequiredString(c.Name); err != nil {
return errors.Wrap(err, ".name")
}
if err := validate.Name(c.Name); err != nil {
return errors.Wrapf(err, ".name %q", c.Name)
}
if err := validate.List(c.Type, []string{"static", "server"}); err != nil {
return errors.Wrap(err, ".type")
}
if err := validate.Lists(c.Regions, regions.IDs); err != nil {
return errors.Wrap(err, ".regions")
}
if err := c.DNS.Validate(); err != nil {
return errors.Wrap(err, ".dns")
}
if err := c.Static.Validate(); err != nil {
return errors.Wrap(err, ".static")
}
if err := c.Inject.Validate(); err != nil {
return errors.Wrap(err, ".inject")
}
if err := c.Lambda.Validate(); err != nil {
return errors.Wrap(err, ".lambda")
}
if err := c.Proxy.Validate(); err != nil {
return errors.Wrap(err, ".proxy")
}
if err := c.Stages.Validate(); err != nil {
return errors.Wrap(err, ".stages")
}
if len(c.Regions) > 1 {
return errors.New("multiple regions is not yet supported, see https://github.com/apex/up/issues/134")
}
return nil
}
// Default implementation.
func (c *Config) Default() error {
if c.Stages == nil {
c.Stages = make(Stages)
}
// we default stages here before others simply to
// initialize the default stages such as "development"
// allowing runtime inference to default values.
if err := c.Stages.Default(); err != nil {
return errors.Wrap(err, ".stages")
}
// TODO: hack, move to the instantiation of aws clients
if c.Profile != "" {
setProfile(c.Profile)
}
// default type to server
if c.Type == "" {
c.Type = "server"
}
// runtime defaults
if c.Type != "static" {
runtime := inferRuntime()
log.WithField("type", runtime).Debug("inferred runtime")
if err := runtimeConfig(runtime, c); err != nil {
return errors.Wrap(err, "runtime")
}
}
// default .regions
if err := c.defaultRegions(); err != nil {
return errors.Wrap(err, ".region")
}
// region globbing
c.Regions = regions.Match(c.Regions)
// default .proxy
if err := c.Proxy.Default(); err != nil {
return errors.Wrap(err, ".proxy")
}
// default .lambda
if err := c.Lambda.Default(); err != nil {
return errors.Wrap(err, ".lambda")
}
// default .dns
if err := c.DNS.Default(); err != nil {
return errors.Wrap(err, ".dns")
}
// default .logs
if err := c.Logs.Default(); err != nil {
return errors.Wrap(err, ".logs")
}
// default .inject
if err := c.Inject.Default(); err != nil {
return errors.Wrap(err, ".inject")
}
// default .error_pages
if err := c.ErrorPages.Default(); err != nil {
return errors.Wrap(err, ".error_pages")
}
// default .stages
if err := c.Stages.Default(); err != nil {
return errors.Wrap(err, ".stages")
}
return nil
}
// Override with stage config if present, and re-validate.
func (c *Config) Override(stage string) error {
s := c.Stages.GetByName(stage)
if s == nil {
return nil
}
s.Override(c)
return c.Validate()
}
// defaultRegions checks AWS_REGION and falls back on us-west-2.
func (c *Config) defaultRegions() error {
if len(c.Regions) != 0 {
log.Debugf("%d regions from config", len(c.Regions))
return nil
}
s, err := session.NewSessionWithOptions(session.Options{
SharedConfigState: session.SharedConfigEnable,
})
if err != nil {
return errors.Wrap(err, "creating session")
}
if r := *s.Config.Region; r != "" {
log.Debugf("region from aws shared config %q", r)
c.Regions = append(c.Regions, r)
return nil
}
r := "us-west-2"
log.Debugf("region defaulted to %q", r)
c.Regions = append(c.Regions, r)
return nil
}
// ParseConfig returns config from JSON bytes.
func ParseConfig(b []byte) (*Config, error) {
c := &Config{}
if err := json.Unmarshal(b, c); err != nil {
return nil, errors.Wrap(err, "parsing json")
}
if err := c.Default(); err != nil {
return nil, errors.Wrap(err, "defaulting")
}
if err := c.Validate(); err != nil {
return nil, errors.Wrap(err, "validating")
}
return c, nil
}
// ParseConfigString returns config from JSON string.
func ParseConfigString(s string) (*Config, error) {
return ParseConfig([]byte(s))
}
// MustParseConfigString returns config from JSON string.
func MustParseConfigString(s string) *Config {
c, err := ParseConfigString(s)
if err != nil {
panic(err)
}
return c
}
// ReadConfig reads the configuration from `path`.
func ReadConfig(path string) (*Config, error) {
b, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
return ParseConfig(b)
}
// setProfile sets the AWS_PROFILE.
func setProfile(name string) {
os.Setenv("AWS_PROFILE", name)
}
================================================
FILE: config/config_test.go
================================================
package config
import (
"io/ioutil"
"os"
"testing"
"github.com/tj/assert"
)
func TestConfig_Name(t *testing.T) {
t.Run("valid", func(t *testing.T) {
c := Config{
Name: "my-app123",
}
assert.NoError(t, c.Default(), "default")
assert.NoError(t, c.Validate(), "validate")
})
t.Run("invalid", func(t *testing.T) {
c := Config{
Name: "my app",
}
assert.NoError(t, c.Default(), "default")
assert.EqualError(t, c.Validate(), `.name "my app": must contain only lowercase alphanumeric characters and '-'`)
})
t.Run("invalid", func(t *testing.T) {
c := Config{
Name: "MYAPP",
}
assert.NoError(t, c.Default(), "default")
assert.EqualError(t, c.Validate(), `.name "MYAPP": must contain only lowercase alphanumeric characters and '-'`)
})
}
func TestConfig_Type(t *testing.T) {
t.Run("default", func(t *testing.T) {
c := Config{
Name: "api",
}
assert.NoError(t, c.Default(), "default")
assert.NoError(t, c.Validate(), "validate")
assert.Equal(t, "server", c.Type)
})
t.Run("valid", func(t *testing.T) {
c := Config{
Name: "api",
Type: "server",
}
assert.NoError(t, c.Default(), "default")
assert.NoError(t, c.Validate(), "validate")
})
t.Run("invalid", func(t *testing.T) {
c := Config{
Name: "api",
Type: "something",
}
assert.NoError(t, c.Default(), "default")
assert.EqualError(t, c.Validate(), `.type: "something" is invalid, must be one of:
• static
• server`)
})
}
func TestConfig_Regions(t *testing.T) {
t.Skip()
t.Run("valid multiple", func(t *testing.T) {
c := Config{
Name: "api",
Type: "server",
Regions: []string{"us-west-2", "us-east-1"},
}
assert.NoError(t, c.Default(), "default")
assert.NoError(t, c.Validate(), "validate")
})
t.Run("valid multiple", func(t *testing.T) {
c := Config{
Name: "api",
Type: "server",
Regions: []string{"us-west-2", "us-east-1"},
}
assert.NoError(t, c.Default(), "default")
assert.NoError(t, c.Validate(), "validate")
})
t.Run("valid globbing", func(t *testing.T) {
c := Config{
Name: "api",
Type: "server",
Regions: []string{"us-*", "us-east-1", "ca-central-*"},
}
assert.NoError(t, c.Default(), "default")
assert.NoError(t, c.Validate(), "validate")
assert.Equal(t, []string{"us-east-2", "us-east-1", "us-west-1", "us-west-2", "us-east-1", "ca-central-1"}, c.Regions)
})
t.Run("invalid globbing", func(t *testing.T) {
c := Config{
Name: "api",
Type: "server",
Regions: []string{"uss-*"},
}
assert.NoError(t, c.Default(), "default")
assert.EqualError(t, c.Validate(), `.regions: "uss-*" is invalid, must be one of:
• us-east-2
• us-east-1
• us-west-1
• us-west-2
• ap-south-1
• ap-northeast-2
• ap-southeast-1
• ap-southeast-2
• ap-northeast-1
• ca-central-1
• eu-central-1
• eu-west-1
• eu-west-2
• eu-west-3
• sa-east-1`)
})
t.Run("invalid", func(t *testing.T) {
c := Config{
Name: "api",
Type: "server",
Regions: []string{"us-west-1", "us-west-9"},
}
assert.NoError(t, c.Default(), "default")
assert.EqualError(t, c.Validate(), `.regions: "us-west-9" is invalid, must be one of:
• us-east-2
• us-east-1
• us-west-1
• us-west-2
• ap-south-1
• ap-northeast-2
• ap-southeast-1
• ap-southeast-2
• ap-northeast-1
• ca-central-1
• eu-central-1
• eu-west-1
• eu-west-2
• eu-west-3
• sa-east-1`)
})
}
func TestConfig_defaultRegions(t *testing.T) {
t.Run("regions from config", func(t *testing.T) {
regions := []string{"us-east-1"}
c := Config{
Name: "api",
Type: "server",
Regions: regions,
}
assert.NoError(t, c.Default(), "default")
assert.NoError(t, c.defaultRegions(), "defaultRegions")
assert.Equal(t, 1, len(c.Regions), "regions should have length 2")
assert.Equal(t, regions, c.Regions, "should read regions from config")
assert.NoError(t, c.Validate(), "validate")
})
t.Run("regions from AWS_REGION", func(t *testing.T) {
region := "sa-east-1"
os.Setenv("AWS_REGION", region)
defer os.Setenv("AWS_REGION", "")
c := Config{
Name: "api",
Type: "server",
}
assert.NoError(t, c.Default(), "default")
assert.NoError(t, c.defaultRegions(), "defaultRegions")
assert.Equal(t, 1, len(c.Regions), "regions should have length 1")
assert.Equal(t, region, c.Regions[0], "should read regions from AWS_REGION")
assert.NoError(t, c.Default(), "default")
assert.NoError(t, c.Validate(), "validate")
})
t.Run("regions from AWS_DEFAULT_REGION", func(t *testing.T) {
region := "sa-east-1"
os.Setenv("AWS_DEFAULT_REGION", region)
defer os.Setenv("AWS_DEFAULT_REGION", "")
c := Config{
Name: "api",
Type: "server",
}
assert.NoError(t, c.Default(), "default")
assert.NoError(t, c.defaultRegions(), "defaultRegions")
assert.Equal(t, 1, len(c.Regions), "regions should have length 1")
assert.Equal(t, region, c.Regions[0], "should read regions from AWS_DEFAULT_REGION")
assert.NoError(t, c.Validate(), "validate")
})
t.Run("regions from shared config with default profile", func(t *testing.T) {
content := `
[default]
region = sa-east-1
output = json
[profile another-profile]
region = ap-southeast-2
output = json`
tmpfile, err := ioutil.TempFile("", "config")
assert.NoError(t, err)
defer os.Remove(tmpfile.Name())
_, err = tmpfile.WriteString(content)
assert.NoError(t, err)
os.Setenv("AWS_CONFIG_FILE", tmpfile.Name())
defer os.Setenv("AWS_CONFIG_FILE", "")
c := Config{
Name: "api",
Type: "server",
}
assert.NoError(t, c.Default(), "default")
assert.NoError(t, c.defaultRegions(), "defaultRegions")
assert.Equal(t, 1, len(c.Regions), "regions should have length 1")
assert.Equal(t, "sa-east-1", c.Regions[0], "should read regions from shared config with default profile")
assert.NoError(t, c.Validate(), "validate")
})
t.Run("regions from shared config with AWS_PROFILE profile", func(t *testing.T) {
content := `
[default]
region = sa-east-1
output = json
[profile another-profile]
region = ap-southeast-2
output = json`
tmpfile, err := ioutil.TempFile("", "config")
assert.NoError(t, err)
defer os.Remove(tmpfile.Name())
_, err = tmpfile.WriteString(content)
assert.NoError(t, err)
os.Setenv("AWS_CONFIG_FILE", tmpfile.Name())
defer os.Setenv("AWS_CONFIG_FILE", "")
os.Setenv("AWS_PROFILE", "another-profile")
defer os.Setenv("AWS_PROFILE", "")
c := Config{
Name: "api",
Type: "server",
}
assert.NoError(t, c.Default(), "default")
assert.NoError(t, c.defaultRegions(), "defaultRegions")
assert.Equal(t, 1, len(c.Regions), "regions should have length 1")
assert.Equal(t, "ap-southeast-2", c.Regions[0], "should read regions from shared config with AWS_PROFILE profile")
assert.NoError(t, c.Validate(), "validate")
})
t.Run("default region must be us-west-2", func(t *testing.T) {
// Make sure we aren't reading AWS config file
os.Setenv("AWS_CONFIG_FILE", "does-not-exist")
c := Config{
Name: "api",
Type: "server",
}
assert.NoError(t, c.Default(), "default")
assert.NoError(t, c.defaultRegions(), "defaultRegions")
assert.Equal(t, 1, len(c.Regions), "regions should have length 1")
assert.Equal(t, "us-west-2", c.Regions[0], "default region must be us-west-2")
assert.NoError(t, c.Validate(), "validate")
})
}
================================================
FILE: config/cors.go
================================================
package config
// CORS configuration.
type CORS struct {
// AllowedOrigins is a list of origins a cross-domain request can be executed from.
// If the special "*" value is present in the list, all origins will be allowed.
// An origin may contain a wildcard (*) to replace 0 or more characters
// (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penalty.
// Only one wildcard can be used per origin.
// Default value is ["*"]
AllowedOrigins []string `json:"allowed_origins"`
// AllowedMethods is a list of methods the client is allowed to use with
// cross-domain requests. Default value is simple methods (GET and POST)
AllowedMethods []string `json:"allowed_methods"`
// AllowedHeaders is list of non simple headers the client is allowed to use with
// cross-domain requests.
// If the special "*" value is present in the list, all headers will be allowed.
// Default value is [] but "Origin" is always appended to the list.
AllowedHeaders []string `json:"allowed_headers"`
// ExposedHeaders indicates which headers are safe to expose to the API of a CORS
// API specification
ExposedHeaders []string `json:"exposed_headers"`
// AllowCredentials indicates whether the request can include user credentials like
// cookies, HTTP authentication or client side SSL certificates.
AllowCredentials bool `json:"allow_credentials"`
// MaxAge indicates how long (in seconds) the results of a preflight request
// can be cached.
MaxAge int `json:"max_age"`
// Debugging flag adds additional output to debug server side CORS issues
Debug bool `json:"debug"`
}
================================================
FILE: config/dns.go
================================================
package config
import (
"encoding/json"
"github.com/apex/up/internal/validate"
"github.com/pkg/errors"
)
// recordTypes is a list of valid record types.
var recordTypes = []string{
"ALIAS",
"A",
"AAAA",
"CNAME",
"MX",
"NAPTR",
"NS",
"PTR",
"SOA",
"SPF",
"SRV",
"TXT",
}
// DNS config.
type DNS struct {
Zones []*Zone `json:"zones"`
}
// UnmarshalJSON implementation.
func (d *DNS) UnmarshalJSON(b []byte) error {
var zones map[string][]*Record
if err := json.Unmarshal(b, &zones); err != nil {
return err
}
for name, records := range zones {
zone := &Zone{Name: name, Records: records}
d.Zones = append(d.Zones, zone)
}
return nil
}
// Default implementation.
func (d *DNS) Default() error {
for _, z := range d.Zones {
if err := z.Default(); err != nil {
return errors.Wrapf(err, "zone %s", z.Name)
}
}
return nil
}
// Validate implementation.
func (d *DNS) Validate() error {
for _, z := range d.Zones {
if err := z.Validate(); err != nil {
return errors.Wrapf(err, "zone %s", z.Name)
}
}
return nil
}
// Zone is a DNS zone.
type Zone struct {
Name string `json:"name"`
Records []*Record `json:"records"`
}
// Default implementation.
func (z *Zone) Default() error {
for i, r := range z.Records {
if err := r.Default(); err != nil {
return errors.Wrapf(err, "record %d", i)
}
}
return nil
}
// Validate implementation.
func (z *Zone) Validate() error {
for i, r := range z.Records {
if err := r.Validate(); err != nil {
return errors.Wrapf(err, "record %d", i)
}
}
return nil
}
// Record is a DNS record.
type Record struct {
Name string `json:"name"`
Type string `json:"type"`
TTL int `json:"ttl"`
Value []string `json:"value"`
}
// Validate implementation.
func (r *Record) Validate() error {
if err := validate.List(r.Type, recordTypes); err != nil {
return errors.Wrap(err, ".type")
}
if err := validate.RequiredString(r.Name); err != nil {
return errors.Wrap(err, ".name")
}
if err := validate.RequiredStrings(r.Value); err != nil {
return errors.Wrap(err, ".value")
}
if err := validate.MinStrings(r.Value, 1); err != nil {
return errors.Wrap(err, ".value")
}
return nil
}
// Default implementation.
func (r *Record) Default() error {
if r.TTL == 0 {
r.TTL = 300
}
return nil
}
================================================
FILE: config/dns_test.go
================================================
package config
import (
"encoding/json"
"log"
"os"
"sort"
"testing"
"github.com/tj/assert"
)
func ExampleDNS() {
s := `{
"something.sh": [
{
"name": "something.com",
"type": "A",
"ttl": 60,
"value": ["35.161.83.243"]
},
{
"name": "blog.something.com",
"type": "CNAME",
"ttl": 60,
"value": ["34.209.172.67"]
},
{
"name": "api.something.com",
"type": "A",
"value": ["54.187.185.18"]
}
]
}`
var c DNS
if err := json.Unmarshal([]byte(s), &c); err != nil {
log.Fatalf("error unmarshaling: %s", err)
}
sort.Slice(c.Zones[0].Records, func(i int, j int) bool {
a := c.Zones[0].Records[i]
b := c.Zones[0].Records[j]
return a.Name > b.Name
})
if err := c.Validate(); err != nil {
log.Fatalf("error validating: %s", err)
}
if err := c.Default(); err != nil {
log.Fatalf("error defaulting: %s", err)
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
enc.Encode(c)
// Output:
// {
// "zones": [
// {
// "name": "something.sh",
// "records": [
// {
// "name": "something.com",
// "type": "A",
// "ttl": 60,
// "value": [
// "35.161.83.243"
// ]
// },
// {
// "name": "blog.something.com",
// "type": "CNAME",
// "ttl": 60,
// "value": [
// "34.209.172.67"
// ]
// },
// {
// "name": "api.something.com",
// "type": "A",
// "ttl": 300,
// "value": [
// "54.187.185.18"
// ]
// }
// ]
// }
// ]
// }
}
func TestDNS_Validate(t *testing.T) {
t.Run("invalid", func(t *testing.T) {
c := &DNS{
Zones: []*Zone{
{
Name: "apex.sh",
Records: []*Record{
{
Name: "blog.apex.sh",
Type: "CNAME",
},
},
},
},
}
assert.EqualError(t, c.Validate(), `zone apex.sh: record 0: .value: must have at least 1 value`)
})
}
func TestRecord_Type(t *testing.T) {
t.Run("valid", func(t *testing.T) {
c := &Record{
Name: "blog.apex.sh",
Type: "A",
Value: []string{"1.1.1.1"},
}
assert.NoError(t, c.Validate(), "validate")
})
t.Run("invalid", func(t *testing.T) {
c := &Record{
Name: "blog.apex.sh",
Type: "AAA",
}
assert.EqualError(t, c.Validate(), `.type: "AAA" is invalid, must be one of:
• ALIAS
• A
• AAAA
• CNAME
• MX
• NAPTR
• NS
• PTR
• SOA
• SPF
• SRV
• TXT`)
})
}
func TestRecord_TTL(t *testing.T) {
c := &Record{Type: "A"}
assert.NoError(t, c.Default(), "default")
assert.Equal(t, 300, c.TTL)
}
func TestRecord_Value(t *testing.T) {
t.Run("empty", func(t *testing.T) {
c := &Record{
Name: "blog.apex.sh",
Type: "A",
}
assert.EqualError(t, c.Validate(), `.value: must have at least 1 value`)
})
t.Run("invalid", func(t *testing.T) {
c := &Record{
Name: "blog.apex.sh",
Type: "A",
Value: []string{"1.1.1.1", ""},
}
assert.EqualError(t, c.Validate(), `.value: at index 1: is required`)
})
}
================================================
FILE: config/doc.go
================================================
// Package config provides configuration structures,
// validation, and defaulting for up.json config.
package config
================================================
FILE: config/duration.go
================================================
package config
import (
"bytes"
"strconv"
"time"
)
// Duration may be specified as numerical seconds or
// as a duration string such as "1.5m".
type Duration time.Duration
// Seconds returns the duration in seconds.
func (d *Duration) Seconds() float64 {
return float64(time.Duration(*d) / time.Second)
}
// UnmarshalJSON implementation.
func (d *Duration) UnmarshalJSON(b []byte) error {
if i, err := strconv.ParseInt(string(b), 10, 64); err == nil {
*d = Duration(time.Second * time.Duration(i))
return nil
}
v, err := time.ParseDuration(string(bytes.Trim(b, `"`)))
if err != nil {
return err
}
*d = Duration(v)
return nil
}
// MarshalJSON implement.
func (d *Duration) MarshalJSON() ([]byte, error) {
return []byte(strconv.Itoa(int(d.Seconds()))), nil
}
================================================
FILE: config/duration_test.go
================================================
package config
import (
"encoding/json"
"testing"
"time"
"github.com/tj/assert"
)
func TestDuration_UnmarshalJSON(t *testing.T) {
t.Run("numeric seconds", func(t *testing.T) {
s := `{
"timeout": 5
}`
var c struct {
Timeout Duration
}
err := json.Unmarshal([]byte(s), &c)
assert.NoError(t, err, "unmarshal")
assert.Equal(t, Duration(5*time.Second), c.Timeout)
})
t.Run("string duration", func(t *testing.T) {
s := `{
"timeout": "1.5m"
}`
var c struct {
Timeout Duration
}
err := json.Unmarshal([]byte(s), &c)
assert.NoError(t, err, "unmarshal")
assert.Equal(t, Duration(90*time.Second), c.Timeout)
})
}
================================================
FILE: config/environment.go
================================================
package config
// Environment variables.
type Environment map[string]string
================================================
FILE: config/errorpages.go
================================================
package config
// ErrorPages configuration.
type ErrorPages struct {
// Enable error pages.
Enable bool `json:"enable"`
// Dir containing error pages.
Dir string `json:"dir"`
// Variables are passed to the template for use.
Variables map[string]interface{} `json:"variables"`
}
// Default implementation.
func (e *ErrorPages) Default() error {
if e.Dir == "" {
e.Dir = "."
}
return nil
}
================================================
FILE: config/errorpages_test.go
================================================
package config
import (
"testing"
"github.com/tj/assert"
)
func TestErrorPages(t *testing.T) {
c := &ErrorPages{}
assert.NoError(t, c.Default(), "default")
assert.Equal(t, ".", c.Dir, "dir")
}
================================================
FILE: config/hooks.go
================================================
package config
import (
"encoding/json"
"errors"
)
// Hook is one or more commands.
type Hook []string
// Hooks for the project.
type Hooks struct {
Build Hook `json:"build"`
Clean Hook `json:"clean"`
PreBuild Hook `json:"prebuild"`
PostBuild Hook `json:"postbuild"`
PreDeploy Hook `json:"predeploy"`
PostDeploy Hook `json:"postdeploy"`
}
// Override config.
func (h *Hooks) Override(c *Config) {
if v := h.Build; v != nil {
c.Hooks.Build = v
}
if v := h.Clean; v != nil {
c.Hooks.Clean = v
}
if v := h.PreBuild; v != nil {
c.Hooks.PreBuild = v
}
if v := h.PostBuild; v != nil {
c.Hooks.PostBuild = v
}
if v := h.PreDeploy; v != nil {
c.Hooks.PreDeploy = v
}
if v := h.PostDeploy; v != nil {
c.Hooks.PostDeploy = v
}
}
// Get returns the hook by name or nil.
func (h *Hooks) Get(s string) Hook {
switch s {
case "build":
return h.Build
case "clean":
return h.Clean
case "prebuild":
return h.PreBuild
case "postbuild":
return h.PostBuild
case "predeploy":
return h.PreDeploy
case "postdeploy":
return h.PostDeploy
default:
return nil
}
}
// UnmarshalJSON implementation.
func (h *Hook) UnmarshalJSON(b []byte) error {
switch b[0] {
case '"':
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
*h = append(*h, s)
return nil
case '[':
return json.Unmarshal(b, (*[]string)(h))
default:
return errors.New("hook must be a string or array of strings")
}
}
// IsEmpty returns true if the hook is empty.
func (h *Hook) IsEmpty() bool {
return h == nil || len(*h) == 0
}
================================================
FILE: config/hooks_test.go
================================================
package config
import (
"encoding/json"
"testing"
"github.com/tj/assert"
)
func TestHook(t *testing.T) {
t.Run("missing", func(t *testing.T) {
s := []byte(`{}`)
var c struct {
Build Hook
}
err := json.Unmarshal(s, &c)
assert.NoError(t, err, "unmarshal")
assert.Equal(t, Hook(nil), c.Build)
})
t.Run("invalid type", func(t *testing.T) {
s := []byte(`
{
"build": 5
}
`)
var c struct {
Build Hook
}
err := json.Unmarshal(s, &c)
assert.EqualError(t, err, `hook must be a string or array of strings`)
})
t.Run("string", func(t *testing.T) {
s := []byte(`
{
"build": "go build main.go"
}
`)
var c struct {
Build Hook
}
err := json.Unmarshal(s, &c)
assert.NoError(t, err, "unmarshal")
assert.Equal(t, Hook{"go build main.go"}, c.Build)
})
t.Run("array", func(t *testing.T) {
s := []byte(`
{
"build": [
"go build main.go",
"browserify src/index.js > app.js"
]
}
`)
var c struct {
Build Hook
}
err := json.Unmarshal(s, &c)
assert.NoError(t, err, "unmarshal")
assert.Equal(t, Hook{
"go build main.go",
"browserify src/index.js > app.js",
}, c.Build)
})
}
================================================
FILE: config/lambda.go
================================================
package config
// defaultRuntime is the default runtime.
var defaultRuntime = "nodejs10.x"
// defaultPolicy is the default function role policy.
var defaultPolicy = IAMPolicyStatement{
"Effect": "Allow",
"Resource": "*",
"Action": []string{
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"ssm:GetParametersByPath",
"ec2:CreateNetworkInterface",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface",
},
}
// IAMPolicyStatement configuration.
type IAMPolicyStatement map[string]interface{}
// VPC configuration.
type VPC struct {
Subnets []string `json:"subnets"`
SecurityGroups []string `json:"security_groups"`
}
// Lambda configuration.
type Lambda struct {
// Memory of the function.
Memory int `json:"memory"`
// Timeout of the function.
Timeout int `json:"timeout"`
// Role of the function.
Role string `json:"role"`
// Runtime of the function.
Runtime string `json:"runtime"`
// Policy of the function role.
Policy []IAMPolicyStatement `json:"policy"`
// VPC configuration.
VPC *VPC `json:"vpc"`
}
// Default implementation.
func (l *Lambda) Default() error {
if l.Timeout == 0 {
l.Timeout = 60
}
if l.Memory == 0 {
l.Memory = 512
}
if l.Runtime == "" {
l.Runtime = defaultRuntime
}
l.Policy = append(l.Policy, defaultPolicy)
return nil
}
// Validate implementation.
func (l *Lambda) Validate() error {
return nil
}
// Override config.
func (l *Lambda) Override(c *Config) {
if l.Memory != 0 {
c.Lambda.Memory = l.Memory
}
if l.Timeout != 0 {
c.Lambda.Timeout = l.Timeout
}
if l.Role != "" {
c.Lambda.Role = l.Role
}
if l.VPC != nil {
c.Lambda.VPC = l.VPC
}
if l.Runtime != "" {
c.Lambda.Runtime = l.Runtime
}
}
================================================
FILE: config/lambda_test.go
================================================
package config
import (
"testing"
"github.com/tj/assert"
)
func TestLambda(t *testing.T) {
c := &Lambda{}
assert.NoError(t, c.Default(), "default")
assert.Equal(t, 60, c.Timeout, "timeout")
assert.Equal(t, 512, c.Memory, "timeout")
}
func TestLambda_Policy(t *testing.T) {
t.Run("defaults", func(t *testing.T) {
c := &Lambda{}
assert.NoError(t, c.Default(), "default")
assert.Len(t, c.Policy, 1)
assert.Equal(t, defaultPolicy, c.Policy[0])
})
t.Run("specified", func(t *testing.T) {
c := &Lambda{
Policy: []IAMPolicyStatement{
{
"Effect": "Allow",
"Resource": "*",
"Action": []string{
"s3:List*",
"s3:Get*",
},
},
},
}
assert.NoError(t, c.Default(), "default")
assert.Len(t, c.Policy, 2)
assert.Equal(t, defaultPolicy, c.Policy[1])
})
}
================================================
FILE: config/logs.go
================================================
package config
// Logs configuration.
type Logs struct {
// Disable json log output.
Disable bool `json:"disable"`
// Stdout default log level.
Stdout string `json:"stdout"`
// Stderr default log level.
Stderr string `json:"stderr"`
}
// Default implementation.
func (l *Logs) Default() error {
if l.Stdout == "" {
l.Stdout = "info"
}
if l.Stderr == "" {
l.Stderr = "error"
}
return nil
}
================================================
FILE: config/relay.go
================================================
package config
import (
"github.com/pkg/errors"
)
// Relay config.
type Relay struct {
// Command run to start your server.
Command string `json:"command"`
// Timeout in seconds to wait for a response.
Timeout int `json:"timeout"`
// ListenTimeout in seconds when waiting for the app to bind to PORT.
ListenTimeout int `json:"listen_timeout"`
}
// Default implementation.
func (r *Relay) Default() error {
if r.Command == "" {
r.Command = "./server"
}
if r.Timeout == 0 {
r.Timeout = 15
}
if r.ListenTimeout == 0 {
r.ListenTimeout = 15
}
return nil
}
// Validate will try to perform sanity checks for this Relay configuration.
func (r *Relay) Validate() error {
if r.Command == "" {
err := errors.New("should not be empty")
return errors.Wrap(err, ".command")
}
if r.ListenTimeout <= 0 {
err := errors.New("should be greater than 0")
return errors.Wrap(err, ".listen_timeout")
}
if r.ListenTimeout > 25 {
err := errors.New("should be <= 25")
return errors.Wrap(err, ".listen_timeout")
}
if r.Timeout > 25 {
err := errors.New("should be <= 25")
return errors.Wrap(err, ".timeout")
}
return nil
}
// Override config.
func (r *Relay) Override(c *Config) {
if r.Command != "" {
c.Proxy.Command = r.Command
}
}
================================================
FILE: config/runtimes.go
================================================
package config
import (
"os"
"github.com/apex/up/internal/util"
"github.com/pkg/errors"
)
// Runtime is an app runtime.
type Runtime string
// Runtimes available.
const (
RuntimeUnknown Runtime = "unknown"
RuntimeGo = "go"
RuntimeNode = "node"
RuntimeClojure = "clojure"
RuntimeCrystal = "crystal"
RuntimePython = "python"
RuntimeStatic = "static"
RuntimeJavaMaven = "java maven"
RuntimeJavaGradle = "java gradle"
)
// inferRuntime returns the runtime based on files present in the CWD.
func inferRuntime() Runtime {
switch {
case util.Exists("main.go"):
return RuntimeGo
case util.Exists("main.cr"):
return RuntimeCrystal
case util.Exists("package.json"):
return RuntimeNode
case util.Exists("app.js"):
return RuntimeNode
case util.Exists("project.clj"):
return RuntimeClojure
case util.Exists("pom.xml"):
return RuntimeJavaMaven
case util.Exists("build.gradle"):
return RuntimeJavaGradle
case util.Exists("app.py"):
return RuntimePython
case util.Exists("index.html"):
return RuntimeStatic
default:
return RuntimeUnknown
}
}
// runtimeConfig performs config inferences based on what Up thinks the runtime is.
func runtimeConfig(runtime Runtime, c *Config) error {
switch runtime {
case RuntimeGo:
golang(c)
case RuntimeClojure:
clojureLein(c)
case RuntimeJavaMaven:
javaMaven(c)
case RuntimeJavaGradle:
javaGradle(c)
case RuntimeCrystal:
crystal(c)
case RuntimePython:
python(c)
case RuntimeStatic:
c.Type = "static"
case RuntimeNode:
if err := nodejs(c); err != nil {
return err
}
}
return nil
}
// golang config.
func golang(c *Config) {
if c.Hooks.Build.IsEmpty() {
c.Hooks.Build = Hook{`GOOS=linux GOARCH=amd64 go build -o server *.go`}
}
if c.Hooks.Clean.IsEmpty() {
c.Hooks.Clean = Hook{`rm server`}
}
if s := c.Stages.GetByName("development"); s != nil {
if s.Proxy.Command == "" {
s.Proxy.Command = "go run *.go"
}
}
}
// java gradle config.
func javaGradle(c *Config) {
if c.Proxy.Command == "" {
c.Proxy.Command = "java -jar server.jar"
}
if c.Hooks.Build.IsEmpty() {
// assumes build results in a shaded jar named server.jar
if util.Exists("gradlew") {
c.Hooks.Build = Hook{`./gradlew clean build && cp build/libs/server.jar .`}
} else {
c.Hooks.Build = Hook{`gradle clean build && cp build/libs/server.jar .`}
}
}
if c.Hooks.Clean.IsEmpty() {
c.Hooks.Clean = Hook{`rm server.jar && gradle clean`}
}
}
// java maven config.
func javaMaven(c *Config) {
if c.Proxy.Command == "" {
c.Proxy.Command = "java -jar server.jar"
}
if c.Hooks.Build.IsEmpty() {
// assumes package results in a shaded jar named server.jar
if util.Exists("mvnw") {
c.Hooks.Build = Hook{`./mvnw clean package && cp target/server.jar .`}
} else {
c.Hooks.Build = Hook{`mvn clean package && cp target/server.jar .`}
}
}
if c.Hooks.Clean.IsEmpty() {
c.Hooks.Clean = Hook{`rm server.jar && mvn clean`}
}
}
// clojure lein config.
func clojureLein(c *Config) {
if c.Proxy.Command == "" {
c.Proxy.Command = "java -jar server.jar"
}
if c.Hooks.Build.IsEmpty() {
// assumes package results in a shaded jar named server.jar
c.Hooks.Build = Hook{`lein uberjar && cp target/*-standalone.jar server.jar`}
}
if c.Hooks.Clean.IsEmpty() {
c.Hooks.Clean = Hook{`lein clean && rm server.jar`}
}
}
// crystal config.
func crystal(c *Config) {
if c.Hooks.Build.IsEmpty() {
c.Hooks.Build = Hook{`docker run --rm -v $(pwd):/src -w /src crystallang/crystal crystal build -o server main.cr --release --static`}
}
if c.Hooks.Clean.IsEmpty() {
c.Hooks.Clean = Hook{`rm server`}
}
if s := c.Stages.GetByName("development"); s != nil {
if s.Proxy.Command == "" {
s.Proxy.Command = "crystal run main.cr"
}
}
}
// nodejs config.
func nodejs(c *Config) error {
var pkg struct {
Scripts struct {
Start string `json:"start"`
Build string `json:"build"`
} `json:"scripts"`
}
// read package.json
if err := util.ReadFileJSON("package.json", &pkg); err != nil && !os.IsNotExist(errors.Cause(err)) {
return err
}
// use "start" script unless explicitly defined in up.json
if c.Proxy.Command == "" {
if s := pkg.Scripts.Start; s == "" {
c.Proxy.Command = `node app.js`
} else {
c.Proxy.Command = s
}
}
// use "build" script unless explicitly defined in up.json
if c.Hooks.Build.IsEmpty() {
c.Hooks.Build = Hook{pkg.Scripts.Build}
}
return nil
}
// python config.
func python(c *Config) {
if c.Proxy.Command == "" {
c.Proxy.Command = "python app.py"
}
// Only add build & clean hooks if a requirements.txt exists
if !util.Exists("requirements.txt") {
return
}
// Set PYTHONPATH env
if c.Environment == nil {
c.Environment = Environment{}
}
c.Environment["PYTHONPATH"] = ".pypath/"
// Copy libraries into .pypath/
if c.Hooks.Build.IsEmpty() {
c.Hooks.Build = Hook{`mkdir -p .pypath/ && pip install -r requirements.txt -t .pypath/`}
}
// Clean .pypath/
if c.Hooks.Clean.IsEmpty() {
c.Hooks.Clean = Hook{`rm -r .pypath/`}
}
}
================================================
FILE: config/stages.go
================================================
package config
import (
"sort"
"github.com/apex/up/internal/validate"
"github.com/pkg/errors"
)
// defaultStages is a list of default stage names.
var defaultStages = []string{
"development",
"staging",
"production",
}
// Stage config.
type Stage struct {
Domain string `json:"domain"`
Zone interface{} `json:"zone"`
Path string `json:"path"`
Cert string `json:"cert"`
Name string `json:"-"`
StageOverrides
}
// IsLocal returns true if the stage represents a local environment.
func (s *Stage) IsLocal() bool {
return s.Name == "development"
}
// IsRemote returns true if the stage represents a remote environment.
func (s *Stage) IsRemote() bool {
return !s.IsLocal()
}
// Validate implementation.
func (s *Stage) Validate() error {
if err := validate.Stage(s.Name); err != nil {
return errors.Wrap(err, ".name")
}
switch s.Zone.(type) {
case bool, string:
return nil
default:
return errors.Errorf(".zone is an invalid type, must be string or boolean")
}
}
// Default implementation.
func (s *Stage) Default() error {
if s.Zone == nil {
s.Zone = true
}
return nil
}
// StageOverrides config.
type StageOverrides struct {
Hooks Hooks `json:"hooks"`
Lambda Lambda `json:"lambda"`
Proxy Relay `json:"proxy"`
}
// Override config.
func (s *StageOverrides) Override(c *Config) {
s.Hooks.Override(c)
s.Lambda.Override(c)
s.Proxy.Override(c)
}
// Stages config.
type Stages map[string]*Stage
// Default implementation.
func (s Stages) Default() error {
// defaults
for _, name := range defaultStages {
if _, ok := s[name]; !ok {
s[name] = &Stage{}
}
}
// assign names
for name, s := range s {
s.Name = name
}
// defaults
for _, s := range s {
if err := s.Default(); err != nil {
return errors.Wrapf(err, "stage %q", s.Name)
}
}
return nil
}
// Validate implementation.
func (s Stages) Validate() error {
for _, s := range s {
if err := s.Validate(); err != nil {
return errors.Wrapf(err, "stage %q", s.Name)
}
}
return nil
}
// List returns configured stages.
func (s Stages) List() (v []*Stage) {
for _, s := range s {
v = append(v, s)
}
return
}
// Domains returns configured domains.
func (s Stages) Domains() (v []string) {
for _, s := range s.List() {
if s.Domain != "" {
v = append(v, s.Domain)
}
}
return
}
// Names returns configured stage names.
func (s Stages) Names() (v []string) {
for _, s := range s.List() {
v = append(v, s.Name)
}
sort.Strings(v)
return
}
// RemoteNames returns configured remote stage names.
func (s Stages) RemoteNames() (v []string) {
for _, s := range s.List() {
if s.IsRemote() {
v = append(v, s.Name)
}
}
sort.Strings(v)
return
}
// GetByDomain returns the stage by domain or nil.
func (s Stages) GetByDomain(domain string) *Stage {
for _, s := range s.List() {
if s.Domain == domain {
return s
}
}
return nil
}
// GetByName returns the stage by name or nil.
func (s Stages) GetByName(name string) *Stage {
for _, s := range s.List() {
if s.Name == name {
return s
}
}
return nil
}
================================================
FILE: config/stages_test.go
================================================
package config
import (
"testing"
"github.com/tj/assert"
)
func TestStage_Override(t *testing.T) {
c, err := ParseConfigString(`{
"name": "app",
"regions": ["us-west-2"],
"lambda": {
"memory": 128
},
"hooks": {
"build": "parcel index.html -o build",
"clean": "rm -fr build"
},
"proxy": {
"command": "node app.js"
},
"stages": {
"production": {
"lambda": {
"memory": 1024
},
"hooks": {
"build": "parcel index.html -o build --production"
}
},
"staging": {
"proxy": {
"command": "node app.js --foo=bar"
}
}
}
}`)
assert.NoError(t, err, "parse")
assert.NoError(t, c.Default(), "default")
assert.NoError(t, c.Validate(), "validate")
assert.NoError(t, c.Override("production"), "override")
assert.Equal(t, 1024, c.Lambda.Memory)
assert.Equal(t, Hook{`parcel index.html -o build --production`}, c.Hooks.Build)
assert.Equal(t, `node app.js`, c.Proxy.Command)
assert.NoError(t, c.Override("staging"), "override")
assert.Equal(t, `node app.js --foo=bar`, c.Proxy.Command)
}
func TestStages_Default(t *testing.T) {
t.Run("no custom stages", func(t *testing.T) {
s := Stages{}
assert.NoError(t, s.Default(), "default")
assert.NoError(t, s.Validate(), "validate")
assert.Len(t, s, 3)
assert.Equal(t, "staging", s["staging"].Name)
assert.Equal(t, "production", s["production"].Name)
})
t.Run("custom stages", func(t *testing.T) {
s := Stages{
"beta": &Stage{},
}
assert.NoError(t, s.Default(), "default")
assert.NoError(t, s.Validate(), "validate")
assert.Len(t, s, 4)
assert.Equal(t, "beta", s["beta"].Name)
assert.Equal(t, true, s["beta"].Zone)
})
}
func TestStages_Validate(t *testing.T) {
t.Run("no stages", func(t *testing.T) {
s := Stages{}
assert.NoError(t, s.Validate(), "validate")
})
t.Run("some stages", func(t *testing.T) {
s := Stages{
"staging": &Stage{
Domain: "gh-polls-stage.com",
},
"production": &Stage{
Domain: "gh-polls.com",
},
}
assert.NoError(t, s.Default(), "default")
assert.NoError(t, s.Validate(), "validate")
assert.Equal(t, "staging", s["staging"].Name)
assert.Equal(t, "production", s["production"].Name)
})
t.Run("valid zone boolean", func(t *testing.T) {
s := Stages{
"production": &Stage{
Domain: "gh-polls.com",
Zone: false,
},
}
assert.NoError(t, s.Default(), "default")
assert.NoError(t, s.Validate(), "validate")
})
t.Run("valid zone string", func(t *testing.T) {
s := Stages{
"production": &Stage{
Domain: "api.gh-polls.com",
Zone: "api.gh-polls.com",
},
}
assert.NoError(t, s.Default(), "default")
assert.NoError(t, s.Validate(), "validate")
})
t.Run("invalid zone type", func(t *testing.T) {
s := Stages{
"production": &Stage{
Domain: "api.gh-polls.com",
Zone: 123,
},
}
assert.NoError(t, s.Default(), "default")
assert.EqualError(t, s.Validate(), `stage "production": .zone is an invalid type, must be string or boolean`)
})
}
func TestStages_List(t *testing.T) {
stage := &Stage{
Domain: "gh-polls-stage.com",
}
prod := &Stage{
Domain: "gh-polls.com",
}
s := Stages{
"staging": stage,
"production": prod,
}
list := []*Stage{
stage,
prod,
}
stages := s.List()
assert.Equal(t, list, stages)
}
func TestStages_GetByDomain(t *testing.T) {
stage := &Stage{
Domain: "gh-polls-stage.com",
}
prod := &Stage{
Domain: "gh-polls.com",
}
s := Stages{
"staging": stage,
"production": prod,
}
assert.Equal(t, prod, s.GetByDomain("gh-polls.com"))
}
================================================
FILE: config/static.go
================================================
package config
import (
"os"
"github.com/pkg/errors"
)
// Static configuration.
type Static struct {
// Dir containing static files.
Dir string `json:"dir"`
// Prefix is an optional URL prefix for serving static files.
Prefix string `json:"prefix"`
}
// Validate implementation.
func (s *Static) Validate() error {
info, err := os.Stat(s.Dir)
if os.IsNotExist(err) {
return nil
}
if err != nil {
return errors.Wrap(err, ".dir")
}
if !info.IsDir() {
return errors.Errorf(".dir %s is not a directory", s.Dir)
}
return nil
}
================================================
FILE: config/static_test.go
================================================
package config
import (
"os"
"testing"
"github.com/tj/assert"
)
func TestStatic(t *testing.T) {
cwd, _ := os.Getwd()
table := []struct {
Static
valid bool
}{
{Static{Dir: cwd}, true},
{Static{Dir: cwd + "/static_test.go"}, false},
}
for _, row := range table {
if row.valid {
assert.NoError(t, row.Validate())
} else {
assert.Error(t, row.Validate())
}
}
}
================================================
FILE: docs/00-introduction.md
================================================
---
title: Introduction
slug: introduction
---
Up deploys infinitely scalable serverless apps, APIs, and static websites in seconds, so you can get back to working on what makes your product unique.
Up focuses on deploying "vanilla" HTTP servers so there's nothing new to learn, just develop with your favorite existing frameworks such as Express, Koa, Django, Golang net/http or others.
Up currently supports Node.js, Golang, Python, Java, Crystal, and static sites out of the box. Up is platform-agnostic, supporting AWS Lambda and API Gateway as the first targets — you can think of Up as self-hosted Heroku style user experience for a fraction of the price, with the security, flexibility, and scalability of AWS — just `$ up` and you're done!
================================================
FILE: docs/01-installation.md
================================================
---
title: Installation
slug: setup
---
Up is distributed in a binary form and can be installed manually via the [tarball releases](https://github.com/apex/up/releases) or one of the options below. The quickest way to get `up` is to run the following command:
```
$ curl -sf https://up.apex.sh/install | sh
```
By default Up is installed to `/usr/local/bin`, to specify a directory use `BINDIR`, this can be useful in CI where you may not have access to `/usr/local/bin`. Here's an example installing to the current directory:
```
$ curl -sf https://up.apex.sh/install | BINDIR=. sh
```
Verify installation with:
```
$ up version
```
Later when you want to update `up` to the latest version use the following command:
```
$ up upgrade
```
If you hit permission issues, you may need to run the following, as `up` is installed to `/usr/local/bin/up` by default.
```
$ sudo chown -R $(whoami) /usr/local/bin/
```
================================================
FILE: docs/02-aws-credentials.md
================================================
---
title: AWS Credentials
slug: credentials
---
Before using Up you need to first provide your AWS account credentials so that Up is allowed to create resources on your behalf.
## AWS credential profiles
Most AWS tools support the `~/.aws/credentials` file for storing credentials, allowing you to specify `AWS_PROFILE` environment variable so Up knows which one to reference. To read more on configuring these files view [Configuring the AWS CLI](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html).
Here's an example of `~/.aws/credentials`, where `export AWS_PROFILE=myaccount` would activate these settings.
```
[myaccount]
aws_access_key_id = xxxxxxxx
aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxx
```
### Best practices
You may store the profile name in the `up.json` file itself as shown in the following snippet:
```json
{
"name": "appname-api",
"profile": "myaccount"
}
```
This is ideal as it ensures you will not accidentally deploy to a different AWS account.
## IAM policy for Up CLI
Below is a policy for [AWS Identity and Access Management](https://aws.amazon.com/iam/) which provides Up access to manage your resources. Note that the policy may change as features are added to Up, so you may have to adjust the policy.
If you're using Up for a production application it's highly recommended to configure an IAM role and user(s) for your team, restricting the access to the account and its resources.
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"acm:*",
"cloudformation:Create*",
"cloudformation:Delete*",
"cloudformation:Describe*",
"cloudformation:ExecuteChangeSet",
"cloudformation:Update*",
"cloudfront:*",
"cloudwatch:*",
"ec2:*",
"ecs:*",
"events:*",
"iam:AttachRolePolicy",
"iam:CreatePolicy",
"iam:CreateRole",
"iam:DeleteRole",
"iam:DeleteRolePolicy",
"iam:GetRole",
"iam:PassRole",
"iam:PutRolePolicy",
"lambda:AddPermission",
"lambda:Create*",
"lambda:Delete*",
"lambda:Get*",
"lambda:InvokeFunction",
"lambda:List*",
"lambda:RemovePermission",
"lambda:Update*",
"logs:Create*",
"logs:Describe*",
"logs:FilterLogEvents",
"logs:Put*",
"logs:Test*",
"route53:*",
"route53domains:*",
"s3:*",
"ssm:*",
"sns:*"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "apigateway:*",
"Resource": "arn:aws:apigateway:*::/*"
}
]
}
```
================================================
FILE: docs/03-getting-started.md
================================================
---
title: Getting Started
slug: getting-started
---
The simplest Up application is a single file for the application itself, with zero dependencies, and an `up.json` file which requires only a `name`.
If the directory does not contain an `up.json` file, the first execution of `up` will prompt you to create it, or you can manually create an `up.json` with some preferences:
```json
{
"name": "appname-api",
"profile": "companyname",
"regions": ["us-west-2"]
}
```
Up runs "vanilla" HTTP servers listening on the `PORT` environment variable, which is passed to your program by Up. For example create a new directory with the following `app.js` file:
```js
const http = require('http')
const { PORT = 3000 } = process.env
http.createServer((req, res) => {
res.end('Hello World from Node.js\n')
}).listen(PORT)
```
Deploy it to the staging environment:
```
$ up
```
Open up the URL in your browser:
```
$ up url --open
```
Or test with curl:
```
$ curl `up url`
```
That's it! You've deployed a basic Up application. To view further help for commands use:
```
$ up help
$ up help COMMAND
$ up help COMMAND SUBCOMMAND
```
If you're not a Node.js developer here are some examples in additional languages.
For Python create `app.py`:
```python
from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer
import os
class myHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-type','text/html')
self.end_headers()
self.wfile.write("Hello World from Python\n")
return
server = HTTPServer(('', int(os.environ['PORT'])), myHandler)
server.serve_forever()
```
For Golang create `main.go`:
```go
package main
import (
"os"
"fmt"
"log"
"net/http"
)
func main() {
addr := ":"+os.Getenv("PORT")
http.HandleFunc("/", hello)
log.Fatal(http.ListenAndServe(addr, nil))
}
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello World from Go")
}
```
Finally for Crystal create `main.cr`:
```ruby
require "http/server"
port = ENV["PORT"].to_i
server = HTTP::Server.new(port) do |ctx|
ctx.response.content_type = "text/plain"
ctx.response.print "Hello world from Crystal"
end
server.listen
```
================================================
FILE: docs/04-configuration.md
================================================
---
title: Configuration
slug: configuration
---
Configuration for your app lives in the `up.json` within your project's directory. This section details each of the options available.
## Name
The name of the application, which is used to name resources such as the Lambda function or API Gateway.
```json
{
"name": "api"
}
```
## Profile
The `profile` property is equivalent to setting `AWS_PROFILE` for referencing AWS credentials in the `~/.aws` directory. Use of this property is preferred as it prevents accidents with environment variables.
```json
{
"profile": "someapp"
}
```
## Regions
You may specify a target region for deployments using the `regions` array. By default "us-west-2" is used unless the `AWS_REGION` environment variable is defined.
Note: Currently only a single region is supported, however you can deploy to many regions one at a time for global deploys, see https://medium.com/@tjholowaychuk/global-serverless-apps-with-aws-lambda-api-gateway-4642ef1f221d for details.
A single region:
```json
{
"regions": ["us-west-2"]
}
```
Currently Lambda supports the following regions:
- **us-east-2** – US East (Ohio)
- **us-east-1** – US East (N. Virginia)
- **us-west-1** – US West (N. California)
- **us-west-2** – US West (Oregon)
- **ap-northeast-2** – Asia Pacific (Seoul)
- **ap-south-1** – Asia Pacific (Mumbai)
- **ap-southeast-1** – Asia Pacific (Singapore)
- **ap-southeast-2** – Asia Pacific (Sydney)
- **ap-northeast-1** – Asia Pacific (Tokyo)
- **ca-central-1** – Canada (Central)
- **eu-central-1** – EU (Frankfurt)
- **eu-west-1** – EU (Ireland)
- **eu-west-2** – EU (London)
- **eu-west-3** – EU (Paris)
- **eu-north-1** – EU (Stockholm)
- **sa-east-1** – South America (São Paulo)
## Lambda settings
The following Lambda-specific settings are available:
- `role` – IAM role ARN, defaulting to the one Up creates for you
- `memory` – Function memory in mb (Default `512`, Min `128`, Max `3008`)
- `policy` – IAM function policy statement(s)
- `runtime` — Lambda function runtime. (Default `nodejs10.x`)
- `vpc` - VPC subnets and security groups
For example:
```json
{
"name": "api",
"lambda": {
"memory": 512,
"runtime": "nodejs8.10",
"vpc": {
"subnets": [
"subnet-aaaaaaa",
"subnet-bbbbbbb",
"subnet-ccccccc",
],
"security_groups": [
"sg-xxxxxxx"
]
}
}
}
```
The Lambda `memory` setting also scales the CPU, if your app is slow, or for cases such as larger Node applications with many `require()`s you may need to increase this value. View the [Lambda Pricing](https://aws.amazon.com/lambda/pricing/) page for more information regarding the `memory` setting.
Using Up Pro in a VPC requires access to the that the AWS SSM Parameter Store API for environment variables, otherwise the app may appear to "hang" and timeout when loading secrets. Removing VPC configuration must currently be done in the AWS console.
Note: Changes to Lambda configuration do not require a `up stack apply`, just deploy and these changes are picked up!
### IAM policy
Up uses IAM policies to grant access to resources within your AWS account such as DynamoDB or S3.
To add additional permissions add one or more IAM policy statements to the `policy` array, in the following example we permit DynamoDB item reading, updating, and deleting.
```json
{
"name": "myapp",
"lambda": {
"memory": 1024,
"policy": [
{
"Effect": "Allow",
"Resource": "*",
"Action": [
"dynamodb:Get*",
"dynamodb:List*",
"dynamodb:PutItem",
"dynamodb:DeleteItem"
]
}
]
}
}
```
Deploy to update the IAM function role permissions.
## Hook scripts
Up provides "hooks" which are commands invoked at certain points within the deployment workflow for automating builds, linting and so on. The following hooks are available:
- `prebuild` – Run before building
- `build` – Run before building. Overrides inferred build command(s)
- `postbuild` – Run after building
- `predeploy` – Run before deploying
- `postdeploy` – Run after deploying
- `clean` – Run after a deploy to clean up artifacts. Overrides inferred clean command(s)
Here's an example using Browserify to bundle a Node application. Use the `-v` verbose log flag to see how long each hook takes.
```json
{
"name": "app",
"hooks": {
"build": "browserify --node app.js > server.js",
"clean": "rm server.js"
}
}
```
Up performs runtime inference to discover what kind of application you're using, and does its best to provide helpful defaults – see the [Runtimes](#runtimes) section.
Multiple commands are provided by using arrays, and are run in separate shells:
```json
{
"name": "app",
"hooks": {
"build": [
"mkdir -p build",
"cp -fr static build",
"browserify --node index.js > build/client.js"
],
"clean": "rm -fr build"
}
}
```
To get a better idea of when hooks run, and how long the command(s) take, you may want to deploy with `-v` for verbose debug logs.
## Static file serving
Up ships with a robust static file server, to enable it specify the app `type` as `"static"`.
```json
{
"type": "static"
}
```
By default the current directory (`.`) is served, however you can change this using the `dir` setting. The following configuration restricts only serving of files in `./public/*`, any attempts to read files from outside of this root directory will fail.
```json
{
"name": "app",
"type": "static",
"static": {
"dir": "public"
}
}
```
Note that `static.dir` only tells Up which directory to serve – it does not exclude other files from the deployment – see [Ignoring Files](#ignoring_files). For example you may want an `.upignore` containing:
```
*
!public/**
```
Note: Files are currently served from AWS Lambda as well, so there is a 6MB restriction on the file size.
### Dynamic applications
If your project is not strictly static, for example a Node.js web app, you may omit `type` and add static file serving simply by defining `static` as shown below. With this setup Up will serve the file if it exists, before passing control to your application.
```json
{
"name": "app",
"static": {
"dir": "public"
}
}
```
By default there is no prefix, so `GET /index.css` will resolve to `./public/index.css`, however, you may specify a prefix such as "/static/" for `GET /static/index.css` to ensure static files never conflict with your app's routes:
```json
{
"name": "app",
"static": {
"dir": "public",
"prefix": "/static/"
}
}
```
Note: Static file serving for dynamic apps does not automatically resolve `index.html` files. The presence of a file is checked before passing control to your application.
## Environment variables
The `environment` object may be used for plain-text environment variables. Note that these are not encrypted, and are stored in up.json which is typically committed to GIT, so do not store secrets here.
```json
{
"name": "api",
"environment": {
"API_FEATURE_FOO": "1",
"API_FEATURE_BAR": "0"
}
}
```
These become available to you via `process.env.API_FEATURES_FOO`, `os.Getenv("API_FEATURES_FOO")` or similar in your language of choice.
The following environment variables are provided by Up:
- `PORT` – port number such as "3000"
- `UP_STAGE` – stage name such as "staging" or "production"
## Header injection
The `headers` object allows you to map HTTP header fields to paths. The most specific pattern takes precedence.
Here's an example of two header fields specified for `/*` and `/*.css`:
```json
{
"name": "app",
"type": "static",
"headers": {
"/*": {
"X-Something": "I am applied to everything"
},
"/*.css": {
"X-Something-Else": "I am applied to styles"
}
}
}
```
Requesting `GET /` will match the first pattern, injecting `X-Something`:
```
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 200
Content-Type: text/html; charset=utf-8
Last-Modified: Fri, 21 Jul 2017 20:42:51 GMT
X-Powered-By: up
X-Something: I am applied to everything
Date: Mon, 31 Jul 2017 20:49:33 GMT
```
Requesting `GET /style.css` will match the second, more specific pattern, injecting `X-Something-Else`:
```json
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 50
Content-Type: text/css; charset=utf-8
Last-Modified: Fri, 21 Jul 2017 20:42:51 GMT
X-Powered-By: up
X-Something-Else: I am applied to styles
Date: Mon, 31 Jul 2017 20:49:35 GMT
```
## Error pages
When enabled Up will serve a minimalistic error page for requests accepting `text/html`. The following settings are available:
- `enable` — enable the error page feature
- `dir` — the directory where the error pages are located
- `variables` — vars available to the pages
The default template's `color` and optionally provide a `support_email` to allow customers to contact your support team, for example:
```json
{
"name": "site",
"type": "static",
"error_pages": {
"enable": true,
"variables": {
"support_email": "support@apex.sh",
"color": "#228ae6"
}
}
}
```
If you'd like to provide custom templates you may create one or more of the following files. The most specific file takes precedence.
- `error.html` – Matches any 4xx or 5xx
- `5xx.html` – Matches any 5xx error
- `4xx.html` – Matches any 4xx error
- `CODE.html` – Matches a specific code such as 404.html
Variables specified via `variables`, as well as `.StatusText` and `.StatusCode` may be used in the template.
```html