[
  {
    "path": ".gitattributes",
    "content": "internal/proxy/bin/bin_assets.go filter=lfs diff=lfs merge=lfs -text\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: tj"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "## Prerequisites\n\n* [ ] I am running the latest version. (`up upgrade`)\n* [ ] I searched to see if the issue already exists.\n* [ ] I inspected the verbose debug output with the `-v, --verbose` flag.\n* [ ] Are you an Up Pro subscriber?\n\n## Description\n\nDescribe the bug or feature.\n\n## Steps to Reproduce\n\nDescribe the steps required to reproduce the issue if applicable.\n\n## Slack\n\nJoin us on Slack https://chat.apex.sh/\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "Open an issue and discuss changes before spending time on them, unless the change is trivial or an issue already exists.\n\nUse \"VERB some thing here. Closes #n\" to close the relevant issue, where VERB is one of:\n\n  - add\n  - remove\n  - change\n  - refactor\n\nIf the change is documentation related prefix with \"docs: \", as these are filtered from the changelog.\n\n  docs: add ~/.aws/config\n\nRun `dep ensure` if you introduce any new `import`'s so they're included in the ./vendor dir.\n"
  },
  {
    "path": ".gitignore",
    "content": ".envrc\nnode_modules/\n.shards/\nlib\nvendor/\ntesting\nup-proxy\n!cmd/up-proxy\ndist\n.idea\n.vscode\n.DS_Store\ninternal/proxy/bin/bin_assets.go\ninternal/shim/bindata.go\n"
  },
  {
    "path": ".goreleaser.yml",
    "content": "build:\n  main: cmd/up/main.go\n  binary: up\n  goos:\n    - darwin\n    - linux\n    - windows\n    - freebsd\n    - netbsd\n    - openbsd\n  goarch:\n    - amd64\n    - 386\n  ignore:\n    - goos: darwin\n      goarch: 386\nchangelog:\n  sort: asc\n  filters:\n    exclude:\n      - '^docs:'\n      - '^refactor'\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn 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.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment include:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a professional setting\n\n## Our Responsibilities\n\nProject 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.\n\nProject 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.\n\n## Scope\n\nThis 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.\n\n## Enforcement\n\nInstances 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.\n\nProject 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.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]\n\n[homepage]: http://contributor-covenant.org\n[version]: http://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nBefore contributing to Up you'll need a few things:\n\n- Install [Golang 1.11](https://golang.org/dl/) for that Go thing if you don't have it\n\nThe following are optional:\n\n- Install [pointlander/peg](https://github.com/pointlander/peg) if you're working on the log grammar\n- Install [shuLhan/go-bindata](https://github.com/shuLhan/go-bindata) if you need to bake `up-proxy` into `up`\n\n## Setup\n\nGrab Up:\n\n```\n$ go get github.com/apex/up\n```\n\nChange into the project:\n\n```\n$ cd $GOPATH/src/github.com/apex/up\n```\n\n## Testing\n\n```\n$ make test\n```\n\n## Layout\n\nAlthough Up is not provided as a library it is structured as if it was, for organizational purposes. The project layout is loosely:\n\n- *.go – Primary API\n- [reporter](reporter) – Event based CLI reporting\n- [platform](platform) – Platform specifics (AWS Lambda, Azure, Google, etc)\n- [internal](internal) – Internal utilities and lower level tooling\n- [http](http) – HTTP middleware for up-proxy\n- [handler](handler) – HTTP middleware aggregate, effectively the entire proxy\n- [docs](docs) – Documentation used to generate the static site\n- [config](config) – Configuration structures and validation for `up.json`\n- [cmd](cmd) – Commands, where `up` is the CLI and `up-proxy` is serving requests in production\n\nNote 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.\n\n## Proxy\n\nOne 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.\n\nThe 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.\n\nReverse proxy features such as URL rewriting, gzip compression, script injection, error pages and others are also provided in `up-proxy`.\n\n## Roadmap\n\nUp 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.\n\n## Releases\n\nNotes for myself:\n\n- Run `make clean build` if necessary to re-build the proxy\n- Run `git changelog`\n- Run `git release`\n- Run `make release`\n- Re-build documentation\n- Notes about any backwards compat issues, migration, IAM policy changes\n- Adjust schemastore JSON schema if necessary\n"
  },
  {
    "path": "History.md",
    "content": "\nv1.7.1 / 2021-09-27\n===================\n\n  * fix Lambda state issue by waiting for an Active state. Closes #833\n\nv1.7.0-pro / 2020-10-07\n=======================\n\n  * add support for tagging resources\n\nv1.7.0 / 2020-10-07\n===================\n\n  * add `lambda.timeout` back, defaulting to 60s. Closes #814\n  * change LICENSE, commercial use requires a subscription\n\nv1.6.2 / 2020-09-23\n===================\n\n  * Rebuild to decrease the binary filesize bloat\n\nv1.6.2-pro / 2020-09-23\n=======================\n\n  * Rebuild to decrease the binary filesize bloat\n\nv1.6.1-pro / 2020-09-23\n=======================\n\n  * Rebuild the proxy to include X-Up-Timeout\n\nv1.6.0-pro / 2020-09-23\n=======================\n\n  * Rebase\n\nv1.6.0 / 2020-09-23\n===================\n\n  * add support for X-Up-Timeout header field. Closes #815\n  * change id field to request_id\n\nv1.5.2 / 2020-06-08\n===================\n\n  * add Hong Kong region. Closes #804\n  * fix `up stack` panic due to missing res.DistributionDomainName. Closes #809\n\nv1.5.1-pro / 2019-12-17\n=======================\n\n  * Rebase\n\nv1.5.1 / 2019-12-17\n===================\n\n  * fix overriding of `lambda.runtime`\n\nv1.5.0-pro / 2019-11-21\n=======================\n\n  * Rebase\n\nv1.5.0 / 2019-11-21\n===================\n\n  * change error_pages to be disabled by default, use `enable: true` to add them\n  * fix stack delete behavior to not attempt to delete configured lambda roles. (Closes #787) (#788)\n\nv1.4.1-pro / 2019-10-23\n=======================\n\n  * fix: add blacklisting of up-env.json so it cannot be .upignored\n\nv1.4.0-pro / 2019-10-23\n=======================\n\n  * change warming functions to nodejs10.x, existing ones will be fine, as AWS doesn't\n  actually stop these functions, they just discontinue updating/creation\n  * Rebase\n\nv1.4.0 / 2019-10-23\n===================\n\n  * refactor nodejs shim to work on node8 or node10\n  * change default runtime to nodejs10.x (potentially breaking change, depends on your application). Closes #784\n\nv1.3.0-pro / 2019-05-30\n=======================\n\n  * add wrapping of env var logs with `logs.disable` check\n  \nv1.3.0 / 2019-05-30\n===================\n\n  * add ./vendor to excluded directories by default\n  * refactor: regenerate parser with updated peg\n  * remove discount message, it didn't work\n\nv1.2.0 / 2019-04-20\n===================\n\n  * add 60% coupon\n\nv1.2.0-pro / 2019-04-05\n=======================\n\n  * add regional DNS with latency-based routing\n  * add `--region` flag for every command to override region id\n\nv1.1.3-pro / 2019-04-02\n=======================\n\n  * Rebase\n\nv1.1.3 / 2019-04-02\n===================\n\n  * update tj/aws dependency for duplicate logs fix\n\nv1.1.2-pro / 2019-03-29\n=======================\n\n  * Rebase\n\nv1.1.2 / 2019-03-29\n===================\n\n  * fix: update tj/aws dependency for ThrottlingException logs exception\n\nv1.1.1-pro / 2019-03-23\n=======================\n\n  * update warming function to nodejs8.10 to prevent EOL warning from AWS\n\nv1.1.0-pro / 2019-03-04\n===================\n\n  * add file based environment variables, removing the size restrictions\n\nv1.0.0-pro / 2019-02-26\n===================\n\n  * add regional endpoint support\n\nv0.9.1-pro / 2019-01-21\n=======================\n\n  * add sorting of env vars. Closes #750\n\nv0.9.0-pro / 2018-12-13\n=======================\n\n  * add Lambda layer support. Closes #743\n\nv0.8.1-pro / 2018-12-11\n=======================\n\n  * improve `up env export` performance, no longer linear time\n  * fix `up deploys` error when the stage is not deployed. Closes #716\n  * Rebase\n\nv0.8.1 / 2018-12-11\n===================\n\n  * update tj/aws for bug preventing all logs from being returned. Closes #733\n  * add deploy --no-build flag for skipping build hooks. Closes #730\n  * Release v0.8.0-pro\n\nv0.8.0-pro / 2018-12-06\n=======================\n\n  * improve `up env export` performance, no longer linear time\n  * fix `up deploys` error when the stage is not deployed. Closes #716\n\nv0.8.0 / 2018-12-04\n===================\n\n  * add endpoint URL to the deployment output\n  * add deploy stage to the deployment output\n  * add msg about Up Pro\n  * remove \"not info\" log example. Closes #724\n  * fix typo in deploy example. Closes #718\n  * fix: use crystallang/crystal for Crystal builds (#713)\n\nv0.7.8-pro / 2018-09-24\n=======================\n\n  * add `up env export` command for exposing env vars to shell scripts\n\nv0.7.7-pro / 2018-09-17\n=======================\n\n  * Rebase\n\nv0.7.7 / 2018-09-17\n===================\n\n  * update go-update dependency for copy regression\n\nv0.7.5-pro / 2018-09-17\n=======================\n\n  * Rebase\n\nv0.7.5 / 2018-09-17\n===================\n\n  * update go-update dependency for rename() to copy replacement\n\nv0.7.4-pro / 2018-09-16\n=======================\n\n  * add baked in env vars from SSM, env vars are no longer loaded at runtime. Closes #547\n\nv0.7.4 / 2018-09-16\n===================\n\n  * add request id to proxy errors\n  * fix missing lambda configurations costs. (#703)\n  * fix order relay errors so timeouts are returned first (leads to better error messages)\n\nv0.7.3-pro / 2018-08-08\n=======================\n\n  * Rebase\n\nv0.7.3 / 2018-08-08\n===================\n\n  * fix crash recovery in lambda, bug was introduced in v0.7.0\n  * update cors middleware for security when using allow-origin * and allow-credentials\n\nv0.7.2-pro / 2018-07-23\n=======================\n\n  * Rebase\n\nv0.7.2 / 2018-07-23\n===================\n\n  * add vpc stage override support. Closes #689\n\nv0.7.1-pro / 2018-07-12\n=======================\n\n  * Rebase\n\nv0.7.1 / 2018-07-12\n===================\n\n  * fix initial IAM role creation waiting due to error response change\n\nv0.7.0-pro / 2018-07-11\n=======================\n\n  * Rebase\n\nv0.7.0 / 2018-07-11\n===================\n\n  * add in-flight request timeouts.\n  * remove retries\n  * refactor crash recovery to be more robust\n\nv0.6.8-pro / 2018-06-07\n=======================\n\n  * Rebase\n\nv0.6.8 / 2018-06-07\n===================\n\n  * fix multiple set-cookie API Gateway limitation for real (previous had a bug)\n\nv0.6.7-pro / 2018-06-07\n=======================\n\n  * fix s3 acceleration update with existing S3 buckets\n  * Rebase\n\nv0.6.7 / 2018-06-07\n===================\n\n  * add striping of @owner/repo@ portion of Lerna tags. Closes #670\n  * fix multiple set-cookies API Gateway limitation with casing hack\n  * fix deployment with empty Git repo\n  * update AWS SDK versions for assuming roles. (#668)\n\nv0.6.6-pro / 2018-05-24\n=======================\n\n  * Rebase\n\nv0.6.6 / 2018-05-24\n===================\n\n  * add vpc support. Closes #281\n  * fix Crystal build on Linux: PWD => pwd (#664)\n\nv0.6.5-pro / 2018-05-16\n=======================\n\n  * Rebase\n\nv0.6.5 / 2018-05-16\n===================\n\n  * add hidden disable stats command (#659)\n  * add X-Context header field. Closes #657\n  * fix CORS header fields from being clobbered by error pages. Closes #661\n\nv0.6.4-pro / 2018-05-09\n=======================\n\n  * add deployment size to `up deploys` output\n  * add asterisk to denote current version in `up deploys` due to rollbacks\n  * Rebase\n\nv0.6.4 / 2018-05-09\n===================\n\n  * add support for customizing the Lambda function IAM role policy. Closes #539\n  * add support for specifying dns zone, and disabling it. Closes #536\n  * add support for updating the role policy upon deploy\n  * change default prune retention to 30 versions\n\nv0.6.3-pro / 2018-05-02\n=======================\n\n  * add deployment size to `up deploys` output\n  * add asterisk to denote current version in `up deploys` due to rollbacks\n  * Rebase\n\nv0.6.3 / 2018-05-02\n===================\n\n  * add `--stage` flag to `up build`\n  * add `--stage` flag to `up run`\n  * change logs, metrics, and url commands to use `-s` flag for stage. Closes #371 (BREAKING)\n\nv0.6.2-pro / 2018-04-25\n=======================\n\n  * Rebase\n\nv0.6.2 / 2018-04-25\n===================\n\n  * add up prune `--stage` flag. Closes #647\n  * add `up` to ignore whitelist by default\n  * remove retries on 5xx. Closes #485\n  * fix login bug preventing `--email` from overriding the active team email\n\nv0.6.1-pro / 2018-04-16\n=======================\n\n  * Rebase\n\nv0.6.1 / 2018-04-16\n===================\n\n  * add guard against `up stack plan` before `up`\n  * add `prune` command to remove old releases from S3. Closes #322\n\nv0.6.0-pro / 2018-04-10\n=======================\n\n  * Rebase\n\nv0.6.0 / 2018-04-10\n===================\n\n  * add annual plan subscription option\n\nv0.5.17-pro / 2018-04-09\n========================\n\n  * Rebase\n\nv0.5.14 / 2018-04-09\n====================\n\n  * add start command --stage flag. Closes #639\n  * fix scenario where JSON logs have invalid .level values\n  * refactor: add note about running `up upgrade` after subscribing\n\nv0.5.16-pro / 2018-04-07\n========================\n\n  * Rebase\n\nv0.5.13 / 2018-04-07\n====================\n\n  * fix \"Error: fetching git commit: \" error when Git is missing from the system\n\nv0.5.15-pro / 2018-04-03\n========================\n\n  * Rebase\n\nv0.5.12 / 2018-04-03\n====================\n\n  * add support for defining `lambda.runtime`\n  * add robots middleware (#627)\n  * change default runtime to nodejs 8.10\n  * refactor: remove redundant wrapping of \"deploying\" message\n\nv0.5.11 / 2018-03-19\n====================\n\n  * fix: update tj/go for Git signer fix\n\nv0.5.12-pro / 2018-03-19\n========================\n\n  * Rebase\n\nv0.5.10 / 2018-03-19\n====================\n\n  * fix: update tj/go for Git subject fix\n\nv0.5.11-pro / 2018-03-16\n========================\n\n  * refactor: add mapping of Alarm and Subscription for `up stack plan` output\n  * refactor: add .duration to Deploys track call\n  * Rebase\n\nv0.5.9 / 2018-03-16\n===================\n\n  * add support for serving static files with dynamic applications. Closes #174\n\nv0.5.10-pro / 2018-03-15\n===================\n\n  * add nicer `up rollback` failure message when version does not exist\n  * add git sha and tag support to `up rollback`\n  * add `up deploys` for listing deployments and versions\n  * fix log filter relational and equality operators with strings\n\nv0.5.8 / 2018-03-15\n===================\n\n  * fix log filter relational and equality operators with strings\n\nv0.5.7 / 2018-03-15\n===================\n\n  * add git versioning, used for Pro rollbacks and deployment changelog. Closes #100\n\nv0.5.9-pro / 2018-03-09\n=======================\n\n  * add stage overrides for lambda warming. Closes #615\n\nv0.5.8-pro / 2018-03-05\n=======================\n\n  * Rebase\n\nv0.5.6 / 2018-03-05\n===================\n\n  * add support for upgrading in-place up(1). Closes #607\n  * add CI specific upgrade to avoid progress bar\n  * fix: remove IsNotFound error check, masks the real issue\n\nv0.5.7-pro / 2018-03-03\n=======================\n\n  * Rebase\n\nv0.5.5 / 2018-03-03\n===================\n\n  * fix: improve idempotency of stack deletion\n  * docs: add sns to policy (necesary for Pro's alerting)\n\nv0.5.6-pro / 2018-03-02\n=======================\n\n  * add support for `=` delimited env vars (\"FOO=bar\")\n  * add support for passing multiple env vars to `up env set`\n  * add support for overriding envs for `up start` (`$ URL=xxx up start`)\n\nv0.5.5-pro / 2018-03-01\n=======================\n\n  * Rebase\n\nv0.5.4 / 2018-03-01\n===================\n\n  * add default `up start` command for Go and Crystal. Closes #581\n  * add log stage field to all logs, not just request-level\n  * add owner to `up team` output\n  * fix `up metrics` output, should be stage-specific, not global\n  * refactor: add humanized error when the stack (app) does not exist\n  * refactor: add stage name to beginning of log line instead of as a field\n  * refactor: add os/arch to debug logs to aid in support\n  * refactor: add alias upserts when updating (merged from pro)\n  * refactor: remove a redundant \"deploying\" error wrap\n  * refactor: tweak some error messages\n  * refactor: change perms of up.json to 0644. Closes #601\n\nv0.5.4-pro / 2018-02-23\n=======================\n\n  * Rebase\n\nv0.5.3 / 2018-02-23\n===================\n\n  * fix log flushing, make it synchronous. Closes #545\n  * docs: add changelog link\n  * docs: add mention of BINDIR\n\nv0.5.3-pro / 2018-02-22\n=======================\n\n  * add 1s sleep to /_ping endpoint for improved warming concurrency accuracy  \n  * add `up env get` command for fetching a value\n  * Rebase\n\nv0.5.2 / 2018-02-22\n===================\n\n  * remove unsetting of `AWS_*` vars for now, reverts #590 fix\n\nv0.5.1 / 2018-02-22\n===================\n\n  * add function version to `up stack` output\n  * change `up team ci` to output base64 encoded config\n  * change UP_CONFIG to attempt base64-decode when not JSON (#594)\n  * fix proxy.command overrides. Closes #597\n  * fix .profile precedence. Closes #590\n\nv0.5.2-pro / 2018-02-12\n=======================\n\n  * add active warming support\n  * Rebase\n\nv0.5.1-pro / 2018-02-08\n=======================\n\n  * add `up env` --decrypt flag for emergencies when you need to list\n\nv0.5.0-pro / 2018-02-08\n=======================\n\n  * add nicer env var logging with masking\n  * add custom stage support to `up env`\n  * add message for `up env` when no vars are defined\n  * fix rollbacks using -previous aliases\n  * Rebase\n\nv0.5.0 / 2018-02-08\n===================\n\n  * add custom stage support. Closes #326\n  * add customer feedback option when unsubscribing\n  * add `up team card change` command for updating the CC\n  * remove sourcing of .gitignore. Closes #557\n  * remove development as a remote stage (now local only). Closes #563\n  * refactor: add separator to make log message more obvious\n  * refactor: add hiding of cursor when verifying email\n  * refactor retry labels below s3 uploads (improves performance)\n  * refactor: add nicer output when using `up url -c`\n\nv0.4.12-pro / 2018-02-01\n========================\n\n  * Rebase\n\nv0.4.12 / 2018-02-01\n====================\n\n  * add -o, --open to `up start` for opening in the browser\n  * add `logs.{stdout,stderr}` for configuring log levels. Closes #565\n  * add `-c, --command` flag to `up start`. Closes #564\n  * fix panic when .domain is missing from a stage, as it is now optional. Closes #567\n  * docs: add example .upignore for static sites\n  * docs: fix team members rm example. Closes #562\n  * docs: add \"Unable to associate certificate error\" to troubleshooting\n  * docs: add gin example\n\nv0.4.11-pro / 2018-01-29\n========================\n\n  * Rebase\n\nv0.4.11 / 2018-01-29\n====================\n\n  * add development config overrides to `up start`\n  * add the ability to override .proxy.command at the stage level\n  * docs: mention that the WHOIS contact emails are used\n  * docs: fix link for acm validation\n  * docs: tweak\n  * docs: add guide for hot reloading\n  * docs: remove old \"Local Environment Variables\" guide section\n  * docs: add gin example for dev command\n\nv0.4.10-pro / 2018-01-25\n========================\n\n  * Rebase\n\nv0.4.10 / 2018-01-25\n====================\n\n  * refactor to use a single account/region level S3 bucket, not per-project. Closes #550\n  * fix base64 encoded json when params are provided\n\nv0.4.9-pro / 2018-01-24\n=======================\n\n  * Rebase\n\nv0.4.9 / 2018-01-24\n===================\n\n  * revert tj/go-update, causing permission issues\n\nv0.4.8-pro / 2018-01-24\n=======================\n\n  * fix validating after overrides\n\nv0.4.8 / 2018-01-24\n===================\n\n  * update tj/go-update for copy instead of rename. Closes #329\n  * update api client for RemoveMember() json body change\n  * docs: add missing ssm to policy\n  * docs: add note about 404s\n\nv0.4.7-pro / 2018-01-19\n=======================\n\n  * add rollback support\n  * fix upgrade deduplication due to version having -pro suffix\n\nv0.4.7 / 2018-01-19\n===================\n\n  * add optimization of ACM certificate creation. Closes #452\n  * add `development` Lambda alias. Closes #542\n  * add start of stage overrides for config. Closes #314\n  * add support for upgrading to a specific version of Up. Closes #387\n  * update go-cli-analytics for disabled segment cli logging\n  * refactor handler.New() to accept an http.Handler\n  * refactor logging configuration, delegate isatty check etc\n  * refactor: move internal logs to tj/aws\n  * refactor platform integration quickly\n\nv0.4.6-pro / 2018-01-03\n=======================\n\n  * add rollback support\n\nv0.4.5-pro / 2018-01-03\n=======================\n\n  * add s3 acceleration\n  * fix a log call in runtime\n\nv0.4.6 / 2018-01-03\n===================\n\n  * add support for Clojure with Leiningen (#522)\n  * add coupon price adjustment to `up team` output. Closes #516\n  * add support for overriding NODE_ENV. Closes #505\n  * add error for multiple regions, until the feature is complete\n  * add Paris region\n  * change `error_pages` to be enabled by default for text/html requests\n  * refactor `handler.New()` to accept config\n  * refactor signal handling\n  * refactor: update api client\n  * refactor: remove unnecessary code (#517)\n  * refactor login and provide a non-error when you are already signed in\n  * fix s3 buckets, should be scoped to region\n  * fix output flickering before build output\n  * fix: add a ! in front of build.gradle for forced inclusion (#518)\n\nv0.4.4-pro / 2017-12-22\n=======================\n\n  * Rebase\n\nv0.4.5 / 2017-12-22\n===================\n\n  * add new subscribe workflow\n  * add team CRUD and rename `up account` to `up team`. Re #410\n  * refactor: replace `kingpin.CmdClause` with `kingpin.Cmd`\n  * refactor: use `time.Since` for time difference (#509)\n  * refactor: add \"ci\" to stats so we can see how often CI is used\n  * refactor: simplify start of plain reporter (#508)\n  * refactor: a typo fix in http/relay (#507)\n  * refactor: drop unnecessary `fmt.Sprintf` in reporter/text (#506)\n  * refactor: simplify personal team check (#500)\n\nv0.4.3-pro / 2017-12-19\n=======================\n\n  * Rebase\n\nv0.4.4 / 2017-12-19\n===================\n\n  * fix `up stack status` scenario before a domain is mapped\n  * refactor: config, simplify unmarshal json of dns. Closes #497\n\nv0.4.2-pro / 2017-12-19\n=======================\n\n  * Rebase\n\nv0.4.3 / 2017-12-19\n===================\n\n  * refactor: shorten s3 bucket name\n\nv0.4.1-pro / 2017-12-19\n=======================\n\n  * remove 0.0.0 hack for pro upgrade\n  * Rebase\n\nv0.4.2 / 2017-12-19\n===================\n\n  * change to disallow uppercase characters in .name. Closes #498\n  * refactor: add humanized string for the current version\n  * refactor: add config/backoff.go\n\nv0.4.1 / 2017-12-18\n===================\n\n  * fix upgrades to pro when version matches\n\nv0.4.0-pro / 2017-12-18\n=======================\n\n  * add slack `gif` option\n  * add slack alert support\n  * add initialization of env vars for builds. Closes #458\n  * add initialization of env vars for deployments. Closes #458\n  * add initialization of env vars for `up start`. Closes #458\n  * add `{alerts,actions}_count` to Deploy track\n  * change missing default to `notBreaching`\n  * refactor: add title casing to `up env` output\n\nv0.4.0 / 2017-12-18\n===================\n\n  * add unquoted string literals for log queries\n  * add log string sans-quote literal. Closes #461\n  * add log message field equality short-hand. Closes #372\n  * add CI=true check for plain text output. Re #422\n  * add --format=plain for CI. Closes #422\n  * add setup workflow for creating up.json and doing the initial deploy. Closes #482, #386\n  * add `NODE_ENV` population by default\n  * add env vars to `up start`\n  * add s3 deployments. Closes #272\n  * add cloudfront endpoint to `up stack` output. Closes #459\n  * change logs to purple (match everything else)\n  * change how expanded log mode looks\n  * remove `--region` flag\n  * fix upgrade messages for OSS -> Pro\n  * fix clearing state in text reporter\n\nv0.3.0-pro / 2017-12-03\n=======================\n\n  * add sms alerting support\n\nv0.2.0-pro / 2017-12-03\n=======================\n\n  * add hosted email alerting for nicer formatting\n  * change alert default `period` to 1m\n\nv0.1.11-pro / 2017-11-30\n========================\n\n  * add support for listing secrets without last modified user name\n  * fix secrets listing when user ARN is not present. Closes #433\n  * refactor alerting into new resources sub-pkg\n  * Rebase\n\nv0.3.8 / 2017-11-30\n===================\n\n  * add {pre,post}{build,deploy} hooks\n  * add flushing of logs after [re]start. See #359\n  * add \"w\" for week to `ParseDuration()`\n  * refactor: fix Map for now\n  * refactor: use effective domain for CFN id\n  * refactor: add test for existing zone and apex domain\n  * refactor: add test for existing zone\n  * refactor: add test coverage for CFN resources\n  * fix hosted zones for sub-domains. Closes #447\n  * fix `.type` precedence when runtime files are detected. Closes #436\n\nv0.3.7 / 2017-11-24\n===================\n\n  * add date formatting for older logs\n  * remove project init from `up account login`\n  * fix timestamps for lambda plain text logs\n\nv0.1.10-pro / 2017-11-23\n========================\n\n  * add support for listing secrets without last modified user name\n  * fix secrets listing when user ARN is not present. Closes #433\n  * Rebase\n\nv0.3.6 / 2017-11-22\n===================\n\n  * fix subscription without coupon\n\nv0.1.9-pro / 2017-11-21\n=======================\n\n  * Rebase\n\nv0.3.5 / 2017-11-21\n===================\n\n  * add `stage` field to all log contexts (fixes log filtering against `production`)\n  * fix DNS record logical id collision. Closes #420\n  * refactor `up stack` output\n\nv0.1.8-pro / 2017-11-20\n=======================\n\n  * add TreatMissingData as ignore by default\n\nv0.1.7-pro / 2017-11-20\n=======================\n\n  * fix email alerting\n\nv0.1.6-pro / 2017-11-20\n=======================\n\n  * add initial alerting support\n\nv0.1.5-pro / 2017-11-20\n=======================\n\n  * fix \"development\" env support for `up env`\n  * Rebase\n\nv0.3.4 / 2017-11-20\n===================\n\n  * add `up accounts ci` and --copy to help with setting up UP_CONFIG for CI\n  * fix domain verification for ssl certificates. Closes #425\n  * update tj/kingpin for arg output formatting fix\n\nv0.1.4-pro / 2017-11-18\n=======================\n\n  * Rebase\n\nv0.3.3 / 2017-11-18\n===================\n\n  * fix zip paths on Windows. Closes #418\n\nv0.1.3-pro / 2017-11-18\n=======================\n\n  * Rebase\n\nv0.3.2 / 2017-11-18\n===================\n\n  * add support for UP_CONFIG from environment\n  * add `up docs` command back for opening documentation in the browser\n  * change logs `--since` default to 1 day\n  * fix intermittent metrics failure. Closes #414\n\nv0.3.1 / 2017-11-15\n===================\n\n  * \u001badd `up account` and sub-commands\n  * \u001badd extended duration parsing for `--since` flags. Closes #401\n  * \u001badd log expansion. Closes #399\n  * \u001badd Content-Length request header\n  * \u001badd request logs\n  * \u001badd pom.xml and build.grade to whitelist which cannot be ignored\n  * \u001bchange metrics `--since` default to 1 month\n  * \u001brefactor: remove .size defaulting of 0\n  * \u001brefactor progress bar with diffing, making it more responsive\n  * fix missing logs when json does not take the shape of a log. Closes #411\n\nv0.1.2-pro / 2017-11-15\n=======================\n\n  * fix missing logs when json does not take the shape of a log. Closes #411\n\nv0.1.0-pro / 2017-11-15\n=======================\n\n  * add `env` command\n\nv0.3.0 / 2017-10-19\n===================\n\n  * add listing of NS records in `up stack` output\n  * add changelog exclusion of docs: for goreleaser\n  * add nicer domain registration form\n  * update tj/survey for color changes\n  * update dependencies\n  * refactor: add more properties to deploy track\n  * refactor: tweak cert email output\n  * refactor: exclude Makefile from todo target (#382)\n  * refactor: add stack to ResourceType mapping\n  * refactor reporting for aws types\n  * fix install.deps target\n  * fix case where improper cert is created due to second-level domain (.co.uk). Closes #350\n  * fix hosted zone regression introduced by e8a33a3\n  * fix permission issues for static file serving. Closes #385\n  * docs: add domains command\n  * docs: move policy behind a details element for collapsing\n  * docs: tweak for domain changes\n\nv0.2.10 / 2017-10-13\n====================\n\n  * add flushing of proxy logs after response. Closes #370\n  * add periodic flushing of proxy logs for `up start`. Closes #369\n  * add internal text handler to `up start`\n\nv0.2.9 / 2017-10-10\n===================\n\n  * fix: disable relay keep alive conns, they interact poorly with suspension (#365)\n\nv0.2.8 / 2017-10-09\n===================\n\n  * fix missing body regression\n\nv0.2.7 / 2017-10-09\n===================\n\n  * update go-apex dep\n  * update lambda shim with concurrency support\n  * fix: implement proxy GetBody to allow for re-reading request bodies. Closes #363\n  * remove .lambda.timeout, replace with .proxy.timeout\n\nv0.2.6 / 2017-09-29\n===================\n\n  * add `proxy.retry` option defaulting to `true`\n  * add UP_STAGE to `up start`\n  * add stage `.path` basepath support\n  * fix install script for Yosemite. Closes #345\n\nv0.2.5 / 2017-09-20\n===================\n\n  * add more relay logs\n  * docs: refactor\n  * add .proxy.timeout for requests and retries. Closes #335\n  * refactor: remove a duplicate test\n  * add retrying of 5xx errors for idempotent requests. Closes #214\n  * docs: change chown to bin only. Closes #337\n  * docs: add deletion info\n  * docs: add more stage info\n  * docs: add guide for full app\n  * docs: add note about CF provisioning\n  * docs: add stage section\n  * docs: refactor dns section\n  * docs: remove references to `certs`\n  * docs: remove \"coming soon\"\n  * docs: tweak faq\n  * docs: add vendor mention\n  * update Bowery/prompt dep and fix spacing\n  * fix 404 checksum not found (#331)\n  * docs: add missing package comments\n  * docs: add missing package comments\n  * docs: add note about omitting proxy bin changes\n\nv0.2.4 / 2017-09-15\n===================\n\n  * add custom domain support\n  * add Up version to the -v debug output\n  * add support for JSON log lines, captured and translated to the internal format\n  * add support for indented log lines to be captured as a single message\n  * add sub-process cleanup and grace period. Closes #311\n  * add `ssm:GetParametersByPath` to the function policy\n  * add UP_STAGE env var. Closes #200\n  * change default `proxy.listen_timeout` to 15\n  * fix gzip handling when previously compressed. Closes #328\n  * fix ignoring of .pypath\n\nv0.2.3 / 2017-09-05\n===================\n\n  * fix rewrite content-type. Closes #304\n\nv0.2.2 / 2017-09-05\n===================\n\n  * add logging of log query for debugging\n  * add stage shorthands to log grammar. Closes #286\n  * add bytes / duration units to logging grammar. Closes #283\n  * add humanization of .size field in logs. Closes #252\n  * add support for checking domain availability and registration. Closes #159\n  * add support for multiple hook commands with arrays. Closes #127\n  * add forced inclusion of ./server\n  * add eu-west-2 to the regions list. Closes #280\n  * fix ignoring of node_modules dotfiles (removed .bin by accident etc)\n  * fix stage validation, move before building zip\n  * fix support for other authentication schemes. Closes #287\n  * fix dns record .ttl default\n  * rename .proxy.timeout to .proxy.listen_timeout (BREAKING)\n  * remove `docs` command\n  * remove omission of stage from logs\n\nv0.2.1 / 2017-08-25\n===================\n\n  * fix missing param in Infof log call, outputting `MISSING`\n\nv0.2.0 / 2017-08-25\n===================\n\n  * add hiding of cursor for stack delete and apply\n  * add support for configuring proxy timeout (#273)\n  * add cost to metrics output. Closes #204\n  * add: ignore dotfiles by default\n  * add nicer formatting for numeric metrics\n  * add build command. Closes #257\n  * add validation of stage name to `url` and `deploy`. Closes #261\n  * remove .npmignore support. Closes #270\n\nv0.1.12 / 2017-08-23\n=====================\n\n  * add some basic formatting to `up stack plan`\n  * rename `up stack show` to `up stack status`\n  * fix hard-coded versions for stack updates\n\nv0.1.11 / 2017-08-22\n====================\n\n  * add support for regions defined in `~/.aws/config`\n  * add `up stack plan` and `up stack apply` support. Closes #115\n  * add environment variables to hooks when performing builds etc\n  * fix support for implicit `app.js` when `package.json` is present without a `start` script defined\n\nv0.1.10 / 2017-08-15\n====================\n\n  * add default of ./server back for when source is omitted (main.go for example)\n  * add `**` .upignore support\n  * add forced inclusion of Up's required files\n  * add support for omitting `node_modules` when using Browserify or Webpack\n  * update go-archive for gitignore parity improvements\n\nv0.1.9 / 2017-08-14\n===================\n\n  * add -modtime 0\n  * add smaller progress bar for initial stack\n  * revert \"add error when a dir does not look like a valid project. Closes #197\"\n    * caused an issue if you ignore *.go for example, not robust enough\n\nv0.1.8 / 2017-08-14\n===================\n\n  * add error when a dir does not look like a valid project. Closes #197\n  * add convenience make targets `install` and `install.deps`\n  * add note about AWS_PROFILE in getting started. Closes #230\n  * add python projects with a requirements.txt\n  * add install.sh\n  * fix greedy default error page, add option to explicitly enable. Closes #233\n  * fix exec bit on windows. Closes #225\n  * fix python overriding of custom command\n  * remove default of ./server\n  * remove \"-api\" suffix from IAM role (breaking change)\n  * refactor NewLogs() to properly delegate the error instead of panic\n\nv0.1.7 / 2017-08-12\n===================\n\n  * add size of code/zip before attempting deploy. Closes #222\n  * add better description for --force\n  * change default timeout to 15s from 5s\n  * change default memory from 128 to 512 (Node.js require() is slow)\n  * fix relay timeout (lack of an error)\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License\n\nCopyright (c) 2020 TJ Holowaychuk tj@tjholowaychuk.com\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n"
  },
  {
    "path": "Makefile",
    "content": "\nGO ?= go\n\n# Build all files.\nbuild:\n\t@echo \"==> Building\"\n\t@$(GO) generate ./...\n.PHONY: build\n\n# Install from source.\ninstall:\n\t@echo \"==> Installing up ${GOPATH}/bin/up\"\n\t@$(GO) install ./...\n.PHONY: install\n\n# Run all tests.\ntest: internal/proxy/bin/bin_assets.go\n\t@$(GO) test -timeout 2m ./... && echo \"\\n==>\\033[32m Ok\\033[m\\n\"\n.PHONY: test\n\n# Run all tests in CI.\ntest.ci: internal/proxy/bin/bin_assets.go\n\t@$(GO) test -v -timeout 5m ./... && echo \"\\n==>\\033[32m Ok\\033[m\\n\"\n.PHONY: test.ci\n\ninternal/proxy/bin/bin_assets.go:\n\t@$(GO) generate ./...\n\n# Show source statistics.\ncloc:\n\t@cloc -exclude-dir=vendor,node_modules .\n.PHONY: cloc\n\n# Release binaries to GitHub.\nrelease: build\n\t@echo \"==> Releasing\"\n\t@goreleaser -p 1 --rm-dist --config .goreleaser.yml\n\t@echo \"==> Complete\"\n.PHONY: release\n\n# Show to-do items per file.\ntodo:\n\t@rg TODO:\n.PHONY: todo\n\n# Show size of imports.\nsize:\n\t@curl -sL https://gist.githubusercontent.com/tj/04e0965e23da00ca33f101e5b2ed4ed4/raw/9aa16698b2bc606cf911219ea540972edef05c4b/gistfile1.txt | bash\n.PHONY: size\n\n# Clean.\nclean:\n\t@rm -fr \\\n\t\tdist \\\n\t\tinternal/proxy/bin/bin_assets.go \\\n\t\tinternal/shim/bindata.go\n.PHONY: clean\n"
  },
  {
    "path": "Readme.md",
    "content": "![](assets/title.png)\n\nUp deploys infinitely scalable serverless apps, APIs, and static websites in seconds, so you can get back to working on what makes your product unique.\n\nWith 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!\n\nUse 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.\n\n## About\n\nUp 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.\n\nUp 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.\n\nCheck 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/).\n\n![](assets/screen2.png)\n\n## OSS Features\n\nFeatures of the free open-source edition.\n\n![Open source edition features](assets/features-community.png)\n\n## Pro Features\n\nUp 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.\n\n![Pro edition features](assets/features-pro.png)\n\n[![](https://gui.apex.sh/component?name=ShadowButton&config=%7B%22text%22%3A%22SUBSCRIBE%22%2C%22color%22%3A%227956EF%22%7D)](https://apex.sh/docs/up/guides/#subscribing_to_up_pro)\n\n## Quick Start\n\nInstall Up:\n\n```\n$ curl -sf https://up.apex.sh/install | sh\n```\n\nCreate an `app.js` file:\n\n```js\nrequire('http').createServer((req, res) => {\n  res.end('Hello World\\n')\n}).listen(process.env.PORT)\n```\n\nDeploy the app:\n\n```\n$ up\n```\n\nOpen it in the browser, or copy the url to your clipboard:\n\n```\n$ up url -o\n$ up url -c\n```\n\n<a href=\"https://apex.sh\"><img src=\"http://tjholowaychuk.com:6000/svg/sponsor\"></a>\n"
  },
  {
    "path": "cmd/up/main.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"runtime\"\n\n\t\"github.com/stripe/stripe-go\"\n\t\"github.com/tj/go/env\"\n\t\"github.com/tj/go/term\"\n\n\t// commands\n\t_ \"github.com/apex/up/internal/cli/build\"\n\t_ \"github.com/apex/up/internal/cli/config\"\n\t_ \"github.com/apex/up/internal/cli/deploy\"\n\t_ \"github.com/apex/up/internal/cli/disable-stats\"\n\t_ \"github.com/apex/up/internal/cli/docs\"\n\t_ \"github.com/apex/up/internal/cli/domains\"\n\t_ \"github.com/apex/up/internal/cli/logs\"\n\t_ \"github.com/apex/up/internal/cli/metrics\"\n\t_ \"github.com/apex/up/internal/cli/prune\"\n\t_ \"github.com/apex/up/internal/cli/run\"\n\t_ \"github.com/apex/up/internal/cli/stack\"\n\t_ \"github.com/apex/up/internal/cli/start\"\n\t_ \"github.com/apex/up/internal/cli/team\"\n\t_ \"github.com/apex/up/internal/cli/upgrade\"\n\t_ \"github.com/apex/up/internal/cli/url\"\n\t_ \"github.com/apex/up/internal/cli/version\"\n\n\t\"github.com/apex/up/internal/cli/app\"\n\t\"github.com/apex/up/internal/signal\"\n\t\"github.com/apex/up/internal/stats\"\n\t\"github.com/apex/up/internal/util\"\n)\n\nvar version = \"master\"\n\nfunc main() {\n\tsignal.Add(reset)\n\tstripe.Key = env.GetDefault(\"STRIPE_KEY\", \"pk_live_23pGrHcZ2QpfX525XYmiyzmx\")\n\tstripe.LogLevel = 0\n\n\terr := run()\n\n\tif err == nil {\n\t\treturn\n\t}\n\n\tterm.ShowCursor()\n\n\tswitch {\n\tcase util.IsNoCredentials(err):\n\t\tutil.Fatal(errors.New(\"Cannot find credentials, visit https://apex.sh/docs/up/credentials/ for help.\"))\n\tdefault:\n\t\tutil.Fatal(err)\n\t}\n}\n\n// run the cli.\nfunc run() error {\n\tstats.SetProperties(map[string]interface{}{\n\t\t\"os\":      runtime.GOOS,\n\t\t\"arch\":    runtime.GOARCH,\n\t\t\"version\": version,\n\t\t\"ci\":      os.Getenv(\"CI\") == \"true\" || os.Getenv(\"CI\") == \"1\",\n\t})\n\n\treturn app.Run(version)\n}\n\n// reset cursor.\nfunc reset() error {\n\tterm.ShowCursor()\n\tprintln()\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/up-proxy/main.go",
    "content": "package main\n\nimport (\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/apex/go-apex\"\n\t\"github.com/apex/log\"\n\t\"github.com/apex/log/handlers/json\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/apex/up/handler\"\n\t\"github.com/apex/up/internal/logs\"\n\t\"github.com/apex/up/internal/proxy\"\n\t\"github.com/apex/up/internal/util\"\n\t\"github.com/apex/up/platform/aws/runtime\"\n)\n\nfunc main() {\n\tstart := time.Now()\n\tstage := os.Getenv(\"UP_STAGE\")\n\n\t// setup logging\n\tlog.SetHandler(json.Default)\n\tif s := os.Getenv(\"LOG_LEVEL\"); s != \"\" {\n\t\tlog.SetLevelFromString(s)\n\t}\n\n\tlog.Log = log.WithFields(logs.Fields())\n\tlog.Info(\"initializing\")\n\n\t// read config\n\tc, err := up.ReadConfig(\"up.json\")\n\tif err != nil {\n\t\tlog.Fatalf(\"error reading config: %s\", err)\n\t}\n\n\tctx := log.WithFields(log.Fields{\n\t\t\"name\": c.Name,\n\t\t\"type\": c.Type,\n\t})\n\n\t// init project\n\tp := runtime.New(c)\n\n\t// init runtime\n\tif err := p.Init(stage); err != nil {\n\t\tctx.Fatalf(\"error initializing: %s\", err)\n\t}\n\n\t// overrides\n\tif err := c.Override(stage); err != nil {\n\t\tctx.Fatalf(\"error overriding: %s\", err)\n\t}\n\n\t// create handler\n\th, err := handler.FromConfig(c)\n\tif err != nil {\n\t\tctx.Fatalf(\"error creating handler: %s\", err)\n\t}\n\n\t// init handler\n\th, err = handler.New(c, h)\n\tif err != nil {\n\t\tctx.Fatalf(\"error initializing handler: %s\", err)\n\t}\n\n\t// serve\n\tlog.WithField(\"duration\", util.MillisecondsSince(start)).Info(\"initialized\")\n\tapex.Handle(proxy.NewHandler(h))\n}\n"
  },
  {
    "path": "config/backoff.go",
    "content": "package config\n\nimport (\n\t\"time\"\n\t\n\t\"github.com/tj/backoff\"\n)\n\n// Backoff config.\ntype Backoff struct {\n\t// Min time in milliseconds.\n\tMin int `json:\"min\"`\n\n\t// Max time in milliseconds.\n\tMax int `json:\"max\"`\n\n\t// Factor applied for every attempt.\n\tFactor float64 `json:\"factor\"`\n\n\t// Attempts performed before failing.\n\tAttempts int `json:\"attempts\"`\n\n\t// Jitter is applied when true.\n\tJitter bool `json:\"jitter\"`\n}\n\n// Default implementation.\nfunc (b *Backoff) Default() error {\n\tif b.Min == 0 {\n\t\tb.Min = 100\n\t}\n\n\tif b.Max == 0 {\n\t\tb.Max = 500\n\t}\n\n\tif b.Factor == 0 {\n\t\tb.Factor = 2\n\t}\n\n\tif b.Attempts == 0 {\n\t\tb.Attempts = 3\n\t}\n\n\treturn nil\n}\n\n// Backoff returns the backoff from config.\nfunc (b *Backoff) Backoff() *backoff.Backoff {\n\treturn &backoff.Backoff{\n\t\tMin:    time.Duration(b.Min) * time.Millisecond,\n\t\tMax:    time.Duration(b.Max) * time.Millisecond,\n\t\tFactor: b.Factor,\n\t\tJitter: b.Jitter,\n\t}\n}\n"
  },
  {
    "path": "config/backoff_test.go",
    "content": "package config\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/tj/assert\"\n)\n\nfunc TestBackoff_Default(t *testing.T) {\n\ta := &Backoff{}\n\tassert.NoError(t, a.Default(), \"default\")\n\n\tb := &Backoff{\n\t\tMin:      100,\n\t\tMax:      500,\n\t\tFactor:   2,\n\t\tAttempts: 3,\n\t}\n\n\tassert.Equal(t, b, a)\n}\n\nfunc TestBackoff_Backoff(t *testing.T) {\n\ta := &Backoff{}\n\tassert.NoError(t, a.Default(), \"default\")\n\n\tb := a.Backoff()\n\tassert.Equal(t, time.Millisecond*100, b.Min)\n\tassert.Equal(t, time.Millisecond*500, b.Max)\n}\n"
  },
  {
    "path": "config/config.go",
    "content": "package config\n\nimport (\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"os\"\n\n\t\"github.com/apex/log\"\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/apex/up/internal/header\"\n\t\"github.com/apex/up/internal/inject\"\n\t\"github.com/apex/up/internal/redirect\"\n\t\"github.com/apex/up/internal/validate\"\n\t\"github.com/apex/up/platform/aws/regions\"\n\t\"github.com/aws/aws-sdk-go/aws/session\"\n)\n\n// defaulter is the interface that provides config defaulting.\ntype defaulter interface {\n\tDefault() error\n}\n\n// validator is the interface that provides config validation.\ntype validator interface {\n\tValidate() error\n}\n\n// Config for the project.\ntype Config struct {\n\tName        string         `json:\"name\"`\n\tDescription string         `json:\"description\"`\n\tType        string         `json:\"type\"`\n\tHeaders     header.Rules   `json:\"headers\"`\n\tRedirects   redirect.Rules `json:\"redirects\"`\n\tHooks       Hooks          `json:\"hooks\"`\n\tEnvironment Environment    `json:\"environment\"`\n\tRegions     []string       `json:\"regions\"`\n\tProfile     string         `json:\"profile\"`\n\tInject      inject.Rules   `json:\"inject\"`\n\tLambda      Lambda         `json:\"lambda\"`\n\tCORS        *CORS          `json:\"cors\"`\n\tErrorPages  ErrorPages     `json:\"error_pages\"`\n\tProxy       Relay          `json:\"proxy\"`\n\tStatic      Static         `json:\"static\"`\n\tLogs        Logs           `json:\"logs\"`\n\tStages      Stages         `json:\"stages\"`\n\tDNS         DNS            `json:\"dns\"`\n}\n\n// Validate implementation.\nfunc (c *Config) Validate() error {\n\tif err := validate.RequiredString(c.Name); err != nil {\n\t\treturn errors.Wrap(err, \".name\")\n\t}\n\n\tif err := validate.Name(c.Name); err != nil {\n\t\treturn errors.Wrapf(err, \".name %q\", c.Name)\n\t}\n\n\tif err := validate.List(c.Type, []string{\"static\", \"server\"}); err != nil {\n\t\treturn errors.Wrap(err, \".type\")\n\t}\n\n\tif err := validate.Lists(c.Regions, regions.IDs); err != nil {\n\t\treturn errors.Wrap(err, \".regions\")\n\t}\n\n\tif err := c.DNS.Validate(); err != nil {\n\t\treturn errors.Wrap(err, \".dns\")\n\t}\n\n\tif err := c.Static.Validate(); err != nil {\n\t\treturn errors.Wrap(err, \".static\")\n\t}\n\n\tif err := c.Inject.Validate(); err != nil {\n\t\treturn errors.Wrap(err, \".inject\")\n\t}\n\n\tif err := c.Lambda.Validate(); err != nil {\n\t\treturn errors.Wrap(err, \".lambda\")\n\t}\n\n\tif err := c.Proxy.Validate(); err != nil {\n\t\treturn errors.Wrap(err, \".proxy\")\n\t}\n\n\tif err := c.Stages.Validate(); err != nil {\n\t\treturn errors.Wrap(err, \".stages\")\n\t}\n\n\tif len(c.Regions) > 1 {\n\t\treturn errors.New(\"multiple regions is not yet supported, see https://github.com/apex/up/issues/134\")\n\t}\n\n\treturn nil\n}\n\n// Default implementation.\nfunc (c *Config) Default() error {\n\tif c.Stages == nil {\n\t\tc.Stages = make(Stages)\n\t}\n\n\t// we default stages here before others simply to\n\t// initialize the default stages such as \"development\"\n\t// allowing runtime inference to default values.\n\tif err := c.Stages.Default(); err != nil {\n\t\treturn errors.Wrap(err, \".stages\")\n\t}\n\n\t// TODO: hack, move to the instantiation of aws clients\n\tif c.Profile != \"\" {\n\t\tsetProfile(c.Profile)\n\t}\n\n\t// default type to server\n\tif c.Type == \"\" {\n\t\tc.Type = \"server\"\n\t}\n\n\t// runtime defaults\n\tif c.Type != \"static\" {\n\t\truntime := inferRuntime()\n\t\tlog.WithField(\"type\", runtime).Debug(\"inferred runtime\")\n\n\t\tif err := runtimeConfig(runtime, c); err != nil {\n\t\t\treturn errors.Wrap(err, \"runtime\")\n\t\t}\n\t}\n\n\t// default .regions\n\tif err := c.defaultRegions(); err != nil {\n\t\treturn errors.Wrap(err, \".region\")\n\t}\n\n\t// region globbing\n\tc.Regions = regions.Match(c.Regions)\n\n\t// default .proxy\n\tif err := c.Proxy.Default(); err != nil {\n\t\treturn errors.Wrap(err, \".proxy\")\n\t}\n\n\t// default .lambda\n\tif err := c.Lambda.Default(); err != nil {\n\t\treturn errors.Wrap(err, \".lambda\")\n\t}\n\n\t// default .dns\n\tif err := c.DNS.Default(); err != nil {\n\t\treturn errors.Wrap(err, \".dns\")\n\t}\n\n\t// default .logs\n\tif err := c.Logs.Default(); err != nil {\n\t\treturn errors.Wrap(err, \".logs\")\n\t}\n\n\t// default .inject\n\tif err := c.Inject.Default(); err != nil {\n\t\treturn errors.Wrap(err, \".inject\")\n\t}\n\n\t// default .error_pages\n\tif err := c.ErrorPages.Default(); err != nil {\n\t\treturn errors.Wrap(err, \".error_pages\")\n\t}\n\n\t// default .stages\n\tif err := c.Stages.Default(); err != nil {\n\t\treturn errors.Wrap(err, \".stages\")\n\t}\n\n\treturn nil\n}\n\n// Override with stage config if present, and re-validate.\nfunc (c *Config) Override(stage string) error {\n\ts := c.Stages.GetByName(stage)\n\tif s == nil {\n\t\treturn nil\n\t}\n\n\ts.Override(c)\n\n\treturn c.Validate()\n}\n\n// defaultRegions checks AWS_REGION and falls back on us-west-2.\nfunc (c *Config) defaultRegions() error {\n\tif len(c.Regions) != 0 {\n\t\tlog.Debugf(\"%d regions from config\", len(c.Regions))\n\t\treturn nil\n\t}\n\n\ts, err := session.NewSessionWithOptions(session.Options{\n\t\tSharedConfigState: session.SharedConfigEnable,\n\t})\n\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"creating session\")\n\t}\n\n\tif r := *s.Config.Region; r != \"\" {\n\t\tlog.Debugf(\"region from aws shared config %q\", r)\n\t\tc.Regions = append(c.Regions, r)\n\t\treturn nil\n\t}\n\n\tr := \"us-west-2\"\n\tlog.Debugf(\"region defaulted to %q\", r)\n\tc.Regions = append(c.Regions, r)\n\treturn nil\n}\n\n// ParseConfig returns config from JSON bytes.\nfunc ParseConfig(b []byte) (*Config, error) {\n\tc := &Config{}\n\n\tif err := json.Unmarshal(b, c); err != nil {\n\t\treturn nil, errors.Wrap(err, \"parsing json\")\n\t}\n\n\tif err := c.Default(); err != nil {\n\t\treturn nil, errors.Wrap(err, \"defaulting\")\n\t}\n\n\tif err := c.Validate(); err != nil {\n\t\treturn nil, errors.Wrap(err, \"validating\")\n\t}\n\n\treturn c, nil\n}\n\n// ParseConfigString returns config from JSON string.\nfunc ParseConfigString(s string) (*Config, error) {\n\treturn ParseConfig([]byte(s))\n}\n\n// MustParseConfigString returns config from JSON string.\nfunc MustParseConfigString(s string) *Config {\n\tc, err := ParseConfigString(s)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn c\n}\n\n// ReadConfig reads the configuration from `path`.\nfunc ReadConfig(path string) (*Config, error) {\n\tb, err := ioutil.ReadFile(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ParseConfig(b)\n}\n\n// setProfile sets the AWS_PROFILE.\nfunc setProfile(name string) {\n\tos.Setenv(\"AWS_PROFILE\", name)\n}\n"
  },
  {
    "path": "config/config_test.go",
    "content": "package config\n\nimport (\n\t\"io/ioutil\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/tj/assert\"\n)\n\nfunc TestConfig_Name(t *testing.T) {\n\tt.Run(\"valid\", func(t *testing.T) {\n\t\tc := Config{\n\t\t\tName: \"my-app123\",\n\t\t}\n\n\t\tassert.NoError(t, c.Default(), \"default\")\n\t\tassert.NoError(t, c.Validate(), \"validate\")\n\t})\n\n\tt.Run(\"invalid\", func(t *testing.T) {\n\t\tc := Config{\n\t\t\tName: \"my app\",\n\t\t}\n\n\t\tassert.NoError(t, c.Default(), \"default\")\n\t\tassert.EqualError(t, c.Validate(), `.name \"my app\": must contain only lowercase alphanumeric characters and '-'`)\n\t})\n\n\tt.Run(\"invalid\", func(t *testing.T) {\n\t\tc := Config{\n\t\t\tName: \"MYAPP\",\n\t\t}\n\n\t\tassert.NoError(t, c.Default(), \"default\")\n\t\tassert.EqualError(t, c.Validate(), `.name \"MYAPP\": must contain only lowercase alphanumeric characters and '-'`)\n\t})\n}\n\nfunc TestConfig_Type(t *testing.T) {\n\tt.Run(\"default\", func(t *testing.T) {\n\t\tc := Config{\n\t\t\tName: \"api\",\n\t\t}\n\n\t\tassert.NoError(t, c.Default(), \"default\")\n\t\tassert.NoError(t, c.Validate(), \"validate\")\n\t\tassert.Equal(t, \"server\", c.Type)\n\t})\n\n\tt.Run(\"valid\", func(t *testing.T) {\n\t\tc := Config{\n\t\t\tName: \"api\",\n\t\t\tType: \"server\",\n\t\t}\n\n\t\tassert.NoError(t, c.Default(), \"default\")\n\t\tassert.NoError(t, c.Validate(), \"validate\")\n\t})\n\n\tt.Run(\"invalid\", func(t *testing.T) {\n\t\tc := Config{\n\t\t\tName: \"api\",\n\t\t\tType: \"something\",\n\t\t}\n\n\t\tassert.NoError(t, c.Default(), \"default\")\n\t\tassert.EqualError(t, c.Validate(), `.type: \"something\" is invalid, must be one of:\n\n  • static\n  • server`)\n\t})\n}\n\nfunc TestConfig_Regions(t *testing.T) {\n\tt.Skip()\n\n\tt.Run(\"valid multiple\", func(t *testing.T) {\n\t\tc := Config{\n\t\t\tName:    \"api\",\n\t\t\tType:    \"server\",\n\t\t\tRegions: []string{\"us-west-2\", \"us-east-1\"},\n\t\t}\n\n\t\tassert.NoError(t, c.Default(), \"default\")\n\t\tassert.NoError(t, c.Validate(), \"validate\")\n\t})\n\n\tt.Run(\"valid multiple\", func(t *testing.T) {\n\t\tc := Config{\n\t\t\tName:    \"api\",\n\t\t\tType:    \"server\",\n\t\t\tRegions: []string{\"us-west-2\", \"us-east-1\"},\n\t\t}\n\n\t\tassert.NoError(t, c.Default(), \"default\")\n\t\tassert.NoError(t, c.Validate(), \"validate\")\n\t})\n\n\tt.Run(\"valid globbing\", func(t *testing.T) {\n\t\tc := Config{\n\t\t\tName:    \"api\",\n\t\t\tType:    \"server\",\n\t\t\tRegions: []string{\"us-*\", \"us-east-1\", \"ca-central-*\"},\n\t\t}\n\n\t\tassert.NoError(t, c.Default(), \"default\")\n\t\tassert.NoError(t, c.Validate(), \"validate\")\n\t\tassert.Equal(t, []string{\"us-east-2\", \"us-east-1\", \"us-west-1\", \"us-west-2\", \"us-east-1\", \"ca-central-1\"}, c.Regions)\n\t})\n\n\tt.Run(\"invalid globbing\", func(t *testing.T) {\n\t\tc := Config{\n\t\t\tName:    \"api\",\n\t\t\tType:    \"server\",\n\t\t\tRegions: []string{\"uss-*\"},\n\t\t}\n\n\t\tassert.NoError(t, c.Default(), \"default\")\n\n\t\tassert.EqualError(t, c.Validate(), `.regions: \"uss-*\" is invalid, must be one of:\n\n  • us-east-2\n  • us-east-1\n  • us-west-1\n  • us-west-2\n  • ap-south-1\n  • ap-northeast-2\n  • ap-southeast-1\n  • ap-southeast-2\n  • ap-northeast-1\n  • ca-central-1\n  • eu-central-1\n  • eu-west-1\n  • eu-west-2\n  • eu-west-3\n  • sa-east-1`)\n\t})\n\n\tt.Run(\"invalid\", func(t *testing.T) {\n\t\tc := Config{\n\t\t\tName:    \"api\",\n\t\t\tType:    \"server\",\n\t\t\tRegions: []string{\"us-west-1\", \"us-west-9\"},\n\t\t}\n\n\t\tassert.NoError(t, c.Default(), \"default\")\n\n\t\tassert.EqualError(t, c.Validate(), `.regions: \"us-west-9\" is invalid, must be one of:\n\n  • us-east-2\n  • us-east-1\n  • us-west-1\n  • us-west-2\n  • ap-south-1\n  • ap-northeast-2\n  • ap-southeast-1\n  • ap-southeast-2\n  • ap-northeast-1\n  • ca-central-1\n  • eu-central-1\n  • eu-west-1\n  • eu-west-2\n  • eu-west-3\n  • sa-east-1`)\n\t})\n}\n\nfunc TestConfig_defaultRegions(t *testing.T) {\n\tt.Run(\"regions from config\", func(t *testing.T) {\n\t\tregions := []string{\"us-east-1\"}\n\t\tc := Config{\n\t\t\tName:    \"api\",\n\t\t\tType:    \"server\",\n\t\t\tRegions: regions,\n\t\t}\n\t\tassert.NoError(t, c.Default(), \"default\")\n\n\t\tassert.NoError(t, c.defaultRegions(), \"defaultRegions\")\n\t\tassert.Equal(t, 1, len(c.Regions), \"regions should have length 2\")\n\t\tassert.Equal(t, regions, c.Regions, \"should read regions from config\")\n\t\tassert.NoError(t, c.Validate(), \"validate\")\n\t})\n\n\tt.Run(\"regions from AWS_REGION\", func(t *testing.T) {\n\t\tregion := \"sa-east-1\"\n\t\tos.Setenv(\"AWS_REGION\", region)\n\n\t\tdefer os.Setenv(\"AWS_REGION\", \"\")\n\t\tc := Config{\n\t\t\tName: \"api\",\n\t\t\tType: \"server\",\n\t\t}\n\t\tassert.NoError(t, c.Default(), \"default\")\n\n\t\tassert.NoError(t, c.defaultRegions(), \"defaultRegions\")\n\t\tassert.Equal(t, 1, len(c.Regions), \"regions should have length 1\")\n\t\tassert.Equal(t, region, c.Regions[0], \"should read regions from AWS_REGION\")\n\t\tassert.NoError(t, c.Default(), \"default\")\n\t\tassert.NoError(t, c.Validate(), \"validate\")\n\t})\n\n\tt.Run(\"regions from AWS_DEFAULT_REGION\", func(t *testing.T) {\n\t\tregion := \"sa-east-1\"\n\n\t\tos.Setenv(\"AWS_DEFAULT_REGION\", region)\n\t\tdefer os.Setenv(\"AWS_DEFAULT_REGION\", \"\")\n\n\t\tc := Config{\n\t\t\tName: \"api\",\n\t\t\tType: \"server\",\n\t\t}\n\t\tassert.NoError(t, c.Default(), \"default\")\n\n\t\tassert.NoError(t, c.defaultRegions(), \"defaultRegions\")\n\t\tassert.Equal(t, 1, len(c.Regions), \"regions should have length 1\")\n\t\tassert.Equal(t, region, c.Regions[0], \"should read regions from AWS_DEFAULT_REGION\")\n\t\tassert.NoError(t, c.Validate(), \"validate\")\n\t})\n\n\tt.Run(\"regions from shared config with default profile\", func(t *testing.T) {\n\t\tcontent := `\n\t\t[default]\n\t\tregion = sa-east-1\n\t\toutput = json\n\t\t[profile another-profile]\n\t\tregion = ap-southeast-2\n\t\toutput = json`\n\n\t\ttmpfile, err := ioutil.TempFile(\"\", \"config\")\n\t\tassert.NoError(t, err)\n\t\tdefer os.Remove(tmpfile.Name())\n\n\t\t_, err = tmpfile.WriteString(content)\n\t\tassert.NoError(t, err)\n\n\t\tos.Setenv(\"AWS_CONFIG_FILE\", tmpfile.Name())\n\t\tdefer os.Setenv(\"AWS_CONFIG_FILE\", \"\")\n\n\t\tc := Config{\n\t\t\tName: \"api\",\n\t\t\tType: \"server\",\n\t\t}\n\t\tassert.NoError(t, c.Default(), \"default\")\n\n\t\tassert.NoError(t, c.defaultRegions(), \"defaultRegions\")\n\t\tassert.Equal(t, 1, len(c.Regions), \"regions should have length 1\")\n\t\tassert.Equal(t, \"sa-east-1\", c.Regions[0], \"should read regions from shared config with default profile\")\n\t\tassert.NoError(t, c.Validate(), \"validate\")\n\t})\n\n\tt.Run(\"regions from shared config with AWS_PROFILE profile\", func(t *testing.T) {\n\t\tcontent := `\n\t\t[default]\n\t\tregion = sa-east-1\n\t\toutput = json\n\t\t[profile another-profile]\n\t\tregion = ap-southeast-2\n\t\toutput = json`\n\n\t\ttmpfile, err := ioutil.TempFile(\"\", \"config\")\n\t\tassert.NoError(t, err)\n\t\tdefer os.Remove(tmpfile.Name())\n\n\t\t_, err = tmpfile.WriteString(content)\n\t\tassert.NoError(t, err)\n\n\t\tos.Setenv(\"AWS_CONFIG_FILE\", tmpfile.Name())\n\t\tdefer os.Setenv(\"AWS_CONFIG_FILE\", \"\")\n\n\t\tos.Setenv(\"AWS_PROFILE\", \"another-profile\")\n\t\tdefer os.Setenv(\"AWS_PROFILE\", \"\")\n\n\t\tc := Config{\n\t\t\tName: \"api\",\n\t\t\tType: \"server\",\n\t\t}\n\t\tassert.NoError(t, c.Default(), \"default\")\n\n\t\tassert.NoError(t, c.defaultRegions(), \"defaultRegions\")\n\t\tassert.Equal(t, 1, len(c.Regions), \"regions should have length 1\")\n\t\tassert.Equal(t, \"ap-southeast-2\", c.Regions[0], \"should read regions from shared config with AWS_PROFILE profile\")\n\t\tassert.NoError(t, c.Validate(), \"validate\")\n\t})\n\n\tt.Run(\"default region must be us-west-2\", func(t *testing.T) {\n\t\t// Make sure we aren't reading AWS config file\n\t\tos.Setenv(\"AWS_CONFIG_FILE\", \"does-not-exist\")\n\n\t\tc := Config{\n\t\t\tName: \"api\",\n\t\t\tType: \"server\",\n\t\t}\n\t\tassert.NoError(t, c.Default(), \"default\")\n\n\t\tassert.NoError(t, c.defaultRegions(), \"defaultRegions\")\n\t\tassert.Equal(t, 1, len(c.Regions), \"regions should have length 1\")\n\t\tassert.Equal(t, \"us-west-2\", c.Regions[0], \"default region must be us-west-2\")\n\t\tassert.NoError(t, c.Validate(), \"validate\")\n\t})\n}\n"
  },
  {
    "path": "config/cors.go",
    "content": "package config\n\n// CORS configuration.\ntype CORS struct {\n\t// AllowedOrigins is a list of origins a cross-domain request can be executed from.\n\t// If the special \"*\" value is present in the list, all origins will be allowed.\n\t// An origin may contain a wildcard (*) to replace 0 or more characters\n\t// (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penalty.\n\t// Only one wildcard can be used per origin.\n\t// Default value is [\"*\"]\n\tAllowedOrigins []string `json:\"allowed_origins\"`\n\n\t// AllowedMethods is a list of methods the client is allowed to use with\n\t// cross-domain requests. Default value is simple methods (GET and POST)\n\tAllowedMethods []string `json:\"allowed_methods\"`\n\n\t// AllowedHeaders is list of non simple headers the client is allowed to use with\n\t// cross-domain requests.\n\t// If the special \"*\" value is present in the list, all headers will be allowed.\n\t// Default value is [] but \"Origin\" is always appended to the list.\n\tAllowedHeaders []string `json:\"allowed_headers\"`\n\n\t// ExposedHeaders indicates which headers are safe to expose to the API of a CORS\n\t// API specification\n\tExposedHeaders []string `json:\"exposed_headers\"`\n\n\t// AllowCredentials indicates whether the request can include user credentials like\n\t// cookies, HTTP authentication or client side SSL certificates.\n\tAllowCredentials bool `json:\"allow_credentials\"`\n\n\t// MaxAge indicates how long (in seconds) the results of a preflight request\n\t// can be cached.\n\tMaxAge int `json:\"max_age\"`\n\n\t// Debugging flag adds additional output to debug server side CORS issues\n\tDebug bool `json:\"debug\"`\n}\n"
  },
  {
    "path": "config/dns.go",
    "content": "package config\n\nimport (\n\t\"encoding/json\"\n\n\t\"github.com/apex/up/internal/validate\"\n\t\"github.com/pkg/errors\"\n)\n\n// recordTypes is a list of valid record types.\nvar recordTypes = []string{\n\t\"ALIAS\",\n\t\"A\",\n\t\"AAAA\",\n\t\"CNAME\",\n\t\"MX\",\n\t\"NAPTR\",\n\t\"NS\",\n\t\"PTR\",\n\t\"SOA\",\n\t\"SPF\",\n\t\"SRV\",\n\t\"TXT\",\n}\n\n// DNS config.\ntype DNS struct {\n\tZones []*Zone `json:\"zones\"`\n}\n\n// UnmarshalJSON implementation.\nfunc (d *DNS) UnmarshalJSON(b []byte) error {\n\tvar zones map[string][]*Record\n\n\tif err := json.Unmarshal(b, &zones); err != nil {\n\t\treturn err\n\t}\n\n\tfor name, records := range zones {\n\t\tzone := &Zone{Name: name, Records: records}\n\t\td.Zones = append(d.Zones, zone)\n\t}\n\n\treturn nil\n}\n\n// Default implementation.\nfunc (d *DNS) Default() error {\n\tfor _, z := range d.Zones {\n\t\tif err := z.Default(); err != nil {\n\t\t\treturn errors.Wrapf(err, \"zone %s\", z.Name)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Validate implementation.\nfunc (d *DNS) Validate() error {\n\tfor _, z := range d.Zones {\n\t\tif err := z.Validate(); err != nil {\n\t\t\treturn errors.Wrapf(err, \"zone %s\", z.Name)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Zone is a DNS zone.\ntype Zone struct {\n\tName    string    `json:\"name\"`\n\tRecords []*Record `json:\"records\"`\n}\n\n// Default implementation.\nfunc (z *Zone) Default() error {\n\tfor i, r := range z.Records {\n\t\tif err := r.Default(); err != nil {\n\t\t\treturn errors.Wrapf(err, \"record %d\", i)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Validate implementation.\nfunc (z *Zone) Validate() error {\n\tfor i, r := range z.Records {\n\t\tif err := r.Validate(); err != nil {\n\t\t\treturn errors.Wrapf(err, \"record %d\", i)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Record is a DNS record.\ntype Record struct {\n\tName  string   `json:\"name\"`\n\tType  string   `json:\"type\"`\n\tTTL   int      `json:\"ttl\"`\n\tValue []string `json:\"value\"`\n}\n\n// Validate implementation.\nfunc (r *Record) Validate() error {\n\tif err := validate.List(r.Type, recordTypes); err != nil {\n\t\treturn errors.Wrap(err, \".type\")\n\t}\n\n\tif err := validate.RequiredString(r.Name); err != nil {\n\t\treturn errors.Wrap(err, \".name\")\n\t}\n\n\tif err := validate.RequiredStrings(r.Value); err != nil {\n\t\treturn errors.Wrap(err, \".value\")\n\t}\n\n\tif err := validate.MinStrings(r.Value, 1); err != nil {\n\t\treturn errors.Wrap(err, \".value\")\n\t}\n\n\treturn nil\n}\n\n// Default implementation.\nfunc (r *Record) Default() error {\n\tif r.TTL == 0 {\n\t\tr.TTL = 300\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "config/dns_test.go",
    "content": "package config\n\nimport (\n\t\"encoding/json\"\n\t\"log\"\n\t\"os\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/tj/assert\"\n)\n\nfunc ExampleDNS() {\n\ts := `{\n\t\t\"something.sh\": [\n\t\t\t{\n\t\t\t\t\"name\": \"something.com\",\n\t\t\t\t\"type\": \"A\",\n\t\t\t\t\"ttl\": 60,\n\t\t\t\t\"value\": [\"35.161.83.243\"]\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"name\": \"blog.something.com\",\n\t\t\t\t\"type\": \"CNAME\",\n\t\t\t\t\"ttl\": 60,\n\t\t\t\t\"value\": [\"34.209.172.67\"]\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"name\": \"api.something.com\",\n\t\t\t\t\"type\": \"A\",\n\t\t\t\t\"value\": [\"54.187.185.18\"]\n\t\t\t}\n\t\t]\n\t}`\n\n\tvar c DNS\n\n\tif err := json.Unmarshal([]byte(s), &c); err != nil {\n\t\tlog.Fatalf(\"error unmarshaling: %s\", err)\n\t}\n\n\tsort.Slice(c.Zones[0].Records, func(i int, j int) bool {\n\t\ta := c.Zones[0].Records[i]\n\t\tb := c.Zones[0].Records[j]\n\t\treturn a.Name > b.Name\n\t})\n\n\tif err := c.Validate(); err != nil {\n\t\tlog.Fatalf(\"error validating: %s\", err)\n\t}\n\n\tif err := c.Default(); err != nil {\n\t\tlog.Fatalf(\"error defaulting: %s\", err)\n\t}\n\n\tenc := json.NewEncoder(os.Stdout)\n\tenc.SetIndent(\"\", \"  \")\n\tenc.Encode(c)\n\t// Output:\n\t// \t{\n\t//   \"zones\": [\n\t//     {\n\t//       \"name\": \"something.sh\",\n\t//       \"records\": [\n\t//         {\n\t//           \"name\": \"something.com\",\n\t//           \"type\": \"A\",\n\t//           \"ttl\": 60,\n\t//           \"value\": [\n\t//             \"35.161.83.243\"\n\t//           ]\n\t//         },\n\t//         {\n\t//           \"name\": \"blog.something.com\",\n\t//           \"type\": \"CNAME\",\n\t//           \"ttl\": 60,\n\t//           \"value\": [\n\t//             \"34.209.172.67\"\n\t//           ]\n\t//         },\n\t//         {\n\t//           \"name\": \"api.something.com\",\n\t//           \"type\": \"A\",\n\t//           \"ttl\": 300,\n\t//           \"value\": [\n\t//             \"54.187.185.18\"\n\t//           ]\n\t//         }\n\t//       ]\n\t//     }\n\t//   ]\n\t// }\n}\n\nfunc TestDNS_Validate(t *testing.T) {\n\tt.Run(\"invalid\", func(t *testing.T) {\n\t\tc := &DNS{\n\t\t\tZones: []*Zone{\n\t\t\t\t{\n\t\t\t\t\tName: \"apex.sh\",\n\t\t\t\t\tRecords: []*Record{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"blog.apex.sh\",\n\t\t\t\t\t\t\tType: \"CNAME\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tassert.EqualError(t, c.Validate(), `zone apex.sh: record 0: .value: must have at least 1 value`)\n\t})\n}\n\nfunc TestRecord_Type(t *testing.T) {\n\tt.Run(\"valid\", func(t *testing.T) {\n\t\tc := &Record{\n\t\t\tName:  \"blog.apex.sh\",\n\t\t\tType:  \"A\",\n\t\t\tValue: []string{\"1.1.1.1\"},\n\t\t}\n\n\t\tassert.NoError(t, c.Validate(), \"validate\")\n\t})\n\n\tt.Run(\"invalid\", func(t *testing.T) {\n\t\tc := &Record{\n\t\t\tName: \"blog.apex.sh\",\n\t\t\tType: \"AAA\",\n\t\t}\n\n\t\tassert.EqualError(t, c.Validate(), `.type: \"AAA\" is invalid, must be one of:\n\n  • ALIAS\n  • A\n  • AAAA\n  • CNAME\n  • MX\n  • NAPTR\n  • NS\n  • PTR\n  • SOA\n  • SPF\n  • SRV\n  • TXT`)\n\t})\n}\n\nfunc TestRecord_TTL(t *testing.T) {\n\tc := &Record{Type: \"A\"}\n\tassert.NoError(t, c.Default(), \"default\")\n\tassert.Equal(t, 300, c.TTL)\n}\n\nfunc TestRecord_Value(t *testing.T) {\n\tt.Run(\"empty\", func(t *testing.T) {\n\t\tc := &Record{\n\t\t\tName: \"blog.apex.sh\",\n\t\t\tType: \"A\",\n\t\t}\n\n\t\tassert.EqualError(t, c.Validate(), `.value: must have at least 1 value`)\n\t})\n\n\tt.Run(\"invalid\", func(t *testing.T) {\n\t\tc := &Record{\n\t\t\tName:  \"blog.apex.sh\",\n\t\t\tType:  \"A\",\n\t\t\tValue: []string{\"1.1.1.1\", \"\"},\n\t\t}\n\n\t\tassert.EqualError(t, c.Validate(), `.value: at index 1: is required`)\n\t})\n}\n"
  },
  {
    "path": "config/doc.go",
    "content": "// Package config provides configuration structures,\n// validation, and defaulting for up.json config.\npackage config\n"
  },
  {
    "path": "config/duration.go",
    "content": "package config\n\nimport (\n\t\"bytes\"\n\t\"strconv\"\n\t\"time\"\n)\n\n// Duration may be specified as numerical seconds or\n// as a duration string such as \"1.5m\".\ntype Duration time.Duration\n\n// Seconds returns the duration in seconds.\nfunc (d *Duration) Seconds() float64 {\n\treturn float64(time.Duration(*d) / time.Second)\n}\n\n// UnmarshalJSON implementation.\nfunc (d *Duration) UnmarshalJSON(b []byte) error {\n\tif i, err := strconv.ParseInt(string(b), 10, 64); err == nil {\n\t\t*d = Duration(time.Second * time.Duration(i))\n\t\treturn nil\n\t}\n\n\tv, err := time.ParseDuration(string(bytes.Trim(b, `\"`)))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t*d = Duration(v)\n\treturn nil\n}\n\n// MarshalJSON implement.\nfunc (d *Duration) MarshalJSON() ([]byte, error) {\n\treturn []byte(strconv.Itoa(int(d.Seconds()))), nil\n}\n"
  },
  {
    "path": "config/duration_test.go",
    "content": "package config\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/tj/assert\"\n)\n\nfunc TestDuration_UnmarshalJSON(t *testing.T) {\n\tt.Run(\"numeric seconds\", func(t *testing.T) {\n\t\ts := `{\n      \"timeout\": 5\n    }`\n\n\t\tvar c struct {\n\t\t\tTimeout Duration\n\t\t}\n\n\t\terr := json.Unmarshal([]byte(s), &c)\n\t\tassert.NoError(t, err, \"unmarshal\")\n\n\t\tassert.Equal(t, Duration(5*time.Second), c.Timeout)\n\t})\n\n\tt.Run(\"string duration\", func(t *testing.T) {\n\t\ts := `{\n      \"timeout\": \"1.5m\"\n    }`\n\n\t\tvar c struct {\n\t\t\tTimeout Duration\n\t\t}\n\n\t\terr := json.Unmarshal([]byte(s), &c)\n\t\tassert.NoError(t, err, \"unmarshal\")\n\n\t\tassert.Equal(t, Duration(90*time.Second), c.Timeout)\n\t})\n}\n"
  },
  {
    "path": "config/environment.go",
    "content": "package config\n\n// Environment variables.\ntype Environment map[string]string\n"
  },
  {
    "path": "config/errorpages.go",
    "content": "package config\n\n// ErrorPages configuration.\ntype ErrorPages struct {\n\t// Enable error pages.\n\tEnable bool `json:\"enable\"`\n\n\t// Dir containing error pages.\n\tDir string `json:\"dir\"`\n\n\t// Variables are passed to the template for use.\n\tVariables map[string]interface{} `json:\"variables\"`\n}\n\n// Default implementation.\nfunc (e *ErrorPages) Default() error {\n\tif e.Dir == \"\" {\n\t\te.Dir = \".\"\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "config/errorpages_test.go",
    "content": "package config\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tj/assert\"\n)\n\nfunc TestErrorPages(t *testing.T) {\n\tc := &ErrorPages{}\n\tassert.NoError(t, c.Default(), \"default\")\n\tassert.Equal(t, \".\", c.Dir, \"dir\")\n}\n"
  },
  {
    "path": "config/hooks.go",
    "content": "package config\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n)\n\n// Hook is one or more commands.\ntype Hook []string\n\n// Hooks for the project.\ntype Hooks struct {\n\tBuild      Hook `json:\"build\"`\n\tClean      Hook `json:\"clean\"`\n\tPreBuild   Hook `json:\"prebuild\"`\n\tPostBuild  Hook `json:\"postbuild\"`\n\tPreDeploy  Hook `json:\"predeploy\"`\n\tPostDeploy Hook `json:\"postdeploy\"`\n}\n\n// Override config.\nfunc (h *Hooks) Override(c *Config) {\n\tif v := h.Build; v != nil {\n\t\tc.Hooks.Build = v\n\t}\n\n\tif v := h.Clean; v != nil {\n\t\tc.Hooks.Clean = v\n\t}\n\n\tif v := h.PreBuild; v != nil {\n\t\tc.Hooks.PreBuild = v\n\t}\n\n\tif v := h.PostBuild; v != nil {\n\t\tc.Hooks.PostBuild = v\n\t}\n\n\tif v := h.PreDeploy; v != nil {\n\t\tc.Hooks.PreDeploy = v\n\t}\n\n\tif v := h.PostDeploy; v != nil {\n\t\tc.Hooks.PostDeploy = v\n\t}\n}\n\n// Get returns the hook by name or nil.\nfunc (h *Hooks) Get(s string) Hook {\n\tswitch s {\n\tcase \"build\":\n\t\treturn h.Build\n\tcase \"clean\":\n\t\treturn h.Clean\n\tcase \"prebuild\":\n\t\treturn h.PreBuild\n\tcase \"postbuild\":\n\t\treturn h.PostBuild\n\tcase \"predeploy\":\n\t\treturn h.PreDeploy\n\tcase \"postdeploy\":\n\t\treturn h.PostDeploy\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// UnmarshalJSON implementation.\nfunc (h *Hook) UnmarshalJSON(b []byte) error {\n\tswitch b[0] {\n\tcase '\"':\n\t\tvar s string\n\t\tif err := json.Unmarshal(b, &s); err != nil {\n\t\t\treturn err\n\t\t}\n\t\t*h = append(*h, s)\n\t\treturn nil\n\tcase '[':\n\t\treturn json.Unmarshal(b, (*[]string)(h))\n\tdefault:\n\t\treturn errors.New(\"hook must be a string or array of strings\")\n\t}\n}\n\n// IsEmpty returns true if the hook is empty.\nfunc (h *Hook) IsEmpty() bool {\n\treturn h == nil || len(*h) == 0\n}\n"
  },
  {
    "path": "config/hooks_test.go",
    "content": "package config\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/tj/assert\"\n)\n\nfunc TestHook(t *testing.T) {\n\tt.Run(\"missing\", func(t *testing.T) {\n\t\ts := []byte(`{}`)\n\n\t\tvar c struct {\n\t\t\tBuild Hook\n\t\t}\n\n\t\terr := json.Unmarshal(s, &c)\n\t\tassert.NoError(t, err, \"unmarshal\")\n\n\t\tassert.Equal(t, Hook(nil), c.Build)\n\t})\n\n\tt.Run(\"invalid type\", func(t *testing.T) {\n\t\ts := []byte(`\n      {\n        \"build\": 5\n      }\n    `)\n\n\t\tvar c struct {\n\t\t\tBuild Hook\n\t\t}\n\n\t\terr := json.Unmarshal(s, &c)\n\t\tassert.EqualError(t, err, `hook must be a string or array of strings`)\n\t})\n\n\tt.Run(\"string\", func(t *testing.T) {\n\t\ts := []byte(`\n      {\n        \"build\": \"go build main.go\"\n      }\n    `)\n\n\t\tvar c struct {\n\t\t\tBuild Hook\n\t\t}\n\n\t\terr := json.Unmarshal(s, &c)\n\t\tassert.NoError(t, err, \"unmarshal\")\n\n\t\tassert.Equal(t, Hook{\"go build main.go\"}, c.Build)\n\t})\n\n\tt.Run(\"array\", func(t *testing.T) {\n\t\ts := []byte(`\n      {\n        \"build\": [\n          \"go build main.go\",\n          \"browserify src/index.js > app.js\"\n        ]\n      }\n    `)\n\n\t\tvar c struct {\n\t\t\tBuild Hook\n\t\t}\n\n\t\terr := json.Unmarshal(s, &c)\n\t\tassert.NoError(t, err, \"unmarshal\")\n\n\t\tassert.Equal(t, Hook{\n\t\t\t\"go build main.go\",\n\t\t\t\"browserify src/index.js > app.js\",\n\t\t}, c.Build)\n\t})\n}\n"
  },
  {
    "path": "config/lambda.go",
    "content": "package config\n\n// defaultRuntime is the default runtime.\nvar defaultRuntime = \"nodejs10.x\"\n\n// defaultPolicy is the default function role policy.\nvar defaultPolicy = IAMPolicyStatement{\n\t\"Effect\":   \"Allow\",\n\t\"Resource\": \"*\",\n\t\"Action\": []string{\n\t\t\"logs:CreateLogGroup\",\n\t\t\"logs:CreateLogStream\",\n\t\t\"logs:PutLogEvents\",\n\t\t\"ssm:GetParametersByPath\",\n\t\t\"ec2:CreateNetworkInterface\",\n\t\t\"ec2:DescribeNetworkInterfaces\",\n\t\t\"ec2:DeleteNetworkInterface\",\n\t},\n}\n\n// IAMPolicyStatement configuration.\ntype IAMPolicyStatement map[string]interface{}\n\n// VPC configuration.\ntype VPC struct {\n\tSubnets        []string `json:\"subnets\"`\n\tSecurityGroups []string `json:\"security_groups\"`\n}\n\n// Lambda configuration.\ntype Lambda struct {\n\t// Memory of the function.\n\tMemory int `json:\"memory\"`\n\n\t// Timeout of the function.\n\tTimeout int `json:\"timeout\"`\n\n\t// Role of the function.\n\tRole string `json:\"role\"`\n\n\t// Runtime of the function.\n\tRuntime string `json:\"runtime\"`\n\n\t// Policy of the function role.\n\tPolicy []IAMPolicyStatement `json:\"policy\"`\n\n\t// VPC configuration.\n\tVPC *VPC `json:\"vpc\"`\n}\n\n// Default implementation.\nfunc (l *Lambda) Default() error {\n\tif l.Timeout == 0 {\n\t\tl.Timeout = 60\n\t}\n\n\tif l.Memory == 0 {\n\t\tl.Memory = 512\n\t}\n\n\tif l.Runtime == \"\" {\n\t\tl.Runtime = defaultRuntime\n\t}\n\n\tl.Policy = append(l.Policy, defaultPolicy)\n\n\treturn nil\n}\n\n// Validate implementation.\nfunc (l *Lambda) Validate() error {\n\treturn nil\n}\n\n// Override config.\nfunc (l *Lambda) Override(c *Config) {\n\tif l.Memory != 0 {\n\t\tc.Lambda.Memory = l.Memory\n\t}\n\n\tif l.Timeout != 0 {\n\t\tc.Lambda.Timeout = l.Timeout\n\t}\n\n\tif l.Role != \"\" {\n\t\tc.Lambda.Role = l.Role\n\t}\n\n\tif l.VPC != nil {\n\t\tc.Lambda.VPC = l.VPC\n\t}\n\n\tif l.Runtime != \"\" {\n\t\tc.Lambda.Runtime = l.Runtime\n\t}\n}\n"
  },
  {
    "path": "config/lambda_test.go",
    "content": "package config\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tj/assert\"\n)\n\nfunc TestLambda(t *testing.T) {\n\tc := &Lambda{}\n\tassert.NoError(t, c.Default(), \"default\")\n\tassert.Equal(t, 60, c.Timeout, \"timeout\")\n\tassert.Equal(t, 512, c.Memory, \"timeout\")\n}\n\nfunc TestLambda_Policy(t *testing.T) {\n\tt.Run(\"defaults\", func(t *testing.T) {\n\t\tc := &Lambda{}\n\t\tassert.NoError(t, c.Default(), \"default\")\n\t\tassert.Len(t, c.Policy, 1)\n\t\tassert.Equal(t, defaultPolicy, c.Policy[0])\n\t})\n\n\tt.Run(\"specified\", func(t *testing.T) {\n\t\tc := &Lambda{\n\t\t\tPolicy: []IAMPolicyStatement{\n\t\t\t\t{\n\t\t\t\t\t\"Effect\":   \"Allow\",\n\t\t\t\t\t\"Resource\": \"*\",\n\t\t\t\t\t\"Action\": []string{\n\t\t\t\t\t\t\"s3:List*\",\n\t\t\t\t\t\t\"s3:Get*\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tassert.NoError(t, c.Default(), \"default\")\n\t\tassert.Len(t, c.Policy, 2)\n\t\tassert.Equal(t, defaultPolicy, c.Policy[1])\n\t})\n}\n"
  },
  {
    "path": "config/logs.go",
    "content": "package config\n\n// Logs configuration.\ntype Logs struct {\n\t// Disable json log output.\n\tDisable bool `json:\"disable\"`\n\n\t// Stdout default log level.\n\tStdout string `json:\"stdout\"`\n\n\t// Stderr default log level.\n\tStderr string `json:\"stderr\"`\n}\n\n// Default implementation.\nfunc (l *Logs) Default() error {\n\tif l.Stdout == \"\" {\n\t\tl.Stdout = \"info\"\n\t}\n\n\tif l.Stderr == \"\" {\n\t\tl.Stderr = \"error\"\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "config/relay.go",
    "content": "package config\n\nimport (\n\t\"github.com/pkg/errors\"\n)\n\n// Relay config.\ntype Relay struct {\n\t// Command run to start your server.\n\tCommand string `json:\"command\"`\n\n\t// Timeout in seconds to wait for a response.\n\tTimeout int `json:\"timeout\"`\n\n\t// ListenTimeout in seconds when waiting for the app to bind to PORT.\n\tListenTimeout int `json:\"listen_timeout\"`\n}\n\n// Default implementation.\nfunc (r *Relay) Default() error {\n\tif r.Command == \"\" {\n\t\tr.Command = \"./server\"\n\t}\n\n\tif r.Timeout == 0 {\n\t\tr.Timeout = 15\n\t}\n\n\tif r.ListenTimeout == 0 {\n\t\tr.ListenTimeout = 15\n\t}\n\n\treturn nil\n}\n\n// Validate will try to perform sanity checks for this Relay configuration.\nfunc (r *Relay) Validate() error {\n\tif r.Command == \"\" {\n\t\terr := errors.New(\"should not be empty\")\n\t\treturn errors.Wrap(err, \".command\")\n\t}\n\n\tif r.ListenTimeout <= 0 {\n\t\terr := errors.New(\"should be greater than 0\")\n\t\treturn errors.Wrap(err, \".listen_timeout\")\n\t}\n\n\tif r.ListenTimeout > 25 {\n\t\terr := errors.New(\"should be <= 25\")\n\t\treturn errors.Wrap(err, \".listen_timeout\")\n\t}\n\n\tif r.Timeout > 25 {\n\t\terr := errors.New(\"should be <= 25\")\n\t\treturn errors.Wrap(err, \".timeout\")\n\t}\n\n\treturn nil\n}\n\n// Override config.\nfunc (r *Relay) Override(c *Config) {\n\tif r.Command != \"\" {\n\t\tc.Proxy.Command = r.Command\n\t}\n}\n"
  },
  {
    "path": "config/runtimes.go",
    "content": "package config\n\nimport (\n\t\"os\"\n\n\t\"github.com/apex/up/internal/util\"\n\t\"github.com/pkg/errors\"\n)\n\n// Runtime is an app runtime.\ntype Runtime string\n\n// Runtimes available.\nconst (\n\tRuntimeUnknown    Runtime = \"unknown\"\n\tRuntimeGo                 = \"go\"\n\tRuntimeNode               = \"node\"\n\tRuntimeClojure            = \"clojure\"\n\tRuntimeCrystal            = \"crystal\"\n\tRuntimePython             = \"python\"\n\tRuntimeStatic             = \"static\"\n\tRuntimeJavaMaven          = \"java maven\"\n\tRuntimeJavaGradle         = \"java gradle\"\n)\n\n// inferRuntime returns the runtime based on files present in the CWD.\nfunc inferRuntime() Runtime {\n\tswitch {\n\tcase util.Exists(\"main.go\"):\n\t\treturn RuntimeGo\n\tcase util.Exists(\"main.cr\"):\n\t\treturn RuntimeCrystal\n\tcase util.Exists(\"package.json\"):\n\t\treturn RuntimeNode\n\tcase util.Exists(\"app.js\"):\n\t\treturn RuntimeNode\n\tcase util.Exists(\"project.clj\"):\n\t\treturn RuntimeClojure\n\tcase util.Exists(\"pom.xml\"):\n\t\treturn RuntimeJavaMaven\n\tcase util.Exists(\"build.gradle\"):\n\t\treturn RuntimeJavaGradle\n\tcase util.Exists(\"app.py\"):\n\t\treturn RuntimePython\n\tcase util.Exists(\"index.html\"):\n\t\treturn RuntimeStatic\n\tdefault:\n\t\treturn RuntimeUnknown\n\t}\n}\n\n// runtimeConfig performs config inferences based on what Up thinks the runtime is.\nfunc runtimeConfig(runtime Runtime, c *Config) error {\n\tswitch runtime {\n\tcase RuntimeGo:\n\t\tgolang(c)\n\tcase RuntimeClojure:\n\t\tclojureLein(c)\n\tcase RuntimeJavaMaven:\n\t\tjavaMaven(c)\n\tcase RuntimeJavaGradle:\n\t\tjavaGradle(c)\n\tcase RuntimeCrystal:\n\t\tcrystal(c)\n\tcase RuntimePython:\n\t\tpython(c)\n\tcase RuntimeStatic:\n\t\tc.Type = \"static\"\n\tcase RuntimeNode:\n\t\tif err := nodejs(c); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// golang config.\nfunc golang(c *Config) {\n\tif c.Hooks.Build.IsEmpty() {\n\t\tc.Hooks.Build = Hook{`GOOS=linux GOARCH=amd64 go build -o server *.go`}\n\t}\n\n\tif c.Hooks.Clean.IsEmpty() {\n\t\tc.Hooks.Clean = Hook{`rm server`}\n\t}\n\n\tif s := c.Stages.GetByName(\"development\"); s != nil {\n\t\tif s.Proxy.Command == \"\" {\n\t\t\ts.Proxy.Command = \"go run *.go\"\n\t\t}\n\t}\n}\n\n// java gradle config.\nfunc javaGradle(c *Config) {\n\tif c.Proxy.Command == \"\" {\n\t\tc.Proxy.Command = \"java -jar server.jar\"\n\t}\n\n\tif c.Hooks.Build.IsEmpty() {\n\t\t// assumes build results in a shaded jar named server.jar\n\t\tif util.Exists(\"gradlew\") {\n\t\t\tc.Hooks.Build = Hook{`./gradlew clean build && cp build/libs/server.jar .`}\n\t\t} else {\n\t\t\tc.Hooks.Build = Hook{`gradle clean build && cp build/libs/server.jar .`}\n\t\t}\n\t}\n\n\tif c.Hooks.Clean.IsEmpty() {\n\t\tc.Hooks.Clean = Hook{`rm server.jar && gradle clean`}\n\t}\n}\n\n// java maven config.\nfunc javaMaven(c *Config) {\n\tif c.Proxy.Command == \"\" {\n\t\tc.Proxy.Command = \"java -jar server.jar\"\n\t}\n\n\tif c.Hooks.Build.IsEmpty() {\n\t\t// assumes package results in a shaded jar named server.jar\n\t\tif util.Exists(\"mvnw\") {\n\t\t\tc.Hooks.Build = Hook{`./mvnw clean package && cp target/server.jar .`}\n\t\t} else {\n\t\t\tc.Hooks.Build = Hook{`mvn clean package && cp target/server.jar .`}\n\t\t}\n\t}\n\n\tif c.Hooks.Clean.IsEmpty() {\n\t\tc.Hooks.Clean = Hook{`rm server.jar && mvn clean`}\n\t}\n}\n\n// clojure lein config.\nfunc clojureLein(c *Config) {\n\tif c.Proxy.Command == \"\" {\n\t\tc.Proxy.Command = \"java -jar server.jar\"\n\t}\n\n\tif c.Hooks.Build.IsEmpty() {\n\t\t// assumes package results in a shaded jar named server.jar\n\t\tc.Hooks.Build = Hook{`lein uberjar && cp target/*-standalone.jar server.jar`}\n\t}\n\n\tif c.Hooks.Clean.IsEmpty() {\n\t\tc.Hooks.Clean = Hook{`lein clean && rm server.jar`}\n\t}\n}\n\n// crystal config.\nfunc crystal(c *Config) {\n\tif c.Hooks.Build.IsEmpty() {\n\t\tc.Hooks.Build = Hook{`docker run --rm -v $(pwd):/src -w /src crystallang/crystal crystal build -o server main.cr --release --static`}\n\t}\n\n\tif c.Hooks.Clean.IsEmpty() {\n\t\tc.Hooks.Clean = Hook{`rm server`}\n\t}\n\n\tif s := c.Stages.GetByName(\"development\"); s != nil {\n\t\tif s.Proxy.Command == \"\" {\n\t\t\ts.Proxy.Command = \"crystal run main.cr\"\n\t\t}\n\t}\n}\n\n// nodejs config.\nfunc nodejs(c *Config) error {\n\tvar pkg struct {\n\t\tScripts struct {\n\t\t\tStart string `json:\"start\"`\n\t\t\tBuild string `json:\"build\"`\n\t\t} `json:\"scripts\"`\n\t}\n\n\t// read package.json\n\tif err := util.ReadFileJSON(\"package.json\", &pkg); err != nil && !os.IsNotExist(errors.Cause(err)) {\n\t\treturn err\n\t}\n\n\t// use \"start\" script unless explicitly defined in up.json\n\tif c.Proxy.Command == \"\" {\n\t\tif s := pkg.Scripts.Start; s == \"\" {\n\t\t\tc.Proxy.Command = `node app.js`\n\t\t} else {\n\t\t\tc.Proxy.Command = s\n\t\t}\n\t}\n\n\t// use \"build\" script unless explicitly defined in up.json\n\tif c.Hooks.Build.IsEmpty() {\n\t\tc.Hooks.Build = Hook{pkg.Scripts.Build}\n\t}\n\n\treturn nil\n}\n\n// python config.\nfunc python(c *Config) {\n\tif c.Proxy.Command == \"\" {\n\t\tc.Proxy.Command = \"python app.py\"\n\t}\n\n\t// Only add build & clean hooks if a requirements.txt exists\n\tif !util.Exists(\"requirements.txt\") {\n\t\treturn\n\t}\n\n\t// Set PYTHONPATH env\n\tif c.Environment == nil {\n\t\tc.Environment = Environment{}\n\t}\n\tc.Environment[\"PYTHONPATH\"] = \".pypath/\"\n\n\t// Copy libraries into .pypath/\n\tif c.Hooks.Build.IsEmpty() {\n\t\tc.Hooks.Build = Hook{`mkdir -p .pypath/ && pip install -r requirements.txt -t .pypath/`}\n\t}\n\n\t// Clean .pypath/\n\tif c.Hooks.Clean.IsEmpty() {\n\t\tc.Hooks.Clean = Hook{`rm -r .pypath/`}\n\t}\n}\n"
  },
  {
    "path": "config/stages.go",
    "content": "package config\n\nimport (\n\t\"sort\"\n\n\t\"github.com/apex/up/internal/validate\"\n\t\"github.com/pkg/errors\"\n)\n\n// defaultStages is a list of default stage names.\nvar defaultStages = []string{\n\t\"development\",\n\t\"staging\",\n\t\"production\",\n}\n\n// Stage config.\ntype Stage struct {\n\tDomain string      `json:\"domain\"`\n\tZone   interface{} `json:\"zone\"`\n\tPath   string      `json:\"path\"`\n\tCert   string      `json:\"cert\"`\n\tName   string      `json:\"-\"`\n\tStageOverrides\n}\n\n// IsLocal returns true if the stage represents a local environment.\nfunc (s *Stage) IsLocal() bool {\n\treturn s.Name == \"development\"\n}\n\n// IsRemote returns true if the stage represents a remote environment.\nfunc (s *Stage) IsRemote() bool {\n\treturn !s.IsLocal()\n}\n\n// Validate implementation.\nfunc (s *Stage) Validate() error {\n\tif err := validate.Stage(s.Name); err != nil {\n\t\treturn errors.Wrap(err, \".name\")\n\t}\n\n\tswitch s.Zone.(type) {\n\tcase bool, string:\n\t\treturn nil\n\tdefault:\n\t\treturn errors.Errorf(\".zone is an invalid type, must be string or boolean\")\n\t}\n}\n\n// Default implementation.\nfunc (s *Stage) Default() error {\n\tif s.Zone == nil {\n\t\ts.Zone = true\n\t}\n\n\treturn nil\n}\n\n// StageOverrides config.\ntype StageOverrides struct {\n\tHooks  Hooks  `json:\"hooks\"`\n\tLambda Lambda `json:\"lambda\"`\n\tProxy  Relay  `json:\"proxy\"`\n}\n\n// Override config.\nfunc (s *StageOverrides) Override(c *Config) {\n\ts.Hooks.Override(c)\n\ts.Lambda.Override(c)\n\ts.Proxy.Override(c)\n}\n\n// Stages config.\ntype Stages map[string]*Stage\n\n// Default implementation.\nfunc (s Stages) Default() error {\n\t// defaults\n\tfor _, name := range defaultStages {\n\t\tif _, ok := s[name]; !ok {\n\t\t\ts[name] = &Stage{}\n\t\t}\n\t}\n\n\t// assign names\n\tfor name, s := range s {\n\t\ts.Name = name\n\t}\n\n\t// defaults\n\tfor _, s := range s {\n\t\tif err := s.Default(); err != nil {\n\t\t\treturn errors.Wrapf(err, \"stage %q\", s.Name)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Validate implementation.\nfunc (s Stages) Validate() error {\n\tfor _, s := range s {\n\t\tif err := s.Validate(); err != nil {\n\t\t\treturn errors.Wrapf(err, \"stage %q\", s.Name)\n\t\t}\n\t}\n\treturn nil\n}\n\n// List returns configured stages.\nfunc (s Stages) List() (v []*Stage) {\n\tfor _, s := range s {\n\t\tv = append(v, s)\n\t}\n\n\treturn\n}\n\n// Domains returns configured domains.\nfunc (s Stages) Domains() (v []string) {\n\tfor _, s := range s.List() {\n\t\tif s.Domain != \"\" {\n\t\t\tv = append(v, s.Domain)\n\t\t}\n\t}\n\n\treturn\n}\n\n// Names returns configured stage names.\nfunc (s Stages) Names() (v []string) {\n\tfor _, s := range s.List() {\n\t\tv = append(v, s.Name)\n\t}\n\n\tsort.Strings(v)\n\treturn\n}\n\n// RemoteNames returns configured remote stage names.\nfunc (s Stages) RemoteNames() (v []string) {\n\tfor _, s := range s.List() {\n\t\tif s.IsRemote() {\n\t\t\tv = append(v, s.Name)\n\t\t}\n\t}\n\n\tsort.Strings(v)\n\treturn\n}\n\n// GetByDomain returns the stage by domain or nil.\nfunc (s Stages) GetByDomain(domain string) *Stage {\n\tfor _, s := range s.List() {\n\t\tif s.Domain == domain {\n\t\t\treturn s\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// GetByName returns the stage by name or nil.\nfunc (s Stages) GetByName(name string) *Stage {\n\tfor _, s := range s.List() {\n\t\tif s.Name == name {\n\t\t\treturn s\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "config/stages_test.go",
    "content": "package config\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tj/assert\"\n)\n\nfunc TestStage_Override(t *testing.T) {\n\tc, err := ParseConfigString(`{\n\t\t\"name\": \"app\",\n\t\t\"regions\": [\"us-west-2\"],\n\t\t\"lambda\": {\n\t\t\t\"memory\": 128\n\t\t},\n\t\t\"hooks\": {\n\t\t\t\"build\": \"parcel index.html -o build\",\n\t\t\t\"clean\": \"rm -fr build\"\n\t\t},\n\t\t\"proxy\": {\n\t\t\t\"command\": \"node app.js\"\n\t\t},\n\t\t\"stages\": {\n\t\t\t\"production\": {\n\t\t\t\t\"lambda\": {\n\t\t\t\t\t\"memory\": 1024\n\t\t\t\t},\n\t\t\t\t\"hooks\": {\n\t\t\t\t\t\"build\": \"parcel index.html -o build --production\"\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"staging\": {\n\t\t\t\t\"proxy\": {\n\t\t\t\t\t\"command\": \"node app.js --foo=bar\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}`)\n\n\tassert.NoError(t, err, \"parse\")\n\tassert.NoError(t, c.Default(), \"default\")\n\tassert.NoError(t, c.Validate(), \"validate\")\n\n\tassert.NoError(t, c.Override(\"production\"), \"override\")\n\tassert.Equal(t, 1024, c.Lambda.Memory)\n\tassert.Equal(t, Hook{`parcel index.html -o build --production`}, c.Hooks.Build)\n\tassert.Equal(t, `node app.js`, c.Proxy.Command)\n\n\tassert.NoError(t, c.Override(\"staging\"), \"override\")\n\tassert.Equal(t, `node app.js --foo=bar`, c.Proxy.Command)\n}\n\nfunc TestStages_Default(t *testing.T) {\n\tt.Run(\"no custom stages\", func(t *testing.T) {\n\t\ts := Stages{}\n\n\t\tassert.NoError(t, s.Default(), \"default\")\n\t\tassert.NoError(t, s.Validate(), \"validate\")\n\n\t\tassert.Len(t, s, 3)\n\t\tassert.Equal(t, \"staging\", s[\"staging\"].Name)\n\t\tassert.Equal(t, \"production\", s[\"production\"].Name)\n\t})\n\n\tt.Run(\"custom stages\", func(t *testing.T) {\n\t\ts := Stages{\n\t\t\t\"beta\": &Stage{},\n\t\t}\n\n\t\tassert.NoError(t, s.Default(), \"default\")\n\t\tassert.NoError(t, s.Validate(), \"validate\")\n\n\t\tassert.Len(t, s, 4)\n\t\tassert.Equal(t, \"beta\", s[\"beta\"].Name)\n\t\tassert.Equal(t, true, s[\"beta\"].Zone)\n\t})\n}\n\nfunc TestStages_Validate(t *testing.T) {\n\tt.Run(\"no stages\", func(t *testing.T) {\n\t\ts := Stages{}\n\t\tassert.NoError(t, s.Validate(), \"validate\")\n\t})\n\n\tt.Run(\"some stages\", func(t *testing.T) {\n\t\ts := Stages{\n\t\t\t\"staging\": &Stage{\n\t\t\t\tDomain: \"gh-polls-stage.com\",\n\t\t\t},\n\t\t\t\"production\": &Stage{\n\t\t\t\tDomain: \"gh-polls.com\",\n\t\t\t},\n\t\t}\n\n\t\tassert.NoError(t, s.Default(), \"default\")\n\t\tassert.NoError(t, s.Validate(), \"validate\")\n\t\tassert.Equal(t, \"staging\", s[\"staging\"].Name)\n\t\tassert.Equal(t, \"production\", s[\"production\"].Name)\n\t})\n\n\tt.Run(\"valid zone boolean\", func(t *testing.T) {\n\t\ts := Stages{\n\t\t\t\"production\": &Stage{\n\t\t\t\tDomain: \"gh-polls.com\",\n\t\t\t\tZone:   false,\n\t\t\t},\n\t\t}\n\n\t\tassert.NoError(t, s.Default(), \"default\")\n\t\tassert.NoError(t, s.Validate(), \"validate\")\n\t})\n\n\tt.Run(\"valid zone string\", func(t *testing.T) {\n\t\ts := Stages{\n\t\t\t\"production\": &Stage{\n\t\t\t\tDomain: \"api.gh-polls.com\",\n\t\t\t\tZone:   \"api.gh-polls.com\",\n\t\t\t},\n\t\t}\n\n\t\tassert.NoError(t, s.Default(), \"default\")\n\t\tassert.NoError(t, s.Validate(), \"validate\")\n\t})\n\n\tt.Run(\"invalid zone type\", func(t *testing.T) {\n\t\ts := Stages{\n\t\t\t\"production\": &Stage{\n\t\t\t\tDomain: \"api.gh-polls.com\",\n\t\t\t\tZone:   123,\n\t\t\t},\n\t\t}\n\n\t\tassert.NoError(t, s.Default(), \"default\")\n\t\tassert.EqualError(t, s.Validate(), `stage \"production\": .zone is an invalid type, must be string or boolean`)\n\t})\n}\n\nfunc TestStages_List(t *testing.T) {\n\tstage := &Stage{\n\t\tDomain: \"gh-polls-stage.com\",\n\t}\n\n\tprod := &Stage{\n\t\tDomain: \"gh-polls.com\",\n\t}\n\n\ts := Stages{\n\t\t\"staging\":    stage,\n\t\t\"production\": prod,\n\t}\n\n\tlist := []*Stage{\n\t\tstage,\n\t\tprod,\n\t}\n\n\tstages := s.List()\n\tassert.Equal(t, list, stages)\n}\n\nfunc TestStages_GetByDomain(t *testing.T) {\n\tstage := &Stage{\n\t\tDomain: \"gh-polls-stage.com\",\n\t}\n\n\tprod := &Stage{\n\t\tDomain: \"gh-polls.com\",\n\t}\n\n\ts := Stages{\n\t\t\"staging\":    stage,\n\t\t\"production\": prod,\n\t}\n\n\tassert.Equal(t, prod, s.GetByDomain(\"gh-polls.com\"))\n}\n"
  },
  {
    "path": "config/static.go",
    "content": "package config\n\nimport (\n\t\"os\"\n\n\t\"github.com/pkg/errors\"\n)\n\n// Static configuration.\ntype Static struct {\n\t// Dir containing static files.\n\tDir string `json:\"dir\"`\n\n\t// Prefix is an optional URL prefix for serving static files.\n\tPrefix string `json:\"prefix\"`\n}\n\n// Validate implementation.\nfunc (s *Static) Validate() error {\n\tinfo, err := os.Stat(s.Dir)\n\n\tif os.IsNotExist(err) {\n\t\treturn nil\n\t}\n\n\tif err != nil {\n\t\treturn errors.Wrap(err, \".dir\")\n\t}\n\n\tif !info.IsDir() {\n\t\treturn errors.Errorf(\".dir %s is not a directory\", s.Dir)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "config/static_test.go",
    "content": "package config\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/tj/assert\"\n)\n\nfunc TestStatic(t *testing.T) {\n\tcwd, _ := os.Getwd()\n\n\ttable := []struct {\n\t\tStatic\n\t\tvalid bool\n\t}{\n\t\t{Static{Dir: cwd}, true},\n\t\t{Static{Dir: cwd + \"/static_test.go\"}, false},\n\t}\n\n\tfor _, row := range table {\n\t\tif row.valid {\n\t\t\tassert.NoError(t, row.Validate())\n\t\t} else {\n\t\t\tassert.Error(t, row.Validate())\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "docs/00-introduction.md",
    "content": "---\ntitle: Introduction\nslug: introduction\n---\n\nUp deploys infinitely scalable serverless apps, APIs, and static websites in seconds, so you can get back to working on what makes your product unique.\n\nUp 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.\n\nUp 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!\n"
  },
  {
    "path": "docs/01-installation.md",
    "content": "---\ntitle: Installation\nslug: setup\n---\n\nUp 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:\n\n```\n$ curl -sf https://up.apex.sh/install | sh\n```\n\nBy 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:\n\n```\n$ curl -sf https://up.apex.sh/install | BINDIR=. sh\n```\n\nVerify installation with:\n\n```\n$ up version\n```\n\nLater when you want to update `up` to the latest version use the following command:\n\n```\n$ up upgrade\n```\n\nIf you hit permission issues, you may need to run the following, as `up` is installed to `/usr/local/bin/up` by default.\n\n```\n$ sudo chown -R $(whoami) /usr/local/bin/\n```\n"
  },
  {
    "path": "docs/02-aws-credentials.md",
    "content": "---\ntitle: AWS Credentials\nslug: credentials\n---\n\nBefore using Up you need to first provide your AWS account credentials so that Up is allowed to create resources on your behalf.\n\n## AWS credential profiles\n\nMost 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).\n\nHere's an example of `~/.aws/credentials`, where `export AWS_PROFILE=myaccount` would activate these settings.\n\n```\n[myaccount]\naws_access_key_id = xxxxxxxx\naws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxx\n```\n\n### Best practices\n\nYou may store the profile name in the `up.json` file itself as shown in the following snippet:\n\n```json\n{\n  \"name\": \"appname-api\",\n  \"profile\": \"myaccount\"\n}\n```\n\nThis is ideal as it ensures you will not accidentally deploy to a different AWS account.\n\n## IAM policy for Up CLI\n\nBelow 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.\n\nIf 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.\n\n```json\n{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"acm:*\",\n                \"cloudformation:Create*\",\n                \"cloudformation:Delete*\",\n                \"cloudformation:Describe*\",\n                \"cloudformation:ExecuteChangeSet\",\n                \"cloudformation:Update*\",\n                \"cloudfront:*\",\n                \"cloudwatch:*\",\n                \"ec2:*\",\n                \"ecs:*\",\n                \"events:*\",\n                \"iam:AttachRolePolicy\",\n                \"iam:CreatePolicy\",\n                \"iam:CreateRole\",\n                \"iam:DeleteRole\",\n                \"iam:DeleteRolePolicy\",\n                \"iam:GetRole\",\n                \"iam:PassRole\",\n                \"iam:PutRolePolicy\",\n                \"lambda:AddPermission\",\n                \"lambda:Create*\",\n                \"lambda:Delete*\",\n                \"lambda:Get*\",\n                \"lambda:InvokeFunction\",\n                \"lambda:List*\",\n                \"lambda:RemovePermission\",\n                \"lambda:Update*\",\n                \"logs:Create*\",\n                \"logs:Describe*\",\n                \"logs:FilterLogEvents\",\n                \"logs:Put*\",\n                \"logs:Test*\",\n                \"route53:*\",\n                \"route53domains:*\",\n                \"s3:*\",\n                \"ssm:*\",\n                \"sns:*\"\n            ],\n            \"Resource\": \"*\"\n        },\n        {\n            \"Effect\": \"Allow\",\n            \"Action\": \"apigateway:*\",\n            \"Resource\": \"arn:aws:apigateway:*::/*\"\n        }\n    ]\n}\n```\n"
  },
  {
    "path": "docs/03-getting-started.md",
    "content": "---\ntitle: Getting Started\nslug: getting-started\n---\n\nThe simplest Up application is a single file for the application itself, with zero dependencies, and an `up.json` file which requires only a `name`.\n\nIf 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:\n\n```json\n{\n  \"name\": \"appname-api\",\n  \"profile\": \"companyname\",\n  \"regions\": [\"us-west-2\"]\n}\n```\n\nUp 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:\n\n```js\nconst http = require('http')\nconst { PORT = 3000 } = process.env\n\nhttp.createServer((req, res) => {\n  res.end('Hello World from Node.js\\n')\n}).listen(PORT)\n```\n\nDeploy it to the staging environment:\n\n```\n$ up\n```\n\nOpen up the URL in your browser:\n\n```\n$ up url --open\n```\n\nOr test with curl:\n\n```\n$ curl `up url`\n```\n\nThat's it! You've deployed a basic Up application. To view further help for commands use:\n\n```\n$ up help\n$ up help COMMAND\n$ up help COMMAND SUBCOMMAND\n```\n\nIf you're not a Node.js developer here are some examples in additional languages.\n\nFor Python create `app.py`:\n\n```python\nfrom BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer\nimport os\n\nclass myHandler(BaseHTTPRequestHandler):\n  def do_GET(self):\n      self.send_response(200)\n      self.send_header('Content-type','text/html')\n      self.end_headers()\n      self.wfile.write(\"Hello World from Python\\n\")\n      return\n\nserver = HTTPServer(('', int(os.environ['PORT'])), myHandler)\nserver.serve_forever()\n```\n\nFor Golang create `main.go`:\n\n```go\npackage main\n\nimport (\n  \"os\"\n  \"fmt\"\n  \"log\"\n  \"net/http\"\n)\n\nfunc main() {\n  addr := \":\"+os.Getenv(\"PORT\")\n  http.HandleFunc(\"/\", hello)\n  log.Fatal(http.ListenAndServe(addr, nil))\n}\n\nfunc hello(w http.ResponseWriter, r *http.Request) {\n  fmt.Fprintln(w, \"Hello World from Go\")\n}\n```\n\nFinally for Crystal create `main.cr`:\n\n```ruby\nrequire \"http/server\"\n\nport = ENV[\"PORT\"].to_i\n\nserver = HTTP::Server.new(port) do |ctx|\n  ctx.response.content_type = \"text/plain\"\n  ctx.response.print \"Hello world from Crystal\"\nend\n\nserver.listen\n```\n"
  },
  {
    "path": "docs/04-configuration.md",
    "content": "---\ntitle: Configuration\nslug: configuration\n---\n\nConfiguration for your app lives in the `up.json` within your project's directory. This section details each of the options available.\n\n## Name\n\nThe name of the application, which is used to name resources such as the Lambda function or API Gateway.\n\n```json\n{\n  \"name\": \"api\"\n}\n```\n\n## Profile\n\nThe `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.\n\n```json\n{\n  \"profile\": \"someapp\"\n}\n```\n\n## Regions\n\nYou 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.\n\nNote: 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.\n\nA single region:\n\n```json\n{\n  \"regions\": [\"us-west-2\"]\n}\n```\n\nCurrently Lambda supports the following regions:\n\n- **us-east-2** – US East (Ohio)\n- **us-east-1** – US East (N. Virginia)\n- **us-west-1** – US West (N. California)\n- **us-west-2** – US West (Oregon)\n- **ap-northeast-2** – Asia Pacific (Seoul)\n- **ap-south-1** – Asia Pacific (Mumbai)\n- **ap-southeast-1** – Asia Pacific (Singapore)\n- **ap-southeast-2** – Asia Pacific (Sydney)\n- **ap-northeast-1** – Asia Pacific (Tokyo)\n- **ca-central-1** – Canada (Central)\n- **eu-central-1** – EU (Frankfurt)\n- **eu-west-1** – EU (Ireland)\n- **eu-west-2** – EU (London)\n- **eu-west-3** – EU (Paris)\n- **eu-north-1** – EU (Stockholm)\n- **sa-east-1** – South America (São Paulo)\n\n## Lambda settings\n\nThe following Lambda-specific settings are available:\n\n- `role` – IAM role ARN, defaulting to the one Up creates for you\n- `memory` – Function memory in mb (Default `512`, Min `128`, Max `3008`)\n- `policy` – IAM function policy statement(s)\n- `runtime` — Lambda function runtime. (Default `nodejs10.x`)\n- `vpc` - VPC subnets and security groups\n\nFor example:\n\n```json\n{\n  \"name\": \"api\",\n  \"lambda\": {\n    \"memory\": 512,\n    \"runtime\": \"nodejs8.10\",\n    \"vpc\": {\n      \"subnets\": [\n        \"subnet-aaaaaaa\",\n        \"subnet-bbbbbbb\",\n        \"subnet-ccccccc\",\n      ],\n      \"security_groups\": [\n        \"sg-xxxxxxx\"\n      ]\n    }\n  }\n}\n```\n\nThe 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.\n\nUsing 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.\n\nNote: Changes to Lambda configuration do not require a `up stack apply`, just deploy and these changes are picked up!\n\n### IAM policy\n\nUp uses IAM policies to grant access to resources within your AWS account such as DynamoDB or S3.\n\nTo 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.\n\n```json\n{\n  \"name\": \"myapp\",\n  \"lambda\": {\n    \"memory\": 1024,\n    \"policy\": [\n      {\n        \"Effect\": \"Allow\",\n        \"Resource\": \"*\",\n        \"Action\": [\n          \"dynamodb:Get*\",\n          \"dynamodb:List*\",\n          \"dynamodb:PutItem\",\n          \"dynamodb:DeleteItem\"\n        ]\n      }\n    ]\n  }\n}\n```\n\nDeploy to update the IAM function role permissions.\n\n## Hook scripts\n\nUp 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:\n\n- `prebuild` – Run before building\n- `build` – Run before building. Overrides inferred build command(s)\n- `postbuild` – Run after building\n- `predeploy` – Run before deploying\n- `postdeploy` – Run after deploying\n- `clean` – Run after a deploy to clean up artifacts. Overrides inferred clean command(s)\n\nHere's an example using Browserify to bundle a Node application. Use the `-v` verbose log flag to see how long each hook takes.\n\n```json\n{\n  \"name\": \"app\",\n  \"hooks\": {\n    \"build\": \"browserify --node app.js > server.js\",\n    \"clean\": \"rm server.js\"\n  }\n}\n```\n\nUp 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.\n\nMultiple commands are provided by using arrays, and are run in separate shells:\n\n```json\n{\n  \"name\": \"app\",\n  \"hooks\": {\n    \"build\": [\n      \"mkdir -p build\",\n      \"cp -fr static build\",\n      \"browserify --node index.js > build/client.js\"\n    ],\n    \"clean\": \"rm -fr build\"\n  }\n}\n```\n\nTo 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.\n\n## Static file serving\n\nUp ships with a robust static file server, to enable it specify the app `type` as `\"static\"`.\n\n```json\n{\n  \"type\": \"static\"\n}\n```\n\nBy 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.\n\n```json\n{\n  \"name\": \"app\",\n  \"type\": \"static\",\n  \"static\": {\n    \"dir\": \"public\"\n  }\n}\n```\n\nNote 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:\n\n```\n*\n!public/**\n```\n\nNote: Files are currently served from AWS Lambda as well, so there is a 6MB restriction on the file size.\n\n### Dynamic applications\n\nIf 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.\n\n```json\n{\n  \"name\": \"app\",\n  \"static\": {\n    \"dir\": \"public\"\n  }\n}\n```\n\nBy 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:\n\n```json\n{\n  \"name\": \"app\",\n  \"static\": {\n    \"dir\": \"public\",\n    \"prefix\": \"/static/\"\n  }\n}\n```\n\nNote: 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.\n\n## Environment variables\n\nThe `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.\n\n```json\n{\n  \"name\": \"api\",\n  \"environment\": {\n    \"API_FEATURE_FOO\": \"1\",\n    \"API_FEATURE_BAR\": \"0\"\n  }\n}\n```\n\nThese become available to you via `process.env.API_FEATURES_FOO`, `os.Getenv(\"API_FEATURES_FOO\")` or similar in your language of choice.\n\nThe following environment variables are provided by Up:\n\n- `PORT` – port number such as \"3000\"\n- `UP_STAGE` – stage name such as \"staging\" or \"production\"\n\n## Header injection\n\nThe `headers` object allows you to map HTTP header fields to paths. The most specific pattern takes precedence.\n\nHere's an example of two header fields specified for `/*` and `/*.css`:\n\n```json\n{\n  \"name\": \"app\",\n  \"type\": \"static\",\n  \"headers\": {\n    \"/*\": {\n      \"X-Something\": \"I am applied to everything\"\n    },\n    \"/*.css\": {\n      \"X-Something-Else\": \"I am applied to styles\"\n    }\n  }\n}\n```\n\nRequesting `GET /` will match the first pattern, injecting `X-Something`:\n\n```\nHTTP/1.1 200 OK\nAccept-Ranges: bytes\nContent-Length: 200\nContent-Type: text/html; charset=utf-8\nLast-Modified: Fri, 21 Jul 2017 20:42:51 GMT\nX-Powered-By: up\nX-Something: I am applied to everything\nDate: Mon, 31 Jul 2017 20:49:33 GMT\n```\n\nRequesting `GET /style.css` will match the second, more specific pattern, injecting `X-Something-Else`:\n\n```json\nHTTP/1.1 200 OK\nAccept-Ranges: bytes\nContent-Length: 50\nContent-Type: text/css; charset=utf-8\nLast-Modified: Fri, 21 Jul 2017 20:42:51 GMT\nX-Powered-By: up\nX-Something-Else: I am applied to styles\nDate: Mon, 31 Jul 2017 20:49:35 GMT\n```\n\n## Error pages\n\nWhen enabled Up will serve a minimalistic error page for requests accepting `text/html`. The following settings are available:\n\n- `enable` — enable the error page feature\n- `dir` — the directory where the error pages are located\n- `variables` — vars available to the pages\n\nThe default template's `color` and optionally provide a `support_email` to allow customers to contact your support team, for example:\n\n```json\n{\n  \"name\": \"site\",\n  \"type\": \"static\",\n  \"error_pages\": {\n    \"enable\": true,\n    \"variables\": {\n      \"support_email\": \"support@apex.sh\",\n      \"color\": \"#228ae6\"\n    }\n  }\n}\n```\n\nIf you'd like to provide custom templates you may create one or more of the following files. The most specific file takes precedence.\n\n- `error.html` – Matches any 4xx or 5xx\n- `5xx.html` – Matches any 5xx error\n- `4xx.html` – Matches any 4xx error\n- `CODE.html` – Matches a specific code such as 404.html\n\nVariables specified via `variables`, as well as `.StatusText` and `.StatusCode` may be used in the template.\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <title>{{.StatusText}} - {{.StatusCode}}</title>\n    <link rel=\"stylesheet\" href=\"/css/style.css\">\n  </head>\n  <body>\n    <h1>{{.StatusText}}</h1>\n    {{with .Variables.support_email}}\n      <span class=\"message\">Please try your request again or <a href=\"mailto:{{.}}\">contact support</a>.</span>\n    {{else}}\n      <span class=\"message\">Please try your request again or contact support.</span>\n    {{end}}\n  </body>\n</html>\n```\n\n## Script injection\n\nScripts, styles, and other tags may be injected to HTML pages before the closing `</head>` tag or closing `</body>` tag.\n\nIn the following example the `<link rel=\"/style.css\">` is injected to the head, as well as the inlining the `scripts/config.js` file. A `<script src=\"/app.js\"></script>` is then injected into the body.\n\n\n```json\n{\n  \"name\": \"site\",\n  \"type\": \"static\",\n  \"inject\": {\n    \"head\": [\n      {\n        \"type\": \"style\",\n        \"value\": \"/style.css\"\n      },\n      {\n        \"type\": \"inline script\",\n        \"file\": \"scripts/config.js\"\n      }\n    ],\n    \"body\": [\n      {\n        \"type\": \"script\",\n        \"value\": \"/app.js\"\n      }\n    ]\n  }\n}\n```\n\nCurrently you may specify the following types:\n\n- `literal` – A literal string\n- `comment` – An html comment\n- `style` – A style `href`\n- `script` – A script `src`\n- `inline style` – An inline style\n- `inline script` – An inline script\n- `google analytics` – Google Analytics snippet with API key\n- `segment` – Segment snippet with API key\n\nAll of these require a `value`, which sets the `src`, `href`, or inline content. Optionally you can populate `value` via a `file` path to a local file on disk, this is typically more convenient for inline scripts or styles. For example:\n\n- `{ \"type\": \"literal\", \"value\": \"<meta name=...>\" }`\n- `{ \"type\": \"comment\", \"value\": \"Just a boring comment\" }`\n- `{ \"type\": \"script\", \"value\": \"/feedback.js\" }`\n- `{ \"type\": \"style\", \"value\": \"/feedback.css\" }`\n- `{ \"type\": \"inline script\", \"file\": \"/feedback.js\" }`\n- `{ \"type\": \"inline style\", \"file\": \"/feedback.css\" }`\n- `{ \"type\": \"script\", \"value\": \"var config = {};\" }`\n- `{ \"type\": \"google analytics\", \"value\": \"API_KEY\" }`\n- `{ \"type\": \"segment\", \"value\": \"API_KEY\" }`\n\n## Redirects and rewrites\n\nUp supports redirects and URL rewriting via the `redirects` object, which maps path patterns to a new location. If `status` is omitted (or 200) then it is a rewrite, otherwise it is a redirect.\n\n```json\n{\n  \"name\": \"app\",\n  \"type\": \"static\",\n  \"redirects\": {\n    \"/blog\": {\n      \"location\": \"https://blog.apex.sh/\",\n      \"status\": 301\n    },\n    \"/docs/:section/guides/:guide\": {\n      \"location\": \"/help/:section/:guide\",\n      \"status\": 302\n    },\n    \"/store/*\": {\n      \"location\": \"/shop/:splat\"\n    }\n  }\n}\n```\n\nIn the previous example `/blog` will redirect to a different site, while `/docs/ping/guides/alerting` will redirect to `/help/ping/alerting`. Finally `/store/ferrets` and nested paths such as `/store/ferrets/tobi` will redirect to `/shop/ferrets/tobi` and so on.\n\nA common use-case for rewrites is for SPAs or Single Page Apps, where you want to serve the `index.html` file regardless of the path. The other common requirement for SPAs is that you of course can serve scripts and styles, so by default if a file is found, it will not be rewritten to `location`.\n\n```json\n{\n  \"name\": \"app\",\n  \"type\": \"static\",\n  \"redirects\": {\n    \"/*\": {\n      \"location\": \"/\",\n      \"status\": 200\n    }\n  }\n}\n```\n\nIf you wish to force the rewrite regardless of a file existing, set `force` to `true` as shown here:\n\n```json\n{\n  \"name\": \"app\",\n  \"type\": \"static\",\n  \"redirects\": {\n    \"/*\": {\n      \"location\": \"/\",\n      \"status\": 200,\n      \"force\": true\n    }\n  }\n}\n```\n\nMore specific target paths take precedence over those which are less specific, for example `/blog` will win over and `/*`.\n\n## Cross-Origin Resource Sharing\n\nCORS is a mechanism which allows requests originating from a different host to make requests to your API. Several options are available to restrict this access, if the defaults are appropriate simply enable it as shown below.\n\n```json\n{\n  \"cors\": {\n    \"enable\": true\n  }\n}\n```\n\nSuppose you have `https://api.myapp.com`, you may want to customize `cors` to permit access only from `https://myapp.com` so that other sites cannot call your API directly.\n\n```json\n{\n  \"cors\": {\n    \"allowed_origins\": [\"https://myapp.com\"],\n    \"allowed_methods\": [\"HEAD\", \"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"],\n    \"allowed_headers\": [\"*\"],\n    \"allow_credentials\": true\n  }\n}\n```\n\n- `allowed_origins` – A list of origins a cross-domain request can be executed from. Use `*` to allow any origin, or a wildcard such as `http://*.domain.com` (Default: `[\"*\"]`)\n- `allowed_methods` – A list of methods the client is allowed to use with cross-domain requests. (Default: `[\"HEAD\", \"GET\", \"POST\"]`)\n- `allowed_headers` – A list of 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: `[\"Origin\", \"Accept\", \"Content-Type\", \"X-Requested-With\"]`)\n- `exposed_headers` – A list of headers which are safe to expose to the API of a CORS response.\n- `max_age` – A number indicating how long (in seconds) the results of a preflight request can be cached.\n- `allow_credentials` – A boolean indicating whether the request can include user credentials such as cookies, HTTP authentication or client side SSL certificates. (Default: `true`)\n- `debug` - A boolean which will output debug logs (Default: `false`)\n\nHere's an example performing a GraphQL query with `fetch()`, note that `Accept` is set to accept only JSON:\n\n```js\nconst body = JSON.stringify({\n  query: `query {\n    pet(id: 2) {\n      name\n    }\n  }`\n})\n\nconst res = await fetch('https://myapp.com', {\n  headers: { 'Content-Type': 'application/json', Accept: 'application/json' },\n  method: 'POST',\n  body\n})\n```\n\nNote: You do not need to run `up stack plan` for CORS settings, simply redeploy the stage.\n\n## Reverse proxy\n\nUp acts as a reverse proxy in front of your server, this is how CORS, redirection, script injection and other middleware style features are provided.\n\nThe following settings are available:\n\n- `command` – Command run through the shell to start your server (Default `./server`)\n  - When `package.json` is detected `npm start` is used\n  - When `app.js` is detected `node app.js` is used\n- `timeout` – Timeout in seconds per request (Default `15`, Max `25`)\n- `listen_timeout` – Timeout in seconds Up will wait for your app to boot and listen on `PORT` (Default `15`, Max `25`)\n\n```json\n{\n  \"proxy\": {\n    \"command\": \"node app.js\",\n    \"timeout\": 10,\n    \"listen_timeout\": 5\n  }\n}\n```\n\nLambda's function timeout is implied from the `.proxy.timeout` setting.\n\n### Crash recovery\n\nAnother benefit of using Up as a reverse proxy is performing crash recovery. Up will attempt to restart your application if the process crashes to continue serving subsequent requests.\n\n## DNS zones & records\n\nUp allows you to configure DNS zones and records. One or more zones may be provided as keys in the `dns` object (\"myapp.com\" here), with a number of records defined within it.\n\n```json\n{\n  \"name\": \"gh-polls\",\n  \"dns\": {\n    \"gh-polls.com\": [\n      {\n        \"name\": \"app.gh-polls.com\",\n        \"type\": \"CNAME\",\n        \"value\": [\"gh-polls.netlify.com\"]\n      }\n    ]\n  }\n}\n```\n\nThe record `type` must be one of:\n\n- A\n- AAAA\n- CNAME\n- MX\n- NAPTR\n- NS\n- PTR\n- SOA\n- SPF\n- SRV\n- TXT\n\n## Stages\n\nUp supports the concept of \"stages\" for configuration, such as mapping of custom domains, or tuneing the size of Lambda function to use.\n\nBy default the following stages are defined:\n\n- `development` — local development environment\n- `staging` — remote environment for staging new features or releases\n- `production` — remote environment for production\n\nTo create a new stage, first add it to your configuration, in this case we'll call it \"beta\":\n\n```json\n{\n  \"name\": \"app\",\n  \"lambda\": {\n    \"memory\": 128\n  },\n  \"stages\": {\n    \"beta\": {\n\n    }\n  }\n}\n```\n\nNow you'll need to plan your stack changes, which will set up a new API Gateway and permissions:\n\n\n```\n$ up stack plan\n\nAdd api deployment\n  id: ApiDeploymentBeta\n\nAdd lambda permission\n  id: ApiLambdaPermissionBeta\n```\n\nApply those changes:\n\n```\n$ up stack apply\n```\n\nNow you can deploy to your new stage by passing the name `beta` and open the end-point in the browser:\n\n```\n$ up beta\n$ up url -o beta\n```\n\nTo delete a stage, simply remove it from the `up.json` configuration and run `up stack plan` again, and `up stack apply` after reviewing the changes.\n\nYou may of course assign a custom domain to these stages as well, let's take a look at that next!\n\n## Stages & custom domains\n\nBy defining a stage and its `domain`, Up knows it will need to create a free SSL certificate—`gh-polls.com` in the following example—setup the DNS records, and map the domain to API Gateway. SSL certificates are managed via [AWS ACM](https://aws.amazon.com/certificate-manager/) which automatically renew for you, there's no additional work or cost associated with them.\n\n```json\n{\n  \"stages\": {\n    \"production\": {\n      \"domain\": \"gh-polls.com\"\n    }\n  }\n}\n```\n\n Here's another example mapping each stage to a domain, note that the domains do not need to be related, you could use `stage-gh-polls.com` for example.\n\n\n```json\n{\n  \"stages\": {\n    \"production\": {\n      \"domain\": \"gh-polls.com\"\n    },\n    \"staging\": {\n      \"domain\": \"stage.gh-polls.com\"\n    }\n  }\n}\n```\n\nYou may also provide an optional base path, for example to prefix your API with `/v1`. Note that currently your application will still receive \"/v1\" in its request path, for example Node's `req.url` will be  \"/v1/users\" instead of \"/users\".\n\n```json\n{\n  \"stages\": {\n    \"production\": {\n      \"domain\": \"api.gh-polls.com\",\n      \"path\": \"/v1\"\n    }\n  }\n}\n```\n\nPlan the changes via `up stack plan` and `up stack apply` to perform the changes. You may [purchase domains](#guides.development_to_production_workflow.purchasing_a_domain) from the command-line, or map custom domains from other registrars. Up uses Route53 to purchase domains using your AWS account credit card. See `up help domains`.\n\nNote: CloudFront can take up to ~40 minutes to distribute this configuration the first time, so grab a coffee while these changes are applied. Also note that ACM certificates are always created in the Virginia (us-east-1) region due to how API Gateway interoperates with CloudFront.\n\n### DNS zones\n\nBy default when you specify a stage `domain` — such as \"api.example.com\" — a DNS zone is created in Route53 for the top level domain \"example.com\", and an ALIAS record \"api.example.com\" is added to this zone.\n\nIf you're using external DNS and wish to omit the zone entirely you can disable it with the `zone` property:\n\n```json\n{\n  \"stages\": {\n    \"production\": {\n      \"domain\": \"gh-polls.com\",\n      \"zone\": false\n    }\n  }\n}\n```\n\nYou may also explicitly specify the zone by providing a string. In the following example an \"api.gh-polls.com\" zone will be created, instead of putting the record in \"gh-polls.com\".\n\n```json\n{\n  \"stages\": {\n    \"production\": {\n      \"domain\": \"api.gh-polls.com\",\n      \"zone\": \"api.gh-polls.com\"\n    }\n  }\n}\n```\n\n## Stage overrides\n\nUp allows some configuration properties to be overridden at the stage level. The following example illustrates how you can tune lambda memory and hooks per-stage.\n\n```json\n{\n  \"name\": \"app\",\n  \"hooks\": {\n    \"build\": \"parcel index.html --no-minify -o build\",\n    \"clean\": \"rm -fr build\"\n  },\n  \"stages\": {\n    \"production\": {\n      \"hooks\": {\n        \"build\": \"parcel index.html -o build\"\n      },\n      \"lambda\": {\n        \"memory\": 1024\n      }\n    }\n  }\n}\n```\n\nCurrently the following properties may be specified at the stage level:\n\n- `hooks`\n- `lambda`\n- `proxy.command`\n\nFor example you may want to override `proxy.command` for development, which is the env `up start` uses. In the following example [gin](https://github.com/codegangsta/gin) is used for hot reloading of Go programs:\n\n```json\n{\n  \"name\": \"app\",\n  \"stages\": {\n    \"development\": {\n      \"proxy\": {\n        \"command\": \"gin --port $PORT\"\n      }\n    }\n  }\n}\n```\n\n## Logs\n\nBy default Up treats stdout as `info` level logs, and stderr as `error` level. If your logger uses stderr, such as Node's `debug()` module and you'd like to change this behaviour you may override these levels:\n\n```json\n{\n  \"name\": \"app\",\n  \"environment\": {\n    \"DEBUG\": \"myapp\"\n  },\n  \"logs\": {\n    \"stdout\": \"info\",\n    \"stderr\": \"info\"\n  }\n}\n```\n\nYou can disable Up's logs entirely by using the \"disable\" option:\n\n```json\n{\n  \"logs\": {\n    \"disable\": true\n  }\n}\n```\n\n## Ignoring files \n\nUp supports gitignore style pattern matching for omitting files from deployment via the `.upignore` file.\n\nAn example `.upignore` to omit markdown and `.go` source files might look like this:\n\n```\n*.md\n*.go\n```\n\n### Negation\n\nBy default dotfiles are ignored, if you wish to include them, you may use `!` to negate a pattern in `.upignore`:\n\n```\n!.myfile\n```\n\nAnother use-case for negation is to ignore everything and explicitly include a number of files instead, to be more specific:\n\n```\n*\n!app.js\n!package.json\n!node_modules/**\n!src/**\n```\n\n### Inspecting\n\nTo get a better idea of which files are being filtered or added, use `up -v` when deploying, and you may also find it useful to `grep` in some cases:\n\n```\n$ up -v 2>&1 | grep filtered\nDEBU filtered .babelrc – 25\nDEBU filtered .git – 408\nDEBU filtered .gitignore – 13\nDEBU filtered node_modules/ansi-regex/readme.md – 1749\nDEBU filtered node_modules/ansi-styles/readme.md – 1448\nDEBU filtered node_modules/binary-extensions/readme.md – 751\nDEBU filtered node_modules/chalk/readme.md – 6136\n```\n\nYou may also wish to use `up build --size` to view the largest files within the zip.\n\n### Pattern matching\n\nNote that patterns are matched much like `.gitignore`, so if you have the following `.upignore` contents even `node_modules/debug/src/index.js` will be ignored since it contains `src`.\n\n```\nsrc\n```\n\nYou can be more specific with a leading `./`:\n\n```\n./src\n```\n\nFiles can be matched recursively using `**`, for example ignoring everything except the files in `dist`:\n\n```\n*\n!dist/**\n```\n"
  },
  {
    "path": "docs/05-runtimes.md",
    "content": "---\ntitle: Runtimes\nslug: runtimes\n---\n\nUp supports a number of interpreted languages, and virtually any language which can be compiled to a binary such as Golang. Up does its best to provide idiomatic and useful out-of-the-box experiences tailored to each language. Currently first-class support is provided for:\n\n- Golang\n- Node.js\n- Crystal\n- Static sites\n\n## Node.js\n\nWhen a `package.json` file is detected, Node.js is the assumed runtime. By default `nodejs10.x` is used, see [Lambda Settings](https://apex.sh/docs/up/configuration/#lambda_settings) for details.\n\nThe `build` hook becomes:\n\n```\n$ npm run build\n```\n\nThe server run by the proxy becomes:\n\n```\n$ npm start\n```\n\n## Golang\n\nWhen a `main.go` file is detected, Golang is the assumed runtime.\n\nThe `build` hook becomes:\n\n```\n$ GOOS=linux GOARCH=amd64 go build -o server *.go\n```\n\nThe `clean` hook becomes:\n\n```\n$ rm server\n```\n\n## Crystal\n\nWhen a `main.cr` file is detected, Crystal is the assumed runtime. Note that this runtime requires Docker to be installed.\n\nThe `build` hook becomes:\n\n```\n$ docker run --rm -v $(pwd):/src -w /src crystallang/crystal crystal build -o server main.cr --release --static\n```\n\nThe `clean` hook becomes:\n\n```\n$ rm server\n```\n\n## Static\n\nWhen an `index.html` file is detected the project is assumed to be static.\n"
  },
  {
    "path": "docs/06-commands.md",
    "content": "---\ntitle: Commands\nslug: commands\n---\n\n\nUp provides the `up` command-line program, used to deploy the app, and manage associated resources such as domains and SSL certificates, as well as operational tasks like viewing logs.\n\nTo view details for a command at any time use `up help`, `up help <command>`, for example `up help team members add`.\n\n```\nUsage:\n\n  up [<flags>] <command> [<args> ...]\n\nFlags:\n\n  -h, --help           Output usage information.\n  -C, --chdir=\".\"      Change working directory.\n  -v, --verbose        Enable verbose log output.\n      --format=\"text\"  Output formatter.\n      --version        Show application version.\n\nCommands:\n\n  help                 Show help for a command.\n  build                Build zip file.\n  config               Show configuration after defaults and validation.\n  deploy               Deploy the project.\n  docs                 Open documentation website in the browser.\n  domains ls           List purchased domains.\n  domains check        Check availability of a domain.\n  domains buy          Purchase a domain.\n  env ls               List variables.\n  env add              Add a variable.\n  env rm               Remove a variable.\n  logs                 Show log output.\n  metrics              Show project metrics.\n  rollback             Rollback to a previous deployment.\n  prune                Prune old S3 deployments of a stage.\n  run                  Run a hook.\n  stack plan           Plan configuration changes.\n  stack apply          Apply configuration changes.\n  stack delete         Delete configured resources.\n  stack status         Show status of resources.\n  start                Start development server.\n  team status          Status of your account.\n  team switch          Switch active team.\n  team login           Sign in to your account.\n  team logout          Sign out of your account.\n  team members add     Add invites a team member.\n  team members rm      Remove a member or invite.\n  team members ls      List team members and invites.\n  team subscribe       Subscribe to the Pro plan.\n  team unsubscribe     Unsubscribe from the Pro plan.\n  team card change     Change the default card.\n  team ci              Credentials for CI.\n  team add             Add a new team.\n  upgrade              Install the latest or specified version of Up.\n  url                  Show, open, or copy a stage endpoint.\n  version              Show version.\n\nExamples:\n\n  Deploy the project to the staging environment.\n  $ up\n\n  Deploy the project to the production stage.\n  $ up deploy production\n\n  Show the staging endpoint url.\n  $ up url\n\n  Tail project logs.\n  $ up logs -f\n\n  Show error or fatal level logs.\n  $ up logs 'error or fatal'\n\n  Run build command manually.\n  $ up run build\n\n  Show help and examples for a command.\n  $ up help team\n\n  Show help and examples for a sub-command.\n  $ up help team members\n```\n\n## Deploy\n\nDeploy the project, by default to the \"staging\" stage. Note that running `up` and `up deploy` are identical, as it is the default command.\n\n```\nUsage:\n\n  up deploy [<stage>]\n\nFlags:\n\n  -h, --help           Output usage information.\n  -C, --chdir=\".\"      Change working directory.\n  -v, --verbose        Enable verbose log output.\n      --format=\"text\"  Output formatter.\n      --version        Show application version.\n\nArgs:\n\n  [<stage>]  Target stage name.\n```\n\n### Examples\n\nDeploy the project to the staging stage.\n\n```\n$ up\n```\n\nDeploy the project to the staging stage, this is the same as running `up` without arguments.\n\n```\n$ up deploy\n```\n\nDeploy the project to the production stage.\n\n```\n$ up deploy production\n```\n\nNote that since `deploy` is the default command the following is also valid:\n\n```\n$ up production\n```\n\n## Config\n\nValidate and output configuration with defaults applied.\n\n```\n$ up config\n```\n\n```json\n{\n  \"name\": \"app\",\n  \"description\": \"\",\n  \"type\": \"server\",\n  \"headers\": null,\n  \"redirects\": null,\n  \"hooks\": {\n    \"build\": \"GOOS=linux GOARCH=amd64 go build -o server *.go\",\n    \"clean\": \"rm server\"\n  },\n  \"environment\": null,\n  \"regions\": [\n    \"us-west-2\"\n  ],\n  \"inject\": null,\n  \"lambda\": {\n    \"role\": \"arn:aws:iam::ACCOUNT:role/lambda_function\",\n    \"memory\": 128,\n    \"timeout\": 5\n  },\n  \"cors\": null,\n  \"error_pages\": {\n    \"dir\": \".\",\n    \"variables\": null\n  },\n  \"proxy\": {\n    \"command\": \"./server\",\n    \"backoff\": {\n      \"min\": 100,\n      \"max\": 500,\n      \"factor\": 2,\n      \"attempts\": 3,\n      \"jitter\": false\n    }\n  },\n  \"static\": {\n    \"dir\": \".\"\n  },\n  \"logs\": {\n    \"disable\": false\n  },\n  \"dns\": {\n    \"zones\": null\n  }\n}\n...\n```\n\n## Logs\n\nShow or tail log output with optional query for filtering. When viewing or tailing logs, you are viewing them from _all_ stages, see the examples below to filter on a stage name.\n\n```\nUsage:\n\n  up logs [<flags>] [<query>]\n\nFlags:\n\n  -h, --help           Output usage information.\n  -C, --chdir=\".\"      Change working directory.\n  -v, --verbose        Enable verbose log output.\n      --format=\"text\"  Output formatter.\n      --version        Show application version.\n  -f, --follow         Follow or tail the live logs.\n  -S, --since=\"1d\"     Show logs since duration (30s, 5m, 2h, 1h30m, 3d, 1M).\n  -e, --expand         Show expanded logs.\n\nArgs:\n\n  [<query>]  Query pattern for filtering logs.\n```\n\n### Expanded output\n\nUse the `-e` or `--expand` flag to expand log fields:\n\n```\n$ up -e 'path = \"/static/*\"'\n\n1:36:34pm INFO request\n           id: 8ff53267-c33a-11e7-9685-15d48d102ae9\n           ip: 70.66.179.182\n       method: GET\n         path: /static/3.jpg\n        stage: production\n      version: 5\n\n1:36:34pm INFO response\n     duration: 1ms\n           id: 8ff53267-c33a-11e7-9685-15d48d102ae9\n           ip: 70.66.179.182\n       method: GET\n         path: /static/3.jpg\n         size: 0 B\n        stage: production\n       status: 304\n      version: 5\n\n1:36:34pm INFO request\n           id: 8ff4bd57-c33a-11e7-bf4b-4f0d97c427c5\n           ip: 70.66.179.182\n       method: GET\n         path: /static/1.png\n        stage: production\n      version: 5\n```\n\n### JSON output\n\nWhen stdout is not a terminal Up will output the logs as JSON, which can be useful for further processing with tools such as [jq](https://stedolan.github.io/jq/).\n\nIn this contrived example the last 5 hours of production errors are piped to `jq` to produce a CSV of HTTP methods to IP address.\n\n```\n$ up logs -s 5h 'production error' | jq -r '.|[.fields.method,.fields.ip]|@csv'\n```\n\nYielding:\n\n```\n\"GET\",\"207.194.34.24\"\n\"GET\",\"207.194.34.24\"\n\"GET\",\"207.194.34.24\"\n```\n\n### Examples\n\nShow logs from the past day.\n\n```\n$ up logs\n```\n\nShow logs from the past 45 minutes.\n\n```\n$ up -S 45m logs\n```\n\nShow logs from the past 12 hours.\n\n```\n$ up -S 12h logs\n```\n\nShow live log output.\n\n```\n$ up logs -f\n```\n\nShow live logs from production only.\n\n```\n$ up logs -f production\n```\n\nShow live error logs from production only.\n\n```\n$ up logs -f 'production error'\n```\n\nShow error logs, which include 5xx responses.\n\n```\n$ up logs error\n```\n\nShow error and warning logs, which include 4xx and 5xx responses.\n\n```\n$ up logs 'warn or error'\n```\n\nShow logs with a specific message.\n\n```\n$ up logs 'message = \"user login\" method = \"GET\"'\n```\n\nShow logs with a specific message with implicit `=`:\n\n```\n$ up logs '\"user login\" method = \"GET\"'\n```\n\nShow responses with latency above 15ms.\n\n```\n$ up logs 'duration > 15'\n```\n\nShow 4xx and 5xx responses in production\n\n```\n$ up logs 'production (warn or error)'\n```\n\nShow production 5xx responses with a POST, PUT, or DELETE method.\n\n```\n$ up logs 'production error method in (\"POST\", \"PUT\", \"DELETE\")\n```\n\nShow 200 responses with latency above 1500ms.\n\n```\n$ up logs 'status = 200 duration > 1.5s'\n```\n\nShow responses with bodies larger than 100kb.\n\n```\n$ up logs 'size > 100kb'\n```\n\nShow 4xx and 5xx responses.\n\n```\n$ up logs 'status >= 400'\n```\n\nShow emails containing @apex.sh.\n\n```\n$ up logs 'user.email contains \"@apex.sh\"'\n```\n\nShow emails ending with @apex.sh.\n\n```\n$ up logs 'user.email = \"*@apex.sh\"'\n```\n\nShow emails starting with tj@.\n\n```\n$ up logs 'user.email = \"tj@*\"'\n```\n\nShow logs with a more complex query.\n\n```\n$ up logs 'method in (\"POST\", \"PUT\") ip = \"207.*\" status = 200 duration >= 50'\n```\n\n## URL\n\nShow, open, or copy a stage endpoint.\n\n```\nUsage:\n\n  up url [<flags>]\n\nFlags:\n\n  -h, --help             Output usage information.\n  -C, --chdir=\".\"        Change working directory.\n  -v, --verbose          Enable verbose log output.\n      --format=\"text\"    Output formatter.\n      --version          Show application version.\n  -s, --stage=\"staging\"  Target stage name.\n  -o, --open             Open endpoint in the browser.\n  -c, --copy             Copy endpoint to the clipboard.\n```\n\n### Examples\n\nShow the staging endpoint.\n\n```\n$ up url\n```\n\nOpen the staging endpoint in the browser.\n\n```\n$ up url --open\n```\n\nCopy the staging endpoint to the clipboard.\n\n```\n$ up url --copy\n```\n\nShow the production endpoint.\n\n```\n$ up url -s production\n```\n\nOpen the production endpoint in the browser.\n\n```\n$ up url -o -s production\n```\n\nCopy the production endpoint to the clipboard.\n\n```\n$ up url -c -s production\n```\n\n## Metrics\n\nShow project metrics and estimated cost breakdown for requests, invocation count and the time spent for Lambda invocations.\n\n```\nUsage:\n\n  up metrics [<flags>]\n\nFlags:\n\n  -h, --help             Output usage information.\n  -C, --chdir=\".\"        Change working directory.\n  -v, --verbose          Enable verbose log output.\n      --format=\"text\"    Output formatter.\n      --version          Show application version.\n  -s, --stage=\"staging\"  Target stage name.\n  -S, --since=\"1M\"       Show metrics since duration (30s, 5m, 2h, 1h30m, 3d, 1M).\n```\n\nFor example:\n\n```\n$ up metrics -s production -S 15d\n\n  Requests: 13,653 ($0.01)\n  Duration min: 0ms\n  Duration avg: 48ms\n  Duration max: 15329ms\n  Duration sum: 3m6.611s ($0.00)\n  Errors 4xx: 1,203\n  Errors 5xx: 2\n  Invocations: 12,787 ($0.00)\n  Errors: 0\n  Throttles: 0\n```\n\n## Start\n\nStart development server. The development server runs the same proxy that is used in production for serving, so you can test a static site or application locally with the same feature-set.\n\nSee [Stage Overrides](https://up.docs.apex.sh/#configuration.stage_overrides) for an example of overriding the proxy command per-stage, especially useful in development.\n\nUp Pro supports environment variables, and these will be loaded with `up start`, and variables mapped to the \"development\" stage will take precedence. For example:\n\n```\n$ up env set NAME Tobi\n$ up start # app has NAME available as Tobi\n\n$ up env set NAME Loki -s development\n$ up start # app has NAME available Loki\n```\n\nThe `UP_STAGE` and `NODE_ENV` environment variables will be set to \"development\" automatically.\n\n```\nUsage:\n\n  up start [<flags>]\n\nFlags:\n\n  -h, --help             Output usage information.\n  -C, --chdir=\".\"        Change working directory.\n  -v, --verbose          Enable verbose log output.\n      --format=\"text\"    Output formatter.\n      --version          Show application version.\n  -c, --command=COMMAND  Proxy command override\n  -o, --open             Open endpoint in the browser.\n      --address=\":3000\"  Address for server.\n```\n\n### Examples\n\nStart development server on port 3000.\n\n```\n$ up start\n```\n\nStart development server on port 5000.\n\n```\n$ up start --address :5000\n```\n\nOverride proxy command. Note that the server created must listen on `PORT`, which is why `--port $PORT` is required for the [gin](https://github.com/codegangsta/gin) example.\n\n```\n$ up start -c 'go run main.go'\n$ up start -c 'gin --port $PORT'\n$ up start -c 'node --some-flag app.js'\n$ up start -c 'parcel'\n```\n\n## Domains\n\nManage domain names, and purchase them from AWS Route53 as the registrar.\n\n```\nUsage:\n\n  up domains <command> [<args> ...]\n\nFlags:\n\n  -h, --help           Output usage information.\n  -C, --chdir=\".\"      Change working directory.\n  -v, --verbose        Enable verbose log output.\n      --version        Show application version.\n\nSubcommands:\n\n  domains list    List purchased domains.\n  domains check   Check availability of a domain.\n  domains buy     Purchase a domain.\n\n```\n\n### Examples\n\nList purchased domains.\n\n```\n$ up domains\n```\n\nCheck availability of a domain.\n\n```\n$ up domains check example.com\n```\n\nPurchase a domain (with interactive form).\n\n```\n$ up domains buy\n```\n\n## Stack\n\nStack resource management. The stack is essentially all of the resources powering your app, which is configured by Up on the first deploy.\n\nAt any time if you'd like to delete the application simply run `$ up stack delete`. To view the status and potential errors use `$ up stack`.\n\n```\nUsage:\n\n  up stack <command> [<args> ...]\n\nFlags:\n\n  -h, --help           Output usage information.\n  -C, --chdir=\".\"      Change working directory.\n  -v, --verbose        Enable verbose log output.\n      --version        Show application version.\n\nSubcommands:\n\n  stack plan      Plan configuration changes.\n  stack apply     Apply configuration changes.\n  stack delete    Delete configured resources.\n  stack status    Show status of resources.\n```\n\n### Examples\n\nShow status of the stack resources and nameservers.\n\n```\n$ up stack\n```\n\nShow resource changes.\n\n```\n$ up stack plan\n```\n\nApply resource changes.\n\n```\n$ up stack apply\n```\n\nDelete the stack resources.\n\n```\n$ up stack delete\n```\n\n\n## Build\n\nBuild zip file, typically only helpful for inspecting its contents. If you're interested in seeing what files are causing bloat, use the `--size` flag to list files by size descending.\n\n```\nUsage:\n\n  up build [<flags>]\n\nFlags:\n\n  -h, --help             Output usage information.\n  -C, --chdir=\".\"        Change working directory.\n  -v, --verbose          Enable verbose log output.\n      --format=\"text\"    Output formatter.\n      --version          Show application version.\n  -s, --stage=\"staging\"  Target stage name.\n      --size             Show zip contents size information.\n```\n\n### Examples\n\nBuild archive and save to ./out.zip\n\n```\n$ up build\n```\n\nBuild archive and output to file via stdout.\n\n```\n$ up build > /tmp/out.zip\n```\n\nBuild archive list files by size.\n\n```\n$ up build --size\n```\n\nBuild archive and list size without creating out.zip.\n\n```\n$ up build --size > /dev/null\n```\n\n## Team\n\nManage team members, plans, and billing.\n\n```\nUsage:\n\n  up team <command> [<args> ...]\n\nFlags:\n\n  -h, --help           Output usage information.\n  -C, --chdir=\".\"      Change working directory.\n  -v, --verbose        Enable verbose log output.\n      --format=\"text\"  Output formatter.\n      --version        Show application version.\n\nSubcommands:\n\n  team status          Status of your account.\n  team switch          Switch active team.\n  team login           Sign in to your account.\n  team logout          Sign out of your account.\n  team members add     Add invites a team member.\n  team members rm      Remove a member or invite.\n  team members ls      List team members and invites.\n  team subscribe       Subscribe to the Pro plan.\n  team unsubscribe     Unsubscribe from the Pro plan.\n  team card change     Change the default card.\n  team ci              Credentials for CI.\n  team add             Add a new team.\n```\n\n### Examples\n\nShow active team and subscription status.\n\n```\n$ up team\n```\n\nSwitch teams interactively.\n\n```\n$ up team switch\n```\n\nSign in or create account with interactive prompt.\n\n```\n$ up team login\n```\n\nSign in to a team.\n\n```\n$ up team login --email tj@example.com --team apex-software\n```\n\nAdd a new team and automatically switch to the team.\n\n```\n$ up team add \"Apex Software\"\n```\n\nSubscribe to the Pro plan.\n\n```\n$ up team subscribe\n```\n\nInvite a team member to your active team.\n\n```\n$ up team members add asya@example.com\n```\n\n## Upgrade\n\nInstall the latest or specified version of Up. The OSS and Pro versions have independent semver, as bugfixes and features for one may not be relevant to the other.\n\nIf you're an Up Pro subscriber, `up upgrade` will _always_ install Up Pro, even when `--target` is specified, there is no need to specify that you want the Pro version.\n\n```\nUsage:\n\n  up upgrade [<flags>]\n\nFlags:\n\n  -h, --help           Output usage information.\n  -C, --chdir=\".\"      Change working directory.\n  -v, --verbose        Enable verbose log output.\n      --format=\"text\"  Output formatter.\n      --version        Show application version.\n  -t, --target=TARGET  Target version for upgrade.\n```\n\n### Examples\n\nUpgrade to the latest version available.\n\n```\n$ up upgrade\n```\n\nUpgrade to the specified version.\n\n```\n$ up upgrade -t 0.4.4\n```\n\n\n## Prune\n\nPrune old S3 deployments of a stage.\n\n```\nUsage:\n\n  up prune [<flags>]\n\nFlags:\n\n  -h, --help             Output usage information.\n  -C, --chdir=\".\"        Change working directory.\n  -v, --verbose          Enable verbose log output.\n      --format=\"text\"    Output formatter.\n      --version          Show application version.\n  -s, --stage=\"staging\"  Target stage name.\n  -r, --retain=30        Number of versions to retain.\n```\n\n### Examples\n\nPrune and retain the most recent 30 staging versions.\n\n```\n$ up prune\n```\n\nPrune and retain the most recent 30 production versions.\n\n```\n$ up prune -s production\n```\n\nPrune and retain the most recent 15 production versions.\n\n```\n$ up prune -s production -r 15\n```\n"
  },
  {
    "path": "docs/07-guides.md",
    "content": "---\ntitle: Guides\nslug: guides\n---\n\n## Subscribing to Up Pro\n\nUp Pro provides additional features which are not available in the open-source version, such as encrypted environment variables, alerting support and more.\n\nFirst sign into the platform with the following command – you'll receive an email for confirmation.\n\n```\n$ up team login\n\n     email: tj@apex.sh\n  ⠋ verify: Check your email for a confirmation link\n\n```\n\nClick the link in your email and you're signed in! If you're using Up Pro with one or more organizations, you should create a team to manage team members and subscriptions independently. If you plan on using Up Pro for personal use you may skip this step.\n\n```\n$ up team add \"My Company\"\n```\n\nNext you'll need to subscribe! You'll be asked for an optional coupon, credit card information – which never touches our servers, only Stripe via HTTPS – and finally a subscription confirmation.\n\n```\n$ up team subscribe\n ```\n\nNow you and your team members may upgrade to the latest version of Up Pro, instead of the open-source distribution:\n\n```\n$ up upgrade\n```\n\nTo view the status of your account at any time run the following:\n\n```\n$ up team\n\n  team: apex\n  subscription: Up Pro\n  amount: $10.00/mo USD\n  created: December 22, 2017\n```\n\nTo switch to another team run the following and select the active team.\n\n```\n$ up team switch\n\n\n   ❯ apex\n     tj@apex.sh\n```\n\n## Inviting team members\n\nTo invite members use the following command:\n\n```\n$ up team members add tobi@apex.sh\n$ up team members add loki@apex.sh\n$ up team members add jane@apex.sh\n```\n\nAt any time you can view invites and members:\n\n```\n$ up team members\n\nteam: apex\n\nMembers\n\n • tj@apex.sh\n • tobi@apex.sh\n • loki@apex.sh\n\nInvites\n\n • jane@apex.sh\n```\n\nYour team members will receive an email with installation instructions, where they run the following to sign in – with your team id of course.\n\n```\n$ up team login --email tobi@apex.sh --team apex\n```\n\n## Development to production workflow\n\nThis section guides you through taking a small application from development, to production, complete with purchasing and mapping a custom domain.\n\n### Deploying\n\nFirst create `app.js` in an empty directory with the following Node.js app. Note that it must listen on __PORT__ which is passed by Up.\n\n```js\nconst http = require('http')\nconst { PORT = 3000 } = process.env\n\nhttp.createServer((req, res) => {\n  res.end('Hello World\\n')\n}).listen(PORT)\n```\n\nNext you should give your application a name and start configuring. The `profile` name should correspond with the name in `~/.aws/credentials` so that Up knows which AWS account to deploy to, and which credentials to use.\n\n```json\n{\n  \"name\": \"up-example\",\n  \"profile\": \"up-tobi\"\n}\n```\n\nRun `up` to deploy the application.\n\n```\n$ up\n\n   build: 5 files, 3.9 MB (358ms)\n  deploy: complete (14.376s)\n   stack: complete (1m12.086s)\n```\n\nTest with `curl` to ensure everything is working:\n\n```\n$ curl `up url`\nHello World\n```\n\n### Purchasing a domain\n\nDomains can be mapped from existing services, or purchased directly from AWS via Route53. First check if the domain you'd like is available:\n\n```\n$ up domains check up.com\n\n  Domain up.com is unavailable\n\n  Suggestions:\n\n  theupwards.com          $12.00 USD\n  upwardonline.com        $12.00 USD\n  myupwards.com           $12.00 USD\n  theastir.com            $12.00 USD\n  astironline.com         $12.00 USD\n  myastir.com             $12.00 USD\n  myupward.net            $11.00 USD\n  cleanup.tv              $32.00 USD\n  myup.tv                 $32.00 USD\n  itup.tv                 $32.00 USD\n  newup.tv                $32.00 USD\n  thedown.net             $11.00 USD\n  theupward.net           $11.00 USD\n  upwardsonline.net       $11.00 USD\n```\n\nOh no up.com is taken! Try another:\n\n```\n$ up domains check up-example.com\n\n  Domain up-example.com is available for $12.00 USD\n```\n\nPurchase it with the following command and fill out the details required by the registrar:\n\n```\n$ up domains buy\n\n  Domain: up-example.com\n  First name: TJ\n  Last name: Holowaychuk\n  Email: tj@apex.sh\n  Phone: +1.2501007000\n  Country code: CA\n  City: Victoria\n  State or province: BC\n  Zip code: X9X 9X9\n  Address: Some address here\n```\n\nIt can take a few minutes for AWS to finalize the purchase after which you should receive an email. Then you'll see it in the `up domains` output along with the automatic renewal time.\n\n```\n$ up domains\n\n  gh-polls.com             renews Aug 28 17:17:58\n  up-example.com           renews Sep 19 19:40:50\n```\n\nBy default domains purchased with Up have privacy protection enabled, hiding your contact information from [WHOIS](https://en.wikipedia.org/wiki/WHOIS).\n\n### Deploying to Stages\n\nBefore deploying to the staging and production stages, first tweak the application a little to include the `UP_STAGE` environment variable:\n\n```js\nconst http = require('http')\nconst { PORT = 3000, UP_STAGE } = process.env\n\nhttp.createServer((req, res) => {\n  res.end('Hello World from ' + UP_STAGE)\n}).listen(PORT)\n```\n\nNow deploy to staging and production. Note that `up` is an alias of `up deploy staging`.\n\n```\n$ up\n$ up deploy production\n```\n\nOpen both in the browser:\n\n```\n$ up url -o\n$ up url -s production -o\n```\n\nYou should see \"Hello World from production\" and \"Hello World from staging\".\n\n### Mapping custom domains to stages\n\nNow that you have an application deployed, you probably want a fancy custom domain for it right? You can map these using the `stages` and `domain` properties.\n\nHere we let Up know that we want `up-example.com` for production and `stage.up-example.com` for staging.\n\n```json\n{\n  \"name\": \"up-example\",\n  \"profile\": \"up-tobi\",\n  \"stages\": {\n    \"staging\": {\n      \"domain\": \"stage.up-example.com\"\n    },\n    \"production\": {\n      \"domain\": \"up-example.com\"\n    }\n  }\n}\n```\n\nNote that you could map staging to a domain like `staging-myapp.com`, it does not have to be a sub-domain of your production domain.\n\nNow when you run `up stack plan` to preview changes to your resources, it will prompt you to verify the Let's Encrypt certificate emails that AWS sends.\n\n```\n$ up stack plan\n\n       domains: Check your email for certificate approval\n     ⠧ confirm: up-example.com\n```\n\nAWS requires email verification to prove you own the domain. AWS sends an email to the 3 contact addresses listed in WHOIS when you registered the domain, and to the following 5 common system addresses for your domain:\n\n- administrator@your_domain_name\n- hostmaster@your_domain_name\n- postmaster@your_domain_name\n- webmaster@your_domain_name\n- admin@your_domain_name\n\nSee [Validate Domain Ownership](https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-validate-email.html) for more information.\n\nAfter clicking \"I Approve\" in one of the emails, the output will resume and you'll see some new resources Up will be creating.\n\n```\nAdd AWS::ApiGateway::DomainName\n  id: ApiDomainDevelopment\n\nAdd AWS::ApiGateway::BasePathMapping\n  id: ApiDomainDevelopmentPathMapping\n\nAdd AWS::ApiGateway::DomainName\n  id: ApiDomainProduction\n\nAdd AWS::ApiGateway::BasePathMapping\n  id: ApiDomainProductionPathMapping\n\nAdd AWS::Route53::RecordSet\n  id: DnsZoneDevUpExampleComRecordDevUpExampleCom\n\nAdd AWS::Route53::RecordSet\n  id: DnsZoneUpExampleComRecordUpExampleCom\n```\n\nIf you're curious, now that Up knows you want to map the domain(s), it will create:\n\n- Registers ACM free SSL certificate(s) for your domain(s)\n- CloudFront distribution for the API Gateway\n- API Gateway stage mapping\n- Route53 DNS zone and record(s) mapping to the CloudFront distribution\n\nNow apply these changes:\n\n```\n$ up stack apply\n```\n\nAfter the changes have been applied, it can take roughly 10-40 minutes for CloudFront to distribute the configuration and SSL certificate globally, so until then our up-example.com domain won't work.\n\nOnce available https://up-example.com will always point to production via `up deploy production`, and https://stage.up-example.com/ will point to the latest deployment via `up`.\n\n### Mapping domains from external registrars\n\nIf you purchased a domain via `up domains buy` you can skip this step, however if you used an external registrar such as Godaddy you will need to delegate to AWS for DNS management.\n\nTo do this you'll need to sign in to your registrar's site, and configure the nameservers. To figure out what values to use for the nameservers, run `up stack`, which outputs the NS records for the apex (top-level) domains of your application.\n\n```\n$ up stack\n\nStaging\n\n  domain: stage.up-example.com\n  endpoint: d2od0udp1p8bru.cloudfront.net\n\nProduction\n\n  domain: up-example.com\n  endpoint: d72wsqljqg5cy.cloudfront.net\n  nameservers:\n   • ns-1495.awsdns-58.org\n   • ns-103.awsdns-12.com\n   • ns-1670.awsdns-16.co.uk\n   • ns-659.awsdns-18.net\n```\n\nSave those four values in your registrar's interface, and you should be good to go! Note that altering DNS records can take some time to propagate.\n\n### Mapping with third-party DNS\n\nIf you manage DNS with a third-party such as Cloudflare, and wish to use Up only for deployment you will need to manually edit or add DNS records.\n\nFor example if your top-level domain `sloths.com` is managed by Cloudflare and you'd like point `api.sloths.com` to your app, you should first add it to your `up.json`:\n\n```json\n{\n  \"name\": \"sloths\"\n  \"stages\": {\n    \"production\": {\n      \"domain\": \"api.sloths.com\"\n    }\n  }\n}\n```\n\nNext you will need to `up stack plan` and `up stack apply`, this will set up a CloudFront end-point for the application. To view the endpoint information, run `up stack`:\n\n```\n$ up stack\n\nProduction\n\n  domain: api.sloths.com\n  endpoint: d72wsqljqg5cy.cloudfront.net\n```\n\nIn your DNS provider – Cloudflare in this example – you should create a `CNAME` record pointing to the production `endpoint`. Make sure that the `domain` you use matches the domain in Cloudflare.\n\n### Stack changes\n\nThe \"stack\" is all of the resources associated with your app. You plan changes via `up stack plan` and perform them with `up stack apply`.\n\nSuppose you wanted to map the \"staging\" stage, you would first add it to `up.json`:\n\n```json\n{\n  \"name\": \"up-example\",\n  \"profile\": \"up-tobi\",\n  \"stages\": {\n    \"staging\": {\n      \"domain\": \"stage.up-example.com\"\n    },\n    \"production\": {\n      \"domain\": \"up-example.com\"\n    }\n  }\n}\n```\n\nThen run:\n\n```\n$ up stack plan\n```\n\nReview the output, it should be all \"Add\"s in this case, then apply:\n\n```\n$ up stack apply\n```\n\n### Deleting the application\n\nAfter you're done messing around, you may want to remove all the resources and the app itself. To do so simply run:\n\n```\n$ up stack delete\n```\n\n## Deploying applications from continuous integration\n\nUp makes it easy to deploy your applications from CI, thanks to its Go binaries you can install Up in seconds in any CI provider such as Travis, Circle, Semaphore among others.\n\n### Environment variables\n\nThe first step is to set up environment variables so that you have access to your AWS account. You can get these values from `cat ~/.aws/credentials`:\n\n- `AWS_ACCESS_KEY_ID` – AWS access key\n- `AWS_SECRET_ACCESS_KEY` – AWS secret key\n\nIf using running Up Pro you'll need your Up credentials in order to access Up Pro via the `up upgrade` command. To obtain this run `up team ci` or `up team ci --copy` to copy it directly to your clipboard, then paste this as the env var's value.\n\n- `UP_CONFIG` – Up configuration as base64-encoded JSON\n\nIf you run into \"403 Forbidden\" errors this is due to GitHub's low rate limit for unauthenticated users, consider creating a [Personal Access Token](https://github.com/settings/tokens) and adding the following variable to your CI:\n\n- `GITHUB_TOKEN` — Github personal access token\n\n### Commands\n\nYou may install Up in the current working directory, and deploy to production with the following commands, omitting the `up upgrade` if you are not an Up Pro subscriber.\n\n```\n$ curl -sf https://up.apex.sh/install | BINDIR=. sh\n$ ./up upgrade\n$ ./up production\n```\n\nOr if you prefer installing globally within `PATH`:\n\n```\n$ sudo chown -R $(whoami) /usr/local/bin\n$ curl -sf https://up.apex.sh/install | sh\n$ up upgrade\n$ up production\n```\n\n## Mastering logging\n\nThis section describes how you can log from your application in a way that Up will recognize. In the future Up will support forwarding your logs to services such as Loggly, Papertrail or ELK.\n\n### Plain text\n\nThe first option is plain-text logs to stdout or stderr. Currently writes to stderr are considered ERROR-level logs, and stdout becomes INFO.\n\nWriting plain-text logs is simple, for example with Node.js:\n\n```js\nconsole.log('User signed in')\nconsole.error('Failed to sign in: %s', err)\n```\n\nWould be collected as:\n\n```\n INFO: User signed in\nERROR: Failed to sign in: something broke\n```\n\n### JSON structured logs\n\nThe second option is structured logging with JSON events, which is preferred as it allows you to query against specific fields and treat logs like events.\n\nJSON logs require a `level` and `message` field:\n\n```js\nconsole.log(`{ \"level\": \"info\", \"message\": \"User login\" }`)\n```\n\nWould be collected as:\n\n```\nINFO: User login\n```\n\nThe `message` field should typically contain no dynamic content, such as user names or emails, these can be provided as fields:\n\n```js\nconsole.log(`{ \"level\": \"info\", \"message\": \"User login\", \"fields\": { \"name\": \"Tobi\", \"email\": \"tobi@apex.sh\" } }`)\n```\n\nWould be collected as:\n\n```\nINFO: User login name=Tobi email=tobi@apex.sh\n```\n\nAllowing you to perform queries such as:\n\n```\n$ up logs 'message = \"User login\" name = \"Tobi\"'\n```\n\nOr:\n\n```\n$ up logs 'name = \"Tobi\" or email = \"tobi@*\"'\n```\n\nHere's a simple JavaScript logger for reference. All you need to do is output some JSON to stdout and Up will handle the rest!\n\n```js\nfunction log(level, message, fields = {}) {\n  const entry = { level, message, fields }\n  console.log(JSON.stringify(entry))\n}\n```\n\nFor example, with the Go [apex/log](https://github.com/apex/log) package you'd use the `json` handler, which outputs this format.\n\n## Log query language\n\nUp supports a comprehensive query language, allowing you to perform complex filters against structured data, supporting operators, equality, substring tests and so on. This section details the options available when querying.\n\n### AND operator\n\nThe `and` operator is implied, and entirely optional to specify, since this is the common case.\n\nSuppose you have the following example query to show only production errors from a the specified IP address.\n\n```\nproduction error ip = \"207.194.32.30\"\n```\n\nThe parser will inject `and`, effectively compiling to:\n\n```\nproduction and error and ip = \"207.194.38.50\"\n```\n\n### Or operator\n\nThere is of course also an `or` operator, for example showing warnings or errors.\n\n```\nproduction (warn or error)\n```\n\nThese may of course be nested as you require:\n\n```\n(production or staging) (warn or error) method = \"GET\"\n```\n\n### Equality operators\n\nThe `=` and `!=` equality operators allow you to filter on the contents of a field.\n\nHere `=` is used to show only GET requests:\n\n```\nmethod = \"GET\"\n```\n\nOr for example `!=` may be used to show anything except GET:\n\n```\nmethod != \"GET\"\n```\n\n### Relational operators\n\nThe `>`, `>=`, `<`, and `<=` relational operators are useful for comparing numeric values, for example response status codes:\n\n```\nstatus >= 200 status < 300\n```\n\n### Stages\n\nCurrently all development, staging, and production logs are all stored in the same location, however you may filter to find exactly what you need.\n\nThe keywords `production`, `staging`, and `development` expand to:\n\n```\nstage = \"production\"\n```\n\nFor example, filtering on slow production responses:\n\n```\nproduction duration >= 1s\n```\n\nIs the same as:\n\n```\nstage = \"production\" duration >= 1s\n```\n\n### Severity levels\n\nUp provides request level logging with severity levels applied automatically. For example, a 5xx response is an ERROR level, while 4xx is a WARN, and 3xx or 2xx are the INFO level.\n\nThis means that instead of using the following for showing production errors:\n\n```\nproduction status >= 500\n```\n\nYou may use:\n\n```\nproduction error\n```\n\n### In Operator\n\nThe `in` operator checks for the presence of a field within the set provided. For example, showing only POST, PUT and PATCH requests:\n\n```\nmethod in (\"POST\", \"PUT\", \"PATCH\")\n```\n\n### Units\n\nThe log grammar supports units for bytes and durations. For example, showing responses larger than 56kb:\n\n```\nsize > 56kb\n```\n\nOr showing responses longer than 1500ms:\n\n```\nduration > 1.5s\n```\n\nByte units are:\n\n- `b` bytes (`123b` or `123` are equivalent)\n- `kb` bytes (`5kb`, `128kb`)\n- `mb` bytes (`5mb`, `15.5mb`)\n\nDuration units are:\n\n- `ms` milliseconds (`100ms` or `100` are equivalent)\n- `s` seconds (`1.5s`, `5s`)\n\n### Substring matches\n\nWhen filtering on strings, such as the log message, you may use the `*` character for substring matches.\n\nFor example if you want to show logs with a remote ip prefix of `207.`:\n\n```\nip = \"207.*\"\n```\n\nOr a message containing the word \"login\":\n\n```\nmessage = \"*login*\"\n```\n\nThere is also a special keyword for this case:\n\n```\nmessage contains \"login\"\n```\n\n## Hot reloading in development\n\nThe `up start` command uses your `proxy.command` by default, which may be inferred based on your application type, such as `node app.js` for Node.js or `./server` for Golang.\n\nYou may alter this command for `up start` with the development environment. For example with Golang you may want `go run main.go`, or hot reloading with [gin](https://github.com/codegangsta/gin) as shown here:\n\n```json\n{\n  \"name\": \"app\",\n  \"stages\": {\n    \"development\": {\n      \"proxy\": {\n        \"command\": \"gin --port $PORT\"\n      }\n    }\n  }\n}\n```\n\nNote that the server must always listen on `PORT` which is provided by `up start`.\n\n## Accessing lambda context\n\nTraditional AWS Lambda functions provided a context object which contains runtime information such as API Gateway user identity. This information is exposed as JSON in the `X-Context` header field in Up as shown here:\n\n```js\nconst http = require('http')\nconst { PORT } = process.env\n\nconst app = http.createServer((req, res) => {\n  const ctx = JSON.parse(req.headers['x-context'])\n  res.end(JSON.stringify(ctx, null, 2))\n})\n\nconsole.log('Starting app on %s', PORT)\napp.listen(PORT)\n```\n\nOutput will be similar to the following. Visit the [AWS Documentation](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html) for details.\n\n```json\n{\n  \"apiId\": \"g4yn392afg\",\n  \"resourceId\": \"ez0z8areob\",\n  \"requestId\": \"d8314ef1-5543-11e8-a925-21fa0dd01c37\",\n  \"accountId\": \"337344593553\",\n  \"stage\": \"staging\",\n  \"identity\": {\n    \"apiKey\": \"\",\n    \"accountId\": \"\",\n    \"userAgent\": \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36\",\n    \"sourceIp\": \"64.110.31.100\",\n    \"accessKey\": \"\",\n    \"caller\": \"\",\n    \"user\": \"\",\n    \"userARN\": \"\",\n    \"cognitoIdentityId\": \"\",\n    \"cognitoIdentityPoolId\": \"\",\n    \"cognitoAuthenticationType\": \"\",\n    \"cognitoAuthenticationProvider\": \"\"\n  },\n  \"authorizer\": null\n}\n```\n"
  },
  {
    "path": "docs/08-troubleshooting.md",
    "content": "---\ntitle: Troubleshooting\nmenu: Help\nslug: troubleshooting\n---\n\nThis section contains self-help troubleshooting information. If you're running into an issue you can't resolve, try the [Slack](https://chat.apex.sh/) chat, or [submit an issue](https://github.com/apex/up).\n\n<details>\n  <summary>I didn't receive a sign-in or certificate confirmation email</summary>\n  <p>AWS email delivery can be slow sometimes. Please give it 30-60s. Otherwise, be sure to check your spam folder.</p>\n</details>\n\n<details>\n  <summary>My application times out or seems slow</summary>\n  <p>Lambda `memory` scales CPU alongside RAM, so if your application is slow to initialize or serve responses, you may want to try `1024` or above. See [Lambda Pricing](https://aws.amazon.com/lambda/pricing/) for options.</p>\n  <p>Ensure that all of your dependencies are deployed. You may use `up -v` to view what is added or filtered from the deployment or `up build --size` to output the contents of the zip.</p>\n</details>\n\n<details>\n  <summary>I'm seeing 404 Not Found responses</summary>\n  <p>By default, Up ignores files which are found in `.upignore`. Use the verbose flag such as `up -v` to see if files have been filtered or `up build --size` to see a list of files within the zip sorted by size. See [Ignoring Files](#configuration.ignoring_files) for more information.</p>\n</details>\n\n<details>\n  <summary>My deployment seems stuck</summary>\n  <p>The first deploy also creates resources associated with your project and can take roughly 1-2 minutes. AWS provides limited granularity into the creation progress of these resources, so the progress bar may appear \"stuck\".</p>\n</details>\n\n<details>\n  <summary>How do I sign into my team?</summary>\n  <p>Run `up team login` if you aren't signed in, then run `up team login --team my-team-id` to sign into any teams you're an owner or member of.</p>\n</details>\n\n<details>\n  <summary>Unable to associate certificate error</summary>\n  <p>If you receive a `Unable to associate certificate` error it is because you have not verified the SSL certificate. Certs for CloudFront when creating a custom domain MUST be in us-east-1, so if you need to manually resend verification emails visit [ACM in US East 1](https://console.aws.amazon.com/acm/home?region=us-east-1).</p>\n</details>\n\n<details>\n  <summary>I'm seeing 403 Forbidden errors in CI</summary>\n  <p>If you run into \"403 Forbidden\" errors this is due to GitHub's low rate limit for unauthenticated users, consider creating a [Personal Access Token](https://github.com/settings/tokens) and adding `GITHUB_TOKEN` to your CI.</p>\n</details>\n"
  },
  {
    "path": "docs/09-faq.md",
    "content": "---\ntitle: Questions\nmenu: FAQ\nslug: faq\n---\n\n<details>\n  <summary>Is this a hosted service?</summary>\n  <p>There are currently no plans for a hosted version. Up lets you deploy applications to your own AWS account for isolation, security, and longevity — don't worry about a startup going out of business.</p>\n</details>\n\n<details>\n  <summary>How much does Up Pro cost?</summary>\n  <p>Up's subscription fee is currently $20/mo USD. When subscribed, your team has access to Up Pro updates until the subscription is cancelled. When cancelled, you receive access until the end of your billing period.</p>\n\n  <p>For the subscription, you get unlimited access within your organization; there is no additional fee per team member or \"seat\". You can deploy any number of applications as you wish.</p>\n\n  <p>Note that AWS charges for use of its resources. However, most small to medium applications will fit within the AWS free tier.</p>\n</details>\n\n<details>\n  <summary>What platforms does Up support?</summary>\n  <p>Currently AWS via API Gateway and Lambda are supported, this is the focus until Up is nearing feature completion, after which additional providers such as GCP and Azure will be added.</p>\n</details>\n\n<details>\n  <summary>How is this different than other serverless frameworks?</summary>\n  <p>Most of the AWS Lambda based tools are function-oriented, while Up abstracts this away entirely. Up does not use framework \"shims\", the servers that you run using Up are regular HTTP servers and require no code changes for Lambda compatibility.</p>\n\n  <p>Up keeps your apps and APIs portable, makes testing them locally easier, and prevents vendor lock-in. The Lambda support for Up is simply an implementation detail, you are not coupled to API Gateway or Lambda. Up uses the API Gateway proxy mode to send all requests (regardless of path or method) to your application.</p>\n\n  <p>If you're looking to manage function-level event processing pipelines, Apex or Serverless are likely better candidates, however if you're creating applications, apis, micro services, or websites, Up is built for you.</p>\n</details>\n\n<details>\n  <summary>Why run HTTP servers in Lambda?</summary>\n  <p>You might be thinking this defeats the purpose of Lambda, however most people just want to use the tools they know and love. Up lets you be productive developing locally as you normally would, Lambda for hosting is only an implementation detail.</p>\n\n  <p>With Up you can use any Python, Node, Go, or Java framework you'd normally use to develop, and deploy with a single command, while maintaining the cost effectiveness, self-healing, and scaling capabilities of Lambda.</p>\n</details>\n\n<details>\n  <summary>How much does it cost to run an application?</summary>\n  <p>AWS API Gateway provides 1 million free requests per month, so there's a good chance you won't have to pay anything at all. Beyond that view the <a href=\"https://aws.amazon.com/api-gateway/pricing/\">AWS Pricing</a> for more information.</p>\n</details>\n\n<details>\n  <summary>How well does it scale?</summary>\n  <p>Up scales to fit your traffic on-demand, you don't have to do anything beyond deploying your code. There's no restriction on the number of concurrent instances, apps, custom domains and so on.</p>\n</details>\n\n<details>\n  <summary>How much latency does Up's reverse proxy introduce?</summary>\n  <p>With a 512mb Lambda function Up introduces an average of around 500µs (microseconds) per request.</p>\n</details>\n\n<details>\n  <summary>Can I remove the /staging and /production paths?</summary>\n  <p>Up uses AWS API Gateway, which imposes the stage base paths. Currently there is no way to remove them, however when you use\n  custom domains these paths are not present.</p>\n</details>\n\n\n<details>\n  <summary>Do the servers stay active while idle?</summary>\n  <p>This depends on the platform, and with Lambda being the initial platform provided the current answer is no, the server(s) are frozen when inactive and are otherwise \"stateless\".</p>\n\n  <p>Typically relying on background work in-process is an anti-pattern as it does not scale. Lambda functions combined with CloudWatch scheduled events for example are a good way to handle this kind of work, if you're looking for a scalable alternative.</p>\n</details>\n\n<details>\n  <summary>What databases can I use?</summary>\n  <p>You're not limited to databases from any given platform, such as AWS Provided that the database host provides authentication, you can use anything. See the <a href=\"https://github.com/apex/up/wiki#databases\">Wiki</a> for a list of managed & serverless database solutions.</p>\n</details>\n\n<details>\n  <summary>Why is Up licensed as GPLv3?</summary>\n  <p>Up is licensed in such a way that myself as an independent developer can continue to improve the product and provide support. Commercial customers receive access to a premium version of Up with additional features, priority support for bugfixes, and of course knowing that the project will stick around! Up saves your team countless hours maintaining infrastructure and custom tooling, so you can get back to what makes your company and products unique.</p>\n</details>\n\n<details>\n  <summary>Can I donate?</summary>\n  <p>Yes you can! Head over to the <a href=\"https://opencollective.com/apex-up\">OpenCollective</a> page. Any donations are greatly appreciated and help me focus more on Up's implementation, documentation, and examples. If you're using the free OSS version for personal or commercial use please consider giving back, even a few bucks buys a coffee :).</p>\n</details>\n"
  },
  {
    "path": "docs/10-links.md",
    "content": "---\ntitle: Links\nslug: links\n---\n\nLinks to helpful resources such as the Up community, changelog, examples, articles, videos and more.\n\n- [Changelog](https://github.com/apex/up/blob/master/History.md) for changes\n- [GitHub repository](https://github.com/apex/up)\n- [GitHub Actions](https://github.com/apex/actions) for continuous deployment\n- [@tjholowaychuk](https://twitter.com/tjholowaychuk) on Twitter for updates\n- [Example applications](https://github.com/apex/up-examples) for Up in various languages\n- [Slack](https://chat.apex.sh/) to chat with apex(1) and up(1) community members\n- [Blog](https://blog.apex.sh/) to follow release posts, tips and tricks\n- [YouTube](https://www.youtube.com/watch?v=1wnSNj-jmo4&index=1&list=PLbFkWVvnVLnRP-E87Tqe6nYVjOk6461o0) for the Apex Up video playlist\n- [Wiki](https://github.com/apex/up/wiki) for article listings, database suggestions, and sample apps\n- [Serverless Calculator](http://serverlesscalc.com/) for helping estimate costs for your use-case\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/apex/up\n\nrequire (\n\tgithub.com/NYTimes/gziphandler v0.0.0-20170916004738-97ae7fbaf816\n\tgithub.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 // indirect\n\tgithub.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 // indirect\n\tgithub.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 // indirect\n\tgithub.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect\n\tgithub.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect\n\tgithub.com/apex/go-apex v1.0.0\n\tgithub.com/apex/log v1.3.0\n\tgithub.com/armon/go-radix v1.0.0 // indirect\n\tgithub.com/atotto/clipboard v0.1.2 // indirect\n\tgithub.com/aws/aws-sdk-go v1.31.9\n\tgithub.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59\n\tgithub.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect\n\tgithub.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2 // indirect\n\tgithub.com/buger/goterm v0.0.0-20181115115552-c206103e1f37 // indirect\n\tgithub.com/c4milo/unpackit v0.0.0-20170704181138-4ed373e9ef1c // indirect\n\tgithub.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect\n\tgithub.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9\n\tgithub.com/denormal/go-gitignore v0.0.0-20170315120618-40de3d33f668 // indirect\n\tgithub.com/dsnet/compress v0.0.0-20171208185109-cc9eb1d7ad76 // indirect\n\tgithub.com/dustin/go-humanize v0.0.0-20171012181109-77ed807830b4\n\tgithub.com/facebookgo/freeport v0.0.0-20150612182905-d4adf43b75b9\n\tgithub.com/fanyang01/radix v0.0.0-20160415095728-e1747dd9eeac\n\tgithub.com/fatih/color v1.7.0 // indirect\n\tgithub.com/golang/sync v0.0.0-20170927054112-8e0aa688b654\n\tgithub.com/google/go-github v14.0.0+incompatible // indirect\n\tgithub.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect\n\tgithub.com/gosuri/uilive v0.0.0-20170323041506-ac356e6e42cd // indirect\n\tgithub.com/gosuri/uiprogress v0.0.0-20170224063937-d0567a9d84a1 // indirect\n\tgithub.com/hashicorp/go-uuid v0.0.0-20160717022140-64130c7a86d7 // indirect\n\tgithub.com/hooklift/assert v0.0.0-20170704181755-9d1defd6d214 // indirect\n\tgithub.com/jehiah/go-strftime v0.0.0-20151206194810-2efbe75097a5 // indirect\n\tgithub.com/klauspost/compress v1.2.1 // indirect\n\tgithub.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5 // indirect\n\tgithub.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6 // indirect\n\tgithub.com/klauspost/pgzip v0.0.0-20170402124221-0bf5dcad4ada // indirect\n\tgithub.com/mattn/go-isatty v0.0.9 // indirect\n\tgithub.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect\n\tgithub.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747\n\tgithub.com/pascaldekloe/name v0.0.0-20170812100307-81013e77fe79\n\tgithub.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4\n\tgithub.com/pkg/errors v0.9.1\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/rs/cors v0.0.0-20180726230524-02026070ea74\n\tgithub.com/segmentio/analytics-go v0.0.0-20160426181448-2d840d861c32 // indirect\n\tgithub.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c // indirect\n\tgithub.com/segmentio/go-snakecase v1.0.0\n\tgithub.com/sergi/go-diff v1.0.0 // indirect\n\tgithub.com/stripe/stripe-go v28.5.0+incompatible\n\tgithub.com/timewasted/go-accept-headers v0.0.0-20130320203746-c78f304b1b09\n\tgithub.com/tj/assert v0.0.1\n\tgithub.com/tj/aws v0.1.4\n\tgithub.com/tj/backoff v1.0.0\n\tgithub.com/tj/go v1.8.6\n\tgithub.com/tj/go-archive v1.0.2\n\tgithub.com/tj/go-cli-analytics v1.0.0\n\tgithub.com/tj/go-headers v0.0.0-20170630155323-711a635412ca\n\tgithub.com/tj/go-progress v0.0.0-20171031175334-333acdb6fe9f\n\tgithub.com/tj/go-spin v1.1.0\n\tgithub.com/tj/go-update v2.2.4+incompatible\n\tgithub.com/tj/kingpin v2.5.0+incompatible\n\tgithub.com/tj/survey v2.0.6+incompatible\n\tgithub.com/ulikunitz/xz v0.5.4 // indirect\n\tgithub.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect\n\tgolang.org/x/net v0.0.0-20200202094626-16171245cfb2\n\tgolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect\n\tgopkg.in/yaml.v3 v3.0.0-20200602174320-3e3e88ca92fa // indirect\n)\n\ngo 1.13\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/NYTimes/gziphandler v0.0.0-20170916004738-97ae7fbaf816 h1:wBaYm5ra+p6jEKkg9G3tCimdOwp/dcCPSfkePrWoc6w=\ngithub.com/NYTimes/gziphandler v0.0.0-20170916004738-97ae7fbaf816/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=\ngithub.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=\ngithub.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=\ngithub.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=\ngithub.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=\ngithub.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=\ngithub.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=\ngithub.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=\ngithub.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=\ngithub.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/apex/go-apex v1.0.0 h1:Em8+vo4WXEQp7GfNDTr35HRnE5sFYcRpkTODpVjU39A=\ngithub.com/apex/go-apex v1.0.0/go.mod h1:Hy8WsL4dnQc/bYBxElRQ7xHXLNBAqz0BVxUhHiGwKLA=\ngithub.com/apex/log v1.1.0 h1:J5rld6WVFi6NxA6m8GJ1LJqu3+GiTFIt3mYv27gdQWI=\ngithub.com/apex/log v1.1.0/go.mod h1:yA770aXIDQrhVOIGurT/pVdfCpSq1GQV/auzMN5fzvY=\ngithub.com/apex/log v1.3.0 h1:1fyfbPvUwD10nMoh3hY6MXzvZShJQn9/ck7ATgAt5pA=\ngithub.com/apex/log v1.3.0/go.mod h1:jd8Vpsr46WAe3EZSQ/IUMs2qQD/GOycT5rPWCO1yGcs=\ngithub.com/apex/logs v0.0.4/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo=\ngithub.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE=\ngithub.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys=\ngithub.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=\ngithub.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=\ngithub.com/atotto/clipboard v0.0.0-20160219034421-bb272b845f11 h1:Gmm0NreNeu4FgEzX1Ht6ceAf5/wOBG+Gi625BEy9d40=\ngithub.com/atotto/clipboard v0.0.0-20160219034421-bb272b845f11/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=\ngithub.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY=\ngithub.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=\ngithub.com/aws/aws-sdk-go v1.19.1 h1:8kOP0/XGJwXIFlYoD1DAtA39cAjc15Iv/QiDMKitD9U=\ngithub.com/aws/aws-sdk-go v1.19.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=\ngithub.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=\ngithub.com/aws/aws-sdk-go v1.31.9 h1:n+b34ydVfgC30j0Qm69yaapmjejQPW2BoDBX7Uy/tLI=\ngithub.com/aws/aws-sdk-go v1.31.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=\ngithub.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 h1:WWB576BN5zNSZc/M9d/10pqEx5VHNhaQ/yOVAkmj5Yo=\ngithub.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=\ngithub.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=\ngithub.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=\ngithub.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2 h1:1B/+1BcRhOMG1KH/YhNIU8OppSWk5d/NGyfRla88CuY=\ngithub.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=\ngithub.com/buger/goterm v0.0.0-20170918171949-d443b9114f9c h1:p1/ndMSoqqRp8tN/HJuqYNt1IlBzDbug0JYTJAktrtg=\ngithub.com/buger/goterm v0.0.0-20170918171949-d443b9114f9c/go.mod h1:u9UyCz2eTrSGy6fbupqJ54eY5c4IC8gREQ1053dK12U=\ngithub.com/buger/goterm v0.0.0-20181115115552-c206103e1f37 h1:uxxtrnACqI9zK4ENDMf0WpXfUsHP5V8liuq5QdgDISU=\ngithub.com/buger/goterm v0.0.0-20181115115552-c206103e1f37/go.mod h1:u9UyCz2eTrSGy6fbupqJ54eY5c4IC8gREQ1053dK12U=\ngithub.com/c4milo/unpackit v0.0.0-20170704181138-4ed373e9ef1c h1:aprLqMn7gSPT+vdDSl+/E6NLEuArwD/J7IWd8bJt5lQ=\ngithub.com/c4milo/unpackit v0.0.0-20170704181138-4ed373e9ef1c/go.mod h1:Ie6SubJv/NTO9Q0UBH0QCl3Ve50lu9hjbi5YJUw03TE=\ngithub.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=\ngithub.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=\ngithub.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9 h1:74lLNRzvsdIlkTgfDSMuaPjBr4cf6k7pwQQANm/yLKU=\ngithub.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=\ngithub.com/denormal/go-gitignore v0.0.0-20170315120618-40de3d33f668 h1:JJc1wCJD+mjauA6uWGKHgwwpoqnuHsFRCVnGDQuyAbI=\ngithub.com/denormal/go-gitignore v0.0.0-20170315120618-40de3d33f668/go.mod h1:C/+sI4IFnEpCn6VQ3GIPEp+FrQnQw+YQP3+n+GdGq7o=\ngithub.com/dsnet/compress v0.0.0-20171208185109-cc9eb1d7ad76 h1:eX+pdPPlD279OWgdx7f6KqIRSONuK7egk+jDx7OM3Ac=\ngithub.com/dsnet/compress v0.0.0-20171208185109-cc9eb1d7ad76/go.mod h1:KjxHHirfLaw19iGT70HvVjHQsL1vq1SRQB4yOsAfy2s=\ngithub.com/dustin/go-humanize v0.0.0-20171012181109-77ed807830b4 h1:I4YDfvHXPYd8OWal9f4CgxbqNH2Bbcqk6wuLTy+ieww=\ngithub.com/dustin/go-humanize v0.0.0-20171012181109-77ed807830b4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/facebookgo/freeport v0.0.0-20150612182905-d4adf43b75b9 h1:wWke/RUCl7VRjQhwPlR/v0glZXNYzBHdNUzf/Am2Nmg=\ngithub.com/facebookgo/freeport v0.0.0-20150612182905-d4adf43b75b9/go.mod h1:uPmAp6Sws4L7+Q/OokbWDAK1ibXYhB3PXFP1kol5hPg=\ngithub.com/fanyang01/radix v0.0.0-20160415095728-e1747dd9eeac h1:fYRW78xH/NepMB5++0kO74kXra7McyBQKsnWlRZyhaQ=\ngithub.com/fanyang01/radix v0.0.0-20160415095728-e1747dd9eeac/go.mod h1:PSQWLj5d94M4cGhTeXFExqzf4B98d1Zlxtwf5wL1Vmc=\ngithub.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=\ngithub.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=\ngithub.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/sync v0.0.0-20170927054112-8e0aa688b654 h1:bzR1is7uS8E5I4vAF7Qu2TaSDoYqneMomTnAuesaMHw=\ngithub.com/golang/sync v0.0.0-20170927054112-8e0aa688b654/go.mod h1:YCHYtYb9c8Q7XgYVYjmJBPtFPKx5QvOcPxHZWjldabE=\ngithub.com/google/go-github v14.0.0+incompatible h1:IH7XxuaXbLVh4iwPks5+jmKZXElyvAf+5K1108Ku8fU=\ngithub.com/google/go-github v14.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=\ngithub.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0=\ngithub.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=\ngithub.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/gosuri/uilive v0.0.0-20170323041506-ac356e6e42cd h1:1e+0Z+T4t1mKL5xxvxXh5FkjuiToQGKreCobLu7lR3Y=\ngithub.com/gosuri/uilive v0.0.0-20170323041506-ac356e6e42cd/go.mod h1:qkLSc0A5EXSP6B04TrN4oQoxqFI7A8XvoXSlJi8cwk8=\ngithub.com/gosuri/uiprogress v0.0.0-20170224063937-d0567a9d84a1 h1:4iPLwzjiWGBQnYdtKbg/JNlGlEEvklrrMdjypdA1LKQ=\ngithub.com/gosuri/uiprogress v0.0.0-20170224063937-d0567a9d84a1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0=\ngithub.com/hashicorp/go-uuid v0.0.0-20160717022140-64130c7a86d7 h1:w8czuEDeFZo7CdZ2APLO24tTbjzO4cNXTfCkBdtU+wg=\ngithub.com/hashicorp/go-uuid v0.0.0-20160717022140-64130c7a86d7/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hooklift/assert v0.0.0-20170704181755-9d1defd6d214 h1:WgfvpuKg42WVLkxNwzfFraXkTXPK36bMqXvMFN67clI=\ngithub.com/hooklift/assert v0.0.0-20170704181755-9d1defd6d214/go.mod h1:kj6hFWqfwSjFjLnYW5PK1DoxZ4O0uapwHRmd9jhln4E=\ngithub.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=\ngithub.com/jehiah/go-strftime v0.0.0-20151206194810-2efbe75097a5 h1:E1bpycfzgfdJWK32+GOJDYVrep2fbX6cN6tYiXd+CGY=\ngithub.com/jehiah/go-strftime v0.0.0-20151206194810-2efbe75097a5/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag=\ngithub.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=\ngithub.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=\ngithub.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=\ngithub.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=\ngithub.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=\ngithub.com/klauspost/compress v1.2.1 h1:z1Ra6IKoPtIeVA8GV0SCQhuo6T4EBjlL9VwonZ8NYBo=\ngithub.com/klauspost/compress v1.2.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=\ngithub.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5 h1:2U0HzY8BJ8hVwDKIzp7y4voR9CX/nvcfymLmg2UiOio=\ngithub.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=\ngithub.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6 h1:KAZ1BW2TCmT6PRihDPpocIy1QTtsAsrx6TneU/4+CMg=\ngithub.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=\ngithub.com/klauspost/pgzip v0.0.0-20170402124221-0bf5dcad4ada h1:ZHhgRyr+9LYwfuWChpSTCCe/07V26LEElTKUXj+2fAg=\ngithub.com/klauspost/pgzip v0.0.0-20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=\ngithub.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=\ngithub.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=\ngithub.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=\ngithub.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=\ngithub.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=\ngithub.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=\ngithub.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=\ngithub.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=\ngithub.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=\ngithub.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=\ngithub.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=\ngithub.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=\ngithub.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747 h1:eQox4Rh4ewJF+mqYPxCkmBAirRnPaHEB26UkNuPyjlk=\ngithub.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=\ngithub.com/pascaldekloe/name v0.0.0-20170812100307-81013e77fe79 h1:01H24QTjrY56mVV6Ms4bbkT+z36nC+c133Cvxe391vQ=\ngithub.com/pascaldekloe/name v0.0.0-20170812100307-81013e77fe79/go.mod h1:eD5JxqMiuNYyFNmyY9rkJ/slN8y59oEu4Ei7F8OoKWQ=\ngithub.com/pkg/browser v0.0.0-20170505125900-c90ca0c84f15 h1:mrI+6Ae64Wjt+uahGe5we/sPS1sXjvfT3YjtawAVgps=\ngithub.com/pkg/browser v0.0.0-20170505125900-c90ca0c84f15/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=\ngithub.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98=\ngithub.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=\ngithub.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=\ngithub.com/rs/cors v0.0.0-20180726230524-02026070ea74 h1:2UqoDqa4MGBAM2/DsHlZEvzpubV5s4jcMi8tXkLtSvE=\ngithub.com/rs/cors v0.0.0-20180726230524-02026070ea74/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=\ngithub.com/segmentio/analytics-go v0.0.0-20160426181448-2d840d861c32 h1:+0sDBHuIsUlerfNGmggprc/aCAFQ5ZvPReQOHHTVZUs=\ngithub.com/segmentio/analytics-go v0.0.0-20160426181448-2d840d861c32/go.mod h1:C7CYBtQWk4vRk2RyLu0qOcbHJ18E3F1HV2C/8JvKN48=\ngithub.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c h1:rsRTAcCR5CeNLkvgBVSjQoDGRRt6kggsE6XYBqCv2KQ=\ngithub.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c/go.mod h1:kJ9mm9YmoWSkk+oQ+5Cj8DEoRCX2JT6As4kEtIIOp1M=\ngithub.com/segmentio/go-snakecase v1.0.0 h1:FSeHpP0sBL3O+MCpxvQZrS5a51WAki6gposZuwVE9L4=\ngithub.com/segmentio/go-snakecase v1.0.0/go.mod h1:jk1miR5MS7Na32PZUykG89Arm+1BUSYhuGR6b7+hJto=\ngithub.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=\ngithub.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=\ngithub.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=\ngithub.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=\ngithub.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.1.4 h1:ToftOQTytwshuOSj6bDSolVUa3GINfJP/fg3OkkOzQQ=\ngithub.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho=\ngithub.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stripe/stripe-go v28.5.0+incompatible h1:YBJrol3D+WNmmpRIfot0bT/3FGWF8to9306nTRmpZD8=\ngithub.com/stripe/stripe-go v28.5.0+incompatible/go.mod h1:A1dQZmO/QypXmsL0T8axYZkSN/uA/T/A64pfKdBAMiY=\ngithub.com/stripe/stripe-go v70.15.0+incompatible h1:hNML7M1zx8RgtepEMlxyu/FpVPrP7KZm1gPFQquJQvM=\ngithub.com/stripe/stripe-go v70.15.0+incompatible/go.mod h1:A1dQZmO/QypXmsL0T8axYZkSN/uA/T/A64pfKdBAMiY=\ngithub.com/timewasted/go-accept-headers v0.0.0-20130320203746-c78f304b1b09 h1:QVxbx5l/0pzciWYOynixQMtUhPYC3YKD6EcUlOsgGqw=\ngithub.com/timewasted/go-accept-headers v0.0.0-20130320203746-c78f304b1b09/go.mod h1:Uy/Rnv5WKuOO+PuDhuYLEpUiiKIZtss3z519uk67aF0=\ngithub.com/tj/assert v0.0.0-20170216210512-748ebc778a69 h1:yjFMw2bnmM1YJSZn/aHiJ2yYZSBVL4oB8TC/+Y2pvIg=\ngithub.com/tj/assert v0.0.0-20170216210512-748ebc778a69/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=\ngithub.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=\ngithub.com/tj/assert v0.0.1 h1:T7ozLNagrCCKl3wc+a706ztUCn/D6WHCJtkyvqYG+kQ=\ngithub.com/tj/assert v0.0.1/go.mod h1:lsg+GHQ0XplTcWKGxFLf/XPcPxWO8x2ut5jminoR2rA=\ngithub.com/tj/aws v0.1.4 h1:DZJArmjug0N7EvQxSs8W3gIDz1UzF8PeEE2MbTPhA80=\ngithub.com/tj/aws v0.1.4/go.mod h1:kG2Lk+22zcwmIKGMfSCcKDrqbVklm6AqVwQ/3BwbFec=\ngithub.com/tj/backoff v1.0.0 h1:Zi5jr//0f/ZtFXzXA2sx4pd/n3KTMK6b3xO2D1y50I4=\ngithub.com/tj/backoff v1.0.0/go.mod h1:opbA1vtO/vdNR4wI5izntg38viykxrVNMku6WVSWU/k=\ngithub.com/tj/go v1.8.5 h1:QbRCjQp1v308lsdHe+x/XYJ2U+WdSxY9tWiduvOFsPU=\ngithub.com/tj/go v1.8.5/go.mod h1:iDIwBG1ZkyeGIOBZLZQfpIztHr5m0gG+YGXrKaUC4yE=\ngithub.com/tj/go v1.8.6 h1:HZ+XV+wB4vqN5y5VLoZqYUuUJTBF+2kblBru7aUa44E=\ngithub.com/tj/go v1.8.6/go.mod h1:iDIwBG1ZkyeGIOBZLZQfpIztHr5m0gG+YGXrKaUC4yE=\ngithub.com/tj/go v1.8.7 h1:a7M1Xo+QKmlUHEzZj2LX0LHqkh7/LpOa6Or8luBvY/c=\ngithub.com/tj/go v1.8.7/go.mod h1:88DQADQo0c0fHmWNcr88pIGUHlV5du8aGtON+S1jr5A=\ngithub.com/tj/go-archive v1.0.2 h1:qrtC9BD1rrxuaUAD3sgzaqkt98YeGelE2aJAwZlFL24=\ngithub.com/tj/go-archive v1.0.2/go.mod h1:qqEdZXPGCYTFmpsGAGlTxUIv5fWK8NBEdvzU7NV3fJo=\ngithub.com/tj/go-cli-analytics v1.0.0 h1:aOf/mTKoyMSy8VnvLOTNPDwImjxT7mNC7ivmpTmRtdg=\ngithub.com/tj/go-cli-analytics v1.0.0/go.mod h1:Iwh92wE6jdgYylSZpfFmEoDtB812G235Nq/FtBYmv3E=\ngithub.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=\ngithub.com/tj/go-headers v0.0.0-20170630155323-711a635412ca h1:xcArqZ1PrVz/YH9AnEd8vCuEVY4vGKqeC6zFNzS56Mg=\ngithub.com/tj/go-headers v0.0.0-20170630155323-711a635412ca/go.mod h1:TplXCeOi+Y4CQVdB8KFG4+EtmxgxQaauYO/h3cASuZA=\ngithub.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=\ngithub.com/tj/go-progress v0.0.0-20171031175334-333acdb6fe9f h1:adrNJejn+15T3PPv/M2scCcFcc9WzCZYdLw25nLl76Y=\ngithub.com/tj/go-progress v0.0.0-20171031175334-333acdb6fe9f/go.mod h1:abH8hpo1+c7MbAa0ZCKvvGOgowFNgaoRQEcY0vsRTh4=\ngithub.com/tj/go-spin v1.1.0 h1:lhdWZsvImxvZ3q1C5OIB7d72DuOwP4O2NdBg9PyzNds=\ngithub.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=\ngithub.com/tj/go-update v2.2.4+incompatible h1:7Rkw5ZyRSFb3QyEWM7sHCy9rCy1/r66elkOyGlfnZFc=\ngithub.com/tj/go-update v2.2.4+incompatible/go.mod h1:waFwwyiAhGey2e+dNoYQ/iLhIcFqhCW7zL/+vDU1WLo=\ngithub.com/tj/kingpin v2.5.0+incompatible h1:nZWdCABGeebLFX5Ha/rYqxgEQpSXYWh5N9Dx2sGR0Bs=\ngithub.com/tj/kingpin v2.5.0+incompatible/go.mod h1:/babRmtQneL+pp+Yi24s2gukswokaKCR4gfjGbnjHBk=\ngithub.com/tj/survey v2.0.6+incompatible h1:tVgc1+kmYX9R0CEoHaTczapjdc4GaJla0VAB7O+w1So=\ngithub.com/tj/survey v2.0.6+incompatible/go.mod h1:vLPzQYAOKWgXqr5jV9luQXJuoXKHOg0ltn5FEw1Nz0c=\ngithub.com/ulikunitz/xz v0.5.4 h1:zATC2OoZ8H1TZll3FpbX+ikwmadbO699PE06cIkm9oU=\ngithub.com/ulikunitz/xz v0.5.4/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=\ngithub.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g=\ngithub.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/net v0.0.0-20171027103834-c73622c77280 h1:TFSo8RGq2v9crRl/RW0EH71y1kdSjqeCxljzuDsD+oA=\ngolang.org/x/net v0.0.0-20171027103834-c73622c77280/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20171031081856-95c657629925 h1:nCH33NboKIsT4HoXBsXTWX8ul303HxWgkc5s2Ezwacg=\ngolang.org/x/sys v0.0.0-20171031081856-95c657629925/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=\ngolang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.0.0-20171102192421-88f656faf3f3 h1:TtrmcC9vFAjk6IwmXFdqQovdiZxrqQycAYaeCHauPKU=\ngolang.org/x/text v0.0.0-20171102192421-88f656faf3f3/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200602174320-3e3e88ca92fa h1:5lGs+2OAqZvyIo1XjvoyXoDb8g6k9uAg2WTflQT/yl8=\ngopkg.in/yaml.v3 v3.0.0-20200602174320-3e3e88ca92fa/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "handler/handler.go",
    "content": "// Package handler provides what is essentially the core of Up's\n// reverse proxy, complete with all middleware for handling\n// logging, redirectcs, static file serving and so on.\npackage handler\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/apex/up/http/cors\"\n\t\"github.com/apex/up/http/errorpages\"\n\t\"github.com/apex/up/http/gzip\"\n\t\"github.com/apex/up/http/headers\"\n\t\"github.com/apex/up/http/inject\"\n\t\"github.com/apex/up/http/logs\"\n\t\"github.com/apex/up/http/poweredby\"\n\t\"github.com/apex/up/http/redirects\"\n\t\"github.com/apex/up/http/relay\"\n\t\"github.com/apex/up/http/robots\"\n\t\"github.com/apex/up/http/static\"\n)\n\n// FromConfig returns the handler based on user config.\nfunc FromConfig(c *up.Config) (http.Handler, error) {\n\tswitch c.Type {\n\tcase \"server\":\n\t\treturn relay.New(c)\n\tcase \"static\":\n\t\treturn static.New(c), nil\n\tdefault:\n\t\treturn nil, errors.Errorf(\"unknown .type %q\", c.Type)\n\t}\n}\n\n// New handler complete with all Up middleware.\nfunc New(c *up.Config, h http.Handler) (http.Handler, error) {\n\th = poweredby.New(\"up\", h)\n\th = robots.New(c, h)\n\th = static.NewDynamic(c, h)\n\n\th, err := headers.New(c, h)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"headers\")\n\t}\n\n\th = cors.New(c, h)\n\n\th, err = errorpages.New(c, h)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"error pages\")\n\t}\n\n\th, err = inject.New(c, h)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"inject\")\n\t}\n\n\th, err = redirects.New(c, h)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"redirects\")\n\t}\n\n\th = gzip.New(c, h)\n\n\th, err = logs.New(c, h)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"logs\")\n\t}\n\n\treturn h, nil\n}\n"
  },
  {
    "path": "handler/handler_test.go",
    "content": "package handler\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/tj/assert\"\n)\n\nfunc newHandler(t testing.TB, c *up.Config) http.Handler {\n\th, err := FromConfig(c)\n\tassert.NoError(t, err, \"FromConfig\")\n\n\th, err = New(c, h)\n\tassert.NoError(t, err, \"New\")\n\n\treturn h\n}\n\nfunc TestNode(t *testing.T) {\n\tos.Chdir(\"testdata/node\")\n\tdefer os.Chdir(\"../..\")\n\n\tc, err := up.ReadConfig(\"up.json\")\n\tassert.NoError(t, err, \"read config\")\n\n\th := newHandler(t, c)\n\n\tres := httptest.NewRecorder()\n\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\n\th.ServeHTTP(res, req)\n\n\tactual := res.Header()\n\tassert.NotEmpty(t, actual.Get(\"Date\"), \"date\")\n\tactual.Del(\"Date\")\n\n\theader := make(http.Header)\n\theader.Add(\"X-Powered-By\", \"up\")\n\theader.Add(\"X-Robots-Tag\", \"none\")\n\theader.Add(\"X-Foo\", \"bar\")\n\theader.Add(\"Content-Length\", \"11\")\n\theader.Add(\"Content-Type\", \"text/plain; charset=utf-8\")\n\theader.Add(\"Vary\", \"Accept-Encoding\")\n\tassert.Equal(t, header, actual)\n}\n\nfunc TestStatic(t *testing.T) {\n\tos.Chdir(\"testdata/static\")\n\tdefer os.Chdir(\"../..\")\n\n\tc, err := up.ReadConfig(\"up.json\")\n\tassert.NoError(t, err, \"read config\")\n\n\th := newHandler(t, c)\n\n\tres := httptest.NewRecorder()\n\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\n\th.ServeHTTP(res, req)\n\n\tactual := res.Header()\n\tassert.NotEmpty(t, actual.Get(\"Last-Modified\"), \"last-modified\")\n\tactual.Del(\"Last-Modified\")\n\n\theader := make(http.Header)\n\theader.Add(\"X-Powered-By\", \"up\")\n\theader.Add(\"X-Robots-Tag\", \"none\")\n\theader.Add(\"Content-Length\", \"12\")\n\theader.Add(\"Content-Type\", \"text/html; charset=utf-8\")\n\theader.Add(\"Accept-Ranges\", \"bytes\")\n\theader.Add(\"Vary\", \"Accept-Encoding\")\n\n\tassert.Equal(t, header, actual)\n}\n\nfunc TestNodeWithPackage(t *testing.T) {\n\tos.Chdir(\"testdata/node-pkg\")\n\tdefer os.Chdir(\"../..\")\n\n\tc, err := up.ReadConfig(\"up.json\")\n\tassert.NoError(t, err, \"read config\")\n\n\th := newHandler(t, c)\n\n\tres := httptest.NewRecorder()\n\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\n\th.ServeHTTP(res, req)\n\n\tassert.Equal(t, \"Hello World\", res.Body.String())\n}\n\nfunc TestNodeWithPackageStart(t *testing.T) {\n\tos.Chdir(\"testdata/node-pkg-start\")\n\tdefer os.Chdir(\"../..\")\n\n\tc, err := up.ReadConfig(\"up.json\")\n\tassert.NoError(t, err, \"read config\")\n\n\th := newHandler(t, c)\n\n\tres := httptest.NewRecorder()\n\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\n\th.ServeHTTP(res, req)\n\n\tassert.Equal(t, \"Hello World\", res.Body.String())\n}\n\nfunc TestHandler_conditionalGet(t *testing.T) {\n\tos.Chdir(\"testdata/static\")\n\tdefer os.Chdir(\"../..\")\n\n\tc, err := up.ReadConfig(\"up.json\")\n\tassert.NoError(t, err, \"read config\")\n\n\th := newHandler(t, c)\n\n\tres := httptest.NewRecorder()\n\treq := httptest.NewRequest(\"GET\", \"/style.css\", nil)\n\treq.Header.Set(\"If-Modified-Since\", \"Thu, 27 Jul 2030 03:30:31 GMT\")\n\th.ServeHTTP(res, req)\n\tassert.Equal(t, 304, res.Code)\n\tassert.Equal(t, \"\", res.Header().Get(\"Content-Length\"))\n\tassert.Equal(t, \"\", res.Body.String())\n}\n\nfunc TestHandler_rewrite(t *testing.T) {\n\tos.Chdir(\"testdata/static-rewrites\")\n\tdefer os.Chdir(\"../..\")\n\n\tc, err := up.ReadConfig(\"up.json\")\n\tassert.NoError(t, err, \"read config\")\n\n\th := newHandler(t, c)\n\n\tres := httptest.NewRecorder()\n\treq := httptest.NewRequest(\"GET\", \"/docs/ping/guides/alerts\", nil)\n\th.ServeHTTP(res, req)\n\tassert.Equal(t, 200, res.Code)\n\tassert.Equal(t, \"14\", res.Header().Get(\"Content-Length\"))\n\tassert.Equal(t, \"Alerting docs\\n\", res.Body.String())\n}\n\nfunc TestHandler_redirect(t *testing.T) {\n\tos.Chdir(\"testdata/static-redirects\")\n\tdefer os.Chdir(\"../..\")\n\n\tc, err := up.ReadConfig(\"up.json\")\n\tassert.NoError(t, err, \"read config\")\n\n\th := newHandler(t, c)\n\n\tres := httptest.NewRecorder()\n\treq := httptest.NewRequest(\"GET\", \"/docs/ping/guides/alerts/\", nil)\n\th.ServeHTTP(res, req)\n\tassert.Equal(t, \"/help/ping/alerts\", res.Header().Get(\"Location\"))\n\tassert.Equal(t, 302, res.Code)\n\tassert.Equal(t, \"Found\\n\", res.Body.String())\n}\n\nfunc TestHandler_spa(t *testing.T) {\n\tos.Chdir(\"testdata/spa\")\n\tdefer os.Chdir(\"../..\")\n\n\tc, err := up.ReadConfig(\"up.json\")\n\tassert.NoError(t, err, \"read config\")\n\n\th := newHandler(t, c)\n\n\tt.Run(\"index\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\t\th.ServeHTTP(res, req)\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"Index\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"redirect\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/index.html\", nil)\n\t\th.ServeHTTP(res, req)\n\t\tassert.Equal(t, 301, res.Code)\n\t})\n\n\tt.Run(\"file does not exist\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/something/here\", nil)\n\t\th.ServeHTTP(res, req)\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"Index\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"file exists\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/app.js\", nil)\n\t\th.ServeHTTP(res, req)\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"app js\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"file exists nested\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/css/bar.css\", nil)\n\t\th.ServeHTTP(res, req)\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"bar css\\n\", res.Body.String())\n\t})\n}\n"
  },
  {
    "path": "handler/testdata/node/app.js",
    "content": "const http = require('http')\nconst port = process.env.PORT\n\nhttp.createServer((req, res) => {\n  res.setHeader('X-Foo', 'bar')\n  res.setHeader('Content-Type', 'text/plain; charset=utf-8')\n  res.end('Hello World')\n}).listen(port, '127.0.0.1', _ => {\n  console.log('listening')\n})\n"
  },
  {
    "path": "handler/testdata/node/up.json",
    "content": "{\n  \"name\": \"app\",\n  \"logs\": {\n    \"enable\": false\n  }\n}\n"
  },
  {
    "path": "handler/testdata/node-pkg/app.js",
    "content": "const http = require('http')\nconst { PORT } = process.env\n\nhttp.createServer((req, res) => {\n  res.end('Hello World')\n}).listen(PORT)\n"
  },
  {
    "path": "handler/testdata/node-pkg/package.json",
    "content": "{\n  \"name\": \"something\"\n}\n"
  },
  {
    "path": "handler/testdata/node-pkg/up.json",
    "content": "{\n  \"name\": \"app\",\n  \"logs\": {\n    \"enable\": false\n  }\n}\n"
  },
  {
    "path": "handler/testdata/node-pkg-start/index.js",
    "content": "const http = require('http')\nconst { PORT } = process.env\n\nhttp.createServer((req, res) => {\n  res.end('Hello World')\n}).listen(PORT)\n"
  },
  {
    "path": "handler/testdata/node-pkg-start/package.json",
    "content": "{\n  \"name\": \"something\",\n  \"scripts\": {\n    \"start\": \"node index\"\n  }\n}\n"
  },
  {
    "path": "handler/testdata/node-pkg-start/up.json",
    "content": "{\n  \"name\": \"app\",\n  \"logs\": {\n    \"enable\": false\n  }\n}\n"
  },
  {
    "path": "handler/testdata/spa/app.js",
    "content": "app js\n"
  },
  {
    "path": "handler/testdata/spa/css/bar.css",
    "content": "bar css\n"
  },
  {
    "path": "handler/testdata/spa/css/foo.css",
    "content": "foo css\n"
  },
  {
    "path": "handler/testdata/spa/index.html",
    "content": "Index\n"
  },
  {
    "path": "handler/testdata/spa/up.json",
    "content": "{\n  \"name\": \"app\",\n  \"type\": \"static\",\n  \"logs\": {\n    \"enable\": false\n  },\n  \"redirects\": {\n    \"/*\": {\n      \"location\": \"/\",\n      \"status\": 200\n    }\n  }\n}\n"
  },
  {
    "path": "handler/testdata/static/index.html",
    "content": "Hello World\n"
  },
  {
    "path": "handler/testdata/static/style.css",
    "content": "body { background: whatever }\n"
  },
  {
    "path": "handler/testdata/static/up.json",
    "content": "{\n  \"name\": \"app\",\n  \"type\": \"static\",\n  \"logs\": {\n    \"enable\": false\n  }\n}\n"
  },
  {
    "path": "handler/testdata/static-redirects/help/ping/alerts/index.html",
    "content": "Alerting docs\n"
  },
  {
    "path": "handler/testdata/static-redirects/index.html",
    "content": "Hello World\n"
  },
  {
    "path": "handler/testdata/static-redirects/up.json",
    "content": "{\n  \"name\": \"app\",\n  \"type\": \"static\",\n  \"logs\": {\n    \"enable\": false\n  },\n  \"redirects\": {\n    \"/docs/:product/guides/:guide\": {\n      \"location\": \"/help/:product/:guide\",\n      \"status\": 302\n    }\n  }\n}\n"
  },
  {
    "path": "handler/testdata/static-rewrites/help/ping/alerts.html",
    "content": "Alerting docs\n"
  },
  {
    "path": "handler/testdata/static-rewrites/index.html",
    "content": "Hello World\n"
  },
  {
    "path": "handler/testdata/static-rewrites/up.json",
    "content": "{\n  \"name\": \"app\",\n  \"type\": \"static\",\n  \"logs\": {\n    \"enable\": false\n  },\n  \"redirects\": {\n    \"/docs/:product/guides/:guide\": {\n      \"location\": \"/help/:product/:guide.html\",\n      \"status\": 200\n    }\n  }\n}\n"
  },
  {
    "path": "http/cors/cors.go",
    "content": "// Package cors provides CORS support.\npackage cors\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/rs/cors\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/apex/up/config\"\n)\n\n// New CORS handler.\nfunc New(c *up.Config, next http.Handler) http.Handler {\n\tif c.CORS == nil {\n\t\treturn next\n\t}\n\n\treturn cors.New(options(c.CORS)).Handler(next)\n}\n\n// options returns the canonical options.\nfunc options(c *config.CORS) cors.Options {\n\treturn cors.Options{\n\t\tAllowedOrigins:   c.AllowedOrigins,\n\t\tAllowedMethods:   c.AllowedMethods,\n\t\tAllowedHeaders:   c.AllowedHeaders,\n\t\tExposedHeaders:   c.ExposedHeaders,\n\t\tAllowCredentials: c.AllowCredentials,\n\t\tMaxAge:           c.MaxAge,\n\t\tDebug:            c.Debug,\n\t}\n}\n"
  },
  {
    "path": "http/cors/cors_test.go",
    "content": "package cors\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/tj/assert\"\n)\n\nvar hello = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\tfmt.Fprint(w, \"Hello World\")\n})\n\nfunc TestCORS_disabled(t *testing.T) {\n\tc, err := up.ParseConfigString(`{\n\t\t\"name\": \"app\"\n\t}`)\n\n\tassert.NoError(t, err, \"config\")\n\n\th := New(c, hello)\n\n\tt.Run(\"GET\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\n\t\treq.Header.Set(\"Origin\", \"https://example.com\")\n\n\t\th.ServeHTTP(res, req)\n\n\t\theader := make(http.Header)\n\t\theader.Add(\"Content-Type\", \"text/plain; charset=utf-8\")\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, header, res.HeaderMap)\n\t\tassert.Equal(t, \"Hello World\", res.Body.String())\n\t})\n}\n\nfunc TestCORS_defaults(t *testing.T) {\n\tc, err := up.ParseConfigString(`{\n\t\t\"name\": \"app\",\n\t\t\"cors\": {}\n\t}`)\n\n\tassert.NoError(t, err, \"config\")\n\n\th := New(c, hello)\n\n\tt.Run(\"GET\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\n\t\treq.Header.Set(\"Origin\", \"https://example.com\")\n\n\t\th.ServeHTTP(res, req)\n\n\t\theader := make(http.Header)\n\t\theader.Add(\"Content-Type\", \"text/plain; charset=utf-8\")\n\t\theader.Add(\"Vary\", \"Origin\")\n\t\theader.Add(\"Access-Control-Allow-Origin\", \"*\")\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, header, res.HeaderMap)\n\t\tassert.Equal(t, \"Hello World\", res.Body.String())\n\t})\n\n\tt.Run(\"OPTIONS\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"OPTIONS\", \"/\", nil)\n\n\t\treq.Header.Set(\"Access-Control-Request-Method\", \"POST\")\n\t\treq.Header.Set(\"Origin\", \"https://example.com\")\n\t\treq.Header.Set(\"Access-Control-Request-Headers\", \"Content-Type\")\n\n\t\th.ServeHTTP(res, req)\n\n\t\theader := make(http.Header)\n\t\theader.Add(\"Vary\", \"Origin\")\n\t\theader.Add(\"Vary\", \"Access-Control-Request-Method\")\n\t\theader.Add(\"Vary\", \"Access-Control-Request-Headers\")\n\t\theader.Add(\"Access-Control-Allow-Methods\", \"POST\")\n\t\theader.Add(\"Access-Control-Allow-Headers\", \"Content-Type\")\n\t\theader.Add(\"Access-Control-Allow-Origin\", \"*\")\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, header, res.HeaderMap)\n\t\tassert.Equal(t, \"\", res.Body.String())\n\t})\n}\n\nfunc TestCORS_options(t *testing.T) {\n\tc := up.MustParseConfigString(`{\n\t\t\"name\": \"app\",\n\t\t\"cors\": {\n\t\t\t\"allowed_origins\": [\"https://apex.sh\"],\n\t\t\t\"allowed_methods\": [\"GET\"],\n\t\t\t\"allow_credentials\": true,\n\t\t\t\"max_age\": 86400\n\t\t}\n\t}`)\n\n\th := New(c, hello)\n\n\tt.Run(\"GET\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\n\t\treq.Header.Set(\"Origin\", \"https://example.com\")\n\n\t\th.ServeHTTP(res, req)\n\n\t\theader := make(http.Header)\n\t\theader.Add(\"Content-Type\", \"text/plain; charset=utf-8\")\n\t\theader.Add(\"Vary\", \"Origin\")\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, header, res.HeaderMap)\n\t\tassert.Equal(t, \"Hello World\", res.Body.String())\n\t})\n\n\tt.Run(\"OPTIONS\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"OPTIONS\", \"/\", nil)\n\n\t\treq.Header.Set(\"Access-Control-Request-Method\", \"POST\")\n\t\treq.Header.Set(\"Origin\", \"https://example.com\")\n\t\treq.Header.Set(\"Access-Control-Request-Headers\", \"Content-Type\")\n\n\t\th.ServeHTTP(res, req)\n\n\t\theader := make(http.Header)\n\t\theader.Add(\"Vary\", \"Origin\")\n\t\theader.Add(\"Vary\", \"Access-Control-Request-Method\")\n\t\theader.Add(\"Vary\", \"Access-Control-Request-Headers\")\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, header, res.HeaderMap)\n\t\tassert.Equal(t, \"\", res.Body.String())\n\t})\n}\n"
  },
  {
    "path": "http/errorpages/errorpages.go",
    "content": "// Package errorpages provides default and customizable\n// error pages, via error.html, 5xx.html, or 500.html\n// for example.\npackage errorpages\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/pkg/errors\"\n\taccept \"github.com/timewasted/go-accept-headers\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/apex/up/internal/errorpage\"\n\t\"github.com/apex/up/internal/logs\"\n\t\"github.com/apex/up/internal/util\"\n)\n\n// log context.\nvar ctx = logs.Plugin(\"errorpages\")\n\n// response wrapper.\ntype response struct {\n\thttp.ResponseWriter\n\tconfig *up.Config\n\tpages  errorpage.Pages\n\theader bool\n\tignore bool\n}\n\n// WriteHeader implementation.\nfunc (r *response) WriteHeader(code int) {\n\tw := r.ResponseWriter\n\n\tr.header = true\n\tpage := r.pages.Match(code)\n\n\tif page == nil {\n\t\tctx.Debugf(\"did not match %d\", code)\n\t\tw.WriteHeader(code)\n\t\treturn\n\t}\n\n\tctx.Debugf(\"matched %d with %q\", code, page.Name)\n\n\tdata := struct {\n\t\tStatusText string\n\t\tStatusCode int\n\t\tVariables  map[string]interface{}\n\t}{\n\t\tStatusText: http.StatusText(code),\n\t\tStatusCode: code,\n\t\tVariables:  r.config.ErrorPages.Variables,\n\t}\n\n\thtml, err := page.Render(data)\n\tif err != nil {\n\t\tctx.WithError(err).Error(\"rendering error page\")\n\t\thttp.Error(w, \"Error rendering error page.\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tr.ignore = true\n\tutil.ClearHeader(w.Header())\n\tw.Header().Set(\"Vary\", \"Accept\")\n\tw.Header().Set(\"Content-Type\", \"text/html; charset=utf-8\")\n\tw.WriteHeader(code)\n\tio.WriteString(w, html)\n}\n\n// Write implementation.\nfunc (r *response) Write(b []byte) (int, error) {\n\tif r.ignore {\n\t\treturn len(b), nil\n\t}\n\n\tif !r.header {\n\t\tr.WriteHeader(200)\n\t\treturn r.Write(b)\n\t}\n\n\treturn r.ResponseWriter.Write(b)\n}\n\n// New error pages handler.\nfunc New(c *up.Config, next http.Handler) (http.Handler, error) {\n\t// disabled\n\tif !c.ErrorPages.Enable {\n\t\treturn next, nil\n\t}\n\n\t// load pages\n\tpages, err := errorpage.Load(c.ErrorPages.Dir)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"loading error pages\")\n\t}\n\n\th := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tmime, _ := accept.Negotiate(r.Header.Get(\"Accept\"), \"text/html\")\n\n\t\tif mime == \"\" {\n\t\t\tnext.ServeHTTP(w, r)\n\t\t\treturn\n\t\t}\n\n\t\tres := &response{ResponseWriter: w, pages: pages, config: c}\n\t\tnext.ServeHTTP(res, r)\n\t})\n\n\treturn h, nil\n}\n"
  },
  {
    "path": "http/errorpages/errorpages_test.go",
    "content": "package errorpages\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/tj/assert\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/apex/up/config\"\n)\n\nvar server = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\tif r.URL.Path == \"/404\" {\n\t\tw.Header().Set(\"ETag\", \"something\")\n\t\thttp.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)\n\t\treturn\n\t}\n\n\tif r.URL.Path == \"/400\" {\n\t\thttp.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tif r.URL.Path == \"/400/json\" {\n\t\tw.WriteHeader(400)\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tio.WriteString(w, `{ \"error\": \"bad_request\" }`)\n\t\treturn\n\t}\n\n\tif r.URL.Path == \"/500\" {\n\t\thttp.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tw.Header().Set(\"ETag\", \"something\")\n\tw.Header().Set(\"Content-Type\", \"text/plain\")\n\tfmt.Fprintf(w, \"Hello\")\n\tfmt.Fprintf(w, \" \")\n\tfmt.Fprintf(w, \"World\")\n})\n\nfunc TestErrors_templates(t *testing.T) {\n\tos.Chdir(\"testdata/templates\")\n\tdefer os.Chdir(\"../..\")\n\n\tc := &up.Config{\n\t\tName: \"app\",\n\t\tErrorPages: config.ErrorPages{\n\t\t\tEnable: true,\n\t\t},\n\t}\n\tassert.NoError(t, c.Default(), \"default\")\n\tassert.NoError(t, c.Validate(), \"validate\")\n\n\ttest(t, c)\n}\n\nfunc TestErrors_dir(t *testing.T) {\n\tc := &up.Config{\n\t\tName: \"app\",\n\t\tErrorPages: config.ErrorPages{\n\t\t\tDir:    \"testdata/templates\",\n\t\t\tEnable: true,\n\t\t},\n\t}\n\n\tassert.NoError(t, c.Default(), \"default\")\n\tassert.NoError(t, c.Validate(), \"validate\")\n\n\ttest(t, c)\n}\n\nfunc TestErrors_defaults(t *testing.T) {\n\tos.Chdir(\"testdata/defaults\")\n\tdefer os.Chdir(\"../..\")\n\n\tc := &up.Config{\n\t\tName: \"app\",\n\t\tErrorPages: config.ErrorPages{\n\t\t\tEnable: true,\n\t\t},\n\t}\n\tassert.NoError(t, c.Default(), \"default\")\n\tassert.NoError(t, c.Validate(), \"validate\")\n\n\th, err := New(c, server)\n\tassert.NoError(t, err, \"init\")\n\n\tt.Run(\"200\", nonError(h))\n\tt.Run(\"accepts text/html\", acceptsHTML(h))\n\tt.Run(\"accepts text/*\", acceptsText(h))\n\tt.Run(\"does not accept html\", doesNotAcceptHTML(h))\n}\n\nfunc TestErrors_disabled(t *testing.T) {\n\tc := &up.Config{\n\t\tName: \"app\",\n\t}\n\n\tassert.NoError(t, c.Default(), \"default\")\n\tassert.NoError(t, c.Validate(), \"validate\")\n\n\th, err := New(c, server)\n\tassert.NoError(t, err, \"init\")\n\n\tt.Run(\"200\", nonError(h))\n\n\tt.Run(\"error\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/404\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 404, res.Code)\n\t\tassert.Equal(t, \"text/plain; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, \"Not Found\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"json error\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/400/json\", nil)\n\t\treq.Header.Set(\"Accept\", \"application/json\")\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 400, res.Code)\n\t\tassert.Equal(t, \"application/json\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, `{ \"error\": \"bad_request\" }`, res.Body.String())\n\t})\n}\n\nfunc test(t *testing.T, c *up.Config) {\n\th, err := New(c, server)\n\tassert.NoError(t, err, \"init\")\n\n\tt.Run(\"200\", nonError(h))\n\tt.Run(\"accepts text/html\", acceptsHTML(h))\n\tt.Run(\"accepts text/*\", acceptsText(h))\n\tt.Run(\"does not accept html\", doesNotAcceptHTML(h))\n\n\tt.Run(\"exact\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/404\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 404, res.Code)\n\t\tassert.Equal(t, \"\", res.Header().Get(\"ETag\"))\n\t\tassert.Equal(t, \"text/html; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, \"Sorry! Can't find that.\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"range\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/500\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 500, res.Code)\n\t\tassert.Equal(t, \"Accept\", res.Header().Get(\"Vary\"))\n\t\tassert.Equal(t, \"\", res.Header().Get(\"ETag\"))\n\t\tassert.Equal(t, \"text/html; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, \"500 – Internal Server Error\\n\", res.Body.String())\n\t})\n}\n\nfunc nonError(h http.Handler) func(t *testing.T) {\n\treturn func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"something\", res.Header().Get(\"ETag\"))\n\t\tassert.Equal(t, \"text/plain\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, \"Hello World\", res.Body.String())\n\t}\n}\n\nfunc acceptsHTML(h http.Handler) func(t *testing.T) {\n\treturn func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/400\", nil)\n\t\treq.Header.Set(\"Accept\", \"text/html\")\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 400, res.Code)\n\t\tassert.Equal(t, \"Accept\", res.Header().Get(\"Vary\"))\n\t\tassert.Equal(t, \"\", res.Header().Get(\"ETag\"))\n\t\tassert.Equal(t, \"text/html; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Contains(t, res.Body.String(), \"<title>Bad Request – 400</title>\", \"title\")\n\t\tassert.Contains(t, res.Body.String(), `<span class=\"status\">Bad Request</span>`, \"status text\")\n\t\tassert.Contains(t, res.Body.String(), `<span class=\"code\">400</span>`, \"status code\")\n\t}\n}\n\nfunc acceptsText(h http.Handler) func(t *testing.T) {\n\treturn func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/400\", nil)\n\t\treq.Header.Set(\"Accept\", \"text/*\")\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 400, res.Code)\n\t\tassert.Equal(t, \"Accept\", res.Header().Get(\"Vary\"))\n\t\tassert.Equal(t, \"\", res.Header().Get(\"ETag\"))\n\t\tassert.Equal(t, \"text/html; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Contains(t, res.Body.String(), \"<title>Bad Request – 400</title>\", \"title\")\n\t\tassert.Contains(t, res.Body.String(), `<span class=\"status\">Bad Request</span>`, \"status text\")\n\t\tassert.Contains(t, res.Body.String(), `<span class=\"code\">400</span>`, \"status code\")\n\t}\n}\n\nfunc doesNotAcceptHTML(h http.Handler) func(t *testing.T) {\n\treturn func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/400/json\", nil)\n\t\treq.Header.Set(\"Accept\", \"application/json\")\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 400, res.Code)\n\t\tassert.Equal(t, \"application/json\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, `{ \"error\": \"bad_request\" }`, res.Body.String())\n\t}\n}\n"
  },
  {
    "path": "http/errorpages/testdata/defaults/index.html",
    "content": "Index HTML\n"
  },
  {
    "path": "http/errorpages/testdata/defaults/up.json",
    "content": "{\n  \"name\": \"app\"\n}\n"
  },
  {
    "path": "http/errorpages/testdata/templates/404.html",
    "content": "Sorry! Can't find that.\n"
  },
  {
    "path": "http/errorpages/testdata/templates/5xx.html",
    "content": "{{.StatusCode}} – {{.StatusText}}\n"
  },
  {
    "path": "http/errorpages/testdata/templates/index.html",
    "content": "Index HTML\n"
  },
  {
    "path": "http/errorpages/testdata/templates/up.json",
    "content": "{\n  \"name\": \"app\"\n}\n"
  },
  {
    "path": "http/gzip/gzip.go",
    "content": "// Package gzip provides gzip compression support.\npackage gzip\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/NYTimes/gziphandler\"\n\n\t\"github.com/apex/up\"\n)\n\n// New gzip handler.\nfunc New(c *up.Config, next http.Handler) http.Handler {\n\treturn gziphandler.GzipHandler(next)\n}\n"
  },
  {
    "path": "http/gzip/gzip_test.go",
    "content": "package gzip\n\nimport (\n\t\"compress/gzip\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/tj/assert\"\n)\n\nvar body = strings.Repeat(\"так\", 5000)\n\nvar hello = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\tfmt.Fprint(w, body)\n})\n\nfunc TestGzip(t *testing.T) {\n\tc, err := up.ParseConfigString(`{ \"name\": \"app\" }`)\n\tassert.NoError(t, err, \"config\")\n\n\th := New(c, hello)\n\n\tt.Run(\"accepts gzip\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\t\treq.Header.Set(\"Accept-Encoding\", \"gzip\")\n\n\t\th.ServeHTTP(res, req)\n\n\t\theader := make(http.Header)\n\t\theader.Add(\"Content-Type\", \"text/plain; charset=utf-8\")\n\t\theader.Add(\"Content-Encoding\", \"gzip\")\n\t\theader.Add(\"Vary\", \"Accept-Encoding\")\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, header, res.HeaderMap)\n\n\t\tgz, err := gzip.NewReader(res.Body)\n\t\tassert.NoError(t, err, \"reader\")\n\n\t\tb, err := ioutil.ReadAll(gz)\n\t\tassert.NoError(t, err, \"reading\")\n\t\tassert.NoError(t, gz.Close(), \"close\")\n\n\t\tassert.Equal(t, body, string(b))\n\t})\n\n\tt.Run(\"accepts identity\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\theader := make(http.Header)\n\t\theader.Add(\"Content-Type\", \"text/plain; charset=utf-8\")\n\t\theader.Add(\"Vary\", \"Accept-Encoding\")\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, header, res.HeaderMap)\n\n\t\tassert.Equal(t, body, res.Body.String())\n\t})\n}\n"
  },
  {
    "path": "http/headers/headers.go",
    "content": "// Package headers provides header injection support.\npackage headers\n\nimport (\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/apex/log\"\n\t\"github.com/pkg/errors\"\n\thdr \"github.com/tj/go-headers\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/apex/up/internal/header\"\n)\n\n// TODO: document precedence and/or add options\n// TODO: maybe allow storing _headers in Static.Dir?\n\n// filename of headers file.\nvar filename = \"_headers\"\n\n// New headers handler.\nfunc New(c *up.Config, next http.Handler) (http.Handler, error) {\n\trulesFromFile, err := readFromFile(filename)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"reading header file\")\n\t}\n\n\trules, err := header.Compile(header.Merge(rulesFromFile, c.Headers))\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"compiling header\")\n\t}\n\n\tlog.Debugf(\"header rules from _headers file: %d\", len(rulesFromFile))\n\tlog.Debugf(\"header rules from up.json: %d\", len(c.Headers))\n\n\th := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tfields := rules.Lookup(r.URL.Path)\n\n\t\tfor k, v := range fields {\n\t\t\tw.Header().Set(k, v)\n\t\t}\n\n\t\tnext.ServeHTTP(w, r)\n\t})\n\n\treturn h, nil\n}\n\n// readFromFile reads from a Netlify style headers file.\nfunc readFromFile(path string) (header.Rules, error) {\n\trules := make(header.Rules)\n\n\tf, err := os.Open(path)\n\n\tif os.IsNotExist(err) {\n\t\treturn nil, nil\n\t}\n\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"opening headers file\")\n\t}\n\n\tdefer f.Close()\n\n\th, err := hdr.Parse(f)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"parsing\")\n\t}\n\n\tfor path, fields := range h {\n\t\trules[path] = make(header.Fields)\n\t\tfor name, vals := range fields {\n\t\t\trules[path][name] = vals[0]\n\t\t}\n\t}\n\n\treturn rules, nil\n}\n"
  },
  {
    "path": "http/headers/headers_test.go",
    "content": "package headers\n\nimport (\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/tj/assert\"\n\t\"github.com/apex/up\"\n\n\t\"github.com/apex/up/http/static\"\n\t\"github.com/apex/up/internal/header\"\n)\n\nfunc TestHeaders(t *testing.T) {\n\tos.Chdir(\"testdata\")\n\tdefer os.Chdir(\"..\")\n\n\tc := &up.Config{\n\t\tHeaders: header.Rules{\n\t\t\t\"/*.css\": {\n\t\t\t\t\"Cache-Control\": \"public, max-age=999999\",\n\t\t\t},\n\t\t},\n\t}\n\n\th, err := New(c, static.New(c))\n\tassert.NoError(t, err, \"init\")\n\n\tt.Run(\"mismatch\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"\", res.Header().Get(\"Cache-Control\"))\n\t\tassert.Equal(t, \"text/html; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, \"Index HTML\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"matched exact\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/style.css\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"public, max-age=999999\", res.Header().Get(\"Cache-Control\"))\n\t\tassert.Equal(t, \"css\", res.Header().Get(\"X-Type\"))\n\t\tassert.Equal(t, \"text/css; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, \"body { color: red }\\n\", res.Body.String())\n\t})\n}\n"
  },
  {
    "path": "http/headers/testdata/_headers",
    "content": "/*.css\n  X-Type: css\n"
  },
  {
    "path": "http/headers/testdata/index.html",
    "content": "Index HTML\n"
  },
  {
    "path": "http/headers/testdata/style.css",
    "content": "body { color: red }\n"
  },
  {
    "path": "http/headers/testdata/up.json",
    "content": "{\n  \"name\": \"app\"\n}\n"
  },
  {
    "path": "http/inject/inject.go",
    "content": "// Package inject provides script and style injection.\npackage inject\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/apex/up/internal/inject\"\n)\n\n// response wrapper.\ntype response struct {\n\thttp.ResponseWriter\n\trules  inject.Rules\n\tbody   bytes.Buffer\n\theader bool\n\tignore bool\n\tcode   int\n}\n\n// Write implementation.\nfunc (r *response) Write(b []byte) (int, error) {\n\tif !r.header {\n\t\tr.WriteHeader(200)\n\t\treturn r.Write(b)\n\t}\n\n\treturn r.body.Write(b)\n}\n\n// WriteHeader implementation.\nfunc (r *response) WriteHeader(code int) {\n\tr.header = true\n\tw := r.ResponseWriter\n\tkind := w.Header().Get(\"Content-Type\")\n\tr.ignore = !strings.HasPrefix(kind, \"text/html\") || code >= 300\n\tr.code = code\n}\n\n// end injects if necessary.\nfunc (r *response) end() {\n\tw := r.ResponseWriter\n\n\tif r.ignore {\n\t\tw.WriteHeader(r.code)\n\t\tr.body.WriteTo(w)\n\t\treturn\n\t}\n\n\tbody := r.rules.Apply(r.body.String())\n\tw.Header().Set(\"Content-Length\", strconv.Itoa(len(body)))\n\tio.WriteString(w, body)\n}\n\n// New inject handler.\nfunc New(c *up.Config, next http.Handler) (http.Handler, error) {\n\tif len(c.Inject) == 0 {\n\t\treturn next, nil\n\t}\n\n\th := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tres := &response{ResponseWriter: w, rules: c.Inject}\n\t\tnext.ServeHTTP(res, r)\n\t\tres.end()\n\t})\n\n\treturn h, nil\n}\n"
  },
  {
    "path": "http/inject/inject_test.go",
    "content": "package inject\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/tj/assert\"\n\n\t\"github.com/apex/up/config\"\n\t\"github.com/apex/up/http/errorpages\"\n\t\"github.com/apex/up/http/static\"\n\t\"github.com/apex/up/internal/inject\"\n)\n\nfunc TestInject(t *testing.T) {\n\tos.Chdir(\"testdata\")\n\tdefer os.Chdir(\"..\")\n\n\tc := &up.Config{\n\t\tName: \"app\",\n\t\tErrorPages: config.ErrorPages{\n\t\t\tEnable: true,\n\t\t},\n\t\tInject: inject.Rules{\n\t\t\t\"head\": []*inject.Rule{\n\t\t\t\t{\n\t\t\t\t\tType:  \"script\",\n\t\t\t\t\tValue: \"/whatever.js\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tassert.NoError(t, c.Default(), \"default\")\n\tassert.NoError(t, c.Validate(), \"validate\")\n\n\th, err := New(c, static.New(c))\n\tassert.NoError(t, err, \"init\")\n\n\th, err = errorpages.New(c, h)\n\tassert.NoError(t, err, \"init\")\n\n\tt.Run(\"2xx\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\thtml := `<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <script src=\"/whatever.js\"></script>\n  </head>\n  <body>\n\n  </body>\n</html>\n`\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"text/html; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, html, res.Body.String())\n\t})\n\n\tt.Run(\"4xx\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/missing\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 404, res.Code)\n\t\tassert.Equal(t, \"text/html; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, \"<p>Not Found</p>\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"non-html\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/style.css\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"text/css; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, \"body{}\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"write before header\", func(t *testing.T) {\n\t\ts := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Header().Set(\"Content-Type\", \"text/html\")\n\t\t\tio.WriteString(w, \"<html>\")\n\t\t\tio.WriteString(w, \"<head>\")\n\t\t\tio.WriteString(w, \"</head>\")\n\t\t\tio.WriteString(w, \"<body>\")\n\t\t\tio.WriteString(w, \"</body>\")\n\t\t\tio.WriteString(w, \"</html>\")\n\t\t})\n\n\t\th, err := New(c, s)\n\t\tassert.NoError(t, err, \"initialize\")\n\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"<html><head>  <script src=\\\"/whatever.js\\\"></script>\\n  </head><body></body></html>\", res.Body.String())\n\t})\n}\n"
  },
  {
    "path": "http/inject/testdata/404.html",
    "content": "<p>Not Found</p>\n"
  },
  {
    "path": "http/inject/testdata/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n  </head>\n  <body>\n\n  </body>\n</html>\n"
  },
  {
    "path": "http/inject/testdata/style.css",
    "content": "body{}\n"
  },
  {
    "path": "http/inject/testdata/up.json",
    "content": "{\n  \"name\": \"app\"\n}\n"
  },
  {
    "path": "http/logs/logs.go",
    "content": "// Package logs provides HTTP request and response logging.\npackage logs\n\nimport (\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/apex/log\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/apex/up/internal/logs\"\n\t\"github.com/apex/up/internal/util\"\n)\n\n// TODO: optional verbose mode with req/res header etc?\n\n// log context.\nvar ctx = logs.Plugin(\"logs\")\n\n// response wrapper.\ntype response struct {\n\thttp.ResponseWriter\n\twritten  int\n\tcode     int\n\tduration time.Duration\n}\n\n// Write implementation.\nfunc (r *response) Write(b []byte) (int, error) {\n\tn, err := r.ResponseWriter.Write(b)\n\tr.written += n\n\treturn n, err\n}\n\n// WriteHeader implementation.\nfunc (r *response) WriteHeader(code int) {\n\tr.code = code\n\tr.ResponseWriter.WriteHeader(code)\n}\n\n// New logs handler.\nfunc New(c *up.Config, next http.Handler) (http.Handler, error) {\n\tif c.Logs.Disable {\n\t\treturn next, nil\n\t}\n\n\th := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tctx := logContext(r)\n\t\tlogRequest(ctx, r)\n\n\t\tstart := time.Now()\n\t\tres := &response{ResponseWriter: w, code: 200}\n\t\tnext.ServeHTTP(res, r)\n\t\tres.duration = time.Since(start)\n\n\t\tlogResponse(ctx, res, r)\n\t})\n\n\treturn h, nil\n}\n\n// logContext returns the common log context for a request.\nfunc logContext(r *http.Request) log.Interface {\n\treturn ctx.WithFields(log.Fields{\n\t\t\"request_id\": r.Header.Get(\"X-Request-Id\"),\n\t\t\"method\":     r.Method,\n\t\t\"path\":       r.URL.Path,\n\t\t\"query\":      r.URL.Query().Encode(),\n\t\t\"ip\":         r.RemoteAddr,\n\t})\n}\n\n// logRequest logs the request.\nfunc logRequest(ctx log.Interface, r *http.Request) {\n\tif s := r.Header.Get(\"Content-Length\"); s != \"\" {\n\t\tn, err := strconv.Atoi(s)\n\t\tif err == nil {\n\t\t\tctx = ctx.WithField(\"size\", n)\n\t\t}\n\t}\n\n\tctx.Info(\"request\")\n}\n\n// logResponse logs the response.\nfunc logResponse(ctx log.Interface, res *response, r *http.Request) {\n\tctx = ctx.WithFields(log.Fields{\n\t\t\"duration\": util.Milliseconds(res.duration),\n\t\t\"size\":     res.written,\n\t\t\"status\":   res.code,\n\t})\n\n\tswitch {\n\tcase res.code >= 500:\n\t\tctx.Error(\"response\")\n\tcase res.code >= 400:\n\t\tctx.Warn(\"response\")\n\tdefault:\n\t\tctx.Info(\"response\")\n\t}\n}\n"
  },
  {
    "path": "http/logs/logs_test.go",
    "content": "package logs\n\nimport (\n\t\"bytes\"\n\t\"log\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/tj/assert\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/apex/up/config\"\n\t\"github.com/apex/up/http/static\"\n)\n\nfunc TestLogs(t *testing.T) {\n\t// TODO: refactor and pass in app name/version/region\n\n\tvar buf bytes.Buffer\n\tlog.SetOutput(&buf)\n\n\tc := &up.Config{\n\t\tStatic: config.Static{\n\t\t\tDir: \"testdata\",\n\t\t},\n\t}\n\n\th, err := New(c, static.New(c))\n\tassert.NoError(t, err)\n\n\tres := httptest.NewRecorder()\n\treq := httptest.NewRequest(\"GET\", \"/?foo=bar\", nil)\n\n\th.ServeHTTP(res, req)\n\n\tassert.Equal(t, 200, res.Code)\n\tassert.Equal(t, \"text/html; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\tassert.Equal(t, \"Index HTML\\n\", res.Body.String())\n\n\ts := buf.String()\n\tassert.Contains(t, s, `info response`)\n\t// assert.Contains(t, s, `app_name=api`)\n\t// assert.Contains(t, s, `app_version=5`)\n\t// assert.Contains(t, s, `app_region=us-west-2`)\n\tassert.Contains(t, s, `ip=192.0.2.1:1234`)\n\tassert.Contains(t, s, `method=GET`)\n\tassert.Contains(t, s, `path=/`)\n\tassert.Contains(t, s, `plugin=logs`)\n\tassert.Contains(t, s, `size=11`)\n\tassert.Contains(t, s, `status=200`)\n}\n"
  },
  {
    "path": "http/logs/testdata/index.html",
    "content": "Index HTML\n"
  },
  {
    "path": "http/logs/testdata/up.json",
    "content": "{\n  \"name\": \"api\"\n}\n"
  },
  {
    "path": "http/poweredby/poweredby.go",
    "content": "// Package poweredby provides nothing :).\npackage poweredby\n\nimport (\n\t\"net/http\"\n)\n\n// New powered-by middleware.\nfunc New(name string, next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"X-Powered-By\", name)\n\t\tnext.ServeHTTP(w, r)\n\t})\n}\n"
  },
  {
    "path": "http/poweredby/poweredby_test.go",
    "content": "package poweredby\n\nimport (\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/tj/assert\"\n\t\"github.com/apex/up\"\n\n\t\"github.com/apex/up/config\"\n\t\"github.com/apex/up/http/static\"\n)\n\nfunc TestPoweredby(t *testing.T) {\n\tc := &up.Config{\n\t\tStatic: config.Static{\n\t\t\tDir: \"testdata\",\n\t\t},\n\t}\n\n\th := New(\"up\", static.New(c))\n\n\tres := httptest.NewRecorder()\n\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\n\th.ServeHTTP(res, req)\n\n\tassert.Equal(t, 200, res.Code)\n\tassert.Equal(t, \"up\", res.Header().Get(\"X-Powered-By\"))\n\tassert.Equal(t, \"text/html; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\tassert.Equal(t, \"Index HTML\\n\", res.Body.String())\n}\n"
  },
  {
    "path": "http/poweredby/testdata/index.html",
    "content": "Index HTML\n"
  },
  {
    "path": "http/poweredby/testdata/up.json",
    "content": "{\n  \"name\": \"app\"\n}\n"
  },
  {
    "path": "http/redirects/redirects.go",
    "content": "// Package redirects provides redirection and URL rewriting.\npackage redirects\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/apex/log\"\n\t\"github.com/apex/up\"\n\t\"github.com/apex/up/internal/logs\"\n\t\"github.com/apex/up/internal/redirect\"\n)\n\n// TODO: tests for popagating 4xx / 5xx, dont mask all these\n// TODO: load _redirects relative to .Static.Dir?\n// TODO: add list of methods to match on\n\n// log context.\nvar ctx = logs.Plugin(\"redirects\")\n\ntype rewrite struct {\n\thttp.ResponseWriter\n\theader     bool\n\tisNotFound bool\n}\n\n// WriteHeader implementation.\nfunc (r *rewrite) WriteHeader(code int) {\n\tr.header = true\n\tr.isNotFound = code == 404\n\n\tif r.isNotFound {\n\t\treturn\n\t}\n\n\tr.ResponseWriter.WriteHeader(code)\n}\n\n// Write implementation.\nfunc (r *rewrite) Write(b []byte) (int, error) {\n\tif r.isNotFound {\n\t\treturn len(b), nil\n\t}\n\n\tif !r.header {\n\t\tr.WriteHeader(200)\n\t\treturn r.Write(b)\n\t}\n\n\treturn r.ResponseWriter.Write(b)\n}\n\n// New redirects handler.\nfunc New(c *up.Config, next http.Handler) (http.Handler, error) {\n\tif len(c.Redirects) == 0 {\n\t\treturn next, nil\n\t}\n\n\trules, err := redirect.Compile(c.Redirects)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\th := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\trule := rules.Lookup(r.URL.Path)\n\n\t\tctx := ctx.WithFields(log.Fields{\n\t\t\t\"path\": r.URL.Path,\n\t\t})\n\n\t\t// pass-through\n\t\tif rule == nil {\n\t\t\tctx.Debug(\"no match\")\n\t\t\tnext.ServeHTTP(w, r)\n\t\t\treturn\n\t\t}\n\n\t\t// destination path\n\t\tpath := rule.URL(r.URL.Path)\n\n\t\t// forced rewrite\n\t\tif rule.IsRewrite() && rule.Force {\n\t\t\tctx.WithField(\"dest\", path).Info(\"forced rewrite\")\n\t\t\tr.Header.Set(\"X-Original-Path\", r.URL.Path)\n\t\t\tr.URL.Path = path\n\t\t\tnext.ServeHTTP(w, r)\n\t\t\treturn\n\t\t}\n\n\t\t// rewrite\n\t\tif rule.IsRewrite() {\n\t\t\tres := &rewrite{ResponseWriter: w}\n\t\t\tnext.ServeHTTP(res, r)\n\n\t\t\tif res.isNotFound {\n\t\t\t\tctx.WithField(\"dest\", path).Info(\"rewrite\")\n\t\t\t\tr.Header.Set(\"X-Original-Path\", r.URL.Path)\n\t\t\t\tr.URL.Path = path\n\t\t\t\t// This hack is necessary for SPAs because the Go\n\t\t\t\t// static file server uses .html to set the correct mime,\n\t\t\t\t// ideally it uses the file's extension or magic number etc.\n\t\t\t\tw.Header().Set(\"Content-Type\", \"text/html; charset=utf-8\")\n\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\t// redirect\n\t\tctx.WithField(\"dest\", path).Info(\"redirect\")\n\t\tw.Header().Set(\"Location\", path)\n\t\tw.Header().Set(\"Content-Type\", \"text/plain; charset=utf-8\")\n\t\tw.WriteHeader(rule.Status)\n\t\tfmt.Fprintln(w, http.StatusText(rule.Status))\n\t})\n\n\treturn h, nil\n}\n"
  },
  {
    "path": "http/redirects/redirects_test.go",
    "content": "package redirects\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/tj/assert\"\n\n\t\"github.com/apex/up/internal/redirect\"\n)\n\nfunc TestRedirects(t *testing.T) {\n\tt.Run(\"from config\", func(t *testing.T) {\n\t\tc := &up.Config{\n\t\t\tRedirects: redirect.Rules{\n\t\t\t\t\"/blog\": {\n\t\t\t\t\tLocation: \"https://blog.apex.sh\",\n\t\t\t\t\tStatus:   301,\n\t\t\t\t},\n\t\t\t\t\"/enterprise\": {\n\t\t\t\t\tLocation: \"/docs/enterprise\",\n\t\t\t\t\tStatus:   302,\n\t\t\t\t},\n\t\t\t\t\"/api\": {\n\t\t\t\t\tLocation: \"/api/v1\",\n\t\t\t\t\tStatus:   200,\n\t\t\t\t},\n\t\t\t\t\"/products\": {\n\t\t\t\t\tLocation: \"/store\",\n\t\t\t\t\tStatus:   301,\n\t\t\t\t},\n\t\t\t\t\"/app/*\": {\n\t\t\t\t\tLocation: \"/\",\n\t\t\t\t},\n\t\t\t\t\"/app/login\": {\n\t\t\t\t\tLocation: \"https://app.apex.sh\",\n\t\t\t\t\tStatus:   301,\n\t\t\t\t},\n\t\t\t\t\"/documentation/:product/guides/:guide\": {\n\t\t\t\t\tLocation: \"/docs/:product/:guide\",\n\t\t\t\t\tStatus:   200,\n\t\t\t\t},\n\t\t\t\t\"/shop/:brand\": {\n\t\t\t\t\tLocation: \"/products/:brand\",\n\t\t\t\t\tStatus:   301,\n\t\t\t\t},\n\t\t\t\t\"/settings/*\": {\n\t\t\t\t\tLocation: \"/admin/:splat\",\n\t\t\t\t\tStatus:   200,\n\t\t\t\t\tForce:    true,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\thandle := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tswitch {\n\t\t\tcase r.URL.Path == \"/\":\n\t\t\t\tfmt.Fprintln(w, \"Index\")\n\t\t\tcase r.URL.Path == \"/api/v1\":\n\t\t\t\tfmt.Fprintln(w, \"API V1\")\n\t\t\tcase r.URL.Path == \"/products\":\n\t\t\t\tfmt.Fprintln(w, \"products\")\n\t\t\tcase strings.Contains(r.URL.Path, \"/docs\"):\n\t\t\t\tfmt.Fprintf(w, \"docs %s\", r.URL.Path)\n\t\t\tcase strings.HasPrefix(r.URL.Path, \"/brand\"):\n\t\t\t\tfmt.Fprintf(w, \"shop %s\", r.URL.Path)\n\t\t\tcase strings.HasPrefix(r.URL.Path, \"/setting\"):\n\t\t\t\tfmt.Fprintf(w, \"settings %s\", r.URL.Path)\n\t\t\tcase strings.HasPrefix(r.URL.Path, \"/admin\"):\n\t\t\t\tfmt.Fprintf(w, \"admin %s\", r.URL.Path)\n\t\t\tdefault:\n\t\t\t\thttp.NotFound(w, r)\n\t\t\t}\n\t\t})\n\n\t\th, err := New(c, handle)\n\t\tassert.NoError(t, err, \"init\")\n\n\t\ttest(t, h)\n\t})\n}\n\nfunc test(t *testing.T, h http.Handler) {\n\tos.Chdir(\"testdata\")\n\tdefer os.Chdir(\"..\")\n\n\tt.Run(\"mismatch\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"Index\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"exact match\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/blog\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 301, res.Code)\n\t\tassert.Equal(t, \"text/plain; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, \"Moved Permanently\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"exact match status\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/enterprise\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 302, res.Code)\n\t\tassert.Equal(t, \"/docs/enterprise\", res.Header().Get(\"Location\"))\n\t\tassert.Equal(t, \"text/plain; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, \"Found\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"exact match rewrite\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/api\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"/api\", req.Header.Get(\"X-Original-Path\"))\n\t\tassert.Empty(t, res.Header().Get(\"Location\"), \"location\")\n\t\tassert.Equal(t, \"API V1\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"shadowed exact\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/products\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 301, res.Code)\n\t\tassert.Equal(t, \"/store\", res.Header().Get(\"Location\"))\n\t\tassert.Equal(t, \"Moved Permanently\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"shadowed dynamic\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/app/contact\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"text/html; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, \"Index\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"match precedence\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/app/login\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 301, res.Code)\n\t\tassert.Equal(t, \"https://app.apex.sh\", res.Header().Get(\"Location\"))\n\t\tassert.Equal(t, \"Moved Permanently\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"rewrite with placeholders\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/documentation/ping/guides/alerting\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"docs /docs/ping/alerting\", res.Body.String())\n\t})\n\n\tt.Run(\"redirect with placeholders\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/shop/apple\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 301, res.Code)\n\t\tassert.Equal(t, \"Moved Permanently\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"forced rewrite\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/settings/login\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"admin /admin/login\", res.Body.String())\n\t})\n}\n"
  },
  {
    "path": "http/relay/relay.go",
    "content": "// Package relay provides a reverse proxy which\n// relays requests to your \"vanilla\" HTTP server,\n// and supports crash recovery.\npackage relay\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/apex/log\"\n\t\"github.com/facebookgo/freeport\"\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/apex/up/internal/logs\"\n\t\"github.com/apex/up/internal/logs/writer\"\n\t\"github.com/apex/up/internal/util\"\n)\n\n// log context.\nvar ctx = logs.Plugin(\"relay\")\n\n// Proxy is a reverse proxy and sub-process monitor\n// for ensuring your web server is running.\ntype Proxy struct {\n\tconfig *up.Config\n\n\t// transport used for the reverse proxy.\n\ttransport http.RoundTripper\n\n\t// stdout is the log writer for structured logging output.\n\tstdout *writer.Writer\n\n\t// stderr is the log writer for structured logging output.\n\tstderr *writer.Writer\n\n\tmu sync.Mutex\n\n\t// restarts is the restart count.\n\trestarts int\n\n\t// url is the active application url.\n\turl *url.URL\n\n\t// ReverseProxy is the reverse proxy making the requests to the app.\n\t*httputil.ReverseProxy\n\n\t// cmd is the current child process of the app.\n\tcmd *exec.Cmd\n}\n\n// New proxy.\n//\n// We want to buffer the cleanup channel so that we can bound the\n// number of concurrent processes executing, and prevent exhausting\n// the ulimits of the host OS.\nfunc New(c *up.Config) (http.Handler, error) {\n\tstdout, err := log.ParseLevel(c.Logs.Stdout)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"invalid stdout log level\")\n\t}\n\n\tstderr, err := log.ParseLevel(c.Logs.Stderr)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"invalid stderr log level\")\n\t}\n\n\ttimeout := time.Duration(c.Proxy.Timeout) * time.Second\n\n\tp := &Proxy{\n\t\tconfig:    c,\n\t\tstdout:    writer.New(stdout, ctx),\n\t\tstderr:    writer.New(stderr, ctx),\n\t\ttransport: newTransport(timeout),\n\t}\n\n\tif err := p.Start(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn p, nil\n}\n\n// Start the server.\nfunc (p *Proxy) Start() error {\n\tif err := p.startServer(); err != nil {\n\t\treturn err\n\t}\n\n\tp.ReverseProxy = httputil.NewSingleHostReverseProxy(p.url)\n\tp.ReverseProxy.Transport = p\n\n\tstart := time.Now()\n\ttimeout := time.Duration(p.config.Proxy.ListenTimeout) * time.Second\n\tctx.Info(\"waiting for app to listen on PORT\")\n\n\tif err := util.WaitForListen(p.url, timeout); err != nil {\n\t\treturn errors.Wrapf(err, \"waiting for %s to be in listening state\", p.url.String())\n\t}\n\n\tctx.WithField(\"duration\", util.MillisecondsSince(start)).Info(\"app listening\")\n\treturn nil\n}\n\n// Restart the server.\nfunc (p *Proxy) Restart() error {\n\tp.mu.Lock()\n\tdefer p.mu.Unlock()\n\n\tctx.Warn(\"restarting\")\n\tp.restarts++\n\n\tif p.cmd != nil {\n\t\tif err := p.cmd.Process.Kill(); err != nil {\n\t\t\tctx.WithError(err).Error(\"killing application process\")\n\t\t}\n\t}\n\n\tif err := p.Start(); err != nil {\n\t\treturn err\n\t}\n\n\tctx.WithField(\"restarts\", p.restarts).Warn(\"restarted\")\n\treturn nil\n}\n\n// RoundTrip implementation.\nfunc (p *Proxy) RoundTrip(r *http.Request) (*http.Response, error) {\n\tid := r.Header.Get(\"X-Request-Id\")\n\tctx = ctx.WithField(\"id\", id)\n\ttransport := p.transport\n\n\t// timeout header\n\tif s := r.Header.Get(\"X-Up-Timeout\"); s != \"\" {\n\t\tif n, err := strconv.ParseInt(s, 10, 64); err == nil {\n\t\t\ttransport = newTransport(time.Duration(n) * time.Second)\n\t\t}\n\t}\n\n\tres, err := transport.RoundTrip(r)\n\n\t// timeout error\n\tif e, ok := err.(net.Error); ok && e.Timeout() {\n\t\tctx.WithError(err).Warn(\"request timeout\")\n\t\treturn res, err\n\t}\n\n\t// temporary error\n\tif e, ok := err.(net.Error); ok && e.Temporary() {\n\t\tctx.WithError(err).Warn(\"request temporary error\")\n\t\treturn res, err\n\t}\n\n\t// network error\n\tif err != nil {\n\t\tctx.WithError(err).Error(\"request network error\")\n\t\tif err := p.Restart(); err != nil {\n\t\t\tctx.WithError(err).Error(\"restarting\")\n\t\t}\n\t}\n\n\treturn res, err\n}\n\n// environment returns the server env variables.\nfunc (p *Proxy) environment() []string {\n\treturn []string{\n\t\tenv(\"PORT\", p.url.Port()),\n\t\tenv(\"UP_RESTARTS\", p.restarts),\n\t}\n}\n\n// startServer the server on a free port.\nfunc (p *Proxy) startServer() error {\n\tport, err := freeport.Get()\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"getting free port\")\n\t}\n\n\ttarget, err := url.Parse(fmt.Sprintf(\"http://127.0.0.1:%d\", port))\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"parsing url\")\n\t}\n\n\tp.url = target\n\n\tctx.WithField(\"command\", p.config.Proxy.Command).WithField(\"PORT\", port).Info(\"starting app\")\n\tp.cmd = p.command(p.config.Proxy.Command, p.environment())\n\n\tif err := p.cmd.Start(); err != nil {\n\t\treturn errors.Wrap(err, \"running command\")\n\t}\n\n\tctx.Info(\"started app\")\n\treturn nil\n}\n\n// command returns the command for spawning a server.\nfunc (p *Proxy) command(s string, env []string) *exec.Cmd {\n\tcmd := exec.Command(\"sh\", \"-c\", s)\n\tcmd.Stdout = p.stdout\n\tcmd.Stderr = p.stderr\n\tcmd.Env = append(os.Environ(), append(env, \"PATH=node_modules/.bin:\"+os.Getenv(\"PATH\"))...)\n\treturn cmd\n}\n\n// newTransport returns a new http.Transport with the given timeout.\nfunc newTransport(timeout time.Duration) *http.Transport {\n\treturn &http.Transport{\n\t\tDialContext: (&net.Dialer{\n\t\t\tTimeout:   2 * time.Second,\n\t\t\tKeepAlive: 2 * time.Second,\n\t\t\tDualStack: true,\n\t\t}).DialContext,\n\t\tResponseHeaderTimeout: timeout,\n\t\tDisableKeepAlives:     true,\n\t}\n}\n\n// env returns an environment variable.\nfunc env(name string, val interface{}) string {\n\treturn fmt.Sprintf(\"%s=%v\", name, val)\n}\n"
  },
  {
    "path": "http/relay/relay_test.go",
    "content": "package relay\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/tj/assert\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/apex/up/config\"\n\t\"github.com/apex/up/internal/util\"\n)\n\nfunc skipCI(t testing.TB) {\n\tif util.IsCI() {\n\t\tt.SkipNow()\n\t}\n}\n\nfunc TestRelay(t *testing.T) {\n\tos.Chdir(\"testdata/basic\")\n\tdefer os.Chdir(\"../..\")\n\n\tc := &up.Config{\n\t\tProxy: config.Relay{\n\t\t\tTimeout:       2,\n\t\t\tListenTimeout: 2,\n\t\t},\n\t}\n\n\tassert.NoError(t, c.Default(), \"default\")\n\n\tvar h http.Handler\n\tnewHandler := func(t *testing.T) {\n\t\tv, err := New(c)\n\t\tassert.NoError(t, err, \"init\")\n\t\th = v\n\t}\n\n\tt.Run(\"GET simple\", func(t *testing.T) {\n\t\tnewHandler(t)\n\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/hello\", nil)\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"text/plain\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, \"Hello World\", res.Body.String())\n\t})\n\n\tt.Run(\"GET encoded path\", func(t *testing.T) {\n\t\tnewHandler(t)\n\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/echo/01BM82CJ9K1WK6EFJX8C1R4YH7/foo%20%25%20bar%20&%20baz%20=%20raz\", nil)\n\t\treq.Header.Set(\"Host\", \"example.com\")\n\t\treq.Header.Set(\"User-Agent\", \"tobi\")\n\t\th.ServeHTTP(res, req)\n\n\t\tbody := `{\n  \"header\": {\n    \"host\": \"example.com\",\n    \"user-agent\": \"tobi\",\n    \"x-forwarded-for\": \"192.0.2.1\",\n    \"accept-encoding\": \"gzip\",\n    \"connection\": \"close\"\n  },\n  \"url\": \"/echo/01BM82CJ9K1WK6EFJX8C1R4YH7/foo%20%25%20bar%20&%20baz%20=%20raz\",\n  \"body\": \"\"\n}`\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"application/json\", res.Header().Get(\"Content-Type\"))\n\t\tassertString(t, body, res.Body.String())\n\t})\n\n\tt.Run(\"POST simple\", func(t *testing.T) {\n\t\tnewHandler(t)\n\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"POST\", \"/echo/something\", strings.NewReader(\"Some body here\"))\n\t\th.ServeHTTP(res, req)\n\n\t\tbody := `{\n  \"header\": {\n    \"host\": \"example.com\",\n    \"content-length\": \"14\",\n    \"x-forwarded-for\": \"192.0.2.1\",\n    \"accept-encoding\": \"gzip\",\n    \"connection\": \"close\"\n  },\n  \"url\": \"/echo/something\",\n  \"body\": \"Some body here\"\n}`\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"application/json\", res.Header().Get(\"Content-Type\"))\n\t\tassertString(t, body, res.Body.String())\n\t})\n\n\tt.Run(\"crash\", func(t *testing.T) {\n\t\tnewHandler(t)\n\n\t\t// first\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/throw\", nil)\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 502, res.Code)\n\t\tassertString(t, \"\", res.Body.String())\n\n\t\t// wait for restart\n\t\ttime.Sleep(time.Second)\n\n\t\t// second\n\t\tres = httptest.NewRecorder()\n\t\treq = httptest.NewRequest(\"GET\", \"/hello\", nil)\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassertString(t, \"Hello World\", res.Body.String())\n\t})\n\n\tt.Run(\"timeout\", func(t *testing.T) {\n\t\tnewHandler(t)\n\n\t\t// first\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/timeout\", nil)\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 502, res.Code)\n\t\tassertString(t, \"\", res.Body.String())\n\n\t\t// second\n\t\tres = httptest.NewRecorder()\n\t\treq = httptest.NewRequest(\"GET\", \"/hello\", nil)\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassertString(t, \"Hello World\", res.Body.String())\n\t})\n\n\tt.Run(\"timeout header field\", func(t *testing.T) {\n\t\tnewHandler(t)\n\n\t\t// first\n\t\tstart := time.Now()\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/timeout\", nil)\n\t\treq.Header.Set(\"X-Up-Timeout\", \"1\")\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.True(t, time.Since(start) < time.Second*2)\n\t\tassert.Equal(t, 502, res.Code)\n\t\tassertString(t, \"\", res.Body.String())\n\n\t\t// second\n\t\tres = httptest.NewRecorder()\n\t\treq = httptest.NewRequest(\"GET\", \"/hello\", nil)\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassertString(t, \"Hello World\", res.Body.String())\n\t})\n}\n\nfunc assertString(t testing.TB, want, got string) {\n\tt.Helper()\n\tif want != got {\n\t\tt.Fatalf(\"\\nwant:\\n\\n%s\\n\\ngot:\\n\\n%s\\n\", want, got)\n\t}\n}\n"
  },
  {
    "path": "http/relay/testdata/basic/app.js",
    "content": "const http = require('http');\nconst url = require('url');\nconst qs = require('querystring');\nconst port = process.env.PORT;\n\nlet server;\n\nconst routes = {};\n\nroutes['/echo'] = (req, res) => {\n  const buffers = []\n  req.on('data', b => buffers.push(b))\n  req.on('end', _ => {\n    const body = Buffer.concat(buffers).toString()\n    res.setHeader('Content-Type', 'application/json')\n    res.end(JSON.stringify({\n      header: req.headers,\n      url: req.url,\n      body\n    }, null, 2))\n  });\n};\n\nroutes['/timeout'] = (req, res) => {\n  setTimeout(function(){\n    res.end('Hello')\n  }, 50000);\n};\n\nroutes['/throw'] = (req, res) => {\n  yaynode()\n};\n\nroutes['/exit'] = (req, res) => {\n  process.exit()\n};\n\nserver = http.createServer((req, res) => {\n  const r = Object.keys(routes).find(pattern => req.url.indexOf(pattern) === 0);\n  const handler = r && routes[r];\n  if (handler) {\n    handler(req, res);\n    return;\n  }\n\n  res.setHeader('Content-Type', 'text/plain')\n  res.end('Hello World')\n}).listen(port);\n"
  },
  {
    "path": "http/relay/testdata/basic/up.json",
    "content": "{\n  \"name\": \"app\"\n}\n"
  },
  {
    "path": "http/relay/testdata/node/package.json",
    "content": "{\n  \"scripts\": {\n    \"start\": \"node server\"\n  }\n}\n"
  },
  {
    "path": "http/relay/testdata/node/server.js",
    "content": "const http = require('http')\nconst port = parseInt(process.env.PORT, 10)\n\nhttp.createServer((req, res) => {\n  res.end('Node')\n}).listen(port)\n"
  },
  {
    "path": "http/relay/testdata/node/up.json",
    "content": "{\n  \"name\": \"app\"\n}\n"
  },
  {
    "path": "http/robots/robots.go",
    "content": "// Package robots provides a way of dealing with robots exclusion protocol\npackage robots\n\nimport (\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/apex/up\"\n)\n\n// New robots middleware.\nfunc New(c *up.Config, next http.Handler) http.Handler {\n\tif os.Getenv(\"UP_STAGE\") == \"production\" {\n\t\treturn next\n\t}\n\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"X-Robots-Tag\", \"none\")\n\t\tnext.ServeHTTP(w, r)\n\t})\n}\n"
  },
  {
    "path": "http/robots/robots_test.go",
    "content": "package robots\n\nimport (\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/tj/assert\"\n\n\t\"github.com/apex/up/config\"\n\t\"github.com/apex/up/http/static\"\n)\n\nfunc TestRobots(t *testing.T) {\n\tc := &up.Config{\n\t\tStatic: config.Static{\n\t\t\tDir: \"testdata\",\n\t\t},\n\t}\n\n\tt.Run(\"should set X-Robots-Tag\", func(t *testing.T) {\n\t\th := New(c, static.New(c))\n\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"none\", res.Header().Get(\"X-Robots-Tag\"))\n\t\tassert.Equal(t, \"text/html; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, \"Index HTML\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"should not set X-Robots-Tag for production stage\", func(t *testing.T) {\n\t\tos.Setenv(\"UP_STAGE\", \"production\")\n\t\tdefer os.Setenv(\"UP_STAGE\", \"\")\n\n\t\th := New(c, static.New(c))\n\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"\", res.Header().Get(\"X-Robots-Tag\"))\n\t\tassert.Equal(t, \"text/html; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, \"Index HTML\\n\", res.Body.String())\n\t})\n}\n"
  },
  {
    "path": "http/robots/testdata/index.html",
    "content": "Index HTML\n"
  },
  {
    "path": "http/robots/testdata/up.json",
    "content": "{\n  \"name\": \"app\"\n}\n"
  },
  {
    "path": "http/static/static.go",
    "content": "// Package static provides static file serving with HTTP cache support.\npackage static\n\nimport (\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/apex/up\"\n)\n\n// New static handler.\nfunc New(c *up.Config) http.Handler {\n\treturn http.FileServer(http.Dir(c.Static.Dir))\n}\n\n// NewDynamic static handler for dynamic apps.\nfunc NewDynamic(c *up.Config, next http.Handler) http.Handler {\n\tprefix := normalizePrefix(c.Static.Prefix)\n\tdir := c.Static.Dir\n\n\tif dir == \"\" {\n\t\treturn next\n\t}\n\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tvar skip bool\n\t\tpath := r.URL.Path\n\n\t\t// prefix\n\t\tif prefix != \"\" {\n\t\t\tif strings.HasPrefix(path, prefix) {\n\t\t\t\tpath = strings.Replace(path, prefix, \"/\", 1)\n\t\t\t} else {\n\t\t\t\tskip = true\n\t\t\t}\n\t\t}\n\n\t\t// convert\n\t\tpath = filepath.FromSlash(path)\n\n\t\t// file exists, serve it\n\t\tif !skip {\n\t\t\tfile := filepath.Join(dir, path)\n\t\t\tinfo, err := os.Stat(file)\n\t\t\tif !os.IsNotExist(err) && !info.IsDir() {\n\t\t\t\thttp.ServeFile(w, r, file)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\t// delegate\n\t\tnext.ServeHTTP(w, r)\n\t})\n}\n\n// normalizePrefix returns a prefix path normalized with leading and trailing \"/\".\nfunc normalizePrefix(s string) string {\n\tif !strings.HasPrefix(s, \"/\") {\n\t\ts = \"/\" + s\n\t}\n\n\tif !strings.HasSuffix(s, \"/\") {\n\t\ts = s + \"/\"\n\t}\n\n\treturn s\n}\n"
  },
  {
    "path": "http/static/static_test.go",
    "content": "package static\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/apex/up/config\"\n\t\"github.com/tj/assert\"\n)\n\nfunc TestStatic_defaults(t *testing.T) {\n\tos.Chdir(\"testdata/static\")\n\tdefer os.Chdir(\"../..\")\n\n\tc := &up.Config{Name: \"app\", Type: \"static\"}\n\tassert.NoError(t, c.Default(), \"default\")\n\tassert.NoError(t, c.Validate(), \"validate\")\n\ttest(t, c)\n}\n\nfunc TestStatic_dir(t *testing.T) {\n\tc := &up.Config{\n\t\tName: \"app\",\n\t\tType: \"static\",\n\t\tStatic: config.Static{\n\t\t\tDir: \"testdata/static\",\n\t\t},\n\t}\n\n\tassert.NoError(t, c.Default(), \"default\")\n\tassert.NoError(t, c.Validate(), \"validate\")\n\ttest(t, c)\n}\n\nfunc test(t *testing.T, c *up.Config) {\n\th := New(c)\n\n\tt.Run(\"index.html\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"text/html; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, \"Index HTML\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"file\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/style.css\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"text/css; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, \"body { background: whatever }\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"missing\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/notfound\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 404, res.Code)\n\t\tassert.Equal(t, \"text/plain; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, \"404 page not found\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"conditional get\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/style.css\", nil)\n\t\treq.Header.Set(\"If-Modified-Since\", \"Thu, 27 Jul 2030 03:30:31 GMT\")\n\t\th.ServeHTTP(res, req)\n\t\tassert.Equal(t, 304, res.Code)\n\t\tassert.Equal(t, \"\", res.Header().Get(\"Content-Length\"))\n\t\tassert.Equal(t, \"\", res.Body.String())\n\t})\n}\n\nfunc TestStatic_dynamic(t *testing.T) {\n\tc := &up.Config{\n\t\tName: \"app\",\n\t\tStatic: config.Static{\n\t\t\tDir: \"testdata/dynamic/public\",\n\t\t},\n\t}\n\n\tassert.NoError(t, c.Default(), \"default\")\n\tassert.NoError(t, c.Validate(), \"validate\")\n\n\th := NewDynamic(c, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprintln(w, \":)\")\n\t}))\n\n\tt.Run(\"file\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/css/style.css\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"text/css; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, \"body { background: whatever }\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"missing\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/notfound\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"text/plain; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, \":)\\n\", res.Body.String())\n\t})\n}\n\nfunc TestStatic_dynamicPrefix(t *testing.T) {\n\tc := &up.Config{\n\t\tName: \"app\",\n\t\tStatic: config.Static{\n\t\t\tDir:    \"testdata/dynamic/public\",\n\t\t\tPrefix: \"/public\",\n\t\t},\n\t}\n\n\tassert.NoError(t, c.Default(), \"default\")\n\tassert.NoError(t, c.Validate(), \"validate\")\n\n\th := NewDynamic(c, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprintln(w, \":)\")\n\t}))\n\n\tt.Run(\"/\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/index.html\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \":)\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"file\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/public/css/style.css\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \"text/css; charset=utf-8\", res.Header().Get(\"Content-Type\"))\n\t\tassert.Equal(t, \"body { background: whatever }\\n\", res.Body.String())\n\t})\n\n\tt.Run(\"missing\", func(t *testing.T) {\n\t\tres := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/public/notfound\", nil)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tassert.Equal(t, 200, res.Code)\n\t\tassert.Equal(t, \":)\\n\", res.Body.String())\n\t})\n}\n\nfunc TestNormalizePrefix(t *testing.T) {\n\tassert.Equal(t, `/public/`, normalizePrefix(`public`))\n\tassert.Equal(t, `/public/`, normalizePrefix(`public/`))\n\tassert.Equal(t, `/public/`, normalizePrefix(`/public`))\n\tassert.Equal(t, `/public/`, normalizePrefix(`/public/`))\n}\n"
  },
  {
    "path": "http/static/testdata/dynamic/app.js",
    "content": "const http = require('http')\nconst port = process.env.PORT\n\nhttp.createServer((req, res) => {\n  res.setHeader('X-Foo', 'bar')\n  res.setHeader('Content-Type', 'text/plain; charset=utf-8')\n  res.end('Hello World')\n}).listen(port)\n"
  },
  {
    "path": "http/static/testdata/dynamic/public/css/style.css",
    "content": "body { background: whatever }\n"
  },
  {
    "path": "http/static/testdata/dynamic/up.json",
    "content": "{\n  \"name\": \"app\"\n}\n"
  },
  {
    "path": "http/static/testdata/static/index.html",
    "content": "Index HTML\n"
  },
  {
    "path": "http/static/testdata/static/style.css",
    "content": "body { background: whatever }\n"
  },
  {
    "path": "http/static/testdata/static/up.json",
    "content": "{\n  \"name\": \"app\"\n}\n"
  },
  {
    "path": "install.sh",
    "content": "#!/bin/sh\nset -e\n#  Code generated by godownloader. DO NOT EDIT.\n#\n\nusage() {\n  this=$1\n  cat <<EOF\n$this: download go binaries for apex/up\n\nUsage: $this [-b] bindir [version]\n  -b sets bindir or installation directory, default \"/usr/local/bin\"\n   [version] is a version number from\n   https://github.com/apex/up/releases\n   If version is missing, then an attempt to find the latest will be found.\n\nGenerated by godownloader\n https://github.com/goreleaser/godownloader\n\nEOF\n  exit 2\n}\n\nparse_args() {\n  #BINDIR is /usr/local/bin unless set be ENV\n  # over-ridden by flag below\n\n  BINDIR=${BINDIR:-/usr/local/bin}\n  while getopts \"b:h?\" arg; do\n    case \"$arg\" in\n      b) BINDIR=\"$OPTARG\" ;;\n      h | \\?) usage \"$0\" ;;\n    esac\n  done\n  shift $((OPTIND - 1))\n  VERSION=$1\n}\n# this function wraps all the destructive operations\n# if a curl|bash cuts off the end of the script due to\n# network, either nothing will happen or will syntax error\n# out preventing half-done work\nexecute() {\n  TMPDIR=$(mktmpdir)\n  echo \"$PREFIX: downloading ${TARBALL_URL}\"\n  http_download \"${TMPDIR}/${TARBALL}\" \"${TARBALL_URL}\"\n\n  echo \"$PREFIX: verifying checksums\"\n  http_download \"${TMPDIR}/${CHECKSUM}\" \"${CHECKSUM_URL}\"\n  hash_sha256_verify \"${TMPDIR}/${TARBALL}\" \"${TMPDIR}/${CHECKSUM}\"\n\n  (cd \"${TMPDIR}\" && untar \"${TARBALL}\")\n  install -d \"${BINDIR}\"\n  install \"${TMPDIR}/${BINARY}\" \"${BINDIR}/\"\n  echo \"$PREFIX: installed as ${BINDIR}/${BINARY}\"\n}\nis_supported_platform() {\n  platform=$1\n  found=1\n  case \"$platform\" in\n    darwin/amd64) found=0 ;;\n    darwin/386) found=0 ;;\n    linux/amd64) found=0 ;;\n    linux/386) found=0 ;;\n    windows/amd64) found=0 ;;\n    windows/386) found=0 ;;\n    freebsd/amd64) found=0 ;;\n    freebsd/386) found=0 ;;\n    netbsd/amd64) found=0 ;;\n    netbsd/386) found=0 ;;\n    openbsd/amd64) found=0 ;;\n    openbsd/386) found=0 ;;\n  esac\n  return $found\n}\ncheck_platform() {\n  if is_supported_platform \"$PLATFORM\"; then\n    # optional logging goes here\n    true\n  else\n    echo \"${PREFIX}: platform $PLATFORM is not supported.  Make sure this script is up-to-date and file request at https://github.com/${PREFIX}/issues/new\"\n    exit 1\n  fi\n}\nadjust_version() {\n  if [ -z \"${VERSION}\" ]; then\n    echo \"$PREFIX: checking GitHub for latest version\"\n    VERSION=$(github_last_release \"$OWNER/$REPO\")\n  fi\n  # if version starts with 'v', remove it\n  VERSION=${VERSION#v}\n}\nadjust_format() {\n  # change format (tar.gz or zip) based on ARCH\n  true\n}\nadjust_os() {\n  # adjust archive name based on OS\n  true\n}\nadjust_arch() {\n  # adjust archive name based on ARCH\n  true\n}\n\ncat /dev/null <<EOF\n------------------------------------------------------------------------\nhttps://github.com/client9/shlib - portable posix shell functions\nPublic domain - http://unlicense.org\nhttps://github.com/client9/shlib/blob/master/LICENSE.md\nbut credit (and pull requests) appreciated.\n------------------------------------------------------------------------\nEOF\nis_command() {\n  command -v \"$1\" >/dev/null\n}\nuname_os() {\n  os=$(uname -s | tr '[:upper:]' '[:lower:]')\n  echo \"$os\"\n}\nuname_arch() {\n  arch=$(uname -m)\n  case $arch in\n    x86_64) arch=\"amd64\" ;;\n    x86) arch=\"386\" ;;\n    i686) arch=\"386\" ;;\n    i386) arch=\"386\" ;;\n    aarch64) arch=\"arm64\" ;;\n    armv5*) arch=\"arm5\" ;;\n    armv6*) arch=\"arm6\" ;;\n    armv7*) arch=\"arm7\" ;;\n  esac\n  echo ${arch}\n}\nuname_os_check() {\n  os=$(uname_os)\n  case \"$os\" in\n    darwin) return 0 ;;\n    dragonfly) return 0 ;;\n    freebsd) return 0 ;;\n    linux) return 0 ;;\n    android) return 0 ;;\n    nacl) return 0 ;;\n    netbsd) return 0 ;;\n    openbsd) return 0 ;;\n    plan9) return 0 ;;\n    solaris) return 0 ;;\n    windows) return 0 ;;\n  esac\n  echo \"$0: uname_os_check: internal error '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib\"\n  return 1\n}\nuname_arch_check() {\n  arch=$(uname_arch)\n  case \"$arch\" in\n    386) return 0 ;;\n    amd64) return 0 ;;\n    arm64) return 0 ;;\n    armv5) return 0 ;;\n    armv6) return 0 ;;\n    armv7) return 0 ;;\n    ppc64) return 0 ;;\n    ppc64le) return 0 ;;\n    mips) return 0 ;;\n    mipsle) return 0 ;;\n    mips64) return 0 ;;\n    mips64le) return 0 ;;\n    s390x) return 0 ;;\n    amd64p32) return 0 ;;\n  esac\n  echo \"$0: uname_arch_check: internal error '$(uname -m)' got converted to '$arch' which is not a GOARCH value.  Please file bug report at https://github.com/client9/shlib\"\n  return 1\n}\nuntar() {\n  tarball=$1\n  case \"${tarball}\" in\n    *.tar.gz | *.tgz) tar -xzf \"${tarball}\" ;;\n    *.tar) tar -xf \"${tarball}\" ;;\n    *.zip) unzip \"${tarball}\" ;;\n    *)\n      echo \"Unknown archive format for ${tarball}\"\n      return 1\n      ;;\n  esac\n}\nmktmpdir() {\n  test -z \"$TMPDIR\" && TMPDIR=\"$(mktemp -d)\"\n  mkdir -p \"${TMPDIR}\"\n  echo \"${TMPDIR}\"\n}\nhttp_download() {\n  local_file=$1\n  source_url=$2\n  header=$3\n  headerflag=''\n  destflag=''\n  if is_command curl; then\n    cmd='curl --fail -sSL'\n    destflag='-o'\n    headerflag='-H'\n  elif is_command wget; then\n    cmd='wget -q'\n    destflag='-O'\n    headerflag='--header'\n  else\n    echo \"http_download: unable to find wget or curl\"\n    return 1\n  fi\n  if [ -z \"$header\" ]; then\n    $cmd $destflag \"$local_file\" \"$source_url\"\n  else\n    $cmd $headerflag \"$header\" $destflag \"$local_file\" \"$source_url\"\n  fi\n}\ngithub_api() {\n  local_file=$1\n  source_url=$2\n  header=\"\"\n  case \"$source_url\" in\n    https://api.github.com*)\n      test -z \"$GITHUB_TOKEN\" || header=\"Authorization: token $GITHUB_TOKEN\"\n      ;;\n  esac\n  http_download \"$local_file\" \"$source_url\" \"$header\"\n}\ngithub_last_release() {\n  owner_repo=$1\n  giturl=\"https://api.github.com/repos/${owner_repo}/releases/latest\"\n  html=$(github_api - \"$giturl\")\n  version=$(echo \"$html\" | tr ',' '\\n' | grep -m 1 \"\\\"tag_name\\\":\" | cut -f4 -d'\"')\n  test -z \"$version\" && return 1\n  echo \"$version\"\n}\nhash_sha256() {\n  TARGET=${1:-/dev/stdin}\n  if is_command gsha256sum; then\n    hash=$(gsha256sum \"$TARGET\") || return 1\n    echo \"$hash\" | cut -d ' ' -f 1\n  elif is_command sha256sum; then\n    hash=$(sha256sum \"$TARGET\") || return 1\n    echo \"$hash\" | cut -d ' ' -f 1\n  elif is_command shasum; then\n    hash=$(shasum -a 256 \"$TARGET\" 2>/dev/null) || return 1\n    echo \"$hash\" | cut -d ' ' -f 1\n  elif is_command openssl; then\n    hash=$(openssl -dst openssl dgst -sha256 \"$TARGET\") || return 1\n    echo \"$hash\" | cut -d ' ' -f a\n  else\n    echo \"hash_sha256: unable to find command to compute sha-256 hash\"\n    return 1\n  fi\n}\nhash_sha256_verify() {\n  TARGET=$1\n  checksums=$2\n  if [ -z \"$checksums\" ]; then\n    echo \"hash_sha256_verify: checksum file not specified in arg2\"\n    return 1\n  fi\n  BASENAME=${TARGET##*/}\n  want=$(grep \"${BASENAME}\" \"${checksums}\" 2>/dev/null | tr '\\t' ' ' | cut -d ' ' -f 1)\n  if [ -z \"$want\" ]; then\n    echo \"hash_sha256_verify: unable to find checksum for '${TARGET}' in '${checksums}'\"\n    return 1\n  fi\n  got=$(hash_sha256 \"$TARGET\")\n  if [ \"$want\" != \"$got\" ]; then\n    echo \"hash_sha256_verify: checksum for '$TARGET' did not verify ${want} vs $got\"\n    return 1\n  fi\n}\ncat /dev/null <<EOF\n------------------------------------------------------------------------\nEnd of functions from https://github.com/client9/shlib\n------------------------------------------------------------------------\nEOF\n\nOWNER=apex\nREPO=up\nBINARY=up\nFORMAT=tar.gz\nOS=$(uname_os)\nARCH=$(uname_arch)\nPREFIX=\"$OWNER/$REPO\"\nPLATFORM=\"${OS}/${ARCH}\"\nGITHUB_DOWNLOAD=https://github.com/${OWNER}/${REPO}/releases/download\n\nuname_os_check \"$OS\"\nuname_arch_check \"$ARCH\"\n\nparse_args \"$@\"\n\ncheck_platform\n\nadjust_version\n\nadjust_format\n\nadjust_os\n\nadjust_arch\n\necho \"$PREFIX: found version ${VERSION} for ${OS}/${ARCH}\"\n\nNAME=${BINARY}_${VERSION}_${OS}_${ARCH}\nTARBALL=${NAME}.${FORMAT}\nTARBALL_URL=${GITHUB_DOWNLOAD}/v${VERSION}/${TARBALL}\nCHECKSUM=${BINARY}_${VERSION}_checksums.txt\nCHECKSUM_URL=${GITHUB_DOWNLOAD}/v${VERSION}/${CHECKSUM}\n\n# Adjust binary name if windows\nif [ \"$OS\" = \"windows\" ]; then\n  BINARY=\"${BINARY}.exe\"\nfi\n\nexecute\n"
  },
  {
    "path": "internal/account/account.go",
    "content": "package account\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n)\n\n// Error is a client error.\ntype Error struct {\n\tMessage string\n\tStatus  int\n}\n\n// Error implementation.\nfunc (e *Error) Error() string {\n\treturn e.Message\n}\n\n// Card model.\ntype Card struct {\n\tID       string `json:\"id\"`\n\tBrand    string `json:\"brand\"`\n\tLastFour string `json:\"last_four\"`\n}\n\n// CouponDuration is the coupon duration.\ntype CouponDuration string\n\n// Durations.\nconst (\n\tForever   CouponDuration = \"forever\"\n\tOnce                     = \"once\"\n\tRepeating                = \"repeating\"\n)\n\n// Coupon model.\ntype Coupon struct {\n\tID             string         `json:\"id\"`\n\tAmount         int            `json:\"amount\"`\n\tPercent        int            `json:\"percent\"`\n\tDuration       CouponDuration `json:\"duration\"`\n\tDurationPeriod int            `json:\"duration_period\"`\n}\n\n// Discount returns the final price from the given amount.\nfunc (c *Coupon) Discount(n int) int {\n\tif c.Amount != 0 {\n\t\treturn n - c.Amount\n\t}\n\n\treturn n - int(float64(n)*(float64(c.Percent)/100))\n}\n\n// Description returns a humanized description of the savings.\nfunc (c *Coupon) Description() (s string) {\n\tswitch {\n\tcase c.Amount != 0:\n\t\tn := fmt.Sprintf(\"%0.2f\", float64(c.Amount)/100)\n\t\ts += fmt.Sprintf(\"$%s off\", strings.Replace(n, \".00\", \"\", 1))\n\tcase c.Percent != 0:\n\t\ts += fmt.Sprintf(\"%d%% off\", c.Percent)\n\t}\n\n\tswitch c.Duration {\n\tcase Repeating:\n\t\ts += fmt.Sprintf(\" for %d months\", c.DurationPeriod)\n\tdefault:\n\t\ts += fmt.Sprintf(\" %s\", c.Duration)\n\t}\n\n\treturn s\n}\n\n// Discount model.\ntype Discount struct {\n\tCoupon Coupon `json:\"coupon\"`\n}\n\n// Plan model.\ntype Plan struct {\n\tID         string    `json:\"id\"`\n\tName       string    `json:\"name\"`\n\tProduct    string    `json:\"product\"`\n\tPlan       string    `json:\"plan\"`\n\tAmount     int       `json:\"amount\"`\n\tInterval   string    `json:\"interval\"`\n\tStatus     string    `json:\"status\"`\n\tCanceled   bool      `json:\"canceled\"`\n\tDiscount   *Discount `json:\"discount\"`\n\tCreatedAt  time.Time `json:\"created_at\"`\n\tCanceledAt time.Time `json:\"canceled_at\"`\n}\n\n// User model.\ntype User struct {\n\tEmail     string    `json:\"email\"`\n\tCreatedAt time.Time `json:\"created_at\"`\n}\n\n// Team model.\ntype Team struct {\n\tID        string    `json:\"id\"`\n\tName      string    `json:\"name\"`\n\tOwner     string    `json:\"owner\"`\n\tType      string    `json:\"type\"`\n\tCard      *Card     `json:\"card\"`\n\tMembers   []User    `json:\"members\"`\n\tInvites   []string  `json:\"invites\"`\n\tUpdatedAt time.Time `json:\"updated_at\"`\n\tCreatedAt time.Time `json:\"created_at\"`\n}\n\n// Client implementation.\ntype Client struct {\n\turl string\n}\n\n// New client.\nfunc New(url string) *Client {\n\treturn &Client{\n\t\turl: url,\n\t}\n}\n\n// GetCoupon by id.\nfunc (c *Client) GetCoupon(id string) (coupon *Coupon, err error) {\n\tres, err := c.request(\"\", \"GET\", \"/billing/coupons/\"+id, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer res.Body.Close()\n\n\tcoupon = new(Coupon)\n\terr = json.NewDecoder(res.Body).Decode(coupon)\n\treturn\n}\n\n// AddCard adds or updates the default card via stripe token.\nfunc (c *Client) AddCard(token, cardToken string) error {\n\tin := struct {\n\t\tToken string `json:\"token\"`\n\t}{\n\t\tToken: cardToken,\n\t}\n\n\tres, err := c.requestJSON(token, \"POST\", \"/billing/cards\", in)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer res.Body.Close()\n\n\treturn nil\n}\n\n// GetTeam returns the active team.\nfunc (c *Client) GetTeam(token string) (*Team, error) {\n\tres, err := c.request(token, \"GET\", \"/\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer res.Body.Close()\n\n\tvar t Team\n\treturn &t, json.NewDecoder(res.Body).Decode(&t)\n}\n\n// GetCards returns the user's cards.\nfunc (c *Client) GetCards(token string) (cards []Card, err error) {\n\tres, err := c.request(token, \"GET\", \"/billing/cards\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer res.Body.Close()\n\n\terr = json.NewDecoder(res.Body).Decode(&cards)\n\treturn\n}\n\n// GetPlans returns the user's plan(s).\nfunc (c *Client) GetPlans(token string) (plans []Plan, err error) {\n\tres, err := c.request(token, \"GET\", \"/billing/plans\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer res.Body.Close()\n\n\terr = json.NewDecoder(res.Body).Decode(&plans)\n\treturn\n}\n\n// RemoveCard removes a user's card by id.\nfunc (c *Client) RemoveCard(token, id string) error {\n\tres, err := c.request(token, \"DELETE\", \"/billing/cards/\"+id, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer res.Body.Close()\n\n\treturn nil\n}\n\n// AddPlan subscribes to plan.\nfunc (c *Client) AddPlan(token, product, interval, coupon string) error {\n\tin := struct {\n\t\tProduct  string `json:\"product\"`\n\t\tInterval string `json:\"interval\"`\n\t\tCoupon   string `json:\"coupon\"`\n\t}{\n\t\tProduct:  product,\n\t\tInterval: interval,\n\t\tCoupon:   coupon,\n\t}\n\n\tres, err := c.requestJSON(token, \"PUT\", \"/billing/plans\", in)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer res.Body.Close()\n\n\treturn nil\n}\n\n// AddTeam adds a new team.\nfunc (c *Client) AddTeam(token, id, name string) error {\n\tin := struct {\n\t\tID   string `json:\"id\"`\n\t\tName string `json:\"name\"`\n\t}{\n\t\tID:   id,\n\t\tName: name,\n\t}\n\n\tres, err := c.requestJSON(token, \"POST\", \"/\", in)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer res.Body.Close()\n\n\treturn nil\n}\n\n// AddInvite adds a team invitation.\nfunc (c *Client) AddInvite(token, email string) error {\n\tin := struct {\n\t\tEmail string `json:\"email\"`\n\t}{\n\t\tEmail: email,\n\t}\n\n\tres, err := c.requestJSON(token, \"POST\", \"/invites\", in)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer res.Body.Close()\n\n\treturn nil\n}\n\n// RemoveMember removes a team member or invitation if present.\nfunc (c *Client) RemoveMember(token, email string) error {\n\tin := struct {\n\t\tEmail string `json:\"email\"`\n\t}{\n\t\tEmail: email,\n\t}\n\n\tres, err := c.requestJSON(token, \"DELETE\", \"/member\", in)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer res.Body.Close()\n\n\treturn nil\n}\n\n// RemovePlan unsubscribes from a plan.\nfunc (c *Client) RemovePlan(token, product string) error {\n\tpath := fmt.Sprintf(\"/billing/plans/%s\", product)\n\tres, err := c.request(token, \"DELETE\", path, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer res.Body.Close()\n\n\treturn nil\n}\n\n// AddFeedback sends customer feedback.\nfunc (c *Client) AddFeedback(token, message string) error {\n\tin := struct {\n\t\tMessage string `json:\"message\"`\n\t}{\n\t\tMessage: message,\n\t}\n\n\tres, err := c.requestJSON(token, \"POST\", \"/feedback\", in)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer res.Body.Close()\n\n\treturn nil\n}\n\n// Login signs in the user.\nfunc (c *Client) Login(email, team string) (code string, err error) {\n\tin := struct {\n\t\tEmail string `json:\"email\"`\n\t\tTeam  string `json:\"team\"`\n\t}{\n\t\tEmail: email,\n\t\tTeam:  team,\n\t}\n\n\tres, err := c.requestJSON(\"\", \"POST\", \"/login\", in)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer res.Body.Close()\n\n\tvar out struct {\n\t\tCode string `json:\"code\"`\n\t}\n\n\terr = json.NewDecoder(res.Body).Decode(&out)\n\tcode = out.Code\n\treturn\n}\n\n// LoginWithToken signs in with the given email by\n// sending a verification email and returning\n// a code which can be exchanged for an access key.\n//\n// When an auth token is provided the user is already\n// authenticated, so this can be used to switch to\n// another team, if the user is a member or owner.\n//\n// The team id is optional, and may only be used when\n// the user's email has been invited to the team.\nfunc (c *Client) LoginWithToken(token, email, team string) (code string, err error) {\n\tin := struct {\n\t\tEmail string `json:\"email\"`\n\t\tTeam  string `json:\"team\"`\n\t}{\n\t\tEmail: email,\n\t\tTeam:  team,\n\t}\n\n\tres, err := c.requestJSON(token, \"POST\", \"/login\", in)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer res.Body.Close()\n\n\tvar out struct {\n\t\tCode string `json:\"code\"`\n\t}\n\n\terr = json.NewDecoder(res.Body).Decode(&out)\n\tcode = out.Code\n\treturn\n}\n\n// GetAccessToken with the given email, team, and code.\nfunc (c *Client) GetAccessToken(email, team, code string) (key string, err error) {\n\tin := struct {\n\t\tEmail string `json:\"email\"`\n\t\tTeam  string `json:\"team\"`\n\t\tCode  string `json:\"code\"`\n\t}{\n\t\tEmail: email,\n\t\tTeam:  team,\n\t\tCode:  code,\n\t}\n\n\tres, err := c.requestJSON(\"\", \"POST\", \"/access_token\", in)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer res.Body.Close()\n\n\tb, err := ioutil.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn string(b), nil\n}\n\n// PollAccessToken polls for an access token.\nfunc (c *Client) PollAccessToken(ctx context.Context, email, team, code string) (key string, err error) {\n\tkeyC := make(chan string, 1)\n\terrC := make(chan error, 1)\n\n\tgo func() {\n\t\tfor {\n\t\t\tkey, err = c.GetAccessToken(email, team, code)\n\n\t\t\tif err, ok := err.(*Error); ok && err.Status == http.StatusUnauthorized {\n\t\t\t\ttime.Sleep(5 * time.Second)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\terrC <- err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tkeyC <- key\n\t\t}\n\t}()\n\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn \"\", ctx.Err()\n\tcase e := <-errC:\n\t\treturn \"\", e\n\tcase k := <-keyC:\n\t\treturn k, nil\n\t}\n}\n\n// requestJSON helper.\nfunc (c *Client) requestJSON(token, method, path string, v interface{}) (*http.Response, error) {\n\tb, err := json.Marshal(v)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"marshaling\")\n\t}\n\n\treturn c.request(token, method, path, bytes.NewReader(b))\n}\n\n// request helper.\nfunc (c *Client) request(token, method, path string, body io.Reader) (*http.Response, error) {\n\treq, err := http.NewRequest(method, c.url+path, body)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"creating request\")\n\t}\n\n\tif body != nil {\n\t\treq.Header.Set(\"Content-Type\", \"application/json\")\n\t}\n\n\tif token != \"\" {\n\t\treq.Header.Set(\"Authorization\", \"Bearer \"+token)\n\t}\n\n\treq.Header.Set(\"Accept\", \"application/json\")\n\n\tres, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"requesting\")\n\t}\n\n\tif res.StatusCode >= 400 {\n\t\tb, _ := ioutil.ReadAll(res.Body)\n\t\tres.Body.Close()\n\t\treturn nil, &Error{\n\t\t\tMessage: strings.TrimSpace(string(b)),\n\t\t\tStatus:  res.StatusCode,\n\t\t}\n\t}\n\n\treturn res, nil\n}\n"
  },
  {
    "path": "internal/account/cards.go",
    "content": "package account\n\nimport (\n\t\"github.com/stripe/stripe-go\"\n\t\"github.com/tj/survey\"\n)\n\n// Questions.\nvar questions = []*survey.Question{\n\t{\n\t\tName:     \"name\",\n\t\tPrompt:   &survey.Input{Message: \"Name:\"},\n\t\tValidate: survey.Required,\n\t},\n\t{\n\t\tName:     \"number\",\n\t\tPrompt:   &survey.Input{Message: \"Number:\"},\n\t\tValidate: survey.Required,\n\t},\n\t{\n\t\tName:     \"cvc\",\n\t\tPrompt:   &survey.Input{Message: \"CVC:\"},\n\t\tValidate: survey.Required,\n\t},\n\t{\n\t\tName:     \"month\",\n\t\tPrompt:   &survey.Input{Message: \"Expiration month:\"},\n\t\tValidate: survey.Required,\n\t},\n\t{\n\t\tName:     \"year\",\n\t\tPrompt:   &survey.Input{Message: \"Expiration year:\"},\n\t\tValidate: survey.Required,\n\t},\n\t{\n\t\tName:     \"address1\",\n\t\tPrompt:   &survey.Input{Message: \"Street Address:\"},\n\t\tValidate: survey.Required,\n\t},\n\t{\n\t\tName:     \"city\",\n\t\tPrompt:   &survey.Input{Message: \"City:\"},\n\t\tValidate: survey.Required,\n\t},\n\t{\n\t\tName:     \"state\",\n\t\tPrompt:   &survey.Input{Message: \"State:\"},\n\t\tValidate: survey.Required,\n\t},\n\t{\n\t\tName:     \"country\",\n\t\tPrompt:   &survey.Input{Message: \"Country:\"},\n\t\tValidate: survey.Required,\n\t},\n\t{\n\t\tName:     \"zip\",\n\t\tPrompt:   &survey.Input{Message: \"Zip:\"},\n\t\tValidate: survey.Required,\n\t},\n}\n\n// PromptForCard displays an interactive form for the user to provide CC details.\nfunc PromptForCard() (card stripe.CardParams, err error) {\n\terr = survey.Ask(questions, &card)\n\treturn\n}\n"
  },
  {
    "path": "internal/cli/app/app.go",
    "content": "package app\n\nimport (\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/apex/up/internal/cli/root\"\n\n\t\"github.com/apex/up/internal/stats\"\n)\n\n// Run the command.\nfunc Run(version string) error {\n\tdefer stats.Client.ConditionalFlush(50, 6*time.Hour)\n\troot.Cmd.Version(version)\n\t_, err := root.Cmd.Parse(os.Args[1:])\n\treturn err\n}\n"
  },
  {
    "path": "internal/cli/build/build.go",
    "content": "package build\n\nimport (\n\t\"archive/zip\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"sort\"\n\n\t\"github.com/dustin/go-humanize\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/tj/go/term\"\n\t\"github.com/tj/kingpin\"\n\n\t\"github.com/apex/up/internal/cli/root\"\n\t\"github.com/apex/up/internal/colors\"\n\t\"github.com/apex/up/internal/stats\"\n\t\"github.com/apex/up/internal/util\"\n)\n\nfunc init() {\n\tcmd := root.Command(\"build\", \"Build zip file.\")\n\tcmd.Example(`up build`, \"Build archive and save to ./out.zip\")\n\tcmd.Example(`up build > /tmp/out.zip`, \"Build archive and output to file via stdout.\")\n\tcmd.Example(`up build --size`, \"Build archive and list files by size.\")\n\n\tstage := cmd.Flag(\"stage\", \"Target stage name.\").Short('s').Default(\"staging\").String()\n\tsize := cmd.Flag(\"size\", \"Show zip contents size information.\").Bool()\n\n\tcmd.Action(func(_ *kingpin.ParseContext) error {\n\t\tdefer util.Pad()()\n\n\t\t_, p, err := root.Init()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"initializing\")\n\t\t}\n\n\t\tstats.Track(\"Build\", nil)\n\n\t\tif err := p.Init(*stage); err != nil {\n\t\t\treturn errors.Wrap(err, \"initializing\")\n\t\t}\n\n\t\tif err := p.Build(true); err != nil {\n\t\t\treturn errors.Wrap(err, \"building\")\n\t\t}\n\n\t\tr, err := p.Zip()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"zip\")\n\t\t}\n\n\t\tvar out io.Writer\n\t\tvar buf bytes.Buffer\n\n\t\tswitch {\n\t\tdefault:\n\t\t\tout = os.Stdout\n\t\tcase *size:\n\t\t\tout = &buf\n\t\tcase term.IsTerminal(os.Stdout.Fd()):\n\t\t\tf, err := os.Create(\"out.zip\")\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrap(err, \"creating zip\")\n\t\t\t}\n\t\t\tdefer f.Close()\n\t\t\tout = f\n\t\t}\n\n\t\tif _, err := io.Copy(out, r); err != nil {\n\t\t\treturn errors.Wrap(err, \"copying\")\n\t\t}\n\n\t\tif *size {\n\t\t\tz, err := zip.NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrap(err, \"opening zip\")\n\t\t\t}\n\n\t\t\tfiles := z.File\n\n\t\t\tsort.Slice(files, func(i int, j int) bool {\n\t\t\t\ta := files[i]\n\t\t\t\tb := files[j]\n\t\t\t\treturn a.UncompressedSize64 > b.UncompressedSize64\n\t\t\t})\n\n\t\t\tfmt.Printf(\"\\n\")\n\t\t\tfor _, f := range files {\n\t\t\t\tsize := humanize.Bytes(f.UncompressedSize64)\n\t\t\t\tfmt.Printf(\"  %10s %s\\n\", size, colors.Purple(f.Name))\n\t\t\t}\n\t\t}\n\n\t\treturn err\n\t})\n}\n"
  },
  {
    "path": "internal/cli/config/config.go",
    "content": "package config\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\n\t\"github.com/apex/up/internal/cli/root\"\n\t\"github.com/apex/up/internal/stats\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/tj/kingpin\"\n)\n\nfunc init() {\n\tcmd := root.Command(\"config\", \"Show configuration after defaults and validation.\")\n\tcmd.Example(`up config`, \"Show the config.\")\n\n\tcmd.Action(func(_ *kingpin.ParseContext) error {\n\t\tc, _, err := root.Init()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"initializing\")\n\t\t}\n\n\t\tstats.Track(\"Show Config\", nil)\n\n\t\tenc := json.NewEncoder(os.Stdout)\n\t\tenc.SetIndent(\"\", \"  \")\n\t\tenc.Encode(c)\n\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "internal/cli/deploy/deploy.go",
    "content": "package deploy\n\nimport (\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/tj/go/git\"\n\t\"github.com/tj/go/term\"\n\t\"github.com/tj/kingpin\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/apex/up/internal/cli/root\"\n\t\"github.com/apex/up/internal/setup\"\n\t\"github.com/apex/up/internal/stats\"\n\t\"github.com/apex/up/internal/util\"\n\t\"github.com/apex/up/internal/validate\"\n)\n\nfunc init() {\n\tcmd := root.Command(\"deploy\", \"Deploy the project.\").Default()\n\tstage := cmd.Arg(\"stage\", \"Target stage name.\").Default(\"staging\").String()\n\tnoBuild := cmd.Flag(\"no-build\", \"Disable build related hooks.\").Bool()\n\n\tcmd.Example(`up deploy`, \"Deploy to the staging environment.\")\n\tcmd.Example(`up deploy production`, \"Deploy to the production environment.\")\n\tcmd.Example(`up deploy --no-build`, \"Skip build hooks, useful in CI when a separate build step is used.\")\n\n\tcmd.Action(func(_ *kingpin.ParseContext) error {\n\t\treturn deploy(*stage, !*noBuild)\n\t})\n}\n\nfunc deploy(stage string, build bool) error {\nretry:\n\tc, p, err := root.Init()\n\n\t// missing up.json non-interactive\n\tif isMissingConfig(err) && !term.IsTerminal(os.Stdin.Fd()) {\n\t\treturn errors.New(\"Cannot find ./up.json configuration file.\")\n\t}\n\n\t// missing up.json interactive\n\tif isMissingConfig(err) {\n\t\terr := setup.Create()\n\n\t\tif err == setup.ErrNoCredentials {\n\t\t\treturn errors.New(\"Cannot find credentials, visit https://apex.sh/docs/up/credentials/ for help.\")\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"setup\")\n\t\t}\n\n\t\tutil.Log(\"Deploying the project and creating resources.\")\n\t\tgoto retry\n\t}\n\n\t// unrelated error\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"initializing\")\n\t}\n\n\t// validate stage name\n\tif err := validate.List(stage, c.Stages.RemoteNames()); err != nil {\n\t\treturn err\n\t}\n\n\t// stage overrides\n\tif err := c.Override(stage); err != nil {\n\t\treturn errors.Wrap(err, \"overriding\")\n\t}\n\n\t// git information\n\tcommit, err := getCommit()\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"fetching git commit\")\n\t}\n\n\tdefer util.Pad()()\n\tstart := time.Now()\n\n\tif err := p.Init(stage); err != nil {\n\t\treturn errors.Wrap(err, \"initializing\")\n\t}\n\n\tif err := p.Deploy(up.Deploy{\n\t\tStage:  stage,\n\t\tCommit: util.StripLerna(commit.Describe()),\n\t\tAuthor: commit.Author.Name,\n\t\tBuild:  build,\n\t}); err != nil {\n\t\treturn err\n\t}\n\n\tstats.Track(\"Deploy\", map[string]interface{}{\n\t\t\"duration\":             util.MillisecondsSince(start),\n\t\t\"type\":                 c.Type,\n\t\t\"regions\":              c.Regions,\n\t\t\"stage\":                stage,\n\t\t\"proxy_timeout\":        c.Proxy.Timeout,\n\t\t\"header_rules_count\":   len(c.Headers),\n\t\t\"redirect_rules_count\": len(c.Redirects),\n\t\t\"inject_rules_count\":   len(c.Inject),\n\t\t\"environment_count\":    len(c.Environment),\n\t\t\"dns_zone_count\":       len(c.DNS.Zones),\n\t\t\"stage_count\":          len(c.Stages.List()),\n\t\t\"stage_domain_count\":   len(c.Stages.Domains()),\n\t\t\"lambda_memory\":        c.Lambda.Memory,\n\t\t\"has_cors\":             c.CORS != nil,\n\t\t\"has_logs\":             !c.Logs.Disable,\n\t\t\"has_profile\":          c.Profile != \"\",\n\t\t\"has_error_pages\":      c.ErrorPages.Enable,\n\t\t\"app_name_hash\":        util.Md5(c.Name),\n\t\t\"is_git\":               commit.Author.Name != \"\",\n\t})\n\n\tstats.Flush()\n\treturn nil\n}\n\n// isMissingConfig returns true if the error represents a missing up.json.\nfunc isMissingConfig(err error) bool {\n\terr = errors.Cause(err)\n\te, ok := err.(*os.PathError)\n\treturn ok && e.Path == \"up.json\"\n}\n\n// getCommit returns the git information when available.\nfunc getCommit() (git.Commit, error) {\n\tc, err := git.GetCommit(\".\", \"HEAD\")\n\tif err != nil && !isIgnorable(err) {\n\t\treturn git.Commit{}, err\n\t}\n\n\tif c == nil {\n\t\treturn git.Commit{}, nil\n\t}\n\n\treturn *c, nil\n}\n\n// isIgnorable returns true if the GIT error is ignorable.\nfunc isIgnorable(err error) bool {\n\tswitch err {\n\tcase git.ErrLookup, git.ErrNoRepo, git.ErrDirty:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n"
  },
  {
    "path": "internal/cli/disable-stats/disable-stats.go",
    "content": "package disablestats\n\nimport (\n\t\"github.com/pkg/errors\"\n\t\"github.com/tj/kingpin\"\n\n\t\"github.com/apex/up/internal/cli/root\"\n\t\"github.com/apex/up/internal/stats\"\n)\n\nfunc init() {\n\tcmd := root.Command(\"disable-stats\", \"Disable anonymized usage stats\").Hidden()\n\tcmd.Action(func(_ *kingpin.ParseContext) error {\n\t\terr := stats.Client.Disable()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"disabling\")\n\t\t}\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "internal/cli/docs/docs.go",
    "content": "package docs\n\nimport (\n\t\"github.com/pkg/browser\"\n\t\"github.com/tj/kingpin\"\n\n\t\"github.com/apex/up/internal/cli/root\"\n\t\"github.com/apex/up/internal/stats\"\n)\n\nvar url = \"https://up.docs.apex.sh\"\n\nfunc init() {\n\tcmd := root.Command(\"docs\", \"Open documentation website in the browser.\")\n\tcmd.Example(`up docs`, \"Open the documentation site.\")\n\n\tcmd.Action(func(_ *kingpin.ParseContext) error {\n\t\tstats.Track(\"Open Docs\", nil)\n\t\treturn browser.OpenURL(url)\n\t})\n}\n"
  },
  {
    "path": "internal/cli/domains/domains.go",
    "content": "package domains\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/tj/kingpin\"\n\t\"github.com/tj/survey\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/apex/up/internal/cli/root\"\n\t\"github.com/apex/up/internal/colors\"\n\t\"github.com/apex/up/internal/stats\"\n\t\"github.com/apex/up/internal/util\"\n\t\"github.com/apex/up/platform/aws/cost\"\n)\n\n// TODO: add ability to move up/down lines more like a form\n// TODO: add polling of registration status (it's async)\n// TODO: auto-fill these details from AWS account?\n\nfunc init() {\n\tcmd := root.Command(\"domains\", \"Manage domain names.\")\n\tcmd.Example(`up domains`, \"List purchased domains.\")\n\tcmd.Example(`up domains check example.com`, \"Check availability of a domain.\")\n\tcmd.Example(`up domains buy`, \"Purchase a domain.\")\n\tlist(cmd)\n\tcheck(cmd)\n\tbuy(cmd)\n}\n\n// USD.\nvar usd = colors.Gray(\"USD\")\n\n// Questions.\nvar questions = []*survey.Question{\n\t{\n\t\tName:     \"email\",\n\t\tPrompt:   &survey.Input{Message: \"Email:\"},\n\t\tValidate: validateEmail,\n\t},\n\t{\n\t\tName:     \"firstname\",\n\t\tPrompt:   &survey.Input{Message: \"First name:\"},\n\t\tValidate: survey.Required,\n\t},\n\t{\n\t\tName:     \"lastname\",\n\t\tPrompt:   &survey.Input{Message: \"Last name:\"},\n\t\tValidate: survey.Required,\n\t},\n\t{\n\t\tName:     \"countrycode\",\n\t\tPrompt:   &survey.Input{Message: \"Country code:\"},\n\t\tValidate: validateCountryCode,\n\t},\n\t{\n\t\tName:     \"city\",\n\t\tPrompt:   &survey.Input{Message: \"City:\"},\n\t\tValidate: survey.Required,\n\t},\n\t{\n\t\tName:     \"address\",\n\t\tPrompt:   &survey.Input{Message: \"Address:\"},\n\t\tValidate: survey.Required,\n\t},\n\t{\n\t\tName:     \"phonenumber\",\n\t\tPrompt:   &survey.Input{Message: \"Phone:\"},\n\t\tValidate: validatePhoneNumber,\n\t},\n\t{\n\t\tName:     \"state\",\n\t\tPrompt:   &survey.Input{Message: \"State:\"},\n\t\tValidate: survey.Required,\n\t},\n\t{\n\t\tName:     \"zipcode\",\n\t\tPrompt:   &survey.Input{Message: \"Zip code:\"},\n\t\tValidate: survey.Required,\n\t},\n}\n\n// buy a domain.\nfunc buy(cmd *kingpin.Cmd) {\n\tc := cmd.Command(\"buy\", \"Purchase a domain.\")\n\n\tc.Action(func(_ *kingpin.ParseContext) error {\n\t\tdefer util.Pad()()\n\n\t\t_, p, err := root.Init()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"initializing\")\n\t\t}\n\n\t\tvar domain string\n\t\tsurvey.AskOne(&survey.Input{\n\t\t\tMessage: \"Domain:\",\n\t\t}, &domain, survey.Required)\n\n\t\tvar contact up.DomainContact\n\n\t\tif err := survey.Ask(questions, &contact); err != nil {\n\t\t\treturn errors.Wrap(err, \"prompting\")\n\t\t}\n\n\t\tdomains := p.Domains()\n\t\tif err := domains.Purchase(domain, contact); err != nil {\n\t\t\treturn errors.Wrap(err, \"purchasing\")\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\n// check domain availability.\nfunc check(cmd *kingpin.Cmd) {\n\tc := cmd.Command(\"check\", \"Check availability of a domain.\")\n\tdomain := c.Arg(\"domain\", \"Domain name.\").Required().String()\n\n\tc.Action(func(_ *kingpin.ParseContext) error {\n\t\tdefer util.Pad()()\n\n\t\t_, p, err := root.Init()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"initializing\")\n\t\t}\n\n\t\tstats.Track(\"Check Domain Availability\", nil)\n\n\t\tdomains := p.Domains()\n\t\td, err := domains.Availability(*domain)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"fetching availability\")\n\t\t}\n\n\t\tstate := fmt.Sprintf(\"Domain %s is unavailable\", d.Name)\n\t\tif d.Available {\n\t\t\tstate = fmt.Sprintf(\"Domain %s is available for %s %s\", d.Name, cost.Domain(d.Name), usd)\n\t\t}\n\n\t\tfmt.Printf(\"  %s\\n\", colors.Bool(d.Available)(state))\n\n\t\tif !d.Available {\n\t\t\tfmt.Printf(\"\\n  Suggestions:\\n\")\n\n\t\t\tsuggestions, err := domains.Suggestions(*domain)\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrap(err, \"fetching suggestions\")\n\t\t\t}\n\n\t\t\tfmt.Printf(\"\\n\")\n\t\t\tfor _, d := range suggestions {\n\t\t\t\tprice := cost.Domain(d.Name)\n\t\t\t\tfmt.Printf(\"  %-40s %s %s\\n\", colors.Purple(d.Name), price, usd)\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\n// list domains purchased.\nfunc list(cmd *kingpin.Cmd) {\n\tc := cmd.Command(\"ls\", \"List purchased domains.\").Alias(\"list\").Default()\n\n\tc.Action(func(_ *kingpin.ParseContext) error {\n\t\tdefer util.Pad()()\n\n\t\t_, p, err := root.Init()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"initializing\")\n\t\t}\n\n\t\tstats.Track(\"List Domains\", nil)\n\n\t\tdomains, err := p.Domains().List()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"listing domains\")\n\t\t}\n\n\t\tfor _, d := range domains {\n\t\t\ts := \"expires\"\n\t\t\tif d.AutoRenew {\n\t\t\t\ts = \"renews\"\n\t\t\t}\n\t\t\tutil.LogName(d.Name, \"%s %s\", s, d.Expiry.Format(time.Stamp))\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\n// validateEmail returns an error if the input does not look like an email.\nfunc validateEmail(v interface{}) error {\n\ts := v.(string)\n\ti := strings.LastIndex(s, \"@\")\n\n\tif s == \"\" {\n\t\treturn errors.New(\"Email is required.\")\n\t}\n\n\tif i == -1 {\n\t\treturn errors.New(\"Email is missing '@'.\")\n\t}\n\n\tif i == len(s)-1 {\n\t\treturn errors.New(\"Email is missing domain.\")\n\t}\n\n\treturn nil\n}\n\n// validateCountryCode returns an error if the input does not look like a valid country code.\nfunc validateCountryCode(v interface{}) error {\n\ts := v.(string)\n\n\tif s == \"\" {\n\t\treturn errors.New(\"Country code is required.\")\n\t}\n\n\tif len(s) != 2 {\n\t\treturn errors.New(\"Country codes must consist of two uppercase letters, such as CA or AU.\")\n\t}\n\n\treturn nil\n}\n\n// validatePhoneNumber returns an error if the input does not look like a valid phone number.\nfunc validatePhoneNumber(v interface{}) error {\n\ts := v.(string)\n\n\tif s == \"\" {\n\t\treturn errors.New(\"Phone number is required.\")\n\t}\n\n\tif !strings.HasPrefix(s, \"+\") {\n\t\treturn errors.New(\"Phone number must contain the country code, for example +1.2223334444 for Canada.\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/cli/logs/logs.go",
    "content": "package logs\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/tj/go/term\"\n\t\"github.com/tj/kingpin\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/apex/up/internal/cli/root\"\n\t\"github.com/apex/up/internal/stats\"\n\t\"github.com/apex/up/internal/util\"\n)\n\nfunc init() {\n\tcmd := root.Command(\"logs\", \"Show log output.\")\n\tcmd.Example(`up logs`, \"Show logs from the past hour.\")\n\tcmd.Example(`up logs -S 30m`, \"Show logs from the past 30 minutes.\")\n\tcmd.Example(`up logs -S 5h`, \"Show logs from the past 5 hours.\")\n\tcmd.Example(`up logs -f`, \"Show live log output.\")\n\tcmd.Example(`up logs error`, \"Show error logs.\")\n\tcmd.Example(`up logs 'level != \"info\"'`, \"Show non-info logs.\")\n\tcmd.Example(`up logs 'production (warn or error)'`, \"Show 4xx and 5xx responses in production.\")\n\tcmd.Example(`up logs 'production error method in (\"POST\", \"PUT\", \"DELETE\")'`, \"Show production 5xx responses with a POST, PUT, or DELETE method.\")\n\tcmd.Example(`up logs 'error or fatal'`, \"Show error and fatal logs.\")\n\tcmd.Example(`up logs 'message = \"user login\"'`, \"Show logs with a specific message.\")\n\tcmd.Example(`up logs 'status = 200 duration > 1.5s'`, \"Show 200 responses with latency above 1500ms.\")\n\tcmd.Example(`up logs 'size > 100kb'`, \"Show responses with bodies larger than 100kb.\")\n\tcmd.Example(`up logs 'status >= 400'`, \"Show 4xx and 5xx responses.\")\n\tcmd.Example(`up logs 'user.email contains \"@apex.sh\"'`, \"Show emails containing @apex.sh.\")\n\tcmd.Example(`up logs 'user.email = \"*@apex.sh\"'`, \"Show emails ending with @apex.sh.\")\n\tcmd.Example(`up logs 'user.email = \"tj@*\"'`, \"Show emails starting with tj@.\")\n\tcmd.Example(`up logs 'method in (\"POST\", \"PUT\") ip = \"207.*\" status = 200 duration >= 50'`, \"Show logs with a more complex query.\")\n\tcmd.Example(`up logs error | jq`, \"Pipe JSON error logs to the jq tool.\")\n\n\tquery := cmd.Arg(\"query\", \"Query pattern for filtering logs.\").String()\n\tfollow := cmd.Flag(\"follow\", \"Follow or tail the live logs.\").Short('f').Bool()\n\tsince := cmd.Flag(\"since\", \"Show logs since duration (30s, 5m, 2h, 1h30m, 3d, 1M).\").Short('S').Default(\"1d\").String()\n\texpand := cmd.Flag(\"expand\", \"Show expanded logs.\").Short('e').Bool()\n\n\tcmd.Action(func(_ *kingpin.ParseContext) error {\n\t\tc, p, err := root.Init()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"initializing\")\n\t\t}\n\n\t\tvar s time.Duration\n\n\t\tif *since != \"\" {\n\t\t\ts, err = util.ParseDuration(*since)\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrap(err, \"parsing --since duration\")\n\t\t\t}\n\t\t}\n\n\t\tif *follow {\n\t\t\ts = time.Duration(0)\n\t\t}\n\n\t\tq := *query\n\n\t\tstats.Track(\"Logs\", map[string]interface{}{\n\t\t\t\"query\":        q != \"\",\n\t\t\t\"query_length\": len(q),\n\t\t\t\"follow\":       *follow,\n\t\t\t\"since\":        s.Round(time.Second),\n\t\t\t\"expand\":       *expand,\n\t\t})\n\n\t\tlogs := p.Logs(up.LogsConfig{\n\t\t\tRegion:     c.Regions[0],\n\t\t\tSince:      time.Now().Add(-s),\n\t\t\tFollow:     *follow,\n\t\t\tExpand:     *expand,\n\t\t\tQuery:      q,\n\t\t\tOutputJSON: !term.IsTerminal(os.Stdout.Fd()),\n\t\t})\n\n\t\tif _, err := io.Copy(os.Stdout, logs); err != nil {\n\t\t\treturn errors.Wrap(err, \"writing logs\")\n\t\t}\n\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "internal/cli/metrics/metrics.go",
    "content": "package metrics\n\nimport (\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/tj/kingpin\"\n\n\t\"github.com/apex/up/internal/cli/root\"\n\t\"github.com/apex/up/internal/stats\"\n\t\"github.com/apex/up/internal/util\"\n)\n\nfunc init() {\n\tcmd := root.Command(\"metrics\", \"Show project metrics.\")\n\tcmd.Example(`up metrics`, \"Show metrics for staging environment.\")\n\tcmd.Example(`up metrics -s production`, \"Show metrics for production environment.\")\n\n\tstage := cmd.Flag(\"stage\", \"Target stage name.\").Short('s').Default(\"staging\").String()\n\tsince := cmd.Flag(\"since\", \"Show metrics since duration (30s, 5m, 2h, 1h30m, 3d, 1M).\").Short('S').Default(\"1M\").String()\n\n\tcmd.Action(func(_ *kingpin.ParseContext) error {\n\t\tc, p, err := root.Init()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"initializing\")\n\t\t}\n\n\t\ts, err := util.ParseDuration(*since)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"parsing --since duration\")\n\t\t}\n\n\t\tregion := c.Regions[0]\n\n\t\tstats.Track(\"Metrics\", map[string]interface{}{\n\t\t\t\"stage\": *stage,\n\t\t\t\"since\": s.Round(time.Second),\n\t\t})\n\n\t\tstart := time.Now().UTC().Add(-s)\n\t\treturn p.ShowMetrics(region, *stage, start)\n\t})\n}\n"
  },
  {
    "path": "internal/cli/prune/prune.go",
    "content": "package prune\n\nimport (\n\t\"github.com/pkg/errors\"\n\t\"github.com/tj/kingpin\"\n\n\t\"github.com/apex/up/internal/cli/root\"\n\t\"github.com/apex/up/internal/stats\"\n)\n\nfunc init() {\n\tcmd := root.Command(\"prune\", \"Prune old S3 deployments of a stage.\")\n\n\tcmd.Example(`up prune`, \"Prune and retain the most recent 30 staging versions.\")\n\tcmd.Example(`up prune -s production`, \"Prune and retain the most recent 30 production versions.\")\n\tcmd.Example(`up prune -s production -r 15`, \"Prune and retain the most recent 15 production versions.\")\n\n\tstage := cmd.Flag(\"stage\", \"Target stage name.\").Short('s').Default(\"staging\").String()\n\tversions := cmd.Flag(\"retain\", \"Number of versions to retain.\").Short('r').Default(\"30\").Int()\n\n\tcmd.Action(func(_ *kingpin.ParseContext) error {\n\t\tc, p, err := root.Init()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"initializing\")\n\t\t}\n\n\t\tregion := c.Regions[0]\n\n\t\tstats.Track(\"Prune\", map[string]interface{}{\n\t\t\t\"versions\": *versions,\n\t\t\t\"stage\":    *stage,\n\t\t})\n\n\t\treturn p.Prune(region, *stage, *versions)\n\t})\n}\n"
  },
  {
    "path": "internal/cli/root/root.go",
    "content": "package root\n\nimport (\n\t\"os\"\n\t\"runtime\"\n\n\t\"github.com/apex/log\"\n\t\"github.com/apex/log/handlers/cli\"\n\t\"github.com/apex/log/handlers/delta\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/tj/kingpin\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/apex/up/internal/util\"\n\t\"github.com/apex/up/platform/event\"\n\t\"github.com/apex/up/platform/lambda\"\n\t\"github.com/apex/up/reporter\"\n)\n\n// Cmd is the root command.\nvar Cmd = kingpin.New(\"up\", \"\")\n\n// Command registers a command.\nvar Command = Cmd.Command\n\n// Init function.\nvar Init func() (*up.Config, *up.Project, error)\n\nfunc init() {\n\tlog.SetHandler(cli.Default)\n\n\tCmd.Example(`up`, \"Deploy the project to the staging environment.\")\n\tCmd.Example(`up deploy production`, \"Deploy the project to the production stage.\")\n\tCmd.Example(`up url`, \"Show the staging endpoint url.\")\n\tCmd.Example(`up logs -f`, \"Tail project logs.\")\n\tCmd.Example(`up logs 'error or fatal'`, \"Show error or fatal level logs.\")\n\tCmd.Example(`up run build`, \"Run build command manually.\")\n\tCmd.Example(`up help team`, \"Show help and examples for a command.\")\n\tCmd.Example(`up help team members`, \"Show help and examples for a sub-command.\")\n\n\tworkdir := Cmd.Flag(\"chdir\", \"Change working directory.\").Default(\".\").Short('C').String()\n\tverbose := Cmd.Flag(\"verbose\", \"Enable verbose log output.\").Short('v').Bool()\n\tformat := Cmd.Flag(\"format\", \"Output formatter.\").Default(\"text\").String()\n\tregion := Cmd.Flag(\"region\", \"Target region id.\").String()\n\n\tCmd.PreAction(func(ctx *kingpin.ParseContext) error {\n\t\tos.Chdir(*workdir)\n\n\t\tif *verbose {\n\t\t\tlog.SetHandler(delta.Default)\n\t\t\tlog.SetLevel(log.DebugLevel)\n\t\t\tlog.Debugf(\"up version %s (os: %s, arch: %s)\", Cmd.GetVersion(), runtime.GOOS, runtime.GOARCH)\n\t\t}\n\n\t\tInit = func() (*up.Config, *up.Project, error) {\n\t\t\tc, err := up.ReadConfig(\"up.json\")\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, errors.Wrap(err, \"reading config\")\n\t\t\t}\n\n\t\t\tif *region != \"\" {\n\t\t\t\tc.Regions = []string{*region}\n\t\t\t}\n\n\t\t\tevents := make(event.Events)\n\t\t\tp := up.New(c, events).WithPlatform(lambda.New(c, events))\n\n\t\t\tswitch {\n\t\t\tcase *verbose:\n\t\t\t\tgo reporter.Discard(events)\n\t\t\tcase *format == \"plain\" || util.IsCI():\n\t\t\t\tgo reporter.Plain(events)\n\t\t\tdefault:\n\t\t\t\tgo reporter.Text(events)\n\t\t\t}\n\n\t\t\treturn c, p, nil\n\t\t}\n\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "internal/cli/run/run.go",
    "content": "package run\n\nimport (\n\t\"github.com/apex/up/internal/cli/root\"\n\t\"github.com/apex/up/internal/stats\"\n\t\"github.com/apex/up/internal/util\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/tj/kingpin\"\n)\n\nfunc init() {\n\tcmd := root.Command(\"run\", \"Run a hook.\")\n\tcmd.Example(`up run build`, \"Run build hook.\")\n\tcmd.Example(`up run clean`, \"Run clean hook.\")\n\n\thook := cmd.Arg(\"hook\", \"Name of the hook to run.\").Required().String()\n\tstage := cmd.Flag(\"stage\", \"Target stage name.\").Short('s').Default(\"staging\").String()\n\n\tcmd.Action(func(_ *kingpin.ParseContext) error {\n\t\t_, p, err := root.Init()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"initializing\")\n\t\t}\n\n\t\tdefer util.Pad()()\n\n\t\tstats.Track(\"Hook\", map[string]interface{}{\n\t\t\t\"name\": *hook,\n\t\t})\n\n\t\tif err := p.Init(*stage); err != nil {\n\t\t\treturn errors.Wrap(err, \"initializing\")\n\t\t}\n\n\t\treturn p.RunHook(*hook)\n\t})\n}\n"
  },
  {
    "path": "internal/cli/stack/stack.go",
    "content": "package stack\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/tj/kingpin\"\n\t\"github.com/tj/survey\"\n\n\t\"github.com/apex/up/internal/cli/root\"\n\t\"github.com/apex/up/internal/stats\"\n\t\"github.com/apex/up/internal/util\"\n)\n\nfunc init() {\n\tcmd := root.Command(\"stack\", \"Stack resource management.\")\n\n\tcmd.Example(`up stack`, \"Show status of the stack resources.\")\n\tcmd.Example(`up stack plan`, \"Show resource changes.\")\n\tcmd.Example(`up stack apply`, \"Apply resource changes.\")\n\tcmd.Example(`up stack delete`, \"Delete the stack resources.\")\n\n\tplan(cmd)\n\tapply(cmd)\n\tdelete(cmd)\n\tstatus(cmd)\n}\n\n// plan changes.\nfunc plan(cmd *kingpin.Cmd) {\n\tc := cmd.Command(\"plan\", \"Plan configuration changes.\")\n\tc.Example(`up stack plan`, \"Show changes planned.\")\n\n\tc.Action(func(_ *kingpin.ParseContext) error {\n\t\tc, p, err := root.Init()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"initializing\")\n\t\t}\n\n\t\tstats.Track(\"Plan Stack\", nil)\n\t\tregion := c.Regions[0]\n\n\t\tok, err := p.Exists(region)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"checking if app exists\")\n\t\t}\n\n\t\tif !ok {\n\t\t\treturn errors.New(\"Application does not exist, please run `$ up` initially to create it.\")\n\t\t}\n\n\t\t// TODO: multi-region\n\t\treturn p.PlanStack(region)\n\t})\n}\n\n// apply changes.\nfunc apply(cmd *kingpin.Cmd) {\n\tc := cmd.Command(\"apply\", \"Apply configuration changes.\")\n\tc.Example(`up stack apply`, \"Apply the changes of the previous plan.\")\n\n\tc.Action(func(_ *kingpin.ParseContext) error {\n\t\tc, p, err := root.Init()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"initializing\")\n\t\t}\n\n\t\tstats.Track(\"Apply Stack\", map[string]interface{}{\n\t\t\t\"dns_zone_count\":     len(c.DNS.Zones),\n\t\t\t\"stage_count\":        len(c.Stages.List()),\n\t\t\t\"stage_domain_count\": len(c.Stages.Domains()),\n\t\t})\n\n\t\t// TODO: multi-region\n\t\treturn p.ApplyStack(c.Regions[0])\n\t})\n}\n\n// delete resources.\nfunc delete(cmd *kingpin.Cmd) {\n\tc := cmd.Command(\"delete\", \"Delete configured resources.\")\n\tc.Example(`up stack delete`, \"Delete stack with confirmation prompt.\")\n\tc.Example(`up stack delete --force`, \"Delete stack without confirmation prompt.\")\n\tc.Example(`up stack delete --async`, \"Don't wait for deletion to complete.\")\n\tc.Example(`up stack delete -fa`, \"Force asynchronous deletion.\")\n\n\tforce := c.Flag(\"force\", \"Skip the confirmation prompt.\").Short('f').Bool()\n\tasync := c.Flag(\"async\", \"Perform deletion asynchronously.\").Short('a').Bool()\n\n\tc.Action(func(_ *kingpin.ParseContext) error {\n\t\tc, p, err := root.Init()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"initializing\")\n\t\t}\n\n\t\twait := !*async\n\t\tdefer util.Pad()()\n\n\t\tstats.Track(\"Delete Stack\", map[string]interface{}{\n\t\t\t\"force\": *force,\n\t\t\t\"wait\":  wait,\n\t\t})\n\n\t\tif *force {\n\t\t\t// TODO: multi-region\n\t\t\treturn p.DeleteStack(c.Regions[0], wait)\n\t\t}\n\n\t\tprompt := &survey.Confirm{\n\t\t\tMessage: fmt.Sprintf(\"Really destroy stack %q?\", c.Name),\n\t\t}\n\n\t\tvar ok bool\n\t\tif err := survey.AskOne(prompt, &ok, nil); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif !ok {\n\t\t\tutil.LogPad(\"Aborted\")\n\t\t\treturn nil\n\t\t}\n\n\t\treturn p.DeleteStack(c.Regions[0], wait)\n\t})\n}\n\n// status of the stack.\nfunc status(cmd *kingpin.Cmd) {\n\tc := cmd.Command(\"status\", \"Show status of resources.\").Default()\n\n\tc.Action(func(_ *kingpin.ParseContext) error {\n\t\tc, p, err := root.Init()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"initializing\")\n\t\t}\n\n\t\tstats.Track(\"Show Stack\", nil)\n\n\t\t// TODO: multi-region\n\t\treturn p.ShowStack(c.Regions[0])\n\t})\n}\n"
  },
  {
    "path": "internal/cli/start/start.go",
    "content": "package start\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/apex/log\"\n\t\"github.com/pkg/browser\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/tj/kingpin\"\n\n\t\"github.com/apex/up/handler\"\n\t\"github.com/apex/up/internal/cli/root\"\n\t\"github.com/apex/up/internal/logs/text\"\n\t\"github.com/apex/up/internal/stats\"\n)\n\nfunc init() {\n\tcmd := root.Command(\"start\", \"Start development server.\")\n\tcmd.Example(`up start`, \"Start development server on port 3000.\")\n\tcmd.Example(`up start -o`, \"Start development server and open in the browser.\")\n\tcmd.Example(`up start --address :5000`, \"Start development server on port 5000.\")\n\tcmd.Example(`up start -c 'go run main.go'`, \"Override proxy command.\")\n\tcmd.Example(`up start -oc 'gin --port $PORT'`, \"Override proxy command and open in the browser.\")\n\n\tstage := cmd.Flag(\"stage\", \"Target stage name.\").Short('s').Default(\"development\").String()\n\tcommand := cmd.Flag(\"command\", \"Proxy command override\").Short('c').String()\n\topen := cmd.Flag(\"open\", \"Open endpoint in the browser.\").Short('o').Bool()\n\taddr := cmd.Flag(\"address\", \"Address for server.\").Default(\"localhost:3000\").String()\n\n\tcmd.Action(func(_ *kingpin.ParseContext) error {\n\t\tlog.SetHandler(text.New(os.Stdout))\n\n\t\tc, p, err := root.Init()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"initializing\")\n\t\t}\n\n\t\tfor k, v := range c.Environment {\n\t\t\tos.Setenv(k, v)\n\t\t}\n\n\t\tstats.Track(\"Start\", map[string]interface{}{\n\t\t\t\"address\":     *addr,\n\t\t\t\"has_command\": *command != \"\",\n\t\t})\n\n\t\tif err := p.Init(*stage); err != nil {\n\t\t\treturn errors.Wrap(err, \"initializing\")\n\t\t}\n\n\t\tif err := c.Override(*stage); err != nil {\n\t\t\treturn errors.Wrap(err, \"overriding\")\n\t\t}\n\n\t\tif s := *command; s != \"\" {\n\t\t\tc.Proxy.Command = s\n\t\t}\n\n\t\th, err := handler.FromConfig(c)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"selecting handler\")\n\t\t}\n\n\t\th, err = handler.New(c, h)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"initializing handler\")\n\t\t}\n\n\t\tif *open {\n\t\t\t_, port, _ := net.SplitHostPort(*addr)\n\t\t\tbrowser.OpenURL(fmt.Sprintf(\"http://localhost:%s\", port))\n\t\t}\n\n\t\tlog.WithField(\"address\", \"http://\"+*addr).Info(\"listening\")\n\t\tif err := http.ListenAndServe(*addr, h); err != nil {\n\t\t\treturn errors.Wrap(err, \"binding\")\n\t\t}\n\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "internal/cli/team/team.go",
    "content": "package team\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/segmentio/go-snakecase\"\n\t\"github.com/stripe/stripe-go\"\n\t\"github.com/stripe/stripe-go/token\"\n\t\"github.com/tj/go/clipboard\"\n\t\"github.com/tj/go/env\"\n\t\"github.com/tj/go/http/request\"\n\t\"github.com/tj/go/term\"\n\t\"github.com/tj/kingpin\"\n\t\"github.com/tj/survey\"\n\n\t\"github.com/apex/log\"\n\t\"github.com/apex/up/internal/account\"\n\t\"github.com/apex/up/internal/cli/root\"\n\t\"github.com/apex/up/internal/colors\"\n\t\"github.com/apex/up/internal/stats\"\n\t\"github.com/apex/up/internal/userconfig\"\n\t\"github.com/apex/up/internal/util\"\n\t\"github.com/apex/up/platform/event\"\n\t\"github.com/apex/up/reporter\"\n)\n\n// api endpoint.\nvar api = env.GetDefault(\"APEX_TEAMS_API\", \"https://teams.apex.sh\")\n\n// api client.\nvar a = account.New(api)\n\n// plan amounts.\nvar amounts = map[string]int{\n\t\"monthly\":  2000,\n\t\"annually\": 21600,\n}\n\n// plan amount select options.\nvar amountOptions = map[string]string{\n\t\"Monthly at $20.00 USD\":   \"monthly\",\n\t\"Annually at $216.00 USD\": \"annually\",\n}\n\nfunc init() {\n\tcmd := root.Command(\"team\", \"Manage team members, plans, and billing.\")\n\tcmd.Example(`up team`, \"Show active team and subscription status.\")\n\tcmd.Example(`up team login`, \"Sign in or create account with interactive prompt.\")\n\tcmd.Example(`up team login --email tj@example.com --team apex-software`, \"Sign in to a team.\")\n\tcmd.Example(`up team add \"Apex Software\"`, \"Add a new team.\")\n\tcmd.Example(`up team subscribe`, \"Subscribe to the Pro plan.\")\n\tcmd.Example(`up team members add asya@example.com`, \"Invite a team member to your active team.\")\n\tcmd.Example(`up team members rm tobi@example.com`, \"Remove a team member from your active team.\")\n\tcmd.Example(`up team card change`, \"Change the default credit card.\")\n\tcmd.Example(`up team switch`, \"Switch teams interactively.\")\n\tstatus(cmd)\n\tswitchTeam(cmd)\n\tlogin(cmd)\n\tlogout(cmd)\n\tmembers(cmd)\n\tsubscribe(cmd)\n\tunsubscribe(cmd)\n\tcard(cmd)\n\tcopy(cmd)\n\tadd(cmd)\n}\n\n// add command.\nfunc add(cmd *kingpin.Cmd) {\n\tc := cmd.Command(\"add\", \"Add a new team.\")\n\tname := c.Arg(\"name\", \"Name of the team.\").Required().String()\n\n\tc.Action(func(_ *kingpin.ParseContext) error {\n\t\tvar config userconfig.Config\n\t\tif err := config.Load(); err != nil {\n\t\t\treturn errors.Wrap(err, \"loading config\")\n\t\t}\n\n\t\tif !config.Authenticated() {\n\t\t\treturn errors.New(\"Must sign in to create a new team.\")\n\t\t}\n\n\t\tteam := strings.Replace(snakecase.Snakecase(*name), \"_\", \"-\", -1)\n\n\t\tstats.Track(\"Add Team\", map[string]interface{}{\n\t\t\t\"team\": team,\n\t\t\t\"name\": name,\n\t\t})\n\n\t\tt := config.GetActiveTeam()\n\n\t\tif err := a.AddTeam(t.Token, team, *name); err != nil {\n\t\t\treturn errors.Wrap(err, \"creating team\")\n\t\t}\n\n\t\tdefer util.Pad()()\n\t\tutil.Log(\"Created team %s with id %s\", *name, team)\n\n\t\tcode, err := a.LoginWithToken(t.Token, t.Email, team)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"login\")\n\t\t}\n\n\t\t// access key\n\t\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)\n\t\tdefer cancel()\n\n\t\ttoken, err := a.PollAccessToken(ctx, t.Email, team, code)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"getting access token\")\n\t\t}\n\n\t\terr = userconfig.Alter(func(c *userconfig.Config) {\n\t\t\tc.Team = team\n\t\t\tc.AddTeam(&userconfig.Team{\n\t\t\t\tToken: token,\n\t\t\t\tID:    team,\n\t\t\t\tEmail: t.Email,\n\t\t\t})\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"config\")\n\t\t}\n\n\t\tutil.Log(\"%s is now the active team.\\n\", *name)\n\t\tutil.Log(\"Use `up team switch` to change teams.\")\n\n\t\treturn nil\n\t})\n}\n\n// copy commands.\nfunc copy(cmd *kingpin.Cmd) {\n\tc := cmd.Command(\"ci\", \"Credentials for CI.\")\n\tcopy := c.Flag(\"copy\", \"Credentials to the clipboard.\").Short('c').Bool()\n\n\tc.Action(func(_ *kingpin.ParseContext) error {\n\t\tvar config userconfig.Config\n\t\tif err := config.Load(); err != nil {\n\t\t\treturn errors.Wrap(err, \"loading\")\n\t\t}\n\n\t\tstats.Track(\"Copy Credentials\", map[string]interface{}{\n\t\t\t\"copy\": *copy,\n\t\t})\n\n\t\tb, err := json.Marshal(config)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"marshaling\")\n\t\t}\n\n\t\ts := base64.StdEncoding.EncodeToString(b)\n\n\t\tif *copy {\n\t\t\tclipboard.Write(s)\n\t\t\tfmt.Println(\"Copied to clipboard!\")\n\t\t\treturn nil\n\t\t}\n\n\t\tfmt.Printf(\"%s\\n\", s)\n\n\t\treturn nil\n\t})\n}\n\n// status of account.\nfunc status(cmd *kingpin.Cmd) {\n\tc := cmd.Command(\"status\", \"Status of your account.\").Default()\n\n\tc.Action(func(_ *kingpin.ParseContext) error {\n\t\tvar config userconfig.Config\n\t\tif err := config.Load(); err != nil {\n\t\t\treturn errors.Wrap(err, \"loading config\")\n\t\t}\n\n\t\tdefer util.Pad()()\n\t\tstats.Track(\"Account Status\", nil)\n\n\t\tif !config.Authenticated() {\n\t\t\tutil.LogName(\"status\", \"Signed out\")\n\t\t\treturn nil\n\t\t}\n\n\t\tt := config.GetActiveTeam()\n\n\t\tdefer util.Pad()()\n\t\tutil.LogName(\"team\", t.ID)\n\n\t\tteam, err := a.GetTeam(t.Token)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"fetching team\")\n\t\t}\n\n\t\tplans, err := a.GetPlans(t.Token)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"fetching plans\")\n\t\t}\n\n\t\tif len(plans) == 0 {\n\t\t\tutil.LogName(\"subscription\", \"none\")\n\t\t\treturn nil\n\t\t}\n\n\t\tif c := team.Card; c != nil {\n\t\t\tutil.LogName(\"card\", \"%s ending with %s\", c.Brand, c.LastFour)\n\t\t}\n\n\t\t// TODO: filter on plan type (later may be other products)\n\t\tp := plans[0]\n\n\t\tif d := p.Discount; d != nil {\n\t\t\tp.Amount = d.Coupon.Discount(p.Amount)\n\t\t\tutil.LogName(\"coupon\", d.Coupon.ID)\n\t\t}\n\n\t\tutil.LogName(\"amount\", \"%s USD per %s\", currency(p.Amount), p.Interval)\n\t\tutil.LogName(\"owner\", team.Owner)\n\t\tutil.LogName(\"created\", p.CreatedAt.Format(\"January 2, 2006\"))\n\t\tif p.Canceled {\n\t\t\tutil.LogName(\"canceled\", p.CanceledAt.Format(\"January 2, 2006\"))\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\n// switchTeam team.\nfunc switchTeam(cmd *kingpin.Cmd) {\n\tc := cmd.Command(\"switch\", \"Switch active team.\")\n\tc.Example(`up team switch`, \"Switch teams interactively.\")\n\n\tc.Action(func(_ *kingpin.ParseContext) error {\n\t\tdefer util.Pad()()\n\n\t\tvar config userconfig.Config\n\t\tif err := config.Load(); err != nil {\n\t\t\treturn errors.Wrap(err, \"loading user config\")\n\t\t}\n\n\t\tvar options []string\n\t\tfor _, t := range config.GetTeams() {\n\t\t\toptions = append(options, t.ID)\n\t\t}\n\t\tsort.Strings(options)\n\n\t\tvar team string\n\t\tprompt := &survey.Select{\n\t\t\tMessage: \"\",\n\t\t\tOptions: options,\n\t\t\tDefault: config.Team,\n\t\t}\n\n\t\tif err := survey.AskOne(prompt, &team, survey.Required); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tstats.Track(\"Switch Team\", nil)\n\n\t\terr := userconfig.Alter(func(c *userconfig.Config) {\n\t\t\tc.Team = team\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"saving config\")\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\n// login user.\nfunc login(cmd *kingpin.Cmd) {\n\tc := cmd.Command(\"login\", \"Sign in to your account.\")\n\tc.Example(`up team login`, \"Sign in or create account with interactive prompt.\")\n\tc.Example(`up team login --team apex-software`, \"Sign in to a team using your existing email.\")\n\tc.Example(`up team login --email tj@example.com --team apex-software`, \"Sign in to a team with email.\")\n\temail := c.Flag(\"email\", \"Email address.\").String()\n\tteam := c.Flag(\"team\", \"Team id.\").String()\n\n\tc.Action(func(_ *kingpin.ParseContext) error {\n\t\tvar config userconfig.Config\n\t\tif err := config.Load(); err != nil {\n\t\t\treturn errors.Wrap(err, \"loading user config\")\n\t\t}\n\n\t\tdefer util.Pad()()\n\n\t\tstats.Track(\"Login\", map[string]interface{}{\n\t\t\t\"team_count\": len(config.GetTeams()),\n\t\t\t\"has_email\":  *email != \"\",\n\t\t\t\"has_team\":   *team != \"\",\n\t\t})\n\n\t\tt := config.GetActiveTeam()\n\n\t\t// both team and email are specified,\n\t\t// so we want to disregard the active team\n\t\t// entirely and sign in using these creds.\n\t\tif *email != \"\" && *team != \"\" {\n\t\t\tt = nil\n\t\t}\n\n\t\t// ensure we have an email\n\t\tif *email == \"\" {\n\t\t\tif t == nil {\n\t\t\t\tif err := prompt(\"email:\", email); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t*email = t.Email\n\t\t\t}\n\t\t}\n\n\t\t// ensure we have a team if already signed-in,\n\t\t// this lets the user specify only --team xxx to\n\t\t// join a team they were invited to, or add one\n\t\t// which they own.\n\t\tif t != nil && *team == \"\" {\n\t\t\tutil.Log(\"Already signed in as %s on team %s.\", t.Email, t.ID)\n\t\t\tutil.Log(\"Use `up team login --team <id>` to join a team.\")\n\t\t\treturn nil\n\t\t}\n\n\t\t// events\n\t\tevents := make(event.Events)\n\t\tgo reporter.Text(events)\n\t\tevents.Emit(\"account.login.verify\", nil)\n\n\t\tl := log.WithFields(log.Fields{\n\t\t\t\"email\": *email,\n\t\t\t\"team\":  *team,\n\t\t})\n\n\t\t// authenticate\n\t\tvar code string\n\t\tvar err error\n\t\tif t == nil {\n\t\t\tl.Debug(\"login without token\")\n\t\t\tcode, err = a.Login(*email, *team)\n\t\t} else {\n\t\t\tl.Debug(\"login with token\")\n\t\t\tcode, err = a.LoginWithToken(t.Token, *email, *team)\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"login\")\n\t\t}\n\n\t\t// personal team\n\t\tif *team == \"\" {\n\t\t\tteam = email\n\t\t}\n\n\t\t// access key\n\t\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)\n\t\tdefer cancel()\n\n\t\tl.WithField(\"team\", *team).Debug(\"poll for access token\")\n\t\ttoken, err := a.PollAccessToken(ctx, *email, *team, code)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"getting access token\")\n\t\t}\n\n\t\tevents.Emit(\"account.login.verified\", nil)\n\t\terr = userconfig.Alter(func(c *userconfig.Config) {\n\t\t\tc.Team = *team\n\t\t\tc.AddTeam(&userconfig.Team{\n\t\t\t\tToken: token,\n\t\t\t\tID:    *team,\n\t\t\t\tEmail: *email,\n\t\t\t})\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"config\")\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\n// logout user.\nfunc logout(cmd *kingpin.Cmd) {\n\tc := cmd.Command(\"logout\", \"Sign out of your account.\")\n\n\tc.Action(func(_ *kingpin.ParseContext) error {\n\t\tstats.Track(\"Logout\", nil)\n\n\t\tvar config userconfig.Config\n\t\tif err := config.Save(); err != nil {\n\t\t\treturn errors.Wrap(err, \"saving\")\n\t\t}\n\n\t\tutil.LogPad(\"Signed out\")\n\n\t\treturn nil\n\t})\n}\n\n// subscribe to plan.\nfunc subscribe(cmd *kingpin.Cmd) {\n\tc := cmd.Command(\"subscribe\", \"Subscribe to the Pro plan.\")\n\n\tc.Action(func(_ *kingpin.ParseContext) error {\n\t\tt, err := userconfig.Require()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tdefer util.Pad()()\n\n\t\t// plan\n\t\tutil.LogTitle(\"Subscription\")\n\t\tutil.Log(\"Choose a monthly billing period, or 10%% off annually.\")\n\t\tprintln()\n\n\t\tvar interval string\n\t\terr = survey.AskOne(&survey.Select{\n\t\t\tMessage: \"Plan:\",\n\t\t\tOptions: keys(amountOptions),\n\t\t}, &interval, survey.Required)\n\n\t\t// amount\n\t\tinterval = amountOptions[interval]\n\t\tamount := amounts[interval]\n\n\t\tutil.LogTitle(\"Coupon\")\n\t\tutil.Log(\"Enter a coupon, or press enter to skip this step\")\n\t\tutil.Log(\"and move on to adding a credit card.\")\n\t\tprintln()\n\n\t\t// coupon\n\t\tvar couponID string\n\t\terr = survey.AskOne(&survey.Input{\n\t\t\tMessage: \"Coupon:\",\n\t\t}, &couponID, nil)\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// coupon\n\t\tif strings.TrimSpace(couponID) == \"\" {\n\t\t\tutil.LogClear(\"No coupon provided\")\n\t\t} else {\n\t\t\tcoupon, err := a.GetCoupon(couponID)\n\t\t\tif err != nil && !request.IsNotFound(err) {\n\t\t\t\treturn errors.Wrap(err, \"fetching coupon\")\n\t\t\t}\n\n\t\t\tif coupon == nil {\n\t\t\t\tutil.LogClear(\"Coupon is invalid\")\n\t\t\t} else {\n\t\t\t\tamount = coupon.Discount(amount)\n\t\t\t\tmsg := colors.Gray(fmt.Sprintf(\"%s — now %s %s\", coupon.Description(), currency(amount), interval))\n\t\t\t\tutil.LogClear(\"Savings: %s\", msg)\n\t\t\t}\n\t\t}\n\n\t\t// add card\n\t\tutil.LogTitle(\"Credit Card\")\n\t\tutil.Log(\"First add your credit card details, these are transferred\")\n\t\tutil.Log(\"directly to Stripe over HTTPS and never touch our servers.\")\n\t\tprintln()\n\n\t\tcard, err := account.PromptForCard()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"prompting for card\")\n\t\t}\n\n\t\ttok, err := token.New(&stripe.TokenParams{\n\t\t\tCard: &card,\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"requesting card token\")\n\t\t}\n\n\t\tif err := a.AddCard(t.Token, tok.ID); err != nil {\n\t\t\treturn errors.Wrap(err, \"adding card\")\n\t\t}\n\n\t\tutil.LogTitle(\"Confirm\")\n\n\t\t// confirm\n\t\tvar ok bool\n\t\terr = survey.AskOne(&survey.Confirm{\n\t\t\tMessage: fmt.Sprintf(\"Subscribe to Up Pro for %s USD %s?\", currency(amount), interval),\n\t\t}, &ok, nil)\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif !ok {\n\t\t\tutil.LogPad(\"Aborted\")\n\t\t\tstats.Track(\"Abort Subscription\", nil)\n\t\t\treturn nil\n\t\t}\n\n\t\tstats.Track(\"Subscribe\", map[string]interface{}{\n\t\t\t\"coupon\":   couponID,\n\t\t\t\"interval\": interval,\n\t\t})\n\n\t\tif err := a.AddPlan(t.Token, \"up\", interval, couponID); err != nil {\n\t\t\treturn errors.Wrap(err, \"subscribing\")\n\t\t}\n\n\t\tutil.LogClear(\"Thanks for subscribing! Run `up upgrade` to install the Pro release.\")\n\n\t\treturn nil\n\t})\n}\n\n// unsubscribe from plan.\nfunc unsubscribe(cmd *kingpin.Cmd) {\n\tc := cmd.Command(\"unsubscribe\", \"Unsubscribe from the Pro plan.\")\n\n\tc.Action(func(_ *kingpin.ParseContext) error {\n\t\tconfig, err := userconfig.Require()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tdefer util.Pad()()\n\n\t\t// confirm\n\t\tvar ok bool\n\t\terr = survey.AskOne(&survey.Confirm{\n\t\t\tMessage: \"Are you sure you want to unsubscribe?\",\n\t\t}, &ok, nil)\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif !ok {\n\t\t\tutil.LogPad(\"Aborted\")\n\t\t\treturn nil\n\t\t}\n\n\t\tterm.MoveUp(1)\n\t\tterm.ClearLine()\n\n\t\tmsg, err := feedback()\n\t\tif err != nil {\n\t\t\tutil.LogPad(\"Aborted\")\n\t\t\treturn nil\n\t\t}\n\n\t\tif strings.TrimSpace(msg) != \"\" {\n\t\t\tutil.Log(\"Thanks for the feedback!\")\n\t\t\t_ = a.AddFeedback(config.Token, msg)\n\t\t}\n\n\t\tstats.Track(\"Unsubscribe\", nil)\n\n\t\tif err := a.RemovePlan(config.Token, \"up\"); err != nil {\n\t\t\treturn errors.Wrap(err, \"unsubscribing\")\n\t\t}\n\n\t\tutil.LogClear(\"Unsubscribed!\")\n\n\t\treturn nil\n\t})\n}\n\n// members commands.\nfunc members(cmd *kingpin.Cmd) {\n\tc := cmd.Command(\"members\", \"Member management.\")\n\taddMember(c)\n\tremoveMember(c)\n\tlistMembers(c)\n}\n\n// addMember command.\nfunc addMember(cmd *kingpin.Cmd) {\n\tc := cmd.Command(\"add\", \"Add invites a team member.\")\n\tc.Example(`up team members add asya@apex.sh`, \"Invite a team member to the active team.\")\n\temail := c.Arg(\"email\", \"Email address.\").Required().String()\n\n\tc.Action(func(_ *kingpin.ParseContext) error {\n\t\tt, err := userconfig.Require()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tstats.Track(\"Add Member\", map[string]interface{}{\n\t\t\t\"team\":  t.ID,\n\t\t\t\"email\": *email,\n\t\t})\n\n\t\tif err := a.AddInvite(t.Token, *email); err != nil {\n\t\t\treturn errors.Wrap(err, \"adding invite\")\n\t\t}\n\n\t\tutil.LogPad(\"Invited %s to team %s\", *email, t.ID)\n\n\t\treturn nil\n\t})\n}\n\n// removeMember command.\nfunc removeMember(cmd *kingpin.Cmd) {\n\tc := cmd.Command(\"rm\", \"Remove a member or invite.\").Alias(\"remove\")\n\tc.Example(`up team members rm tobi@apex.sh`, \"Remove a team member or invite from the active team.\")\n\temail := c.Arg(\"email\", \"Email address.\").Required().String()\n\n\tc.Action(func(_ *kingpin.ParseContext) error {\n\t\tt, err := userconfig.Require()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tstats.Track(\"Remove Member\", map[string]interface{}{\n\t\t\t\"team\":  t.ID,\n\t\t\t\"email\": *email,\n\t\t})\n\n\t\tif err := a.RemoveMember(t.Token, *email); err != nil {\n\t\t\treturn errors.Wrap(err, \"removing member\")\n\t\t}\n\n\t\tutil.LogPad(\"Removed %s from team %s\", *email, t.ID)\n\n\t\treturn nil\n\t})\n}\n\n// list members\nfunc listMembers(cmd *kingpin.Cmd) {\n\tc := cmd.Command(\"ls\", \"List team members and invites.\").Alias(\"list\").Default()\n\n\tc.Action(func(_ *kingpin.ParseContext) error {\n\t\tt, err := userconfig.Require()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tstats.Track(\"List Members\", map[string]interface{}{\n\t\t\t\"team\": t.ID,\n\t\t})\n\n\t\tteam, err := a.GetTeam(t.Token)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"fetching team\")\n\t\t}\n\n\t\tdefer util.Pad()()\n\n\t\tutil.LogName(\"team\", t.ID)\n\n\t\tif len(team.Members) > 0 {\n\t\t\tutil.LogTitle(\"Members\")\n\t\t\tfor _, u := range team.Members {\n\t\t\t\tutil.LogListItem(u.Email)\n\t\t\t}\n\t\t}\n\n\t\tif len(team.Invites) > 0 {\n\t\t\tutil.LogTitle(\"Invites\")\n\t\t\tfor _, email := range team.Invites {\n\t\t\t\tutil.LogListItem(email)\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\n// card commands.\nfunc card(cmd *kingpin.Cmd) {\n\tc := cmd.Command(\"card\", \"Card management.\")\n\tchangeCard(c)\n}\n\n// changeCard command.\nfunc changeCard(cmd *kingpin.Cmd) {\n\tc := cmd.Command(\"change\", \"Change the default card.\")\n\n\tc.Action(func(_ *kingpin.ParseContext) error {\n\t\tt, err := userconfig.Require()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tdefer util.Pad()()\n\n\t\tutil.LogTitle(\"Credit Card\")\n\t\tutil.Log(\"Enter new credit card details to replace the existing card.\")\n\t\tprintln()\n\n\t\tcard, err := account.PromptForCard()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"prompting for card\")\n\t\t}\n\n\t\ttok, err := token.New(&stripe.TokenParams{\n\t\t\tCard: &card,\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"requesting card token\")\n\t\t}\n\n\t\tif err := a.AddCard(t.Token, tok.ID); err != nil {\n\t\t\treturn errors.Wrap(err, \"adding card\")\n\t\t}\n\n\t\tprintln()\n\t\tutil.Log(\"Updated\")\n\n\t\treturn nil\n\t})\n}\n\n// prompt helper.\nfunc prompt(name string, s *string) error {\n\tprompt := &survey.Input{Message: name}\n\treturn survey.AskOne(prompt, s, survey.Required)\n}\n\n// feedback prompt helper.\nfunc feedback() (string, error) {\n\tvar s string\n\tprompt := &survey.Input{Message: \"Have any feedback? (optional)\"}\n\tif err := survey.AskOne(prompt, &s, nil); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn s, nil\n}\n\n// currency returns formatted currency.\nfunc currency(n int) string {\n\treturn fmt.Sprintf(\"$%0.2f\", float64(n)/100)\n}\n\n// keys returns the keys of a string map.\nfunc keys(m map[string]string) (v []string) {\n\tfor k := range m {\n\t\tv = append(v, k)\n\t}\n\n\tsort.Slice(v, func(i int, j int) bool {\n\t\treturn v[i] > v[j]\n\t})\n\n\treturn\n}\n"
  },
  {
    "path": "internal/cli/upgrade/upgrade.go",
    "content": "package upgrade\n\nimport (\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/tj/go-update\"\n\t\"github.com/tj/go-update/stores/apex\"\n\t\"github.com/tj/go-update/stores/github\"\n\t\"github.com/tj/go/env\"\n\t\"github.com/tj/go/http/request\"\n\t\"github.com/tj/go/term\"\n\t\"github.com/tj/kingpin\"\n\n\t\"github.com/apex/up/internal/cli/root\"\n\t\"github.com/apex/up/internal/progressreader\"\n\t\"github.com/apex/up/internal/stats\"\n\t\"github.com/apex/up/internal/userconfig\"\n\t\"github.com/apex/up/internal/util\"\n)\n\nvar releasesAPI = env.GetDefault(\"APEX_RELEASES_API\", \"https://releases.apex.sh\")\n\nfunc init() {\n\tcmd := root.Command(\"upgrade\", \"Install the latest or specified version of Up.\")\n\tcmd.Example(`up upgrade`, \"Upgrade to the latest version available.\")\n\tcmd.Example(`up upgrade -t 0.4.4`, \"Upgrade to the specified version.\")\n\ttarget := cmd.Flag(\"target\", \"Target version for upgrade.\").Short('t').String()\n\n\tcmd.Action(func(_ *kingpin.ParseContext) error {\n\t\tversion := root.Cmd.GetVersion()\n\t\tstart := time.Now()\n\n\t\tterm.HideCursor()\n\t\tdefer term.ShowCursor()\n\n\t\tvar config userconfig.Config\n\t\tif err := config.Load(); err != nil {\n\t\t\treturn errors.Wrap(err, \"loading user config\")\n\t\t}\n\n\t\t// open-source edition\n\t\tp := &update.Manager{\n\t\t\tCommand: \"up\",\n\t\t\tStore: &github.Store{\n\t\t\t\tOwner:   \"apex\",\n\t\t\t\tRepo:    \"up\",\n\t\t\t\tVersion: version,\n\t\t\t},\n\t\t}\n\n\t\t// commercial edition\n\t\tif t := config.GetActiveTeam(); t != nil {\n\t\t\t// we pass 0.0.0 here beause the OSS\n\t\t\t// binary should always upgrade to Pro\n\t\t\t// regardless of versions matching.\n\t\t\tp.Store = &apex.Store{\n\t\t\t\tURL:       releasesAPI,\n\t\t\t\tProduct:   \"up\",\n\t\t\t\tVersion:   \"0.0.0\",\n\t\t\t\tPlan:      \"pro\",\n\t\t\t\tAccessKey: t.Token,\n\t\t\t}\n\t\t}\n\n\t\t// fetch latest or specified release\n\t\tr, err := getLatestOrSpecified(p, *target)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"fetching latest release\")\n\t\t}\n\n\t\t// no updates\n\t\tif r == nil {\n\t\t\tutil.LogPad(\"No updates available, you're good :)\")\n\t\t\treturn nil\n\t\t}\n\n\t\t// find the tarball for this system\n\t\ta := r.FindTarball(runtime.GOOS, runtime.GOARCH)\n\t\tif a == nil {\n\t\t\treturn errors.Errorf(\"failed to find a binary for %s %s\", runtime.GOOS, runtime.GOARCH)\n\t\t}\n\n\t\t// download tarball to a tmp dir\n\t\tvar tarball string\n\t\tif util.IsCI() {\n\t\t\ttarball, err = a.Download()\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrap(err, \"downloading tarball\")\n\t\t\t}\n\t\t} else {\n\t\t\ttarball, err = a.DownloadProxy(progressreader.New)\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrap(err, \"downloading tarball\")\n\t\t\t}\n\t\t}\n\n\t\t// determine path\n\t\tpath, err := exec.LookPath(os.Args[0])\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"looking up executable path\")\n\t\t}\n\t\tdst := filepath.Dir(path)\n\n\t\t// install it\n\t\tif err := p.InstallTo(tarball, dst); err != nil {\n\t\t\treturn errors.Wrap(err, \"installing\")\n\t\t}\n\n\t\tterm.ClearAll()\n\n\t\tif strings.Contains(a.URL, \"up/pro\") {\n\t\t\tutil.LogPad(\"Updated %s to %s Pro\", versionName(version), r.Version)\n\t\t} else {\n\t\t\tutil.LogPad(\"Updated %s to %s OSS\", versionName(version), r.Version)\n\t\t}\n\n\t\tstats.Track(\"Upgrade\", map[string]interface{}{\n\t\t\t\"from\":     version,\n\t\t\t\"to\":       r.Version,\n\t\t\t\"duration\": time.Since(start).Round(time.Millisecond),\n\t\t})\n\n\t\treturn nil\n\t})\n}\n\n// getLatestOrSpecified returns the latest or specified release.\nfunc getLatestOrSpecified(s update.Store, version string) (*update.Release, error) {\n\tif version == \"\" {\n\t\treturn getLatest(s)\n\t}\n\n\treturn s.GetRelease(version)\n}\n\n// getLatest returns the latest release, error, or nil when there is none.\nfunc getLatest(s update.Store) (*update.Release, error) {\n\treleases, err := s.LatestReleases()\n\n\tif request.IsClient(err) {\n\t\treturn nil, errors.Wrap(err, \"You're not subscribed to Up Pro\")\n\t}\n\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"fetching releases\")\n\t}\n\n\tif len(releases) == 0 {\n\t\treturn nil, nil\n\t}\n\n\treturn releases[0], nil\n}\n\n// versionName returns the humanized version name.\nfunc versionName(s string) string {\n\tif strings.Contains(s, \"-pro\") {\n\t\treturn strings.Replace(s, \"-pro\", \"\", 1) + \" Pro\"\n\t}\n\n\treturn s + \" OSS\"\n}\n"
  },
  {
    "path": "internal/cli/url/url.go",
    "content": "package url\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/pkg/browser\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/tj/go/clipboard\"\n\t\"github.com/tj/kingpin\"\n\n\t\"github.com/apex/up/internal/cli/root\"\n\t\"github.com/apex/up/internal/stats\"\n\t\"github.com/apex/up/internal/util\"\n\t\"github.com/apex/up/internal/validate\"\n)\n\nfunc init() {\n\tcmd := root.Command(\"url\", \"Show, open, or copy a stage endpoint.\")\n\n\tcmd.Example(`up url`, \"Show the staging endpoint.\")\n\tcmd.Example(`up url --open`, \"Open the staging endpoint in the browser.\")\n\tcmd.Example(`up url --copy`, \"Copy the staging endpoint to the clipboard.\")\n\tcmd.Example(`up url -s production`, \"Show the production endpoint.\")\n\tcmd.Example(`up url -o -s production`, \"Open the production endpoint in the browser.\")\n\tcmd.Example(`up url -c -s production`, \"Copy the production endpoint to the clipboard.\")\n\n\tstage := cmd.Flag(\"stage\", \"Target stage name.\").Short('s').Default(\"staging\").String()\n\topen := cmd.Flag(\"open\", \"Open endpoint in the browser.\").Short('o').Bool()\n\tcopy := cmd.Flag(\"copy\", \"Copy endpoint to the clipboard.\").Short('c').Bool()\n\n\tcmd.Action(func(_ *kingpin.ParseContext) error {\n\t\tc, p, err := root.Init()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"initializing\")\n\t\t}\n\n\t\tregion := c.Regions[0]\n\n\t\tstats.Track(\"URL\", map[string]interface{}{\n\t\t\t\"region\": region,\n\t\t\t\"stage\":  *stage,\n\t\t\t\"open\":   *open,\n\t\t\t\"copy\":   *copy,\n\t\t})\n\n\t\tif err := validate.List(*stage, c.Stages.RemoteNames()); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\turl, err := p.URL(region, *stage)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tswitch {\n\t\tcase *open:\n\t\t\tbrowser.OpenURL(url)\n\t\tcase *copy:\n\t\t\tclipboard.Write(url)\n\t\t\tutil.LogPad(\"Copied to clipboard!\")\n\t\tdefault:\n\t\t\tfmt.Println(url)\n\t\t}\n\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "internal/cli/version/version.go",
    "content": "package version\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/tj/kingpin\"\n\n\t\"github.com/apex/up/internal/cli/root\"\n\t\"github.com/apex/up/internal/stats\"\n)\n\nfunc init() {\n\tcmd := root.Command(\"version\", \"Show version.\")\n\tcmd.Action(func(_ *kingpin.ParseContext) error {\n\t\tstats.Track(\"Show Version\", nil)\n\t\tfmt.Println(root.Cmd.GetVersion())\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "internal/colors/colors.go",
    "content": "// Package colors provides colors used by the CLI.\npackage colors\n\nimport (\n\tcolor \"github.com/aybabtme/rgbterm\"\n)\n\n// Func is a color function.\ntype Func func(string) string\n\n// Gray string.\nfunc Gray(s string) string {\n\treturn color.FgString(s, 150, 150, 150)\n}\n\n// Blue string.\nfunc Blue(s string) string {\n\treturn color.FgString(s, 77, 173, 247)\n}\n\n// Cyan string.\nfunc Cyan(s string) string {\n\treturn color.FgString(s, 34, 184, 207)\n}\n\n// Green string.\nfunc Green(s string) string {\n\treturn color.FgString(s, 0, 200, 255)\n}\n\n// Red string.\nfunc Red(s string) string {\n\treturn color.FgString(s, 194, 37, 92)\n}\n\n// Yellow string.\nfunc Yellow(s string) string {\n\treturn color.FgString(s, 252, 196, 25)\n}\n\n// Purple string.\nfunc Purple(s string) string {\n\treturn color.FgString(s, 96, 97, 190)\n}\n\n// Bool returns a color func based on the state.\nfunc Bool(ok bool) Func {\n\tif ok {\n\t\treturn Purple\n\t}\n\n\treturn Red\n}\n"
  },
  {
    "path": "internal/errorpage/errorpage.go",
    "content": "// Package errorpage provides error page loading utilities.\npackage errorpage\n\nimport (\n\t\"bytes\"\n\t\"html/template\"\n\t\"io/ioutil\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n)\n\n// Page is a single .html file matching\n// one or more status codes.\ntype Page struct {\n\tName     string\n\tCode     int\n\tRange    bool\n\tTemplate *template.Template\n}\n\n// Match returns true if the page matches code.\nfunc (p *Page) Match(code int) bool {\n\tswitch {\n\tcase p.Code == code:\n\t\treturn true\n\tcase p.Range && p.Code == code/100:\n\t\treturn true\n\tcase p.Name == \"error\" && code >= 400:\n\t\treturn true\n\tcase p.Name == \"default\" && code >= 400:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\n// Specificity returns the specificity, where higher is more precise.\nfunc (p *Page) Specificity() int {\n\tswitch {\n\tcase p.Name == \"default\":\n\t\treturn 4\n\tcase p.Name == \"error\":\n\t\treturn 3\n\tcase p.Range:\n\t\treturn 2\n\tdefault:\n\t\treturn 1\n\t}\n}\n\n// Render the page.\nfunc (p *Page) Render(data interface{}) (string, error) {\n\tvar buf bytes.Buffer\n\n\tif err := p.Template.Execute(&buf, data); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn buf.String(), nil\n}\n\n// Pages is a group of .html files\n// matching one or more status codes.\ntype Pages []Page\n\n// Match returns the matching page.\nfunc (p Pages) Match(code int) *Page {\n\tfor _, page := range p {\n\t\tif page.Match(code) {\n\t\t\treturn &page\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Load pages in dir.\nfunc Load(dir string) (pages Pages, err error) {\n\tfiles, err := ioutil.ReadDir(dir)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"reading dir\")\n\t}\n\n\tfor _, file := range files {\n\t\tif !isErrorPage(file.Name()) {\n\t\t\tcontinue\n\t\t}\n\n\t\tpath := filepath.Join(dir, file.Name())\n\n\t\tt, err := template.New(file.Name()).ParseFiles(path)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"parsing template\")\n\t\t}\n\n\t\tname := stripExt(file.Name())\n\t\tcode, _ := strconv.Atoi(name)\n\n\t\tif isRange(name) {\n\t\t\tcode = int(name[0] - '0')\n\t\t}\n\n\t\tpage := Page{\n\t\t\tName:     name,\n\t\t\tCode:     code,\n\t\t\tRange:    isRange(name),\n\t\t\tTemplate: t,\n\t\t}\n\n\t\tpages = append(pages, page)\n\t}\n\n\tpages = append(pages, Page{\n\t\tName:     \"default\",\n\t\tTemplate: defaultPage,\n\t})\n\n\tSort(pages)\n\treturn\n}\n\n// Sort pages by specificity.\nfunc Sort(pages Pages) {\n\tsort.Slice(pages, func(i int, j int) bool {\n\t\ta := pages[i]\n\t\tb := pages[j]\n\t\treturn a.Specificity() < b.Specificity()\n\t})\n}\n\n// isErrorPage returns true if it looks like an error page.\nfunc isErrorPage(path string) bool {\n\tif filepath.Ext(path) != \".html\" {\n\t\treturn false\n\t}\n\n\tname := stripExt(path)\n\n\tif name == \"error\" {\n\t\treturn true\n\t}\n\n\tif isRange(name) {\n\t\treturn true\n\t}\n\n\t_, err := strconv.Atoi(name)\n\treturn err == nil\n}\n\n// isRange returns true if the name matches xx.s\nfunc isRange(name string) bool {\n\treturn strings.HasSuffix(name, \"xx\")\n}\n\n// stripExt returns path without extname.\nfunc stripExt(path string) string {\n\treturn strings.Replace(path, filepath.Ext(path), \"\", 1)\n}\n"
  },
  {
    "path": "internal/errorpage/errorpage_test.go",
    "content": "package errorpage\n\nimport (\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/tj/assert\"\n)\n\n// load pages from dir.\nfunc load(t testing.TB, dir string) Pages {\n\tdir = filepath.Join(\"testdata\", dir)\n\tpages, err := Load(dir)\n\tassert.NoError(t, err, \"load\")\n\treturn pages\n}\n\nfunc TestPages_precedence(t *testing.T) {\n\tpages := load(t, \".\")\n\n\tt.Run(\"code 500 match exact\", func(t *testing.T) {\n\t\tp := pages.Match(500)\n\t\tassert.NotNil(t, p, \"no match\")\n\n\t\thtml, err := p.Render(nil)\n\t\tassert.NoError(t, err)\n\n\t\tassert.Equal(t, \"500 page.\\n\", html)\n\t})\n\n\tt.Run(\"code 404 match exact\", func(t *testing.T) {\n\t\tp := pages.Match(404)\n\t\tassert.NotNil(t, p, \"no match\")\n\n\t\thtml, err := p.Render(nil)\n\t\tassert.NoError(t, err)\n\n\t\tassert.Equal(t, \"404 page.\\n\", html)\n\t})\n\n\tt.Run(\"code 200 match exact\", func(t *testing.T) {\n\t\tp := pages.Match(200)\n\t\tassert.NotNil(t, p, \"no match\")\n\n\t\thtml, err := p.Render(nil)\n\t\tassert.NoError(t, err)\n\n\t\tassert.Equal(t, \"200 page.\\n\", html)\n\t})\n\n\tt.Run(\"code 403 match range\", func(t *testing.T) {\n\t\tp := pages.Match(403)\n\t\tassert.NotNil(t, p, \"no match\")\n\n\t\thtml, err := p.Render(nil)\n\t\tassert.NoError(t, err)\n\n\t\tassert.Equal(t, \"4xx page.\\n\", html)\n\t})\n\n\tt.Run(\"502 match global\", func(t *testing.T) {\n\t\tp := pages.Match(502)\n\t\tassert.NotNil(t, p, \"no match\")\n\n\t\tdata := struct {\n\t\t\tStatusText string\n\t\t\tStatusCode int\n\t\t}{\"Bad Gateway\", 502}\n\n\t\thtml, err := p.Render(data)\n\t\tassert.NoError(t, err)\n\n\t\tassert.Equal(t, \"Bad Gateway - 502.\\n\", html)\n\t})\n}\n"
  },
  {
    "path": "internal/errorpage/template.go",
    "content": "package errorpage\n\nimport \"html/template\"\n\n// defaultPage is the default error page.\nvar defaultPage = template.Must(template.New(\"errorpage\").Parse(`<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <title>{{.StatusText}} – {{.StatusCode}}</title>\n    <style>\n      html, body {\n        margin: 0;\n        width: 100%;\n        height: 100%;\n        display: flex;\n        justify-content: center;\n        align-items: center;\n      }\n\n      body {\n        font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n        font-size: 15px;\n      }\n\n      .Error {\n        font-size: 1.35em;\n        {{with .Variables.color}}\n          color: {{.}};\n        {{else}}\n          color: #6061BE;\n        {{end}}\n      }\n\n      .Error .message {\n        font-weight: 200;\n        letter-spacing: 0.095em;\n      }\n\n      .Error .message a {\n        text-decoration: none;\n        color: inherit;\n        {{with .Variables.color}}\n          border-bottom: 1px dotted {{.}};\n        {{else}}\n          border-bottom: 1px dotted #6061BE;\n        {{end}}\n      }\n\n      .Error .status {\n        font-weight: 700;\n      }\n\n      .Error .code {\n        display: none;\n      }\n\n      .Error .dot {\n        font-weight: 100;\n      }\n\n      @media screen and (max-width: 800px) {\n        body {\n          font-size: 10px;\n        }\n\n        .Error {\n          display: flex;\n          flex-direction: column;\n        }\n\n        .Error .status {\n          font-size: 1.1em;\n        }\n\n        .Error .dot {\n          display: none\n        }\n      }\n    </style>\n  </head>\n  <body>\n    <div class=\"Error\">\n      <span class=\"status\">{{.StatusText}}</span>\n      <span class=\"code\">{{.StatusCode}}</span>\n      <span class=\"dot\">•</span>\n      {{with .Variables.support_email}}\n        <span class=\"message\">Please try your request again or <a href=\"mailto:{{.}}\">contact support</a>.</span>\n      {{else}}\n        <span class=\"message\">Please try your request again or contact support.</span>\n      {{end}}\n    </div>\n  </body>\n</html>`))\n"
  },
  {
    "path": "internal/errorpage/testdata/200.html",
    "content": "200 page.\n"
  },
  {
    "path": "internal/errorpage/testdata/404.html",
    "content": "404 page.\n"
  },
  {
    "path": "internal/errorpage/testdata/4xx.html",
    "content": "4xx page.\n"
  },
  {
    "path": "internal/errorpage/testdata/500.html",
    "content": "500 page.\n"
  },
  {
    "path": "internal/errorpage/testdata/error.html",
    "content": "{{.StatusText}} - {{.StatusCode}}.\n"
  },
  {
    "path": "internal/errorpage/testdata/other.html",
    "content": ""
  },
  {
    "path": "internal/errorpage/testdata/somedir/test.html",
    "content": ""
  },
  {
    "path": "internal/header/header.go",
    "content": "// Package header provides path-matched header injection rules.\npackage header\n\nimport (\n\t\"github.com/fanyang01/radix\"\n)\n\n// Fields map.\ntype Fields map[string]string\n\n// Rules map of paths to fields.\ntype Rules map[string]Fields\n\n// Matcher for header lookup.\ntype Matcher struct {\n\tt *radix.PatternTrie\n}\n\n// Lookup returns fields for the given path.\nfunc (m *Matcher) Lookup(path string) Fields {\n\tv, ok := m.t.Lookup(path)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\treturn v.(Fields)\n}\n\n// Compile the given rules to a trie.\nfunc Compile(rules Rules) (*Matcher, error) {\n\tt := radix.NewPatternTrie()\n\tm := &Matcher{t}\n\n\tfor path, fields := range rules {\n\t\tt.Add(path, fields)\n\t}\n\n\treturn m, nil\n}\n\n// Merge returns a new rules set giving precedence to `b`.\nfunc Merge(a, b Rules) Rules {\n\tr := make(Rules)\n\n\tfor path, fields := range a {\n\t\tr[path] = fields\n\t}\n\n\tfor path, fields := range b {\n\t\tif _, ok := r[path]; !ok {\n\t\t\tr[path] = make(Fields)\n\t\t}\n\n\t\tfor name, val := range fields {\n\t\t\tr[path][name] = val\n\t\t}\n\t}\n\n\treturn r\n}\n"
  },
  {
    "path": "internal/header/header_test.go",
    "content": "package header\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tj/assert\"\n)\n\nfunc TestMatcher_Lookup(t *testing.T) {\n\trules := Rules{\n\t\t\"*\": {\n\t\t\t\"X-Type\": \"html\",\n\t\t},\n\t\t\"*.css\": {\n\t\t\t\"X-Type\": \"css\",\n\t\t},\n\t\t\"/docs/alerts\": {\n\t\t\t\"X-Type\": \"docs alerts\",\n\t\t},\n\t\t\"/docs/*\": {\n\t\t\t\"X-Type\": \"docs\",\n\t\t},\n\t}\n\n\tm, err := Compile(rules)\n\tassert.NoError(t, err, \"compile\")\n\n\tassert.Equal(t, Fields{\"X-Type\": \"html\"}, m.Lookup(\"/something\"))\n\tassert.Equal(t, Fields{\"X-Type\": \"html\"}, m.Lookup(\"/docs\"))\n\tassert.Equal(t, Fields{\"X-Type\": \"docs\"}, m.Lookup(\"/docs/\"))\n\tassert.Equal(t, Fields{\"X-Type\": \"css\"}, m.Lookup(\"/style.css\"))\n\tassert.Equal(t, Fields{\"X-Type\": \"css\"}, m.Lookup(\"/public/css/style.css\"))\n\tassert.Equal(t, Fields{\"X-Type\": \"docs\"}, m.Lookup(\"/docs/checks\"))\n\tassert.Equal(t, Fields{\"X-Type\": \"docs alerts\"}, m.Lookup(\"/docs/alerts\"))\n}\n\nfunc TestMerge(t *testing.T) {\n\trules := Rules{\n\t\t\"*\": {\n\t\t\t\"X-Type\": \"html\",\n\t\t\t\"X-Foo\":  \"bar\",\n\t\t},\n\t\t\"/login\": {\n\t\t\t\"X-Something\": \"here\",\n\t\t},\n\t}\n\n\trules = Merge(rules, Rules{\n\t\t\"*\": {\n\t\t\t\"X-Type\": \"pdf\",\n\t\t},\n\t\t\"/admin\": {\n\t\t\t\"X-Something\": \"here\",\n\t\t},\n\t})\n\n\texpected := Rules{\n\t\t\"*\": {\n\t\t\t\"X-Type\": \"pdf\",\n\t\t\t\"X-Foo\":  \"bar\",\n\t\t},\n\t\t\"/login\": {\n\t\t\t\"X-Something\": \"here\",\n\t\t},\n\t\t\"/admin\": {\n\t\t\t\"X-Something\": \"here\",\n\t\t},\n\t}\n\n\tassert.Equal(t, expected, rules)\n}\n"
  },
  {
    "path": "internal/inject/inject.go",
    "content": "// Package inject provides script and style injection utilities.\npackage inject\n\nimport (\n\t\"encoding/json\"\n\t\"html\"\n\t\"io/ioutil\"\n\t\"strings\"\n\n\t\"github.com/apex/log\"\n\t\"github.com/apex/up/internal/validate\"\n\t\"github.com/pkg/errors\"\n)\n\n// TODO: template support\n// TODO: move config to \"config\" pkg\n\n// locations valid.\nvar locations = []string{\n\t\"head\",\n\t\"body\",\n}\n\n// types valid.\nvar types = []string{\n\t\"literal\",\n\t\"comment\",\n\t\"style\",\n\t\"script\",\n\t\"inline style\",\n\t\"inline script\",\n\t\"google analytics\",\n\t\"segment\",\n}\n\n// Rules is a set of rules mapped by location.\ntype Rules map[string][]*Rule\n\n// Default rules.\nfunc (r Rules) Default() error {\n\tfor pos, rules := range r {\n\t\tfor i, rule := range rules {\n\t\t\tif err := rule.Default(); err != nil {\n\t\t\t\treturn errors.Wrapf(err, \"%s rule #%d\", pos, i+1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Validate rules.\nfunc (r Rules) Validate() error {\n\tfor pos, rules := range r {\n\t\tif err := validate.List(pos, locations); err != nil {\n\t\t\treturn errors.Wrap(err, \"invalid location\")\n\t\t}\n\n\t\tfor i, rule := range rules {\n\t\t\tif err := rule.Validate(); err != nil {\n\t\t\t\treturn errors.Wrapf(err, \"%s rule #%d\", pos, i+1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Apply rules to html.\nfunc (r Rules) Apply(html string) string {\n\tfor pos, rules := range r {\n\t\tlog.Debugf(\"injecting %s rules\", pos)\n\t\tfor _, rule := range rules {\n\t\t\tlog.Debugf(\"  inject %s %q\", rule.Type, rule.Value)\n\t\t\tswitch pos {\n\t\t\tcase \"head\":\n\t\t\t\thtml = Head(html, rule.Apply(html))\n\t\t\tcase \"body\":\n\t\t\t\thtml = Body(html, rule.Apply(html))\n\t\t\t}\n\t\t}\n\t}\n\n\treturn html\n}\n\n// Rule is an injection rule.\ntype Rule struct {\n\t// Type of injection, defaults to \"literal\" unless File is used,\n\t// or Value contains .js or .css extensions.\n\tType string `json:\"type\"`\n\n\t// Value is the literal, inline string, or src/href of the injected tag.\n\tValue string `json:\"value\"`\n\n\t// File is used to load source from disk instead of providing Value. Note\n\t// that if Type is not explicitly provided, then it will default to\n\t// \"inline script\" or \"inline style\" for .js and .css files respectively.\n\tFile string `json:\"file\"`\n}\n\n// Apply rule to html.\nfunc (r *Rule) Apply(html string) string {\n\tswitch r.Type {\n\tcase \"literal\":\n\t\treturn r.Value\n\tcase \"script\":\n\t\treturn Script(r.Value)\n\tcase \"style\":\n\t\treturn Style(r.Value)\n\tcase \"inline script\":\n\t\treturn ScriptInline(r.Value)\n\tcase \"inline style\":\n\t\treturn StyleInline(r.Value)\n\tcase \"comment\":\n\t\treturn Comment(r.Value)\n\tcase \"segment\":\n\t\treturn Segment(r.Value)\n\tcase \"google analytics\":\n\t\treturn GoogleAnalytics(r.Value)\n\tdefault:\n\t\treturn \"\"\n\t}\n}\n\n// Default applies defaults.\nfunc (r *Rule) Default() error {\n\tif r.Type == \"\" {\n\t\tr.Type = \"literal\"\n\t}\n\n\tif r.File != \"\" {\n\t\tif err := r.defaultFile(r.File); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Validate returns an error if incorrect.\nfunc (r *Rule) Validate() error {\n\tif err := validate.List(r.Type, types); err != nil {\n\t\treturn errors.Wrap(err, \"invalid .type\")\n\t}\n\n\tif strings.TrimSpace(r.Value) == \"\" {\n\t\treturn errors.Errorf(`.value is required`)\n\t}\n\n\treturn nil\n}\n\n// defaultFile defaults value\" from the given path.\nfunc (r *Rule) defaultFile(path string) error {\n\tb, err := ioutil.ReadFile(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.Value = string(b)\n\treturn nil\n}\n\n// Head injects a string before the closing head tag.\nfunc Head(html, s string) string {\n\treturn strings.Replace(html, \"</head>\", \"  \"+s+\"\\n  </head>\", 1)\n}\n\n// Body injects a string before the closing body tag.\nfunc Body(html, s string) string {\n\treturn strings.Replace(html, \"</body>\", \"  \"+s+\"\\n  </body>\", 1)\n}\n\n// Script returns an script.\nfunc Script(src string) string {\n\treturn `<script src=\"` + html.EscapeString(src) + `\"></script>`\n}\n\n// ScriptInline returns an inline script.\nfunc ScriptInline(s string) string {\n\treturn `<script>` + s + `</script>`\n}\n\n// Style returns an style.\nfunc Style(href string) string {\n\treturn `<link rel=\"stylesheet\" href=\"` + html.EscapeString(href) + `\">`\n}\n\n// StyleInline returns an inline style.\nfunc StyleInline(s string) string {\n\treturn `<style>` + s + `</style>`\n}\n\n// Comment returns an html comment.\nfunc Comment(s string) string {\n\treturn \"<!-- \" + html.EscapeString(s) + \" -->\"\n}\n\n// Segment inline script with key.\nfunc Segment(key string) string {\n\treturn ScriptInline(`\n  !function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error(\"Segment snippet included twice.\");else{analytics.invoked=!0;analytics.methods=[\"trackSubmit\",\"trackClick\",\"trackLink\",\"trackForm\",\"pageview\",\"identify\",\"reset\",\"group\",\"track\",\"ready\",\"alias\",\"debug\",\"page\",\"once\",\"off\",\"on\"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t<analytics.methods.length;t++){var e=analytics.methods[t];analytics[e]=analytics.factory(e)}analytics.load=function(t){var e=document.createElement(\"script\");e.type=\"text/javascript\";e.async=!0;e.src=(\"https:\"===document.location.protocol?\"https://\":\"http://\")+\"cdn.segment.com/analytics.js/v1/\"+t+\"/analytics.min.js\";var n=document.getElementsByTagName(\"script\")[0];n.parentNode.insertBefore(e,n)};analytics.SNIPPET_VERSION=\"4.0.0\";\n  analytics.load(\"` + key + `\");\n  analytics.page();\n  }}();\n`)\n}\n\n// GoogleAnalytics inline script with tracking key.\nfunc GoogleAnalytics(trackingID string) string {\n\treturn ScriptInline(`\n  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\n  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),\n  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)\n  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');\n\n  ga('create', '` + trackingID + `', 'auto');\n  ga('send', 'pageview');\n`)\n}\n\n// Var injection.\nfunc Var(kind, name string, v interface{}) string {\n\tb, _ := json.Marshal(v)\n\treturn ScriptInline(kind + ` ` + name + ` = ` + string(b))\n}\n"
  },
  {
    "path": "internal/inject/inject_test.go",
    "content": "package inject_test\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/tj/assert\"\n\t\"github.com/apex/up/internal/inject\"\n)\n\nvar html = `<!doctype html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <title>Example</title>\n  </head>\n  <body>\n    <p>Hello World</p>\n  </body>\n</html>\n`\n\nfunc ExampleStyle() {\n\tfmt.Printf(\"%s\\n\", inject.Style(`/sloth.css`))\n\t// Output:\n\t// <link rel=\"stylesheet\" href=\"/sloth.css\">\n}\n\nfunc ExampleStyleInline() {\n\tfmt.Printf(\"%s\\n\", inject.StyleInline(`body { display: none }`))\n\t// Output:\n\t// <style>body { display: none }</style>\n}\n\nfunc ExampleScript() {\n\tfmt.Printf(\"%s\\n\", inject.Script(`/sloth.js`))\n\t// Output:\n\t// <script src=\"/sloth.js\"></script>\n}\n\nfunc ExampleScriptInline() {\n\tfmt.Printf(\"%s\\n\", inject.ScriptInline(`const user = { \"name\": \"Tobi\" }`))\n\t// Output:\n\t// <script>const user = { \"name\": \"Tobi\" }</script>\n}\n\nfunc ExampleComment() {\n\tfmt.Printf(\"%s\\n\", inject.Comment(`Hello World`))\n\t// Output:\n\t// <!-- Hello World -->\n}\n\nfunc ExampleHead() {\n\ts := inject.Head(html, `<link rel=\"stylesheet\" href=\"/style.css\">`)\n\tfmt.Printf(\"%s\\n\", s)\n\t// Output:\n\t// <!doctype html>\n\t// <html>\n\t//   <head>\n\t//     <meta charset=\"utf-8\">\n\t//     <title>Example</title>\n\t//     <link rel=\"stylesheet\" href=\"/style.css\">\n\t//   </head>\n\t//   <body>\n\t//     <p>Hello World</p>\n\t//   </body>\n\t// </html>\n}\n\nfunc ExampleBody() {\n\ts := inject.Body(html, inject.Comment(\"Version 1.0.3\"))\n\tfmt.Printf(\"%s\\n\", s)\n\t// Output:\n\t// <!doctype html>\n\t// <html>\n\t//   <head>\n\t//     <meta charset=\"utf-8\">\n\t//     <title>Example</title>\n\t//   </head>\n\t//   <body>\n\t//     <p>Hello World</p>\n\t//     <!-- Version 1.0.3 -->\n\t//   </body>\n\t// </html>\n}\n\nfunc ExampleSegment() {\n\tfmt.Printf(\"%s\\n\", inject.Segment(`KEY HERE`))\n\t// Output:\n\t// <script>\n\t//   !function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error(\"Segment snippet included twice.\");else{analytics.invoked=!0;analytics.methods=[\"trackSubmit\",\"trackClick\",\"trackLink\",\"trackForm\",\"pageview\",\"identify\",\"reset\",\"group\",\"track\",\"ready\",\"alias\",\"debug\",\"page\",\"once\",\"off\",\"on\"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t<analytics.methods.length;t++){var e=analytics.methods[t];analytics[e]=analytics.factory(e)}analytics.load=function(t){var e=document.createElement(\"script\");e.type=\"text/javascript\";e.async=!0;e.src=(\"https:\"===document.location.protocol?\"https://\":\"http://\")+\"cdn.segment.com/analytics.js/v1/\"+t+\"/analytics.min.js\";var n=document.getElementsByTagName(\"script\")[0];n.parentNode.insertBefore(e,n)};analytics.SNIPPET_VERSION=\"4.0.0\";\n\t//   analytics.load(\"KEY HERE\");\n\t//   analytics.page();\n\t//   }}();\n\t// </script>\n}\n\nfunc ExampleGoogleAnalytics() {\n\tfmt.Printf(\"%s\\n\", inject.GoogleAnalytics(`KEY HERE`))\n\t// Output:\n\t// <script>\n\t//   (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\n\t//   (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),\n\t//   m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)\n\t//   })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');\n\t//\n\t//   ga('create', 'KEY HERE', 'auto');\n\t//   ga('send', 'pageview');\n\t// </script>\n}\n\nfunc ExampleVar() {\n\tuser := map[string]string{\n\t\t\"name\": \"Tobi\",\n\t}\n\n\tfmt.Printf(\"%s\\n\", inject.Var(\"const\", \"user\", user))\n\t// Output:\n\t// <script>const user = {\"name\":\"Tobi\"}</script>\n}\n\nfunc TestRule_Default(t *testing.T) {\n\tr := inject.Rule{Value: `<script></script>`}\n\tassert.NoError(t, r.Default(), \"default\")\n\tassert.NoError(t, r.Validate(), \"validate\")\n\tassert.Equal(t, \"literal\", r.Type)\n}\n\nfunc TestRule_Validate(t *testing.T) {\n\tr := inject.Rule{Type: \"whatever\"}\n\tassert.NoError(t, r.Default(), \"default\")\n\tassert.EqualError(t, r.Validate(), `invalid .type: \"whatever\" is invalid, must be one of:\n\n  • literal\n  • comment\n  • style\n  • script\n  • inline style\n  • inline script\n  • google analytics\n  • segment`)\n}\n\nfunc TestRules_Default(t *testing.T) {\n\tt.Run(\"type literal\", func(t *testing.T) {\n\t\trules := inject.Rules{\n\t\t\t\"head\": []*inject.Rule{\n\t\t\t\t{\n\t\t\t\t\tValue: `<script>var user = {}</script>`,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tassert.NoError(t, rules.Default(), \"default\")\n\t\tassert.NoError(t, rules.Validate(), \"validate\")\n\t})\n}\n\nfunc TestRules_Validate(t *testing.T) {\n\tt.Run(\"missing value\", func(t *testing.T) {\n\t\trules := inject.Rules{\n\t\t\t\"head\": []*inject.Rule{\n\t\t\t\t{\n\t\t\t\t\tType: \"inline script\",\n\t\t\t\t\t// Value: \"var user = {}\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tassert.NoError(t, rules.Default(), \"default\")\n\t\tassert.EqualError(t, rules.Validate(), `head rule #1: .value is required`)\n\t})\n}\n"
  },
  {
    "path": "internal/logs/logs.go",
    "content": "// Package logs provides logging utilities.\npackage logs\n\nimport (\n\t\"os\"\n\n\t\"github.com/apex/log\"\n)\n\n// Fields returns the global log fields.\nfunc Fields() log.Fields {\n\tf := log.Fields{\n\t\t\"app\":     os.Getenv(\"AWS_LAMBDA_FUNCTION_NAME\"),\n\t\t\"region\":  os.Getenv(\"AWS_REGION\"),\n\t\t\"version\": os.Getenv(\"AWS_LAMBDA_FUNCTION_VERSION\"),\n\t\t\"stage\":   os.Getenv(\"UP_STAGE\"),\n\t}\n\n\tif s := os.Getenv(\"UP_COMMIT\"); s != \"\" {\n\t\tf[\"commit\"] = s\n\t}\n\n\treturn f\n}\n\n// Plugin returns a log context for the given plugin name.\nfunc Plugin(name string) log.Interface {\n\tf := Fields()\n\tf[\"plugin\"] = name\n\treturn log.WithFields(f)\n}\n"
  },
  {
    "path": "internal/logs/parser/ast/ast.go",
    "content": "// Package ast provides the log query language abstract syntax tree.\npackage ast\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// Op type.\ntype Op string\n\n// Op types.\nconst (\n\tLNOT Op = \"not\"\n\tNOT     = \"!\"\n\tIN      = \"in\"\n\tOR      = \"||\"\n\tAND     = \"&&\"\n\tNE      = \"!=\"\n\tEQ      = \"=\"\n\tGT      = \">\"\n\tLT      = \"<\"\n\tGE      = \">=\"\n\tLE      = \"<=\"\n)\n\n// Node interface.\ntype Node interface {\n\tString() string\n}\n\n// Root node.\ntype Root struct {\n\tNode Node\n}\n\n// String implementation.\nfunc (n Root) String() string {\n\treturn fmt.Sprintf(`{ %s }`, n.Node)\n}\n\n// Expr node.\ntype Expr struct {\n\tNode Node\n}\n\n// String implementation.\nfunc (n Expr) String() string {\n\treturn fmt.Sprintf(`(%s)`, n.Node)\n}\n\n// Literal node.\ntype Literal string\n\n// String implementation.\nfunc (n Literal) String() string {\n\treturn fmt.Sprintf(`%s`, string(n))\n}\n\n// Tuple node.\ntype Tuple []Node\n\n// String implementation.\nfunc (n Tuple) String() string {\n\treturn fmt.Sprintf(`%#v`, n)\n}\n\n// Contains node.\ntype Contains struct {\n\tNode Node\n}\n\n// String implementation.\nfunc (n Contains) String() string {\n\tswitch v := n.Node.(type) {\n\tcase String:\n\t\treturn fmt.Sprintf(`\"*%s*\"`, string(v))\n\tdefault:\n\t\treturn fmt.Sprintf(`%s`, n.Node)\n\t}\n}\n\n// String node.\ntype String string\n\n// String implementation.\nfunc (n String) String() string {\n\treturn fmt.Sprintf(`$.message = %q`, string(n))\n}\n\n// Property node.\ntype Property string\n\n// String implementation.\nfunc (n Property) String() string {\n\treturn fmt.Sprintf(`$.%s`, string(n))\n}\n\n// Field node.\ntype Field string\n\n// String implementation.\nfunc (n Field) String() string {\n\treturn fmt.Sprintf(`$.fields.%s`, string(n))\n}\n\n// Subscript node.\ntype Subscript struct {\n\tLeft  Node\n\tRight Node\n}\n\n// String implementation.\nfunc (n Subscript) String() string {\n\treturn fmt.Sprintf(`%s[%s]`, n.Left, n.Right)\n}\n\n// Member node.\ntype Member struct {\n\tLeft  Node\n\tRight Node\n}\n\n// String implementation.\nfunc (n Member) String() string {\n\treturn fmt.Sprintf(`%s.%s`, n.Left, n.Right)\n}\n\n// Number node.\ntype Number struct {\n\tValue float64\n\tUnit  string\n}\n\n// String implementation.\nfunc (n Number) String() string {\n\tv := n.Value\n\n\tswitch n.Unit {\n\tcase \"kb\":\n\t\tv *= 1 << 10\n\tcase \"mb\":\n\t\tv *= 1 << 20\n\tcase \"gb\":\n\t\tv *= 1 << 30\n\tcase \"s\":\n\t\tv *= 1000\n\t}\n\n\treturn strconv.FormatFloat(v, 'f', -1, 64)\n}\n\n// Binary node.\ntype Binary struct {\n\tOp    Op\n\tLeft  Node\n\tRight Node\n}\n\n// String implementation.\nfunc (n Binary) String() string {\n\tswitch n.Op {\n\tcase IN:\n\t\tvar s []string\n\t\tfor _, v := range n.Right.(Tuple) {\n\t\t\ts = append(s, fmt.Sprintf(`%s %s %s`, n.Left, EQ, value(v)))\n\t\t}\n\t\treturn fmt.Sprintf(`(%s)`, strings.Join(s, \" || \"))\n\tcase EQ, NE, GT, LT, GE, LE:\n\t\treturn fmt.Sprintf(`%s %s %s`, n.Left, n.Op, value(n.Right))\n\tdefault:\n\t\treturn fmt.Sprintf(`%s %s %s`, n.Left, n.Op, n.Right)\n\t}\n}\n\n// Unary node.\ntype Unary struct {\n\tOp    Op\n\tRight Node\n}\n\n// String implementation.\nfunc (n Unary) String() string {\n\tswitch n.Op {\n\tcase LNOT:\n\t\treturn fmt.Sprintf(`!(%s)`, n.Right)\n\tdefault:\n\t\treturn fmt.Sprintf(`%s%s`, n.Op, n.Right)\n\t}\n}\n\n// value from node.\nfunc value(n Node) string {\n\tswitch v := n.(type) {\n\tcase String:\n\t\treturn fmt.Sprintf(\"%q\", string(v))\n\tcase Field:\n\t\treturn fmt.Sprintf(\"%q\", string(v))\n\tdefault:\n\t\treturn n.String()\n\t}\n}\n"
  },
  {
    "path": "internal/logs/parser/grammar.peg",
    "content": "package parser\n\nimport \"github.com/apex/up/internal/logs/parser/ast\"\n\ntype parser Peg {\n  stack []ast.Node\n  number string\n}\n\nQuery <- _ Expr _ EOF\n\nPrimaryExpr\n  <- Numbers Unit _         { p.AddNumber(text) }\n  / Numbers _               { p.AddNumber(\"\")   }\n  / Severity                { p.AddLevel(text)  }\n  / Stage                   { p.AddStage(text)  }\n  / Id                      { p.AddField(text)  }\n  / String                  { p.AddString(text) }\n  / UnquotedString          { p.AddString(text) }\n  / LPAR Expr RPAR          { p.AddExpr()       }\n\nTupleExpr\n  <- LPAR\n    Expr                    { p.AddTupleValue() }\n    (COMMA\n      Expr                  { p.AddTupleValue() }\n    )*\n  RPAR\n\nInExpr\n  <- IN                     { p.AddTuple() }\n  TupleExpr                 { p.AddBinary(ast.IN) }\n\nNotInExpr\n  <- NOT IN                 { p.AddTuple() }\n  TupleExpr                 { p.AddBinary(ast.IN); p.AddUnary(ast.LNOT) }\n\nPostfixExpr\n  <- PrimaryExpr (\n      DOT Id                { p.AddMember(text)    }\n    / LBRK Number _ RBRK    { p.AddSubscript(text) }\n    / InExpr\n    / NotInExpr\n  )*\n\nUnaryExpr\n  <- PostfixExpr\n  / BANG RelationalExpr     { p.AddUnary(ast.NOT) }\n\nRelationalExpr\n  <- UnaryExpr (\n      GE UnaryExpr          { p.AddBinary(ast.GE) }\n    / GT UnaryExpr          { p.AddBinary(ast.GT) }\n    / LE UnaryExpr          { p.AddBinary(ast.LE) }\n    / LT UnaryExpr          { p.AddBinary(ast.LT) }\n  )*\n\nEqualityExpr\n  <- RelationalExpr (\n      EQEQ RelationalExpr     { p.AddBinary(ast.EQ)   }\n    / NE RelationalExpr       { p.AddBinary(ast.NE)   }\n    / EQ RelationalExpr       { p.AddBinary(ast.EQ)   }\n    / CONTAINS RelationalExpr { p.AddBinaryContains() }\n  )*\n\nLogicalAndExpr <-\n  EqualityExpr (\n      AND EqualityExpr      { p.AddBinary(ast.AND) }\n    / ANDAND EqualityExpr   { p.AddBinary(ast.AND) }\n    / _ EqualityExpr        { p.AddBinary(ast.AND) }\n  )*\n\nLogicalOrExpr <-\n  LogicalAndExpr (\n      OR LogicalAndExpr     { p.AddBinary(ast.OR) }\n    / OROR LogicalAndExpr   { p.AddBinary(ast.OR) }\n  )*\n\nLowNotExpr <-\n    LogicalOrExpr\n  / NOT LogicalOrExpr       { p.AddUnary(ast.LNOT) }\n\nExpr <- LowNotExpr\n\n#\n# Strings\n#\n\nString\n  <- [\"] < StringChar* > [\"] _\n\nStringChar\n  <- Escape / ![\\\"\\n\\\\] .\n\nUnquotedString\n  <- !Keyword < UnquotedStringStartChar UnquotedStringChar* > _\n\nUnquotedStringStartChar\n  <- [a-z] / [A-Z] / [/_]\n\nUnquotedStringChar\n  <- [a-z] / [A-Z] / [0-9] / [/_]\n\nEscape\n  <- SimpleEscape\n  / OctalEscape\n  / HexEscape\n  / UniversalCharacter\n\nSimpleEscape\n  <- '\\\\' ['\\\"?\\\\abfnrtv]\n\nOctalEscape\n  <- '\\\\' [0-7][0-7]?[0-7]?\n\nHexEscape\n  <- '\\\\x' HexDigit+\n\nUniversalCharacter\n   <- '\\\\u' HexQuad\n    / '\\\\U' HexQuad HexQuad\n\nHexQuad\n  <- HexDigit HexDigit HexDigit HexDigit\n\nHexDigit\n  <- [a-f] / [A-F] / [0-9]\n\n#\n# Numeric\n#\n\nNumbers\n  <- Number { p.SetNumber(text) }\n\nNumber\n  <- < Float >\n  / < Integer >\n\nInteger\n  <- [0-9]*\n\nFloat\n  <- Fraction Exponent?\n  / [0-9]+ Exponent\n\nFraction\n  <- [0-9]* '.' [0-9]+\n  / [0-9]+ '.'\n\nExponent\n  <- [eE][+\\-]? [0-9]+\n\n#\n# Stages\n#\n\nStage\n  <- DEVELOPMENT\n  / STAGING\n  / PRODUCTION\n\nDEVELOPMENT <- < 'development' > !IdChar _\nSTAGING     <- < 'staging' > !IdChar _\nPRODUCTION  <- < 'production' > !IdChar _\n\n#\n# Units\n#\n\nUnit\n  <- Bytes\n  / Duration\n\nDuration\n  <- S\n  / MS\n\nS  <- < 's'  > !IdChar _\nMS <- < 'ms' > !IdChar _\n\n\nBytes\n  <- B\n  / KB\n  / MB\n  / GB\n\nB  <- < 'b'  > !IdChar _\nKB <- < 'kb' > !IdChar _\nMB <- < 'mb' > !IdChar _\nGB <- < 'gb' > !IdChar _\n\n#\n# Identifiers\n#\n\nId\n  <- !Keyword < IdCharNoDigit IdChar* > _\n\nIdChar\n  <- [a-z] / [A-Z] / [0-9] / [_]\n\nIdCharNoDigit\n  <- [a-z] / [A-Z] / [_]\n\n#\n# Severity\n#\n\nSeverity\n  <- DEBUG\n  / INFO\n  / WARN\n  / ERROR\n  / FATAL\n\n#\n# Keywords\n#\n\nIN       <- 'in'       !IdChar _\nOR       <- 'or'       !IdChar _\nAND      <- 'and'      !IdChar _\nNOT      <- 'not'      !IdChar _\nCONTAINS <- 'contains' !IdChar _\n\nDEBUG  <- < 'debug' > !IdChar  _\nINFO   <- < 'info'  > !IdChar  _\nWARN   <- < 'warn'  > !IdChar  _\nERROR  <- < 'error' > !IdChar  _\nFATAL  <- < 'fatal' > !IdChar  _\n\nKeyword\n  <- (\n      'production'\n    / 'staging'\n    / 'development'\n    / 'or'\n    / 'and'\n    / 'not'\n    / 'contains'\n    / 'debug'\n    / 'info'\n    / 'warn'\n    / 'error'\n    / 'fatal'\n    / 'in'\n    / 'gb'\n    / 'mb'\n    / 'kb'\n    / 'b'\n    / 'ms'\n    / 's'\n  ) !IdChar\n\n#\n# Punctuators\n#\n\nEQ      <-  '='       _\nLBRK    <-  '['       _\nRBRK    <-  ']'       _\nLPAR    <-  '('       _\nRPAR    <-  ')'       _\nDOT     <-  '.'       _\nBANG    <-  '!'  ![=] _\nLT      <-  '<'  ![=] _\nGT      <-  '>'  ![=] _\nLE      <-  '<='      _\nEQEQ    <-  '=='      _\nGE      <-  '>='      _\nNE      <-  '!='      _\nANDAND  <-  '&&'      _\nOROR    <-  '||'      _\nCOMMA   <-  ','       _\n\n#\n# Whitespace\n#\n\n_\n  <- Whitespace*\n\nWhitespace\n  <- ' ' / '\\t' / EOL\n\nEOL\n  <- '\\r\\n' / '\\n' / '\\r'\n\nEOF\n  <- !.\n"
  },
  {
    "path": "internal/logs/parser/grammar.peg.go",
    "content": "package parser\n\n// Code generated by peg -inline -switch grammar.peg DO NOT EDIT\n\nimport (\n\t\"fmt\"\n\t\"github.com/apex/up/internal/logs/parser/ast\"\n\t\"io\"\n\t\"os\"\n\t\"sort\"\n\t\"strconv\"\n)\n\nconst endSymbol rune = 1114112\n\n/* The rule types inferred from the grammar are below. */\ntype pegRule uint8\n\nconst (\n\truleUnknown pegRule = iota\n\truleQuery\n\trulePrimaryExpr\n\truleTupleExpr\n\truleInExpr\n\truleNotInExpr\n\trulePostfixExpr\n\truleUnaryExpr\n\truleRelationalExpr\n\truleEqualityExpr\n\truleLogicalAndExpr\n\truleLogicalOrExpr\n\truleLowNotExpr\n\truleExpr\n\truleString\n\truleStringChar\n\truleUnquotedString\n\truleUnquotedStringStartChar\n\truleUnquotedStringChar\n\truleEscape\n\truleSimpleEscape\n\truleOctalEscape\n\truleHexEscape\n\truleUniversalCharacter\n\truleHexQuad\n\truleHexDigit\n\truleNumbers\n\truleNumber\n\truleInteger\n\truleFloat\n\truleFraction\n\truleExponent\n\truleStage\n\truleDEVELOPMENT\n\truleSTAGING\n\trulePRODUCTION\n\truleUnit\n\truleDuration\n\truleS\n\truleMS\n\truleBytes\n\truleB\n\truleKB\n\truleMB\n\truleGB\n\truleId\n\truleIdChar\n\truleIdCharNoDigit\n\truleSeverity\n\truleIN\n\truleOR\n\truleAND\n\truleNOT\n\truleCONTAINS\n\truleDEBUG\n\truleINFO\n\truleWARN\n\truleERROR\n\truleFATAL\n\truleKeyword\n\truleEQ\n\truleLBRK\n\truleRBRK\n\truleLPAR\n\truleRPAR\n\truleDOT\n\truleBANG\n\truleLT\n\truleGT\n\truleLE\n\truleEQEQ\n\truleGE\n\truleNE\n\truleANDAND\n\truleOROR\n\truleCOMMA\n\trule_\n\truleWhitespace\n\truleEOL\n\truleEOF\n\truleAction0\n\truleAction1\n\truleAction2\n\truleAction3\n\truleAction4\n\truleAction5\n\truleAction6\n\truleAction7\n\truleAction8\n\truleAction9\n\truleAction10\n\truleAction11\n\truleAction12\n\truleAction13\n\truleAction14\n\truleAction15\n\truleAction16\n\truleAction17\n\truleAction18\n\truleAction19\n\truleAction20\n\truleAction21\n\truleAction22\n\truleAction23\n\truleAction24\n\truleAction25\n\truleAction26\n\truleAction27\n\truleAction28\n\truleAction29\n\truleAction30\n\trulePegText\n\truleAction31\n)\n\nvar rul3s = [...]string{\n\t\"Unknown\",\n\t\"Query\",\n\t\"PrimaryExpr\",\n\t\"TupleExpr\",\n\t\"InExpr\",\n\t\"NotInExpr\",\n\t\"PostfixExpr\",\n\t\"UnaryExpr\",\n\t\"RelationalExpr\",\n\t\"EqualityExpr\",\n\t\"LogicalAndExpr\",\n\t\"LogicalOrExpr\",\n\t\"LowNotExpr\",\n\t\"Expr\",\n\t\"String\",\n\t\"StringChar\",\n\t\"UnquotedString\",\n\t\"UnquotedStringStartChar\",\n\t\"UnquotedStringChar\",\n\t\"Escape\",\n\t\"SimpleEscape\",\n\t\"OctalEscape\",\n\t\"HexEscape\",\n\t\"UniversalCharacter\",\n\t\"HexQuad\",\n\t\"HexDigit\",\n\t\"Numbers\",\n\t\"Number\",\n\t\"Integer\",\n\t\"Float\",\n\t\"Fraction\",\n\t\"Exponent\",\n\t\"Stage\",\n\t\"DEVELOPMENT\",\n\t\"STAGING\",\n\t\"PRODUCTION\",\n\t\"Unit\",\n\t\"Duration\",\n\t\"S\",\n\t\"MS\",\n\t\"Bytes\",\n\t\"B\",\n\t\"KB\",\n\t\"MB\",\n\t\"GB\",\n\t\"Id\",\n\t\"IdChar\",\n\t\"IdCharNoDigit\",\n\t\"Severity\",\n\t\"IN\",\n\t\"OR\",\n\t\"AND\",\n\t\"NOT\",\n\t\"CONTAINS\",\n\t\"DEBUG\",\n\t\"INFO\",\n\t\"WARN\",\n\t\"ERROR\",\n\t\"FATAL\",\n\t\"Keyword\",\n\t\"EQ\",\n\t\"LBRK\",\n\t\"RBRK\",\n\t\"LPAR\",\n\t\"RPAR\",\n\t\"DOT\",\n\t\"BANG\",\n\t\"LT\",\n\t\"GT\",\n\t\"LE\",\n\t\"EQEQ\",\n\t\"GE\",\n\t\"NE\",\n\t\"ANDAND\",\n\t\"OROR\",\n\t\"COMMA\",\n\t\"_\",\n\t\"Whitespace\",\n\t\"EOL\",\n\t\"EOF\",\n\t\"Action0\",\n\t\"Action1\",\n\t\"Action2\",\n\t\"Action3\",\n\t\"Action4\",\n\t\"Action5\",\n\t\"Action6\",\n\t\"Action7\",\n\t\"Action8\",\n\t\"Action9\",\n\t\"Action10\",\n\t\"Action11\",\n\t\"Action12\",\n\t\"Action13\",\n\t\"Action14\",\n\t\"Action15\",\n\t\"Action16\",\n\t\"Action17\",\n\t\"Action18\",\n\t\"Action19\",\n\t\"Action20\",\n\t\"Action21\",\n\t\"Action22\",\n\t\"Action23\",\n\t\"Action24\",\n\t\"Action25\",\n\t\"Action26\",\n\t\"Action27\",\n\t\"Action28\",\n\t\"Action29\",\n\t\"Action30\",\n\t\"PegText\",\n\t\"Action31\",\n}\n\ntype token32 struct {\n\tpegRule\n\tbegin, end uint32\n}\n\nfunc (t *token32) String() string {\n\treturn fmt.Sprintf(\"\\x1B[34m%v\\x1B[m %v %v\", rul3s[t.pegRule], t.begin, t.end)\n}\n\ntype node32 struct {\n\ttoken32\n\tup, next *node32\n}\n\nfunc (node *node32) print(w io.Writer, pretty bool, buffer string) {\n\tvar print func(node *node32, depth int)\n\tprint = func(node *node32, depth int) {\n\t\tfor node != nil {\n\t\t\tfor c := 0; c < depth; c++ {\n\t\t\t\tfmt.Fprintf(w, \" \")\n\t\t\t}\n\t\t\trule := rul3s[node.pegRule]\n\t\t\tquote := strconv.Quote(string(([]rune(buffer)[node.begin:node.end])))\n\t\t\tif !pretty {\n\t\t\t\tfmt.Fprintf(w, \"%v %v\\n\", rule, quote)\n\t\t\t} else {\n\t\t\t\tfmt.Fprintf(w, \"\\x1B[34m%v\\x1B[m %v\\n\", rule, quote)\n\t\t\t}\n\t\t\tif node.up != nil {\n\t\t\t\tprint(node.up, depth+1)\n\t\t\t}\n\t\t\tnode = node.next\n\t\t}\n\t}\n\tprint(node, 0)\n}\n\nfunc (node *node32) Print(w io.Writer, buffer string) {\n\tnode.print(w, false, buffer)\n}\n\nfunc (node *node32) PrettyPrint(w io.Writer, buffer string) {\n\tnode.print(w, true, buffer)\n}\n\ntype tokens32 struct {\n\ttree []token32\n}\n\nfunc (t *tokens32) Trim(length uint32) {\n\tt.tree = t.tree[:length]\n}\n\nfunc (t *tokens32) Print() {\n\tfor _, token := range t.tree {\n\t\tfmt.Println(token.String())\n\t}\n}\n\nfunc (t *tokens32) AST() *node32 {\n\ttype element struct {\n\t\tnode *node32\n\t\tdown *element\n\t}\n\ttokens := t.Tokens()\n\tvar stack *element\n\tfor _, token := range tokens {\n\t\tif token.begin == token.end {\n\t\t\tcontinue\n\t\t}\n\t\tnode := &node32{token32: token}\n\t\tfor stack != nil && stack.node.begin >= token.begin && stack.node.end <= token.end {\n\t\t\tstack.node.next = node.up\n\t\t\tnode.up = stack.node\n\t\t\tstack = stack.down\n\t\t}\n\t\tstack = &element{node: node, down: stack}\n\t}\n\tif stack != nil {\n\t\treturn stack.node\n\t}\n\treturn nil\n}\n\nfunc (t *tokens32) PrintSyntaxTree(buffer string) {\n\tt.AST().Print(os.Stdout, buffer)\n}\n\nfunc (t *tokens32) WriteSyntaxTree(w io.Writer, buffer string) {\n\tt.AST().Print(w, buffer)\n}\n\nfunc (t *tokens32) PrettyPrintSyntaxTree(buffer string) {\n\tt.AST().PrettyPrint(os.Stdout, buffer)\n}\n\nfunc (t *tokens32) Add(rule pegRule, begin, end, index uint32) {\n\ttree, i := t.tree, int(index)\n\tif i >= len(tree) {\n\t\tt.tree = append(tree, token32{pegRule: rule, begin: begin, end: end})\n\t\treturn\n\t}\n\ttree[i] = token32{pegRule: rule, begin: begin, end: end}\n}\n\nfunc (t *tokens32) Tokens() []token32 {\n\treturn t.tree\n}\n\ntype parser struct {\n\tstack  []ast.Node\n\tnumber string\n\n\tBuffer string\n\tbuffer []rune\n\trules  [113]func() bool\n\tparse  func(rule ...int) error\n\treset  func()\n\tPretty bool\n\ttokens32\n}\n\nfunc (p *parser) Parse(rule ...int) error {\n\treturn p.parse(rule...)\n}\n\nfunc (p *parser) Reset() {\n\tp.reset()\n}\n\ntype textPosition struct {\n\tline, symbol int\n}\n\ntype textPositionMap map[int]textPosition\n\nfunc translatePositions(buffer []rune, positions []int) textPositionMap {\n\tlength, translations, j, line, symbol := len(positions), make(textPositionMap, len(positions)), 0, 1, 0\n\tsort.Ints(positions)\n\nsearch:\n\tfor i, c := range buffer {\n\t\tif c == '\\n' {\n\t\t\tline, symbol = line+1, 0\n\t\t} else {\n\t\t\tsymbol++\n\t\t}\n\t\tif i == positions[j] {\n\t\t\ttranslations[positions[j]] = textPosition{line, symbol}\n\t\t\tfor j++; j < length; j++ {\n\t\t\t\tif i != positions[j] {\n\t\t\t\t\tcontinue search\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak search\n\t\t}\n\t}\n\n\treturn translations\n}\n\ntype parseError struct {\n\tp   *parser\n\tmax token32\n}\n\nfunc (e *parseError) Error() string {\n\ttokens, err := []token32{e.max}, \"\\n\"\n\tpositions, p := make([]int, 2*len(tokens)), 0\n\tfor _, token := range tokens {\n\t\tpositions[p], p = int(token.begin), p+1\n\t\tpositions[p], p = int(token.end), p+1\n\t}\n\ttranslations := translatePositions(e.p.buffer, positions)\n\tformat := \"parse error near %v (line %v symbol %v - line %v symbol %v):\\n%v\\n\"\n\tif e.p.Pretty {\n\t\tformat = \"parse error near \\x1B[34m%v\\x1B[m (line %v symbol %v - line %v symbol %v):\\n%v\\n\"\n\t}\n\tfor _, token := range tokens {\n\t\tbegin, end := int(token.begin), int(token.end)\n\t\terr += fmt.Sprintf(format,\n\t\t\trul3s[token.pegRule],\n\t\t\ttranslations[begin].line, translations[begin].symbol,\n\t\t\ttranslations[end].line, translations[end].symbol,\n\t\t\tstrconv.Quote(string(e.p.buffer[begin:end])))\n\t}\n\n\treturn err\n}\n\nfunc (p *parser) PrintSyntaxTree() {\n\tif p.Pretty {\n\t\tp.tokens32.PrettyPrintSyntaxTree(p.Buffer)\n\t} else {\n\t\tp.tokens32.PrintSyntaxTree(p.Buffer)\n\t}\n}\n\nfunc (p *parser) WriteSyntaxTree(w io.Writer) {\n\tp.tokens32.WriteSyntaxTree(w, p.Buffer)\n}\n\nfunc (p *parser) Execute() {\n\tbuffer, _buffer, text, begin, end := p.Buffer, p.buffer, \"\", 0, 0\n\tfor _, token := range p.Tokens() {\n\t\tswitch token.pegRule {\n\n\t\tcase rulePegText:\n\t\t\tbegin, end = int(token.begin), int(token.end)\n\t\t\ttext = string(_buffer[begin:end])\n\n\t\tcase ruleAction0:\n\t\t\tp.AddNumber(text)\n\t\tcase ruleAction1:\n\t\t\tp.AddNumber(\"\")\n\t\tcase ruleAction2:\n\t\t\tp.AddLevel(text)\n\t\tcase ruleAction3:\n\t\t\tp.AddStage(text)\n\t\tcase ruleAction4:\n\t\t\tp.AddField(text)\n\t\tcase ruleAction5:\n\t\t\tp.AddString(text)\n\t\tcase ruleAction6:\n\t\t\tp.AddString(text)\n\t\tcase ruleAction7:\n\t\t\tp.AddExpr()\n\t\tcase ruleAction8:\n\t\t\tp.AddTupleValue()\n\t\tcase ruleAction9:\n\t\t\tp.AddTupleValue()\n\t\tcase ruleAction10:\n\t\t\tp.AddTuple()\n\t\tcase ruleAction11:\n\t\t\tp.AddBinary(ast.IN)\n\t\tcase ruleAction12:\n\t\t\tp.AddTuple()\n\t\tcase ruleAction13:\n\t\t\tp.AddBinary(ast.IN)\n\t\t\tp.AddUnary(ast.LNOT)\n\t\tcase ruleAction14:\n\t\t\tp.AddMember(text)\n\t\tcase ruleAction15:\n\t\t\tp.AddSubscript(text)\n\t\tcase ruleAction16:\n\t\t\tp.AddUnary(ast.NOT)\n\t\tcase ruleAction17:\n\t\t\tp.AddBinary(ast.GE)\n\t\tcase ruleAction18:\n\t\t\tp.AddBinary(ast.GT)\n\t\tcase ruleAction19:\n\t\t\tp.AddBinary(ast.LE)\n\t\tcase ruleAction20:\n\t\t\tp.AddBinary(ast.LT)\n\t\tcase ruleAction21:\n\t\t\tp.AddBinary(ast.EQ)\n\t\tcase ruleAction22:\n\t\t\tp.AddBinary(ast.NE)\n\t\tcase ruleAction23:\n\t\t\tp.AddBinary(ast.EQ)\n\t\tcase ruleAction24:\n\t\t\tp.AddBinaryContains()\n\t\tcase ruleAction25:\n\t\t\tp.AddBinary(ast.AND)\n\t\tcase ruleAction26:\n\t\t\tp.AddBinary(ast.AND)\n\t\tcase ruleAction27:\n\t\t\tp.AddBinary(ast.AND)\n\t\tcase ruleAction28:\n\t\t\tp.AddBinary(ast.OR)\n\t\tcase ruleAction29:\n\t\t\tp.AddBinary(ast.OR)\n\t\tcase ruleAction30:\n\t\t\tp.AddUnary(ast.LNOT)\n\t\tcase ruleAction31:\n\t\t\tp.SetNumber(text)\n\n\t\t}\n\t}\n\t_, _, _, _, _ = buffer, _buffer, text, begin, end\n}\n\nfunc Pretty(pretty bool) func(*parser) error {\n\treturn func(p *parser) error {\n\t\tp.Pretty = pretty\n\t\treturn nil\n\t}\n}\n\nfunc Size(size int) func(*parser) error {\n\treturn func(p *parser) error {\n\t\tp.tokens32 = tokens32{tree: make([]token32, 0, size)}\n\t\treturn nil\n\t}\n}\nfunc (p *parser) Init(options ...func(*parser) error) error {\n\tvar (\n\t\tmax                  token32\n\t\tposition, tokenIndex uint32\n\t\tbuffer               []rune\n\t)\n\tfor _, option := range options {\n\t\terr := option(p)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tp.reset = func() {\n\t\tmax = token32{}\n\t\tposition, tokenIndex = 0, 0\n\n\t\tp.buffer = []rune(p.Buffer)\n\t\tif len(p.buffer) == 0 || p.buffer[len(p.buffer)-1] != endSymbol {\n\t\t\tp.buffer = append(p.buffer, endSymbol)\n\t\t}\n\t\tbuffer = p.buffer\n\t}\n\tp.reset()\n\n\t_rules := p.rules\n\ttree := p.tokens32\n\tp.parse = func(rule ...int) error {\n\t\tr := 1\n\t\tif len(rule) > 0 {\n\t\t\tr = rule[0]\n\t\t}\n\t\tmatches := p.rules[r]()\n\t\tp.tokens32 = tree\n\t\tif matches {\n\t\t\tp.Trim(tokenIndex)\n\t\t\treturn nil\n\t\t}\n\t\treturn &parseError{p, max}\n\t}\n\n\tadd := func(rule pegRule, begin uint32) {\n\t\ttree.Add(rule, begin, position, tokenIndex)\n\t\ttokenIndex++\n\t\tif begin != position && position > max.end {\n\t\t\tmax = token32{rule, begin, position}\n\t\t}\n\t}\n\n\tmatchDot := func() bool {\n\t\tif buffer[position] != endSymbol {\n\t\t\tposition++\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}\n\n\t/*matchChar := func(c byte) bool {\n\t\tif buffer[position] == c {\n\t\t\tposition++\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}*/\n\n\t/*matchRange := func(lower byte, upper byte) bool {\n\t\tif c := buffer[position]; c >= lower && c <= upper {\n\t\t\tposition++\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}*/\n\n\t_rules = [...]func() bool{\n\t\tnil,\n\t\t/* 0 Query <- <(_ Expr _ EOF)> */\n\t\tfunc() bool {\n\t\t\tposition0, tokenIndex0 := position, tokenIndex\n\t\t\t{\n\t\t\t\tposition1 := position\n\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\tgoto l0\n\t\t\t\t}\n\t\t\t\tif !_rules[ruleExpr]() {\n\t\t\t\t\tgoto l0\n\t\t\t\t}\n\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\tgoto l0\n\t\t\t\t}\n\t\t\t\t{\n\t\t\t\t\tposition2 := position\n\t\t\t\t\t{\n\t\t\t\t\t\tposition3, tokenIndex3 := position, tokenIndex\n\t\t\t\t\t\tif !matchDot() {\n\t\t\t\t\t\t\tgoto l3\n\t\t\t\t\t\t}\n\t\t\t\t\t\tgoto l0\n\t\t\t\t\tl3:\n\t\t\t\t\t\tposition, tokenIndex = position3, tokenIndex3\n\t\t\t\t\t}\n\t\t\t\t\tadd(ruleEOF, position2)\n\t\t\t\t}\n\t\t\t\tadd(ruleQuery, position1)\n\t\t\t}\n\t\t\treturn true\n\t\tl0:\n\t\t\tposition, tokenIndex = position0, tokenIndex0\n\t\t\treturn false\n\t\t},\n\t\t/* 1 PrimaryExpr <- <((Numbers Unit _ Action0) / (Severity Action2) / (Stage Action3) / (Id Action4) / ((&('(') (LPAR Expr RPAR Action7)) | (&('\"') (String Action5)) | (&('\\t' | '\\n' | '\\r' | ' ' | '.' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9') (Numbers _ Action1)) | (&('/' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z' | '_' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z') (UnquotedString Action6))))> */\n\t\tnil,\n\t\t/* 2 TupleExpr <- <(LPAR Expr Action8 (COMMA Expr Action9)* RPAR)> */\n\t\tfunc() bool {\n\t\t\tposition5, tokenIndex5 := position, tokenIndex\n\t\t\t{\n\t\t\t\tposition6 := position\n\t\t\t\tif !_rules[ruleLPAR]() {\n\t\t\t\t\tgoto l5\n\t\t\t\t}\n\t\t\t\tif !_rules[ruleExpr]() {\n\t\t\t\t\tgoto l5\n\t\t\t\t}\n\t\t\t\t{\n\t\t\t\t\tadd(ruleAction8, position)\n\t\t\t\t}\n\t\t\tl8:\n\t\t\t\t{\n\t\t\t\t\tposition9, tokenIndex9 := position, tokenIndex\n\t\t\t\t\t{\n\t\t\t\t\t\tposition10 := position\n\t\t\t\t\t\tif buffer[position] != rune(',') {\n\t\t\t\t\t\t\tgoto l9\n\t\t\t\t\t\t}\n\t\t\t\t\t\tposition++\n\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\tgoto l9\n\t\t\t\t\t\t}\n\t\t\t\t\t\tadd(ruleCOMMA, position10)\n\t\t\t\t\t}\n\t\t\t\t\tif !_rules[ruleExpr]() {\n\t\t\t\t\t\tgoto l9\n\t\t\t\t\t}\n\t\t\t\t\t{\n\t\t\t\t\t\tadd(ruleAction9, position)\n\t\t\t\t\t}\n\t\t\t\t\tgoto l8\n\t\t\t\tl9:\n\t\t\t\t\tposition, tokenIndex = position9, tokenIndex9\n\t\t\t\t}\n\t\t\t\tif !_rules[ruleRPAR]() {\n\t\t\t\t\tgoto l5\n\t\t\t\t}\n\t\t\t\tadd(ruleTupleExpr, position6)\n\t\t\t}\n\t\t\treturn true\n\t\tl5:\n\t\t\tposition, tokenIndex = position5, tokenIndex5\n\t\t\treturn false\n\t\t},\n\t\t/* 3 InExpr <- <(IN Action10 TupleExpr Action11)> */\n\t\tnil,\n\t\t/* 4 NotInExpr <- <(NOT IN Action12 TupleExpr Action13)> */\n\t\tnil,\n\t\t/* 5 PostfixExpr <- <(PrimaryExpr ((&('n') NotInExpr) | (&('i') InExpr) | (&('[') (LBRK Number _ RBRK Action15)) | (&('.') (DOT Id Action14)))*)> */\n\t\tnil,\n\t\t/* 6 UnaryExpr <- <(PostfixExpr / (BANG RelationalExpr Action16))> */\n\t\tfunc() bool {\n\t\t\tposition15, tokenIndex15 := position, tokenIndex\n\t\t\t{\n\t\t\t\tposition16 := position\n\t\t\t\t{\n\t\t\t\t\tposition17, tokenIndex17 := position, tokenIndex\n\t\t\t\t\t{\n\t\t\t\t\t\tposition19 := position\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tposition20 := position\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tposition21, tokenIndex21 := position, tokenIndex\n\t\t\t\t\t\t\t\tif !_rules[ruleNumbers]() {\n\t\t\t\t\t\t\t\t\tgoto l22\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tposition23 := position\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tposition24, tokenIndex24 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tposition26 := position\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tswitch buffer[position] {\n\t\t\t\t\t\t\t\t\t\t\t\tcase 'g':\n\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition28 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition29 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('g') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l25\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('b') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l25\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tadd(rulePegText, position29)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition30, tokenIndex30 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[ruleIdChar]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l30\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l25\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tl30:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position30, tokenIndex30\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l25\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tadd(ruleGB, position28)\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\tcase 'm':\n\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition31 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition32 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('m') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l25\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('b') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l25\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tadd(rulePegText, position32)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition33, tokenIndex33 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[ruleIdChar]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l33\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l25\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tl33:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position33, tokenIndex33\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l25\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tadd(ruleMB, position31)\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\tcase 'k':\n\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition34 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition35 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('k') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l25\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('b') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l25\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tadd(rulePegText, position35)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition36, tokenIndex36 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[ruleIdChar]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l36\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l25\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tl36:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position36, tokenIndex36\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l25\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tadd(ruleKB, position34)\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition37 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition38 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('b') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l25\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tadd(rulePegText, position38)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition39, tokenIndex39 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[ruleIdChar]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l39\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l25\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tl39:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position39, tokenIndex39\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l25\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tadd(ruleB, position37)\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\tadd(ruleBytes, position26)\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tgoto l24\n\t\t\t\t\t\t\t\t\tl25:\n\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position24, tokenIndex24\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tposition40 := position\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tposition41, tokenIndex41 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition43 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition44 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('s') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l42\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tadd(rulePegText, position44)\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition45, tokenIndex45 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[ruleIdChar]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l45\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l42\n\t\t\t\t\t\t\t\t\t\t\t\t\tl45:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position45, tokenIndex45\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l42\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tadd(ruleS, position43)\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tgoto l41\n\t\t\t\t\t\t\t\t\t\t\tl42:\n\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position41, tokenIndex41\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition46 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition47 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('m') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l22\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('s') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l22\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tadd(rulePegText, position47)\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition48, tokenIndex48 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[ruleIdChar]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l48\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l22\n\t\t\t\t\t\t\t\t\t\t\t\t\tl48:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position48, tokenIndex48\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l22\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tadd(ruleMS, position46)\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tl41:\n\t\t\t\t\t\t\t\t\t\t\tadd(ruleDuration, position40)\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tl24:\n\t\t\t\t\t\t\t\t\tadd(ruleUnit, position23)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\tgoto l22\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tadd(ruleAction0, position)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tgoto l21\n\t\t\t\t\t\t\tl22:\n\t\t\t\t\t\t\t\tposition, tokenIndex = position21, tokenIndex21\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tposition51 := position\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tswitch buffer[position] {\n\t\t\t\t\t\t\t\t\t\tcase 'f':\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tposition53 := position\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition54 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('f') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('a') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('t') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('a') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('l') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tadd(rulePegText, position54)\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition55, tokenIndex55 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[ruleIdChar]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l55\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\tl55:\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position55, tokenIndex55\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tadd(ruleFATAL, position53)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\tcase 'e':\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tposition56 := position\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition57 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('e') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('r') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('r') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('o') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('r') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tadd(rulePegText, position57)\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition58, tokenIndex58 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[ruleIdChar]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l58\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\tl58:\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position58, tokenIndex58\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tadd(ruleERROR, position56)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\tcase 'w':\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tposition59 := position\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition60 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('w') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('a') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('r') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('n') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tadd(rulePegText, position60)\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition61, tokenIndex61 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[ruleIdChar]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l61\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\tl61:\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position61, tokenIndex61\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tadd(ruleWARN, position59)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\tcase 'i':\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tposition62 := position\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition63 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('i') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('n') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('f') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('o') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tadd(rulePegText, position63)\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition64, tokenIndex64 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[ruleIdChar]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l64\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\tl64:\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position64, tokenIndex64\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tadd(ruleINFO, position62)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tposition65 := position\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition66 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('d') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('e') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('b') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('u') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('g') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tadd(rulePegText, position66)\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition67, tokenIndex67 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[ruleIdChar]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l67\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\tl67:\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position67, tokenIndex67\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l50\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tadd(ruleDEBUG, position65)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tadd(ruleSeverity, position51)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tadd(ruleAction2, position)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tgoto l21\n\t\t\t\t\t\t\tl50:\n\t\t\t\t\t\t\t\tposition, tokenIndex = position21, tokenIndex21\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tposition70 := position\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tswitch buffer[position] {\n\t\t\t\t\t\t\t\t\t\tcase 'p':\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tposition72 := position\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition73 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('p') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('r') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('o') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('d') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('u') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('c') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('t') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('i') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('o') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('n') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tadd(rulePegText, position73)\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition74, tokenIndex74 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[ruleIdChar]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l74\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\tl74:\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position74, tokenIndex74\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tadd(rulePRODUCTION, position72)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\tcase 's':\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tposition75 := position\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition76 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('s') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('t') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('a') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('g') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('i') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('n') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('g') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tadd(rulePegText, position76)\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition77, tokenIndex77 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[ruleIdChar]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l77\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\tl77:\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position77, tokenIndex77\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tadd(ruleSTAGING, position75)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tposition78 := position\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition79 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('d') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('e') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('v') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('e') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('l') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('o') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('p') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('m') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('e') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('n') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('t') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\tadd(rulePegText, position79)\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition80, tokenIndex80 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[ruleIdChar]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l80\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\tl80:\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position80, tokenIndex80\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l69\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tadd(ruleDEVELOPMENT, position78)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tadd(ruleStage, position70)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tadd(ruleAction3, position)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tgoto l21\n\t\t\t\t\t\t\tl69:\n\t\t\t\t\t\t\t\tposition, tokenIndex = position21, tokenIndex21\n\t\t\t\t\t\t\t\tif !_rules[ruleId]() {\n\t\t\t\t\t\t\t\t\tgoto l82\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tadd(ruleAction4, position)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tgoto l21\n\t\t\t\t\t\t\tl82:\n\t\t\t\t\t\t\t\tposition, tokenIndex = position21, tokenIndex21\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tswitch buffer[position] {\n\t\t\t\t\t\t\t\t\tcase '(':\n\t\t\t\t\t\t\t\t\t\tif !_rules[ruleLPAR]() {\n\t\t\t\t\t\t\t\t\t\t\tgoto l18\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif !_rules[ruleExpr]() {\n\t\t\t\t\t\t\t\t\t\t\tgoto l18\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif !_rules[ruleRPAR]() {\n\t\t\t\t\t\t\t\t\t\t\tgoto l18\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tadd(ruleAction7, position)\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\tcase '\"':\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tposition86 := position\n\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('\"') {\n\t\t\t\t\t\t\t\t\t\t\t\tgoto l18\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tposition87 := position\n\t\t\t\t\t\t\t\t\t\t\tl88:\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition89, tokenIndex89 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition90 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition91, tokenIndex91 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition93 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition94, tokenIndex94 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition96 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('\\\\') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l95\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tswitch buffer[position] {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcase 'v':\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('v') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l95\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcase 't':\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('t') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l95\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcase 'r':\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('r') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l95\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcase 'n':\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('n') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l95\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcase 'f':\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('f') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l95\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcase 'b':\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('b') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l95\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcase 'a':\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('a') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l95\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcase '\\\\':\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('\\\\') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l95\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcase '?':\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('?') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l95\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcase '\"':\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('\"') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l95\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('\\'') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l95\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tadd(ruleSimpleEscape, position96)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l94\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tl95:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position94, tokenIndex94\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition99 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('\\\\') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l98\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif c := buffer[position]; c < rune('0') || c > rune('7') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l98\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition100, tokenIndex100 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif c := buffer[position]; c < rune('0') || c > rune('7') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l100\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l101\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tl100:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position100, tokenIndex100\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tl101:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition102, tokenIndex102 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif c := buffer[position]; c < rune('0') || c > rune('7') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l102\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l103\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tl102:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position102, tokenIndex102\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tl103:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tadd(ruleOctalEscape, position99)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l94\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tl98:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position94, tokenIndex94\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition105 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('\\\\') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l104\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('x') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l104\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[ruleHexDigit]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l104\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tl106:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition107, tokenIndex107 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[ruleHexDigit]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l107\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l106\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tl107:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position107, tokenIndex107\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tadd(ruleHexEscape, position105)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l94\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tl104:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position94, tokenIndex94\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition108 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition109, tokenIndex109 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('\\\\') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l110\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('u') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l110\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[ruleHexQuad]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l110\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l109\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tl110:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position109, tokenIndex109\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('\\\\') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l92\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('U') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l92\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[ruleHexQuad]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l92\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[ruleHexQuad]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l92\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tl109:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tadd(ruleUniversalCharacter, position108)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tl94:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tadd(ruleEscape, position93)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l91\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tl92:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position91, tokenIndex91\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition111, tokenIndex111 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tswitch buffer[position] {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcase '\\\\':\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('\\\\') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l111\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcase '\\n':\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('\\n') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l111\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('\"') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l111\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l89\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tl111:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position111, tokenIndex111\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif !matchDot() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l89\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tl91:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tadd(ruleStringChar, position90)\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l88\n\t\t\t\t\t\t\t\t\t\t\t\tl89:\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position89, tokenIndex89\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tadd(rulePegText, position87)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('\"') {\n\t\t\t\t\t\t\t\t\t\t\t\tgoto l18\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\t\t\tgoto l18\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tadd(ruleString, position86)\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tadd(ruleAction5, position)\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\tcase '\\t', '\\n', '\\r', ' ', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':\n\t\t\t\t\t\t\t\t\t\tif !_rules[ruleNumbers]() {\n\t\t\t\t\t\t\t\t\t\t\tgoto l18\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\t\tgoto l18\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tadd(ruleAction1, position)\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tposition115 := position\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tposition116, tokenIndex116 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\tif !_rules[ruleKeyword]() {\n\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l116\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tgoto l18\n\t\t\t\t\t\t\t\t\t\t\tl116:\n\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position116, tokenIndex116\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tposition117 := position\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition118 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tswitch buffer[position] {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcase '/', '_':\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition120, tokenIndex120 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('/') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l121\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l120\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tl121:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position120, tokenIndex120\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('_') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l18\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tl120:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcase 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z':\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif c := buffer[position]; c < rune('A') || c > rune('Z') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l18\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif c := buffer[position]; c < rune('a') || c > rune('z') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l18\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\t\t\tadd(ruleUnquotedStringStartChar, position118)\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tl122:\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition123, tokenIndex123 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition124 := position\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tswitch buffer[position] {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcase '/', '_':\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition126, tokenIndex126 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('/') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l127\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l126\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tl127:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position126, tokenIndex126\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('_') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l123\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tl126:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcase '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif c := buffer[position]; c < rune('0') || c > rune('9') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l123\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcase 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z':\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif c := buffer[position]; c < rune('A') || c > rune('Z') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l123\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif c := buffer[position]; c < rune('a') || c > rune('z') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l123\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tadd(ruleUnquotedStringChar, position124)\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tgoto l122\n\t\t\t\t\t\t\t\t\t\t\t\tl123:\n\t\t\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position123, tokenIndex123\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tadd(rulePegText, position117)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\t\t\tgoto l18\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tadd(ruleUnquotedString, position115)\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tadd(ruleAction6, position)\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tl21:\n\t\t\t\t\t\t\tadd(rulePrimaryExpr, position20)\n\t\t\t\t\t\t}\n\t\t\t\t\tl129:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tposition130, tokenIndex130 := position, tokenIndex\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tswitch buffer[position] {\n\t\t\t\t\t\t\t\tcase 'n':\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tposition132 := position\n\t\t\t\t\t\t\t\t\t\tif !_rules[ruleNOT]() {\n\t\t\t\t\t\t\t\t\t\t\tgoto l130\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif !_rules[ruleIN]() {\n\t\t\t\t\t\t\t\t\t\t\tgoto l130\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tadd(ruleAction12, position)\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif !_rules[ruleTupleExpr]() {\n\t\t\t\t\t\t\t\t\t\t\tgoto l130\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tadd(ruleAction13, position)\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tadd(ruleNotInExpr, position132)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\tcase 'i':\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tposition135 := position\n\t\t\t\t\t\t\t\t\t\tif !_rules[ruleIN]() {\n\t\t\t\t\t\t\t\t\t\t\tgoto l130\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tadd(ruleAction10, position)\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif !_rules[ruleTupleExpr]() {\n\t\t\t\t\t\t\t\t\t\t\tgoto l130\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tadd(ruleAction11, position)\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tadd(ruleInExpr, position135)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\tcase '[':\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tposition138 := position\n\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('[') {\n\t\t\t\t\t\t\t\t\t\t\tgoto l130\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\t\tgoto l130\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tadd(ruleLBRK, position138)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif !_rules[ruleNumber]() {\n\t\t\t\t\t\t\t\t\t\tgoto l130\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\tgoto l130\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tposition139 := position\n\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune(']') {\n\t\t\t\t\t\t\t\t\t\t\tgoto l130\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\t\tgoto l130\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tadd(ruleRBRK, position139)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tadd(ruleAction15, position)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tposition141 := position\n\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('.') {\n\t\t\t\t\t\t\t\t\t\t\tgoto l130\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\t\tgoto l130\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tadd(ruleDOT, position141)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif !_rules[ruleId]() {\n\t\t\t\t\t\t\t\t\t\tgoto l130\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tadd(ruleAction14, position)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tgoto l129\n\t\t\t\t\t\tl130:\n\t\t\t\t\t\t\tposition, tokenIndex = position130, tokenIndex130\n\t\t\t\t\t\t}\n\t\t\t\t\t\tadd(rulePostfixExpr, position19)\n\t\t\t\t\t}\n\t\t\t\t\tgoto l17\n\t\t\t\tl18:\n\t\t\t\t\tposition, tokenIndex = position17, tokenIndex17\n\t\t\t\t\t{\n\t\t\t\t\t\tposition143 := position\n\t\t\t\t\t\tif buffer[position] != rune('!') {\n\t\t\t\t\t\t\tgoto l15\n\t\t\t\t\t\t}\n\t\t\t\t\t\tposition++\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tposition144, tokenIndex144 := position, tokenIndex\n\t\t\t\t\t\t\tif buffer[position] != rune('=') {\n\t\t\t\t\t\t\t\tgoto l144\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tgoto l15\n\t\t\t\t\t\tl144:\n\t\t\t\t\t\t\tposition, tokenIndex = position144, tokenIndex144\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\tgoto l15\n\t\t\t\t\t\t}\n\t\t\t\t\t\tadd(ruleBANG, position143)\n\t\t\t\t\t}\n\t\t\t\t\tif !_rules[ruleRelationalExpr]() {\n\t\t\t\t\t\tgoto l15\n\t\t\t\t\t}\n\t\t\t\t\t{\n\t\t\t\t\t\tadd(ruleAction16, position)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tl17:\n\t\t\t\tadd(ruleUnaryExpr, position16)\n\t\t\t}\n\t\t\treturn true\n\t\tl15:\n\t\t\tposition, tokenIndex = position15, tokenIndex15\n\t\t\treturn false\n\t\t},\n\t\t/* 7 RelationalExpr <- <(UnaryExpr ((GE UnaryExpr Action17) / (GT UnaryExpr Action18) / (LE UnaryExpr Action19) / (LT UnaryExpr Action20))*)> */\n\t\tfunc() bool {\n\t\t\tposition146, tokenIndex146 := position, tokenIndex\n\t\t\t{\n\t\t\t\tposition147 := position\n\t\t\t\tif !_rules[ruleUnaryExpr]() {\n\t\t\t\t\tgoto l146\n\t\t\t\t}\n\t\t\tl148:\n\t\t\t\t{\n\t\t\t\t\tposition149, tokenIndex149 := position, tokenIndex\n\t\t\t\t\t{\n\t\t\t\t\t\tposition150, tokenIndex150 := position, tokenIndex\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tposition152 := position\n\t\t\t\t\t\t\tif buffer[position] != rune('>') {\n\t\t\t\t\t\t\t\tgoto l151\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('=') {\n\t\t\t\t\t\t\t\tgoto l151\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\tgoto l151\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tadd(ruleGE, position152)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !_rules[ruleUnaryExpr]() {\n\t\t\t\t\t\t\tgoto l151\n\t\t\t\t\t\t}\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tadd(ruleAction17, position)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tgoto l150\n\t\t\t\t\tl151:\n\t\t\t\t\t\tposition, tokenIndex = position150, tokenIndex150\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tposition155 := position\n\t\t\t\t\t\t\tif buffer[position] != rune('>') {\n\t\t\t\t\t\t\t\tgoto l154\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tposition156, tokenIndex156 := position, tokenIndex\n\t\t\t\t\t\t\t\tif buffer[position] != rune('=') {\n\t\t\t\t\t\t\t\t\tgoto l156\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\tgoto l154\n\t\t\t\t\t\t\tl156:\n\t\t\t\t\t\t\t\tposition, tokenIndex = position156, tokenIndex156\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\tgoto l154\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tadd(ruleGT, position155)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !_rules[ruleUnaryExpr]() {\n\t\t\t\t\t\t\tgoto l154\n\t\t\t\t\t\t}\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tadd(ruleAction18, position)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tgoto l150\n\t\t\t\t\tl154:\n\t\t\t\t\t\tposition, tokenIndex = position150, tokenIndex150\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tposition159 := position\n\t\t\t\t\t\t\tif buffer[position] != rune('<') {\n\t\t\t\t\t\t\t\tgoto l158\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('=') {\n\t\t\t\t\t\t\t\tgoto l158\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\tgoto l158\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tadd(ruleLE, position159)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !_rules[ruleUnaryExpr]() {\n\t\t\t\t\t\t\tgoto l158\n\t\t\t\t\t\t}\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tadd(ruleAction19, position)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tgoto l150\n\t\t\t\t\tl158:\n\t\t\t\t\t\tposition, tokenIndex = position150, tokenIndex150\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tposition161 := position\n\t\t\t\t\t\t\tif buffer[position] != rune('<') {\n\t\t\t\t\t\t\t\tgoto l149\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tposition162, tokenIndex162 := position, tokenIndex\n\t\t\t\t\t\t\t\tif buffer[position] != rune('=') {\n\t\t\t\t\t\t\t\t\tgoto l162\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\tgoto l149\n\t\t\t\t\t\t\tl162:\n\t\t\t\t\t\t\t\tposition, tokenIndex = position162, tokenIndex162\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\tgoto l149\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tadd(ruleLT, position161)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !_rules[ruleUnaryExpr]() {\n\t\t\t\t\t\t\tgoto l149\n\t\t\t\t\t\t}\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tadd(ruleAction20, position)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\tl150:\n\t\t\t\t\tgoto l148\n\t\t\t\tl149:\n\t\t\t\t\tposition, tokenIndex = position149, tokenIndex149\n\t\t\t\t}\n\t\t\t\tadd(ruleRelationalExpr, position147)\n\t\t\t}\n\t\t\treturn true\n\t\tl146:\n\t\t\tposition, tokenIndex = position146, tokenIndex146\n\t\t\treturn false\n\t\t},\n\t\t/* 8 EqualityExpr <- <(RelationalExpr ((EQEQ RelationalExpr Action21) / ((&('c') (CONTAINS RelationalExpr Action24)) | (&('=') (EQ RelationalExpr Action23)) | (&('!') (NE RelationalExpr Action22))))*)> */\n\t\tfunc() bool {\n\t\t\tposition164, tokenIndex164 := position, tokenIndex\n\t\t\t{\n\t\t\t\tposition165 := position\n\t\t\t\tif !_rules[ruleRelationalExpr]() {\n\t\t\t\t\tgoto l164\n\t\t\t\t}\n\t\t\tl166:\n\t\t\t\t{\n\t\t\t\t\tposition167, tokenIndex167 := position, tokenIndex\n\t\t\t\t\t{\n\t\t\t\t\t\tposition168, tokenIndex168 := position, tokenIndex\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tposition170 := position\n\t\t\t\t\t\t\tif buffer[position] != rune('=') {\n\t\t\t\t\t\t\t\tgoto l169\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('=') {\n\t\t\t\t\t\t\t\tgoto l169\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\tgoto l169\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tadd(ruleEQEQ, position170)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !_rules[ruleRelationalExpr]() {\n\t\t\t\t\t\t\tgoto l169\n\t\t\t\t\t\t}\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tadd(ruleAction21, position)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tgoto l168\n\t\t\t\t\tl169:\n\t\t\t\t\t\tposition, tokenIndex = position168, tokenIndex168\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tswitch buffer[position] {\n\t\t\t\t\t\t\tcase 'c':\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tposition173 := position\n\t\t\t\t\t\t\t\t\tif buffer[position] != rune('c') {\n\t\t\t\t\t\t\t\t\t\tgoto l167\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\tif buffer[position] != rune('o') {\n\t\t\t\t\t\t\t\t\t\tgoto l167\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\tif buffer[position] != rune('n') {\n\t\t\t\t\t\t\t\t\t\tgoto l167\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\tif buffer[position] != rune('t') {\n\t\t\t\t\t\t\t\t\t\tgoto l167\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\tif buffer[position] != rune('a') {\n\t\t\t\t\t\t\t\t\t\tgoto l167\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\tif buffer[position] != rune('i') {\n\t\t\t\t\t\t\t\t\t\tgoto l167\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\tif buffer[position] != rune('n') {\n\t\t\t\t\t\t\t\t\t\tgoto l167\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\tif buffer[position] != rune('s') {\n\t\t\t\t\t\t\t\t\t\tgoto l167\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tposition174, tokenIndex174 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\tif !_rules[ruleIdChar]() {\n\t\t\t\t\t\t\t\t\t\t\tgoto l174\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tgoto l167\n\t\t\t\t\t\t\t\t\tl174:\n\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position174, tokenIndex174\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\tgoto l167\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tadd(ruleCONTAINS, position173)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif !_rules[ruleRelationalExpr]() {\n\t\t\t\t\t\t\t\t\tgoto l167\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tadd(ruleAction24, position)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\tcase '=':\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tposition176 := position\n\t\t\t\t\t\t\t\t\tif buffer[position] != rune('=') {\n\t\t\t\t\t\t\t\t\t\tgoto l167\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\tgoto l167\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tadd(ruleEQ, position176)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif !_rules[ruleRelationalExpr]() {\n\t\t\t\t\t\t\t\t\tgoto l167\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tadd(ruleAction23, position)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tposition178 := position\n\t\t\t\t\t\t\t\t\tif buffer[position] != rune('!') {\n\t\t\t\t\t\t\t\t\t\tgoto l167\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\tif buffer[position] != rune('=') {\n\t\t\t\t\t\t\t\t\t\tgoto l167\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\t\t\tgoto l167\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tadd(ruleNE, position178)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif !_rules[ruleRelationalExpr]() {\n\t\t\t\t\t\t\t\t\tgoto l167\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tadd(ruleAction22, position)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\t\t\t\tl168:\n\t\t\t\t\tgoto l166\n\t\t\t\tl167:\n\t\t\t\t\tposition, tokenIndex = position167, tokenIndex167\n\t\t\t\t}\n\t\t\t\tadd(ruleEqualityExpr, position165)\n\t\t\t}\n\t\t\treturn true\n\t\tl164:\n\t\t\tposition, tokenIndex = position164, tokenIndex164\n\t\t\treturn false\n\t\t},\n\t\t/* 9 LogicalAndExpr <- <(EqualityExpr ((AND EqualityExpr Action25) / (ANDAND EqualityExpr Action26) / (_ EqualityExpr Action27))*)> */\n\t\tfunc() bool {\n\t\t\tposition180, tokenIndex180 := position, tokenIndex\n\t\t\t{\n\t\t\t\tposition181 := position\n\t\t\t\tif !_rules[ruleEqualityExpr]() {\n\t\t\t\t\tgoto l180\n\t\t\t\t}\n\t\t\tl182:\n\t\t\t\t{\n\t\t\t\t\tposition183, tokenIndex183 := position, tokenIndex\n\t\t\t\t\t{\n\t\t\t\t\t\tposition184, tokenIndex184 := position, tokenIndex\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tposition186 := position\n\t\t\t\t\t\t\tif buffer[position] != rune('a') {\n\t\t\t\t\t\t\t\tgoto l185\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('n') {\n\t\t\t\t\t\t\t\tgoto l185\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('d') {\n\t\t\t\t\t\t\t\tgoto l185\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tposition187, tokenIndex187 := position, tokenIndex\n\t\t\t\t\t\t\t\tif !_rules[ruleIdChar]() {\n\t\t\t\t\t\t\t\t\tgoto l187\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tgoto l185\n\t\t\t\t\t\t\tl187:\n\t\t\t\t\t\t\t\tposition, tokenIndex = position187, tokenIndex187\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\tgoto l185\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tadd(ruleAND, position186)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !_rules[ruleEqualityExpr]() {\n\t\t\t\t\t\t\tgoto l185\n\t\t\t\t\t\t}\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tadd(ruleAction25, position)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tgoto l184\n\t\t\t\t\tl185:\n\t\t\t\t\t\tposition, tokenIndex = position184, tokenIndex184\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tposition190 := position\n\t\t\t\t\t\t\tif buffer[position] != rune('&') {\n\t\t\t\t\t\t\t\tgoto l189\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('&') {\n\t\t\t\t\t\t\t\tgoto l189\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\tgoto l189\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tadd(ruleANDAND, position190)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !_rules[ruleEqualityExpr]() {\n\t\t\t\t\t\t\tgoto l189\n\t\t\t\t\t\t}\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tadd(ruleAction26, position)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tgoto l184\n\t\t\t\t\tl189:\n\t\t\t\t\t\tposition, tokenIndex = position184, tokenIndex184\n\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\tgoto l183\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !_rules[ruleEqualityExpr]() {\n\t\t\t\t\t\t\tgoto l183\n\t\t\t\t\t\t}\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tadd(ruleAction27, position)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\tl184:\n\t\t\t\t\tgoto l182\n\t\t\t\tl183:\n\t\t\t\t\tposition, tokenIndex = position183, tokenIndex183\n\t\t\t\t}\n\t\t\t\tadd(ruleLogicalAndExpr, position181)\n\t\t\t}\n\t\t\treturn true\n\t\tl180:\n\t\t\tposition, tokenIndex = position180, tokenIndex180\n\t\t\treturn false\n\t\t},\n\t\t/* 10 LogicalOrExpr <- <(LogicalAndExpr ((OR LogicalAndExpr Action28) / (OROR LogicalAndExpr Action29))*)> */\n\t\tfunc() bool {\n\t\t\tposition193, tokenIndex193 := position, tokenIndex\n\t\t\t{\n\t\t\t\tposition194 := position\n\t\t\t\tif !_rules[ruleLogicalAndExpr]() {\n\t\t\t\t\tgoto l193\n\t\t\t\t}\n\t\t\tl195:\n\t\t\t\t{\n\t\t\t\t\tposition196, tokenIndex196 := position, tokenIndex\n\t\t\t\t\t{\n\t\t\t\t\t\tposition197, tokenIndex197 := position, tokenIndex\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tposition199 := position\n\t\t\t\t\t\t\tif buffer[position] != rune('o') {\n\t\t\t\t\t\t\t\tgoto l198\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('r') {\n\t\t\t\t\t\t\t\tgoto l198\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tposition200, tokenIndex200 := position, tokenIndex\n\t\t\t\t\t\t\t\tif !_rules[ruleIdChar]() {\n\t\t\t\t\t\t\t\t\tgoto l200\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tgoto l198\n\t\t\t\t\t\t\tl200:\n\t\t\t\t\t\t\t\tposition, tokenIndex = position200, tokenIndex200\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\tgoto l198\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tadd(ruleOR, position199)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !_rules[ruleLogicalAndExpr]() {\n\t\t\t\t\t\t\tgoto l198\n\t\t\t\t\t\t}\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tadd(ruleAction28, position)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tgoto l197\n\t\t\t\t\tl198:\n\t\t\t\t\t\tposition, tokenIndex = position197, tokenIndex197\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tposition202 := position\n\t\t\t\t\t\t\tif buffer[position] != rune('|') {\n\t\t\t\t\t\t\t\tgoto l196\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('|') {\n\t\t\t\t\t\t\t\tgoto l196\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\t\t\t\tgoto l196\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tadd(ruleOROR, position202)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !_rules[ruleLogicalAndExpr]() {\n\t\t\t\t\t\t\tgoto l196\n\t\t\t\t\t\t}\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tadd(ruleAction29, position)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\tl197:\n\t\t\t\t\tgoto l195\n\t\t\t\tl196:\n\t\t\t\t\tposition, tokenIndex = position196, tokenIndex196\n\t\t\t\t}\n\t\t\t\tadd(ruleLogicalOrExpr, position194)\n\t\t\t}\n\t\t\treturn true\n\t\tl193:\n\t\t\tposition, tokenIndex = position193, tokenIndex193\n\t\t\treturn false\n\t\t},\n\t\t/* 11 LowNotExpr <- <(LogicalOrExpr / (NOT LogicalOrExpr Action30))> */\n\t\tnil,\n\t\t/* 12 Expr <- <LowNotExpr> */\n\t\tfunc() bool {\n\t\t\tposition205, tokenIndex205 := position, tokenIndex\n\t\t\t{\n\t\t\t\tposition206 := position\n\t\t\t\t{\n\t\t\t\t\tposition207 := position\n\t\t\t\t\t{\n\t\t\t\t\t\tposition208, tokenIndex208 := position, tokenIndex\n\t\t\t\t\t\tif !_rules[ruleLogicalOrExpr]() {\n\t\t\t\t\t\t\tgoto l209\n\t\t\t\t\t\t}\n\t\t\t\t\t\tgoto l208\n\t\t\t\t\tl209:\n\t\t\t\t\t\tposition, tokenIndex = position208, tokenIndex208\n\t\t\t\t\t\tif !_rules[ruleNOT]() {\n\t\t\t\t\t\t\tgoto l205\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !_rules[ruleLogicalOrExpr]() {\n\t\t\t\t\t\t\tgoto l205\n\t\t\t\t\t\t}\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tadd(ruleAction30, position)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\tl208:\n\t\t\t\t\tadd(ruleLowNotExpr, position207)\n\t\t\t\t}\n\t\t\t\tadd(ruleExpr, position206)\n\t\t\t}\n\t\t\treturn true\n\t\tl205:\n\t\t\tposition, tokenIndex = position205, tokenIndex205\n\t\t\treturn false\n\t\t},\n\t\t/* 13 String <- <('\"' <StringChar*> '\"' _)> */\n\t\tnil,\n\t\t/* 14 StringChar <- <(Escape / (!((&('\\\\') '\\\\') | (&('\\n') '\\n') | (&('\"') '\"')) .))> */\n\t\tnil,\n\t\t/* 15 UnquotedString <- <(!Keyword <(UnquotedStringStartChar UnquotedStringChar*)> _)> */\n\t\tnil,\n\t\t/* 16 UnquotedStringStartChar <- <((&('/' | '_') ('/' / '_')) | (&('A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z') [A-Z]) | (&('a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z') [a-z]))> */\n\t\tnil,\n\t\t/* 17 UnquotedStringChar <- <((&('/' | '_') ('/' / '_')) | (&('0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9') [0-9]) | (&('A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z') [A-Z]) | (&('a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z') [a-z]))> */\n\t\tnil,\n\t\t/* 18 Escape <- <(SimpleEscape / OctalEscape / HexEscape / UniversalCharacter)> */\n\t\tnil,\n\t\t/* 19 SimpleEscape <- <('\\\\' ((&('v') 'v') | (&('t') 't') | (&('r') 'r') | (&('n') 'n') | (&('f') 'f') | (&('b') 'b') | (&('a') 'a') | (&('\\\\') '\\\\') | (&('?') '?') | (&('\"') '\"') | (&('\\'') '\\'')))> */\n\t\tnil,\n\t\t/* 20 OctalEscape <- <('\\\\' [0-7] [0-7]? [0-7]?)> */\n\t\tnil,\n\t\t/* 21 HexEscape <- <('\\\\' 'x' HexDigit+)> */\n\t\tnil,\n\t\t/* 22 UniversalCharacter <- <(('\\\\' 'u' HexQuad) / ('\\\\' 'U' HexQuad HexQuad))> */\n\t\tnil,\n\t\t/* 23 HexQuad <- <(HexDigit HexDigit HexDigit HexDigit)> */\n\t\tfunc() bool {\n\t\t\tposition221, tokenIndex221 := position, tokenIndex\n\t\t\t{\n\t\t\t\tposition222 := position\n\t\t\t\tif !_rules[ruleHexDigit]() {\n\t\t\t\t\tgoto l221\n\t\t\t\t}\n\t\t\t\tif !_rules[ruleHexDigit]() {\n\t\t\t\t\tgoto l221\n\t\t\t\t}\n\t\t\t\tif !_rules[ruleHexDigit]() {\n\t\t\t\t\tgoto l221\n\t\t\t\t}\n\t\t\t\tif !_rules[ruleHexDigit]() {\n\t\t\t\t\tgoto l221\n\t\t\t\t}\n\t\t\t\tadd(ruleHexQuad, position222)\n\t\t\t}\n\t\t\treturn true\n\t\tl221:\n\t\t\tposition, tokenIndex = position221, tokenIndex221\n\t\t\treturn false\n\t\t},\n\t\t/* 24 HexDigit <- <((&('A' | 'B' | 'C' | 'D' | 'E' | 'F') [A-F]) | (&('a' | 'b' | 'c' | 'd' | 'e' | 'f') [a-f]) | (&('0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9') [0-9]))> */\n\t\tfunc() bool {\n\t\t\tposition223, tokenIndex223 := position, tokenIndex\n\t\t\t{\n\t\t\t\tposition224 := position\n\t\t\t\t{\n\t\t\t\t\tswitch buffer[position] {\n\t\t\t\t\tcase 'A', 'B', 'C', 'D', 'E', 'F':\n\t\t\t\t\t\tif c := buffer[position]; c < rune('A') || c > rune('F') {\n\t\t\t\t\t\t\tgoto l223\n\t\t\t\t\t\t}\n\t\t\t\t\t\tposition++\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'a', 'b', 'c', 'd', 'e', 'f':\n\t\t\t\t\t\tif c := buffer[position]; c < rune('a') || c > rune('f') {\n\t\t\t\t\t\t\tgoto l223\n\t\t\t\t\t\t}\n\t\t\t\t\t\tposition++\n\t\t\t\t\t\tbreak\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tif c := buffer[position]; c < rune('0') || c > rune('9') {\n\t\t\t\t\t\t\tgoto l223\n\t\t\t\t\t\t}\n\t\t\t\t\t\tposition++\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tadd(ruleHexDigit, position224)\n\t\t\t}\n\t\t\treturn true\n\t\tl223:\n\t\t\tposition, tokenIndex = position223, tokenIndex223\n\t\t\treturn false\n\t\t},\n\t\t/* 25 Numbers <- <(Number Action31)> */\n\t\tfunc() bool {\n\t\t\tposition226, tokenIndex226 := position, tokenIndex\n\t\t\t{\n\t\t\t\tposition227 := position\n\t\t\t\tif !_rules[ruleNumber]() {\n\t\t\t\t\tgoto l226\n\t\t\t\t}\n\t\t\t\t{\n\t\t\t\t\tadd(ruleAction31, position)\n\t\t\t\t}\n\t\t\t\tadd(ruleNumbers, position227)\n\t\t\t}\n\t\t\treturn true\n\t\tl226:\n\t\t\tposition, tokenIndex = position226, tokenIndex226\n\t\t\treturn false\n\t\t},\n\t\t/* 26 Number <- <(<Float> / <Integer>)> */\n\t\tfunc() bool {\n\t\t\t{\n\t\t\t\tposition230 := position\n\t\t\t\t{\n\t\t\t\t\tposition231, tokenIndex231 := position, tokenIndex\n\t\t\t\t\t{\n\t\t\t\t\t\tposition233 := position\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tposition234 := position\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tposition235, tokenIndex235 := position, tokenIndex\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tposition237 := position\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tposition238, tokenIndex238 := position, tokenIndex\n\t\t\t\t\t\t\t\t\tl240:\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tposition241, tokenIndex241 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\tif c := buffer[position]; c < rune('0') || c > rune('9') {\n\t\t\t\t\t\t\t\t\t\t\t\tgoto l241\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\tgoto l240\n\t\t\t\t\t\t\t\t\t\tl241:\n\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position241, tokenIndex241\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('.') {\n\t\t\t\t\t\t\t\t\t\t\tgoto l239\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\tif c := buffer[position]; c < rune('0') || c > rune('9') {\n\t\t\t\t\t\t\t\t\t\t\tgoto l239\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\tl242:\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tposition243, tokenIndex243 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\tif c := buffer[position]; c < rune('0') || c > rune('9') {\n\t\t\t\t\t\t\t\t\t\t\t\tgoto l243\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\tgoto l242\n\t\t\t\t\t\t\t\t\t\tl243:\n\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position243, tokenIndex243\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tgoto l238\n\t\t\t\t\t\t\t\t\tl239:\n\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position238, tokenIndex238\n\t\t\t\t\t\t\t\t\t\tif c := buffer[position]; c < rune('0') || c > rune('9') {\n\t\t\t\t\t\t\t\t\t\t\tgoto l236\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\tl244:\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tposition245, tokenIndex245 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\t\tif c := buffer[position]; c < rune('0') || c > rune('9') {\n\t\t\t\t\t\t\t\t\t\t\t\tgoto l245\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\t\tgoto l244\n\t\t\t\t\t\t\t\t\t\tl245:\n\t\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position245, tokenIndex245\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('.') {\n\t\t\t\t\t\t\t\t\t\t\tgoto l236\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tl238:\n\t\t\t\t\t\t\t\t\tadd(ruleFraction, position237)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tposition246, tokenIndex246 := position, tokenIndex\n\t\t\t\t\t\t\t\t\tif !_rules[ruleExponent]() {\n\t\t\t\t\t\t\t\t\t\tgoto l246\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tgoto l247\n\t\t\t\t\t\t\t\tl246:\n\t\t\t\t\t\t\t\t\tposition, tokenIndex = position246, tokenIndex246\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tl247:\n\t\t\t\t\t\t\t\tgoto l235\n\t\t\t\t\t\t\tl236:\n\t\t\t\t\t\t\t\tposition, tokenIndex = position235, tokenIndex235\n\t\t\t\t\t\t\t\tif c := buffer[position]; c < rune('0') || c > rune('9') {\n\t\t\t\t\t\t\t\t\tgoto l232\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tl248:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tposition249, tokenIndex249 := position, tokenIndex\n\t\t\t\t\t\t\t\t\tif c := buffer[position]; c < rune('0') || c > rune('9') {\n\t\t\t\t\t\t\t\t\t\tgoto l249\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\tgoto l248\n\t\t\t\t\t\t\t\tl249:\n\t\t\t\t\t\t\t\t\tposition, tokenIndex = position249, tokenIndex249\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif !_rules[ruleExponent]() {\n\t\t\t\t\t\t\t\t\tgoto l232\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tl235:\n\t\t\t\t\t\t\tadd(ruleFloat, position234)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tadd(rulePegText, position233)\n\t\t\t\t\t}\n\t\t\t\t\tgoto l231\n\t\t\t\tl232:\n\t\t\t\t\tposition, tokenIndex = position231, tokenIndex231\n\t\t\t\t\t{\n\t\t\t\t\t\tposition250 := position\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tposition251 := position\n\t\t\t\t\t\tl252:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tposition253, tokenIndex253 := position, tokenIndex\n\t\t\t\t\t\t\t\tif c := buffer[position]; c < rune('0') || c > rune('9') {\n\t\t\t\t\t\t\t\t\tgoto l253\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\tgoto l252\n\t\t\t\t\t\t\tl253:\n\t\t\t\t\t\t\t\tposition, tokenIndex = position253, tokenIndex253\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tadd(ruleInteger, position251)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tadd(rulePegText, position250)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tl231:\n\t\t\t\tadd(ruleNumber, position230)\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\t\t/* 27 Integer <- <[0-9]*> */\n\t\tnil,\n\t\t/* 28 Float <- <((Fraction Exponent?) / ([0-9]+ Exponent))> */\n\t\tnil,\n\t\t/* 29 Fraction <- <(([0-9]* '.' [0-9]+) / ([0-9]+ '.'))> */\n\t\tnil,\n\t\t/* 30 Exponent <- <(('e' / 'E') ('+' / '-')? [0-9]+)> */\n\t\tfunc() bool {\n\t\t\tposition257, tokenIndex257 := position, tokenIndex\n\t\t\t{\n\t\t\t\tposition258 := position\n\t\t\t\t{\n\t\t\t\t\tposition259, tokenIndex259 := position, tokenIndex\n\t\t\t\t\tif buffer[position] != rune('e') {\n\t\t\t\t\t\tgoto l260\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tgoto l259\n\t\t\t\tl260:\n\t\t\t\t\tposition, tokenIndex = position259, tokenIndex259\n\t\t\t\t\tif buffer[position] != rune('E') {\n\t\t\t\t\t\tgoto l257\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t}\n\t\t\tl259:\n\t\t\t\t{\n\t\t\t\t\tposition261, tokenIndex261 := position, tokenIndex\n\t\t\t\t\t{\n\t\t\t\t\t\tposition263, tokenIndex263 := position, tokenIndex\n\t\t\t\t\t\tif buffer[position] != rune('+') {\n\t\t\t\t\t\t\tgoto l264\n\t\t\t\t\t\t}\n\t\t\t\t\t\tposition++\n\t\t\t\t\t\tgoto l263\n\t\t\t\t\tl264:\n\t\t\t\t\t\tposition, tokenIndex = position263, tokenIndex263\n\t\t\t\t\t\tif buffer[position] != rune('-') {\n\t\t\t\t\t\t\tgoto l261\n\t\t\t\t\t\t}\n\t\t\t\t\t\tposition++\n\t\t\t\t\t}\n\t\t\t\tl263:\n\t\t\t\t\tgoto l262\n\t\t\t\tl261:\n\t\t\t\t\tposition, tokenIndex = position261, tokenIndex261\n\t\t\t\t}\n\t\t\tl262:\n\t\t\t\tif c := buffer[position]; c < rune('0') || c > rune('9') {\n\t\t\t\t\tgoto l257\n\t\t\t\t}\n\t\t\t\tposition++\n\t\t\tl265:\n\t\t\t\t{\n\t\t\t\t\tposition266, tokenIndex266 := position, tokenIndex\n\t\t\t\t\tif c := buffer[position]; c < rune('0') || c > rune('9') {\n\t\t\t\t\t\tgoto l266\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tgoto l265\n\t\t\t\tl266:\n\t\t\t\t\tposition, tokenIndex = position266, tokenIndex266\n\t\t\t\t}\n\t\t\t\tadd(ruleExponent, position258)\n\t\t\t}\n\t\t\treturn true\n\t\tl257:\n\t\t\tposition, tokenIndex = position257, tokenIndex257\n\t\t\treturn false\n\t\t},\n\t\t/* 31 Stage <- <((&('p') PRODUCTION) | (&('s') STAGING) | (&('d') DEVELOPMENT))> */\n\t\tnil,\n\t\t/* 32 DEVELOPMENT <- <(<('d' 'e' 'v' 'e' 'l' 'o' 'p' 'm' 'e' 'n' 't')> !IdChar _)> */\n\t\tnil,\n\t\t/* 33 STAGING <- <(<('s' 't' 'a' 'g' 'i' 'n' 'g')> !IdChar _)> */\n\t\tnil,\n\t\t/* 34 PRODUCTION <- <(<('p' 'r' 'o' 'd' 'u' 'c' 't' 'i' 'o' 'n')> !IdChar _)> */\n\t\tnil,\n\t\t/* 35 Unit <- <(Bytes / Duration)> */\n\t\tnil,\n\t\t/* 36 Duration <- <(S / MS)> */\n\t\tnil,\n\t\t/* 37 S <- <(<'s'> !IdChar _)> */\n\t\tnil,\n\t\t/* 38 MS <- <(<('m' 's')> !IdChar _)> */\n\t\tnil,\n\t\t/* 39 Bytes <- <((&('g') GB) | (&('m') MB) | (&('k') KB) | (&('b') B))> */\n\t\tnil,\n\t\t/* 40 B <- <(<'b'> !IdChar _)> */\n\t\tnil,\n\t\t/* 41 KB <- <(<('k' 'b')> !IdChar _)> */\n\t\tnil,\n\t\t/* 42 MB <- <(<('m' 'b')> !IdChar _)> */\n\t\tnil,\n\t\t/* 43 GB <- <(<('g' 'b')> !IdChar _)> */\n\t\tnil,\n\t\t/* 44 Id <- <(!Keyword <(IdCharNoDigit IdChar*)> _)> */\n\t\tfunc() bool {\n\t\t\tposition280, tokenIndex280 := position, tokenIndex\n\t\t\t{\n\t\t\t\tposition281 := position\n\t\t\t\t{\n\t\t\t\t\tposition282, tokenIndex282 := position, tokenIndex\n\t\t\t\t\tif !_rules[ruleKeyword]() {\n\t\t\t\t\t\tgoto l282\n\t\t\t\t\t}\n\t\t\t\t\tgoto l280\n\t\t\t\tl282:\n\t\t\t\t\tposition, tokenIndex = position282, tokenIndex282\n\t\t\t\t}\n\t\t\t\t{\n\t\t\t\t\tposition283 := position\n\t\t\t\t\t{\n\t\t\t\t\t\tposition284 := position\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tswitch buffer[position] {\n\t\t\t\t\t\t\tcase '_':\n\t\t\t\t\t\t\t\tif buffer[position] != rune('_') {\n\t\t\t\t\t\t\t\t\tgoto l280\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\tcase 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z':\n\t\t\t\t\t\t\t\tif c := buffer[position]; c < rune('A') || c > rune('Z') {\n\t\t\t\t\t\t\t\t\tgoto l280\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\tif c := buffer[position]; c < rune('a') || c > rune('z') {\n\t\t\t\t\t\t\t\t\tgoto l280\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tadd(ruleIdCharNoDigit, position284)\n\t\t\t\t\t}\n\t\t\t\tl286:\n\t\t\t\t\t{\n\t\t\t\t\t\tposition287, tokenIndex287 := position, tokenIndex\n\t\t\t\t\t\tif !_rules[ruleIdChar]() {\n\t\t\t\t\t\t\tgoto l287\n\t\t\t\t\t\t}\n\t\t\t\t\t\tgoto l286\n\t\t\t\t\tl287:\n\t\t\t\t\t\tposition, tokenIndex = position287, tokenIndex287\n\t\t\t\t\t}\n\t\t\t\t\tadd(rulePegText, position283)\n\t\t\t\t}\n\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\tgoto l280\n\t\t\t\t}\n\t\t\t\tadd(ruleId, position281)\n\t\t\t}\n\t\t\treturn true\n\t\tl280:\n\t\t\tposition, tokenIndex = position280, tokenIndex280\n\t\t\treturn false\n\t\t},\n\t\t/* 45 IdChar <- <((&('_') '_') | (&('0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9') [0-9]) | (&('A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z') [A-Z]) | (&('a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z') [a-z]))> */\n\t\tfunc() bool {\n\t\t\tposition288, tokenIndex288 := position, tokenIndex\n\t\t\t{\n\t\t\t\tposition289 := position\n\t\t\t\t{\n\t\t\t\t\tswitch buffer[position] {\n\t\t\t\t\tcase '_':\n\t\t\t\t\t\tif buffer[position] != rune('_') {\n\t\t\t\t\t\t\tgoto l288\n\t\t\t\t\t\t}\n\t\t\t\t\t\tposition++\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':\n\t\t\t\t\t\tif c := buffer[position]; c < rune('0') || c > rune('9') {\n\t\t\t\t\t\t\tgoto l288\n\t\t\t\t\t\t}\n\t\t\t\t\t\tposition++\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z':\n\t\t\t\t\t\tif c := buffer[position]; c < rune('A') || c > rune('Z') {\n\t\t\t\t\t\t\tgoto l288\n\t\t\t\t\t\t}\n\t\t\t\t\t\tposition++\n\t\t\t\t\t\tbreak\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tif c := buffer[position]; c < rune('a') || c > rune('z') {\n\t\t\t\t\t\t\tgoto l288\n\t\t\t\t\t\t}\n\t\t\t\t\t\tposition++\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tadd(ruleIdChar, position289)\n\t\t\t}\n\t\t\treturn true\n\t\tl288:\n\t\t\tposition, tokenIndex = position288, tokenIndex288\n\t\t\treturn false\n\t\t},\n\t\t/* 46 IdCharNoDigit <- <((&('_') '_') | (&('A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z') [A-Z]) | (&('a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z') [a-z]))> */\n\t\tnil,\n\t\t/* 47 Severity <- <((&('f') FATAL) | (&('e') ERROR) | (&('w') WARN) | (&('i') INFO) | (&('d') DEBUG))> */\n\t\tnil,\n\t\t/* 48 IN <- <('i' 'n' !IdChar _)> */\n\t\tfunc() bool {\n\t\t\tposition293, tokenIndex293 := position, tokenIndex\n\t\t\t{\n\t\t\t\tposition294 := position\n\t\t\t\tif buffer[position] != rune('i') {\n\t\t\t\t\tgoto l293\n\t\t\t\t}\n\t\t\t\tposition++\n\t\t\t\tif buffer[position] != rune('n') {\n\t\t\t\t\tgoto l293\n\t\t\t\t}\n\t\t\t\tposition++\n\t\t\t\t{\n\t\t\t\t\tposition295, tokenIndex295 := position, tokenIndex\n\t\t\t\t\tif !_rules[ruleIdChar]() {\n\t\t\t\t\t\tgoto l295\n\t\t\t\t\t}\n\t\t\t\t\tgoto l293\n\t\t\t\tl295:\n\t\t\t\t\tposition, tokenIndex = position295, tokenIndex295\n\t\t\t\t}\n\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\tgoto l293\n\t\t\t\t}\n\t\t\t\tadd(ruleIN, position294)\n\t\t\t}\n\t\t\treturn true\n\t\tl293:\n\t\t\tposition, tokenIndex = position293, tokenIndex293\n\t\t\treturn false\n\t\t},\n\t\t/* 49 OR <- <('o' 'r' !IdChar _)> */\n\t\tnil,\n\t\t/* 50 AND <- <('a' 'n' 'd' !IdChar _)> */\n\t\tnil,\n\t\t/* 51 NOT <- <('n' 'o' 't' !IdChar _)> */\n\t\tfunc() bool {\n\t\t\tposition298, tokenIndex298 := position, tokenIndex\n\t\t\t{\n\t\t\t\tposition299 := position\n\t\t\t\tif buffer[position] != rune('n') {\n\t\t\t\t\tgoto l298\n\t\t\t\t}\n\t\t\t\tposition++\n\t\t\t\tif buffer[position] != rune('o') {\n\t\t\t\t\tgoto l298\n\t\t\t\t}\n\t\t\t\tposition++\n\t\t\t\tif buffer[position] != rune('t') {\n\t\t\t\t\tgoto l298\n\t\t\t\t}\n\t\t\t\tposition++\n\t\t\t\t{\n\t\t\t\t\tposition300, tokenIndex300 := position, tokenIndex\n\t\t\t\t\tif !_rules[ruleIdChar]() {\n\t\t\t\t\t\tgoto l300\n\t\t\t\t\t}\n\t\t\t\t\tgoto l298\n\t\t\t\tl300:\n\t\t\t\t\tposition, tokenIndex = position300, tokenIndex300\n\t\t\t\t}\n\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\tgoto l298\n\t\t\t\t}\n\t\t\t\tadd(ruleNOT, position299)\n\t\t\t}\n\t\t\treturn true\n\t\tl298:\n\t\t\tposition, tokenIndex = position298, tokenIndex298\n\t\t\treturn false\n\t\t},\n\t\t/* 52 CONTAINS <- <('c' 'o' 'n' 't' 'a' 'i' 'n' 's' !IdChar _)> */\n\t\tnil,\n\t\t/* 53 DEBUG <- <(<('d' 'e' 'b' 'u' 'g')> !IdChar _)> */\n\t\tnil,\n\t\t/* 54 INFO <- <(<('i' 'n' 'f' 'o')> !IdChar _)> */\n\t\tnil,\n\t\t/* 55 WARN <- <(<('w' 'a' 'r' 'n')> !IdChar _)> */\n\t\tnil,\n\t\t/* 56 ERROR <- <(<('e' 'r' 'r' 'o' 'r')> !IdChar _)> */\n\t\tnil,\n\t\t/* 57 FATAL <- <(<('f' 'a' 't' 'a' 'l')> !IdChar _)> */\n\t\tnil,\n\t\t/* 58 Keyword <- <((('s' 't' 'a' 'g' 'i' 'n' 'g') / ('d' 'e' 'v' 'e' 'l' 'o' 'p' 'm' 'e' 'n' 't') / ('i' 'n' 'f' 'o') / ('m' 'b') / ((&('s') 's') | (&('m') ('m' 's')) | (&('b') 'b') | (&('k') ('k' 'b')) | (&('g') ('g' 'b')) | (&('i') ('i' 'n')) | (&('f') ('f' 'a' 't' 'a' 'l')) | (&('e') ('e' 'r' 'r' 'o' 'r')) | (&('w') ('w' 'a' 'r' 'n')) | (&('d') ('d' 'e' 'b' 'u' 'g')) | (&('c') ('c' 'o' 'n' 't' 'a' 'i' 'n' 's')) | (&('n') ('n' 'o' 't')) | (&('a') ('a' 'n' 'd')) | (&('o') ('o' 'r')) | (&('p') ('p' 'r' 'o' 'd' 'u' 'c' 't' 'i' 'o' 'n')))) !IdChar)> */\n\t\tfunc() bool {\n\t\t\tposition307, tokenIndex307 := position, tokenIndex\n\t\t\t{\n\t\t\t\tposition308 := position\n\t\t\t\t{\n\t\t\t\t\tposition309, tokenIndex309 := position, tokenIndex\n\t\t\t\t\tif buffer[position] != rune('s') {\n\t\t\t\t\t\tgoto l310\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tif buffer[position] != rune('t') {\n\t\t\t\t\t\tgoto l310\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tif buffer[position] != rune('a') {\n\t\t\t\t\t\tgoto l310\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tif buffer[position] != rune('g') {\n\t\t\t\t\t\tgoto l310\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tif buffer[position] != rune('i') {\n\t\t\t\t\t\tgoto l310\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tif buffer[position] != rune('n') {\n\t\t\t\t\t\tgoto l310\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tif buffer[position] != rune('g') {\n\t\t\t\t\t\tgoto l310\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tgoto l309\n\t\t\t\tl310:\n\t\t\t\t\tposition, tokenIndex = position309, tokenIndex309\n\t\t\t\t\tif buffer[position] != rune('d') {\n\t\t\t\t\t\tgoto l311\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tif buffer[position] != rune('e') {\n\t\t\t\t\t\tgoto l311\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tif buffer[position] != rune('v') {\n\t\t\t\t\t\tgoto l311\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tif buffer[position] != rune('e') {\n\t\t\t\t\t\tgoto l311\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tif buffer[position] != rune('l') {\n\t\t\t\t\t\tgoto l311\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tif buffer[position] != rune('o') {\n\t\t\t\t\t\tgoto l311\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tif buffer[position] != rune('p') {\n\t\t\t\t\t\tgoto l311\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tif buffer[position] != rune('m') {\n\t\t\t\t\t\tgoto l311\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tif buffer[position] != rune('e') {\n\t\t\t\t\t\tgoto l311\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tif buffer[position] != rune('n') {\n\t\t\t\t\t\tgoto l311\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tif buffer[position] != rune('t') {\n\t\t\t\t\t\tgoto l311\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tgoto l309\n\t\t\t\tl311:\n\t\t\t\t\tposition, tokenIndex = position309, tokenIndex309\n\t\t\t\t\tif buffer[position] != rune('i') {\n\t\t\t\t\t\tgoto l312\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tif buffer[position] != rune('n') {\n\t\t\t\t\t\tgoto l312\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tif buffer[position] != rune('f') {\n\t\t\t\t\t\tgoto l312\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tif buffer[position] != rune('o') {\n\t\t\t\t\t\tgoto l312\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tgoto l309\n\t\t\t\tl312:\n\t\t\t\t\tposition, tokenIndex = position309, tokenIndex309\n\t\t\t\t\tif buffer[position] != rune('m') {\n\t\t\t\t\t\tgoto l313\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tif buffer[position] != rune('b') {\n\t\t\t\t\t\tgoto l313\n\t\t\t\t\t}\n\t\t\t\t\tposition++\n\t\t\t\t\tgoto l309\n\t\t\t\tl313:\n\t\t\t\t\tposition, tokenIndex = position309, tokenIndex309\n\t\t\t\t\t{\n\t\t\t\t\t\tswitch buffer[position] {\n\t\t\t\t\t\tcase 's':\n\t\t\t\t\t\t\tif buffer[position] != rune('s') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 'm':\n\t\t\t\t\t\t\tif buffer[position] != rune('m') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('s') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 'b':\n\t\t\t\t\t\t\tif buffer[position] != rune('b') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 'k':\n\t\t\t\t\t\t\tif buffer[position] != rune('k') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('b') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 'g':\n\t\t\t\t\t\t\tif buffer[position] != rune('g') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('b') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 'i':\n\t\t\t\t\t\t\tif buffer[position] != rune('i') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('n') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 'f':\n\t\t\t\t\t\t\tif buffer[position] != rune('f') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('a') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('t') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('a') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('l') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 'e':\n\t\t\t\t\t\t\tif buffer[position] != rune('e') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('r') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('r') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('o') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('r') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 'w':\n\t\t\t\t\t\t\tif buffer[position] != rune('w') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('a') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('r') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('n') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 'd':\n\t\t\t\t\t\t\tif buffer[position] != rune('d') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('e') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('b') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('u') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('g') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 'c':\n\t\t\t\t\t\t\tif buffer[position] != rune('c') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('o') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('n') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('t') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('a') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('i') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('n') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('s') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 'n':\n\t\t\t\t\t\t\tif buffer[position] != rune('n') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('o') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('t') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 'a':\n\t\t\t\t\t\t\tif buffer[position] != rune('a') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('n') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('d') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 'o':\n\t\t\t\t\t\t\tif buffer[position] != rune('o') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('r') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tif buffer[position] != rune('p') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('r') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('o') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('d') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('u') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('c') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('t') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('i') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('o') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tif buffer[position] != rune('n') {\n\t\t\t\t\t\t\t\tgoto l307\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t}\n\t\t\tl309:\n\t\t\t\t{\n\t\t\t\t\tposition315, tokenIndex315 := position, tokenIndex\n\t\t\t\t\tif !_rules[ruleIdChar]() {\n\t\t\t\t\t\tgoto l315\n\t\t\t\t\t}\n\t\t\t\t\tgoto l307\n\t\t\t\tl315:\n\t\t\t\t\tposition, tokenIndex = position315, tokenIndex315\n\t\t\t\t}\n\t\t\t\tadd(ruleKeyword, position308)\n\t\t\t}\n\t\t\treturn true\n\t\tl307:\n\t\t\tposition, tokenIndex = position307, tokenIndex307\n\t\t\treturn false\n\t\t},\n\t\t/* 59 EQ <- <('=' _)> */\n\t\tnil,\n\t\t/* 60 LBRK <- <('[' _)> */\n\t\tnil,\n\t\t/* 61 RBRK <- <(']' _)> */\n\t\tnil,\n\t\t/* 62 LPAR <- <('(' _)> */\n\t\tfunc() bool {\n\t\t\tposition319, tokenIndex319 := position, tokenIndex\n\t\t\t{\n\t\t\t\tposition320 := position\n\t\t\t\tif buffer[position] != rune('(') {\n\t\t\t\t\tgoto l319\n\t\t\t\t}\n\t\t\t\tposition++\n\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\tgoto l319\n\t\t\t\t}\n\t\t\t\tadd(ruleLPAR, position320)\n\t\t\t}\n\t\t\treturn true\n\t\tl319:\n\t\t\tposition, tokenIndex = position319, tokenIndex319\n\t\t\treturn false\n\t\t},\n\t\t/* 63 RPAR <- <(')' _)> */\n\t\tfunc() bool {\n\t\t\tposition321, tokenIndex321 := position, tokenIndex\n\t\t\t{\n\t\t\t\tposition322 := position\n\t\t\t\tif buffer[position] != rune(')') {\n\t\t\t\t\tgoto l321\n\t\t\t\t}\n\t\t\t\tposition++\n\t\t\t\tif !_rules[rule_]() {\n\t\t\t\t\tgoto l321\n\t\t\t\t}\n\t\t\t\tadd(ruleRPAR, position322)\n\t\t\t}\n\t\t\treturn true\n\t\tl321:\n\t\t\tposition, tokenIndex = position321, tokenIndex321\n\t\t\treturn false\n\t\t},\n\t\t/* 64 DOT <- <('.' _)> */\n\t\tnil,\n\t\t/* 65 BANG <- <('!' !'=' _)> */\n\t\tnil,\n\t\t/* 66 LT <- <('<' !'=' _)> */\n\t\tnil,\n\t\t/* 67 GT <- <('>' !'=' _)> */\n\t\tnil,\n\t\t/* 68 LE <- <('<' '=' _)> */\n\t\tnil,\n\t\t/* 69 EQEQ <- <('=' '=' _)> */\n\t\tnil,\n\t\t/* 70 GE <- <('>' '=' _)> */\n\t\tnil,\n\t\t/* 71 NE <- <('!' '=' _)> */\n\t\tnil,\n\t\t/* 72 ANDAND <- <('&' '&' _)> */\n\t\tnil,\n\t\t/* 73 OROR <- <('|' '|' _)> */\n\t\tnil,\n\t\t/* 74 COMMA <- <(',' _)> */\n\t\tnil,\n\t\t/* 75 _ <- <Whitespace*> */\n\t\tfunc() bool {\n\t\t\t{\n\t\t\t\tposition335 := position\n\t\t\tl336:\n\t\t\t\t{\n\t\t\t\t\tposition337, tokenIndex337 := position, tokenIndex\n\t\t\t\t\t{\n\t\t\t\t\t\tposition338 := position\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tswitch buffer[position] {\n\t\t\t\t\t\t\tcase '\\t':\n\t\t\t\t\t\t\t\tif buffer[position] != rune('\\t') {\n\t\t\t\t\t\t\t\t\tgoto l337\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\tcase ' ':\n\t\t\t\t\t\t\t\tif buffer[position] != rune(' ') {\n\t\t\t\t\t\t\t\t\tgoto l337\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tposition340 := position\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tposition341, tokenIndex341 := position, tokenIndex\n\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('\\r') {\n\t\t\t\t\t\t\t\t\t\t\tgoto l342\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('\\n') {\n\t\t\t\t\t\t\t\t\t\t\tgoto l342\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\tgoto l341\n\t\t\t\t\t\t\t\t\tl342:\n\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position341, tokenIndex341\n\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('\\n') {\n\t\t\t\t\t\t\t\t\t\t\tgoto l343\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t\tgoto l341\n\t\t\t\t\t\t\t\t\tl343:\n\t\t\t\t\t\t\t\t\t\tposition, tokenIndex = position341, tokenIndex341\n\t\t\t\t\t\t\t\t\t\tif buffer[position] != rune('\\r') {\n\t\t\t\t\t\t\t\t\t\t\tgoto l337\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tposition++\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tl341:\n\t\t\t\t\t\t\t\t\tadd(ruleEOL, position340)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tadd(ruleWhitespace, position338)\n\t\t\t\t\t}\n\t\t\t\t\tgoto l336\n\t\t\t\tl337:\n\t\t\t\t\tposition, tokenIndex = position337, tokenIndex337\n\t\t\t\t}\n\t\t\t\tadd(rule_, position335)\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\t\t/* 76 Whitespace <- <((&('\\t') '\\t') | (&(' ') ' ') | (&('\\n' | '\\r') EOL))> */\n\t\tnil,\n\t\t/* 77 EOL <- <(('\\r' '\\n') / '\\n' / '\\r')> */\n\t\tnil,\n\t\t/* 78 EOF <- <!.> */\n\t\tnil,\n\t\t/* 80 Action0 <- <{ p.AddNumber(text) }> */\n\t\tnil,\n\t\t/* 81 Action1 <- <{ p.AddNumber(\"\")   }> */\n\t\tnil,\n\t\t/* 82 Action2 <- <{ p.AddLevel(text)  }> */\n\t\tnil,\n\t\t/* 83 Action3 <- <{ p.AddStage(text)  }> */\n\t\tnil,\n\t\t/* 84 Action4 <- <{ p.AddField(text)  }> */\n\t\tnil,\n\t\t/* 85 Action5 <- <{ p.AddString(text) }> */\n\t\tnil,\n\t\t/* 86 Action6 <- <{ p.AddString(text) }> */\n\t\tnil,\n\t\t/* 87 Action7 <- <{ p.AddExpr()       }> */\n\t\tnil,\n\t\t/* 88 Action8 <- <{ p.AddTupleValue() }> */\n\t\tnil,\n\t\t/* 89 Action9 <- <{ p.AddTupleValue() }> */\n\t\tnil,\n\t\t/* 90 Action10 <- <{ p.AddTuple() }> */\n\t\tnil,\n\t\t/* 91 Action11 <- <{ p.AddBinary(ast.IN) }> */\n\t\tnil,\n\t\t/* 92 Action12 <- <{ p.AddTuple() }> */\n\t\tnil,\n\t\t/* 93 Action13 <- <{ p.AddBinary(ast.IN); p.AddUnary(ast.LNOT) }> */\n\t\tnil,\n\t\t/* 94 Action14 <- <{ p.AddMember(text)    }> */\n\t\tnil,\n\t\t/* 95 Action15 <- <{ p.AddSubscript(text) }> */\n\t\tnil,\n\t\t/* 96 Action16 <- <{ p.AddUnary(ast.NOT) }> */\n\t\tnil,\n\t\t/* 97 Action17 <- <{ p.AddBinary(ast.GE) }> */\n\t\tnil,\n\t\t/* 98 Action18 <- <{ p.AddBinary(ast.GT) }> */\n\t\tnil,\n\t\t/* 99 Action19 <- <{ p.AddBinary(ast.LE) }> */\n\t\tnil,\n\t\t/* 100 Action20 <- <{ p.AddBinary(ast.LT) }> */\n\t\tnil,\n\t\t/* 101 Action21 <- <{ p.AddBinary(ast.EQ)   }> */\n\t\tnil,\n\t\t/* 102 Action22 <- <{ p.AddBinary(ast.NE)   }> */\n\t\tnil,\n\t\t/* 103 Action23 <- <{ p.AddBinary(ast.EQ)   }> */\n\t\tnil,\n\t\t/* 104 Action24 <- <{ p.AddBinaryContains() }> */\n\t\tnil,\n\t\t/* 105 Action25 <- <{ p.AddBinary(ast.AND) }> */\n\t\tnil,\n\t\t/* 106 Action26 <- <{ p.AddBinary(ast.AND) }> */\n\t\tnil,\n\t\t/* 107 Action27 <- <{ p.AddBinary(ast.AND) }> */\n\t\tnil,\n\t\t/* 108 Action28 <- <{ p.AddBinary(ast.OR) }> */\n\t\tnil,\n\t\t/* 109 Action29 <- <{ p.AddBinary(ast.OR) }> */\n\t\tnil,\n\t\t/* 110 Action30 <- <{ p.AddUnary(ast.LNOT) }> */\n\t\tnil,\n\t\tnil,\n\t\t/* 112 Action31 <- <{ p.SetNumber(text) }> */\n\t\tnil,\n\t}\n\tp.rules = _rules\n\treturn nil\n}\n"
  },
  {
    "path": "internal/logs/parser/parser.go",
    "content": "//go:generate peg -inline -switch grammar.peg\n\n// Package parser provides a parser for Up's\n// log query language, abstracting away provider\n// specifics.\npackage parser\n\nimport (\n\t\"strconv\"\n\n\t\"github.com/apex/up/internal/logs/parser/ast\"\n)\n\n// Parse query string.\nfunc Parse(s string) (ast.Node, error) {\n\tp := &parser{Buffer: s}\n\tp.Init()\n\n\tif err := p.Parse(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tp.Execute()\n\tn := ast.Root{Node: p.stack[0]}\n\treturn n, nil\n}\n\n// push node.\nfunc (p *parser) push(n ast.Node) {\n\tp.stack = append(p.stack, n)\n}\n\n// pop node.\nfunc (p *parser) pop() ast.Node {\n\tif len(p.stack) == 0 {\n\t\tpanic(\"pop: no nodes\")\n\t}\n\n\tn := p.stack[len(p.stack)-1]\n\tp.stack = p.stack[:len(p.stack)-1]\n\treturn n\n}\n\n// AddLevel node.\nfunc (p *parser) AddLevel(s string) {\n\tp.AddField(\"level\")\n\tp.AddString(s)\n\tp.AddBinary(ast.EQ)\n\tp.AddExpr()\n}\n\n// AddExpr node.\nfunc (p *parser) AddExpr() {\n\tp.push(ast.Expr{\n\t\tNode: p.pop(),\n\t})\n}\n\n// AddField node.\nfunc (p *parser) AddField(s string) {\n\tswitch s {\n\tcase \"level\", \"message\", \"timestamp\":\n\t\tp.push(ast.Property(s))\n\tdefault:\n\t\tp.push(ast.Field(s))\n\t}\n}\n\n// AddString node.\nfunc (p *parser) AddString(s string) {\n\tp.push(ast.String(s))\n}\n\n// AddSubscript node.\nfunc (p *parser) AddSubscript(s string) {\n\tp.push(ast.Subscript{\n\t\tLeft:  p.pop(),\n\t\tRight: ast.Literal(s),\n\t})\n}\n\n// AddMember node.\nfunc (p *parser) AddMember(s string) {\n\tp.push(ast.Member{\n\t\tLeft:  p.pop(),\n\t\tRight: ast.Literal(s),\n\t})\n}\n\n// SetNumber text.\nfunc (p *parser) SetNumber(s string) {\n\tp.number = s\n}\n\n// AddNumber node.\nfunc (p *parser) AddNumber(unit string) {\n\tf, _ := strconv.ParseFloat(p.number, 64)\n\tp.push(ast.Number{\n\t\tValue: f,\n\t\tUnit:  unit,\n\t})\n}\n\n// AddTuple node.\nfunc (p *parser) AddTuple() {\n\tp.push(ast.Tuple{})\n}\n\n// AddTupleValue node.\nfunc (p *parser) AddTupleValue() {\n\tv := p.pop()\n\tt := p.pop().(ast.Tuple)\n\tt = append(t, v)\n\tp.push(t)\n}\n\n// AddBinary node.\nfunc (p *parser) AddBinary(op ast.Op) {\n\tp.push(ast.Binary{\n\t\tOp:    op,\n\t\tRight: p.pop(),\n\t\tLeft:  p.pop(),\n\t})\n}\n\n// AddStage node.\nfunc (p *parser) AddStage(stage string) {\n\tp.push(ast.Binary{\n\t\tOp:    ast.EQ,\n\t\tLeft:  ast.Field(\"stage\"),\n\t\tRight: ast.String(stage),\n\t})\n}\n\n// AddBinaryContains node.\nfunc (p *parser) AddBinaryContains() {\n\tp.push(ast.Binary{\n\t\tOp:    ast.EQ,\n\t\tRight: ast.Contains{Node: p.pop()},\n\t\tLeft:  p.pop(),\n\t})\n}\n\n// AddUnary node.\nfunc (p *parser) AddUnary(op ast.Op) {\n\tp.push(ast.Unary{\n\t\tOp:    op,\n\t\tRight: p.pop(),\n\t})\n}\n"
  },
  {
    "path": "internal/logs/parser/parser_test.go",
    "content": "package parser\n\nimport (\n\t\"testing\"\n)\n\n// TODO: precedence...\n// TODO: byte size literals\n// TODO: support literals: `method = GET`, `ip = 70.*` etc\n// TODO: error tests\n// TODO: test error messages\n// TODO: add \"starts with\" / \"ends with\" to compliment \"contains\"?\n// TODO: document best practices for logging json from an app\n\nvar cases = []struct {\n\tInput  string\n\tOutput string\n}{\n\t{`production`, `{ $.fields.stage = \"production\" }`},\n\t{`development`, `{ $.fields.stage = \"development\" }`},\n\t{`staging`, `{ $.fields.stage = \"staging\" }`},\n\t{`method = \"GET\"`, `{ $.fields.method = \"GET\" }`},\n\t{`debug`, `{ ($.level = \"debug\") }`},\n\t{`info`, `{ ($.level = \"info\") }`},\n\t{`warn`, `{ ($.level = \"warn\") }`},\n\t{`error`, `{ ($.level = \"error\") }`},\n\t{`fatal`, `{ ($.level = \"fatal\") }`},\n\t{`not info`, `{ !(($.level = \"info\")) }`},\n\t{`not error or fatal`, `{ !(($.level = \"error\") || ($.level = \"fatal\")) }`},\n\t{`!info`, `{ !($.level = \"info\") }`},\n\t{`level = \"info\"`, `{ $.level = \"info\" }`},\n\t{`message = \"user signin\"`, `{ $.message = \"user signin\" }`},\n\t{`email = \"tj@apex.sh\"`, `{ $.fields.email = \"tj@apex.sh\" }`},\n\t{`status = 0`, `{ $.fields.status = 0 }`},\n\t{`status = 0.123`, `{ $.fields.status = 0.123 }`},\n\t{`status = .123`, `{ $.fields.status = 0.123 }`},\n\t{`status = 200`, `{ $.fields.status = 200 }`},\n\t{`price = 1.95`, `{ $.fields.price = 1.95 }`},\n\t{`price == 1.95`, `{ $.fields.price = 1.95 }`},\n\t{`price > 1.95`, `{ $.fields.price > 1.95 }`},\n\t{`price < 1.95`, `{ $.fields.price < 1.95 }`},\n\t{`price >= 1.95`, `{ $.fields.price >= 1.95 }`},\n\t{`price <= 1.95`, `{ $.fields.price <= 1.95 }`},\n\t{`price != 1.95`, `{ $.fields.price != 1.95 }`},\n\t{`!enabled`, `{ !$.fields.enabled }`},\n\t{`!   enabled`, `{ !$.fields.enabled }`},\n\t{`foo = 1 || bar = 2`, `{ $.fields.foo = 1 || $.fields.bar = 2 }`},\n\t{`foo = 1 && bar = 2`, `{ $.fields.foo = 1 && $.fields.bar = 2 }`},\n\t{`foo = 1 or bar = 2`, `{ $.fields.foo = 1 || $.fields.bar = 2 }`},\n\t{`foo = 1 and bar = 2`, `{ $.fields.foo = 1 && $.fields.bar = 2 }`},\n\t{`foo = 1 bar = 2`, `{ $.fields.foo = 1 && $.fields.bar = 2 }`},\n\t{`foo.bar.baz = 1`, `{ $.fields.foo.bar.baz = 1 }`},\n\t{`level = \"error\" and (duration >= 500 or duration = 0)`, `{ $.level = \"error\" && ($.fields.duration >= 500 || $.fields.duration = 0) }`},\n\t{`level = \"error\" (duration >= 500 or duration = 0)`, `{ $.level = \"error\" && ($.fields.duration >= 500 || $.fields.duration = 0) }`},\n\t{`cart.total = 15.99`, `{ $.fields.cart.total = 15.99 }`},\n\t{`user.name contains \"obi\"`, `{ $.fields.user.name = \"*obi*\" }`},\n\t{`user in (\"Tobi\")`, `{ ($.fields.user = \"Tobi\") }`},\n\t{`pet.age in (1, 2, 3)`, `{ ($.fields.pet.age = 1 || $.fields.pet.age = 2 || $.fields.pet.age = 3) }`},\n\t{`user in (\"Tobi\", \"Loki\", \"Jane\")`, `{ ($.fields.user = \"Tobi\" || $.fields.user = \"Loki\" || $.fields.user = \"Jane\") }`},\n\t{`user.name in (\"Tobi\", \"Loki\", \"Jane\")`, `{ ($.fields.user.name = \"Tobi\" || $.fields.user.name = \"Loki\" || $.fields.user.name = \"Jane\") }`},\n\t{`not user.admin`, `{ !($.fields.user.admin) }`},\n\t{`not user.role in (\"Admin\", \"Moderator\")`, `{ !(($.fields.user.role = \"Admin\" || $.fields.user.role = \"Moderator\")) }`},\n\t{`user.role not in (\"Admin\", \"Moderator\")`, `{ !(($.fields.user.role = \"Admin\" || $.fields.user.role = \"Moderator\")) }`},\n\t{`not level = \"error\" or level = \"fatal\"`, `{ !($.level = \"error\" || $.level = \"fatal\") }`},\n\t{`cart.products[0] = \"something\"`, `{ $.fields.cart.products[0] = \"something\" }`},\n\t{`cart.products[0].price = 15.99`, `{ $.fields.cart.products[0].price = 15.99 }`},\n\t{`cart.products[0][1].price = 15.99`, `{ $.fields.cart.products[0][1].price = 15.99 }`},\n\t{`cart.products[0].items[1].price = 15.99`, `{ $.fields.cart.products[0].items[1].price = 15.99 }`},\n\t{`user.name in (\"Tobi\", \"Loki\") and status >= 500`, `{ ($.fields.user.name = \"Tobi\" || $.fields.user.name = \"Loki\") && $.fields.status >= 500 }`},\n\t{`method in (\"POST\", \"PUT\") and ip = \"207.*\" and status = 200 and duration >= 50`, `{ ($.fields.method = \"POST\" || $.fields.method = \"PUT\") && $.fields.ip = \"207.*\" && $.fields.status = 200 && $.fields.duration >= 50 }`},\n\t{`method in (\"POST\", \"PUT\") ip = \"207.*\" status = 200 duration >= 50`, `{ ($.fields.method = \"POST\" || $.fields.method = \"PUT\") && $.fields.ip = \"207.*\" && $.fields.status = 200 && $.fields.duration >= 50 }`},\n\t{`size > 1kb`, `{ $.fields.size > 1024 }`},\n\t{`size > 2kb`, `{ $.fields.size > 2048 }`},\n\t{`size > 1.5mb`, `{ $.fields.size > 1572864 }`},\n\t{`size > 100b`, `{ $.fields.size > 100 }`},\n\t{`duration > 100ms`, `{ $.fields.duration > 100 }`},\n\t{`duration > 1s`, `{ $.fields.duration > 1000 }`},\n\t{`duration > 4.5s`, `{ $.fields.duration > 4500 }`},\n\t{`\"User Login\"`, `{ $.message = \"User Login\" }`},\n\t{`\"User*\"`, `{ $.message = \"User*\" }`},\n\t{`\"Signup\" or \"Signin\"`, `{ $.message = \"Signup\" || $.message = \"Signin\" }`},\n\t{`\"User Login\" method = \"GET\"`, `{ $.message = \"User Login\" && $.fields.method = \"GET\" }`},\n\t{`method = GET`, `{ $.fields.method = \"GET\" }`},\n\t{`method in (GET, HEAD, OPTIONS)`, `{ ($.fields.method = \"GET\" || $.fields.method = \"HEAD\" || $.fields.method = \"OPTIONS\") }`},\n\t{`name = tj`, `{ $.fields.name = \"tj\" }`},\n\t{`method = GET path = /account/billing`, `{ $.fields.method = \"GET\" && $.fields.path = \"/account/billing\" }`},\n\t{`cart.products[0].name = ps4`, `{ $.fields.cart.products[0].name = \"ps4\" }`},\n\t{`path = \"/_health\"`, `{ $.fields.path = \"/_health\" }`},\n\t{`path == \"/_health\"`, `{ $.fields.path = \"/_health\" }`},\n\t{`path > \"/_health\"`, `{ $.fields.path > \"/_health\" }`},\n\t{`path >= \"/_health\"`, `{ $.fields.path >= \"/_health\" }`},\n\t{`path != \"/_health\"`, `{ $.fields.path != \"/_health\" }`},\n}\n\nfunc TestParse(t *testing.T) {\n\tfor _, c := range cases {\n\t\tt.Logf(\"parsing %q\", c.Input)\n\t\tn, err := Parse(c.Input)\n\n\t\tif err != nil {\n\t\t\tt.Errorf(\"error parsing %q: %s\", c.Input, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tif n.String() != c.Output {\n\t\t\tt.Errorf(\"\\n\\ntext: %s\\nwant: %s\\n got: %s\\n\\n\", c.Input, c.Output, n.String())\n\t\t}\n\t}\n}\n\nfunc BenchmarkParse(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\tParse(`user.name in (\"Tobi\", \"Loki\", \"Jane\")`)\n\t}\n}\n"
  },
  {
    "path": "internal/logs/text/text.go",
    "content": "// Package text implements a development-friendly textual handler.\npackage text\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/apex/log\"\n\t\"github.com/dustin/go-humanize\"\n\n\t\"github.com/apex/up/internal/colors\"\n\t\"github.com/apex/up/internal/util\"\n)\n\nvar (\n\tspacerPlaceholderBytes = []byte(\"{{spacer}}\")\n\tspacerBytes            = []byte(colors.Gray(\":\"))\n\tnewlineBytes           = []byte(\"\\n\")\n\temptyBytes             = []byte(\"\")\n)\n\n// color function.\ntype colorFunc func(string) string\n\n// omit fields.\nvar omit = map[string]bool{\n\t\"app\":     true,\n\t\"stage\":   true,\n\t\"region\":  true,\n\t\"plugin\":  true,\n\t\"commit\":  true,\n\t\"version\": true,\n}\n\n// Colors mapping.\nvar Colors = [...]colorFunc{\n\tlog.DebugLevel: colors.Gray,\n\tlog.InfoLevel:  colors.Purple,\n\tlog.WarnLevel:  colors.Yellow,\n\tlog.ErrorLevel: colors.Red,\n\tlog.FatalLevel: colors.Red,\n}\n\n// Strings mapping.\nvar Strings = [...]string{\n\tlog.DebugLevel: \"DEBU\",\n\tlog.InfoLevel:  \"INFO\",\n\tlog.WarnLevel:  \"WARN\",\n\tlog.ErrorLevel: \"ERRO\",\n\tlog.FatalLevel: \"FATA\",\n}\n\n// Handler implementation.\ntype Handler struct {\n\tmu     sync.Mutex\n\tWriter io.Writer\n\texpand bool\n\tlayout string\n}\n\n// New handler.\nfunc New(w io.Writer) *Handler {\n\treturn &Handler{\n\t\tWriter: w,\n\t}\n}\n\n// WithExpandedFields sets the expanded field state.\nfunc (h *Handler) WithExpandedFields(v bool) *Handler {\n\th.expand = v\n\treturn h\n}\n\n// HandleLog implements log.Handler.\nfunc (h *Handler) HandleLog(e *log.Entry) error {\n\tswitch {\n\tcase h.expand:\n\t\treturn h.handleExpanded(e)\n\tdefault:\n\t\treturn h.handleInline(e)\n\t}\n}\n\n// handleExpanded fields.\nfunc (h *Handler) handleExpanded(e *log.Entry) error {\n\tcolor := Colors[e.Level]\n\tlevel := Strings[e.Level]\n\tnames := e.Fields.Names()\n\n\th.mu.Lock()\n\tdefer h.mu.Unlock()\n\n\tts := formatDate(e.Timestamp.Local())\n\tfmt.Fprintf(h.Writer, \"  %s %s %s\\n\", colors.Gray(ts), bold(color(level)), colors.Purple(e.Message))\n\n\tfor _, name := range names {\n\t\tv := e.Fields.Get(name)\n\n\t\tif v == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tfmt.Fprintf(h.Writer, \"    %s%s%v\\n\", color(name), colors.Gray(\": \"), value(name, v))\n\t}\n\n\tif len(names) > 0 {\n\t\tfmt.Fprintf(h.Writer, \"\\n\")\n\t}\n\n\treturn nil\n}\n\n// handleInline fields.\nfunc (h *Handler) handleInline(e *log.Entry) error {\n\tvar buf bytes.Buffer\n\tvar fields int\n\n\tcolor := Colors[e.Level]\n\tlevel := Strings[e.Level]\n\tnames := e.Fields.Names()\n\tts := formatDate(e.Timestamp.Local())\n\n\tif stage, ok := e.Fields.Get(\"stage\").(string); ok && stage != \"\" {\n\t\tfmt.Fprintf(&buf, \"  %s %s %s %s %s{{spacer}}\", colors.Gray(ts), bold(color(level)), colors.Gray(stage), colors.Gray(version(e)), colors.Purple(e.Message))\n\t} else {\n\t\tfmt.Fprintf(&buf, \"  %s %s %s{{spacer}}\", colors.Gray(ts), bold(color(level)), colors.Purple(e.Message))\n\t}\n\n\tfor _, name := range names {\n\t\tif omit[name] {\n\t\t\tcontinue\n\t\t}\n\n\t\tv := e.Fields.Get(name)\n\n\t\tif v == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tfields++\n\t\tfmt.Fprintf(&buf, \" %s%s%v\", color(name), colors.Gray(\"=\"), value(name, v))\n\t}\n\n\tb := buf.Bytes()\n\n\tif fields > 0 {\n\t\tb = bytes.Replace(b, spacerPlaceholderBytes, spacerBytes, 1)\n\t} else {\n\t\tb = bytes.Replace(b, spacerPlaceholderBytes, emptyBytes, 1)\n\t}\n\n\th.mu.Lock()\n\th.Writer.Write(b)\n\th.Writer.Write(newlineBytes)\n\th.mu.Unlock()\n\n\treturn nil\n}\n\n// value returns the formatted value.\nfunc value(name string, v interface{}) interface{} {\n\tswitch name {\n\tcase \"size\":\n\t\treturn humanize.Bytes(uint64(util.ToFloat(v)))\n\tcase \"duration\":\n\t\treturn time.Millisecond * time.Duration(util.ToFloat(v))\n\tdefault:\n\t\treturn v\n\t}\n}\n\n// day duration.\nvar day = time.Hour * 24\n\n// formatDate formats t relative to now.\nfunc formatDate(t time.Time) string {\n\treturn t.Format(`Jan 2` + util.DateSuffix(t) + ` 03:04:05pm`)\n}\n\n// version returns the entry version via GIT commit or lambda version.\nfunc version(e *log.Entry) string {\n\tif s, ok := e.Fields.Get(\"commit\").(string); ok && s != \"\" {\n\t\treturn s\n\t}\n\n\tif s, ok := e.Fields.Get(\"version\").(string); ok && s != \"\" {\n\t\treturn s\n\t}\n\n\treturn \"\"\n}\n\n// bold string.\nfunc bold(s string) string {\n\treturn fmt.Sprintf(\"\\033[1m%s\\033[0m\", s)\n}\n"
  },
  {
    "path": "internal/logs/text/text_test.go",
    "content": "package text\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/apex/log\"\n)\n\nfunc init() {\n\tlog.Now = func() time.Time {\n\t\treturn time.Unix(0, 0)\n\t}\n}\n\nfunc Test(t *testing.T) {\n\tvar buf bytes.Buffer\n\n\tlog.SetHandler(New(&buf))\n\tlog.WithField(\"user\", \"tj\").WithField(\"id\", \"123\").Info(\"hello\")\n\tlog.WithField(\"user\", \"tj\").Info(\"something broke\")\n\tlog.WithField(\"user\", \"tj\").Warn(\"something kind of broke\")\n\tlog.WithField(\"user\", \"tj\").Error(\"boom\")\n\n\tio.Copy(os.Stdout, &buf)\n}\n"
  },
  {
    "path": "internal/logs/writer/writer.go",
    "content": "// Package writer provides an io.Writer for capturing\n// process output as logs, so that stdout may become\n// INFO, and stderr ERROR.\npackage writer\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/json\"\n\n\t\"github.com/apex/log\"\n\t\"github.com/apex/up/internal/util\"\n)\n\n// Writer struct.\ntype Writer struct {\n\tlog   log.Interface\n\tlevel log.Level\n}\n\n// New writer with the given log level.\nfunc New(l log.Level, ctx log.Interface) *Writer {\n\treturn &Writer{\n\t\tlog:   ctx,\n\t\tlevel: l,\n\t}\n}\n\n// Write implementation.\nfunc (w *Writer) Write(b []byte) (int, error) {\n\ts := bufio.NewScanner(bytes.NewReader(b))\n\n\tfor s.Scan() {\n\t\tif err := w.write(s.Text()); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\n\tif err := s.Err(); err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn len(b), nil\n}\n\n// write the line.\nfunc (w *Writer) write(s string) error {\n\tif util.IsJSONLog(s) {\n\t\treturn w.writeJSON(s)\n\t}\n\n\treturn w.writeText(s)\n}\n\n// writeJSON writes a json log, interpreting it as a log.Entry.\nfunc (w *Writer) writeJSON(s string) error {\n\t// TODO: make this less ugly in apex/log,\n\t// you should be able to write an arbitrary Entry.\n\tvar e log.Entry\n\n\tif err := json.Unmarshal([]byte(s), &e); err != nil {\n\t\treturn w.writeText(s)\n\t}\n\n\tswitch e.Level {\n\tcase log.DebugLevel:\n\t\tw.log.WithFields(e.Fields).Debug(e.Message)\n\tcase log.InfoLevel:\n\t\tw.log.WithFields(e.Fields).Info(e.Message)\n\tcase log.WarnLevel:\n\t\tw.log.WithFields(e.Fields).Warn(e.Message)\n\tcase log.ErrorLevel:\n\t\tw.log.WithFields(e.Fields).Error(e.Message)\n\tcase log.FatalLevel:\n\t\t// TODO: FATAL without exit...\n\t\tw.log.WithFields(e.Fields).Error(e.Message)\n\t}\n\n\treturn nil\n}\n\n// writeText writes plain text.\nfunc (w *Writer) writeText(s string) error {\n\tswitch w.level {\n\tcase log.InfoLevel:\n\t\tw.log.Info(s)\n\tcase log.ErrorLevel:\n\t\tw.log.Error(s)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "internal/logs/writer/writer_test.go",
    "content": "package writer\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/apex/log\"\n\t\"github.com/apex/log/handlers/json\"\n\t\"github.com/tj/assert\"\n)\n\nfunc init() {\n\tlog.Now = func() time.Time {\n\t\treturn time.Unix(0, 0).UTC()\n\t}\n}\n\nfunc TestWriter_plainTextFlat(t *testing.T) {\n\tvar buf bytes.Buffer\n\n\tlog.SetHandler(json.New(&buf))\n\n\tw := New(log.InfoLevel, log.Log)\n\n\tinput := `GET /\nGET /account\nGET /login\nPOST /\nPOST /logout\n`\n\n\t_, err := io.Copy(w, strings.NewReader(input))\n\tassert.NoError(t, err, \"copy\")\n\n\texpected := `{\"fields\":{},\"level\":\"info\",\"timestamp\":\"1970-01-01T00:00:00Z\",\"message\":\"GET /\"}\n{\"fields\":{},\"level\":\"info\",\"timestamp\":\"1970-01-01T00:00:00Z\",\"message\":\"GET /account\"}\n{\"fields\":{},\"level\":\"info\",\"timestamp\":\"1970-01-01T00:00:00Z\",\"message\":\"GET /login\"}\n{\"fields\":{},\"level\":\"info\",\"timestamp\":\"1970-01-01T00:00:00Z\",\"message\":\"POST /\"}\n{\"fields\":{},\"level\":\"info\",\"timestamp\":\"1970-01-01T00:00:00Z\",\"message\":\"POST /logout\"}\n`\n\n\tassert.Equal(t, expected, buf.String())\n}\n\nfunc TestWriter_json(t *testing.T) {\n\tvar buf bytes.Buffer\n\n\tlog.SetHandler(json.New(&buf))\n\n\tw := New(log.InfoLevel, log.Log)\n\n\tinput := `{ \"level\": \"info\", \"message\": \"request\", \"fields\": { \"method\": \"GET\", \"path\": \"/\" } }\n{ \"level\": \"info\", \"message\": \"request\", \"fields\": { \"method\": \"GET\", \"path\": \"/login\" } }\n{ \"level\": \"info\", \"message\": \"request\", \"fields\": { \"method\": \"POST\", \"path\": \"/login\" } }\n`\n\n\t_, err := io.Copy(w, strings.NewReader(input))\n\tassert.NoError(t, err, \"copy\")\n\n\texpected := `{\"fields\":{\"method\":\"GET\",\"path\":\"/\"},\"level\":\"info\",\"timestamp\":\"1970-01-01T00:00:00Z\",\"message\":\"request\"}\n{\"fields\":{\"method\":\"GET\",\"path\":\"/login\"},\"level\":\"info\",\"timestamp\":\"1970-01-01T00:00:00Z\",\"message\":\"request\"}\n{\"fields\":{\"method\":\"POST\",\"path\":\"/login\"},\"level\":\"info\",\"timestamp\":\"1970-01-01T00:00:00Z\",\"message\":\"request\"}\n`\n\n\tassert.Equal(t, expected, buf.String())\n}\n"
  },
  {
    "path": "internal/metrics/metrics.go",
    "content": "// Package metrics provides higher level CloudWatch metrics operations.\npackage metrics\n\nimport (\n\t\"time\"\n\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/service/cloudwatch\"\n)\n\n// Metrics helper.\ntype Metrics struct {\n\tin cloudwatch.GetMetricStatisticsInput\n}\n\n// New metrics.\nfunc New() *Metrics {\n\treturn &Metrics{}\n}\n\n// Namespace sets the namespace.\nfunc (m *Metrics) Namespace(name string) *Metrics {\n\tm.in.Namespace = &name\n\treturn m\n}\n\n// Metric sets the metric name.\nfunc (m *Metrics) Metric(name string) *Metrics {\n\tm.in.MetricName = &name\n\treturn m\n}\n\n// Stats sets the stats.\nfunc (m *Metrics) Stats(names []string) *Metrics {\n\tm.in.Statistics = aws.StringSlice(names)\n\treturn m\n}\n\n// Stat adds the stat.\nfunc (m *Metrics) Stat(name string) *Metrics {\n\tm.in.Statistics = append(m.in.Statistics, &name)\n\treturn m\n}\n\n// Dimension adds a dimension.\nfunc (m *Metrics) Dimension(name, value string) *Metrics {\n\tm.in.Dimensions = append(m.in.Dimensions, &cloudwatch.Dimension{\n\t\tName:  &name,\n\t\tValue: &value,\n\t})\n\n\treturn m\n}\n\n// Period sets the period in seconds.\nfunc (m *Metrics) Period(seconds int) *Metrics {\n\tm.in.Period = aws.Int64(int64(seconds))\n\treturn m\n}\n\n// TimeRange sets the start and time times.\nfunc (m *Metrics) TimeRange(start, end time.Time) *Metrics {\n\tm.in.StartTime = &start\n\tm.in.EndTime = &end\n\treturn m\n}\n\n// Params returns the API input.\nfunc (m *Metrics) Params() *cloudwatch.GetMetricStatisticsInput {\n\treturn &m.in\n}\n"
  },
  {
    "path": "internal/progressreader/progressreader.go",
    "content": "// Package progressreader provides an io.Reader progress bar.\npackage progressreader\n\nimport (\n\t\"io\"\n\t\"sync\"\n\n\t\"github.com/apex/up/internal/util\"\n\t\"github.com/tj/go-progress\"\n\t\"github.com/tj/go/term\"\n)\n\n// reader wrapping a progress bar.\ntype reader struct {\n\tio.ReadCloser\n\tp       *progress.Bar\n\trender  func(string)\n\twritten int\n\tsync.Once\n}\n\n// Read implementation.\nfunc (r *reader) Read(b []byte) (int, error) {\n\tr.Do(term.ClearAll)\n\tn, err := r.ReadCloser.Read(b)\n\tr.written += n\n\tr.p.ValueInt(r.written)\n\tr.render(term.CenterLine(r.p.String()))\n\treturn n, err\n}\n\n// New returns a progress bar reader.\nfunc New(size int, r io.ReadCloser) io.ReadCloser {\n\treturn &reader{\n\t\tReadCloser: r,\n\t\tp:          util.NewProgressInt(size),\n\t\trender:     term.Renderer(),\n\t}\n}\n"
  },
  {
    "path": "internal/proxy/bin/bin.go",
    "content": "//go:generate sh -c \"GOOS=linux GOARCH=amd64 go build -o up-proxy ../../../cmd/up-proxy/main.go\"\n//go:generate go-bindata -modtime 0 -pkg bin -o bin_assets.go .\n\npackage bin\n"
  },
  {
    "path": "internal/proxy/event.go",
    "content": "package proxy\n\n// Identity is the identity information associated with the request.\ntype Identity struct {\n\tAPIKey                        string `json:\"apiKey\"`\n\tAccountID                     string `json:\"accountId\"`\n\tUserAgent                     string `json:\"userAgent\"`\n\tSourceIP                      string `json:\"sourceIp\"`\n\tAccessKey                     string `json:\"accessKey\"`\n\tCaller                        string `json:\"caller\"`\n\tUser                          string `json:\"user\"`\n\tUserARN                       string `json:\"userARN\"`\n\tCognitoIdentityID             string `json:\"cognitoIdentityId\"`\n\tCognitoIdentityPoolID         string `json:\"cognitoIdentityPoolId\"`\n\tCognitoAuthenticationType     string `json:\"cognitoAuthenticationType\"`\n\tCognitoAuthenticationProvider string `json:\"cognitoAuthenticationProvider\"`\n}\n\n// RequestContext is the contextual information provided by API Gateway.\ntype RequestContext struct {\n\tAPIID        string                 `json:\"apiId\"`\n\tResourceID   string                 `json:\"resourceId\"`\n\tRequestID    string                 `json:\"requestId\"`\n\tHTTPMethod   string                 `json:\"-\"`\n\tResourcePath string                 `json:\"-\"`\n\tAccountID    string                 `json:\"accountId\"`\n\tStage        string                 `json:\"stage\"`\n\tIdentity     Identity               `json:\"identity\"`\n\tAuthorizer   map[string]interface{} `json:\"authorizer\"`\n}\n\n// Input is the input provided by API Gateway.\ntype Input struct {\n\tHTTPMethod            string\n\tHeaders               map[string]string\n\tResource              string\n\tPathParameters        map[string]string\n\tPath                  string\n\tQueryStringParameters map[string]string\n\tBody                  string\n\tIsBase64Encoded       bool\n\tStageVariables        map[string]string\n\tRequestContext        RequestContext\n}\n\n// Output is the output expected by API Gateway.\ntype Output struct {\n\tStatusCode      int               `json:\"statusCode\"`\n\tHeaders         map[string]string `json:\"headers,omitempty\"`\n\tBody            string            `json:\"body,omitempty\"`\n\tIsBase64Encoded bool              `json:\"isBase64Encoded\"`\n}\n"
  },
  {
    "path": "internal/proxy/event_test.go",
    "content": "package proxy\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n)\n\nvar getEvent = `{\n  \"resource\": \"/{proxy+}\",\n  \"path\": \"/pets/tobi\",\n  \"httpMethod\": \"GET\",\n  \"headers\": {\n    \"Accept\": \"*/*\",\n    \"CloudFront-Forwarded-Proto\": \"https\",\n    \"CloudFront-Is-Desktop-Viewer\": \"true\",\n    \"CloudFront-Is-Mobile-Viewer\": \"false\",\n    \"CloudFront-Is-SmartTV-Viewer\": \"false\",\n    \"CloudFront-Is-Tablet-Viewer\": \"false\",\n    \"CloudFront-Viewer-Country\": \"CA\",\n    \"Host\": \"apex-ping.com\",\n    \"User-Agent\": \"curl/7.48.0\",\n    \"Via\": \"2.0 a44b4468444ef3ee67472bd5c5016098.cloudfront.net (CloudFront)\",\n    \"X-Amz-Cf-Id\": \"VRxPGF8rOXD7xpRjAjseXfRrFD3wg-QPUHY6chzB9bR7pXlct1NTpg==\",\n    \"X-Amzn-Trace-Id\": \"Root=1-59554c99-4375fc8705ccb554008b3aad\",\n    \"X-Forwarded-For\": \"207.102.57.26, 54.182.214.69\",\n    \"X-Forwarded-Port\": \"443\",\n    \"X-Forwarded-Proto\": \"https\"\n  },\n  \"queryStringParameters\": {\n    \"format\": \"json\"\n  },\n  \"pathParameters\": {\n    \"proxy\": \"pets/tobi\"\n  },\n  \"stageVariables\": {\n    \"env\": \"prod\"\n  },\n  \"requestContext\": {\n    \"path\": \"/pets/tobi\",\n    \"accountId\": \"111111111\",\n    \"resourceId\": \"jcl9w3\",\n    \"stage\": \"prod\",\n    \"requestId\": \"344b184b-5cfc-11e7-8483-27dbb2d30a77\",\n    \"identity\": {\n      \"cognitoIdentityPoolId\": null,\n      \"accountId\": null,\n      \"cognitoIdentityId\": null,\n      \"caller\": null,\n      \"apiKey\": \"\",\n      \"sourceIp\": \"207.102.57.26\",\n      \"accessKey\": null,\n      \"cognitoAuthenticationType\": null,\n      \"cognitoAuthenticationProvider\": null,\n      \"userArn\": null,\n      \"userAgent\": \"curl/7.48.0\",\n      \"user\": null\n    },\n    \"resourcePath\": \"/{proxy+}\",\n    \"httpMethod\": \"GET\",\n    \"apiId\": \"iwcgwgigca\"\n  },\n  \"body\": null,\n  \"isBase64Encoded\": false\n}`\n\nvar getEventBasicAuth = `{\n  \"resource\": \"/{proxy+}\",\n  \"path\": \"/pets/tobi\",\n  \"httpMethod\": \"GET\",\n  \"headers\": {\n    \"Accept\": \"*/*\",\n    \"CloudFront-Forwarded-Proto\": \"https\",\n    \"CloudFront-Is-Desktop-Viewer\": \"true\",\n    \"CloudFront-Is-Mobile-Viewer\": \"false\",\n    \"CloudFront-Is-SmartTV-Viewer\": \"false\",\n    \"CloudFront-Is-Tablet-Viewer\": \"false\",\n    \"CloudFront-Viewer-Country\": \"CA\",\n    \"Host\": \"apex-ping.com\",\n    \"User-Agent\": \"curl/7.48.0\",\n    \"Via\": \"2.0 a44b4468444ef3ee67472bd5c5016098.cloudfront.net (CloudFront)\",\n    \"X-Amz-Cf-Id\": \"VRxPGF8rOXD7xpRjAjseXfRrFD3wg-QPUHY6chzB9bR7pXlct1NTpg==\",\n    \"X-Amzn-Trace-Id\": \"Root=1-59554c99-4375fc8705ccb554008b3aad\",\n    \"X-Forwarded-For\": \"207.102.57.26, 54.182.214.69\",\n    \"X-Forwarded-Port\": \"443\",\n    \"X-Forwarded-Proto\": \"https\",\n\t\t\"Authorization\": \"Basic dG9iaTpmZXJyZXQ=\"\n  },\n  \"queryStringParameters\": {\n    \"format\": \"json\"\n  },\n  \"pathParameters\": {\n    \"proxy\": \"pets/tobi\"\n  },\n  \"stageVariables\": {\n    \"env\": \"prod\"\n  },\n  \"requestContext\": {\n    \"path\": \"/pets/tobi\",\n    \"accountId\": \"111111111\",\n    \"resourceId\": \"jcl9w3\",\n    \"stage\": \"prod\",\n    \"requestId\": \"344b184b-5cfc-11e7-8483-27dbb2d30a77\",\n    \"identity\": {\n      \"cognitoIdentityPoolId\": null,\n      \"accountId\": null,\n      \"cognitoIdentityId\": null,\n      \"caller\": null,\n      \"apiKey\": \"\",\n      \"sourceIp\": \"207.102.57.26\",\n      \"accessKey\": null,\n      \"cognitoAuthenticationType\": null,\n      \"cognitoAuthenticationProvider\": null,\n      \"userArn\": null,\n      \"userAgent\": \"curl/7.48.0\",\n      \"user\": null\n    },\n    \"resourcePath\": \"/{proxy+}\",\n    \"httpMethod\": \"GET\",\n    \"apiId\": \"iwcgwgigca\"\n  },\n  \"body\": null,\n  \"isBase64Encoded\": false\n}`\n\nvar postEvent = `{\n  \"resource\": \"/{proxy+}\",\n  \"path\": \"/pets/tobi\",\n  \"httpMethod\": \"POST\",\n  \"headers\": {\n    \"Accept\": \"*/*\",\n    \"CloudFront-Forwarded-Proto\": \"https\",\n    \"CloudFront-Is-Desktop-Viewer\": \"true\",\n    \"CloudFront-Is-Mobile-Viewer\": \"false\",\n    \"CloudFront-Is-SmartTV-Viewer\": \"false\",\n    \"CloudFront-Is-Tablet-Viewer\": \"false\",\n    \"CloudFront-Viewer-Country\": \"CA\",\n    \"content-type\": \"application/json\",\n    \"Host\": \"apex-ping.com\",\n    \"User-Agent\": \"curl/7.48.0\",\n    \"Via\": \"2.0 b790a9f06b09414fec5d8b87e81d4b7f.cloudfront.net (CloudFront)\",\n    \"X-Amz-Cf-Id\": \"_h1jFD3wjq6ZIyr8be6RS7Y7665jF9SjACmVodBMRefoQCs7KwTxMw==\",\n    \"X-Amzn-Trace-Id\": \"Root=1-59554cc9-35de2f970f0fdf017f16927f\",\n    \"X-Forwarded-For\": \"207.102.57.26, 54.182.214.86\",\n    \"X-Forwarded-Port\": \"443\",\n    \"X-Forwarded-Proto\": \"https\"\n  },\n  \"queryStringParameters\": null,\n  \"pathParameters\": {\n    \"proxy\": \"pets/tobi\"\n  },\n  \"requestContext\": {\n    \"path\": \"/pets/tobi\",\n    \"accountId\": \"111111111\",\n    \"resourceId\": \"jcl9w3\",\n    \"stage\": \"prod\",\n    \"requestId\": \"50f6e0ce-5cfc-11e7-ada1-4f5cfe727f01\",\n    \"identity\": {\n      \"cognitoIdentityPoolId\": null,\n      \"accountId\": null,\n      \"cognitoIdentityId\": null,\n      \"caller\": null,\n      \"apiKey\": \"\",\n      \"sourceIp\": \"207.102.57.26\",\n      \"accessKey\": null,\n      \"cognitoAuthenticationType\": null,\n      \"cognitoAuthenticationProvider\": null,\n      \"userArn\": null,\n      \"userAgent\": \"curl/7.48.0\",\n      \"user\": null\n    },\n    \"resourcePath\": \"/{proxy+}\",\n    \"httpMethod\": \"POST\",\n    \"apiId\": \"iwcgwgigca\"\n  },\n  \"body\": \"{ \\\"name\\\": \\\"Tobi\\\" }\",\n  \"isBase64Encoded\": false\n}`\n\nvar postEventBinary = `{\n  \"resource\": \"/{proxy+}\",\n  \"path\": \"/pets/tobi\",\n  \"httpMethod\": \"POST\",\n  \"headers\": {\n    \"Accept\": \"*/*\",\n    \"CloudFront-Forwarded-Proto\": \"https\",\n    \"CloudFront-Is-Desktop-Viewer\": \"true\",\n    \"CloudFront-Is-Mobile-Viewer\": \"false\",\n    \"CloudFront-Is-SmartTV-Viewer\": \"false\",\n    \"CloudFront-Is-Tablet-Viewer\": \"false\",\n    \"CloudFront-Viewer-Country\": \"CA\",\n    \"content-type\": \"text/plain\",\n    \"Host\": \"apex-ping.com\",\n    \"User-Agent\": \"curl/7.48.0\",\n    \"Via\": \"2.0 b790a9f06b09414fec5d8b87e81d4b7f.cloudfront.net (CloudFront)\",\n    \"X-Amz-Cf-Id\": \"_h1jFD3wjq6ZIyr8be6RS7Y7665jF9SjACmVodBMRefoQCs7KwTxMw==\",\n    \"X-Amzn-Trace-Id\": \"Root=1-59554cc9-35de2f970f0fdf017f16927f\",\n    \"X-Forwarded-For\": \"207.102.57.26, 54.182.214.86\",\n    \"X-Forwarded-Port\": \"443\",\n    \"X-Forwarded-Proto\": \"https\"\n  },\n  \"queryStringParameters\": null,\n  \"pathParameters\": {\n    \"proxy\": \"pets/tobi\"\n  },\n  \"requestContext\": {\n    \"path\": \"/pets/tobi\",\n    \"accountId\": \"111111111\",\n    \"resourceId\": \"jcl9w3\",\n    \"stage\": \"prod\",\n    \"requestId\": \"50f6e0ce-5cfc-11e7-ada1-4f5cfe727f01\",\n    \"identity\": {\n      \"cognitoIdentityPoolId\": null,\n      \"accountId\": null,\n      \"cognitoIdentityId\": null,\n      \"caller\": null,\n      \"apiKey\": \"\",\n      \"sourceIp\": \"207.102.57.26\",\n      \"accessKey\": null,\n      \"cognitoAuthenticationType\": null,\n      \"cognitoAuthenticationProvider\": null,\n      \"userArn\": null,\n      \"userAgent\": \"curl/7.48.0\",\n      \"user\": null\n    },\n    \"resourcePath\": \"/{proxy+}\",\n    \"httpMethod\": \"POST\",\n    \"apiId\": \"iwcgwgigca\"\n  },\n  \"body\": \"SGVsbG8gV29ybGQ=\",\n  \"isBase64Encoded\": true\n}`\n\nfunc output(v interface{}) {\n\tb, _ := json.MarshalIndent(v, \"\", \"  \")\n\tfmt.Printf(\"%s\\n\", string(b))\n}\n\nfunc ExampleInput_get() {\n\tvar in Input\n\tjson.Unmarshal([]byte(getEvent), &in)\n\toutput(in)\n\t// Output:\n\t// {\n\t//   \"HTTPMethod\": \"GET\",\n\t//   \"Headers\": {\n\t//     \"Accept\": \"*/*\",\n\t//     \"CloudFront-Forwarded-Proto\": \"https\",\n\t//     \"CloudFront-Is-Desktop-Viewer\": \"true\",\n\t//     \"CloudFront-Is-Mobile-Viewer\": \"false\",\n\t//     \"CloudFront-Is-SmartTV-Viewer\": \"false\",\n\t//     \"CloudFront-Is-Tablet-Viewer\": \"false\",\n\t//     \"CloudFront-Viewer-Country\": \"CA\",\n\t//     \"Host\": \"apex-ping.com\",\n\t//     \"User-Agent\": \"curl/7.48.0\",\n\t//     \"Via\": \"2.0 a44b4468444ef3ee67472bd5c5016098.cloudfront.net (CloudFront)\",\n\t//     \"X-Amz-Cf-Id\": \"VRxPGF8rOXD7xpRjAjseXfRrFD3wg-QPUHY6chzB9bR7pXlct1NTpg==\",\n\t//     \"X-Amzn-Trace-Id\": \"Root=1-59554c99-4375fc8705ccb554008b3aad\",\n\t//     \"X-Forwarded-For\": \"207.102.57.26, 54.182.214.69\",\n\t//     \"X-Forwarded-Port\": \"443\",\n\t//     \"X-Forwarded-Proto\": \"https\"\n\t//   },\n\t//   \"Resource\": \"/{proxy+}\",\n\t//   \"PathParameters\": {\n\t//     \"proxy\": \"pets/tobi\"\n\t//   },\n\t//   \"Path\": \"/pets/tobi\",\n\t//   \"QueryStringParameters\": {\n\t//     \"format\": \"json\"\n\t//   },\n\t//   \"Body\": \"\",\n\t//   \"IsBase64Encoded\": false,\n\t//   \"StageVariables\": {\n\t//     \"env\": \"prod\"\n\t//   },\n\t//   \"RequestContext\": {\n\t//     \"apiId\": \"iwcgwgigca\",\n\t//     \"resourceId\": \"jcl9w3\",\n\t//     \"requestId\": \"344b184b-5cfc-11e7-8483-27dbb2d30a77\",\n\t//     \"accountId\": \"111111111\",\n\t//     \"stage\": \"prod\",\n\t//     \"identity\": {\n\t//       \"apiKey\": \"\",\n\t//       \"accountId\": \"\",\n\t//       \"userAgent\": \"curl/7.48.0\",\n\t//       \"sourceIp\": \"207.102.57.26\",\n\t//       \"accessKey\": \"\",\n\t//       \"caller\": \"\",\n\t//       \"user\": \"\",\n\t//       \"userARN\": \"\",\n\t//       \"cognitoIdentityId\": \"\",\n\t//       \"cognitoIdentityPoolId\": \"\",\n\t//       \"cognitoAuthenticationType\": \"\",\n\t//       \"cognitoAuthenticationProvider\": \"\"\n\t//     },\n\t//     \"authorizer\": null\n\t//   }\n\t// }\n}\n\nfunc ExampleInput_post() {\n\tvar in Input\n\tjson.Unmarshal([]byte(postEvent), &in)\n\toutput(in)\n\t// Output:\n\t// {\n\t//   \"HTTPMethod\": \"POST\",\n\t//   \"Headers\": {\n\t//     \"Accept\": \"*/*\",\n\t//     \"CloudFront-Forwarded-Proto\": \"https\",\n\t//     \"CloudFront-Is-Desktop-Viewer\": \"true\",\n\t//     \"CloudFront-Is-Mobile-Viewer\": \"false\",\n\t//     \"CloudFront-Is-SmartTV-Viewer\": \"false\",\n\t//     \"CloudFront-Is-Tablet-Viewer\": \"false\",\n\t//     \"CloudFront-Viewer-Country\": \"CA\",\n\t//     \"Host\": \"apex-ping.com\",\n\t//     \"User-Agent\": \"curl/7.48.0\",\n\t//     \"Via\": \"2.0 b790a9f06b09414fec5d8b87e81d4b7f.cloudfront.net (CloudFront)\",\n\t//     \"X-Amz-Cf-Id\": \"_h1jFD3wjq6ZIyr8be6RS7Y7665jF9SjACmVodBMRefoQCs7KwTxMw==\",\n\t//     \"X-Amzn-Trace-Id\": \"Root=1-59554cc9-35de2f970f0fdf017f16927f\",\n\t//     \"X-Forwarded-For\": \"207.102.57.26, 54.182.214.86\",\n\t//     \"X-Forwarded-Port\": \"443\",\n\t//     \"X-Forwarded-Proto\": \"https\",\n\t//     \"content-type\": \"application/json\"\n\t//   },\n\t//   \"Resource\": \"/{proxy+}\",\n\t//   \"PathParameters\": {\n\t//     \"proxy\": \"pets/tobi\"\n\t//   },\n\t//   \"Path\": \"/pets/tobi\",\n\t//   \"QueryStringParameters\": null,\n\t//   \"Body\": \"{ \\\"name\\\": \\\"Tobi\\\" }\",\n\t//   \"IsBase64Encoded\": false,\n\t//   \"StageVariables\": null,\n\t//   \"RequestContext\": {\n\t//     \"apiId\": \"iwcgwgigca\",\n\t//     \"resourceId\": \"jcl9w3\",\n\t//     \"requestId\": \"50f6e0ce-5cfc-11e7-ada1-4f5cfe727f01\",\n\t//     \"accountId\": \"111111111\",\n\t//     \"stage\": \"prod\",\n\t//     \"identity\": {\n\t//       \"apiKey\": \"\",\n\t//       \"accountId\": \"\",\n\t//       \"userAgent\": \"curl/7.48.0\",\n\t//       \"sourceIp\": \"207.102.57.26\",\n\t//       \"accessKey\": \"\",\n\t//       \"caller\": \"\",\n\t//       \"user\": \"\",\n\t//       \"userARN\": \"\",\n\t//       \"cognitoIdentityId\": \"\",\n\t//       \"cognitoIdentityPoolId\": \"\",\n\t//       \"cognitoAuthenticationType\": \"\",\n\t//       \"cognitoAuthenticationProvider\": \"\"\n\t//     },\n\t//     \"authorizer\": null\n\t//   }\n\t// }\n}\n"
  },
  {
    "path": "internal/proxy/lambda.go",
    "content": "// Package proxy provides API Gateway and Lambda interoperability.\npackage proxy\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\n\t\"github.com/apex/go-apex\"\n\t\"github.com/pkg/errors\"\n)\n\n// NewHandler returns an apex.Handler.\nfunc NewHandler(h http.Handler) apex.Handler {\n\treturn apex.HandlerFunc(func(event json.RawMessage, ctx *apex.Context) (interface{}, error) {\n\t\te := new(Input)\n\n\t\terr := json.Unmarshal(event, e)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"parsing proxy event\")\n\t\t}\n\n\t\treq, err := NewRequest(e)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"creating new request from event\")\n\t\t}\n\n\t\tres := NewResponse()\n\t\th.ServeHTTP(res, req)\n\t\treturn res.End(), nil\n\t})\n}\n"
  },
  {
    "path": "internal/proxy/request.go",
    "content": "package proxy\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n)\n\n// NewRequest returns a new http.Request from the given Lambda event.\nfunc NewRequest(e *Input) (*http.Request, error) {\n\t// path\n\tu, err := url.Parse(e.Path)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"parsing path\")\n\t}\n\n\t// querystring\n\tq := u.Query()\n\tfor k, v := range e.QueryStringParameters {\n\t\tq.Set(k, v)\n\t}\n\tu.RawQuery = q.Encode()\n\n\t// base64 encoded body\n\tbody := e.Body\n\tif e.IsBase64Encoded {\n\t\tb, err := base64.StdEncoding.DecodeString(body)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"decoding base64 body\")\n\t\t}\n\t\tbody = string(b)\n\t}\n\n\t// new request\n\treq, err := http.NewRequest(e.HTTPMethod, u.String(), strings.NewReader(body))\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"creating request\")\n\t}\n\n\t// remote addr\n\treq.RemoteAddr = e.RequestContext.Identity.SourceIP\n\n\t// header fields\n\tfor k, v := range e.Headers {\n\t\treq.Header.Set(k, v)\n\t}\n\n\t// content-length\n\tif req.Header.Get(\"Content-Length\") == \"\" && body != \"\" {\n\t\treq.Header.Set(\"Content-Length\", strconv.Itoa(len(body)))\n\t}\n\n\t// custom fields\n\tb, _ := json.Marshal(e.RequestContext)\n\treq.Header.Set(\"X-Context\", string(b))\n\treq.Header.Set(\"X-Request-Id\", e.RequestContext.RequestID)\n\treq.Header.Set(\"X-Stage\", e.RequestContext.Stage)\n\n\t// host\n\treq.URL.Host = req.Header.Get(\"Host\")\n\treq.Host = req.URL.Host\n\n\treturn req, nil\n}\n"
  },
  {
    "path": "internal/proxy/request_test.go",
    "content": "package proxy\n\nimport (\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"testing\"\n\n\t\"github.com/tj/assert\"\n)\n\nfunc TestNewRequest(t *testing.T) {\n\tt.Run(\"GET\", func(t *testing.T) {\n\t\tvar in Input\n\t\terr := json.Unmarshal([]byte(getEvent), &in)\n\t\tassert.NoError(t, err, \"unmarshal\")\n\n\t\treq, err := NewRequest(&in)\n\t\tassert.NoError(t, err, \"new request\")\n\n\t\tassert.Equal(t, \"GET\", req.Method)\n\t\tassert.Equal(t, \"apex-ping.com\", req.Host)\n\t\tassert.Equal(t, \"/pets/tobi\", req.URL.Path)\n\t\tassert.Equal(t, \"format=json\", req.URL.Query().Encode())\n\t\tassert.Equal(t, \"207.102.57.26\", req.RemoteAddr)\n\t})\n\n\tt.Run(\"POST\", func(t *testing.T) {\n\t\tvar in Input\n\t\terr := json.Unmarshal([]byte(postEvent), &in)\n\t\tassert.NoError(t, err, \"unmarshal\")\n\n\t\treq, err := NewRequest(&in)\n\t\tassert.NoError(t, err, \"new request\")\n\n\t\tassert.Equal(t, \"POST\", req.Method)\n\t\tassert.Equal(t, \"apex-ping.com\", req.Host)\n\t\tassert.Equal(t, \"/pets/tobi\", req.URL.Path)\n\t\tassert.Equal(t, \"\", req.URL.Query().Encode())\n\t\tassert.Equal(t, \"207.102.57.26\", req.RemoteAddr)\n\n\t\tb, err := ioutil.ReadAll(req.Body)\n\t\tassert.NoError(t, err, \"read body\")\n\n\t\tassert.Equal(t, `{ \"name\": \"Tobi\" }`, string(b))\n\t})\n\n\tt.Run(\"POST binary\", func(t *testing.T) {\n\t\tvar in Input\n\t\terr := json.Unmarshal([]byte(postEventBinary), &in)\n\t\tassert.NoError(t, err, \"unmarshal\")\n\n\t\treq, err := NewRequest(&in)\n\t\tassert.NoError(t, err, \"new request\")\n\n\t\tassert.Equal(t, \"POST\", req.Method)\n\t\tassert.Equal(t, \"/pets/tobi\", req.URL.Path)\n\t\tassert.Equal(t, \"\", req.URL.Query().Encode())\n\t\tassert.Equal(t, \"207.102.57.26\", req.RemoteAddr)\n\n\t\tb, err := ioutil.ReadAll(req.Body)\n\t\tassert.NoError(t, err, \"read body\")\n\n\t\tassert.Equal(t, `Hello World`, string(b))\n\t})\n\n\tt.Run(\"Basic Auth\", func(t *testing.T) {\n\t\tvar in Input\n\t\terr := json.Unmarshal([]byte(getEventBasicAuth), &in)\n\t\tassert.NoError(t, err, \"unmarshal\")\n\n\t\treq, err := NewRequest(&in)\n\t\tassert.NoError(t, err, \"new request\")\n\n\t\tassert.Equal(t, \"GET\", req.Method)\n\t\tassert.Equal(t, \"/pets/tobi\", req.URL.Path)\n\t\tuser, pass, ok := req.BasicAuth()\n\t\tassert.Equal(t, \"tobi\", user)\n\t\tassert.Equal(t, \"ferret\", pass)\n\t\tassert.True(t, ok)\n\t})\n}\n"
  },
  {
    "path": "internal/proxy/response.go",
    "content": "package proxy\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"mime\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/apex/up/internal/util\"\n)\n\n// ResponseWriter implements the http.ResponseWriter interface\n// in order to support the API Gateway Lambda HTTP \"protocol\".\ntype ResponseWriter struct {\n\tout         Output\n\tbuf         bytes.Buffer\n\theader      http.Header\n\twroteHeader bool\n}\n\n// NewResponse returns a new response writer to capture http output.\nfunc NewResponse() *ResponseWriter {\n\treturn &ResponseWriter{}\n}\n\n// Header implementation.\nfunc (w *ResponseWriter) Header() http.Header {\n\tif w.header == nil {\n\t\tw.header = make(http.Header)\n\t}\n\n\treturn w.header\n}\n\n// Write implementation.\nfunc (w *ResponseWriter) Write(b []byte) (int, error) {\n\tif !w.wroteHeader {\n\t\tw.WriteHeader(http.StatusOK)\n\t}\n\n\t// TODO: HEAD? ignore\n\n\treturn w.buf.Write(b)\n}\n\n// WriteHeader implementation.\nfunc (w *ResponseWriter) WriteHeader(status int) {\n\tif w.wroteHeader {\n\t\treturn\n\t}\n\n\tif w.Header().Get(\"Content-Type\") == \"\" {\n\t\tw.Header().Set(\"Content-Type\", \"text/plain; charset=utf8\")\n\t}\n\n\tw.out.StatusCode = status\n\n\th := make(map[string]string)\n\n\t// API Gateway does not support multiple set-cookie fields\n\t// so we have to stagger the casing in order to support this.\n\tutil.FixMultipleSetCookie(w.Header())\n\n\tfor k, v := range w.Header() {\n\t\tif len(v) > 0 {\n\t\t\th[k] = v[len(v)-1]\n\t\t}\n\t}\n\n\tw.out.Headers = h\n\tw.wroteHeader = true\n}\n\n// End the request.\nfunc (w *ResponseWriter) End() Output {\n\tw.out.IsBase64Encoded = isBinary(w.header)\n\n\tif w.out.IsBase64Encoded {\n\t\tw.out.Body = base64.StdEncoding.EncodeToString(w.buf.Bytes())\n\t} else {\n\t\tw.out.Body = w.buf.String()\n\t}\n\n\treturn w.out\n}\n\n// isBinary returns true if the response reprensents binary.\nfunc isBinary(h http.Header) bool {\n\tif !isTextMime(h.Get(\"Content-Type\")) {\n\t\treturn true\n\t}\n\n\tif h.Get(\"Content-Encoding\") == \"gzip\" {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\n// isTextMime returns true if the content type represents textual data.\nfunc isTextMime(kind string) bool {\n\tmt, _, err := mime.ParseMediaType(kind)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tif strings.HasPrefix(mt, \"text/\") {\n\t\treturn true\n\t}\n\n\tswitch mt {\n\tcase \"image/svg+xml\":\n\t\treturn true\n\tcase \"application/json\":\n\t\treturn true\n\tcase \"application/xml\":\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n"
  },
  {
    "path": "internal/proxy/response_test.go",
    "content": "package proxy\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/tj/assert\"\n)\n\nfunc Test_JSON_isTextMime(t *testing.T) {\n\tassert.Equal(t, isTextMime(\"application/json\"), true)\n\tassert.Equal(t, isTextMime(\"application/json; charset=utf-8\"), true)\n\tassert.Equal(t, isTextMime(\"Application/JSON\"), true)\n}\n\nfunc Test_XML_isTextMime(t *testing.T) {\n\tassert.Equal(t, isTextMime(\"application/xml\"), true)\n\tassert.Equal(t, isTextMime(\"application/xml; charset=utf-8\"), true)\n\tassert.Equal(t, isTextMime(\"ApPlicaTion/xMl\"), true)\n}\n\nfunc TestResponseWriter_Header(t *testing.T) {\n\tw := NewResponse()\n\tw.Header().Set(\"Foo\", \"bar\")\n\tw.Header().Set(\"Bar\", \"baz\")\n\n\tvar buf bytes.Buffer\n\tw.header.Write(&buf)\n\n\tassert.Equal(t, \"Bar: baz\\r\\nFoo: bar\\r\\n\", buf.String())\n}\n\nfunc TestResponseWriter_Write_text(t *testing.T) {\n\ttypes := []string{\n\t\t\"text/x-custom\",\n\t\t\"text/plain\",\n\t\t\"text/plain; charset=utf-8\",\n\t\t\"application/json\",\n\t\t\"application/json; charset=utf-8\",\n\t\t\"application/xml\",\n\t\t\"image/svg+xml\",\n\t}\n\n\tfor _, kind := range types {\n\t\tt.Run(kind, func(t *testing.T) {\n\t\t\tw := NewResponse()\n\t\t\tw.Header().Set(\"Content-Type\", kind)\n\t\t\tw.Write([]byte(\"hello world\\n\"))\n\n\t\t\te := w.End()\n\t\t\tassert.Equal(t, 200, e.StatusCode)\n\t\t\tassert.Equal(t, \"hello world\\n\", e.Body)\n\t\t\tassert.Equal(t, kind, e.Headers[\"Content-Type\"])\n\t\t\tassert.False(t, e.IsBase64Encoded)\n\t\t})\n\t}\n}\n\nfunc TestResponseWriter_Write_binary(t *testing.T) {\n\tw := NewResponse()\n\tw.Header().Set(\"Content-Type\", \"image/png\")\n\tw.Write([]byte(\"data\"))\n\n\te := w.End()\n\tassert.Equal(t, 200, e.StatusCode)\n\tassert.Equal(t, \"ZGF0YQ==\", e.Body)\n\tassert.Equal(t, \"image/png\", e.Headers[\"Content-Type\"])\n\tassert.True(t, e.IsBase64Encoded)\n}\n\nfunc TestResponseWriter_Write_gzip(t *testing.T) {\n\tw := NewResponse()\n\tw.Header().Set(\"Content-Type\", \"text/plain\")\n\tw.Header().Set(\"Content-Encoding\", \"gzip\")\n\tw.Write([]byte(\"data\"))\n\n\te := w.End()\n\tassert.Equal(t, 200, e.StatusCode)\n\tassert.Equal(t, \"ZGF0YQ==\", e.Body)\n\tassert.Equal(t, \"text/plain\", e.Headers[\"Content-Type\"])\n\tassert.True(t, e.IsBase64Encoded)\n}\n\nfunc TestResponseWriter_WriteHeader(t *testing.T) {\n\tw := NewResponse()\n\tw.WriteHeader(404)\n\tw.Write([]byte(\"Not Found\\n\"))\n\n\te := w.End()\n\tassert.Equal(t, 404, e.StatusCode)\n\tassert.Equal(t, \"Not Found\\n\", e.Body)\n\tassert.Equal(t, \"text/plain; charset=utf8\", e.Headers[\"Content-Type\"])\n}\n"
  },
  {
    "path": "internal/redirect/redirect.go",
    "content": "// Package redirect provides compiling and matching\n// redirect and rewrite rules.\npackage redirect\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/fanyang01/radix\"\n\t\"github.com/pkg/errors\"\n)\n\n// placeholders regexp.\nvar placeholders = regexp.MustCompile(`:(\\w+)`)\n\n// Rule is a single redirect rule.\ntype Rule struct {\n\tPath     string `json:\"path\"`\n\tLocation string `json:\"location\"`\n\tStatus   int    `json:\"status\"`\n\tForce    bool   `json:\"force\"`\n\tnames    map[string]bool\n\tdynamic  bool\n\tsub      string\n\tpath     *regexp.Regexp\n}\n\n// URL returns the final destination after substitutions from path.\nfunc (r Rule) URL(path string) string {\n\treturn r.path.ReplaceAllString(path, r.sub)\n}\n\n// IsDynamic returns true if a splat or placeholder is used.\nfunc (r *Rule) IsDynamic() bool {\n\treturn r.dynamic\n}\n\n// IsRewrite returns true if the rule represents a rewrite.\nfunc (r *Rule) IsRewrite() bool {\n\treturn r.Status == 200 || r.Status == 0\n}\n\n// Compile the rule.\nfunc (r *Rule) Compile() {\n\tr.path, r.names = compilePath(r.Path)\n\tr.sub = compileSub(r.Path, r.Location, r.names)\n\tr.dynamic = isDynamic(r.Path)\n}\n\n// Rules map of paths to redirects.\ntype Rules map[string]Rule\n\n// Matcher for header lookup.\ntype Matcher struct {\n\tt *radix.PatternTrie\n}\n\n// Lookup returns fields for the given path.\nfunc (m *Matcher) Lookup(path string) *Rule {\n\tv, ok := m.t.Lookup(path)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tr := v.(Rule)\n\treturn &r\n}\n\n// Compile the given rules to a trie.\nfunc Compile(rules Rules) (*Matcher, error) {\n\tt := radix.NewPatternTrie()\n\tm := &Matcher{t}\n\n\tfor path, rule := range rules {\n\t\trule.Path = path\n\t\trule.Compile()\n\t\tt.Add(compilePattern(path), rule)\n\t\tt.Add(compilePattern(path)+\"/\", rule)\n\t}\n\n\treturn m, nil\n}\n\n// compileSub returns a substitution string.\nfunc compileSub(path, s string, names map[string]bool) string {\n\t// splat\n\ts = strings.Replace(s, `:splat`, `${splat}`, -1)\n\n\t// placeholders\n\ts = placeholders.ReplaceAllStringFunc(s, func(v string) string {\n\t\tname := v[1:]\n\n\t\t// TODO: refactor to not panic\n\t\tif !names[name] {\n\t\t\tpanic(errors.Errorf(\"placeholder %q is not present in the path pattern %q\", v, path))\n\t\t}\n\n\t\treturn fmt.Sprintf(\"${%s}\", name)\n\t})\n\n\treturn s\n}\n\n// compilePath returns a regexp for substitutions and return\n// a map of placeholder names for validation.\nfunc compilePath(s string) (*regexp.Regexp, map[string]bool) {\n\tnames := make(map[string]bool)\n\n\t// escape\n\ts = regexp.QuoteMeta(s)\n\n\t// splat\n\ts = strings.Replace(s, `\\*`, `(?P<splat>.*?)`, -1)\n\n\t// placeholders\n\ts = placeholders.ReplaceAllStringFunc(s, func(v string) string {\n\t\tname := v[1:]\n\t\tnames[name] = true\n\t\treturn fmt.Sprintf(`(?P<%s>[^/]+)`, name)\n\t})\n\n\t// trailing slash\n\ts += `\\/?`\n\n\ts = fmt.Sprintf(`^%s$`, s)\n\treturn regexp.MustCompile(s), names\n}\n\n// compilePattern to a syntax usable by the trie.\nfunc compilePattern(s string) string {\n\treturn placeholders.ReplaceAllString(s, \"*\")\n}\n\n// isDynamic returns true for splats or placeholders.\nfunc isDynamic(s string) bool {\n\treturn hasPlaceholder(s) || hasSplat(s)\n}\n\n// hasPlaceholder returns true for placeholders\nfunc hasPlaceholder(s string) bool {\n\treturn strings.ContainsRune(s, ':')\n}\n\n// hasSplat returns true for splats.\nfunc hasSplat(s string) bool {\n\treturn strings.ContainsRune(s, '*')\n}\n"
  },
  {
    "path": "internal/redirect/redirect_test.go",
    "content": "package redirect\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/tj/assert\"\n)\n\nfunc rule(from, to string) Rule {\n\tr := Rule{\n\t\tPath:     from,\n\t\tLocation: to,\n\t}\n\n\tr.Compile()\n\treturn r\n}\n\nfunc TestRule_URL(t *testing.T) {\n\tt.Run(\"exact\", func(t *testing.T) {\n\t\ts := rule(\"/docs\", \"/help\").URL(\"/docs\")\n\t\tassert.Equal(t, \"/help\", s)\n\t})\n\n\tt.Run(\"splat one segment\", func(t *testing.T) {\n\t\tr := rule(\"/docs/*\", \"/help/:splat\")\n\t\tassert.Equal(t, \"/help/foo\", r.URL(\"/docs/foo\"))\n\t})\n\n\tt.Run(\"splat many segments\", func(t *testing.T) {\n\t\tr := rule(\"/docs/*\", \"/help/:splat\")\n\t\tassert.Equal(t, \"/help/foo/bar/baz\", r.URL(\"/docs/foo/bar/baz\"))\n\t})\n\n\tt.Run(\"placeholder\", func(t *testing.T) {\n\t\tr := rule(\"/shop/:brand\", \"/store/:brand\")\n\t\tassert.Equal(t, \"/store/apple\", r.URL(\"/shop/apple\"))\n\t})\n\n\tt.Run(\"placeholders\", func(t *testing.T) {\n\t\tr := rule(\"/shop/:brand/category/:cat\", \"/products/:brand/:cat\")\n\t\tassert.Equal(t, \"/products/apple/laptops\", r.URL(\"/shop/apple/category/laptops\"))\n\t})\n\n\tt.Run(\"placeholders trailing slash\", func(t *testing.T) {\n\t\tr := rule(\"/docs/:product/guides/:guide\", \"/help/:product/:guide\")\n\t\tassert.Equal(t, \"/help/ping/alerting\", r.URL(\"/docs/ping/guides/alerting/\"))\n\t})\n\n\tt.Run(\"placeholders rearranged\", func(t *testing.T) {\n\t\tr := rule(\"/shop/:brand/category/:cat\", \"/products/:cat/:brand\")\n\t\tassert.Equal(t, \"/products/laptops/apple\", r.URL(\"/shop/apple/category/laptops\"))\n\t})\n\n\tt.Run(\"placeholders mismatch\", func(t *testing.T) {\n\t\t// TODO: sorry :D\n\t\terr := func() (err error) {\n\t\t\tdefer func() {\n\t\t\t\terr = recover().(error)\n\t\t\t}()\n\n\t\t\trule(\"/shop/:brand/category/:category\", \"/products/:cat/:brand\")\n\t\t\treturn nil\n\t\t}()\n\n\t\tassert.EqualError(t, err, `placeholder \":cat\" is not present in the path pattern \"/shop/:brand/category/:category\"`)\n\t})\n}\n\nfunc TestMatcher_Lookup(t *testing.T) {\n\trules := Rules{\n\t\t\"/docs/:product/guides/:guide\": Rule{\n\t\t\tLocation: \"/help/:product/:guide\",\n\t\t\tStatus:   301,\n\t\t},\n\t\t\"/blog\": Rule{\n\t\t\tLocation: \"https://blog.apex.sh\",\n\t\t\tStatus:   302,\n\t\t},\n\t\t\"/articles/*\": Rule{\n\t\t\tLocation: \"/guides/:splat\",\n\t\t},\n\t}\n\n\tm, err := Compile(rules)\n\tassert.NoError(t, err, \"compile\")\n\n\tt.Run(\"exact\", func(t *testing.T) {\n\t\tassert.NotNil(t, m.Lookup(\"/blog\"))\n\t})\n\n\tt.Run(\"exact trailing slash\", func(t *testing.T) {\n\t\tassert.NotNil(t, m.Lookup(\"/blog/\"))\n\t})\n\n\tt.Run(\"placeholders\", func(t *testing.T) {\n\t\tassert.NotNil(t, m.Lookup(\"/docs/ping/guides/alerts\"))\n\t})\n\n\t// TODO: need to fork the trie to be less greedy\n\t// t.Run(\"mismatch\", func(t *testing.T) {\n\t// \tr := m.Lookup(\"/docs/ping/another/guides/alerts\")\n\t// \tassert.NotNil(t, r)\n\t// })\n\n\tt.Run(\"splat one segment\", func(t *testing.T) {\n\t\tassert.NotNil(t, m.Lookup(\"/articles/alerting\"))\n\t})\n\n\tt.Run(\"splat many segments\", func(t *testing.T) {\n\t\tassert.NotNil(t, m.Lookup(\"/articles/alerting/pagerduty\"))\n\t\tassert.NotNil(t, m.Lookup(\"/articles/alerting/pagerduty/\"))\n\t})\n}\n\nfunc BenchmarkMatcher_Lookup(b *testing.B) {\n\trules := Rules{\n\t\t\"/docs/:product/guides/:guide\": Rule{\n\t\t\tLocation: \"/help/:product/:guide\",\n\t\t\tStatus:   301,\n\t\t},\n\t}\n\n\tm, err := Compile(rules)\n\tassert.NoError(b, err, \"compile\")\n\n\tb.ResetTimer()\n\n\tb.Run(\"match\", func(b *testing.B) {\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tfmt.Printf(\"%#v\\n\", m.Lookup(\"/docs/ping/guides/alerts\"))\n\t\t}\n\t})\n\n\tb.Run(\"mismatch\", func(b *testing.B) {\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tm.Lookup(\"/some/other/page\")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "internal/setup/setup.go",
    "content": "// Package setup provides up.json initialization.\npackage setup\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\n\t\"github.com/mitchellh/go-homedir\"\n\t\"github.com/tj/go/term\"\n\t\"github.com/tj/survey\"\n\n\t\"github.com/apex/up/internal/util\"\n\t\"github.com/apex/up/internal/validate\"\n\t\"github.com/apex/up/platform/aws/regions\"\n)\n\n// ErrNoCredentials is the error returned when no AWS credential profiles are available.\nvar ErrNoCredentials = errors.New(\"no credentials\")\n\n// config saved to up.json\ntype config struct {\n\tName    string   `json:\"name\"`\n\tProfile string   `json:\"profile\"`\n\tRegions []string `json:\"regions\"`\n}\n\n// questions for the user.\nvar questions = []*survey.Question{\n\t{\n\t\tName: \"name\",\n\t\tPrompt: &survey.Input{\n\t\t\tMessage: \"Project name:\",\n\t\t\tDefault: defaultName(),\n\t\t},\n\t\tValidate: validateName,\n\t},\n\t{\n\t\tName: \"profile\",\n\t\tPrompt: &survey.Select{\n\t\t\tMessage:  \"AWS profile:\",\n\t\t\tOptions:  awsProfiles(),\n\t\t\tDefault:  os.Getenv(\"AWS_PROFILE\"),\n\t\t\tPageSize: 10,\n\t\t},\n\t\tValidate: survey.Required,\n\t},\n\t{\n\t\tName: \"region\",\n\t\tPrompt: &survey.Select{\n\t\t\tMessage:  \"AWS region:\",\n\t\t\tOptions:  regions.Names,\n\t\t\tDefault:  defaultRegion(),\n\t\t\tPageSize: 15,\n\t\t},\n\t\tValidate: survey.Required,\n\t},\n}\n\n// Create an up.json file for the user.\nfunc Create() error {\n\tvar in struct {\n\t\tName    string `json:\"name\"`\n\t\tProfile string `json:\"profile\"`\n\t\tRegion  string `json:\"region\"`\n\t}\n\n\tif len(awsProfiles()) == 0 {\n\t\treturn ErrNoCredentials\n\t}\n\n\tprintln()\n\n\t// confirm\n\tvar ok bool\n\terr := survey.AskOne(&survey.Confirm{\n\t\tMessage: fmt.Sprintf(\"No up.json found, create a new project?\"),\n\t\tDefault: true,\n\t}, &ok, nil)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !ok {\n\t\treturn errors.New(\"aborted\")\n\t}\n\n\t// prompt\n\tterm.MoveUp(1)\n\tterm.ClearLine()\n\tif err := survey.Ask(questions, &in); err != nil {\n\t\treturn err\n\t}\n\n\tc := config{\n\t\tName:    in.Name,\n\t\tProfile: in.Profile,\n\t\tRegions: []string{\n\t\t\tregions.GetIdByName(in.Region),\n\t\t},\n\t}\n\n\tb, _ := json.MarshalIndent(c, \"\", \"  \")\n\treturn ioutil.WriteFile(\"up.json\", b, 0644)\n}\n\n// defaultName returns the default app name.\n// The name is only inferred if it is valid.\nfunc defaultName() string {\n\tdir, _ := os.Getwd()\n\tname := filepath.Base(dir)\n\tif validate.Name(name) != nil {\n\t\treturn \"\"\n\t}\n\treturn name\n}\n\n// defaultRegion returns the default aws region.\nfunc defaultRegion() string {\n\tif s := os.Getenv(\"AWS_DEFAULT_REGION\"); s != \"\" {\n\t\treturn s\n\t}\n\n\tif s := os.Getenv(\"AWS_REGION\"); s != \"\" {\n\t\treturn s\n\t}\n\n\treturn \"\"\n}\n\n// validateName validates the name prompt.\nfunc validateName(v interface{}) error {\n\tif err := validate.Name(v.(string)); err != nil {\n\t\treturn err\n\t}\n\n\treturn survey.Required(v)\n}\n\n// awsProfiles returns the AWS profiles found.\nfunc awsProfiles() []string {\n\tpath, err := homedir.Expand(\"~/.aws/credentials\")\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil\n\t}\n\tdefer f.Close()\n\n\ts, err := util.ParseSections(f)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tsort.Strings(s)\n\treturn s\n}\n"
  },
  {
    "path": "internal/shim/index.js",
    "content": "const child = require('child_process');\n\n/**\n * Debug env var.\n */\n\nconst debug = process.env.DEBUG_SHIM;\n\n/**\n * A map of string(id) to callback function, used for when\n * many concurrent requests are outstanding.\n */\n\nconst callbacks = new Map();\n\n/**\n * The last id attached to a request / callback pair\n */\n\nlet lastId = (Date.now() / 1000) | 0;\n\n/**\n * nextId generates ids which will only be repeated every 2^52 times being generated\n */\n\nfunction nextId(){\n  // Prevent bugs where integer precision wraps around on floating point numbers\n  // (usually around 52-53 bits)\n  let id = (lastId + 1) | 0;\n  if (id === lastId) {\n    id = 1;\n  }\n\n  lastId = id;\n  return String(id);\n}\n\n/**\n * handleLine is responsible for taking a line of output from the child process\n * and calling the appropiate callbacks.\n */\nfunction handleLine(line) {\n  if (debug) {\n    console.log('[shim] parsing: `%s`', line);\n  }\n\n  let msg;\n  try {\n    msg = JSON.parse(line);\n  } catch (err) {\n    console.log('[shim] unexpected non-json line: `%s`', line);\n    return;\n  }\n\n  if (typeof msg.id !== 'string') {\n    console.log('[shim] unexpected line - do not use stdout: `%s`', line);\n    return;\n  }\n\n  const c = callbacks.get(msg.id);\n  callbacks.delete(msg.id);\n\n  if (!c) {\n    if (debug) {\n      console.log('[shim] unexpected duplicate response: `%s`', line);\n    }\n\n    return;\n  }\n\n  c(msg.error, msg.value);\n}\n\n/**\n * Child process for binary I/O.\n */\n\nconst proc = child.spawn('./main', { stdio: ['pipe', 'pipe', process.stderr] });\n\nproc.on('error', function(err){\n  console.error('[shim] error: %s', err);\n  process.exit(1);\n})\n\nproc.on('exit', function(code, signal){\n  console.error('[shim] exit: code=%s signal=%s', code, signal);\n  process.exit(1);\n})\n\n/**\n * Newline-delimited JSON stdout.\n */\n\n// Chunks holds onto partial chunks received in the absense of a newline.\n// invariant: an array of Buffer objects, all of which do not have any newline characters\nlet chunks = [];\nconst NEWLINE = '\\n'.charCodeAt(0);\n\n// Find successive newlines in this chunk, and pass them along to `handleChunk`\nfunction handleChunk(chunk) {\n  // since this current chunk can have multple lines inside of it\n  // keep track of how much of the current chunk we've consumed\n  let chunkPos = 0;\n  for (;;) {\n    // Find the first newline in the current, in the part of the current chunk we have not\n    // looked yet.\n    const newlinePos = chunk.indexOf(NEWLINE, chunkPos);\n\n    // We were not able to find any more newline characters in this chunk,\n    // save the remaineder in `chunks` for later processing\n    if (newlinePos === -1) {\n      chunks.push(chunk.slice(chunkPos));\n      break;\n    }\n\n    // We have found an end of a whole line, the beginning of the line will be the combination\n    // of all Buffers currently buffered in the `chunks` array (if any)\n    const start = chunk.slice(chunkPos, newlinePos);\n\n    chunks.push(start);\n    const line = Buffer.concat(chunks);\n    chunks = [];\n\n    // increase the chunk position, to skip over the last line we just found\n    chunkPos = newlinePos + 1;\n    handleLine(line)\n  }\n}\n\nconst out = proc.stdout;\n\nout.on('readable', () => {\n  for (;;) {\n    const chunk = out.read();\n    if (chunk === null) {\n      break;\n    }\n\n    // Pump all data chunks into chunk handler\n    handleChunk(chunk);\n  }\n});\n\n/**\n * Handle events.\n */\nexports.handle = function(event, ctx, cb) {\n  ctx.callbackWaitsForEmptyEventLoop = false;\n\n  const id = nextId();\n  callbacks.set(id, cb);\n\n  proc.stdin.write(JSON.stringify({\n    \"id\": id,\n    \"event\": event,\n    \"context\": ctx\n  })+'\\n');\n}\n"
  },
  {
    "path": "internal/shim/shim.go",
    "content": "//go:generate go-bindata -modtime 0 -pkg shim .\n\n// Package shim provides a shim for running arbitrary languages on Lambda.\npackage shim\n"
  },
  {
    "path": "internal/signal/signal.go",
    "content": "package signal\n\nimport (\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\n\t\"github.com/apex/up/internal/util\"\n)\n\n// close funcs.\nvar fns []Func\n\n// Init signals channel\nfunc init() {\n\ts := make(chan os.Signal, 1)\n\tgo trap(s)\n\tsignal.Notify(s, syscall.SIGINT)\n}\n\n// Func is a close function.\ntype Func func() error\n\n// Add registers a close handler func.\nfunc Add(fn Func) {\n\tfns = append(fns, fn)\n}\n\n// trap signals to invoke callbacks and exit.\nfunc trap(ch chan os.Signal) {\n\t<-ch\n\tfor _, fn := range fns {\n\t\tif err := fn(); err != nil {\n\t\t\tutil.Fatal(err)\n\t\t}\n\t}\n\tos.Exit(1)\n}\n"
  },
  {
    "path": "internal/stats/stats.go",
    "content": "// Package stats provides CLI analytics.\npackage stats\n\nimport (\n\t\"github.com/apex/log\"\n\t\"github.com/tj/go-cli-analytics\"\n)\n\n// p merged with track calls.\nvar p = map[string]interface{}{}\n\n// Client for Segment analytics.\nvar Client = analytics.New(&analytics.Config{\n\tWriteKey: \"qnvYCHktBBgACBkQ6V4dzh7aFCe8LF8u\",\n\tDir:      \".up\",\n})\n\n// Track event `name` with optional `props`.\nfunc Track(name string, props map[string]interface{}) {\n\tif props == nil {\n\t\tprops = map[string]interface{}{}\n\t}\n\n\tfor k, v := range p {\n\t\tprops[k] = v\n\t}\n\n\tlog.Debugf(\"track %q %v\", name, props)\n\tClient.Track(name, props)\n}\n\n// SetProperties sets global properties.\nfunc SetProperties(props map[string]interface{}) {\n\tp = props\n}\n\n// Flush stats.\nfunc Flush() {\n\tlog.Debug(\"flushing analytics\")\n\tif err := Client.Flush(); err != nil {\n\t\tlog.WithError(err).Debug(\"flushing analytics\")\n\t}\n\tlog.Debug(\"flushing analytics\")\n}\n"
  },
  {
    "path": "internal/userconfig/userconfig.go",
    "content": "// Package userconfig provides user machine-level configuration.\npackage userconfig\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/apex/up/internal/util\"\n\t\"github.com/mitchellh/go-homedir\"\n\t\"github.com/pkg/errors\"\n)\n\nvar (\n\t// configDir is the dir name where up config is stored relative to HOME.\n\tconfigDir = \".up\"\n\n\t// envName is the environment variable which can be used to store\n\t// Up's configuration, primarily for continuous integration.\n\tenvName = \"UP_CONFIG\"\n)\n\n// Team is the user configuration for a given team.\ntype Team struct {\n\t// ID is the team identifier.\n\tID string `json:\"team\"`\n\n\t// Email is the user's email.\n\tEmail string `json:\"email\"`\n\n\t// Token is the access token.\n\tToken string `json:\"token\"`\n}\n\n// IsPersonal returns true if it is a personal team.\nfunc (t *Team) IsPersonal() bool {\n\treturn t.Email == t.ID\n}\n\n// Config is the user configuration.\ntype Config struct {\n\t// Team is the active team.\n\tTeam string `json:\"team\"`\n\n\t// Teams is the user's active teams.\n\tTeams map[string]*Team `json:\"teams\"`\n}\n\n// initTeams inits the map.\nfunc (c *Config) initTeams() {\n\tif c.Teams == nil {\n\t\tc.Teams = make(map[string]*Team)\n\t}\n}\n\n// AddTeam adds or replaces the given team.\nfunc (c *Config) AddTeam(t *Team) {\n\tc.initTeams()\n\tc.Teams[t.ID] = t\n}\n\n// GetTeams returns a list of teams.\nfunc (c *Config) GetTeams() (teams []*Team) {\n\tfor _, t := range c.Teams {\n\t\tteams = append(teams, t)\n\t}\n\treturn\n}\n\n// GetTeam returns a team by id or nil\nfunc (c *Config) GetTeam(id string) *Team {\n\treturn c.Teams[id]\n}\n\n// GetActiveTeam returns the active team.\nfunc (c *Config) GetActiveTeam() *Team {\n\treturn c.GetTeam(c.Team)\n}\n\n// Authenticated returns true if the user has an active team.\nfunc (c *Config) Authenticated() bool {\n\treturn c.GetActiveTeam() != nil\n}\n\n// Require requires authentication and returns the active team.\nfunc Require() (*Team, error) {\n\tvar c Config\n\n\tif err := c.Load(); err != nil {\n\t\treturn nil, errors.Wrap(err, \"loading config\")\n\t}\n\n\tif !c.Authenticated() {\n\t\treturn nil, errors.New(\"user credentials missing, make sure to `up team login` first\")\n\t}\n\n\treturn c.GetActiveTeam(), nil\n}\n\n// Alter config, loading and saving after manipulation.\nfunc Alter(fn func(*Config)) error {\n\tvar config Config\n\n\tif err := config.Load(); err != nil {\n\t\treturn errors.Wrap(err, \"loading\")\n\t}\n\n\tfn(&config)\n\n\tif err := config.Save(); err != nil {\n\t\treturn errors.Wrap(err, \"saving\")\n\t}\n\n\treturn nil\n}\n\n// Load the configuration.\nfunc (c *Config) Load() error {\n\tpath, err := c.path()\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"getting path\")\n\t}\n\n\t// env\n\tif s := os.Getenv(envName); s != \"\" {\n\t\t// if not JSON, check for base64 encoding\n\t\tif !util.IsJSON(s) {\n\t\t\tdecoded, err := base64.StdEncoding.DecodeString(s)\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrap(err, \"decoding base64\")\n\t\t\t}\n\t\t\ts = string(decoded)\n\t\t}\n\n\t\tif err := json.Unmarshal([]byte(s), &c); err != nil {\n\t\t\treturn errors.Wrap(err, \"unmarshaling\")\n\t\t}\n\t\treturn nil\n\t}\n\n\t// file\n\tb, err := ioutil.ReadFile(path)\n\n\tif os.IsNotExist(err) {\n\t\treturn nil\n\t}\n\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"reading\")\n\t}\n\n\tif err := json.Unmarshal(b, c); err != nil {\n\t\treturn errors.Wrap(err, \"unmarshaling\")\n\t}\n\n\treturn nil\n}\n\n// Save the configuration.\nfunc (c *Config) Save() error {\n\tb, err := json.MarshalIndent(c, \"\", \" \")\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"marshaling\")\n\t}\n\n\tpath, err := c.path()\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"getting path\")\n\t}\n\n\tif err := ioutil.WriteFile(path, b, 0755); err != nil {\n\t\treturn errors.Wrap(err, \"writing\")\n\t}\n\n\treturn nil\n}\n\n// path returns the path and sets up dir if necessary.\nfunc (c *Config) path() (string, error) {\n\thome, err := homedir.Dir()\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"homedir\")\n\t}\n\n\tdir := filepath.Join(home, configDir)\n\tif err := os.MkdirAll(dir, 0755); err != nil {\n\t\treturn \"\", errors.Wrap(err, \"mkdir\")\n\t}\n\n\tpath := filepath.Join(dir, \"config.json\")\n\treturn path, nil\n}\n"
  },
  {
    "path": "internal/userconfig/userconfig_test.go",
    "content": "package userconfig\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/mitchellh/go-homedir\"\n\t\"github.com/tj/assert\"\n)\n\nfunc init() {\n\tconfigDir = \".up-test\"\n}\n\nfunc TestConfig_file(t *testing.T) {\n\tt.Run(\"load when missing\", func(t *testing.T) {\n\t\tdir, _ := homedir.Dir()\n\t\tos.RemoveAll(filepath.Join(dir, configDir))\n\n\t\tc := Config{}\n\t\tassert.NoError(t, c.Load(), \"load\")\n\t})\n\n\tt.Run(\"save\", func(t *testing.T) {\n\t\tc := Config{}\n\t\tassert.NoError(t, c.Load(), \"load\")\n\t\tassert.Equal(t, \"\", c.Team)\n\n\t\tc.Team = \"apex\"\n\t\tassert.NoError(t, c.Save(), \"save\")\n\t})\n\n\tt.Run(\"load after save\", func(t *testing.T) {\n\t\tc := Config{}\n\t\tassert.NoError(t, c.Load(), \"save\")\n\t\tassert.Equal(t, \"apex\", c.Team)\n\t})\n}\n\nfunc TestConfig_env(t *testing.T) {\n\tt.Run(\"load\", func(t *testing.T) {\n\t\tos.Setenv(\"UP_CONFIG\", `{ \"team\": \"tj@apex.sh\" }`)\n\t\tc := Config{}\n\t\tassert.NoError(t, c.Load(), \"load\")\n\t\tassert.Equal(t, \"tj@apex.sh\", c.Team)\n\t})\n}\n"
  },
  {
    "path": "internal/util/util.go",
    "content": "// Package util haters gonna hate.\npackage util\n\nimport (\n\t\"bufio\"\n\t\"crypto/md5\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"math\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/apex/up/internal/colors\"\n\t\"github.com/pascaldekloe/name\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/tj/backoff\"\n\t\"github.com/tj/go-progress\"\n\t\"github.com/tj/go/term\"\n\t\"golang.org/x/net/publicsuffix\"\n)\n\n// ClearHeader removes all content header fields.\nfunc ClearHeader(h http.Header) {\n\th.Del(\"Content-Type\")\n\th.Del(\"Content-Length\")\n\th.Del(\"Content-Encoding\")\n\th.Del(\"Content-Range\")\n\th.Del(\"Content-MD5\")\n\th.Del(\"Cache-Control\")\n\th.Del(\"ETag\")\n\th.Del(\"Last-Modified\")\n}\n\n// ManagedByUp appends \"Managed by Up\".\nfunc ManagedByUp(s string) string {\n\tif s == \"\" {\n\t\treturn \"Managed by Up.\"\n\t}\n\n\treturn s + \" (Managed by Up).\"\n}\n\n// Exists returns true if the file exists.\nfunc Exists(path string) bool {\n\t_, err := os.Stat(path)\n\treturn err == nil\n}\n\n// ReadFileJSON reads json from the given path.\nfunc ReadFileJSON(path string, v interface{}) error {\n\tb, err := ioutil.ReadFile(path)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"reading\")\n\t}\n\n\tif err := json.Unmarshal(b, &v); err != nil {\n\t\treturn errors.Wrap(err, \"unmarshaling\")\n\t}\n\n\treturn nil\n}\n\n// Camelcase string with optional args.\nfunc Camelcase(s string, v ...interface{}) string {\n\treturn name.CamelCase(fmt.Sprintf(s, v...), true)\n}\n\n// NewProgressInt with the given total.\nfunc NewProgressInt(total int) *progress.Bar {\n\tb := progress.NewInt(total)\n\tb.Template(`{{.Bar}} {{.Percent | printf \"%0.0f\"}}% {{.Text}}`)\n\tb.Width = 35\n\tb.StartDelimiter = colors.Gray(\"|\")\n\tb.EndDelimiter = colors.Gray(\"|\")\n\tb.Filled = colors.Purple(\"█\")\n\tb.Empty = colors.Gray(\"░\")\n\treturn b\n}\n\n// NewInlineProgressInt with the given total.\nfunc NewInlineProgressInt(total int) *progress.Bar {\n\tb := progress.NewInt(total)\n\tb.Template(`{{.Bar}} {{.Percent | printf \"%0.0f\"}}% {{.Text}}`)\n\tb.Width = 20\n\tb.StartDelimiter = colors.Gray(\"|\")\n\tb.EndDelimiter = colors.Gray(\"|\")\n\tb.Filled = colors.Purple(\"█\")\n\tb.Empty = colors.Gray(\" \")\n\treturn b\n}\n\n// Pad helper.\nfunc Pad() func() {\n\tprintln()\n\treturn func() {\n\t\tprintln()\n\t}\n}\n\n// Fatal error.\nfunc Fatal(err error) {\n\tfmt.Fprintf(os.Stderr, \"\\n     %s %s\\n\\n\", colors.Red(\"Error:\"), err)\n\tos.Exit(1)\n}\n\n// IsJSON returns true if the string looks like json.\nfunc IsJSON(s string) bool {\n\treturn len(s) > 1 && s[0] == '{' && s[len(s)-1] == '}'\n}\n\n// IsJSONLog returns true if the string looks likes a json log.\nfunc IsJSONLog(s string) bool {\n\treturn IsJSON(s) && strings.Contains(s, `\"level\"`)\n}\n\n// IsNotFound returns true if err is not nil and represents a missing resource.\nfunc IsNotFound(err error) bool {\n\tswitch {\n\tcase err == nil:\n\t\treturn false\n\tcase strings.Contains(err.Error(), \"ResourceNotFoundException\"):\n\t\treturn true\n\tcase strings.Contains(err.Error(), \"NoSuchEntity\"):\n\t\treturn true\n\tcase strings.Contains(err.Error(), \"does not exist\"):\n\t\treturn true\n\tcase strings.Contains(err.Error(), \"not found\"):\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\n// IsBucketExists returns true if err is not nil and represents an existing bucket.\nfunc IsBucketExists(err error) bool {\n\tswitch {\n\tcase err == nil:\n\t\treturn false\n\tcase strings.Contains(err.Error(), \"BucketAlreadyOwnedByYou\"):\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\n// IsThrottled returns true if err is not nil and represents a throttled request.\nfunc IsThrottled(err error) bool {\n\tswitch {\n\tcase err == nil:\n\t\treturn false\n\tcase strings.Contains(err.Error(), \"Throttling: Rate exceeded\"):\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\n// IsNoCredentials returns true if err is not nil and represents missing credentials.\nfunc IsNoCredentials(err error) bool {\n\tswitch {\n\tcase err == nil:\n\t\treturn false\n\tcase strings.Contains(err.Error(), \"NoCredentialProviders\"):\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\n// Env returns a slice from environment variable map.\nfunc Env(m map[string]string) (env []string) {\n\tfor k, v := range m {\n\t\tenv = append(env, fmt.Sprintf(\"%s=%s\", k, v))\n\t}\n\treturn\n}\n\n// PrefixLines prefixes the lines in s with prefix.\nfunc PrefixLines(s string, prefix string) string {\n\tlines := strings.Split(s, \"\\n\")\n\tfor i, l := range lines {\n\t\tlines[i] = prefix + l\n\t}\n\treturn strings.Join(lines, \"\\n\")\n}\n\n// Indent the given string.\nfunc Indent(s string) string {\n\treturn PrefixLines(s, \"  \")\n}\n\n// WaitForListen blocks until `u` is listening with timeout.\nfunc WaitForListen(u *url.URL, timeout time.Duration) error {\n\ttimedout := time.After(timeout)\n\n\tb := backoff.Backoff{\n\t\tMin:    100 * time.Millisecond,\n\t\tMax:    time.Second,\n\t\tFactor: 1.5,\n\t}\n\n\tfor {\n\t\tselect {\n\t\tcase <-timedout:\n\t\t\treturn errors.Errorf(\"timed out after %s\", timeout)\n\t\tcase <-time.After(b.Duration()):\n\t\t\tif IsListening(u) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n}\n\n// IsListening returns true if there's a server listening on `u`.\nfunc IsListening(u *url.URL) bool {\n\tconn, err := net.Dial(\"tcp\", u.Host)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tconn.Close()\n\treturn true\n}\n\n// ExitStatus returns the exit status of cmd.\nfunc ExitStatus(cmd *exec.Cmd, err error) string {\n\tps := cmd.ProcessState\n\n\tif e, ok := err.(*exec.ExitError); ok {\n\t\tps = e.ProcessState\n\t}\n\n\tif ps != nil {\n\t\ts, ok := ps.Sys().(syscall.WaitStatus)\n\t\tif ok {\n\t\t\treturn fmt.Sprintf(\"%d\", s.ExitStatus())\n\t\t}\n\t}\n\n\treturn \"?\"\n}\n\n// StringsContains returns true if list contains s.\nfunc StringsContains(list []string, s string) bool {\n\tfor _, v := range list {\n\t\tif v == s {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// BasePath returns a normalized base path,\n// stripping the leading '/' if present.\nfunc BasePath(s string) string {\n\treturn strings.TrimLeft(s, \"/\")\n}\n\n// LogPad outputs a log message with padding.\nfunc LogPad(msg string, v ...interface{}) {\n\tdefer Pad()()\n\tLog(msg, v...)\n}\n\n// Log outputs a log message.\nfunc Log(msg string, v ...interface{}) {\n\tfmt.Printf(\"     %s\\n\", colors.Purple(fmt.Sprintf(msg, v...)))\n}\n\n// LogClear clears the line and outputs a log message.\nfunc LogClear(msg string, v ...interface{}) {\n\tterm.MoveUp(1)\n\tterm.ClearLine()\n\tfmt.Printf(\"\\r     %s\\n\", colors.Purple(fmt.Sprintf(msg, v...)))\n}\n\n// LogTitle outputs a log title.\nfunc LogTitle(msg string, v ...interface{}) {\n\tfmt.Printf(\"\\n     \\x1b[1m%s\\x1b[m\\n\\n\", fmt.Sprintf(msg, v...))\n}\n\n// LogName outputs a log message with name.\nfunc LogName(name, msg string, v ...interface{}) {\n\tfmt.Printf(\"     %s %s\\n\", colors.Purple(name+\":\"), fmt.Sprintf(msg, v...))\n}\n\n// LogListItem outputs a list item.\nfunc LogListItem(msg string, v ...interface{}) {\n\tfmt.Printf(\"      • %s\\n\", fmt.Sprintf(msg, v...))\n}\n\n// ToFloat returns a float or NaN.\nfunc ToFloat(v interface{}) float64 {\n\tswitch n := v.(type) {\n\tcase int:\n\t\treturn float64(n)\n\tcase int8:\n\t\treturn float64(n)\n\tcase int16:\n\t\treturn float64(n)\n\tcase int32:\n\t\treturn float64(n)\n\tcase int64:\n\t\treturn float64(n)\n\tcase uint:\n\t\treturn float64(n)\n\tcase uint8:\n\t\treturn float64(n)\n\tcase uint16:\n\t\treturn float64(n)\n\tcase uint32:\n\t\treturn float64(n)\n\tcase uint64:\n\t\treturn float64(n)\n\tcase float32:\n\t\treturn float64(n)\n\tcase float64:\n\t\treturn n\n\tdefault:\n\t\treturn math.NaN()\n\t}\n}\n\n// Milliseconds returns the duration as milliseconds.\nfunc Milliseconds(d time.Duration) int {\n\treturn int(d / time.Millisecond)\n}\n\n// MillisecondsSince returns the duration as milliseconds relative to time t.\nfunc MillisecondsSince(t time.Time) int {\n\treturn int(time.Since(t) / time.Millisecond)\n}\n\n// ParseDuration string with day and month approximation support.\nfunc ParseDuration(s string) (d time.Duration, err error) {\n\tr := strings.NewReader(s)\n\n\tswitch {\n\tcase strings.HasSuffix(s, \"d\"):\n\t\tvar v float64\n\t\t_, err = fmt.Fscanf(r, \"%fd\", &v)\n\t\td = time.Duration(v * float64(24*time.Hour))\n\tcase strings.HasSuffix(s, \"w\"):\n\t\tvar v float64\n\t\t_, err = fmt.Fscanf(r, \"%fw\", &v)\n\t\td = time.Duration(v * float64(24*time.Hour*7))\n\tcase strings.HasSuffix(s, \"mo\"):\n\t\tvar v float64\n\t\t_, err = fmt.Fscanf(r, \"%fmo\", &v)\n\t\td = time.Duration(v * float64(30*24*time.Hour))\n\tcase strings.HasSuffix(s, \"M\"):\n\t\tvar v float64\n\t\t_, err = fmt.Fscanf(r, \"%fM\", &v)\n\t\td = time.Duration(v * float64(30*24*time.Hour))\n\tdefault:\n\t\td, err = time.ParseDuration(s)\n\t}\n\n\treturn\n}\n\n// Md5 returns an md5 hash for s.\nfunc Md5(s string) string {\n\th := md5.New()\n\th.Write([]byte(s))\n\treturn hex.EncodeToString(h.Sum(nil))\n}\n\n// Domain returns the effective domain (TLD plus one).\nfunc Domain(s string) string {\n\td, err := publicsuffix.EffectiveTLDPlusOne(s)\n\tif err != nil {\n\t\tpanic(errors.Wrap(err, \"effective domain\"))\n\t}\n\n\treturn d\n}\n\n// CertDomainNames returns the certificate domain name\n// and alternative names for a requested domain.\nfunc CertDomainNames(s string) []string {\n\t// effective domain\n\tif Domain(s) == s {\n\t\treturn []string{s, \"*.\" + s}\n\t}\n\n\t// subdomain\n\treturn []string{RemoveSubdomains(s, 1), \"*.\" + RemoveSubdomains(s, 1)}\n}\n\n// IsWildcardDomain returns true if the domain is a wildcard.\nfunc IsWildcardDomain(s string) bool {\n\treturn strings.HasPrefix(s, \"*.\")\n}\n\n// WildcardMatches returns true if wildcard is a wildcard domain\n// and it satisfies the given domain.\nfunc WildcardMatches(wildcard, domain string) bool {\n\tif !IsWildcardDomain(wildcard) {\n\t\treturn false\n\t}\n\n\tw := RemoveSubdomains(wildcard, 1)\n\td := RemoveSubdomains(domain, 1)\n\treturn w == d\n}\n\n// RemoveSubdomains returns the domain without the n left-most subdomain(s).\nfunc RemoveSubdomains(s string, n int) string {\n\tdomains := strings.Split(s, \".\")\n\treturn strings.Join(domains[n:], \".\")\n}\n\n// ParseSections returns INI style sections from r.\nfunc ParseSections(r io.Reader) (sections []string, err error) {\n\ts := bufio.NewScanner(r)\n\n\tfor s.Scan() {\n\t\tt := s.Text()\n\t\tif strings.HasPrefix(t, \"[\") {\n\t\t\tsections = append(sections, strings.Trim(t, \"[]\"))\n\t\t}\n\t}\n\n\terr = s.Err()\n\n\treturn\n}\n\n// UniqueStrings returns a string slice of unique values.\nfunc UniqueStrings(s []string) (v []string) {\n\tm := make(map[string]struct{})\n\tfor _, val := range s {\n\t\t_, ok := m[val]\n\t\tif !ok {\n\t\t\tv = append(v, val)\n\t\t\tm[val] = struct{}{}\n\t\t}\n\t}\n\treturn\n}\n\n// IsCI returns true if the env looks like it's a CI platform.\nfunc IsCI() bool {\n\treturn os.Getenv(\"CI\") == \"true\"\n}\n\n// EncodeAlias encodes an alias string so that it conforms to the\n// requirement of matching (?!^[0-9]+$)([a-zA-Z0-9-_]+).\nfunc EncodeAlias(s string) string {\n\treturn \"commit-\" + strings.Replace(s, \".\", \"_\", -1)\n}\n\n// DecodeAlias decodes an alias string which was encoded by\n// the EncodeAlias function.\nfunc DecodeAlias(s string) string {\n\ts = strings.Replace(s, \"_\", \".\", -1)\n\ts = strings.Replace(s, \"commit-\", \"\", 1)\n\treturn s\n}\n\n// DateSuffix returns the date suffix for t.\nfunc DateSuffix(t time.Time) string {\n\tswitch t.Day() {\n\tcase 1, 21, 31:\n\t\treturn \"st\"\n\tcase 2, 22:\n\t\treturn \"nd\"\n\tcase 3, 23:\n\t\treturn \"rd\"\n\tdefault:\n\t\treturn \"th\"\n\t}\n}\n\n// StripLerna strips the owner portion of a Lerna-based tag. See #670 for\n// details. They are in the form of \"@owner/repo@0.5.0\".\nfunc StripLerna(s string) string {\n\tif strings.HasPrefix(s, \"@\") {\n\t\tp := strings.Split(s, \"@\")\n\t\treturn p[len(p)-1]\n\t}\n\n\treturn s\n}\n\n// FixMultipleSetCookie staggers the casing of each set-cookie\n// value to trick API Gateway into setting multiple in the response.\nfunc FixMultipleSetCookie(h http.Header) {\n\tcookies := h[\"Set-Cookie\"]\n\n\tif len(cookies) == 0 {\n\t\treturn\n\t}\n\n\th.Del(\"Set-Cookie\")\n\n\tfor i, v := range cookies {\n\t\th[BinaryCase(\"set-cookie\", i)] = []string{v}\n\t}\n}\n\n// BinaryCase ported from https://github.com/Gi60s/binary-case/blob/master/index.js#L86.\nfunc BinaryCase(s string, n int) string {\n\tvar res []rune\n\n\tfor _, c := range s {\n\t\tif c >= 65 && c <= 90 {\n\t\t\tif n&1 > 0 {\n\t\t\t\tc += 32\n\t\t\t}\n\t\t\tres = append(res, c)\n\t\t\tn >>= 1\n\t\t} else if c >= 97 && c <= 122 {\n\t\t\tif n&1 > 0 {\n\t\t\t\tc -= 32\n\t\t\t}\n\t\t\tres = append(res, c)\n\t\t\tn >>= 1\n\t\t} else {\n\t\t\tres = append(res, c)\n\t\t}\n\t}\n\n\treturn string(res)\n}\n"
  },
  {
    "path": "internal/util/util_test.go",
    "content": "package util\n\nimport (\n\t\"net/http\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/tj/assert\"\n)\n\nfunc TestExitStatus(t *testing.T) {\n\tt.Run(\"success\", func(t *testing.T) {\n\t\tcmd := exec.Command(\"echo\", \"hello\", \"world\")\n\t\tcode := ExitStatus(cmd, cmd.Run())\n\t\tassert.Equal(t, \"0\", code)\n\t})\n\n\tt.Run(\"missing\", func(t *testing.T) {\n\t\tcmd := exec.Command(\"nope\")\n\t\tcode := ExitStatus(cmd, cmd.Run())\n\t\tassert.Equal(t, \"?\", code)\n\t})\n\n\tt.Run(\"failure\", func(t *testing.T) {\n\t\tcmd := exec.Command(\"sh\", \"-c\", `echo hello && exit 5`)\n\t\tcode := ExitStatus(cmd, cmd.Run())\n\t\tassert.Equal(t, \"5\", code)\n\t})\n}\n\nfunc TestParseDuration(t *testing.T) {\n\tt.Run(\"day\", func(t *testing.T) {\n\t\tv, err := ParseDuration(\"1d\")\n\t\tassert.NoError(t, err, \"parsing\")\n\t\tassert.Equal(t, time.Hour*24, v)\n\t})\n\n\tt.Run(\"day with faction\", func(t *testing.T) {\n\t\tv, err := ParseDuration(\"1.5d\")\n\t\tassert.NoError(t, err, \"parsing\")\n\t\tassert.Equal(t, time.Duration(float64(time.Hour*24)*1.5), v)\n\t})\n\n\tt.Run(\"week\", func(t *testing.T) {\n\t\tv, err := ParseDuration(\"1w\")\n\t\tassert.NoError(t, err, \"parsing\")\n\t\tassert.Equal(t, time.Hour*24*7, v)\n\n\t\tv, err = ParseDuration(\"2w\")\n\t\tassert.NoError(t, err, \"parsing\")\n\t\tassert.Equal(t, time.Hour*24*7*2, v)\n\t})\n\n\tt.Run(\"month\", func(t *testing.T) {\n\t\tv, err := ParseDuration(\"1mo\")\n\t\tassert.NoError(t, err, \"parsing\")\n\t\tassert.Equal(t, time.Hour*24*30, v)\n\n\t\tv, err = ParseDuration(\"1M\")\n\t\tassert.NoError(t, err, \"parsing\")\n\t\tassert.Equal(t, time.Hour*24*30, v)\n\t})\n\n\tt.Run(\"month with faction\", func(t *testing.T) {\n\t\tv, err := ParseDuration(\"1.5mo\")\n\t\tassert.NoError(t, err, \"parsing\")\n\t\tassert.Equal(t, time.Duration(float64(time.Hour*24*30)*1.5), v)\n\t})\n\n\tt.Run(\"default\", func(t *testing.T) {\n\t\tv, err := ParseDuration(\"15m\")\n\t\tassert.NoError(t, err, \"parsing\")\n\t\tassert.Equal(t, 15*time.Minute, v)\n\t})\n}\n\nfunc TestDomain(t *testing.T) {\n\tassert.Equal(t, \"example.com\", Domain(\"example.com\"))\n\tassert.Equal(t, \"example.com\", Domain(\"api.example.com\"))\n\tassert.Equal(t, \"example.com\", Domain(\"v1.api.example.com\"))\n\n\tassert.Equal(t, \"example.co.uk\", Domain(\"example.co.uk\"))\n\tassert.Equal(t, \"example.co.uk\", Domain(\"api.example.co.uk\"))\n\tassert.Equal(t, \"example.co.uk\", Domain(\"v1.api.example.co.uk\"))\n}\n\nfunc TestCertDomainNames(t *testing.T) {\n\tassert.Equal(t, []string{\"example.com\", \"*.example.com\"}, CertDomainNames(\"example.com\"))\n\tassert.Equal(t, []string{\"example.com\", \"*.example.com\"}, CertDomainNames(\"api.example.com\"))\n\tassert.Equal(t, []string{\"api.example.com\", \"*.api.example.com\"}, CertDomainNames(\"v1.api.example.com\"))\n}\n\nfunc TestWildcardMatches(t *testing.T) {\n\tassert.True(t, WildcardMatches(\"*.api.example.com\", \"v1.api.example.com\"))\n\tassert.True(t, WildcardMatches(\"*.example.com\", \"api.example.com\"))\n\tassert.False(t, WildcardMatches(\"example.com\", \"api.example.com\"))\n\tassert.False(t, WildcardMatches(\"*.api.example.com\", \"api.example.com\"))\n}\n\nfunc TestParseSections(t *testing.T) {\n\tr := strings.NewReader(`[personal]\naws_access_key_id = personal_key\naws_secret_access_key = personal_secret\n[app]\naws_access_key_id = app_key\naws_secret_access_key = app_secret\n[foo_bar]\naws_access_key_id = foo_bar_key\naws_secret_access_key = foo_bar_secret\n`)\n\n\tv, err := ParseSections(r)\n\tassert.NoError(t, err)\n\n\tassert.Equal(t, []string{\"personal\", \"app\", \"foo_bar\"}, v)\n}\n\nfunc TestEncodeAlias(t *testing.T) {\n\tassert.Equal(t, `commit-v1_2_3-beta`, EncodeAlias(`v1.2.3-beta`))\n}\n\nfunc TestDecodeAlias(t *testing.T) {\n\tassert.Equal(t, `v1.2.3-beta`, DecodeAlias(EncodeAlias(`v1.2.3-beta`)))\n}\n\nfunc TestFixMultipleSetCookie(t *testing.T) {\n\th := http.Header{}\n\th.Add(\"Set-Cookie\", \"first=tj\")\n\th.Add(\"Set-Cookie\", \"last=holowaychuk\")\n\th.Add(\"set-cookie\", \"pet=tobi\")\n\tFixMultipleSetCookie(h)\n\tassert.Len(t, h, 3)\n\tassert.Equal(t, []string{\"last=holowaychuk\"}, h[\"Set-cookie\"])\n\tassert.Equal(t, []string{\"pet=tobi\"}, h[\"sEt-cookie\"])\n\tassert.Equal(t, []string{\"first=tj\"}, h[\"set-cookie\"])\n}\n\nfunc TestBinaryCase(t *testing.T) {\n\tvar variations []string\n\n\t// create variations\n\tfor i := 0; i < 50; i++ {\n\t\tvariations = append(variations, BinaryCase(\"set-cookie\", i))\n\t}\n\n\t// ensure none are malformed\n\tfor _, v := range variations {\n\t\tassert.Equal(t, \"set-cookie\", strings.ToLower(v))\n\t}\n\n\t// ensure none are duplicates\n\tfor i, a := range variations {\n\t\tfor j, b := range variations {\n\t\t\tif i != j {\n\t\t\t\tassert.NotEqual(t, a, b)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/validate/validate.go",
    "content": "// Package validate provides config validation functions.\npackage validate\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n)\n\n// RequiredString validation.\nfunc RequiredString(s string) error {\n\tif strings.TrimSpace(s) == \"\" {\n\t\treturn errors.New(\"is required\")\n\t}\n\n\treturn nil\n}\n\n// RequiredStrings validation.\nfunc RequiredStrings(s []string) error {\n\tfor i, v := range s {\n\t\tif err := RequiredString(v); err != nil {\n\t\t\treturn errors.Wrapf(err, \"at index %d\", i)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// MinStrings validation.\nfunc MinStrings(s []string, n int) error {\n\tif len(s) < n {\n\t\tif n == 1 {\n\t\t\treturn errors.Errorf(\"must have at least %d value\", n)\n\t\t}\n\n\t\treturn errors.Errorf(\"must have at least %d values\", n)\n\t}\n\n\treturn nil\n}\n\n// name regexp.\nvar name = regexp.MustCompile(`^[a-z][-a-z0-9]*$`)\n\n// Name validation.\nfunc Name(s string) error {\n\tif !name.MatchString(s) {\n\t\treturn errors.Errorf(\"must contain only lowercase alphanumeric characters and '-'\")\n\t}\n\n\treturn nil\n}\n\n// stage regexp.\nvar stage = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)\n\n// Stage name validation.\nfunc Stage(s string) error {\n\tif !stage.MatchString(s) {\n\t\treturn errors.Errorf(\"must contain only alphanumeric characters and '_'\")\n\t}\n\n\treturn nil\n}\n\n// List validation.\nfunc List(s string, list []string) error {\n\tfor _, v := range list {\n\t\tif s == v {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\treturn errors.Errorf(\"%q is invalid, must be one of:\\n\\n  • %s\", s, strings.Join(list, \"\\n  • \"))\n}\n\n// Lists validation.\nfunc Lists(vals, list []string) error {\n\tfor _, v := range vals {\n\t\tif err := List(v, list); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/zip/testdata/.file",
    "content": "👻 \n"
  },
  {
    "path": "internal/zip/testdata/.upignore",
    "content": "*.md\n"
  },
  {
    "path": "internal/zip/testdata/Readme.md",
    "content": "Hello World\n"
  },
  {
    "path": "internal/zip/testdata/bar.js",
    "content": "bar\n"
  },
  {
    "path": "internal/zip/testdata/foo.js",
    "content": "foo\n"
  },
  {
    "path": "internal/zip/testdata/index.js",
    "content": "module.exports = 'hello world'\n"
  },
  {
    "path": "internal/zip/zip.go",
    "content": "package zip\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\tarchive \"github.com/tj/go-archive\"\n)\n\nvar transform = archive.TransformFunc(func(r io.Reader, i os.FileInfo) (io.Reader, os.FileInfo) {\n\tname := strings.Replace(i.Name(), \"\\\\\", \"/\", -1)\n\n\ti = archive.Info{\n\t\tName:     name,\n\t\tSize:     i.Size(),\n\t\tMode:     i.Mode() | 0555,\n\t\tModified: i.ModTime(),\n\t\tDir:      i.IsDir(),\n\t}.FileInfo()\n\n\treturn r, i\n})\n\n// Build the given `dir`.\nfunc Build(dir string) (io.ReadCloser, *archive.Stats, error) {\n\tupignore, err := read(\".upignore\")\n\tif err != nil {\n\t\treturn nil, nil, errors.Wrap(err, \"reading .upignore\")\n\t}\n\tdefer upignore.Close()\n\n\tr := io.MultiReader(\n\t\tstrings.NewReader(\".*\\n\"),\n\t\tstrings.NewReader(\"\\n!vendor\\n!node_modules/**\\n!.pypath/**\\n\"),\n\t\tupignore,\n\t\tstrings.NewReader(\"\\n!main\\n!server\\n!_proxy.js\\n!up.json\\n!pom.xml\\n!build.gradle\\n!project.clj\\ngin-bin\\nup\\n\"))\n\n\tfilter, err := archive.FilterPatterns(r)\n\tif err != nil {\n\t\treturn nil, nil, errors.Wrap(err, \"parsing ignore patterns\")\n\t}\n\n\tbuf := new(bytes.Buffer)\n\tzip := archive.NewZip(buf).\n\t\tWithFilter(filter).\n\t\tWithTransform(transform)\n\n\tif err := zip.Open(); err != nil {\n\t\treturn nil, nil, errors.Wrap(err, \"opening\")\n\t}\n\n\tif err := zip.AddDir(dir); err != nil {\n\t\treturn nil, nil, errors.Wrap(err, \"adding dir\")\n\t}\n\n\tif err := zip.Close(); err != nil {\n\t\treturn nil, nil, errors.Wrap(err, \"closing\")\n\t}\n\n\treturn ioutil.NopCloser(buf), zip.Stats(), nil\n}\n\n// read file.\nfunc read(path string) (io.ReadCloser, error) {\n\tf, err := os.Open(path)\n\n\tif os.IsNotExist(err) {\n\t\treturn ioutil.NopCloser(bytes.NewReader(nil)), nil\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn f, nil\n}\n"
  },
  {
    "path": "internal/zip/zip_test.go",
    "content": "package zip\n\nimport (\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/tj/assert\"\n)\n\n// TODO: better tests\n\nfunc TestBuild(t *testing.T) {\n\tos.Chdir(\"testdata\")\n\tdefer os.Chdir(\"..\")\n\n\tzip, _, err := Build(\".\")\n\tassert.NoError(t, err)\n\n\tout, err := ioutil.TempDir(os.TempDir(), \"-up\")\n\tassert.NoError(t, err, \"tmpdir\")\n\tdst := filepath.Join(out, \"out.zip\")\n\n\tf, err := os.Create(dst)\n\tassert.NoError(t, err, \"create\")\n\n\t_, err = io.Copy(f, zip)\n\tassert.NoError(t, err, \"copy\")\n\n\tassert.NoError(t, f.Close(), \"close\")\n\n\tcmd := exec.Command(\"unzip\", \"out.zip\")\n\tcmd.Dir = out\n\tassert.NoError(t, cmd.Run(), \"unzip\")\n\n\tfiles, err := ioutil.ReadDir(out)\n\tassert.NoError(t, err, \"readdir\")\n\n\tvar names []string\n\tfor _, f := range files {\n\t\tnames = append(names, f.Name())\n\t}\n\tsort.Strings(names)\n\n\tassert.Equal(t, []string{\"bar.js\", \"foo.js\", \"index.js\", \"out.zip\"}, names)\n}\n"
  },
  {
    "path": "platform/aws/cost/cost.go",
    "content": "// Package cost provides utilities for calculating AWS Lambda pricing.\npackage cost\n\n// pricePerInvoke is the cost per function invocation.\nvar pricePerInvoke = 0.0000002\n\n// pricePerRequestUnit is the cost per api gateway request unit.\nvar pricePerRequestUnit = 5\n\n// requestUnit is 5 million requests.\nvar requestUnit = 5e6\n\n// memoryConfigurations available.\nvar memoryConfigurations = map[int]float64{\n\t128:  0.000000208,\n\t192:  0.000000313,\n\t256:  0.000000417,\n\t320:  0.000000521,\n\t384:  0.000000625,\n\t448:  0.000000729,\n\t512:  0.000000834,\n\t576:  0.000000938,\n\t640:  0.000001042,\n\t704:  0.000001146,\n\t768:  0.00000125,\n\t832:  0.000001354,\n\t896:  0.000001459,\n\t960:  0.000001563,\n\t1024: 0.000001667,\n\t1088: 0.000001771,\n\t1152: 0.000001875,\n\t1216: 0.00000198,\n\t1280: 0.000002084,\n\t1344: 0.000002188,\n\t1408: 0.000002292,\n\t1472: 0.000002396,\n\t1536: 0.000002501,\n\t1600: 0.000002605,\n\t1664: 0.000002709,\n\t1728: 0.000002813,\n\t1792: 0.000002917,\n\t1856: 0.000003021,\n\t1920: 0.000003126,\n\t1984: 0.000003230,\n\t2048: 0.000003334,\n\t2112: 0.000003438,\n\t2176: 0.000003542,\n\t2240: 0.000003647,\n\t2304: 0.000003751,\n\t2368: 0.000003855,\n\t2432: 0.000003959,\n\t2496: 0.000004063,\n\t2560: 0.000004168,\n\t2624: 0.000004272,\n\t2688: 0.000004376,\n\t2752: 0.000004480,\n\t2816: 0.000004584,\n\t2880: 0.000004688,\n\t2944: 0.000004793,\n\t3008: 0.000004897,\n}\n\n// Requests returns the cost for the given number of http requests.\nfunc Requests(n int) float64 {\n\treturn (float64(n) / float64(requestUnit)) * float64(pricePerRequestUnit)\n}\n\n// Rate returns the cost per 100ms for the given `memory` configuration in megabytes.\nfunc Rate(memory int) float64 {\n\treturn memoryConfigurations[memory]\n}\n\n// Invocations returns the cost of `n` requests.\nfunc Invocations(n int) float64 {\n\treturn pricePerInvoke * float64(n)\n}\n\n// Duration returns the cost of `ms` for the given `memory` configuration in megabytes.\nfunc Duration(ms, memory int) float64 {\n\treturn Rate(memory) * (float64(ms) / 100)\n}\n"
  },
  {
    "path": "platform/aws/cost/cost_test.go",
    "content": "package cost\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tj/assert\"\n)\n\nfunc TestRequests(t *testing.T) {\n\ttable := []struct {\n\t\trequests int\n\t\texpected float64\n\t}{\n\t\t{0, 0.0},\n\t\t{1000, 0.001},\n\t\t{1000000, 1.0},\n\t}\n\n\tfor _, row := range table {\n\t\tassert.Equal(t, row.expected, Requests(row.requests))\n\t}\n}\n\nfunc TestRate(t *testing.T) {\n\ttable := []struct {\n\t\tmemory   int\n\t\texpected float64\n\t}{\n\t\t{-1, 0.0},\n\t\t{0, 0.0},\n\t\t{128, 2.08e-7},\n\t\t{156, 0.0},\n\t}\n\n\tfor _, row := range table {\n\t\tassert.Equal(t, row.expected, Rate(row.memory))\n\t}\n}\n\nfunc TestInvocations(t *testing.T) {\n\ttable := []struct {\n\t\tinvocations int\n\t\texpected    float64\n\t}{\n\t\t{0, 0.0},\n\t\t{1, 2.0e-7},\n\t\t{1.0e7, 2.0},\n\t}\n\n\tfor _, row := range table {\n\t\tassert.Equal(t, row.expected, Invocations(row.invocations))\n\t}\n}\n\nfunc TestDuration(t *testing.T) {\n\ttable := []struct {\n\t\tduration int\n\t\tmemory   int\n\t\texpected float64\n\t}{\n\t\t{0, 128, 0},\n\t\t{100000, 256, 4.17e-4},\n\t\t{1e8, 1536, 2.501},\n\t}\n\n\tfor _, row := range table {\n\t\tassert.Equal(t, row.expected, Duration(row.duration, row.memory))\n\t}\n}\n"
  },
  {
    "path": "platform/aws/cost/domains.go",
    "content": "package cost\n\nimport (\n\t\"encoding/csv\"\n\t\"strings\"\n\n\t\"github.com/apex/log\"\n)\n\n// tlds is a map of tlds to price.\nvar tlds = map[string]string{}\n\n// Domain returns the price of domain's tld.\nfunc Domain(domain string) string {\n\treturn TLD(strings.SplitN(domain, \".\", 2)[1])\n}\n\n// TLD returns the price of a tld.\nfunc TLD(tld string) string {\n\treturn tlds[tld]\n}\n\n// priceList is a raw CSV price list.\nvar priceList = `\nac,$48.00\nacademy,$32.00\naccountants,$94.00\nadult,$88.00\nagency,$19.00\napartments,$47.00\nassociates,$29.00\nauction,$29.00\naudio,$13.00\nband,$22.00\nbargains,$30.00\nbe,$9.00\nberlin,$52.00\nbike,$32.00\nbingo,$47.00\nbiz,$12.00\nblack,$43.00\nblue,$15.00\nboutique,$30.00\nbuilders,$32.00\nbusiness,$18.00\nbuzz,$37.00\nca,$13.00\ncab,$32.00\ncafe,$31.00\ncamera,$46.00\ncamp,$46.00\ncapital,$47.00\ncards,$29.00\ncare,$29.00\ncareers,$35.00\ncash,$29.00\ncasino,$141.00\ncatering,$29.00\ncc,$12.00\ncenter,$21.00\nceo,$74.00\nch,$12.00\nchat,$29.00\ncheap,$30.00\nchurch,$29.00\ncity,$19.00\ncl,$93.00*\ncloud,$23.00\nclaims,$47.00\ncleaning,$46.00\nclick,$7.00\nclinic,$47.00\nclothing,$32.00\nclub,$12.00\nco,$25.00\nco.nz,$24.00\nco.uk,$9.00\nco.za,$13.00\ncoach,$47.00\ncodes,$35.00\ncoffee,$32.00\ncollege,$69.00\ncom,$12.00\ncom.ar,$76.00*\ncom.au,$15.00\ncom.br,$58.00*\ncom.mx,$34.00\ncom.sg,$47.00*\ncommunity,$29.00\ncompany,$18.00\ncomputer,$32.00\ncondos,$49.00\nconstruction,$32.00\nconsulting,$29.00\ncontractors,$32.00\ncool,$30.00\ncoupons,$51.00\ncredit,$94.00\ncreditcard,$141.00\ncruises,$49.00\ndance,$22.00\ndating,$49.00\nde,$9.00\ndeals,$29.00\ndelivery,$47.00\ndemocrat,$30.00\ndental,$47.00\ndiamonds,$35.00\ndiet,$19.00\ndigital,$29.00\ndirect,$29.00\ndirectory,$21.00\ndiscount,$29.00\ndog,$46.00\ndomains,$32.00\neducation,$21.00\nemail,$25.00\nenergy,$94.00\nengineering,$47.00\nenterprises,$32.00\nequipment,$21.00\nes,$10.00\nestate,$32.00\neu,$13.00\nevents,$30.00\nexchange,$29.00\nexpert,$49.00\nexposed,$19.00\nexpress,$31.00\nfail,$29.00\nfarm,$32.00\nfi,$24.00\nfinance,$47.00\nfinancial,$47.00\nfish,$29.00\nfitness,$29.00\nflights,$49.00\nflorist,$32.00\nflowers,$25.00\nfm,$92.00\nfootball,$19.00\nforsale,$29.00\nfoundation,$30.00\nfr,$12.00\nfund,$47.00\nfurniture,$47.00\nfutbol,$12.00\nfyi,$20.00\ngallery,$21.00\ngg,$75.00\ngift,$20.00\ngifts,$19.00\nglass,$46.00\nglobal,$71.00\ngold,$101.00\ngolf,$51.00\ngraphics,$21.00\ngratis,$19.00\ngreen,$71.00\ngripe,$29.00\nguide,$29.00\nguitars,$30.00\nguru,$25.00\nhaus,$29.00\nhealthcare,$47.00\nhelp,$19.00\nhiv,$254.00\nhockey,$51.00\nholdings,$35.00\nholiday,$35.00\nhost,$93.00\nhosting,$29.00\nhouse,$32.00\nim,$19.00\nimmo,$29.00\nimmobilien,$30.00\nin,$15.00\nindustries,$29.00\ninfo,$12.00\nink,$29.00\ninstitute,$21.00\ninsure,$47.00\ninternational,$21.00\ninvestments,$94.00\nio,$39.00\nirish,$36.00\nit,$15.00\njewelry,$51.00\njp,$90.00\njuegos,$13.00\nkaufen,$30.00\nkim,$15.00\nkitchen,$46.00\nkiwi,$32.00\nland,$32.00\nlease,$47.00\nlegal,$47.00\nlgbt,$43.00\nlife,$29.00\nlighting,$21.00\nlimited,$29.00\nlimo,$35.00\nlink,$10.00\nlive,$23.00\nloan,$31.00\nloans,$94.00\nlol,$31.00\nmaison,$49.00\nmanagement,$21.00\nmarketing,$32.00\nmba,$31.00\nme,$17.00\nme.uk,$8.00\nmedia,$29.00\nmemorial,$47.00\nmobi,$12.00\nmoda,$22.00\nmoney,$29.00\nmortgage,$43.00\nmovie,$306.00\nmx,$34.00\nname,$9.00\nnet,$11.00\nnet.au,$15.00\nnet.nz,$24.00\nnetwork,$19.00\nnews,$23.00\nninja,$18.00\nnl,$10.00\nonl,$15.00\nonline,$39.00\norg,$12.00\norg.nz,$24.00\norg.uk,$9.00\npartners,$49.00\nparts,$29.00\nphoto,$30.00\nphotography,$21.00\nphotos,$21.00\npics,$20.00\npictures,$10.00\npink,$15.00\npizza,$47.00\nplace,$29.00\nplumbing,$46.00\nplus,$31.00\npoker,$43.00\nporn,$88.00\npro,$14.00\nproductions,$30.00\nproperties,$30.00\nproperty,$29.00\npub,$22.00\nqa,$64.00*\nqpon,$15.00\nrecipes,$35.00\nred,$15.00\nreise,$101.00\nreisen,$19.00\nrentals,$30.00\nrepair,$32.00\nreport,$19.00\nrepublican,$29.00\nrestaurant,$47.00\nreviews,$22.00\nrip,$17.00\nrocks,$12.00\nru,$36.00\nruhr,$30.00\nrun,$20.00\nsale,$29.00\nsarl,$29.00\nschool,$29.00\nschule,$19.00\nse,$23.00\nservices,$29.00\nsex,$95.00\nsexy,$22.00\nsg,$47.00*\nsh,$48.00\nshiksha,$16.00\nshoes,$46.00\nshow,$31.00\nsingles,$30.00\nsoccer,$20.00\nsocial,$32.00\nsolar,$46.00\nsolutions,$25.00\nstudio,$23.00\nstyle,$29.00\nsucks,$282.00\nsupplies,$19.00\nsupply,$19.00\nsupport,$21.00\nsurgery,$47.00\nsystems,$21.00\ntattoo,$30.00\ntax,$47.00\ntaxi,$51.00\nteam,$31.00\ntechnology,$21.00\ntennis,$47.00\ntheater,$51.00\ntienda,$50.00\ntips,$21.00\ntires,$94.00\ntoday,$21.00\ntools,$29.00\ntours,$51.00\ntown,$29.00\ntoys,$46.00\ntrade,$29.00\ntraining,$27.00\ntv,$32.00\nuk,$9.00\nuniversity,$47.00\nuno,$30.00\nus,$15.00\nvacations,$35.00\nvc,$33.00\nvegas,$57.00\nventures,$35.00\nvg,$35.00\nviajes,$49.00\nvideo,$22.00\nvillas,$35.00\nvision,$29.00\nvoyage,$50.00\nwatch,$37.00\nwebsite,$23.00\nwien,$29.00\nwiki,$30.00\nworks,$30.00\nworld,$29.00\nwtf,$29.00\nxyz,$12.00\nzone,$32.00\n`\n\nfunc init() {\n\tr := csv.NewReader(strings.NewReader(priceList))\n\n\trows, err := r.ReadAll()\n\tif err != nil {\n\t\tlog.WithError(err).Debug(\"reading price list\")\n\t\treturn\n\t}\n\n\tfor _, row := range rows {\n\t\tname := row[0]\n\t\tprice := row[1]\n\t\ttlds[name] = price\n\t}\n}\n"
  },
  {
    "path": "platform/aws/domains/domains.go",
    "content": "// Package domains provides domain management for AWS platforms.\npackage domains\n\nimport (\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/aws/session\"\n\tr \"github.com/aws/aws-sdk-go/service/route53domains\"\n\n\t\"github.com/apex/up\"\n)\n\n// Domains implementation.\ntype Domains struct {\n\tclient *r.Route53Domains\n}\n\n// New returns a new domain manager.\nfunc New() *Domains {\n\treturn &Domains{\n\t\tclient: r.New(session.New(aws.NewConfig().WithRegion(\"us-east-1\"))),\n\t}\n}\n\n// List implementation.\nfunc (d *Domains) List() (v []*up.Domain, err error) {\n\tres, err := d.client.ListDomains(&r.ListDomainsInput{\n\t\tMaxItems: aws.Int64(100),\n\t})\n\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, d := range res.Domains {\n\t\tv = append(v, &up.Domain{\n\t\t\tName:      *d.DomainName,\n\t\t\tExpiry:    *d.Expiry,\n\t\t\tAutoRenew: *d.AutoRenew,\n\t\t})\n\t}\n\n\treturn\n}\n\n// Availability implementation.\nfunc (d *Domains) Availability(domain string) (*up.Domain, error) {\n\tres, err := d.client.CheckDomainAvailability(&r.CheckDomainAvailabilityInput{\n\t\tDomainName: &domain,\n\t})\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif *res.Availability == \"AVAILABLE\" {\n\t\treturn &up.Domain{\n\t\t\tName:      domain,\n\t\t\tAvailable: true,\n\t\t}, nil\n\t}\n\n\treturn &up.Domain{\n\t\tName:      domain,\n\t\tAvailable: false,\n\t}, nil\n}\n\n// Suggestions implementation.\nfunc (d *Domains) Suggestions(domain string) (domains []*up.Domain, err error) {\n\tres, err := d.client.GetDomainSuggestions(&r.GetDomainSuggestionsInput{\n\t\tDomainName:      &domain,\n\t\tOnlyAvailable:   aws.Bool(true),\n\t\tSuggestionCount: aws.Int64(15),\n\t})\n\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, s := range res.SuggestionsList {\n\t\tdomains = append(domains, &up.Domain{\n\t\t\tName:      *s.DomainName,\n\t\t\tAvailable: true,\n\t\t})\n\t}\n\n\treturn\n}\n\n// Purchase implementation.\nfunc (d *Domains) Purchase(domain string, contact up.DomainContact) error {\n\t_, err := d.client.RegisterDomain(&r.RegisterDomainInput{\n\t\tDomainName:        &domain,\n\t\tAutoRenew:         aws.Bool(true),\n\t\tDurationInYears:   aws.Int64(1),\n\t\tRegistrantContact: contactDetails(contact),\n\t\tAdminContact:      contactDetails(contact),\n\t\tTechContact:       contactDetails(contact),\n\t})\n\n\treturn err\n}\n\n// contactDetails returns route53 contact details.\nfunc contactDetails(c up.DomainContact) *r.ContactDetail {\n\treturn &r.ContactDetail{\n\t\tAddressLine1: aws.String(c.Address),\n\t\tCity:         aws.String(c.City),\n\t\tState:        aws.String(c.State),\n\t\tZipCode:      aws.String(c.ZipCode),\n\t\tCountryCode:  aws.String(c.CountryCode),\n\t\tEmail:        aws.String(c.Email),\n\t\tPhoneNumber:  aws.String(c.PhoneNumber),\n\t\tFirstName:    aws.String(c.FirstName),\n\t\tLastName:     aws.String(c.LastName),\n\t\tContactType:  aws.String(\"PERSON\"),\n\t}\n}\n"
  },
  {
    "path": "platform/aws/logs/logs.go",
    "content": "// Package logs provides log management for AWS platforms.\npackage logs\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/apex/log\"\n\tjsonlog \"github.com/apex/log/handlers/json\"\n\t\"github.com/apex/up\"\n\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/aws/session\"\n\t\"github.com/aws/aws-sdk-go/service/cloudwatchlogs\"\n\t\"github.com/tj/aws/logs\"\n\n\t\"github.com/apex/up/internal/logs/parser\"\n\t\"github.com/apex/up/internal/logs/text\"\n\t\"github.com/apex/up/internal/util\"\n)\n\n// Logs implementation.\ntype Logs struct {\n\tup.LogsConfig\n\tgroup string\n\tquery string\n\tw     io.WriteCloser\n\tio.Reader\n}\n\n// New log tailer.\nfunc New(group string, c up.LogsConfig) up.Logs {\n\tr, w := io.Pipe()\n\n\tquery, err := parseQuery(c.Query)\n\tif err != nil {\n\t\tw.CloseWithError(err)\n\t}\n\tlog.Debugf(\"query %q\", query)\n\n\tl := &Logs{\n\t\tLogsConfig: c,\n\t\tquery:      query,\n\t\tgroup:      group,\n\t\tReader:     r,\n\t\tw:          w,\n\t}\n\n\tgo l.start()\n\n\treturn l\n}\n\n// start fetching logs.\nfunc (l *Logs) start() {\n\ttailer := logs.New(logs.Config{\n\t\tService:       cloudwatchlogs.New(session.New(aws.NewConfig().WithRegion(l.Region))),\n\t\tStartTime:     l.Since,\n\t\tPollInterval:  2 * time.Second,\n\t\tFollow:        l.Follow,\n\t\tFilterPattern: l.query,\n\t\tGroupNames:    []string{l.group},\n\t})\n\n\tvar handler log.Handler\n\n\tif l.OutputJSON {\n\t\thandler = jsonlog.New(os.Stdout)\n\t} else {\n\t\thandler = text.New(os.Stdout).WithExpandedFields(l.Expand)\n\t}\n\n\t// TODO: transform to reader of nl-delimited json, move to apex/log?\n\t// TODO: marshal/unmarshal as JSON so that numeric values are always float64... remove util.ToFloat()\n\tfor l := range tailer.Start() {\n\t\tline := strings.TrimSpace(l.Message)\n\n\t\t// json log\n\t\tif util.IsJSONLog(line) {\n\t\t\tvar e log.Entry\n\t\t\terr := json.Unmarshal([]byte(line), &e)\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatalf(\"error parsing json: %s\", err)\n\t\t\t}\n\n\t\t\thandler.HandleLog(&e)\n\t\t\tcontinue\n\t\t}\n\n\t\t// skip START / END logs since they are redundant\n\t\tif skippable(l.Message) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// lambda textual logs\n\t\thandler.HandleLog(&log.Entry{\n\t\t\tTimestamp: l.Timestamp,\n\t\t\tLevel:     log.InfoLevel,\n\t\t\tMessage:   strings.TrimRight(l.Message, \" \\n\"),\n\t\t})\n\t}\n\n\t// TODO: refactor interface to delegate\n\tif err := tailer.Err(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tl.w.Close()\n}\n\n// parseQuery parses and converts the query to a CW friendly syntax.\nfunc parseQuery(s string) (string, error) {\n\tif s == \"\" {\n\t\treturn s, nil\n\t}\n\n\tn, err := parser.Parse(s)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn n.String(), nil\n}\n\n// skippable returns true if the message is skippable.\nfunc skippable(s string) bool {\n\treturn strings.Contains(s, \"END RequestId\") ||\n\t\tstrings.Contains(s, \"START RequestId\")\n}\n"
  },
  {
    "path": "platform/aws/regions/regions.go",
    "content": "// Package regions provides AWS region utilities.\npackage regions\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n)\n\n// hostedZoneIDs is a set of hosted zone ids for API Gateway.\nvar hostedZoneIDs = map[string]string{\n\t\"us-east-2\":      \"ZOJJZC49E0EPZ\",\n\t\"us-east-1\":      \"Z1UJRXOUMOOFQ8\",\n\t\"us-west-1\":      \"Z2MUQ32089INYE\",\n\t\"us-west-2\":      \"Z2OJLYMUO9EFXC\",\n\t\"ap-east-1\":      \"Z3FD1VL90ND7K5\",\n\t\"ap-south-1\":     \"Z3VO1THU9YC4UR\",\n\t\"ap-northeast-3\": \"Z2YQB5RD63NC85\",\n\t\"ap-northeast-2\": \"Z20JF4UZKIW1U8\",\n\t\"ap-southeast-1\": \"ZL327KTPIQFUL\",\n\t\"ap-southeast-2\": \"Z2RPCDW04V8134\",\n\t\"ap-northeast-1\": \"Z1YSHQZHG15GKL\",\n\t\"ca-central-1\":   \"Z19DQILCV0OWEC\",\n\t\"eu-central-1\":   \"Z1U9ULNL0V5AJ3\",\n\t\"eu-west-1\":      \"ZLY8HYME6SFDD\",\n\t\"eu-west-2\":      \"ZJ5UAJN8Y3Z2Q\",\n\t\"eu-west-3\":      \"Z3KY65QIEKYHQQ\",\n\t\"eu-north-1\":     \"Z2YB950C88HT6D\",\n\t\"sa-east-1\":      \"ZCMLWB8V5SYIT\",\n}\n\n// IDs of regions.\nvar IDs = []string{\n\t\"us-east-2\",\n\t\"us-east-1\",\n\t\"us-west-1\",\n\t\"us-west-2\",\n\t\"ap-east-1\",\n\t\"ap-south-1\",\n\t\"ap-northeast-2\",\n\t\"ap-southeast-1\",\n\t\"ap-southeast-2\",\n\t\"ap-northeast-1\",\n\t\"ca-central-1\",\n\t\"eu-central-1\",\n\t\"eu-west-1\",\n\t\"eu-west-2\",\n\t\"eu-west-3\",\n\t\"eu-north-1\",\n\t\"sa-east-1\",\n}\n\n// Names of regions.\nvar Names = []string{\n\t\"US East (Ohio)\",\n\t\"US East (N. Virginia)\",\n\t\"US West (N. California)\",\n\t\"US West (Oregon)\",\n\t\"Asia Pacific (Hong Kong)\",\n\t\"Asia Pacific (Mumbai)\",\n\t\"Asia Pacific (Seoul)\",\n\t\"Asia Pacific (Singapore)\",\n\t\"Asia Pacific (Sydney)\",\n\t\"Asia Pacific (Tokyo)\",\n\t\"Canada (Central)\",\n\t\"EU (Frankfurt)\",\n\t\"EU (Ireland)\",\n\t\"EU (London)\",\n\t\"EU (Paris)\",\n\t\"EU (Stockholm)\",\n\t\"South America (São Paulo)\",\n}\n\n// Match returns regions matching the pattern(s) provided. Patterns\n// which are not \"expanded\" are returned as-is.\nfunc Match(regions []string) (v []string) {\n\tfor _, pattern := range regions {\n\t\tmatched := false\n\n\t\tfor _, id := range IDs {\n\t\t\tif ok, _ := filepath.Match(pattern, id); ok {\n\t\t\t\tv = append(v, id)\n\t\t\t\tmatched = true\n\t\t\t}\n\t\t}\n\n\t\tif !matched {\n\t\t\tv = append(v, pattern)\n\t\t}\n\t}\n\n\treturn\n}\n\n// GetIdByName returns a region id by name.\nfunc GetIdByName(name string) string {\n\tfor i, n := range Names {\n\t\tif n == name {\n\t\t\treturn IDs[i]\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// GetHostedZoneID returns a hosted zone id by region.\nfunc GetHostedZoneID(region string) string {\n\tid, ok := hostedZoneIDs[region]\n\tif !ok {\n\t\tpanic(fmt.Sprintf(\"region %q is not yet supported\", region))\n\t}\n\treturn id\n}\n"
  },
  {
    "path": "platform/aws/regions/regions_test.go",
    "content": "package regions\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tj/assert\"\n)\n\nfunc TestMatch(t *testing.T) {\n\tt.Run(\"explicit\", func(t *testing.T) {\n\t\tv := Match([]string{\"us-west-2\", \"us-east-1\"})\n\t\tassert.Equal(t, []string{\"us-west-2\", \"us-east-1\"}, v)\n\t})\n\n\tt.Run(\"glob all\", func(t *testing.T) {\n\t\tv := Match([]string{\"*\"})\n\t\tassert.Equal(t, IDs, v)\n\t})\n\n\tt.Run(\"glob some\", func(t *testing.T) {\n\t\tv := Match([]string{\"us-west-*\", \"ca-*\"})\n\t\te := []string{\"us-west-1\", \"us-west-2\", \"ca-central-1\"}\n\t\tassert.Equal(t, e, v)\n\t})\n}\n"
  },
  {
    "path": "platform/aws/runtime/runtime.go",
    "content": "package runtime\n\nimport (\n\t\"os\"\n\n\t\"github.com/apex/log\"\n\t\"github.com/apex/up\"\n)\n\n// Runtime implementation.\ntype Runtime struct {\n\tconfig *up.Config\n\tlog    log.Interface\n}\n\n// Option function.\ntype Option func(*Runtime)\n\n// New with the given options.\nfunc New(c *up.Config, options ...Option) *Runtime {\n\tvar v Runtime\n\tv.config = c\n\tv.log = log.Log\n\tfor _, o := range options {\n\t\to(&v)\n\t}\n\treturn &v\n}\n\n// WithLogger option.\nfunc WithLogger(l log.Interface) Option {\n\treturn func(v *Runtime) {\n\t\tv.log = l\n\t}\n}\n\n// Init implementation.\nfunc (r *Runtime) Init(stage string) error {\n\tos.Setenv(\"UP_STAGE\", stage)\n\n\tif os.Getenv(\"NODE_ENV\") == \"\" {\n\t\tos.Setenv(\"NODE_ENV\", stage)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "platform/event/event.go",
    "content": "// Package event provides an evented mechanism for hooking into platform specifics.\n//\n// This is necessary as not all platforms have identical capabilities,\n// so the reporting output (among other things) may differ\n// slightly.\npackage event\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/apex/log\"\n)\n\n// Events channel.\ntype Events chan *Event\n\n// Emit an event.\nfunc (e Events) Emit(name string, fields Fields) {\n\tif !strings.Contains(name, \".event\") {\n\t\tlog.Debugf(\"event %s %v\", name, fields)\n\t}\n\n\te <- &Event{\n\t\tName:   name,\n\t\tFields: fields,\n\t}\n}\n\n// Time an event.\nfunc (e Events) Time(name string, fields Fields) func() {\n\tstart := time.Now()\n\n\te.Emit(name, fields)\n\n\treturn func() {\n\t\tif fields == nil {\n\t\t\tfields = make(Fields)\n\t\t}\n\n\t\tf := make(Fields)\n\n\t\tfor k, v := range fields {\n\t\t\tf[k] = v\n\t\t}\n\n\t\tf[\"duration\"] = time.Since(start)\n\t\te.Emit(name+\".complete\", f)\n\t}\n}\n\n// Fields for an event.\ntype Fields map[string]interface{}\n\n// Event is a representation of an operation performed\n// by a platform, and is used for reporting.\ntype Event struct {\n\tName   string\n\tFields Fields\n}\n\n// Strings value.\nfunc (e *Event) Strings(name string) []string {\n\tv, ok := e.Fields[name].([]string)\n\tif !ok {\n\t\tpanic(fmt.Errorf(\"%#v field %s is not []string\", e, name))\n\t}\n\treturn v\n}\n\n// String value.\nfunc (e *Event) String(name string) string {\n\tv, ok := e.Fields[name].(string)\n\tif !ok {\n\t\tpanic(fmt.Errorf(\"%#v field %s is not a string\", e, name))\n\t}\n\treturn v\n}\n\n// Duration value.\nfunc (e *Event) Duration(name string) time.Duration {\n\tv, ok := e.Fields[name].(time.Duration)\n\tif !ok {\n\t\tpanic(fmt.Errorf(\"%#v field %s is not a time.Duration\", e, name))\n\t}\n\treturn v\n}\n\n// Int64 value.\nfunc (e *Event) Int64(name string) int64 {\n\tv, ok := e.Fields[name].(int64)\n\tif !ok {\n\t\tpanic(fmt.Errorf(\"%#v field %s is not a int64\", e, name))\n\t}\n\treturn v\n}\n\n// Int value.\nfunc (e *Event) Int(name string) int {\n\tv, ok := e.Fields[name].(int)\n\tif !ok {\n\t\tpanic(fmt.Errorf(\"%#v field %s is not a int\", e, name))\n\t}\n\treturn v\n}\n"
  },
  {
    "path": "platform/lambda/lambda.go",
    "content": "// Package lambda implements the API Gateway & AWS Lambda platform.\npackage lambda\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/apex/log\"\n\t\"github.com/apex/log/handlers/discard\"\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/aws/session\"\n\t\"github.com/aws/aws-sdk-go/service/acm\"\n\t\"github.com/aws/aws-sdk-go/service/apigateway\"\n\t\"github.com/aws/aws-sdk-go/service/iam\"\n\t\"github.com/aws/aws-sdk-go/service/lambda\"\n\t\"github.com/aws/aws-sdk-go/service/route53\"\n\t\"github.com/aws/aws-sdk-go/service/s3\"\n\t\"github.com/aws/aws-sdk-go/service/s3/s3manager\"\n\t\"github.com/dchest/uniuri\"\n\thumanize \"github.com/dustin/go-humanize\"\n\t\"github.com/golang/sync/errgroup\"\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/apex/up/config\"\n\t\"github.com/apex/up/internal/proxy/bin\"\n\t\"github.com/apex/up/internal/shim\"\n\t\"github.com/apex/up/internal/util\"\n\t\"github.com/apex/up/internal/zip\"\n\t\"github.com/apex/up/platform/aws/domains\"\n\t\"github.com/apex/up/platform/aws/logs\"\n\t\"github.com/apex/up/platform/aws/runtime\"\n\t\"github.com/apex/up/platform/event\"\n\t\"github.com/apex/up/platform/lambda/stack\"\n\t\"github.com/apex/up/platform/lambda/stack/resources\"\n)\n\n// errFirstDeploy is returned from .deploy() when a function is created.\nvar errFirstDeploy = errors.New(\"first deploy\")\n\nconst (\n\t// maxCodeSize is the max code size supported by Lambda (250MiB).\n\tmaxCodeSize = 250 << 20\n)\n\n// assume policy for the lambda function.\nvar apiGatewayAssumePolicy = `{\n\t\"Version\": \"2012-10-17\",\n\t\"Statement\": [\n\t\t{\n\t\t\t\"Effect\": \"Allow\",\n\t\t\t\"Principal\": {\n\t\t\t\t\"Service\": \"apigateway.amazonaws.com\"\n\t\t\t},\n\t\t\t\"Action\": \"sts:AssumeRole\"\n\t\t},\n\t\t{\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"Service\": \"lambda.amazonaws.com\"\n      },\n      \"Action\": \"sts:AssumeRole\"\n    }\n\t]\n}`\n\n// TODO: aggregate progress report for N regions or distinct progress bars\n// TODO: refactor with another region-scoped struct to clean this up\n\n// Platform implementation.\ntype Platform struct {\n\tconfig  *up.Config\n\thandler string\n\tzip     *bytes.Buffer\n\tevents  event.Events\n}\n\n// New platform.\nfunc New(c *up.Config, events event.Events) *Platform {\n\treturn &Platform{\n\t\tconfig:  c,\n\t\thandler: \"_proxy.handle\",\n\t\tevents:  events,\n\t}\n}\n\n// Build implementation.\nfunc (p *Platform) Build() error {\n\tstart := time.Now()\n\tp.zip = new(bytes.Buffer)\n\n\tif err := p.injectProxy(); err != nil {\n\t\treturn errors.Wrap(err, \"injecting proxy\")\n\t}\n\tdefer p.removeProxy()\n\n\tr, stats, err := zip.Build(\".\")\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"zip\")\n\t}\n\n\tif _, err := io.Copy(p.zip, r); err != nil {\n\t\treturn errors.Wrap(err, \"copying\")\n\t}\n\n\tif err := r.Close(); err != nil {\n\t\treturn errors.Wrap(err, \"closing\")\n\t}\n\n\tp.events.Emit(\"platform.build.zip\", event.Fields{\n\t\t\"files\":             stats.FilesAdded,\n\t\t\"size_uncompressed\": stats.SizeUncompressed,\n\t\t\"size_compressed\":   p.zip.Len(),\n\t\t\"duration\":          time.Since(start),\n\t})\n\n\tif stats.SizeUncompressed > maxCodeSize {\n\t\tsize := humanize.Bytes(uint64(stats.SizeUncompressed))\n\t\tmax := humanize.Bytes(uint64(maxCodeSize))\n\t\treturn errors.Errorf(\"zip contents is %s, exceeding Lambda's limit of %s\", size, max)\n\t}\n\n\treturn nil\n}\n\n// Zip returns the zip reader.\nfunc (p *Platform) Zip() io.Reader {\n\treturn p.zip\n}\n\n// Init initializes the runtime.\nfunc (p *Platform) Init(stage string) error {\n\treturn runtime.New(\n\t\tp.config,\n\t\truntime.WithLogger(&log.Logger{\n\t\t\tHandler: discard.Default,\n\t\t}),\n\t).Init(stage)\n}\n\n// Deploy implementation.\nfunc (p *Platform) Deploy(d up.Deploy) error {\n\tregions := p.config.Regions\n\tvar g errgroup.Group\n\n\tif err := p.createRole(); err != nil {\n\t\treturn errors.Wrap(err, \"iam\")\n\t}\n\n\tfor _, r := range regions {\n\t\tregion := r\n\t\tg.Go(func() error {\n\t\t\tversion, err := p.deploy(region, d)\n\t\t\tif err == nil {\n\t\t\t\tgoto endpoint\n\t\t\t}\n\n\t\t\tif err != errFirstDeploy {\n\t\t\t\treturn errors.Wrap(err, region)\n\t\t\t}\n\n\t\t\tif err := p.CreateStack(region, version); err != nil {\n\t\t\t\treturn errors.Wrap(err, region)\n\t\t\t}\n\n\t\tendpoint:\n\t\t\turl, err := p.URL(region, d.Stage)\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrap(err, \"fetching url\")\n\t\t\t}\n\n\t\t\tp.events.Emit(\"platform.deploy.url\", event.Fields{\n\t\t\t\t\"url\": url,\n\t\t\t})\n\n\t\t\treturn nil\n\t\t})\n\t}\n\n\treturn g.Wait()\n}\n\n// Logs implementation.\nfunc (p *Platform) Logs(c up.LogsConfig) up.Logs {\n\tg := \"/aws/lambda/\" + p.config.Name\n\treturn logs.New(g, c)\n}\n\n// Domains implementation.\nfunc (p *Platform) Domains() up.Domains {\n\treturn domains.New()\n}\n\n// URL returns the stage url.\nfunc (p *Platform) URL(region, stage string) (string, error) {\n\ts := session.New(aws.NewConfig().WithRegion(region))\n\tc := apigateway.New(s)\n\n\tapi, err := p.getAPI(c)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"fetching api\")\n\t}\n\n\tif api == nil {\n\t\treturn \"\", errors.Errorf(\"cannot find the API, looks like you haven't deployed\")\n\t}\n\n\tid := fmt.Sprintf(\"https://%s.execute-api.%s.amazonaws.com/%s/\", *api.Id, region, stage)\n\treturn id, nil\n}\n\n// CreateStack implementation.\nfunc (p *Platform) CreateStack(region, version string) error {\n\tversions := make(resources.Versions)\n\n\tfor _, s := range p.config.Stages {\n\t\tversions[s.Name] = version\n\t}\n\n\tif err := p.createCerts(); err != nil {\n\t\treturn errors.Wrap(err, \"creating certs\")\n\t}\n\n\tzones, err := p.getHostedZone()\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"fetching zones\")\n\t}\n\n\treturn stack.New(p.config, p.events, zones, region).Create(versions)\n}\n\n// DeleteStack implementation.\nfunc (p *Platform) DeleteStack(region string, wait bool) error {\n\tversions := resources.Versions{}\n\n\tfor _, s := range p.config.Stages {\n\t\tversions[s.Name] = \"1\"\n\t}\n\n\tif err := p.createRole(); err != nil {\n\t\treturn errors.Wrap(err, \"creating iam role\")\n\t}\n\n\tlog.Debug(\"deleting bucket objects\")\n\tif err := p.deleteBucketObjects(region); err != nil && !util.IsNotFound(err) {\n\t\treturn errors.Wrap(err, \"deleting s3 objects\")\n\t}\n\n\tlog.Debug(\"deleting stack\")\n\tif err := stack.New(p.config, p.events, nil, region).Delete(versions, wait); err != nil && !util.IsNotFound(err) {\n\t\treturn errors.Wrap(err, \"deleting stack\")\n\t}\n\n\tlog.Debug(\"deleting function\")\n\tif err := p.deleteFunction(region); err != nil && !util.IsNotFound(err) {\n\t\treturn errors.Wrap(err, \"deleting function\")\n\t}\n\n\tlog.Debug(\"deleting role\")\n\tif err := p.deleteRole(region); err != nil && !util.IsNotFound(err) {\n\t\treturn errors.Wrap(err, \"deleting function iam role\")\n\t}\n\n\treturn nil\n}\n\n// ShowStack implementation.\nfunc (p *Platform) ShowStack(region string) error {\n\treturn stack.New(p.config, p.events, nil, region).Show()\n}\n\n// PlanStack implementation.\nfunc (p *Platform) PlanStack(region string) error {\n\tversions, err := p.getAliasVersions(region)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"fetching alias versions\")\n\t}\n\n\tif err := p.createCerts(); err != nil {\n\t\treturn errors.Wrap(err, \"creating certs\")\n\t}\n\n\tzones, err := p.getHostedZone()\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"fetching zones\")\n\t}\n\n\treturn stack.New(p.config, p.events, zones, region).Plan(versions)\n}\n\n// ApplyStack implementation.\nfunc (p *Platform) ApplyStack(region string) error {\n\tif err := p.createCerts(); err != nil {\n\t\treturn errors.Wrap(err, \"creating certs\")\n\t}\n\n\treturn stack.New(p.config, p.events, nil, region).Apply()\n}\n\n// Exists implementation.\nfunc (p *Platform) Exists(region string) (bool, error) {\n\tlog.Debug(\"checking if application exists\")\n\tc := lambda.New(session.New(aws.NewConfig().WithRegion(region)))\n\n\t_, err := c.GetFunctionConfiguration(&lambda.GetFunctionConfigurationInput{\n\t\tFunctionName: &p.config.Name,\n\t})\n\n\tif util.IsNotFound(err) {\n\t\treturn false, nil\n\t}\n\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treturn true, nil\n}\n\n// getAliasVersions returns the function alias versions.\nfunc (p *Platform) getAliasVersions(region string) (resources.Versions, error) {\n\tvar g errgroup.Group\n\tvar mu sync.Mutex\n\n\tc := lambda.New(session.New(aws.NewConfig().WithRegion(region)))\n\tversions := make(resources.Versions)\n\n\tlog.Debug(\"fetching aliases\")\n\tfor _, s := range p.config.Stages {\n\t\ts := s\n\n\t\tg.Go(func() error {\n\t\t\tlog.Debugf(\"fetching %s alias\", s.Name)\n\t\t\tversion, err := p.getAliasVersion(c, s.Name)\n\n\t\t\tif util.IsNotFound(err) {\n\t\t\t\tlog.Debugf(\"%s has no alias, defaulting to staging\", s.Name)\n\t\t\t\tversion, err = p.getAliasVersion(c, \"staging\")\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn errors.Wrap(err, \"fetching staging alias\")\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrapf(err, \"fetching %q alias\", s.Name)\n\t\t\t}\n\n\t\t\tlog.Debugf(\"fetched %s alias (%s)\", s.Name, version)\n\t\t\tmu.Lock()\n\t\t\tversions[s.Name] = version\n\t\t\tmu.Unlock()\n\n\t\t\treturn nil\n\t\t})\n\t}\n\n\treturn versions, g.Wait()\n}\n\n// getAliasVersion retruns the alias version for a stage.\nfunc (p *Platform) getAliasVersion(c *lambda.Lambda, stage string) (string, error) {\n\tres, err := c.GetAlias(&lambda.GetAliasInput{\n\t\tFunctionName: &p.config.Name,\n\t\tName:         &stage,\n\t})\n\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn *res.FunctionVersion, nil\n}\n\n// getHostedZone returns existing hosted zones.\nfunc (p *Platform) getHostedZone() (zones []*route53.HostedZone, err error) {\n\tr := route53.New(session.New(aws.NewConfig()))\n\n\tlog.Debug(\"fetching hosted zones\")\n\tres, err := r.ListHostedZonesByName(&route53.ListHostedZonesByNameInput{\n\t\tMaxItems: aws.String(\"100\"),\n\t})\n\n\tif err != nil {\n\t\treturn\n\t}\n\n\tzones = res.HostedZones\n\treturn\n}\n\n// createCerts creates the certificates if necessary.\n//\n// We perform this task outside of CloudFormation because\n// the certificates currently must be created in the us-east-1\n// region. This also gives us a chance to let the user know\n// that they have to confirm an email.\nfunc (p *Platform) createCerts() error {\n\ts := session.New(aws.NewConfig().WithRegion(\"us-east-1\"))\n\ta := acm.New(s)\n\tvar domains []string\n\n\t// existing certs\n\tlog.Debug(\"fetching existing certs\")\n\tcerts, err := getCerts(a)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"fetching certs\")\n\t}\n\n\t// request certs\n\tfor _, s := range p.config.Stages.List() {\n\t\tif s.Domain == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tcertDomains := util.CertDomainNames(s.Domain)\n\n\t\t// see if the cert exists\n\t\tlog.Debugf(\"looking up cert for %s\", s.Domain)\n\t\tarn := getCert(certs, s.Domain)\n\t\tif arn != \"\" {\n\t\t\tlog.Debugf(\"found cert for %s: %s\", s.Domain, arn)\n\t\t\ts.Cert = arn\n\t\t\tcontinue\n\t\t}\n\n\t\toption := acm.DomainValidationOption{\n\t\t\tDomainName:       aws.String(certDomains[0]),\n\t\t\tValidationDomain: aws.String(util.Domain(s.Domain)),\n\t\t}\n\n\t\toptions := []*acm.DomainValidationOption{\n\t\t\t&option,\n\t\t}\n\n\t\t// request the cert\n\t\tres, err := a.RequestCertificate(&acm.RequestCertificateInput{\n\t\t\tDomainName:              aws.String(certDomains[0]),\n\t\t\tDomainValidationOptions: options,\n\t\t\tSubjectAlternativeNames: aws.StringSlice(certDomains[1:]),\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn errors.Wrapf(err, \"requesting cert for %v\", certDomains)\n\t\t}\n\n\t\tdomains = append(domains, certDomains[0])\n\t\ts.Cert = *res.CertificateArn\n\t}\n\n\t// no certs needed\n\tif len(domains) == 0 {\n\t\treturn nil\n\t}\n\n\tdefer p.events.Time(\"platform.certs.create\", event.Fields{\n\t\t\"domains\": domains,\n\t})()\n\n\t// wait for approval\n\tfor range time.Tick(4 * time.Second) {\n\t\tres, err := a.ListCertificates(&acm.ListCertificatesInput{\n\t\t\tMaxItems:            aws.Int64(1000),\n\t\t\tCertificateStatuses: aws.StringSlice([]string{acm.CertificateStatusPendingValidation}),\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"listing\")\n\t\t}\n\n\t\tif len(res.CertificateSummaryList) == 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// deploy to the given region.\nfunc (p *Platform) deploy(region string, d up.Deploy) (version string, err error) {\n\tstart := time.Now()\n\n\tfields := event.Fields{\n\t\t\"commit\": d.Commit,\n\t\t\"stage\":  d.Stage,\n\t\t\"region\": region,\n\t}\n\n\tp.events.Emit(\"platform.deploy\", fields)\n\n\tdefer func() {\n\t\tfields[\"duration\"] = time.Since(start)\n\t\tfields[\"commit\"] = d.Commit\n\t\tfields[\"version\"] = version\n\t\tp.events.Emit(\"platform.deploy.complete\", fields)\n\t}()\n\n\tctx := log.WithField(\"region\", region)\n\ts := session.New(aws.NewConfig().WithRegion(region))\n\tu := s3manager.NewUploaderWithClient(s3.New(s))\n\ta := apigateway.New(s)\n\tc := lambda.New(s)\n\n\tctx.Debug(\"fetching function config\")\n\t_, err = c.GetFunctionConfiguration(&lambda.GetFunctionConfigurationInput{\n\t\tFunctionName: &p.config.Name,\n\t})\n\n\tif util.IsNotFound(err) {\n\t\tdefer p.events.Time(\"platform.function.create\", fields)\n\t\treturn p.createFunction(c, a, u, region, d)\n\t}\n\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"fetching function config\")\n\t}\n\n\tdefer p.events.Time(\"platform.function.update\", fields)\n\treturn p.updateFunction(c, a, u, region, d)\n}\n\n// createFunction creates the function.\nfunc (p *Platform) createFunction(c *lambda.Lambda, a *apigateway.APIGateway, up *s3manager.Uploader, region string, d up.Deploy) (version string, err error) {\n\t// ensure bucket exists\n\tif err := p.createBucket(region); err != nil && !util.IsBucketExists(err) {\n\t\treturn \"\", errors.Wrap(err, \"creating s3 bucket\")\n\t}\n\n\t// upload to s3\n\tb := aws.String(p.getS3BucketName(region))\n\tk := aws.String(p.getS3Key(d.Stage))\n\n\tlog.Debugf(\"uploading function to bucket %s key %s\", *b, *k)\n\t_, err = up.Upload(&s3manager.UploadInput{\n\t\tBucket:               b,\n\t\tKey:                  k,\n\t\tBody:                 bytes.NewReader(p.zip.Bytes()),\n\t\tServerSideEncryption: aws.String(\"aws:kms\"),\n\t})\n\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"uploading function\")\n\t}\n\n\t// load environment\n\tenv, err := p.loadEnvironment(d)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"loading environment variables\")\n\t}\n\n\t// create function\nretry:\n\tlog.Debug(\"creating function\")\n\tres, err := c.CreateFunction(&lambda.CreateFunctionInput{\n\t\tFunctionName: &p.config.Name,\n\t\tHandler:      &p.handler,\n\t\tRuntime:      &p.config.Lambda.Runtime,\n\t\tRole:         &p.config.Lambda.Role,\n\t\tMemorySize:   aws.Int64(int64(p.config.Lambda.Memory)),\n\t\tTimeout:      aws.Int64(int64(p.config.Lambda.Timeout)),\n\t\tPublish:      aws.Bool(true),\n\t\tEnvironment:  env,\n\t\tCode: &lambda.FunctionCode{\n\t\t\tS3Bucket: b,\n\t\t\tS3Key:    k,\n\t\t},\n\t\tVpcConfig: p.vpc(),\n\t})\n\n\t// IAM is eventually consistent apparently, so we have to keep retrying\n\tif isCreatingRole(err) {\n\t\tlog.Debug(\"waiting for role to be created\")\n\t\ttime.Sleep(500 * time.Millisecond)\n\t\tgoto retry\n\t}\n\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"creating function\")\n\t}\n\n\treturn *res.Version, errFirstDeploy\n}\n\n// updateFunction updates the function.\nfunc (p *Platform) updateFunction(c *lambda.Lambda, a *apigateway.APIGateway, up *s3manager.Uploader, region string, d up.Deploy) (version string, err error) {\n\tb := aws.String(p.getS3BucketName(region))\n\tk := aws.String(p.getS3Key(d.Stage))\n\n\t// upload\n\tlog.Debugf(\"uploading function to bucket %s key %s\", *b, *k)\n\t_, err = up.Upload(&s3manager.UploadInput{\n\t\tBucket:               b,\n\t\tKey:                  k,\n\t\tBody:                 bytes.NewReader(p.zip.Bytes()),\n\t\tServerSideEncryption: aws.String(\"aws:kms\"),\n\t})\n\n\t// ensure bucket exists\n\tif util.IsNotFound(err) {\n\t\tif err := p.createBucket(region); err != nil {\n\t\t\treturn \"\", errors.Wrap(err, \"creating s3 bucket\")\n\t\t}\n\t\terr = nil\n\t}\n\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"uploading function\")\n\t}\n\n\t// load environment\n\tenv, err := p.loadEnvironment(d)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"loading environment variables\")\n\t}\n\n\t// update function config\n\tlog.Debug(\"updating function\")\n\tif err := p.isPending(c); err != nil {\n\t\treturn \"\", err\n\t}\n\t_, err = c.UpdateFunctionConfiguration(&lambda.UpdateFunctionConfigurationInput{\n\t\tFunctionName: &p.config.Name,\n\t\tHandler:      &p.handler,\n\t\tRuntime:      &p.config.Lambda.Runtime,\n\t\tRole:         &p.config.Lambda.Role,\n\t\tMemorySize:   aws.Int64(int64(p.config.Lambda.Memory)),\n\t\tTimeout:      aws.Int64(int64(p.config.Lambda.Timeout)),\n\t\tEnvironment:  env,\n\t\tVpcConfig:    p.vpc(),\n\t})\n\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"updating function config\")\n\t}\n\n\t// update function code\n\tlog.Debug(\"updating function code\")\n\tif err := p.isPending(c); err != nil {\n\t\treturn \"\", err\n\t}\n\tres, err := c.UpdateFunctionCode(&lambda.UpdateFunctionCodeInput{\n\t\tFunctionName: &p.config.Name,\n\t\tPublish:      aws.Bool(true),\n\t\tS3Bucket:     b,\n\t\tS3Key:        k,\n\t})\n\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"updating function code\")\n\t}\n\n\t// create stage alias\n\tif err := p.alias(c, d.Stage, *res.Version); err != nil {\n\t\treturn \"\", errors.Wrapf(err, \"creating function stage %q alias\", d.Stage)\n\t}\n\n\t// create git alias\n\tif d.Commit != \"\" {\n\t\tif err := p.alias(c, util.EncodeAlias(d.Commit), *res.Version); err != nil {\n\t\t\treturn \"\", errors.Wrapf(err, \"creating function git %q alias\", d.Commit)\n\t\t}\n\t}\n\n\treturn *res.Version, nil\n}\n\n// isPending implementation.\nfunc (p *Platform) isPending(c *lambda.Lambda) error {\n\tvar attempt int\n\tmaxAttempts := 30       // TODO: ideally max attempts is configurable\n\twait := time.Second * 5 // TODO: ideally some backoff\n\nretry:\n\tattempt++\n\n\tlog.Debugf(\"checking if function is pending (attempt %d of %d)\", attempt, maxAttempts)\n\tconf, err := c.GetFunctionConfiguration(&lambda.GetFunctionConfigurationInput{\n\t\tFunctionName: &p.config.Name,\n\t})\n\tif err != nil {\n\t\treturn errors.Wrapf(err, \"getting function config\")\n\t}\n\n\tif *conf.State == \"Active\" && *conf.LastUpdateStatus != \"InProgress\" {\n\t\tlog.Debugf(\"function is in state %q / %q\", *conf.State, *conf.LastUpdateStatus)\n\t\treturn nil\n\t}\n\n\tif attempt >= maxAttempts {\n\t\tlog.Debugf(\"max attempts exceeded\")\n\t\treturn errors.Errorf(\"function is stuck in the state %q / %q\", *conf.State, *conf.LastUpdateStatus)\n\t}\n\n\tlog.Debugf(\"function is in state %q / %q, trying again in %s\", *conf.State, *conf.LastUpdateStatus, wait)\n\ttime.Sleep(wait)\n\tgoto retry\n}\n\n// vpc returns the vpc configuration or nil.\nfunc (p *Platform) vpc() *lambda.VpcConfig {\n\tv := p.config.Lambda.VPC\n\tif v == nil {\n\t\treturn nil\n\t}\n\n\treturn &lambda.VpcConfig{\n\t\tSubnetIds:        aws.StringSlice(v.Subnets),\n\t\tSecurityGroupIds: aws.StringSlice(v.SecurityGroups),\n\t}\n}\n\n// alias creates or updates an alias.\nfunc (p *Platform) alias(c *lambda.Lambda, alias, version string) error {\n\tlog.Debugf(\"alias %s to %s\", alias, version)\n\t_, err := c.UpdateAlias(&lambda.UpdateAliasInput{\n\t\tFunctionName:    &p.config.Name,\n\t\tFunctionVersion: &version,\n\t\tName:            &alias,\n\t\tDescription:     aws.String(util.ManagedByUp(\"\")),\n\t})\n\n\tif util.IsNotFound(err) {\n\t\t_, err = c.CreateAlias(&lambda.CreateAliasInput{\n\t\t\tFunctionName:    &p.config.Name,\n\t\t\tFunctionVersion: &version,\n\t\t\tName:            &alias,\n\t\t\tDescription:     aws.String(util.ManagedByUp(\"\")),\n\t\t})\n\t}\n\n\treturn err\n}\n\n// deleteFunction deletes the lambda function.\nfunc (p *Platform) deleteFunction(region string) error {\n\t// TODO: sessions all over... refactor\n\tc := lambda.New(session.New(aws.NewConfig().WithRegion(region)))\n\n\t_, err := c.DeleteFunction(&lambda.DeleteFunctionInput{\n\t\tFunctionName: &p.config.Name,\n\t})\n\n\treturn err\n}\n\n// loadEnvironment loads environment variables.\nfunc (p *Platform) loadEnvironment(d up.Deploy) (*lambda.Environment, error) {\n\tm := aws.StringMap(p.config.Environment)\n\tm[\"UP_STAGE\"] = &d.Stage\n\tm[\"UP_COMMIT\"] = &d.Commit\n\tm[\"UP_AUTHOR\"] = &d.Author\n\treturn &lambda.Environment{\n\t\tVariables: m,\n\t}, nil\n}\n\n// createRole creates the IAM role unless it is present.\nfunc (p *Platform) createRole() error {\n\ts := session.New(aws.NewConfig())\n\tc := iam.New(s)\n\n\tname := p.roleName()\n\tdesc := util.ManagedByUp(\"\")\n\n\t// role is provided\n\tif s := p.config.Lambda.Role; s != \"\" {\n\t\tlog.Debugf(\"using role from config %s\", s)\n\t\treturn nil\n\t}\n\n\tlog.Debug(\"checking for role\")\n\texisting, err := c.GetRole(&iam.GetRoleInput{\n\t\tRoleName: &name,\n\t})\n\n\t// network or permission error\n\tif err != nil && !util.IsNotFound(err) {\n\t\treturn errors.Wrap(err, \"fetching role\")\n\t}\n\n\t// use the existing role\n\tif err == nil {\n\t\tlog.Debug(\"found existing role\")\n\n\t\tif err := p.updateRole(c); err != nil {\n\t\t\treturn errors.Wrap(err, \"updating role policy\")\n\t\t}\n\n\t\tp.setRoleARN(*existing.Role.Arn)\n\t\treturn nil\n\t}\n\n\tlog.Debug(\"creating role\")\n\trole, err := c.CreateRole(&iam.CreateRoleInput{\n\t\tRoleName:                 &name,\n\t\tDescription:              &desc,\n\t\tAssumeRolePolicyDocument: &apiGatewayAssumePolicy,\n\t})\n\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"creating role\")\n\t}\n\n\tif err := p.updateRole(c); err != nil {\n\t\treturn errors.Wrap(err, \"updating role policy\")\n\t}\n\n\tp.setRoleARN(*role.Role.Arn)\n\n\treturn nil\n}\n\n// updateRole updates the IAM role.\nfunc (p *Platform) updateRole(c *iam.IAM) error {\n\tname := p.roleName()\n\n\tpolicy, err := p.functionPolicy()\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"creating function policy\")\n\t}\n\n\tlog.Debug(\"updating role policy\")\n\t_, err = c.PutRolePolicy(&iam.PutRolePolicyInput{\n\t\tPolicyName:     &name,\n\t\tRoleName:       &name,\n\t\tPolicyDocument: &policy,\n\t})\n\n\treturn err\n}\n\n// setRoleARN sets the role ARN.\nfunc (p *Platform) setRoleARN(arn string) {\n\tlog.Debugf(\"set role to %s\", arn)\n\tp.config.Lambda.Role = arn\n}\n\n// roleName returns the IAM role name.\nfunc (p *Platform) roleName() string {\n\treturn fmt.Sprintf(\"%s-function\", p.config.Name)\n}\n\n// deleteRole deletes the role and policy.\nfunc (p *Platform) deleteRole(region string) error {\n\tname := fmt.Sprintf(\"%s-function\", p.config.Name)\n\tc := iam.New(session.New(aws.NewConfig().WithRegion(region)))\n\n\t// role is provided\n\tif s := p.config.Lambda.Role; s != \"\" {\n\t\tlog.Debugf(\"using role from config %s; not deleting\", s)\n\t\treturn nil\n\t}\n\n\t_, err := c.DeleteRolePolicy(&iam.DeleteRolePolicyInput{\n\t\tRoleName:   &name,\n\t\tPolicyName: &name,\n\t})\n\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"deleting policy\")\n\t}\n\n\t_, err = c.DeleteRole(&iam.DeleteRoleInput{\n\t\tRoleName: &name,\n\t})\n\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"deleting iam role\")\n\t}\n\n\treturn nil\n}\n\n// createBucket creates the bucket.\nfunc (p *Platform) createBucket(region string) error {\n\ts := s3.New(session.New(aws.NewConfig().WithRegion(region)))\n\tn := p.getS3BucketName(region)\n\n\tlog.WithField(\"name\", n).Debug(\"creating s3 bucket\")\n\t_, err := s.CreateBucket(&s3.CreateBucketInput{\n\t\tBucket: &n,\n\t})\n\n\treturn err\n}\n\n// deleteBucketObjects deletes the objects for the app.\nfunc (p *Platform) deleteBucketObjects(region string) error {\n\ts := s3.New(session.New(aws.NewConfig().WithRegion(region)))\n\tb := aws.String(p.getS3BucketName(region))\n\tprefix := p.config.Name + \"/\"\n\n\tparams := &s3.ListObjectsInput{\n\t\tBucket: b,\n\t\tPrefix: &prefix,\n\t}\n\n\treturn s.ListObjectsPages(params, func(page *s3.ListObjectsOutput, lastPage bool) bool {\n\t\tfor _, c := range page.Contents {\n\t\t\tctx := log.WithField(\"key\", *c.Key)\n\n\t\t\tctx.Debug(\"deleting object\")\n\t\t\t_, err := s.DeleteObject(&s3.DeleteObjectInput{\n\t\t\t\tBucket: b,\n\t\t\t\tKey:    c.Key,\n\t\t\t})\n\n\t\t\tif err != nil {\n\t\t\t\tctx.WithError(err).Warn(\"deleting object\")\n\t\t\t}\n\t\t}\n\n\t\treturn *page.IsTruncated\n\t})\n}\n\n// getAPI returns the API if present or nil.\nfunc (p *Platform) getAPI(c *apigateway.APIGateway) (api *apigateway.RestApi, err error) {\n\tname := p.config.Name\n\n\tres, err := c.GetRestApis(&apigateway.GetRestApisInput{\n\t\tLimit: aws.Int64(500),\n\t})\n\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"fetching apis\")\n\t}\n\n\tfor _, a := range res.Items {\n\t\tif *a.Name == name {\n\t\t\tapi = a\n\t\t}\n\t}\n\n\treturn\n}\n\n// injectProxy injects the Go proxy.\nfunc (p *Platform) injectProxy() error {\n\tlog.Debugf(\"injecting proxy\")\n\n\tif err := ioutil.WriteFile(\"main\", bin.MustAsset(\"up-proxy\"), 0777); err != nil {\n\t\treturn errors.Wrap(err, \"writing up-proxy\")\n\t}\n\n\tif err := ioutil.WriteFile(\"_proxy.js\", shim.MustAsset(\"index.js\"), 0755); err != nil {\n\t\treturn errors.Wrap(err, \"writing _proxy.js\")\n\t}\n\n\treturn nil\n}\n\n// removeProxy removes the Go proxy.\nfunc (p *Platform) removeProxy() error {\n\tlog.Debugf(\"removing proxy\")\n\tos.Remove(\"main\")\n\tos.Remove(\"_proxy.js\")\n\treturn nil\n}\n\n// getS3Key returns a randomized s3 key.\nfunc (p *Platform) getS3Key(stage string) string {\n\tts := time.Now().Unix()\n\tuid := uniuri.New()\n\treturn fmt.Sprintf(\"%s/%s/%d-%s.zip\", p.config.Name, stage, ts, uid)\n}\n\n// getS3BucketName returns the s3 bucket name.\nfunc (p *Platform) getS3BucketName(region string) string {\n\treturn fmt.Sprintf(\"up-%s-%s\", p.getAccountID(), region)\n}\n\n// getAccountID returns the AWS account id derived from Lambda role,\n// which is currently always present, implicitly or explicitly.\nfunc (p *Platform) getAccountID() string {\n\treturn strings.Split(p.config.Lambda.Role, \":\")[4]\n}\n\n// functionPolicy returns the IAM function role policy.\nfunc (p *Platform) functionPolicy() (string, error) {\n\tpolicy := struct {\n\t\tVersion   string\n\t\tStatement []config.IAMPolicyStatement\n\t}{\n\t\tVersion:   \"2012-10-17\",\n\t\tStatement: p.config.Lambda.Policy,\n\t}\n\n\tb, err := json.MarshalIndent(policy, \"\", \"  \")\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn string(b), nil\n}\n\n// isCreatingRole returns true if the role has not been created.\nfunc isCreatingRole(err error) bool {\n\treturn err != nil && strings.Contains(err.Error(), \"role defined for the function cannot be assumed by Lambda\")\n}\n\n// getCerts returns the certificates available.\nfunc getCerts(a *acm.ACM) (certs []*acm.CertificateDetail, err error) {\n\tvar g errgroup.Group\n\tvar mu sync.Mutex\n\n\tres, err := a.ListCertificates(&acm.ListCertificatesInput{\n\t\tMaxItems: aws.Int64(1000),\n\t})\n\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"listing\")\n\t}\n\n\tfor _, c := range res.CertificateSummaryList {\n\t\tc := c\n\t\tg.Go(func() error {\n\t\t\tres, err := a.DescribeCertificate(&acm.DescribeCertificateInput{\n\t\t\t\tCertificateArn: c.CertificateArn,\n\t\t\t})\n\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrap(err, \"describing\")\n\t\t\t}\n\n\t\t\tmu.Lock()\n\t\t\tcerts = append(certs, res.Certificate)\n\t\t\tmu.Unlock()\n\t\t\treturn nil\n\t\t})\n\t}\n\n\terr = g.Wait()\n\treturn\n}\n\n// getCert returns the ARN of a certificate with can satisfy domain,\n// favoring more specific certificates, then falling back on wildcards.\nfunc getCert(certs []*acm.CertificateDetail, domain string) string {\n\t// exact domain\n\tfor _, c := range certs {\n\t\tif *c.DomainName == domain {\n\t\t\treturn *c.CertificateArn\n\t\t}\n\t}\n\n\t// exact alt\n\tfor _, c := range certs {\n\t\tfor _, a := range c.SubjectAlternativeNames {\n\t\t\tif *a == domain {\n\t\t\t\treturn *c.CertificateArn\n\t\t\t}\n\t\t}\n\t}\n\n\t// wildcards\n\tfor _, c := range certs {\n\t\tif util.WildcardMatches(*c.DomainName, domain) {\n\t\t\treturn *c.CertificateArn\n\t\t}\n\n\t\tfor _, a := range c.SubjectAlternativeNames {\n\t\t\tif util.WildcardMatches(*a, domain) {\n\t\t\t\treturn *c.CertificateArn\n\t\t\t}\n\t\t}\n\t}\n\n\treturn \"\"\n}\n"
  },
  {
    "path": "platform/lambda/lambda_test.go",
    "content": "package lambda\n\nimport (\n\t\"testing\"\n\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/service/acm\"\n\t\"github.com/tj/assert\"\n\t\n\t\"github.com/apex/up/config\"\n\t\"github.com/apex/up/platform/event\"\n)\n\nfunc TestGetCert(t *testing.T) {\n\tcerts := []*acm.CertificateDetail{\n\t\t{\n\t\t\tDomainName:     aws.String(\"example.com\"),\n\t\t\tCertificateArn: aws.String(\"arn:example.com\"),\n\t\t\tSubjectAlternativeNames: aws.StringSlice([]string{\n\t\t\t\t\"*.example.com\",\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tDomainName:     aws.String(\"*.apex.sh\"),\n\t\t\tCertificateArn: aws.String(\"arn:*.apex.sh\"),\n\t\t},\n\t\t{\n\t\t\tDomainName:     aws.String(\"api.example.com\"),\n\t\t\tCertificateArn: aws.String(\"arn:api.example.com\"),\n\t\t\tSubjectAlternativeNames: aws.StringSlice([]string{\n\t\t\t\t\"*.api.example.com\",\n\t\t\t\t\"something.example.com\",\n\t\t\t}),\n\t\t},\n\t}\n\n\tarn := getCert(certs, \"example.com\")\n\tassert.Equal(t, \"arn:example.com\", arn)\n\n\tarn = getCert(certs, \"www.example.com\")\n\tassert.Equal(t, \"arn:example.com\", arn)\n\n\tarn = getCert(certs, \"api.example.com\")\n\tassert.Equal(t, \"arn:api.example.com\", arn)\n\n\tarn = getCert(certs, \"apex.sh\")\n\tassert.Empty(t, arn)\n\n\tarn = getCert(certs, \"api.apex.sh\")\n\tassert.Equal(t, \"arn:*.apex.sh\", arn)\n\n\tarn = getCert(certs, \"v1.api.example.com\")\n\tassert.Equal(t, \"arn:api.example.com\", arn)\n\n\tarn = getCert(certs, \"something.example.com\")\n\tassert.Equal(t, \"arn:api.example.com\", arn)\n\n\tarn = getCert(certs, \"staging.v1.api.example.com\")\n\tassert.Empty(t, arn)\n}\n\nfunc TestCreateRole(t *testing.T) {\n\tt.Run(\"doesn't attempt to create configured role\", func(t *testing.T) {\n\t\tc := &config.Config{\n\t\t\tLambda: config.Lambda{\n\t\t\t\tRole: \"custom-role-name\",\n\t\t\t},\n\t\t}\n\t\tevents := make(event.Events)\n\t\tp := New(c, events)\n\t\tassert.NoError(t, p.createRole(), \"createRole\")\n\t})\n}\n\nfunc TestDeleteRole(t *testing.T) {\n\tt.Run(\"doesn't attempt to delete configured role\", func(t *testing.T) {\n\t\tc := &config.Config{\n\t\t\tLambda: config.Lambda{\n\t\t\t\tRole: \"custom-role-name\",\n\t\t\t},\n\t\t}\n\t\tevents := make(event.Events)\n\t\tp := New(c, events)\n\t\tassert.NoError(t, p.deleteRole(\"us-west-2\"), \"deleteRole\")\n\t})\n}\n"
  },
  {
    "path": "platform/lambda/metrics.go",
    "content": "package lambda\n\nimport (\n\t\"time\"\n\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/aws/session\"\n\t\"github.com/aws/aws-sdk-go/service/cloudwatch\"\n\t\"github.com/golang/sync/errgroup\"\n\n\t\"github.com/apex/up/internal/metrics\"\n\t\"github.com/apex/up/platform/event\"\n)\n\n// TODO: write a higher level pkg in tj/aws\n// TODO: move the metrics pkg to tj/aws\n\ntype stat struct {\n\tNamespace string\n\tName      string\n\tMetric    string\n\tStat      string\n\tpoint     *cloudwatch.Datapoint\n}\n\n// Value returns the metric value.\nfunc (s *stat) Value() int {\n\tif s.point == nil {\n\t\treturn 0\n\t}\n\n\tswitch s.Stat {\n\tcase \"Sum\":\n\t\treturn int(*s.point.Sum)\n\tcase \"Average\":\n\t\treturn int(*s.point.Average)\n\tcase \"Minimum\":\n\t\treturn int(*s.point.Minimum)\n\tcase \"Maximum\":\n\t\treturn int(*s.point.Maximum)\n\tdefault:\n\t\treturn 0\n\t}\n}\n\n// stats to fetch.\nvar stats = []*stat{\n\t{\"AWS/ApiGateway\", \"Requests\", \"Count\", \"Sum\", nil},\n\t{\"AWS/ApiGateway\", \"Duration min\", \"Latency\", \"Minimum\", nil},\n\t{\"AWS/ApiGateway\", \"Duration avg\", \"Latency\", \"Average\", nil},\n\t{\"AWS/ApiGateway\", \"Duration max\", \"Latency\", \"Maximum\", nil},\n\t{\"AWS/Lambda\", \"Duration sum\", \"Duration\", \"Sum\", nil},\n\t{\"AWS/ApiGateway\", \"Errors 4xx\", \"4XXError\", \"Sum\", nil},\n\t{\"AWS/ApiGateway\", \"Errors 5xx\", \"5XXError\", \"Sum\", nil},\n\t{\"AWS/Lambda\", \"Invocations\", \"Invocations\", \"Sum\", nil},\n\t{\"AWS/Lambda\", \"Errors\", \"Errors\", \"Sum\", nil},\n\t{\"AWS/Lambda\", \"Throttles\", \"Throttles\", \"Sum\", nil},\n}\n\n// ShowMetrics implementation.\nfunc (p *Platform) ShowMetrics(region, stage string, start time.Time) error {\n\ts := session.New(aws.NewConfig().WithRegion(region))\n\tc := cloudwatch.New(s)\n\tvar g errgroup.Group\n\tname := p.config.Name\n\n\td := time.Now().UTC().Sub(start)\n\n\tfor _, s := range stats {\n\t\ts := s\n\t\tg.Go(func() error {\n\t\t\tm := metrics.New().\n\t\t\t\tNamespace(s.Namespace).\n\t\t\t\tTimeRange(time.Now().Add(-d), time.Now()).\n\t\t\t\tPeriod(int(d.Seconds() * 2)).\n\t\t\t\tStat(s.Stat).\n\t\t\t\tMetric(s.Metric)\n\n\t\t\tswitch s.Namespace {\n\t\t\tcase \"AWS/ApiGateway\":\n\t\t\t\tm = m.Dimension(\"ApiName\", name).Dimension(\"Stage\", stage)\n\t\t\tcase \"AWS/Lambda\":\n\t\t\t\tm = m.Dimension(\"FunctionName\", name).Dimension(\"Resource\", name+\":\"+stage)\n\t\t\t}\n\n\t\t\tres, err := c.GetMetricStatistics(m.Params())\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif len(res.Datapoints) > 0 {\n\t\t\t\ts.point = res.Datapoints[0]\n\t\t\t}\n\n\t\t\treturn nil\n\t\t})\n\t}\n\n\tif err := g.Wait(); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, s := range stats {\n\t\tp.events.Emit(\"metrics.value\", event.Fields{\n\t\t\t\"name\":   s.Name,\n\t\t\t\"value\":  s.Value(),\n\t\t\t\"memory\": p.config.Lambda.Memory,\n\t\t})\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "platform/lambda/prune.go",
    "content": "package lambda\n\nimport (\n\t\"sort\"\n\t\"time\"\n\n\t\"github.com/apex/log\"\n\t\"github.com/apex/up/platform/event\"\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/aws/session\"\n\t\"github.com/aws/aws-sdk-go/service/s3\"\n\t\"github.com/pkg/errors\"\n)\n\n// Prune implementation.\nfunc (p *Platform) Prune(region, stage string, versions int) error {\n\tp.events.Emit(\"prune\", nil)\n\n\tif err := p.createRole(); err != nil {\n\t\treturn errors.Wrap(err, \"creating iam role\")\n\t}\n\n\ts := s3.New(session.New(aws.NewConfig().WithRegion(region)))\n\tb := aws.String(p.getS3BucketName(region))\n\tprefix := p.config.Name + \"/\" + stage + \"/\"\n\n\tparams := &s3.ListObjectsInput{\n\t\tBucket: b,\n\t\tPrefix: &prefix,\n\t}\n\n\tstart := time.Now()\n\tvar objects []*s3.Object\n\tvar count int\n\tvar size int64\n\n\t// fetch objects\n\terr := s.ListObjectsPages(params, func(page *s3.ListObjectsOutput, lastPage bool) bool {\n\t\tfor _, o := range page.Contents {\n\t\t\tobjects = append(objects, o)\n\t\t}\n\t\treturn *page.IsTruncated\n\t})\n\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"listing s3 objects\")\n\t}\n\n\t// sort by time descending\n\tsort.Slice(objects, func(i int, j int) bool {\n\t\ta := objects[i]\n\t\tb := objects[j]\n\t\treturn (*b).LastModified.Before(*a.LastModified)\n\t})\n\n\t// remove old versions\n\tfor i, o := range objects {\n\t\tctx := log.WithFields(log.Fields{\n\t\t\t\"index\":         i,\n\t\t\t\"key\":           *o.Key,\n\t\t\t\"size\":          *o.Size,\n\t\t\t\"last_modified\": *o.LastModified,\n\t\t})\n\n\t\tif i < versions {\n\t\t\tctx.Debug(\"retain\")\n\t\t\tcontinue\n\t\t}\n\n\t\tctx.Debug(\"remove\")\n\t\tsize += *o.Size\n\t\tcount++\n\n\t\t_, err := s.DeleteObject(&s3.DeleteObjectInput{\n\t\t\tBucket: b,\n\t\t\tKey:    o.Key,\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"removing object\")\n\t\t}\n\t}\n\n\tp.events.Emit(\"prune.complete\", event.Fields{\n\t\t\"duration\": time.Since(start),\n\t\t\"size\":     size,\n\t\t\"count\":    count,\n\t})\n\n\treturn nil\n}\n"
  },
  {
    "path": "platform/lambda/reporter/reporter.go",
    "content": "package reporter\n\nimport \"strings\"\n\n// TODO: move most of reporting here\n\n// types map.\nvar types = map[string]string{\n\t\"AWS::CloudFormation::Stack\":       \"Stack\",\n\t\"AWS::Lambda::Alias\":               \"Lambda alias\",\n\t\"AWS::Lambda::Permission\":          \"Lambda permission\",\n\t\"AWS::ApiGateway::RestApi\":         \"API\",\n\t\"AWS::ApiGateway::Method\":          \"API method\",\n\t\"AWS::ApiGateway::Deployment\":      \"API deployment\",\n\t\"AWS::ApiGateway::Resource\":        \"API resource\",\n\t\"AWS::ApiGateway::DomainName\":      \"API domain\",\n\t\"AWS::ApiGateway::BasePathMapping\": \"API mapping\",\n\t\"AWS::Route53::HostedZone\":         \"DNS zone\",\n\t\"AWS::Route53::RecordSet\":          \"DNS record\",\n}\n\n// ResourceType returns a human-friendly resource type name.\nfunc ResourceType(s string) string {\n\tif types[s] != \"\" {\n\t\treturn strings.ToLower(types[s])\n\t}\n\n\treturn s\n}\n"
  },
  {
    "path": "platform/lambda/stack/resources/resources.go",
    "content": "package resources\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/apex/up/config\"\n\t\"github.com/apex/up/internal/util\"\n\t\"github.com/aws/aws-sdk-go/service/route53\"\n)\n\n// Map type.\ntype Map map[string]interface{}\n\n// Versions is a map of stage to lambda function version.\ntype Versions map[string]string\n\n// Config for the resource template.\ntype Config struct {\n\t// Zones already present in route53. This is used to\n\t// ensure that existing zones previously set up, or\n\t// automatically configured when purchasing a domain\n\t// are not duplicated.\n\tZones []*route53.HostedZone\n\n\t// Versions map used to maintain the correct lambda\n\t// function aliases when updating a stack.\n\tVersions Versions\n\n\t*up.Config\n}\n\n// New template.\nfunc New(c *Config) map[string]interface{} {\n\treturn Map{\n\t\t\"AWSTemplateFormatVersion\": \"2010-09-09\",\n\t\t\"Parameters\":               parameters(c),\n\t\t\"Outputs\":                  outputs(c),\n\t\t\"Resources\":                resources(c),\n\t}\n}\n\n// ref of id.\nfunc ref(id string) Map {\n\treturn Map{\n\t\t\"Ref\": id,\n\t}\n}\n\n// get value from named ref.\nfunc get(name, value string) Map {\n\treturn Map{\n\t\t\"Fn::GetAtt\": []string{\n\t\t\tname,\n\t\t\tvalue,\n\t\t},\n\t}\n}\n\n// join strings with delim.\nfunc join(delim string, s ...interface{}) Map {\n\treturn Map{\n\t\t\"Fn::Join\": []interface{}{\n\t\t\tdelim,\n\t\t\ts,\n\t\t},\n\t}\n}\n\n// stageVariable by name.\nfunc stageVariable(name string) string {\n\treturn fmt.Sprintf(\"${stageVariables.%s}\", name)\n}\n\n// lambda ARN for function name.\nfunc lambdaArn(name string) Map {\n\treturn join(\":\", \"arn\", \"aws\", \"lambda\", ref(\"AWS::Region\"), ref(\"AWS::AccountId\"), \"function\", ref(name))\n}\n\n// lambda ARN for function name with qualifier.\nfunc lambdaArnQualifier(name, qualifier string) Map {\n\treturn join(\":\", \"arn\", \"aws\", \"lambda\", ref(\"AWS::Region\"), ref(\"AWS::AccountId\"), \"function\", join(\":\", ref(name), qualifier))\n}\n\n// getZone returns a zone by domain or nil.\nfunc getZone(c *Config, domain string) *route53.HostedZone {\n\tfor _, z := range c.Zones {\n\t\tif *z.Name == domain+\".\" {\n\t\t\treturn z\n\t\t}\n\t}\n\treturn nil\n}\n\n// dnsZone returns the ref to a new zone, or id to an existing zone.\nfunc dnsZone(c *Config, m Map, domain string) interface{} {\n\t// already exists\n\tif z := getZone(c, domain); z != nil {\n\t\treturn *z.Id\n\t}\n\n\tid := util.Camelcase(\"dns_zone_%s\", domain)\n\n\t// already registered for creation\n\tif m[id] != nil {\n\t\treturn ref(id)\n\t}\n\n\t// new zone\n\tm[id] = Map{\n\t\t\"Type\":                \"AWS::Route53::HostedZone\",\n\t\t\"DeletionPolicy\":      \"Retain\",\n\t\t\"UpdateReplacePolicy\": \"Retain\",\n\t\t\"Properties\": Map{\n\t\t\t\"Name\": domain,\n\t\t},\n\t}\n\n\treturn ref(id)\n}\n\n// api sets up the app resources.\nfunc api(c *Config, m Map) {\n\tm[\"Api\"] = Map{\n\t\t\"Type\": \"AWS::ApiGateway::RestApi\",\n\t\t\"Properties\": Map{\n\t\t\t\"Name\":        ref(\"Name\"),\n\t\t\t\"Description\": util.ManagedByUp(c.Description),\n\t\t\t\"BinaryMediaTypes\": []string{\n\t\t\t\t\"*/*\",\n\t\t\t},\n\t\t},\n\t}\n\n\tintegration := Map{\n\t\t\"Type\":                  \"AWS_PROXY\",\n\t\t\"IntegrationHttpMethod\": \"POST\",\n\t\t\"Uri\": join(\"\",\n\t\t\t\"arn:aws:apigateway:\",\n\t\t\tref(\"AWS::Region\"),\n\t\t\t\":lambda:path/2015-03-31/functions/\",\n\t\t\tlambdaArnQualifier(\"FunctionName\", stageVariable(\"qualifier\")),\n\t\t\t\"/invocations\"),\n\t}\n\n\tm[\"ApiRootMethod\"] = Map{\n\t\t\"Type\": \"AWS::ApiGateway::Method\",\n\t\t\"Properties\": Map{\n\t\t\t\"RestApiId\":         ref(\"Api\"),\n\t\t\t\"ResourceId\":        get(\"Api\", \"RootResourceId\"),\n\t\t\t\"HttpMethod\":        \"ANY\",\n\t\t\t\"AuthorizationType\": \"NONE\",\n\t\t\t\"Integration\":       integration,\n\t\t},\n\t}\n\n\tm[\"ApiProxyResource\"] = Map{\n\t\t\"Type\": \"AWS::ApiGateway::Resource\",\n\t\t\"Properties\": Map{\n\t\t\t\"RestApiId\": ref(\"Api\"),\n\t\t\t\"ParentId\":  get(\"Api\", \"RootResourceId\"),\n\t\t\t\"PathPart\":  \"{proxy+}\",\n\t\t},\n\t}\n\n\tm[\"ApiProxyMethod\"] = Map{\n\t\t\"Type\": \"AWS::ApiGateway::Method\",\n\t\t\"Properties\": Map{\n\t\t\t\"RestApiId\":         ref(\"Api\"),\n\t\t\t\"ResourceId\":        ref(\"ApiProxyResource\"),\n\t\t\t\"HttpMethod\":        \"ANY\",\n\t\t\t\"AuthorizationType\": \"NONE\",\n\t\t\t\"Integration\":       integration,\n\t\t},\n\t}\n\n\tstages(c, m)\n}\n\n// stages sets up the stage specific resources.\nfunc stages(c *Config, m Map) {\n\tfor _, s := range c.Stages.List() {\n\t\tif s.IsRemote() {\n\t\t\tstage(c, s, m)\n\t\t}\n\t}\n}\n\n// stage sets up the stage specific resources.\nfunc stage(c *Config, s *config.Stage, m Map) {\n\taliasID := stageAlias(c, s, m)\n\tdeploymentID := stageDeployment(c, s, m, aliasID)\n\tstagePermissions(c, s, m, aliasID)\n\tstageDomain(c, s, m, deploymentID)\n}\n\n// stageAlias sets up the lambda alias and deployment and returns the alias id.\nfunc stageAlias(c *Config, s *config.Stage, m Map) string {\n\tid := util.Camelcase(\"api_function_alias_%s\", s.Name)\n\tversion, ok := c.Versions[s.Name]\n\n\tif !ok {\n\t\tpanic(fmt.Sprintf(\"stage %q is missing a function version mapping\", s.Name))\n\t}\n\n\tm[id] = Map{\n\t\t\"Type\": \"AWS::Lambda::Alias\",\n\t\t\"Properties\": Map{\n\t\t\t\"Name\":            s.Name,\n\t\t\t\"Description\":     util.ManagedByUp(\"\"),\n\t\t\t\"FunctionName\":    ref(\"FunctionName\"),\n\t\t\t\"FunctionVersion\": version,\n\t\t},\n\t}\n\n\treturn id\n}\n\n// stagePermissions sets up the lambda:invokeFunction permissions for API Gateway.\nfunc stagePermissions(c *Config, s *config.Stage, m Map, aliasID string) {\n\tid := util.Camelcase(\"api_lambda_permission_%s\", s.Name)\n\n\tm[id] = Map{\n\t\t\"Type\":      \"AWS::Lambda::Permission\",\n\t\t\"DependsOn\": aliasID,\n\t\t\"Properties\": Map{\n\t\t\t\"Action\":       \"lambda:invokeFunction\",\n\t\t\t\"FunctionName\": lambdaArnQualifier(\"FunctionName\", s.Name),\n\t\t\t\"Principal\":    \"apigateway.amazonaws.com\",\n\t\t\t\"SourceArn\": join(\"\",\n\t\t\t\t\"arn:aws:execute-api\",\n\t\t\t\t\":\",\n\t\t\t\tref(\"AWS::Region\"),\n\t\t\t\t\":\",\n\t\t\t\tref(\"AWS::AccountId\"),\n\t\t\t\t\":\",\n\t\t\t\tref(\"Api\"),\n\t\t\t\t\"/*\"),\n\t\t},\n\t}\n}\n\n// stageDeployment sets up the API Gateway deployment.\nfunc stageDeployment(c *Config, s *config.Stage, m Map, aliasID string) string {\n\tid := util.Camelcase(\"api_deployment_%s\", s.Name)\n\n\tm[id] = Map{\n\t\t\"Type\":      \"AWS::ApiGateway::Deployment\",\n\t\t\"DependsOn\": []string{\"ApiRootMethod\", \"ApiProxyMethod\", aliasID},\n\t\t\"Properties\": Map{\n\t\t\t\"RestApiId\": ref(\"Api\"),\n\t\t\t\"StageName\": s.Name,\n\t\t\t\"StageDescription\": Map{\n\t\t\t\t\"Variables\": Map{\n\t\t\t\t\t\"qualifier\": s.Name,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\treturn id\n}\n\n// stageDomain sets up a custom domain, dns record and path mapping.\nfunc stageDomain(c *Config, s *config.Stage, m Map, deploymentID string) {\n\tif s.Domain == \"\" {\n\t\treturn\n\t}\n\n\tid := util.Camelcase(\"api_domain_%s\", s.Name)\n\n\tm[id] = Map{\n\t\t\"Type\": \"AWS::ApiGateway::DomainName\",\n\t\t\"Properties\": Map{\n\t\t\t\"CertificateArn\": s.Cert,\n\t\t\t\"DomainName\":     s.Domain,\n\t\t},\n\t}\n\n\tstagePathMapping(c, s, m, deploymentID, id)\n\n\tif s.Zone != false {\n\t\tstageDNSRecord(c, s, m, id)\n\t}\n}\n\n// stagePathMapping sets up the stage deployment mapping.\nfunc stagePathMapping(c *Config, s *config.Stage, m Map, deploymentID, domainID string) {\n\tid := util.Camelcase(\"api_domain_%s_path_mapping\", s.Name)\n\n\tm[id] = Map{\n\t\t\"Type\":      \"AWS::ApiGateway::BasePathMapping\",\n\t\t\"DependsOn\": []string{deploymentID, domainID},\n\t\t\"Properties\": Map{\n\t\t\t\"DomainName\": s.Domain,\n\t\t\t\"BasePath\":   util.BasePath(s.Path),\n\t\t\t\"RestApiId\":  ref(\"Api\"),\n\t\t\t\"Stage\":      s.Name,\n\t\t},\n\t}\n}\n\n// stageDNSRecord sets up an ALIAS record and zone if necessary for a custom domain.\nfunc stageDNSRecord(c *Config, s *config.Stage, m Map, domainID string) {\n\tid := util.Camelcase(\"dns_zone_%s_record_%s\", util.Domain(s.Domain), s.Domain)\n\tzoneName := util.Domain(s.Domain)\n\n\t// explicit .zone was specified\n\tif s, ok := s.Zone.(string); ok {\n\t\tzoneName = s\n\t}\n\n\tzone := dnsZone(c, m, zoneName)\n\n\tm[id] = Map{\n\t\t\"Type\": \"AWS::Route53::RecordSet\",\n\t\t\"Properties\": Map{\n\t\t\t\"Name\":         s.Domain,\n\t\t\t\"Type\":         \"A\",\n\t\t\t\"Comment\":      util.ManagedByUp(\"\"),\n\t\t\t\"HostedZoneId\": zone,\n\t\t\t\"AliasTarget\": Map{\n\t\t\t\t\"DNSName\":      get(domainID, \"DistributionDomainName\"),\n\t\t\t\t\"HostedZoneId\": \"Z2FDTNDATAQYW2\",\n\t\t\t},\n\t\t},\n\t}\n}\n\n// dns setups the the user-defined DNS zones and records.\nfunc dns(c *Config, m Map) {\n\tfor _, z := range c.DNS.Zones {\n\t\tzone := dnsZone(c, m, z.Name)\n\n\t\tfor _, r := range z.Records {\n\t\t\tid := util.Camelcase(\"dns_zone_%s_record_%s_%s\", z.Name, r.Name, r.Type)\n\n\t\t\tm[id] = Map{\n\t\t\t\t\"Type\": \"AWS::Route53::RecordSet\",\n\t\t\t\t\"Properties\": Map{\n\t\t\t\t\t\"Name\":            r.Name,\n\t\t\t\t\t\"Type\":            r.Type,\n\t\t\t\t\t\"TTL\":             strconv.Itoa(r.TTL),\n\t\t\t\t\t\"ResourceRecords\": r.Value,\n\t\t\t\t\t\"HostedZoneId\":    zone,\n\t\t\t\t\t\"Comment\":         util.ManagedByUp(\"\"),\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\t}\n}\n\n// resources of the stack.\nfunc resources(c *Config) Map {\n\tm := Map{}\n\tapi(c, m)\n\tdns(c, m)\n\treturn m\n}\n\n// parameters of the stack.\nfunc parameters(c *Config) Map {\n\treturn Map{\n\t\t\"Name\": Map{\n\t\t\t\"Description\": \"Name of application\",\n\t\t\t\"Type\":        \"String\",\n\t\t},\n\t\t\"FunctionName\": Map{\n\t\t\t\"Description\": \"Name of application function\",\n\t\t\t\"Type\":        \"String\",\n\t\t},\n\t}\n}\n\n// outputs of the stack.\nfunc outputs(c *Config) Map {\n\treturn Map{\n\t\t\"ApiName\": Map{\n\t\t\t\"Description\": \"API name\",\n\t\t\t\"Value\":       ref(\"Name\"),\n\t\t},\n\t\t\"ApiFunctionName\": Map{\n\t\t\t\"Description\": \"API Lambda function name\",\n\t\t\t\"Value\":       ref(\"FunctionName\"),\n\t\t},\n\t\t\"ApiFunctionArn\": Map{\n\t\t\t\"Description\": \"API Lambda function ARN\",\n\t\t\t\"Value\":       lambdaArn(\"FunctionName\"),\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "platform/lambda/stack/resources/resources_test.go",
    "content": "package resources\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/apex/up\"\n\t\"github.com/apex/up/config\"\n)\n\n// keys returns keys from a map.\nfunc keys(m Map) (v []string) {\n\tfor k := range m {\n\t\tv = append(v, k)\n\t}\n\treturn\n}\n\n// getResource returns resource by name.\nfunc getResource(c *Config, name string) Map {\n\ttmpl := New(c)\n\tr := tmpl[\"Resources\"].(Map)\n\n\tv, ok := r[name].(Map)\n\tif !ok {\n\t\tk := strings.Join(keys(r), \"\\n  - \")\n\t\tpanic(fmt.Sprintf(\"resource %q does not exist in:\\n\\n  - %s\", name, k))\n\t}\n\n\treturn v\n}\n\n// dump a resource to stdout.\nfunc dump(c *Config, name string) {\n\tr := getResource(c, name)\n\t{\n\t\tenc := json.NewEncoder(os.Stdout)\n\t\tenc.SetIndent(\"\", \"  \")\n\t\tenc.Encode(r)\n\t}\n}\n\nfunc Example_api() {\n\tc := &Config{\n\t\tConfig: &up.Config{\n\t\t\tName: \"polls\",\n\t\t},\n\t}\n\n\tdump(c, \"Api\")\n\t// Output:\n\t// {\n\t//   \"Properties\": {\n\t//     \"BinaryMediaTypes\": [\n\t//       \"*/*\"\n\t//     ],\n\t//     \"Description\": \"Managed by Up.\",\n\t//     \"Name\": {\n\t//       \"Ref\": \"Name\"\n\t//     }\n\t//   },\n\t//   \"Type\": \"AWS::ApiGateway::RestApi\"\n\t// }\n}\n\nfunc Example_apiRootMethod() {\n\tc := &Config{\n\t\tConfig: &up.Config{\n\t\t\tName: \"polls\",\n\t\t},\n\t}\n\n\tdump(c, \"ApiRootMethod\")\n\t// Output:\n\t// {\n\t//   \"Properties\": {\n\t//     \"AuthorizationType\": \"NONE\",\n\t//     \"HttpMethod\": \"ANY\",\n\t//     \"Integration\": {\n\t//       \"IntegrationHttpMethod\": \"POST\",\n\t//       \"Type\": \"AWS_PROXY\",\n\t//       \"Uri\": {\n\t//         \"Fn::Join\": [\n\t//           \"\",\n\t//           [\n\t//             \"arn:aws:apigateway:\",\n\t//             {\n\t//               \"Ref\": \"AWS::Region\"\n\t//             },\n\t//             \":lambda:path/2015-03-31/functions/\",\n\t//             {\n\t//               \"Fn::Join\": [\n\t//                 \":\",\n\t//                 [\n\t//                   \"arn\",\n\t//                   \"aws\",\n\t//                   \"lambda\",\n\t//                   {\n\t//                     \"Ref\": \"AWS::Region\"\n\t//                   },\n\t//                   {\n\t//                     \"Ref\": \"AWS::AccountId\"\n\t//                   },\n\t//                   \"function\",\n\t//                   {\n\t//                     \"Fn::Join\": [\n\t//                       \":\",\n\t//                       [\n\t//                         {\n\t//                           \"Ref\": \"FunctionName\"\n\t//                         },\n\t//                         \"${stageVariables.qualifier}\"\n\t//                       ]\n\t//                     ]\n\t//                   }\n\t//                 ]\n\t//               ]\n\t//             },\n\t//             \"/invocations\"\n\t//           ]\n\t//         ]\n\t//       }\n\t//     },\n\t//     \"ResourceId\": {\n\t//       \"Fn::GetAtt\": [\n\t//         \"Api\",\n\t//         \"RootResourceId\"\n\t//       ]\n\t//     },\n\t//     \"RestApiId\": {\n\t//       \"Ref\": \"Api\"\n\t//     }\n\t//   },\n\t//   \"Type\": \"AWS::ApiGateway::Method\"\n\t// }\n}\n\nfunc Example_apiProxyResource() {\n\tc := &Config{\n\t\tConfig: &up.Config{\n\t\t\tName: \"polls\",\n\t\t},\n\t}\n\n\tdump(c, \"ApiProxyResource\")\n\t// Output:\n\t// {\n\t//   \"Properties\": {\n\t//     \"ParentId\": {\n\t//       \"Fn::GetAtt\": [\n\t//         \"Api\",\n\t//         \"RootResourceId\"\n\t//       ]\n\t//     },\n\t//     \"PathPart\": \"{proxy+}\",\n\t//     \"RestApiId\": {\n\t//       \"Ref\": \"Api\"\n\t//     }\n\t//   },\n\t//   \"Type\": \"AWS::ApiGateway::Resource\"\n\t// }\n}\n\nfunc Example_apiProxyMethod() {\n\tc := &Config{\n\t\tConfig: &up.Config{\n\t\t\tName: \"polls\",\n\t\t},\n\t}\n\n\tdump(c, \"ApiProxyMethod\")\n\t// Output:\n\t// {\n\t//   \"Properties\": {\n\t//     \"AuthorizationType\": \"NONE\",\n\t//     \"HttpMethod\": \"ANY\",\n\t//     \"Integration\": {\n\t//       \"IntegrationHttpMethod\": \"POST\",\n\t//       \"Type\": \"AWS_PROXY\",\n\t//       \"Uri\": {\n\t//         \"Fn::Join\": [\n\t//           \"\",\n\t//           [\n\t//             \"arn:aws:apigateway:\",\n\t//             {\n\t//               \"Ref\": \"AWS::Region\"\n\t//             },\n\t//             \":lambda:path/2015-03-31/functions/\",\n\t//             {\n\t//               \"Fn::Join\": [\n\t//                 \":\",\n\t//                 [\n\t//                   \"arn\",\n\t//                   \"aws\",\n\t//                   \"lambda\",\n\t//                   {\n\t//                     \"Ref\": \"AWS::Region\"\n\t//                   },\n\t//                   {\n\t//                     \"Ref\": \"AWS::AccountId\"\n\t//                   },\n\t//                   \"function\",\n\t//                   {\n\t//                     \"Fn::Join\": [\n\t//                       \":\",\n\t//                       [\n\t//                         {\n\t//                           \"Ref\": \"FunctionName\"\n\t//                         },\n\t//                         \"${stageVariables.qualifier}\"\n\t//                       ]\n\t//                     ]\n\t//                   }\n\t//                 ]\n\t//               ]\n\t//             },\n\t//             \"/invocations\"\n\t//           ]\n\t//         ]\n\t//       }\n\t//     },\n\t//     \"ResourceId\": {\n\t//       \"Ref\": \"ApiProxyResource\"\n\t//     },\n\t//     \"RestApiId\": {\n\t//       \"Ref\": \"Api\"\n\t//     }\n\t//   },\n\t//   \"Type\": \"AWS::ApiGateway::Method\"\n\t// }\n}\n\nfunc Example_stageAlias() {\n\tc := &Config{\n\t\tConfig: &up.Config{\n\t\t\tName: \"polls\",\n\t\t\tStages: config.Stages{\n\t\t\t\t\"production\": &config.Stage{\n\t\t\t\t\tName: \"production\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tVersions: Versions{\n\t\t\t\"production\": \"15\",\n\t\t},\n\t}\n\n\tdump(c, \"ApiFunctionAliasProduction\")\n\t// Output:\n\t// {\n\t//   \"Properties\": {\n\t//     \"Description\": \"Managed by Up.\",\n\t//     \"FunctionName\": {\n\t//       \"Ref\": \"FunctionName\"\n\t//     },\n\t//     \"FunctionVersion\": \"15\",\n\t//     \"Name\": \"production\"\n\t//   },\n\t//   \"Type\": \"AWS::Lambda::Alias\"\n\t// }\n}\n\nfunc Example_stagePermission() {\n\tc := &Config{\n\t\tConfig: &up.Config{\n\t\t\tName: \"polls\",\n\t\t\tStages: config.Stages{\n\t\t\t\t\"production\": &config.Stage{\n\t\t\t\t\tName: \"production\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tVersions: Versions{\n\t\t\t\"production\": \"15\",\n\t\t},\n\t}\n\n\tdump(c, \"ApiLambdaPermissionProduction\")\n\t// Output:\n\t// {\n\t//   \"DependsOn\": \"ApiFunctionAliasProduction\",\n\t//   \"Properties\": {\n\t//     \"Action\": \"lambda:invokeFunction\",\n\t//     \"FunctionName\": {\n\t//       \"Fn::Join\": [\n\t//         \":\",\n\t//         [\n\t//           \"arn\",\n\t//           \"aws\",\n\t//           \"lambda\",\n\t//           {\n\t//             \"Ref\": \"AWS::Region\"\n\t//           },\n\t//           {\n\t//             \"Ref\": \"AWS::AccountId\"\n\t//           },\n\t//           \"function\",\n\t//           {\n\t//             \"Fn::Join\": [\n\t//               \":\",\n\t//               [\n\t//                 {\n\t//                   \"Ref\": \"FunctionName\"\n\t//                 },\n\t//                 \"production\"\n\t//               ]\n\t//             ]\n\t//           }\n\t//         ]\n\t//       ]\n\t//     },\n\t//     \"Principal\": \"apigateway.amazonaws.com\",\n\t//     \"SourceArn\": {\n\t//       \"Fn::Join\": [\n\t//         \"\",\n\t//         [\n\t//           \"arn:aws:execute-api\",\n\t//           \":\",\n\t//           {\n\t//             \"Ref\": \"AWS::Region\"\n\t//           },\n\t//           \":\",\n\t//           {\n\t//             \"Ref\": \"AWS::AccountId\"\n\t//           },\n\t//           \":\",\n\t//           {\n\t//             \"Ref\": \"Api\"\n\t//           },\n\t//           \"/*\"\n\t//         ]\n\t//       ]\n\t//     }\n\t//   },\n\t//   \"Type\": \"AWS::Lambda::Permission\"\n\t// }\n}\n\nfunc Example_stageDeployment() {\n\tc := &Config{\n\t\tConfig: &up.Config{\n\t\t\tName: \"polls\",\n\t\t\tStages: config.Stages{\n\t\t\t\t\"production\": &config.Stage{\n\t\t\t\t\tName: \"production\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tVersions: Versions{\n\t\t\t\"production\": \"15\",\n\t\t},\n\t}\n\n\tdump(c, \"ApiDeploymentProduction\")\n\t// Output:\n\t// {\n\t//   \"DependsOn\": [\n\t//     \"ApiRootMethod\",\n\t//     \"ApiProxyMethod\",\n\t//     \"ApiFunctionAliasProduction\"\n\t//   ],\n\t//   \"Properties\": {\n\t//     \"RestApiId\": {\n\t//       \"Ref\": \"Api\"\n\t//     },\n\t//     \"StageDescription\": {\n\t//       \"Variables\": {\n\t//         \"qualifier\": \"production\"\n\t//       }\n\t//     },\n\t//     \"StageName\": \"production\"\n\t//   },\n\t//   \"Type\": \"AWS::ApiGateway::Deployment\"\n\t// }\n}\n\nfunc Example_stageDomain() {\n\tc := &Config{\n\t\tConfig: &up.Config{\n\t\t\tName: \"polls\",\n\t\t\tStages: config.Stages{\n\t\t\t\t\"production\": &config.Stage{\n\t\t\t\t\tName:   \"production\",\n\t\t\t\t\tDomain: \"up-example.com\",\n\t\t\t\t\tCert:   \"arn::something\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tVersions: Versions{\n\t\t\t\"production\": \"15\",\n\t\t},\n\t}\n\n\tdump(c, \"ApiDomainProduction\")\n\t// Output:\n\t// \t{\n\t//   \"Properties\": {\n\t//     \"CertificateArn\": \"arn::something\",\n\t//     \"DomainName\": \"up-example.com\"\n\t//   },\n\t//   \"Type\": \"AWS::ApiGateway::DomainName\"\n\t// }\n}\n\nfunc Example_stagePathMapping() {\n\tc := &Config{\n\t\tConfig: &up.Config{\n\t\t\tName: \"polls\",\n\t\t\tStages: config.Stages{\n\t\t\t\t\"production\": &config.Stage{\n\t\t\t\t\tName:   \"production\",\n\t\t\t\t\tDomain: \"up-example.com\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tVersions: Versions{\n\t\t\t\"production\": \"15\",\n\t\t},\n\t}\n\n\tdump(c, \"ApiDomainProductionPathMapping\")\n\t// Output:\n\t// {\n\t//   \"DependsOn\": [\n\t//     \"ApiDeploymentProduction\",\n\t//     \"ApiDomainProduction\"\n\t//   ],\n\t//   \"Properties\": {\n\t//     \"BasePath\": \"\",\n\t//     \"DomainName\": \"up-example.com\",\n\t//     \"RestApiId\": {\n\t//       \"Ref\": \"Api\"\n\t//     },\n\t//     \"Stage\": \"production\"\n\t//   },\n\t//   \"Type\": \"AWS::ApiGateway::BasePathMapping\"\n\t// }\n}\n\nfunc Example_stageDNSZone() {\n\tc := &Config{\n\t\tConfig: &up.Config{\n\t\t\tName: \"polls\",\n\t\t\tStages: config.Stages{\n\t\t\t\t\"production\": &config.Stage{\n\t\t\t\t\tName:   \"production\",\n\t\t\t\t\tDomain: \"up-example.com\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tVersions: Versions{\n\t\t\t\"production\": \"15\",\n\t\t},\n\t}\n\n\tdump(c, \"DnsZoneUpExampleCom\")\n\t// Output:\n\t// {\n\t//   \"DeletionPolicy\": \"Retain\",\n\t//   \"Properties\": {\n\t//     \"Name\": \"up-example.com\"\n\t//   },\n\t//   \"Type\": \"AWS::Route53::HostedZone\",\n\t//   \"UpdateReplacePolicy\": \"Retain\"\n\t// }\n}\n\nfunc Example_stageDNSZoneRecord() {\n\tc := &Config{\n\t\tConfig: &up.Config{\n\t\t\tName: \"polls\",\n\t\t\tStages: config.Stages{\n\t\t\t\t\"production\": &config.Stage{\n\t\t\t\t\tName:   \"production\",\n\t\t\t\t\tDomain: \"up-example.com\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tVersions: Versions{\n\t\t\t\"production\": \"15\",\n\t\t},\n\t}\n\n\tdump(c, \"DnsZoneUpExampleComRecordUpExampleCom\")\n\t// Output:\n\t// {\n\t//   \"Properties\": {\n\t//     \"AliasTarget\": {\n\t//       \"DNSName\": {\n\t//         \"Fn::GetAtt\": [\n\t//           \"ApiDomainProduction\",\n\t//           \"DistributionDomainName\"\n\t//         ]\n\t//       },\n\t//       \"HostedZoneId\": \"Z2FDTNDATAQYW2\"\n\t//     },\n\t//     \"Comment\": \"Managed by Up.\",\n\t//     \"HostedZoneId\": {\n\t//       \"Ref\": \"DnsZoneUpExampleCom\"\n\t//     },\n\t//     \"Name\": \"up-example.com\",\n\t//     \"Type\": \"A\"\n\t//   },\n\t//   \"Type\": \"AWS::Route53::RecordSet\"\n\t// }\n}\n\nfunc Example_dnsZone() {\n\tc := &Config{\n\t\tConfig: &up.Config{\n\t\t\tName: \"polls\",\n\t\t\tDNS: config.DNS{\n\t\t\t\tZones: []*config.Zone{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"up-example.com\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tdump(c, \"DnsZoneUpExampleCom\")\n\t// Output:\n\t// {\n\t//   \"DeletionPolicy\": \"Retain\",\n\t//   \"Properties\": {\n\t//     \"Name\": \"up-example.com\"\n\t//   },\n\t//   \"Type\": \"AWS::Route53::HostedZone\",\n\t//   \"UpdateReplacePolicy\": \"Retain\"\n\t// }\n}\n\nfunc Example_dnsZoneRecord() {\n\tc := &Config{\n\t\tConfig: &up.Config{\n\t\t\tName: \"polls\",\n\t\t\tDNS: config.DNS{\n\t\t\t\tZones: []*config.Zone{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"up-example.com\",\n\t\t\t\t\t\tRecords: []*config.Record{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  \"blog.up-example.com\",\n\t\t\t\t\t\t\t\tType:  \"CNAME\",\n\t\t\t\t\t\t\t\tTTL:   600,\n\t\t\t\t\t\t\t\tValue: []string{\"example.medium.com\"},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tdump(c, \"DnsZoneUpExampleComRecordBlogUpExampleComCNAME\")\n\t// Output:\n\t// {\n\t//   \"Properties\": {\n\t//     \"Comment\": \"Managed by Up.\",\n\t//     \"HostedZoneId\": {\n\t//       \"Ref\": \"DnsZoneUpExampleCom\"\n\t//     },\n\t//     \"Name\": \"blog.up-example.com\",\n\t//     \"ResourceRecords\": [\n\t//       \"example.medium.com\"\n\t//     ],\n\t//     \"TTL\": \"600\",\n\t//     \"Type\": \"CNAME\"\n\t//   },\n\t//   \"Type\": \"AWS::Route53::RecordSet\"\n\t// }\n}\n"
  },
  {
    "path": "platform/lambda/stack/stack.go",
    "content": "// Package stack provides CloudFormation stack support.\npackage stack\n\nimport (\n\t\"encoding/json\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/aws/session\"\n\t\"github.com/aws/aws-sdk-go/service/apigateway\"\n\t\"github.com/aws/aws-sdk-go/service/cloudformation\"\n\t\"github.com/aws/aws-sdk-go/service/lambda\"\n\t\"github.com/aws/aws-sdk-go/service/route53\"\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/apex/log\"\n\t\"github.com/apex/up\"\n\t\"github.com/apex/up/config\"\n\t\"github.com/apex/up/internal/util\"\n\t\"github.com/apex/up/platform/event\"\n\t\"github.com/apex/up/platform/lambda/stack/resources\"\n)\n\n// TODO: refactor a lot\n// TODO: backoff\n// TODO: profile changeset name and description flags\n// TODO: flags for changeset name / description\n\n// defaultChangeset name.\nvar defaultChangeset = \"changes\"\n\n// Map type.\ntype Map = resources.Map\n\n// Stack represents a single CloudFormation stack.\ntype Stack struct {\n\tclient     *cloudformation.CloudFormation\n\tlambda     *lambda.Lambda\n\troute53    *route53.Route53\n\tapigateway *apigateway.APIGateway\n\tevents     event.Events\n\tzones      []*route53.HostedZone\n\tconfig     *up.Config\n}\n\n// New stack.\nfunc New(c *up.Config, events event.Events, zones []*route53.HostedZone, region string) *Stack {\n\tsess := session.New(aws.NewConfig().WithRegion(region))\n\treturn &Stack{\n\t\tclient:     cloudformation.New(sess),\n\t\tlambda:     lambda.New(sess),\n\t\troute53:    route53.New(sess),\n\t\tapigateway: apigateway.New(sess),\n\t\tevents:     events,\n\t\tzones:      zones,\n\t\tconfig:     c,\n\t}\n}\n\n// template returns a configured resource template.\nfunc (s *Stack) template(versions resources.Versions) Map {\n\treturn resources.New(&resources.Config{\n\t\tConfig:   s.config,\n\t\tZones:    s.zones,\n\t\tVersions: versions,\n\t})\n}\n\n// Create the stack.\nfunc (s *Stack) Create(versions resources.Versions) error {\n\tc := s.config\n\ttmpl := s.template(versions)\n\tname := c.Name\n\n\tb, err := json.MarshalIndent(tmpl, \"\", \"  \")\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"marshaling\")\n\t}\n\n\t_, err = s.client.CreateStack(&cloudformation.CreateStackInput{\n\t\tStackName:        &name,\n\t\tTemplateBody:     aws.String(string(b)),\n\t\tTimeoutInMinutes: aws.Int64(60),\n\t\tDisableRollback:  aws.Bool(true),\n\t\tCapabilities:     aws.StringSlice([]string{\"CAPABILITY_NAMED_IAM\"}),\n\t\tParameters: []*cloudformation.Parameter{\n\t\t\t{\n\t\t\t\tParameterKey:   aws.String(\"Name\"),\n\t\t\t\tParameterValue: &name,\n\t\t\t},\n\t\t\t{\n\t\t\t\tParameterKey:   aws.String(\"FunctionName\"),\n\t\t\t\tParameterValue: &name,\n\t\t\t},\n\t\t},\n\t})\n\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"creating stack\")\n\t}\n\n\tif err := s.report(resourceStateFromTemplate(tmpl, CreateComplete)); err != nil {\n\t\treturn errors.Wrap(err, \"reporting\")\n\t}\n\n\tstack, err := s.getStack()\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"fetching stack\")\n\t}\n\n\tstatus := Status(*stack.StackStatus)\n\tif status.State() == Failure {\n\t\treturn errors.New(*stack.StackStatusReason)\n\t}\n\n\treturn nil\n}\n\n// Delete the stack, optionally waiting for completion.\nfunc (s *Stack) Delete(versions resources.Versions, wait bool) error {\n\t_, err := s.client.DeleteStack(&cloudformation.DeleteStackInput{\n\t\tStackName: &s.config.Name,\n\t})\n\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"deleting\")\n\t}\n\n\tif wait {\n\t\ttmpl := s.template(versions)\n\t\tif err := s.report(resourceStateFromTemplate(tmpl, DeleteComplete)); err != nil {\n\t\t\treturn errors.Wrap(err, \"reporting\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Show resources.\nfunc (s *Stack) Show() error {\n\tdefer s.events.Time(\"platform.stack.show\", nil)()\n\n\t// show stack status\n\tstack, err := s.getStack()\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"fetching stack\")\n\t}\n\n\ts.events.Emit(\"platform.stack.show.stack\", event.Fields{\n\t\t\"stack\": stack,\n\t})\n\n\t// stages\n\tfor _, stage := range s.config.Stages.List() {\n\t\tif stage.Domain == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\ts.events.Emit(\"platform.stack.show.stage\", event.Fields{\n\t\t\t\"name\":   stage.Name,\n\t\t\t\"domain\": stage.Domain,\n\t\t})\n\n\t\t// show cloudfront endpoint\n\t\tif err := s.showCloudfront(stage); err != nil {\n\t\t\tlog.WithError(err).Debug(\"showing cloudfront\")\n\t\t}\n\n\t\t// show function version\n\t\tif err := s.showVersion(stage); err != nil {\n\t\t\tlog.WithError(err).Debug(\"showing version\")\n\t\t}\n\n\t\t// show nameservers\n\t\tif err := s.showNameservers(stage); err != nil {\n\t\t\treturn errors.Wrap(err, \"showing nameservers\")\n\t\t}\n\t}\n\n\t// skip events if everything is ok\n\tif Status(*stack.StackStatus).State() == Success {\n\t\treturn nil\n\t}\n\n\t// show events\n\ts.events.Emit(\"platform.stack.show.stack.events\", nil)\n\n\tevents, err := s.getFailedEvents()\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"fetching latest events\")\n\t}\n\n\tfor _, e := range events {\n\t\tif *e.LogicalResourceId == s.config.Name {\n\t\t\tcontinue\n\t\t}\n\n\t\ts.events.Emit(\"platform.stack.show.stack.event\", event.Fields{\n\t\t\t\"event\": e,\n\t\t})\n\t}\n\n\treturn nil\n}\n\n// Plan changes.\nfunc (s *Stack) Plan(versions resources.Versions) error {\n\tc := s.config\n\ttmpl := s.template(versions)\n\tname := c.Name\n\n\tb, err := json.MarshalIndent(tmpl, \"\", \"  \")\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"marshaling\")\n\t}\n\n\tdefer s.events.Time(\"platform.stack.plan\", nil)\n\n\tlog.Debug(\"deleting changeset\")\n\t_, err = s.client.DeleteChangeSet(&cloudformation.DeleteChangeSetInput{\n\t\tStackName:     &name,\n\t\tChangeSetName: &defaultChangeset,\n\t})\n\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"deleting changeset\")\n\t}\n\n\tlog.Debug(\"creating changeset\")\n\t_, err = s.client.CreateChangeSet(&cloudformation.CreateChangeSetInput{\n\t\tStackName:     &name,\n\t\tChangeSetName: &defaultChangeset,\n\t\tTemplateBody:  aws.String(string(b)),\n\t\tCapabilities:  aws.StringSlice([]string{\"CAPABILITY_NAMED_IAM\"}),\n\t\tChangeSetType: aws.String(\"UPDATE\"),\n\t\tDescription:   aws.String(\"Managed by Up.\"),\n\t\tParameters: []*cloudformation.Parameter{\n\t\t\t{\n\t\t\t\tParameterKey:   aws.String(\"Name\"),\n\t\t\t\tParameterValue: &name,\n\t\t\t},\n\t\t\t{\n\t\t\t\tParameterKey:   aws.String(\"FunctionName\"),\n\t\t\t\tParameterValue: &name,\n\t\t\t},\n\t\t},\n\t})\n\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"creating changeset\")\n\t}\n\n\tvar next *string\n\n\tfor {\n\t\tlog.Debug(\"describing changeset\")\n\t\tres, err := s.client.DescribeChangeSet(&cloudformation.DescribeChangeSetInput{\n\t\t\tStackName:     &name,\n\t\t\tChangeSetName: &defaultChangeset,\n\t\t\tNextToken:     next,\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"describing changeset\")\n\t\t}\n\n\t\tstatus := Status(*res.Status)\n\n\t\tif status.State() == Failure {\n\t\t\tif _, err := s.client.DeleteChangeSet(&cloudformation.DeleteChangeSetInput{\n\t\t\t\tStackName:     &name,\n\t\t\t\tChangeSetName: &defaultChangeset,\n\t\t\t}); err != nil {\n\t\t\t\treturn errors.Wrap(err, \"deleting changeset\")\n\t\t\t}\n\n\t\t\treturn errors.New(*res.StatusReason)\n\t\t}\n\n\t\tif !status.IsDone() {\n\t\t\tlog.Debug(\"waiting for completion\")\n\t\t\ttime.Sleep(750 * time.Millisecond)\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, c := range res.Changes {\n\t\t\ts.events.Emit(\"platform.stack.plan.change\", event.Fields{\n\t\t\t\t\"change\": c,\n\t\t\t})\n\t\t}\n\n\t\tnext = res.NextToken\n\n\t\tif next == nil {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Apply changes.\nfunc (s *Stack) Apply() error {\n\tc := s.config\n\tname := c.Name\n\n\tres, err := s.client.DescribeChangeSet(&cloudformation.DescribeChangeSetInput{\n\t\tStackName:     &name,\n\t\tChangeSetName: &defaultChangeset,\n\t})\n\n\tif isNotFound(err) {\n\t\treturn errors.Errorf(\"changeset does not exist, run `up stack plan` first\")\n\t}\n\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"describing changeset\")\n\t}\n\n\tdefer s.events.Time(\"platform.stack.apply\", event.Fields{\n\t\t\"changes\": len(res.Changes),\n\t})()\n\n\t_, err = s.client.ExecuteChangeSet(&cloudformation.ExecuteChangeSetInput{\n\t\tStackName:     &name,\n\t\tChangeSetName: &defaultChangeset,\n\t})\n\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"executing changeset\")\n\t}\n\n\tif err := s.report(resourceStateFromChanges(res.Changes)); err != nil {\n\t\treturn errors.Wrap(err, \"reporting\")\n\t}\n\n\treturn nil\n}\n\n// report events with a map of desired stats from logical or physical id,\n// any resources not mapped are ignored as they do not contribute to changes.\nfunc (s *Stack) report(states map[string]Status) error {\n\tdefer s.events.Time(\"platform.stack.report\", event.Fields{\n\t\t\"total\":    len(states),\n\t\t\"complete\": 0,\n\t})()\n\n\tticker := time.NewTicker(time.Second)\n\tdefer ticker.Stop()\n\n\tfor range ticker.C {\n\t\tstack, err := s.getStack()\n\n\t\tif util.IsNotFound(err) {\n\t\t\treturn nil\n\t\t}\n\n\t\tif util.IsThrottled(err) {\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t\tcontinue\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"fetching stack\")\n\t\t}\n\n\t\tstatus := Status(*stack.StackStatus)\n\n\t\tif status.IsDone() {\n\t\t\treturn nil\n\t\t}\n\n\t\tres, err := s.client.DescribeStackResources(&cloudformation.DescribeStackResourcesInput{\n\t\t\tStackName: &s.config.Name,\n\t\t})\n\n\t\tif util.IsThrottled(err) {\n\t\t\ttime.Sleep(time.Second * 3)\n\t\t\tcontinue\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"describing stack resources\")\n\t\t}\n\n\t\tcomplete := len(resourcesCompleted(res.StackResources, states))\n\n\t\ts.events.Emit(\"platform.stack.report.event\", event.Fields{\n\t\t\t\"total\":    len(states),\n\t\t\t\"complete\": complete,\n\t\t})\n\t}\n\n\treturn nil\n}\n\n// showVersion emits events for showing the Lambda version.\nfunc (s *Stack) showVersion(stage *config.Stage) error {\n\tres, err := s.lambda.GetAlias(&lambda.GetAliasInput{\n\t\tFunctionName: &s.config.Name,\n\t\tName:         &stage.Name,\n\t})\n\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"fetching alias\")\n\t}\n\n\ts.events.Emit(\"platform.stack.show.version\", event.Fields{\n\t\t\"domain\":  stage.Domain,\n\t\t\"version\": *res.FunctionVersion,\n\t})\n\n\treturn nil\n}\n\n// showCloudfront emits events for listing cloudfront end-points.\nfunc (s *Stack) showCloudfront(stage *config.Stage) error {\n\tif stage.Domain == \"\" {\n\t\treturn nil\n\t}\n\n\tres, err := s.apigateway.GetDomainName(&apigateway.GetDomainNameInput{\n\t\tDomainName: &stage.Domain,\n\t})\n\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"getting domain mapping\")\n\t}\n\n\tif res.DistributionDomainName == nil {\n\t\treturn nil\n\t}\n\n\ts.events.Emit(\"platform.stack.show.domain\", event.Fields{\n\t\t\"domain\":   stage.Domain,\n\t\t\"endpoint\": *res.DistributionDomainName,\n\t})\n\n\treturn nil\n}\n\n// showNameservers emits events for listing name servers.\nfunc (s *Stack) showNameservers(stage *config.Stage) error {\n\tif stage.Domain == \"\" {\n\t\treturn nil\n\t}\n\n\tres, err := s.route53.ListHostedZonesByName(&route53.ListHostedZonesByNameInput{\n\t\tDNSName:  &stage.Domain,\n\t\tMaxItems: aws.String(\"1\"),\n\t})\n\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"listing hosted zone\")\n\t}\n\n\tif len(res.HostedZones) == 0 {\n\t\treturn nil\n\t}\n\n\tz := res.HostedZones[0]\n\tif stage.Domain+\".\" != *z.Name {\n\t\treturn nil\n\t}\n\n\tzone, err := s.route53.GetHostedZone(&route53.GetHostedZoneInput{\n\t\tId: z.Id,\n\t})\n\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"fetching hosted zone\")\n\t}\n\n\tvar ns []string\n\n\tfor _, s := range zone.DelegationSet.NameServers {\n\t\tns = append(ns, *s)\n\t}\n\n\ts.events.Emit(\"platform.stack.show.nameservers\", event.Fields{\n\t\t\"nameservers\": ns,\n\t})\n\n\treturn nil\n}\n\n// getStack returns the stack.\nfunc (s *Stack) getStack() (*cloudformation.Stack, error) {\n\tres, err := s.client.DescribeStacks(&cloudformation.DescribeStacksInput{\n\t\tStackName: &s.config.Name,\n\t})\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstack := res.Stacks[0]\n\treturn stack, nil\n}\n\n// getLatestEvents returns the latest events for each resource.\nfunc (s *Stack) getLatestEvents() (v []*cloudformation.StackEvent, err error) {\n\tevents, err := s.getEvents()\n\tif err != nil {\n\t\treturn\n\t}\n\n\thit := make(map[string]bool)\n\n\tfor _, e := range events {\n\t\tid := *e.LogicalResourceId\n\t\tif hit[id] {\n\t\t\tcontinue\n\t\t}\n\n\t\thit[id] = true\n\t\tv = append(v, e)\n\t}\n\n\treturn\n}\n\n// getFailedEvents returns failed events.\nfunc (s *Stack) getFailedEvents() (v []*cloudformation.StackEvent, err error) {\n\tevents, err := s.getEvents()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, e := range events {\n\t\tif Status(*e.ResourceStatus).State() == Failure {\n\t\t\tv = append(v, e)\n\t\t}\n\t}\n\n\treturn\n}\n\n// getEvents returns events.\nfunc (s *Stack) getEvents() (events []*cloudformation.StackEvent, err error) {\n\tvar next *string\n\n\tfor {\n\t\tres, err := s.client.DescribeStackEvents(&cloudformation.DescribeStackEventsInput{\n\t\t\tStackName: &s.config.Name,\n\t\t\tNextToken: next,\n\t\t})\n\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tevents = append(events, res.StackEvents...)\n\n\t\tnext = res.NextToken\n\n\t\tif next == nil {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn\n}\n\n// resourceStateFromTemplate returns a map of the logical ids from template t, to status s.\nfunc resourceStateFromTemplate(t Map, s Status) map[string]Status {\n\tr := t[\"Resources\"].(Map)\n\tm := make(map[string]Status)\n\n\tfor id := range r {\n\t\tm[id] = s\n\t}\n\n\treturn m\n}\n\n// TODO: ignore deletes since they're in cleanup phase?\n\n// resourceStateFromChanges returns a map of statuses from a changeset.\nfunc resourceStateFromChanges(changes []*cloudformation.Change) map[string]Status {\n\tm := make(map[string]Status)\n\n\tfor _, c := range changes {\n\t\tvar state Status\n\t\tvar id string\n\n\t\tif s := c.ResourceChange.PhysicalResourceId; s != nil {\n\t\t\tid = *s\n\t\t}\n\n\t\tif id == \"\" {\n\t\t\tid = *c.ResourceChange.LogicalResourceId\n\t\t}\n\n\t\tswitch a := *c.ResourceChange.Action; a {\n\t\tcase \"Add\":\n\t\t\tstate = CreateComplete\n\t\tcase \"Modify\":\n\t\t\tstate = UpdateComplete\n\t\tcase \"Remove\":\n\t\t\tstate = DeleteComplete\n\t\tdefault:\n\t\t\tpanic(errors.Errorf(\"unhandled Action %q\", a))\n\t\t}\n\n\t\tm[id] = state\n\t}\n\n\treturn m\n}\n\n// resourcesCompleted returns a map of the completed resources. When the resource is not\n// present in states, it is ignored as no changes are expected.\nfunc resourcesCompleted(resources []*cloudformation.StackResource, states map[string]Status) map[string]*cloudformation.StackResource {\n\tm := make(map[string]*cloudformation.StackResource)\n\n\tfor _, r := range resources {\n\t\tvar expected Status\n\t\tvar id string\n\n\t\t// try physical id first, this is necessary as\n\t\t// replacement of a logical id will cause the id\n\t\t// to appear twice (once for Add once for Remove).\n\t\tif s := r.PhysicalResourceId; s != nil {\n\t\t\tif _, ok := states[*s]; ok {\n\t\t\t\tid = *s\n\t\t\t}\n\t\t}\n\n\t\t// try logical id\n\t\tif s := *r.LogicalResourceId; id == \"\" {\n\t\t\tif _, ok := states[s]; ok {\n\t\t\t\tid = s\n\t\t\t}\n\t\t}\n\n\t\t// expected state\n\t\tif id != \"\" {\n\t\t\texpected = states[id]\n\t\t}\n\n\t\t// matched expected state\n\t\tif expected == Status(*r.ResourceStatus) {\n\t\t\tm[id] = r\n\t\t}\n\t}\n\n\treturn m\n}\n\n// isNotFound returns true if the error indicates a missing changeset.\nfunc isNotFound(err error) bool {\n\treturn err != nil && strings.Contains(err.Error(), \"ChangeSetNotFound\")\n}\n"
  },
  {
    "path": "platform/lambda/stack/stack_test.go",
    "content": "package stack\n\nimport (\n\t\"testing\"\n\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/service/cloudformation\"\n\t\"github.com/tj/assert\"\n)\n\nfunc TestResourcesCompleted(t *testing.T) {\n\tresources := []*cloudformation.StackResource{\n\t\t{\n\t\t\tLogicalResourceId:    aws.String(\"DnsZoneSomethingComRecordApiSomethingCom\"),\n\t\t\tPhysicalResourceId:   aws.String(\"api.something.com\"),\n\t\t\tResourceStatus:       aws.String(\"CREATE_IN_PROGRESS\"),\n\t\t\tResourceStatusReason: aws.String(\"Resource creation Initiated\"),\n\t\t\tResourceType:         aws.String(\"AWS::Route53::RecordSet\"),\n\t\t\tStackId:              aws.String(\"arn:aws:cloudformation:us-west-2:foobarbaz:stack/app/ad3af570-8511-11e7-8832-50d5ca789e4a\"),\n\t\t\tStackName:            aws.String(\"app\"),\n\t\t},\n\t\t{\n\t\t\tLogicalResourceId:  aws.String(\"ApiProxyMethod\"),\n\t\t\tPhysicalResourceId: aws.String(\"app-ApiProx-33K7PKBL7HNI\"),\n\t\t\tResourceStatus:     aws.String(\"CREATE_COMPLETE\"),\n\t\t\tResourceType:       aws.String(\"AWS::ApiGateway::Method\"),\n\t\t\tStackId:            aws.String(\"arn:aws:cloudformation:us-west-2:foobarbaz:stack/app/ad3af570-8511-11e7-8832-50d5ca789e4a\"),\n\t\t\tStackName:          aws.String(\"app\"),\n\t\t},\n\t\t{\n\t\t\tLogicalResourceId: aws.String(\"Another\"),\n\t\t\tResourceStatus:    aws.String(\"CREATE_COMPLETE\"),\n\t\t\tResourceType:      aws.String(\"AWS::ApiGateway::Method\"),\n\t\t\tStackId:           aws.String(\"arn:aws:cloudformation:us-west-2:foobarbaz:stack/app/ad3af570-8511-11e7-8832-50d5ca789e4a\"),\n\t\t\tStackName:         aws.String(\"app\"),\n\t\t},\n\t}\n\n\tstates := map[string]Status{\n\t\t\"DnsZoneSomethingComRecordApiSomethingCom\": CreateComplete,\n\t\t\"app-ApiProx-33K7PKBL7HNI\":                 CreateComplete,\n\t}\n\n\tc := resourcesCompleted(resources, states)\n\tassert.Len(t, c, 1)\n}\n"
  },
  {
    "path": "platform/lambda/stack/status.go",
    "content": "package stack\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/apex/up/internal/colors\"\n)\n\n// status map for humanization.\nvar statusMap = map[Status]string{\n\tUnknown: \"Unknown\",\n\n\tCreateInProgress: \"Creating\",\n\tCreateFailed:     \"Failed to create\",\n\tCreateComplete:   \"Created\",\n\n\tDeleteInProgress: \"Deleting\",\n\tDeleteFailed:     \"Failed to delete\",\n\tDeleteComplete:   \"Deleted\",\n\tDeleteSkipped:    \"Skipped\",\n\n\tUpdateInProgress: \"Updating\",\n\tUpdateFailed:     \"Failed to update\",\n\tUpdateComplete:   \"Updated\",\n\n\tUpdateCompleteCleanup:         \"Update complete cleanup in progress\",\n\tUpdateRollbackCompleteCleanup: \"Update rollback complete cleanup in progress\",\n\tUpdateRollbackInProgress:      \"Update rollback in progress\",\n\tUpdateRollbackComplete:        \"Update rollback complete\",\n\n\tRollbackInProgress: \"Rolling back\",\n\tRollbackFailed:     \"Failed to rollback\",\n\tRollbackComplete:   \"Rollback complete\",\n\n\tCreatePending: \"Create pending\",\n\tFailed:        \"Failed\",\n}\n\n// State represents a generalized stack event state.\ntype State int\n\n// States available.\nconst (\n\tSuccess State = iota\n\tPending\n\tFailure\n)\n\n// Status represents a stack event status.\ntype Status string\n\n// Statuses available.\nconst (\n\tUnknown Status = \"\"\n\n\tCreateInProgress = \"CREATE_IN_PROGRESS\"\n\tCreateFailed     = \"CREATE_FAILED\"\n\tCreateComplete   = \"CREATE_COMPLETE\"\n\tCreatePending    = \"CREATE_PENDING\"\n\n\tDeleteInProgress = \"DELETE_IN_PROGRESS\"\n\tDeleteFailed     = \"DELETE_FAILED\"\n\tDeleteComplete   = \"DELETE_COMPLETE\"\n\tDeleteSkipped    = \"DELETE_SKIPPED\"\n\n\tUpdateInProgress = \"UPDATE_IN_PROGRESS\"\n\tUpdateFailed     = \"UPDATE_FAILED\"\n\tUpdateComplete   = \"UPDATE_COMPLETE\"\n\n\tUpdateRollbackInProgress      = \"UPDATE_ROLLBACK_IN_PROGRESS\"\n\tUpdateRollbackComplete        = \"UPDATE_ROLLBACK_COMPLETE\"\n\tUpdateRollbackCompleteCleanup = \"UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS\"\n\tUpdateCompleteCleanup         = \"UPDATE_COMPLETE_CLEANUP_IN_PROGRESS\"\n\n\tRollbackInProgress = \"ROLLBACK_IN_PROGRESS\"\n\tRollbackFailed     = \"ROLLBACK_FAILED\"\n\tRollbackComplete   = \"ROLLBACK_COMPLETE\"\n\n\tFailed = \"FAILED\"\n)\n\n// String returns the human representation.\nfunc (s Status) String() string {\n\treturn statusMap[s]\n}\n\n// IsDone returns true when failed or complete.\nfunc (s Status) IsDone() bool {\n\treturn s.State() != Pending\n}\n\n// Color the given string based on the status.\nfunc (s Status) Color(v string) string {\n\tswitch s.State() {\n\tcase Success:\n\t\treturn colors.Blue(v)\n\tcase Pending:\n\t\treturn colors.Yellow(v)\n\tcase Failure:\n\t\treturn colors.Red(v)\n\tdefault:\n\t\treturn v\n\t}\n}\n\n// State returns a generalized state.\nfunc (s Status) State() State {\n\tswitch s {\n\tcase CreateFailed, UpdateFailed, DeleteFailed, RollbackFailed, Failed, UpdateRollbackCompleteCleanup, UpdateRollbackComplete:\n\t\treturn Failure\n\tcase CreateInProgress, UpdateInProgress, DeleteInProgress, RollbackInProgress, CreatePending, UpdateRollbackInProgress:\n\t\treturn Pending\n\tcase CreateComplete, UpdateComplete, DeleteComplete, DeleteSkipped, RollbackComplete, UpdateCompleteCleanup:\n\t\treturn Success\n\tdefault:\n\t\tpanic(fmt.Sprintf(\"unhandled state %q\", string(s)))\n\t}\n}\n"
  },
  {
    "path": "platform/lambda/stack/status_test.go",
    "content": "package stack\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tj/assert\"\n)\n\nfunc TestStatus_String(t *testing.T) {\n\tassert.Equal(t, \"Unknown\", Status(\"\").String())\n\tassert.Equal(t, \"Creating\", Status(\"CREATE_IN_PROGRESS\").String())\n\tassert.Equal(t, \"Deleting\", Status(\"DELETE_IN_PROGRESS\").String())\n\tassert.Equal(t, \"Failed to update\", Status(\"UPDATE_FAILED\").String())\n}\n\nfunc TestStatus_State(t *testing.T) {\n\tassert.Equal(t, Pending, Status(\"CREATE_IN_PROGRESS\").State())\n\tassert.Equal(t, Pending, Status(\"UPDATE_IN_PROGRESS\").State())\n\tassert.Equal(t, Success, Status(\"CREATE_COMPLETE\").State())\n\tassert.Equal(t, Failure, Status(\"CREATE_FAILED\").State())\n}\n\nfunc TestStatus_IsDone(t *testing.T) {\n\tassert.False(t, Status(\"CREATE_IN_PROGRESS\").IsDone())\n\tassert.False(t, Status(\"UPDATE_IN_PROGRESS\").IsDone())\n\tassert.True(t, Status(\"CREATE_COMPLETE\").IsDone())\n\tassert.True(t, Status(\"UPDATE_COMPLETE\").IsDone())\n\tassert.True(t, Status(\"DELETE_COMPLETE\").IsDone())\n\tassert.True(t, Status(\"DELETE_FAILED\").IsDone())\n}\n"
  },
  {
    "path": "platform.go",
    "content": "package up\n\nimport (\n\t\"io\"\n\t\"time\"\n)\n\n// TODO: finalize and finish documentation\n\n// LogsConfig is configuration for viewing logs.\ntype LogsConfig struct {\n\t// Region is the target region.\n\tRegion string\n\n\t// Query is the filter pattern.\n\tQuery string\n\n\t// Since is used as the starting point when filtering\n\t// historical logs, no logs before this point are returned.\n\tSince time.Time\n\n\t// Follow is used to stream new logs.\n\tFollow bool\n\n\t// Expand is used to expand logs to a verbose format.\n\tExpand bool\n\n\t// OutputJSON is used to output raw json.\n\tOutputJSON bool\n}\n\n// Logs is the interface for viewing platform logs.\ntype Logs interface {\n\tio.Reader\n}\n\n// Domains is the interface for purchasing and\n// managing domains names.\ntype Domains interface {\n\tAvailability(domain string) (*Domain, error)\n\tSuggestions(domain string) ([]*Domain, error)\n\tPurchase(domain string, contact DomainContact) error\n\tList() ([]*Domain, error)\n}\n\n// Deploy config.\ntype Deploy struct {\n\tStage  string\n\tCommit string\n\tAuthor string\n\tBuild  bool\n}\n\n// Platform is the interface for platform integration,\n// defining the basic set of functionality required for\n// Up applications.\ntype Platform interface {\n\t// Build the project.\n\tBuild() error\n\n\t// Deploy to the given stage, to the\n\t// region(s) configured by the user.\n\tDeploy(Deploy) error\n\n\t// Logs returns an interface for working\n\t// with logging data.\n\tLogs(LogsConfig) Logs\n\n\t// Domains returns an interface for\n\t// managing domain names.\n\tDomains() Domains\n\n\t// URL returns the endpoint for the given\n\t// region and stage combination, or an\n\t// empty string.\n\tURL(region, stage string) (string, error)\n\n\t// Exists returns true if the application has been created.\n\tExists(region string) (bool, error)\n\n\tCreateStack(region, version string) error\n\tDeleteStack(region string, wait bool) error\n\tShowStack(region string) error\n\tPlanStack(region string) error\n\tApplyStack(region string) error\n\n\tShowMetrics(region, stage string, start time.Time) error\n}\n\n// Pruner is the interface used to prune old versions and\n// the artifacts associated such as S3 zip files for Lambda.\ntype Pruner interface {\n\tPrune(region, stage string, versions int) error\n}\n\n// Runtime is the interface used by a platform to support\n// runtime operations such as initializing environment\n// variables from remote storage.\ntype Runtime interface {\n\tInit(stage string) error\n}\n\n// Zipper is the interface used by platforms which\n// utilize zips for delivery of deployments.\ntype Zipper interface {\n\tZip() io.Reader\n}\n\n// Domain is a domain name and its availability.\ntype Domain struct {\n\tName      string\n\tAvailable bool\n\tExpiry    time.Time\n\tAutoRenew bool\n}\n\n// DomainContact is the domain name contact\n// information required for registration.\ntype DomainContact struct {\n\tEmail            string\n\tFirstName        string\n\tLastName         string\n\tCountryCode      string\n\tCity             string\n\tAddress          string\n\tOrganizationName string\n\tPhoneNumber      string\n\tState            string\n\tZipCode          string\n}\n"
  },
  {
    "path": "reporter/discard/discard.go",
    "content": "// Package discard provides a reporter for discarding events.\npackage discard\n\nimport \"github.com/apex/up/platform/event\"\n\n// Report events.\nfunc Report(events <-chan *event.Event) {\n\tfor range events {\n\t\t// :)\n\t}\n}\n"
  },
  {
    "path": "reporter/plain/plain.go",
    "content": "// Package plain provides plain-text reporting for CI.\npackage plain\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/dustin/go-humanize\"\n\n\t\"github.com/apex/up/platform/event\"\n)\n\n// Report on events.\nfunc Report(events <-chan *event.Event) {\n\tr := reporter{\n\t\tevents: events,\n\t}\n\n\tr.Start()\n}\n\n// reporter struct.\ntype reporter struct {\n\tevents <-chan *event.Event\n}\n\n// complete log with duration.\nfunc (r *reporter) complete(name, value string, d time.Duration) {\n\tduration := fmt.Sprintf(\"(%s)\", d.Round(time.Millisecond))\n\tfmt.Printf(\"     %s %s %s\\n\", name+\":\", value, duration)\n}\n\n// log line.\nfunc (r *reporter) log(name, value string) {\n\tfmt.Printf(\"     %s %s\\n\", name+\":\", value)\n}\n\n// error line.\nfunc (r *reporter) error(name, value string) {\n\tfmt.Printf(\"     %s %s\\n\", name+\":\", value)\n}\n\n// Start handling events.\nfunc (r *reporter) Start() {\n\tfor e := range r.events {\n\t\tswitch e.Name {\n\t\tcase \"account.login.verify\":\n\t\t\tr.log(\"verify\", \"Check your email for a confirmation link\")\n\t\tcase \"account.login.verified\":\n\t\t\tr.log(\"verify\", \"complete\")\n\t\tcase \"hook\":\n\t\t\tr.log(\"hook\", e.String(\"name\"))\n\t\tcase \"hook.complete\":\n\t\t\tr.complete(\"hook\", e.String(\"name\"), e.Duration(\"duration\"))\n\t\tcase \"platform.build.zip\":\n\t\t\ts := fmt.Sprintf(\"%s files, %s\", humanize.Comma(e.Int64(\"files\")), humanize.Bytes(uint64(e.Int(\"size_compressed\"))))\n\t\t\tr.complete(\"build\", s, e.Duration(\"duration\"))\n\t\tcase \"platform.deploy.complete\":\n\t\t\ts := \"complete\"\n\t\t\tif v := e.String(\"version\"); v != \"\" {\n\t\t\t\ts = \"version \" + v\n\t\t\t}\n\t\t\tr.complete(\"deploy\", s, e.Duration(\"duration\"))\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "reporter/reporter.go",
    "content": "package reporter\n\nimport (\n\t\"github.com/apex/up/reporter/discard\"\n\t\"github.com/apex/up/reporter/plain\"\n\t\"github.com/apex/up/reporter/text\"\n)\n\nvar (\n\t// Discard reporter.\n\tDiscard = discard.Report\n\n\t// Plain reporter.\n\tPlain = plain.Report\n\n\t// Text reporter.\n\tText = text.Report\n)\n"
  },
  {
    "path": "reporter/text/text.go",
    "content": "// Package text provides a reporter for humanized interactive events.\npackage text\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/aws/aws-sdk-go/service/cloudformation\"\n\t\"github.com/dustin/go-humanize\"\n\t\"github.com/tj/go-progress\"\n\t\"github.com/tj/go-spin\"\n\t\"github.com/tj/go/term\"\n\n\t\"github.com/apex/up/internal/colors\"\n\t\"github.com/apex/up/internal/util\"\n\t\"github.com/apex/up/platform/aws/cost\"\n\t\"github.com/apex/up/platform/event\"\n\tlambdautil \"github.com/apex/up/platform/lambda/reporter\"\n\t\"github.com/apex/up/platform/lambda/stack\"\n)\n\n// TODO: platform-specific reporting should live in the platform\n// TODO: typed events would be nicer.. refactor event names\n// TODO: refactor, this is a hot mess :D\n\n// Report events.\nfunc Report(events <-chan *event.Event) {\n\tr := reporter{\n\t\tevents:  events,\n\t\tspinner: spin.New(),\n\t}\n\n\tr.Start()\n}\n\n// reporter struct.\ntype reporter struct {\n\tevents         <-chan *event.Event\n\tspinner        *spin.Spinner\n\tprevTime       time.Time\n\tbar            *progress.Bar\n\tinlineProgress bool\n\tpendingName    string\n\tpendingValue   string\n}\n\n// spin the spinner by moving to the start of the line and re-printing.\nfunc (r *reporter) spin() {\n\tif r.pendingName != \"\" {\n\t\tr.pending(r.pendingName, r.pendingValue)\n\t}\n}\n\n// clear the liner.\nfunc (r *reporter) clear() {\n\tr.pendingName = \"\"\n\tr.pendingValue = \"\"\n\tterm.ClearLine()\n}\n\n// pending log with spinner.\nfunc (r *reporter) pending(name, value string) {\n\tr.pendingName = name\n\tr.pendingValue = value\n\tterm.ClearLine()\n\tfmt.Printf(\"\\r   %s %s\", colors.Purple(r.spinner.Next()+\" \"+name+\":\"), value)\n}\n\n// complete log with duration.\nfunc (r *reporter) complete(name, value string, d time.Duration) {\n\tr.pendingName = \"\"\n\tr.pendingValue = \"\"\n\tterm.ClearLine()\n\tduration := fmt.Sprintf(\"(%s)\", d.Round(time.Millisecond))\n\tfmt.Printf(\"\\r     %s %s %s\\n\", colors.Purple(name+\":\"), value, colors.Gray(duration))\n}\n\n// completeWithoutDuration log without duration.\nfunc (r *reporter) completeWithoutDuration(name, value string) {\n\tr.pendingName = \"\"\n\tr.pendingValue = \"\"\n\tterm.ClearLine()\n\tfmt.Printf(\"\\r     %s %s\\n\", colors.Purple(name+\":\"), value)\n}\n\n// log line.\nfunc (r *reporter) log(name, value string) {\n\tfmt.Printf(\"\\r     %s %s\\n\", colors.Purple(name+\":\"), value)\n}\n\n// error line.\nfunc (r *reporter) error(name, value string) {\n\tfmt.Printf(\"\\r     %s %s\\n\", colors.Red(name+\":\"), value)\n}\n\n// Start handling events.\nfunc (r *reporter) Start() {\n\ttick := time.NewTicker(150 * time.Millisecond)\n\tdefer tick.Stop()\n\n\trender := term.Renderer()\n\n\tfor {\n\t\tselect {\n\t\tcase <-tick.C:\n\t\t\tr.spin()\n\t\tcase e := <-r.events:\n\t\t\tswitch e.Name {\n\t\t\tcase \"account.login.verify\":\n\t\t\t\tterm.HideCursor()\n\t\t\t\tr.pending(\"verify\", \"Check your email for a confirmation link\")\n\t\t\tcase \"account.login.verified\":\n\t\t\t\tterm.ShowCursor()\n\t\t\t\tr.completeWithoutDuration(\"verify\", \"complete\")\n\t\t\tcase \"hook\":\n\t\t\t\tr.pending(e.String(\"name\"), \"\")\n\t\t\tcase \"hook.complete\":\n\t\t\t\tname := e.String(\"name\")\n\t\t\t\tif name != \"build\" {\n\t\t\t\t\tr.clear()\n\t\t\t\t}\n\t\t\tcase \"deploy\", \"stack.delete\", \"platform.stack.apply\":\n\t\t\t\tterm.HideCursor()\n\t\t\tcase \"deploy.complete\", \"stack.delete.complete\", \"platform.stack.apply.complete\":\n\t\t\t\tterm.ShowCursor()\n\t\t\tcase \"platform.build.zip\":\n\t\t\t\ts := fmt.Sprintf(\"%s files, %s\", humanize.Comma(e.Int64(\"files\")), humanize.Bytes(uint64(e.Int(\"size_compressed\"))))\n\t\t\t\tr.complete(\"build\", s, e.Duration(\"duration\"))\n\t\t\tcase \"platform.deploy\":\n\t\t\t\tr.pending(\"deploy\", e.String(\"stage\"))\n\t\t\tcase \"platform.deploy.complete\":\n\t\t\t\ts := e.String(\"stage\")\n\t\t\t\tif v := e.String(\"commit\"); v != \"\" {\n\t\t\t\t\ts += \" (commit \" + v + \")\"\n\t\t\t\t} else if v := e.String(\"version\"); v != \"\" {\n\t\t\t\t\ts += \" (version \" + v + \")\"\n\t\t\t\t}\n\t\t\t\tr.complete(\"deploy\", s, e.Duration(\"duration\"))\n\t\t\tcase \"platform.deploy.url\":\n\t\t\t\tr.log(\"endpoint\", e.String(\"url\"))\n\t\t\tcase \"platform.function.create\":\n\t\t\t\tr.inlineProgress = true\n\t\t\tcase \"stack.create\":\n\t\t\t\tr.inlineProgress = true\n\t\t\tcase \"platform.stack.report\":\n\t\t\t\tif r.inlineProgress {\n\t\t\t\t\tr.bar = util.NewInlineProgressInt(e.Int(\"total\"))\n\t\t\t\t\tr.pending(\"stack\", r.bar.String())\n\t\t\t\t} else {\n\t\t\t\t\tterm.ClearAll()\n\t\t\t\t\tr.bar = util.NewProgressInt(e.Int(\"total\"))\n\t\t\t\t\trender(term.CenterLine(r.bar.String()))\n\t\t\t\t}\n\t\t\tcase \"platform.stack.report.event\":\n\t\t\t\tif r.inlineProgress {\n\t\t\t\t\tr.bar.ValueInt(e.Int(\"complete\"))\n\t\t\t\t\tr.pending(\"stack\", r.bar.String())\n\t\t\t\t} else {\n\t\t\t\t\tr.bar.ValueInt(e.Int(\"complete\"))\n\t\t\t\t\trender(term.CenterLine(r.bar.String()))\n\t\t\t\t}\n\t\t\tcase \"platform.stack.report.complete\":\n\t\t\t\tif r.inlineProgress {\n\t\t\t\t\tr.complete(\"stack\", \"complete\", e.Duration(\"duration\"))\n\t\t\t\t} else {\n\t\t\t\t\tterm.ClearAll()\n\t\t\t\t\tterm.ShowCursor()\n\t\t\t\t}\n\t\t\tcase \"platform.stack.show\", \"platform.stack.show.complete\":\n\t\t\t\tfmt.Printf(\"\\n\")\n\t\t\tcase \"platform.stack.show.stack\":\n\t\t\t\ts := e.Fields[\"stack\"].(*cloudformation.Stack)\n\t\t\t\tutil.LogName(\"status\", \"%s\", stack.Status(*s.StackStatus))\n\t\t\t\tif reason := s.StackStatusReason; reason != nil {\n\t\t\t\t\tutil.LogName(\"reason\", *reason)\n\t\t\t\t}\n\t\t\tcase \"platform.stack.show.stack.events\":\n\t\t\t\tutil.LogTitle(\"Events\")\n\t\t\tcase \"platform.stack.show.nameservers\":\n\t\t\t\tutil.Log(\"nameservers:\")\n\t\t\t\tfor _, ns := range e.Strings(\"nameservers\") {\n\t\t\t\t\tutil.LogListItem(ns)\n\t\t\t\t}\n\t\t\tcase \"platform.stack.show.stack.event\":\n\t\t\t\tevent := e.Fields[\"event\"].(*cloudformation.StackEvent)\n\t\t\t\tstatus := stack.Status(*event.ResourceStatus)\n\t\t\t\tif status.State() == stack.Failure {\n\t\t\t\t\tr.error(*event.LogicalResourceId, *event.ResourceStatusReason)\n\t\t\t\t} else {\n\t\t\t\t\tr.log(*event.LogicalResourceId, status.String())\n\t\t\t\t}\n\t\t\tcase \"platform.stack.show.stage\":\n\t\t\t\tutil.LogTitle(strings.Title(e.String(\"name\")))\n\t\t\t\tif s := e.String(\"domain\"); s != \"\" {\n\t\t\t\t\tutil.LogName(\"domain\", e.String(\"domain\"))\n\t\t\t\t}\n\t\t\tcase \"platform.stack.show.domain\":\n\t\t\t\tutil.LogName(\"endpoint\", e.String(\"endpoint\"))\n\t\t\tcase \"platform.stack.show.version\":\n\t\t\t\tutil.LogName(\"version\", e.String(\"version\"))\n\t\t\tcase \"stack.plan\":\n\t\t\t\tfmt.Printf(\"\\n\")\n\t\t\tcase \"platform.stack.plan.change\":\n\t\t\t\tc := e.Fields[\"change\"].(*cloudformation.Change).ResourceChange\n\t\t\t\tif *c.ResourceType == \"AWS::Lambda::Alias\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tcolor := actionColor(*c.Action)\n\t\t\t\tfmt.Printf(\"  %s %s\\n\", color(*c.Action), lambdautil.ResourceType(*c.ResourceType))\n\t\t\t\tfmt.Printf(\"    %s: %s\\n\", color(\"id\"), *c.LogicalResourceId)\n\t\t\t\tif c.Replacement != nil {\n\t\t\t\t\tfmt.Printf(\"    %s: %s\\n\", color(\"replace\"), *c.Replacement)\n\t\t\t\t}\n\t\t\t\tfmt.Printf(\"\\n\")\n\t\t\tcase \"platform.certs.create\":\n\t\t\t\tdomains := util.UniqueStrings(e.Fields[\"domains\"].([]string))\n\t\t\t\tr.log(\"domains\", \"Check your email to approve the certificate\")\n\t\t\t\tr.pending(\"confirm\", strings.Join(domains, \", \"))\n\t\t\tcase \"platform.certs.create.complete\":\n\t\t\t\tr.complete(\"confirm\", \"complete\", e.Duration(\"duration\"))\n\t\t\t\tfmt.Printf(\"\\n\")\n\t\t\tcase \"metrics\", \"metrics.complete\":\n\t\t\t\tfmt.Printf(\"\\n\")\n\t\t\tcase \"metrics.value\":\n\t\t\t\tswitch n := e.String(\"name\"); n {\n\t\t\t\tcase \"Duration min\", \"Duration avg\", \"Duration max\":\n\t\t\t\t\tr.log(n, fmt.Sprintf(\"%dms\", e.Int(\"value\")))\n\t\t\t\tcase \"Requests\":\n\t\t\t\t\tv := humanize.Comma(int64(e.Int(\"value\")))\n\t\t\t\t\tc := cost.Requests(e.Int(\"value\"))\n\t\t\t\t\tr.log(n, fmt.Sprintf(\"%s %s\", v, currency(c)))\n\t\t\t\tcase \"Duration sum\":\n\t\t\t\t\td := time.Millisecond * time.Duration(e.Int(\"value\"))\n\t\t\t\t\tc := cost.Duration(e.Int(\"value\"), e.Int(\"memory\"))\n\t\t\t\t\tr.log(n, fmt.Sprintf(\"%s %s\", d, currency(c)))\n\t\t\t\tcase \"Invocations\":\n\t\t\t\t\td := humanize.Comma(int64(e.Int(\"value\")))\n\t\t\t\t\tc := cost.Invocations(e.Int(\"value\"))\n\t\t\t\t\tr.log(n, fmt.Sprintf(\"%s %s\", d, currency(c)))\n\t\t\t\tdefault:\n\t\t\t\t\tr.log(n, humanize.Comma(int64(e.Int(\"value\"))))\n\t\t\t\t}\n\t\t\tcase \"prune\":\n\t\t\t\tfmt.Printf(\"\\n\")\n\t\t\t\tr.pending(\"prune\", \"removing old releases\")\n\t\t\tcase \"prune.complete\":\n\t\t\t\tn := e.Int(\"count\")\n\t\t\t\tb := e.Int64(\"size\")\n\t\t\t\ts := fmt.Sprintf(\"%d old files removed from S3 (%s)\", n, humanize.Bytes(uint64(b)))\n\t\t\t\tr.complete(\"prune\", s, e.Duration(\"duration\"))\n\t\t\t\tfmt.Printf(\"\\n\")\n\t\t\t}\n\n\t\t\tr.prevTime = time.Now()\n\t\t}\n\t}\n}\n\n// currency format.\nfunc currency(n float64) string {\n\treturn colors.Gray(fmt.Sprintf(\"($%0.2f)\", n))\n}\n\n// countEventsByStatus returns the number of events with the given state.\nfunc countEventsByStatus(events []*cloudformation.StackEvent, desired stack.Status) (n int) {\n\tfor _, e := range events {\n\t\tstatus := stack.Status(*e.ResourceStatus)\n\n\t\tif *e.ResourceType == \"AWS::CloudFormation::Stack\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tif status == desired {\n\t\t\tn++\n\t\t}\n\t}\n\n\treturn\n}\n\n// countEventsComplete returns the number of completed or failed events.\nfunc countEventsComplete(events []*cloudformation.StackEvent) (n int) {\n\tfor _, e := range events {\n\t\tstatus := stack.Status(*e.ResourceStatus)\n\n\t\tif *e.ResourceType == \"AWS::CloudFormation::Stack\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tif status.IsDone() {\n\t\t\tn++\n\t\t}\n\t}\n\n\treturn\n}\n\n// actionColor returns a color func by action.\nfunc actionColor(s string) colors.Func {\n\tswitch s {\n\tcase \"Add\":\n\t\treturn colors.Purple\n\tcase \"Remove\":\n\t\treturn colors.Red\n\tcase \"Modify\":\n\t\treturn colors.Blue\n\tdefault:\n\t\treturn colors.Gray\n\t}\n}\n"
  },
  {
    "path": "up.go",
    "content": "package up\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"time\"\n\n\t\"github.com/apex/log\"\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/apex/up/config\"\n\t\"github.com/apex/up/internal/util\"\n\t\"github.com/apex/up/platform/event\"\n)\n\n// Config for a project.\ntype Config = config.Config\n\n// ReadConfig reads the configuration from `path`.\nvar ReadConfig = config.ReadConfig\n\n// ParseConfigString returns config from JSON string.\nvar ParseConfigString = config.ParseConfigString\n\n// MustParseConfigString returns config from JSON string.\nvar MustParseConfigString = config.MustParseConfigString\n\n// Project manager.\ntype Project struct {\n\tPlatform\n\tconfig *Config\n\tevents event.Events\n}\n\n// New project.\nfunc New(c *Config, events event.Events) *Project {\n\treturn &Project{\n\t\tconfig: c,\n\t\tevents: events,\n\t}\n}\n\n// WithPlatform to `platform`.\nfunc (p *Project) WithPlatform(platform Platform) *Project {\n\tp.Platform = platform\n\treturn p\n}\n\n// RunHook runs a hook by name.\nfunc (p *Project) RunHook(name string) error {\n\thook := p.config.Hooks.Get(name)\n\n\tif hook.IsEmpty() {\n\t\tlog.Debugf(\"hook %s is not defined\", name)\n\t\treturn nil\n\t}\n\n\tdefer p.events.Time(\"hook\", event.Fields{\n\t\t\"name\": name,\n\t\t\"hook\": hook,\n\t})()\n\n\tfor _, command := range hook {\n\t\tlog.Debugf(\"hook %q command %q\", name, command)\n\n\t\tcmd := exec.Command(\"sh\", \"-c\", command)\n\t\tcmd.Env = os.Environ()\n\t\tcmd.Env = append(cmd.Env, util.Env(p.config.Environment)...)\n\t\tcmd.Env = append(cmd.Env, \"PATH=node_modules/.bin:\"+os.Getenv(\"PATH\"))\n\n\t\tb, err := cmd.CombinedOutput()\n\t\tif err != nil {\n\t\t\treturn errors.Errorf(\"%q: %s\", command, b)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// RunHooks runs hooks by name.\nfunc (p *Project) RunHooks(names ...string) error {\n\tfor _, n := range names {\n\t\tif err := p.RunHook(n); err != nil {\n\t\t\treturn errors.Wrapf(err, \"%q hook\", n)\n\t\t}\n\t}\n\treturn nil\n}\n\n// Build the project.\nfunc (p *Project) Build(hooks bool) error {\n\tdefer p.events.Time(\"platform.build\", nil)()\n\n\tif hooks {\n\t\tif err := p.RunHooks(\"prebuild\", \"build\"); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif err := p.Platform.Build(); err != nil {\n\t\treturn errors.Wrap(err, \"building\")\n\t}\n\n\tif hooks {\n\t\treturn p.RunHooks(\"postbuild\")\n\t}\n\n\treturn nil\n}\n\n// Deploy the project.\nfunc (p *Project) Deploy(d Deploy) error {\n\tdefer p.events.Time(\"deploy\", event.Fields{\n\t\t\"commit\": d.Commit,\n\t\t\"stage\":  d.Stage,\n\t})()\n\n\tif err := p.Build(d.Build); err != nil {\n\t\treturn errors.Wrap(err, \"building\")\n\t}\n\n\tif err := p.deploy(d); err != nil {\n\t\treturn errors.Wrap(err, \"deploying\")\n\t}\n\n\tif d.Build {\n\t\tif err := p.RunHook(\"clean\"); err != nil {\n\t\t\treturn errors.Wrap(err, \"clean hook\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// deploy stage.\nfunc (p *Project) deploy(d Deploy) error {\n\tif err := p.RunHooks(\"predeploy\", \"deploy\"); err != nil {\n\t\treturn err\n\t}\n\n\tif err := p.Platform.Deploy(d); err != nil {\n\t\treturn err\n\t}\n\n\treturn p.RunHooks(\"postdeploy\")\n}\n\n// Zip returns the zip if supported by the platform.\nfunc (p *Project) Zip() (io.Reader, error) {\n\tz, ok := p.Platform.(Zipper)\n\tif !ok {\n\t\treturn nil, errors.Errorf(\"platform does not support zips\")\n\t}\n\n\treturn z.Zip(), nil\n}\n\n// Init initializes the runtime such as remote environment variables.\nfunc (p *Project) Init(stage string) error {\n\tr, ok := p.Platform.(Runtime)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\treturn r.Init(stage)\n}\n\n// CreateStack implementation.\nfunc (p *Project) CreateStack(region, version string) error {\n\tdefer p.events.Time(\"stack.create\", event.Fields{\n\t\t\"region\":  region,\n\t\t\"version\": version,\n\t})()\n\n\treturn p.Platform.CreateStack(region, version)\n}\n\n// DeleteStack implementation.\nfunc (p *Project) DeleteStack(region string, wait bool) error {\n\tdefer p.events.Time(\"stack.delete\", event.Fields{\n\t\t\"region\": region,\n\t})()\n\n\treturn p.Platform.DeleteStack(region, wait)\n}\n\n// ShowStack implementation.\nfunc (p *Project) ShowStack(region string) error {\n\tdefer p.events.Time(\"stack.show\", event.Fields{\n\t\t\"region\": region,\n\t})()\n\n\treturn p.Platform.ShowStack(region)\n}\n\n// ShowMetrics implementation.\nfunc (p *Project) ShowMetrics(region, stage string, start time.Time) error {\n\tdefer p.events.Time(\"metrics\", event.Fields{\n\t\t\"region\": region,\n\t\t\"stage\":  stage,\n\t\t\"start\":  start,\n\t})()\n\n\treturn p.Platform.ShowMetrics(region, stage, start)\n}\n\n// PlanStack implementation.\nfunc (p *Project) PlanStack(region string) error {\n\tdefer p.events.Time(\"stack.plan\", event.Fields{\n\t\t\"region\": region,\n\t})()\n\n\treturn p.Platform.PlanStack(region)\n}\n\n// ApplyStack implementation.\nfunc (p *Project) ApplyStack(region string) error {\n\tdefer p.events.Time(\"stack.apply\", event.Fields{\n\t\t\"region\": region,\n\t})()\n\n\treturn p.Platform.ApplyStack(region)\n}\n\n// Prune implementation.\nfunc (p *Project) Prune(region, stage string, versions int) error {\n\tpruner, ok := p.Platform.(Pruner)\n\tif !ok {\n\t\treturn errors.Errorf(\"platform does not support pruning\")\n\t}\n\n\treturn pruner.Prune(region, stage, versions)\n}\n"
  }
]