Skip to content

Commit

Permalink
Merge pull request #1940 from barney-s/validate-composition
Browse files Browse the repository at this point in the history
Support for Composition Validation
  • Loading branch information
google-oss-prow[bot] authored Jun 5, 2024
2 parents 6557875 + 9dee2bc commit 0ba5dca
Show file tree
Hide file tree
Showing 24 changed files with 679 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 0ba5dca

Please sign in to comment.