Skip to content

Commit

Permalink
support directory walking and multiple documents per file (#244)
Browse files Browse the repository at this point in the history
* support directory walking and multiple documents per file

This extends the directory support to include recursive directories for further organization.

Additionally, this adds support for multiple yaml documents in a single file which allows for simple organization without cluttering folders.

Signed-off-by: Mike Mason <[email protected]>

* move directory walk logic into function which can be used for both new policies and mermaid to remove duplicate code

Signed-off-by: Mike Mason <[email protected]>

---------

Signed-off-by: Mike Mason <[email protected]>
  • Loading branch information
mikemrm authored Apr 17, 2024
1 parent 9417171 commit c71c129
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 67 deletions.
20 changes: 2 additions & 18 deletions cmd/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ package cmd
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"

v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -73,21 +70,8 @@ func writeSchema(_ context.Context, dryRun bool, cfg *config.AppConfig) {
}

if viper.GetBool("mermaid") || viper.GetBool("mermaid-markdown") {
if cfg.SpiceDB.PolicyDir != "" {
files, err := os.ReadDir(cfg.SpiceDB.PolicyDir)
if err != nil {
logger.Fatalw("failed to read policy files from directory", "error", err)
}

filePaths := make([]string, 0, len(files))

for _, file := range files {
if !file.IsDir() && (strings.EqualFold(filepath.Ext(file.Name()), ".yml") || strings.EqualFold(filepath.Ext(file.Name()), ".yaml")) {
filePaths = append(filePaths, cfg.SpiceDB.PolicyDir+"/"+file.Name())
}
}

outputPolicyMermaid(filePaths, viper.GetBool("mermaid-markdown"))
if policyDir := cfg.SpiceDB.PolicyDir; policyDir != "" {
outputPolicyMermaid(policyDir, viper.GetBool("mermaid-markdown"))
}

return
Expand Down
29 changes: 9 additions & 20 deletions cmd/schema_mermaid.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@ package cmd
import (
"bytes"
"fmt"
"os"
"text/template"

"gopkg.in/yaml.v3"

"go.infratographer.com/permissions-api/internal/iapl"
)

Expand Down Expand Up @@ -69,24 +66,16 @@ type mermaidContext struct {
RBAC *iapl.RBAC
}

func outputPolicyMermaid(filePaths []string, markdown bool) {
policy := iapl.PolicyDocument{}

if len(filePaths) > 0 {
for _, filePath := range filePaths {
file, err := os.Open(filePath)
if err != nil {
logger.Fatalw("failed to open policy document file", "error", err)
}
defer file.Close()

var filePolicy iapl.PolicyDocument

if err := yaml.NewDecoder(file).Decode(&filePolicy); err != nil {
logger.Fatalw("failed to open policy document file", "error", err)
}
func outputPolicyMermaid(dirPath string, markdown bool) {
var (
policy iapl.PolicyDocument
err error
)

policy = policy.MergeWithPolicyDocument(filePolicy)
if dirPath != "" {
policy, err = iapl.LoadPolicyDocumentFromDirectory(dirPath)
if err != nil {
logger.Fatalw("failed to load policy documents", "error", err)
}
} else {
policy = iapl.DefaultPolicyDocument()
Expand Down
112 changes: 83 additions & 29 deletions internal/iapl/policy.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package iapl

import (
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -151,65 +154,116 @@ func (p PolicyDocument) MergeWithPolicyDocument(other PolicyDocument) PolicyDocu
return p
}

// NewPolicyFromFile reads the provided file path and returns a new Policy.
func NewPolicyFromFile(filePath string) (Policy, error) {
func loadPolicyDocumentFromFile(filePath string) (PolicyDocument, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, err
return PolicyDocument{}, fmt.Errorf("%s: %w", filePath, err)
}

var policy PolicyDocument
defer file.Close()

if err := yaml.NewDecoder(file).Decode(&policy); err != nil {
return nil, err
var (
finalPolicyDocument = PolicyDocument{}
decoder = yaml.NewDecoder(file)
documentIndex int
)

for {
var policyDocument PolicyDocument

if err = decoder.Decode(&policyDocument); err != nil {
if !errors.Is(err, io.EOF) {
return PolicyDocument{}, fmt.Errorf("%s document %d: %w", filePath, documentIndex, err)
}

break
}

if finalPolicyDocument.RBAC != nil && policyDocument.RBAC != nil {
return PolicyDocument{}, fmt.Errorf("%s document %d: %w", filePath, documentIndex, ErrorDuplicateRBACDefinition)
}

finalPolicyDocument = finalPolicyDocument.MergeWithPolicyDocument(policyDocument)

documentIndex++
}

return NewPolicy(policy), nil
return finalPolicyDocument, nil
}

// NewPolicyFromFiles reads the provided file paths, merges them, and returns a new Policy.
func NewPolicyFromFiles(filePaths []string) (Policy, error) {
mergedPolicy := PolicyDocument{}
// LoadPolicyDocumentFromFiles loads all policy documents in the order provided and returns a merged PolicyDocument.
func LoadPolicyDocumentFromFiles(filePaths ...string) (PolicyDocument, error) {
var policyDocument PolicyDocument

for _, filePath := range filePaths {
file, err := os.Open(filePath)
filePolicyDocument, err := loadPolicyDocumentFromFile(filePath)
if err != nil {
return nil, err
return PolicyDocument{}, err
}
defer file.Close()

var filePolicy PolicyDocument
policyDocument = policyDocument.MergeWithPolicyDocument(filePolicyDocument)
}

return policyDocument, nil
}

if err := yaml.NewDecoder(file).Decode(&filePolicy); err != nil {
return nil, err
// LoadPolicyDocumentFromDirectory reads the provided directory path, reads all files in the directory, merges them, and returns a new merged PolicyDocument.
func LoadPolicyDocumentFromDirectory(directoryPath string) (PolicyDocument, error) {
var filePaths []string

err := filepath.WalkDir(directoryPath, func(path string, entry fs.DirEntry, err error) error {
if err != nil {
return err
}

if mergedPolicy.RBAC != nil && filePolicy.RBAC != nil {
return nil, ErrorDuplicateRBACDefinition
if entry.IsDir() {
return nil
}

mergedPolicy = mergedPolicy.MergeWithPolicyDocument(filePolicy)
ext := filepath.Ext(entry.Name())

if strings.EqualFold(ext, ".yml") || strings.EqualFold(ext, ".yaml") {
filePaths = append(filePaths, path)
}

return nil
})

if err != nil {
return PolicyDocument{}, err
}

return NewPolicy(mergedPolicy), nil
return LoadPolicyDocumentFromFiles(filePaths...)
}

// NewPolicyFromDirectory reads the provided directory path, reads all files in the directory, merges them, and returns a new Policy.
func NewPolicyFromDirectory(directoryPath string) (Policy, error) {
files, err := os.ReadDir(directoryPath)
// NewPolicyFromFile reads the provided file path and returns a new Policy.
func NewPolicyFromFile(filePath string) (Policy, error) {
policyDocument, err := LoadPolicyDocumentFromFiles(filePath)
if err != nil {
return nil, err
}

filePaths := make([]string, 0, len(files))
return NewPolicy(policyDocument), nil
}

for _, file := range files {
if !file.IsDir() && (strings.EqualFold(filepath.Ext(file.Name()), ".yml") || strings.EqualFold(filepath.Ext(file.Name()), ".yaml")) {
filePaths = append(filePaths, directoryPath+"/"+file.Name())
}
// NewPolicyFromFiles reads the provided file paths, merges them, and returns a new Policy.
func NewPolicyFromFiles(filePaths []string) (Policy, error) {
policyDocument, err := LoadPolicyDocumentFromFiles(filePaths...)
if err != nil {
return nil, err
}

return NewPolicy(policyDocument), nil
}

// NewPolicyFromDirectory reads the provided directory path, reads all files in the directory, merges them, and returns a new Policy.
func NewPolicyFromDirectory(directoryPath string) (Policy, error) {
policyDocument, err := LoadPolicyDocumentFromDirectory(directoryPath)
if err != nil {
return nil, err
}

return NewPolicyFromFiles(filePaths)
return NewPolicy(policyDocument), nil
}

func (v *policy) validateUnions() error {
Expand Down

0 comments on commit c71c129

Please sign in to comment.