Skip to content

Commit 6c695c6

Browse files
authored
Merge pull request #1701 from gruntwork-io/chore/expanding-linting-to-opa
chore: Expanding linting to `opa`
2 parents 95c15d7 + 85e90dd commit 6c695c6

6 files changed

Lines changed: 106 additions & 53 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ lint:
1010
golangci-lint run ./...
1111

1212
lint-allow-list:
13-
golangci-lint run ./modules/random/... ./modules/testing/... ./modules/slack/... ./modules/collections/... ./modules/environment/... ./modules/retry/... ./modules/shell/... ./modules/git/... ./modules/files/... ./modules/oci/... ./modules/version-checker/... ./modules/database/... ./modules/logger/...
13+
golangci-lint run ./modules/random/... ./modules/testing/... ./modules/slack/... ./modules/collections/... ./modules/environment/... ./modules/retry/... ./modules/shell/... ./modules/git/... ./modules/files/... ./modules/oci/... ./modules/version-checker/... ./modules/database/... ./modules/logger/... ./modules/opa/...
1414

1515
.PHONY: lint update-lint-config

modules/opa/download_policy.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package opa
22

33
import (
44
"context"
5+
"fmt"
56
"os"
67
"path/filepath"
78
"sync"
@@ -33,7 +34,7 @@ var (
3334
func DownloadPolicyE(t testing.TestingT, rulePath string) (string, error) {
3435
cwd, err := os.Getwd()
3536
if err != nil {
36-
return "", err
37+
return "", fmt.Errorf("getting current working directory: %w", err)
3738
}
3839

3940
// File getters are assumed to be a local path reference, so pass through the original path.
@@ -50,6 +51,7 @@ func DownloadPolicyE(t testing.TestingT, rulePath string) (string, error) {
5051

5152
// First, check if we had already downloaded the source and it is in our cache.
5253
baseDir, subDir := getter.SourceDirSubdir(rulePath)
54+
5355
downloadPath, hasDownloaded := policyDirCache.Load(baseDir)
5456
if hasDownloaded {
5557
logger.Default.Logf(t, "Previously downloaded %s: returning cached path", baseDir)
@@ -59,16 +61,20 @@ func DownloadPolicyE(t testing.TestingT, rulePath string) (string, error) {
5961
// Not downloaded, so use go-getter to download the remote source to a temp dir.
6062
tempDir, err := os.MkdirTemp("", "terratest-opa-policy-*")
6163
if err != nil {
62-
return "", err
64+
return "", fmt.Errorf("creating temp directory for policy download: %w", err)
6365
}
66+
6467
// go-getter doesn't work if you give it a directory that already exists, so we add an additional path in the
6568
// tempDir to make sure we feed a directory that doesn't exist yet.
6669
tempDir = filepath.Join(tempDir, "getter")
6770

6871
logger.Default.Logf(t, "Downloading %s to temp dir %s", rulePath, tempDir)
72+
6973
if _, err := getter.GetAny(context.Background(), tempDir, baseDir); err != nil {
70-
return "", err
74+
return "", fmt.Errorf("downloading policy from %s: %w", baseDir, err)
7175
}
76+
7277
policyDirCache.Store(baseDir, tempDir)
78+
7379
return filepath.Join(tempDir, subDir), nil
7480
}

modules/opa/download_policy_test.go

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
package opa
1+
package opa_test
22

33
import (
4-
"fmt"
54
"os"
65
"path/filepath"
76
"strings"
@@ -12,40 +11,45 @@ import (
1211

1312
"github.com/gruntwork-io/terratest/modules/files"
1413
"github.com/gruntwork-io/terratest/modules/git"
14+
"github.com/gruntwork-io/terratest/modules/opa"
1515
)
1616

17-
// Test to make sure the DownloadPolicyE function returns a local path without processing it.
17+
// TestDownloadPolicyReturnsLocalPath makes sure the DownloadPolicyE function returns a local path without processing it.
1818
func TestDownloadPolicyReturnsLocalPath(t *testing.T) {
1919
t.Parallel()
2020

2121
localPath := "../../examples/terraform-opa-example/policy/enforce_source.rego"
22-
path, err := DownloadPolicyE(t, localPath)
22+
path, err := opa.DownloadPolicyE(t, localPath)
2323
require.NoError(t, err)
2424
assert.Equal(t, localPath, path)
2525
}
2626

27-
// Test to make sure the DownloadPolicyE function returns a remote path to a temporary directory.
27+
// TestDownloadPolicyDownloadsRemote makes sure the DownloadPolicyE function returns a remote path to a temporary
28+
// directory.
2829
func TestDownloadPolicyDownloadsRemote(t *testing.T) {
2930
t.Parallel()
3031

31-
curRef := git.GetCurrentGitRef(t)
32-
baseDir := fmt.Sprintf("git::https://github.com/gruntwork-io/terratest.git?ref=%s", curRef)
32+
curRef := git.GetCurrentGitRefContext(t, t.Context(), "")
33+
baseDir := "git::https://github.com/gruntwork-io/terratest.git?ref=" + curRef
3334
localPath := "../../examples/terraform-opa-example/policy/enforce_source.rego"
34-
remotePath := fmt.Sprintf("git::https://github.com/gruntwork-io/terratest.git//examples/terraform-opa-example/policy/enforce_source.rego?ref=%s", curRef)
35+
remotePath := "git::https://github.com/gruntwork-io/terratest.git//examples/terraform-opa-example/policy/enforce_source.rego?ref=" + curRef
3536

3637
// Make sure we clean up the downloaded file, while simultaneously asserting that the download dir was stored in the
3738
// cache.
3839
defer func() {
39-
downloadPathRaw, inCache := policyDirCache.Load(baseDir)
40+
downloadPathRaw, inCache := opa.PolicyDirCache.Load(baseDir)
4041
require.True(t, inCache)
42+
4143
downloadPath := downloadPathRaw.(string)
44+
4245
if strings.HasSuffix(downloadPath, "/getter") {
4346
downloadPath = filepath.Dir(downloadPath)
4447
}
48+
4549
assert.NoError(t, os.RemoveAll(downloadPath))
4650
}()
4751

48-
path, err := DownloadPolicyE(t, remotePath)
52+
path, err := opa.DownloadPolicyE(t, remotePath)
4953
require.NoError(t, err)
5054

5155
absPath, err := filepath.Abs(localPath)
@@ -54,12 +58,14 @@ func TestDownloadPolicyDownloadsRemote(t *testing.T) {
5458

5559
localContents, err := os.ReadFile(localPath)
5660
require.NoError(t, err)
61+
5762
remoteContents, err := os.ReadFile(path)
5863
require.NoError(t, err)
5964
assert.Equal(t, localContents, remoteContents)
6065
}
6166

62-
// Test to make sure the DownloadPolicyE function uses the cache if it has already downloaded an existing base path.
67+
// TestDownloadPolicyReusesCachedDir makes sure the DownloadPolicyE function uses the cache if it has already downloaded
68+
// an existing base path.
6369
func TestDownloadPolicyReusesCachedDir(t *testing.T) {
6470
t.Parallel()
6571

@@ -70,31 +76,34 @@ func TestDownloadPolicyReusesCachedDir(t *testing.T) {
7076
// Make sure we clean up the downloaded file, while simultaneously asserting that the download dir was stored in the
7177
// cache.
7278
defer func() {
73-
downloadPathRaw, inCache := policyDirCache.Load(baseDir)
79+
downloadPathRaw, inCache := opa.PolicyDirCache.Load(baseDir)
7480
require.True(t, inCache)
81+
7582
downloadPath := downloadPathRaw.(string)
7683

7784
if strings.HasSuffix(downloadPath, "/getter") {
7885
downloadPath = filepath.Dir(downloadPath)
7986
}
87+
8088
assert.NoError(t, os.RemoveAll(downloadPath))
8189
}()
8290

83-
path, err := DownloadPolicyE(t, remotePath)
91+
path, err := opa.DownloadPolicyE(t, remotePath)
8492
require.NoError(t, err)
8593
files.FileExists(path)
8694

87-
downloadPathRaw, inCache := policyDirCache.Load(baseDir)
95+
downloadPathRaw, inCache := opa.PolicyDirCache.Load(baseDir)
8896
require.True(t, inCache)
97+
8998
downloadPath := downloadPathRaw.(string)
9099

91100
// make sure the second call is exactly equal to the first call
92-
newPath, err := DownloadPolicyE(t, remotePath)
101+
newPath, err := opa.DownloadPolicyE(t, remotePath)
93102
require.NoError(t, err)
94103
assert.Equal(t, path, newPath)
95104

96105
// Also make sure the cache is reused for alternative sub dirs.
97-
newAltPath, err := DownloadPolicyE(t, remotePathAltSubPath)
106+
newAltPath, err := opa.DownloadPolicyE(t, remotePathAltSubPath)
98107
require.NoError(t, err)
99108
assert.True(t, strings.HasPrefix(path, downloadPath))
100109
assert.True(t, strings.HasPrefix(newAltPath, downloadPath))

modules/opa/eval.go

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
// Package opa provides helpers for running Open Policy Agent (OPA) evaluations in automated tests.
12
package opa
23

34
import (
5+
"context"
6+
"fmt"
47
"path/filepath"
58
"strings"
69
"sync"
@@ -15,9 +18,6 @@ import (
1518
// EvalOptions defines options that can be passed to the 'opa eval' command for checking policies on arbitrary JSON data
1619
// via OPA.
1720
type EvalOptions struct {
18-
// Whether OPA should run checks with failure.
19-
FailMode FailMode
20-
2121
// Path to rego file containing the OPA rules. Can also be a remote path defined in go-getter syntax. Refer to
2222
// https://github.com/hashicorp/go-getter#url-format for supported options.
2323
RulePath string
@@ -31,6 +31,9 @@ type EvalOptions struct {
3131
// Example: []string{"--strict"} to enable strict mode for the eval subcommand.
3232
ExtraArgs []string
3333

34+
// Whether OPA should run checks with failure.
35+
FailMode FailMode
36+
3437
// The following options can be used to change the behavior of the related functions for debuggability.
3538

3639
// When true, keep any temp files and folders that are created for the purpose of running opa eval.
@@ -52,7 +55,7 @@ const (
5255
NoFail
5356
)
5457

55-
// EvalE runs `opa eval` on the given JSON files using the configured policy file and result query. Translates to:
58+
// Eval runs `opa eval` on the given JSON files using the configured policy file and result query. Translates to:
5659
//
5760
// opa eval -i $JSONFile -d $RulePath $ResultQuery
5861
//
@@ -62,7 +65,8 @@ func Eval(t testing.TestingT, options *EvalOptions, jsonFilePaths []string, resu
6265
require.NoError(t, EvalE(t, options, jsonFilePaths, resultQuery))
6366
}
6467

65-
// EvalE runs `opa eval` on the given JSON files using the configured policy file and result query. Translates to:
68+
// EvalWithOutput runs `opa eval` on the given JSON files using the configured policy file and result query.
69+
// Translates to:
6670
//
6771
// opa eval -i $JSONFile -d $RulePath $ResultQuery
6872
//
@@ -72,6 +76,7 @@ func Eval(t testing.TestingT, options *EvalOptions, jsonFilePaths []string, resu
7276
func EvalWithOutput(t testing.TestingT, options *EvalOptions, jsonFilePaths []string, resultQuery string) (outputs []string) {
7377
outputs, err := EvalWithOutputE(t, options, jsonFilePaths, resultQuery)
7478
require.NoError(t, err)
79+
7580
return
7681
}
7782

@@ -82,6 +87,7 @@ func EvalWithOutput(t testing.TestingT, options *EvalOptions, jsonFilePaths []st
8287
// This will asynchronously run OPA on each file concurrently using goroutines.
8388
func EvalE(t testing.TestingT, options *EvalOptions, jsonFilePaths []string, resultQuery string) (err error) {
8489
_, err = evalE(t, options, jsonFilePaths, resultQuery)
90+
8591
return
8692
}
8793

@@ -98,14 +104,17 @@ func EvalWithOutputE(t testing.TestingT, options *EvalOptions, jsonFilePaths []s
98104
func evalE(t testing.TestingT, options *EvalOptions, jsonFilePaths []string, resultQuery string) (outputs []string, err error) {
99105
downloadedPolicyPath, err := DownloadPolicyE(t, options.RulePath)
100106
if err != nil {
101-
return
107+
return nil, fmt.Errorf("downloading policy %s: %w", options.RulePath, err)
102108
}
103109

104110
outputs = make([]string, len(jsonFilePaths))
105111
wg := new(sync.WaitGroup)
106112
wg.Add(len(jsonFilePaths))
113+
107114
errorsOccurred := new(multierror.Error)
115+
108116
errChans := make([]chan error, len(jsonFilePaths))
117+
109118
for i, jsonFilePath := range jsonFilePaths {
110119
errChan := make(chan error, 1)
111120
errChans[i] = errChan
@@ -114,13 +123,16 @@ func evalE(t testing.TestingT, options *EvalOptions, jsonFilePaths []string, res
114123
outputs[i] = asyncEval(t, wg, errChan, options, downloadedPolicyPath, jsonFilePath, resultQuery)
115124
}(i, jsonFilePath)
116125
}
126+
117127
wg.Wait()
128+
118129
for _, errChan := range errChans {
119130
err := <-errChan
120131
if err != nil {
121132
errorsOccurred = multierror.Append(errorsOccurred, err)
122133
}
123134
}
135+
124136
return outputs, errorsOccurred.ErrorOrNil()
125137
}
126138

@@ -135,27 +147,33 @@ func asyncEval(
135147
resultQuery string,
136148
) (output string) {
137149
defer wg.Done()
138-
cmd := shell.Command{
150+
151+
cmd := &shell.Command{
139152
Command: "opa",
140153
Args: formatOPAEvalArgs(options, downloadedPolicyPath, jsonFilePath, resultQuery),
141154

142155
// Do not log output from shell package so we can log the full json without breaking it up. This is ok, because
143156
// opa eval is typically very quick.
144157
Logger: logger.Discard,
145158
}
159+
146160
output, err := runCommandWithFullLoggingE(t, options.Logger, cmd)
161+
147162
ruleBasePath := filepath.Base(downloadedPolicyPath)
163+
148164
if err == nil {
149165
options.Logger.Logf(t, "opa eval passed on file %s (policy %s; query %s)", jsonFilePath, ruleBasePath, resultQuery)
150166
} else {
151167
options.Logger.Logf(t, "Failed opa eval on file %s (policy %s; query %s)", jsonFilePath, ruleBasePath, resultQuery)
152-
if options.DebugDisableQueryDataOnError == false {
168+
169+
if !options.DebugDisableQueryDataOnError {
153170
options.Logger.Logf(t, "DEBUG: rerunning opa eval to query for full data.")
154171
cmd.Args = formatOPAEvalArgs(options, downloadedPolicyPath, jsonFilePath, "data")
155172
// We deliberately ignore the error here as we want to only return the original error.
156173
output, _ = runCommandWithFullLoggingE(t, options.Logger, cmd)
157174
}
158175
}
176+
159177
errChan <- err
160178

161179
return
@@ -179,6 +197,8 @@ func formatOPAEvalArgs(options *EvalOptions, rulePath, jsonFilePath, resultQuery
179197
args = append(args, "--fail")
180198
case FailDefined:
181199
args = append(args, "--fail-defined")
200+
case NoFail:
201+
// No additional flags needed.
182202
}
183203

184204
args = append(
@@ -189,14 +209,16 @@ func formatOPAEvalArgs(options *EvalOptions, rulePath, jsonFilePath, resultQuery
189209
resultQuery,
190210
}...,
191211
)
212+
192213
return args
193214
}
194215

195-
// runCommandWithFullLogging will log the command output in its entirety with buffering. This avoids breaking up the
216+
// runCommandWithFullLoggingE will log the command output in its entirety with buffering. This avoids breaking up the
196217
// logs when commands are run concurrently. This is a private function used in the context of opa only because opa runs
197218
// very quickly, and the output of opa is hard to parse if it is broken up by interleaved logs.
198-
func runCommandWithFullLoggingE(t testing.TestingT, logger *logger.Logger, cmd shell.Command) (output string, err error) {
199-
output, err = shell.RunCommandAndGetOutputE(t, cmd)
200-
logger.Logf(t, "Output of command `%s %s`:\n%s", cmd.Command, strings.Join(cmd.Args, " "), output)
219+
func runCommandWithFullLoggingE(t testing.TestingT, lgr *logger.Logger, cmd *shell.Command) (output string, err error) {
220+
output, err = shell.RunCommandContextAndGetOutputE(t, context.Background(), cmd)
221+
lgr.Logf(t, "Output of command `%s %s`:\n%s", cmd.Command, strings.Join(cmd.Args, " "), output)
222+
201223
return
202224
}

0 commit comments

Comments
 (0)