Skip to content

Commit

Permalink
add json-parser step
Browse files Browse the repository at this point in the history
Signed-off-by: Faeka Ansari <faeka6@gmail.com>
  • Loading branch information
fykaa committed Feb 6, 2025
1 parent 8154259 commit 4ef94c4
Show file tree
Hide file tree
Showing 2 changed files with 536 additions and 0 deletions.
137 changes: 137 additions & 0 deletions internal/directives/json_parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package directives

import (
"context"
"encoding/json"
"fmt"
"os"

"github.com/expr-lang/expr"
"github.com/xeipuuv/gojsonschema"

kargoapi "github.com/akuity/kargo/api/v1alpha1"
)

func init() {
builtins.RegisterPromotionStepRunner(newJSONParser(), nil)
}

// jsonParser is an implementation of the PromotionStepRunner interface that
// parses a JSON file and extracts specified outputs.
type jsonParser struct {
schemaLoader gojsonschema.JSONLoader
}

// newJSONParser returns a new instance of jsonParser.
func newJSONParser() PromotionStepRunner {
r := &jsonParser{}
r.schemaLoader = getConfigSchemaLoader(r.Name())
return r
}

// Name implements the PromotionStepRunner interface.
func (jp *jsonParser) Name() string {
return "json-parse"
}

func (jp *jsonParser) RunPromotionStep(
ctx context.Context,
stepCtx *PromotionStepContext,
) (PromotionStepResult, error) {
failure := PromotionStepResult{Status: kargoapi.PromotionPhaseErrored}

if err := jp.validate(stepCtx.Config); err != nil {
return failure, err
}

Check warning on line 45 in internal/directives/json_parser.go

View check run for this annotation

Codecov / codecov/patch

internal/directives/json_parser.go#L40-L45

Added lines #L40 - L45 were not covered by tests

cfg, err := ConfigToStruct[JSONParseConfig](stepCtx.Config)
if err != nil {
return failure, fmt.Errorf("could not convert config into %s config: %w", jp.Name(), err)
}

Check warning on line 50 in internal/directives/json_parser.go

View check run for this annotation

Codecov / codecov/patch

internal/directives/json_parser.go#L47-L50

Added lines #L47 - L50 were not covered by tests

return jp.runPromotionStep(ctx, stepCtx, cfg)

Check warning on line 52 in internal/directives/json_parser.go

View check run for this annotation

Codecov / codecov/patch

internal/directives/json_parser.go#L52

Added line #L52 was not covered by tests
}

// validate validates jsonParser configuration against a JSON schema.
func (jp *jsonParser) validate(cfg Config) error {
return validate(jp.schemaLoader, gojsonschema.NewGoLoader(cfg), jp.Name())
}

func (jp *jsonParser) runPromotionStep(
_ context.Context,
_ *PromotionStepContext,
cfg JSONParseConfig,
) (PromotionStepResult, error) {
failure := PromotionStepResult{Status: kargoapi.PromotionPhaseErrored}

if cfg.Path == "" {
return failure, fmt.Errorf("JSON file path cannot be empty")
}

if len(cfg.Outputs) == 0 {
return failure, fmt.Errorf("invalid json-parse config: outputs is required")
}

data, err := jp.readAndParseJSON(cfg.Path)
if err != nil {
return failure, err
}

extractedValues, err := jp.extractValues(data, cfg.Outputs)
if err != nil {
return failure, err
}

return PromotionStepResult{
Status: kargoapi.PromotionPhaseSucceeded,
Output: extractedValues,
}, nil
}

// readAndParseJSON reads a JSON file and unmarshals it into a map.
func (jp *jsonParser) readAndParseJSON(path string) (map[string]any, error) {
jsonData, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("could not read file: %w", err)
}

var data map[string]any
if err := json.Unmarshal(jsonData, &data); err != nil {
return nil, fmt.Errorf("could not parse JSON: %w", err)
}

return data, nil
}

// extractValues evaluates JSONPath expressions using expr and returns extracted values.
func (jp *jsonParser) extractValues(data map[string]any, outputs []JSONParse) (map[string]any, error) {
results := make(map[string]any)

for _, output := range outputs {
value, err := jp.evaluateExpression(data, output.FromExpression)
if err != nil {
return nil, fmt.Errorf("failed to extract outputs from '%s': %w", output.FromExpression, err)
}
results[output.Name] = value
}

return results, nil
}

// evaluateExpression compiles and runs an expression against the JSON data.
func (jp *jsonParser) evaluateExpression(
data map[string]any,
expression string,
) (any, error) {
program, err := expr.Compile(expression, expr.Env(data))
if err != nil {
return nil, fmt.Errorf("error compiling expression: %w", err)
}

result, err := expr.Run(program, data)
if err != nil {
return nil, fmt.Errorf("error evaluating expression: %w", err)
}

Check warning on line 134 in internal/directives/json_parser.go

View check run for this annotation

Codecov / codecov/patch

internal/directives/json_parser.go#L133-L134

Added lines #L133 - L134 were not covered by tests

return result, nil
}
Loading

0 comments on commit 4ef94c4

Please sign in to comment.