diff --git a/internal/build.go b/internal/build.go index f6e1add..cf6e55d 100644 --- a/internal/build.go +++ b/internal/build.go @@ -3,8 +3,10 @@ package internal import ( "bytes" "context" + "fmt" "io" "os" + "path" "strings" "github.com/open-policy-agent/opa/ast" @@ -30,6 +32,11 @@ type BuildCommandParams struct { func RunBuild(args []string, params *BuildCommandParams) error { buf := bytes.NewBuffer(nil) + metadataFile, err := createManifest(args) + if err == nil && metadataFile != "" { + defer os.Remove(metadataFile) + } + var capabilities *ast.Capabilities // if capabilities are not provided as a cmd flag, // then ast.CapabilitiesForThisVersion must be called @@ -42,19 +49,20 @@ func RunBuild(args []string, params *BuildCommandParams) error { compiler := compile.New(). WithCapabilities(capabilities). WithTarget(params.Target.String()). - WithAsBundle(false). + WithAsBundle(true). WithOptimizationLevel(0). WithOutput(buf). WithEntrypoints(params.Entrypoint.Strings()...). WithPaths(args...). - WithFilter(buildCommandLoaderFilter(false, params.Ignore)) + WithFilter(buildCommandLoaderFilter(true, params.Ignore)) - err := compiler.Build(context.Background()) + err = compiler.Build(context.Background()) if err != nil { return err } out, err := os.Create(params.OutputFile) + defer out.Close() if err != nil { return err } @@ -64,14 +72,33 @@ func RunBuild(args []string, params *BuildCommandParams) error { return err } - return out.Close() + return nil } func buildCommandLoaderFilter(bundleMode bool, ignore []string) func(string, os.FileInfo, int) bool { return func(abspath string, info os.FileInfo, depth int) bool { - if !info.IsDir() && strings.HasSuffix(abspath, ".tar.gz") { - return true + if !bundleMode { + if !info.IsDir() && strings.HasSuffix(abspath, ".tar.gz") { + return true + } } return util.LoaderFilter{Ignore: ignore}.Apply(abspath, info, depth) } } + +func createManifest(inputPaths []string) (string, error) { + rules, err := util.RetrieveRules(inputPaths) + if err != nil { + return "", err + } + + // choose either one of the locations for the metadata.json + // it will be included in the OPA generated data.json + metadataFile := path.Join(inputPaths[0], ".manifest") + err = os.WriteFile(metadataFile, []byte(fmt.Sprintf("{\"metadata\":{\"numberOfRules\":%d}}", len(rules))), + 0644) + if err != nil { + return "", err + } + return metadataFile, nil +} diff --git a/internal/build_test.go b/internal/build_test.go index 066f145..3a1c98d 100644 --- a/internal/build_test.go +++ b/internal/build_test.go @@ -2,6 +2,7 @@ package internal import ( "archive/tar" + "bytes" "compress/gzip" "io" "os" @@ -268,3 +269,53 @@ func TestBuildRespectsCapabilitiesFailure(t *testing.T) { assert.Contains(t, err.Error(), "undefined function is_foo") }) } + +func TestBuildProducesMetadata(t *testing.T) { + files := map[string]string{ + "test.rego": ` + package test + msg = { + "publicId": + "1" + } + `, + } + + test.WithTempFS(files, func(root string) { + buildParams := mockBuildParams() + buildParams.OutputFile = path.Join(root, "bundle.tar.gz") + err := buildParams.Target.Set(TargetRego) + assert.Nil(t, err) + + err = RunBuild([]string{root}, buildParams) + assert.Nil(t, err) + + _, err = loader.NewFileLoader().AsBundle(buildParams.OutputFile) + assert.Nil(t, err) + + // Check that manifest is not written given no input manifest and no other flags + f, err := os.Open(buildParams.OutputFile) + assert.Nil(t, err) + defer f.Close() + + gr, err := gzip.NewReader(f) + assert.Nil(t, err) + + tr := tar.NewReader(gr) + + for { + f, err := tr.Next() + if err == io.EOF { + break + } + assert.Nil(t, err) + + if f.Name == "/.manifest" { + data := new(bytes.Buffer) + _, err := data.ReadFrom(tr) + assert.Nil(t, err) + assert.Contains(t, data.String(), "{\"numberOfRules\":1}") + } + } + }) +}