-
Notifications
You must be signed in to change notification settings - Fork 179
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Faeka Ansari <faeka6@gmail.com>
- Loading branch information
Showing
2 changed files
with
536 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
|
||
cfg, err := ConfigToStruct[JSONParseConfig](stepCtx.Config) | ||
if err != nil { | ||
return failure, fmt.Errorf("could not convert config into %s config: %w", jp.Name(), err) | ||
} | ||
|
||
return jp.runPromotionStep(ctx, stepCtx, cfg) | ||
} | ||
|
||
// 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) | ||
} | ||
|
||
return result, nil | ||
} |
Oops, something went wrong.