diff --git a/.circleci/config.yml b/.circleci/config.yml index c2fde3b..e180bbe 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,12 +9,8 @@ jobs: working_directory: /go/src/github.com/xchapter7x/clarity steps: - checkout - - setup_remote_docker: - docker_layer_caching: true - run: sudo chmod -R 777 /usr/local - - run: go get -u github.com/maxbrunsfeld/counterfeiter - - run: go get github.com/DATA-DOG/godog/cmd/godog && mv /go/bin/godog /usr/local/bin/. - - run: dep ensure + - run: go get github.com/cucumber/godog/cmd/godog && mv /go/bin/godog /usr/local/bin/. - run: make unit - run: make build - run: mv cmd/clarity/build build diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dd84ea7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 0000000..48d5f81 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,10 @@ +--- +name: Custom issue template +about: Describe this issue template's purpose here. +title: '' +labels: '' +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml new file mode 100644 index 0000000..1264115 --- /dev/null +++ b/.github/workflows/greetings.yml @@ -0,0 +1,13 @@ +name: Greetings + +on: [pull_request, issues] + +jobs: + greeting: + runs-on: ubuntu-latest + steps: + - uses: actions/first-interaction@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + issue-message: 'Welcome and thank you for submitting this issue!! :)' + pr-message: 'Welcome and thank you for submitting this PR!! :)' diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..b8e93ab --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,19 @@ +name: Mark stale issues and pull requests + +on: + schedule: + - cron: "0 0 * * *" + +jobs: + stale: + + runs-on: ubuntu-latest + + steps: + - uses: actions/stale@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'This issue has been inactive for a while and is now considered stale' + stale-pr-message: 'This PRE has been inactive for a while and is now considered stale' + stale-issue-label: 'no-issue-activity' + stale-pr-label: 'no-pr-activity' diff --git a/.gitignore b/.gitignore index 2d4c299..499f083 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ vendor build/clarity* cmd/clarity/build +.DS_Store diff --git a/Gopkg.lock b/Gopkg.lock deleted file mode 100644 index fe35949..0000000 --- a/Gopkg.lock +++ /dev/null @@ -1,110 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - digest = "1:276b1553a866a15eeb3817f8689b51b69f1903a38ad7692bb369ff5b1e171910" - name = "github.com/DATA-DOG/godog" - packages = [ - ".", - "colors", - "gherkin", - ] - pruneopts = "UT" - revision = "d1db163b8f115dc74e1f504fc6a5558bdb5b3256" - version = "v0.7.10" - -[[projects]] - digest = "1:ea40c24cdbacd054a6ae9de03e62c5f252479b96c716375aace5c120d68647c8" - name = "github.com/hashicorp/hcl" - packages = [ - ".", - "hcl/ast", - "hcl/parser", - "hcl/scanner", - "hcl/strconv", - "hcl/token", - "json/parser", - "json/scanner", - "json/token", - ] - pruneopts = "UT" - revision = "8cb6e5b959231cc1119e43259c4a608f9c51a241" - version = "v1.0.0" - -[[projects]] - digest = "1:7a137fb7718928e473b7d805434ae563ec41790d3d227cdc64e8b14d1cab8a1f" - name = "github.com/onsi/gomega" - packages = [ - ".", - "format", - "internal/assertion", - "internal/asyncassertion", - "internal/oraclematcher", - "internal/testingtsupport", - "matchers", - "matchers/support/goraph/bipartitegraph", - "matchers/support/goraph/edge", - "matchers/support/goraph/node", - "matchers/support/goraph/util", - "types", - ] - pruneopts = "UT" - revision = "65fb64232476ad9046e57c26cd0bff3d3a8dc6cd" - version = "v1.4.3" - -[[projects]] - branch = "master" - digest = "1:03a8ff54c1a62710fe21a08ad9ccd554ac90232c4fd34c2731f65dd47e1f2077" - name = "golang.org/x/net" - packages = [ - "html", - "html/atom", - "html/charset", - ] - pruneopts = "UT" - revision = "3a22650c66bd7f4fb6d1e8072ffd7b75c8a27898" - -[[projects]] - digest = "1:aa4d6967a3237f8367b6bf91503964a77183ecf696f1273e8ad3551bb4412b5f" - name = "golang.org/x/text" - packages = [ - "encoding", - "encoding/charmap", - "encoding/htmlindex", - "encoding/internal", - "encoding/internal/identifier", - "encoding/japanese", - "encoding/korean", - "encoding/simplifiedchinese", - "encoding/traditionalchinese", - "encoding/unicode", - "internal/gen", - "internal/tag", - "internal/utf8internal", - "language", - "runes", - "transform", - "unicode/cldr", - ] - pruneopts = "UT" - revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" - version = "v0.3.0" - -[[projects]] - digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96" - name = "gopkg.in/yaml.v2" - packages = ["."] - pruneopts = "UT" - revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" - version = "v2.2.2" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - input-imports = [ - "github.com/DATA-DOG/godog", - "github.com/hashicorp/hcl", - "github.com/onsi/gomega", - ] - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml deleted file mode 100644 index f99b566..0000000 --- a/Gopkg.toml +++ /dev/null @@ -1,34 +0,0 @@ -# Gopkg.toml example -# -# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html -# for detailed Gopkg.toml documentation. -# -# required = ["github.com/user/thing/cmd/thing"] -# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] -# -# [[constraint]] -# name = "github.com/user/project" -# version = "1.0.0" -# -# [[constraint]] -# name = "github.com/user/project2" -# branch = "dev" -# source = "github.com/myfork/project2" -# -# [[override]] -# name = "github.com/x/y" -# version = "2.4.0" -# -# [prune] -# non-go = false -# go-tests = true -# unused-packages = true - - -[[constraint]] - name = "github.com/DATA-DOG/godog" - version = "0.7.10" - -[prune] - go-tests = true - unused-packages = true diff --git a/README.md b/README.md index f8c0336..4b19987 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,77 @@ # Clarity [![CircleCI](https://circleci.com/gh/xchapter7x/clarity.svg?style=svg)](https://circleci.com/gh/xchapter7x/clarity) -## A declaritive test framewark for Terraform +## A declaritive test framework for Terraform - reason: B/c unit testing terraform needs to be a thing ## Info: +- its a single binary, thats it. no need to write steps or go code. just write a declarative feature file. +- good for writing fitness function tests on your HCL - its gherkin bdd inspired - provides its own matchers and hcl parser - must be run from the directory where your terraform files live +- its a wrapper on [godog](https://github.com/DATA-DOG/godog), which provides built in terraform specific steps ## Install ```bash -$ export VERSION="v0.1.0" $ export OS="osx" #(osx | unix) -$ curl -sL https://github.com/xchapter7x/clarity/releases/download/${VERSION}/clarity_${OS} -o /usr/local/bin/clarity && chmod +x /usr/local/bin/clarity +$ curl -sL https://github.com/xchapter7x/clarity/releases/latest/download/clarity_${OS} -o /usr/local/bin/clarity && chmod +x /usr/local/bin/clarity ``` -## Download Binaries +### Download Binaries [HERE](https://github.com/xchapter7x/clarity/releases/latest) -### gherkin steps - | '([^"]*)' | noop to insert context into behavior def | - | 'Terraform' | parses the terraform from your local dir | - | 'a "([^"]*)" of type "([^"]*)"' | matches on types such as resource,data and the resource name | - | 'a "([^"]*)" of type "([^"]*)" named "([^"]*)"' | matches on types, resource names and instance names | - | 'attribute "([^"]*)" equals "([^"]*)"' | matches on the value given and the value of the attribute | - | 'attribute "([^"]*)" does not equal "([^"]*)"' | inverse match on attr value and given value | - | 'attribute "([^"]*)" exists' | if the given attribute exists in the matching objects | - | 'it occurs at least (\d+) times' | if the match set contains at least the given number | - | 'it occurs at most (\d+) times' | if the match set contains at most the given number | - | 'it occurs exactly (\d+) times' | if the match set continas exactly the given number | - | 'attribute "([^"]*)" matches regex "([^"]*)"' | matches the attributes value on the given regex | - | 'attribute "([^"]*)" is greater than (\d+)' | matches on gt against the given value and attr value | - | 'attribute "([^"]*)" is less than (\d+)' | matches on lt against the given value and attr value | - -## Run the Tests -```bash -$ make unit -``` +### CLI Options -### Build the binary -```bash -$ make build +``` +-> % clarity --help +Usage: + godog [options] [] + +Builds a test package and runs given feature files. +Command should be run from the directory of tested package and contain buildable go source. + +Arguments: + features Optional feature(s) to run. Can be: + - dir (features/) + - feature (*.feature) + - scenario at specific line (*.feature:10) + If no feature paths are listed, suite tries features path by default. + +Options: + -c, --concurrency=1 Run the test suite with concurrency level: + - = 1: supports all types of formats. + - >= 2: only supports progress. Note, that + your context needs to support parallel execution. + -d, --definitions Print all available step definitions. + -f, --format=pretty How to format tests output. Built-in formats: + - events: Produces JSON event stream, based on spec: 0.1.0. + - junit: Prints junit compatible xml to stdout + - pretty: Prints every feature with runtime statuses. + - progress: Prints a character per step. + - cucumber: Produces cucumber JSON format output. + --no-colors Disable ansi colors. + --random[=SEED] Randomly shuffle the scenario execution order. + Specify SEED to reproduce the shuffling from a previous run. + e.g. --random or --random=5738 + --stop-on-failure Stop processing on first failed scenario. + --strict Fail suite when there are pending or undefined steps. + -t, --tags= Filter scenarios by tags. Expression can be: + - "@wip": run all scenarios with wip tag + - "~@wip": exclude all scenarios with wip tag + - "@wip && ~@new": run wip scenarios, but exclude new + - "@wip,@undone": run wip or undone scenarios ``` -### Feature setup -```bash -$ nvim terraform/modules/control_plane.feature +### Writting your terraform tests + +Simply put a .feature file in the directory where the terraform you wish to test resides +```gherkin +$ ls +control_plane.feature dns.tf lb.tf network.tf outputs.tf variables.tf + +$ cat terraform/modules/control_plane.feature Feature: We should have a LB for our control plane and its components and as such we should configure the proper security groups and listeners @@ -56,6 +80,7 @@ Feature: We should have a LB for our control plane and its components and as Given Terraform And a "aws_lb" of type "resource" Then attribute "load_balancer_type" equals "network" + And it occurs exactly 1 times Scenario Outline: Every component of the control plane which needs a LB should be properly configured to have one @@ -64,16 +89,18 @@ Feature: We should have a LB for our control plane and its components and as And a "aws_security_group" of type "resource" And "our component is " When attribute "ingress" exists - Then attribute "from_port" equals "" - And attribute "to_port" equals "" + Then attribute "ingress" matches regex "from_port.*" + And attribute "ingress" matches regex "to_port.*" - Given a "aws_lb_listener" of type "resource" + Given Terraform + And a "aws_lb_listener" of type "resource" And "our component is " - Then attribute "port" equals "" + Then attribute "port" equals - Given a "aws_lb_target_group" of type "resource" + Given Terraform + And a "aws_lb_target_group" of type "resource" And "our component is " - Then attribute "port" equals "" + Then attribute "port" equals Examples: | port | component | @@ -84,8 +111,10 @@ Feature: We should have a LB for our control plane and its components and as | 8844 | CredHub | ``` -### Sample usage -``` +### Running your terraform tests + +Use the clarity cli to run any feature files you wish +```bash -> % clarity control_plane.feature Feature: We should have a LB for our control plane and its components and as such we should configure the proper security groups and listeners @@ -93,62 +122,94 @@ Feature: We should have a LB for our control plane and its components and as Scenario: we are using a single LB to route to all control plane components # control_plane.feature:4 Given Terraform # clarity_test.go:15 -> *Match And a "aws_lb" of type "resource" # clarity_test.go:16 -> *Match - Then attribute "load_balancer_type" equals "network" # clarity_test.go:17 -> *Match + Then attribute "load_balancer_type" equals "network" # clarity_test.go:19 -> *Match + And it occurs exactly 1 times # clarity_test.go:24 -> *Match - Scenario Outline: Every component of the control plane which needs a LB # control_plane.feature:9 + Scenario Outline: Every component of the control plane which needs a LB # control_plane.feature:10 Given Terraform # clarity_test.go:15 -> *Match And a "aws_security_group" of type "resource" # clarity_test.go:16 -> *Match And "our component is " # clarity_test.go:9 -> noopComment - When attribute "ingress" exists # clarity_test.go:19 -> *Match - Then attribute "from_port" equals "" # clarity_test.go:17 -> *Match - And attribute "to_port" equals "" # clarity_test.go:17 -> *Match - Given a "aws_lb_listener" of type "resource" # clarity_test.go:16 -> *Match + When attribute "ingress" exists # clarity_test.go:21 -> *Match + Then attribute "ingress" matches regex "from_port.*" # clarity_test.go:25 -> *Match + And attribute "ingress" matches regex "to_port.*" # clarity_test.go:25 -> *Match + Given Terraform # clarity_test.go:15 -> *Match + And a "aws_lb_listener" of type "resource" # clarity_test.go:16 -> *Match And "our component is " # clarity_test.go:9 -> noopComment - Then attribute "port" equals "" # clarity_test.go:17 -> *Match - Given a "aws_lb_target_group" of type "resource" # clarity_test.go:16 -> *Match + Then attribute "port" equals # clarity_test.go:17 -> *Match + Given Terraform # clarity_test.go:15 -> *Match + And a "aws_lb_target_group" of type "resource" # clarity_test.go:16 -> *Match And "our component is " # clarity_test.go:9 -> noopComment - Then attribute "port" equals "" # clarity_test.go:17 -> *Match + Then attribute "port" equals # clarity_test.go:17 -> *Match Examples: | port | component | | 443 | ATC | - no matches found for attribute from_port | 80 | ATC | - no matches found for attribute from_port | 8443 | UAA | - no matches found for attribute from_port | 2222 | TSA | - no matches found for attribute from_port | 8844 | CredHub | - no matches found for attribute from_port ---- Failed steps: +6 scenarios (6 passed) +74 steps (74 passed) +35.093777ms +``` - Scenario Outline: Every component of the control plane which needs a LB # control_plane.feature:9 - Then attribute "from_port" equals "443" # control_plane.feature:16 - Error: no matches found for attribute from_port +### gherkin step matchers available + | '([^"]*)' | noop to insert context into behavior def | + | 'HCL2' | parses the hcl2 from your local dir | + | 'Terraform' | parses the terraform <=.11 from your local dir | + | 'pending "([^"]*)"$' | mark pending and skip all following steps in the scenario | + | 'a "([^"]*)" of type "([^"]*)"' | matches on types such as resource,data and the resource name | + | 'a "([^"]*)" of type "([^"]*)" named "([^"]*)"' | matches on types, resource names and instance names | + | 'the value of "([^"]*)" always equals (\d+)' | matches on the value given and the value of the attribute | + | 'the value of "([^"]*)" never equals (\d+)' | inverse match on attr value and given value | + | 'the value of "([^"]*)" always equals "([^"]*)"' | matches on the value given and the value of the attribute | + | 'the value of "([^"]*)" never equals "([^"]*)"' | inverse match on attr value and given value | + | 'the value of "([^"]*)" always matches regex "([^"]*)"' | matches the attributes value on the given regex | + | 'the value of "([^"]*)" is always greater than (\d+)' | matches on gt against the given value and attr value | + | 'the value of "([^"]*)" is always less than (\d+)' | matches on lt against the given value and attr value | + | 'attribute "([^"]*)" always equals (\d+)' | matches on the value given and the value of the attribute | + | 'attribute "([^"]*)" never equals (\d+)' | inverse match on attr value and given value | + | 'attribute "([^"]*)" always equals "([^"]*)"' | matches on the value given and the value of the attribute | + | 'attribute "([^"]*)" never equals "([^"]*)"' | inverse match on attr value and given value | + | 'attribute "([^"]*)" always matches regex "([^"]*)"' | matches the attributes value on the given regex | + | 'attribute "([^"]*)" is always greater than (\d+)' | matches on gt against the given value and attr value | + | 'attribute "([^"]*)" is always less than (\d+)' | matches on lt against the given value and attr value | + | 'the value of "([^"]*)" equals (\d+)' | matches on the value given and the value of the attribute | + | 'the value of "([^"]*)" does not equal (\d+)' | inverse match on attr value and given value | + | 'the value of "([^"]*)" equals "([^"]*)"' | matches on the value given and the value of the attribute | + | 'the value of "([^"]*)" does not equal "([^"]*)"' | inverse match on attr value and given value | + | 'the value of "([^"]*)" matches regex "([^"]*)"' | matches the attributes value on the given regex | + | 'the value of "([^"]*)" is greater than (\d+)' | matches on gt against the given value and attr value | + | 'the value of "([^"]*)" is less than (\d+)' | matches on lt against the given value and attr value | + | 'attribute "([^"]*)" equals (\d+)' | matches on the value given and the value of the attribute | + | 'attribute "([^"]*)" does not equal (\d+)' | inverse match on attr value and given value | + | 'attribute "([^"]*)" equals "([^"]*)"' | matches on the value given and the value of the attribute | + | 'attribute "([^"]*)" does not equal "([^"]*)"' | inverse match on attr value and given value | + | 'attribute "([^"]*)" matches regex "([^"]*)"' | matches the attributes value on the given regex | + | 'attribute "([^"]*)" is greater than (\d+)' | matches on gt against the given value and attr value | + | 'attribute "([^"]*)" is less than (\d+)' | matches on lt against the given value and attr value | + | 'attribute "([^"]*)" exists' | if the given attribute exists in the matching objects | + | 'it occurs at least (\d+) times' | if the match set contains at least the given number | + | 'it occurs at most (\d+) times' | if the match set contains at most the given number | + | 'it occurs exactly (\d+) times' | if the match set continas exactly the given number | + +## Development & Contributions: +- all issues and PRs welcome. - Scenario Outline: Every component of the control plane which needs a LB # control_plane.feature:9 - Then attribute "from_port" equals "80" # control_plane.feature:16 - Error: no matches found for attribute from_port - Scenario Outline: Every component of the control plane which needs a LB # control_plane.feature:9 - Then attribute "from_port" equals "8443" # control_plane.feature:16 - Error: no matches found for attribute from_port +### Run the Tests +```bash +$ make unit +``` - Scenario Outline: Every component of the control plane which needs a LB # control_plane.feature:9 - Then attribute "from_port" equals "2222" # control_plane.feature:16 - Error: no matches found for attribute from_port +### Build the binary +```bash +$ make build +``` - Scenario Outline: Every component of the control plane which needs a LB # control_plane.feature:9 - Then attribute "from_port" equals "8844" # control_plane.feature:16 - Error: no matches found for attribute from_port -6 scenarios (1 passed, 5 failed) -63 steps (23 passed, 5 failed, 35 skipped) -14.033302ms -``` -## Contributions: -- all issues and PRs welcome. +## For more complete testing support using OPA policies check out: +https://github.com/instrumenta/conftest diff --git a/cmd/clarity/clarity_test.go b/cmd/clarity/clarity_test.go index 2a04f7f..8804717 100644 --- a/cmd/clarity/clarity_test.go +++ b/cmd/clarity/clarity_test.go @@ -1,29 +1,54 @@ package main import ( - "github.com/DATA-DOG/godog" + "github.com/cucumber/godog" + "github.com/cucumber/messages-go/v10" "github.com/xchapter7x/clarity/pkg/matchers" ) -func noopComment(arg1 string) error { - return nil -} - func FeatureContext(s *godog.Suite) { match := matchers.NewMatch() - s.Step(`^"([^"]*)"$`, noopComment) + s.BeforeScenario(func(*messages.Pickle) { + match = matchers.NewMatch() + }) s.Step(`^Terraform$`, match.Terraform) + s.Step(`^HCL2$`, match.HCL2) s.Step(`^a "([^"]*)" of type "([^"]*)"$`, match.AOfType) - s.Step(`^attribute "([^"]*)" equals (\d+)$`, match.AttributeEqualsInt) - s.Step(`^attribute "([^"]*)" does not equal (\d+)$`, match.AttributeDoesNotEqualInt) - s.Step(`^attribute "([^"]*)" equals "([^"]*)"$`, match.AttributeEquals) - s.Step(`^attribute "([^"]*)" does not equal "([^"]*)"$`, match.AttributeDoesNotEqual) + s.Step(`^a "([^"]*)" of type "([^"]*)" named "([^"]*)"$`, match.AOfTypeNamed) + + s.Step(`^"([^"]*)"$`, noopComment) + s.Step(`^pending "([^"]*)"$`, markPending) + for _, phrasePrefix := range []string{ + "the value of", + "attribute", + } { + s.Step(`^`+phrasePrefix+` "([^"]*)" equals (\d+)$`, match.AttributeEqualsInt) + s.Step(`^`+phrasePrefix+` "([^"]*)" does not equal (\d+)$`, match.AttributeDoesNotEqualInt) + s.Step(`^`+phrasePrefix+` "([^"]*)" equals "([^"]*)"$`, match.AttributeEquals) + s.Step(`^`+phrasePrefix+` "([^"]*)" does not equal "([^"]*)"$`, match.AttributeDoesNotEqual) + s.Step(`^`+phrasePrefix+` "([^"]*)" matches regex "([^"]*)"$`, match.AttributeRegex) + s.Step(`^`+phrasePrefix+` "([^"]*)" is greater than (\d+)$`, match.AttributeGreaterThan) + s.Step(`^`+phrasePrefix+` "([^"]*)" is less than (\d+)$`, match.AttributeLessThan) + + s.Step(`^`+phrasePrefix+` "([^"]*)" always equals (\d+)$`, match.AlwaysAttributeEqualsInt) + s.Step(`^`+phrasePrefix+` "([^"]*)" never equals (\d+)$`, match.AlwaysAttributeDoesNotEqualInt) + s.Step(`^`+phrasePrefix+` "([^"]*)" always equals "([^"]*)"$`, match.AlwaysAttributeEquals) + s.Step(`^`+phrasePrefix+` "([^"]*)" never equals "([^"]*)"$`, match.AlwaysAttributeDoesNotEqual) + s.Step(`^`+phrasePrefix+` "([^"]*)" always matches regex "([^"]*)"$`, match.AlwaysAttributeRegex) + s.Step(`^`+phrasePrefix+` "([^"]*)" is always greater than (\d+)$`, match.AlwaysAttributeGreaterThan) + s.Step(`^`+phrasePrefix+` "([^"]*)" is always less than (\d+)$`, match.AlwaysAttributeLessThan) + } + s.Step(`^attribute "([^"]*)" exists$`, match.AttributeExists) s.Step(`^it occurs at least (\d+) times$`, match.ItOccursAtLeastTimes) s.Step(`^it occurs at most (\d+) times$`, match.ItOccursAtMostTimes) s.Step(`^it occurs exactly (\d+) times$`, match.ItOccursExactlyTimes) - s.Step(`^attribute "([^"]*)" matches regex "([^"]*)"$`, match.AttributeRegex) - s.Step(`^a "([^"]*)" of type "([^"]*)" named "([^"]*)"$`, match.AOfTypeNamed) - s.Step(`^attribute "([^"]*)" is greater than (\d+)$`, match.AttributeGreaterThan) - s.Step(`^attribute "([^"]*)" is less than (\d+)$`, match.AttributeLessThan) +} + +func noopComment(arg1 string) error { + return nil +} + +func markPending(arg1 string) error { + return godog.ErrPending } diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..08b7d9e --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module github.com/xchapter7x/clarity + +go 1.13 + +require ( + github.com/cucumber/godog v0.10.0 + github.com/cucumber/messages-go/v10 v10.0.3 + github.com/hashicorp/hcl v1.0.0 + github.com/hashicorp/hcl/v2 v2.4.0 // indirect + github.com/instrumenta/conftest v0.18.1 + github.com/onsi/gomega v1.10.2 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..bf7d49a --- /dev/null +++ b/go.sum @@ -0,0 +1,485 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cuelang.org/go v0.0.15/go.mod h1:gehQASsTv+lFZknWIG0hANGVSBiHD7HyKWmAdEZL3No= +github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/KeisukeYamashita/go-vcl v0.4.0/go.mod h1:af2qGlXbsHDQN5abN7hyGNKtGhcFSaDdbLl4sfud+AU= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OneOfOne/xxhash v1.2.7/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= +github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= +github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0= +github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= +github.com/apparentlymart/go-textseg/v12 v12.0.0 h1:bNEQyAGak9tojivJNkoqWErVCQbjdL7GzRt3F8NvfJ0= +github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/aslakhellesoy/gox v1.0.100/go.mod h1:AJl542QsKKG96COVsv0N74HHzVQgDIQPceVUh1aeU2M= +github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= +github.com/basgys/goxml2json v1.1.0/go.mod h1:wH7a5Np/Q4QoECFIU8zTQlZwZkrilY0itPfecMw41Dw= +github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cockroachdb/apd/v2 v2.0.1/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= +github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cucumber/gherkin-go/v11 v11.0.0 h1:cwVwN1Qn2VRSfHZNLEh5x00tPBmZcjATBWDpxsR5Xug= +github.com/cucumber/gherkin-go/v11 v11.0.0/go.mod h1:CX33k2XU2qog4e+TFjOValoq6mIUq0DmVccZs238R9w= +github.com/cucumber/godog v0.9.0 h1:QOb8wyC7f+FVFXzY3RdgowwJUb4WeJfqbnQqaH4jp+A= +github.com/cucumber/godog v0.9.0/go.mod h1:roWCHkpeK6UTOyIRRl7IR+fgfBeZ4vZR7OSq2J/NbM4= +github.com/cucumber/godog v0.10.0 h1:W01u1+o8bRpgqJRLrclN3iAanU1jAao+TwOMoSV9g1Y= +github.com/cucumber/godog v0.10.0/go.mod h1:0Q+MOUg8Z9AhzLV+nNMbThQ2x1b17yYwGyahApTLjJA= +github.com/cucumber/messages-go/v10 v10.0.1/go.mod h1:kA5T38CBlBbYLU12TIrJ4fk4wSkVVOgyh7Enyy8WnSg= +github.com/cucumber/messages-go/v10 v10.0.3 h1:m/9SD/K/A15WP7i1aemIv7cwvUw+viS51Ui5HBw1cdE= +github.com/cucumber/messages-go/v10 v10.0.3/go.mod h1:9jMZ2Y8ZxjLY6TG2+x344nt5rXstVVDYSdS5ySfI1WY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deislabs/oras v0.8.0/go.mod h1:v1Vmv0oHch7HS/gK7CGIdIp7s5lEiLlKvwMxZ34sQDs= +github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= +github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v0.0.0-20191008191456-ae2e973db936/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= +github.com/docker/docker v0.7.3-0.20190826074503-38ab9da00309/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/emicklei/proto v1.6.15/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-akka/configuration v0.0.0-20190919102339-a31c845c4b1b/go.mod h1:19bUnum2ZAeftfwwLZ/wRe7idyfoW2MfmXO464Hrfbw= +github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-ini/ini v1.51.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/godoctor/godoctor v0.0.0-20181123222458-69df17f3a6f6/go.mod h1:+tyhT8jBF8E0XvdlSXOSL7Iko7DlNiongHq3q+wcsPs= +github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/protobuf v0.0.0-20181025225059-d3de96c4c28e/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v0.0.0-20181024020800-521ea7b17d02/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-getter v1.4.0/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY= +github.com/hashicorp/go-immutable-radix v1.2.0 h1:l6UW37iCXwZkZoAbEYnptSHVE/cQ5bOTPYG5W3vf9+8= +github.com/hashicorp/go-immutable-radix v1.2.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-memdb v1.2.1 h1:wI9btDjYUOJJHTCnRlAG/TkRyD/ij7meJMrLK9X31Cc= +github.com/hashicorp/go-memdb v1.2.1/go.mod h1:OSvLJ662Jim8hMM+gWGyhktyWk2xPCnWMc7DWIqtkGA= +github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/hcl/v2 v2.2.0/go.mod h1:MD4q2LOluJ5pRwTVkCXmJOY7ODWDXVXGVB8LY0t7wig= +github.com/hashicorp/hcl/v2 v2.4.0 h1:xwVa1aj4nCSoAjUnFPBAIfqlzPgSZEVMdkJv/mgj4jY= +github.com/hashicorp/hcl/v2 v2.4.0/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/instrumenta/conftest v0.18.1 h1:uQ/yiuFNziLRTR3ttzOimR+uX416VVcOYpPFd9SnUKQ= +github.com/instrumenta/conftest v0.18.1/go.mod h1:mwEMPaIgyqKjw4/suqZ21YEXdeCp2Kx51n4wosbM9JE= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-runewidth v0.0.0-20181025052659-b20a3daf6a39/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= +github.com/mna/pigeon v0.0.0-20180808201053-bb0192cfc2ae/go.mod h1:Iym28+kJVnC1hfQvv5MUtI6AiFFzvQjHcvI4RFTG/04= +github.com/moby/buildkit v0.3.3/go.mod h1:nnELdKPRkUAQR6pAB3mRU3+IlbqL3SSaAWqQL8k/K+4= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= +github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs= +github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/open-policy-agent/opa v0.18.0/go.mod h1:6pC1cMYDI92i9EY/GoA2m+HcZlcCrh3jbfny5F7JVTA= +github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= +github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= +github.com/pkg/errors v0.0.0-20181023235946-059132a15dd0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.0.0-20181025174421-f30f42803563/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/testscript v1.1.0/go.mod h1:lzMlnW8LS56mcdJoQYkrlzqOoTFCOemzt5LusJ93bDM= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.0-20181021141114-fe5e611709b0/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20181024212040-082b515c9490/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.5.0/go.mod h1:AkYRkVJF8TkSG/xet6PzXX+l39KhhXa2pdqVSxnTcn4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/vektah/gqlparser v1.2.0/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74= +github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b/go.mod h1:HptNXiXVDcJjXe9SqMd0v2FsL9f8dz4GnXgltU6q/co= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +github.com/zclconf/go-cty v1.1.1/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= +github.com/zclconf/go-cty v1.2.0 h1:sPHsy7ADcIZQP3vILvTjrh74ZA175TFP5vqiNK1UmlI= +github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/lint v0.0.0-20181023182221-1baf3a9d7d67/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +olympos.io/encoding/edn v0.0.0-20191103180435-78e1aef28b15/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/makefile b/makefile index c474552..5ded3d9 100644 --- a/makefile +++ b/makefile @@ -13,13 +13,13 @@ BINARY_DARWIN=$(BINARY_NAME)_osx all: test build build: build-darwin build-win build-linux -test: gen unit integration +test: gen unit unit: $(GOTEST) ./pkg/... -v integration: - GOCACHE=off $(GOTEST) ./test/integration/... -v + $(GOTEST) ./test/integration/... -v e2e: - GOCACHE=off $(GOTEST) ./test/e2e/... -v + $(GOTEST) ./test/e2e/... -v clean: $(GOCLEAN) find . -name "*.test" | xargs rm diff --git a/pkg/matchers/terraform.go b/pkg/matchers/terraform.go index 079de3a..b6c5a68 100644 --- a/pkg/matchers/terraform.go +++ b/pkg/matchers/terraform.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/hashicorp/hcl" + hcl2 "github.com/instrumenta/conftest/parser/hcl2" ) func NewMatch() *Match { @@ -29,29 +30,57 @@ type HCLEntry struct { Attributes interface{} } -func parseHCL(rawHCL map[string]interface{}) []HCLEntry { +func parseHCL2(rawHCL map[string]interface{}) []HCLEntry { hclEntries := make([]HCLEntry, 0) for hclType, componentsArrayMap := range rawHCL { - for _, componentsMap := range componentsArrayMap.([]map[string]interface{}) { - for componentName, instancesFormatted := range componentsMap { - switch instancesCast := instancesFormatted.(type) { - case []map[string]interface{}: - instances := flattenArrayMap(instancesCast) - for instanceName, attributes := range instances { - hclEntries = append(hclEntries, HCLEntry{ - HCLType: hclType, - ComponentName: componentName, - InstanceName: instanceName, - Attributes: attributes, - }) - } - default: + switch components := componentsArrayMap.(type) { + case map[string]interface{}: + hclEntries = addComponentsToEntries(hclEntries, hclType, components) + + case []map[string]interface{}: + for _, componentsMap := range components { + hclEntries = addComponentsToEntries(hclEntries, hclType, componentsMap) + } + } + } + return hclEntries +} + +func addComponentsToEntries(hclEntries []HCLEntry, hclType string, componentsMap map[string]interface{}) []HCLEntry { + for componentName, instancesFormatted := range componentsMap { + if hclType == "output" || hclType == "module" || hclType == "provider" { + hclEntries = append(hclEntries, HCLEntry{ + HCLType: hclType, + ComponentName: componentName, + Attributes: instancesFormatted, + }) + } else { + switch instancesCast := instancesFormatted.(type) { + case []map[string]interface{}: + instances := flattenArrayMap(instancesCast) + for instanceName, attributes := range instances { hclEntries = append(hclEntries, HCLEntry{ - HCLType: hclType, - InstanceName: componentName, - Attributes: instancesCast, + HCLType: hclType, + ComponentName: componentName, + InstanceName: instanceName, + Attributes: attributes, }) } + case map[string]interface{}: + for instanceName, instance := range instancesCast { + hclEntries = append(hclEntries, HCLEntry{ + HCLType: hclType, + ComponentName: componentName, + InstanceName: instanceName, + Attributes: instance, + }) + } + default: + hclEntries = append(hclEntries, HCLEntry{ + HCLType: hclType, + InstanceName: componentName, + Attributes: instancesCast, + }) } } } @@ -65,12 +94,129 @@ func (m *Match) Terraform() error { if err != nil { log.Panic(err) } - return m.ReadTerraform(pwd) + return m.ReadTerraform(pwd, GetUnmarshallerVersion(1)) +} + +// HCL2 a simple matcher to show intent to init from a hcl2 terraform in +// current directory +func (m *Match) HCL2() error { + pwd, err := os.Getwd() + if err != nil { + log.Panic(err) + } + return m.ReadTerraform(pwd, GetUnmarshallerVersion(2)) +} + +// AlwaysAttributeEqualsInt - requires all elements to have an exact match on attributes or it fails +func (m *Match) AlwaysAttributeEqualsInt(searchKey string, searchValue int) error { + startingEntryCount := len(m.MatchingEntries) + err := m.AttributeEqualsInt(searchKey, searchValue) + if err != nil { + return err + } + + if startingEntryCount != len(m.MatchingEntries) { + return fmt.Errorf("not all entries match on %v : %v", searchKey, searchValue) + } + return nil +} + +// AlwaysAttributeEquals - requires all elements to have an exact match on attributes or it fails +func (m *Match) AlwaysAttributeEquals(searchKey, searchValue string) error { + startingEntryCount := len(m.MatchingEntries) + err := m.AttributeEquals(searchKey, searchValue) + if err != nil { + return err + } + + if startingEntryCount != len(m.MatchingEntries) { + return fmt.Errorf("not all entries match on %v : %v", searchKey, searchValue) + } + return nil +} + +// AlwaysAttributeDoesNotEqualInt - all elements' attr of search key must not match the search value +func (m *Match) AlwaysAttributeDoesNotEqualInt(searchKey string, searchValue int) error { + startingEntryCount := len(m.MatchingEntries) + err := m.AttributeDoesNotEqualInt(searchKey, searchValue) + if err != nil { + return err + } + + if startingEntryCount != len(m.MatchingEntries) { + return fmt.Errorf("we found un-expected matches on %v : %v", searchKey, searchValue) + } + return nil +} + +// AlwaysAttributeDoesNotEqual - all elements' attr of search key must not match the search value +func (m *Match) AlwaysAttributeDoesNotEqual(searchKey string, searchValue string) error { + startingEntryCount := len(m.MatchingEntries) + err := m.AttributeDoesNotEqual(searchKey, searchValue) + if err != nil { + return err + } + + if startingEntryCount != len(m.MatchingEntries) { + return fmt.Errorf("we found un-expected matches on %v : %v", searchKey, searchValue) + } + return nil +} + +// AlwaysAttributeRegex - Check if all elements match regex +func (m *Match) AlwaysAttributeRegex(attributeName, regexString string) error { + startingEntryCount := len(m.MatchingEntries) + err := m.AttributeRegex(attributeName, regexString) + if err != nil { + return err + } + + if startingEntryCount != len(m.MatchingEntries) { + return fmt.Errorf("not every entry matches on %v : %v", attributeName, regexString) + } + return nil +} + +func (m *Match) AlwaysAttributeGreaterThan(searchKey string, searchValue int) error { + startingEntryCount := len(m.MatchingEntries) + err := m.AttributeGreaterThan(searchKey, searchValue) + if err != nil { + return err + } + + if startingEntryCount != len(m.MatchingEntries) { + return fmt.Errorf("not every entry matches on %v : %v", searchKey, searchValue) + } + return nil +} + +func (m *Match) AlwaysAttributeLessThan(searchKey string, searchValue int) error { + startingEntryCount := len(m.MatchingEntries) + err := m.AttributeLessThan(searchKey, searchValue) + if err != nil { + return err + } + + if startingEntryCount != len(m.MatchingEntries) { + return fmt.Errorf("not every entry matches on %v : %v", searchKey, searchValue) + } + return nil +} + +type Unmarshaller func(p []byte, v interface{}) error + +func GetUnmarshallerVersion(version int) Unmarshaller { + if version == 1 { + return hcl.Unmarshal + } + + p := new(hcl2.Parser) + return p.Unmarshal } // ReadTerrraform a simple matcher to init from terraform in // a given directory -func (m *Match) ReadTerraform(tpath string) error { +func (m *Match) ReadTerraform(tpath string, unmarshal Unmarshaller) error { baseHCL := make(map[string]interface{}, 0) dirContents := "" files, err := ioutil.ReadDir(tpath) @@ -86,15 +232,16 @@ func (m *Match) ReadTerraform(tpath string) error { } dirContents += string(contents) + dirContents += "\n" } } - err = hcl.Unmarshal([]byte(dirContents), &baseHCL) + err = unmarshal([]byte(dirContents), &baseHCL) if err != nil { return fmt.Errorf("hcl Unmarshal failed: %v", err) } - m.HCLEntries = parseHCL(baseHCL) + m.HCLEntries = parseHCL2(baseHCL) return nil } @@ -136,7 +283,6 @@ func (m *Match) AOfType(providerFeature, providerFeatureType string) error { entries, _ := json.Marshal(m.HCLEntries) return fmt.Errorf("no matches found for attribute %v \n %v", providerFeature, string(entries)) } - m.MatchingEntries = append(m.MatchingEntries, matchingFeature...) return nil } @@ -163,7 +309,7 @@ func (m *Match) AttributeExists(searchKey string) error { } for _, entry := range m.MatchingEntries { - exists, _ := attributeExists(searchKey, entry.Attributes.([]map[string]interface{})) + exists, _ := attributeExists(searchKey, entry.Attributes) if exists { tmpEntries = append(tmpEntries, entry) } @@ -177,9 +323,18 @@ func (m *Match) AttributeExists(searchKey string) error { return nil } -func attributeExists(searchName string, attributes []map[string]interface{}) (bool, interface{}) { - for _, attributeMap := range attributes { - for k, v := range attributeMap { +func attributeExists(searchName string, attributes interface{}) (bool, interface{}) { + switch attributesCast := attributes.(type) { + case []map[string]interface{}: + for _, attributeMap := range attributesCast { + for k, v := range attributeMap { + if k == searchName { + return true, v + } + } + } + case map[string]interface{}: + for k, v := range attributesCast { if k == searchName { return true, v } @@ -197,8 +352,16 @@ func (m *Match) AttributeEqualsInt(searchKey string, searchValue int) error { } for _, entry := range m.MatchingEntries { - exists, attributeValue := attributeExists(searchKey, entry.Attributes.([]map[string]interface{})) - if exists && attributeValue == searchValue { + exists, attributeValue := attributeExists(searchKey, entry.Attributes) + var attributeInteger int + switch v := attributeValue.(type) { + case int: + attributeInteger = v + case float64: + attributeInteger = int(v) + } + + if exists && attributeInteger == searchValue { tmpEntries = append(tmpEntries, entry) } } @@ -220,8 +383,16 @@ func (m *Match) AttributeDoesNotEqualInt(searchKey string, searchValue int) erro } for _, entry := range m.MatchingEntries { - exists, attributeValue := attributeExists(searchKey, entry.Attributes.([]map[string]interface{})) - if exists && attributeValue != searchValue { + exists, attributeValue := attributeExists(searchKey, entry.Attributes) + var attributeInteger int + switch v := attributeValue.(type) { + case int: + attributeInteger = v + case float64: + attributeInteger = int(v) + } + + if exists && attributeInteger != searchValue { tmpEntries = append(tmpEntries, entry) } } @@ -243,7 +414,7 @@ func (m *Match) AttributeEquals(searchKey, searchValue string) error { } for _, entry := range m.MatchingEntries { - exists, attributeValue := attributeExists(searchKey, entry.Attributes.([]map[string]interface{})) + exists, attributeValue := attributeExists(searchKey, entry.Attributes) if exists && attributeValue == searchValue { tmpEntries = append(tmpEntries, entry) } @@ -266,7 +437,7 @@ func (m *Match) AttributeDoesNotEqual(searchKey, searchValue string) error { } for _, entry := range m.MatchingEntries { - exists, attributeValue := attributeExists(searchKey, entry.Attributes.([]map[string]interface{})) + exists, attributeValue := attributeExists(searchKey, entry.Attributes) if exists && attributeValue != searchValue { tmpEntries = append(tmpEntries, entry) } @@ -288,7 +459,7 @@ func (m *Match) AttributeGreaterThan(searchKey string, searchValue int) error { } for _, entry := range m.MatchingEntries { - exists, attributeValue := attributeExists(searchKey, entry.Attributes.([]map[string]interface{})) + exists, attributeValue := attributeExists(searchKey, entry.Attributes) if !exists { return fmt.Errorf("no attribute found named: %s", searchKey) } @@ -302,6 +473,10 @@ func (m *Match) AttributeGreaterThan(searchKey string, searchValue int) error { } case int: actualValue = attributeValue + case float64: + actualValue = int(attributeValue) + default: + return fmt.Errorf("could not translate to int") } if exists && actualValue > searchValue { @@ -325,7 +500,7 @@ func (m *Match) AttributeLessThan(searchKey string, searchValue int) error { } for _, entry := range m.MatchingEntries { - exists, attributeValue := attributeExists(searchKey, entry.Attributes.([]map[string]interface{})) + exists, attributeValue := attributeExists(searchKey, entry.Attributes) if !exists { return fmt.Errorf("no attribute found named: %s", searchKey) } @@ -390,7 +565,7 @@ func (m *Match) AttributeRegex(attributeName, regexString string) error { } for _, entry := range m.MatchingEntries { - exists, attributeValue := attributeExists(attributeName, entry.Attributes.([]map[string]interface{})) + exists, attributeValue := attributeExists(attributeName, entry.Attributes) if !exists { return fmt.Errorf("no attribute found named: %s", attributeName) } diff --git a/pkg/matchers/terraform_test.go b/pkg/matchers/terraform_test.go index 87c8b46..469fb4d 100644 --- a/pkg/matchers/terraform_test.go +++ b/pkg/matchers/terraform_test.go @@ -1,6 +1,7 @@ package matchers_test import ( + "sort" "testing" . "github.com/onsi/gomega" @@ -9,336 +10,573 @@ import ( func TestMatchers(t *testing.T) { RegisterTestingT(t) - t.Run("m.ReadTerraform", func(t *testing.T) { - m := &matchers.Match{} - controlHCL := multiTFControl() - t.Run("should read all the terraform files into the matcher", func(t *testing.T) { - err := m.ReadTerraform("testdata/multitf") - Expect(err).NotTo(HaveOccurred()) - Expect(m.HCLEntries).To(Equal(controlHCL)) - }) - }) - t.Run("m.AOfTypeNamed", func(t *testing.T) { - t.Run("should return a filtered list of definitions", func(t *testing.T) { - m := &matchers.Match{} - m.ReadTerraform("testdata") - err := m.AOfTypeNamed("google_compute_network", "resource", "my-custom-network") - Expect(err).NotTo(HaveOccurred()) - Expect(m.MatchingEntries).To(BeEquivalentTo(controlInstanceMatch())) - }) - }) - - t.Run("m.AOfType", func(t *testing.T) { - t.Run("should return a filtered list of definitions", func(t *testing.T) { - m := &matchers.Match{} - m.ReadTerraform("testdata") - err := m.AOfType("google_compute_network", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.MatchingEntries).To(BeEquivalentTo(controlInstanceMatch())) - }) - }) - - t.Run("m.AttributeExists", func(t *testing.T) { - t.Run("when there are no matches", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("google_compute_subnetwork", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeExists("bbbbbasdf")).To(HaveOccurred()) - }) - - t.Run("when there are matches", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("google_compute_network", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeExists("name")).NotTo(HaveOccurred()) - Expect(m.MatchingEntries).To(BeEquivalentTo(controlInstanceMatch())) - }) - }) - - t.Run("m.AttributeGreaterThan", func(t *testing.T) { - t.Run("when there are no matches", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("blah_blah", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeGreaterThan("nadafield", 81)).To(HaveOccurred()) - }) - - t.Run("when a valid attribute has non gt value", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("blah_blah", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeGreaterThan("port", 81)).To(HaveOccurred()) - }) - - t.Run("when there are matches with a correct value", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("blah_blah", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeGreaterThan("port", 79)).NotTo(HaveOccurred()) - Expect(m.MatchingEntries).To(BeEquivalentTo(controlInstanceMatchIntString())) - }) - - t.Run("when there are matches with a correct value and the attr value is a int type", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("foo", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeGreaterThan("port", 79)).NotTo(HaveOccurred()) - Expect(m.MatchingEntries).To(BeEquivalentTo(controlInstanceMatchInt())) - }) - }) - - t.Run("m.ItOccursAtLeastTimes", func(t *testing.T) { - t.Run("when we dont have enough to satisfy at least", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("blah_blah", "resource") - Expect(m.ItOccursAtLeastTimes(5)).To(HaveOccurred()) - }) - t.Run("when we have enough to satisfy at least", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("blah_blah", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.ItOccursAtLeastTimes(1)).NotTo(HaveOccurred()) - }) - }) - - t.Run("m.ItOccursAtMostTimes", func(t *testing.T) { - t.Run("when we have too many to satisfy at most", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("blah_blah", "resource") - Expect(m.ItOccursAtMostTimes(0)).To(HaveOccurred()) - }) - t.Run("when we have enough to satisfy at most", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("blah_blah", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.ItOccursAtMostTimes(3)).NotTo(HaveOccurred()) - }) - }) - - t.Run("m.ItOccursExactlyTimes", func(t *testing.T) { - t.Run("when we dont have exactly the count", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("blah_blah", "resource") - Expect(m.ItOccursExactlyTimes(2)).To(HaveOccurred()) - }) - t.Run("when we have exactly the count", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("blah_blah", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.ItOccursExactlyTimes(1)).NotTo(HaveOccurred()) - }) - }) - - t.Run("m.AttributeRegex", func(t *testing.T) { - t.Run("when we do not have a attr value which satisfies the regex", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("blah_blah", "resource") - Expect(m.AttributeRegex("port", "abc")).To(HaveOccurred()) - }) - t.Run("when our attribute value satisfies the regex", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("blah_blah", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeRegex("port", "8*")).NotTo(HaveOccurred()) - }) - t.Run("when our attribute is a complex object and it satisfies the regex", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfTypeNamed("google_compute_firewall", "resource", "allow-all-internal") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeExists("allow")).NotTo(HaveOccurred()) - Expect(m.AttributeRegex("allow", "protocol.*tcp")).NotTo(HaveOccurred()) - }) - }) - - t.Run("m.AttributeLessThan", func(t *testing.T) { - t.Run("when there are no matches", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("blah_blah", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeLessThan("nadafield", 79)).To(HaveOccurred()) - }) - - t.Run("when a valid attribute has non gt value", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("blah_blah", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeLessThan("port", 79)).To(HaveOccurred()) - }) - - t.Run("when there are matches with a correct value", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("blah_blah", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeLessThan("port", 81)).NotTo(HaveOccurred()) - Expect(m.MatchingEntries).To(BeEquivalentTo(controlInstanceMatchIntString())) - }) - - t.Run("when there are matches with a correct value and attr value is type int", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("foo", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeLessThan("port", 81)).NotTo(HaveOccurred()) - Expect(m.MatchingEntries).To(BeEquivalentTo(controlInstanceMatchInt())) - }) - }) - - t.Run("m.AttributeEqualsInt", func(t *testing.T) { - t.Run("when there are no matches", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("foo", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeEqualsInt("bbbbbasdf", 0)).To(HaveOccurred()) - }) - - t.Run("when a valid attribute has non equal value", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("foo", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeEqualsInt("port", 43)).To(HaveOccurred()) - }) - - t.Run("when there are matches with a correct value", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("foo", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeEqualsInt("port", 80)).NotTo(HaveOccurred()) - Expect(m.MatchingEntries).To(BeEquivalentTo(controlInstanceMatchInt())) - }) - }) - - t.Run("m.AttributeDoesNotEqualInt", func(t *testing.T) { - t.Run("when there are no matches", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("foo", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeDoesNotEqualInt("bbbbbasdf", 0)).To(HaveOccurred()) + for _, tt := range []struct { + unmarshal matchers.Unmarshaller + name string + skip bool + }{ + {name: "hcl1", unmarshal: matchers.GetUnmarshallerVersion(1)}, + {name: "hcl2", unmarshal: matchers.GetUnmarshallerVersion(2)}, + } { + t.Run(tt.name, func(t *testing.T) { + if tt.skip { + t.Skip("skipping", tt.name) + } + unmarshal := tt.unmarshal + t.Run("m.ReadTerraform", func(t *testing.T) { + m := &matchers.Match{} + controlHCL := multiTFControl() + t.Run("should read all the terraform files into the matcher", func(t *testing.T) { + for _, dirname := range []string{"testdata/multitf", "testdata/multitf_newline"} { + unmarshal := tt.unmarshal + err := m.ReadTerraform(dirname, unmarshal) + Expect(err).NotTo(HaveOccurred()) + sort.SliceStable(controlHCL, func(i, j int) bool { return controlHCL[i].InstanceName < controlHCL[j].InstanceName }) + sort.SliceStable(m.HCLEntries, func(i, j int) bool { return m.HCLEntries[i].InstanceName < m.HCLEntries[j].InstanceName }) + Expect(len(m.HCLEntries)).To(Equal(len(controlHCL))) + attributesMatch(m.HCLEntries, controlHCL) + Expect(m.HCLEntries[0].HCLType).To(BeEquivalentTo(controlHCL[0].HCLType)) + Expect(m.HCLEntries[0].ComponentName).To(BeEquivalentTo(controlHCL[0].ComponentName)) + Expect(m.HCLEntries[0].InstanceName).To(BeEquivalentTo(controlHCL[0].InstanceName)) + } + }) + }) + + t.Run("m.AOfTypeNamed", func(t *testing.T) { + t.Run("should return a filtered list of definitions", func(t *testing.T) { + m := &matchers.Match{} + m.ReadTerraform("testdata", unmarshal) + err := m.AOfTypeNamed("google_compute_network", "resource", "my-custom-network") + Expect(err).NotTo(HaveOccurred()) + control := controlInstanceMatch() + Expect(len(m.MatchingEntries)).To(Equal(len(control))) + attributesMatch(m.MatchingEntries, control) + Expect(m.MatchingEntries[0].HCLType).To(BeEquivalentTo(control[0].HCLType)) + Expect(m.MatchingEntries[0].ComponentName).To(BeEquivalentTo(control[0].ComponentName)) + Expect(m.MatchingEntries[0].InstanceName).To(BeEquivalentTo(control[0].InstanceName)) + }) + }) + + t.Run("m.AlwaysAttributeEqualsInt", func(t *testing.T) { + t.Run("should fail but not panic if the attribute doesnt exist", func(t *testing.T) { + m := &matchers.Match{MatchingEntries: controlMissingAttribute()} + Expect(m.AlwaysAttributeEqualsInt("name", 5)).To(HaveOccurred()) + }) + + t.Run("should fail if there are any elements that dont match", func(t *testing.T) { + m := &matchers.Match{MatchingEntries: controlAlwaysMissInt()} + Expect(m.AlwaysAttributeEqualsInt("name", 5)).To(HaveOccurred()) + }) + + t.Run("should succeed all elements match", func(t *testing.T) { + m := &matchers.Match{MatchingEntries: controlAlwaysMatchInt()} + Expect(m.AlwaysAttributeEqualsInt("name", 5)).NotTo(HaveOccurred()) + Expect(m.MatchingEntries).To(BeEquivalentTo(controlAlwaysMatchInt())) + }) + }) + + t.Run("m.AlwaysAttributeEquals", func(t *testing.T) { + t.Run("should fail but not panic if the attribute doesnt exist", func(t *testing.T) { + m := &matchers.Match{MatchingEntries: controlMissingAttribute()} + Expect(m.AlwaysAttributeEquals("name", "my-custom-network")).To(HaveOccurred()) + }) + + t.Run("should fail if there are any elements that dont match", func(t *testing.T) { + m := &matchers.Match{MatchingEntries: controlAlwaysMiss()} + Expect(m.AlwaysAttributeEquals("name", "my-custom-network")).To(HaveOccurred()) + }) + + t.Run("should succeed all elements match", func(t *testing.T) { + m := &matchers.Match{MatchingEntries: controlAlwaysMatch()} + Expect(m.AlwaysAttributeEquals("name", "my-custom-network")).NotTo(HaveOccurred()) + Expect(m.MatchingEntries).To(BeEquivalentTo(controlAlwaysMatch())) + }) + }) + + t.Run("m.AlwaysAttributeDoesNotEqual", func(t *testing.T) { + t.Run("should fail but not panic if the attribute doesnt exist", func(t *testing.T) { + m := &matchers.Match{MatchingEntries: controlMissingAttribute()} + Expect(m.AlwaysAttributeDoesNotEqual("name", "def-doesnt-exist")).To(HaveOccurred()) + }) + + t.Run("should fail if there are any elements that match", func(t *testing.T) { + m := &matchers.Match{MatchingEntries: controlAlwaysMatch()} + Expect(m.AlwaysAttributeDoesNotEqual("name", "my-custom-network")).To(HaveOccurred()) + }) + + t.Run("should succeed NO elements match", func(t *testing.T) { + m := &matchers.Match{MatchingEntries: controlAlwaysMiss()} + Expect(m.AlwaysAttributeDoesNotEqual("name", "def-doesnt-exist")).NotTo(HaveOccurred()) + Expect(m.MatchingEntries).To(BeEquivalentTo(controlAlwaysMiss())) + }) + }) + + t.Run("m.AlwaysAttributeDoesNotEqualInt", func(t *testing.T) { + t.Run("should fail but not panic if the attribute doesnt exist", func(t *testing.T) { + m := &matchers.Match{MatchingEntries: controlMissingAttribute()} + Expect(m.AlwaysAttributeDoesNotEqualInt("name", 5)).To(HaveOccurred()) + }) + + t.Run("should fail if there are any elements that match", func(t *testing.T) { + m := &matchers.Match{MatchingEntries: controlAlwaysMatchInt()} + Expect(m.AlwaysAttributeDoesNotEqualInt("name", 5)).To(HaveOccurred()) + }) + + t.Run("should succeed NO elements match", func(t *testing.T) { + m := &matchers.Match{MatchingEntries: controlAlwaysMissInt()} + Expect(m.AlwaysAttributeDoesNotEqualInt("name", 24)).NotTo(HaveOccurred()) + Expect(m.MatchingEntries).To(BeEquivalentTo(controlAlwaysMissInt())) + }) + }) + + t.Run("m.AlwaysAttributeRegex", func(t *testing.T) { + t.Run("should fail but not panic if the attribute doesnt exist", func(t *testing.T) { + m := &matchers.Match{MatchingEntries: controlMissingAttribute()} + Expect(m.AlwaysAttributeRegex("name", "off")).To(HaveOccurred()) + }) + + t.Run("should fail if there are any elements that dont match", func(t *testing.T) { + m := &matchers.Match{MatchingEntries: controlAlwaysMiss()} + Expect(m.AlwaysAttributeRegex("name", "my-custom-network")).To(HaveOccurred()) + }) + + t.Run("should succeed when all elements match", func(t *testing.T) { + m := &matchers.Match{MatchingEntries: controlAlwaysMatch()} + Expect(m.AlwaysAttributeRegex("name", "my-custom-network")).NotTo(HaveOccurred()) + Expect(m.MatchingEntries).To(BeEquivalentTo(controlAlwaysMatch())) + }) + }) + + t.Run("m.AlwaysAttributeGreaterThan", func(t *testing.T) { + t.Run("should fail but not panic if the attribute doesnt exist", func(t *testing.T) { + m := &matchers.Match{MatchingEntries: controlMissingAttribute()} + Expect(m.AlwaysAttributeGreaterThan("name", 5)).To(HaveOccurred()) + }) + + t.Run("should fail if there are any elements that dont match", func(t *testing.T) { + m := &matchers.Match{MatchingEntries: controlAlwaysMissInt()} + Expect(m.AlwaysAttributeGreaterThan("name", 4)).To(HaveOccurred()) + }) + + t.Run("should succeed when all elements match", func(t *testing.T) { + m := &matchers.Match{MatchingEntries: controlAlwaysMatchInt()} + Expect(m.AlwaysAttributeGreaterThan("name", 2)).NotTo(HaveOccurred()) + Expect(m.MatchingEntries).To(BeEquivalentTo(controlAlwaysMatchInt())) + }) + }) + + t.Run("m.AlwaysAttributeLessThan", func(t *testing.T) { + t.Run("should fail but not panic if the attribute doesnt exist", func(t *testing.T) { + m := &matchers.Match{MatchingEntries: controlMissingAttribute()} + Expect(m.AlwaysAttributeLessThan("name", 5)).To(HaveOccurred()) + }) + + t.Run("should fail if there are any elements that dont match", func(t *testing.T) { + m := &matchers.Match{MatchingEntries: controlAlwaysMissInt()} + Expect(m.AlwaysAttributeLessThan("name", 4)).To(HaveOccurred()) + }) + + t.Run("should succeed when all elements match", func(t *testing.T) { + m := &matchers.Match{MatchingEntries: controlAlwaysMatchInt()} + Expect(m.AlwaysAttributeLessThan("name", 6)).NotTo(HaveOccurred()) + Expect(m.MatchingEntries).To(BeEquivalentTo(controlAlwaysMatchInt())) + }) + }) + + t.Run("m.AOfType", func(t *testing.T) { + t.Run("should return a filtered list of definitions", func(t *testing.T) { + m := resourceMatcherByType("google_compute_network", unmarshal) + control := controlInstanceMatch() + Expect(len(m.MatchingEntries)).To(Equal(len(control))) + attributesMatch(m.MatchingEntries, control) + Expect(m.MatchingEntries[0].HCLType).To(BeEquivalentTo(control[0].HCLType)) + Expect(m.MatchingEntries[0].ComponentName).To(BeEquivalentTo(control[0].ComponentName)) + Expect(m.MatchingEntries[0].InstanceName).To(BeEquivalentTo(control[0].InstanceName)) + }) + }) + + t.Run("m.AttributeExists", func(t *testing.T) { + t.Run("when there are no matches", func(t *testing.T) { + m := resourceMatcherByType("google_compute_subnetwork", unmarshal) + Expect(m.AttributeExists("bbbbbasdf")).To(HaveOccurred()) + }) + + t.Run("when there are matches", func(t *testing.T) { + m := resourceMatcherByType("google_compute_network", unmarshal) + Expect(m.AttributeExists("name")).NotTo(HaveOccurred()) + control := controlInstanceMatch() + Expect(len(m.MatchingEntries)).To(Equal(len(control))) + attributesMatch(m.MatchingEntries, control) + Expect(m.MatchingEntries[0].HCLType).To(BeEquivalentTo(control[0].HCLType)) + Expect(m.MatchingEntries[0].ComponentName).To(BeEquivalentTo(control[0].ComponentName)) + Expect(m.MatchingEntries[0].InstanceName).To(BeEquivalentTo(control[0].InstanceName)) + }) + + t.Run("when attributes is not derived from a output block", func(t *testing.T) { + m := &matchers.Match{} + control := controlOutputMatch() + err := m.ReadTerraform("testdata/outputs", unmarshal) + Expect(err).NotTo(HaveOccurred()) + err = m.AOfType("rds_password", "output") + Expect(err).NotTo(HaveOccurred()) + Expect(m.AttributeExists("sensitive")).NotTo(HaveOccurred()) + Expect(m.AttributeExists("value")).NotTo(HaveOccurred()) + Expect(len(m.MatchingEntries)).To(Equal(len(control))) + attributesMatch(m.MatchingEntries, control) + Expect(m.MatchingEntries[0].HCLType).To(BeEquivalentTo(control[0].HCLType)) + Expect(m.MatchingEntries[0].ComponentName).To(BeEquivalentTo(control[0].ComponentName)) + Expect(m.MatchingEntries[0].InstanceName).To(BeEquivalentTo(control[0].InstanceName)) + }) + }) + + t.Run("m.AttributeGreaterThan", func(t *testing.T) { + t.Run("when there are no matches", func(t *testing.T) { + m := resourceMatcherByType("blah_blah", unmarshal) + Expect(m.AttributeGreaterThan("nadafield", 81)).To(HaveOccurred()) + }) + + t.Run("when a valid attribute has non gt value", func(t *testing.T) { + m := resourceMatcherByType("blah_blah", unmarshal) + Expect(m.AttributeGreaterThan("port", 81)).To(HaveOccurred()) + }) + + t.Run("when there are matches with a correct value", func(t *testing.T) { + m := resourceMatcherByType("blah_blah", unmarshal) + Expect(m.AttributeGreaterThan("port", 79)).NotTo(HaveOccurred()) + control := controlInstanceMatchIntString() + Expect(len(m.MatchingEntries)).To(Equal(len(control))) + attributesMatch(m.MatchingEntries, control) + Expect(m.MatchingEntries[0].HCLType).To(BeEquivalentTo(control[0].HCLType)) + Expect(m.MatchingEntries[0].ComponentName).To(BeEquivalentTo(control[0].ComponentName)) + Expect(m.MatchingEntries[0].InstanceName).To(BeEquivalentTo(control[0].InstanceName)) + }) + + t.Run("when there are matches with a correct value and the attr value is a int type", func(t *testing.T) { + m := resourceMatcherByType("foo", unmarshal) + Expect(m.AttributeGreaterThan("port", 79)).NotTo(HaveOccurred()) + control := controlInstanceMatchInt() + Expect(len(m.MatchingEntries)).To(Equal(len(control))) + attributesMatch(m.MatchingEntries, control) + Expect(m.MatchingEntries[0].HCLType).To(BeEquivalentTo(control[0].HCLType)) + Expect(m.MatchingEntries[0].ComponentName).To(BeEquivalentTo(control[0].ComponentName)) + Expect(m.MatchingEntries[0].InstanceName).To(BeEquivalentTo(control[0].InstanceName)) + }) + }) + + t.Run("m.ItOccursAtLeastTimes", func(t *testing.T) { + t.Run("when we dont have enough to satisfy at least", func(t *testing.T) { + m := resourceMatcherByType("blah_blah", unmarshal) + Expect(m.ItOccursAtLeastTimes(5)).To(HaveOccurred()) + }) + + t.Run("when we have enough to satisfy at least", func(t *testing.T) { + m := resourceMatcherByType("blah_blah", unmarshal) + Expect(m.ItOccursAtLeastTimes(1)).NotTo(HaveOccurred()) + }) + }) + + t.Run("m.ItOccursAtMostTimes", func(t *testing.T) { + t.Run("when we have too many to satisfy at most", func(t *testing.T) { + m := resourceMatcherByType("blah_blah", unmarshal) + Expect(m.ItOccursAtMostTimes(0)).To(HaveOccurred()) + }) + + t.Run("when we have enough to satisfy at most", func(t *testing.T) { + m := resourceMatcherByType("blah_blah", unmarshal) + Expect(m.ItOccursAtMostTimes(3)).NotTo(HaveOccurred()) + }) + }) + + t.Run("m.ItOccursExactlyTimes", func(t *testing.T) { + t.Run("when we dont have exactly the count", func(t *testing.T) { + m := resourceMatcherByType("blah_blah", unmarshal) + Expect(m.ItOccursExactlyTimes(2)).To(HaveOccurred()) + }) + + t.Run("when we have exactly the count", func(t *testing.T) { + m := resourceMatcherByType("blah_blah", unmarshal) + Expect(m.ItOccursExactlyTimes(1)).NotTo(HaveOccurred()) + }) + }) + + t.Run("m.AttributeRegex", func(t *testing.T) { + t.Run("when we do not have a attr value which satisfies the regex", func(t *testing.T) { + m := resourceMatcherByType("blah_blah", unmarshal) + Expect(m.AttributeRegex("port", "abc")).To(HaveOccurred()) + }) + + t.Run("when our attribute value satisfies the regex", func(t *testing.T) { + m := resourceMatcherByType("blah_blah", unmarshal) + Expect(m.AttributeRegex("port", "8*")).NotTo(HaveOccurred()) + }) + + t.Run("when our attribute is a complex object and it satisfies the regex", func(t *testing.T) { + m := &matchers.Match{} + err := m.ReadTerraform("testdata", unmarshal) + Expect(err).NotTo(HaveOccurred()) + err = m.AOfTypeNamed("google_compute_firewall", "resource", "allow-all-internal") + Expect(err).NotTo(HaveOccurred()) + Expect(m.AttributeExists("allow")).NotTo(HaveOccurred()) + Expect(m.AttributeRegex("allow", "protocol.*tcp")).NotTo(HaveOccurred()) + }) + }) + + t.Run("m.AttributeLessThan", func(t *testing.T) { + t.Run("when there are no matches", func(t *testing.T) { + m := resourceMatcherByType("blah_blah", unmarshal) + Expect(m.AttributeLessThan("nadafield", 79)).To(HaveOccurred()) + }) + + t.Run("when a valid attribute has non gt value", func(t *testing.T) { + m := resourceMatcherByType("blah_blah", unmarshal) + Expect(m.AttributeLessThan("port", 79)).To(HaveOccurred()) + }) + + t.Run("when there are matches with a correct value", func(t *testing.T) { + m := resourceMatcherByType("blah_blah", unmarshal) + Expect(m.AttributeLessThan("port", 81)).NotTo(HaveOccurred()) + control := controlInstanceMatchIntString() + Expect(len(m.MatchingEntries)).To(Equal(len(control))) + attributesMatch(m.MatchingEntries, control) + Expect(m.MatchingEntries[0].HCLType).To(BeEquivalentTo(control[0].HCLType)) + Expect(m.MatchingEntries[0].ComponentName).To(BeEquivalentTo(control[0].ComponentName)) + Expect(m.MatchingEntries[0].InstanceName).To(BeEquivalentTo(control[0].InstanceName)) + }) + + t.Run("when there are matches with a correct value and attr value is type int", func(t *testing.T) { + m := resourceMatcherByType("foo", unmarshal) + control := controlInstanceMatchInt() + Expect(m.AttributeLessThan("port", 81)).NotTo(HaveOccurred()) + Expect(len(m.MatchingEntries)).To(Equal(len(control))) + attributesMatch(m.MatchingEntries, control) + Expect(m.MatchingEntries[0].HCLType).To(BeEquivalentTo(control[0].HCLType)) + Expect(m.MatchingEntries[0].ComponentName).To(BeEquivalentTo(control[0].ComponentName)) + Expect(m.MatchingEntries[0].InstanceName).To(BeEquivalentTo(control[0].InstanceName)) + }) + }) + + t.Run("m.AttributeEqualsInt", func(t *testing.T) { + t.Run("when there are no matches", func(t *testing.T) { + m := resourceMatcherByType("foo", unmarshal) + Expect(m.AttributeEqualsInt("bbbbbasdf", 0)).To(HaveOccurred()) + }) + + t.Run("when a valid attribute has non equal value", func(t *testing.T) { + m := resourceMatcherByType("foo", unmarshal) + Expect(m.AttributeEqualsInt("port", 43)).To(HaveOccurred()) + }) + + t.Run("when there are matches with a correct value", func(t *testing.T) { + m := resourceMatcherByType("foo", unmarshal) + control := controlInstanceMatchInt() + Expect(m.AttributeEqualsInt("port", 80)).NotTo(HaveOccurred()) + Expect(len(m.MatchingEntries)).To(Equal(len(control))) + attributesMatch(m.MatchingEntries, control) + Expect(m.MatchingEntries[0].HCLType).To(BeEquivalentTo(control[0].HCLType)) + Expect(m.MatchingEntries[0].ComponentName).To(BeEquivalentTo(control[0].ComponentName)) + Expect(m.MatchingEntries[0].InstanceName).To(BeEquivalentTo(control[0].InstanceName)) + }) + }) + + t.Run("m.AttributeDoesNotEqualInt", func(t *testing.T) { + t.Run("when there are no matches", func(t *testing.T) { + m := resourceMatcherByType("foo", unmarshal) + Expect(m.AttributeDoesNotEqualInt("bbbbbasdf", 0)).To(HaveOccurred()) + }) + + t.Run("when a valid attribute has non equal value", func(t *testing.T) { + m := resourceMatcherByType("foo", unmarshal) + Expect(m.AttributeDoesNotEqualInt("port", 80)).To(HaveOccurred()) + }) + + t.Run("when there are matches with a correct value", func(t *testing.T) { + m := resourceMatcherByType("foo", unmarshal) + control := controlInstanceMatchInt() + Expect(m.AttributeDoesNotEqualInt("port", 81)).NotTo(HaveOccurred()) + Expect(len(m.MatchingEntries)).To(Equal(len(control))) + attributesMatch(m.MatchingEntries, control) + Expect(m.MatchingEntries[0].HCLType).To(BeEquivalentTo(control[0].HCLType)) + Expect(m.MatchingEntries[0].ComponentName).To(BeEquivalentTo(control[0].ComponentName)) + Expect(m.MatchingEntries[0].InstanceName).To(BeEquivalentTo(control[0].InstanceName)) + }) + }) + + t.Run("m.AttributeEquals", func(t *testing.T) { + t.Run("when there are no matches", func(t *testing.T) { + m := resourceMatcherByType("google_compute_network", unmarshal) + Expect(m.AttributeEquals("bbbbbasdf", "my-custom-network")).To(HaveOccurred()) + }) + + t.Run("when a valid attribute has non equal value", func(t *testing.T) { + m := resourceMatcherByType("google_compute_network", unmarshal) + Expect(m.AttributeEquals("name", "'wrong-name'")).To(HaveOccurred()) + }) + + t.Run("when there are matches with a correct value", func(t *testing.T) { + m := resourceMatcherByType("google_compute_network", unmarshal) + Expect(m.AttributeEquals("name", "my-custom-network")).NotTo(HaveOccurred()) + control := controlInstanceMatch() + Expect(len(m.MatchingEntries)).To(Equal(len(control))) + attributesMatch(m.MatchingEntries, control) + Expect(m.MatchingEntries[0].HCLType).To(BeEquivalentTo(control[0].HCLType)) + Expect(m.MatchingEntries[0].ComponentName).To(BeEquivalentTo(control[0].ComponentName)) + Expect(m.MatchingEntries[0].InstanceName).To(BeEquivalentTo(control[0].InstanceName)) + }) + }) + + t.Run("m.AttributeDoesNotEqual", func(t *testing.T) { + t.Run("when there are no matches", func(t *testing.T) { + m := resourceMatcherByType("google_compute_network", unmarshal) + Expect(m.AttributeDoesNotEqual("bbbbbasdf", "my-custom-network")).To(HaveOccurred()) + }) + + t.Run("when a valid attribute has non equal value", func(t *testing.T) { + m := resourceMatcherByType("google_compute_network", unmarshal) + Expect(m.AttributeDoesNotEqual("name", "wrong-name")).NotTo(HaveOccurred()) + control := controlInstanceMatch() + Expect(len(m.MatchingEntries)).To(Equal(len(control))) + attributesMatch(m.MatchingEntries, control) + Expect(m.MatchingEntries[0].HCLType).To(BeEquivalentTo(control[0].HCLType)) + Expect(m.MatchingEntries[0].ComponentName).To(BeEquivalentTo(control[0].ComponentName)) + Expect(m.MatchingEntries[0].InstanceName).To(BeEquivalentTo(control[0].InstanceName)) + }) + + t.Run("when there are matches with a correct value", func(t *testing.T) { + m := resourceMatcherByType("google_compute_network", unmarshal) + Expect(m.AttributeDoesNotEqual("name", "my-custom-network")).To(HaveOccurred()) + }) + }) }) + } +} - t.Run("when a valid attribute has non equal value", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("foo", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeDoesNotEqualInt("port", 80)).To(HaveOccurred()) - }) +func controlOutputMatch() []matchers.HCLEntry { + return []matchers.HCLEntry{ + matchers.HCLEntry{ + HCLType: "output", + ComponentName: "rds_password", + InstanceName: "", + Attributes: []map[string]interface{}{ + map[string]interface{}{ + "sensitive": true, + "value": "${element(concat(aws_db_instance.rds.*.password, list(\"\")), 0)}", + }, + }, + }, + } +} - t.Run("when there are matches with a correct value", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("foo", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeDoesNotEqualInt("port", 81)).NotTo(HaveOccurred()) - Expect(m.MatchingEntries).To(BeEquivalentTo(controlInstanceMatchInt())) - }) - }) - - t.Run("m.AttributeEquals", func(t *testing.T) { - t.Run("when there are no matches", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("google_compute_network", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeEquals("bbbbbasdf", "my-custom-network")).To(HaveOccurred()) - }) +func controlMissingAttribute() []matchers.HCLEntry { + return []matchers.HCLEntry{ + matchers.HCLEntry{ + HCLType: "resource", + ComponentName: "google_compute_network", + InstanceName: "my-custom-network", + Attributes: []map[string]interface{}{ + map[string]interface{}{}, + }, + }, + } +} - t.Run("when a valid attribute has non equal value", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("google_compute_network", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeEquals("name", "'wrong-name'")).To(HaveOccurred()) - }) +func controlAlwaysMissInt() []matchers.HCLEntry { + return []matchers.HCLEntry{ + matchers.HCLEntry{ + HCLType: "resource", + ComponentName: "google_compute_network", + InstanceName: "my-custom-network", + Attributes: []map[string]interface{}{ + map[string]interface{}{ + "name": 5, + }, + }, + }, + matchers.HCLEntry{ + HCLType: "resource", + ComponentName: "google_compute_network", + InstanceName: "my-custom-network", + Attributes: []map[string]interface{}{ + map[string]interface{}{ + "name": 3, + }, + }, + }, + } +} - t.Run("when there are matches with a correct value", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("google_compute_network", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeEquals("name", "my-custom-network")).NotTo(HaveOccurred()) - Expect(m.MatchingEntries).To(BeEquivalentTo(controlInstanceMatch())) - }) - }) - - t.Run("m.AttributeDoesNotEqual", func(t *testing.T) { - t.Run("when there are no matches", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("google_compute_network", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeDoesNotEqual("bbbbbasdf", "my-custom-network")).To(HaveOccurred()) - }) +func controlAlwaysMatchInt() []matchers.HCLEntry { + return []matchers.HCLEntry{ + matchers.HCLEntry{ + HCLType: "resource", + ComponentName: "google_compute_network", + InstanceName: "my-custom-network", + Attributes: []map[string]interface{}{ + map[string]interface{}{ + "name": 5, + }, + }, + }, + matchers.HCLEntry{ + HCLType: "resource", + ComponentName: "google_compute_network", + InstanceName: "my-custom-network", + Attributes: []map[string]interface{}{ + map[string]interface{}{ + "name": 5, + }, + }, + }, + } +} - t.Run("when a valid attribute has non equal value", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("google_compute_network", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeDoesNotEqual("name", "wrong-name")).NotTo(HaveOccurred()) - Expect(m.MatchingEntries).To(BeEquivalentTo(controlInstanceMatch())) - }) +func controlAlwaysMiss() []matchers.HCLEntry { + return []matchers.HCLEntry{ + matchers.HCLEntry{ + HCLType: "resource", + ComponentName: "google_compute_network", + InstanceName: "my-custom-network", + Attributes: []map[string]interface{}{ + map[string]interface{}{ + "name": "my-custom-network", + }, + }, + }, + matchers.HCLEntry{ + HCLType: "resource", + ComponentName: "google_compute_network", + InstanceName: "my-custom-network", + Attributes: []map[string]interface{}{ + map[string]interface{}{ + "name": "epic fail", + }, + }, + }, + } +} - t.Run("when there are matches with a correct value", func(t *testing.T) { - m := &matchers.Match{} - err := m.ReadTerraform("testdata") - Expect(err).NotTo(HaveOccurred()) - err = m.AOfType("google_compute_network", "resource") - Expect(err).NotTo(HaveOccurred()) - Expect(m.AttributeDoesNotEqual("name", "my-custom-network")).To(HaveOccurred()) - }) - }) +func controlAlwaysMatch() []matchers.HCLEntry { + return []matchers.HCLEntry{ + matchers.HCLEntry{ + HCLType: "resource", + ComponentName: "google_compute_network", + InstanceName: "my-custom-network", + Attributes: []map[string]interface{}{ + map[string]interface{}{ + "name": "my-custom-network", + }, + }, + }, + matchers.HCLEntry{ + HCLType: "resource", + ComponentName: "google_compute_network", + InstanceName: "my-custom-network", + Attributes: []map[string]interface{}{ + map[string]interface{}{ + "name": "my-custom-network", + }, + }, + }, + } } func controlInstanceMatch() []matchers.HCLEntry { @@ -406,3 +644,30 @@ func multiTFControl() []matchers.HCLEntry { }, } } + +func attributesMatch(object1, object2 interface{}) { + var attr1, attr2 []map[string]interface{} + switch attr := object1.(type) { + case map[string]interface{}: + attr1 = []map[string]interface{}{attr} + case []map[string]interface{}: + attr1 = attr + } + + switch attr := object2.(type) { + case map[string]interface{}: + attr2 = []map[string]interface{}{attr} + case []map[string]interface{}: + attr2 = attr + } + Expect(attr1).To(BeEquivalentTo(attr2)) +} + +func resourceMatcherByType(resourceType string, unmarshal matchers.Unmarshaller) *matchers.Match { + m := &matchers.Match{} + err := m.ReadTerraform("testdata", unmarshal) + Expect(err).NotTo(HaveOccurred()) + err = m.AOfType(resourceType, "resource") + Expect(err).NotTo(HaveOccurred()) + return m +} diff --git a/pkg/matchers/testdata/multitf_newline/1.tf b/pkg/matchers/testdata/multitf_newline/1.tf new file mode 100644 index 0000000..770ff73 --- /dev/null +++ b/pkg/matchers/testdata/multitf_newline/1.tf @@ -0,0 +1,3 @@ +resource "aws" "blah" { + +} \ No newline at end of file diff --git a/pkg/matchers/testdata/multitf_newline/2.tf b/pkg/matchers/testdata/multitf_newline/2.tf new file mode 100644 index 0000000..f446d7c --- /dev/null +++ b/pkg/matchers/testdata/multitf_newline/2.tf @@ -0,0 +1,3 @@ +resource "aws" "bar" { + +} diff --git a/pkg/matchers/testdata/outputs/sample_out.tf b/pkg/matchers/testdata/outputs/sample_out.tf new file mode 100644 index 0000000..c90328d --- /dev/null +++ b/pkg/matchers/testdata/outputs/sample_out.tf @@ -0,0 +1,20 @@ +output "rds_address" { + value = "${element(concat(aws_db_instance.rds.*.address, list("")), 0)}" +} + +output "rds_port" { + value = "${element(concat(aws_db_instance.rds.*.port, list("")), 0)}" +} + +output "rds_username" { + value = "${element(concat(aws_db_instance.rds.*.username, list("")), 0)}" +} + +output "rds_password" { + sensitive = true + value = "${element(concat(aws_db_instance.rds.*.password, list("")), 0)}" +} + +output "rds_db_name" { + value = "${element(concat(aws_db_instance.rds.*.name, list("")), 0)}" +}