...

Source file src/sigs.k8s.io/kustomize/kyaml/fn/framework/result.go

Documentation: sigs.k8s.io/kustomize/kyaml/fn/framework

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package framework
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  	"strings"
    10  
    11  	"sigs.k8s.io/kustomize/kyaml/yaml"
    12  )
    13  
    14  // Severity indicates the severity of the Result
    15  type Severity string
    16  
    17  const (
    18  	// Error indicates the result is an error.  Will cause the function to exit non-0.
    19  	Error Severity = "error"
    20  	// Warning indicates the result is a warning
    21  	Warning Severity = "warning"
    22  	// Info indicates the result is an informative message
    23  	Info Severity = "info"
    24  )
    25  
    26  // ResultItem defines a validation result
    27  type Result struct {
    28  	// Message is a human readable message. This field is required.
    29  	Message string `yaml:"message,omitempty" json:"message,omitempty"`
    30  
    31  	// Severity is the severity of this result
    32  	Severity Severity `yaml:"severity,omitempty" json:"severity,omitempty"`
    33  
    34  	// ResourceRef is a reference to a resource.
    35  	// Required fields: apiVersion, kind, name.
    36  	ResourceRef *yaml.ResourceIdentifier `yaml:"resourceRef,omitempty" json:"resourceRef,omitempty"`
    37  
    38  	// Field is a reference to the field in a resource this result refers to
    39  	Field *Field `yaml:"field,omitempty" json:"field,omitempty"`
    40  
    41  	// File references a file containing the resource this result refers to
    42  	File *File `yaml:"file,omitempty" json:"file,omitempty"`
    43  
    44  	// Tags is an unstructured key value map stored with a result that may be set
    45  	// by external tools to store and retrieve arbitrary metadata
    46  	Tags map[string]string `yaml:"tags,omitempty" json:"tags,omitempty"`
    47  }
    48  
    49  // String provides a human-readable message for the result item
    50  func (i Result) String() string {
    51  	identifier := i.ResourceRef
    52  	var idStringList []string
    53  	if identifier != nil {
    54  		if identifier.APIVersion != "" {
    55  			idStringList = append(idStringList, identifier.APIVersion)
    56  		}
    57  		if identifier.Kind != "" {
    58  			idStringList = append(idStringList, identifier.Kind)
    59  		}
    60  		if identifier.Namespace != "" {
    61  			idStringList = append(idStringList, identifier.Namespace)
    62  		}
    63  		if identifier.Name != "" {
    64  			idStringList = append(idStringList, identifier.Name)
    65  		}
    66  	}
    67  	formatString := "[%s]"
    68  	severity := i.Severity
    69  	// We default Severity to Info when converting a result to a message.
    70  	if i.Severity == "" {
    71  		severity = Info
    72  	}
    73  	list := []interface{}{severity}
    74  	if len(idStringList) > 0 {
    75  		formatString += " %s"
    76  		list = append(list, strings.Join(idStringList, "/"))
    77  	}
    78  	if i.Field != nil {
    79  		formatString += " %s"
    80  		list = append(list, i.Field.Path)
    81  	}
    82  	formatString += ": %s"
    83  	list = append(list, i.Message)
    84  	return fmt.Sprintf(formatString, list...)
    85  }
    86  
    87  // File references a file containing a resource
    88  type File struct {
    89  	// Path is relative path to the file containing the resource.
    90  	// This field is required.
    91  	Path string `yaml:"path,omitempty" json:"path,omitempty"`
    92  
    93  	// Index is the index into the file containing the resource
    94  	// (i.e. if there are multiple resources in a single file)
    95  	Index int `yaml:"index,omitempty" json:"index,omitempty"`
    96  }
    97  
    98  // Field references a field in a resource
    99  type Field struct {
   100  	// Path is the field path. This field is required.
   101  	Path string `yaml:"path,omitempty" json:"path,omitempty"`
   102  
   103  	// CurrentValue is the current field value
   104  	CurrentValue interface{} `yaml:"currentValue,omitempty" json:"currentValue,omitempty"`
   105  
   106  	// ProposedValue is the proposed value of the field to fix an issue.
   107  	ProposedValue interface{} `yaml:"proposedValue,omitempty" json:"proposedValue,omitempty"`
   108  }
   109  
   110  type Results []*Result
   111  
   112  // Error enables Results to be returned as an error
   113  func (e Results) Error() string {
   114  	var msgs []string
   115  	for _, i := range e {
   116  		msgs = append(msgs, i.String())
   117  	}
   118  	return strings.Join(msgs, "\n\n")
   119  }
   120  
   121  // ExitCode provides the exit code based on the result's severity
   122  func (e Results) ExitCode() int {
   123  	for _, i := range e {
   124  		if i.Severity == Error {
   125  			return 1
   126  		}
   127  	}
   128  	return 0
   129  }
   130  
   131  // Sort performs an in place stable sort of Results
   132  func (e Results) Sort() {
   133  	sort.SliceStable(e, func(i, j int) bool {
   134  		if fileLess(e, i, j) != 0 {
   135  			return fileLess(e, i, j) < 0
   136  		}
   137  		if severityLess(e, i, j) != 0 {
   138  			return severityLess(e, i, j) < 0
   139  		}
   140  		return resultToString(*e[i]) < resultToString(*e[j])
   141  	})
   142  }
   143  
   144  func severityLess(items Results, i, j int) int {
   145  	severityToNumber := map[Severity]int{
   146  		Error:   0,
   147  		Warning: 1,
   148  		Info:    2,
   149  	}
   150  
   151  	severityLevelI, found := severityToNumber[items[i].Severity]
   152  	if !found {
   153  		severityLevelI = 3
   154  	}
   155  	severityLevelJ, found := severityToNumber[items[j].Severity]
   156  	if !found {
   157  		severityLevelJ = 3
   158  	}
   159  	return severityLevelI - severityLevelJ
   160  }
   161  
   162  func fileLess(items Results, i, j int) int {
   163  	var fileI, fileJ File
   164  	if items[i].File == nil {
   165  		fileI = File{}
   166  	} else {
   167  		fileI = *items[i].File
   168  	}
   169  	if items[j].File == nil {
   170  		fileJ = File{}
   171  	} else {
   172  		fileJ = *items[j].File
   173  	}
   174  	if fileI.Path != fileJ.Path {
   175  		if fileI.Path < fileJ.Path {
   176  			return -1
   177  		} else {
   178  			return 1
   179  		}
   180  	}
   181  	return fileI.Index - fileJ.Index
   182  }
   183  
   184  func resultToString(item Result) string {
   185  	return fmt.Sprintf("resource-ref:%s,field:%s,message:%s",
   186  		item.ResourceRef, item.Field, item.Message)
   187  }
   188  

View as plain text