Skip to content

Commit

Permalink
Support for Composition Validation
Browse files Browse the repository at this point in the history
- Add fields in composition status
  - record last generation seen
  - add status map for each expander capturing validation result
- Add logic to callout the grpc.validate method for each expander
  • Loading branch information
barney-s committed Jun 5, 2024
1 parent ea7ee5c commit 0225dc7
Show file tree
Hide file tree
Showing 22 changed files with 686 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ COPY --from=build-stage /go/src/app/expander .

COPY ./expanders/jinja2/requirements.txt ./
RUN pip install --require-hashes -r requirements.txt
COPY ./expanders/jinja2/parse_template.py ./

# Required when setting pod .spec.securityContext.runAsNonRoot: true
#USER 65532:65532
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,31 @@ type CompositionSpec struct {
NamespaceMode NamespaceMode `json:"namespaceMode,omitempty"`
}

type ValidationStatus string

const (
// ValidationStatusUnkown is when it is not validated
ValidationStatusUnknown ValidationStatus = "unknown"
// ValidationStatusSuccess is when valdiation succeeds
ValidationStatusSuccess ValidationStatus = "success"
// ValidationStatusFailed is when valdiation fails
ValidationStatusFailed ValidationStatus = "failed"
// ValidationStatusError is when validation was not called
ValidationStatusError ValidationStatus = "error"
)

// StageStatus captures the status of a stage
type StageValidationStatus struct {
ValidationStatus ValidationStatus `json:"validationStatus,omitempty"`
Reason string `json:"reason,omitempty"`
Message string `json:"message,omitempty"`
}

// CompositionStatus defines the observed state of Composition
type CompositionStatus struct {
Conditions []metav1.Condition `json:"conditions,omitempty"`
Generation int64 `json:"generation,omitempty"`
Conditions []metav1.Condition `json:"conditions,omitempty"`
Stages map[string]StageValidationStatus `json:"stages,omitempty"`
}

//+kubebuilder:object:root=true
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,21 @@ spec:
- type
type: object
type: array
generation:
format: int64
type: integer
stages:
additionalProperties:
description: StageStatus captures the status of a stage
properties:
message:
type: string
reason:
type: string
validationStatus:
type: string
type: object
type: object
type: object
type: object
served: true
Expand Down
94 changes: 78 additions & 16 deletions experiments/compositions/composition/expanders/jinja2/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,47 @@ type server struct {
}

type expander struct {
path string
req *pb.EvaluateRequest
path string
context []byte
config []byte
value []byte
facade []byte
resource string
}

// Verify the expander config/template
func (s *server) Validate(context.Context, *pb.ValidateRequest) (*pb.ValidateResult, error) {
return &pb.ValidateResult{Status: pb.Status_SUCCESS}, nil
func (s *server) Validate(ctx context.Context, req *pb.ValidateRequest) (*pb.ValidateResult, error) {
result := &pb.ValidateResult{
Status: pb.Status_SUCCESS,
Error: &pb.Error{},
}

//log.Printf("ValidateRequest:\n config: %s\n context: %s\n facade: %s\n values: %s\n",
// req.Config, req.Context, req.Facade, req.Value)
dir, err := os.MkdirTemp("", "jinja2")
if err != nil {
newerr := fmt.Errorf("unexpected tmp file creation failure. %w", err)
log.Print(newerr.Error())
return nil, newerr
}
// cleanup
defer os.RemoveAll(dir)

e := expander{
path: dir,
context: req.Context,
facade: req.Facade,
value: req.Value,
config: req.Config,
resource: req.Resource,
}
if err = e.WriteInputsToFileSystem(); err != nil {
newerr := fmt.Errorf("error processing inputs: %w", err)
log.Print(newerr.Error())
return nil, newerr
}

return e.Validate(result)
}

// Evaluate the expander config in context of inputs and return manifests
Expand All @@ -67,7 +101,14 @@ func (s *server) Evaluate(ctx context.Context, req *pb.EvaluateRequest) (*pb.Eva
// cleanup
defer os.RemoveAll(dir)

e := expander{path: dir, req: req}
e := expander{
path: dir,
context: req.Context,
facade: req.Facade,
value: req.Value,
config: req.Config,
resource: req.Resource,
}
if err = e.WriteInputsToFileSystem(); err != nil {
newerr := fmt.Errorf("error processing inputs: %w", err)
log.Print(newerr.Error())
Expand All @@ -79,31 +120,33 @@ func (s *server) Evaluate(ctx context.Context, req *pb.EvaluateRequest) (*pb.Eva

func (e *expander) WriteInputsToFileSystem() error {
context := map[string]interface{}{}
if string(e.req.Context) != "" {
err := json.Unmarshal(e.req.Context, &context)
if string(e.context) != "" {
err := json.Unmarshal(e.context, &context)
if err != nil {
return fmt.Errorf("error unmarshalling req.Context: %w", err)
}
}

facade := map[string]interface{}{}
err := json.Unmarshal(e.req.Facade, &facade)
if err != nil {
return fmt.Errorf("error unmarshalling req.Facade: %w", err)
if string(e.facade) != "" {
err := json.Unmarshal(e.facade, &facade)
if err != nil {
return fmt.Errorf("error unmarshalling req.Facade: %w", err)
}
}

getterValues := map[string]interface{}{}
if string(e.req.Value) != "" {
err := json.Unmarshal(e.req.Value, &getterValues)
if string(e.value) != "" {
err := json.Unmarshal(e.value, &getterValues)
if err != nil {
return fmt.Errorf("error unmarshalling req.Values: %w", err)
}
}

valuesObj := map[string]interface{}{
"context": context,
e.req.Resource: facade,
"values": getterValues,
"context": context,
e.resource: facade,
"values": getterValues,
}

// marshall values
Expand All @@ -119,14 +162,33 @@ func (e *expander) WriteInputsToFileSystem() error {
}

// Write template to file
err = atomicfile.WriteFile(filepath.Join(e.path, "/template"), e.req.Config, 0644)
err = atomicfile.WriteFile(filepath.Join(e.path, "/template"), e.config, 0644)
if err != nil {
return fmt.Errorf("failed to write req.Config to file: %w", err)
}

return nil
}

func (e *expander) Validate(result *pb.ValidateResult) (*pb.ValidateResult, error) {
// usage: python parse_template.py <file>
args := []string{
"parse_template.py",
filepath.Join(e.path, "/template"),
}

op, err := exec.Command("python", args...).CombinedOutput()
if err != nil {
result.Error.Message = fmt.Sprintf("failed validating template:\n %s", string(op))
result.Status = pb.Status_VALIDATE_FAILED
log.Print(result.Error.Message)
return result, nil
}

//log.Printf("Result:\n type: %s\n error: %s\n status: %s\n", result.Type, result.Errors, result.Status)
return result, nil
}

func (e *expander) Evaluate(result *pb.EvaluateResult) (*pb.EvaluateResult, error) {
// wait for /expanded/expanded to be created and then read it
args := []string{
Expand Down
Loading

0 comments on commit 0225dc7

Please sign in to comment.