Repository: commitdev/zero Branch: main Commit: de71b8bd09b0 Files: 126 Total size: 24.6 MB Directory structure: gitextract_gf3x61g3/ ├── .circleci/ │ └── config.yml ├── .dockerignore ├── .github/ │ └── workflows/ │ ├── codeql-analysis.yml │ ├── config.yml │ └── doc-site.yml ├── .gitignore ├── .goreleaser.yml ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── LICENSE.spdx ├── Makefile ├── README.md ├── cmd/ │ ├── apply.go │ ├── check.go │ ├── create.go │ ├── init.go │ ├── version.go │ └── zero.go ├── doc-site/ │ ├── .gitignore │ ├── README.md │ ├── babel.config.js │ ├── docs/ │ │ ├── about/ │ │ │ ├── opensource.md │ │ │ ├── overview.md │ │ │ ├── real-world-usage.md │ │ │ ├── roadmap.md │ │ │ └── technology-choices.md │ │ ├── concepts/ │ │ │ ├── core-concepts.md │ │ │ └── project-life-cycle.md │ │ ├── getting-started/ │ │ │ ├── installation.md │ │ │ ├── prerequisites.md │ │ │ ├── zero-apply.md │ │ │ ├── zero-create.md │ │ │ └── zero-init.md │ │ └── reference/ │ │ ├── learning-resources.md │ │ ├── module-definition.md │ │ ├── project-definition.md │ │ └── working-on-zero.md │ ├── docusaurus.config.js │ ├── package.json │ ├── sidebars.js │ ├── src/ │ │ ├── components/ │ │ │ ├── HomepageFeatures.js │ │ │ ├── HomepageFeatures.module.scss │ │ │ ├── HomepageOfferings.js │ │ │ ├── HomepageOfferings.module.scss │ │ │ ├── HomepageTrustedBy.js │ │ │ ├── HomepageTrustedBy.module.scss │ │ │ ├── HomepageVideo.js │ │ │ ├── HomepageVideo.module.scss │ │ │ ├── HomepageWhyZero.js │ │ │ └── HomepageWhyZero.module.scss │ │ ├── css/ │ │ │ └── custom.css │ │ ├── pages/ │ │ │ ├── docs/ │ │ │ │ ├── index.js │ │ │ │ └── zero/ │ │ │ │ └── index.js │ │ │ ├── index.js │ │ │ └── index.module.scss │ │ └── theme/ │ │ └── DocSidebar/ │ │ ├── index.js │ │ └── styles.module.css │ └── static/ │ └── .nojekyll ├── go.mod ├── go.sum ├── internal/ │ ├── apply/ │ │ ├── apply.go │ │ └── apply_test.go │ ├── condition/ │ │ ├── condition.go │ │ └── condition_test.go │ ├── config/ │ │ ├── moduleconfig/ │ │ │ └── module_config.go │ │ └── projectconfig/ │ │ ├── init.go │ │ ├── init_test.go │ │ ├── project_config.go │ │ └── project_config_test.go │ ├── constants/ │ │ └── constants.go │ ├── generate/ │ │ ├── generate_modules.go │ │ └── generate_test.go │ ├── init/ │ │ ├── custom-prompts.go │ │ ├── debug.test │ │ ├── init.go │ │ ├── prompts.go │ │ └── prompts_test.go │ ├── module/ │ │ ├── module.go │ │ ├── module_internal_test.go │ │ └── module_test.go │ ├── registry/ │ │ ├── registry.go │ │ └── registry_test.go │ ├── util/ │ │ └── util.go │ └── vcs/ │ └── create-git-repos.go ├── main.go ├── pkg/ │ ├── credentials/ │ │ ├── credentials.go │ │ └── credentials_test.go │ └── util/ │ ├── exit/ │ │ └── exit.go │ ├── flog/ │ │ └── log.go │ └── fs/ │ ├── fs.go │ └── fs_test.go ├── registry.yaml ├── tests/ │ ├── integration/ │ │ └── ci/ │ │ └── ci_test.go │ └── test_data/ │ ├── apply/ │ │ ├── project1/ │ │ │ ├── Makefile │ │ │ └── zero-module.yml │ │ ├── project2/ │ │ │ ├── Makefile │ │ │ ├── check.sh │ │ │ └── zero-module.yml │ │ └── zero-project.yml │ ├── apply-failing/ │ │ ├── project1/ │ │ │ ├── Makefile │ │ │ ├── project.out │ │ │ └── zero-module.yml │ │ ├── project2/ │ │ │ ├── Makefile │ │ │ ├── project.out │ │ │ └── zero-module.yml │ │ ├── project3/ │ │ │ ├── Makefile │ │ │ ├── check.sh │ │ │ ├── project.out │ │ │ └── zero-module.yml │ │ └── zero-project.yml │ ├── aws/ │ │ └── mock_credentials.yml │ ├── ci/ │ │ └── expected/ │ │ ├── .circleci/ │ │ │ └── config.yml │ │ ├── .travis.yml │ │ └── Jenkinsfile │ ├── configs/ │ │ ├── commit0_submodules.yml │ │ ├── credentials.yml │ │ └── zero-basic.yml │ ├── generate/ │ │ ├── file_to_template.txt │ │ └── zero-module.yml │ ├── modules/ │ │ ├── ci/ │ │ │ ├── config1.yml │ │ │ ├── dir/ │ │ │ │ └── config2.yml │ │ │ └── zero-module.yml │ │ └── no-version-constraint/ │ │ └── zero-module.yml │ └── projectconfig/ │ └── zero-project.yml └── version/ └── version.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .circleci/config.yml ================================================ --- version: 2.1 orbs: slack-message: commitdev/slack-message@0.0.3 version-tag: commitdev/version-tag@0.0.3 variables: - &workspace /home/circleci/project - &build-image circleci/golang:1.16.8 aliases: # Shallow Clone - this allows us to cut the 2 minute repo clone down to about 10 seconds for repos with 50,000 commits+ - &checkout-shallow name: Checkout (Shallow) command: | #!/bin/sh set -e # Workaround old docker images with incorrect $HOME # check https://github.com/docker/docker/issues/2968 for details if [ "${HOME}" = "/" ] then export HOME=$(getent passwd $(id -un) | cut -d: -f6) fi mkdir -p ~/.ssh echo 'github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== bitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAubiN81eDcafrgMeLzaFPsw2kNvEcqTKl/VqLat/MaB33pZy0y3rJZtnqwR2qOOvbwKZYKiEO1O6VqNEBxKvJJelCq0dTXWT5pbO2gDXC6h6QDXCaHo6pOHGPUy+YBaGQRGuSusMEASYiWunYN0vCAI8QaXnWMXNMdFP3jHAJH0eDsoiGnLPBlBp4TNm6rYI74nMzgz3B9IikW4WVK+dc8KZJZWYjAuORU3jc1c/NPskD2ASinf8v3xnfXeukU0sJ5N6m5E8VLjObPEO+mN2t/FZTMZLiFqPWc/ALSqnMnnhwrNi2rbfg/rd/IpL8Le3pSBne8+seeFVBoGqzHM9yXw==' >> ~/.ssh/known_hosts (umask 077; touch ~/.ssh/id_rsa) chmod 0600 ~/.ssh/id_rsa (cat \< ~/.ssh/id_rsa $CHECKOUT_KEY EOF ) # use git+ssh instead of https git config --global url."ssh://git@github.com".insteadOf "https://github.com" || true if [ -e /home/circleci/project/.git ] then cd /home/circleci/project git remote set-url origin "$CIRCLE_REPOSITORY_URL" || true else mkdir -p /home/circleci/project cd /home/circleci/project git clone --depth=1 "$CIRCLE_REPOSITORY_URL" . fi if [ -n "$CIRCLE_TAG" ] then git fetch --depth=10 --force origin "refs/tags/${CIRCLE_TAG}" elif [[ "$CIRCLE_BRANCH" =~ ^pull\/* ]] then # For PR from Fork git fetch --depth=10 --force origin "$CIRCLE_BRANCH/head:remotes/origin/$CIRCLE_BRANCH" else git fetch --depth=10 --force origin "$CIRCLE_BRANCH:remotes/origin/$CIRCLE_BRANCH" fi if [ -n "$CIRCLE_TAG" ] then git reset --hard "$CIRCLE_SHA1" git checkout -q "$CIRCLE_TAG" elif [ -n "$CIRCLE_BRANCH" ] then git reset --hard "$CIRCLE_SHA1" git checkout -q -B "$CIRCLE_BRANCH" fi git reset --hard "$CIRCLE_SHA1" pwd jobs: checkout_code: docker: - image: *build-image steps: - run: *checkout-shallow - persist_to_workspace: root: /home/circleci/project paths: - . unit_test: docker: - image: *build-image working_directory: *workspace steps: # steps that comprise the `build` job - attach_workspace: at: *workspace - restore_cache: # restores saved cache if no changes are detected since last run keys: - v1-pkg-cache-{{ checksum "go.sum" }} - v1-pkg-cache- - run: name: Run unit tests # store the results of our tests in the $TEST_RESULTS directory command: | go get -u github.com/jstemmer/go-junit-report mkdir -p test-reports PACKAGE_NAMES=$(go list ./... | circleci tests split --split-by=timings --timings-type=classname) echo "Tests: $PACKAGE_NAMES" go test -v $PACKAGE_NAMES | go-junit-report > test-reports/junit.xml - save_cache: # Store cache in the /go/pkg directory key: v1-pkg-cache-{{ checksum "go.sum" }} paths: - "/go/pkg" - store_test_results: path: test-reports - store_artifacts: path: test-reports # Requires the SLACK_WEBHOOK # - slack/notify-on-failure build_and_push: machine: image: circleci/classic:latest # docker_layer_caching: true # only for performance plan circleci accounts steps: - attach_workspace: at: *workspace - run: *checkout-shallow - version-tag/create - run: name: Update Version command command: | ./updateVersion.sh "$VERSION_TAG" - run: name: Build Docker image command: | make ci-docker-build - run: name: Push to Docker Hub command: | make ci-docker-push workflows: version: 2 # The main workflow. Check out the code, build it, push it, deploy to staging, test, deploy to production build_test_and_deploy: jobs: - checkout_code - unit_test: requires: - checkout_code - build_and_push: requires: - unit_test filters: branches: only: # only branches matching the below regex filters will run - /^master$/ ================================================ FILE: .dockerignore ================================================ Dockerfile README.md .history/ .travis.yml .gitignore .git example/ tmp/ ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ name: "CodeQL" on: push: branches: [main] pull_request: # The branches below must be a subset of the branches above branches: [main] schedule: - cron: '0 4 * * 1' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: security-events: write steps: - name: Checkout repository uses: actions/checkout@v3 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. fetch-depth: 2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: languages: go # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v2 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 ================================================ FILE: .github/workflows/config.yml ================================================ on: pull_request: branches: - main jobs: unit_test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: go-version: 1.16 - uses: actions/cache@v2 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - name: Run Go Tests run: | make check ================================================ FILE: .github/workflows/doc-site.yml ================================================ ## The is a combination of sites where ## Zero serves on the root of the domain / ## and module serves on /docs/modules// # from the same S3 bucket name: "Build Documentation Site" on: push: branches: - main paths: - doc-site/** env: region: us-west-2 s3_sync_path_to_exclude: docs/modules/* s3_sync_path: "" BUILD_DOMAIN: ${{ secrets.ZERO_DOC_SITE_DOMAIN_NAME }} jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Setup node.js uses: actions/setup-node@v3 with: node-version: 16 # - name: Documentaiton site folder # run: cd doc-site - name: Install Dependencies working-directory: doc-site run: npm install - name: Build website working-directory: doc-site run: | npm run build pwd ls -la - name: Upload build artifact to Github uses: actions/upload-artifact@v2 with: name: build-artifact path: doc-site/build deploy: name: Deploy runs-on: ubuntu-latest needs: build # These permissions are needed to interact with GitHub's OIDC Token endpoint. permissions: id-token: write contents: read steps: # Once github action supports nested composite actions (anything `uses` is a composite action) # Therefore we cannot reuse the code as a separate composite action until it supports it, # current the deploy logic is in this file twice because of it ## https://github.com/actions/runner/issues/862 - uses: actions/checkout@v2 - name: Configure AWS credentials for S3 sync uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.ZERO_DOC_SITE_AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.ZERO_DOC_SITE_AWS_SECRET_ACCESS_KEY }} aws-region: ${{ env.region }} - name: Download build artifact from Github uses: actions/download-artifact@v1 with: name: build-artifact path: build/ - name: Sync with S3 shell: bash run: | cd build aws s3 sync . "s3://${{ secrets.ZERO_DOC_SITE_BUCKET_NAME }}${{ env.s3_sync_path }}" --exclude "${{ env.s3_sync_path_to_exclude }}" --delete - name: Invalidate Cloudfront shell: bash run: | export DIST_ID=$(aws cloudfront list-distributions --query "DistributionList.Items[?Aliases.Items[?@=='${{ secrets.ZERO_DOC_SITE_BUCKET_NAME }}']].Id | [0]" | tr -d '"') aws cloudfront create-invalidation --distribution-id ${DIST_ID} --paths "/*" ================================================ FILE: .gitignore ================================================ main-packr.go packrd /zero .history/ tmp .vscode example/ test-reports/ .circleci/config-compiled.yml .idea/ ================================================ FILE: .goreleaser.yml ================================================ # http://goreleaser.com before: hooks: - go mod download builds: - env: - CGO_ENABLED=0 ldflags: - -X github.com/commitdev/zero/version.AppVersion={{.Version}} -X github.com/commitdev/zero/version.AppBuild={{.ShortCommit}} goarch: - amd64 - arm64 - 386 archives: - replacements: darwin: Darwin linux: Linux 386: i386 amd64: x86_64 checksum: name_template: 'checksums.txt' snapshot: name_template: "{{ .Tag }}-next" changelog: sort: asc filters: exclude: - '^docs:' - '^test:' brews: - name: zero tap: owner: commitdev name: homebrew-zero commit_author: name: Commit.dev email: contact@commit.dev homepage: "https://github.com/commitdev/zero" description: "Allow startup developers to ship to production on day 1." dependencies: - name: git type: optional - name: terraform type: optional - name: jq type: optional - name: awscli type: optional - name: kubectl type: optional - name: wget type: optional test: | system "#{bin}/zero version" nfpms: - id: zero package_name: zero vendor: Commit.dev homepage: https://github.com/commitdev/zero maintainer: commitdev description: "Allow startup developers to ship to production on day 1." formats: - apk - deb - rpm recommends: - awscli - git - jq - wget overrides: apk: recommends: - aws-cli - git - jq - wget ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Zero Thanks for your interest in Zero! Zero is a fully open source project started by [Commit](https://commit.dev) but we are happy to be receiving support from our users and community. If you have used Zero or are just interested in helping out there are a few key ways to contribute: ## Contribute some code Zero is made up of a number of different, modular components. Eventually the idea is to make these more composable and discoverable with a module repository for anyone to supply their own, but for now we mostly organize the modules into predefined "stacks" combining the layers of infrastructure, backend, frontend, and static site. Each module is in its own repo and has its own focus, language, etc. so there's plenty to contribute, regardless of your language of choice. Here's a list of the core repositories: |_Repo_|_Language_|_Description_| |--|--|--| | [zero](https://github.com/commitdev/zero) | Go | This repo - the application used to do prompting, module fetching, template rendering, and executing the commands of each module | | [zero-aws-eks-stack](https://github.com/commitdev/zero-aws-eks-stack) | Terraform | The terraform code to create all infrastructure required to host the backend and frontend applications in AWS (primarily using EKS) | | [zero-backend-go](https://github.com/commitdev/zero-backend-go) | Go | A deployable backend service providing an API written in Go | | [zero-backend-node](https://github.com/commitdev/zero-backend-node) | Node.js | A deployable backend service providing an API written in Node | | [zero-frontend-react](https://github.com/commitdev/zero-frontend-react) | Javascript / React | A deployable web frontend meant to communicate with one of the zero backend services | | [zero-static-site-gatsby](https://github.com/commitdev/zero-static-site-gatsby) | Javascript / Gatsby | A deployable static site / marketing site for your application | | [zero-notification-service](https://github.com/commitdev/zero-notification-service) | Go | A service to abstract away some concepts around sending notifications (via Email, SMS, Slack, etc.) | | [terraform-aws-zero](https://github.com/commitdev/zero-notification-service) | Terraform | Terraform modules that are exposed via the [Terrform Registry](https://registry.terraform.io/modules/commitdev/zero/aws/latest) and used in Zero modules. Typically functionality that is reusable and standalone | There is a [GitHub Projects board](https://github.com/orgs/commitdev/projects/6/views/2) to aggregate the issues across all these repositories. This is a great way to get a sense of the work that is available and in the backlog across all the various modules and languages. We are trying to make sure to keep a good amount of issues in the backlog with the "[good first issue](https://github.com/orgs/commitdev/projects/6/views/2?filterQuery=label%3A%22good+first+issue%22)" label and any issues with this label could be a good place to start either because they are relatively easy or have few dependencies. We also try to include an estimate (Fibonacci where 1 is trivial, probably just a couple lines of code, and 8 or 13 would be a huge undertaking that likely needs to be split up into smaller issues.) ### Pull Requests If you're interested in taking on an issue please comment on it to let other people know that someone is working on it. Then you can fork the repo and start local development. When committing code, please sign your commits and try to include relevant commit messages, starting with a tag indicating what type of change you are making. Some of the repositories use these tags to automatically generate changelogs. (`feat`, `fix`, `enhancement`, `docs`, etc.) For example: `fix: add proper encoding of billing parameters` or `enhancement: support new terraform kubernetes provider` When submitting a pull request to one of the projects please try to follow any PR and style guidelines, and make sure any relevant GitHub Actions tests are passing. If one of the tests is failing and you don't think it's related to your change, please let us know. ## Contribute documentation Any place you find the documentation to be lacking or incorrect we would be happy for someone to contribute a change. The documentation at our [public docs site](https://getzero.dev/docs/) is auto-generated using [Docusaurus](https://docusaurus.io/), and is updated automatically when any PR is merged into the main branch in one of the repositories. ## Write up a bug report If you run into a problem when using Zero, please feel free to create a ticket in the relevant repository. Ideally it will be clear which repo the issue should be in, but if you're not sure you can create it in the main zero repo and we will move it around if necessary. Please include as much detail as possible and any reproduction steps that might be necessary. ## Request a feature If there's something you think should be part of Zero but you don't see it yet, you can join the conversation in the [Zero community Slack](https://slack.getzero.dev) or [GitHub Discussions](https://github.com/commitdev/zero/discussions) and if we think it fits and it's not already covered in our roadmap we will add an issue for it. ## Tell a friend If you like what we're doing, please share it with your network! ================================================ FILE: Dockerfile ================================================ FROM golang:1.12.12-alpine3.10 as builder ENV GO111MODULE=on ENV TERRAFORM_VERSION=0.12.13 RUN apk add --update --no-cache build-base curl git upx && \ rm -rf /var/cache/apk/* RUN apk add --update nodejs npm RUN curl -sSL \ https://amazon-eks.s3-us-west-2.amazonaws.com/1.14.6/2019-08-22/bin/linux/amd64/aws-iam-authenticator \ -o /usr/local/bin/aws-iam-authenticator RUN GO111MODULE=off go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway RUN curl -sSLo /tmp/terraform.zip "https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip" && \ unzip -d /usr/local/bin/ /tmp/terraform.zip RUN chmod +x /usr/local/bin/* && \ upx --lzma /usr/local/bin/* # Hydrate the dependency cache. This way, if the go.mod or go.sum files do not # change we can cache the depdency layer without having to reinstall them. WORKDIR /tmp/zero COPY go.mod go.sum ./ RUN go mod download COPY . . RUN make build && \ mv zero /usr/local/bin && \ upx --lzma /usr/local/bin/zero FROM alpine:3.10 ENV \ PROTOBUF_VERSION=3.6.1-r1 \ GOPATH=/proto-libs RUN apk add --update bash ca-certificates git python && \ apk add --update -t deps make py-pip RUN mkdir ${GOPATH} COPY --from=builder /usr/local/bin /usr/local/bin COPY --from=builder /go/src/github.com/grpc-ecosystem/grpc-gateway ${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway WORKDIR /project ENTRYPOINT ["/usr/local/bin/zero"] ================================================ FILE: LICENSE ================================================ Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. ================================================ FILE: LICENSE.spdx ================================================ SPDXVersion: SPDX-2.1 DataLicense: CC0-1.0 PackageName: Zero PackageOriginator: Commit PackageHomePage: https://github.com/commitdev/zero/ PackageLicenseDeclared: MPL-2.0 ================================================ FILE: Makefile ================================================ VERSION = 0.0.1 BUILD ?=$(shell git rev-parse --short HEAD) PKG ?=github.com/commitdev/zero BUILD_ARGS=-v -trimpath -ldflags=all="-X ${PKG}/version.AppVersion=${VERSION} -X ${PKG}/version.AppBuild=${BUILD}" deps: go mod download check: go list -f '{{.Dir}}' ./... | grep -v /tmp/ | xargs go test -v fmt: go fmt ./... build-docker-local: docker build . -t zero:v0 build-example-docker: clean-example mkdir -p example docker run -v "$(shell pwd)/example:/project" --user $(shell id -u):$(shell id -g) zero:v0 create "hello-world" docker run -v "$(shell pwd)/example/hello-world:/project" --user $(shell id -u):$(shell id -g) zero:v0 generate -l go build: go build ${BUILD_ARGS} # Installs the CLI int your GOPATH install-go: go build -o ${GOPATH}/bin/zero # CI Commands used on CircleCI ci-docker-build: docker build . -t commitdev/zero:${VERSION_TAG} -t commitdev/zero:latest ci-docker-push: echo "${DOCKERHUB_PASS}" | docker login -u commitdev --password-stdin docker push commitdev/zero:${VERSION_TAG} ================================================ FILE: README.md ================================================ [![Tests](https://circleci.com/gh/commitdev/zero.svg?style=shield)](https://app.circleci.com/pipelines/github/commitdev/zero) [![Go Report Card](https://goreportcard.com/badge/commitdev/zero)](https://goreportcard.com/report/commitdev/zero) [![Slack](https://img.shields.io/badge/slack-join-brightgreen?logo=slack&style=social)](https://slack.getzero.dev)

## What is Zero Zero is an open source tool which makes it quick and easy for startup technical founders and developers to build everything they need to launch and grow high-quality SaaS applications faster and more cost-effectively. Zero sets up everything you need so you can immediately start building your product. Zero was created by [Commit](https://commit.dev). ## Why is Zero good for startups As a technical founder or the first technical hire at a startup, your sole focus is to build the logic for your application and get it into customers’ hands as quickly and reliably as possible. Yet you immediately face multiple hurdles before even writing the first line of code. You’re forced to make many tech trade-offs, leading to decision fatigue. You waste countless hours building boilerplate SaaS features not adding direct value to your customers. You spend precious time picking up unfamiliar tech, make wrong choices that result in costly refactoring or rebuilding in the future, and are unaware of tools and best practices that would speed up your product iteration. Zero was built by a team of engineers with many years of experience in building and scaling startups. We have faced all the problems you will and want to provide a way for new startups to avoid all those pitfalls. We also want to help you learn about the tech choices we made so your team can become proficient in some of the great tools we have included. The system you get starts small but allows you to scale well into the future when you need to. Everything built by Zero is yours. After using Zero to generate your infrastructure, backend, and frontend, all the code is checked into your source control repositories and becomes the basis for your new system. We provide constant updates and new modules that you can pull in on an ongoing basis, but you can also feel free to customize as much as you like with no strings attached. If you do happen to make a change to core functionality and feel like contributing it back to the project, we'd love that too! It's easy to get started, the only thing you'll need is an AWS account. Just enter your AWS CLI tokens or choose your existing profile during the setup process and everything is built for you automatically using infrastructure-as-code so you can see exactly what's happening and easily modify it if necessary. [Read about the day-to-day experience of using a system set up using Zero](https://getzero.dev/docs/zero/about/real-world-usage) ## Why is Zero Reliable, Scalable, Performant, and Secure Reliability: Your infrastructure will be set up in multiple availability zones making it highly available and fault tolerant. All production workloads will run with multiple instances by default, using AWS ELB and Nginx to load balance traffic. All infrastructure is represented with code using [HashiCorp Terraform][terraform] so your environments are reproducible, auditable, and easy to configure. Scalability: Your services will be running in Kubernetes, with the EKS nodes running in an AWS [Auto Scaling Group][asg]. Both the application workloads and cluster size are ready to scale whenever the need arises. Your frontend assets will be stored in S3 and served from AWS' Cloudfront CDN which operates at global scale. Security: Properly configured access-control to resources/security groups, using secret storage systems (AWS Secret Manager, Kubernetes secrets), and following best practices provides great security out of the box. Our practices are built on top of multiple security audits and penetration tests. Automatic certificate management using [Let's Encrypt][letsencrypt], database encryption, VPN support, and more means your traffic will always be encrypted. Built-in application features like user authentication help you bullet-proof your application by using existing, tested tools rather than reinventing the wheel when it comes to features like user management and auth. ## What do you get out of the box? [Read about why we made these technology choices and where they are most applicable.](https://getzero.dev/docs/zero/about/technology-choices) [Check out some resources for learning more about these technologies.](https://getzero.dev/docs/zero/reference/learning-resources) ### Infrastructure - Fully configured infrastructure-as-code AWS environment including: - VPCs per environment (staging, production) with pre-configured subnets, security groups, etc. - EKS Kubernetes cluster per environment, pre-configured with helpful tools like cert-manager, external-dns, nginx-ingress-controller - RDS database for your application (Postgres or MySQL) - S3 buckets and Cloudfront distributions to serve your assets - Logging and Metrics collected automatically using either Cloudwatch or Prometheus + Grafana, Elasticsearch + Kibana - VPN using [Wireguard][wireguard] (Optional) - User management and Identity / Access Proxy using Ory [Kratos][kratos] and [Oathkeeper][oathkeeper] (Optional) - Tooling to make it easy to set up secure access for your dev team - Local/Cloud Hybrid developer environment using Telepresence (Optional) ### Backend - Golang or Node.js example project automatically set up, Dockerized, and deployed to your new Kubernetes cluster - CI pipeline built with [CircleCI][circleci] or GitHub Actions. Just merge a PR and a deploy will start. Your code will be built and tested, deployed to staging, then prompt you to push to production - File upload / download support using signed Cloudfront URLs (Optional) - Email support using [SendGrid][sendgrid] or AWS SES (Optional) - Notification support for sending and receiving messages in your application (web, mobile, SMS, Email, etc.) (Optional) (In Progress) - User management integration with Kratos and Oathkeeper - No need to handle login, signup, authentication yourself (Optional) ### Frontend - React example project automatically set up, deployed and served securely to your customers - CI pipeline built with CircleCI or GitHub Actions. Just merge a PR and a deploy will start. Your code will be built and tested, deployed to staging, then prompt you to push to production - File upload / download support using signed Cloudfront URLs (Optional) - User management integration with Kratos - Just style the example login / signup flow to look the way you want (Optional) - Static site example project using Gatsby to easily make a landing page, also set up with a CI Pipeline using CircleCI (Optional) ___ ## Getting Started [See the getting started guide at the Zero docs site.](https://getzero.dev/docs/zero/getting-started/installation) ### Building blocks of Zero ### Project Definition: Each project is defined by this project definition file, this manifest contains your project details, and is the source of truth for the templating(`zero create`) and provision(`zero apply`) steps. See [`zero-project.yml` reference](https://getzero.dev/docs/zero/reference/project-definition) for details. ### Module Definition Module definition defines the information needed for the module to run (`zero apply`). Also declares dependency used to determine the order of execution with other modules. See [`zero-module.yml` reference](https://getzero.dev/docs/zero/reference/module-definition) for details. ___ ## Zero Default Stack [System Architecture Diagram](https://raw.githubusercontent.com/commitdev/zero-aws-eks-stack/main/docs/architecture-overview.svg) The core zero modules currently available are: | Project | URL | |---|---| | AWS Infrastructure | [https://github.com/commitdev/zero-aws-eks-stack](https://github.com/commitdev/zero-aws-eks-stack) | | Backend (Go) | [https://github.com/commitdev/zero-backend-go](https://github.com/commitdev/zero-backend-go) | | Backend (Node.js) | [https://github.com/commitdev/zero-backend-node](https://github.com/commitdev/zero-backend-node) | | Frontend (React) | [https://github.com/commitdev/zero-frontend-react](https://github.com/commitdev/zero-frontend-react) | | Static Site (Gatsby) | [https://github.com/commitdev/zero-static-site-gatsby](https://github.com/commitdev/zero-static-site-gatsby) | ___ ## Contributing to Zero Zero welcomes collaboration from the community; you can open new issues in our GitHub repo, Submit PRs' for bug fixes or browse through the tickets currently open to see what you can contribute too. We use Zenhub to show us the entire project across all repositories, so if you are interested in seeing that or participating, you can can [check out our workspace](https://app.zenhub.com/workspaces/commit-zero-5da8decc7046a60001c6db44/board?repos=203630543,247773730,257676371,258369081,291818252,293942410,285931648,317656612) ### Building the tool ```shell $ git clone git@github.com:commitdev/zero.git $ cd zero && make build ``` ### Running the tool locally To install the CLI into your GOPATH and test it, run: ```shell $ make install-go $ zero --help ``` ### Releasing a new version on GitHub and Brew We are using a tool called `goreleaser` which you can get from brew if you're on MacOS: `brew install goreleaser` After you have the tool, you can follow these steps: ``` export GITHUB_TOKEN= git tag -s -a -m "Some message about this release" git push origin goreleaser release ``` This will create a new release in GitHub and automatically collect all the commits since the last release into a changelog. It will also build binaries for various OSes and attach them to the release and push them to brew. The configuration for goreleaser is in [.goreleaser.yml](.goreleaser.yml) ___ ## FAQ Why is my deployed application not yet accessible? - It takes about 20 - 35 mins for your deployed application to be globally available through AWS CloudFront CDN. [acw]: https://aws.amazon.com/cloudwatch/ [vpc]: https://aws.amazon.com/vpc/ [iam]: https://aws.amazon.com/iam/ [asg]: https://aws.amazon.com/autoscaling/ [zero binary]: https://github.com/commitdev/zero/releases/ [and more]: https://github.com/commitdev/zero-aws-eks-stack/blob/master/docs/resources.md [terraform]: https://terraform.io [letsencrypt]: https://letsencrypt.org/ [kratos]: https://www.ory.sh/kratos/ [oathkeeper]: https://www.ory.sh/oathkeeper/ [wireguard]: https://wireguard.com/ [circleci]: https://circleci.com/ [sendgrid]: https://sendgrid.com/ [launchdarkly]: https://launchdarkly.com/ ================================================ FILE: cmd/apply.go ================================================ package cmd import ( "log" "os" "github.com/commitdev/zero/internal/apply" "github.com/commitdev/zero/internal/config/projectconfig" "github.com/commitdev/zero/internal/constants" "github.com/spf13/cobra" ) var applyConfigPath string var applyEnvironments []string func init() { applyCmd.PersistentFlags().StringVarP(&applyConfigPath, "config", "c", constants.ZeroProjectYml, "config path") applyCmd.PersistentFlags().StringSliceVarP(&applyEnvironments, "env", "e", []string{}, "environments to set up (staging, production) - specify multiple times for multiple") rootCmd.AddCommand(applyCmd) } var applyCmd = &cobra.Command{ Use: "apply", Short: "Execute modules to create projects, infrastructure, etc.", Run: func(cmd *cobra.Command, args []string) { rootDir, err := os.Getwd() if err != nil { log.Println(err) rootDir = projectconfig.RootDir } applyErr := apply.Apply(rootDir, applyConfigPath, applyEnvironments) if applyErr != nil { log.Fatal(applyErr) } }, } ================================================ FILE: cmd/check.go ================================================ package cmd import ( "fmt" "os" "os/exec" "regexp" "strings" "text/tabwriter" "github.com/coreos/go-semver/semver" "github.com/spf13/cobra" ) func init() { rootCmd.AddCommand(checkCmd) } type requirement struct { name string command string args []string minVersion string regexStr string docsURL string } type versionError struct { errorText string } type commandError struct { Command string ErrorText string Suggestion string } func (e *versionError) Error() string { return fmt.Sprintf("%s", e.errorText) } func (e *commandError) Error() string { return fmt.Sprintf("%s", e.ErrorText) } func printErrors(errors []commandError) { // initialize tabwriter w := new(tabwriter.Writer) // minwidth, tabwidth, padding, padchar, flags w.Init(os.Stdout, 10, 12, 2, ' ', 0) defer w.Flush() fmt.Fprintf(w, "\n %s\t%s\t%s\t", "Command", "Error", "Info") fmt.Fprintf(w, "\n %s\t%s\t%s\t", "---------", "---------", "---------") for _, e := range errors { fmt.Fprintf(w, "\n%s\t%s\t%s\t", e.Command, e.ErrorText, e.Suggestion) } } // getSemver uses the regular expression from the requirement to parse the // output of a command and extract the version from it. Returns the version // or an error if the version string could not be parsed. func getSemver(req requirement, out []byte) (*semver.Version, error) { re := regexp.MustCompile(req.regexStr) v := re.FindStringSubmatch(string(out)) if len(v) < 4 { return nil, &commandError{ req.command, "Could not find version number in output", fmt.Sprintf("Try running %s %s locally and checking it works.", req.command, strings.Join(req.args, " ")), } } // Default patch version number to 0 if it doesn't exist if v[3] == "" { v[3] = "0" } versionString := fmt.Sprintf("%s.%s.%s", v[1], v[2], v[3]) version, err := semver.NewVersion(versionString) if err != nil { return version, err } return version, nil } // checkSemver validates that the version of a tool meets the minimum required // version listed in your requirement. Returns a boolean. // For more information on parsing semver, see semver.org // If your tool doesn't do full semver then you may need to add custom logic // to support it. func checkSemver(req requirement, actualVersion *semver.Version) bool { requiredVersion := semver.New(req.minVersion) return actualVersion.LessThan(*requiredVersion) } var checkCmd = &cobra.Command{ Use: "check", Short: "Check Zero requirements", Run: func(cmd *cobra.Command, args []string) { // Add any new requirements to this slice. required := []requirement{ { name: "AWS CLI\t\t", command: "aws", args: []string{"--version"}, regexStr: `aws-cli\/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)`, minVersion: "1.16.0", docsURL: "https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html", }, { name: "Kubectl\t\t", command: "kubectl", args: []string{"version", "--client=true", "--short"}, regexStr: `Client Version: v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)`, minVersion: "1.15.2", docsURL: "https://kubernetes.io/docs/tasks/tools/install-kubectl/", }, { name: "Docker\t\t", command: "docker", args: []string{"--version"}, regexStr: `Docker version (0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*), build ([a-f0-9]{7})`, minVersion: "18.0.0", docsURL: "https://docs.docker.com/get-docker/", }, { name: "Terraform\t", command: "terraform", args: []string{"version"}, regexStr: `Terraform v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)`, minVersion: "0.13.0", docsURL: "https://www.terraform.io/downloads.html", }, { name: "jq\t\t", command: "jq", args: []string{"--version"}, regexStr: `jq-(0|[1-9]\d*)\.(0|[1-9]\d*)\-?(0|[1-9]\d*)?`, minVersion: "1.5.0", docsURL: "https://stedolan.github.io/jq/download/", }, { name: "Git\t\t", command: "git", args: []string{"version"}, regexStr: `^git version (0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)`, minVersion: "2.17.1", docsURL: "https://git-scm.com/book/en/v2/Getting-Started-Installing-Git", }, { name: "Wget\t\t", command: "wget", args: []string{"--version"}, regexStr: `^GNU Wget (0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)`, minVersion: "1.14.0", docsURL: "https://www.gnu.org/software/wget/", }, } // Store and errors from the commands we run. errors := []commandError{} fmt.Println("Checking Zero Requirements...") for _, r := range required { fmt.Printf("%s", r.name) // In future we could parse the stderr and stdout separately, but for now it's nice to see // the full output on a failure. out, err := exec.Command(r.command, r.args...).CombinedOutput() if err != nil { cerr := commandError{ fmt.Sprintf("%s %s", r.command, strings.Join(r.args, " ")), err.Error(), r.docsURL, } errors = append(errors, cerr) fmt.Printf("\033[0;31mFAIL\033[0m\t\t%s\n", "-") continue } version, err := getSemver(r, out) if err != nil { cerr := commandError{ r.command, err.Error(), r.docsURL, } errors = append(errors, cerr) fmt.Printf("\033[0;31mFAIL\033[0m\t\t%s\n", version) continue } if checkSemver(r, version) { cerr := commandError{ r.command, fmt.Sprintf("Version does not meet required. Want: %s; Got: %s", r.minVersion, version), r.docsURL, } errors = append(errors, cerr) fmt.Printf("\033[0;31mFAIL\033[0m\t\t%s\n", version) } else { fmt.Printf("\033[0;32mPASS\033[0m\t\t%s\n", version) } } if len(errors) > 0 { printErrors(errors) os.Exit(1) } fmt.Println() }, } ================================================ FILE: cmd/create.go ================================================ package cmd import ( "fmt" "path" "strings" "github.com/commitdev/zero/internal/config/projectconfig" "github.com/commitdev/zero/internal/constants" "github.com/commitdev/zero/internal/generate" "github.com/commitdev/zero/internal/vcs" "github.com/commitdev/zero/pkg/util/exit" "github.com/commitdev/zero/pkg/util/flog" "github.com/spf13/cobra" ) var ( createConfigPath string overwriteFiles bool ) func init() { createCmd.PersistentFlags().StringVarP(&createConfigPath, "config", "c", constants.ZeroProjectYml, "The project.yml file to load. The default is the one in the current directory.") createCmd.PersistentFlags().BoolVarP(&overwriteFiles, "overwrite", "o", false, "overwrite pre-existing files") rootCmd.AddCommand(createCmd) } var createCmd = &cobra.Command{ Use: "create", Short: fmt.Sprintf("Create projects for modules and configuration specified in %s", constants.ZeroProjectYml), Run: func(cmd *cobra.Command, args []string) { Create(projectconfig.RootDir, createConfigPath) }, } func Create(dir string, createConfigPath string) { if strings.Trim(createConfigPath, " ") == "" { exit.Fatal("config path cannot be empty!") } configFilePath := path.Join(dir, createConfigPath) projectConfig := projectconfig.LoadConfig(configFilePath) generate.Generate(*projectConfig, overwriteFiles) if projectConfig.ShouldPushRepositories { flog.Infof(":up_arrow: Done Rendering - committing repositories to version control.") for _, module := range projectConfig.Modules { err, githubApiKey := projectconfig.ReadVendorCredentialsFromModule(module, "github") if err != nil { flog.Errorf(err.Error()) } vcs.InitializeRepository(module.Files.Repository, githubApiKey) } } else { flog.Infof(":up_arrow: Done Rendering - you will need to commit the created projects to version control.") } flog.Infof(":check_mark_button: Done - run zero apply to create any required infrastructure or execute any other remote commands to prepare your environments.") } ================================================ FILE: cmd/init.go ================================================ package cmd import ( "fmt" "github.com/commitdev/zero/internal/config/projectconfig" initPrompts "github.com/commitdev/zero/internal/init" "github.com/commitdev/zero/pkg/util/exit" "github.com/commitdev/zero/pkg/util/flog" "github.com/spf13/cobra" ) var localModulePath string var registryFilePath string func init() { initCmd.PersistentFlags().StringVarP(&localModulePath, "local-module-path", "m", "github.com/commitdev", "local module path - for using local modules instead of downloading from github") initCmd.PersistentFlags().StringVarP(®istryFilePath, "registry-file-path", "r", "https://raw.githubusercontent.com/commitdev/zero/main/registry.yaml", "registry file path - for using a custom list of stacks") rootCmd.AddCommand(initCmd) } var initCmd = &cobra.Command{ Use: "init", Short: "Create new project with provided name and initialize configuration based on user input.", Run: func(cmd *cobra.Command, args []string) { flog.Debugf("Root directory is %s", projectconfig.RootDir) projectContext := initPrompts.Init(projectconfig.RootDir, localModulePath, registryFilePath) projectConfigErr := projectconfig.CreateProjectConfigFile(projectconfig.RootDir, projectContext.Name, projectContext) if projectConfigErr != nil { exit.Fatal(fmt.Sprintf(" Init failed while creating the zero project config file %s", projectConfigErr.Error())) } else { flog.Infof(`:tada: Done - Your project definition file has been initialized with your choices. Please review it, make any required changes and then create your project. cd %s cat zero-project.yml zero create`, projectContext.Name) } }, } ================================================ FILE: cmd/version.go ================================================ package cmd import ( "fmt" "github.com/commitdev/zero/version" "github.com/spf13/cobra" ) func init() { rootCmd.AddCommand(versionCmd) } var versionCmd = &cobra.Command{ Use: "version", Short: "Print the version number of zero", Run: func(cmd *cobra.Command, args []string) { fmt.Printf("version: %v\n", version.AppVersion) fmt.Printf("build: %v\n", version.AppBuild) }, } ================================================ FILE: cmd/zero.go ================================================ package cmd import ( "os" "github.com/spf13/cobra" ) var rootCmd = &cobra.Command{ Use: "zero", Short: "zero gets you to writing code quicker.", Long: "Zero is an open-source CLI tool which makes it quick and easy for technical founders & developers \nto build high-quality, reliable infrastructure to launch, grow, and scale production-ready SaaS applications faster and more cost-effectively.\n https://getzero.dev\n", Run: func(cmd *cobra.Command, args []string) { }, } func Execute() { if len(os.Args) > 1 { if err := rootCmd.Execute(); err != nil { os.Exit(1) } } else { // If no arguments were provided, print the usage message. rootCmd.Help() } } ================================================ FILE: doc-site/.gitignore ================================================ # Dependencies /node_modules # Production /build # Generated files .docusaurus .cache-loader # Misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* # to test theme color for elements locally docs/about/color-test.md* ================================================ FILE: doc-site/README.md ================================================ # Website This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. ## Installation ```console yarn install ``` ## Local Development ```console yarn start ``` This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. ## Build ```console yarn build ``` This command generates static content into the `build` directory and can be served using any static contents hosting service. ## Deployment ```console GIT_USER= USE_SSH=true yarn deploy ``` If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. ================================================ FILE: doc-site/babel.config.js ================================================ module.exports = { presets: [require.resolve('@docusaurus/core/lib/babel/preset')], }; ================================================ FILE: doc-site/docs/about/opensource.md ================================================ --- title: Opensource sidebar_label: Opensource sidebar_position: 2 --- ## Contributing to Zero Zero welcomes collaboration from the community; you can open new issues in our GitHub repo, Submit PRs' for bug fixes or browse through the tickets currently open to see what you can contribute too. We use Zenhub to show us the entire project across all repositories, so if you are interested in seeing that or participating, you can can [check out our workspace](https://app.zenhub.com/workspaces/commit-zero-5da8decc7046a60001c6db44/board?repos=203630543,247773730,257676371,258369081,291818252,293942410,285931648,317656612) ================================================ FILE: doc-site/docs/about/overview.md ================================================ --- title: Overview sidebar_label: Overview sidebar_position: 1 --- ## What is Zero Zero is an open source tool which makes it quick and easy for startup technical founders and developers to build everything they need to launch and grow high-quality SaaS applications faster and more cost-effectively. Zero sets up everything you need so you can immediately start building your product. Zero was created by [Commit](https://commit.dev). ## Why is Zero good for startups As a technical founder or the first technical hire at a startup, your sole focus is to build the logic for your application and get it into customers’ hands as quickly and reliably as possible. Yet you immediately face multiple hurdles before even writing the first line of code. You’re forced to make many tech trade-offs, leading to decision fatigue. You waste countless hours building boilerplate SaaS features not adding direct value to your customers. You spend precious time picking up unfamiliar tech, make wrong choices that result in costly refactoring or rebuilding in the future, and are unaware of tools and best practices that would speed up your product iteration. Zero was built by a team of engineers with many years of experience in building and scaling startups. We have faced all the problems you will and want to provide a way for new startups to avoid all those pitfalls. We also want to help you learn about the tech choices we made so your team can become proficient in some of the great tools we have included. The system you get starts small but allows you to scale well into the future when you need to. Everything built by Zero is yours. After using Zero to generate your infrastructure, backend, and frontend, all the code is checked into your source control repositories and becomes the basis for your new system. We provide constant updates and new modules that you can pull in on an ongoing basis, but you can also feel free to customize as much as you like with no strings attached. If you do happen to make a change to core functionality and feel like contributing it back to the project, we'd love that too! It's easy to get started, the only thing you'll need is an AWS account. Just enter your AWS CLI tokens or choose your existing profile during the setup process and everything is built for you automatically using infrastructure-as-code so you can see exactly what's happening and easily modify it if necessary. [Read about the day-to-day experience of using a system set up using Zero][real-world-usage] ## Why is Zero Reliable, Scalable, Performant, and Secure Reliability: Your infrastructure will be set up in multiple availability zones making it highly available and fault tolerant. All production workloads will run with multiple instances by default, using AWS ELB and Nginx to load balance traffic. All infrastructure is represented with code using [HashiCorp Terraform][terraform] so your environments are reproducible, auditable, and easy to configure. Scalability: Your services will be running in Kubernetes, with the EKS nodes running in an AWS [Auto Scaling Group][asg]. Both the application workloads and cluster size are ready to scale whenever the need arises. Your frontend assets will be stored in S3 and served from AWS' Cloudfront CDN which operates at global scale. Security: Properly configured access-control to resources/security groups, using secret storage systems (AWS Secret Manager, Kubernetes secrets), and following best practices provides great security out of the box. Our practices are built on top of multiple security audits and penetration tests. Automatic certificate management using [Let's Encrypt][letsencrypt], database encryption, VPN support, and more means your traffic will always be encrypted. Built-in application features like user authentication help you bullet-proof your application by using existing, tested tools rather than reinventing the wheel when it comes to features like user management and auth. ## What do you get out of the box? [Read about why we made these technology choices and where they are most applicable.][technology-choices] [Check out some resources for learning more about these technologies.][learning-resources] ### [Infrastructure][docs-infra] - Fully configured infrastructure-as-code AWS environment including: - VPCs per environment (staging, production) with pre-configured subnets, security groups, etc. - EKS Kubernetes cluster per environment, pre-configured with helpful tools like cert-manager, external-dns, nginx-ingress-controller - RDS database for your application (Postgres or MySQL) - S3 buckets and Cloudfront distributions to serve your assets - Logging and Metrics collected automatically using either Cloudwatch or Prometheus + Grafana, Elasticsearch + Kibana - VPN using [Wireguard][wireguard] (Optional) - User management and Identity / Access Proxy using Ory [Kratos][kratos] and [Oathkeeper][oathkeeper] (Optional) - Tooling to make it easy to set up secure access for your dev team - Local/Cloud Hybrid developer environment using Telepresence (Optional) ### [Backend][docs-backend-go] ([Go][docs-backend-go] / [Node.js][docs-backend-nodejs]) - Golang or Node.js example project automatically set up, Dockerized, and deployed to your new Kubernetes cluster - CI pipeline built with [CircleCI][circleci] or GitHub Actions. Just merge a PR and a deploy will start. Your code will be built and tested, deployed to staging, then prompt you to push to production - File upload / download support using signed Cloudfront URLs (Optional) - Email support using [SendGrid][sendgrid] or AWS SES (Optional) - Notification support for sending and receiving messages in your application (web, mobile, SMS, Email, etc.) (Optional) (In Progress) - User management integration with Kratos and Oathkeeper - No need to handle login, signup, authentication yourself (Optional) ### [Frontend][docs-frontend-react] - React example project automatically set up, deployed and served securely to your customers - CI pipeline built with CircleCI or GitHub Actions. Just merge a PR and a deploy will start. Your code will be built and tested, deployed to staging, then prompt you to push to production - File upload / download support using signed Cloudfront URLs (Optional) - User management integration with Kratos - Just style the example login / signup flow to look the way you want (Optional) - Static site example project using Gatsby to easily make a landing page, also set up with a CI Pipeline using CircleCI (Optional) [real-world-usage]: ./real-world-usage [technology-choices]: ./technology-choices [learning-resources]: ../reference/learning-resources [docs-infra]: https://getzero.dev/docs/modules/aws-eks-stack/ [docs-backend-go]: https://getzero.dev/docs/modules/backend-go/ [docs-backend-nodejs]: https://getzero.dev/docs/modules/backend-nodejs/ [docs-frontend-react]: https://getzero.dev/docs/modules/frontend-react/ [git]: https://git-scm.com [kubectl]: https://kubernetes.io/docs/tasks/tools/install-kubectl/ [terraform]:https://www.terraform.io/downloads.html [jq]: https://github.com/stedolan/jq [AWS CLI]: https://aws.amazon.com/cli/ [acw]: https://aws.amazon.com/cloudwatch/ [vpc]: https://aws.amazon.com/vpc/ [iam]: https://aws.amazon.com/iam/ [asg]: https://aws.amazon.com/autoscaling/ [zero binary]: https://github.com/commitdev/zero/releases/ [Wget]: https://stackoverflow.com/questions/33886917/how-to-install-wget-in-macos [and more]: https://github.com/commitdev/zero-aws-eks-stack/blob/master/docs/resources.md [terraform]: https://terraform.io [letsencrypt]: https://letsencrypt.org/ [kratos]: https://www.ory.sh/kratos/ [oathkeeper]: https://www.ory.sh/oathkeeper/ [wireguard]: https://wireguard.com/ [circleci]: https://circleci.com/ [sendgrid]: https://sendgrid.com/ [launchdarkly]: https://launchdarkly.com/ ================================================ FILE: doc-site/docs/about/real-world-usage.md ================================================ --- title: Real-world Usage Scenarios sidebar_label: Real-world Usage sidebar_position: 4 --- ## Developing and deploying application changes 1. Clone your git repository. 2. Make a branch, start working on your code. 3. If using the Telepresence dev experience, run the `start-dev-env.sh` script to allow you to use the hybrid cloud environment as you work, to run and test your code in a realistic environment. 3. Commit your finished code, make a PR, have it reviewed. Lightweight tests will run against your branch and prevent merging if they fail. 4. Merge your branch to the main branch. A build will start automatically. 5. The pipeline will build an artifact, run tests, deploy your change to staging, then wait for your input to deploy to production. ## Debugging a problem on production 1. Check the logs of your service: - If using cloudwatch, log into the AWS console and go to the [Logs Insights tool](https://us-west-2.console.aws.amazon.com/cloudwatch/home#logsV2:logs-insights). Choose the log group for your production environment ending in `/application` and hit the "Run query" button. - If using kibana, make sure you are on the VPN and open the Kibana URL in your browser. Click the "Discover" tab and try searching for logs based on the name of your service. - Alternatively, watch the logs in realtime via the CLI using the command `kubectl logs -f -l app=` or `stern ` 2. Check the state of your application pods. Look for strange events or errors from the pods: ```shell $ kubectl get pods $ kubectl get events $ kubectl describe pods ``` 3. Exec into your application pod. From here you can check connectivity with `ping` or `nc`, or inspect anything else application-specific. ```shell $ kubectl get pods NAME READY STATUS RESTARTS AGE your-service-6c5f6b56b7-2w447 1/1 Running 0 30m49s $ kubectl exec -it your-service-6c5f6b56b7-2w447 sh ``` ## Adding support for a new subdomain or site 1. Check the currently configured ingresses in your cluster: ```shell $ kubectl get ingress -A NAMESPACE NAME CLASS HOSTS ADDRESS PORTS AGE your-service your-service api.your-service.dev abcd1234-1234.us-west-2.elb.amazonaws.com 80, 443 130d ``` 2. If this is for a new service entirely, make sure there is an ingress defined in the `kubernetes/` directory of your repo. If you want to add a new domain pointing to an existing service, just go into the file `kubernetes/overlays//ingress.yml` and add a section to `spec:` and `tls:`, specifying your new domain. - `spec` is where you can define the hostname, any special path rules, and which service you want traffic to be sent to - if your hostname is in the `tls` section, a TLS certificate will automatically be provisioned for it using Let's Encrypt 3. A number of things will happen once this is deployed to the cluster: - Routing rules will be created to let traffic in to the cluster and send it to the service based on the hostname and path - An AWS load balancer will be created if one doesn't already exist and it will be pointed to the cluster - In the case of a subdomain, a DNS record will be automatically created for you - A certificate will be provisioned using Let's Encrypt for the domain you specified ================================================ FILE: doc-site/docs/about/roadmap.md ================================================ --- title: Roadmap sidebar_label: Roadmap sidebar_position: 5 --- :::info Coming soon ::: ================================================ FILE: doc-site/docs/about/technology-choices.md ================================================ --- title: Technology Choices sidebar_label: Technology Choices sidebar_position: 4 --- As we add features to Zero, we rely heavily on our years of experience with founding and growing startups, and judge tools and technologies based on the axes of: - Quality - Is it the best tool for the job? Will it allow a project to start small and scale big? - Simplicity - Is it easy to set up automatically and in a way that is easy to understand and maintain for the developers and operators of the project? - Price - Ideally we look for open source tools, but there are some tools we integrate with that have a cost if we see it as providing enough value to justify that cost. In this case we will often try to also include an open source alternative. When there are multiple technologies that we consider to be front-runners, we try to add multiple options, along with documentation and use cases to describe in which situations each tool might be the right choice for a project. ### Infrastructure #### **Cloud Provider** [AWS](https://aws.amazon.com) AWS has been around much longer than the other large cloud providers, and while GCP or Azure may have some additional niceties in terms of developer experience, ML functionality, etc., most of the features and functionality are pretty common across all platforms. AWS is fairly low-cost, has many features, good API and CLI support, easy to set up, and with full support for infrastructure-as-code using Terraform. It can also be quite easy for startups to get credits to offset some of their initial costs. GCP and Azure support are planned but not implemented. #### **Orchestration** [Kubernetes](https://kubernetes.io) Initially developed by Google as a scaled-down, open source version of the Borg system that runs all their applications. It has become the de facto choice for container orchestration due to its huge community adoption, flexibility of deployment, and giant feature set. Between its native functionality, and various tools that we include by default, it's an incredibly powerful platform for easily deploying secure, stable, highly-available applications. It also has first-class support for managed clusters in all the major cloud providers. We have used Kubernetes at startups in the past and have found it to be an incredible tool to allow you to start small, but scale up a huge amount. It offers a lot of native functionality that enables great developer flows, zero-downtime deploys, auto scaling, high availability, easy visibility, and more. Compared to something like EC2, there are lots of benefits to deploying containers as immutable artifacts, good integration with load balancers, fast deploys, etc. Compared to serverless, it is much easier to control, monitor, and have visibility, in addition to having much more flexibility, while giving you quite a similar experience in many ways. #### **Database** [RDS MySQL](https://aws.amazon.com/rds/mysql/) / [RDS Postgresql](https://aws.amazon.com/rds/postgresql/) RDS MySQL and Postgres give you all the benefits of these RDBMS tools without the burden of managing them yourself. Either one of these database technologies are great for most startups, and allow you to scale quite large, depending on your schema design and data set. For large or complex data you can also potentially move to [RDS Aurora](https://aws.amazon.com/rds/aurora/) which has additional functionality which allows you to scale further. #### **Logging** [Elasticsearch + Kibana](https://aws.amazon.com/elasticsearch-service/) AWS Managed Elasticsearch with Kibana is a great tool for centralized logging from your system. We use a tool called [Fluentd](https://www.fluentd.org/) to collect all the logs from your system and send them to Elasticsearch. Then Kibana is used to run queries against that data, set up alerts, create dashboards, etc. This is a great choice if you want more flexibility and more advanced features. It adds some complexity due to requiring an Elasticsearch cluster, but AWS' managed ES offering allows you to offload a lot of work that would typically be required. _- OR -_ [Cloudwatch](https://aws.amazon.com/cloudwatch/) Potentially a cheaper and lighter-weight option, Cloudwatch is an AWS tool that supports many (but not all) of the features of Kibana. This is a good choice if you don't have too many requirements about logging yet. #### **Monitoring** [Prometheus](https://prometheus.io/) + [Grafana](https://grafana.com/) Prometheus and Grafana run in the Kubernetes cluster. Prometheus provides metrics collection from your applications and Grafana provides graphing, dashboards, alerting, etc. on that data. It can also pull in data from many other sources. This is a great choice if you want more flexibility and more advanced features. It adds some complexity due to requiring these tools to run in your cluster but does add a significant amount of features over Cloudwatch. _- OR -_ [Cloudwatch](https://aws.amazon.com/cloudwatch/) Potentially a lighter-weight option, Cloudwatch is an AWS tool that supports many (but not all) of the features of Grafana. This is a good choice if you don't have too many requirements about monitoring yet. #### **Access** [VPN (Wireguard)](https://www.wireguard.com/) It is a good policy to keep as much of your infrastructure as possible private, exposing only what is necessary to the public internet. When we set up the Kubernetes cluster and other resources, they are all on private VPC Subnets so they can reach each other but are not otherwise reachable. When dealing with Kubernetes there is already great tooling for being able to access your application (via [`kubectl exec`](https://kubernetes.io/docs/tasks/debug-application-cluster/get-shell-running-container/)) but you may find cases where you want to access other resources as if you were on the private network yourself. To this end we are using Wireguard for VPN to allow your personal computer to act as if it were on the private network, having access to the things running inside your Kubernetes cluster, and within AWS. Wireguard is a great secure, light-weight tool that runs well on Kubernetes, so it doesn't require us to set up any other infrastructure. You may be able to get by without it if you are comfortable with the Kubernetes tooling, but it would be very useful if you are using something like Kibana above, so you can access it directly through your web browser. ### Application #### **User Management** [Ory Kratos](https://github.com/ory/kratos) Kratos is an open-source identity and user management tool. It can take the place of something like Auth0, or the user management that many startups end up writing themselves. By delegating this work to Kratos, you can save time and effort that you would otherwise have to put in to build a stable, secure user management system. If you are building a system that will require authenticated users, it may make sense to use Kratos with Oathkeeper (below) to save a lot of effort that may otherwise be spent building user management and auth features that have been built a million times before. #### **Authentication / Authorization** [Ory Oathkeeper](https://github.com/ory/oathkeeper) Oathkeeper is an open-source Identity & Access Proxy that can securely act as a gateway to your system, preventing unauthorized access. It uses Oauth & JWT to log a user in and maintain a session, and proxies traffic to your services for users with valid sessions. This allows you to save time and effort by not having to write auth and session management code. If a specific header exists, the specified user is logged in. That's it. #### **Dev Experience** [Telepresence](https://www.telepresence.io/) Telepresence is a great tool for creating a fast and effective "hybrid cloud/local" developer experience using Kubernetes. You can have your services, databases, and other dependencies running in the cloud, and then run the service you are working on locally, and have it override the one running in the cloud. In this way you can use all the same IDEs, debuggers, and local tools you usually do, but have your code running in a realistic environment much more like staging or production, complete with all the dependencies that can make local dev a pain. It's a great tool even when starting a project to quickly and easily test things in a realistic environment but can also really shine if you are working in a system that has many dependencies (multiple state stores, microservices, etc.) ### Backend While we offer ways to bootstrap your backend application in various languages, you can also choose to use whichever language you like and just make use of the infrastructure we set up, or use your custom backend with a bootstrapped frontend. The backend application will have a build and deploy pipeline that will build a docker image and push it to AWS' ECR image repository. The image will be used to deploy containers in the Kubernetes cluster. **Language** Whichever language you choose will be automatically set up, Dockerized, and deployed into the Kubernetes cluster. [Golang](https://golang.org/) Go is a great language for backend application development, especially in a microservices environment. It is a typed, compiled language with powerful support for parallel processing, and can create small, self-contained binaries for any operating system. It has a huge community, and is used by some very significant open source projects such as Kubernetes, and the HashiCorp tools. Being a compiled language may slightly raise the barrier to entry for newer developers. Lack of generic types and traditional object-oriented features, and preference for code generation may deter more experienced but unfamiliar developers. _- OR -_ [Node.js](https://nodejs.org/en/) Node.js is an extremely popular language for backend development. It has a huge community and tons of libraries available, since it is based on JavaScript. It can also be beneficial to use JavaScript for both the backend and frontend, in case developers are not familiar with other backend languages. Being a [single-threaded](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/), interpreted language, Node may not be ideal for high-performance applications. ### Frontend While we offer ways to bootstrap your frontend application in various languages, you can also choose to use whichever language you like and just make use of the infrastructure we set up, or use your custom frontend with a bootstrapped backend. The frontend application will have a build and deploy pipeline that will build an artifact that will be uploaded to S3, and then hosted through AWS' Cloudfront CDN. This allows your application to be stable and fast from all over the world. **Language** [React.js](https://reactjs.org/) One of the most popular modern libraries for building dynamic frontend applications. React is hugely popular, powerful, and is an obvious choice for most frontends. ### CI We advocate for (and configure by default) build pipelines for deploying your application. As opposed to deploying manually via the command line which may work fine for a single developer, when you are working on a team there are huge benefits to making GitHub your source of truth, and relying on your version control system to trigger builds and deployments. Our preferred pipeline looks something like this: ``` merge to main branch -> build artifacts -> run tests -> deploy to staging -> ask for user input -> deploy to production ``` But you can also configure it to fit your desired workflow. [CircleCI](https://circleci.com/) CircleCI is a powerful tool for creating CI/CD pipelines. They have a free tier which is fully featured, with paid plans that add more concurrency and speed for builds. It requires a bit more setup at the beginning, as you need to create an account with them, but it does have some additional features compared to GitHub Actions. _- OR -_ [GitHub Actions](https://github.com/features/actions) GitHub Actions is newer than CircleCI, so it's features are a bit more limited, but the fact that it is integrated with GitHub makes it much easier to get started with. Also similar, it has a free tier with paid versions that add more concurrency. GHA would be useful for projects that don't require a lot of advanced features, visualization, etc. ### Fundamentals **[Twelve-Factor App](https://12factor.net/)** The Twelve-Factor App is a methodology for building Software-as-a-Service that encourages building applications in a way that inherently makes them more stable, secure, failure-safe, easier to measure, and easier to reason about. As much as possible we try to stick with these principles when building Zero, as we have seen it to be a very effective way to build scalable, manageable applications. **[CNCF](https://www.cncf.io/)** The Cloud Native Computing Foundation hosts and promotes a bunch of great open source software, including Kubernetes, Prometheus, Helm, Fluentd, and Envoy. Their principles and the design of many of the CNCF projects really resonate with the team behind Zero, and we try to make use of CNCF-backed tools whenever possible. ================================================ FILE: doc-site/docs/concepts/core-concepts.md ================================================ --- title: Core Concepts sidebar_label: Core Concepts sidebar_position: 1 --- ## Project A project defines a set of **modules** to use, along with a full set of config parameters for each module which were entered by the user during the `zero init` step. These config options are stored in `zero-project.yml`. The project manifest (`zero-project.yml`) is the source of truth for the commands `create` and `apply`. It determines from where to fetch the modules, the execution order of modules, whether it will push your project to version control, module configuration, and other project information. Both staging and production environments are provisioned using the same manifest to ensure the environments are reproducible and controlled. ## Module A module is a useful bundle of code and/or resources that can be templated out during the `create` step, then executes a provisioning flow. This could be templating out terraform infrastructure as code then provisioning the resources, or creating a backend application, making API calls to set up a build pipeline, and then deploying it. A module is defined by the **module manifest** (`zero-module.yml`) in its root folder. It contains all the parameters required to render the templated files (during `zero create`) and execute any provisioning steps, and declares it's requirements and the commands to execute during provisioning (`zero apply`). Modules can declare their dependencies, for example a backend that will be deployed can declare its dependency on the infrastructure repository so that the infrastructure will already exist by the time we want to deploy to it. ================================================ FILE: doc-site/docs/concepts/project-life-cycle.md ================================================ --- title: Project Life Cycle sidebar_label: Project Life Cycle sidebar_position: 2 --- ## zero init The goal of the `init` step is to create the project manifest (`zero-project.yml`). `zero init` will fetch each **module** defined in `zero-project.yml` from their remote repository, and prompt the user through a series of questions to fill in parameters required by each module. In this phase, the module definition will be parsed and provide defaults, options, and extra context to guide users through filling in their project details. :::note It's recommended to review the `zero-project.yml` and make adjustments as needed before running `zero create` and `zero apply`. ::: ## zero create `zero create` is run in the same folder as `zero-project.yml`. It will template out each module specified in the project manifest as the basis of your repositories, then push them to your version control repository (Github). During the `create` step, Zero will also conditionally include or exclude certain sets of files, as defined by each module. For example, it will not scaffold the authentication examples if you opted not to use this feature. ## zero apply `zero apply` is the provisioning step that starts to make real-world changes. By default, it runs a command defined by the module to check for any dependencies, and then runs a command to actually apply any changes. ### Check `check` is run for all the modules in your project before attempting to do the `run` step, so if a dependent's `check` fails it will not start the actual provisioning for any of the modules. This is useful to check for missing binaries or API token permissions before starting to apply any changes. ### Apply By default, the run step is to execute `make` in the root of the module, but that can be overridden in the module definition. Run should be the one-time setup commands that allow the module to function. For example, in the infrastructure repository, this would be to **run terraform**, and for the backend repository, this could be to **make API calls to CircleCI to set up your build pipeline**. ================================================ FILE: doc-site/docs/getting-started/installation.md ================================================ --- title: Installation sidebar_label: Installation sidebar_position: 1 --- ## How to Install and Configure Zero There are multiple ways to install Zero: - Install Zero using your systems package manager. ```shell # MacOS brew tap commitdev/zero brew install zero ``` - Install Zero by downloading the binary. Download the latest [Zero binary](https://github.com/commitdev/zero/releases) for your systems architecture. Unzip your downloaded package and copy the Zero binary to the desired location and add it to your system PATH. Zero currently supports: | System | Support| Package Manager | |---------|:-----:|:------:| | MacOS | ✅ | `brew` | | Linux | ✅ | `deb, rpm, apk` | | Windows | ❌ | n/a | ================================================ FILE: doc-site/docs/getting-started/prerequisites.md ================================================ --- title: Prerequisites sidebar_label: Prerequisites sidebar_position: 2 --- Using Zero to spin up your infrastructure and application is easy and straightforward. Using just a few commands, you can configure and deploy your very own scalable, high-performance, production-ready infrastructure. A few caveats before getting started: - For Zero to provision resources, you will need to be [authenticated with the AWS CLI tool ](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html#cli-configure-files-methods). - It is recommended practice to [create a GitHub org](https://docs.github.com/en/github/setting-up-and-managing-organizations-and-teams/creating-a-new-organization-from-scratch) where your code is going to live. If you choose, after creating your codebases, Zero will automatically create repositories and check in your code for you. You will need to [create a Personal Access Token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) to enable this.
If using CircleCI as your build pipeline...
  • Grant CircleCi Organization access to your repositories to allow pulling the code during the build pipeline.
  • You will need to create a CircleCi access token and enter it during the setup process; you should store your generated tokens securely.
  • For your CI build to work, you need to opt into the use of third-party orbs. You can find this in your CircleCi Org Setting > Security > Allow Uncertified Orbs.
### `zero check` In order to use Zero, run the `zero check` command on your system to find out which other tools / dependencies you might need to install. [AWS CLI], [Kubectl], [Terraform], [jq], [Git], [Wget] You need to [register a new domain](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/domain-register.html) / [host a registered domain](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/MigratingDNS.html) you will use to access your infrastructure on [Amazon Route 53](https://aws.amazon.com/route53/). :::tip We recommended you have two domains - one for staging and another for production. For example, mydomain.com and mydomain-staging.com. This will lead to environments that are more similar, rather than trying to use a subdomain like staging.mydomain.com for staging which may cause issues in your app later on. ::: [AWS CLI]: https://aws.amazon.com/cli/ [git]: https://git-scm.com [kubectl]: https://kubernetes.io/docs/tasks/tools/install-kubectl/ [terraform]:https://www.terraform.io/downloads.html [jq]: https://github.com/stedolan/jq [Wget]: https://stackoverflow.com/questions/33886917/how-to-install-wget-in-macos ================================================ FILE: doc-site/docs/getting-started/zero-apply.md ================================================ --- title: zero apply sidebar_label: zero apply sidebar_position: 5 --- The `zero apply` command takes the templated modules generated based on your input and spins up a scalable & performant infrastructure for you! :::note This can take 20 minutes or more depending on your choices, as it must wait for all the provisioned infrastructure to be created ::: ```shell $ zero apply # Sample Output Choose the environments to apply. This will create infrastructure, CI pipelines, etc. At this point, real things will be generated that may cost money! Only a single environment may be suitable for an initial test, but for a real system we suggest setting up both staging and production environments. ✔ Production 🎉 Bootstrapping project zero-init. Please use the zero-project.yml file to modify the project as needed. Cloud provider: AWS Runtime platform: Kubernetes Infrastructure executor: Terraform ... ... ✅ Done. Your projects and infrastructure have been successfully created. Here are some useful links and commands to get you started: zero-aws-eks-stack: - Repository URL: github.com/myapp-org/infrastructure - To see your kubernetes clusters, run: 'kubectl config get-contexts' - To switch to a cluster, use the following commands: - for production use: kubectl config use-context arn:aws:eks:us-west-2:123456789:cluster/myapp-infra-production-us-west-2 - To inspect the selected cluster, run 'kubectl get node,service,deployment,pods' zero-frontend-react: - Repository URL: github.com/myapp-org/frontend - Deployment Pipeline URL: https://app.circleci.com/pipelines/github/myapp-org/frontend - Production Landing Page: app.commitzero.com zero-backend-go: - Repository URL: github.com/myapp-org/backend-service - Deployment Pipeline URL: https://app.circleci.com/pipelines/github/myapp-org/backend-service - Production API: api.commitzero.com ``` ***Your stack is now up and running, follow the links in your terminal to visit your application 🎉*** ================================================ FILE: doc-site/docs/getting-started/zero-create.md ================================================ --- title: zero create sidebar_label: zero create sidebar_position: 4 --- The `zero create` command renders the infrastructure modules you've configured into your project folder and pushes your code to GitHub. ```shell # Template the selected modules and configuration specified in zero-project.yml and push to the repository. $ cd zero-init # change your working dir to YOUR_PROJECT_NAME $ zero create ## Sample Output 🕰 Fetching Modules 📝 Rendering Modules Finished templating : backend-service/.circleci/README.md ✅ Finished templating : backend-service/.circleci/config.yml ✅ Finished templating : backend-service/.gitignore ... ... ✅ Finished templating : infrastructure/terraform/modules/vpc/versions.tf ⬆ Done Rendering - committing repositories to version control. ✅ Repository created: github.com/myapp-org/infrastructure ✅ Repository created: github.com/myapp-org/backend-service ✅ Repository created: github.com/myapp-org/frontend ✅ Done - run zero apply to create any required infrastructure or execute any other remote commands to prepare your environments. ``` After this step you will be able to examine the created repositories before proceeding to `zero apply`. If you chose not to have zero create a repository for you, you can still use the `zero apply` command to create the infrastructure but you will need to check these repositories into your version control system of choice. ================================================ FILE: doc-site/docs/getting-started/zero-init.md ================================================ --- title: zero init sidebar_label: zero init sidebar_position: 3 --- The `zero init` command creates a new project and outputs an infrastructure configuration file with user input prompted responses into a file. -> 📁 `YOUR_PROJECT_NAME/zero-project.yml` ```shell # To create and customize a new project you run $ zero init ## Sample project initialization ✔ Project Name: myapp-infra 🎉 Initializing project ✔ EKS + Go + React + Gatsby ✔ Should the created projects be checked into github automatically? (y/n): y ✔ What's the root of the github org to create repositories in?: github.com/myapp-org ✔ Existing AWS Profiles ✔ default Github personal access token: used for creating repositories for your project Requires the following permissions: [repo::public_repo, admin::orgread:org] The token can be created at https://github.com/settings/tokens ✔ Github Personal Access Token with access to the above organization: CircleCI api token: used for setting up CI/CD for your project The token can be created at https://app.circleci.com/settings/user/tokens ✔ Circleci api key for CI/CD: ✔ us-west-2 ✔ Production Root Host Name (e.g. mydomain.com) - this must be the root of the chosen domain, not a subdomain.: commitzero.com ✔ Production Frontend Host Name (e.g. app.): app. ✔ Production Backend Host Name (e.g. api.): api. ✔ Staging Root Host Name (e.g. mydomain-staging.com) - this must be the root of the chosen domain, not a subdomain.: commitzero-stage.com ✔ Staging Frontend Host Name (e.g. app.): app. ✔ Staging Backend Host Name (e.g. api.): api. ✔ What do you want to call the zero-aws-eks-stack project?: infrastructure ✔ What do you want to call the zero-backend-go project?: backend-service ✔ What do you want to call the zero-frontend-react project?: frontend ``` After this step you will be able to examine the `zero-project.yml` file to ensure your settings are correct before proceeding to `zero create`. ================================================ FILE: doc-site/docs/reference/learning-resources.md ================================================ --- title: Learning Resources sidebar_label: Learning Resources sidebar_position: 3 --- ### AWS - [Getting started with AWS](https://aws.amazon.com/getting-started/) ### Kubernetes - [Kubernetes Basics](https://kubernetes.io/docs/tutorials/kubernetes-basics/) - [Kubernetes Training and Certification](https://kubernetes.io/training/) ### Terraform - [Getting started with Terraform in AWS](https://learn.hashicorp.com/collections/terraform/aws-get-started) ### Golang - [A Tour of Go](https://tour.golang.org) ### Node.js - [Getting started with Node.js](https://nodejs.org/en/docs/guides/getting-started-guide/) - [Getting started with Express](https://expressjs.com/en/starter/installing.html) - [Getting started with Apollo GraphQL](https://www.apollographql.com/docs/apollo-server/getting-started/) ### React - [Getting started with React](https://reactjs.org/docs/getting-started.html) ================================================ FILE: doc-site/docs/reference/module-definition.md ================================================ --- title: Module Definition sidebar_label: Module Definition sidebar_position: 1 --- ### `zero-module.yml` This file is the definition of a Zero module. It contains a list of all the required parameters to be able to prompt a user for choices during `zero init`, information about how to template the contents of the module during `zero create`, and the information needed for the module to run (`zero apply`). It also declares the module's dependencies to determine the order of execution in relation to other modules. | Parameters | type | Description | |---------------|----------------------|--------------------------------------------------| | `name` | `string` | Name of module | | `description` | `string` | Description of the module | | `template` | `template` | default settings for templating out the module | | `author` | `string` | Author of the module | | `icon` | `string` | Path to logo image | | `parameters` | `list(Parameter)` | Parameters to prompt users | | `commands` | `Commands` | Commands to use instead of makefile defaults | | `zeroVersion` | string([go-semver]) | Zero binary versions it's compatible with | ### Commands Commands are the lifecycle of `zero apply`, it will run all modules' `check` phase, then once satisfied, run in sequence the `apply` phase, then if successful run the `summary` phase. | Parameters | Type | Default | Description | |------------|----------|----------------|--------------------------------------------------------------------------| | `check` | `string` | `make check` | Command to check module requirements. check is satisfied if exit code is 0 eg: `sh check-token.sh`, `zero apply` will check all modules before executing | | `apply` | `string` | `make` | Command to execute the project provisioning. | | `summary` | `string` | `make summary` | Command to summarize to users the module's output and next steps. | #### Template Control how module templates will be parsed during the `zero create` command. | Parameters | Type | Description | |--------------|-----------|-----------------------------------------------------------------------| | `strictMode` | `boolean` | whether strict mode is enabled | | `delimiters` | `tuple` | A tuple of open delimiter and ending delimiter eg: `<%` and `%>` | | `inputDir` | `string` | Folder to template from the module, becomes the module root for users | | `outputDir` | `string` | local directory name for the module, gets commited to version control | ### Condition (module) Module conditions are considered during the templating phase (`zero create`), based on parameters supplied from the project definition. Modules can decide to have specific files or directories excluded from the user's project. For example if the user picks `userAuth: no`, we can exclude all the auth resources via templating. | Parameters | Type | Description | |--------------|----------------|-------------------------------------------------------------------------------------------------------------------------------| | `action` | `enum(string)` | type of condition, currently supports [`ignoreFile`] | | `matchField` | `string` | Allows us to check the contents of another parameter's value | | `WhenValue` | `string` | Matches for this value to satisfy the condition | | `data` | `list(string)` | Supply extra data for the condition action. `ignoreFile`: list of paths (file or directory) to omit from the rendered project | ### Parameter Parameter defines how the user will be prompted during the interview phase of `zero init`. There are multiple ways of obtaining the value for each parameter. Parameters may have `Conditions` that must be fulfilled, otherwise it skips the field entirely. The precedence for different types of parameter prompts are as follow. 1. `execute`: If this parameter is supplied, the command will be executed and the value will be recorded 2. `type`: zero-defined special ways of obtaining values (for example `AWSProfilePicker` which will set 2 values to the map) 3. `value`: directly assigns a static value to a parameter 4. `prompt`: requires users to select an option OR input a string Note: `default` specifies the value that will appear initially for that prompt, but the user could still choose a new string or entirely erase it | Parameters | Type | Description | |-----------------------|-------------------|---------------------------------------------------------------------------------------------------------------------------| | `field` | `string` | Key to store result for project definition | | `label` | `string` | Displayed name for the prompt | | `options` | `map` | A map of `value: display name` pairs for users to pick from | | `default` | `string` | Defaults to this value during prompt | | `value` | `string` | Skips prompt entirely when set | | `info` | `string` | Displays during prompt as extra information at the top of the screen guiding user's input | | `fieldValidation` | `Validation` | Validations for the prompt value | | `type` | `enum(string)` | Built-in custom prompts: currently supports [`AWSProfilePicker`] | | `execute` | `string` | Executes commands and takes stdout as prompt result | | `omitFromProjectFile` | `bool` | Field is skipped from adding to project definition | | `conditions` | `list(Condition)` | Conditions for prompt to run. If supplied, all conditions must pass. See below. | | `envVarName` | `string` | During `zero apply` parameters are available as env vars. Defaults to field name but can be overwritten with `envVarName` | ### Condition (parameter) Parameter conditions are considered while running user prompts. Prompts are executed in order of the yml, and will be skipped if conditions are not satisfied. For example if a user decides to not use circleCI, a condition can be used to skip the circleCI_api_key prompt. | Parameters | Type | Description | |--------------|----------------|-------------------------------------------------------------------| | `action` | `enum(string)` | type of condition, currently supports [`KeyMatchCondition`] | | `matchField` | `string` | The name of the parameter to check the value of | | `whenValue` | `string` | The exact value to match | | `elseValue` | `string` | The value that will be set for this parameter if the condition match fails. Otherwise the value will be set to an empty string. | | `data` | `list(string)` | Supply extra data for condition to run | ### Validation Allows forcing the user to adhere to a certain format of value for a parameter, defined as a regular expression. | Parameters | type | Description | |----------------|----------------|-------------------------------------| | `type` | `enum(string)` | Currently supports [[regex](https://github.com/google/re2/wiki/Syntax)] | | `value` | `string` | Regular expression string | | `errorMessage` | `string` | Error message when validation fails | [go-semver]: https://github.com/hashicorp/go-version/blob/master/README.md ================================================ FILE: doc-site/docs/reference/project-definition.md ================================================ --- title: Project Definition sidebar_label: Project Definition sidebar_position: 1 --- ### `zero-project.yml` Each project is defined by this file. This manifest reflects all the options a user chose during the `zero init` step. It defines which modules are part of the project, each of their parameters, and is the source of truth for the templating (`zero create`) and provision (`zero apply`) steps. _Note: This file contains credentials, so make sure it is not shared with others._ | Parameters | Type | Description | |--------------------------|--------------|------------------------------------------------| | `name` | string | name of the project | | `shouldPushRepositories` | boolean | whether to push the modules to version control | | `modules` | map(modules) | a map containing modules of your project | ### Modules | Parameters | Type | Description | |--------------|-----------------|-------------------------------------------------------------------------| | `parameters` | map(string) | key-value map of all the parameters to run the module | | `files` | File | Stores information such as source-module location and destination | | `dependsOn` | list(string) | a list of dependencies that should be fulfilled before this module | | `conditions` | list(condition) | conditions to apply while templating out the module based on parameters | ### Condition | Parameters | Type | Description | |--------------|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------| | `action` | enum(string) | type of condition, currently supports [`ignoreFile`] | | `matchField` | string | Allows you to condition prompt based on another parameter's value | | `WhenValue` | string | Matches for this value to satisfy the condition | | `data` | list(string) | Supply extra data for condition to run `ignoreFile`: provide list of paths (file or directory path) to omit from module when condition is satisfied | ================================================ FILE: doc-site/docs/reference/working-on-zero.md ================================================ --- title: Working on Zero sidebar_label: Working on Zero sidebar_position: 3 --- ### Building the tool ```shell $ git clone git@github.com:commitdev/zero.git $ cd zero && make build ``` ### Releasing a new version on GitHub and Brew We are using a tool called `goreleaser` which you can get from brew if you're on MacOS: `brew install goreleaser` After you have the tool, you can follow these steps: ``` export GITHUB_TOKEN= git tag -s -a -m "Some message about this release" git push origin goreleaser release ``` ================================================ FILE: doc-site/docusaurus.config.js ================================================ /** @type {import('@docusaurus/types').DocusaurusConfig} */ const { stylesheets, misc } = require('@commitdev/zero-doc-site-common-elements'); const siteUrl = process.env.BUILD_DOMAIN ? `https://${process.env.BUILD_DOMAIN}` : 'https://staging.getzero.dev'; const baseUrl = '/'; const repositoryName = 'zero'; module.exports = { title: 'Zero', tagline: 'Opinionated infrastructure to take you from idea to production on day one', url: siteUrl, baseUrl, ...misc(), projectName: repositoryName, themeConfig: { colorMode: { defaultMode: 'dark', }, navbar: { logo: { alt: 'Zero Logo', src: 'img/zero.svg', }, items: [ { to: '/docs/zero/about/overview', label: 'Docs', className: 'header-docs-link header-logo-24', position: 'right' }, { href: 'https://slack.getzero.dev', label: 'Slack', className: 'header-slack-link header-logo-24', position: 'right', }, { href: 'https://github.com/commitdev/zero', label: 'Github', className: 'header-github-link header-logo-24', position: 'right', }, ], }, footer: { links: [ { items: [ { to: '/docs/zero/about/overview', label: 'Docs', className: 'header-docs-link header-logo-24', position: 'right' }, { href: 'https://slack.getzero.dev', label: 'Slack', className: 'header-slack-link header-logo-24', position: 'right', }, { href: 'https://github.com/commitdev/zero', label: 'Github', className: 'header-github-link header-logo-24', position: 'right', }, ], }, ], }, }, presets: [ [ '@docusaurus/preset-classic', { docs: { sidebarPath: require.resolve('./sidebars.js'), path: 'docs', routeBasePath: 'docs/zero/', include: ['**/*.md', '**/*.mdx'], editUrl: 'https://github.com/commitdev/zero/blob/main/doc-site/', }, theme: { customCss: require.resolve('./src/css/custom.css'), }, gtag: { trackingID: 'G-6FN66NMDES', }, debug: true, }, ], ], plugins: [ 'docusaurus-plugin-sass' ], stylesheets: stylesheets(), }; ================================================ FILE: doc-site/package.json ================================================ { "name": "doc-site", "version": "0.0.0", "private": true, "scripts": { "docusaurus": "docusaurus", "start": "docusaurus start", "build": "docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", "clear": "docusaurus clear", "serve": "docusaurus serve", "write-translations": "docusaurus write-translations", "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { "@commitdev/zero-doc-site-common-elements": "0.0.7", "@docusaurus/core": "2.2.0", "@docusaurus/preset-classic": "2.0.0-beta.3", "@mdx-js/react": "^1.6.21", "@svgr/webpack": "^5.5.0", "clsx": "^1.1.1", "docusaurus-plugin-sass": "^0.2.1", "file-loader": "^6.2.0", "react": "^17.0.1", "react-dom": "^17.0.1", "sass": "^1.35.1", "url-loader": "^4.1.1" }, "browserslist": { "production": [ ">0.5%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } } ================================================ FILE: doc-site/sidebars.js ================================================ const config = require('./docusaurus.config'); const { sidebarsNavModules } = require('@commitdev/zero-doc-site-common-elements'); module.exports = { zero: [ { "About": [{ type: 'autogenerated', dirName: 'about', }], "Getting started": [{ type: 'autogenerated', dirName: 'getting-started', }], "Concepts": [{ type: 'autogenerated', dirName: 'concepts', }], "Reference": [{ type: 'autogenerated', dirName: 'reference', }], }, sidebarsNavModules(config), ] } ================================================ FILE: doc-site/src/components/HomepageFeatures.js ================================================ import React from 'react'; import clsx from 'clsx'; import styles from './HomepageFeatures.module.scss'; const FeatureList = [ { title: 'Reliable', Svg: require('../../static/img/icons/attr-reliable.svg').default, description: ( <> Fault-tolerant infrastructure. Production workloads will be highly available, with traffic load balanced to multiple instances of your application. All the infrastructure is represented with code to be reproducible and easy to configure. ), }, { title: 'Scalable', Svg: require('../../static/img/icons/attr-scalable.svg').default, description: ( <> Your system will scale automatically based on your application’s needs. For frontend assets, using a CDN will ensure availability at global scale. ), }, { title: 'Secure', Svg: require('../../static/img/icons/attr-secure.svg').default, description: ( <> All your systems will follow security best practices backed up by multiple security audits and penetration tests, and will be properly configured to limit access to private networks, secrets, and data. Bullet-proof your application by default using existing, tested tools. ), }, ]; const Feature = ({Svg, title, description}) => (

{title}

{description}

) export default function HomepageFeatures() { return (<>

Building something fast doesn't mean you can't also build it right

{FeatureList.map((props, idx) => ( ))}
) } ================================================ FILE: doc-site/src/components/HomepageFeatures.module.scss ================================================ .features { align-items: center; @media only screen and (max-width: 641px) { padding-left: 1rem; padding-right: 1rem; } .title { color: white; font-weight: 400; text-transform: inherit; max-width: 34rem; } @media screen and (max-width: 768px) { .title { padding: 0 2rem; } } .row { justify-content: center; } &>div{ row-gap: 3rem; } h3 { line-height: 250%; color: var(--ifm-color-secondary); margin-bottom: 1rem; } p { color: white; font-size: 0.9rem; padding: 0 calc(var(--ifm-spacing-horizontal)*2); } .featureSvg { height: 2.5rem; } .featureSvg path { fill: var(--ifm-color-secondary); } } ================================================ FILE: doc-site/src/components/HomepageOfferings.js ================================================ import React, { useState } from 'react'; import styles from './HomepageOfferings.module.scss'; const offerings = [ { logo: require('../../static/img/icons/offering-infra.svg').default, label: 'Infrastructure', tools: [ { name: 'Terraform', logo: 'img/tools/terraform.png', url: 'https://terraform.io', }, { name: 'Kubernetes', logo: 'img/tools/kubernetes.png', url: 'https://kubernetes.io/', noCrop: true, }, { name: 'Amazon Web Services', logo: 'img/tools/aws.png', url: 'https://aws.amazon.com/', }, { name: 'Cert Manager', logo: 'img/tools/cert-manager.png', url: 'https://cert-manager.io/docs/', }, { name: 'External DNS', logo: 'img/tools/external-dns.png', url: 'https://github.com/kubernetes-sigs/external-dns', }, { name: 'Wireguard', logo: 'img/tools/wireguard.png', url: 'https://www.wireguard.com/', }, { name: 'Prometheus', logo: 'img/tools/prometheus.png', url: 'https://prometheus.io/', }, { name: 'Grafana', logo: 'img/tools/grafana.png', url: 'https://grafana.com/', }, ] }, { logo: require('../../static/img/icons/offering-cicd.svg').default, label: 'CI/CD', tools: [ { name: 'Github Actions', logo: 'img/tools/github-actions.svg', noCrop: true, url: 'https://github.com/features/actions', }, { name: 'CircleCI', logo: 'img/tools/circleci.png', url: 'https://circleci.com', }, { name: 'Docker', logo: 'img/tools/docker.png', url: 'https://docker.com/', }, { name: 'AWS ECR', logo: 'img/tools/ecr.png', url: 'https://aws.amazon.com/ecr/', }, ] }, { logo: require('../../static/img/icons/offering-frontend.svg').default, label: 'FRONTEND', tools: [ { name: 'React js', logo: 'img/tools/react.png', url: 'https://reactjs.org', }, { name: 'AWS S3', logo: 'img/tools/s3.png', url: 'https://aws.amazon.com/s3/', }, { name: 'AWS Cloudfront', logo: 'img/tools/cloudfront.png', url: 'https://aws.amazon.com/cloudfront/', }, { name: 'ECMAScript 2018', logo: 'img/tools/js.png', url: 'https://www.w3schools.com/js/js_2018.asp', }, ] }, { logo: require('../../static/img/icons/offering-backend.svg').default, label: 'BACKEND', tools: [ { name: 'Golang', logo: 'img/tools/golang.png', url: 'https://golang.org', }, { name: 'Node.js', logo: 'img/tools/nodejs.png', url: 'https://nodejs.org', noCrop: true, }, { name: 'Open ID Connect', logo: 'img/tools/openid.png', url: 'https://openid.net/connect/', }, { name: 'Ory Kratos & Oathkeeper', logo: 'img/tools/ory.png', url: 'https://www.ory.sh/kratos/docs/', }, { name: 'Telepresence', logo: 'img/tools/telepresence.png', url: 'https://www.telepresence.io/', noCrop: true, }, { name: 'Stripe', logo: 'img/tools/stripe.png', url: 'https://stripe.com', noCrop: true, }, ] }, ] const Offerings = ({data, active, clickHandler}) => (
{ data.map((i, idx) => ) }
i.label == active).tools } />
) const Discipline = ({logo: LogoSvg, label, clickHandler, active}) => (
clickHandler({ active: label})} >

{label}

) const ToolBox = ({data}) =>
    { data.map((tool, idx) => ) }
const Tool = ({tool, idx}) => (
  • {tool.name}

  • ) export default function FeaturedOfferings() { const title = "What do you get out of the box ?" const [state, setState] = useState({ active: "Infrastructure", }) return

    {title}

    } ================================================ FILE: doc-site/src/components/HomepageOfferings.module.scss ================================================ .offerings_container{ text-align: center; padding: 9rem 8rem 5rem; margin: 0 auto; min-height: 53rem; background-color: #E8EDF4; color: var(--ifm-landing-page-inverse-font-color); .offering_box { display: flex; column-gap: 5rem; flex-wrap: wrap; justify-content: center; } } .left_box{ flex: 0 1 270px; margin: 2rem 0px; display: flex; flex-direction: column; column-gap: 1rem; row-gap: 1rem; .discipline { display: flex; height: 5rem; column-gap: 1rem; position: relative; font-weight: bold; text-transform: uppercase; flex: 0 0 auto; cursor: pointer; border-radius: 12px; background: #D7DEE8; padding-right: 1rem; .logo { flex: 1; height: 3rem; align-self: center; margin: 1rem; } .discipline_name { flex: 4; align-self: center; line-height: 0; margin: 0; text-align: left; font-size: 1rem; } &:hover:not(.discipline_active) .discipline_name{ color: var(--ifm-color-secondary); } } .discipline_active{ background: var(--ifm-color-secondary); color: var(--ifm-color-dark-active); .logo path{ fill: var(--ifm-color-dark-active); } &:after { content: ""; width: 5rem; right: -5rem; height: 0px; align-self: center; position: absolute; border-top: 4px dashed var(--ifm-color-secondary); } } } .right_box{ border: 4px dashed var(--ifm-color-secondary); border-radius: 12px; display: flex; padding: 2rem; ul{ height: 100%; width: 100%; align-self: center; margin: 0; padding: 0; display: flex; flex-direction: column; row-gap: 1.2rem; li { list-style: none; justify-self: stretch; flex: 1 1 40px; a{ display: flex; column-gap: 1rem; text-decoration: none; color: var(--ifm-landing-page-inverse-font-color); font-weight: bold; &:hover{ color: var(--ifm-color-secondary); } } img{ width: 32px; border-radius: 50%; padding: 0; &.no-crop{ border-radius: 0; } } p{ flex: 5; text-align: left; font-size: 1.1rem; margin: 0; align-self: center; font-family: 'Montserrat'; font-weight: 600; white-space: nowrap; } @media only screen and (max-width: 400px) { p { font-size: 0.8rem; } } } } } /** box becomes stacked when screen is small and the dotted line is vertical instead of horizontal **/ @media screen and (max-width: 967px) { .offerings_container { padding: 5rem 2rem; min-height: 62rem; } .left_box { justify-content: center; flex-direction: row; flex-wrap: wrap; flex: 1 1 auto; .discipline { .logo { flex: 1 1 33px; margin: 0.5rem; } } .discipline_active{ order: 1; flex: 4 4 100%; margin: 0 16%; &:after { content: ""; width: 0px; height: 2rem; right: 50%; position: absolute; border-right: var(--ifm-color-secondary) dashed 4px; bottom: -2rem; box-sizing: border-box; overflow: visible; } } } } ================================================ FILE: doc-site/src/components/HomepageTrustedBy.js ================================================ import React from 'react'; import styles from './HomepageTrustedBy.module.scss'; const trustedByData = [ { img: "img/partners/planworth.png", src: "https://www.planworth.co/", }, { img: "img/partners/patch.png", src: "https://www.patch.io/", }, { img: "img/partners/atlasone.png", src: "https://www.atlasone.ca/", }, { img: "img/partners/placeholder.png", src: "https://placeholder.co/", }, ] const Carousel = ({data}) => (
      { data.map((item, idx) => (
    • )) }
    ) export default function TrustedByCarousel() { return

    Trusted By

    } ================================================ FILE: doc-site/src/components/HomepageTrustedBy.module.scss ================================================ h3.title { letter-spacing: 0.1rem; text-align: center; font-weight: 400; font-family: "Montserrat"; } .trusted { display: flex; flex-direction: row; flex-wrap: wrap; justify-content: center; padding: 0; row-gap: 2em; li{ list-style: none; margin: 0 1.25rem; height: 100px; width: 200px; background-color: rgba(196, 196, 196, 0.5); display: flex; border-radius: 5px; padding: 2rem; &:hover{ background-color: rgba(220,235,245, 0.8); } a{ align-self: center; line-height: 0; } } } ================================================ FILE: doc-site/src/components/HomepageVideo.js ================================================ import React from 'react'; import styles from './HomepageVideo.module.scss'; export default function FeatureVideo () { return
    } ================================================ FILE: doc-site/src/components/HomepageVideo.module.scss ================================================ .video { width: 100%; text-align: center; iframe { max-width: 65%; } } ================================================ FILE: doc-site/src/components/HomepageWhyZero.js ================================================ import React, { useState } from 'react'; import clsx from 'clsx'; import styles from './HomepageWhyZero.module.scss'; const reasons = [ { logo: require('../../static/img/icons/reason-diamond.svg').default, title: 'Quality', text: `Like the best DevOps engineer you’ve ever met - except open source and free.`, details: [ `The devops skill gap is real. Why spend precious time picking up unfamiliar tech, making wrong choices that result in costly refactoring or rebuilding in the future, and missing tools and best practices that would speed up your product iteration?`, `Get real-time support for all your questions from Zero’s community.` ] }, { logo: require('../../static/img/icons/reason-clockwise.svg').default, title: 'Speed', text: `Just as fast as other tools like Heroku to get up and running.`, details: [ `Building foundational infrastructure the right way doesn’t have to take a long time. Our team has years of experience building and scaling startups and have poured that knowledge into Zero. What used to take us weeks of DevOps work can now take you 30 minutes.`, `We provide constant updates and new modules that you can pull in on an ongoing basis.`, ] }, { logo: require('../../static/img/icons/reason-key.svg').default, title: 'Ownership', text: `You own 100% of the code. No lock-in!`, details: [ `Everything built by Zero is yours. It’s your code to change or migrate off at any point.`, `Cloud application hosting tools are built to lock you in and don’t scale. `, `Infrastructure is created in your cloud provider account. You can customize as much as you like with no strings attached. You control how much you spend.` ] } ]; const Reasons = ({ data, expanded, setExpanded }) => (
    { data.map((i, idx) => (

    {i.title}

    {i.text}

    {expanded &&
      {i.details.map(content=>
    • {content}
    • )}
    }
    )) }
    ) export default function FeatureWhyZero () { const [expanded, setExpanded] = useState(false) const title = "Why is Zero good for startups ?" return

    {title}

    As engineer #1, your sole priority is to build the logic for your application and get it into customers’ hands as quickly and reliably as possible.

    } ================================================ FILE: doc-site/src/components/HomepageWhyZero.module.scss ================================================ .reasons_container{ text-align: center; background: white; color: var(--ifm-landing-page-inverse-font-color); @media only screen and (max-width: 641px) { padding-left: 3rem; padding-right: 3rem; } .title { margin-bottom: 5rem; letter-spacing: 0.05rem; } .subtitle { text-transform: none; margin: 1rem auto; font-family: 'lato'; font-weight: 400; max-width: 34rem; letter-spacing: normal; } .expand { margin-top: 2rem; a{ display: flex; justify-content: center; &:active, &:link, &:visited{ text-decoration: none; color: var(--ifm-landing-page-inverse-font-color); } &:after { align-self: center; margin: 0 0.5rem; content: ""; width: 18px; height: 18px; background: url(../../static/img/icons/icon-plus.svg); background-size: cover; } } &.expanded a{ &:after { background: url(../../static/img/icons/icon-minus.svg); background-size: cover; } } } } .reasons { display: flex; flex-direction: row; row-gap: 3rem; justify-content: center; column-gap: 2rem; } .reason { max-width: 10rem; .title{ color: var(--ifm-color-secondary); text-transform: uppercase; margin: 1rem; } .description{ min-height: 2.5rem; font-size: 0.9rem; align-self: center; } ul.description { margin-top: 2rem; padding-left: 1rem; } .description li{ text-align: left; } .reason_logo { height: 3rem; flex: 0 0 3rem; align-self: center; path { stroke: var(--ifm-color-secondary); } } } ================================================ FILE: doc-site/src/css/custom.css ================================================ /** * Any CSS included here will be global. The classic template * bundles Infima by default. Infima is a CSS framework designed to * work well for content-centric websites. */ /* You can override the default Infima variables here. */ :root { --ifm-color-primary-dark: rgb(33, 175, 144); --ifm-color-primary-darker: rgb(31, 165, 136); --ifm-color-primary-darkest: rgb(26, 136, 112); --ifm-color-primary-light: rgb(70, 203, 174); --ifm-color-primary-lighter: rgb(102, 212, 189); --ifm-color-primary-lightest: rgb(146, 224, 208); --ifm-navbar-link-hover-color: rgb(33, 175, 144); --ifm-code-font-size: 95%; --ifm-footer-padding-vertical: 20px; --ifm-footer-padding-horizontal: 10px; --ifm-footer-background-color: transparent; --ifm-color-secondary: rgb(255, 106, 185); --ifm-button-cta-background: linear-gradient(294.55deg, #F2BD6D 17.88%, #F17F84 65.95%, #FF3EA7 93.96%); --ifm--hero--text-background-gradient: linear-gradient(234.45deg, #12C6FF 0%, #6184C9 68.23%); --ifm-landing-page-inverse-font-color: #333; --ifm-color-dark-active: rgba(5, 6, 55, 1); --ifm-spacing-vertical: 0px; --ifm-navbar-sidebar-width: 15rem; --ifm-alert-padding-vertical: 1rem; --ifm-alert-padding-horizontal: 1rem; } html[data-theme='dark'] { --ifm-color-primary: #edc281; --ifm-font-color-base-inverse: white; --ifm-navbar-background-color: transparent; --ifm-background-color: linear-gradient(90deg, rgba(15, 16, 17, 1) 0%, rgba(1, 2, 66, 1) 100%); --ifm-color-info: rgba(255, 166, 0, 0.4); --ifm-menu-color-active: #edc281; --ifm-navbar-link-hover-color: #edc281; --ifm-link-color: #edc281; --ifm-code-background: rgba(140,140,140,0.5); } html[data-theme='light'] { --ifm-heading-color: navy; --ifm-font-color-base-inverse: navy; --ifm-color-secondary: #6184C9; --ifm-background-color: #fefefe; } html[data-theme='light'] .navbar--fixed-top, html[data-theme='light'] .navbar-sidebar__brand{ background: linear-gradient(90deg, rgba(15, 16, 17, 0.3) 0%, rgba(1, 2, 66, 0.4) 100%); } /** PAGE BACKGROUND **/ html{ background: var(--ifm-background-color); } .hero--primary{ background: none; } /** FONTS **/ .navbar { font-family: 'lato'; font-weight: 900; } .menu__list .menu__link--sublist { font-weight: 900; } .menu__list .menu__list-item { font-weight: 400; } .featured-sections h3 , .featured-sections h2, .featured-sections h4{ font-family: 'Montserrat'; font-weight: 800; } .button-cta{ font-family: 'Montserrat'; font-weight: 700; } .description { font-family: 'lato'; font-weight: 400; } /** Small screen hamburger menu slideout background **/ .navbar-sidebar--show .navbar-sidebar { background-color: var(--ifm-code-background); } .navbar-sidebar__brand .navbar__brand { height: 1rem; } .navbar-sidebar__brand .navbar__brand img{ margin: 1rem; } .navbar-sidebar__items .menu__link{ column-gap: 1rem; justify-content: flex-start; } .navbar__brand img{ height: 80%; margin-left: 0.6rem; } /** Docs sidebar - folder name capitalize */ .menu__link--sublist{ text-transform: capitalize; } /** FLATTEN FOOTER ITEMS **/ .footer__items{ display: flex; justify-content: flex-end; } .footer__item{ padding:0 var(--ifm-spacing-horizontal); } @media only screen and (max-width: 400px) { .footer { display: none; } } /** LANDING PAGE SECTION **/ .featured-sections { text-align: center; padding: 6rem 0; } .featured-sections h3, .featured-sections h2 { text-transform: uppercase; letter-spacing: 0.05rem; padding: 0; margin: 0 auto 3rem auto; } .featured-sections h2 { margin: 0 auto 5rem auto; } /** NAV BAR Icons flattens footer icons */ .header-logo-24.navbar__link, .footer__item .header-logo-24 { display:flex; line-height: 24px; text-transform: uppercase; color: var(--ifm-font-color-base-inverse); font-weight: 700; } .header-logo-24:hover{ text-decoration: none; } .header-logo-24:before{ content: ""; display: flex; height: 24px; width: 24px; margin: 0 3px; background-size: cover; } html[data-theme='dark'] .header-github-link:before { background: url(../../static/img/icons/octocat.svg) no-repeat; } html[data-theme='light'] .header-github-link:before { background: url(../../static/img/icons/octocat-navy.svg) no-repeat; } html[data-theme='dark'] .header-slack-link:before { background: url(../../static/img/icons/slack.svg) no-repeat; } html[data-theme='light'] .header-slack-link:before { background: url(../../static/img/icons/slack-navy.svg) no-repeat; } html[data-theme='dark'] .header-docs-link:before { background: url(../../static/img/icons/notes.svg) no-repeat; } html[data-theme='light'] .header-docs-link:before { background: url(../../static/img/icons/notes-navy.svg) no-repeat; } /* alert - note */ .alert--secondary{ --ifm-alert-background-color: rgb(200, 212, 226); --ifm-alert-border-color: rgb(200, 212, 226); --ifm-alert-color: var(--ifm-color-gray-900); --ra-admonition-icon-color: var(--ifm-color-gray-900); } /* alert - info */ .alert--info{ --ifm-alert-background-color: rgb(117, 157, 209); --ifm-alert-border-color: rgb(117, 157, 209); --ifm-alert-color: var(--ifm-color-gray-900); --ra-admonition-icon-color: var(--ifm-color-gray-900); } .docs-main-toc .toc-item { margin: 2rem 1rem; } .header-logo-24 span svg { display: none; } ================================================ FILE: doc-site/src/pages/docs/index.js ================================================ import React from 'react'; import { Redirect } from 'react-router-dom'; export default () => ================================================ FILE: doc-site/src/pages/docs/zero/index.js ================================================ import React from 'react'; import { Redirect } from 'react-router-dom'; export default () => ================================================ FILE: doc-site/src/pages/index.js ================================================ import React from 'react'; import clsx from 'clsx'; import Layout from '@theme/Layout'; import Link from '@docusaurus/Link'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import styles from './index.module.scss'; import HomepageFeatures from '../components/HomepageFeatures'; import HomepageTrustedBy from '../components/HomepageTrustedBy'; import HomepageVideo from '../components/HomepageVideo'; import HomepageWhyZero from '../components/HomepageWhyZero'; import HomepageOfferings from '../components/HomepageOfferings'; function HomepageHeader() { const {siteConfig} = useDocusaurusContext(); return (

    {siteConfig.tagline}

    ); } function HomePageCallToAction () { return
    Get Started
    } export default function Home() { const landingPageOnlyGlobalItemStyle = ` .navbar { padding: 2.5rem 0 3.5rem; box-shadow: none; background: linear-gradient(90deg, rgba(15, 16, 17, 0.6) 0%, rgba(1, 2, 66, 0.6) 100%); } @media only screen and (max-width: 641px) { .navbar__items--right { display: none; } } .navbar__inner { padding: 0 3rem; } .navbar__brand img{ height: 130%; } .react-toggle{ display: none; } `; const {siteConfig} = useDocusaurusContext(); return (
    ); } ================================================ FILE: doc-site/src/pages/index.module.scss ================================================ /** * CSS files with the .module.css suffix will be treated as CSS modules * and scoped locally. */ .heroBanner { padding: 4rem 0 2rem; text-align: center; .hero__subtitle { background: var(--ifm--hero--text-background-gradient); background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: 900; font-size: 2rem; max-width: 50rem; margin: 3rem auto; } } .buttons { display: flex; align-items: center; justify-content: center; margin: 4rem auto 4rem; a { text-transform: uppercase; background: var(--ifm-button-cta-background); border-radius: 25px; border-color: transparent; border-style: none; background-size: 150% 100%; } } .hiring { display: flex; align-items: center; justify-content: center; margin: 4rem auto 4rem; text-align: center; a { font-size: 2rem; font-weight: bold; color: #ffffff; } @media only screen and (max-width: 600px) { a { padding: 0 2rem; } } } @media screen and (max-width: 966px) { .heroBanner { padding: 2rem; } } ================================================ FILE: doc-site/src/theme/DocSidebar/index.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ import React, {useState, useCallback, useEffect, useRef, memo} from 'react'; import clsx from 'clsx'; import {useThemeConfig, isSamePath} from '@docusaurus/theme-common'; import useUserPreferencesContext from '@theme/hooks/useUserPreferencesContext'; import useLockBodyScroll from '@theme/hooks/useLockBodyScroll'; import useWindowSize, {windowSizes} from '@theme/hooks/useWindowSize'; import useScrollPosition from '@theme/hooks/useScrollPosition'; import Link from '@docusaurus/Link'; import isInternalUrl from '@docusaurus/isInternalUrl'; import Logo from '@theme/Logo'; import IconArrow from '@theme/IconArrow'; import IconMenu from '@theme/IconMenu'; import {translate} from '@docusaurus/Translate'; import styles from './styles.module.css'; const MOBILE_TOGGLE_SIZE = 24; function usePrevious(value) { const ref = useRef(value); useEffect(() => { ref.current = value; }, [value]); return ref.current; } const isActiveSidebarItem = (item, activePath) => { if (item.type === 'link') { return isSamePath(item.href, activePath); } if (item.type === 'category') { return item.items.some((subItem) => isActiveSidebarItem(subItem, activePath), ); } return false; }; // Optimize sidebar at each "level" // TODO this item should probably not receive the "activePath" props // TODO this triggers whole sidebar re-renders on navigation const DocSidebarItems = memo(function DocSidebarItems({items, ...props}) { return items.map((item, index) => ( )); }); function DocSidebarItem(props) { switch (props.item.type) { case 'category': return ; case 'link': default: return ; } } function DocSidebarItemCategory({ item, onItemClick, collapsible, activePath, ...props }) { const {items, label} = item; const isActive = isActiveSidebarItem(item, activePath); const wasActive = usePrevious(isActive); // active categories are always initialized as expanded // the default (item.collapsed) is only used for non-active categories const [collapsed, setCollapsed] = useState(() => { if (!collapsible) { return false; } return isActive ? false : item.collapsed; }); const menuListRef = useRef(null); const [menuListHeight, setMenuListHeight] = useState(undefined); const handleMenuListHeight = (calc = true) => { setMenuListHeight( calc ? `${menuListRef.current?.scrollHeight}px` : undefined, ); }; // If we navigate to a category, it should automatically expand itself useEffect(() => { const justBecameActive = isActive && !wasActive; if (justBecameActive && collapsed) { setCollapsed(false); } }, [isActive, wasActive, collapsed]); const handleItemClick = useCallback( (e) => { e.preventDefault(); if (!menuListHeight) { handleMenuListHeight(); } setTimeout(() => setCollapsed((state) => !state), 100); }, [menuListHeight], ); if (items.length === 0) { return null; } return (
  • {label}
      { if (!collapsed) { handleMenuListHeight(false); } }}>
  • ); } function DocSidebarItemLink({ item, onItemClick, activePath, collapsible: _collapsible, ...props }) { const {href, label, customProps} = item; const isActive = isActiveSidebarItem(item, activePath); return (
  • {label}
  • ); } function useShowAnnouncementBar() { const {isAnnouncementBarClosed} = useUserPreferencesContext(); const [showAnnouncementBar, setShowAnnouncementBar] = useState( !isAnnouncementBarClosed, ); useScrollPosition(({scrollY}) => { if (!isAnnouncementBarClosed) { setShowAnnouncementBar(scrollY === 0); } }); return showAnnouncementBar; } function useResponsiveSidebar() { const [showResponsiveSidebar, setShowResponsiveSidebar] = useState(false); useLockBodyScroll(showResponsiveSidebar); const windowSize = useWindowSize(); useEffect(() => { if (windowSize === windowSizes.desktop) { setShowResponsiveSidebar(false); } }, [windowSize]); const closeResponsiveSidebar = useCallback( (e) => { e.target.blur(); setShowResponsiveSidebar(false); }, [setShowResponsiveSidebar], ); const toggleResponsiveSidebar = useCallback(() => { setShowResponsiveSidebar((value) => !value); }, [setShowResponsiveSidebar]); return { showResponsiveSidebar, closeResponsiveSidebar, toggleResponsiveSidebar, }; } function HideableSidebarButton({onClick}) { return ( ); } function ResponsiveSidebarButton({responsiveSidebarOpened, onClick}) { return ( ); } function DocSidebar({ path, sidebar, sidebarCollapsible = true, onCollapse, isHidden, }) { const showAnnouncementBar = useShowAnnouncementBar(); const { navbar: {hideOnScroll}, hideableSidebar, } = useThemeConfig(); const {isAnnouncementBarClosed} = useUserPreferencesContext(); const { showResponsiveSidebar, closeResponsiveSidebar, toggleResponsiveSidebar, } = useResponsiveSidebar(); return (
    {hideOnScroll && }
    {hideableSidebar && }
    ); } export default DocSidebar; ================================================ FILE: doc-site/src/theme/DocSidebar/styles.module.css ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ :root { --collapse-button-bg-color-dark: #2e333a; overflow-x: hidden; } @media (min-width: 997px) { .sidebar { display: flex; flex-direction: column; max-height: 100vh; height: 100%; position: sticky; top: 0; padding-top: var(--ifm-navbar-height); width: var(--doc-sidebar-width); transition: opacity 50ms ease; } .sidebarWithHideableNavbar { padding-top: 0; } .sidebarHidden { opacity: 0; height: 0; overflow: hidden; visibility: hidden; } .sidebarLogo { display: flex !important; align-items: center; margin: 0 var(--ifm-navbar-padding-horizontal); min-height: var(--ifm-navbar-height); max-height: var(--ifm-navbar-height); color: inherit !important; text-decoration: none !important; } .sidebarLogo img { margin-right: 0.5rem; height: 2rem; } .menu { flex-grow: 1; padding: 0.5rem; } .menuLinkText { cursor: initial; } .menuLinkText:hover { background: none; } .menuWithAnnouncementBar { margin-bottom: var(--docusaurus-announcement-bar-height); } .collapseSidebarButton { display: block !important; background-color: var(--ifm-button-background-color); height: 40px; position: sticky; bottom: 0; border-radius: 0; border: 1px solid var(--ifm-toc-border-color); } .collapseSidebarButtonIcon { transform: rotate(180deg); margin-top: 4px; } html[dir='rtl'] .collapseSidebarButtonIcon { transform: rotate(0); } html[data-theme='dark'] .collapseSidebarButton { background-color: var(--collapse-button-bg-color-dark); } html[data-theme='dark'] .collapseSidebarButton:hover, html[data-theme='dark'] .collapseSidebarButton:focus { background-color: var(--ifm-color-emphasis-200); } } .sidebarLogo, .collapseSidebarButton { display: none; } .sidebarMenuIcon { vertical-align: middle; } .sidebarMenuCloseIcon { display: inline-flex; justify-content: center; align-items: center; height: 24px; font-size: 1.5rem; font-weight: var(--ifm-font-weight-bold); line-height: 0.9; width: 24px; } :global(.menu__list) :global(.menu__list) { overflow-y: hidden; will-change: height; transition: height var(--ifm-transition-fast) linear; } :global(.menu__list-item--collapsed) :global(.menu__list) { height: 0 !important; } .menuLinkExternal { align-items: center; } .menuLinkExternal:after { content: ''; height: 1.15rem; width: 1.15rem; min-width: 1.15rem; margin: 0 0 0 3%; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(0,0,0,0.5)' d='M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z'/%3E%3C/svg%3E") no-repeat; filter: var(--ifm-menu-link-sublist-icon-filter); } ================================================ FILE: doc-site/static/.nojekyll ================================================ ================================================ FILE: go.mod ================================================ module github.com/commitdev/zero go 1.16 require ( github.com/aws/aws-sdk-go v1.30.12 github.com/buger/goterm v1.0.0 github.com/coreos/go-semver v0.3.0 github.com/gabriel-vasile/mimetype v1.1.1 github.com/google/go-cmp v0.3.1 github.com/google/uuid v1.1.1 github.com/hashicorp/go-getter v1.4.2-0.20200106182914-9813cbd4eb02 github.com/hashicorp/go-version v1.2.1 github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/terraform v0.12.26 github.com/iancoleman/strcase v0.1.2 github.com/juju/ansiterm v0.0.0-20210706145210-9283cdf370b5 // indirect github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect github.com/k0kubun/pp v3.0.1+incompatible github.com/kyokomi/emoji v2.1.0+incompatible github.com/logrusorgru/aurora v0.0.0-20191017060258-dc85c304c434 github.com/lunixbochs/vtclean v1.0.0 // indirect github.com/machinebox/graphql v0.2.2 github.com/manifoldco/promptui v0.8.0 github.com/matryer/is v1.3.0 // indirect github.com/mattn/go-colorable v0.1.8 // indirect github.com/mattn/go-isatty v0.0.13 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/sirupsen/logrus v1.6.0 github.com/spf13/cobra v1.1.3 github.com/stretchr/testify v1.7.0 github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae golang.org/x/net v0.0.0-20200226121028-0de0cce0169b // indirect golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v2 v2.4.0 ) // Tencent cloud unpublished their version v3.0.82 and became v1.0.191 // https://github.com/hashicorp/terraform/issues/29021 replace github.com/tencentcloud/tencentcloud-sdk-go v3.0.82+incompatible => github.com/tencentcloud/tencentcloud-sdk-go v1.0.191 ================================================ FILE: go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3 h1:AVXDdKsrtX33oR9fbCMu/+c1o8Ofjq6Ku/MInaLVg5Y= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go/bigquery v1.0.1 h1:hL+ycaJpVE9M7nLoiXb/Pn10ENE2u+oddxbD8uu0ZVU= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/datastore v1.0.0 h1:Kt+gOPPp2LEPWp8CSfxhsM8ik9CcyE/gYu+0r+RnZvM= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1 h1:W9tAK3E57P75u0XLLR82LZyw8VpAnhmyTOxW9qzmyj8= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0 h1:VV2nUM3wwLLGh9lSABFgZMjInyUbJeaRSE64WuAIQ+4= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v36.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest v0.9.2/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.8.1-0.20191028180845-3492b2aff503/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= github.com/Azure/go-autorest/autorest/azure/cli v0.2.0/go.mod h1:WWTbGPvkAg3I4ms2j2s+Zr5xCGwGqTQh+6M2ZqOczkE= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChrisTrenkamp/goxpath v0.0.0-20170922090931-c385f95c6022/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM= github.com/Unknwon/com v0.0.0-20151008135407-28b053d5a292/go.mod h1:KYCjqMOeHpNuTOiFQU6WEcTG7poCJrUs0YgyHNtn1no= github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agl/ed25519 v0.0.0-20150830182803-278e1ec8e8a6/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190329064014-6e358769c32a/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA= github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190103054945-8205d1f41e70/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/aliyun/aliyun-tablestore-go-sdk v4.1.2+incompatible/go.mod h1:LDQHRZylxvcg8H7wBIDfvO5g/cy4/sz1iucBlc2l3Jw= github.com/antchfx/xpath v0.0.0-20190129040759-c8489ed3251e/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= github.com/antchfx/xquery v0.0.0-20180515051857-ad5b8c7a47b0/go.mod h1:LzD22aAzDP8/dyiCKFp31He4m2GPjl0AFyzDtZzUu9M= github.com/apparentlymart/go-cidr v1.0.1/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-versions v0.0.2-0.20180815153302-64b99f7cb171/go.mod h1:JXY95WvQrPJQtudvNARshgWajS7jNNlM90altXIPNyI= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= github.com/aws/aws-sdk-go v1.25.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.30.12 h1:KrjyosZvkpJjcwMk0RNxMZewQ47v7+ZkbQDXjWsJMs8= github.com/aws/aws-sdk-go v1.30.12/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmatcuk/doublestar v1.1.5/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/buger/goterm v1.0.0 h1:ZB6uUlY8+sjJyFGzz2WpRqX2XYPeXVgtZAOJMwOsTWM= github.com/buger/goterm v1.0.0/go.mod h1:16STi3LquiscTIHA8SXUNKEa/Cnu4ZHBH8NsCaWgso0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.0/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dnaeon/go-vcr v0.0.0-20180920040454-5637cf3d8a31/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/dylanmei/iso8601 v0.1.0/go.mod h1:w9KhXSgIyROl1DefbMYIE7UVSIvELTbMrCfx+QkYnoQ= github.com/dylanmei/winrmtest v0.0.0-20190225150635-99b7fe2fddf1/go.mod h1:lcy9/2gH1jn/VCLouHA6tOEwLoNVd4GW6zhuKLmHC2Y= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gabriel-vasile/mimetype v1.1.1 h1:qbN9MPuRf3bstHu9zkI9jDWNfH//9+9kHxr9oRBBBOA= github.com/gabriel-vasile/mimetype v1.1.1/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gophercloud/gophercloud v0.0.0-20190208042652-bc37892e1968/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= github.com/gophercloud/utils v0.0.0-20190128072930-fbb6ab446f01/go.mod h1:wjDF8z83zTeg5eMLml5EBSlAhbF7G8DobyI1YsMuyzw= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/aws-sdk-go-base v0.4.0/go.mod h1:eRhlz3c4nhqxFZJAahJEFL7gh6Jyj5rQmQc7F9eHFyQ= github.com/hashicorp/consul v0.0.0-20171026175957-610f3c86a089/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-azure-helpers v0.10.0/go.mod h1:YuAtHxm2v74s+IjQwUG88dHBJPd5jL+cXr5BGVzSKhE= github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-getter v1.4.2-0.20200106182914-9813cbd4eb02 h1:l1KB3bHVdvegcIf5upQ5mjcHjs2qsWnKh4Yr9xgIuu8= github.com/hashicorp/go-getter v1.4.2-0.20200106182914-9813cbd4eb02/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY= github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= github.com/hashicorp/go-hclog v0.0.0-20181001195459-61d530d6c27f/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-immutable-radix v0.0.0-20180129170900-7f3cd4390caa/go.mod h1:6ij3Z20p+OhOkCSrA0gImAWoHYQRGbnlcuk6XYTiaRw= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.4/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-plugin v1.3.0/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0= github.com/hashicorp/go-retryablehttp v0.5.2/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= github.com/hashicorp/go-slug v0.4.1/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8= github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-tfe v0.8.1/go.mod h1:XAV72S4O1iP8BDaqiaPLmL2B4EE6almocnOn8E8stHc= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl/v2 v2.0.0/go.mod h1:oVVDG71tEinNGYCxinCYadcmKU9bglqW9pV3txagJ90= github.com/hashicorp/hcl/v2 v2.3.0 h1:iRly8YaMwTBAKhn1Ybk7VSdzbnopghktCD031P8ggUE= github.com/hashicorp/hcl/v2 v2.3.0/go.mod h1:d+FwDBbOLvpAM3Z6J7gPj/VoAGkNe/gm352ZhjJ/Zv8= github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590/go.mod h1:n2TSygSNwsLJ76m8qFXTSc7beTb+auJxYdqrnoqwZWE= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.0/go.mod h1:ncdBp14cuox2iFOq3kDiquKU6fqsTBc3W6JvZwjxxsE= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.0.0-20160124182025-e4ec8cc423bb/go.mod h1:h/Ru6tmZazX7WO/GDmwdpS975F019L4t5ng5IgwbNrE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/terraform v0.12.26 h1:FVsTCH1DMvTlzVSO2sKCzkwLczf/eZBO4GuY5IbHFk4= github.com/hashicorp/terraform v0.12.26/go.mod h1:CBxNAiTW0pLap44/3GU4j7cYE2bMhkKZNlHPcr4P55U= github.com/hashicorp/terraform-config-inspect v0.0.0-20191212124732-c6ae6269b9d7/go.mod h1:p+ivJws3dpqbp1iP84+npOyAmTTOLMgCzrXd3GSdn/A= github.com/hashicorp/terraform-svchost v0.0.0-20191011084731-65d371908596/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg= github.com/hashicorp/vault v0.10.4/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bAbosPMpP0= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/iancoleman/strcase v0.1.2 h1:gnomlvw9tnV3ITTAxzKSgTF+8kFWcU/f+TgttpXGz1U= github.com/iancoleman/strcase v0.1.2/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/joyent/triton-go v0.0.0-20180313100802-d8f9c0314926/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= github.com/juju/ansiterm v0.0.0-20210706145210-9283cdf370b5 h1:Q5klzs6BL5FkassBX65t+KkG0XjYcjxEm+GNcQAsuaw= github.com/juju/ansiterm v0.0.0-20210706145210-9283cdf370b5/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/k0kubun/pp v3.0.1+incompatible h1:3tqvf7QgUnZ5tXO6pNAZlrvHgl6DvifjDrd9g2S9Z40= github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kyokomi/emoji v2.1.0+incompatible h1:+DYU2RgpI6OHG4oQkM5KlqD3Wd3UPEsX8jamTo1Mp6o= github.com/kyokomi/emoji v2.1.0+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/likexian/gokit v0.0.0-20190309162924-0a377eecf7aa/go.mod h1:QdfYv6y6qPA9pbBA2qXtoT8BMKha6UyNbxWGWl/9Jfk= github.com/likexian/gokit v0.0.0-20190418170008-ace88ad0983b/go.mod h1:KKqSnk/VVSW8kEyO2vVCXoanzEutKdlBAPohmGXkxCk= github.com/likexian/gokit v0.0.0-20190501133040-e77ea8b19cdc/go.mod h1:3kvONayqCaj+UgrRZGpgfXzHdMYCAO0KAt4/8n0L57Y= github.com/likexian/gokit v0.20.15/go.mod h1:kn+nTv3tqh6yhor9BC4Lfiu58SmH8NmQ2PmEl+uM6nU= github.com/likexian/simplejson-go v0.0.0-20190409170913-40473a74d76d/go.mod h1:Typ1BfnATYtZ/+/shXfFYLrovhFyuKvzwrdOnIDHlmg= github.com/likexian/simplejson-go v0.0.0-20190419151922-c1f9f0b4f084/go.mod h1:U4O1vIJvIKwbMZKUJ62lppfdvkCdVd2nfMimHK81eec= github.com/likexian/simplejson-go v0.0.0-20190502021454-d8787b4bfa0b/go.mod h1:3BWwtmKP9cXWwYCr5bkoVDEfLywacOv0s06OBEDpyt8= github.com/logrusorgru/aurora v0.0.0-20191017060258-dc85c304c434 h1:im9kkmH0WWwxzegiv18gSUJbuXR9y028rXrWuPp6Jug= github.com/logrusorgru/aurora v0.0.0-20191017060258-dc85c304c434/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lusis/go-artifactory v0.0.0-20160115162124-7e4ce345df82/go.mod h1:y54tfGmO3NKssKveTEFFzH8C/akrSOy/iW9qEAUDV84= github.com/machinebox/graphql v0.2.2 h1:dWKpJligYKhYKO5A2gvNhkJdQMNZeChZYyBbrZkBZfo= github.com/machinebox/graphql v0.2.2/go.mod h1:F+kbVMHuwrQ5tYgU9JXlnskM8nOaFxCAEolaQybkjWA= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/manifoldco/promptui v0.8.0 h1:R95mMF+McvXZQ7j1g8ucVZE1gLP3Sv6j9vlF9kyRqQo= github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ= github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc= github.com/masterzen/winrm v0.0.0-20190223112901-5e5c9a7fe54b/go.mod h1:wr1VqkwW0AB5JS0QLy5GpVMS9E3VtRoSYXUYyVk46KY= github.com/matryer/is v1.3.0 h1:9qiso3jaJrOe6qBRJRBt2Ldht05qDiFP9le0JOIhRSI= github.com/matryer/is v1.3.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-shellwords v1.0.4/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-linereader v0.0.0-20190213213312-1b945b3263eb/go.mod h1:OaY7UOoTkkrX3wRwjpYRKafIkkyeD0UtweSHAWWiqQM= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/panicwrap v1.0.0/go.mod h1:pKvZHwWrZowLUzftuFq7coarnxbBXU4aQh3N0BJOeeA= github.com/mitchellh/prefixedio v0.0.0-20190213213902-5733675afd51/go.mod h1:kB1naBgV9ORnkiTVeyJOI1DavaJkG4oNIq0Af6ZVKUo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/packer-community/winrmcp v0.0.0-20180102160824-81144009af58/go.mod h1:f6Izs6JvFTdnRbziASagjZ2vmf55NSIkC/weStxCHqk= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.1/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DKN0g6uH7E= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d/go.mod h1:BSTlc8jOjh0niykqEGVXOLXdi9o0r0kR8tCYiMvjFgw= github.com/tencentcloud/tencentcloud-sdk-go v1.0.191/go.mod h1:asUz5BPXxgoPGaRgZaVm1iGcUAuHyYUo1nXqKa83cvI= github.com/tencentyun/cos-go-sdk-v5 v0.0.0-20190808065407-f07404cefc8c/go.mod h1:wk2XFUg6egk4tSDNZtXeKfe2G6690UVyt163PuUxBZk= github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae h1:vgGSvdW5Lqg+I1aZOlG32uyE6xHpLdKhZzcTEktz5wM= github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae/go.mod h1:quDq6Se6jlGwiIKia/itDZxqC5rj6/8OdFyMMAwTxCs= github.com/terraform-providers/terraform-provider-openstack v1.15.0/go.mod h1:2aQ6n/BtChAl1y2S60vebhyJyZXBsuAI5G4+lHrT1Ew= github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v0.0.0-20180813092308-00b869d2f4a5/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok= github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.1+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.2.1 h1:vGMsygfmeCl4Xb6OA5U5XVAaQZ69FvoG7X2jUtQujb8= github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty-yaml v1.0.1/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 h1:A1gGSx58LAGVHUUsOf7IiR0u8Xb6W51gRwfDBhkdcaw= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f h1:3MlESg/jvTr87F4ttA/q4B+uhe/q6qleC9/DP+IwQmY= golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0 h1:Q3Ui3V3/CVinFWFiW39Iw0kMuVrRzYX0wN6OPFp0lTA= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= ================================================ FILE: internal/apply/apply.go ================================================ package apply import ( "errors" "fmt" "path/filepath" "log" "os/exec" "path" "strings" "github.com/commitdev/zero/internal/module" "github.com/commitdev/zero/internal/util" "github.com/hashicorp/terraform/dag" "github.com/commitdev/zero/internal/config/moduleconfig" "github.com/commitdev/zero/internal/config/projectconfig" "github.com/commitdev/zero/pkg/util/exit" "github.com/commitdev/zero/pkg/util/flog" "github.com/manifoldco/promptui" ) func Apply(rootDir string, configPath string, environments []string) error { var errs []error if strings.Trim(configPath, " ") == "" { exit.Fatal("config path cannot be empty!") } configFilePath := path.Join(rootDir, configPath) projectConfig := projectconfig.LoadConfig(configFilePath) if len(environments) == 0 { fmt.Println(`Choose the environments to apply. This will create infrastructure, CI pipelines, etc. At this point, real things will be generated that may cost money! Only a single environment may be suitable for an initial test, but for a real system we suggest setting up both staging and production environments.`) environments = promptEnvironments() } flog.Infof(":mag: checking project %s's module requirements.", projectConfig.Name) errs = modulesWalkCmd("check", rootDir, projectConfig, "check", environments, false, false) // Check operation walks through all modules and can return multiple errors if len(errs) > 0 { msg := "" for i := 0; i < len(errs); i++ { msg += "- " + errs[i].Error() } return errors.New(fmt.Sprintf("The following Module check(s) failed: \n%s", msg)) } flog.Infof(":tada: Bootstrapping project %s. Please use the zero-project.yml file to modify the project as needed.", projectConfig.Name) flog.Infof("Cloud provider: %s", "AWS") // will this come from the config? flog.Infof("Runtime platform: %s", "Kubernetes") flog.Infof("Infrastructure executor: %s", "Terraform") errs = modulesWalkCmd("apply", rootDir, projectConfig, "apply", environments, true, true) if len(errs) > 0 { return errors.New(fmt.Sprintf("Module Apply failed: %s", errs[0])) } flog.Infof(":check_mark_button: Done.") flog.Infof("Your projects and infrastructure have been successfully created. Here are some useful links and commands to get you started:") errs = modulesWalkCmd("summary", rootDir, projectConfig, "summary", environments, true, true) if len(errs) > 0 { return errors.New(fmt.Sprintf("Module summary failed: %s", errs[0])) } return nil } func modulesWalkCmd(lifecycleName string, dir string, projectConfig *projectconfig.ZeroProjectConfig, operation string, environments []string, bailOnError bool, shouldPipeStderr bool) []error { var moduleErrors []error graph := projectConfig.GetDAG() root := []dag.Vertex{projectconfig.GraphRootName} environmentArg := fmt.Sprintf("ENVIRONMENT=%s", strings.Join(environments, ",")) err := graph.DepthFirstWalk(root, func(v dag.Vertex, depth int) error { // Don't process the root if depth == 0 { return nil } name := v.(string) mod := projectConfig.Modules[name] // Add env vars for the makefile envList := []string{ environmentArg, fmt.Sprintf("PROJECT_NAME=%s", projectConfig.Name), fmt.Sprintf("PROJECT_DIR=%s", path.Join(dir, mod.Files.Directory)), fmt.Sprintf("REPOSITORY=%s", mod.Files.Repository), } modulePath := module.GetSourceDir(mod.Files.Source) // Passed in `dir` will only be used to find the project path, not the module path, // unless the module path is relative if module.IsLocal(mod.Files.Source) && !filepath.IsAbs(modulePath) { modulePath = filepath.Join(dir, modulePath) } flog.Debugf("Loaded module: %s from %s", name, modulePath) // TODO: in the case user lost the `/tmp` (module source dir), this will fail // and we should redownload the module for the user modConfig, err := module.ParseModuleConfig(modulePath) if err != nil { exit.Fatal("Failed to load Module: %s", err) } envVarTranslationMap := modConfig.GetParamEnvVarTranslationMap() envList = util.AppendProjectEnvToCmdEnv(mod.Parameters, envList, envVarTranslationMap) flog.Debugf("Env injected: %#v", envList) // only print msg for apply, or else it gets a little spammy if lifecycleName == "apply" { flog.Infof("Executing %s command for %s...", lifecycleName, modConfig.Name) } operationCommand := getModuleOperationCommand(modConfig, operation) execErr := util.ExecuteCommand(exec.Command(operationCommand[0], operationCommand[1:]...), modulePath, envList, shouldPipeStderr) if execErr != nil { formatedErr := errors.New(fmt.Sprintf("Module (%s) %s", modConfig.Name, execErr.Error())) if bailOnError { return formatedErr } else { moduleErrors = append(moduleErrors, formatedErr) } } return nil }) if err != nil { moduleErrors = append(moduleErrors, err) } return moduleErrors } func getModuleOperationCommand(mod moduleconfig.ModuleConfig, operation string) (operationCommand []string) { defaultCheck := []string{"make", "check"} defaultApply := []string{"make"} defaultSummary := []string{"make", "summary"} switch operation { case "check": if mod.Commands.Check != "" { operationCommand = []string{"sh", "-c", mod.Commands.Check} } else { operationCommand = defaultCheck } case "apply": if mod.Commands.Apply != "" { operationCommand = []string{"sh", "-c", mod.Commands.Apply} } else { operationCommand = defaultApply } case "summary": if mod.Commands.Summary != "" { operationCommand = []string{"sh", "-c", mod.Commands.Summary} } else { operationCommand = defaultSummary } default: panic("Unexpected operation") } return operationCommand } // promptEnvironments Prompts the user for the environments to apply against and returns a slice of strings representing the environments func promptEnvironments() []string { items := map[string][]string{ "Staging": {"stage"}, "Production": {"prod"}, } labels := []string{"Staging", "Production"} providerPrompt := promptui.Select{ Label: "Environments", Items: labels, } _, providerResult, err := providerPrompt.Run() if err != nil { log.Fatalf("Prompt failed %v\n", err) panic(err) } return items[providerResult] } func validateEnvironments(applyEnvironments []string) { // Strict for now, we can brainstorm how much we want to support custom environments later for _, env := range applyEnvironments { if env != "staging" && env != "production" { exit.Fatal("The currently supported environments are \"staging\" and \"production\"") } } } ================================================ FILE: internal/apply/apply_test.go ================================================ package apply_test import ( "io/ioutil" "os" "path/filepath" "testing" "github.com/commitdev/zero/internal/apply" "github.com/commitdev/zero/internal/constants" "github.com/stretchr/testify/assert" "github.com/termie/go-shutil" ) func TestApply(t *testing.T) { applyConfigPath := constants.ZeroProjectYml applyEnvironments := []string{"staging", "production"} var tmpDir string t.Run("Should run apply and execute make on each folder module", func(t *testing.T) { tmpDir = setupTmpDir(t, "../../tests/test_data/apply/") err := apply.Apply(tmpDir, applyConfigPath, applyEnvironments) assert.FileExists(t, filepath.Join(tmpDir, "project1/project.out")) assert.FileExists(t, filepath.Join(tmpDir, "project2/project.out")) content, err := ioutil.ReadFile(filepath.Join(tmpDir, "project1/project.out")) assert.NoError(t, err) assert.Equal(t, "foo: bar\nrepo: github.com/commitdev/project1\n", string(content)) content, err = ioutil.ReadFile(filepath.Join(tmpDir, "project2/project.out")) assert.NoError(t, err) assert.Equal(t, "baz: qux\n", string(content)) }) t.Run("Modules runs command overides", func(t *testing.T) { content, err := ioutil.ReadFile(filepath.Join(tmpDir, "project2/check.out")) assert.NoError(t, err) assert.Equal(t, "custom check\n", string(content)) }) t.Run("Zero apply honors the envVarName overwrite from module definition", func(t *testing.T) { content, err := ioutil.ReadFile(filepath.Join(tmpDir, "project1/feature.out")) assert.NoError(t, err) assert.Equal(t, "envVarName of viaEnvVarName: baz\n", string(content)) }) t.Run("Modules with failing checks should return error", func(t *testing.T) { tmpDir = setupTmpDir(t, "../../tests/test_data/apply-failing/") err := apply.Apply(tmpDir, applyConfigPath, applyEnvironments) assert.Regexp(t, "^The following Module check\\(s\\) failed:", err.Error()) assert.Regexp(t, "Module \\(project1\\)", err.Error()) assert.Regexp(t, "Module \\(project2\\)", err.Error()) assert.Regexp(t, "Module \\(project3\\)", err.Error()) }) } func setupTmpDir(t *testing.T, exampleDirPath string) string { var err error tmpDir := filepath.Join(os.TempDir(), "apply") err = os.RemoveAll(tmpDir) assert.NoError(t, err) err = shutil.CopyTree(exampleDirPath, tmpDir, nil) assert.NoError(t, err) return tmpDir } ================================================ FILE: internal/condition/condition.go ================================================ // This module is invoked when we do template rendering during "zero create." // // Each module can have a "conditions" section in their zero-module.yml that // specifies a condition in the form: // // conditions: // - action: ignoreFile // matchField: // whenValue: // data: // - // // The structure for this is defined in: // internal/config/projectconfig/project_config.go. // The definition is in that file simply to avoid cyclic dependencies; but // The logic for each type of condition exists here. // // See: internal/generate/generate_modules.go // See: internal/config/projectconfig/project_config.go // package condition import ( "os" "path" "github.com/commitdev/zero/internal/config/projectconfig" ) // Function dispatch for any kind of condition. func Perform(cond projectconfig.Condition, mod projectconfig.Module) { value, found := mod.Parameters[cond.MatchField] // Exit if the condition isn't met. if !found || value != cond.WhenValue { return } // Okay, the condition was met, let's execute it. switch cond.Action { case "ignoreFile": ignoreFile(cond.Data, mod) } } // Excludes paths from template rendering. // This occurs after-the-fact. That is, we render all templates to disk, then // use 'paths' to determine which files and directories to remove from disk. // func ignoreFile(paths []string, mod projectconfig.Module) { for _, file := range paths { filepath := path.Join(mod.Files.Directory, file) os.RemoveAll(filepath) } } ================================================ FILE: internal/condition/condition_test.go ================================================ package condition_test import ( "encoding/base64" "math/rand" "os" "testing" "github.com/commitdev/zero/internal/condition" "github.com/commitdev/zero/internal/config/projectconfig" ) func testSetup(paramKey, paramValue string) (string, projectconfig.Module) { bytes := make([]byte, 15) _, _ = rand.Read(bytes) name := string(base64.StdEncoding.EncodeToString(bytes[:])) _, _ = os.Create(name) params := make(projectconfig.Parameters) params[paramKey] = paramValue mod := projectconfig.Module{ Parameters: params, Files: projectconfig.Files{ Directory: ".", Source: ".", }, } return name, mod } func TestPerformIgnoreFileConditionNotMet(t *testing.T) { field := "testField" value := "trigger" filename, mod := testSetup(field, "other value") defer os.Remove(filename) cond := projectconfig.Condition{ Action: "ignoreFile", MatchField: field, WhenValue: value, Data: []string{filename}, } condition.Perform(cond, mod) _, err := os.Stat(filename) if err != nil && !os.IsExist(err) { t.Errorf("Expected %v not to be removed\n", filename) } } func TestPerformIgnoreFileConditionMet(t *testing.T) { field := "testField" value := "trigger" filename, mod := testSetup(field, value) defer os.Remove(filename) cond := projectconfig.Condition{ Action: "ignoreFile", MatchField: field, WhenValue: value, Data: []string{filename}, } condition.Perform(cond, mod) _, err := os.Stat(filename) if !os.IsNotExist(err) { t.Errorf("Expected %v to be removed\n", filename) } } ================================================ FILE: internal/config/moduleconfig/module_config.go ================================================ package moduleconfig import ( "errors" "fmt" "io/ioutil" "log" "reflect" "strings" goVersion "github.com/hashicorp/go-version" yaml "gopkg.in/yaml.v2" "github.com/commitdev/zero/internal/config/projectconfig" "github.com/commitdev/zero/internal/constants" "github.com/commitdev/zero/pkg/util/flog" "github.com/commitdev/zero/version" "github.com/iancoleman/strcase" ) type ModuleConfig struct { Name string Description string Author string Commands ModuleCommands `yaml:"commands,omitempty"` DependsOn []string `yaml:"dependsOn,omitempty"` TemplateConfig `yaml:"template"` RequiredCredentials []string `yaml:"requiredCredentials"` ZeroVersion VersionConstraints `yaml:"zeroVersion,omitempty"` Parameters []Parameter Conditions []Condition `yaml:"conditions,omitempty"` } type ModuleCommands struct { Apply string `yaml:"apply,omitempty"` Check string `yaml:"check,omitempty"` Summary string `yaml:"summary,omitempty"` } func checkVersionAgainstConstrains(vc VersionConstraints, versionString string) bool { v, err := goVersion.NewVersion(versionString) if err != nil { return false } return vc.Check(v) } // ValidateZeroVersion receives a module config, and returns whether the running zero's binary // is compatible with the module func ValidateZeroVersion(mc ModuleConfig) bool { if mc.ZeroVersion.String() == "" { return true } zeroVersion := version.AppVersion flog.Debugf("Checking Zero version (%s) against %s", zeroVersion, mc.ZeroVersion) // Unreleased versions or test runs, defaults to SNAPSHOT when not declared if zeroVersion == "SNAPSHOT" { return true } return checkVersionAgainstConstrains(mc.ZeroVersion, zeroVersion) } type Parameter struct { Field string Label string `yaml:"label,omitempty"` Options yaml.MapSlice `yaml:"options,omitempty"` Execute string `yaml:"execute,omitempty"` Value string `yaml:"value,omitempty"` Default string `yaml:"default,omitempty"` Info string `yaml:"info,omitempty"` FieldValidation Validate `yaml:"fieldValidation,omitempty"` Type string `yaml:"type,omitempty"` OmitFromProjectFile bool `yaml:"omitFromProjectFile,omitempty"` Conditions []Condition `yaml:"conditions,omitempty"` EnvVarName string `yaml:"envVarName,omitempty"` } type Condition struct { Action string `yaml:"action"` MatchField string `yaml:"matchField"` WhenValue string `yaml:"whenValue"` Data []string `yaml:"data,omitempty"` ElseValue string `yaml:"elseValue,omitempty"` } type Validate struct { Type string `yaml:"type,omitempty"` Value string `yaml:"value,omitempty"` ErrorMessage string `yaml:"errorMessage,omitempty"` } type TemplateConfig struct { StrictMode bool Delimiters []string InputDir string `yaml:"inputDir"` OutputDir string `yaml:"outputDir"` } type VersionConstraints struct { goVersion.Constraints } // A "nice" wrapper around findMissing() func (cfg ModuleConfig) collectMissing() []string { var missing []string findMissing(reflect.ValueOf(cfg), "", "", &missing) return missing } // GetParamEnvVarTranslationMap returns a map for translating parameter's `Field` into env-var keys // It loops through each parameter then adds to translation map if applicable // for zero apply / zero init's prompt execute, // this is useful for translating params like AWS credentials for running the AWS cli func (cfg ModuleConfig) GetParamEnvVarTranslationMap() map[string]string { translationMap := make(map[string]string) for i := 0; i < len(cfg.Parameters); i++ { param := cfg.Parameters[i] if param.EnvVarName != "" { translationMap[param.Field] = param.EnvVarName } } return translationMap } func LoadModuleConfig(filePath string) (ModuleConfig, error) { config := ModuleConfig{} data, err := ioutil.ReadFile(filePath) if err != nil { return config, err } err = yaml.Unmarshal(data, &config) if err != nil { return config, err } missing := config.collectMissing() if len(missing) > 0 { flog.Errorf("%v is missing information", filePath) for _, m := range missing { flog.Errorf("\t %v", m) } log.Fatal("") } if !ValidateZeroVersion(config) { constraint := config.ZeroVersion.Constraints.String() errTpl := `Module(%s) requires Zero to be version %s. Your current Zero version is: %s Please update your Zero version to %s. Please check %s for available releases.` return config, errors.New(fmt.Sprintf(errTpl, config.Name, constraint, version.AppVersion, constraint, constants.ZeroReleaseURL)) } return config, nil } // Recurses through a datastructure to find any missing data. // This assumes several things: // 1. The structure matches that defined by ModuleConfig and its child datastructures. // 2. YAML struct field metadata is sufficient to define whether an attribute is missing or not. // That is, "yaml:foo,omitempty" tells us this is not a required field because we can omit it. // 3. Slices and arrays are assumed to be optional. // // As this function recurses through the datastructure, it builds up a string // path representing each node's path within the datastructure. // If the value of the current node is equal to the zero value for its datatype // and its struct field does *not* have a "omitempty" value, then we assume it // is missing and add it to the resultset. func findMissing(obj reflect.Value, path, metadata string, missing *[]string) { t := obj.Type() switch t.Kind() { case reflect.String: if obj.String() == "" && !strings.Contains(metadata, "omitempty") { *missing = append(*missing, path) } case reflect.Slice, reflect.Array: for i := 0; i < obj.Len(); i++ { prefix := fmt.Sprintf("%v[%v]", path, i) findMissing(obj.Index(i), prefix, metadata, missing) } case reflect.Struct: for i := 0; i < t.NumField(); i++ { fieldType := t.Field(i) fieldTags, _ := fieldType.Tag.Lookup("yaml") fieldVal := obj.Field(i) tags := strings.Split(fieldTags, ",") hasOmitEmpty := false // We have all metadata yaml tags, now let's remove the "omitempty" tag if // it is present. // Then if we have only one tag remaining, this must be the expected yaml // identifer. // Otherwise the name of the yaml identifier should match the struct // attribute name. for i := len(tags) - 1; i >= 0; i-- { tag := tags[i] if tag == "omitempty" { hasOmitEmpty = true tags = append(tags[:i], tags[i+1:]...) } } yamlName := strcase.ToLowerCamel(fieldType.Name) if len(tags) == 1 && tags[0] != "" { // For some reason, empty tag lists are giving a count of 1. yamlName = tags[0] } prefix := yamlName if path != "" { prefix = fmt.Sprintf("%v.%v", path, yamlName) } zeroVal := reflect.Zero(fieldType.Type) if fieldVal == zeroVal && !hasOmitEmpty { *missing = append(*missing, prefix) } findMissing(fieldVal, prefix, fieldTags, missing) } } } // SummarizeParameters receives all parameters gathered from prompts during `Zero init` // and based on module definition to construct the parameters for each module for zero-project.yml // filters out parameters defined as OmitFromProjectFile: true func SummarizeParameters(module ModuleConfig, allParams map[string]string) map[string]string { moduleParams := make(projectconfig.Parameters) // Loop through all the prompted values and find the ones relevant to this module for parameterKey, parameterValue := range allParams { for _, moduleParameter := range module.Parameters { if moduleParameter.Field == parameterKey { if moduleParameter.OmitFromProjectFile { flog.Debugf("Omitted %s from %s", parameterKey, module.Name) } else { moduleParams[parameterKey] = parameterValue } } } } return moduleParams } // SummarizeConditions based on conditions from zero-module.yml // creates and returns slice of conditions for project config func SummarizeConditions(module ModuleConfig) []projectconfig.Condition { moduleConditions := make([]projectconfig.Condition, len(module.Conditions)) for i, condition := range module.Conditions { moduleConditions[i] = projectconfig.Condition{ Action: condition.Action, MatchField: condition.MatchField, WhenValue: condition.WhenValue, Data: condition.Data, } } return moduleConditions } // GetFirstConditionElseValue returns the default value of the first condition that has a default func GetFirstConditionElseValue(parameter Parameter) string { for _, condition := range parameter.Conditions { if condition.ElseValue != "" { return condition.ElseValue } } return "" } // UnmarshalYAML Parses a version constraint string into go-version constraint during yaml parsing func (semVer *VersionConstraints) UnmarshalYAML(unmarshal func(interface{}) error) error { var versionString string err := unmarshal(&versionString) if err != nil { return err } if versionString != "" { constraints, constErr := goVersion.NewConstraint(versionString) // If an invalid constraint is declared in a module // instead of erroring out we just print a warning message if constErr != nil { flog.Warnf("Zero version constraint invalid format: %s", constErr) } *semVer = VersionConstraints{constraints} } return nil } ================================================ FILE: internal/config/projectconfig/init.go ================================================ package projectconfig import ( "bytes" "fmt" "io/ioutil" "path" "text/template" "github.com/commitdev/zero/internal/constants" "github.com/commitdev/zero/internal/util" "github.com/commitdev/zero/pkg/util/flog" "gopkg.in/yaml.v2" ) const zeroProjectConfigTemplate = ` # zero-project.yml file containing all the required modules and their configuration. # This file is generated by the zero init command but can be modified by hand before running zero create. # Do not check this into source control, it may contain sensitive credentials. name: {{.Name}} shouldPushRepositories: {{.ShouldPushRepositories | printf "%v"}} modules: {{.Modules}} ` var RootDir = "./" func SetRootDir(dir string) { RootDir = dir } // CreateProjectConfigFile extracts the required content for zero project config file then write to disk. func CreateProjectConfigFile(dir string, projectName string, projectContext *ZeroProjectConfig) error { content, err := getProjectFileContent(*projectContext) if err != nil { return err } filePath := path.Join(dir, projectName, constants.ZeroProjectYml) flog.Debugf("Project file path: %s", filePath) writeErr := ioutil.WriteFile(filePath, []byte(content), 0644) if writeErr != nil { return err } return nil } func getProjectFileContent(projectConfig ZeroProjectConfig) (string, error) { var tplBuffer bytes.Buffer tmpl, err := template.New("projectConfig").Parse(zeroProjectConfigTemplate) if err != nil { return "", err } if len(projectConfig.Modules) == 0 { return "", fmt.Errorf("Invalid project config, expected config modules to be non-empty") } pConfigModules, err := yaml.Marshal(projectConfig.Modules) if err != nil { return "", err } t := struct { Name string ShouldPushRepositories bool Modules string }{ Name: projectConfig.Name, ShouldPushRepositories: projectConfig.ShouldPushRepositories, Modules: util.IndentString(string(pConfigModules), 2), } if err := tmpl.Execute(&tplBuffer, t); err != nil { return "", err } return tplBuffer.String(), nil } ================================================ FILE: internal/config/projectconfig/init_test.go ================================================ package projectconfig_test import ( "os" "path" "testing" "github.com/commitdev/zero/internal/config/projectconfig" "github.com/commitdev/zero/internal/constants" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/assert" ) func TestCreateProjectConfigFile(t *testing.T) { const testDir = "../../test-sandbox" projectName := "test-project" projectconfig.SetRootDir(testDir) defer os.RemoveAll(testDir) testDirPath := path.Join(projectconfig.RootDir, projectName) // create sandbox dir err := os.MkdirAll(testDirPath, os.ModePerm) if err != nil { t.Fatal(err) } expectedConfig := &projectconfig.ZeroProjectConfig{ Name: projectName, ShouldPushRepositories: false, Modules: eksGoReactSampleModules(), } assert.NoError(t, projectconfig.CreateProjectConfigFile(projectconfig.RootDir, projectName, expectedConfig)) // make sure the file exists if _, err := os.Stat(path.Join(testDirPath, constants.ZeroProjectYml)); err != nil { t.Fatal(err) } t.Run("Should return a valid project config", func(t *testing.T) { resultConfig := projectconfig.LoadConfig(path.Join(testDirPath, constants.ZeroProjectYml)) if !cmp.Equal(expectedConfig, resultConfig, cmpopts.EquateEmpty()) { t.Errorf("projectconfig.ZeroProjectConfig.Unmarshal mismatch (-expected +result):\n%s", cmp.Diff(expectedConfig, resultConfig)) } }) t.Run("Should fail if modules are missing from project config", func(t *testing.T) { expectedConfig.Modules = nil assert.Error(t, projectconfig.CreateProjectConfigFile(projectconfig.RootDir, projectName, expectedConfig)) }) } ================================================ FILE: internal/config/projectconfig/project_config.go ================================================ package projectconfig import ( "errors" "io/ioutil" "github.com/hashicorp/terraform/dag" "github.com/k0kubun/pp" "gopkg.in/yaml.v2" "github.com/commitdev/zero/pkg/util/exit" "github.com/commitdev/zero/pkg/util/flog" ) // GraphRootName represents the root of the graph of modules in a project const GraphRootName = "graphRoot" type ZeroProjectConfig struct { Name string `yaml:"name"` ShouldPushRepositories bool `yaml:"shouldPushRepositories"` Parameters map[string]string Modules Modules `yaml:"modules"` } type Modules map[string]Module type Module struct { DependsOn []string `yaml:"dependsOn,omitempty"` Parameters Parameters `yaml:"parameters,omitempty"` Files Files Conditions []Condition `yaml:"conditions,omitempty"` } // ReadVendorCredentialsFromModule uses parsed project-config's module // based on vendor parameter, retrieve the vendor's credential // for pre-defined functionalities (eg: Github api key for pushing repos to github) func ReadVendorCredentialsFromModule(m Module, vendor string) (error, string) { // this mapping could be useful for module config as well vendorToParamMap := map[string]string{ "github": "githubAccessToken", "circleci": "circleciApiKey", } if parameterKey, ok := vendorToParamMap[vendor]; ok { if val, ok := m.Parameters[parameterKey]; ok { return nil, val } return errors.New("Parameter not found in module."), "" } return errors.New("Unsupported vendor provided."), "" } type Parameters map[string]string type Condition struct { Action string `yaml:"action"` MatchField string `yaml:"matchField"` WhenValue string `yaml:"whenValue"` Data []string `yaml:"data,omitempty"` } type Files struct { Directory string `yaml:"dir,omitempty"` Repository string `yaml:"repo,omitempty"` Source string } func LoadConfig(filePath string) *ZeroProjectConfig { config := &ZeroProjectConfig{} data, err := ioutil.ReadFile(filePath) if err != nil { exit.Fatal("failed to read config: %v", err) } err = yaml.Unmarshal(data, &config) if err != nil { exit.Fatal("failed to parse config: %v", err) } flog.Debugf("Loaded project config: %s from %s", config.Name, filePath) return config } func (c *ZeroProjectConfig) Print() { pp.Println(c) } // GetDAG returns a graph of the module names used in this project config func (c *ZeroProjectConfig) GetDAG() dag.AcyclicGraph { var g dag.AcyclicGraph // Add vertices to graph g.Add(GraphRootName) for name := range c.Modules { g.Add(name) } // Connect modules in graph for name, m := range c.Modules { if len(m.DependsOn) == 0 { g.Connect(dag.BasicEdge(GraphRootName, name)) } else { for _, dependencyName := range m.DependsOn { g.Connect(dag.BasicEdge(dependencyName, name)) } } } return g } func NewModule(parameters Parameters, directory string, repository string, source string, dependsOn []string, conditions []Condition) Module { return Module{ Parameters: parameters, DependsOn: dependsOn, Files: Files{ Directory: directory, Repository: repository, Source: source, }, Conditions: conditions, } } ================================================ FILE: internal/config/projectconfig/project_config_test.go ================================================ package projectconfig_test import ( "io/ioutil" "log" "os" "path/filepath" "testing" "github.com/commitdev/zero/internal/config/projectconfig" "github.com/commitdev/zero/internal/constants" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/assert" ) func TestLoadConfig(t *testing.T) { file, err := ioutil.TempFile(os.TempDir(), "config.yml") if err != nil { log.Fatal(err) } defer os.Remove(file.Name()) file.Write([]byte(validConfigContent())) filePath := file.Name() want := &projectconfig.ZeroProjectConfig{ Name: "abc", ShouldPushRepositories: true, Modules: eksGoReactSampleModules(), } t.Run("Should load and unmarshal config correctly", func(t *testing.T) { got := projectconfig.LoadConfig(filePath) if !cmp.Equal(want, got, cmpopts.EquateEmpty()) { t.Errorf("projectconfig.ZeroProjectConfig.Unmarshal mismatch (-want +got):\n%s", cmp.Diff(want, got)) } }) } func eksGoReactSampleModules() projectconfig.Modules { parameters := projectconfig.Parameters{"a": "b"} return projectconfig.Modules{ "aws-eks-stack": projectconfig.NewModule(parameters, "zero-aws-eks-stack", "github.com/something/repo1", "github.com/commitdev/zero-aws-eks-stack", []string{}, []projectconfig.Condition{}), "backend-go": projectconfig.NewModule(parameters, "zero-backend-go", "github.com/something/repo2", "github.com/commitdev/zero-backend-go", []string{}, []projectconfig.Condition{}), "frontend-react": projectconfig.NewModule(parameters, "zero-frontend-react", "github.com/something/repo3", "github.com/commitdev/zero-frontend-react", []string{}, []projectconfig.Condition{}), } } func validConfigContent() string { return ` # Templated zero-project.yml file name: abc shouldPushRepositories: true modules: aws-eks-stack: parameters: a: b files: dir: zero-aws-eks-stack repo: github.com/something/repo1 source: github.com/commitdev/zero-aws-eks-stack backend-go: parameters: a: b files: dir: zero-backend-go repo: github.com/something/repo2 source: github.com/commitdev/zero-backend-go frontend-react: parameters: a: b files: dir: zero-frontend-react repo: github.com/something/repo3 source: github.com/commitdev/zero-frontend-react ` } func TestProjectConfigModuleGraph(t *testing.T) { configPath := filepath.Join("../../../tests/test_data/projectconfig/", constants.ZeroProjectYml) t.Run("Should generate a valid, correct graph based on the project config", func(t *testing.T) { pc := projectconfig.LoadConfig(configPath) graph := pc.GetDAG() // Validate the graph assert.NoError(t, graph.Validate()) // Check the structure of the graph root, err := graph.Root() assert.NoError(t, err) assert.Equal(t, "graphRoot", root) want := `graphRoot project1 project1 project2 project3 project2 project4 project3 project4 project5 project4 project5 ` assert.Equal(t, want, graph.String()) }) } ================================================ FILE: internal/constants/constants.go ================================================ package constants const ( TmpRegistryYml = "tmp/registry.yaml" TemplatesDir = "tmp/templates" ZeroProjectYml = "zero-project.yml" ZeroModuleYml = "zero-module.yml" ZeroHomeDirectory = ".zero" IgnoredPaths = "(?i)zero.module.yml|.git/" TemplateExtn = ".tmpl" // prompt constants MaxPnameLength = 16 MaxOnameLength = 39 RegexValidation = "regex" FunctionValidation = "function" ZeroReleaseURL = "https://github.com/commitdev/zero/releases" ) ================================================ FILE: internal/generate/generate_modules.go ================================================ package generate import ( "fmt" "io" "log" "os" "path" "path/filepath" "regexp" "sync" "text/template" "github.com/commitdev/zero/internal/condition" "github.com/commitdev/zero/internal/config/projectconfig" "github.com/commitdev/zero/internal/constants" "github.com/commitdev/zero/internal/module" "github.com/commitdev/zero/internal/util" "github.com/commitdev/zero/pkg/util/flog" "github.com/commitdev/zero/pkg/util/fs" "github.com/gabriel-vasile/mimetype" ) // Generate accepts a projectconfig struct and renders the templates for all referenced modules func Generate(projectConfig projectconfig.ZeroProjectConfig, overwriteFiles bool) error { flog.Infof(":clock: Fetching Modules") // Make sure module sources are on disk wg := sync.WaitGroup{} wg.Add(len(projectConfig.Modules)) for _, mod := range projectConfig.Modules { go module.FetchModule(mod.Files.Source, &wg) } wg.Wait() flog.Infof(":memo: Rendering Modules") for _, mod := range projectConfig.Modules { // Load module configuration moduleConfig, err := module.ParseModuleConfig(mod.Files.Source) if err != nil { return fmt.Errorf("unable to load module (%s): %v", mod.Files.Source, err) } moduleDir := path.Join(module.GetSourceDir(mod.Files.Source), moduleConfig.InputDir) delimiters := moduleConfig.Delimiters outputDir := mod.Files.Directory // Data that will be passed in to each template templateData := struct { Name string Params projectconfig.Parameters Files projectconfig.Files Conditions []projectconfig.Condition }{ projectConfig.Name, mod.Parameters, mod.Files, mod.Conditions, } txtTypeFiles, binTypeFiles := sortFileType(moduleDir, outputDir, overwriteFiles) executeTemplates(txtTypeFiles, templateData, delimiters) copyBinFiles(binTypeFiles) for _, cond := range mod.Conditions { condition.Perform(cond, mod) } } return nil } type fileConfig struct { source string destination string modeBits os.FileMode } // sortFileType walks the module directory to find and classify all files into bin / text/plain (non-bin) types. func sortFileType(moduleDir string, outputDir string, overwrite bool) ([]*fileConfig, []*fileConfig) { binTypeFiles := []*fileConfig{} txtTypeFiles := []*fileConfig{} paths, err := getAllFilePathsInDirectory(moduleDir) if err != nil { panic(err) } for _, path := range paths { ignoredPaths, _ := regexp.Compile(constants.IgnoredPaths) if ignoredPaths.MatchString(path) { continue } outputPath := fs.ReplacePath(path, moduleDir, outputDir) if !overwrite { if exists, _ := fs.FileExists(outputPath); exists { flog.Warnf("%v already exists. skipping.", outputPath) continue } } fileInfo, err := os.Stat(path) if err != nil { panic(err) } // detect the file type detectedMIME, err := mimetype.DetectFile(path) if err != nil { panic(err) } // detect root file type isBinary := true for mime := detectedMIME; mime != nil; mime = mime.Parent() { if mime.Is("text/plain") { isBinary = false } } if isBinary { binTypeFiles = append(binTypeFiles, &fileConfig{ source: path, destination: outputPath, modeBits: fileInfo.Mode().Perm(), }) continue } txtTypeFiles = append(txtTypeFiles, &fileConfig{ source: path, destination: outputPath, modeBits: fileInfo.Mode().Perm(), }) } return txtTypeFiles, binTypeFiles } // getAllFilePathsInDirectory Recursively get all file paths in directory, including sub-directories. func getAllFilePathsInDirectory(moduleDir string) ([]string, error) { var paths []string err := filepath.Walk(moduleDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() { paths = append(paths, path) } return nil }) if err != nil { return nil, err } return paths, nil } func executeTemplates(templates []*fileConfig, data interface{}, delimiters []string) { var wg sync.WaitGroup leftDelim := delimiters[0] rightDelim := delimiters[1] if leftDelim == "" { leftDelim = "{{" } if rightDelim == "" { rightDelim = "}}" } // flog.Infof("Templating params:") // pp.Println(data) for _, tmpltConfig := range templates { source := tmpltConfig.source dest := tmpltConfig.destination outputDirPath, _ := path.Split(dest) err := fs.CreateDirs(outputDirPath) if err != nil { flog.Errorf("Error creating directory '%s': %v", source, err) } f, err := os.Create(dest) if err != nil { flog.Errorf("Error initializing file '%s'", err) } err = f.Chmod(tmpltConfig.modeBits) if err != nil { flog.Errorf("Error changing mode bits '%s'", err) } // @TODO if strict mode then only copy file name := path.Base(source) template, err := template.New(name).Delims(leftDelim, rightDelim).Funcs(util.FuncMap).ParseFiles(source) if err != nil { flog.Errorf("Error in template '%s': %v", source, err) } err = template.Execute(f, data) if err != nil { flog.Errorf("Error templating '%s': %v", source, err) } else { flog.Successf("Finished templating : %s", dest) } } wg.Wait() } func copyBinFiles(binTypeFiles []*fileConfig) { for _, binFile := range binTypeFiles { source := binFile.source dest := binFile.destination // create dir outputDirPath, _ := path.Split(dest) err := fs.CreateDirs(outputDirPath) if err != nil { flog.Errorf("Error creating directory '%s': %v", source, err) } // create refs to src and dest from, err := os.Open(source) if err != nil { flog.Errorf("Error opening file to read '%s' : %v", source, err) } defer from.Close() to, err := os.OpenFile(dest, os.O_RDWR|os.O_CREATE, binFile.modeBits) if err != nil { log.Fatal(err) flog.Errorf("Error creating file '%s': %v", dest, err) } defer to.Close() // copy file _, err = io.Copy(to, from) if err != nil { flog.Errorf("Error copying file '%s' : %v", source, err) } else { flog.Successf("Finished copying file : %s", dest) } } } ================================================ FILE: internal/generate/generate_test.go ================================================ package generate_test import ( "io/ioutil" "os" "path/filepath" "testing" "github.com/commitdev/zero/internal/config/projectconfig" "github.com/commitdev/zero/internal/generate" "github.com/stretchr/testify/assert" ) const baseTestFixturesDir = "../../tests/test_data/generate/" func setupTeardown(t *testing.T) (func(t *testing.T), string) { tmpDir := filepath.Join(os.TempDir(), "generate") os.MkdirAll(tmpDir, 0755) os.RemoveAll(tmpDir) return func(t *testing.T) { os.RemoveAll(tmpDir) }, tmpDir } func TestGenerateModules(t *testing.T) { teardown, tmpDir := setupTeardown(t) defer teardown(t) projectConfig := projectconfig.ZeroProjectConfig{ Name: "foo", Modules: projectconfig.Modules{ "mod1": projectconfig.NewModule(map[string]string{"test": "bar"}, tmpDir, "github.com/fake-org/repo-foo", baseTestFixturesDir, []string{}, []projectconfig.Condition{}), }, } generate.Generate(projectConfig, true) content, err := ioutil.ReadFile(filepath.Join(tmpDir, "file_to_template.txt")) assert.NoError(t, err) expectedContent := `Name is foo Params.test is bar Files.Repository is github.com/fake-org/repo-foo ` assert.Equal(t, string(content), expectedContent) } ================================================ FILE: internal/init/custom-prompts.go ================================================ package init import ( "errors" "fmt" "github.com/commitdev/zero/internal/config/moduleconfig" project "github.com/commitdev/zero/pkg/credentials" ) // CustomPromptHandler handles non-input and enum options prompts // zero-module's parameters allow prompts to specify types of custom actions // this allows non-standard enum / string input to be added, such as AWS profile picker func CustomPromptHandler(promptType string, params map[string]string) error { switch promptType { case "AWSProfilePicker": err := promptAWSProfilePicker(params) if err != nil { params["useExistingAwsProfile"] = "no" return err } default: return errors.New(fmt.Sprintf("Unsupported custom prompt type %s.", promptType)) } return nil } func promptAWSProfilePicker(params map[string]string) error { profiles, err := project.GetAWSProfiles() if err != nil { return err } awsPrompt := PromptHandler{ Parameter: moduleconfig.Parameter{ Field: "aws_profile", Label: "Select AWS Profile", Options: listToPromptOptions(profiles), }, Condition: NoCondition, Validate: NoValidation, } _, value := promptParameter(awsPrompt) credErr := project.FillAWSProfile("", value, params) if credErr != nil { return errors.New("Failed to retrieve profile, falling back to User input") } return nil } ================================================ FILE: internal/init/debug.test ================================================ [File too large to display: 24.3 MB] ================================================ FILE: internal/init/init.go ================================================ package init import ( "fmt" "os" "path" "sync" "github.com/commitdev/zero/internal/config/moduleconfig" "github.com/commitdev/zero/internal/config/projectconfig" "github.com/commitdev/zero/internal/module" "github.com/commitdev/zero/internal/registry" "github.com/commitdev/zero/pkg/util/exit" "github.com/commitdev/zero/pkg/util/flog" "github.com/manifoldco/promptui" "gopkg.in/yaml.v2" ) // Create cloud provider context func Init(outDir, localModulePath, registryFilePath string) *projectconfig.ZeroProjectConfig { projectConfig := defaultProjConfig() projectRootParams := map[string]string{} emptyEnvVarTranslationMap := map[string]string{} promptName := getProjectNamePrompt() promptName.RunPrompt(projectRootParams, emptyEnvVarTranslationMap) projectConfig.Name = projectRootParams[promptName.Field] rootDir := path.Join(outDir, projectConfig.Name) flog.Infof(":tada: Initializing project") err := os.MkdirAll(rootDir, os.ModePerm) if os.IsExist(err) { exit.Fatal("Directory %v already exists! Error: %v", projectConfig.Name, err) } else if err != nil { exit.Fatal("Error creating root: %v ", err) } registry, err := registry.GetRegistry(localModulePath, registryFilePath) if err != nil { exit.Fatal("Error getting registry: %v ", err) } moduleSources := chooseStack(registry) moduleConfigs, mappedSources := loadAllModules(moduleSources) prompts := getProjectPrompts(projectConfig.Name, moduleConfigs) initParams := make(map[string]string) projectConfig.ShouldPushRepositories = true prompts["ShouldPushRepositories"].RunPrompt(initParams, emptyEnvVarTranslationMap) if initParams["ShouldPushRepositories"] == "n" { projectConfig.ShouldPushRepositories = false } // Prompting for push-up stream, then conditionally prompting for github prompts["GithubRootOrg"].RunPrompt(initParams, emptyEnvVarTranslationMap) projectData := promptAllModules(moduleConfigs, &projectConfig) // Map parameter values back to specific modules for moduleName, module := range moduleConfigs { prompts[moduleName].RunPrompt(initParams, emptyEnvVarTranslationMap) repoName := initParams[prompts[moduleName].Field] repoURL := fmt.Sprintf("%s/%s", initParams["GithubRootOrg"], repoName) projectModuleParams := moduleconfig.SummarizeParameters(module, projectData) projectModuleConditions := moduleconfig.SummarizeConditions(module) projectConfig.Modules[moduleName] = projectconfig.NewModule( projectModuleParams, repoName, repoURL, mappedSources[moduleName], module.DependsOn, projectModuleConditions, ) } return &projectConfig } // loadAllModules takes a list of module sources, downloads those modules, and parses their config func loadAllModules(moduleSources []string) (map[string]moduleconfig.ModuleConfig, map[string]string) { modules := make(map[string]moduleconfig.ModuleConfig) mappedSources := make(map[string]string) wg := sync.WaitGroup{} wg.Add(len(moduleSources)) for _, moduleSource := range moduleSources { go module.FetchModule(moduleSource, &wg) } wg.Wait() for _, moduleSource := range moduleSources { mod, err := module.ParseModuleConfig(moduleSource) if err != nil { exit.Fatal("Unable to load module (%s): %v\n", moduleSource, err) } modules[mod.Name] = mod mappedSources[mod.Name] = moduleSource } return modules, mappedSources } // Project name is prompt individually because the rest of the prompts // requires the projectName to populate defaults func getProjectNamePrompt() PromptHandler { return PromptHandler{ Parameter: moduleconfig.Parameter{ Field: "projectName", Label: "Project Name", Info: "This name will be used in most of the resources that are created and should be unique within an AWS account.", Default: "", }, Condition: NoCondition, Validate: ValidateProjectName, } } func getProjectPrompts(projectName string, modules map[string]moduleconfig.ModuleConfig) map[string]PromptHandler { handlers := map[string]PromptHandler{ "ShouldPushRepositories": { Parameter: moduleconfig.Parameter{ Field: "ShouldPushRepositories", Label: "Should the created projects be checked into github automatically?", Info: "If yes, we will automatically create repositories for you in github and check in the generated code.\nIf no, you will need to do these steps manually after running the zero create command.", Options: yaml.MapSlice{ yaml.MapItem{Key: "y", Value: "yes"}, yaml.MapItem{Key: "n", Value: "no"}, }, }, Condition: NoCondition, Validate: SpecificValueValidation("y", "n"), }, "GithubRootOrg": { Parameter: moduleconfig.Parameter{ Field: "GithubRootOrg", Label: "What's the root of the github organization that will own these repositories?", Info: "This should be github.com/", Default: "github.com/", }, Condition: NoCondition, Validate: ValidateOrganizationName, }, } for moduleName, module := range modules { label := fmt.Sprintf("What do you want to call the %s project?", moduleName) handlers[moduleName] = PromptHandler{ Parameter: moduleconfig.Parameter{ Field: moduleName, Label: label, Info: "This will be used as the name of the repository.", Default: module.OutputDir, }, Condition: NoCondition, Validate: NoValidation, } } return handlers } func chooseCloudProvider(projectConfig *projectconfig.ZeroProjectConfig) { // @TODO move options into configs providerPrompt := promptui.Select{ Label: "Select Cloud Provider", Items: []string{"Amazon AWS", "Google GCP", "Microsoft Azure"}, } _, providerResult, err := providerPrompt.Run() if err != nil { exit.Fatal("Prompt failed %v\n", err) } if providerResult != "Amazon AWS" { exit.Fatal("Only the AWS provider is available at this time") } } func chooseStack(reg registry.Registry) []string { showInfoBox("A stack is a group of Zero modules. They will be pulled in from the registry and templated to create the different parts of your project such as infrastructure, backend, frontend, etc.") providerPrompt := promptui.Select{ Label: "Pick the stack you'd like to use", Items: registry.AvailableLabels(reg), } _, providerResult, err := providerPrompt.Run() if err != nil { exit.Fatal("Prompt failed %v\n", err) } return registry.GetModulesByName(reg, providerResult) } func defaultProjConfig() projectconfig.ZeroProjectConfig { return projectconfig.ZeroProjectConfig{ Name: "", Parameters: map[string]string{}, Modules: projectconfig.Modules{}, } } ================================================ FILE: internal/init/prompts.go ================================================ package init import ( "errors" "fmt" "log" "os" "os/exec" "regexp" "strings" tm "github.com/buger/goterm" "github.com/commitdev/zero/internal/config/moduleconfig" "github.com/commitdev/zero/internal/config/projectconfig" "github.com/commitdev/zero/internal/constants" "github.com/commitdev/zero/internal/util" "github.com/commitdev/zero/pkg/util/exit" "github.com/commitdev/zero/pkg/util/flog" "github.com/manifoldco/promptui" "gopkg.in/yaml.v2" ) const cyanArrow = "\033[36m\U000025B6\033[0m" const greenCheckMark = "\033[32m\U00002714\033[0m" const awsPickProfile = "Existing AWS Profiles" const awsManualInputCredentials = "Enter my own AWS credentials" // PromptHandler defines how a user is prompted for a parameter, containing information about the parameter, conditions, and value validation type PromptHandler struct { moduleconfig.Parameter Condition CustomConditionSignature Validate func(string) error } // CredentialPrompts is a list of prompts for sensitive credentials type CredentialPrompts struct { Vendor string Prompts []PromptHandler } // CustomConditionSignature is the function signature of a custom condition. It takes a map of parameters and returns a boolean type CustomConditionSignature func(map[string]string) bool // NoCondition is a no-op condition check function that always returns true func NoCondition(map[string]string) bool { return true } // KeyMatchCondition is a condition that checks if the key matches the value func KeyMatchCondition(key string, value string) CustomConditionSignature { return func(param map[string]string) bool { return param[key] == value } } // NoValidation is a no-op validation function func NoValidation(string) error { return nil } // SpecificValueValidation is a validation function that checks if the value is in the list of options func SpecificValueValidation(values ...string) func(string) error { return func(checkValue string) error { for _, allowedValue := range values { if checkValue == allowedValue { return nil } } return fmt.Errorf("Please choose one of %s", strings.Join(values, "/")) } } // ValidateAKID checks if the input is a valid AWS Access Key ID func ValidateAKID(input string) error { // 20 uppercase alphanumeric characters var awsAccessKeyIDPat = regexp.MustCompile(`^[A-Z0-9]{20}$`) if !awsAccessKeyIDPat.MatchString(input) { return errors.New("Invalid aws_access_key_id") } return nil } // ValidateSAK checks if the input is a valid AWS Secret Access Key func ValidateSAK(input string) error { // 40 base64 characters var awsSecretAccessKeyPat = regexp.MustCompile(`^[A-Za-z0-9/+=]{40}$`) if !awsSecretAccessKeyPat.MatchString(input) { return errors.New("Invalid aws_secret_access_key") } return nil } // ValidateProjectName validates Project Name field user input. func ValidateProjectName(input string) error { // the first 62 char out of base64 and - var pName = regexp.MustCompile(`^[a-zA-Z][A-Za-z0-9-]{1,16}$`) if !pName.MatchString(input) { // error if char len is greater than 16 if len(input) > constants.MaxPnameLength { return errors.New("Invalid, Project Name: (cannot exceed a max length of 16)") } return errors.New("invalid, Project Name: (can only contain alphanumeric chars & '-') and must start with a letter") } return nil } // ValidateOrganizationName validates Organization Name field user input. func ValidateOrganizationName(input string) error { // the first 62 char out of base64 and - var organizationName = strings.TrimLeft(input, "github.com/") var oName = regexp.MustCompile(`^[A-Za-z0-9-]{1,39}$`) // error if char len is greater than 39 if len(organizationName) > constants.MaxOnameLength { return errors.New("Invalid, Organization Name: (cannot exceed a max length of 39)") } if !oName.MatchString(organizationName) { return errors.New("Invalid, Organization Name: (can only contain alphanumeric chars & '-')") } return nil } const infoBoxHeight = 4 var currentLine int = infoBoxHeight // showInfoBox prints a box with some text in it, and the title "Info" func showInfoBox(infoText string) { box := tm.NewBox(100|tm.PCT, 4, 0) fmt.Fprint(box, infoText) tm.Print(tm.MoveTo(box.String(), 1, 1)) tm.MoveCursor(4, 1) tm.Printf("Info") } // RunPrompt obtains the value of PromptHandler depending on the parameter's definition // for the project config, there are multiple ways of obtaining the value // values go into params depending on `Condition` as the highest precedence (Whether it gets this value) // then follows this order to determine HOW it obtains that value // 1. Execute (this could potentially be refactored into type + data) // 2. type: specific ways of obtaining values (in AWS credential case it will set 2 values to the map) // 3. value: directly assigns a value to a parameter // 4. prompt: requires users to select an option OR input a string func (p PromptHandler) RunPrompt(projectParams map[string]string, envVarTranslationMap map[string]string) error { var err error var result string if p.Condition(projectParams) { // If we start printing below the bottom of the terminal screen, go back to the top if currentLine+infoBoxHeight+1 > tm.Height() { tm.Clear() currentLine = infoBoxHeight } // TODO: figure out scope of projectParams per project // potentially dangerous to have cross module env leaking // so if community module has an `execute: twitter tweet $ENV` // it wouldnt leak things the module shouldnt have access to if p.Parameter.Execute != "" { result = executeCmd(p.Parameter.Execute, projectParams, envVarTranslationMap) } else if p.Parameter.Type != "" { err = CustomPromptHandler(p.Parameter.Type, projectParams) } else if p.Parameter.Value != "" { result = p.Parameter.Value } else { showInfoBox(p.Parameter.Info) // Move down to the next line to show the prompt currentLine++ tm.MoveCursor(1, currentLine) tm.Flush() // Call it every time at the end of rendering err, result = promptParameter(p) } if err != nil { return err } // Append the result to parameter map projectParams[p.Field] = sanitizeParameterValue(result) } else { elseValue := moduleconfig.GetFirstConditionElseValue(p.Parameter) if elseValue != "" { flog.Debugf("Skipping prompt(%s) due to condition failed but assigning default value \"%s\"", p.Field, elseValue) projectParams[p.Field] = elseValue } else { flog.Debugf("Skipping prompt(%s) due to condition failed", p.Field) } } return nil } func promptParameter(prompt PromptHandler) (error, string) { param := prompt.Parameter label := param.Label if param.Label == "" { label = param.Field } defaultValue := param.Default var err error var result string if len(param.Options) > 0 { var selectedIndex int // Scope of selected does not have the label data, so we need a dynamic // template with string format to put in the label in `selected` optionTemplate := &promptui.SelectTemplates{ Label: `{{ . }}`, Active: fmt.Sprintf("%s {{ .Value | cyan }}", cyanArrow), Inactive: " {{ .Value }}", Selected: fmt.Sprintf("%s %s: {{ .Value }}", greenCheckMark, label), } prompt := promptui.Select{ Label: label, Items: param.Options, Templates: optionTemplate, } selectedIndex, _, err = prompt.Run() result = param.Options[selectedIndex].Key.(string) } else { prompt := promptui.Prompt{ Label: label, Default: defaultValue, AllowEdit: true, Validate: prompt.Validate, } result, err = prompt.Run() } if err != nil { return err, "" } return nil, result } func executeCmd(command string, envVars map[string]string, envVarTranslationMap map[string]string) string { cmd := exec.Command("bash", "-c", command) // Might need to pass down module's translation map as well, // currently only works in `zero apply` cmd.Env = util.AppendProjectEnvToCmdEnv(envVars, os.Environ(), envVarTranslationMap) out, err := cmd.Output() flog.Debugf("Running command: %s", command) if err != nil { log.Fatalf("Failed to execute %v\n", err) } flog.Debugf("Command result: %s", string(out)) return string(out) } // aws cli prints output with linebreak in them func sanitizeParameterValue(str string) string { re := regexp.MustCompile("\\n") return re.ReplaceAllString(str, "") } // PromptModuleParams renders series of prompt UI based on the config func PromptModuleParams(moduleConfig moduleconfig.ModuleConfig, parameters map[string]string) (map[string]string, error) { envVarTranslationMap := moduleConfig.GetParamEnvVarTranslationMap() for _, parameter := range moduleConfig.Parameters { // deduplicate fields already prompted and received if _, isAlreadySet := parameters[parameter.Field]; isAlreadySet { continue } var validateFunc func(input string) error = nil // type:regex field validation for zero-module.yaml if parameter.FieldValidation.Type == constants.RegexValidation { validateFunc = func(input string) error { var regexRule = regexp.MustCompile(parameter.FieldValidation.Value) if !regexRule.MatchString(input) { return errors.New(parameter.FieldValidation.ErrorMessage) } return nil } } // TODO: type:fuction field validation for zero-module.yaml promptHandler := PromptHandler{ Parameter: parameter, Condition: paramConditionsMapper(parameter.Conditions), Validate: validateFunc, } // merging the context of param and credentals // this treats credentialEnvs as throwaway, parameters is shared between modules // so credentials should not be in parameters as it gets returned to parent // for k, v := range parameters { // credentialEnvs[k] = v // } err := promptHandler.RunPrompt(parameters, envVarTranslationMap) if err != nil { return parameters, err } } flog.Debugf("Module %s prompt: \n %#v", moduleConfig.Name, parameters) return parameters, nil } // promptAllModules takes a map of all the modules and prompts the user for values for all the parameters // Important: This is done here because in this step we share the parameter across modules, // meaning if module A and B both asks for region, it will reuse the response for both (and is deduped during runtime) func promptAllModules(modules map[string]moduleconfig.ModuleConfig, projectConfig *projectconfig.ZeroProjectConfig) map[string]string { parameterValues := availableProjectContext(projectConfig) for _, config := range modules { var err error parameterValues, err = PromptModuleParams(config, parameterValues) if err != nil { exit.Fatal("Exiting prompt(%s): %v\n", config.Name, err) } } return parameterValues } // availableProjectContext declares a list of variables usable in modules parameter prompt's execute step func availableProjectContext(projectConfig *projectconfig.ZeroProjectConfig) map[string]string { return map[string]string{ "projectName": projectConfig.Name, } } // paramConditionsMapper returns a condition checking function that checks if all the conditions are met func paramConditionsMapper(conditions []moduleconfig.Condition) CustomConditionSignature { if len(conditions) == 0 { return NoCondition } else { return func(params map[string]string) bool { // Prompts must pass every condition to proceed for i := 0; i < len(conditions); i++ { cond := conditions[i] if !conditionHandler(cond)(params) { flog.Debugf("Did not meet condition %v, expected %v to be %v", cond.Action, cond.MatchField, cond.WhenValue) return false } } return true } } } // conditionHandler is a helper that accepts condition config and returns a function that checks if the condition is met func conditionHandler(cond moduleconfig.Condition) CustomConditionSignature { if cond.Action == "KeyMatchCondition" { return KeyMatchCondition(cond.MatchField, cond.WhenValue) } else { flog.Errorf("Unsupported condition") return nil } } func appendToSet(set []string, toAppend []string) []string { for _, appendee := range toAppend { if !util.ItemInSlice(set, appendee) { set = append(set, appendee) } } return set } func listToPromptOptions(list []string) yaml.MapSlice { mapSlice := make(yaml.MapSlice, len(list)) for i := 0; i < len(list); i++ { mapSlice[i] = yaml.MapItem{ Key: list[i], Value: list[i], } } return mapSlice } ================================================ FILE: internal/init/prompts_test.go ================================================ package init_test import ( "testing" "github.com/commitdev/zero/internal/config/moduleconfig" // init is a reserved word initPrompts "github.com/commitdev/zero/internal/init" "github.com/stretchr/testify/assert" ) func TestGetParam(t *testing.T) { envVarTranslationMap := map[string]string{} projectParams := map[string]string{} t.Run("Should execute params without prompt", func(t *testing.T) { param := moduleconfig.Parameter{ Field: "account-id", Execute: "echo \"my-account-id\"", } prompt := initPrompts.PromptHandler{ param, initPrompts.NoCondition, initPrompts.NoValidation, } prompt.RunPrompt(projectParams, envVarTranslationMap) assert.Equal(t, "my-account-id", projectParams[param.Field]) }) t.Run("executes with project context", func(t *testing.T) { param := moduleconfig.Parameter{ Field: "myEnv", Execute: "echo $INJECTEDENV", } prompt := initPrompts.PromptHandler{ param, initPrompts.NoCondition, initPrompts.NoValidation, } projectParams := map[string]string{"INJECTEDENV": "SOME_ENV_VAR_VALUE"} prompt.RunPrompt(projectParams, envVarTranslationMap) assert.Equal(t, "SOME_ENV_VAR_VALUE", projectParams[param.Field]) }) t.Run("Should return static value", func(t *testing.T) { param := moduleconfig.Parameter{ Field: "placeholder", Value: "lorem-ipsum", } prompt := initPrompts.PromptHandler{ param, initPrompts.NoCondition, initPrompts.NoValidation, } prompt.RunPrompt(projectParams, envVarTranslationMap) assert.Equal(t, "lorem-ipsum", projectParams[param.Field]) }) t.Run("Prompt value to retain existing params", func(t *testing.T) { projectParams = map[string]string{ "existing_value": "foo", } param := moduleconfig.Parameter{ Field: "new_value", Value: "bar", } prompt := initPrompts.PromptHandler{ param, initPrompts.NoCondition, initPrompts.NoValidation, } prompt.RunPrompt(projectParams, envVarTranslationMap) assert.Equal(t, "foo", projectParams["existing_value"]) assert.Equal(t, "bar", projectParams[param.Field]) }) t.Run("Prompt to apply in order and allow EnvVarMapping", func(t *testing.T) { projectParams = map[string]string{} params := []moduleconfig.Parameter{ { Field: "param1", Value: "foo", EnvVarName: "envvar1", }, { Field: "param2", Execute: "echo $envvar1 bar", EnvVarName: "envvar2", }, { Field: "param3", Execute: "echo $envvar2 baz", }, } module := moduleconfig.ModuleConfig{Parameters: params} projectParams, _ = initPrompts.PromptModuleParams(module, projectParams) assert.Equal(t, "foo", projectParams["param1"]) assert.Equal(t, "foo bar", projectParams["param2"], "should reference param1 via env-var") assert.Equal(t, "foo bar baz", projectParams["param3"], "should reference param2 via env-var") }) t.Run("Prompt conditions", func(t *testing.T) { projectParams = map[string]string{} params := []moduleconfig.Parameter{ { Field: "param1", Value: "pass", }, { Field: "passing_condition", Value: "pass", Conditions: []moduleconfig.Condition{ { Action: "KeyMatchCondition", MatchField: "param1", WhenValue: "pass", }, }, }, { Field: "failing_condition", Value: "pass", Conditions: []moduleconfig.Condition{ { Action: "KeyMatchCondition", MatchField: "param1", WhenValue: "not pass", }, }, }, { Field: "multiple_condition", Value: "pass", Conditions: []moduleconfig.Condition{ { Action: "KeyMatchCondition", MatchField: "param1", WhenValue: "pass", }, { Action: "KeyMatchCondition", MatchField: "passing_condition", WhenValue: "pass", }, }, }, { Field: "condition_with_default", Value: "pass", Conditions: []moduleconfig.Condition{ { Action: "KeyMatchCondition", MatchField: "param1", WhenValue: "not pass", ElseValue: "itsadefault", }, }, }, } module := moduleconfig.ModuleConfig{Parameters: params} projectParams, _ = initPrompts.PromptModuleParams(module, projectParams) assert.Equal(t, "pass", projectParams["param1"], "Value just hardcoded") assert.Equal(t, "pass", projectParams["passing_condition"], "Expected to pass condition and set value") assert.NotContains(t, projectParams, "failing_condition", "Expected to fail condition and not set value") assert.Equal(t, "pass", projectParams["multiple_condition"], "Expected to pass multiple condition and set value") assert.Equal(t, "itsadefault", projectParams["condition_with_default"], "Expected to set a default value for a condition that failed") }) t.Run("Should return error upon unsupported custom prompt type", func(t *testing.T) { projectParams = map[string]string{} params := []moduleconfig.Parameter{ { Field: "param1", Type: "random-type", }, } module := moduleconfig.ModuleConfig{Parameters: params} _, err := initPrompts.PromptModuleParams(module, projectParams) assert.Equal(t, "Unsupported custom prompt type random-type.", err.Error()) }) } func TestValidateProjectNam(t *testing.T) { t.Run("Should return error upon invalid project name", func(t *testing.T) { err := initPrompts.ValidateProjectName("0invalid") assert.Error(t, err, "Project name should not start with a number") }) t.Run("Should return error upon invalid project name length", func(t *testing.T) { err := initPrompts.ValidateProjectName("invalid name with more than 30 characters") assert.Error(t, err, "Project name should not be longer than 30 characters") }) t.Run("Should return nil upon valid project name", func(t *testing.T) { err := initPrompts.ValidateProjectName("valid-name") assert.Nil(t, err) }) } ================================================ FILE: internal/module/module.go ================================================ package module import ( "crypto/md5" "encoding/base64" "io" "log" "path" "regexp" "sync" "github.com/commitdev/zero/internal/config/moduleconfig" "github.com/commitdev/zero/internal/constants" "github.com/commitdev/zero/internal/util" "github.com/commitdev/zero/pkg/util/exit" "github.com/commitdev/zero/pkg/util/flog" "github.com/hashicorp/go-getter" ) // FetchModule downloads the remote module source if necessary. Meant to be run in a goroutine. func FetchModule(source string, wg *sync.WaitGroup) { defer wg.Done() localPath := GetSourceDir(source) if !IsLocal(source) { flog.Debugf("Downloading module: %s to %s", source, localPath) err := getter.Get(localPath, source) if err != nil { exit.Fatal("Failed to fetch remote module from %s: %v\n", source, err) } } return } // ParseModuleConfig loads the local config file for a module and parses the yaml func ParseModuleConfig(source string) (moduleconfig.ModuleConfig, error) { localPath := GetSourceDir(source) config := moduleconfig.ModuleConfig{} configPath := path.Join(localPath, constants.ZeroModuleYml) config, err := moduleconfig.LoadModuleConfig(configPath) return config, err } // GetSourcePath gets a unique local source directory name. For local modules, it use the local directory func GetSourceDir(source string) string { if !IsLocal(source) { h := md5.New() io.WriteString(h, source) source = base64.StdEncoding.EncodeToString(h.Sum(nil)) return path.Join(constants.TemplatesDir, source) } else { return source } } // IsLocal uses the go-getter FileDetector to check if source is a file func IsLocal(source string) bool { pwd := util.GetCwd() // ref: https://github.com/hashicorp/go-getter/blob/master/detect_test.go out, err := getter.Detect(source, pwd, getter.Detectors) match, err := regexp.MatchString("^file://.*", out) if err != nil { log.Panicf("invalid source format %s", err) } return match } func withPWD(pwd string) func(*getter.Client) error { return func(c *getter.Client) error { c.Pwd = pwd return nil } } ================================================ FILE: internal/module/module_internal_test.go ================================================ package module import ( "testing" ) func TestIsLocal(t *testing.T) { source := "./tests/test_data/modules" res := IsLocal(source) if !res { t.Errorf("Error, source %s SHOULD BE determined as local", source) } source = "https://github.com/commitdev/my-repo" res = IsLocal(source) if res { t.Errorf("Error, source %s SHOULD NOT BE determined as local", source) } } ================================================ FILE: internal/module/module_test.go ================================================ package module_test import ( "errors" "fmt" "testing" "github.com/commitdev/zero/internal/config/moduleconfig" "github.com/stretchr/testify/assert" "github.com/commitdev/zero/internal/module" "github.com/commitdev/zero/version" ) func TestGetSourceDir(t *testing.T) { source := "tests/test_data/modules" relativeSource := source dir := module.GetSourceDir(source) t.Log("dir", dir) if dir != relativeSource { t.Errorf("Error, local sources should not be changed: %s", source) } source = "github.com/commitdev/my-repo" dir = module.GetSourceDir(source) if dir == relativeSource { t.Errorf("Error, remote sources should be converted to a local dir: %s", source) } } func TestParseModuleConfig(t *testing.T) { testModuleSource := "../../tests/test_data/modules/ci" var mod moduleconfig.ModuleConfig t.Run("Loading module from source", func(t *testing.T) { mod, _ = module.ParseModuleConfig(testModuleSource) moduleconfig.ValidateZeroVersion(mod) assert.Equal(t, "CI templates", mod.Name) }) t.Run("Parameters are loaded", func(t *testing.T) { param, err := findParameter(mod.Parameters, "platform") if err != nil { panic(err) } assert.Equal(t, "platform", param.Field) assert.Equal(t, "CI Platform", param.Label) }) t.Run("OmitFromProjectFile default", func(t *testing.T) { param, err := findParameter(mod.Parameters, "platform") if err != nil { panic(err) } assert.Equal(t, false, param.OmitFromProjectFile, "OmitFromProjectFile should default to false") useCredsParam, useCredsErr := findParameter(mod.Parameters, "useExistingAwsProfile") if useCredsErr != nil { panic(useCredsErr) } assert.Equal(t, true, useCredsParam.OmitFromProjectFile, "OmitFromProjectFile should be read from file") }) t.Run("Parsing Conditions and Typed prompts from config", func(t *testing.T) { param, err := findParameter(mod.Parameters, "profilePicker") if err != nil { panic(err) } assert.Equal(t, "AWSProfilePicker", param.Type) assert.Equal(t, "KeyMatchCondition", param.Conditions[0].Action) assert.Equal(t, "useExistingAwsProfile", param.Conditions[0].MatchField) assert.Equal(t, "yes", param.Conditions[0].WhenValue) }) t.Run("parsing envVarName from module config", func(t *testing.T) { param, err := findParameter(mod.Parameters, "accessKeyId") if err != nil { panic(err) } assert.Equal(t, "AWS_ACCESS_KEY_ID", param.EnvVarName) }) t.Run("TemplateConfig is unmarshaled", func(t *testing.T) { mod, _ = module.ParseModuleConfig(testModuleSource) assert.Equal(t, ".circleci", mod.TemplateConfig.OutputDir) assert.Equal(t, "templates", mod.TemplateConfig.InputDir) assert.Equal(t, []string{"<%", "%>"}, mod.TemplateConfig.Delimiters) }) t.Run("Parsing commands", func(t *testing.T) { checkCommand := mod.Commands.Check assert.Equal(t, "ls", checkCommand) }) t.Run("Parsing zero version constraints", func(t *testing.T) { moduleConstraints := mod.ZeroVersion.Constraints.String() assert.Equal(t, ">= 3.0.0, < 4.0.0", moduleConstraints) }) t.Run("Should Fail against old zero version", func(t *testing.T) { moduleConstraints := mod.ZeroVersion.Constraints.String() // Mocking zero's version, testing against ">= 3.0.0, <= 4.0.0" originalVersion := version.AppVersion version.AppVersion = "2.0.0" defer func() { version.AppVersion = originalVersion }() // end of mock isValid := moduleconfig.ValidateZeroVersion(mod) assert.Equal(t, false, isValid, fmt.Sprintf("Version should satisfy %s", moduleConstraints)) }) t.Run("Should Fail against too new zero version", func(t *testing.T) { moduleConstraints := mod.ZeroVersion.Constraints.String() // Mocking zero's version, testing against ">= 3.0.0, <= 4.0.0" originalVersion := version.AppVersion version.AppVersion = "4.0.0" defer func() { version.AppVersion = originalVersion }() // end of mock isValid := moduleconfig.ValidateZeroVersion(mod) assert.Equal(t, false, isValid, fmt.Sprintf("Version should satisfy %s", moduleConstraints)) }) t.Run("Should validate against valid versions", func(t *testing.T) { moduleConstraints := mod.ZeroVersion.Constraints.String() // Mocking zero's version, testing against ">= 3.0.0, <= 4.0.0" const newZeroVersion = "3.0.5" originalVersion := version.AppVersion version.AppVersion = newZeroVersion defer func() { version.AppVersion = originalVersion }() // end of mock isValid := moduleconfig.ValidateZeroVersion(mod) assert.Equal(t, true, isValid, fmt.Sprintf("Version should satisfy %s", moduleConstraints)) }) t.Run("default to SNAPSHOT version passes tests", func(t *testing.T) { assert.Equal(t, "SNAPSHOT", version.AppVersion) isValid := moduleconfig.ValidateZeroVersion(mod) assert.Equal(t, true, isValid, "default test run should pass version constraint") }) } func TestModuleWithNoVersionConstraint(t *testing.T) { testModuleSource := "../../tests/test_data/modules/no-version-constraint" var mod moduleconfig.ModuleConfig var err error t.Run("Parsing Module with no version constraint", func(t *testing.T) { mod, err = module.ParseModuleConfig(testModuleSource) assert.Equal(t, "", mod.ZeroVersion.String()) assert.Nil(t, err) }) t.Run("Should pass Validation if constraint not specified", func(t *testing.T) { isValid := moduleconfig.ValidateZeroVersion(mod) assert.Equal(t, true, isValid, "Module with no constraint should pass version validation") }) } func findParameter(params []moduleconfig.Parameter, field string) (moduleconfig.Parameter, error) { for _, v := range params { if v.Field == field { return v, nil } } return moduleconfig.Parameter{}, errors.New("parameter not found") } ================================================ FILE: internal/registry/registry.go ================================================ package registry import ( "io/ioutil" "github.com/commitdev/zero/internal/constants" "github.com/hashicorp/go-getter" yaml "gopkg.in/yaml.v2" ) type Registry []Stack type Stack struct { Name string `yaml:"name"` ModuleSources []string `yaml:"moduleSources"` } func GetRegistry(localModulePath, registryFilePath string) (Registry, error) { registry := Registry{} err := getter.GetFile(constants.TmpRegistryYml, registryFilePath) if err != nil { return nil, err } data, err := ioutil.ReadFile(constants.TmpRegistryYml) if err != nil { return nil, err } err = yaml.Unmarshal(data, ®istry) if err != nil { return nil, err } for i := 0; i < len(registry); i++ { for j := 0; j < len(registry[i].ModuleSources); j++ { registry[i].ModuleSources[j] = localModulePath + registry[i].ModuleSources[j] } } return registry, nil } func GetModulesByName(registry Registry, name string) []string { for _, v := range registry { if v.Name == name { return v.ModuleSources } } return []string{} } func AvailableLabels(registry Registry) []string { labels := make([]string, len(registry)) i := 0 for _, stack := range registry { labels[i] = stack.Name i++ } return labels } ================================================ FILE: internal/registry/registry_test.go ================================================ package registry_test import ( "testing" "github.com/commitdev/zero/internal/registry" "github.com/stretchr/testify/assert" ) func TestAvailableLabels(t *testing.T) { reg := testRegistry() t.Run("should be same order as declared", func(t *testing.T) { labels := registry.AvailableLabels(reg) assert.Equal(t, labels, []string{ "EKS + Go + React + Gatsby", "foo", "bar", "lorem", "ipsum", "Custom", }) }) } func TestGetModulesByName(t *testing.T) { reg := testRegistry() t.Run("should return modules of specified stack", func(t *testing.T) { assert.Equal(t, registry.GetModulesByName(reg, "EKS + Go + React + Gatsby"), []string{"module-source 1", "module-source 2"}) assert.Equal(t, registry.GetModulesByName(reg, "lorem"), []string{"module-source 5"}) assert.Equal(t, registry.GetModulesByName(reg, "ipsum"), []string{"module-source 6"}) assert.Equal(t, registry.GetModulesByName(reg, "Custom"), []string{"module-source 7"}) }) } func testRegistry() registry.Registry { return registry.Registry{ {"EKS + Go + React + Gatsby", []string{"module-source 1", "module-source 2"}}, {"foo", []string{"module-source 3"}}, {"bar", []string{"module-source 4"}}, {"lorem", []string{"module-source 5"}}, {"ipsum", []string{"module-source 6"}}, {"Custom", []string{"module-source 7"}}, } } ================================================ FILE: internal/util/util.go ================================================ package util // @TODO split up and move into /pkg directory import ( "bytes" "errors" "fmt" "io" "log" "os" "os/exec" "path" "path/filepath" "reflect" "regexp" "strconv" "strings" "syscall" "text/template" "github.com/google/uuid" ) func CreateDirIfDoesNotExist(path string) error { if _, err := os.Stat(path); os.IsNotExist(err) { err = os.MkdirAll(path, os.ModePerm) return err } return nil } func CleanGoIdentifier(identifier string) string { return strings.ReplaceAll(identifier, "-", "") } // @TODO how can we make these type of helpers extensible? var FuncMap = template.FuncMap{ "Title": strings.Title, "ToLower": strings.ToLower, "CleanGoIdentifier": CleanGoIdentifier, "GenerateUUID": uuid.New, } func GetCwd() string { dir, err := os.Getwd() if err != nil { log.Fatalf("Getting working directory failed: %v\n", err) panic(err) } return dir } func ExecuteCommand(cmd *exec.Cmd, pathPrefix string, envars []string, shouldPipeStdErr bool) error { cmd.Dir = pathPrefix if !filepath.IsAbs(pathPrefix) { dir := GetCwd() cmd.Dir = path.Join(dir, pathPrefix) } stdoutPipe, _ := cmd.StdoutPipe() stderrPipe, _ := cmd.StderrPipe() var errStdout, errStderr error errContent := new(bytes.Buffer) cmd.Env = os.Environ() if envars != nil { cmd.Env = append(os.Environ(), envars...) } err := cmd.Start() if err != nil { return err } go func() { _, errStdout = io.Copy(os.Stdout, stdoutPipe) }() go func() { stderrStreams := []io.Writer{errContent} if shouldPipeStdErr { stderrStreams = append(stderrStreams, os.Stderr) } stdErr := io.MultiWriter(stderrStreams...) _, errStderr = io.Copy(stdErr, stderrPipe) }() err = cmd.Wait() if err != nil { // Detecting and returning the makefile error to cmd // Passing alone makefile stderr as error message, otherwise it just says "exit status 2" if exitError, ok := err.(*exec.ExitError); ok { ws := exitError.Sys().(syscall.WaitStatus) exitCode := ws.ExitStatus() if exitCode == 2 { stderrOut := errContent.String() isMissingTarget, _ := regexp.MatchString("No rule to make target", stderrOut) if isMissingTarget { return errors.New("Module missing mandatory targets, this is likely an issue with the module itself.") } return errors.New(stderrOut) } } return errors.New(errContent.String()) } if errStdout != nil { log.Printf("Failed to capture stdout: %v\n", errStdout) } if errStderr != nil { log.Printf("Failed to capture stderr: %v\n", errStderr) } return nil } // ExecuteCommandOutput runs the command and returns its // combined standard output and standard error. func ExecuteCommandOutput(cmd *exec.Cmd, pathPrefix string, envars []string) string { cmd.Dir = pathPrefix if !filepath.IsAbs(pathPrefix) { dir := GetCwd() cmd.Dir = path.Join(dir, pathPrefix) } cmd.Env = os.Environ() if envars != nil { cmd.Env = append(os.Environ(), envars...) } out, err := cmd.CombinedOutput() if err != nil { log.Fatalf("Executing command with output failed: (%v) %s\n", err, out) } return string(out) } // AppendProjectEnvToCmdEnv converts a key-value pair map into a slice of `key=value`s // allow module definition to use an alternative env-var-name than field while apply func AppendProjectEnvToCmdEnv(envMap map[string]string, envList []string, translationMap map[string]string) []string { for key, val := range envMap { if val != "" { // overwrite key if exist in translation map if val, ok := translationMap[key]; ok { key = val } envList = append(envList, fmt.Sprintf("%s=%s", key, val)) } } return envList } // IndentString will Add x space char padding at the beginging of each line. func IndentString(content string, spaces int) string { var result string subStr := strings.Split(content, "\n") for _, s := range subStr { result += fmt.Sprintf("%"+strconv.Itoa(spaces)+"s%s\n", "", s) } return result } func ItemInSlice(slice []string, target string) bool { for _, item := range slice { if item == target { return true } } return false } // ReflectStructValueIntoMap receives a resource of struct type as // type AWSCreds struct{ // AccessKeyID string `yaml:"accessKeyId,omitempty"` // SecretAccessKey string `yaml:"secretAccessKey,omitempty"` // }{ // AccessKeyID: "FOO", // SecretAccessKey: "BAR", // } // It will base on the tag, fill in the value to supplied map[string]string func ReflectStructValueIntoMap(resource interface{}, tagName string, paramsToFill map[string]string) { t := reflect.ValueOf(resource) for i := 0; i < t.NumField(); i++ { childStruct := t.Type().Field(i) childValue := t.Field(i) if childValue.Kind().String() != "string" { continue } tag, _ := parseTag(childStruct.Tag.Get(tagName)) paramsToFill[tag] = childValue.String() } } func parseTag(tag string) (string, string) { if idx := strings.Index(tag, ","); idx != -1 { return tag[:idx], tag[idx+1:] } return tag, "" } ================================================ FILE: internal/vcs/create-git-repos.go ================================================ package vcs import ( "context" "fmt" "os/exec" "strings" "github.com/commitdev/zero/pkg/util/flog" "github.com/machinebox/graphql" ) // InitializeRepository Creates and initializes a github repository for the given url // repositoryUrl is expected to be in the format "github.com/{ownerName}/{repositoryName}" func InitializeRepository(repositoryUrl string, githubApiKey string) { var err error ownerName, repositoryName, err := parseRepositoryUrl(repositoryUrl) if err != nil { fmt.Printf("error creating repository: %s\n", err.Error()) return } flog.Debugf("Initialized repo: %s/%s", ownerName, repositoryName) isOrgOwned, ownerId, err := isOrganizationOwned(ownerName, githubApiKey) if err != nil { fmt.Printf("error creating repository: %s\n", err.Error()) return } if isOrgOwned { r := graphql.NewRequest(createOrganizationRepositoryMutation) r.Var("repoName", repositoryName) r.Var("repoDescription", fmt.Sprintf("Repository for %s", repositoryName)) r.Var("ownerId", ownerId) r.Header.Add("Authorization", fmt.Sprintf("Bearer %s", githubApiKey)) if err := createRepository(r); err != nil { fmt.Printf("error creating repository: %s\n", err.Error()) return } } else { r := graphql.NewRequest(createPersonalRepositoryMutation) r.Var("repoName", repositoryName) r.Var("repoDescription", fmt.Sprintf("Repository for %s", repositoryName)) r.Header.Add("Authorization", fmt.Sprintf("Bearer %s", githubApiKey)) if err := createRepository(r); err != nil { fmt.Printf("error creating repository: %s\n", err.Error()) return } } if err := doInitialCommit(ownerName, repositoryName); err != nil { fmt.Printf("error initializing repository: %s\n", err.Error()) return } flog.Infof(":check_mark_button: Repository created: %s", repositoryUrl) } // parseRepositoryUrl extracts the owner name and repository name from a repository url. // repositoryUrl is expected to be in the format "github.com/{ownerName}/{repositoryName}" func parseRepositoryUrl(repositoryUrl string) (string, string, error) { if len(repositoryUrl) == 0 { return "", "", fmt.Errorf("invalid repository url. expected format \"github.com/{ownerName}/{repositoryName}\"") } segments := strings.Split(repositoryUrl, "/") if len(segments) != 3 { return "", "", fmt.Errorf("invalid repository url. expected format \"github.com/{ownerName}/{repositoryName}\"") } ownerName := segments[1] repositoryName := segments[2] return ownerName, repositoryName, nil } const createPersonalRepositoryMutation = `mutation ($repoName: String!, $repoDescription: String!) { createRepository( input: { name:$repoName, visibility: PRIVATE, description: $repoDescription }) { clientMutationId } }` const createOrganizationRepositoryMutation = `mutation ($repoName: String!, $repoDescription: String!, $ownerId: ID) { createRepository( input: { name:$repoName, visibility: PRIVATE, description: $repoDescription ownerId: $ownerId }) { clientMutationId } }` // createRepository will create a new repository in github func createRepository(request *graphql.Request) error { c := graphql.NewClient("https://api.github.com/graphql") ctx := context.Background() if err := c.Run(ctx, request, nil); err != nil { return err } return nil } const getOrganizationQuery = `query ($ownerName: String!) { organization(login: $ownerName) { id } }` type organizationQueryResponse struct { Organization struct { Id string } } // isOrganizationOwned will determine if ownerName is an organization. // If ownerName is an organization it's id will be returned. func isOrganizationOwned(ownerName string, githubApiKey string) (bool, string, error) { oRequest := graphql.NewRequest(getOrganizationQuery) oRequest.Var("ownerName", ownerName) oRequest.Header.Add("Authorization", fmt.Sprintf("Bearer %s", githubApiKey)) var oResponse organizationQueryResponse c := graphql.NewClient("https://api.github.com/graphql") ctx := context.Background() if err := c.Run(ctx, oRequest, &oResponse); err != nil { notAnOrgMessage := fmt.Sprintf("graphql: Could not resolve to an Organization with the login of '%s'.", ownerName) if err.Error() == notAnOrgMessage { return false, "", nil } return false, "", err } organizationId := oResponse.Organization.Id return true, organizationId, nil } type initialCommands struct { description string command string args []string } // getInitDefaultBranch return init.defaultBranch value in git config. // If init.defaultBranch isn't set, getInitDefaultBranch return 'main'. func getInitDefaultBranch() string { cmd := exec.Command("git", "config", "--get", "init.defaultBranch") output, err := cmd.CombinedOutput() if err != nil { return "main" } branchName := strings.TrimSuffix(string(output), "\n") return branchName } // doInitialCommit runs the git commands that initialize and do the first commit to a repository. func doInitialCommit(ownerName string, repositoryName string) error { remoteOrigin := fmt.Sprintf("git@github.com:%s/%s.git", ownerName, repositoryName) initDefaultBranch := getInitDefaultBranch() commands := []initialCommands{ { description: "git init", command: "git", args: []string{"init"}, }, { description: "git add .", command: "git", args: []string{"add", "."}, }, { description: "git commit -m \"initial commit by zero\"", command: "git", args: []string{"commit", "-m", "initial commit by zero"}, }, { description: fmt.Sprintf("git remote add origin %s", remoteOrigin), command: "git", args: []string{"remote", "add", "origin", remoteOrigin}, }, { description: fmt.Sprintf("git push -u origin %s", initDefaultBranch), command: "git", args: []string{"push", "-u", "origin", initDefaultBranch}, }, } for _, command := range commands { // TODO: Debug-level logging? // fmt.Printf(">> %s\n", command.description) cmd := exec.Command(command.command, command.args...) cmd.Dir = "./" + repositoryName flog.Debugf("Running (%s) command in %s, %#v", command.command, cmd.Dir, command.args) _, err := cmd.CombinedOutput() if err != nil { fmt.Printf("ERROR: failed to run %s: %s\n", command.description, err.Error()) // this is a partial failure. some commands may have exec'ed successfully. break } //else { // TODO: Debug-level logging? // response := string(out) // if len(response) > 0 { // fmt.Println(response) // } // } } return nil } ================================================ FILE: main.go ================================================ package main import ( "github.com/commitdev/zero/cmd" ) func main() { cmd.Execute() } ================================================ FILE: pkg/credentials/credentials.go ================================================ package credentials import ( "io/ioutil" "log" "os/user" "path/filepath" "regexp" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/commitdev/zero/internal/util" ) type AWSResourceConfig struct { AccessKeyID string `key:"accessKeyId"` SecretAccessKey string `key:"secretAccessKey"` } func awsCredsPath() string { usr, err := user.Current() if err != nil { log.Fatal(err) } return filepath.Join(usr.HomeDir, ".aws/credentials") } func fetchAWSConfig(awsPath string, profileName string) (error, AWSResourceConfig) { awsCreds, err := credentials.NewSharedCredentials(awsPath, profileName).Get() if err != nil { return err, AWSResourceConfig{} } return nil, AWSResourceConfig{ AccessKeyID: awsCreds.AccessKeyID, SecretAccessKey: awsCreds.SecretAccessKey, } } // FillAWSProfile receives the AWS profile name, then parses // the accessKeyId / secretAccessKey values into a map func FillAWSProfile(pathToCredentialsFile string, profileName string, paramsToFill map[string]string) error { if pathToCredentialsFile == "" { pathToCredentialsFile = awsCredsPath() } err, awsCreds := fetchAWSConfig(pathToCredentialsFile, profileName) if err != nil { return err } util.ReflectStructValueIntoMap(awsCreds, "key", paramsToFill) return nil } // GetAWSProfiles returns a list of AWS forprofiles set up on the user's sytem func GetAWSProfiles() ([]string, error) { usr, err := user.Current() if err != nil { return nil, err } // Load the credentials file to look for profiles credsFile := filepath.Join(usr.HomeDir, ".aws/credentials") creds, err := ioutil.ReadFile(credsFile) if err != nil { return nil, err } // Get all profiles re := regexp.MustCompile(`\[(.*)\]`) profileMatches := re.FindAllStringSubmatch(string(creds), -1) profiles := make([]string, len(profileMatches)) for i, p := range profileMatches { profiles[i] = p[1] } return profiles, nil } ================================================ FILE: pkg/credentials/credentials_test.go ================================================ package credentials_test import ( "testing" "github.com/commitdev/zero/pkg/credentials" "github.com/stretchr/testify/assert" ) func TestFillAWSProfileCredentials(t *testing.T) { mockAwsCredentialFilePath := "../../tests/test_data/aws/mock_credentials.yml" t.Run("fills project credentials", func(t *testing.T) { params := map[string]string{} err := credentials.FillAWSProfile(mockAwsCredentialFilePath, "default", params) if err != nil { panic(err) } assert.Equal(t, "MOCK1_ACCESS_KEY", params["accessKeyId"]) assert.Equal(t, "MOCK1_SECRET_ACCESS_KEY", params["secretAccessKey"]) }) t.Run("supports non-default profiles", func(t *testing.T) { params := map[string]string{} err := credentials.FillAWSProfile(mockAwsCredentialFilePath, "foobar", params) if err != nil { panic(err) } assert.Equal(t, "MOCK2_ACCESS_KEY", params["accessKeyId"]) assert.Equal(t, "MOCK2_SECRET_ACCESS_KEY", params["secretAccessKey"]) }) } ================================================ FILE: pkg/util/exit/exit.go ================================================ package exit import ( "os" "github.com/commitdev/zero/pkg/util/flog" ) const ( // CodeOK indicates successful execution. CodeOK = 0 // CodeError indicates erroneous execution. CodeError = 1 // CodeFatal indicates erroneous use by user. CodeFatal = 2 ) // Fatal terminates execution using fatal exit code. func Fatal(format string, a ...interface{}) { flog.Errorf(format, a...) os.Exit(CodeFatal) } // Error terminates execution using unsuccessful execution exit code. func Error(format string, a ...interface{}) { flog.Errorf(format, a...) os.Exit(CodeError) } // OK terminates execution successfully. func OK(format string, a ...interface{}) { flog.Infof(format, a) os.Exit(CodeOK) } ================================================ FILE: pkg/util/flog/log.go ================================================ package flog import ( "fmt" "os" "github.com/kyokomi/emoji" "github.com/logrusorgru/aurora" "github.com/sirupsen/logrus" ) const LogEnvVariable = "LOG_LEVEL" const defaultLogLevel = "info" var logger = getLogger() var infoFormatter = new(InfoFormatter) var debugFormatter = &logrus.TextFormatter{ DisableLevelTruncation: true, FullTimestamp: true, EnvironmentOverrideColors: true, } func getLogger() *logrus.Logger { logger := logrus.New() lvl, ok := os.LookupEnv(LogEnvVariable) if !ok { lvl = defaultLogLevel } logLevel, _ := logrus.ParseLevel(lvl) logger.SetOutput(os.Stdout) logger.SetLevel(logLevel) return logger } // Warnf logs a formatted error message func Infof(format string, a ...interface{}) { logger.SetFormatter(infoFormatter) logger.Info(aurora.Cyan(emoji.Sprintf(format, a...))) } func Debugf(format string, a ...interface{}) { logger.SetFormatter(debugFormatter) logger.Debug(aurora.Green(emoji.Sprintf(format, a...))) } // Infof prints out a timestamp as prefix, Guidef just prints the message func Guidef(format string, a ...interface{}) { fmt.Println(aurora.Cyan(emoji.Sprintf(format, a...))) } // Successf logs a formatted success message func Successf(format string, a ...interface{}) { logger.Info(aurora.Green(emoji.Sprintf(":white_check_mark: "+format, a...))) } // Warnf logs a formatted warning message func Warnf(format string, a ...interface{}) { logger.Warn(aurora.Yellow(emoji.Sprintf(":exclamation: "+format, a...))) } // Warnf logs a formatted error message func Errorf(format string, a ...interface{}) { logger.Error(aurora.Red(emoji.Sprintf(":exclamation: "+format, a...))) } // Info formatter is to not display the LOG_LEVEL in front of the command eg. INFO[2020-070-01T15:22:22] Hello World type InfoFormatter struct { } func (f *InfoFormatter) Format(entry *logrus.Entry) ([]byte, error) { // extra line break stops the prompts from overtaking Existing line return []byte(entry.Message + "\n"), nil } ================================================ FILE: pkg/util/fs/fs.go ================================================ package fs import ( "fmt" "os" "path" "regexp" "strings" ) // CreateDirs creates directories from the given directory path arguments. func CreateDirs(dirPaths ...string) error { for _, path := range dirPaths { if err := os.MkdirAll(path, 0755); err != nil { return err } } return nil } // FileExists checks whether the given path exists and belongs to a file. func FileExists(path string) (bool, error) { info, err := os.Stat(path) if err != nil { if os.IsNotExist(err) { return false, nil } return false, err } if info.IsDir() { return false, fmt.Errorf("%v: is a directory, expected file", path) } return true, nil } // PrependPath prepends a path with prefix while disregarding back directories ../ func ReplacePath(p, old, new string) string { return path.Clean(strings.Replace(p, old, new, 1)) } // PrependPath prepends a path with prefix while disregarding back directories ../ func PrependPath(filepath string, prefix string) string { re := regexp.MustCompile(`(\.\.\/)+`) cleanPath := path.Clean(filepath) baseDir := re.FindString(cleanPath) if baseDir == "" { return path.Join(prefix, cleanPath) } return strings.Replace(cleanPath, baseDir, path.Join(baseDir, prefix)+"/", 1) } ================================================ FILE: pkg/util/fs/fs_test.go ================================================ package fs import ( "testing" ) var replacePathTest = []struct { path string old string new string out string }{ {"../../dir/file.ext", "../../dir", "output", "output/file.ext"}, {"dir/file.ext", "dir", "output", "output/file.ext"}, } func TestReplacePath(t *testing.T) { for _, tt := range replacePathTest { t.Run(tt.path, func(t *testing.T) { out := ReplacePath(tt.path, tt.old, tt.new) if out != tt.out { t.Errorf("got %q, want %q", out, tt.out) } }) } } var prependPathTests = []struct { in string prefix string out string }{ {"../../dir/file.ext", "prefix", "../../prefix/dir/file.ext"}, {"../opps/../../dir/file.ext", "prefix", "../../prefix/dir/file.ext"}, {"../opps/../../dir/file.ext", "", "../../dir/file.ext"}, {"dir/file.ext", "prefix", "prefix/dir/file.ext"}, {"dir/file.ext", "../prefix", "../prefix/dir/file.ext"}, {"./dir/file.ext", "prefix", "prefix/dir/file.ext"}, } func TestPrependPath(t *testing.T) { for _, tt := range prependPathTests { t.Run(tt.in, func(t *testing.T) { out := PrependPath(tt.in, tt.prefix) if out != tt.out { t.Errorf("got %q, want %q", out, tt.out) } }) } } ================================================ FILE: registry.yaml ================================================ - name: EKS + Go + React + Gatsby moduleSources: - /zero-aws-eks-stack - /zero-static-site-gatsby - /zero-backend-go - /zero-frontend-react - name: EKS + NodeJS + React + Gatsby moduleSources: - /zero-aws-eks-stack - /zero-static-site-gatsby - /zero-backend-node - /zero-frontend-react ================================================ FILE: tests/integration/ci/ci_test.go ================================================ package ci_test // @TODO refactor into new set of integration tests // import ( // "bytes" // "io/ioutil" // "os" // "sync" // "testing" // "github.com/commitdev/zero/internal/config" // "github.com/commitdev/zero/internal/generate/ci" // "github.com/commitdev/zero/internal/templator" // "github.com/gobuffalo/packr/v2" // ) // var testData = "../../test_data/ci/" // // setupTeardown removes all the generated test files before and after // // the test runs to ensure clean data. // func setupTeardown(t *testing.T) func(t *testing.T) { // os.RemoveAll("../../test_data/ci/actual") // return func(t *testing.T) { // os.RemoveAll("../../test_data/ci/actual") // } // } // func TestGenerateJenkins(t *testing.T) { // teardown := setupTeardown(t) // defer teardown(t) // templates := packr.New("templates", "../../../templates") // testTemplator := templator.NewTemplator(templates) // var waitgroup sync.WaitGroup // testConf := &projectconfig.ZeroProjectConfig{} // testCI := config.CI{ // System: "jenkins", // BuildImage: "golang/golang", // BuildTag: "1.12", // BuildCommand: "make build", // TestCommand: "make test", // } // err := ci.Generate(testTemplator.CI, testConf, testCI, testData+"/actual", &waitgroup) // if err != nil { // t.Errorf("Error when executing test. %s", err) // } // waitgroup.Wait() // actual, err := ioutil.ReadFile(testData + "actual/Jenkinsfile") // if err != nil { // t.Errorf("Error reading created file: %s", err.Error()) // } // expected, err := ioutil.ReadFile(testData + "/expected/Jenkinsfile") // if err != nil { // t.Errorf("Error reading created file: %s", err.Error()) // } // if !bytes.Equal(expected, actual) { // t.Errorf("want:\n%s\n\n, got:\n%s\n\n", string(expected), string(actual)) // } // } // func TestGenerateCircleCI(t *testing.T) { // teardown := setupTeardown(t) // defer teardown(t) // templates := packr.New("templates", "../../../templates") // testTemplator := templator.NewTemplator(templates) // var waitgroup sync.WaitGroup // testConf := &projectconfig.ZeroProjectConfig{} // testCI := config.CI{ // System: "circleci", // BuildImage: "golang/golang", // BuildTag: "1.12", // BuildCommand: "make build", // TestCommand: "make test", // } // err := ci.Generate(testTemplator.CI, testConf, testCI, testData+"/actual", &waitgroup) // if err != nil { // t.Errorf("Error when executing test. %s", err) // } // waitgroup.Wait() // actual, err := ioutil.ReadFile(testData + "actual/.circleci/config.yml") // if err != nil { // t.Errorf("Error reading created file: %s", err.Error()) // } // expected, err := ioutil.ReadFile(testData + "/expected/.circleci/config.yml") // if err != nil { // t.Errorf("Error reading created file: %s", err.Error()) // } // if !bytes.Equal(expected, actual) { // t.Errorf("want:\n%s\n\ngot:\n%s\n\n", string(expected), string(actual)) // } // } // func TestGenerateTravisCI(t *testing.T) { // teardown := setupTeardown(t) // defer teardown(t) // templates := packr.New("templates", "../../../templates") // testTemplator := templator.NewTemplator(templates) // var waitgroup sync.WaitGroup // testConf := &projectconfig.ZeroProjectConfig{} // testCI := config.CI{ // System: "travisci", // Language: "go", // BuildImage: "golang/golang", // BuildTag: "1.12", // BuildCommand: "make build", // TestCommand: "make test", // } // err := ci.Generate(testTemplator.CI, testConf, testCI, testData+"/actual", &waitgroup) // if err != nil { // t.Errorf("Error when executing test. %s", err) // } // waitgroup.Wait() // actual, err := ioutil.ReadFile(testData + "actual/.travis.yml") // if err != nil { // t.Errorf("Error reading created file: %s", err.Error()) // } // expected, err := ioutil.ReadFile(testData + "/expected/.travis.yml") // if err != nil { // t.Errorf("Error reading created file: %s", err.Error()) // } // if !bytes.Equal(expected, actual) { // t.Errorf("want:\n%s\n\n, got:\n%s\n\n", string(expected), string(actual)) // } // } ================================================ FILE: tests/test_data/apply/project1/Makefile ================================================ current_dir: @echo "foo: ${foo}" > project.out @echo "repo: ${REPOSITORY}" >> project.out @echo "envVarName of viaEnvVarName: ${viaEnvVarName}" >> feature.out summary: check: ================================================ FILE: tests/test_data/apply/project1/zero-module.yml ================================================ name: project1 description: 'project1' author: 'Commit' template: strictMode: true delimiters: - "<%" - "%>" inputDir: '.' outputDir: 'test' requiredCredentials: - aws - github parameters: - field: foo label: foo - field: param1 envVarName: viaEnvVarName ================================================ FILE: tests/test_data/apply/project2/Makefile ================================================ current_dir: @echo "baz: ${baz}" > project.out summary: check: ================================================ FILE: tests/test_data/apply/project2/check.sh ================================================ pwd echo "custom check" > check.out ================================================ FILE: tests/test_data/apply/project2/zero-module.yml ================================================ name: project2 description: 'project2' author: 'Commit' commands: check: sh check.sh template: strictMode: true delimiters: - "<%" - "%>" inputDir: '.' outputDir: 'test' requiredCredentials: - aws - github parameters: - field: baz label: baz ================================================ FILE: tests/test_data/apply/zero-project.yml ================================================ name: sample_project modules: project1: parameters: foo: bar param1: baz files: dir: project1 repo: github.com/commitdev/project1 source: project1 project2: parameters: baz: qux files: dir: project2 repo: github.com/commitdev/project2 source: project2 ================================================ FILE: tests/test_data/apply-failing/project1/Makefile ================================================ current_dir: @echo "foo: ${foo}" > project.out @echo "repo: ${REPOSITORY}" >> project.out summary: check: @$(error "Failure 1 of 2") ================================================ FILE: tests/test_data/apply-failing/project1/project.out ================================================ foo: bar repo: github.com/commitdev/project1 ================================================ FILE: tests/test_data/apply-failing/project1/zero-module.yml ================================================ name: project1 description: 'project1' author: 'Commit' template: strictMode: true delimiters: - "<%" - "%>" inputDir: '.' outputDir: 'test' requiredCredentials: - aws - github parameters: - field: foo label: foo ================================================ FILE: tests/test_data/apply-failing/project2/Makefile ================================================ REQUIRED_BINS := ls nonexisting-binary current_dir: @echo "baz: ${baz}" > project.out summary: check: $(foreach bin, $(REQUIRED_BINS),\ $(if $(shell command -v $(bin) 2> /dev/null),$(info Found `$(bin)`),$(error Please install `$(bin)`))) ================================================ FILE: tests/test_data/apply-failing/project2/project.out ================================================ baz: qux ================================================ FILE: tests/test_data/apply-failing/project2/zero-module.yml ================================================ name: project2 description: 'project2' author: 'Commit' template: strictMode: true delimiters: - "<%" - "%>" inputDir: '.' outputDir: 'test' requiredCredentials: - aws - github parameters: - field: baz label: baz ================================================ FILE: tests/test_data/apply-failing/project3/Makefile ================================================ REQUIRED_BINS := ls nonexisting-binary current_dir: summary: ================================================ FILE: tests/test_data/apply-failing/project3/check.sh ================================================ >&2 echo "Check script erroring out";exit 1; ================================================ FILE: tests/test_data/apply-failing/project3/project.out ================================================ baz: qux ================================================ FILE: tests/test_data/apply-failing/project3/zero-module.yml ================================================ name: project3 description: 'project3' author: 'Commit' commands: check: sh check.sh template: strictMode: true delimiters: - "<%" - "%>" inputDir: '.' outputDir: 'test' requiredCredentials: - aws - github parameters: - field: baz label: baz ================================================ FILE: tests/test_data/apply-failing/zero-project.yml ================================================ name: sample_project modules: project1: parameters: foo: bar files: dir: project1 repo: github.com/commitdev/project1 source: project1 project2: parameters: baz: qux files: dir: project2 repo: github.com/commitdev/project2 source: project2 project3: files: dir: project3 repo: github.com/commitdev/project3 source: project3 ================================================ FILE: tests/test_data/aws/mock_credentials.yml ================================================ [default] aws_access_key_id=MOCK1_ACCESS_KEY aws_secret_access_key=MOCK1_SECRET_ACCESS_KEY [foobar] aws_access_key_id=MOCK2_ACCESS_KEY aws_secret_access_key=MOCK2_SECRET_ACCESS_KEY ================================================ FILE: tests/test_data/ci/expected/.circleci/config.yml ================================================ version: 2.1 jobs: build: docker: - image: golang/golang:1.12 steps: - checkout - run: name: Build command: | make build test: docker: - image: golang/golang:1.12 steps: - checkout - run: name: Test command: | make test workflow: version: 2.1 build_and_test: jobs: - build - test ================================================ FILE: tests/test_data/ci/expected/.travis.yml ================================================ language: go go: - 1.12 scripts: - make build - make test ================================================ FILE: tests/test_data/ci/expected/Jenkinsfile ================================================ pipeline { agent none stages { stage('Build and Test') { parallel { stage('Build') { agent { docker { image 'golang/golang:1.12' } } steps { sh 'make build' } } stage('Test') { agent { docker { image 'golang/golang:1.12' } } steps { sh 'make test' } } } } } } ================================================ FILE: tests/test_data/configs/commit0_submodules.yml ================================================ name: hello-world # Context will populated automatically or could be added manually context: cognitoPoolID: 123 cognitoClientID: ABC modules: # module can be in any format the go-getter supports (path, github, url, etc.) # supports https://github.com/hashicorp/go-getter#url-format # - source: './tests/test_data/modules/ci' # params: # ci: github - source: './remote_templates/services' # alternatively we can recursively support sub modules modules: - './remote_templates/ci/go' ================================================ FILE: tests/test_data/configs/credentials.yml ================================================ another-project: github: accessToken: "654" my-project: aws: accessKeyId: AKIAABCD secretAccessKey: ZXCV github: accessToken: "0987" circleci: apiKey: SOME_API_KEY ================================================ FILE: tests/test_data/configs/zero-basic.yml ================================================ name: hello-world # Context will populated automatically or could be added manually context: cognitoPoolID: 123 cognitoClientID: ABC modules: # module can be in any format the go-getter supports (path, github, url, etc.) # supports https://github.com/hashicorp/go-getter#url-format - source: "../../tests/test_data/modules/ci" params: ci: github # - source: './remote_templates/services' # # alternatively we can recursively support sub modules # modules: # - './remote_templates/ci/go' ================================================ FILE: tests/test_data/generate/file_to_template.txt ================================================ Name is {{.Name}} Params.test is {{.Params.test}} Files.Repository is {{.Files.Repository}} ================================================ FILE: tests/test_data/generate/zero-module.yml ================================================ name: test-generate description: 'generation test module' author: 'Commit' template: strictMode: true delimiters: - '{{' - '}}' inputDir: '.' outputDir: 'test' requiredCredentials: parameters: ================================================ FILE: tests/test_data/modules/ci/config1.yml ================================================ content1: {{ .ci }} ================================================ FILE: tests/test_data/modules/ci/dir/config2.yml ================================================ content2: ================================================ FILE: tests/test_data/modules/ci/zero-module.yml ================================================ name: "CI templates" description: "CI description" author: "CI author" icon: "" thumbnail: "" zeroVersion: ">= 3.0.0, < 4.0.0" commands: check: ls requiredCredentials: - aws - circleci - github # Template variables to populate, these could be overwritten by the file spefic frontmatter variables template: # strictMode: true # will only parse files that includes the .tmpl.* extension, otherwise it will copy file delimiters: - "<%" - "%>" inputDir: 'templates' outputDir: ".circleci" # required context parameters: will throw a warning message at the end if any of the context parameters are not present # contextRequired: # - cognitoPoolID # - cognitoClientID # parameters required from user to populate the template params parameters: - field: platform label: CI Platform # default: github options: github: Github circleci: Circle CI - field: circleci_api_key label: "Circle CI API Key to setup your CI/CD for repositories" conditions: - action: KeyMatchCondition matchField: platform whenValue: "circlci" - field: useExistingAwsProfile label: "Use credentials from an existing AWS profile?" options: "yes": "Yes" "no": "No" omitFromProjectFile: yes - field: profilePicker omitFromProjectFile: yes type: AWSProfilePicker conditions: - action: KeyMatchCondition whenValue: "yes" matchField: useExistingAwsProfile - field: accessKeyId label: AWS AccessKeyId envVarName: "AWS_ACCESS_KEY_ID" conditions: - action: KeyMatchCondition whenValue: "no" matchField: useExistingAwsProfile - field: secretAccessKey envVarName: "AWS_SECRET_ACCESS_KEY" label: AWS SecretAccessKey conditions: - action: KeyMatchCondition whenValue: "no" matchField: useExistingAwsProfile - field: testExecute execute: echo $AWS_ACCESS_KEY_ID ================================================ FILE: tests/test_data/modules/no-version-constraint/zero-module.yml ================================================ name: "Test module" description: "a module for testing, with no zero version requirement" author: "Test module author" icon: "" thumbnail: "" template: delimiters: - "<%" - "%>" inputDir: templates outputDir: test-module-output requiredCredentials: - aws - circleci - github parameters: ================================================ FILE: tests/test_data/projectconfig/zero-project.yml ================================================ # Graph shape: # 2 # / \ # 1 4 # \ / # 3 - 5 name: graph_test modules: project1: parameters: foo: bar project2: dependsOn: - project1 parameters: baz: qux project3: dependsOn: - project1 parameters: baz: qux project4: dependsOn: - project2 - project3 parameters: baz: qux project5: dependsOn: - project3 parameters: baz: qux ================================================ FILE: version/version.go ================================================ package version // These values are overridden by makefile during build var ( AppVersion = "SNAPSHOT" AppBuild = "SNAPSHOT" )