Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for go-getter syntax when calling opa.Eval #1026

Merged
merged 2 commits into from
Nov 10, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ require (
github.com/google/go-containerregistry v0.6.0
github.com/google/uuid v1.2.0
github.com/gruntwork-io/go-commons v0.8.0
github.com/hashicorp/go-getter v1.5.9
github.com/hashicorp/go-multierror v1.1.0
github.com/hashicorp/go-version v1.3.0
github.com/hashicorp/hcl/v2 v2.9.1
Expand Down
17 changes: 17 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,15 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:l
github.com/aws/aws-lambda-go v1.13.3 h1:SuCy7H3NLyp+1Mrfp+m80jcbi9KYWAs9/BXwppwRDzY=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
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/aws/aws-sdk-go v1.40.56 h1:FM2yjR0UUYFzDTMx+mH9Vyw1k1EUUxsAFzk+BjkzANA=
github.com/aws/aws-sdk-go v1.40.56/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
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/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
Expand All @@ -145,6 +148,7 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
Expand Down Expand Up @@ -474,17 +478,24 @@ github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FK
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-getter v1.5.9 h1:b7ahZW50iQiUek/at3CvZhPK1/jiV6CtKcsJiR6E4R0=
github.com/hashicorp/go-getter v1.5.9/go.mod h1:BrrV/1clo8cCYu6mxvboYg+KutTiFnXjMEgDD8+i7ZI=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw=
github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
Expand Down Expand Up @@ -533,6 +544,7 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
Expand Down Expand Up @@ -569,6 +581,7 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326 h1:ofNAzWCcyTALn2Zv40+8XitdzCgXY6e9qvXwN9W0YXg=
Expand All @@ -585,6 +598,7 @@ github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HK
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
Expand Down Expand Up @@ -774,6 +788,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1
github.com/tmccombs/hcl2json v0.3.3 h1:+DLNYqpWE0CsOQiEZu+OZm5ZBImake3wtITYxQ8uLFQ=
github.com/tmccombs/hcl2json v0.3.3/go.mod h1:Y2chtz2x9bAeRTvSibVRVgbLJhLJXKlUeIvjeVdnm4w=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
Expand Down Expand Up @@ -1261,6 +1277,7 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
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.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
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/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
Expand Down
73 changes: 73 additions & 0 deletions modules/opa/download_policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package opa

import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"

getter "github.com/hashicorp/go-getter"

"github.com/gruntwork-io/terratest/modules/logger"
"github.com/gruntwork-io/terratest/modules/testing"
)

var (
// A map that maps the go-getter base URL to the temporary directory where it is downloaded.
policyDirCache sync.Map
)

// downloadPolicy takes in a rule path written in go-getter syntax and downloads it to a temporary directory so that it
yorinasub17 marked this conversation as resolved.
Show resolved Hide resolved
// can be passed to opa. The temporary directory that is used is cached based on the go-getter base path, and reused
// across calls.
// For example, if you call downloadPolicyE with the go-getter URL multiple times:
// git::https://github.com/gruntwork-io/terratest.git//policies/foo.rego?ref=master
// The first time the gruntwork-io/terratest repo will be downloaded to a new temp directory. All subsequent calls will
// reuse that first temporary dir where the repo was cloned. This is preserved even if a different subdir is requested
// later, e.g.: git::https://github.com/gruntwork-io/terratest.git//examples/bar.rego?ref=master.
// Note that the query parameters are always included in the base URL. This means that if you use a different ref (e.g.,
// git::https://github.com/gruntwork-io/terratest.git//examples/bar.rego?ref=v0.39.3), then that will be cloned to a new
// temporary directory rather than the cached dir.
func downloadPolicyE(t testing.TestingT, rulePath string) (string, error) {
cwd, err := os.Getwd()
if err != nil {
return "", err
}

detected, err := getter.Detect(rulePath, cwd, getter.Detectors)
if err != nil {
return "", err
}

// File getters are assumed to be a local path reference, so pass through the original path.
if strings.HasPrefix(detected, "file") {
return rulePath, nil
}

// At this point we assume the getter URL is a remote URL, so we start the process of downloading it to a temp dir.

// First, check if we had already downloaded the source and it is in our cache.
baseDir, subDir := getter.SourceDirSubdir(rulePath)
downloadPath, hasDownloaded := policyDirCache.Load(baseDir)
if hasDownloaded {
logger.Logf(t, "Previously downloaded %s: returning cached path", baseDir)
return filepath.Join(downloadPath.(string), subDir), nil
}

// Not downloaded, so use go-getter to download the remote source to a temp dir.
tempDir, err := ioutil.TempDir("", "terratest-opa-policy-*")
if err != nil {
return "", err
}
// go-getter doesn't work if you give it a directory that already exists, so we add an additional path in the
// tempDir to make sure we feed a directory that doesn't exist yet.
tempDir = filepath.Join(tempDir, "getter")

logger.Logf(t, "Downloading %s to temp dir %s", rulePath, tempDir)
if err := getter.GetAny(tempDir, baseDir); err != nil {
return "", err
}
policyDirCache.Store(baseDir, tempDir)
return filepath.Join(tempDir, subDir), nil
}
102 changes: 102 additions & 0 deletions modules/opa/download_policy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package opa

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/gruntwork-io/terratest/modules/files"
"github.com/gruntwork-io/terratest/modules/git"
)

// Test to make sure the downloadPolicyE function returns a local path without processing it.
func TestDownloadPolicyReturnsLocalPath(t *testing.T) {
t.Parallel()

localPath := "../../examples/terraform-opa-example/policy/enforce_source.rego"
path, err := downloadPolicyE(t, localPath)
require.NoError(t, err)
assert.Equal(t, localPath, path)
}

// Test to make sure the downloadPolicyE function returns a remote path to a temporary directory.
func TestDownloadPolicyDownloadsRemote(t *testing.T) {
t.Parallel()

curRef := git.GetCurrentGitRef(t)
baseDir := fmt.Sprintf("git::https://github.com/gruntwork-io/terratest.git?ref=%s", curRef)
localPath := "../../examples/terraform-opa-example/policy/enforce_source.rego"
remotePath := fmt.Sprintf("git::https://github.com/gruntwork-io/terratest.git//examples/terraform-opa-example/policy/enforce_source.rego?ref=%s", curRef)

// Make sure we clean up the downloaded file, while simultaneously asserting that the download dir was stored in the
// cache.
defer func() {
downloadPathRaw, inCache := policyDirCache.Load(baseDir)
require.True(t, inCache)
downloadPath := downloadPathRaw.(string)
if strings.HasSuffix(downloadPath, "/getter") {
downloadPath = filepath.Dir(downloadPath)
}
assert.NoError(t, os.RemoveAll(downloadPath))
}()

path, err := downloadPolicyE(t, remotePath)
require.NoError(t, err)

absPath, err := filepath.Abs(localPath)
require.NoError(t, err)
assert.NotEqual(t, absPath, path)

localContents, err := ioutil.ReadFile(localPath)
require.NoError(t, err)
remoteContents, err := ioutil.ReadFile(path)
require.NoError(t, err)
assert.Equal(t, localContents, remoteContents)
}

// Test to make sure the downloadPolicyE function uses the cache if it has already downloaded an existing base path.
func TestDownloadPolicyReusesCachedDir(t *testing.T) {
t.Parallel()

baseDir := "git::https://github.com/gruntwork-io/terratest.git?ref=master"
remotePath := "git::https://github.com/gruntwork-io/terratest.git//examples/terraform-opa-example/policy/enforce_source.rego?ref=master"
remotePathAltSubPath := "git::https://github.com/gruntwork-io/terratest.git//modules/opa/eval.go?ref=master"

// Make sure we clean up the downloaded file, while simultaneously asserting that the download dir was stored in the
// cache.
defer func() {
downloadPathRaw, inCache := policyDirCache.Load(baseDir)
require.True(t, inCache)
downloadPath := downloadPathRaw.(string)

if strings.HasSuffix(downloadPath, "/getter") {
downloadPath = filepath.Dir(downloadPath)
}
assert.NoError(t, os.RemoveAll(downloadPath))
}()

path, err := downloadPolicyE(t, remotePath)
require.NoError(t, err)
files.FileExists(path)

downloadPathRaw, inCache := policyDirCache.Load(baseDir)
require.True(t, inCache)
downloadPath := downloadPathRaw.(string)

// make sure the second call is exactly equal to the first call
newPath, err := downloadPolicyE(t, remotePath)
require.NoError(t, err)
assert.Equal(t, path, newPath)

// Also make sure the cache is reused for alternative sub dirs.
newAltPath, err := downloadPolicyE(t, remotePathAltSubPath)
require.NoError(t, err)
assert.True(t, strings.HasPrefix(path, downloadPath))
assert.True(t, strings.HasPrefix(newAltPath, downloadPath))
}
21 changes: 14 additions & 7 deletions modules/opa/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ type EvalOptions struct {
// Whether OPA should run checks with failure.
FailMode FailMode

// Path to rego file containing the OPA rules.
// Path to rego file containing the OPA rules. Can also be a remote path defined in go-getter syntax. Refer to
// https://github.com/hashicorp/go-getter#url-format for supported options.
RulePath string

// Set a logger that should be used. See the logger package for more info.
Expand Down Expand Up @@ -57,14 +58,19 @@ func Eval(t testing.TestingT, options *EvalOptions, jsonFilePaths []string, resu
// opa eval -i $JSONFile -d $RulePath $ResultQuery
// This will asynchronously run OPA on each file concurrently using goroutines.
func EvalE(t testing.TestingT, options *EvalOptions, jsonFilePaths []string, resultQuery string) error {
downloadedPolicyPath, err := downloadPolicyE(t, options.RulePath)
if err != nil {
return err
}

wg := new(sync.WaitGroup)
wg.Add(len(jsonFilePaths))
errorsOccurred := new(multierror.Error)
errChans := make([]chan error, len(jsonFilePaths))
for i, jsonFilePath := range jsonFilePaths {
errChan := make(chan error, 1)
errChans[i] = errChan
go asyncEval(t, wg, errChan, options, jsonFilePath, resultQuery)
go asyncEval(t, wg, errChan, options, downloadedPolicyPath, jsonFilePath, resultQuery)
}
wg.Wait()
for _, errChan := range errChans {
Expand All @@ -82,27 +88,28 @@ func asyncEval(
wg *sync.WaitGroup,
errChan chan error,
options *EvalOptions,
downloadedPolicyPath string,
jsonFilePath string,
resultQuery string,
) {
defer wg.Done()
cmd := shell.Command{
Command: "opa",
Args: formatOPAEvalArgs(options, jsonFilePath, resultQuery),
Args: formatOPAEvalArgs(options, downloadedPolicyPath, jsonFilePath, resultQuery),

// Do not log output from shell package so we can log the full json without breaking it up. This is ok, because
// opa eval is typically very quick.
Logger: logger.Discard,
}
err := runCommandWithFullLoggingE(t, options.Logger, cmd)
ruleBasePath := filepath.Base(options.RulePath)
ruleBasePath := filepath.Base(downloadedPolicyPath)
if err == nil {
options.Logger.Logf(t, "opa eval passed on file %s (policy %s; query %s)", jsonFilePath, ruleBasePath, resultQuery)
} else {
options.Logger.Logf(t, "Failed opa eval on file %s (policy %s; query %s)", jsonFilePath, ruleBasePath, resultQuery)
if options.DebugDisableQueryDataOnError == false {
options.Logger.Logf(t, "DEBUG: rerunning opa eval to query for full data.")
cmd.Args = formatOPAEvalArgs(options, jsonFilePath, "data")
cmd.Args = formatOPAEvalArgs(options, downloadedPolicyPath, jsonFilePath, "data")
// We deliberately ignore the error here as we want to only return the original error.
runCommandWithFullLoggingE(t, options.Logger, cmd)
}
Expand All @@ -111,7 +118,7 @@ func asyncEval(
}

// formatOPAEvalArgs formats the arguments for the `opa eval` command.
func formatOPAEvalArgs(options *EvalOptions, jsonFilePath string, resultQuery string) []string {
func formatOPAEvalArgs(options *EvalOptions, rulePath, jsonFilePath, resultQuery string) []string {
args := []string{"eval"}

switch options.FailMode {
Expand All @@ -125,7 +132,7 @@ func formatOPAEvalArgs(options *EvalOptions, jsonFilePath string, resultQuery st
args,
[]string{
"-i", jsonFilePath,
"-d", options.RulePath,
"-d", rulePath,
resultQuery,
}...,
)
Expand Down
16 changes: 16 additions & 0 deletions test/terraform_opa_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,19 @@ func TestOPAEvalTerraformModuleFailsCheck(t *testing.T) {
// website::tag::6:: Here we expect the checks to fail, so we use `OPAEvalE` to check the error. Note that on the files that failed, this function will rerun `opa eval` with the query set to `data`, so you can see the values of all the variables in the policy. This is useful for debugging failures.
require.Error(t, terraform.OPAEvalE(t, tfOpts, opaOpts, "data.enforce_source.allow"))
}

// An example of how to use Terratest to run OPA policy checks on Terraform source code using a remote OPA policy source
// file. This will check the module called `pass` against the rego policy `enforce_source` defined in the
// `terraform-opa-example` folder of the terratest repository.
func TestOPAEvalTerraformModuleRemotePolicy(t *testing.T) {
t.Parallel()

tfOpts := &terraform.Options{
TerraformDir: "../examples/terraform-opa-example/pass",
}
opaOpts := &opa.EvalOptions{
RulePath: "git::https://github.com/gruntwork-io/terratest.git//examples/terraform-opa-example/policy/enforce_source.rego?ref=master",
FailMode: opa.FailUndefined,
}
terraform.OPAEval(t, tfOpts, opaOpts, "data.enforce_source.allow")
}