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

fix(validate): support returning empty resources object/slice #704

Merged
merged 18 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ee0e055
feat(validate): support returning empty resources object/slice
brandtkeller Oct 4, 2024
6be97bd
fix(validate): kyverno example for checking zero resources
brandtkeller Oct 4, 2024
b86bfb7
Merge branch 'main' of github.com:defenseunicorns/lula into 589_zero_…
brandtkeller Oct 28, 2024
d82af08
fix(resources): allow creation of resource file on error
brandtkeller Oct 29, 2024
add9a9d
fix(cleanup): cleanup testing resources
brandtkeller Oct 29, 2024
3d85286
fix(errors): add multierror use for querycluster
brandtkeller Oct 29, 2024
97be4bd
fix(cleanup): cleanup test resources
brandtkeller Oct 29, 2024
b4bae91
fix(k8s): allow for capturing multiple errors
brandtkeller Oct 29, 2024
a8baa46
fix(resources): files domain support for resources auditing
brandtkeller Nov 1, 2024
9dd7bec
fix(dev): get-resources handle schema error
brandtkeller Nov 1, 2024
41c3a13
fix(dev): correct placeholder for string from path to name
brandtkeller Nov 1, 2024
3974617
Merge branch 'main' of github.com:defenseunicorns/lula into 589_zero_…
brandtkeller Nov 1, 2024
0f699fc
fix(resources): resolve error comments/ return available data
brandtkeller Nov 5, 2024
e9776ca
fix(resources): augment testing to support zero resources
brandtkeller Nov 5, 2024
ca8c2f2
chore(docs): add evidence collection docs for file domain
brandtkeller Nov 5, 2024
483cc11
fix(test): update to expected type cast
brandtkeller Nov 5, 2024
0b6c87b
fix(test): zero resources test added in pod validation
brandtkeller Nov 5, 2024
97f5c5c
Merge branch 'main' into 589_zero_resources_allowed
brandtkeller Nov 6, 2024
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 demo/simple/oscal-component-kyverno.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ component-definition:
assert:
all:
- check:
(length(podsvt) > `0`): true
~.podsvt:
metadata:
labels:
Expand Down
1 change: 1 addition & 0 deletions demo/simple/oscal-component-opa.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ component-definition:
import future.keywords.every

validate {
count(input.podsvt) > 0
every pod in input.podsvt {
podLabel := pod.metadata.labels.foo
podLabel == "bar"
Expand Down
11 changes: 9 additions & 2 deletions src/cmd/dev/get-resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,16 @@ var getResourcesCmd = &cobra.Command{
}

collection, err := DevGetResources(ctx, validationBytes, spinner)

// do not perform the write if there is nothing to write (likely error)
if collection != nil {
writeResources(collection, getResourcesOpts.OutputFile)
}

if err != nil {
message.Fatalf(err, "error running dev get-resources: %v", err)
}

writeResources(collection, getResourcesOpts.OutputFile)

spinner.Success()
},
}
Expand All @@ -80,6 +84,9 @@ func DevGetResources(ctx context.Context, validationBytes []byte, spinner *messa
types.GetResourcesOnly(true),
)
if err != nil {
if lulaValidation.DomainResources != nil {
return *lulaValidation.DomainResources, err
}
return nil, err
}

Expand Down
45 changes: 34 additions & 11 deletions src/pkg/domains/files/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package files

import (
"context"
"errors"
"fmt"
"io/fs"
"os"
Expand All @@ -20,6 +21,8 @@ type Domain struct {
func (d Domain) GetResources(ctx context.Context) (types.DomainResources, error) {
var workDir string
var ok bool
var errs error
tmpDRs := make(map[string]interface{})
if workDir, ok = ctx.Value(types.LulaValidationWorkDir).(string); !ok {
// if unset, assume lula is working in the same directory the inputFile is in
workDir = "."
Expand All @@ -28,6 +31,7 @@ func (d Domain) GetResources(ctx context.Context) (types.DomainResources, error)
// see TODO below: maybe this is a REAL directory?
dst, err := os.MkdirTemp("", "lula-files")
if err != nil {
// allow returning on error here?
return nil, err
}

Expand Down Expand Up @@ -58,7 +62,11 @@ func (d Domain) GetResources(ctx context.Context) (types.DomainResources, error)
file := filepath.Join(workDir, fi.Path)
relname, err := copyFile(dst, file)
if err != nil {
return nil, fmt.Errorf("error writing local files: %w", err)
// Assign empty data value for reporting purposes
tmpDRs[fi.Name] = map[string]interface{}{}
filenames[file] = fi.Name
errs = errors.Join(errs, fmt.Errorf("error writing local files: %w", err))
continue
}

// and save this info for later
Expand All @@ -68,23 +76,32 @@ func (d Domain) GetResources(ctx context.Context) (types.DomainResources, error)
// get a list of all the files we just downloaded in the temporary directory
files, err := listFiles(dst)
if err != nil {
brandtkeller marked this conversation as resolved.
Show resolved Hide resolved
return nil, fmt.Errorf("error walking downloaded file tree: %w", err)
errs = errors.Join(errs, fmt.Errorf("error walking downloaded file tree: %w", err))
return tmpDRs, errs
}

// conftest's parser returns a map[string]interface where the filenames are
// the primary map keys.
// need to test this to understand the outcomes on a single file error on the return values
config, err := parser.ParseConfigurations(files)
// Copy values over to the temporary domain resources
for k, v := range config {
tmpDRs[k] = v
}
if err != nil {
return nil, err
errs = errors.Join(errs, err)
return tmpDRs, errs
}

// clean up the resources so it's using the filepath.Name as the map key,
// instead of the file path.
drs := make(types.DomainResources, len(config)+len(unstructuredFiles)+len(filesWithParsers))
for k, v := range config {
drs := make(types.DomainResources, len(tmpDRs)+len(unstructuredFiles)+len(filesWithParsers))
for k, v := range tmpDRs {
rel, err := filepath.Rel(dst, k)
if err != nil {
return nil, fmt.Errorf("error determining relative file path: %w", err)
errs = errors.Join(errs, fmt.Errorf("error determining relative file path: %w", err))
drs[k] = v
continue
}
drs[filenames[rel]] = v
}
Expand All @@ -102,7 +119,8 @@ func (d Domain) GetResources(ctx context.Context) (types.DomainResources, error)
file := filepath.Join(workDir, fi.Path)
relname, err := copyFile(parserDir, file)
if err != nil {
return nil, fmt.Errorf("error writing local files: %w", err)
drs[fi.Name] = map[string]interface{}{}
errs = errors.Join(errs, fmt.Errorf("error writing local files: %w", err))
}

// and save this info for later
Expand All @@ -112,7 +130,8 @@ func (d Domain) GetResources(ctx context.Context) (types.DomainResources, error)
// get a list of all the files we just downloaded in the temporary directory
files, err := listFiles(parserDir)
if err != nil {
return nil, fmt.Errorf("error walking downloaded file tree: %w", err)
errs = errors.Join(errs, fmt.Errorf("error walking downloaded file tree: %w", err))
return drs, errs
brandtkeller marked this conversation as resolved.
Show resolved Hide resolved
}

parsedConfig, err := parser.ParseConfigurationsAs(files, parserName)
Expand All @@ -123,7 +142,9 @@ func (d Domain) GetResources(ctx context.Context) (types.DomainResources, error)
for k, v := range parsedConfig {
rel, err := filepath.Rel(parserDir, k)
if err != nil {
return nil, fmt.Errorf("error determining relative file path: %w", err)
errs = errors.Join(errs, fmt.Errorf("error determining relative file path: %w", err))
drs[filenames[k]] = v
continue
}
drs[filenames[rel]] = v
}
Expand All @@ -136,12 +157,14 @@ func (d Domain) GetResources(ctx context.Context) (types.DomainResources, error)
path := filepath.Clean(filepath.Join(workDir, f.Path))
b, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("error reading source files: %w", err)
errs = errors.Join(errs, fmt.Errorf("error reading source files: %w", err))
drs[f.Name] = ""
continue
}
drs[f.Name] = string(b)
}

return drs, nil
return drs, errs
}

// IsExecutable returns false; the file domain is read-only.
Expand Down
26 changes: 17 additions & 9 deletions src/pkg/domains/kubernetes/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"strings"

Expand All @@ -20,28 +21,35 @@ func QueryCluster(ctx context.Context, cluster *Cluster, resources []Resource) (
return nil, fmt.Errorf("cluster is nil")
}

// We may need a new type here to hold groups of resources

collections := make(map[string]interface{}, 0)
var errs error

for _, resource := range resources {
collection, err := GetResourcesDynamically(ctx, cluster, resource.ResourceRule)
// log error but continue with other resources
// capture error but continue with other resources
if err != nil {
return nil, err
errs = errors.Join(errs, err)
}

if len(collection) > 0 {
// Append to collections if not empty collection
// convert to object if named resource
if resource.ResourceRule.Name != "" {
if resource.ResourceRule.Name != "" {
if len(collection) > 0 {
collections[resource.Name] = collection[0]
} else {
// This request returned no resources
collections[resource.Name] = map[string]interface{}{}
}

} else {
if len(collection) > 0 {
collections[resource.Name] = collection
} else {
// This request returned no resources
collections[resource.Name] = []map[string]interface{}{}
}
}
}
return collections, nil

return collections, errs
}

// GetResourcesDynamically() requires a dynamic interface and processes GVR to return []map[string]interface{}
Expand Down
3 changes: 2 additions & 1 deletion src/pkg/domains/kubernetes/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,13 @@ func (k KubernetesDomain) GetResources(ctx context.Context) (types.DomainResourc
if k.Spec.Resources != nil {
resources, err = QueryCluster(ctx, cluster, k.Spec.Resources)
if err != nil {
return nil, fmt.Errorf("error in query: %v", err)
return resources, fmt.Errorf("error in query: %v", err)
}
}

// Join the resources and createdResources
// Note - resource keys must be unique
// TODO revisit the provenance of this activity
if len(resources) == 0 {
return createdResources, nil
} else {
Expand Down