Skip to content

Commit

Permalink
Add OutputRegistry interface
Browse files Browse the repository at this point in the history
  • Loading branch information
jwillp committed Aug 21, 2024
1 parent 7dae1a0 commit cf34047
Show file tree
Hide file tree
Showing 10 changed files with 491 additions and 182 deletions.
8 changes: 4 additions & 4 deletions hcl.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (l HCLGenericSpecLoader) Load(s Source) ([]Specification, error) {
return nil, errors.NewWithMessage(
UnsupportedSourceErrorCode,
fmt.Sprintf(
"invalid specification source \"%s\", unsupported format \"%s\"",
"invalid specification source %q, unsupported format %q",
s.Location,
s.Format,
),
Expand All @@ -63,7 +63,7 @@ func (l HCLGenericSpecLoader) Load(s Source) ([]Specification, error) {
return nil, errors.NewWithMessage(
InvalidHCLErrorCode,
fmt.Sprintf(
"invalid specification source \"%s\" at line %d:%d, block \"%s\" should contain a name",
"invalid specification source %q at line %d:%d, block %q should contain a name",
s.Location,
block.Range().Start.Line,
block.Range().Start.Column,
Expand All @@ -79,7 +79,7 @@ func (l HCLGenericSpecLoader) Load(s Source) ([]Specification, error) {
err,
InvalidHCLErrorCode,
fmt.Sprintf(
"invalid specification source \"%s\" at line %d:%d for block \"%s\"",
"invalid specification source %q at line %d:%d for block %q",
s.Location,
block.Range().Start.Line,
block.Range().Start.Column,
Expand Down Expand Up @@ -190,7 +190,7 @@ func (l HCLSpecLoader) Load(s Source) ([]Specification, error) {
return nil, errors.NewWithMessage(
UnsupportedSourceErrorCode,
fmt.Sprintf(
"invalid specification source \"%s\", unsupported format \"%s\"",
"invalid specification source %q, unsupported format %q",
s.Location,
s.Format,
),
Expand Down
12 changes: 12 additions & 0 deletions logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ type Logger interface {
Success(msg string)
}

type NoopLogger struct{}

func (n NoopLogger) Trace(msg string) {}

func (n NoopLogger) Info(msg string) {}

func (n NoopLogger) Warning(msg string) {}

func (n NoopLogger) Error(msg string) {}

func (n NoopLogger) Success(msg string) {}

type DefaultLoggerConfig struct {
DisableColors bool
Writer io.Writer
Expand Down
56 changes: 56 additions & 0 deletions mkdiroutproc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package specter

import (
"fmt"
"github.com/morebec/go-errors/errors"
"os"
)

type DirectoryOutput struct {
Path string
Mode os.FileMode
}

type WriteDirectoryOutputsProcessorConfig struct {
// Add any configuration specific to directory processing if needed
UseRegistry bool
}

type WriteDirectoryOutputsProcessor struct {
config WriteDirectoryOutputsProcessorConfig
}

func NewWriteDirectoryOutputsProcessor(conf WriteDirectoryOutputsProcessorConfig) *WriteDirectoryOutputsProcessor {
return &WriteDirectoryOutputsProcessor{
config: conf,
}
}

func (p WriteDirectoryOutputsProcessor) Name() string {
return "directory_outputs_processor"
}

func (p WriteDirectoryOutputsProcessor) Process(ctx OutputProcessingContext) error {
ctx.Logger.Info("Creating output directories ...")

var directories []DirectoryOutput
for _, o := range ctx.Outputs {
dir, ok := o.Value.(DirectoryOutput)
if !ok {
continue
}
directories = append(directories, dir)
}

for _, dir := range directories {
ctx.Logger.Info(fmt.Sprintf("Creating directory %s ...", dir.Path))
err := os.MkdirAll(dir.Path, dir.Mode)
if err != nil {
ctx.Logger.Error(fmt.Sprintf("failed creating directory at %s", dir.Path))
return errors.Wrap(err, "failed creating output directories")
}
}

ctx.Logger.Success("Output directories created successfully.")
return nil
}
108 changes: 108 additions & 0 deletions outputregistry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package specter

import (
"encoding/json"
"github.com/morebec/go-errors/errors"
"os"
"sync"
"time"
)

// JSONOutputRegistry allows tracking on the file system the files that were written by the last execution
// of the WriteFileOutputsProcessor to perform cleaning operations on next executions.
type JSONOutputRegistry struct {
GeneratedAt time.Time `json:"generatedAt"`
OutputMap map[string]*JSONOutputRegistryProcessor `json:"files"`
FilePath string
mu sync.RWMutex // Mutex to protect concurrent access
}

type JSONOutputRegistryProcessor struct {
Outputs []string `json:"files"`
}

// NewJSONOutputRegistry returns a new output file registry.
func NewJSONOutputRegistry(fileName string) *JSONOutputRegistry {
return &JSONOutputRegistry{
GeneratedAt: time.Now(),
OutputMap: nil,
FilePath: fileName,
}
}

func (r *JSONOutputRegistry) Load() error {
r.mu.Lock()
defer r.mu.Unlock()

bytes, err := os.ReadFile(r.FilePath)

if err != nil {
if os.IsNotExist(err) {
return nil
}
return errors.WrapWithMessage(err, errors.InternalErrorCode, "failed loading output file registry")
}
if err := json.Unmarshal(bytes, r); err != nil {
return errors.WrapWithMessage(err, errors.InternalErrorCode, "failed loading output file registry")
}

return nil
}

func (r *JSONOutputRegistry) Save() error {
r.mu.RLock()
defer r.mu.RUnlock()

if r.OutputMap == nil {
return nil
}
// Generate a JSON file containing all output files for clean up later on
js, err := json.MarshalIndent(r, "", " ")
if err != nil {
return errors.Wrap(err, "failed generating output file registry")
}
if err := os.WriteFile(r.FilePath, js, os.ModePerm); err != nil {
return errors.Wrap(err, "failed generating output file registry")
}

return nil
}

func (r *JSONOutputRegistry) AddOutput(processorName string, outputName string) {
r.mu.Lock()
defer r.mu.Unlock()

if _, ok := r.OutputMap[processorName]; !ok {
r.OutputMap[processorName] = &JSONOutputRegistryProcessor{}
}
r.OutputMap[processorName].Outputs = append(r.OutputMap[processorName].Outputs, outputName)
}

func (r *JSONOutputRegistry) RemoveOutput(processorName string, outputName string) {
r.mu.Lock()
defer r.mu.Unlock()

if _, ok := r.OutputMap[processorName]; !ok {
return
}

var files []string
for _, file := range r.OutputMap[processorName].Outputs {
if file != outputName {
files = append(files, file)
}
}

r.OutputMap[processorName].Outputs = files
}

func (r *JSONOutputRegistry) Outputs(processorName string) []string {
r.mu.RLock()
defer r.mu.RUnlock()

outputs, ok := r.OutputMap[processorName]
if !ok {
return nil
}
return outputs.Outputs
}
68 changes: 67 additions & 1 deletion processing.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ type ProcessingOutput struct {
Value any
}

// SpecificationProcessor are services responsible for performing work using Specifications.
// SpecificationProcessor are services responsible for performing work using Specifications
// and which can possibly generate outputs.
type SpecificationProcessor interface {
// Name returns the unique FilePath of this processor.
Name() string
Expand All @@ -34,10 +35,75 @@ type SpecificationProcessor interface {
Process(ctx ProcessingContext) ([]ProcessingOutput, error)
}

// OutputRegistry provides an interface for managing a registry of outputs. This
// registry tracks outputs generated during processing runs, enabling clean-up
// in subsequent runs to avoid residual artifacts and maintain a clean slate.
//
// Implementations of the OutputRegistry interface must be thread-safe to handle
// concurrent calls to TrackFile and UntrackFile methods. Multiple goroutines may
// access the registry simultaneously, so appropriate synchronization mechanisms
// should be implemented to prevent race conditions and ensure data integrity.
type OutputRegistry interface {
// Load the registry state from persistent storage. If an error occurs, it
// should be returned to indicate the failure of the loading operation.
Load() error

// Save the current state of the registry to persistent storage. If an
// error occurs, it should be returned to indicate the failure of the saving operation.
Save() error

// AddOutput registers an output name under a specific processor name. This method
// should ensure that the file path is associated with the given processor name
// in the registry.
AddOutput(processorName string, outputName string)

// RemoveOutput removes a given named output registration for a specific processor name. This
// method should ensure that the file path is disassociated from the given
// processor name in the registry.
RemoveOutput(processorName string, outputName string)

// Outputs returns the outputs for a given processor.
Outputs(processorName string) []string
}

type NoopOutputRegistry struct {
}

func (n NoopOutputRegistry) Load() error {
return nil
}

func (n NoopOutputRegistry) Save() error {
return nil
}

func (n NoopOutputRegistry) AddOutput(processorName string, outputName string) {}

func (n NoopOutputRegistry) RemoveOutput(processorName string, outputName string) {}

func (n NoopOutputRegistry) Outputs(processorName string) []string {
return nil
}

type OutputProcessingContext struct {
Specifications SpecificationGroup
Outputs []ProcessingOutput
Logger Logger

outputRegistry OutputRegistry
processorName string
}

func (c *OutputProcessingContext) AddToRegistry(outputName string) {
c.outputRegistry.AddOutput(c.processorName, outputName)
}

func (c *OutputProcessingContext) RemoveFromRegistry(outputName string) {
c.outputRegistry.RemoveOutput(c.processorName, outputName)
}

func (c *OutputProcessingContext) RegistryOutputs() []string {
return c.outputRegistry.Outputs(c.processorName)
}

// OutputProcessor are services responsible for processing outputs of SpecProcessors.
Expand Down
Loading

0 comments on commit cf34047

Please sign in to comment.