From 77f9a06c06c384f5b0db4ec55823ccf3b1094f5f Mon Sep 17 00:00:00 2001 From: Simon Baird Date: Thu, 15 Jan 2026 17:10:49 -0500 Subject: [PATCH 1/3] Add support for embedded rego The idea is that this provides a way to make common library functions available to Conforma policies, without needing to fetch them down as a policy source. It's kind of experimental currently, but the initial motivation is to avoid the awkwardness around the way we'd like to make lib.result_helper availble to users writing their own policies. If we like the idea, it should be simple enough to extend it to whatever shared library we'd like to provide. There are pros and cons with this idea so let's not commit to it fully just yet. In this commit the only embedded rego is a simple "hello_world" rule. Co-Authored-By: Claude --- .../embedded/rego/ec_lib/hello_world.rego | 4 ++ internal/embedded/rego/embed.go | 26 +++++++++++++ internal/embedded/rego/embed_test.go | 38 +++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 internal/embedded/rego/ec_lib/hello_world.rego create mode 100644 internal/embedded/rego/embed.go create mode 100644 internal/embedded/rego/embed_test.go diff --git a/internal/embedded/rego/ec_lib/hello_world.rego b/internal/embedded/rego/ec_lib/hello_world.rego new file mode 100644 index 000000000..1189e83f1 --- /dev/null +++ b/internal/embedded/rego/ec_lib/hello_world.rego @@ -0,0 +1,4 @@ +package ec_lib + +# Simple POC to demonstrate the idea of embedding rego in the cli +hello_world(name) := sprintf("Hello, %s! (from embedded rego)", [name]) diff --git a/internal/embedded/rego/embed.go b/internal/embedded/rego/embed.go new file mode 100644 index 000000000..20c0c0acb --- /dev/null +++ b/internal/embedded/rego/embed.go @@ -0,0 +1,26 @@ +// Copyright The Conforma Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package rego + +import "embed" + +// EmbeddedRego contains rego files embedded in the CLI binary. +// These files are available without needing to fetch them from external +// sources, solving the shared dependency problem for common rego utilities. +// +//go:embed ec_lib/*.rego +var EmbeddedRego embed.FS diff --git a/internal/embedded/rego/embed_test.go b/internal/embedded/rego/embed_test.go new file mode 100644 index 000000000..349535f30 --- /dev/null +++ b/internal/embedded/rego/embed_test.go @@ -0,0 +1,38 @@ +// Copyright The Conforma Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +//go:build unit + +package rego + +import ( + "io/fs" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestHelloWorldRegoContent(t *testing.T) { + // Test that the hello_world.rego file can be read and has expected content + content, err := fs.ReadFile(EmbeddedRego, "ec_lib/hello_world.rego") + require.NoError(t, err, "Should be able to read hello_world.rego") + + contentStr := string(content) + assert.Contains(t, contentStr, "package ec_lib") + assert.Contains(t, contentStr, "hello_world(name)") + assert.Contains(t, contentStr, "from embedded rego") +} From 2e13cf654d01a3ac8f82b29c5f46970a4bf51f62 Mon Sep 17 00:00:00 2001 From: Simon Baird Date: Thu, 15 Jan 2026 17:19:50 -0500 Subject: [PATCH 2/3] Add ability to write embeded rego to disk In the previous commit we focus on reading the embded rego files. Here we're writing it out. Introduces a reusable utility function that writes embedded rego files to the filesystem, following the same directory structure pattern used by external policy sources. Also, call it to write the embedded rego to disk when preparing the policy dir for the Conftest evaluator. The function follows the uniqueDestination pattern where each policy source gets its own subdirectory, with "embedded" acting as the identifier for embedded rego files. We're assuming there's only one embedded directory currently. Co-Authored-By: Claude --- internal/evaluator/conftest_evaluator.go | 6 + internal/utils/embedded_rego_test.go | 156 +++++++++++++++++++++++ internal/utils/helpers.go | 55 ++++++++ 3 files changed, 217 insertions(+) create mode 100644 internal/utils/embedded_rego_test.go diff --git a/internal/evaluator/conftest_evaluator.go b/internal/evaluator/conftest_evaluator.go index f2e434ae6..190429ce7 100644 --- a/internal/evaluator/conftest_evaluator.go +++ b/internal/evaluator/conftest_evaluator.go @@ -352,6 +352,12 @@ func NewConftestEvaluatorWithNamespaceAndFilterType( c.policyDir = filepath.Join(c.workDir, "policy") c.dataDir = filepath.Join(c.workDir, "data") + // Write embedded rego files to policy directory + if err := utils.WriteEmbeddedRego(ctx, fs, c.policyDir); err != nil { + log.Warnf("Failed to write embedded rego files: %v", err) + // Continue without embedded files - graceful degradation + } + if err := c.createDataDirectory(ctx); err != nil { return nil, err } diff --git a/internal/utils/embedded_rego_test.go b/internal/utils/embedded_rego_test.go new file mode 100644 index 000000000..884198ff1 --- /dev/null +++ b/internal/utils/embedded_rego_test.go @@ -0,0 +1,156 @@ +// Copyright The Conforma Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +//go:build unit + +package utils + +import ( + "context" + "path/filepath" + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestWriteEmbeddedRego(t *testing.T) { + // Test that embedded rego files are written correctly to filesystem + ctx := context.Background() + fs := afero.NewMemMapFs() + + // Create a temporary policy directory + policyDir := "/tmp/policy" + err := fs.MkdirAll(policyDir, 0o755) + require.NoError(t, err) + + // Call WriteEmbeddedRego + err = WriteEmbeddedRego(ctx, fs, policyDir) + require.NoError(t, err, "WriteEmbeddedRego should succeed") + + // Verify embedded directory was created + embeddedDir := filepath.Join(policyDir, "embedded") + exists, err := afero.DirExists(fs, embeddedDir) + require.NoError(t, err) + assert.True(t, exists, "embedded directory should be created") + + // Verify hello_world.rego file was written + helloWorldPath := filepath.Join(embeddedDir, "ec_lib", "hello_world.rego") + exists, err = afero.Exists(fs, helloWorldPath) + require.NoError(t, err) + assert.True(t, exists, "hello_world.rego should be written") + + // Verify file content is correct + content, err := afero.ReadFile(fs, helloWorldPath) + require.NoError(t, err) + + contentStr := string(content) + assert.Contains(t, contentStr, "package ec_lib") + assert.Contains(t, contentStr, "hello_world(name)") + assert.Contains(t, contentStr, "from embedded rego") +} + +func TestWriteEmbeddedRegoMaintainsDirectoryStructure(t *testing.T) { + // Test that directory structure from embedded files is maintained + ctx := context.Background() + fs := afero.NewMemMapFs() + + policyDir := "/tmp/policy" + err := fs.MkdirAll(policyDir, 0o755) + require.NoError(t, err) + + err = WriteEmbeddedRego(ctx, fs, policyDir) + require.NoError(t, err) + + // Verify the full directory structure: policy/embedded/ec_lib/hello_world.rego + expectedPath := filepath.Join(policyDir, "embedded", "ec_lib", "hello_world.rego") + exists, err := afero.Exists(fs, expectedPath) + require.NoError(t, err) + assert.True(t, exists, "Full directory structure should be maintained") + + // Verify ec_lib directory exists + ecLibDir := filepath.Join(policyDir, "embedded", "ec_lib") + exists, err = afero.DirExists(fs, ecLibDir) + require.NoError(t, err) + assert.True(t, exists, "ec_lib subdirectory should be created") +} + +func TestWriteEmbeddedRegoFilePermissions(t *testing.T) { + // Test that files are written with correct permissions + ctx := context.Background() + fs := afero.NewMemMapFs() + + policyDir := "/tmp/policy" + err := fs.MkdirAll(policyDir, 0o755) + require.NoError(t, err) + + err = WriteEmbeddedRego(ctx, fs, policyDir) + require.NoError(t, err) + + // Check file permissions + helloWorldPath := filepath.Join(policyDir, "embedded", "ec_lib", "hello_world.rego") + info, err := fs.Stat(helloWorldPath) + require.NoError(t, err) + + // Should be readable and writable by owner, readable by group and others + assert.Equal(t, "-rw-r--r--", info.Mode().String()) +} + +func TestWriteEmbeddedRegoNonExistentPolicyDir(t *testing.T) { + // Test behavior when policy directory doesn't exist + ctx := context.Background() + fs := afero.NewMemMapFs() + + // Don't create the policy directory + policyDir := "/tmp/nonexistent/policy" + + // WriteEmbeddedRego should create necessary directories + err := WriteEmbeddedRego(ctx, fs, policyDir) + require.NoError(t, err, "Should create necessary directories") + + // Verify the embedded directory was created + embeddedDir := filepath.Join(policyDir, "embedded") + exists, err := afero.DirExists(fs, embeddedDir) + require.NoError(t, err) + assert.True(t, exists, "Should create embedded directory and parents") +} + +func TestWriteEmbeddedRegoIdempotent(t *testing.T) { + // Test that calling WriteEmbeddedRego multiple times is safe + ctx := context.Background() + fs := afero.NewMemMapFs() + + policyDir := "/tmp/policy" + err := fs.MkdirAll(policyDir, 0o755) + require.NoError(t, err) + + // Call WriteEmbeddedRego twice + err = WriteEmbeddedRego(ctx, fs, policyDir) + require.NoError(t, err, "First call should succeed") + + err = WriteEmbeddedRego(ctx, fs, policyDir) + require.NoError(t, err, "Second call should succeed (idempotent)") + + // Verify file still exists and has correct content + helloWorldPath := filepath.Join(policyDir, "embedded", "ec_lib", "hello_world.rego") + content, err := afero.ReadFile(fs, helloWorldPath) + require.NoError(t, err) + + contentStr := string(content) + assert.Contains(t, contentStr, "package ec_lib") + assert.Contains(t, contentStr, "hello_world(name)") +} diff --git a/internal/utils/helpers.go b/internal/utils/helpers.go index 082c6dc00..191f05502 100644 --- a/internal/utils/helpers.go +++ b/internal/utils/helpers.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "fmt" + "io/fs" "os" "path/filepath" "strings" @@ -28,6 +29,8 @@ import ( log "github.com/sirupsen/logrus" "github.com/spf13/afero" "sigs.k8s.io/yaml" + + embeddedRego "github.com/conforma/cli/internal/embedded/rego" ) // CreateWorkDir creates the working directory in tmp and some subdirectories @@ -162,3 +165,55 @@ func anyEnvSet(varNames []string) bool { } return false } + +// WriteEmbeddedRego writes embedded rego files to the specified policy directory. +// This follows the same pattern as external policy sources, where each source +// gets its own subdirectory. Embedded rego files are placed in an "embedded" +// subdirectory to match the uniqueDestination pattern used for external sources. +func WriteEmbeddedRego(ctx context.Context, afs afero.Fs, policyDir string) error { + // Create the embedded subdirectory (follows pattern where each source gets its own subdir) + embeddedDir := filepath.Join(policyDir, "embedded") + if err := afs.MkdirAll(embeddedDir, 0o755); err != nil { + return fmt.Errorf("failed to create embedded rego directory: %w", err) + } + + // Walk through all embedded rego files and write them to the filesystem + return fs.WalkDir(embeddedRego.EmbeddedRego, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return fmt.Errorf("failed to walk embedded rego file %s: %w", path, err) + } + + // Skip directories + if d.IsDir() { + return nil + } + + // Only process .rego files + if !strings.HasSuffix(path, ".rego") { + return nil + } + + // Read the embedded file content + content, err := fs.ReadFile(embeddedRego.EmbeddedRego, path) + if err != nil { + return fmt.Errorf("failed to read embedded rego file %s: %w", path, err) + } + + // Write to the target location, maintaining directory structure + targetPath := filepath.Join(embeddedDir, path) + targetDir := filepath.Dir(targetPath) + + // Ensure target directory exists + if err := afs.MkdirAll(targetDir, 0o755); err != nil { + return fmt.Errorf("failed to create directory %s: %w", targetDir, err) + } + + // Write the file + if err := afero.WriteFile(afs, targetPath, content, 0o644); err != nil { + return fmt.Errorf("failed to write embedded rego file to %s: %w", targetPath, err) + } + + log.Debugf("Wrote embedded rego file: %s", targetPath) + return nil + }) +} From 7103e0d3e39cd4fb931f1a1fcc90e3261a075115 Mon Sep 17 00:00:00 2001 From: Simon Baird Date: Mon, 19 Jan 2026 12:50:38 -0500 Subject: [PATCH 3/3] Add acceptance tests for embedded rego This commit includes: - features/embedded_rego.feature: Gherkin acceptance test scenario - acceptance/examples/embedded_rego_test.rego: Test policy using ec_lib.hello_world - acceptance/examples/embedded_rego_config.yaml: Policy configuration The acceptance test verifies that: - Embedded rego functions are available in policy evaluation - The ec_lib.hello_world function works correctly - Policies can import and use data.ec_lib namespace - Output contains expected success messages Co-Authored-By: Claude --- acceptance/examples/embedded_rego_config.yaml | 4 ++++ acceptance/examples/embedded_rego_test.rego | 16 ++++++++++++++ features/__snapshots__/embedded_rego.snap | 20 +++++++++++++++++ features/embedded_rego.feature | 22 +++++++++++++++++++ 4 files changed, 62 insertions(+) create mode 100644 acceptance/examples/embedded_rego_config.yaml create mode 100644 acceptance/examples/embedded_rego_test.rego create mode 100755 features/__snapshots__/embedded_rego.snap create mode 100644 features/embedded_rego.feature diff --git a/acceptance/examples/embedded_rego_config.yaml b/acceptance/examples/embedded_rego_config.yaml new file mode 100644 index 000000000..ee4f0a283 --- /dev/null +++ b/acceptance/examples/embedded_rego_config.yaml @@ -0,0 +1,4 @@ +--- +sources: + - policy: + - "git::https://${GITHOST}/git/embedded-rego-policy.git" diff --git a/acceptance/examples/embedded_rego_test.rego b/acceptance/examples/embedded_rego_test.rego new file mode 100644 index 000000000..837fec405 --- /dev/null +++ b/acceptance/examples/embedded_rego_test.rego @@ -0,0 +1,16 @@ +package main + +import data.ec_lib + +# METADATA +# custom: +# short_name: embedded_test +deny contains result if { + # Always deny + true + + # We're expecting this to be defined in the "embedded" rego + msg := ec_lib.hello_world("testy mctest") + + result := {"code": "main.embedded_test", "msg": msg} +} diff --git a/features/__snapshots__/embedded_rego.snap b/features/__snapshots__/embedded_rego.snap new file mode 100755 index 000000000..083191521 --- /dev/null +++ b/features/__snapshots__/embedded_rego.snap @@ -0,0 +1,20 @@ + +[policy using embedded rego functions:stdout - 1] +Success: false +Result: FAILURE +Violations: 1, Warnings: 0, Successes: 0 +Input File: pipeline_definition.json + +Results: +✕ [Violation] main.embedded_test + FilePath: pipeline_definition.json + Reason: Hello, testy mctest! (from embedded rego) + +For more information about policy issues, see the policy documentation: https://conforma.dev/docs/policy/ + +--- + +[policy using embedded rego functions:stderr - 1] +Error: success criteria not met + +--- diff --git a/features/embedded_rego.feature b/features/embedded_rego.feature new file mode 100644 index 000000000..3dd3bbe22 --- /dev/null +++ b/features/embedded_rego.feature @@ -0,0 +1,22 @@ +Feature: embedded rego functionality + The ec command should be able to use embedded rego functions in policy evaluation + + Background: + # Todo: We can use file paths so we don't really need git to test this + Given stub git daemon running + + Scenario: policy using embedded rego functions + Given a git repository named "embedded-rego-config" with + | policy.yaml | examples/embedded_rego_config.yaml | + Given a git repository named "embedded-rego-policy" with + | main.rego | examples/embedded_rego_test.rego | + + # The rego in embedded_rego_test ignores the input + Given a pipeline definition file named "pipeline_definition.json" containing + """ + {} + """ + + When ec command is run with "validate input --file pipeline_definition.json --policy git::https://${GITHOST}/git/embedded-rego-config.git --show-successes" + Then the exit status should be 1 + Then the output should match the snapshot