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

support directory walking and multiple documents per file #244

Merged
merged 2 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading