From 7c8a655eae03cea7be5df30ecefd791801117e7a Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 29 Jul 2020 11:24:38 -0700 Subject: [PATCH 1/2] pkg/apis: add scorecard v1alpha3 API --- go.mod | 1 + .../scorecard/v1alpha3/configuration_types.go | 41 ++++ pkg/apis/scorecard/v1alpha3/doc.go | 5 + pkg/apis/scorecard/v1alpha3/formatter.go | 90 +++++++++ pkg/apis/scorecard/v1alpha3/register.go | 14 ++ pkg/apis/scorecard/v1alpha3/test_types.go | 73 +++++++ .../v1alpha3/zz_generated.deepcopy.go | 185 ++++++++++++++++++ 7 files changed, 409 insertions(+) create mode 100644 pkg/apis/scorecard/v1alpha3/configuration_types.go create mode 100644 pkg/apis/scorecard/v1alpha3/doc.go create mode 100644 pkg/apis/scorecard/v1alpha3/formatter.go create mode 100644 pkg/apis/scorecard/v1alpha3/register.go create mode 100644 pkg/apis/scorecard/v1alpha3/test_types.go create mode 100644 pkg/apis/scorecard/v1alpha3/zz_generated.deepcopy.go diff --git a/go.mod b/go.mod index b9f195884..42caeaced 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/google/go-cmp v0.4.0 // indirect github.com/imdario/mergo v0.3.8 // indirect github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect + github.com/mattn/go-isatty v0.0.8 github.com/mikefarah/yq/v2 v2.4.1 github.com/onsi/ginkgo v1.12.0 // indirect github.com/onsi/gomega v1.9.0 // indirect diff --git a/pkg/apis/scorecard/v1alpha3/configuration_types.go b/pkg/apis/scorecard/v1alpha3/configuration_types.go new file mode 100644 index 000000000..0bda063e6 --- /dev/null +++ b/pkg/apis/scorecard/v1alpha3/configuration_types.go @@ -0,0 +1,41 @@ +package v1alpha3 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ConfigurationKind is the default scorecard componentconfig kind. +const ConfigurationKind = "Configuration" + +// Configuration represents the set of test configurations which scorecard would run. +type Configuration struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + + // Do not use metav1.ObjectMeta because this "object" should not be treated as an actual object. + Metadata struct { + // Name is a required field for kustomize-able manifests, and is not used on-cluster (nor is the config itself). + Name string `json:"name,omitempty" yaml:"name,omitempty"` + } `json:"metadata,omitempty" yaml:"metadata,omitempty"` + + // Stages is a set of test stages to run. Once a stage is finished, the next stage in the slice will be run. + Stages []StageConfiguration `json:"stages" yaml:"stages"` +} + +// StageConfiguration configures a set of tests to be run. +type StageConfiguration struct { + // Parallel, if true, will run each test in tests in parallel. + // The default is to wait until a test finishes to run the next. + Parallel bool `json:"parallel,omitempty" yaml:"parallel,omitempty"` + // Tests are a list of tests to run. + Tests []TestConfiguration `json:"tests" yaml:"tests"` +} + +// TestConfiguration configures a specific scorecard test, identified by entrypoint. +type TestConfiguration struct { + // Image is the name of the test image. + Image string `json:"image" yaml:"image"` + // Entrypoint is a list of commands and arguments passed to the test image. + Entrypoint []string `json:"entrypoint,omitempty" yaml:"entrypoint,omitempty"` + // Labels further describe the test and enable selection. + Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` +} diff --git a/pkg/apis/scorecard/v1alpha3/doc.go b/pkg/apis/scorecard/v1alpha3/doc.go new file mode 100644 index 000000000..b7bc93b90 --- /dev/null +++ b/pkg/apis/scorecard/v1alpha3/doc.go @@ -0,0 +1,5 @@ +// +k8s:deepcopy-gen=package,register +// +groupName=scorecard.operatorframework.io + +// Package v1alpha3 contains resources types for version v1alpha3 of the scorecard.operatorframework.com API group. +package v1alpha3 diff --git a/pkg/apis/scorecard/v1alpha3/formatter.go b/pkg/apis/scorecard/v1alpha3/formatter.go new file mode 100644 index 000000000..245c0a2f9 --- /dev/null +++ b/pkg/apis/scorecard/v1alpha3/formatter.go @@ -0,0 +1,90 @@ +package v1alpha3 + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/mattn/go-isatty" +) + +const ( + redColor = "31" + greenColor = "32" + yellowColor = "33" + noColor = "%s\n" +) + +func (s Test) MarshalText() string { + var sb strings.Builder + + failColor := "\033[1;" + redColor + "m%s\033[0m" + passColor := "\033[1;" + greenColor + "m%s\033[0m" + warnColor := "\033[1;" + yellowColor + "m%s\033[0m" + + // turn off colorization if not in a terminal + if !isatty.IsTerminal(os.Stdout.Fd()) && + !isatty.IsCygwinTerminal(os.Stdout.Fd()) { + passColor = noColor + failColor = noColor + warnColor = noColor + } + + sb.WriteString(fmt.Sprintf("%s\n", strings.Repeat("-", 80))) + sb.WriteString(fmt.Sprintf("Image: %s\n", s.Spec.Image)) + + if len(s.Spec.Entrypoint) > 0 { + sb.WriteString(fmt.Sprintf("Entrypoint: %s\n", s.Spec.Entrypoint)) + } + + if len(s.Spec.Labels) > 0 { + sb.WriteString("Labels:\n") + for labelKey, labelValue := range s.Spec.Labels { + sb.WriteString(fmt.Sprintf("\t%q:%q\n", labelKey, labelValue)) + } + } + if len(s.Status.Results) > 0 { + sb.WriteString("Results:\n") + for _, result := range s.Status.Results { + if len(result.Name) > 0 { + sb.WriteString(fmt.Sprintf("\tName: %s\n", result.Name)) + } + sb.WriteString("\tState: ") + if result.State == PassState { + sb.WriteString(fmt.Sprintf(passColor, PassState)) + } else if result.State == FailState { + sb.WriteString(fmt.Sprintf(failColor, FailState)) + } else if result.State == ErrorState { + sb.WriteString(fmt.Sprintf(failColor, ErrorState)) + } else { + sb.WriteString("unknown") + } + sb.WriteString("\n") + + if len(result.Suggestions) > 0 { + sb.WriteString(fmt.Sprintf(warnColor, "\tSuggestions:\n")) + for _, suggestion := range result.Suggestions { + sb.WriteString(fmt.Sprintf("\t\t%s\n", suggestion)) + } + } + + if len(result.Errors) > 0 { + sb.WriteString(fmt.Sprintf(failColor, "\tErrors:\n")) + for _, err := range result.Errors { + sb.WriteString(fmt.Sprintf("\t\t%s\n", err)) + } + } + + if result.Log != "" { + sb.WriteString("\tLog:\n") + scanner := bufio.NewScanner(strings.NewReader(result.Log)) + for scanner.Scan() { + sb.WriteString(fmt.Sprintf("\t\t%s\n", scanner.Text())) + } + } + sb.WriteString("\n") + } + } + return sb.String() +} diff --git a/pkg/apis/scorecard/v1alpha3/register.go b/pkg/apis/scorecard/v1alpha3/register.go new file mode 100644 index 000000000..4ee9f2068 --- /dev/null +++ b/pkg/apis/scorecard/v1alpha3/register.go @@ -0,0 +1,14 @@ +package v1alpha3 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // SchemeGroupVersion is group version used to register these objects + SchemeGroupVersion = schema.GroupVersion{Group: "scorecard.operatorframework.io", Version: "v1alpha3"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} +) diff --git a/pkg/apis/scorecard/v1alpha3/test_types.go b/pkg/apis/scorecard/v1alpha3/test_types.go new file mode 100644 index 000000000..2597afd24 --- /dev/null +++ b/pkg/apis/scorecard/v1alpha3/test_types.go @@ -0,0 +1,73 @@ +package v1alpha3 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// State is a type used to indicate the result state of a Test. +type State string + +const ( + // PassState occurs when a Test's ExpectedPoints == MaximumPoints. + PassState State = "pass" + // FailState occurs when a Test's ExpectedPoints == 0. + FailState State = "fail" + // ErrorState occurs when a Test encounters a fatal error and the reported points should not be considered. + ErrorState State = "error" +) + +// TestResult contains the results of an individual scorecard test +type TestResult struct { + // Name is the name of the test + Name string `json:"name,omitempty"` + // Log holds a log produced from the test (if applicable) + Log string `json:"log,omitempty"` + // State is the final state of the test + State State `json:"state"` + // Errors is a list of the errors that occurred during the test (this can include both fatal and non-fatal errors) + Errors []string `json:"errors,omitempty"` + // Suggestions is a list of suggestions for the user to improve their score (if applicable) + Suggestions []string `json:"suggestions,omitempty"` +} + +// TestStatus contains collection of testResults. +type TestStatus struct { + Results []TestResult `json:"results,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Test specifies a single test run. +type Test struct { + metav1.TypeMeta `json:",inline"` + Spec TestConfiguration `json:"spec,omitempty"` + Status TestStatus `json:"status,omitempty"` +} + +// TestList is a list of tests. +type TestList struct { + metav1.TypeMeta `json:",inline"` + Items []Test `json:"items"` +} + +func NewTest() Test { + return Test{ + TypeMeta: metav1.TypeMeta{ + APIVersion: SchemeGroupVersion.String(), + Kind: "Test", + }, + } +} + +func NewTestList() TestList { + return TestList{ + TypeMeta: metav1.TypeMeta{ + APIVersion: SchemeGroupVersion.String(), + Kind: "TestList", + }, + } +} + +func init() { + SchemeBuilder.Register(&Test{}) +} diff --git a/pkg/apis/scorecard/v1alpha3/zz_generated.deepcopy.go b/pkg/apis/scorecard/v1alpha3/zz_generated.deepcopy.go new file mode 100644 index 000000000..70074117f --- /dev/null +++ b/pkg/apis/scorecard/v1alpha3/zz_generated.deepcopy.go @@ -0,0 +1,185 @@ +// +build !ignore_autogenerated + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha3 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Configuration) DeepCopyInto(out *Configuration) { + *out = *in + out.TypeMeta = in.TypeMeta + out.Metadata = in.Metadata + if in.Stages != nil { + in, out := &in.Stages, &out.Stages + *out = make([]StageConfiguration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Configuration. +func (in *Configuration) DeepCopy() *Configuration { + if in == nil { + return nil + } + out := new(Configuration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StageConfiguration) DeepCopyInto(out *StageConfiguration) { + *out = *in + if in.Tests != nil { + in, out := &in.Tests, &out.Tests + *out = make([]TestConfiguration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StageConfiguration. +func (in *StageConfiguration) DeepCopy() *StageConfiguration { + if in == nil { + return nil + } + out := new(StageConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Test) DeepCopyInto(out *Test) { + *out = *in + out.TypeMeta = in.TypeMeta + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Test. +func (in *Test) DeepCopy() *Test { + if in == nil { + return nil + } + out := new(Test) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Test) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TestConfiguration) DeepCopyInto(out *TestConfiguration) { + *out = *in + if in.Entrypoint != nil { + in, out := &in.Entrypoint, &out.Entrypoint + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TestConfiguration. +func (in *TestConfiguration) DeepCopy() *TestConfiguration { + if in == nil { + return nil + } + out := new(TestConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TestList) DeepCopyInto(out *TestList) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Test, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TestList. +func (in *TestList) DeepCopy() *TestList { + if in == nil { + return nil + } + out := new(TestList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TestResult) DeepCopyInto(out *TestResult) { + *out = *in + if in.Errors != nil { + in, out := &in.Errors, &out.Errors + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Suggestions != nil { + in, out := &in.Suggestions, &out.Suggestions + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TestResult. +func (in *TestResult) DeepCopy() *TestResult { + if in == nil { + return nil + } + out := new(TestResult) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TestStatus) DeepCopyInto(out *TestStatus) { + *out = *in + if in.Results != nil { + in, out := &in.Results, &out.Results + *out = make([]TestResult, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TestStatus. +func (in *TestStatus) DeepCopy() *TestStatus { + if in == nil { + return nil + } + out := new(TestStatus) + in.DeepCopyInto(out) + return out +} From 7bc9d3c6b2da4c98fb3a1c707de1cc8ba1380b07 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 29 Jul 2020 13:51:54 -0700 Subject: [PATCH 2/2] remove scheme-related variables and scorecard output coloring --- go.mod | 1 - pkg/apis/scorecard/v1alpha3/formatter.go | 38 +++++------------------ pkg/apis/scorecard/v1alpha3/register.go | 8 ++--- pkg/apis/scorecard/v1alpha3/test_types.go | 8 ++--- 4 files changed, 11 insertions(+), 44 deletions(-) diff --git a/go.mod b/go.mod index 42caeaced..b9f195884 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/google/go-cmp v0.4.0 // indirect github.com/imdario/mergo v0.3.8 // indirect github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect - github.com/mattn/go-isatty v0.0.8 github.com/mikefarah/yq/v2 v2.4.1 github.com/onsi/ginkgo v1.12.0 // indirect github.com/onsi/gomega v1.9.0 // indirect diff --git a/pkg/apis/scorecard/v1alpha3/formatter.go b/pkg/apis/scorecard/v1alpha3/formatter.go index 245c0a2f9..ae7d3414f 100644 --- a/pkg/apis/scorecard/v1alpha3/formatter.go +++ b/pkg/apis/scorecard/v1alpha3/formatter.go @@ -3,34 +3,12 @@ package v1alpha3 import ( "bufio" "fmt" - "os" "strings" - - "github.com/mattn/go-isatty" -) - -const ( - redColor = "31" - greenColor = "32" - yellowColor = "33" - noColor = "%s\n" ) func (s Test) MarshalText() string { var sb strings.Builder - failColor := "\033[1;" + redColor + "m%s\033[0m" - passColor := "\033[1;" + greenColor + "m%s\033[0m" - warnColor := "\033[1;" + yellowColor + "m%s\033[0m" - - // turn off colorization if not in a terminal - if !isatty.IsTerminal(os.Stdout.Fd()) && - !isatty.IsCygwinTerminal(os.Stdout.Fd()) { - passColor = noColor - failColor = noColor - warnColor = noColor - } - sb.WriteString(fmt.Sprintf("%s\n", strings.Repeat("-", 80))) sb.WriteString(fmt.Sprintf("Image: %s\n", s.Spec.Image)) @@ -51,26 +29,24 @@ func (s Test) MarshalText() string { sb.WriteString(fmt.Sprintf("\tName: %s\n", result.Name)) } sb.WriteString("\tState: ") - if result.State == PassState { - sb.WriteString(fmt.Sprintf(passColor, PassState)) - } else if result.State == FailState { - sb.WriteString(fmt.Sprintf(failColor, FailState)) - } else if result.State == ErrorState { - sb.WriteString(fmt.Sprintf(failColor, ErrorState)) - } else { + switch result.State { + case PassState, FailState, ErrorState: + sb.WriteString(string(result.State)) + sb.WriteString("\n") + default: sb.WriteString("unknown") } sb.WriteString("\n") if len(result.Suggestions) > 0 { - sb.WriteString(fmt.Sprintf(warnColor, "\tSuggestions:\n")) + sb.WriteString("\tSuggestions:\n") for _, suggestion := range result.Suggestions { sb.WriteString(fmt.Sprintf("\t\t%s\n", suggestion)) } } if len(result.Errors) > 0 { - sb.WriteString(fmt.Sprintf(failColor, "\tErrors:\n")) + sb.WriteString("\tErrors:\n") for _, err := range result.Errors { sb.WriteString(fmt.Sprintf("\t\t%s\n", err)) } diff --git a/pkg/apis/scorecard/v1alpha3/register.go b/pkg/apis/scorecard/v1alpha3/register.go index 4ee9f2068..647b73cc4 100644 --- a/pkg/apis/scorecard/v1alpha3/register.go +++ b/pkg/apis/scorecard/v1alpha3/register.go @@ -2,13 +2,9 @@ package v1alpha3 import ( "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/scheme" ) var ( - // SchemeGroupVersion is group version used to register these objects - SchemeGroupVersion = schema.GroupVersion{Group: "scorecard.operatorframework.io", Version: "v1alpha3"} - - // SchemeBuilder is used to add go types to the GroupVersionKind scheme - SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} + // GroupVersion is the group and version of this package. Used for parsing purposes only. + GroupVersion = schema.GroupVersion{Group: "scorecard.operatorframework.io", Version: "v1alpha3"} ) diff --git a/pkg/apis/scorecard/v1alpha3/test_types.go b/pkg/apis/scorecard/v1alpha3/test_types.go index 2597afd24..cba7d3a58 100644 --- a/pkg/apis/scorecard/v1alpha3/test_types.go +++ b/pkg/apis/scorecard/v1alpha3/test_types.go @@ -53,7 +53,7 @@ type TestList struct { func NewTest() Test { return Test{ TypeMeta: metav1.TypeMeta{ - APIVersion: SchemeGroupVersion.String(), + APIVersion: GroupVersion.String(), Kind: "Test", }, } @@ -62,12 +62,8 @@ func NewTest() Test { func NewTestList() TestList { return TestList{ TypeMeta: metav1.TypeMeta{ - APIVersion: SchemeGroupVersion.String(), + APIVersion: GroupVersion.String(), Kind: "TestList", }, } } - -func init() { - SchemeBuilder.Register(&Test{}) -}