├── .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 │ │ ├── img │ │ │ └── zero-check.png │ │ ├── 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 ├── logo.png ├── package-lock.json ├── 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 │ └── img │ │ ├── docs │ │ └── zero-check.png │ │ ├── favicon.ico │ │ ├── icons │ │ ├── attr-modular.svg │ │ ├── attr-reliable.svg │ │ ├── attr-scalable.svg │ │ ├── attr-secure.svg │ │ ├── icon-minus.svg │ │ ├── icon-plus.svg │ │ ├── notes-navy.svg │ │ ├── notes.svg │ │ ├── octocat-navy.svg │ │ ├── octocat.svg │ │ ├── offering-backend.svg │ │ ├── offering-cicd.svg │ │ ├── offering-frontend.svg │ │ ├── offering-infra.svg │ │ ├── reason-clockwise.svg │ │ ├── reason-diamond.svg │ │ ├── reason-key.svg │ │ ├── slack-navy.svg │ │ └── slack.svg │ │ ├── partners │ │ ├── atlasone.png │ │ ├── patch.png │ │ ├── placeholder.png │ │ └── planworth.png │ │ ├── tools │ │ ├── aws.png │ │ ├── cert-manager.png │ │ ├── circleci.png │ │ ├── cloudfront.png │ │ ├── docker.png │ │ ├── ecr.png │ │ ├── eks.png │ │ ├── external-dns.png │ │ ├── github-actions.svg │ │ ├── golang.png │ │ ├── grafana.png │ │ ├── js.png │ │ ├── kubernetes.png │ │ ├── nodejs.png │ │ ├── openid.png │ │ ├── ory.png │ │ ├── prometheus.png │ │ ├── react.png │ │ ├── s3.png │ │ ├── stripe.png │ │ ├── telepresence.png │ │ ├── terraform.png │ │ ├── wireguard.png │ │ └── wireguard.svg │ │ └── zero.svg └── yarn.lock ├── 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-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 │ ├── apply │ ├── project1 │ │ ├── Makefile │ │ └── zero-module.yml │ ├── project2 │ │ ├── Makefile │ │ ├── check.sh │ │ └── 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 /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2.1 3 | orbs: 4 | slack-message: commitdev/slack-message@0.0.3 5 | version-tag: commitdev/version-tag@0.0.3 6 | 7 | variables: 8 | - &workspace /home/circleci/project 9 | - &build-image circleci/golang:1.16.8 10 | 11 | aliases: 12 | # Shallow Clone - this allows us to cut the 2 minute repo clone down to about 10 seconds for repos with 50,000 commits+ 13 | - &checkout-shallow 14 | name: Checkout (Shallow) 15 | command: | 16 | #!/bin/sh 17 | set -e 18 | 19 | # Workaround old docker images with incorrect $HOME 20 | # check https://github.com/docker/docker/issues/2968 for details 21 | if [ "${HOME}" = "/" ] 22 | then 23 | export HOME=$(getent passwd $(id -un) | cut -d: -f6) 24 | fi 25 | 26 | mkdir -p ~/.ssh 27 | 28 | echo 'github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== 29 | bitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAubiN81eDcafrgMeLzaFPsw2kNvEcqTKl/VqLat/MaB33pZy0y3rJZtnqwR2qOOvbwKZYKiEO1O6VqNEBxKvJJelCq0dTXWT5pbO2gDXC6h6QDXCaHo6pOHGPUy+YBaGQRGuSusMEASYiWunYN0vCAI8QaXnWMXNMdFP3jHAJH0eDsoiGnLPBlBp4TNm6rYI74nMzgz3B9IikW4WVK+dc8KZJZWYjAuORU3jc1c/NPskD2ASinf8v3xnfXeukU0sJ5N6m5E8VLjObPEO+mN2t/FZTMZLiFqPWc/ALSqnMnnhwrNi2rbfg/rd/IpL8Le3pSBne8+seeFVBoGqzHM9yXw==' >> ~/.ssh/known_hosts 30 | 31 | (umask 077; touch ~/.ssh/id_rsa) 32 | chmod 0600 ~/.ssh/id_rsa 33 | (cat \< ~/.ssh/id_rsa 34 | $CHECKOUT_KEY 35 | EOF 36 | ) 37 | 38 | # use git+ssh instead of https 39 | git config --global url."ssh://git@github.com".insteadOf "https://github.com" || true 40 | 41 | if [ -e /home/circleci/project/.git ] 42 | then 43 | cd /home/circleci/project 44 | git remote set-url origin "$CIRCLE_REPOSITORY_URL" || true 45 | else 46 | mkdir -p /home/circleci/project 47 | cd /home/circleci/project 48 | git clone --depth=1 "$CIRCLE_REPOSITORY_URL" . 49 | fi 50 | 51 | if [ -n "$CIRCLE_TAG" ] 52 | then 53 | git fetch --depth=10 --force origin "refs/tags/${CIRCLE_TAG}" 54 | elif [[ "$CIRCLE_BRANCH" =~ ^pull\/* ]] 55 | then 56 | # For PR from Fork 57 | git fetch --depth=10 --force origin "$CIRCLE_BRANCH/head:remotes/origin/$CIRCLE_BRANCH" 58 | else 59 | git fetch --depth=10 --force origin "$CIRCLE_BRANCH:remotes/origin/$CIRCLE_BRANCH" 60 | fi 61 | 62 | if [ -n "$CIRCLE_TAG" ] 63 | then 64 | git reset --hard "$CIRCLE_SHA1" 65 | git checkout -q "$CIRCLE_TAG" 66 | elif [ -n "$CIRCLE_BRANCH" ] 67 | then 68 | git reset --hard "$CIRCLE_SHA1" 69 | git checkout -q -B "$CIRCLE_BRANCH" 70 | fi 71 | 72 | git reset --hard "$CIRCLE_SHA1" 73 | pwd 74 | 75 | jobs: 76 | checkout_code: 77 | docker: 78 | - image: *build-image 79 | steps: 80 | - run: *checkout-shallow 81 | - persist_to_workspace: 82 | root: /home/circleci/project 83 | paths: 84 | - . 85 | 86 | unit_test: 87 | docker: 88 | - image: *build-image 89 | working_directory: *workspace 90 | steps: # steps that comprise the `build` job 91 | - attach_workspace: 92 | at: *workspace 93 | 94 | - restore_cache: # restores saved cache if no changes are detected since last run 95 | keys: 96 | - v1-pkg-cache-{{ checksum "go.sum" }} 97 | - v1-pkg-cache- 98 | 99 | - run: 100 | name: Run unit tests 101 | # store the results of our tests in the $TEST_RESULTS directory 102 | command: | 103 | go get -u github.com/jstemmer/go-junit-report 104 | mkdir -p test-reports 105 | PACKAGE_NAMES=$(go list ./... | circleci tests split --split-by=timings --timings-type=classname) 106 | echo "Tests: $PACKAGE_NAMES" 107 | go test -v $PACKAGE_NAMES | go-junit-report > test-reports/junit.xml 108 | 109 | - save_cache: # Store cache in the /go/pkg directory 110 | key: v1-pkg-cache-{{ checksum "go.sum" }} 111 | paths: 112 | - "/go/pkg" 113 | 114 | - store_test_results: 115 | path: test-reports 116 | 117 | - store_artifacts: 118 | path: test-reports 119 | 120 | # Requires the SLACK_WEBHOOK 121 | # - slack/notify-on-failure 122 | 123 | build_and_push: 124 | machine: 125 | image: circleci/classic:latest 126 | # docker_layer_caching: true # only for performance plan circleci accounts 127 | steps: 128 | - attach_workspace: 129 | at: *workspace 130 | - run: *checkout-shallow 131 | - version-tag/create 132 | - run: 133 | name: Update Version command 134 | command: | 135 | ./updateVersion.sh "$VERSION_TAG" 136 | - run: 137 | name: Build Docker image 138 | command: | 139 | make ci-docker-build 140 | - run: 141 | name: Push to Docker Hub 142 | command: | 143 | make ci-docker-push 144 | 145 | 146 | workflows: 147 | version: 2 148 | # The main workflow. Check out the code, build it, push it, deploy to staging, test, deploy to production 149 | build_test_and_deploy: 150 | jobs: 151 | - checkout_code 152 | 153 | - unit_test: 154 | requires: 155 | - checkout_code 156 | 157 | - build_and_push: 158 | requires: 159 | - unit_test 160 | filters: 161 | branches: 162 | only: # only branches matching the below regex filters will run 163 | - /^master$/ 164 | 165 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | README.md 3 | .history/ 4 | .travis.yml 5 | .gitignore 6 | .git 7 | example/ 8 | tmp/ 9 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [main] 9 | schedule: 10 | - cron: '0 4 * * 1' 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | permissions: 17 | security-events: write 18 | 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v3 22 | with: 23 | # We must fetch at least the immediate parents so that if this is 24 | # a pull request then we can checkout the head. 25 | fetch-depth: 2 26 | 27 | # Initializes the CodeQL tools for scanning. 28 | - name: Initialize CodeQL 29 | uses: github/codeql-action/init@v2 30 | with: 31 | languages: go 32 | 33 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 34 | # If this step fails, then you should remove it and run the build manually (see below) 35 | - name: Autobuild 36 | uses: github/codeql-action/autobuild@v2 37 | 38 | # ℹ️ Command-line programs to run using the OS shell. 39 | # 📚 https://git.io/JvXDl 40 | 41 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 42 | # and modify them (or add more) to build your code if your project 43 | # uses a compiled language 44 | 45 | #- run: | 46 | # make bootstrap 47 | # make release 48 | 49 | - name: Perform CodeQL Analysis 50 | uses: github/codeql-action/analyze@v2 51 | -------------------------------------------------------------------------------- /.github/workflows/config.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | branches: 4 | - main 5 | 6 | jobs: 7 | unit_test: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-go@v2 12 | with: 13 | go-version: 1.16 14 | - uses: actions/cache@v2 15 | with: 16 | path: ~/go/pkg/mod 17 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 18 | restore-keys: | 19 | ${{ runner.os }}-go- 20 | - name: Run Go Tests 21 | run: | 22 | make check 23 | -------------------------------------------------------------------------------- /.github/workflows/doc-site.yml: -------------------------------------------------------------------------------- 1 | ## The is a combination of sites where 2 | ## Zero serves on the root of the domain / 3 | ## and module serves on /docs/modules// 4 | # from the same S3 bucket 5 | 6 | name: "Build Documentation Site" 7 | on: 8 | push: 9 | branches: 10 | - main 11 | paths: 12 | - doc-site/** 13 | 14 | env: 15 | region: us-west-2 16 | s3_sync_path_to_exclude: docs/modules/* 17 | s3_sync_path: "" 18 | BUILD_DOMAIN: ${{ secrets.ZERO_DOC_SITE_DOMAIN_NAME }} 19 | jobs: 20 | build: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Setup node.js 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: 16 28 | # - name: Documentaiton site folder 29 | # run: cd doc-site 30 | - name: Install Dependencies 31 | working-directory: doc-site 32 | run: npm install 33 | - name: Build website 34 | working-directory: doc-site 35 | run: | 36 | npm run build 37 | pwd 38 | ls -la 39 | - name: Upload build artifact to Github 40 | uses: actions/upload-artifact@v2 41 | with: 42 | name: build-artifact 43 | path: doc-site/build 44 | 45 | deploy: 46 | name: Deploy 47 | runs-on: ubuntu-latest 48 | needs: build 49 | # These permissions are needed to interact with GitHub's OIDC Token endpoint. 50 | permissions: 51 | id-token: write 52 | contents: read 53 | 54 | steps: 55 | # Once github action supports nested composite actions (anything `uses` is a composite action) 56 | # Therefore we cannot reuse the code as a separate composite action until it supports it, 57 | # current the deploy logic is in this file twice because of it 58 | ## https://github.com/actions/runner/issues/862 59 | - uses: actions/checkout@v2 60 | - name: Configure AWS credentials for S3 sync 61 | uses: aws-actions/configure-aws-credentials@v1 62 | with: 63 | aws-access-key-id: ${{ secrets.ZERO_DOC_SITE_AWS_ACCESS_KEY_ID }} 64 | aws-secret-access-key: ${{ secrets.ZERO_DOC_SITE_AWS_SECRET_ACCESS_KEY }} 65 | aws-region: ${{ env.region }} 66 | - name: Download build artifact from Github 67 | uses: actions/download-artifact@v1 68 | with: 69 | name: build-artifact 70 | path: build/ 71 | - name: Sync with S3 72 | shell: bash 73 | run: | 74 | cd build 75 | aws s3 sync . "s3://${{ secrets.ZERO_DOC_SITE_BUCKET_NAME }}${{ env.s3_sync_path }}" --exclude "${{ env.s3_sync_path_to_exclude }}" --delete 76 | - name: Invalidate Cloudfront 77 | shell: bash 78 | run: | 79 | export DIST_ID=$(aws cloudfront list-distributions --query "DistributionList.Items[?Aliases.Items[?@=='${{ secrets.ZERO_DOC_SITE_BUCKET_NAME }}']].Id | [0]" | tr -d '"') 80 | aws cloudfront create-invalidation --distribution-id ${DIST_ID} --paths "/*" 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | main-packr.go 2 | packrd 3 | /zero 4 | .history/ 5 | tmp 6 | .vscode 7 | example/ 8 | test-reports/ 9 | .circleci/config-compiled.yml 10 | .idea/ -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # http://goreleaser.com 2 | before: 3 | hooks: 4 | - go mod download 5 | builds: 6 | - env: 7 | - CGO_ENABLED=0 8 | ldflags: 9 | - -X github.com/commitdev/zero/version.AppVersion={{.Version}} -X github.com/commitdev/zero/version.AppBuild={{.ShortCommit}} 10 | goarch: 11 | - amd64 12 | - arm64 13 | - 386 14 | archives: 15 | - replacements: 16 | darwin: Darwin 17 | linux: Linux 18 | 386: i386 19 | amd64: x86_64 20 | checksum: 21 | name_template: 'checksums.txt' 22 | snapshot: 23 | name_template: "{{ .Tag }}-next" 24 | changelog: 25 | sort: asc 26 | filters: 27 | exclude: 28 | - '^docs:' 29 | - '^test:' 30 | 31 | brews: 32 | - name: zero 33 | tap: 34 | owner: commitdev 35 | name: homebrew-zero 36 | commit_author: 37 | name: Commit.dev 38 | email: contact@commit.dev 39 | homepage: "https://github.com/commitdev/zero" 40 | description: "Allow startup developers to ship to production on day 1." 41 | dependencies: 42 | - name: git 43 | type: optional 44 | - name: terraform 45 | type: optional 46 | - name: jq 47 | type: optional 48 | - name: awscli 49 | type: optional 50 | - name: kubectl 51 | type: optional 52 | - name: wget 53 | type: optional 54 | test: | 55 | system "#{bin}/zero version" 56 | 57 | nfpms: 58 | - 59 | id: zero 60 | package_name: zero 61 | vendor: Commit.dev 62 | homepage: https://github.com/commitdev/zero 63 | maintainer: commitdev 64 | description: "Allow startup developers to ship to production on day 1." 65 | formats: 66 | - apk 67 | - deb 68 | - rpm 69 | 70 | recommends: 71 | - awscli 72 | - git 73 | - jq 74 | - wget 75 | 76 | overrides: 77 | apk: 78 | recommends: 79 | - aws-cli 80 | - git 81 | - jq 82 | - wget 83 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Zero 2 | 3 | Thanks for your interest in Zero! 4 | 5 | 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. 6 | 7 | If you have used Zero or are just interested in helping out there are a few key ways to contribute: 8 | 9 | ## Contribute some code 10 | 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. 11 | 12 | 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. 13 | Here's a list of the core repositories: 14 | |_Repo_|_Language_|_Description_| 15 | |--|--|--| 16 | | [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 | 17 | | [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) | 18 | | [zero-backend-go](https://github.com/commitdev/zero-backend-go) | Go | A deployable backend service providing an API written in Go | 19 | | [zero-backend-node](https://github.com/commitdev/zero-backend-node) | Node.js | A deployable backend service providing an API written in Node | 20 | | [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 | 21 | | [zero-static-site-gatsby](https://github.com/commitdev/zero-static-site-gatsby) | Javascript / Gatsby | A deployable static site / marketing site for your application | 22 | | [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.) | 23 | | [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 | 24 | 25 | 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. 26 | 27 | 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.) 28 | 29 | ### Pull Requests 30 | 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. 31 | 32 | 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.) 33 | For example: 34 | `fix: add proper encoding of billing parameters` 35 | or 36 | `enhancement: support new terraform kubernetes provider` 37 | 38 | 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. 39 | 40 | 41 | 42 | ## Contribute documentation 43 | 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. 44 | 45 | ## Write up a bug report 46 | 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. 47 | 48 | ## Request a feature 49 | 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. 50 | 51 | ## Tell a friend 52 | If you like what we're doing, please share it with your network! 53 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.12.12-alpine3.10 as builder 2 | 3 | ENV GO111MODULE=on 4 | ENV TERRAFORM_VERSION=0.12.13 5 | 6 | RUN apk add --update --no-cache build-base curl git upx && \ 7 | rm -rf /var/cache/apk/* 8 | 9 | RUN apk add --update nodejs npm 10 | 11 | RUN curl -sSL \ 12 | https://amazon-eks.s3-us-west-2.amazonaws.com/1.14.6/2019-08-22/bin/linux/amd64/aws-iam-authenticator \ 13 | -o /usr/local/bin/aws-iam-authenticator 14 | 15 | RUN GO111MODULE=off go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway 16 | 17 | RUN curl -sSLo /tmp/terraform.zip "https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip" && \ 18 | unzip -d /usr/local/bin/ /tmp/terraform.zip 19 | 20 | RUN chmod +x /usr/local/bin/* && \ 21 | upx --lzma /usr/local/bin/* 22 | 23 | # Hydrate the dependency cache. This way, if the go.mod or go.sum files do not 24 | # change we can cache the depdency layer without having to reinstall them. 25 | WORKDIR /tmp/zero 26 | COPY go.mod go.sum ./ 27 | RUN go mod download 28 | 29 | COPY . . 30 | 31 | RUN make build && \ 32 | mv zero /usr/local/bin && \ 33 | upx --lzma /usr/local/bin/zero 34 | 35 | FROM alpine:3.10 36 | ENV \ 37 | PROTOBUF_VERSION=3.6.1-r1 \ 38 | GOPATH=/proto-libs 39 | 40 | RUN apk add --update bash ca-certificates git python && \ 41 | apk add --update -t deps make py-pip 42 | 43 | RUN mkdir ${GOPATH} 44 | COPY --from=builder /usr/local/bin /usr/local/bin 45 | COPY --from=builder /go/src/github.com/grpc-ecosystem/grpc-gateway ${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway 46 | WORKDIR /project 47 | 48 | ENTRYPOINT ["/usr/local/bin/zero"] 49 | -------------------------------------------------------------------------------- /LICENSE.spdx: -------------------------------------------------------------------------------- 1 | SPDXVersion: SPDX-2.1 2 | DataLicense: CC0-1.0 3 | PackageName: Zero 4 | PackageOriginator: Commit 5 | PackageHomePage: https://github.com/commitdev/zero/ 6 | PackageLicenseDeclared: MPL-2.0 -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION = 0.0.1 2 | BUILD ?=$(shell git rev-parse --short HEAD) 3 | PKG ?=github.com/commitdev/zero 4 | BUILD_ARGS=-v -trimpath -ldflags=all="-X ${PKG}/version.AppVersion=${VERSION} -X ${PKG}/version.AppBuild=${BUILD}" 5 | 6 | deps: 7 | go mod download 8 | 9 | check: 10 | go list -f '{{.Dir}}' ./... | grep -v /tmp/ | xargs go test -v 11 | 12 | fmt: 13 | go fmt ./... 14 | 15 | build-docker-local: 16 | docker build . -t zero:v0 17 | 18 | build-example-docker: clean-example 19 | mkdir -p example 20 | docker run -v "$(shell pwd)/example:/project" --user $(shell id -u):$(shell id -g) zero:v0 create "hello-world" 21 | docker run -v "$(shell pwd)/example/hello-world:/project" --user $(shell id -u):$(shell id -g) zero:v0 generate -l go 22 | 23 | build: 24 | go build ${BUILD_ARGS} 25 | 26 | # Installs the CLI int your GOPATH 27 | install-go: 28 | go build -o ${GOPATH}/bin/zero 29 | 30 | # CI Commands used on CircleCI 31 | ci-docker-build: 32 | docker build . -t commitdev/zero:${VERSION_TAG} -t commitdev/zero:latest 33 | 34 | ci-docker-push: 35 | echo "${DOCKERHUB_PASS}" | docker login -u commitdev --password-stdin 36 | docker push commitdev/zero:${VERSION_TAG} 37 | -------------------------------------------------------------------------------- /cmd/apply.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/commitdev/zero/internal/apply" 8 | "github.com/commitdev/zero/internal/config/projectconfig" 9 | "github.com/commitdev/zero/internal/constants" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var applyConfigPath string 14 | var applyEnvironments []string 15 | 16 | func init() { 17 | applyCmd.PersistentFlags().StringVarP(&applyConfigPath, "config", "c", constants.ZeroProjectYml, "config path") 18 | applyCmd.PersistentFlags().StringSliceVarP(&applyEnvironments, "env", "e", []string{}, "environments to set up (staging, production) - specify multiple times for multiple") 19 | 20 | rootCmd.AddCommand(applyCmd) 21 | } 22 | 23 | var applyCmd = &cobra.Command{ 24 | Use: "apply", 25 | Short: "Execute modules to create projects, infrastructure, etc.", 26 | Run: func(cmd *cobra.Command, args []string) { 27 | rootDir, err := os.Getwd() 28 | if err != nil { 29 | log.Println(err) 30 | rootDir = projectconfig.RootDir 31 | } 32 | applyErr := apply.Apply(rootDir, applyConfigPath, applyEnvironments) 33 | if applyErr != nil { 34 | log.Fatal(applyErr) 35 | } 36 | }, 37 | } 38 | -------------------------------------------------------------------------------- /cmd/create.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "path" 6 | "strings" 7 | 8 | "github.com/commitdev/zero/internal/config/projectconfig" 9 | "github.com/commitdev/zero/internal/constants" 10 | "github.com/commitdev/zero/internal/generate" 11 | "github.com/commitdev/zero/internal/vcs" 12 | "github.com/commitdev/zero/pkg/util/exit" 13 | "github.com/commitdev/zero/pkg/util/flog" 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | var ( 18 | createConfigPath string 19 | overwriteFiles bool 20 | ) 21 | 22 | func init() { 23 | createCmd.PersistentFlags().StringVarP(&createConfigPath, "config", "c", constants.ZeroProjectYml, "The project.yml file to load. The default is the one in the current directory.") 24 | createCmd.PersistentFlags().BoolVarP(&overwriteFiles, "overwrite", "o", false, "overwrite pre-existing files") 25 | 26 | rootCmd.AddCommand(createCmd) 27 | } 28 | 29 | var createCmd = &cobra.Command{ 30 | Use: "create", 31 | Short: fmt.Sprintf("Create projects for modules and configuration specified in %s", constants.ZeroProjectYml), 32 | Run: func(cmd *cobra.Command, args []string) { 33 | Create(projectconfig.RootDir, createConfigPath) 34 | }, 35 | } 36 | 37 | func Create(dir string, createConfigPath string) { 38 | if strings.Trim(createConfigPath, " ") == "" { 39 | exit.Fatal("config path cannot be empty!") 40 | } 41 | configFilePath := path.Join(dir, createConfigPath) 42 | projectConfig := projectconfig.LoadConfig(configFilePath) 43 | 44 | generate.Generate(*projectConfig, overwriteFiles) 45 | 46 | if projectConfig.ShouldPushRepositories { 47 | flog.Infof(":up_arrow: Done Rendering - committing repositories to version control.") 48 | 49 | for _, module := range projectConfig.Modules { 50 | err, githubApiKey := projectconfig.ReadVendorCredentialsFromModule(module, "github") 51 | if err != nil { 52 | flog.Errorf(err.Error()) 53 | } 54 | vcs.InitializeRepository(module.Files.Repository, githubApiKey) 55 | } 56 | } else { 57 | flog.Infof(":up_arrow: Done Rendering - you will need to commit the created projects to version control.") 58 | } 59 | 60 | flog.Infof(":check_mark_button: Done - run zero apply to create any required infrastructure or execute any other remote commands to prepare your environments.") 61 | } 62 | -------------------------------------------------------------------------------- /cmd/init.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/commitdev/zero/internal/config/projectconfig" 7 | initPrompts "github.com/commitdev/zero/internal/init" 8 | "github.com/commitdev/zero/pkg/util/exit" 9 | "github.com/commitdev/zero/pkg/util/flog" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var localModulePath string 14 | var registryFilePath string 15 | 16 | func init() { 17 | initCmd.PersistentFlags().StringVarP(&localModulePath, "local-module-path", "m", "github.com/commitdev", "local module path - for using local modules instead of downloading from github") 18 | 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") 19 | rootCmd.AddCommand(initCmd) 20 | } 21 | 22 | var initCmd = &cobra.Command{ 23 | Use: "init", 24 | Short: "Create new project with provided name and initialize configuration based on user input.", 25 | Run: func(cmd *cobra.Command, args []string) { 26 | flog.Debugf("Root directory is %s", projectconfig.RootDir) 27 | projectContext := initPrompts.Init(projectconfig.RootDir, localModulePath, registryFilePath) 28 | projectConfigErr := projectconfig.CreateProjectConfigFile(projectconfig.RootDir, projectContext.Name, projectContext) 29 | 30 | if projectConfigErr != nil { 31 | exit.Fatal(fmt.Sprintf(" Init failed while creating the zero project config file %s", projectConfigErr.Error())) 32 | } else { 33 | 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. 34 | cd %s 35 | cat zero-project.yml 36 | zero create`, projectContext.Name) 37 | } 38 | }, 39 | } 40 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/commitdev/zero/version" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func init() { 11 | rootCmd.AddCommand(versionCmd) 12 | } 13 | 14 | var versionCmd = &cobra.Command{ 15 | Use: "version", 16 | Short: "Print the version number of zero", 17 | Run: func(cmd *cobra.Command, args []string) { 18 | fmt.Printf("version: %v\n", version.AppVersion) 19 | fmt.Printf("build: %v\n", version.AppBuild) 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /cmd/zero.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var rootCmd = &cobra.Command{ 10 | Use: "zero", 11 | Short: "zero gets you to writing code quicker.", 12 | 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", 13 | Run: func(cmd *cobra.Command, args []string) { 14 | }, 15 | } 16 | 17 | func Execute() { 18 | if len(os.Args) > 1 { 19 | if err := rootCmd.Execute(); err != nil { 20 | os.Exit(1) 21 | } 22 | } else { // If no arguments were provided, print the usage message. 23 | rootCmd.Help() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /doc-site/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | 22 | # to test theme color for elements locally 23 | docs/about/color-test.md* 24 | -------------------------------------------------------------------------------- /doc-site/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ## Installation 6 | 7 | ```console 8 | yarn install 9 | ``` 10 | 11 | ## Local Development 12 | 13 | ```console 14 | yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ## Build 20 | 21 | ```console 22 | yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ## Deployment 28 | 29 | ```console 30 | GIT_USER= USE_SSH=true yarn deploy 31 | ``` 32 | 33 | 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. 34 | -------------------------------------------------------------------------------- /doc-site/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /doc-site/docs/about/opensource.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Opensource 3 | sidebar_label: Opensource 4 | sidebar_position: 2 5 | --- 6 | 7 | 8 | ## Contributing to Zero 9 | 10 | 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. 11 | 12 | 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) -------------------------------------------------------------------------------- /doc-site/docs/about/real-world-usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Real-world Usage Scenarios 3 | sidebar_label: Real-world Usage 4 | sidebar_position: 4 5 | --- 6 | 7 | ## Developing and deploying application changes 8 | 1. Clone your git repository. 9 | 2. Make a branch, start working on your code. 10 | 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. 11 | 3. Commit your finished code, make a PR, have it reviewed. Lightweight tests will run against your branch and prevent merging if they fail. 12 | 4. Merge your branch to the main branch. A build will start automatically. 13 | 5. The pipeline will build an artifact, run tests, deploy your change to staging, then wait for your input to deploy to production. 14 | 15 | ## Debugging a problem on production 16 | 1. Check the logs of your service: 17 | - 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. 18 | - 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. 19 | - Alternatively, watch the logs in realtime via the CLI using the command `kubectl logs -f -l app=` or `stern ` 20 | 2. Check the state of your application pods. Look for strange events or errors from the pods: 21 | ```shell 22 | $ kubectl get pods 23 | $ kubectl get events 24 | $ kubectl describe pods 25 | ``` 26 | 3. Exec into your application pod. From here you can check connectivity with `ping` or `nc`, or inspect anything else application-specific. 27 | ```shell 28 | $ kubectl get pods 29 | NAME READY STATUS RESTARTS AGE 30 | your-service-6c5f6b56b7-2w447 1/1 Running 0 30m49s 31 | $ kubectl exec -it your-service-6c5f6b56b7-2w447 sh 32 | ``` 33 | 34 | 35 | ## Adding support for a new subdomain or site 36 | 1. Check the currently configured ingresses in your cluster: 37 | ```shell 38 | $ kubectl get ingress -A 39 | NAMESPACE NAME CLASS HOSTS ADDRESS PORTS AGE 40 | your-service your-service api.your-service.dev abcd1234-1234.us-west-2.elb.amazonaws.com 80, 443 130d 41 | ``` 42 | 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. 43 | - `spec` is where you can define the hostname, any special path rules, and which service you want traffic to be sent to 44 | - if your hostname is in the `tls` section, a TLS certificate will automatically be provisioned for it using Let's Encrypt 45 | 3. A number of things will happen once this is deployed to the cluster: 46 | - Routing rules will be created to let traffic in to the cluster and send it to the service based on the hostname and path 47 | - An AWS load balancer will be created if one doesn't already exist and it will be pointed to the cluster 48 | - In the case of a subdomain, a DNS record will be automatically created for you 49 | - A certificate will be provisioned using Let's Encrypt for the domain you specified 50 | -------------------------------------------------------------------------------- /doc-site/docs/about/roadmap.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Roadmap 3 | sidebar_label: Roadmap 4 | sidebar_position: 5 5 | --- 6 | 7 | :::info 8 | Coming soon 9 | ::: 10 | -------------------------------------------------------------------------------- /doc-site/docs/concepts/core-concepts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Core Concepts 3 | sidebar_label: Core Concepts 4 | sidebar_position: 1 5 | --- 6 | 7 | ## Project 8 | 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`. 9 | 10 | 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. 11 | 12 | ## Module 13 | 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. 14 | 15 | 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`). 16 | 17 | 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. 18 | -------------------------------------------------------------------------------- /doc-site/docs/concepts/project-life-cycle.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Project Life Cycle 3 | sidebar_label: Project Life Cycle 4 | sidebar_position: 2 5 | --- 6 | 7 | ## zero init 8 | The goal of the `init` step is to create the project manifest (`zero-project.yml`). 9 | 10 | `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. 11 | 12 | :::note 13 | It's recommended to review the `zero-project.yml` and make adjustments as needed before running `zero create` and `zero apply`. 14 | ::: 15 | 16 | ## zero create 17 | `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). 18 | 19 | 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. 20 | 21 | ## zero apply 22 | `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. 23 | 24 | ### Check 25 | `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. 26 | 27 | ### Apply 28 | 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. 29 | 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**. 30 | -------------------------------------------------------------------------------- /doc-site/docs/getting-started/img/zero-check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/docs/getting-started/img/zero-check.png -------------------------------------------------------------------------------- /doc-site/docs/getting-started/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | sidebar_label: Installation 4 | sidebar_position: 1 5 | --- 6 | 7 | ## How to Install and Configure Zero 8 | 9 | There are multiple ways to install Zero: 10 | 11 | - Install Zero using your systems package manager. 12 | 13 | ```shell 14 | # MacOS 15 | brew tap commitdev/zero 16 | brew install zero 17 | ``` 18 | 19 | - Install Zero by downloading the binary. 20 | 21 | 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. 22 | 23 | Zero currently supports: 24 | 25 | | System | Support| Package Manager | 26 | |---------|:-----:|:------:| 27 | | MacOS | ✅ | `brew` | 28 | | Linux | ✅ | `deb, rpm, apk` | 29 | | Windows | ❌ | n/a | 30 | -------------------------------------------------------------------------------- /doc-site/docs/getting-started/prerequisites.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Prerequisites 3 | sidebar_label: Prerequisites 4 | sidebar_position: 2 5 | --- 6 | 7 | 8 | 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. 9 | 10 | A few caveats before getting started: 11 | 12 | - 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). 13 | 14 | - 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. 15 | 16 |
17 | If using CircleCI as your build pipeline... 18 |
    19 |
  • 20 | Grant CircleCi Organization access to your repositories to allow pulling the code during the build pipeline. 21 |
  • 22 |
  • 23 | You will need to create a CircleCi access token and enter it during the setup process; you should store your generated tokens securely. 24 |
  • 25 |
  • 26 | 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. 27 |
  • 28 |
29 |
30 | 31 | 32 | ### `zero check` 33 | 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. 34 | 35 | 36 | 37 | [AWS CLI], [Kubectl], [Terraform], [jq], [Git], [Wget] 38 | 39 | 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/). 40 | 41 | :::tip 42 | 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. 43 | ::: 44 | 45 | [AWS CLI]: https://aws.amazon.com/cli/ 46 | [git]: https://git-scm.com 47 | [kubectl]: https://kubernetes.io/docs/tasks/tools/install-kubectl/ 48 | [terraform]:https://www.terraform.io/downloads.html 49 | [jq]: https://github.com/stedolan/jq 50 | [Wget]: https://stackoverflow.com/questions/33886917/how-to-install-wget-in-macos 51 | -------------------------------------------------------------------------------- /doc-site/docs/getting-started/zero-apply.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: zero apply 3 | sidebar_label: zero apply 4 | sidebar_position: 5 5 | --- 6 | 7 | The `zero apply` command takes the templated modules generated based on your input and spins up a scalable & performant infrastructure for you! 8 | 9 | :::note 10 | This can take 20 minutes or more depending on your choices, as it must wait for all the provisioned infrastructure to be created 11 | ::: 12 | 13 | ```shell 14 | $ zero apply 15 | 16 | # Sample Output 17 | Choose the environments to apply. This will create infrastructure, CI pipelines, etc. 18 | At this point, real things will be generated that may cost money! 19 | 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. 20 | ✔ Production 21 | 🎉 Bootstrapping project zero-init. Please use the zero-project.yml file to modify the project as needed. 22 | Cloud provider: AWS 23 | Runtime platform: Kubernetes 24 | Infrastructure executor: Terraform 25 | 26 | ... 27 | ... 28 | 29 | 30 | ✅ Done. 31 | Your projects and infrastructure have been successfully created. Here are some useful links and commands to get you started: 32 | zero-aws-eks-stack: 33 | - Repository URL: github.com/myapp-org/infrastructure 34 | - To see your kubernetes clusters, run: 'kubectl config get-contexts' 35 | - To switch to a cluster, use the following commands: 36 | - for production use: kubectl config use-context arn:aws:eks:us-west-2:123456789:cluster/myapp-infra-production-us-west-2 37 | 38 | - To inspect the selected cluster, run 'kubectl get node,service,deployment,pods' 39 | zero-frontend-react: 40 | - Repository URL: github.com/myapp-org/frontend 41 | - Deployment Pipeline URL: https://app.circleci.com/pipelines/github/myapp-org/frontend 42 | - Production Landing Page: app.commitzero.com 43 | 44 | zero-backend-go: 45 | - Repository URL: github.com/myapp-org/backend-service 46 | - Deployment Pipeline URL: https://app.circleci.com/pipelines/github/myapp-org/backend-service 47 | - Production API: api.commitzero.com 48 | ``` 49 | 50 | ***Your stack is now up and running, follow the links in your terminal to visit your application 🎉*** 51 | -------------------------------------------------------------------------------- /doc-site/docs/getting-started/zero-create.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: zero create 3 | sidebar_label: zero create 4 | sidebar_position: 4 5 | --- 6 | 7 | The `zero create` command renders the infrastructure modules you've configured into your project folder and pushes your code to GitHub. 8 | 9 | ```shell 10 | # Template the selected modules and configuration specified in zero-project.yml and push to the repository. 11 | $ cd zero-init # change your working dir to YOUR_PROJECT_NAME 12 | $ zero create 13 | 14 | ## Sample Output 15 | 🕰 Fetching Modules 16 | 📝 Rendering Modules 17 | Finished templating : backend-service/.circleci/README.md 18 | ✅ Finished templating : backend-service/.circleci/config.yml 19 | ✅ Finished templating : backend-service/.gitignore 20 | ... 21 | ... 22 | ✅ Finished templating : infrastructure/terraform/modules/vpc/versions.tf 23 | ⬆ Done Rendering - committing repositories to version control. 24 | ✅ Repository created: github.com/myapp-org/infrastructure 25 | ✅ Repository created: github.com/myapp-org/backend-service 26 | ✅ Repository created: github.com/myapp-org/frontend 27 | ✅ Done - run zero apply to create any required infrastructure or execute any other remote commands to prepare your environments. 28 | 29 | 30 | ``` 31 | 32 | 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. 33 | -------------------------------------------------------------------------------- /doc-site/docs/getting-started/zero-init.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: zero init 3 | sidebar_label: zero init 4 | sidebar_position: 3 5 | --- 6 | 7 | 8 | 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` 9 | 10 | ```shell 11 | # To create and customize a new project you run 12 | $ zero init 13 | 14 | ## Sample project initialization 15 | ✔ Project Name: myapp-infra 16 | 🎉 Initializing project 17 | ✔ EKS + Go + React + Gatsby 18 | ✔ Should the created projects be checked into github automatically? (y/n): y 19 | ✔ What's the root of the github org to create repositories in?: github.com/myapp-org 20 | ✔ Existing AWS Profiles 21 | ✔ default 22 | 23 | Github personal access token: used for creating repositories for your project 24 | Requires the following permissions: [repo::public_repo, admin::orgread:org] 25 | The token can be created at https://github.com/settings/tokens 26 | ✔ Github Personal Access Token with access to the above organization: 27 | 28 | CircleCI api token: used for setting up CI/CD for your project 29 | The token can be created at https://app.circleci.com/settings/user/tokens 30 | ✔ Circleci api key for CI/CD: 31 | ✔ us-west-2 32 | ✔ Production Root Host Name (e.g. mydomain.com) - this must be the root of the chosen domain, not a subdomain.: commitzero.com 33 | ✔ Production Frontend Host Name (e.g. app.): app. 34 | ✔ Production Backend Host Name (e.g. api.): api. 35 | ✔ Staging Root Host Name (e.g. mydomain-staging.com) - this must be the root of the chosen domain, not a subdomain.: commitzero-stage.com 36 | ✔ Staging Frontend Host Name (e.g. app.): app. 37 | ✔ Staging Backend Host Name (e.g. api.): api. 38 | ✔ What do you want to call the zero-aws-eks-stack project?: infrastructure 39 | ✔ What do you want to call the zero-backend-go project?: backend-service 40 | ✔ What do you want to call the zero-frontend-react project?: frontend 41 | 42 | ``` 43 | 44 | 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`. 45 | -------------------------------------------------------------------------------- /doc-site/docs/reference/learning-resources.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Learning Resources 3 | sidebar_label: Learning Resources 4 | sidebar_position: 3 5 | --- 6 | 7 | 8 | ### AWS 9 | - [Getting started with AWS](https://aws.amazon.com/getting-started/) 10 | 11 | ### Kubernetes 12 | - [Kubernetes Basics](https://kubernetes.io/docs/tutorials/kubernetes-basics/) 13 | - [Kubernetes Training and Certification](https://kubernetes.io/training/) 14 | 15 | ### Terraform 16 | - [Getting started with Terraform in AWS](https://learn.hashicorp.com/collections/terraform/aws-get-started) 17 | 18 | ### Golang 19 | - [A Tour of Go](https://tour.golang.org) 20 | 21 | ### Node.js 22 | - [Getting started with Node.js](https://nodejs.org/en/docs/guides/getting-started-guide/) 23 | - [Getting started with Express](https://expressjs.com/en/starter/installing.html) 24 | - [Getting started with Apollo GraphQL](https://www.apollographql.com/docs/apollo-server/getting-started/) 25 | 26 | ### React 27 | - [Getting started with React](https://reactjs.org/docs/getting-started.html) 28 | -------------------------------------------------------------------------------- /doc-site/docs/reference/project-definition.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Project Definition 3 | sidebar_label: Project Definition 4 | sidebar_position: 1 5 | --- 6 | 7 | ### `zero-project.yml` 8 | 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. 9 | 10 | _Note: This file contains credentials, so make sure it is not shared with others._ 11 | 12 | | Parameters | Type | Description | 13 | |--------------------------|--------------|------------------------------------------------| 14 | | `name` | string | name of the project | 15 | | `shouldPushRepositories` | boolean | whether to push the modules to version control | 16 | | `modules` | map(modules) | a map containing modules of your project | 17 | 18 | 19 | ### Modules 20 | | Parameters | Type | Description | 21 | |--------------|-----------------|-------------------------------------------------------------------------| 22 | | `parameters` | map(string) | key-value map of all the parameters to run the module | 23 | | `files` | File | Stores information such as source-module location and destination | 24 | | `dependsOn` | list(string) | a list of dependencies that should be fulfilled before this module | 25 | | `conditions` | list(condition) | conditions to apply while templating out the module based on parameters | 26 | 27 | ### Condition 28 | | Parameters | Type | Description | 29 | |--------------|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------| 30 | | `action` | enum(string) | type of condition, currently supports [`ignoreFile`] | 31 | | `matchField` | string | Allows you to condition prompt based on another parameter's value | 32 | | `WhenValue` | string | Matches for this value to satisfy the condition | 33 | | `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 | 34 | -------------------------------------------------------------------------------- /doc-site/docs/reference/working-on-zero.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Working on Zero 3 | sidebar_label: Working on Zero 4 | sidebar_position: 3 5 | --- 6 | 7 | ### Building the tool 8 | 9 | ```shell 10 | $ git clone git@github.com:commitdev/zero.git 11 | $ cd zero && make build 12 | ``` 13 | 14 | ### Releasing a new version on GitHub and Brew 15 | 16 | We are using a tool called `goreleaser` which you can get from brew if you're on MacOS: 17 | `brew install goreleaser` 18 | 19 | After you have the tool, you can follow these steps: 20 | ``` 21 | export GITHUB_TOKEN= 22 | git tag -s -a -m "Some message about this release" 23 | git push origin 24 | goreleaser release 25 | ``` -------------------------------------------------------------------------------- /doc-site/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@docusaurus/types').DocusaurusConfig} */ 2 | const { stylesheets, misc } = require('@commitdev/zero-doc-site-common-elements'); 3 | 4 | const siteUrl = process.env.BUILD_DOMAIN ? `https://${process.env.BUILD_DOMAIN}` : 'https://staging.getzero.dev'; 5 | const baseUrl = '/'; 6 | const repositoryName = 'zero'; 7 | 8 | module.exports = { 9 | title: 'Zero', 10 | tagline: 'Opinionated infrastructure to take you from idea to production on day one', 11 | url: siteUrl, 12 | baseUrl, 13 | ...misc(), 14 | projectName: repositoryName, 15 | themeConfig: { 16 | colorMode: { 17 | defaultMode: 'dark', 18 | }, 19 | navbar: { 20 | logo: { 21 | alt: 'Zero Logo', 22 | src: 'img/zero.svg', 23 | }, 24 | items: [ 25 | { 26 | to: '/docs/zero/about/overview', 27 | label: 'Docs', 28 | className: 'header-docs-link header-logo-24', 29 | position: 'right' 30 | }, 31 | { 32 | href: 'https://slack.getzero.dev', 33 | label: 'Slack', 34 | className: 'header-slack-link header-logo-24', 35 | position: 'right', 36 | }, 37 | { 38 | href: 'https://github.com/commitdev/zero', 39 | label: 'Github', 40 | className: 'header-github-link header-logo-24', 41 | position: 'right', 42 | }, 43 | ], 44 | }, 45 | footer: { 46 | links: [ 47 | { 48 | items: [ 49 | { 50 | to: '/docs/zero/about/overview', 51 | label: 'Docs', 52 | className: 'header-docs-link header-logo-24', 53 | position: 'right' 54 | }, 55 | { 56 | href: 'https://slack.getzero.dev', 57 | label: 'Slack', 58 | className: 'header-slack-link header-logo-24', 59 | position: 'right', 60 | }, 61 | { 62 | href: 'https://github.com/commitdev/zero', 63 | label: 'Github', 64 | className: 'header-github-link header-logo-24', 65 | position: 'right', 66 | }, 67 | ], 68 | }, 69 | ], 70 | }, 71 | }, 72 | presets: [ 73 | [ 74 | '@docusaurus/preset-classic', 75 | { 76 | docs: { 77 | sidebarPath: require.resolve('./sidebars.js'), 78 | path: 'docs', 79 | routeBasePath: 'docs/zero/', 80 | include: ['**/*.md', '**/*.mdx'], 81 | editUrl: 'https://github.com/commitdev/zero/blob/main/doc-site/', 82 | }, 83 | theme: { 84 | customCss: require.resolve('./src/css/custom.css'), 85 | }, 86 | gtag: { 87 | trackingID: 'G-6FN66NMDES', 88 | }, 89 | debug: true, 90 | }, 91 | ], 92 | ], 93 | plugins: [ 94 | 'docusaurus-plugin-sass' 95 | ], 96 | stylesheets: stylesheets(), 97 | }; 98 | -------------------------------------------------------------------------------- /doc-site/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/logo.png -------------------------------------------------------------------------------- /doc-site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "doc-site", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@commitdev/zero-doc-site-common-elements": "0.0.7", 18 | "@docusaurus/core": "2.2.0", 19 | "@docusaurus/preset-classic": "2.0.0-beta.3", 20 | "@mdx-js/react": "^1.6.21", 21 | "@svgr/webpack": "^5.5.0", 22 | "clsx": "^1.1.1", 23 | "docusaurus-plugin-sass": "^0.2.1", 24 | "file-loader": "^6.2.0", 25 | "react": "^17.0.1", 26 | "react-dom": "^17.0.1", 27 | "sass": "^1.35.1", 28 | "url-loader": "^4.1.1" 29 | }, 30 | "browserslist": { 31 | "production": [ 32 | ">0.5%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 1 chrome version", 38 | "last 1 firefox version", 39 | "last 1 safari version" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /doc-site/sidebars.js: -------------------------------------------------------------------------------- 1 | const config = require('./docusaurus.config'); 2 | const { sidebarsNavModules } = require('@commitdev/zero-doc-site-common-elements'); 3 | 4 | module.exports = { 5 | zero: [ 6 | { 7 | "About": [{ 8 | type: 'autogenerated', 9 | dirName: 'about', 10 | }], 11 | "Getting started": [{ 12 | type: 'autogenerated', 13 | dirName: 'getting-started', 14 | }], 15 | "Concepts": [{ 16 | type: 'autogenerated', 17 | dirName: 'concepts', 18 | }], 19 | "Reference": [{ 20 | type: 'autogenerated', 21 | dirName: 'reference', 22 | }], 23 | }, 24 | sidebarsNavModules(config), 25 | ] 26 | } 27 | 28 | -------------------------------------------------------------------------------- /doc-site/src/components/HomepageFeatures.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import styles from './HomepageFeatures.module.scss'; 4 | 5 | const FeatureList = [ 6 | { 7 | title: 'Reliable', 8 | Svg: require('../../static/img/icons/attr-reliable.svg').default, 9 | description: ( 10 | <> 11 | Fault-tolerant infrastructure. Production workloads will be 12 | highly available, with traffic load balanced to multiple 13 | instances of your application. All the infrastructure is 14 | represented with code to be reproducible and easy to configure. 15 | 16 | ), 17 | }, 18 | { 19 | title: 'Scalable', 20 | Svg: require('../../static/img/icons/attr-scalable.svg').default, 21 | description: ( 22 | <> 23 | Your system will scale automatically based on your application’s 24 | needs. For frontend assets, using a CDN will ensure availability 25 | at global scale. 26 | 27 | ), 28 | }, 29 | { 30 | title: 'Secure', 31 | Svg: require('../../static/img/icons/attr-secure.svg').default, 32 | description: ( 33 | <> 34 | All your systems will follow security best practices backed up 35 | by multiple security audits and penetration tests, and will be 36 | properly configured to limit access to private networks, 37 | secrets, and data. Bullet-proof your application by default 38 | using existing, tested tools. 39 | 40 | ), 41 | }, 42 | ]; 43 | 44 | const Feature = ({Svg, title, description}) => ( 45 |
46 |
47 | 48 |
49 |
50 |

{title}

51 |

{description}

52 |
53 |
54 | ) 55 | 56 | export default function HomepageFeatures() { 57 | return (<> 58 |
59 | 60 |

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

61 |
62 | {FeatureList.map((props, idx) => ( 63 | 64 | ))} 65 |
66 |
67 | ) 68 | } 69 | -------------------------------------------------------------------------------- /doc-site/src/components/HomepageFeatures.module.scss: -------------------------------------------------------------------------------- 1 | .features { 2 | align-items: center; 3 | @media only screen and (max-width: 641px) { 4 | padding-left: 1rem; 5 | padding-right: 1rem; 6 | } 7 | 8 | .title { 9 | color: white; 10 | font-weight: 400; 11 | text-transform: inherit; 12 | max-width: 34rem; 13 | } 14 | @media screen and (max-width: 768px) { 15 | .title { 16 | padding: 0 2rem; 17 | } 18 | } 19 | 20 | .row { 21 | justify-content: center; 22 | } 23 | 24 | &>div{ 25 | row-gap: 3rem; 26 | } 27 | 28 | h3 { 29 | line-height: 250%; 30 | color: var(--ifm-color-secondary); 31 | margin-bottom: 1rem; 32 | } 33 | 34 | p { 35 | color: white; 36 | font-size: 0.9rem; 37 | padding: 0 calc(var(--ifm-spacing-horizontal)*2); 38 | } 39 | 40 | .featureSvg { 41 | height: 2.5rem; 42 | } 43 | .featureSvg path { 44 | fill: var(--ifm-color-secondary); 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /doc-site/src/components/HomepageOfferings.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import styles from './HomepageOfferings.module.scss'; 3 | 4 | const offerings = [ 5 | { 6 | logo: require('../../static/img/icons/offering-infra.svg').default, 7 | label: 'Infrastructure', 8 | tools: [ 9 | { 10 | name: 'Terraform', 11 | logo: 'img/tools/terraform.png', 12 | url: 'https://terraform.io', 13 | }, 14 | { 15 | name: 'Kubernetes', 16 | logo: 'img/tools/kubernetes.png', 17 | url: 'https://kubernetes.io/', 18 | noCrop: true, 19 | }, 20 | { 21 | name: 'Amazon Web Services', 22 | logo: 'img/tools/aws.png', 23 | url: 'https://aws.amazon.com/', 24 | }, 25 | { 26 | name: 'Cert Manager', 27 | logo: 'img/tools/cert-manager.png', 28 | url: 'https://cert-manager.io/docs/', 29 | }, 30 | { 31 | name: 'External DNS', 32 | logo: 'img/tools/external-dns.png', 33 | url: 'https://github.com/kubernetes-sigs/external-dns', 34 | }, 35 | { 36 | name: 'Wireguard', 37 | logo: 'img/tools/wireguard.png', 38 | url: 'https://www.wireguard.com/', 39 | }, 40 | { 41 | name: 'Prometheus', 42 | logo: 'img/tools/prometheus.png', 43 | url: 'https://prometheus.io/', 44 | }, 45 | { 46 | name: 'Grafana', 47 | logo: 'img/tools/grafana.png', 48 | url: 'https://grafana.com/', 49 | }, 50 | ] 51 | }, 52 | { 53 | logo: require('../../static/img/icons/offering-cicd.svg').default, 54 | label: 'CI/CD', 55 | tools: [ 56 | { 57 | name: 'Github Actions', 58 | logo: 'img/tools/github-actions.svg', 59 | noCrop: true, 60 | url: 'https://github.com/features/actions', 61 | }, 62 | { 63 | name: 'CircleCI', 64 | logo: 'img/tools/circleci.png', 65 | url: 'https://circleci.com', 66 | }, 67 | { 68 | name: 'Docker', 69 | logo: 'img/tools/docker.png', 70 | url: 'https://docker.com/', 71 | }, 72 | { 73 | name: 'AWS ECR', 74 | logo: 'img/tools/ecr.png', 75 | url: 'https://aws.amazon.com/ecr/', 76 | }, 77 | ] 78 | }, 79 | { 80 | logo: require('../../static/img/icons/offering-frontend.svg').default, 81 | label: 'FRONTEND', 82 | tools: [ 83 | { 84 | name: 'React js', 85 | logo: 'img/tools/react.png', 86 | url: 'https://reactjs.org', 87 | }, 88 | { 89 | name: 'AWS S3', 90 | logo: 'img/tools/s3.png', 91 | url: 'https://aws.amazon.com/s3/', 92 | }, 93 | { 94 | name: 'AWS Cloudfront', 95 | logo: 'img/tools/cloudfront.png', 96 | url: 'https://aws.amazon.com/cloudfront/', 97 | }, 98 | { 99 | name: 'ECMAScript 2018', 100 | logo: 'img/tools/js.png', 101 | url: 'https://www.w3schools.com/js/js_2018.asp', 102 | }, 103 | ] 104 | }, 105 | { 106 | logo: require('../../static/img/icons/offering-backend.svg').default, 107 | label: 'BACKEND', 108 | tools: [ 109 | { 110 | name: 'Golang', 111 | logo: 'img/tools/golang.png', 112 | url: 'https://golang.org', 113 | }, 114 | { 115 | name: 'Node.js', 116 | logo: 'img/tools/nodejs.png', 117 | url: 'https://nodejs.org', 118 | noCrop: true, 119 | }, 120 | { 121 | name: 'Open ID Connect', 122 | logo: 'img/tools/openid.png', 123 | url: 'https://openid.net/connect/', 124 | }, 125 | { 126 | name: 'Ory Kratos & Oathkeeper', 127 | logo: 'img/tools/ory.png', 128 | url: 'https://www.ory.sh/kratos/docs/', 129 | }, 130 | { 131 | name: 'Telepresence', 132 | logo: 'img/tools/telepresence.png', 133 | url: 'https://www.telepresence.io/', 134 | noCrop: true, 135 | }, 136 | { 137 | name: 'Stripe', 138 | logo: 'img/tools/stripe.png', 139 | url: 'https://stripe.com', 140 | noCrop: true, 141 | }, 142 | ] 143 | }, 144 | ] 145 | 146 | const Offerings = ({data, active, clickHandler}) => ( 147 |
148 |
149 | { 150 | data.map((i, idx) => 151 | 158 | ) 159 | } 160 |
161 | 162 |
163 | i.label == active).tools } 165 | /> 166 |
167 |
168 | ) 169 | 170 | const Discipline = ({logo: LogoSvg, label, clickHandler, active}) => ( 171 |
clickHandler({ active: label})} 174 | > 175 | 176 |

{label}

177 |
178 | ) 179 | 180 | const ToolBox = ({data}) =>
    181 | { 182 | data.map((tool, idx) => 183 | 184 | ) 185 | } 186 |
187 | 188 | const Tool = ({tool, idx}) => (
  • 189 | 190 | 191 |

    {tool.name}

    192 |
    193 |
  • ) 194 | 195 | export default function FeaturedOfferings() { 196 | const title = "What do you get out of the box ?" 197 | const [state, setState] = useState({ 198 | active: "Infrastructure", 199 | }) 200 | 201 | return
    202 |

    {title}

    203 | 204 |
    205 | } 206 | -------------------------------------------------------------------------------- /doc-site/src/components/HomepageOfferings.module.scss: -------------------------------------------------------------------------------- 1 | .offerings_container{ 2 | text-align: center; 3 | padding: 9rem 8rem 5rem; 4 | margin: 0 auto; 5 | min-height: 53rem; 6 | background-color: #E8EDF4; 7 | color: var(--ifm-landing-page-inverse-font-color); 8 | 9 | 10 | .offering_box { 11 | display: flex; 12 | column-gap: 5rem; 13 | flex-wrap: wrap; 14 | justify-content: center; 15 | } 16 | } 17 | 18 | .left_box{ 19 | flex: 0 1 270px; 20 | margin: 2rem 0px; 21 | display: flex; 22 | flex-direction: column; 23 | column-gap: 1rem; 24 | row-gap: 1rem; 25 | 26 | .discipline { 27 | display: flex; 28 | height: 5rem; 29 | column-gap: 1rem; 30 | position: relative; 31 | font-weight: bold; 32 | text-transform: uppercase; 33 | flex: 0 0 auto; 34 | cursor: pointer; 35 | border-radius: 12px; 36 | background: #D7DEE8; 37 | padding-right: 1rem; 38 | 39 | .logo { 40 | flex: 1; 41 | height: 3rem; 42 | align-self: center; 43 | margin: 1rem; 44 | } 45 | 46 | .discipline_name { 47 | flex: 4; 48 | align-self: center; 49 | line-height: 0; 50 | margin: 0; 51 | text-align: left; 52 | font-size: 1rem; 53 | } 54 | &:hover:not(.discipline_active) .discipline_name{ 55 | color: var(--ifm-color-secondary); 56 | } 57 | } 58 | 59 | .discipline_active{ 60 | background: var(--ifm-color-secondary); 61 | color: var(--ifm-color-dark-active); 62 | 63 | .logo path{ 64 | fill: var(--ifm-color-dark-active); 65 | } 66 | 67 | &:after { 68 | content: ""; 69 | width: 5rem; 70 | right: -5rem; 71 | height: 0px; 72 | align-self: center; 73 | position: absolute; 74 | border-top: 4px dashed var(--ifm-color-secondary); 75 | } 76 | } 77 | } 78 | 79 | .right_box{ 80 | border: 4px dashed var(--ifm-color-secondary); 81 | border-radius: 12px; 82 | display: flex; 83 | padding: 2rem; 84 | 85 | ul{ 86 | height: 100%; 87 | width: 100%; 88 | align-self: center; 89 | margin: 0; 90 | padding: 0; 91 | display: flex; 92 | flex-direction: column; 93 | row-gap: 1.2rem; 94 | 95 | li { 96 | list-style: none; 97 | justify-self: stretch; 98 | flex: 1 1 40px; 99 | 100 | a{ 101 | display: flex; 102 | column-gap: 1rem; 103 | text-decoration: none; 104 | color: var(--ifm-landing-page-inverse-font-color); 105 | font-weight: bold; 106 | 107 | &:hover{ 108 | color: var(--ifm-color-secondary); 109 | } 110 | } 111 | 112 | img{ 113 | width: 32px; 114 | border-radius: 50%; 115 | padding: 0; 116 | 117 | &.no-crop{ 118 | border-radius: 0; 119 | } 120 | } 121 | 122 | p{ 123 | flex: 5; 124 | text-align: left; 125 | font-size: 1.1rem; 126 | margin: 0; 127 | align-self: center; 128 | font-family: 'Montserrat'; 129 | font-weight: 600; 130 | white-space: nowrap; 131 | } 132 | @media only screen and (max-width: 400px) { 133 | p { 134 | font-size: 0.8rem; 135 | } 136 | } 137 | } 138 | } 139 | } 140 | 141 | /** 142 | box becomes stacked when screen is small 143 | and the dotted line is vertical instead of horizontal 144 | **/ 145 | @media screen and (max-width: 967px) { 146 | .offerings_container { 147 | padding: 5rem 2rem; 148 | min-height: 62rem; 149 | } 150 | .left_box { 151 | justify-content: center; 152 | flex-direction: row; 153 | flex-wrap: wrap; 154 | flex: 1 1 auto; 155 | 156 | .discipline { 157 | 158 | .logo { 159 | flex: 1 1 33px; 160 | margin: 0.5rem; 161 | } 162 | } 163 | .discipline_active{ 164 | order: 1; 165 | flex: 4 4 100%; 166 | margin: 0 16%; 167 | 168 | &:after { 169 | content: ""; 170 | width: 0px; 171 | height: 2rem; 172 | right: 50%; 173 | position: absolute; 174 | border-right: var(--ifm-color-secondary) dashed 4px; 175 | bottom: -2rem; 176 | box-sizing: border-box; 177 | overflow: visible; 178 | } 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /doc-site/src/components/HomepageTrustedBy.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './HomepageTrustedBy.module.scss'; 3 | 4 | const trustedByData = [ 5 | { 6 | img: "img/partners/planworth.png", 7 | src: "https://www.planworth.co/", 8 | }, 9 | { 10 | img: "img/partners/patch.png", 11 | src: "https://www.patch.io/", 12 | }, 13 | { 14 | img: "img/partners/atlasone.png", 15 | src: "https://www.atlasone.ca/", 16 | }, 17 | { 18 | img: "img/partners/placeholder.png", 19 | src: "https://placeholder.co/", 20 | }, 21 | ] 22 | 23 | const Carousel = ({data}) => ( 24 |
      25 | { 26 | data.map((item, idx) => ( 27 |
    • 28 | 29 | 30 | 31 |
    • 32 | )) 33 | } 34 |
    35 | ) 36 | 37 | export default function TrustedByCarousel() { 38 | return
    39 |

    Trusted By

    40 | 41 |
    42 | } 43 | -------------------------------------------------------------------------------- /doc-site/src/components/HomepageTrustedBy.module.scss: -------------------------------------------------------------------------------- 1 | h3.title { 2 | letter-spacing: 0.1rem; 3 | text-align: center; 4 | font-weight: 400; 5 | font-family: "Montserrat"; 6 | } 7 | 8 | .trusted { 9 | display: flex; 10 | flex-direction: row; 11 | flex-wrap: wrap; 12 | justify-content: center; 13 | padding: 0; 14 | row-gap: 2em; 15 | 16 | li{ 17 | list-style: none; 18 | margin: 0 1.25rem; 19 | height: 100px; 20 | width: 200px; 21 | background-color: rgba(196, 196, 196, 0.5); 22 | display: flex; 23 | border-radius: 5px; 24 | padding: 2rem; 25 | 26 | &:hover{ 27 | background-color: rgba(220,235,245, 0.8); 28 | } 29 | 30 | a{ 31 | align-self: center; 32 | line-height: 0; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /doc-site/src/components/HomepageVideo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './HomepageVideo.module.scss'; 3 | 4 | export default function FeatureVideo () { 5 | return
    6 | 7 |
    8 | } 9 | -------------------------------------------------------------------------------- /doc-site/src/components/HomepageVideo.module.scss: -------------------------------------------------------------------------------- 1 | .video { 2 | width: 100%; 3 | text-align: center; 4 | 5 | iframe { 6 | max-width: 65%; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /doc-site/src/components/HomepageWhyZero.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import clsx from 'clsx'; 3 | import styles from './HomepageWhyZero.module.scss'; 4 | 5 | const reasons = [ 6 | { 7 | logo: require('../../static/img/icons/reason-diamond.svg').default, 8 | title: 'Quality', 9 | text: `Like the best DevOps engineer you’ve ever met - except open source and free.`, 10 | details: [ 11 | `The devops skill gap is real. Why spend precious time picking up unfamiliar tech, 12 | making wrong choices that result in costly refactoring or rebuilding in the future, 13 | and missing tools and best practices that would speed up your product iteration?`, 14 | `Get real-time support for all your questions from Zero’s community.` 15 | ] 16 | }, 17 | { 18 | logo: require('../../static/img/icons/reason-clockwise.svg').default, 19 | title: 'Speed', 20 | text: `Just as fast as other tools like Heroku to get up and running.`, 21 | details: [ 22 | `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.`, 23 | `We provide constant updates and new modules that you can pull in on an ongoing basis.`, 24 | ] 25 | }, 26 | { 27 | logo: require('../../static/img/icons/reason-key.svg').default, 28 | title: 'Ownership', 29 | text: `You own 100% of the code. No lock-in!`, 30 | details: [ 31 | `Everything built by Zero is yours. It’s your code to change or migrate off at any point.`, 32 | `Cloud application hosting tools are built to lock you in and don’t scale. `, 33 | `Infrastructure is created in your cloud provider account. You can customize as much 34 | as you like with no strings attached. You control how much you spend.` 35 | ] 36 | } 37 | ]; 38 | 39 | const Reasons = ({ data, expanded, setExpanded }) => ( 40 |
    41 | { 42 | data.map((i, idx) => ( 43 |
    44 | 45 |

    {i.title}

    46 | 47 |

    {i.text}

    48 | {expanded &&
      {i.details.map(content=>
    • {content}
    • )}
    } 49 |
    50 | )) 51 | } 52 |
    53 | ) 54 | 55 | export default function FeatureWhyZero () { 56 | const [expanded, setExpanded] = useState(false) 57 | const title = "Why is Zero good for startups ?" 58 | return
    59 |

    60 | {title} 61 |

    62 | 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. 63 |

    64 | 65 | 66 | 69 |
    70 | } 71 | -------------------------------------------------------------------------------- /doc-site/src/components/HomepageWhyZero.module.scss: -------------------------------------------------------------------------------- 1 | .reasons_container{ 2 | text-align: center; 3 | background: white; 4 | color: var(--ifm-landing-page-inverse-font-color); 5 | @media only screen and (max-width: 641px) { 6 | padding-left: 3rem; 7 | padding-right: 3rem; 8 | } 9 | 10 | .title { 11 | margin-bottom: 5rem; 12 | letter-spacing: 0.05rem; 13 | } 14 | .subtitle { 15 | text-transform: none; 16 | margin: 1rem auto; 17 | font-family: 'lato'; 18 | font-weight: 400; 19 | max-width: 34rem; 20 | letter-spacing: normal; 21 | } 22 | .expand { 23 | margin-top: 2rem; 24 | 25 | a{ 26 | display: flex; 27 | justify-content: center; 28 | 29 | &:active, &:link, &:visited{ 30 | text-decoration: none; 31 | color: var(--ifm-landing-page-inverse-font-color); 32 | } 33 | &:after { 34 | align-self: center; 35 | margin: 0 0.5rem; 36 | content: ""; 37 | width: 18px; 38 | height: 18px; 39 | background: url(../../static/img/icons/icon-plus.svg); 40 | background-size: cover; 41 | } 42 | } 43 | &.expanded a{ 44 | &:after { 45 | background: url(../../static/img/icons/icon-minus.svg); 46 | background-size: cover; 47 | } 48 | } 49 | } 50 | } 51 | 52 | .reasons { 53 | display: flex; 54 | flex-direction: row; 55 | row-gap: 3rem; 56 | justify-content: center; 57 | column-gap: 2rem; 58 | } 59 | 60 | .reason { 61 | max-width: 10rem; 62 | 63 | .title{ 64 | color: var(--ifm-color-secondary); 65 | text-transform: uppercase; 66 | margin: 1rem; 67 | } 68 | 69 | .description{ 70 | min-height: 2.5rem; 71 | font-size: 0.9rem; 72 | align-self: center; 73 | } 74 | ul.description { 75 | margin-top: 2rem; 76 | padding-left: 1rem; 77 | } 78 | .description li{ 79 | text-align: left; 80 | } 81 | 82 | .reason_logo { 83 | height: 3rem; 84 | flex: 0 0 3rem; 85 | align-self: center; 86 | 87 | path { 88 | stroke: var(--ifm-color-secondary); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /doc-site/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary-dark: rgb(33, 175, 144); 10 | --ifm-color-primary-darker: rgb(31, 165, 136); 11 | --ifm-color-primary-darkest: rgb(26, 136, 112); 12 | --ifm-color-primary-light: rgb(70, 203, 174); 13 | --ifm-color-primary-lighter: rgb(102, 212, 189); 14 | --ifm-color-primary-lightest: rgb(146, 224, 208); 15 | --ifm-navbar-link-hover-color: rgb(33, 175, 144); 16 | --ifm-code-font-size: 95%; 17 | --ifm-footer-padding-vertical: 20px; 18 | --ifm-footer-padding-horizontal: 10px; 19 | --ifm-footer-background-color: transparent; 20 | --ifm-color-secondary: rgb(255, 106, 185); 21 | --ifm-button-cta-background: linear-gradient(294.55deg, #F2BD6D 17.88%, #F17F84 65.95%, #FF3EA7 93.96%); 22 | --ifm--hero--text-background-gradient: linear-gradient(234.45deg, #12C6FF 0%, #6184C9 68.23%); 23 | --ifm-landing-page-inverse-font-color: #333; 24 | --ifm-color-dark-active: rgba(5, 6, 55, 1); 25 | --ifm-spacing-vertical: 0px; 26 | --ifm-navbar-sidebar-width: 15rem; 27 | --ifm-alert-padding-vertical: 1rem; 28 | --ifm-alert-padding-horizontal: 1rem; 29 | } 30 | 31 | html[data-theme='dark'] { 32 | --ifm-color-primary: #edc281; 33 | --ifm-font-color-base-inverse: white; 34 | --ifm-navbar-background-color: transparent; 35 | --ifm-background-color: linear-gradient(90deg, rgba(15, 16, 17, 1) 0%, rgba(1, 2, 66, 1) 100%); 36 | --ifm-color-info: rgba(255, 166, 0, 0.4); 37 | --ifm-menu-color-active: #edc281; 38 | --ifm-navbar-link-hover-color: #edc281; 39 | --ifm-link-color: #edc281; 40 | --ifm-code-background: rgba(140,140,140,0.5); 41 | } 42 | 43 | html[data-theme='light'] { 44 | --ifm-heading-color: navy; 45 | --ifm-font-color-base-inverse: navy; 46 | --ifm-color-secondary: #6184C9; 47 | --ifm-background-color: #fefefe; 48 | } 49 | html[data-theme='light'] .navbar--fixed-top, html[data-theme='light'] .navbar-sidebar__brand{ 50 | background: linear-gradient(90deg, rgba(15, 16, 17, 0.3) 0%, rgba(1, 2, 66, 0.4) 100%); 51 | } 52 | 53 | /** PAGE BACKGROUND **/ 54 | html{ 55 | background: var(--ifm-background-color); 56 | } 57 | 58 | .hero--primary{ 59 | background: none; 60 | } 61 | 62 | /** FONTS **/ 63 | .navbar { 64 | font-family: 'lato'; 65 | font-weight: 900; 66 | } 67 | .menu__list .menu__link--sublist { 68 | font-weight: 900; 69 | } 70 | .menu__list .menu__list-item { 71 | font-weight: 400; 72 | } 73 | .featured-sections h3 , .featured-sections h2, .featured-sections h4{ 74 | font-family: 'Montserrat'; 75 | font-weight: 800; 76 | } 77 | .button-cta{ 78 | font-family: 'Montserrat'; 79 | font-weight: 700; 80 | } 81 | .description { 82 | font-family: 'lato'; 83 | font-weight: 400; 84 | } 85 | 86 | /** Small screen hamburger menu slideout background **/ 87 | .navbar-sidebar--show .navbar-sidebar { 88 | background-color: var(--ifm-code-background); 89 | } 90 | .navbar-sidebar__brand .navbar__brand { 91 | height: 1rem; 92 | } 93 | .navbar-sidebar__brand .navbar__brand img{ 94 | margin: 1rem; 95 | } 96 | .navbar-sidebar__items .menu__link{ 97 | column-gap: 1rem; 98 | justify-content: flex-start; 99 | } 100 | .navbar__brand img{ 101 | height: 80%; 102 | margin-left: 0.6rem; 103 | } 104 | 105 | /** Docs sidebar - folder name capitalize */ 106 | .menu__link--sublist{ 107 | text-transform: capitalize; 108 | } 109 | 110 | /** FLATTEN FOOTER ITEMS **/ 111 | .footer__items{ 112 | display: flex; 113 | justify-content: flex-end; 114 | } 115 | 116 | .footer__item{ 117 | padding:0 var(--ifm-spacing-horizontal); 118 | } 119 | 120 | @media only screen and (max-width: 400px) { 121 | .footer { 122 | display: none; 123 | } 124 | } 125 | 126 | /** LANDING PAGE SECTION **/ 127 | .featured-sections { 128 | text-align: center; 129 | padding: 6rem 0; 130 | } 131 | 132 | .featured-sections h3, .featured-sections h2 { 133 | text-transform: uppercase; 134 | letter-spacing: 0.05rem; 135 | padding: 0; 136 | margin: 0 auto 3rem auto; 137 | } 138 | 139 | .featured-sections h2 { 140 | margin: 0 auto 5rem auto; 141 | } 142 | 143 | /** 144 | NAV BAR Icons 145 | flattens footer icons 146 | */ 147 | .header-logo-24.navbar__link, .footer__item .header-logo-24 { 148 | display:flex; 149 | line-height: 24px; 150 | text-transform: uppercase; 151 | color: var(--ifm-font-color-base-inverse); 152 | font-weight: 700; 153 | } 154 | 155 | .header-logo-24:hover{ 156 | text-decoration: none; 157 | } 158 | 159 | .header-logo-24:before{ 160 | content: ""; 161 | display: flex; 162 | height: 24px; 163 | width: 24px; 164 | margin: 0 3px; 165 | background-size: cover; 166 | } 167 | 168 | html[data-theme='dark'] .header-github-link:before { 169 | background: url(../../static/img/icons/octocat.svg) no-repeat; 170 | } 171 | 172 | html[data-theme='light'] .header-github-link:before { 173 | background: url(../../static/img/icons/octocat-navy.svg) no-repeat; 174 | } 175 | 176 | html[data-theme='dark'] .header-slack-link:before { 177 | background: url(../../static/img/icons/slack.svg) no-repeat; 178 | } 179 | 180 | html[data-theme='light'] .header-slack-link:before { 181 | background: url(../../static/img/icons/slack-navy.svg) no-repeat; 182 | } 183 | 184 | html[data-theme='dark'] .header-docs-link:before { 185 | background: url(../../static/img/icons/notes.svg) no-repeat; 186 | } 187 | 188 | html[data-theme='light'] .header-docs-link:before { 189 | background: url(../../static/img/icons/notes-navy.svg) no-repeat; 190 | } 191 | 192 | /* alert - note */ 193 | .alert--secondary{ 194 | --ifm-alert-background-color: rgb(200, 212, 226); 195 | --ifm-alert-border-color: rgb(200, 212, 226); 196 | --ifm-alert-color: var(--ifm-color-gray-900); 197 | --ra-admonition-icon-color: var(--ifm-color-gray-900); 198 | } 199 | /* alert - info */ 200 | .alert--info{ 201 | --ifm-alert-background-color: rgb(117, 157, 209); 202 | --ifm-alert-border-color: rgb(117, 157, 209); 203 | --ifm-alert-color: var(--ifm-color-gray-900); 204 | --ra-admonition-icon-color: var(--ifm-color-gray-900); 205 | } 206 | 207 | .docs-main-toc .toc-item { 208 | margin: 2rem 1rem; 209 | } 210 | 211 | .header-logo-24 span svg { 212 | display: none; 213 | } 214 | -------------------------------------------------------------------------------- /doc-site/src/pages/docs/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Redirect } from 'react-router-dom'; 3 | 4 | export default () => 5 | -------------------------------------------------------------------------------- /doc-site/src/pages/docs/zero/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Redirect } from 'react-router-dom'; 3 | 4 | export default () => 5 | -------------------------------------------------------------------------------- /doc-site/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import Layout from '@theme/Layout'; 4 | import Link from '@docusaurus/Link'; 5 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 6 | import styles from './index.module.scss'; 7 | import HomepageFeatures from '../components/HomepageFeatures'; 8 | import HomepageTrustedBy from '../components/HomepageTrustedBy'; 9 | import HomepageVideo from '../components/HomepageVideo'; 10 | import HomepageWhyZero from '../components/HomepageWhyZero'; 11 | import HomepageOfferings from '../components/HomepageOfferings'; 12 | 13 | function HomepageHeader() { 14 | const {siteConfig} = useDocusaurusContext(); 15 | 16 | return ( 17 |
    18 |

    {siteConfig.tagline}

    19 |
    20 | ); 21 | } 22 | 23 | function HomePageCallToAction () { 24 | return
    25 | 28 | Get Started 29 | 30 |
    31 | } 32 | 33 | export default function Home() { 34 | const landingPageOnlyGlobalItemStyle = ` 35 | .navbar { 36 | padding: 2.5rem 0 3.5rem; 37 | box-shadow: none; 38 | background: linear-gradient(90deg, rgba(15, 16, 17, 0.6) 0%, rgba(1, 2, 66, 0.6) 100%); 39 | } 40 | @media only screen and (max-width: 641px) { 41 | .navbar__items--right { 42 | display: none; 43 | } 44 | } 45 | .navbar__inner { 46 | padding: 0 3rem; 47 | } 48 | .navbar__brand img{ 49 | height: 130%; 50 | } 51 | .react-toggle{ 52 | display: none; 53 | } 54 | `; 55 | const {siteConfig} = useDocusaurusContext(); 56 | return ( 57 | 61 | 62 | 63 |
    64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
    72 |
    73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /doc-site/src/pages/index.module.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 4rem 0 2rem; 8 | text-align: center; 9 | 10 | .hero__subtitle { 11 | background: var(--ifm--hero--text-background-gradient); 12 | background-clip: text; 13 | -webkit-background-clip: text; 14 | -webkit-text-fill-color: transparent; 15 | font-weight: 900; 16 | font-size: 2rem; 17 | max-width: 50rem; 18 | margin: 3rem auto; 19 | } 20 | } 21 | 22 | .buttons { 23 | display: flex; 24 | align-items: center; 25 | justify-content: center; 26 | margin: 4rem auto 4rem; 27 | 28 | a { 29 | text-transform: uppercase; 30 | background: var(--ifm-button-cta-background); 31 | border-radius: 25px; 32 | border-color: transparent; 33 | border-style: none; 34 | background-size: 150% 100%; 35 | } 36 | } 37 | 38 | .hiring { 39 | display: flex; 40 | align-items: center; 41 | justify-content: center; 42 | margin: 4rem auto 4rem; 43 | text-align: center; 44 | a { 45 | font-size: 2rem; 46 | font-weight: bold; 47 | color: #ffffff; 48 | } 49 | @media only screen and (max-width: 600px) { 50 | a { 51 | padding: 0 2rem; 52 | } 53 | } 54 | } 55 | 56 | @media screen and (max-width: 966px) { 57 | .heroBanner { 58 | padding: 2rem; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /doc-site/src/theme/DocSidebar/styles.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | :root { 9 | --collapse-button-bg-color-dark: #2e333a; 10 | overflow-x: hidden; 11 | } 12 | 13 | @media (min-width: 997px) { 14 | .sidebar { 15 | display: flex; 16 | flex-direction: column; 17 | max-height: 100vh; 18 | height: 100%; 19 | position: sticky; 20 | top: 0; 21 | padding-top: var(--ifm-navbar-height); 22 | width: var(--doc-sidebar-width); 23 | transition: opacity 50ms ease; 24 | } 25 | 26 | .sidebarWithHideableNavbar { 27 | padding-top: 0; 28 | } 29 | 30 | .sidebarHidden { 31 | opacity: 0; 32 | height: 0; 33 | overflow: hidden; 34 | visibility: hidden; 35 | } 36 | 37 | .sidebarLogo { 38 | display: flex !important; 39 | align-items: center; 40 | margin: 0 var(--ifm-navbar-padding-horizontal); 41 | min-height: var(--ifm-navbar-height); 42 | max-height: var(--ifm-navbar-height); 43 | color: inherit !important; 44 | text-decoration: none !important; 45 | } 46 | 47 | .sidebarLogo img { 48 | margin-right: 0.5rem; 49 | height: 2rem; 50 | } 51 | 52 | .menu { 53 | flex-grow: 1; 54 | padding: 0.5rem; 55 | } 56 | 57 | .menuLinkText { 58 | cursor: initial; 59 | } 60 | 61 | .menuLinkText:hover { 62 | background: none; 63 | } 64 | 65 | .menuWithAnnouncementBar { 66 | margin-bottom: var(--docusaurus-announcement-bar-height); 67 | } 68 | 69 | .collapseSidebarButton { 70 | display: block !important; 71 | background-color: var(--ifm-button-background-color); 72 | height: 40px; 73 | position: sticky; 74 | bottom: 0; 75 | border-radius: 0; 76 | border: 1px solid var(--ifm-toc-border-color); 77 | } 78 | 79 | .collapseSidebarButtonIcon { 80 | transform: rotate(180deg); 81 | margin-top: 4px; 82 | } 83 | html[dir='rtl'] .collapseSidebarButtonIcon { 84 | transform: rotate(0); 85 | } 86 | 87 | html[data-theme='dark'] .collapseSidebarButton { 88 | background-color: var(--collapse-button-bg-color-dark); 89 | } 90 | 91 | html[data-theme='dark'] .collapseSidebarButton:hover, 92 | html[data-theme='dark'] .collapseSidebarButton:focus { 93 | background-color: var(--ifm-color-emphasis-200); 94 | } 95 | } 96 | 97 | .sidebarLogo, 98 | .collapseSidebarButton { 99 | display: none; 100 | } 101 | 102 | .sidebarMenuIcon { 103 | vertical-align: middle; 104 | } 105 | 106 | .sidebarMenuCloseIcon { 107 | display: inline-flex; 108 | justify-content: center; 109 | align-items: center; 110 | height: 24px; 111 | font-size: 1.5rem; 112 | font-weight: var(--ifm-font-weight-bold); 113 | line-height: 0.9; 114 | width: 24px; 115 | } 116 | 117 | :global(.menu__list) :global(.menu__list) { 118 | overflow-y: hidden; 119 | will-change: height; 120 | transition: height var(--ifm-transition-fast) linear; 121 | } 122 | 123 | :global(.menu__list-item--collapsed) :global(.menu__list) { 124 | height: 0 !important; 125 | } 126 | 127 | .menuLinkExternal { 128 | align-items: center; 129 | } 130 | .menuLinkExternal:after { 131 | content: ''; 132 | height: 1.15rem; 133 | width: 1.15rem; 134 | min-width: 1.15rem; 135 | margin: 0 0 0 3%; 136 | 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") 137 | no-repeat; 138 | filter: var(--ifm-menu-link-sublist-icon-filter); 139 | } 140 | -------------------------------------------------------------------------------- /doc-site/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/.nojekyll -------------------------------------------------------------------------------- /doc-site/static/img/docs/zero-check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/docs/zero-check.png -------------------------------------------------------------------------------- /doc-site/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/favicon.ico -------------------------------------------------------------------------------- /doc-site/static/img/icons/attr-modular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /doc-site/static/img/icons/attr-reliable.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /doc-site/static/img/icons/attr-scalable.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /doc-site/static/img/icons/attr-secure.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /doc-site/static/img/icons/icon-minus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /doc-site/static/img/icons/icon-plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /doc-site/static/img/icons/notes-navy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /doc-site/static/img/icons/notes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /doc-site/static/img/icons/octocat-navy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /doc-site/static/img/icons/octocat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /doc-site/static/img/icons/offering-backend.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /doc-site/static/img/icons/offering-cicd.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /doc-site/static/img/icons/offering-frontend.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /doc-site/static/img/icons/offering-infra.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /doc-site/static/img/icons/reason-clockwise.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /doc-site/static/img/icons/reason-diamond.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /doc-site/static/img/icons/reason-key.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /doc-site/static/img/icons/slack-navy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /doc-site/static/img/icons/slack.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /doc-site/static/img/partners/atlasone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/partners/atlasone.png -------------------------------------------------------------------------------- /doc-site/static/img/partners/patch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/partners/patch.png -------------------------------------------------------------------------------- /doc-site/static/img/partners/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/partners/placeholder.png -------------------------------------------------------------------------------- /doc-site/static/img/partners/planworth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/partners/planworth.png -------------------------------------------------------------------------------- /doc-site/static/img/tools/aws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/tools/aws.png -------------------------------------------------------------------------------- /doc-site/static/img/tools/cert-manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/tools/cert-manager.png -------------------------------------------------------------------------------- /doc-site/static/img/tools/circleci.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/tools/circleci.png -------------------------------------------------------------------------------- /doc-site/static/img/tools/cloudfront.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/tools/cloudfront.png -------------------------------------------------------------------------------- /doc-site/static/img/tools/docker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/tools/docker.png -------------------------------------------------------------------------------- /doc-site/static/img/tools/ecr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/tools/ecr.png -------------------------------------------------------------------------------- /doc-site/static/img/tools/eks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/tools/eks.png -------------------------------------------------------------------------------- /doc-site/static/img/tools/external-dns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/tools/external-dns.png -------------------------------------------------------------------------------- /doc-site/static/img/tools/github-actions.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /doc-site/static/img/tools/golang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/tools/golang.png -------------------------------------------------------------------------------- /doc-site/static/img/tools/grafana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/tools/grafana.png -------------------------------------------------------------------------------- /doc-site/static/img/tools/js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/tools/js.png -------------------------------------------------------------------------------- /doc-site/static/img/tools/kubernetes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/tools/kubernetes.png -------------------------------------------------------------------------------- /doc-site/static/img/tools/nodejs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/tools/nodejs.png -------------------------------------------------------------------------------- /doc-site/static/img/tools/openid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/tools/openid.png -------------------------------------------------------------------------------- /doc-site/static/img/tools/ory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/tools/ory.png -------------------------------------------------------------------------------- /doc-site/static/img/tools/prometheus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/tools/prometheus.png -------------------------------------------------------------------------------- /doc-site/static/img/tools/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/tools/react.png -------------------------------------------------------------------------------- /doc-site/static/img/tools/s3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/tools/s3.png -------------------------------------------------------------------------------- /doc-site/static/img/tools/stripe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/tools/stripe.png -------------------------------------------------------------------------------- /doc-site/static/img/tools/telepresence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/tools/telepresence.png -------------------------------------------------------------------------------- /doc-site/static/img/tools/terraform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/tools/terraform.png -------------------------------------------------------------------------------- /doc-site/static/img/tools/wireguard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/doc-site/static/img/tools/wireguard.png -------------------------------------------------------------------------------- /doc-site/static/img/zero.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/commitdev/zero 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go v1.30.12 7 | github.com/buger/goterm v1.0.0 8 | github.com/coreos/go-semver v0.3.0 9 | github.com/gabriel-vasile/mimetype v1.1.1 10 | github.com/google/go-cmp v0.3.1 11 | github.com/google/uuid v1.1.1 12 | github.com/hashicorp/go-getter v1.4.2-0.20200106182914-9813cbd4eb02 13 | github.com/hashicorp/go-version v1.2.1 14 | github.com/hashicorp/golang-lru v0.5.4 // indirect 15 | github.com/hashicorp/terraform v0.12.26 16 | github.com/iancoleman/strcase v0.1.2 17 | github.com/juju/ansiterm v0.0.0-20210706145210-9283cdf370b5 // indirect 18 | github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect 19 | github.com/k0kubun/pp v3.0.1+incompatible 20 | github.com/kyokomi/emoji v2.1.0+incompatible 21 | github.com/logrusorgru/aurora v0.0.0-20191017060258-dc85c304c434 22 | github.com/lunixbochs/vtclean v1.0.0 // indirect 23 | github.com/machinebox/graphql v0.2.2 24 | github.com/manifoldco/promptui v0.8.0 25 | github.com/matryer/is v1.3.0 // indirect 26 | github.com/mattn/go-colorable v0.1.8 // indirect 27 | github.com/mattn/go-isatty v0.0.13 // indirect 28 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect 29 | github.com/sirupsen/logrus v1.6.0 30 | github.com/spf13/cobra v1.1.3 31 | github.com/stretchr/testify v1.7.0 32 | github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae 33 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b // indirect 34 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect 35 | golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f // indirect 36 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect 37 | gopkg.in/yaml.v2 v2.4.0 38 | 39 | ) 40 | 41 | // Tencent cloud unpublished their version v3.0.82 and became v1.0.191 42 | // https://github.com/hashicorp/terraform/issues/29021 43 | replace github.com/tencentcloud/tencentcloud-sdk-go v3.0.82+incompatible => github.com/tencentcloud/tencentcloud-sdk-go v1.0.191 44 | -------------------------------------------------------------------------------- /internal/apply/apply_test.go: -------------------------------------------------------------------------------- 1 | package apply_test 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | 9 | "github.com/commitdev/zero/internal/apply" 10 | "github.com/commitdev/zero/internal/constants" 11 | "github.com/stretchr/testify/assert" 12 | "github.com/termie/go-shutil" 13 | ) 14 | 15 | func TestApply(t *testing.T) { 16 | applyConfigPath := constants.ZeroProjectYml 17 | applyEnvironments := []string{"staging", "production"} 18 | var tmpDir string 19 | 20 | t.Run("Should run apply and execute make on each folder module", func(t *testing.T) { 21 | tmpDir = setupTmpDir(t, "../../tests/test_data/apply/") 22 | err := apply.Apply(tmpDir, applyConfigPath, applyEnvironments) 23 | assert.FileExists(t, filepath.Join(tmpDir, "project1/project.out")) 24 | assert.FileExists(t, filepath.Join(tmpDir, "project2/project.out")) 25 | 26 | content, err := ioutil.ReadFile(filepath.Join(tmpDir, "project1/project.out")) 27 | assert.NoError(t, err) 28 | assert.Equal(t, "foo: bar\nrepo: github.com/commitdev/project1\n", string(content)) 29 | 30 | content, err = ioutil.ReadFile(filepath.Join(tmpDir, "project2/project.out")) 31 | assert.NoError(t, err) 32 | assert.Equal(t, "baz: qux\n", string(content)) 33 | 34 | }) 35 | 36 | t.Run("Modules runs command overides", func(t *testing.T) { 37 | content, err := ioutil.ReadFile(filepath.Join(tmpDir, "project2/check.out")) 38 | assert.NoError(t, err) 39 | assert.Equal(t, "custom check\n", string(content)) 40 | }) 41 | 42 | t.Run("Zero apply honors the envVarName overwrite from module definition", func(t *testing.T) { 43 | content, err := ioutil.ReadFile(filepath.Join(tmpDir, "project1/feature.out")) 44 | assert.NoError(t, err) 45 | assert.Equal(t, "envVarName of viaEnvVarName: baz\n", string(content)) 46 | }) 47 | 48 | t.Run("Modules with failing checks should return error", func(t *testing.T) { 49 | tmpDir = setupTmpDir(t, "../../tests/test_data/apply-failing/") 50 | 51 | err := apply.Apply(tmpDir, applyConfigPath, applyEnvironments) 52 | assert.Regexp(t, "^The following Module check\\(s\\) failed:", err.Error()) 53 | assert.Regexp(t, "Module \\(project1\\)", err.Error()) 54 | assert.Regexp(t, "Module \\(project2\\)", err.Error()) 55 | assert.Regexp(t, "Module \\(project3\\)", err.Error()) 56 | }) 57 | 58 | } 59 | 60 | func setupTmpDir(t *testing.T, exampleDirPath string) string { 61 | var err error 62 | tmpDir := filepath.Join(os.TempDir(), "apply") 63 | 64 | err = os.RemoveAll(tmpDir) 65 | assert.NoError(t, err) 66 | 67 | err = shutil.CopyTree(exampleDirPath, tmpDir, nil) 68 | assert.NoError(t, err) 69 | return tmpDir 70 | } 71 | -------------------------------------------------------------------------------- /internal/condition/condition.go: -------------------------------------------------------------------------------- 1 | // This module is invoked when we do template rendering during "zero create." 2 | // 3 | // Each module can have a "conditions" section in their zero-module.yml that 4 | // specifies a condition in the form: 5 | // 6 | // conditions: 7 | // - action: ignoreFile 8 | // matchField: 9 | // whenValue: 10 | // data: 11 | // - 12 | // 13 | // The structure for this is defined in: 14 | // internal/config/projectconfig/project_config.go. 15 | // The definition is in that file simply to avoid cyclic dependencies; but 16 | // The logic for each type of condition exists here. 17 | // 18 | // See: internal/generate/generate_modules.go 19 | // See: internal/config/projectconfig/project_config.go 20 | // 21 | package condition 22 | 23 | import ( 24 | "os" 25 | "path" 26 | 27 | "github.com/commitdev/zero/internal/config/projectconfig" 28 | ) 29 | 30 | // Function dispatch for any kind of condition. 31 | func Perform(cond projectconfig.Condition, mod projectconfig.Module) { 32 | value, found := mod.Parameters[cond.MatchField] 33 | 34 | // Exit if the condition isn't met. 35 | if !found || value != cond.WhenValue { 36 | return 37 | } 38 | 39 | // Okay, the condition was met, let's execute it. 40 | switch cond.Action { 41 | case "ignoreFile": 42 | ignoreFile(cond.Data, mod) 43 | } 44 | } 45 | 46 | // Excludes paths from template rendering. 47 | // This occurs after-the-fact. That is, we render all templates to disk, then 48 | // use 'paths' to determine which files and directories to remove from disk. 49 | // 50 | func ignoreFile(paths []string, mod projectconfig.Module) { 51 | for _, file := range paths { 52 | filepath := path.Join(mod.Files.Directory, file) 53 | os.RemoveAll(filepath) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /internal/condition/condition_test.go: -------------------------------------------------------------------------------- 1 | package condition_test 2 | 3 | import ( 4 | "encoding/base64" 5 | "math/rand" 6 | "os" 7 | "testing" 8 | 9 | "github.com/commitdev/zero/internal/condition" 10 | "github.com/commitdev/zero/internal/config/projectconfig" 11 | ) 12 | 13 | func testSetup(paramKey, paramValue string) (string, projectconfig.Module) { 14 | bytes := make([]byte, 15) 15 | _, _ = rand.Read(bytes) 16 | name := string(base64.StdEncoding.EncodeToString(bytes[:])) 17 | 18 | _, _ = os.Create(name) 19 | 20 | params := make(projectconfig.Parameters) 21 | params[paramKey] = paramValue 22 | 23 | mod := projectconfig.Module{ 24 | Parameters: params, 25 | Files: projectconfig.Files{ 26 | Directory: ".", 27 | Source: ".", 28 | }, 29 | } 30 | 31 | return name, mod 32 | } 33 | 34 | func TestPerformIgnoreFileConditionNotMet(t *testing.T) { 35 | field := "testField" 36 | value := "trigger" 37 | 38 | filename, mod := testSetup(field, "other value") 39 | defer os.Remove(filename) 40 | 41 | cond := projectconfig.Condition{ 42 | Action: "ignoreFile", 43 | MatchField: field, 44 | WhenValue: value, 45 | Data: []string{filename}, 46 | } 47 | condition.Perform(cond, mod) 48 | 49 | _, err := os.Stat(filename) 50 | if err != nil && !os.IsExist(err) { 51 | t.Errorf("Expected %v not to be removed\n", filename) 52 | } 53 | } 54 | 55 | func TestPerformIgnoreFileConditionMet(t *testing.T) { 56 | field := "testField" 57 | value := "trigger" 58 | 59 | filename, mod := testSetup(field, value) 60 | defer os.Remove(filename) 61 | 62 | cond := projectconfig.Condition{ 63 | Action: "ignoreFile", 64 | MatchField: field, 65 | WhenValue: value, 66 | Data: []string{filename}, 67 | } 68 | condition.Perform(cond, mod) 69 | 70 | _, err := os.Stat(filename) 71 | if !os.IsNotExist(err) { 72 | t.Errorf("Expected %v to be removed\n", filename) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /internal/config/projectconfig/init.go: -------------------------------------------------------------------------------- 1 | package projectconfig 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "path" 8 | "text/template" 9 | 10 | "github.com/commitdev/zero/internal/constants" 11 | "github.com/commitdev/zero/internal/util" 12 | "github.com/commitdev/zero/pkg/util/flog" 13 | "gopkg.in/yaml.v2" 14 | ) 15 | 16 | const zeroProjectConfigTemplate = ` 17 | # zero-project.yml file containing all the required modules and their configuration. 18 | # This file is generated by the zero init command but can be modified by hand before running zero create. 19 | # Do not check this into source control, it may contain sensitive credentials. 20 | 21 | name: {{.Name}} 22 | 23 | shouldPushRepositories: {{.ShouldPushRepositories | printf "%v"}} 24 | 25 | modules: 26 | {{.Modules}} 27 | ` 28 | 29 | var RootDir = "./" 30 | 31 | func SetRootDir(dir string) { 32 | RootDir = dir 33 | } 34 | 35 | // CreateProjectConfigFile extracts the required content for zero project config file then write to disk. 36 | func CreateProjectConfigFile(dir string, projectName string, projectContext *ZeroProjectConfig) error { 37 | content, err := getProjectFileContent(*projectContext) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | filePath := path.Join(dir, projectName, constants.ZeroProjectYml) 43 | flog.Debugf("Project file path: %s", filePath) 44 | writeErr := ioutil.WriteFile(filePath, []byte(content), 0644) 45 | if writeErr != nil { 46 | return err 47 | } 48 | 49 | return nil 50 | } 51 | 52 | func getProjectFileContent(projectConfig ZeroProjectConfig) (string, error) { 53 | var tplBuffer bytes.Buffer 54 | tmpl, err := template.New("projectConfig").Parse(zeroProjectConfigTemplate) 55 | if err != nil { 56 | return "", err 57 | } 58 | 59 | if len(projectConfig.Modules) == 0 { 60 | return "", fmt.Errorf("Invalid project config, expected config modules to be non-empty") 61 | } 62 | 63 | pConfigModules, err := yaml.Marshal(projectConfig.Modules) 64 | if err != nil { 65 | return "", err 66 | } 67 | 68 | t := struct { 69 | Name string 70 | ShouldPushRepositories bool 71 | Modules string 72 | }{ 73 | Name: projectConfig.Name, 74 | ShouldPushRepositories: projectConfig.ShouldPushRepositories, 75 | Modules: util.IndentString(string(pConfigModules), 2), 76 | } 77 | 78 | if err := tmpl.Execute(&tplBuffer, t); err != nil { 79 | return "", err 80 | } 81 | return tplBuffer.String(), nil 82 | } 83 | -------------------------------------------------------------------------------- /internal/config/projectconfig/init_test.go: -------------------------------------------------------------------------------- 1 | package projectconfig_test 2 | 3 | import ( 4 | "os" 5 | "path" 6 | "testing" 7 | 8 | "github.com/commitdev/zero/internal/config/projectconfig" 9 | "github.com/commitdev/zero/internal/constants" 10 | "github.com/google/go-cmp/cmp" 11 | "github.com/google/go-cmp/cmp/cmpopts" 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestCreateProjectConfigFile(t *testing.T) { 16 | const testDir = "../../test-sandbox" 17 | projectName := "test-project" 18 | 19 | projectconfig.SetRootDir(testDir) 20 | defer os.RemoveAll(testDir) 21 | 22 | testDirPath := path.Join(projectconfig.RootDir, projectName) 23 | 24 | // create sandbox dir 25 | err := os.MkdirAll(testDirPath, os.ModePerm) 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | 30 | expectedConfig := &projectconfig.ZeroProjectConfig{ 31 | Name: projectName, 32 | ShouldPushRepositories: false, 33 | Modules: eksGoReactSampleModules(), 34 | } 35 | assert.NoError(t, projectconfig.CreateProjectConfigFile(projectconfig.RootDir, projectName, expectedConfig)) 36 | 37 | // make sure the file exists 38 | if _, err := os.Stat(path.Join(testDirPath, constants.ZeroProjectYml)); err != nil { 39 | t.Fatal(err) 40 | } 41 | 42 | t.Run("Should return a valid project config", func(t *testing.T) { 43 | resultConfig := projectconfig.LoadConfig(path.Join(testDirPath, constants.ZeroProjectYml)) 44 | 45 | if !cmp.Equal(expectedConfig, resultConfig, cmpopts.EquateEmpty()) { 46 | t.Errorf("projectconfig.ZeroProjectConfig.Unmarshal mismatch (-expected +result):\n%s", cmp.Diff(expectedConfig, resultConfig)) 47 | } 48 | }) 49 | 50 | t.Run("Should fail if modules are missing from project config", func(t *testing.T) { 51 | expectedConfig.Modules = nil 52 | assert.Error(t, projectconfig.CreateProjectConfigFile(projectconfig.RootDir, projectName, expectedConfig)) 53 | }) 54 | 55 | } 56 | -------------------------------------------------------------------------------- /internal/config/projectconfig/project_config.go: -------------------------------------------------------------------------------- 1 | package projectconfig 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | 7 | "github.com/hashicorp/terraform/dag" 8 | "github.com/k0kubun/pp" 9 | "gopkg.in/yaml.v2" 10 | 11 | "github.com/commitdev/zero/pkg/util/exit" 12 | "github.com/commitdev/zero/pkg/util/flog" 13 | ) 14 | 15 | // GraphRootName represents the root of the graph of modules in a project 16 | const GraphRootName = "graphRoot" 17 | 18 | type ZeroProjectConfig struct { 19 | Name string `yaml:"name"` 20 | ShouldPushRepositories bool `yaml:"shouldPushRepositories"` 21 | Parameters map[string]string 22 | Modules Modules `yaml:"modules"` 23 | } 24 | 25 | type Modules map[string]Module 26 | 27 | type Module struct { 28 | DependsOn []string `yaml:"dependsOn,omitempty"` 29 | Parameters Parameters `yaml:"parameters,omitempty"` 30 | Files Files 31 | Conditions []Condition `yaml:"conditions,omitempty"` 32 | } 33 | 34 | // ReadVendorCredentialsFromModule uses parsed project-config's module 35 | // based on vendor parameter, retrieve the vendor's credential 36 | // for pre-defined functionalities (eg: Github api key for pushing repos to github) 37 | func ReadVendorCredentialsFromModule(m Module, vendor string) (error, string) { 38 | // this mapping could be useful for module config as well 39 | vendorToParamMap := map[string]string{ 40 | "github": "githubAccessToken", 41 | "circleci": "circleciApiKey", 42 | } 43 | if parameterKey, ok := vendorToParamMap[vendor]; ok { 44 | if val, ok := m.Parameters[parameterKey]; ok { 45 | return nil, val 46 | } 47 | return errors.New("Parameter not found in module."), "" 48 | } 49 | return errors.New("Unsupported vendor provided."), "" 50 | } 51 | 52 | type Parameters map[string]string 53 | 54 | type Condition struct { 55 | Action string `yaml:"action"` 56 | MatchField string `yaml:"matchField"` 57 | WhenValue string `yaml:"whenValue"` 58 | Data []string `yaml:"data,omitempty"` 59 | } 60 | 61 | type Files struct { 62 | Directory string `yaml:"dir,omitempty"` 63 | Repository string `yaml:"repo,omitempty"` 64 | Source string 65 | } 66 | 67 | func LoadConfig(filePath string) *ZeroProjectConfig { 68 | config := &ZeroProjectConfig{} 69 | data, err := ioutil.ReadFile(filePath) 70 | if err != nil { 71 | exit.Fatal("failed to read config: %v", err) 72 | } 73 | err = yaml.Unmarshal(data, &config) 74 | if err != nil { 75 | exit.Fatal("failed to parse config: %v", err) 76 | } 77 | flog.Debugf("Loaded project config: %s from %s", config.Name, filePath) 78 | return config 79 | } 80 | 81 | func (c *ZeroProjectConfig) Print() { 82 | pp.Println(c) 83 | } 84 | 85 | // GetDAG returns a graph of the module names used in this project config 86 | func (c *ZeroProjectConfig) GetDAG() dag.AcyclicGraph { 87 | var g dag.AcyclicGraph 88 | 89 | // Add vertices to graph 90 | g.Add(GraphRootName) 91 | for name := range c.Modules { 92 | g.Add(name) 93 | } 94 | 95 | // Connect modules in graph 96 | for name, m := range c.Modules { 97 | if len(m.DependsOn) == 0 { 98 | g.Connect(dag.BasicEdge(GraphRootName, name)) 99 | } else { 100 | for _, dependencyName := range m.DependsOn { 101 | g.Connect(dag.BasicEdge(dependencyName, name)) 102 | } 103 | } 104 | } 105 | return g 106 | } 107 | 108 | func NewModule(parameters Parameters, directory string, repository string, source string, dependsOn []string, conditions []Condition) Module { 109 | return Module{ 110 | Parameters: parameters, 111 | DependsOn: dependsOn, 112 | Files: Files{ 113 | Directory: directory, 114 | Repository: repository, 115 | Source: source, 116 | }, 117 | Conditions: conditions, 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /internal/config/projectconfig/project_config_test.go: -------------------------------------------------------------------------------- 1 | package projectconfig_test 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "os" 7 | "path/filepath" 8 | "testing" 9 | 10 | "github.com/commitdev/zero/internal/config/projectconfig" 11 | "github.com/commitdev/zero/internal/constants" 12 | "github.com/google/go-cmp/cmp" 13 | "github.com/google/go-cmp/cmp/cmpopts" 14 | "github.com/stretchr/testify/assert" 15 | ) 16 | 17 | func TestLoadConfig(t *testing.T) { 18 | file, err := ioutil.TempFile(os.TempDir(), "config.yml") 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | defer os.Remove(file.Name()) 23 | file.Write([]byte(validConfigContent())) 24 | filePath := file.Name() 25 | 26 | want := &projectconfig.ZeroProjectConfig{ 27 | Name: "abc", 28 | ShouldPushRepositories: true, 29 | Modules: eksGoReactSampleModules(), 30 | } 31 | 32 | t.Run("Should load and unmarshal config correctly", func(t *testing.T) { 33 | got := projectconfig.LoadConfig(filePath) 34 | if !cmp.Equal(want, got, cmpopts.EquateEmpty()) { 35 | t.Errorf("projectconfig.ZeroProjectConfig.Unmarshal mismatch (-want +got):\n%s", cmp.Diff(want, got)) 36 | } 37 | }) 38 | } 39 | 40 | func eksGoReactSampleModules() projectconfig.Modules { 41 | parameters := projectconfig.Parameters{"a": "b"} 42 | return projectconfig.Modules{ 43 | "aws-eks-stack": projectconfig.NewModule(parameters, "zero-aws-eks-stack", "github.com/something/repo1", "github.com/commitdev/zero-aws-eks-stack", []string{}, []projectconfig.Condition{}), 44 | "backend-go": projectconfig.NewModule(parameters, "zero-backend-go", "github.com/something/repo2", "github.com/commitdev/zero-backend-go", []string{}, []projectconfig.Condition{}), 45 | "frontend-react": projectconfig.NewModule(parameters, "zero-frontend-react", "github.com/something/repo3", "github.com/commitdev/zero-frontend-react", []string{}, []projectconfig.Condition{}), 46 | } 47 | } 48 | 49 | func validConfigContent() string { 50 | return ` 51 | # Templated zero-project.yml file 52 | name: abc 53 | 54 | shouldPushRepositories: true 55 | 56 | modules: 57 | aws-eks-stack: 58 | parameters: 59 | a: b 60 | files: 61 | dir: zero-aws-eks-stack 62 | repo: github.com/something/repo1 63 | source: github.com/commitdev/zero-aws-eks-stack 64 | backend-go: 65 | parameters: 66 | a: b 67 | files: 68 | dir: zero-backend-go 69 | repo: github.com/something/repo2 70 | source: github.com/commitdev/zero-backend-go 71 | frontend-react: 72 | parameters: 73 | a: b 74 | files: 75 | dir: zero-frontend-react 76 | repo: github.com/something/repo3 77 | source: github.com/commitdev/zero-frontend-react 78 | ` 79 | } 80 | 81 | func TestProjectConfigModuleGraph(t *testing.T) { 82 | configPath := filepath.Join("../../../tests/test_data/projectconfig/", constants.ZeroProjectYml) 83 | 84 | t.Run("Should generate a valid, correct graph based on the project config", func(t *testing.T) { 85 | pc := projectconfig.LoadConfig(configPath) 86 | graph := pc.GetDAG() 87 | 88 | // Validate the graph 89 | assert.NoError(t, graph.Validate()) 90 | 91 | // Check the structure of the graph 92 | root, err := graph.Root() 93 | assert.NoError(t, err) 94 | assert.Equal(t, "graphRoot", root) 95 | 96 | want := `graphRoot 97 | project1 98 | project1 99 | project2 100 | project3 101 | project2 102 | project4 103 | project3 104 | project4 105 | project5 106 | project4 107 | project5 108 | ` 109 | assert.Equal(t, want, graph.String()) 110 | 111 | }) 112 | 113 | } 114 | -------------------------------------------------------------------------------- /internal/constants/constants.go: -------------------------------------------------------------------------------- 1 | package constants 2 | 3 | const ( 4 | TmpRegistryYml = "tmp/registry.yaml" 5 | TemplatesDir = "tmp/templates" 6 | ZeroProjectYml = "zero-project.yml" 7 | ZeroModuleYml = "zero-module.yml" 8 | ZeroHomeDirectory = ".zero" 9 | IgnoredPaths = "(?i)zero.module.yml|.git/" 10 | TemplateExtn = ".tmpl" 11 | 12 | // prompt constants 13 | 14 | MaxPnameLength = 16 15 | MaxOnameLength = 39 16 | RegexValidation = "regex" 17 | FunctionValidation = "function" 18 | ZeroReleaseURL = "https://github.com/commitdev/zero/releases" 19 | ) 20 | -------------------------------------------------------------------------------- /internal/generate/generate_test.go: -------------------------------------------------------------------------------- 1 | package generate_test 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | 9 | "github.com/commitdev/zero/internal/config/projectconfig" 10 | "github.com/commitdev/zero/internal/generate" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | const baseTestFixturesDir = "../../tests/test_data/generate/" 15 | 16 | func setupTeardown(t *testing.T) (func(t *testing.T), string) { 17 | tmpDir := filepath.Join(os.TempDir(), "generate") 18 | os.MkdirAll(tmpDir, 0755) 19 | os.RemoveAll(tmpDir) 20 | return func(t *testing.T) { 21 | os.RemoveAll(tmpDir) 22 | }, tmpDir 23 | } 24 | 25 | func TestGenerateModules(t *testing.T) { 26 | teardown, tmpDir := setupTeardown(t) 27 | defer teardown(t) 28 | 29 | projectConfig := projectconfig.ZeroProjectConfig{ 30 | Name: "foo", 31 | Modules: projectconfig.Modules{ 32 | "mod1": projectconfig.NewModule(map[string]string{"test": "bar"}, tmpDir, "github.com/fake-org/repo-foo", baseTestFixturesDir, []string{}, []projectconfig.Condition{}), 33 | }, 34 | } 35 | generate.Generate(projectConfig, true) 36 | 37 | content, err := ioutil.ReadFile(filepath.Join(tmpDir, "file_to_template.txt")) 38 | assert.NoError(t, err) 39 | 40 | expectedContent := `Name is foo 41 | Params.test is bar 42 | Files.Repository is github.com/fake-org/repo-foo 43 | ` 44 | assert.Equal(t, string(content), expectedContent) 45 | } 46 | -------------------------------------------------------------------------------- /internal/init/custom-prompts.go: -------------------------------------------------------------------------------- 1 | package init 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/commitdev/zero/internal/config/moduleconfig" 8 | project "github.com/commitdev/zero/pkg/credentials" 9 | ) 10 | 11 | // CustomPromptHandler handles non-input and enum options prompts 12 | // zero-module's parameters allow prompts to specify types of custom actions 13 | // this allows non-standard enum / string input to be added, such as AWS profile picker 14 | func CustomPromptHandler(promptType string, params map[string]string) error { 15 | switch promptType { 16 | 17 | case "AWSProfilePicker": 18 | err := promptAWSProfilePicker(params) 19 | if err != nil { 20 | params["useExistingAwsProfile"] = "no" 21 | return err 22 | } 23 | default: 24 | return errors.New(fmt.Sprintf("Unsupported custom prompt type %s.", promptType)) 25 | } 26 | return nil 27 | } 28 | 29 | func promptAWSProfilePicker(params map[string]string) error { 30 | profiles, err := project.GetAWSProfiles() 31 | if err != nil { 32 | return err 33 | } 34 | 35 | awsPrompt := PromptHandler{ 36 | Parameter: moduleconfig.Parameter{ 37 | Field: "aws_profile", 38 | Label: "Select AWS Profile", 39 | Options: listToPromptOptions(profiles), 40 | }, 41 | Condition: NoCondition, 42 | Validate: NoValidation, 43 | } 44 | _, value := promptParameter(awsPrompt) 45 | credErr := project.FillAWSProfile("", value, params) 46 | if credErr != nil { 47 | return errors.New("Failed to retrieve profile, falling back to User input") 48 | } 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /internal/init/debug.test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commitdev/zero/de71b8bd09b0d8d57107fbdd61ec870994a6cc41/internal/init/debug.test -------------------------------------------------------------------------------- /internal/module/module.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/base64" 6 | "io" 7 | "log" 8 | "path" 9 | "regexp" 10 | "sync" 11 | 12 | "github.com/commitdev/zero/internal/config/moduleconfig" 13 | "github.com/commitdev/zero/internal/constants" 14 | "github.com/commitdev/zero/internal/util" 15 | "github.com/commitdev/zero/pkg/util/exit" 16 | "github.com/commitdev/zero/pkg/util/flog" 17 | "github.com/hashicorp/go-getter" 18 | ) 19 | 20 | // FetchModule downloads the remote module source if necessary. Meant to be run in a goroutine. 21 | func FetchModule(source string, wg *sync.WaitGroup) { 22 | defer wg.Done() 23 | 24 | localPath := GetSourceDir(source) 25 | if !IsLocal(source) { 26 | flog.Debugf("Downloading module: %s to %s", source, localPath) 27 | err := getter.Get(localPath, source) 28 | if err != nil { 29 | exit.Fatal("Failed to fetch remote module from %s: %v\n", source, err) 30 | } 31 | } 32 | return 33 | } 34 | 35 | // ParseModuleConfig loads the local config file for a module and parses the yaml 36 | func ParseModuleConfig(source string) (moduleconfig.ModuleConfig, error) { 37 | localPath := GetSourceDir(source) 38 | config := moduleconfig.ModuleConfig{} 39 | configPath := path.Join(localPath, constants.ZeroModuleYml) 40 | config, err := moduleconfig.LoadModuleConfig(configPath) 41 | return config, err 42 | } 43 | 44 | // GetSourcePath gets a unique local source directory name. For local modules, it use the local directory 45 | func GetSourceDir(source string) string { 46 | if !IsLocal(source) { 47 | h := md5.New() 48 | io.WriteString(h, source) 49 | source = base64.StdEncoding.EncodeToString(h.Sum(nil)) 50 | return path.Join(constants.TemplatesDir, source) 51 | } else { 52 | return source 53 | } 54 | } 55 | 56 | // IsLocal uses the go-getter FileDetector to check if source is a file 57 | func IsLocal(source string) bool { 58 | pwd := util.GetCwd() 59 | 60 | // ref: https://github.com/hashicorp/go-getter/blob/master/detect_test.go 61 | out, err := getter.Detect(source, pwd, getter.Detectors) 62 | 63 | match, err := regexp.MatchString("^file://.*", out) 64 | if err != nil { 65 | log.Panicf("invalid source format %s", err) 66 | } 67 | 68 | return match 69 | } 70 | 71 | func withPWD(pwd string) func(*getter.Client) error { 72 | return func(c *getter.Client) error { 73 | c.Pwd = pwd 74 | return nil 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /internal/module/module_internal_test.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIsLocal(t *testing.T) { 8 | source := "./tests/test_data/modules" 9 | res := IsLocal(source) 10 | if !res { 11 | t.Errorf("Error, source %s SHOULD BE determined as local", source) 12 | } 13 | 14 | source = "https://github.com/commitdev/my-repo" 15 | res = IsLocal(source) 16 | if res { 17 | t.Errorf("Error, source %s SHOULD NOT BE determined as local", source) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /internal/module/module_test.go: -------------------------------------------------------------------------------- 1 | package module_test 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/commitdev/zero/internal/config/moduleconfig" 9 | "github.com/stretchr/testify/assert" 10 | 11 | "github.com/commitdev/zero/internal/module" 12 | "github.com/commitdev/zero/version" 13 | ) 14 | 15 | func TestGetSourceDir(t *testing.T) { 16 | source := "tests/test_data/modules" 17 | relativeSource := source 18 | dir := module.GetSourceDir(source) 19 | 20 | t.Log("dir", dir) 21 | if dir != relativeSource { 22 | t.Errorf("Error, local sources should not be changed: %s", source) 23 | } 24 | 25 | source = "github.com/commitdev/my-repo" 26 | dir = module.GetSourceDir(source) 27 | if dir == relativeSource { 28 | t.Errorf("Error, remote sources should be converted to a local dir: %s", source) 29 | } 30 | } 31 | 32 | func TestParseModuleConfig(t *testing.T) { 33 | testModuleSource := "../../tests/test_data/modules/ci" 34 | var mod moduleconfig.ModuleConfig 35 | 36 | t.Run("Loading module from source", func(t *testing.T) { 37 | mod, _ = module.ParseModuleConfig(testModuleSource) 38 | moduleconfig.ValidateZeroVersion(mod) 39 | 40 | assert.Equal(t, "CI templates", mod.Name) 41 | }) 42 | 43 | t.Run("Parameters are loaded", func(t *testing.T) { 44 | param, err := findParameter(mod.Parameters, "platform") 45 | if err != nil { 46 | panic(err) 47 | } 48 | assert.Equal(t, "platform", param.Field) 49 | assert.Equal(t, "CI Platform", param.Label) 50 | 51 | }) 52 | 53 | t.Run("OmitFromProjectFile default", func(t *testing.T) { 54 | param, err := findParameter(mod.Parameters, "platform") 55 | if err != nil { 56 | panic(err) 57 | } 58 | assert.Equal(t, false, param.OmitFromProjectFile, "OmitFromProjectFile should default to false") 59 | useCredsParam, useCredsErr := findParameter(mod.Parameters, "useExistingAwsProfile") 60 | if useCredsErr != nil { 61 | panic(useCredsErr) 62 | } 63 | assert.Equal(t, true, useCredsParam.OmitFromProjectFile, "OmitFromProjectFile should be read from file") 64 | }) 65 | 66 | t.Run("Parsing Conditions and Typed prompts from config", func(t *testing.T) { 67 | param, err := findParameter(mod.Parameters, "profilePicker") 68 | if err != nil { 69 | panic(err) 70 | } 71 | assert.Equal(t, "AWSProfilePicker", param.Type) 72 | assert.Equal(t, "KeyMatchCondition", param.Conditions[0].Action) 73 | assert.Equal(t, "useExistingAwsProfile", param.Conditions[0].MatchField) 74 | assert.Equal(t, "yes", param.Conditions[0].WhenValue) 75 | }) 76 | 77 | t.Run("parsing envVarName from module config", func(t *testing.T) { 78 | param, err := findParameter(mod.Parameters, "accessKeyId") 79 | if err != nil { 80 | panic(err) 81 | } 82 | assert.Equal(t, "AWS_ACCESS_KEY_ID", param.EnvVarName) 83 | }) 84 | 85 | t.Run("TemplateConfig is unmarshaled", func(t *testing.T) { 86 | mod, _ = module.ParseModuleConfig(testModuleSource) 87 | assert.Equal(t, ".circleci", mod.TemplateConfig.OutputDir) 88 | assert.Equal(t, "templates", mod.TemplateConfig.InputDir) 89 | assert.Equal(t, []string{"<%", "%>"}, mod.TemplateConfig.Delimiters) 90 | }) 91 | 92 | t.Run("Parsing commands", func(t *testing.T) { 93 | checkCommand := mod.Commands.Check 94 | assert.Equal(t, "ls", checkCommand) 95 | }) 96 | 97 | t.Run("Parsing zero version constraints", func(t *testing.T) { 98 | moduleConstraints := mod.ZeroVersion.Constraints.String() 99 | assert.Equal(t, ">= 3.0.0, < 4.0.0", moduleConstraints) 100 | }) 101 | 102 | t.Run("Should Fail against old zero version", func(t *testing.T) { 103 | moduleConstraints := mod.ZeroVersion.Constraints.String() 104 | 105 | // Mocking zero's version, testing against ">= 3.0.0, <= 4.0.0" 106 | originalVersion := version.AppVersion 107 | version.AppVersion = "2.0.0" 108 | defer func() { version.AppVersion = originalVersion }() 109 | // end of mock 110 | 111 | isValid := moduleconfig.ValidateZeroVersion(mod) 112 | assert.Equal(t, false, isValid, fmt.Sprintf("Version should satisfy %s", moduleConstraints)) 113 | }) 114 | 115 | t.Run("Should Fail against too new zero version", func(t *testing.T) { 116 | moduleConstraints := mod.ZeroVersion.Constraints.String() 117 | 118 | // Mocking zero's version, testing against ">= 3.0.0, <= 4.0.0" 119 | originalVersion := version.AppVersion 120 | version.AppVersion = "4.0.0" 121 | defer func() { version.AppVersion = originalVersion }() 122 | // end of mock 123 | 124 | isValid := moduleconfig.ValidateZeroVersion(mod) 125 | assert.Equal(t, false, isValid, fmt.Sprintf("Version should satisfy %s", moduleConstraints)) 126 | }) 127 | 128 | t.Run("Should validate against valid versions", func(t *testing.T) { 129 | moduleConstraints := mod.ZeroVersion.Constraints.String() 130 | 131 | // Mocking zero's version, testing against ">= 3.0.0, <= 4.0.0" 132 | const newZeroVersion = "3.0.5" 133 | originalVersion := version.AppVersion 134 | version.AppVersion = newZeroVersion 135 | defer func() { version.AppVersion = originalVersion }() 136 | // end of mock 137 | 138 | isValid := moduleconfig.ValidateZeroVersion(mod) 139 | assert.Equal(t, true, isValid, fmt.Sprintf("Version should satisfy %s", moduleConstraints)) 140 | }) 141 | 142 | t.Run("default to SNAPSHOT version passes tests", func(t *testing.T) { 143 | assert.Equal(t, "SNAPSHOT", version.AppVersion) 144 | isValid := moduleconfig.ValidateZeroVersion(mod) 145 | assert.Equal(t, true, isValid, "default test run should pass version constraint") 146 | }) 147 | 148 | } 149 | 150 | func TestModuleWithNoVersionConstraint(t *testing.T) { 151 | testModuleSource := "../../tests/test_data/modules/no-version-constraint" 152 | var mod moduleconfig.ModuleConfig 153 | var err error 154 | 155 | t.Run("Parsing Module with no version constraint", func(t *testing.T) { 156 | mod, err = module.ParseModuleConfig(testModuleSource) 157 | assert.Equal(t, "", mod.ZeroVersion.String()) 158 | assert.Nil(t, err) 159 | }) 160 | 161 | t.Run("Should pass Validation if constraint not specified", func(t *testing.T) { 162 | isValid := moduleconfig.ValidateZeroVersion(mod) 163 | assert.Equal(t, true, isValid, "Module with no constraint should pass version validation") 164 | }) 165 | } 166 | 167 | func findParameter(params []moduleconfig.Parameter, field string) (moduleconfig.Parameter, error) { 168 | for _, v := range params { 169 | if v.Field == field { 170 | return v, nil 171 | } 172 | } 173 | return moduleconfig.Parameter{}, errors.New("parameter not found") 174 | } 175 | -------------------------------------------------------------------------------- /internal/registry/registry.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "io/ioutil" 5 | 6 | "github.com/commitdev/zero/internal/constants" 7 | "github.com/hashicorp/go-getter" 8 | 9 | yaml "gopkg.in/yaml.v2" 10 | ) 11 | 12 | type Registry []Stack 13 | 14 | type Stack struct { 15 | Name string `yaml:"name"` 16 | ModuleSources []string `yaml:"moduleSources"` 17 | } 18 | 19 | func GetRegistry(localModulePath, registryFilePath string) (Registry, error) { 20 | registry := Registry{} 21 | 22 | err := getter.GetFile(constants.TmpRegistryYml, registryFilePath) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | data, err := ioutil.ReadFile(constants.TmpRegistryYml) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | err = yaml.Unmarshal(data, ®istry) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | for i := 0; i < len(registry); i++ { 38 | for j := 0; j < len(registry[i].ModuleSources); j++ { 39 | registry[i].ModuleSources[j] = localModulePath + registry[i].ModuleSources[j] 40 | } 41 | } 42 | 43 | return registry, nil 44 | } 45 | 46 | func GetModulesByName(registry Registry, name string) []string { 47 | for _, v := range registry { 48 | if v.Name == name { 49 | return v.ModuleSources 50 | } 51 | } 52 | return []string{} 53 | } 54 | 55 | func AvailableLabels(registry Registry) []string { 56 | labels := make([]string, len(registry)) 57 | i := 0 58 | for _, stack := range registry { 59 | labels[i] = stack.Name 60 | i++ 61 | } 62 | return labels 63 | } 64 | -------------------------------------------------------------------------------- /internal/registry/registry_test.go: -------------------------------------------------------------------------------- 1 | package registry_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/commitdev/zero/internal/registry" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestAvailableLabels(t *testing.T) { 11 | reg := testRegistry() 12 | 13 | t.Run("should be same order as declared", func(t *testing.T) { 14 | labels := registry.AvailableLabels(reg) 15 | assert.Equal(t, labels, []string{ 16 | "EKS + Go + React + Gatsby", 17 | "foo", 18 | "bar", 19 | "lorem", 20 | "ipsum", 21 | "Custom", 22 | }) 23 | }) 24 | } 25 | 26 | func TestGetModulesByName(t *testing.T) { 27 | reg := testRegistry() 28 | t.Run("should return modules of specified stack", func(t *testing.T) { 29 | 30 | assert.Equal(t, registry.GetModulesByName(reg, "EKS + Go + React + Gatsby"), 31 | []string{"module-source 1", "module-source 2"}) 32 | assert.Equal(t, registry.GetModulesByName(reg, "lorem"), []string{"module-source 5"}) 33 | assert.Equal(t, registry.GetModulesByName(reg, "ipsum"), []string{"module-source 6"}) 34 | assert.Equal(t, registry.GetModulesByName(reg, "Custom"), []string{"module-source 7"}) 35 | }) 36 | } 37 | 38 | func testRegistry() registry.Registry { 39 | return registry.Registry{ 40 | {"EKS + Go + React + Gatsby", []string{"module-source 1", "module-source 2"}}, 41 | {"foo", []string{"module-source 3"}}, 42 | {"bar", []string{"module-source 4"}}, 43 | {"lorem", []string{"module-source 5"}}, 44 | {"ipsum", []string{"module-source 6"}}, 45 | {"Custom", []string{"module-source 7"}}, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /internal/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | // @TODO split up and move into /pkg directory 4 | 5 | import ( 6 | "bytes" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "log" 11 | "os" 12 | "os/exec" 13 | "path" 14 | "path/filepath" 15 | "reflect" 16 | "regexp" 17 | "strconv" 18 | "strings" 19 | "syscall" 20 | "text/template" 21 | 22 | "github.com/google/uuid" 23 | ) 24 | 25 | func CreateDirIfDoesNotExist(path string) error { 26 | if _, err := os.Stat(path); os.IsNotExist(err) { 27 | err = os.MkdirAll(path, os.ModePerm) 28 | return err 29 | } 30 | return nil 31 | } 32 | 33 | func CleanGoIdentifier(identifier string) string { 34 | return strings.ReplaceAll(identifier, "-", "") 35 | } 36 | 37 | // @TODO how can we make these type of helpers extensible? 38 | var FuncMap = template.FuncMap{ 39 | "Title": strings.Title, 40 | "ToLower": strings.ToLower, 41 | "CleanGoIdentifier": CleanGoIdentifier, 42 | "GenerateUUID": uuid.New, 43 | } 44 | 45 | func GetCwd() string { 46 | dir, err := os.Getwd() 47 | if err != nil { 48 | log.Fatalf("Getting working directory failed: %v\n", err) 49 | panic(err) 50 | } 51 | 52 | return dir 53 | } 54 | 55 | func ExecuteCommand(cmd *exec.Cmd, pathPrefix string, envars []string, shouldPipeStdErr bool) error { 56 | 57 | cmd.Dir = pathPrefix 58 | if !filepath.IsAbs(pathPrefix) { 59 | dir := GetCwd() 60 | cmd.Dir = path.Join(dir, pathPrefix) 61 | } 62 | 63 | stdoutPipe, _ := cmd.StdoutPipe() 64 | stderrPipe, _ := cmd.StderrPipe() 65 | 66 | var errStdout, errStderr error 67 | errContent := new(bytes.Buffer) 68 | 69 | cmd.Env = os.Environ() 70 | if envars != nil { 71 | cmd.Env = append(os.Environ(), envars...) 72 | } 73 | 74 | err := cmd.Start() 75 | if err != nil { 76 | return err 77 | } 78 | 79 | go func() { 80 | _, errStdout = io.Copy(os.Stdout, stdoutPipe) 81 | }() 82 | go func() { 83 | stderrStreams := []io.Writer{errContent} 84 | if shouldPipeStdErr { 85 | stderrStreams = append(stderrStreams, os.Stderr) 86 | } 87 | stdErr := io.MultiWriter(stderrStreams...) 88 | _, errStderr = io.Copy(stdErr, stderrPipe) 89 | }() 90 | 91 | err = cmd.Wait() 92 | if err != nil { 93 | // Detecting and returning the makefile error to cmd 94 | // Passing alone makefile stderr as error message, otherwise it just says "exit status 2" 95 | if exitError, ok := err.(*exec.ExitError); ok { 96 | ws := exitError.Sys().(syscall.WaitStatus) 97 | exitCode := ws.ExitStatus() 98 | if exitCode == 2 { 99 | stderrOut := errContent.String() 100 | isMissingTarget, _ := regexp.MatchString("No rule to make target", stderrOut) 101 | if isMissingTarget { 102 | return errors.New("Module missing mandatory targets, this is likely an issue with the module itself.") 103 | } 104 | return errors.New(stderrOut) 105 | } 106 | } 107 | 108 | return errors.New(errContent.String()) 109 | } 110 | 111 | if errStdout != nil { 112 | log.Printf("Failed to capture stdout: %v\n", errStdout) 113 | } 114 | 115 | if errStderr != nil { 116 | log.Printf("Failed to capture stderr: %v\n", errStderr) 117 | } 118 | return nil 119 | } 120 | 121 | // ExecuteCommandOutput runs the command and returns its 122 | // combined standard output and standard error. 123 | func ExecuteCommandOutput(cmd *exec.Cmd, pathPrefix string, envars []string) string { 124 | 125 | cmd.Dir = pathPrefix 126 | if !filepath.IsAbs(pathPrefix) { 127 | dir := GetCwd() 128 | cmd.Dir = path.Join(dir, pathPrefix) 129 | } 130 | 131 | cmd.Env = os.Environ() 132 | if envars != nil { 133 | cmd.Env = append(os.Environ(), envars...) 134 | } 135 | 136 | out, err := cmd.CombinedOutput() 137 | if err != nil { 138 | log.Fatalf("Executing command with output failed: (%v) %s\n", err, out) 139 | } 140 | return string(out) 141 | } 142 | 143 | // AppendProjectEnvToCmdEnv converts a key-value pair map into a slice of `key=value`s 144 | // allow module definition to use an alternative env-var-name than field while apply 145 | func AppendProjectEnvToCmdEnv(envMap map[string]string, envList []string, translationMap map[string]string) []string { 146 | 147 | for key, val := range envMap { 148 | if val != "" { 149 | // overwrite key if exist in translation map 150 | if val, ok := translationMap[key]; ok { 151 | key = val 152 | } 153 | envList = append(envList, fmt.Sprintf("%s=%s", key, val)) 154 | } 155 | } 156 | return envList 157 | } 158 | 159 | // IndentString will Add x space char padding at the beginging of each line. 160 | func IndentString(content string, spaces int) string { 161 | var result string 162 | subStr := strings.Split(content, "\n") 163 | for _, s := range subStr { 164 | result += fmt.Sprintf("%"+strconv.Itoa(spaces)+"s%s\n", "", s) 165 | } 166 | return result 167 | } 168 | 169 | func ItemInSlice(slice []string, target string) bool { 170 | for _, item := range slice { 171 | if item == target { 172 | return true 173 | } 174 | } 175 | return false 176 | } 177 | 178 | // ReflectStructValueIntoMap receives a resource of struct type as 179 | // type AWSCreds struct{ 180 | // AccessKeyID string `yaml:"accessKeyId,omitempty"` 181 | // SecretAccessKey string `yaml:"secretAccessKey,omitempty"` 182 | // }{ 183 | // AccessKeyID: "FOO", 184 | // SecretAccessKey: "BAR", 185 | // } 186 | // It will base on the tag, fill in the value to supplied map[string]string 187 | func ReflectStructValueIntoMap(resource interface{}, tagName string, paramsToFill map[string]string) { 188 | t := reflect.ValueOf(resource) 189 | 190 | for i := 0; i < t.NumField(); i++ { 191 | 192 | childStruct := t.Type().Field(i) 193 | childValue := t.Field(i) 194 | if childValue.Kind().String() != "string" { 195 | continue 196 | } 197 | tag, _ := parseTag(childStruct.Tag.Get(tagName)) 198 | paramsToFill[tag] = childValue.String() 199 | } 200 | } 201 | 202 | func parseTag(tag string) (string, string) { 203 | if idx := strings.Index(tag, ","); idx != -1 { 204 | return tag[:idx], tag[idx+1:] 205 | } 206 | return tag, "" 207 | } 208 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/commitdev/zero/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /pkg/credentials/credentials.go: -------------------------------------------------------------------------------- 1 | package credentials 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "os/user" 7 | "path/filepath" 8 | "regexp" 9 | 10 | "github.com/aws/aws-sdk-go/aws/credentials" 11 | "github.com/commitdev/zero/internal/util" 12 | ) 13 | 14 | type AWSResourceConfig struct { 15 | AccessKeyID string `key:"accessKeyId"` 16 | SecretAccessKey string `key:"secretAccessKey"` 17 | } 18 | 19 | func awsCredsPath() string { 20 | usr, err := user.Current() 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | return filepath.Join(usr.HomeDir, ".aws/credentials") 25 | } 26 | 27 | func fetchAWSConfig(awsPath string, profileName string) (error, AWSResourceConfig) { 28 | 29 | awsCreds, err := credentials.NewSharedCredentials(awsPath, profileName).Get() 30 | if err != nil { 31 | return err, AWSResourceConfig{} 32 | } 33 | return nil, AWSResourceConfig{ 34 | AccessKeyID: awsCreds.AccessKeyID, 35 | SecretAccessKey: awsCreds.SecretAccessKey, 36 | } 37 | } 38 | 39 | // FillAWSProfile receives the AWS profile name, then parses 40 | // the accessKeyId / secretAccessKey values into a map 41 | func FillAWSProfile(pathToCredentialsFile string, profileName string, paramsToFill map[string]string) error { 42 | if pathToCredentialsFile == "" { 43 | pathToCredentialsFile = awsCredsPath() 44 | } 45 | 46 | err, awsCreds := fetchAWSConfig(pathToCredentialsFile, profileName) 47 | if err != nil { 48 | return err 49 | } 50 | util.ReflectStructValueIntoMap(awsCreds, "key", paramsToFill) 51 | return nil 52 | } 53 | 54 | // GetAWSProfiles returns a list of AWS forprofiles set up on the user's sytem 55 | func GetAWSProfiles() ([]string, error) { 56 | usr, err := user.Current() 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | // Load the credentials file to look for profiles 62 | credsFile := filepath.Join(usr.HomeDir, ".aws/credentials") 63 | creds, err := ioutil.ReadFile(credsFile) 64 | if err != nil { 65 | return nil, err 66 | } 67 | // Get all profiles 68 | re := regexp.MustCompile(`\[(.*)\]`) 69 | profileMatches := re.FindAllStringSubmatch(string(creds), -1) 70 | profiles := make([]string, len(profileMatches)) 71 | for i, p := range profileMatches { 72 | profiles[i] = p[1] 73 | } 74 | return profiles, nil 75 | } 76 | -------------------------------------------------------------------------------- /pkg/credentials/credentials_test.go: -------------------------------------------------------------------------------- 1 | package credentials_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/commitdev/zero/pkg/credentials" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestFillAWSProfileCredentials(t *testing.T) { 11 | mockAwsCredentialFilePath := "../../tests/test_data/aws/mock_credentials.yml" 12 | 13 | t.Run("fills project credentials", func(t *testing.T) { 14 | params := map[string]string{} 15 | err := credentials.FillAWSProfile(mockAwsCredentialFilePath, "default", params) 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | assert.Equal(t, "MOCK1_ACCESS_KEY", params["accessKeyId"]) 21 | assert.Equal(t, "MOCK1_SECRET_ACCESS_KEY", params["secretAccessKey"]) 22 | }) 23 | 24 | t.Run("supports non-default profiles", func(t *testing.T) { 25 | params := map[string]string{} 26 | err := credentials.FillAWSProfile(mockAwsCredentialFilePath, "foobar", params) 27 | if err != nil { 28 | panic(err) 29 | } 30 | assert.Equal(t, "MOCK2_ACCESS_KEY", params["accessKeyId"]) 31 | assert.Equal(t, "MOCK2_SECRET_ACCESS_KEY", params["secretAccessKey"]) 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /pkg/util/exit/exit.go: -------------------------------------------------------------------------------- 1 | package exit 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/commitdev/zero/pkg/util/flog" 7 | ) 8 | 9 | const ( 10 | // CodeOK indicates successful execution. 11 | CodeOK = 0 12 | 13 | // CodeError indicates erroneous execution. 14 | CodeError = 1 15 | 16 | // CodeFatal indicates erroneous use by user. 17 | CodeFatal = 2 18 | ) 19 | 20 | // Fatal terminates execution using fatal exit code. 21 | func Fatal(format string, a ...interface{}) { 22 | flog.Errorf(format, a...) 23 | os.Exit(CodeFatal) 24 | } 25 | 26 | // Error terminates execution using unsuccessful execution exit code. 27 | func Error(format string, a ...interface{}) { 28 | flog.Errorf(format, a...) 29 | os.Exit(CodeError) 30 | } 31 | 32 | // OK terminates execution successfully. 33 | func OK(format string, a ...interface{}) { 34 | flog.Infof(format, a) 35 | os.Exit(CodeOK) 36 | } 37 | -------------------------------------------------------------------------------- /pkg/util/flog/log.go: -------------------------------------------------------------------------------- 1 | package flog 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/kyokomi/emoji" 8 | "github.com/logrusorgru/aurora" 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | const LogEnvVariable = "LOG_LEVEL" 13 | const defaultLogLevel = "info" 14 | 15 | var logger = getLogger() 16 | var infoFormatter = new(InfoFormatter) 17 | var debugFormatter = &logrus.TextFormatter{ 18 | DisableLevelTruncation: true, 19 | FullTimestamp: true, 20 | EnvironmentOverrideColors: true, 21 | } 22 | 23 | func getLogger() *logrus.Logger { 24 | logger := logrus.New() 25 | 26 | lvl, ok := os.LookupEnv(LogEnvVariable) 27 | if !ok { 28 | lvl = defaultLogLevel 29 | } 30 | logLevel, _ := logrus.ParseLevel(lvl) 31 | logger.SetOutput(os.Stdout) 32 | logger.SetLevel(logLevel) 33 | return logger 34 | } 35 | 36 | // Warnf logs a formatted error message 37 | func Infof(format string, a ...interface{}) { 38 | logger.SetFormatter(infoFormatter) 39 | logger.Info(aurora.Cyan(emoji.Sprintf(format, a...))) 40 | } 41 | 42 | func Debugf(format string, a ...interface{}) { 43 | logger.SetFormatter(debugFormatter) 44 | logger.Debug(aurora.Green(emoji.Sprintf(format, a...))) 45 | } 46 | 47 | // Infof prints out a timestamp as prefix, Guidef just prints the message 48 | func Guidef(format string, a ...interface{}) { 49 | fmt.Println(aurora.Cyan(emoji.Sprintf(format, a...))) 50 | } 51 | 52 | // Successf logs a formatted success message 53 | func Successf(format string, a ...interface{}) { 54 | logger.Info(aurora.Green(emoji.Sprintf(":white_check_mark: "+format, a...))) 55 | } 56 | 57 | // Warnf logs a formatted warning message 58 | func Warnf(format string, a ...interface{}) { 59 | logger.Warn(aurora.Yellow(emoji.Sprintf(":exclamation: "+format, a...))) 60 | } 61 | 62 | // Warnf logs a formatted error message 63 | func Errorf(format string, a ...interface{}) { 64 | logger.Error(aurora.Red(emoji.Sprintf(":exclamation: "+format, a...))) 65 | } 66 | 67 | // Info formatter is to not display the LOG_LEVEL in front of the command eg. INFO[2020-070-01T15:22:22] Hello World 68 | type InfoFormatter struct { 69 | } 70 | 71 | func (f *InfoFormatter) Format(entry *logrus.Entry) ([]byte, error) { 72 | // extra line break stops the prompts from overtaking Existing line 73 | return []byte(entry.Message + "\n"), nil 74 | } 75 | -------------------------------------------------------------------------------- /pkg/util/fs/fs.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | "regexp" 8 | "strings" 9 | ) 10 | 11 | // CreateDirs creates directories from the given directory path arguments. 12 | func CreateDirs(dirPaths ...string) error { 13 | for _, path := range dirPaths { 14 | if err := os.MkdirAll(path, 0755); err != nil { 15 | return err 16 | } 17 | } 18 | 19 | return nil 20 | } 21 | 22 | // FileExists checks whether the given path exists and belongs to a file. 23 | func FileExists(path string) (bool, error) { 24 | info, err := os.Stat(path) 25 | if err != nil { 26 | if os.IsNotExist(err) { 27 | return false, nil 28 | } 29 | 30 | return false, err 31 | } 32 | 33 | if info.IsDir() { 34 | return false, fmt.Errorf("%v: is a directory, expected file", path) 35 | } 36 | 37 | return true, nil 38 | } 39 | 40 | // PrependPath prepends a path with prefix while disregarding back directories ../ 41 | func ReplacePath(p, old, new string) string { 42 | return path.Clean(strings.Replace(p, old, new, 1)) 43 | } 44 | 45 | // PrependPath prepends a path with prefix while disregarding back directories ../ 46 | func PrependPath(filepath string, prefix string) string { 47 | re := regexp.MustCompile(`(\.\.\/)+`) 48 | cleanPath := path.Clean(filepath) 49 | baseDir := re.FindString(cleanPath) 50 | if baseDir == "" { 51 | return path.Join(prefix, cleanPath) 52 | } 53 | return strings.Replace(cleanPath, baseDir, path.Join(baseDir, prefix)+"/", 1) 54 | } 55 | -------------------------------------------------------------------------------- /pkg/util/fs/fs_test.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var replacePathTest = []struct { 8 | path string 9 | old string 10 | new string 11 | out string 12 | }{ 13 | {"../../dir/file.ext", "../../dir", "output", "output/file.ext"}, 14 | {"dir/file.ext", "dir", "output", "output/file.ext"}, 15 | } 16 | 17 | func TestReplacePath(t *testing.T) { 18 | for _, tt := range replacePathTest { 19 | t.Run(tt.path, func(t *testing.T) { 20 | out := ReplacePath(tt.path, tt.old, tt.new) 21 | if out != tt.out { 22 | t.Errorf("got %q, want %q", out, tt.out) 23 | } 24 | }) 25 | } 26 | } 27 | 28 | var prependPathTests = []struct { 29 | in string 30 | prefix string 31 | out string 32 | }{ 33 | {"../../dir/file.ext", "prefix", "../../prefix/dir/file.ext"}, 34 | {"../opps/../../dir/file.ext", "prefix", "../../prefix/dir/file.ext"}, 35 | {"../opps/../../dir/file.ext", "", "../../dir/file.ext"}, 36 | {"dir/file.ext", "prefix", "prefix/dir/file.ext"}, 37 | {"dir/file.ext", "../prefix", "../prefix/dir/file.ext"}, 38 | {"./dir/file.ext", "prefix", "prefix/dir/file.ext"}, 39 | } 40 | 41 | func TestPrependPath(t *testing.T) { 42 | for _, tt := range prependPathTests { 43 | t.Run(tt.in, func(t *testing.T) { 44 | out := PrependPath(tt.in, tt.prefix) 45 | if out != tt.out { 46 | t.Errorf("got %q, want %q", out, tt.out) 47 | } 48 | }) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /registry.yaml: -------------------------------------------------------------------------------- 1 | - name: EKS + Go + React + Gatsby 2 | moduleSources: 3 | - /zero-aws-eks-stack 4 | - /zero-static-site-gatsby 5 | - /zero-backend-go 6 | - /zero-frontend-react 7 | 8 | - name: EKS + NodeJS + React + Gatsby 9 | moduleSources: 10 | - /zero-aws-eks-stack 11 | - /zero-static-site-gatsby 12 | - /zero-backend-node 13 | - /zero-frontend-react 14 | -------------------------------------------------------------------------------- /tests/integration/ci/ci_test.go: -------------------------------------------------------------------------------- 1 | package ci_test 2 | 3 | // @TODO refactor into new set of integration tests 4 | // import ( 5 | // "bytes" 6 | // "io/ioutil" 7 | // "os" 8 | // "sync" 9 | // "testing" 10 | 11 | // "github.com/commitdev/zero/internal/config" 12 | // "github.com/commitdev/zero/internal/generate/ci" 13 | // "github.com/commitdev/zero/internal/templator" 14 | // "github.com/gobuffalo/packr/v2" 15 | // ) 16 | 17 | // var testData = "../../test_data/ci/" 18 | 19 | // // setupTeardown removes all the generated test files before and after 20 | // // the test runs to ensure clean data. 21 | // func setupTeardown(t *testing.T) func(t *testing.T) { 22 | // os.RemoveAll("../../test_data/ci/actual") 23 | // return func(t *testing.T) { 24 | // os.RemoveAll("../../test_data/ci/actual") 25 | // } 26 | // } 27 | 28 | // func TestGenerateJenkins(t *testing.T) { 29 | // teardown := setupTeardown(t) 30 | // defer teardown(t) 31 | 32 | // templates := packr.New("templates", "../../../templates") 33 | // testTemplator := templator.NewTemplator(templates) 34 | 35 | // var waitgroup sync.WaitGroup 36 | 37 | // testConf := &projectconfig.ZeroProjectConfig{} 38 | // testCI := config.CI{ 39 | // System: "jenkins", 40 | // BuildImage: "golang/golang", 41 | // BuildTag: "1.12", 42 | // BuildCommand: "make build", 43 | // TestCommand: "make test", 44 | // } 45 | 46 | // err := ci.Generate(testTemplator.CI, testConf, testCI, testData+"/actual", &waitgroup) 47 | // if err != nil { 48 | // t.Errorf("Error when executing test. %s", err) 49 | // } 50 | // waitgroup.Wait() 51 | 52 | // actual, err := ioutil.ReadFile(testData + "actual/Jenkinsfile") 53 | // if err != nil { 54 | // t.Errorf("Error reading created file: %s", err.Error()) 55 | // } 56 | // expected, err := ioutil.ReadFile(testData + "/expected/Jenkinsfile") 57 | // if err != nil { 58 | // t.Errorf("Error reading created file: %s", err.Error()) 59 | // } 60 | 61 | // if !bytes.Equal(expected, actual) { 62 | // t.Errorf("want:\n%s\n\n, got:\n%s\n\n", string(expected), string(actual)) 63 | // } 64 | // } 65 | 66 | // func TestGenerateCircleCI(t *testing.T) { 67 | // teardown := setupTeardown(t) 68 | // defer teardown(t) 69 | 70 | // templates := packr.New("templates", "../../../templates") 71 | // testTemplator := templator.NewTemplator(templates) 72 | 73 | // var waitgroup sync.WaitGroup 74 | 75 | // testConf := &projectconfig.ZeroProjectConfig{} 76 | // testCI := config.CI{ 77 | // System: "circleci", 78 | // BuildImage: "golang/golang", 79 | // BuildTag: "1.12", 80 | // BuildCommand: "make build", 81 | // TestCommand: "make test", 82 | // } 83 | 84 | // err := ci.Generate(testTemplator.CI, testConf, testCI, testData+"/actual", &waitgroup) 85 | // if err != nil { 86 | // t.Errorf("Error when executing test. %s", err) 87 | // } 88 | // waitgroup.Wait() 89 | 90 | // actual, err := ioutil.ReadFile(testData + "actual/.circleci/config.yml") 91 | // if err != nil { 92 | // t.Errorf("Error reading created file: %s", err.Error()) 93 | // } 94 | // expected, err := ioutil.ReadFile(testData + "/expected/.circleci/config.yml") 95 | // if err != nil { 96 | // t.Errorf("Error reading created file: %s", err.Error()) 97 | // } 98 | 99 | // if !bytes.Equal(expected, actual) { 100 | // t.Errorf("want:\n%s\n\ngot:\n%s\n\n", string(expected), string(actual)) 101 | // } 102 | // } 103 | 104 | // func TestGenerateTravisCI(t *testing.T) { 105 | // teardown := setupTeardown(t) 106 | // defer teardown(t) 107 | 108 | // templates := packr.New("templates", "../../../templates") 109 | // testTemplator := templator.NewTemplator(templates) 110 | 111 | // var waitgroup sync.WaitGroup 112 | 113 | // testConf := &projectconfig.ZeroProjectConfig{} 114 | // testCI := config.CI{ 115 | // System: "travisci", 116 | // Language: "go", 117 | // BuildImage: "golang/golang", 118 | // BuildTag: "1.12", 119 | // BuildCommand: "make build", 120 | // TestCommand: "make test", 121 | // } 122 | // err := ci.Generate(testTemplator.CI, testConf, testCI, testData+"/actual", &waitgroup) 123 | // if err != nil { 124 | // t.Errorf("Error when executing test. %s", err) 125 | // } 126 | // waitgroup.Wait() 127 | 128 | // actual, err := ioutil.ReadFile(testData + "actual/.travis.yml") 129 | // if err != nil { 130 | // t.Errorf("Error reading created file: %s", err.Error()) 131 | // } 132 | // expected, err := ioutil.ReadFile(testData + "/expected/.travis.yml") 133 | // if err != nil { 134 | // t.Errorf("Error reading created file: %s", err.Error()) 135 | // } 136 | 137 | // if !bytes.Equal(expected, actual) { 138 | // t.Errorf("want:\n%s\n\n, got:\n%s\n\n", string(expected), string(actual)) 139 | // } 140 | // } 141 | -------------------------------------------------------------------------------- /tests/test_data/apply-failing/project1/Makefile: -------------------------------------------------------------------------------- 1 | current_dir: 2 | @echo "foo: ${foo}" > project.out 3 | @echo "repo: ${REPOSITORY}" >> project.out 4 | 5 | summary: 6 | 7 | check: 8 | @$(error "Failure 1 of 2") 9 | -------------------------------------------------------------------------------- /tests/test_data/apply-failing/project1/project.out: -------------------------------------------------------------------------------- 1 | foo: bar 2 | repo: github.com/commitdev/project1 3 | -------------------------------------------------------------------------------- /tests/test_data/apply-failing/project1/zero-module.yml: -------------------------------------------------------------------------------- 1 | name: project1 2 | description: 'project1' 3 | author: 'Commit' 4 | 5 | template: 6 | strictMode: true 7 | delimiters: 8 | - "<%" 9 | - "%>" 10 | inputDir: '.' 11 | outputDir: 'test' 12 | 13 | requiredCredentials: 14 | - aws 15 | - github 16 | 17 | parameters: 18 | - field: foo 19 | label: foo 20 | -------------------------------------------------------------------------------- /tests/test_data/apply-failing/project2/Makefile: -------------------------------------------------------------------------------- 1 | REQUIRED_BINS := ls nonexisting-binary 2 | 3 | current_dir: 4 | @echo "baz: ${baz}" > project.out 5 | 6 | summary: 7 | 8 | check: 9 | $(foreach bin, $(REQUIRED_BINS),\ 10 | $(if $(shell command -v $(bin) 2> /dev/null),$(info Found `$(bin)`),$(error Please install `$(bin)`))) 11 | -------------------------------------------------------------------------------- /tests/test_data/apply-failing/project2/project.out: -------------------------------------------------------------------------------- 1 | baz: qux 2 | -------------------------------------------------------------------------------- /tests/test_data/apply-failing/project2/zero-module.yml: -------------------------------------------------------------------------------- 1 | name: project2 2 | description: 'project2' 3 | author: 'Commit' 4 | 5 | template: 6 | strictMode: true 7 | delimiters: 8 | - "<%" 9 | - "%>" 10 | inputDir: '.' 11 | outputDir: 'test' 12 | 13 | requiredCredentials: 14 | - aws 15 | - github 16 | 17 | parameters: 18 | - field: baz 19 | label: baz 20 | -------------------------------------------------------------------------------- /tests/test_data/apply-failing/project3/Makefile: -------------------------------------------------------------------------------- 1 | REQUIRED_BINS := ls nonexisting-binary 2 | 3 | current_dir: 4 | 5 | summary: 6 | -------------------------------------------------------------------------------- /tests/test_data/apply-failing/project3/check.sh: -------------------------------------------------------------------------------- 1 | >&2 echo "Check script erroring out";exit 1; -------------------------------------------------------------------------------- /tests/test_data/apply-failing/project3/project.out: -------------------------------------------------------------------------------- 1 | baz: qux 2 | -------------------------------------------------------------------------------- /tests/test_data/apply-failing/project3/zero-module.yml: -------------------------------------------------------------------------------- 1 | name: project3 2 | description: 'project3' 3 | author: 'Commit' 4 | 5 | commands: 6 | check: sh check.sh 7 | template: 8 | strictMode: true 9 | delimiters: 10 | - "<%" 11 | - "%>" 12 | inputDir: '.' 13 | outputDir: 'test' 14 | 15 | requiredCredentials: 16 | - aws 17 | - github 18 | 19 | parameters: 20 | - field: baz 21 | label: baz 22 | -------------------------------------------------------------------------------- /tests/test_data/apply-failing/zero-project.yml: -------------------------------------------------------------------------------- 1 | name: sample_project 2 | 3 | modules: 4 | project1: 5 | parameters: 6 | foo: bar 7 | files: 8 | dir: project1 9 | repo: github.com/commitdev/project1 10 | source: project1 11 | project2: 12 | parameters: 13 | baz: qux 14 | files: 15 | dir: project2 16 | repo: github.com/commitdev/project2 17 | source: project2 18 | project3: 19 | files: 20 | dir: project3 21 | repo: github.com/commitdev/project3 22 | source: project3 23 | -------------------------------------------------------------------------------- /tests/test_data/apply/project1/Makefile: -------------------------------------------------------------------------------- 1 | current_dir: 2 | @echo "foo: ${foo}" > project.out 3 | @echo "repo: ${REPOSITORY}" >> project.out 4 | @echo "envVarName of viaEnvVarName: ${viaEnvVarName}" >> feature.out 5 | 6 | summary: 7 | 8 | check: 9 | -------------------------------------------------------------------------------- /tests/test_data/apply/project1/zero-module.yml: -------------------------------------------------------------------------------- 1 | name: project1 2 | description: 'project1' 3 | author: 'Commit' 4 | 5 | template: 6 | strictMode: true 7 | delimiters: 8 | - "<%" 9 | - "%>" 10 | inputDir: '.' 11 | outputDir: 'test' 12 | 13 | requiredCredentials: 14 | - aws 15 | - github 16 | 17 | parameters: 18 | - field: foo 19 | label: foo 20 | - field: param1 21 | envVarName: viaEnvVarName 22 | -------------------------------------------------------------------------------- /tests/test_data/apply/project2/Makefile: -------------------------------------------------------------------------------- 1 | current_dir: 2 | @echo "baz: ${baz}" > project.out 3 | 4 | summary: 5 | 6 | check: 7 | -------------------------------------------------------------------------------- /tests/test_data/apply/project2/check.sh: -------------------------------------------------------------------------------- 1 | pwd 2 | echo "custom check" > check.out -------------------------------------------------------------------------------- /tests/test_data/apply/project2/zero-module.yml: -------------------------------------------------------------------------------- 1 | name: project2 2 | description: 'project2' 3 | author: 'Commit' 4 | commands: 5 | check: sh check.sh 6 | template: 7 | strictMode: true 8 | delimiters: 9 | - "<%" 10 | - "%>" 11 | inputDir: '.' 12 | outputDir: 'test' 13 | 14 | requiredCredentials: 15 | - aws 16 | - github 17 | 18 | parameters: 19 | - field: baz 20 | label: baz 21 | -------------------------------------------------------------------------------- /tests/test_data/apply/zero-project.yml: -------------------------------------------------------------------------------- 1 | name: sample_project 2 | 3 | modules: 4 | project1: 5 | parameters: 6 | foo: bar 7 | param1: baz 8 | files: 9 | dir: project1 10 | repo: github.com/commitdev/project1 11 | source: project1 12 | project2: 13 | parameters: 14 | baz: qux 15 | files: 16 | dir: project2 17 | repo: github.com/commitdev/project2 18 | source: project2 19 | -------------------------------------------------------------------------------- /tests/test_data/aws/mock_credentials.yml: -------------------------------------------------------------------------------- 1 | [default] 2 | aws_access_key_id=MOCK1_ACCESS_KEY 3 | aws_secret_access_key=MOCK1_SECRET_ACCESS_KEY 4 | 5 | [foobar] 6 | aws_access_key_id=MOCK2_ACCESS_KEY 7 | aws_secret_access_key=MOCK2_SECRET_ACCESS_KEY 8 | -------------------------------------------------------------------------------- /tests/test_data/ci/expected/.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | jobs: 3 | build: 4 | docker: 5 | - image: golang/golang:1.12 6 | steps: 7 | - checkout 8 | - run: 9 | name: Build 10 | command: | 11 | make build 12 | 13 | test: 14 | docker: 15 | - image: golang/golang:1.12 16 | steps: 17 | - checkout 18 | - run: 19 | name: Test 20 | command: | 21 | make test 22 | 23 | 24 | workflow: 25 | version: 2.1 26 | build_and_test: 27 | jobs: 28 | - build 29 | - test 30 | -------------------------------------------------------------------------------- /tests/test_data/ci/expected/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.12 4 | 5 | scripts: 6 | - make build 7 | - make test 8 | -------------------------------------------------------------------------------- /tests/test_data/ci/expected/Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent none 3 | stages { 4 | stage('Build and Test') { 5 | parallel { 6 | stage('Build') { 7 | agent { 8 | docker { 9 | image 'golang/golang:1.12' 10 | } 11 | } 12 | steps { 13 | sh 'make build' 14 | } 15 | } 16 | stage('Test') { 17 | agent { 18 | docker { 19 | image 'golang/golang:1.12' 20 | } 21 | } 22 | steps { 23 | sh 'make test' 24 | } 25 | } 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/test_data/configs/commit0_submodules.yml: -------------------------------------------------------------------------------- 1 | name: hello-world 2 | 3 | # Context will populated automatically or could be added manually 4 | context: 5 | cognitoPoolID: 123 6 | cognitoClientID: ABC 7 | 8 | modules: 9 | # module can be in any format the go-getter supports (path, github, url, etc.) 10 | # supports https://github.com/hashicorp/go-getter#url-format 11 | # - source: './tests/test_data/modules/ci' 12 | # params: 13 | # ci: github 14 | 15 | - source: './remote_templates/services' 16 | # alternatively we can recursively support sub modules 17 | modules: 18 | - './remote_templates/ci/go' 19 | -------------------------------------------------------------------------------- /tests/test_data/configs/credentials.yml: -------------------------------------------------------------------------------- 1 | another-project: 2 | github: 3 | accessToken: "654" 4 | my-project: 5 | aws: 6 | accessKeyId: AKIAABCD 7 | secretAccessKey: ZXCV 8 | github: 9 | accessToken: "0987" 10 | circleci: 11 | apiKey: SOME_API_KEY 12 | -------------------------------------------------------------------------------- /tests/test_data/configs/zero-basic.yml: -------------------------------------------------------------------------------- 1 | name: hello-world 2 | 3 | # Context will populated automatically or could be added manually 4 | context: 5 | cognitoPoolID: 123 6 | cognitoClientID: ABC 7 | 8 | modules: 9 | # module can be in any format the go-getter supports (path, github, url, etc.) 10 | # supports https://github.com/hashicorp/go-getter#url-format 11 | - source: "../../tests/test_data/modules/ci" 12 | params: 13 | ci: github 14 | 15 | # - source: './remote_templates/services' 16 | # # alternatively we can recursively support sub modules 17 | # modules: 18 | # - './remote_templates/ci/go' 19 | -------------------------------------------------------------------------------- /tests/test_data/generate/file_to_template.txt: -------------------------------------------------------------------------------- 1 | Name is {{.Name}} 2 | Params.test is {{.Params.test}} 3 | Files.Repository is {{.Files.Repository}} 4 | -------------------------------------------------------------------------------- /tests/test_data/generate/zero-module.yml: -------------------------------------------------------------------------------- 1 | name: test-generate 2 | description: 'generation test module' 3 | author: 'Commit' 4 | 5 | template: 6 | strictMode: true 7 | delimiters: 8 | - '{{' 9 | - '}}' 10 | inputDir: '.' 11 | outputDir: 'test' 12 | 13 | requiredCredentials: 14 | 15 | parameters: 16 | -------------------------------------------------------------------------------- /tests/test_data/modules/ci/config1.yml: -------------------------------------------------------------------------------- 1 | content1: {{ .ci }} -------------------------------------------------------------------------------- /tests/test_data/modules/ci/dir/config2.yml: -------------------------------------------------------------------------------- 1 | content2: 2 | -------------------------------------------------------------------------------- /tests/test_data/modules/ci/zero-module.yml: -------------------------------------------------------------------------------- 1 | name: "CI templates" 2 | description: "CI description" 3 | author: "CI author" 4 | icon: "" 5 | thumbnail: "" 6 | zeroVersion: ">= 3.0.0, < 4.0.0" 7 | commands: 8 | check: ls 9 | 10 | requiredCredentials: 11 | - aws 12 | - circleci 13 | - github 14 | 15 | # Template variables to populate, these could be overwritten by the file spefic frontmatter variables 16 | template: 17 | # strictMode: true # will only parse files that includes the .tmpl.* extension, otherwise it will copy file 18 | delimiters: 19 | - "<%" 20 | - "%>" 21 | inputDir: 'templates' 22 | outputDir: ".circleci" 23 | 24 | # required context parameters: will throw a warning message at the end if any of the context parameters are not present 25 | # contextRequired: 26 | # - cognitoPoolID 27 | # - cognitoClientID 28 | 29 | # parameters required from user to populate the template params 30 | parameters: 31 | - field: platform 32 | label: CI Platform 33 | # default: github 34 | options: 35 | github: Github 36 | circleci: Circle CI 37 | - field: circleci_api_key 38 | label: "Circle CI API Key to setup your CI/CD for repositories" 39 | conditions: 40 | - action: KeyMatchCondition 41 | matchField: platform 42 | whenValue: "circlci" 43 | - field: useExistingAwsProfile 44 | label: "Use credentials from an existing AWS profile?" 45 | options: 46 | "yes": "Yes" 47 | "no": "No" 48 | omitFromProjectFile: yes 49 | - field: profilePicker 50 | omitFromProjectFile: yes 51 | type: AWSProfilePicker 52 | conditions: 53 | - action: KeyMatchCondition 54 | whenValue: "yes" 55 | matchField: useExistingAwsProfile 56 | - field: accessKeyId 57 | label: AWS AccessKeyId 58 | envVarName: "AWS_ACCESS_KEY_ID" 59 | conditions: 60 | - action: KeyMatchCondition 61 | whenValue: "no" 62 | matchField: useExistingAwsProfile 63 | - field: secretAccessKey 64 | envVarName: "AWS_SECRET_ACCESS_KEY" 65 | label: AWS SecretAccessKey 66 | conditions: 67 | - action: KeyMatchCondition 68 | whenValue: "no" 69 | matchField: useExistingAwsProfile 70 | - field: testExecute 71 | execute: echo $AWS_ACCESS_KEY_ID 72 | -------------------------------------------------------------------------------- /tests/test_data/modules/no-version-constraint/zero-module.yml: -------------------------------------------------------------------------------- 1 | name: "Test module" 2 | description: "a module for testing, with no zero version requirement" 3 | author: "Test module author" 4 | icon: "" 5 | thumbnail: "" 6 | 7 | template: 8 | delimiters: 9 | - "<%" 10 | - "%>" 11 | inputDir: templates 12 | outputDir: test-module-output 13 | 14 | requiredCredentials: 15 | - aws 16 | - circleci 17 | - github 18 | 19 | parameters: 20 | -------------------------------------------------------------------------------- /tests/test_data/projectconfig/zero-project.yml: -------------------------------------------------------------------------------- 1 | # Graph shape: 2 | # 2 3 | # / \ 4 | # 1 4 5 | # \ / 6 | # 3 - 5 7 | 8 | name: graph_test 9 | 10 | modules: 11 | project1: 12 | parameters: 13 | foo: bar 14 | project2: 15 | dependsOn: 16 | - project1 17 | parameters: 18 | baz: qux 19 | project3: 20 | dependsOn: 21 | - project1 22 | parameters: 23 | baz: qux 24 | project4: 25 | dependsOn: 26 | - project2 27 | - project3 28 | parameters: 29 | baz: qux 30 | project5: 31 | dependsOn: 32 | - project3 33 | parameters: 34 | baz: qux 35 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | // These values are overridden by makefile during build 4 | var ( 5 | AppVersion = "SNAPSHOT" 6 | AppBuild = "SNAPSHOT" 7 | ) 8 | --------------------------------------------------------------------------------