├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── custom.md
│ └── feature_request.md
├── dependabot.yml
└── workflows
│ ├── goreleaser.yml
│ ├── lint.yml
│ └── test.yml
├── .gitignore
├── .gitlab-ci.yml
├── .golang-ci.yml
├── .goreleaser.yml
├── .pre-commit-config.yaml
├── CODE_OF_CONDUCT.md
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── _config.yml
├── assets
├── example.png
└── logo
│ └── logo.png
├── cmd
├── root.go
└── root_test.go
├── codecov.yml
├── examples
├── terracove.html
├── terracove.json
├── terracove.xml
├── terraform
│ ├── success
│ │ ├── applied.txt
│ │ ├── example.txt
│ │ ├── main.tf
│ │ └── terraform.tfstate
│ └── tfstate-diff
│ │ ├── example.txt
│ │ ├── main.tf
│ │ └── terraform.tfstate
└── terragrunt
│ ├── error
│ ├── main.tf
│ └── terragrunt.hcl
│ └── no-resources
│ ├── main.tf
│ ├── terraform.tfstate
│ └── terragrunt.hcl
├── go.mod
├── go.sum
├── install.sh
├── internal
└── types
│ ├── types.go
│ └── types_test.go
├── main.go
├── pkg
├── html
│ ├── html.go
│ └── template.go
├── report
│ ├── report.go
│ └── report_test.go
└── scan
│ ├── scan.go
│ └── scan_test.go
├── tests
├── terracove.json
└── terracove.xml
└── tools
└── tools.go
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: https://www.buymeacoffee.com/elementtech
14 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/custom.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Custom issue template
3 | about: Describe this issue template's purpose here.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "gomod"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 | time: "08:00"
8 | labels:
9 | - "dependencies"
10 | commit-message:
11 | prefix: "feat"
12 | include: "scope"
13 | - package-ecosystem: "github-actions"
14 | directory: "/"
15 | schedule:
16 | interval: "daily"
17 | time: "08:00"
18 | labels:
19 | - "dependencies"
20 | commit-message:
21 | prefix: "chore"
22 | include: "scope"
23 | - package-ecosystem: "docker"
24 | directory: "/"
25 | schedule:
26 | interval: "daily"
27 | time: "08:00"
28 | labels:
29 | - "dependencies"
30 | commit-message:
31 | prefix: "feat"
32 | include: "scope"
--------------------------------------------------------------------------------
/.github/workflows/goreleaser.yml:
--------------------------------------------------------------------------------
1 | name: goreleaser
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | permissions:
9 | contents: write
10 | packages: write
11 |
12 | jobs:
13 | goreleaser:
14 | runs-on: ubuntu-latest
15 | env:
16 | DOCKER_CLI_EXPERIMENTAL: "enabled"
17 | steps:
18 | -
19 | name: Checkout
20 | uses: actions/checkout@v3
21 | with:
22 | fetch-depth: 0
23 | -
24 | name: Set up Go
25 | uses: actions/setup-go@v4
26 | with:
27 | go-version: 1.19
28 | -
29 | name: Set up QEMU
30 | uses: docker/setup-qemu-action@v2
31 | -
32 | name: Set up Docker Buildx
33 | uses: docker/setup-buildx-action@v2
34 | -
35 | name: ghcr-login
36 | uses: docker/login-action@v2
37 | with:
38 | registry: ghcr.io
39 | username: ${{ github.repository_owner }}
40 | password: ${{ secrets.GITHUB_TOKEN }}
41 | -
42 | name: Run GoReleaser
43 | uses: goreleaser/goreleaser-action@v4
44 | with:
45 | version: ${{ env.GITHUB_REF_NAME }}
46 | args: release --rm-dist
47 | env:
48 | GITHUB_TOKEN: ${{ secrets.PUBLISHER_TOKEN }}
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: golangci-lint
2 | on:
3 | push:
4 | paths:
5 | - '**.go'
6 | branches:
7 | - master
8 | pull_request:
9 |
10 | jobs:
11 | golangci:
12 | name: lint
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v3
16 |
17 | - uses: actions/setup-go@v4
18 | with:
19 | go-version: '1.19'
20 |
21 | - name: golangci-lint
22 | uses: golangci/golangci-lint-action@v3
23 | with:
24 | version: v1.50.0
25 | args: -c .golang-ci.yml -v --timeout=5m
26 | env:
27 | GO111MODULES: off
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test and coverage
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | strategy:
8 | matrix:
9 | go-version: [1.19.x]
10 | os: [ubuntu-latest, macos-latest]
11 |
12 | runs-on: ${{ matrix.os }}
13 |
14 | steps:
15 | - uses: actions/checkout@v3
16 | with:
17 | fetch-depth: 2
18 |
19 | - uses: actions/setup-go@v4
20 | with:
21 | go-version: ${{ matrix.go-version }}
22 |
23 | - name: go get
24 | run: go get ./...
25 |
26 | - name: go mod tidy
27 | run: go mod tidy
28 |
29 | - name: Setup Terraform
30 | uses: hashicorp/setup-terraform@v2.0.3
31 |
32 | - name: Setup Terragrunt
33 | uses: eLco/setup-terragrunt@v1.0.2
34 |
35 | - name: Run coverage
36 | run: go test -race -coverprofile="coverage.out" -covermode=atomic ./...
37 |
38 | - name: Upload coverage to Codecov
39 | if: matrix.os == 'ubuntu-latest'
40 | run: bash <(curl -s https://codecov.io/bash)
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | terracove
2 | vendor/
3 | coverage.out
4 | /dist
5 | .envrc
6 | manpages/
7 | dist/
8 | .DS_Store
9 | .terraform*
10 | .terracove*
11 | junit.xml
12 | terraform.tfstate.backup
13 | /terracove.json
14 | /terracove.yaml
15 | /terracove.xml
16 | /terracove.html
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | ---
2 | image: tetafro/golang-gcc:1.19-alpine
3 |
4 | stages:
5 | - lint
6 | - build
7 | - test
8 | - release
9 |
10 | lint:
11 | stage: lint
12 | before_script:
13 | - wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.41.1
14 | script:
15 | - ./bin/golangci-lint run -c .golang-ci.yml
16 | allow_failure: true
17 |
18 | build:
19 | stage: build
20 | script:
21 | - go build
22 |
23 | test:
24 | stage: test
25 | script:
26 | - go test -v -race "$(go list ./... | grep -v /vendor/)" -v -coverprofile=coverage.out
27 | - go tool cover -func=coverage.out
28 |
29 | release:
30 | stage: release
31 | image:
32 | name: goreleaser/goreleaser:v0.164.0
33 | entrypoint: ["/bin/bash", "-c"]
34 | only:
35 | refs:
36 | - tags
37 | variables:
38 | GITLAB_TOKEN: $GITLAB_TOKEN
39 |
40 | script:
41 | - cd "$CI_PROJECT_DIR"
42 | - goreleaser release --rm-dist
43 |
--------------------------------------------------------------------------------
/.golang-ci.yml:
--------------------------------------------------------------------------------
1 | linters-settings:
2 | lll:
3 | line-length: 180
4 | linters:
5 | enable-all: true
6 | disable:
7 | - testpackage
8 | - forbidigo
9 | - paralleltest
10 | - exhaustivestruct
11 | - varnamelen
12 | - interfacer
13 | - maligned
14 | - scopelint
15 | - golint
16 | - varcheck
17 | - nosnakecase
18 | - deadcode
19 | - ifshort
20 | - structcheck
21 | - rowserrcheck
22 | - sqlclosecheck
23 | - structcheck
24 | - wastedassign
25 | - exhaustruct
26 | - nolintlint
27 | - wrapcheck
--------------------------------------------------------------------------------
/.goreleaser.yml:
--------------------------------------------------------------------------------
1 | env:
2 | - GO111MODULE=on
3 | - CGO_ENABLED=0
4 |
5 | builds:
6 | - binary: terracove
7 | ldflags: -s -w -X main.version={{ .Version }}
8 | goos:
9 | - linux
10 | - darwin
11 | goarch:
12 | - amd64
13 | - arm64
14 |
15 | brews:
16 | - name: terracove
17 | homepage: "https://github.com/elementtech/terracove"
18 | tap:
19 | owner: elementtech
20 | name: homebrew-elementtech
21 | commit_author:
22 | name: elementtech
23 | email: amitai333@gmail.com
24 |
25 | archives:
26 | - builds:
27 | - terracove
28 | format_overrides:
29 | - goos: windows
30 | format: zip
31 | replacements:
32 | darwin: Darwin
33 | linux: Linux
34 | amd64: x86_64
35 |
36 | dockers:
37 | - image_templates:
38 | - "ghcr.io/elementtech/{{.ProjectName}}:{{ .Tag }}-amd64"
39 | dockerfile: Dockerfile
40 | use: buildx
41 | build_flag_templates:
42 | - "--pull"
43 | - "--label=io.artifacthub.package.readme-url=https://raw.githubusercontent.com/elementtech/terracove/main/README.md"
44 | - '--label=io.artifacthub.package.maintainers=[{"name":"ElementTech","email":"amitai333@gmail.com"}]'
45 | - "--label=io.artifacthub.package.license=MIT"
46 | - "--label=org.opencontainers.image.description=A recursive terraform repository tester powered by Terratest"
47 | - "--label=org.opencontainers.image.created={{.Date}}"
48 | - "--label=org.opencontainers.image.name={{.ProjectName}}"
49 | - "--label=org.opencontainers.image.revision={{.FullCommit}}"
50 | - "--label=org.opencontainers.image.version={{.Version}}"
51 | - "--label=org.opencontainers.image.source={{.GitURL}}"
52 | - "--platform=linux/amd64"
53 | - image_templates:
54 | - "ghcr.io/elementtech/{{.ProjectName}}:{{ .Tag }}-arm64"
55 | dockerfile: Dockerfile
56 | use: buildx
57 | build_flag_templates:
58 | - "--pull"
59 | - "--label=io.artifacthub.package.readme-url=https://raw.githubusercontent.com/elementtech/terracove/main/README.md"
60 | - "--label=io.artifacthub.package.logo-url=https://raw.githubusercontent.com/elementtech/terracove/main/assets/logo/logo.png"
61 | - '--label=io.artifacthub.package.maintainers=[{"name":"ElementTech","email":"amitai333@gmail.com"}]'
62 | - "--label=io.artifacthub.package.license=MIT"
63 | - "--label=org.opencontainers.image.description=A recursive terraform repository tester powered by Terratest"
64 | - "--label=org.opencontainers.image.created={{.Date}}"
65 | - "--label=org.opencontainers.image.name={{.ProjectName}}"
66 | - "--label=org.opencontainers.image.revision={{.FullCommit}}"
67 | - "--label=org.opencontainers.image.version={{.Version}}"
68 | - "--label=org.opencontainers.image.source={{.GitURL}}"
69 | - "--platform=linux/arm64"
70 | goarch: arm64
71 |
72 | docker_manifests:
73 | - name_template: "ghcr.io/elementtech/{{.ProjectName}}:{{ .Tag }}"
74 | image_templates:
75 | - "ghcr.io/elementtech/{{.ProjectName}}:{{ .Tag }}-amd64"
76 | - "ghcr.io/elementtech/{{.ProjectName}}:{{ .Tag }}-arm64"
77 | - name_template: "ghcr.io/elementtech/{{.ProjectName}}:latest"
78 | image_templates:
79 | - "ghcr.io/elementtech/{{.ProjectName}}:{{ .Tag }}-amd64"
80 | - "ghcr.io/elementtech/{{.ProjectName}}:{{ .Tag }}-arm64"
81 |
82 | checksum:
83 | name_template: "checksums.txt"
84 |
85 | changelog:
86 | sort: asc
87 | use: github
88 | filters:
89 | exclude:
90 | - "^test:"
91 | - "^chore"
92 | - "merge conflict"
93 | - Merge pull request
94 | - Merge remote-tracking branch
95 | - Merge branch
96 | - go mod tidy
97 | groups:
98 | - title: Dependency updates
99 | regexp: '^.*?(feat|fix)\(deps\)!?:.+$'
100 | order: 300
101 | - title: "New Features"
102 | regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$'
103 | order: 100
104 | - title: "Bug fixes"
105 | regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$'
106 | order: 200
107 | - title: "Documentation updates"
108 | regexp: ^.*?doc(\([[:word:]]+\))??!?:.+$
109 | order: 400
110 | - title: Other work
111 | order: 9999
112 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/tekwizely/pre-commit-golang
3 | rev: v1.0.0-rc.1
4 | hooks:
5 | - id: go-build-mod
6 | - id: go-test-mod
7 | - id: go-vet-mod
8 | - id: go-staticcheck-mod
9 | - id: go-fmt
10 | - id: go-fumpt
11 | - id: go-imports
12 | - id: go-lint
13 | - id: golangci-lint-mod
14 | args: [-c.golang-ci.yml]
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | elementtech.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ghcr.io/devops-infra/docker-terragrunt:aws-azure-gcp-tf-1.4.4-tg-0.45.2
2 | COPY terracove /usr/bin/terracove
3 | WORKDIR /data
4 | ENTRYPOINT ["/usr/bin/terracove"]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 elementtech
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | projectname?=terracove
2 |
3 | default: help
4 |
5 | .PHONY: help
6 | help: ## list makefile targets
7 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
8 |
9 | .PHONY: build
10 | build: ## build golang binary
11 | @go build -ldflags "-X main.version=$(shell git describe --abbrev=0 --tags)" -o $(projectname)
12 |
13 | .PHONY: install
14 | install: ## install golang binary
15 | @go install -ldflags "-X main.version=$(shell git describe --abbrev=0 --tags)"
16 |
17 | .PHONY: run
18 | run: ## run the app
19 | @go run -ldflags "-X main.version=$(shell git describe --abbrev=0 --tags)" main.go
20 |
21 | .PHONY: bootstrap
22 | bootstrap: ## install build deps
23 | go generate -tags tools tools/tools.go
24 |
25 | PHONY: test
26 | test: clean ## display test coverage
27 | go test --cover -parallel=1 -v -coverprofile=coverage.out ./...
28 | go tool cover -func=coverage.out | sort -rnk3
29 |
30 | PHONY: clean
31 | clean: ## clean up environment
32 | @rm -rf coverage.out dist/ $(projectname)
33 |
34 | PHONY: cover
35 | cover: ## display test coverage
36 | go test -v -race $(shell go list ./... | grep -v /vendor/) -v -coverprofile=coverage.out
37 | go tool cover -func=coverage.out
38 |
39 | PHONY: fmt
40 | fmt: ## format go files
41 | gofumpt -w .
42 | gci write .
43 |
44 | PHONY: lint
45 | lint: ## lint go files
46 | golangci-lint run -c .golang-ci.yml
47 |
48 | .PHONY: pre-commit
49 | pre-commit: ## run pre-commit hooks
50 | pre-commit run --all-files
51 |
52 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | A recursive terraform repository tester powered by Terratest.
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Key Features •
19 | Install •
20 | Usage •
21 | Quickstart •
22 | Credits •
23 | Support •
24 | License
25 |
26 |
27 | 
28 |
29 |
30 |
31 |
32 | ## Key Features
33 |
34 | * Test in parallel multiple directory paths
35 | * Export Results:
36 | * [junit](./examples/terracove.xml) with `--junit`
37 | * [json](./examples/terracove.json) summary with `--json`
38 | * [html](./examples/terracove.html) report with `--html`
39 | * Generate `%` coverage for each module and root directory
40 | * Ignore Errors and Empty Modules
41 | * Supports [terraform](https://www.terraform.io/) and [terragrunt](https://terragrunt.gruntwork.io/) in the same directory tree
42 |
43 |
44 | ## Install
45 |
46 | The recommended way to install on MacOS is via brew:
47 |
48 | ```sh
49 | brew tap elementtech/elementtech
50 | brew install terracove
51 | ```
52 |
53 | If you'd like to use Docker, you can use the official image:
54 | ```sh
55 | docker run --rm -v $(pwd):/data ghcr.io/elementtech/terracove /data --json --junit --html
56 | ```
57 |
58 | Or, you can install directly from release:
59 | ```sh
60 | curl -sS https://raw.githubusercontent.com/elementtech/terracove/main/install.sh | bash
61 | ```
62 | ## Usage
63 |
64 | ```sh
65 | Usage:
66 | terracove [paths]... [flags]
67 |
68 | Flags:
69 | -e, --exclude strings Exclude directories while parsing tree
70 | -h, --help help for terracove
71 | -w, --html Output HTML Report
72 | --ignore-empty Ignore Modules with 0 Resources
73 | --ignore-errors Ignore Planning Errors
74 | -j, --json Output JSON
75 | -x, --junit Output Junit XML
76 | --minimal Don't Append Raw/JSON Plan to the Exported Output
77 | --o-html string Output HTML Report File (default "terracove.html")
78 | --o-json string Output JSON File (default "terracove.json")
79 | --o-junit string Output Junit XML File (default "terracove.xml")
80 | -t, --validate-tf-by string validate terraform by the existence of [filename] in a directory (default "main.tf")
81 | -g, --validate-tg-by string validate terragrunt by the existence of [filename] in a directory (default "terragrunt.hcl")
82 | -v, --version version for terracove
83 | ```
84 |
85 | ## Quickstart
86 | > Note that you must have terraform/terragrunt binaries installed on your machine
87 |
88 | > The [examples](./examples) directory contains **4 modules**. 2 of them are [terraform](./examples/terraform) and 2 are [terragrunt](./examples/terragrunt).
89 | >
90 | > **Oh no!** It appears some of them have some problems. Let's see exactly what is going on.
91 | > Clone this repository and give it a try.
92 |
93 | ```sh
94 | git clone https://github.com/elementtech/terracove.git
95 | cd terracove
96 | terracove --minimal --junit --json --html .
97 | # . == examples == examples/terraform examples/terragrunt
98 | ```
99 |
100 | Open the **terracove.xml**, **terracove.json** or **terracove.html** and observe the results. You should see the following:
101 |
102 | ```json
103 | [
104 | {
105 | "Timestamp": "1984-01-01T19:32:58+05:00",
106 | "Path": ".",
107 | "Results": [
108 | {
109 | "Path": "examples/terragrunt/no-resources",
110 | "ResourceCount": 0,
111 | "Coverage": 100,
112 | ...
113 | },
114 | {
115 | "Path": "examples/terragrunt/error",
116 | "Coverage": 0,
117 | ...
118 | },
119 | {
120 | "Path": "examples/terraform/tfstate-diff",
121 | "ResourceCount": 2,
122 | "ResourceCountDiff": 1,
123 | "Coverage": 50,
124 | ...
125 | },
126 | {
127 | "Path": "examples/terraform/success",
128 | "ResourceCount": 2,
129 | "ResourceCountExists": 2,
130 | "Coverage": 100,
131 | ...
132 | }
133 | ],
134 | "Coverage": 62.5
135 | }
136 | ]
137 | ```
138 |
139 |
140 | ## Credits
141 |
142 | This project uses or is inspired by the following open source projects:
143 |
144 | - [golang-cli-template](https://github.com/FalcoSuessgott/golang-cli-template)
145 | - [terratest](https://terratest.gruntwork.io/)
146 | - [docker-terragrunt](https://github.com/devops-infra/docker-terragrunt)
147 | - [junit2html](https://github.com/kitproj/junit2html)
148 | ## Support
149 |
150 |
151 |
152 | ## License
153 |
154 | [MIT](LICENSE)
155 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-minimal
--------------------------------------------------------------------------------
/assets/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElementTech/terracove/e8f51194fee629a6a0483cbcb81fcbe5ace9b3d4/assets/example.png
--------------------------------------------------------------------------------
/assets/logo/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElementTech/terracove/e8f51194fee629a6a0483cbcb81fcbe5ace9b3d4/assets/logo/logo.png
--------------------------------------------------------------------------------
/cmd/root.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/elementtech/terracove/internal/types"
7 | "github.com/elementtech/terracove/pkg/scan"
8 | "github.com/spf13/cobra"
9 | )
10 |
11 | var OutputOptions types.OutputOptions
12 | var ValidateOptions types.ValidateOptions
13 | var RecursiveOptions types.RecursiveOptions
14 |
15 | func newRootCmd(version string) *cobra.Command {
16 | cmd := &cobra.Command{
17 | Use: "terracove [paths]...",
18 | Short: "terracove tests a directory tree for terraform/terragrunt diffs",
19 | Long: `terracove provides a recursive way to test the health and validity
20 | of a terraform/terragrunt repository structues.
21 | It plans all modules in parallel and outputs a report
22 | in one of more of the following formats: junit, html or json.`,
23 | Version: version,
24 | Args: cobra.MinimumNArgs(1),
25 | RunE: run,
26 | }
27 |
28 | cmd.Flags().BoolVarP(&OutputOptions.Json, "json", "j", false, "Output JSON")
29 | // cmd.Flags().BoolVarP(&OutputOptions.Yaml, "yaml", "y", false, "Output YAML")
30 | cmd.Flags().BoolVarP(&OutputOptions.Junit, "junit", "x", false, "Output Junit XML")
31 | cmd.Flags().BoolVarP(&OutputOptions.HTML, "html", "w", false, "Output HTML Report")
32 | cmd.Flags().StringVar(&OutputOptions.JsonOutPath, "o-json", "terracove.json", "Output JSON File")
33 | cmd.Flags().BoolVar(&OutputOptions.Minimal, "minimal", false, "Don't Append Raw/JSON Plan to the Exported Output")
34 | cmd.Flags().BoolVar(&OutputOptions.IgnoreError, "ignore-errors", false, "Ignore Planning Errors")
35 | cmd.Flags().BoolVar(&OutputOptions.IgnoreEmpty, "ignore-empty", false, "Ignore Modules with 0 Resources")
36 | // cmd.Flags().StringVar(&OutputOptions.YamlOutPath, "o-yaml", "terracove.yaml", "Output YAML")
37 | cmd.Flags().StringVar(&OutputOptions.JunitOutPath, "o-junit", "terracove.xml", "Output Junit XML File")
38 | cmd.Flags().StringVar(&OutputOptions.HTMLOutPath, "o-html", "terracove.html", "Output HTML Report File")
39 | cmd.Flags().StringSliceVarP(&RecursiveOptions.Exclude, "exclude", "e", []string{}, "Exclude directories while parsing tree")
40 | cmd.Flags().StringVarP(&ValidateOptions.ValidateTerraformBy, "validate-tf-by", "t", "main.tf", "validate terraform by the existence of [filename] in a directory")
41 | cmd.Flags().StringVarP(&ValidateOptions.ValidateTerragruntBy, "validate-tg-by", "g", "terragrunt.hcl", "validate terragrunt by the existence of [filename] in a directory")
42 | return cmd
43 | }
44 |
45 | // Execute invokes the command.
46 | func Execute(version string, testing bool) error {
47 | if err := newRootCmd(version).Execute(); err != nil {
48 | if testing {
49 | return nil
50 | } else {
51 | return fmt.Errorf("error executing root command: %w", err)
52 | }
53 | }
54 |
55 | return nil
56 | }
57 |
58 | func run(cmd *cobra.Command, args []string) error {
59 | scan.TerraformModulesTerratest(args, OutputOptions, ValidateOptions, RecursiveOptions)
60 | return nil
61 | }
62 |
--------------------------------------------------------------------------------
/cmd/root_test.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestNewRootCmd(t *testing.T) {
11 | version := "1.0.0"
12 | cmd := newRootCmd(version)
13 |
14 | assert.Equal(t, "terracove [paths]...", cmd.Use)
15 | assert.Equal(t, "terracove tests a directory tree for terraform/terragrunt diffs", cmd.Short)
16 | assert.Contains(t, cmd.Long, "terracove provides a recursive way to test the health and validity")
17 | assert.Equal(t, version, cmd.Version)
18 |
19 | assert.False(t, OutputOptions.Json)
20 | assert.False(t, OutputOptions.Junit)
21 | assert.Equal(t, "terracove.json", OutputOptions.JsonOutPath)
22 | assert.Equal(t, "terracove.xml", OutputOptions.JunitOutPath)
23 | assert.Equal(t, "main.tf", ValidateOptions.ValidateTerraformBy)
24 | assert.Equal(t, "terragrunt.hcl", ValidateOptions.ValidateTerragruntBy)
25 | }
26 |
27 | func TestRun(t *testing.T) {
28 | args := []string{"examples"}
29 | var stdout bytes.Buffer
30 | OutputOptions.Json = true
31 | OutputOptions.JsonOutPath = "output.json"
32 | rootCmd := newRootCmd("1.0.0")
33 | rootCmd.SetOut(&stdout)
34 |
35 | err := run(rootCmd, args)
36 |
37 | assert.NoError(t, err)
38 |
39 | }
40 |
41 | func TestExecute(t *testing.T) {
42 |
43 | err := Execute("1.0.0", true)
44 |
45 | assert.NoError(t, err)
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | status:
3 | project:
4 | default:
5 | target: auto
6 | threshold: 5%
7 | patch:
8 | default:
9 | target: 50%
10 | threshold: 5%
11 |
--------------------------------------------------------------------------------
/examples/terracove.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
62.5% Coverage
125 | .
126 | Tuesday, Apr 11, 2023 at 6:43pm
127 |
128 |
129 |
130 |
132 |
133 | examples/terragrunt/no-resources
134 |
135 | Skip
136 |
137 |
138 |
139 |
177 |
178 |
179 |
180 |
181 |
182 |
183 | [0m[1mInitializing the backend...[0m
184 |
185 | [0m[1mInitializing provider plugins...[0m
186 |
187 | [0m[1m[32mTerraform has been successfully initialized![0m[32m[0m
188 | [0m[32m
189 | You may now begin working with Terraform. Try running "terraform plan" to see
190 | any changes that are required for your infrastructure. All Terraform commands
191 | should now work.
192 |
193 | If you ever set or change modules or backend configuration for Terraform,
194 | rerun this command to reinitialize your working directory. If you forget, other
195 | commands will detect it and remind you to do so if necessary.[0m
196 |
197 | Changes to Outputs:
198 | [32m+[0m[0m output = "one input another input"
199 |
200 | You can apply this plan to save these new output values to the Terraform
201 | state, without changing any real infrastructure.
202 | [90m
203 | ─────────────────────────────────────────────────────────────────────────────[0m
204 |
205 | Saved the plan to: .terracove.plan
206 |
207 | To perform exactly these actions, run the following command to apply:
208 | terraform apply ".terracove.plan"
209 |
210 |
211 |
1.317304889s
212 |
213 |
214 |
215 |
217 |
218 | examples/terragrunt/error
219 |
220 | Error
221 |
222 |
223 |
224 |
262 |
263 |
264 |
265 |
266 |
267 | FatalError{Underlying: error while running command: exit status 1; [31m╷[0m[0m
268 | [31m│[0m [0m[1m[31mError: [0m[0m[1mReference to undeclared input variable[0m
269 | [31m│[0m [0m
270 | [31m│[0m [0m[0m on main.tf line 4, in output "output":
271 | [31m│[0m [0m 4: value = "${var.input} ${[4mvar.other_input[0m}"[0m
272 | [31m│[0m [0m
273 | [31m│[0m [0mAn input variable with the name "other_input" has not been declared. This
274 | [31m│[0m [0mvariable can be declared with a variable "other_input" {} block.
275 | [31m╵[0m[0m
276 | time=2023-04-11T18:43:57+03:00 level=error msg=Terraform invocation failed in /Users/amitai.getzler/Desktop/Explorium/projects/terracove/examples/terragrunt/error
277 | time=2023-04-11T18:43:57+03:00 level=error msg=1 error occurred:
278 | * exit status 1
279 |
280 | }
281 |
282 |
283 |
1.880412659s
284 |
285 |
286 |
287 |
352 |
353 |
441 |
442 |
443 |
444 |
445 |
446 |