From 63dd62a744d2a5295580c769e6d98f5141e6a579 Mon Sep 17 00:00:00 2001 From: mattmoor-sockpuppet Date: Wed, 7 Aug 2019 07:22:56 -0700 Subject: [PATCH] Auto-update dependencies (#68) Produced via: `dep ensure -update knative.dev/test-infra knative.dev/pkg` /assign @mattmoor --- Gopkg.lock | 6 +- vendor/knative.dev/pkg/Gopkg.lock | 12 +- vendor/knative.dev/pkg/test/request.go | 10 +- .../coveragecalculator/README.md | 23 + .../coveragecalculator/calculator.go | 40 ++ .../coveragecalculator/coveragedata.go | 51 +++ .../coveragecalculator/ignorefields.go | 81 ++++ .../resourcetree/README.md | 60 +++ .../resourcetree/arraykindnode.go | 71 ++++ .../resourcetree/basictypekindnode.go | 95 +++++ .../webhook-apicoverage/resourcetree/node.go | 69 +++ .../resourcetree/otherkindnode.go | 53 +++ .../resourcetree/ptrkindnode.go | 67 +++ .../resourcetree/resourceforest.go | 80 ++++ .../resourcetree/resourcetree.go | 97 +++++ .../webhook-apicoverage/resourcetree/rule.go | 49 +++ .../resourcetree/structkindnode.go | 109 +++++ .../resourcetree/test_util.go | 399 ++++++++++++++++++ .../resourcetree/timetypenode.go | 57 +++ .../test/webhook-apicoverage/tools/README.md | 21 + .../test/webhook-apicoverage/tools/tools.go | 190 +++++++++ .../test/webhook-apicoverage/view/README.md | 39 ++ .../webhook-apicoverage/view/html_display.go | 131 ++++++ .../pkg/test/webhook-apicoverage/view/rule.go | 27 ++ .../webhook-apicoverage/webhook/README.md | 45 ++ .../webhook/apicoverage_recorder.go | 208 +++++++++ .../webhook-apicoverage/webhook/webhook.go | 246 +++++++++++ 27 files changed, 2325 insertions(+), 11 deletions(-) create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/README.md create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/calculator.go create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/coveragedata.go create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/ignorefields.go create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/README.md create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/arraykindnode.go create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/basictypekindnode.go create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/node.go create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/otherkindnode.go create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/ptrkindnode.go create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/resourceforest.go create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/resourcetree.go create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/rule.go create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/structkindnode.go create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/test_util.go create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/timetypenode.go create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/tools/README.md create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/tools/tools.go create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/view/README.md create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/view/html_display.go create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/view/rule.go create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/webhook/README.md create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/webhook/apicoverage_recorder.go create mode 100644 vendor/knative.dev/pkg/test/webhook-apicoverage/webhook/webhook.go diff --git a/Gopkg.lock b/Gopkg.lock index 9054fe4d..89f098c1 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -927,7 +927,7 @@ [[projects]] branch = "master" - digest = "1:a54bc33aee102c303962336ef74981e9c49f4a4a26f411c5bd9f6b6b248fd3c3" + digest = "1:5e2eff8a4b0de901e3183489bcfa0a303ff249e74939b2259578ea23a093b5b8" name = "knative.dev/pkg" packages = [ "apis", @@ -946,7 +946,7 @@ "metrics/metricskey", ] pruneopts = "T" - revision = "8a10634b4fa4df6dc450bfd883e85a33e76f2471" + revision = "30a47c2c75046e401d6f4fd9e95b523f71107229" [[projects]] branch = "master" @@ -957,7 +957,7 @@ "tools/dep-collector", ] pruneopts = "UT" - revision = "d65201fadb82dbdc5713f9ba9a1313150c7aa9b5" + revision = "8356197d9783864a845a21dca3e898faa5d7d7c5" [solve-meta] analyzer-name = "dep" diff --git a/vendor/knative.dev/pkg/Gopkg.lock b/vendor/knative.dev/pkg/Gopkg.lock index bb0d672d..0193dd05 100644 --- a/vendor/knative.dev/pkg/Gopkg.lock +++ b/vendor/knative.dev/pkg/Gopkg.lock @@ -911,7 +911,7 @@ version = "kubernetes-1.12.9" [[projects]] - digest = "1:132ea9b0bdac4c461e2de359d44ece44531b3062c1d52ff88a7cc8857be25063" + digest = "1:476d1780b33b4c9a27e7f7e722f36115d1e1ccd0fca97be331181c84e0f9ae68" name = "k8s.io/client-go" packages = [ "discovery", @@ -1059,9 +1059,11 @@ "pkg/apis/clientauthentication/v1beta1", "pkg/version", "plugin/pkg/client/auth/exec", + "plugin/pkg/client/auth/gcp", "rest", "rest/watch", "testing", + "third_party/forked/golang/template", "tools/auth", "tools/cache", "tools/clientcmd", @@ -1079,6 +1081,7 @@ "util/flowcontrol", "util/homedir", "util/integer", + "util/jsonpath", "util/retry", "util/workqueue", ] @@ -1149,14 +1152,14 @@ [[projects]] branch = "master" - digest = "1:68050b4ce26531655bac0f6e109d04eb0a4550ead73e320119ed3c628ced4a0a" + digest = "1:39bb1014c454fbf9d8f4eab62a44fcf8d3ade774e2b42015e533e5d9f3fe23d9" name = "knative.dev/test-infra" packages = [ "scripts", "tools/dep-collector", ] pruneopts = "UT" - revision = "17f2331e80ad0d3e170ea2bae45c3922744f83af" + revision = "d65201fadb82dbdc5713f9ba9a1313150c7aa9b5" [solve-meta] analyzer-name = "dep" @@ -1196,12 +1199,14 @@ "go.uber.org/zap/zapcore", "go.uber.org/zap/zaptest", "golang.org/x/sync/errgroup", + "gopkg.in/yaml.v2", "k8s.io/api/admission/v1beta1", "k8s.io/api/admissionregistration/v1beta1", "k8s.io/api/apps/v1", "k8s.io/api/authentication/v1", "k8s.io/api/batch/v1", "k8s.io/api/core/v1", + "k8s.io/api/extensions/v1beta1", "k8s.io/api/rbac/v1", "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset", "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake", @@ -1242,6 +1247,7 @@ "k8s.io/client-go/kubernetes/fake", "k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1", "k8s.io/client-go/kubernetes/typed/core/v1", + "k8s.io/client-go/plugin/pkg/client/auth/gcp", "k8s.io/client-go/rest", "k8s.io/client-go/testing", "k8s.io/client-go/tools/cache", diff --git a/vendor/knative.dev/pkg/test/request.go b/vendor/knative.dev/pkg/test/request.go index 462c857e..39c6b580 100644 --- a/vendor/knative.dev/pkg/test/request.go +++ b/vendor/knative.dev/pkg/test/request.go @@ -150,12 +150,12 @@ func WaitForEndpointStateWithTimeout( defer logging.GetEmitableSpan(context.Background(), fmt.Sprintf("WaitForEndpointState/%s", desc)).End() // Try parsing the "theURL" with and without a scheme. - asURL, err := url.Parse(fmt.Sprintf("http://%s", theURL)) + asURL, err := url.Parse(theURL) if err != nil { - asURL, err = url.Parse(theURL) - if err != nil { - return nil, err - } + return nil, err + } + if asURL.Scheme == "" { + asURL.Scheme = "http" } req, err := http.NewRequest(http.MethodGet, asURL.String(), nil) diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/README.md b/vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/README.md new file mode 100644 index 00000000..39838d04 --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/README.md @@ -0,0 +1,23 @@ +# Coverage Calculator + +`coveragecalculator` package contains types and helper methods pertaining to +coverage calculation. + +[TypeCoverage](coveragedata.go) is a type to represent coverage data for a +particular API object type. This is the wire contract between the webhook server +running inside the K8s cluster and any client using the API-Coverage tool. All +API calls into the webhook-server would return response containing this object +to represent coverage data. + +[IgnoredFields](ignorefields.go) type provides ability for individual repos to +specify fields that they would like the API Coverage tool to ignore for coverage +calculation. Individual repos are expected to provide a .yaml file providing +fields that they would like to ignore and use helper method +`ReadFromFile(filePath)` to read and intialize this type. `FieldIgnored()` can +then be called by providing `packageName`, `typeName` and `FieldName` to check +if the field needs to be ignored. + +[CalculateCoverage](calculator.go) method provides a capability to calculate +coverage values. This method takes an array of [TypeCoverage](coveragedata.go) +and iterates over them to aggreage coverage values. The aggregate result is +encapsulated inside [CoverageValues](calculator.go) and returned. diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/calculator.go b/vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/calculator.go new file mode 100644 index 00000000..26d97dee --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/calculator.go @@ -0,0 +1,40 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package coveragecalculator + +// CoverageValues encapsulates all the coverage related values. +type CoverageValues struct { + TotalFields int + CoveredFields int + IgnoredFields int +} + +// CalculateTypeCoverage calculates aggregate coverage values based on provided []TypeCoverage +func CalculateTypeCoverage(typeCoverage []TypeCoverage) *CoverageValues { + cv := CoverageValues{} + for _, coverage := range typeCoverage { + for _, field := range coverage.Fields { + cv.TotalFields++ + if field.Ignored { + cv.IgnoredFields++ + } else if field.Coverage { + cv.CoveredFields++ + } + } + } + return &cv +} diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/coveragedata.go b/vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/coveragedata.go new file mode 100644 index 00000000..bba92002 --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/coveragedata.go @@ -0,0 +1,51 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package coveragecalculator + +import "k8s.io/apimachinery/pkg/util/sets" + +// FieldCoverage represents coverage data for a field. +type FieldCoverage struct { + Field string `json:"Field"` + Values sets.String `json:"Values"` + Coverage bool `json:"Covered"` + Ignored bool `json:"Ignored"` +} + +// Merge operation merges the field coverage data when multiple nodes represent the same type. (e.g. ConnectedNodes traversal) +func (f *FieldCoverage) Merge(coverage bool, values sets.String) { + if coverage { + f.Coverage = coverage + f.Values = f.Values.Union(values) + } +} + +// GetValues returns Values as slice +func (f *FieldCoverage) GetValues() []string { + values := []string{} + for key := range f.Values { + values = append(values, key) + } + return values +} + +// TypeCoverage encapsulates type information and field coverage. +type TypeCoverage struct { + Package string `json:"Package"` + Type string `json:"Type"` + Fields map[string]*FieldCoverage `json:"Fields"` +} diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/ignorefields.go b/vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/ignorefields.go new file mode 100644 index 00000000..a144b42a --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/ignorefields.go @@ -0,0 +1,81 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package coveragecalculator + +import ( + "fmt" + "io/ioutil" + "strings" + + "k8s.io/apimachinery/pkg/util/sets" + + yaml "gopkg.in/yaml.v2" +) + +// IgnoredFields encapsulates fields to be ignored in a package for API coverage calculation. +type IgnoredFields struct { + ignoredFields map[string]sets.String +} + +// This type is used for deserialization from the input .yaml file +type inputIgnoredFields struct { + Package string `yaml:"package"` + Type string `yaml:"type"` + Fields []string `yaml:"fields"` +} + +// ReadFromFile is a utility method that can be used by repos to read .yaml input file into +// IgnoredFields type. +func (ig *IgnoredFields) ReadFromFile(filePath string) error { + data, err := ioutil.ReadFile(filePath) + if err != nil { + return fmt.Errorf("Error reading file: %s Error : %v", filePath, err) + } + + var inputEntries []inputIgnoredFields + err = yaml.Unmarshal(data, &inputEntries) + if err != nil { + return fmt.Errorf("Error unmarshalling ignoredfields input yaml file: %s Content: %s Error: %v", filePath, string(data), err) + } + + ig.ignoredFields = make(map[string]sets.String) + + for _, entry := range inputEntries { + if _, ok := ig.ignoredFields[entry.Package]; !ok { + ig.ignoredFields[entry.Package] = sets.String{} + } + + for _, field := range entry.Fields { + ig.ignoredFields[entry.Package].Insert(entry.Type + "." + field) + } + } + return nil +} + +// FieldIgnored method given a package, type and field returns true if the field is marked ignored. +func (ig *IgnoredFields) FieldIgnored(packageName string, typeName string, fieldName string) bool { + if ig.ignoredFields != nil { + for key, value := range ig.ignoredFields { + if strings.HasSuffix(packageName, key) { + if value.Has(typeName + "." + fieldName) { + return true + } + } + } + } + return false +} diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/README.md b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/README.md new file mode 100644 index 00000000..660d57ff --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/README.md @@ -0,0 +1,60 @@ +# Resource Tree + +resourcetree package contains types and interfaces that define a resource +tree(n-ary tree based representation of an API resource). Each resource tree is +composed of nodes which have data encapsulated inside [nodeData](node.go) and +operations that can be performed on each node in the interface +[NodeInterface](node.go). Type of a node is logically defined by reflect.Kind. +Each node type is expected to satisfy the [NodeInterface](node.go) interface. + +## Resource Forest + +[ResourceForest](resourceforest.go) groups all resource trees that are part of +an API version into a single construct and defines operations that span across +them. As an example for Knative Serving, we will have individual resource trees +for Configuration, Revision, Route and Service, and they are encapsulated inside +a resource forest under version v1alpha1. Example of an operation that spans +resource trees would be to get coverage details for outlined types connected +using ConnectedNodes. + +ConnectedNodes represent connections between nodes that are of same +type(reflect.Type) and belong to same package but span across different trees or +branches of same tree. An example of ConnectedNodes would be +v1alpha1.Route.Spec.Traffic and v1alpha1.Route.Status.Traffic, both these +Traffic fields are of type v1alpha1.TrafficTarget, but are present in different +paths inside the resource tree. ConnectedNodes connects these two nodes, and an +outlining of this type would present the coverage across the two branches and +gives a unified view of what fields are covered. + +## Type Analysis + +A Resource tree is built using reflect.Type Each node type is expected to +implement NodeInterface method _buildChildNodes(t reflect.Type)_. Inside this +method each node creates child nodes based on its type, for e.g. StructKindNode +creates one child for each field defined in the struct. Type analysis are +defined inside [typeanalyzer_tests](buildChildNodes_test.go) + +## Value Analysis + +A Resource tree is updated using reflect.Value Each node type is expected to +implement NodeInterface method _updateCoverage(v reflect.Value)_. Inisde this +method each node updates its nodeData.covered field based on whether the +reflect.Value parameter being passed is set or not. + +## Rules + +To define traversal pattern on a [resourcetree](../resourcetree/resourcetree.go) +`coveragecalculator` package supports defining rules that are applied during +apicoverage walk-through. Currently the tool supports two types of Rule objects: + +1. `NodeRules`: Enforces node level semantics. e.g.: Avoid traversing any type + that belong to a particular package. To enforce this rule, the repo would + define an object that implements the [NodeRule](rule.go) interface's + `Apply(nodeInterface resourcetree.NodeInterface) bool` method and pass that + onto the `resourcetree` traversal routine. + +1. `FieldRules`: Enforces field level semantics. e.g. Avoid calculating coverage + for any field that starts with prefix `deprecated`. To enforce this rule, the + repo would define an object that implements [FieldRule](rule.go) interface's + `Apply(fieldName string) bool` method and pass that onto the `resourcetree` + traversal routine. diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/arraykindnode.go b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/arraykindnode.go new file mode 100644 index 00000000..ac21fcd4 --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/arraykindnode.go @@ -0,0 +1,71 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resourcetree + +import ( + "reflect" + + "k8s.io/apimachinery/pkg/util/sets" +) + +const ( + arrayNodeNameSuffix = "-arr" +) + +// ArrayKindNode represents resource tree node of types reflect.Kind.Array and reflect.Kind.Slice +type ArrayKindNode struct { + NodeData + // Array type e.g. []int will store reflect.Kind.Int. + // This is required for type-expansion and value-evaluation decisions. + arrKind reflect.Kind +} + +// GetData returns node data +func (a *ArrayKindNode) GetData() NodeData { + return a.NodeData +} + +func (a *ArrayKindNode) initialize(field string, parent NodeInterface, t reflect.Type, rt *ResourceTree) { + a.NodeData.initialize(field, parent, t, rt) + a.arrKind = t.Elem().Kind() +} + +func (a *ArrayKindNode) buildChildNodes(t reflect.Type) { + childName := a.Field + arrayNodeNameSuffix + childNode := a.Tree.createNode(childName, a, t.Elem()) + a.Children[childName] = childNode + childNode.buildChildNodes(t.Elem()) +} + +func (a *ArrayKindNode) updateCoverage(v reflect.Value) { + if !v.IsNil() { + a.Covered = true + for i := 0; i < v.Len(); i++ { + a.Children[a.Field+arrayNodeNameSuffix].updateCoverage(v.Index(i)) + } + } +} + +func (a *ArrayKindNode) buildCoverageData(coverageHelper coverageDataHelper) { + if a.arrKind == reflect.Struct { + a.Children[a.Field+arrayNodeNameSuffix].buildCoverageData(coverageHelper) + } +} + +func (a *ArrayKindNode) getValues() sets.String { + return nil +} diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/basictypekindnode.go b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/basictypekindnode.go new file mode 100644 index 00000000..eb6dde9b --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/basictypekindnode.go @@ -0,0 +1,95 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resourcetree + +import ( + "fmt" + "reflect" + "strconv" + + "k8s.io/apimachinery/pkg/util/sets" +) + +// BasicTypeKindNode represents resource tree node of basic types like int, float, etc. +type BasicTypeKindNode struct { + NodeData + values sets.String // Values seen for this node. Useful for enum types. + possibleEnum bool // Flag to indicate if this is a possible enum. +} + +// GetData returns node data +func (b *BasicTypeKindNode) GetData() NodeData { + return b.NodeData +} + +func (b *BasicTypeKindNode) initialize(field string, parent NodeInterface, t reflect.Type, rt *ResourceTree) { + b.NodeData.initialize(field, parent, t, rt) + b.values = sets.String{} + b.NodeData.LeafNode = true +} + +func (b *BasicTypeKindNode) buildChildNodes(t reflect.Type) { + // Treating bools as possible enums to support tighter coverage information. + if t.Name() != t.Kind().String() || b.FieldType.Kind() == reflect.Bool { + b.possibleEnum = true + } +} + +func (b *BasicTypeKindNode) updateCoverage(v reflect.Value) { + if value := b.string(v); len(value) > 0 { + if b.possibleEnum || b.FieldType.Kind() == reflect.Bool { + b.values.Insert(value) + } + b.Covered = true + } +} + +// no-op as the coverage is calculated as field coverage in parent node. +func (b *BasicTypeKindNode) buildCoverageData(coverageHelper coverageDataHelper) {} + +func (b *BasicTypeKindNode) string(v reflect.Value) string { + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if v.Int() != 0 { + return strconv.Itoa(int(v.Int())) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if v.Uint() != 0 { + return strconv.FormatUint(v.Uint(), 10) + } + case reflect.Float32, reflect.Float64: + if v.Float() != 0 { + return fmt.Sprintf("%f", v.Float()) + } + case reflect.String: + if v.Len() != 0 { + return v.String() + } + case reflect.Bool: + return strconv.FormatBool(v.Bool()) + } + + return "" +} + +func (b *BasicTypeKindNode) getValues() sets.String { + if b.possibleEnum { + return b.values + } + + return nil +} diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/node.go b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/node.go new file mode 100644 index 00000000..935a9fe3 --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/node.go @@ -0,0 +1,69 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// node.go contains types and interfaces pertaining to nodes inside resource tree. + +package resourcetree + +import ( + "reflect" + + "k8s.io/apimachinery/pkg/util/sets" +) + +// NodeInterface defines methods that can be performed on each node in the resource tree. +type NodeInterface interface { + GetData() NodeData + initialize(field string, parent NodeInterface, t reflect.Type, rt *ResourceTree) + buildChildNodes(t reflect.Type) + updateCoverage(v reflect.Value) + buildCoverageData(coverageDataHelper coverageDataHelper) + getValues() sets.String +} + +// NodeData is the data stored in each node of the resource tree. +type NodeData struct { + // Represents the Name of the field e.g. field name inside the struct. + Field string + // Reference back to the resource tree. Required for cross-tree traversal(connected nodes traversal) + Tree *ResourceTree + // Required as type information is not available during tree traversal. + FieldType reflect.Type + // Path in the resource tree reaching this node. + NodePath string + // Link back to parent. + Parent NodeInterface + // Child nodes are keyed using field names(nodeData.field). + Children map[string]NodeInterface + // Storing this as an additional field because type-analysis determines the value, + // which gets used later in value-evaluation + LeafNode bool + Covered bool +} + +func (nd *NodeData) initialize(field string, parent NodeInterface, t reflect.Type, rt *ResourceTree) { + nd.Field = field + nd.Tree = rt + nd.Parent = parent + nd.FieldType = t + nd.Children = make(map[string]NodeInterface) + + if parent != nil { + nd.NodePath = parent.GetData().NodePath + "." + field + } else { + nd.NodePath = field + } +} diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/otherkindnode.go b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/otherkindnode.go new file mode 100644 index 00000000..a44e5fd5 --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/otherkindnode.go @@ -0,0 +1,53 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resourcetree + +import ( + "reflect" + + "k8s.io/apimachinery/pkg/util/sets" +) + +// OtherKindNode represents nodes in the resource tree of types like maps, interfaces, etc +type OtherKindNode struct { + NodeData +} + +// GetData returns node data +func (o *OtherKindNode) GetData() NodeData { + return o.NodeData +} + +func (o *OtherKindNode) initialize(field string, parent NodeInterface, t reflect.Type, rt *ResourceTree) { + o.NodeData.initialize(field, parent, t, rt) + o.NodeData.LeafNode = true +} + +func (o *OtherKindNode) buildChildNodes(t reflect.Type) {} + +func (o *OtherKindNode) updateCoverage(v reflect.Value) { + if !v.IsNil() { + o.Covered = true + } +} + +// no-op as the coverage is calculated as field coverage in parent node. +func (o *OtherKindNode) buildCoverageData(coverageHelper coverageDataHelper) {} + +func (o *OtherKindNode) getValues() sets.String { + return nil +} diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/ptrkindnode.go b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/ptrkindnode.go new file mode 100644 index 00000000..4689bf04 --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/ptrkindnode.go @@ -0,0 +1,67 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resourcetree + +import ( + "reflect" + + "k8s.io/apimachinery/pkg/util/sets" +) + +const ( + ptrNodeNameSuffix = "-ptr" +) + +// PtrKindNode represents nodes in the resource tree of type reflect.Kind.Ptr, reflect.Kind.UnsafePointer, etc. +type PtrKindNode struct { + NodeData + objKind reflect.Kind // Type of the object being pointed to. Eg: *int will store reflect.Kind.Int. This is required for type-expansion and value-evaluation decisions. +} + +// GetData returns node data +func (p *PtrKindNode) GetData() NodeData { + return p.NodeData +} + +func (p *PtrKindNode) initialize(field string, parent NodeInterface, t reflect.Type, rt *ResourceTree) { + p.NodeData.initialize(field, parent, t, rt) + p.objKind = t.Elem().Kind() +} + +func (p *PtrKindNode) buildChildNodes(t reflect.Type) { + childName := p.Field + ptrNodeNameSuffix + childNode := p.Tree.createNode(childName, p, t.Elem()) + p.Children[childName] = childNode + childNode.buildChildNodes(t.Elem()) +} + +func (p *PtrKindNode) updateCoverage(v reflect.Value) { + if !v.IsNil() { + p.Covered = true + p.Children[p.Field+ptrNodeNameSuffix].updateCoverage(v.Elem()) + } +} + +func (p *PtrKindNode) buildCoverageData(coverageHelper coverageDataHelper) { + if p.objKind == reflect.Struct { + p.Children[p.Field+ptrNodeNameSuffix].buildCoverageData(coverageHelper) + } +} + +func (p *PtrKindNode) getValues() sets.String { + return nil +} diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/resourceforest.go b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/resourceforest.go new file mode 100644 index 00000000..9f0aba62 --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/resourceforest.go @@ -0,0 +1,80 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resourcetree + +import ( + "container/list" + "reflect" + + "k8s.io/apimachinery/pkg/util/sets" + "knative.dev/pkg/test/webhook-apicoverage/coveragecalculator" +) + +// ResourceForest represents the top-level forest that contains individual resource trees +// for top-level resource types and all connected nodes across resource trees. +type ResourceForest struct { + Version string + // Key is ResourceTree.ResourceName + TopLevelTrees map[string]ResourceTree + // Head of the linked list keyed by nodeData.fieldType.pkg + nodeData.fieldType.Name() + ConnectedNodes map[string]*list.List +} + +// AddResourceTree adds a resource tree to the resource forest. +func (r *ResourceForest) AddResourceTree(resourceName string, resourceType reflect.Type) { + tree := ResourceTree{ + ResourceName: resourceName, + Forest: r, + } + tree.BuildResourceTree(resourceType) + r.TopLevelTrees[resourceName] = tree +} + +// getConnectedNodeCoverage calculates the outlined coverage for a Type using ConnectedNodes linkedlist. +// We traverse through each element in the linkedlist and merge +// coverage data into a single coveragecalculator.TypeCoverage object. +func (r *ResourceForest) getConnectedNodeCoverage( + fieldType reflect.Type, + fieldRules FieldRules, + ignoredFields coveragecalculator.IgnoredFields) coveragecalculator.TypeCoverage { + packageName := fieldType.PkgPath() + coverage := coveragecalculator.TypeCoverage{ + Type: fieldType.Name(), + Package: packageName, + Fields: make(map[string]*coveragecalculator.FieldCoverage), + } + + if value, ok := r.ConnectedNodes[fieldType.PkgPath()+"."+fieldType.Name()]; ok { + for elem := value.Front(); elem != nil; elem = elem.Next() { + node := elem.Value.(NodeInterface) + for field, v := range node.GetData().Children { + if fieldRules.Apply(field) { + if _, ok := coverage.Fields[field]; !ok { + coverage.Fields[field] = &coveragecalculator.FieldCoverage{ + Field: field, + Ignored: ignoredFields.FieldIgnored(packageName, fieldType.Name(), field), + Values: sets.String{}, + } + } + // merge values across the list. + coverage.Fields[field].Merge(v.GetData().Covered, v.getValues()) + } + } + } + } + return coverage +} diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/resourcetree.go b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/resourcetree.go new file mode 100644 index 00000000..b4c5c992 --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/resourcetree.go @@ -0,0 +1,97 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resourcetree + +import ( + "container/list" + "reflect" + + "k8s.io/apimachinery/pkg/util/sets" + "knative.dev/pkg/test/webhook-apicoverage/coveragecalculator" +) + +// ResourceTree encapsulates a tree corresponding to a resource type. +type ResourceTree struct { + ResourceName string + Root NodeInterface + Forest *ResourceForest +} + +// coverageDataHelper is a encapsulator parameter type to the BuildCoverageData method +// so as to avoid long parameter list. +type coverageDataHelper struct { + typeCoverage *[]coveragecalculator.TypeCoverage + nodeRules NodeRules + fieldRules FieldRules + ignoredFields coveragecalculator.IgnoredFields + coveredTypes sets.String +} + +func (r *ResourceTree) createNode(field string, parent NodeInterface, t reflect.Type) NodeInterface { + var n NodeInterface + switch t.Kind() { + case reflect.Struct: + n = new(StructKindNode) + case reflect.Array, reflect.Slice: + n = new(ArrayKindNode) + case reflect.Ptr, reflect.UnsafePointer, reflect.Uintptr: + n = new(PtrKindNode) + case reflect.Bool, reflect.String, reflect.Float32, reflect.Float64, + reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, + reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + n = new(BasicTypeKindNode) + default: + n = new(OtherKindNode) // Maps, interfaces, etc + } + + n.initialize(field, parent, t, r) + + if len(t.PkgPath()) != 0 { + typeName := t.PkgPath() + "." + t.Name() + if _, ok := r.Forest.ConnectedNodes[typeName]; !ok { + r.Forest.ConnectedNodes[typeName] = list.New() + } + r.Forest.ConnectedNodes[typeName].PushBack(n) + } + + return n +} + +// BuildResourceTree builds a resource tree by calling into analyzeType method starting from root. +func (r *ResourceTree) BuildResourceTree(t reflect.Type) { + r.Root = r.createNode(r.ResourceName, nil, t) + r.Root.buildChildNodes(t) +} + +// UpdateCoverage updates coverage data in the resource tree based on the provided reflect.Value +func (r *ResourceTree) UpdateCoverage(v reflect.Value) { + r.Root.updateCoverage(v) +} + +// BuildCoverageData calculates the coverage information for a resource tree by applying provided Node and Field rules. +func (r *ResourceTree) BuildCoverageData(nodeRules NodeRules, fieldRules FieldRules, + ignoredFields coveragecalculator.IgnoredFields) []coveragecalculator.TypeCoverage { + coverageHelper := coverageDataHelper{ + nodeRules: nodeRules, + fieldRules: fieldRules, + typeCoverage: &[]coveragecalculator.TypeCoverage{}, + ignoredFields: ignoredFields, + coveredTypes: sets.String{}, + } + r.Root.buildCoverageData(coverageHelper) + return *coverageHelper.typeCoverage +} diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/rule.go b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/rule.go new file mode 100644 index 00000000..f8d0c640 --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/rule.go @@ -0,0 +1,49 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resourcetree + +// rule.go contains different rules that can be defined to control resource tree traversal. + +// NodeRules encapsulates all the node level rules defined by a repo. +type NodeRules struct { + Rules []func(nodeInterface NodeInterface) bool +} + +// Apply runs all the rules defined by a repo against a node. +func (n *NodeRules) Apply(node NodeInterface) bool { + for _, rule := range n.Rules { + if !rule(node) { + return false + } + } + return true +} + +// FieldRules encapsulates all the field level rules defined by a repo. +type FieldRules struct { + Rules []func(fieldName string) bool +} + +// Apply runs all the rules defined by a repo against a field. +func (f *FieldRules) Apply(fieldName string) bool { + for _, rule := range f.Rules { + if !rule(fieldName) { + return false + } + } + return true +} diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/structkindnode.go b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/structkindnode.go new file mode 100644 index 00000000..b96640fc --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/structkindnode.go @@ -0,0 +1,109 @@ +/* +Copyright 2018 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resourcetree + +import ( + "reflect" + + "k8s.io/apimachinery/pkg/util/sets" +) + +const ( + v1TimeType = "v1.Time" + volatileTimeType = "apis.VolatileTime" +) + +// StructKindNode represents nodes in the resource tree of type reflect.Kind.Struct +type StructKindNode struct { + NodeData +} + +// GetData returns node data +func (s *StructKindNode) GetData() NodeData { + return s.NodeData +} + +func (s *StructKindNode) initialize(field string, parent NodeInterface, t reflect.Type, rt *ResourceTree) { + s.NodeData.initialize(field, parent, t, rt) +} + +func (s *StructKindNode) buildChildNodes(t reflect.Type) { + // For types that are part of the standard package, we treat them as leaf nodes and don't expand further. + // https://golang.org/pkg/reflect/#StructField. + if len(s.FieldType.PkgPath()) == 0 { + s.LeafNode = true + return + } + + for i := 0; i < t.NumField(); i++ { + var childNode NodeInterface + if s.isTimeNode(t.Field(i).Type) { + childNode = new(TimeTypeNode) + childNode.initialize(t.Field(i).Name, s, t.Field(i).Type, s.Tree) + } else { + childNode = s.Tree.createNode(t.Field(i).Name, s, t.Field(i).Type) + } + s.Children[t.Field(i).Name] = childNode + childNode.buildChildNodes(t.Field(i).Type) + } +} + +func (s *StructKindNode) isTimeNode(t reflect.Type) bool { + if t.Kind() == reflect.Struct { + return t.String() == v1TimeType || t.String() == volatileTimeType + } else if t.Kind() == reflect.Ptr { + return t.Elem().String() == v1TimeType || t.String() == volatileTimeType + } else { + return false + } +} + +func (s *StructKindNode) updateCoverage(v reflect.Value) { + if v.IsValid() { + s.Covered = true + if !s.LeafNode { + for i := 0; i < v.NumField(); i++ { + s.Children[v.Type().Field(i).Name].updateCoverage(v.Field(i)) + } + } + } +} + +func (s *StructKindNode) buildCoverageData(coverageHelper coverageDataHelper) { + if len(s.Children) == 0 { + return + } + + coverage := s.Tree.Forest.getConnectedNodeCoverage(s.FieldType, coverageHelper.fieldRules, coverageHelper.ignoredFields) + *coverageHelper.typeCoverage = append(*coverageHelper.typeCoverage, coverage) + // Adding the type to covered fields so as to avoid revisiting the same node in other parts of the resource tree. + coverageHelper.coveredTypes.Insert(s.FieldType.PkgPath() + "." + s.FieldType.Name()) + + for field := range coverage.Fields { + node := s.Children[field] + if !coverage.Fields[field].Ignored && node.GetData().Covered && coverageHelper.nodeRules.Apply(node) { + // Check to see if the type has already been covered. + if !coverageHelper.coveredTypes.Has(node.GetData().FieldType.PkgPath() + "." + node.GetData().FieldType.Name()) { + node.buildCoverageData(coverageHelper) + } + } + } +} + +func (s *StructKindNode) getValues() sets.String { + return nil +} diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/test_util.go b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/test_util.go new file mode 100644 index 00000000..b44b05f0 --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/test_util.go @@ -0,0 +1,399 @@ +/* +Copyright 2018 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resourcetree + +//test_util contains types defined and used by types and their corresponding verification methods. + +import ( + "container/list" + "errors" + "fmt" + "reflect" +) + +const ( + basicTypeName = "BasicType" + ptrTypeName = "PtrType" + arrayTypeName = "ArrayType" + otherTypeName = "OtherType" + combinedTypeName = "CombinedType" +) + +type baseType struct { + field1 string + field2 int16 +} + +type ptrType struct { + structPtr *baseType + basePtr *float32 +} + +type arrayType struct { + structArr []baseType + baseArr []bool +} + +type otherType struct { + structMap map[string]baseType + baseMap map[string]string +} + +type combinedNodeType struct { + b baseType + a arrayType + p ptrType +} + +func getBaseTypeValue() baseType { + return baseType{ + field1: "test", + } +} + +func getPtrTypeValueAllCovered() ptrType { + f := new(float32) + *f = 3.142 + b := getBaseTypeValue() + return ptrType{ + basePtr: f, + structPtr: &b, + } +} + +func getPtrTypeValueSomeCovered() ptrType { + b := getBaseTypeValue() + return ptrType{ + structPtr: &b, + } +} + +func getArrValueAllCovered() arrayType { + b1 := getBaseTypeValue() + b2 := baseType{ + field2: 32, + } + + return arrayType{ + structArr: []baseType{b1, b2}, + baseArr: []bool{true, false}, + } +} + +func getArrValueSomeCovered() arrayType { + return arrayType{ + structArr: []baseType{getBaseTypeValue()}, + } +} + +func getOtherTypeValue() otherType { + m := make(map[string]baseType) + m["test"] = getBaseTypeValue() + return otherType{ + structMap: m, + } +} + +func getTestTree(treeName string, t reflect.Type) *ResourceTree { + forest := ResourceForest{ + Version: "TestVersion", + ConnectedNodes: make(map[string]*list.List), + TopLevelTrees: make(map[string]ResourceTree), + } + + tree := ResourceTree{ + ResourceName: treeName, + Forest: &forest, + } + + tree.BuildResourceTree(t) + forest.TopLevelTrees[treeName] = tree + return &tree +} + +func verifyBaseTypeNode(logPrefix string, data NodeData) error { + if len(data.Children) != 2 { + return fmt.Errorf("%s Expected 2 Children got only : %d", logPrefix, len(data.Children)) + } + + if value, ok := data.Children["field1"]; ok { + n := value.GetData() + if !n.LeafNode || n.FieldType.Kind() != reflect.String || n.FieldType.PkgPath() != "" || len(n.Children) != 0 { + return fmt.Errorf("%s Unexpected field: field1. Expected LeafNode:true, Kind: %s, pkgPath: '' Children: 0 Found LeafNode: %t Kind: %s pkgPath: %s Children:%d", + logPrefix, reflect.String, n.LeafNode, n.FieldType.Kind(), n.FieldType.PkgPath(), len(n.Children)) + } + } else { + return fmt.Errorf("%s field1 child Not found", logPrefix) + } + + return nil +} + +func verifyPtrNode(data NodeData) error { + if len(data.Children) != 2 { + return fmt.Errorf("Expected 2 Children got: %d", len(data.Children)) + } + + child := data.Children["structPtr"] + if len(child.GetData().Children) != 1 { + return fmt.Errorf("Unexpected size for field:structPtr. Expected : 1, Found : %d", len(child.GetData().Children)) + } + + child = child.GetData().Children["structPtr-ptr"] + if err := verifyBaseTypeNode("child structPtr-ptr: ", child.GetData()); err != nil { + return err + } + + child = data.Children["basePtr"] + if len(child.GetData().Children) != 1 { + return fmt.Errorf("Unexpected size for field:basePtr. Expected : 1 Found : %d", len(child.GetData().Children)) + } + + child = child.GetData().Children["basePtr-ptr"] + d := child.GetData() + if d.FieldType.Kind() != reflect.Float32 || !d.LeafNode || d.FieldType.PkgPath() != "" || len(d.Children) != 0 { + return fmt.Errorf("Unexpected field:basePtr-ptr: Expected: Kind: %s, LeafNode: true, pkgPath: '' Children: 0 Found Kind: %s, LeafNode: %t, pkgPath: %s Children:%d", + reflect.Float32, d.FieldType.Kind(), d.LeafNode, d.FieldType.PkgPath(), len(d.Children)) + } + + return nil +} + +func verifyArrayNode(data NodeData) error { + if len(data.Children) != 2 { + return fmt.Errorf("Expected 2 Children got: %d", len(data.Children)) + } + + child := data.Children["structArr"] + d := child.GetData() + if d.FieldType.Kind() != reflect.Slice { + return fmt.Errorf("Unexpected kind for field:structArr: Expected : %s Found: %s", reflect.Slice, d.FieldType.Kind()) + } else if len(d.Children) != 1 { + return fmt.Errorf("Unexpected number of Children for field:structArr: Expected : 1 Found : %d", len(d.Children)) + } + + child = child.GetData().Children["structArr-arr"] + if err := verifyBaseTypeNode("child structArr-arr:", child.GetData()); err != nil { + return err + } + + child = data.Children["baseArr"] + d = child.GetData() + if d.FieldType.Kind() != reflect.Slice { + return fmt.Errorf("Unexpected kind for field:baseArr: Expected : %s Found : %s", reflect.Slice, d.FieldType.Kind()) + } else if len(d.Children) != 1 { + return fmt.Errorf("Unexpected number of Children for field:baseArr: Expected : 1 Found : %d", len(d.Children)) + } + + child = child.GetData().Children["baseArr-arr"] + d = child.GetData() + if d.FieldType.Kind() != reflect.Bool || !d.LeafNode || d.FieldType.PkgPath() != "" || len(d.Children) != 0 { + return fmt.Errorf("Unexpected field:baseArr-arr Expected kind: %s, LeafNode: true, pkgPath: '', Children:0 Found: kind: %s, LeafNode: %t, pkgPath: %s, Children:%d", + reflect.Bool, d.FieldType.Kind(), d.LeafNode, d.FieldType.PkgPath(), len(d.Children)) + } + + return nil +} + +func verifyOtherTypeNode(data NodeData) error { + if len(data.Children) != 2 { + return fmt.Errorf("OtherTypeVerification: Expected 2 Children got: %d", len(data.Children)) + } + + child := data.Children["structMap"] + d := child.GetData() + if d.FieldType.Kind() != reflect.Map || !d.LeafNode || len(d.Children) != 0 { + return fmt.Errorf("Unexpected field:structMap - Expected Kind: %s, LeafNode: true, Children:0 Found Kind: %s, LeafNode: %t, Children: %d", + reflect.Map, d.FieldType.Kind(), d.LeafNode, len(d.Children)) + } + + child = data.Children["baseMap"] + d = child.GetData() + if d.FieldType.Kind() != reflect.Map || !d.LeafNode || len(d.Children) != 0 { + return fmt.Errorf("Unexpected field:structMap - Expected Kind: %s, LeafNode: true, Children: 0 Found kind: %s, LeafNode: %t, Children: %d", + reflect.Map, d.FieldType.Kind(), d.LeafNode, len(d.Children)) + } + + return nil +} + +func verifyResourceForest(forest *ResourceForest) error { + if len(forest.ConnectedNodes) != 4 { + return fmt.Errorf("Invalid number of connected nodes found. Expected : 4, Found : %d", len(forest.ConnectedNodes)) + } + + baseType := reflect.TypeOf(baseType{}) + if value, found := forest.ConnectedNodes[baseType.PkgPath()+"."+baseType.Name()]; !found { + return errors.New("Cannot find baseType{} connectedNode") + } else if value.Len() != 3 { + return fmt.Errorf("Invalid length of baseType{} Node. Expected : 3 Found : %d", value.Len()) + } + + arrayType := reflect.TypeOf(arrayType{}) + if value, found := forest.ConnectedNodes[arrayType.PkgPath()+"."+arrayType.Name()]; !found { + return errors.New("Cannot find arrayType{} connectedNode") + } else if value.Len() != 1 { + return fmt.Errorf("Invalid length of arrayType{} Node. Expected : 1 Found : %d", value.Len()) + } + + return nil +} + +func verifyBaseTypeValue(logPrefix string, node NodeInterface) error { + if !node.GetData().Covered { + return errors.New(logPrefix + " Node marked as not-Covered. Expected to be Covered") + } + + if !node.GetData().Children["field1"].GetData().Covered { + return errors.New(logPrefix + " field1 marked as not-Covered. Expected to be Covered") + } + + if node.GetData().Children["field2"].GetData().Covered { + return errors.New(logPrefix + " field2 marked as Covered. Expected to be not-Covered") + } + + return nil +} + +func verifyPtrValueAllCovered(node NodeInterface) error { + if !node.GetData().Covered { + return errors.New("Node marked as not-Covered. Expected to be Covered") + } + + child := node.GetData().Children["basePtr"] + if !child.GetData().Covered { + return errors.New("field:base_ptr marked as not-Covered. Expected to be Covered") + } + + if !child.GetData().Children["basePtr"+ptrNodeNameSuffix].GetData().Covered { + return errors.New("field:basePtr" + ptrNodeNameSuffix + "marked as not-Covered. Expected to be Covered") + } + + child = node.GetData().Children["structPtr"] + if !child.GetData().Covered { + return errors.New("field:structPtr marked as not-Covered. Expected to be Covered") + } + + if err := verifyBaseTypeValue("field:structPtr"+ptrNodeNameSuffix, child.GetData().Children["structPtr"+ptrNodeNameSuffix]); err != nil { + return err + } + + return nil +} + +func verifyPtrValueSomeCovered(node NodeInterface) error { + if !node.GetData().Covered { + return errors.New("Node marked as not-Covered. Expected to be Covered") + } + + child := node.GetData().Children["basePtr"] + if child.GetData().Covered { + return errors.New("field:basePtr marked as Covered. Expected to be not-Covered") + } + + if child.GetData().Children["basePtr"+ptrNodeNameSuffix].GetData().Covered { + return errors.New("field:basePtr" + ptrNodeNameSuffix + "marked as Covered. Expected to be not-Covered") + } + + child = node.GetData().Children["structPtr"] + if !child.GetData().Covered { + return errors.New("field:structPtr marked as not-Covered. Expected to be Covered") + } + + if err := verifyBaseTypeValue("field:structPtr"+ptrNodeNameSuffix, child.GetData().Children["structPtr"+ptrNodeNameSuffix]); err != nil { + return err + } + + return nil +} + +func verifyArryValueAllCovered(node NodeInterface) error { + if !node.GetData().Covered { + return errors.New("Node marked as not-Covered. Expected to be Covered") + } + + child := node.GetData().Children["baseArr"] + if !child.GetData().Covered { + return errors.New("field:baseArr marked as not-Covered. Expected to be Covered") + } + + if !child.GetData().Children["baseArr"+arrayNodeNameSuffix].GetData().Covered { + return errors.New("field:baseArr" + arrayNodeNameSuffix + " marked as not-Covered. Expected to be Covered") + } + + child = node.GetData().Children["structArr"] + if !child.GetData().Covered { + return errors.New("field:structArr marked as not-Covered. Expected to be Covered") + } + + child = child.GetData().Children["structArr"+arrayNodeNameSuffix] + if !child.GetData().Covered { + return errors.New("structArr" + arrayNodeNameSuffix + " marked as not-Covered. Expected to be Covered") + } + + if !child.GetData().Children["field1"].GetData().Covered { + return errors.New("structArr" + arrayNodeNameSuffix + ".field1 marked as not-Covered. Expected to be Covered") + } + + if !child.GetData().Children["field2"].GetData().Covered { + return errors.New("structArr" + arrayNodeNameSuffix + ".field1 marked as not-Covered. Expected to be Covered") + } + + return nil +} + +func verifyArrValueSomeCovered(node NodeInterface) error { + if !node.GetData().Covered { + return errors.New("Node marked as not-Covered. Expected to be Covered") + } + + child := node.GetData().Children["baseArr"] + if child.GetData().Covered { + return errors.New("field:baseArr marked as Covered. Expected to be not-Covered") + } + + if child.GetData().Children["baseArr"+arrayNodeNameSuffix].GetData().Covered { + return errors.New("field:baseArr" + arrayNodeNameSuffix + " marked as Covered. Expected to be not-Covered") + } + + child = node.GetData().Children["structArr"] + if !child.GetData().Covered { + return errors.New("field:structArr marked as not-Covered. Expected to be Covered") + } + + if err := verifyBaseTypeValue("field:structArr"+arrayNodeNameSuffix, child.GetData().Children["structArr"+arrayNodeNameSuffix]); err != nil { + return err + } + + return nil +} + +func verifyOtherTypeValue(node NodeInterface) error { + if !node.GetData().Covered { + return errors.New("Node marked as not-Covered. Expected to be Covered") + } + + if !node.GetData().Children["structMap"].GetData().Covered { + return errors.New("field:structMap marked as not-Covered. Expected to be Covered") + } + + if node.GetData().Children["baseMap"].GetData().Covered { + return errors.New("field:baseMap marked as Covered. Expected to be not-Covered") + } + + return nil +} diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/timetypenode.go b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/timetypenode.go new file mode 100644 index 00000000..237d12d3 --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/timetypenode.go @@ -0,0 +1,57 @@ +/* +Copyright 2018 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resourcetree + +import ( + "reflect" + + "k8s.io/apimachinery/pkg/util/sets" +) + +// TimeTypeNode is a node type that encapsulates fields that are internally time based. E.g metav1.ObjectMeta.CreationTimestamp or metav1.ObjectMeta.DeletionTimestamp. +// These are internally of type metav1.Time which use standard time type, but their values are specified as timestamp strings with parsing logic to create time objects. For +// use-case we only care if the value is set, so we create TimeTypeNodes and mark them as leafnodes. +type TimeTypeNode struct { + NodeData +} + +// GetData returns node data +func (ti *TimeTypeNode) GetData() NodeData { + return ti.NodeData +} + +func (ti *TimeTypeNode) initialize(field string, parent NodeInterface, t reflect.Type, rt *ResourceTree) { + ti.NodeData.initialize(field, parent, t, rt) + ti.LeafNode = true +} + +func (ti *TimeTypeNode) buildChildNodes(t reflect.Type) {} + +func (ti *TimeTypeNode) updateCoverage(v reflect.Value) { + if v.Type().Kind() == reflect.Struct && v.IsValid() { + ti.Covered = true + } else if v.Type().Kind() == reflect.Ptr && !v.IsNil() { + ti.Covered = true + } +} + +// no-op as the coverage is calculated as field coverage in parent node. +func (ti *TimeTypeNode) buildCoverageData(coverageHelper coverageDataHelper) {} + +func (ti *TimeTypeNode) getValues() sets.String { + return nil +} diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/tools/README.md b/vendor/knative.dev/pkg/test/webhook-apicoverage/tools/README.md new file mode 100644 index 00000000..3588e058 --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/tools/README.md @@ -0,0 +1,21 @@ +# Tools + +Tools package is intended to contain types and public helper methods that +provide utilities to solves common requirements across repos. It currently +contains following helper methods: + +1. `GetDefaultKubePath`: Helper method to retrieve the path for Kubeconfig + inside users home directory. +1. `GetWebhookServiceIP`: Helper method to retrieve the public IP address for + the webhook service. The service is setup as part of the apicoverage-webhook + setup. +1. `GetResourceCoverage`: Helper method to retrieve Coverage data for a resource + passed as parameter. The coverage data is retrieved from the API that is + exposed by the HTTP server in [Webhook Setup](../webhook/webhook.go) +1. `GetAndWriteResourceCoverage`: Helper method that uses `GetResourceCoverage` + to retrieve resource coverage and writes output to a file. +1. `GetTotalCoverage`: Helper method to retrieve total coverage data for a repo. + The coverage data is retrieved from the API that is exposed by the HTTP + server in [Webhook Setup](../webhook/webhook.go) +1. `GetAndWriteTotalCoverage`: Helper method that uses `GetTotalCoverage` to + retrieve total coverage and writes output to a file. diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/tools/tools.go b/vendor/knative.dev/pkg/test/webhook-apicoverage/tools/tools.go new file mode 100644 index 00000000..fec5c5a0 --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/tools/tools.go @@ -0,0 +1,190 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tools + +import ( + "crypto/tls" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "os/user" + "path" + "strings" + + "knative.dev/pkg/test/webhook-apicoverage/coveragecalculator" + "knative.dev/pkg/test/webhook-apicoverage/view" + "knative.dev/pkg/test/webhook-apicoverage/webhook" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + + // Mysteriously required to support GCP auth (required by k8s libs). + // Apparently just importing it is enough. @_@ side effects @_@. + // https://github.com/kubernetes/client-go/issues/242 + _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + "k8s.io/client-go/tools/clientcmd" +) + +// tools.go contains utility methods to help repos use the webhook-apicoverage tool. + +const ( + // WebhookResourceCoverageEndPoint constant for resource coverage API endpoint. + WebhookResourceCoverageEndPoint = "https://%s:443" + webhook.ResourceCoverageEndPoint + "?resource=%s" + + // WebhookTotalCoverageEndPoint constant for total coverage API endpoint. + WebhookTotalCoverageEndPoint = "https://%s:443" + webhook.TotalCoverageEndPoint +) + +// GetDefaultKubePath helper method to fetch kubeconfig path. +func GetDefaultKubePath() (string, error) { + var ( + usr *user.User + err error + ) + if usr, err = user.Current(); err != nil { + return "", fmt.Errorf("error retrieving current user: %v", err) + } + + return path.Join(usr.HomeDir, ".kube/config"), nil +} + +func getKubeClient(kubeConfigPath string, clusterName string) (*kubernetes.Clientset, error) { + overrides := clientcmd.ConfigOverrides{} + overrides.Context.Cluster = clusterName + clientCfg, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeConfigPath}, + &overrides).ClientConfig() + if err != nil { + return nil, fmt.Errorf("error building kube client config: %v", err) + } + + var kubeClient *kubernetes.Clientset + if kubeClient, err = kubernetes.NewForConfig(clientCfg); err != nil { + return nil, fmt.Errorf("error building KubeClient from config: %v", err) + } + + return kubeClient, nil +} + +// GetWebhookServiceIP is a helper method to fetch IP Address of the LoadBalancer webhook service. +func GetWebhookServiceIP(kubeConfigPath string, clusterName string, namespace string, serviceName string) (string, error) { + kubeClient, err := getKubeClient(kubeConfigPath, clusterName) + if err != nil { + return "", err + } + + svc, err := kubeClient.CoreV1().Services(namespace).Get(serviceName, v1.GetOptions{}) + if err != nil { + return "", fmt.Errorf("error encountered while retrieving service: %s Error: %v", serviceName, err) + } + + if len(svc.Status.LoadBalancer.Ingress) == 0 { + return "", fmt.Errorf("found zero Ingress instances for service: %s", serviceName) + } + + return svc.Status.LoadBalancer.Ingress[0].IP, nil +} + +// GetResourceCoverage is a helper method to get Coverage data for a resource from the service webhook. +func GetResourceCoverage(webhookIP string, resourceName string) (string, error) { + client := &http.Client{Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + } + resp, err := client.Get(fmt.Sprintf(WebhookResourceCoverageEndPoint, webhookIP, resourceName)) + if err != nil { + return "", fmt.Errorf("encountered error making resource coverage request: %v", err) + } else if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("invalid HTTP Status received for resource coverage request. Status: %d", resp.StatusCode) + } + + var body []byte + if body, err = ioutil.ReadAll(resp.Body); err != nil { + return "", fmt.Errorf("error reading resource coverage response: %v", err) + } + + return string(body), nil +} + +// GetAndWriteResourceCoverage is a helper method that uses GetResourceCoverage to get coverage and write it to a file. +func GetAndWriteResourceCoverage(webhookIP string, resourceName string, outputFile string, displayRules view.DisplayRules) error { + var ( + err error + resourceCoverage string + ) + + if resourceCoverage, err = GetResourceCoverage(webhookIP, resourceName); err != nil { + return err + } + + if err = ioutil.WriteFile(outputFile, []byte(resourceCoverage), 0400); err != nil { + return fmt.Errorf("error writing resource coverage to output file: %s, error: %v coverage: %s", outputFile, err, resourceCoverage) + } + + return nil +} + +// GetTotalCoverage calls the total coverage API to retrieve total coverage values. +func GetTotalCoverage(webhookIP string) (*coveragecalculator.CoverageValues, error) { + client := &http.Client{Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + } + + resp, err := client.Get(fmt.Sprintf(WebhookTotalCoverageEndPoint, webhookIP)) + if err != nil { + return nil, fmt.Errorf("encountered error making total coverage request: %v", err) + } else if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("invalid HTTP Status received for total coverage request. Status: %d", resp.StatusCode) + } + + var body []byte + if body, err = ioutil.ReadAll(resp.Body); err != nil { + return nil, fmt.Errorf("error reading total coverage response: %v", err) + } + + var coverage coveragecalculator.CoverageValues + if err = json.Unmarshal(body, &coverage); err != nil { + return nil, fmt.Errorf("error unmarshalling response to CoverageValues instance: %v", err) + } + + return &coverage, nil +} + +// GetAndWriteTotalCoverage uses the GetTotalCoverage method to get total coverage and write it to a output file. +func GetAndWriteTotalCoverage(webhookIP string, outputFile string) error { + var ( + totalCoverage *coveragecalculator.CoverageValues + err error + ) + + if totalCoverage, err = GetTotalCoverage(webhookIP); err != nil { + return err + } + + var buffer strings.Builder + buffer.WriteString(view.HTMLHeader) + totalCoverageDisplay := view.GetHTMLCoverageValuesDisplay(totalCoverage) + buffer.WriteString(totalCoverageDisplay) + buffer.WriteString(view.HTMLFooter) + if err = ioutil.WriteFile(outputFile, []byte(buffer.String()), 0400); err != nil { + return fmt.Errorf("error writing total coverage to output file: %s, error: %v coverage: %s", outputFile, err, totalCoverageDisplay) + } + + return nil +} diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/view/README.md b/vendor/knative.dev/pkg/test/webhook-apicoverage/view/README.md new file mode 100644 index 00000000..db7cb941 --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/view/README.md @@ -0,0 +1,39 @@ +# View + +This package contains types and helper methods that repos can use to display API +Coverage results. + +[DisplayRules](rule.go) provides a mechanism for repos to define their own +display rules. DisplayHelper methods can use these rules to define how to +display results. + +`GetHTMLDisplay()` is a utility method that can be used by repos to get a +HTML(JSON) like textual display of API Coverage. This method takes an array of +[TypeCoverage](../coveragecalculator/coveragedata.go) and +[DisplayRules](rule.go) object and returns a string representing its coverage in +the color coded format inside a HTML page: + +``` +Package: +Type: +{ + / [Values] + .... + .... + .... +} +``` + +`GetHTMLCoverageValuesDisplay()` is a utility method that can be used by repos +to produce coverage values display. The method takes as input +[CoverageValue](../coveragecalculator/calculator.go) and produces a display in +the format inside a HTML page: + +``` +CoverageValues: + +Total Fields: +Covered Fields: +Ignored Fields: +Coverage Percentage: +``` diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/view/html_display.go b/vendor/knative.dev/pkg/test/webhook-apicoverage/view/html_display.go new file mode 100644 index 00000000..c8330544 --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/view/html_display.go @@ -0,0 +1,131 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package view + +import ( + "fmt" + "strings" + + "knative.dev/pkg/test/webhook-apicoverage/coveragecalculator" +) + +// HTMLHeader sets up an HTML page with the right style format +var HTMLHeader = fmt.Sprintf(` + + + + +`) + +// HTMLFooter closes the tags for the HTML page. +var HTMLFooter = fmt.Sprintf(` + + +`) + +// GetHTMLDisplay is a helper method to display API Coverage details in json-like format inside a HTML page. +func GetHTMLDisplay(coverageData []coveragecalculator.TypeCoverage, displayRules DisplayRules) string { + var buffer strings.Builder + buffer.WriteString(HTMLHeader) + for _, typeCoverage := range coverageData { + packageName := typeCoverage.Package + if displayRules.PackageNameRule != nil { + packageName = displayRules.PackageNameRule(packageName) + } + + typeName := typeCoverage.Type + if displayRules.TypeNameRule != nil { + typeName = displayRules.TypeNameRule(typeName) + } + buffer.WriteString(fmt.Sprint(`
`)) + buffer.WriteString(fmt.Sprintf(`
Package: %s`, packageName)) + buffer.WriteString(fmt.Sprintf(`
Type: %s`, typeName)) + buffer.WriteString(fmt.Sprint(`
`)) + buffer.WriteString(fmt.Sprint(`

{
`)) + for _, fieldCoverage := range typeCoverage.Fields { + var fieldDisplay string + if displayRules.FieldRule != nil { + fieldDisplay = displayRules.FieldRule(fieldCoverage) + } else { + fieldDisplay = defaultHTMLTypeDisplay(fieldCoverage) + } + buffer.WriteString(fieldDisplay) + } + + buffer.WriteString(fmt.Sprint(`
}
`)) + buffer.WriteString(fmt.Sprint(`
`)) + + } + + buffer.WriteString(HTMLFooter) + return buffer.String() +} + +func defaultHTMLTypeDisplay(field *coveragecalculator.FieldCoverage) string { + var buffer strings.Builder + if field.Ignored { + buffer.WriteString(fmt.Sprintf(`
%s
`, field.Field)) + } else if field.Coverage { + buffer.WriteString(fmt.Sprintf(`
%s`, field.Field)) + if len(field.Values) > 0 && !strings.Contains(strings.ToLower(field.Field), "uid") { + buffer.WriteString(fmt.Sprintf(`    Values: [%s]`, strings.Join(field.GetValues(), ", "))) + } + buffer.WriteString(fmt.Sprint(`
`)) + } else { + buffer.WriteString(fmt.Sprintf(`
%s
`, field.Field)) + } + return buffer.String() +} + +// GetHTMLCoverageValuesDisplay is a helper method to display coverage values inside a HTML table. +func GetHTMLCoverageValuesDisplay(coverageValues *coveragecalculator.CoverageValues) string { + var buffer strings.Builder + buffer.WriteString(fmt.Sprint(`
`)) + buffer.WriteString(fmt.Sprint(`
Coverage Values
`)) + buffer.WriteString(fmt.Sprint(`
`)) + buffer.WriteString(fmt.Sprint(` `)) + buffer.WriteString(fmt.Sprintf(``, coverageValues.TotalFields)) + buffer.WriteString(fmt.Sprintf(``, coverageValues.CoveredFields)) + buffer.WriteString(fmt.Sprintf(``, coverageValues.IgnoredFields)) + + percentCoverage := 0.0 + if coverageValues.TotalFields > 0 { + percentCoverage = (float64(coverageValues.CoveredFields) / float64(coverageValues.TotalFields-coverageValues.IgnoredFields)) * 100 + } + buffer.WriteString(fmt.Sprintf(``, percentCoverage)) + buffer.WriteString(fmt.Sprint(`
Total Fields%d
Covered Fields%d
Ignored Fields%d
Coverage Percentage%f
`)) + return buffer.String() +} diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/view/rule.go b/vendor/knative.dev/pkg/test/webhook-apicoverage/view/rule.go new file mode 100644 index 00000000..defa9805 --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/view/rule.go @@ -0,0 +1,27 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package view + +import "knative.dev/pkg/test/webhook-apicoverage/coveragecalculator" + +// DisplayRules provides a mechanism for repos to define their own display rules. +// DisplayHelper methods can use these rules to define how to display results. +type DisplayRules struct { + PackageNameRule func(packageName string) string + TypeNameRule func(typeName string) string + FieldRule func(coverage *coveragecalculator.FieldCoverage) string +} diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/webhook/README.md b/vendor/knative.dev/pkg/test/webhook-apicoverage/webhook/README.md new file mode 100644 index 00000000..5888dbd5 --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/webhook/README.md @@ -0,0 +1,45 @@ +# Webhook + +Webhook based API-Coverage tool uses +[ValidatingAdmissionWebhook](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#validatingadmissionwebhook) +which is a web-server that the K8 API-Server calls into for every API-Object +update to verify if the object is valid before storing it into its datastore. +Each validation request has the json representation of the object being +created/modified, that the tool uses to capture coverage data. `webhook` package +inside this folder provides a mechanism for individual repos to setup +ValidatingAdmissionWebhook. + +[APICoverageWebhook](webhook.go) type inside the package encapsulates necessary +configuration details and helper methods required to setup the webhook. Each +repo is expected to call into `SetupWebhook()` providing following three +parameters: + +1. `http.Handler`: This is the http handler (that implements + `ServeHTTP( w http.ResponseWriter, r *http.Request)`) that the web server + created by APICoverageWebhook uses. +1. `rules`: This is an array of `RuleWithOperations` objects from the + `k8s.io/api/admissionregistration/v1beta1` package that the webhook uses for + validation on each API Object update. e.g: knative-serving while calling this + method would provide rules that will handle API Objects like `Service`, + `Configuration`, `Route` and `Revision`. +1. `namespace`: Namespace name where the webhook would be installed. +1. `stop` channel: Channel to terminate webhook's web server. + +`SetupWebhook()` method in its implementation creates a TLS based web server and +registers the webhook by creating a ValidatingWebhookConfiguration object inside +the K8 cluster. + +[APICoverageRecorder](apicoverage_recorder.go) type inside the package +encapsulates the apicoverage recording capabilities. Repo using this type is +expected to set: + +1. `ResourceForest`: Specifying the version and initializing the + [ResourceTrees](../resourcetree/resourcetree.go) +1. `ResourceMap`: Identifying the resources whose APICoverage needs to be + calculated. +1. `NodeRules`: [NodeRules](../resourcetree/rule.go) that are applicable for the + repo. +1. `FieldRules`: [FieldRules](../resourcetree/rule.go) that are applicable for + the repo. +1. `DisplayRules`: [DisplayRules](../view/rule.go) to be used by + `GetResourceCoverage` method. diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/webhook/apicoverage_recorder.go b/vendor/knative.dev/pkg/test/webhook-apicoverage/webhook/apicoverage_recorder.go new file mode 100644 index 00000000..7f4f516f --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/webhook/apicoverage_recorder.go @@ -0,0 +1,208 @@ +/* +Copyright 2018 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "os" + "reflect" + "strings" + + "go.uber.org/zap" + "k8s.io/api/admission/v1beta1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + "knative.dev/pkg/webhook" + "knative.dev/pkg/test/webhook-apicoverage/coveragecalculator" + "knative.dev/pkg/test/webhook-apicoverage/resourcetree" + "knative.dev/pkg/test/webhook-apicoverage/view" +) + +var ( + decoder = serializer.NewCodecFactory(runtime.NewScheme()).UniversalDeserializer() +) + +const ( + // ResourceQueryParam query param name to provide the resource. + ResourceQueryParam = "resource" + + // ResourceCoverageEndPoint is the endpoint for Resource Coverage API + ResourceCoverageEndPoint = "/resourcecoverage" + + // TotalCoverageEndPoint is the endpoint for Total Coverage API + TotalCoverageEndPoint = "/totalcoverage" + + // resourceChannelQueueSize size of the queue maintained for resource channel. + resourceChannelQueueSize = 10 +) + +type resourceChannelMsg struct { + resourceType schema.GroupVersionKind + rawResourceValue []byte +} + +// APICoverageRecorder type contains resource tree to record API coverage for resources. +type APICoverageRecorder struct { + Logger *zap.SugaredLogger + ResourceForest resourcetree.ResourceForest + ResourceMap map[schema.GroupVersionKind]webhook.GenericCRD + NodeRules resourcetree.NodeRules + FieldRules resourcetree.FieldRules + DisplayRules view.DisplayRules + resourceChannel chan resourceChannelMsg +} + +// Init initializes the resources trees for set resources. +func (a *APICoverageRecorder) Init() { + for resourceKind, resourceObj := range a.ResourceMap { + a.ResourceForest.AddResourceTree(resourceKind.Kind, reflect.ValueOf(resourceObj).Elem().Type()) + } + a.resourceChannel = make(chan resourceChannelMsg, resourceChannelQueueSize) + go a.updateResourceCoverageTree() +} + +// updateResourceCoverageTree updates the resource coverage tree. +func (a *APICoverageRecorder) updateResourceCoverageTree() { + for { + channelMsg := <-a.resourceChannel + if err := json.Unmarshal(channelMsg.rawResourceValue, a.ResourceMap[channelMsg.resourceType]); err != nil { + a.Logger.Errorf("Failed unmarshalling review.Request.Object.Raw for type: %s Error: %v", channelMsg.resourceType.Kind, err) + continue + } + resourceTree := a.ResourceForest.TopLevelTrees[channelMsg.resourceType.Kind] + resourceTree.UpdateCoverage(reflect.ValueOf(a.ResourceMap[channelMsg.resourceType]).Elem()) + a.Logger.Info("Successfully recorded coverage for resource ", channelMsg.resourceType.Kind) + } +} + +// RecordResourceCoverage updates the resource tree with the request. +func (a *APICoverageRecorder) RecordResourceCoverage(w http.ResponseWriter, r *http.Request) { + var ( + body []byte + err error + ) + + review := &v1beta1.AdmissionReview{} + if body, err = ioutil.ReadAll(r.Body); err != nil { + a.Logger.Errorf("Failed reading request body: %v", err) + a.appendAndWriteAdmissionResponse(review, false, "Admission Denied", w) + return + } + + if _, _, err := decoder.Decode(body, nil, review); err != nil { + a.Logger.Errorf("Unable to decode request: %v", err) + a.appendAndWriteAdmissionResponse(review, false, "Admission Denied", w) + return + } + + gvk := schema.GroupVersionKind{ + Group: review.Request.Kind.Group, + Version: review.Request.Kind.Version, + Kind: review.Request.Kind.Kind, + } + // We only care about resources the repo has setup. + if _, ok := a.ResourceMap[gvk]; !ok { + a.Logger.Info("By-passing resource coverage update for resource : %s", gvk.Kind) + a.appendAndWriteAdmissionResponse(review, true, "Welcome Aboard", w) + return + } + + a.resourceChannel <- resourceChannelMsg{ + resourceType: gvk, + rawResourceValue: review.Request.Object.Raw, + } + a.appendAndWriteAdmissionResponse(review, true, "Welcome Aboard", w) +} + +func (a *APICoverageRecorder) appendAndWriteAdmissionResponse(review *v1beta1.AdmissionReview, allowed bool, message string, w http.ResponseWriter) { + review.Response = &v1beta1.AdmissionResponse{ + Allowed: allowed, + Result: &v1.Status{ + Message: message, + }, + } + + responseInBytes, err := json.Marshal(review) + if err != nil { + a.Logger.Errorf("Failing mashalling review response: %v", err) + } + + if _, err := w.Write(responseInBytes); err != nil { + a.Logger.Errorf("%v", err) + } +} + +// GetResourceCoverage retrieves resource coverage data for the passed in resource via query param. +func (a *APICoverageRecorder) GetResourceCoverage(w http.ResponseWriter, r *http.Request) { + resource := r.URL.Query().Get(ResourceQueryParam) + if _, ok := a.ResourceForest.TopLevelTrees[resource]; !ok { + fmt.Fprintf(w, "Resource information not found for resource: %s", resource) + return + } + + var ignoredFields coveragecalculator.IgnoredFields + ignoredFieldsFilePath := os.Getenv("KO_DATA_PATH") + "/ignoredfields.yaml" + if err := ignoredFields.ReadFromFile(ignoredFieldsFilePath); err != nil { + fmt.Fprintf(w, "Error reading file: %s", ignoredFieldsFilePath) + } + + tree := a.ResourceForest.TopLevelTrees[resource] + typeCoverage := tree.BuildCoverageData(a.NodeRules, a.FieldRules, ignoredFields) + coverageValues := coveragecalculator.CalculateTypeCoverage(typeCoverage) + + var buffer strings.Builder + buffer.WriteString(view.GetHTMLDisplay(typeCoverage, a.DisplayRules)) + buffer.WriteString(view.GetHTMLCoverageValuesDisplay(coverageValues)) + fmt.Fprint(w, buffer.String()) +} + +// GetTotalCoverage goes over all the resources setup for the apicoverage tool and returns total coverage values. +func (a *APICoverageRecorder) GetTotalCoverage(w http.ResponseWriter, r *http.Request) { + var ( + ignoredFields coveragecalculator.IgnoredFields + err error + ) + + ignoredFieldsFilePath := os.Getenv("KO_DATA_PATH") + "/ignoredfields.yaml" + if err = ignoredFields.ReadFromFile(ignoredFieldsFilePath); err != nil { + fmt.Fprintf(w, "error reading file: %s error: %v", ignoredFieldsFilePath, err) + } + + totalCoverage := coveragecalculator.CoverageValues{} + for resource := range a.ResourceMap { + tree := a.ResourceForest.TopLevelTrees[resource.Kind] + typeCoverage := tree.BuildCoverageData(a.NodeRules, a.FieldRules, ignoredFields) + coverageValues := coveragecalculator.CalculateTypeCoverage(typeCoverage) + totalCoverage.TotalFields += coverageValues.TotalFields + totalCoverage.CoveredFields += coverageValues.CoveredFields + totalCoverage.IgnoredFields += coverageValues.IgnoredFields + } + + var body []byte + if body, err = json.Marshal(totalCoverage); err != nil { + fmt.Fprintf(w, "error marshalling total coverage response: %v", err) + } + + if _, err = w.Write(body); err != nil { + fmt.Fprintf(w, "error writing total coverage response: %v", err) + } +} diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/webhook/webhook.go b/vendor/knative.dev/pkg/test/webhook-apicoverage/webhook/webhook.go new file mode 100644 index 00000000..ea7aef8b --- /dev/null +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/webhook/webhook.go @@ -0,0 +1,246 @@ +/* +Copyright 2018 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "log" + "net/http" + "strings" + "time" + + "github.com/markbates/inflect" + "go.uber.org/zap" + admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "knative.dev/pkg/configmap" + "knative.dev/pkg/logging" + "knative.dev/pkg/webhook" +) + +var ( + // GroupVersionKind for deployment to be used to set the webhook's owner reference. + deploymentKind = extensionsv1beta1.SchemeGroupVersion.WithKind("Deployment") +) + +// APICoverageWebhook encapsulates necessary configuration details for the api-coverage webhook. +type APICoverageWebhook struct { + // WebhookName is the name of the validation webhook we create to intercept API calls. + WebhookName string + + // ServiceName is the name of K8 service under which the webhook runs. + ServiceName string + + // DeploymentName is the deployment name for the webhook. + DeploymentName string + + // Namespace is the namespace in which everything above lives. + Namespace string + + // Port where the webhook is served. + Port int + + // RegistrationDelay controls how long validation requests + // occurs after the webhook is started. This is used to avoid + // potential races where registration completes and k8s apiserver + // invokes the webhook before the HTTP server is started. + RegistrationDelay time.Duration + + // ClientAuthType declares the policy the webhook server will follow for TLS Client Authentication. + ClientAuth tls.ClientAuthType + + // CaCert is the CA Cert for the webhook server. + CaCert []byte + + // FailurePolicy policy governs the webhook validation decisions. + FailurePolicy admissionregistrationv1beta1.FailurePolicyType + + // Logger is the configured logger for the webhook. + Logger *zap.SugaredLogger + + // KubeClient is the K8 client to the target cluster. + KubeClient kubernetes.Interface +} + +func (acw *APICoverageWebhook) generateServerConfig() (*tls.Config, error) { + serverKey, serverCert, caCert, err := webhook.CreateCerts(context.Background(), acw.ServiceName, acw.Namespace) + if err != nil { + return nil, fmt.Errorf("Error creating webhook certificates: %v", err) + } + + cert, err := tls.X509KeyPair(serverCert, serverKey) + if err != nil { + return nil, fmt.Errorf("Error creating X509 Key pair for webhook server: %v", err) + } + + acw.CaCert = caCert + return &tls.Config{ + Certificates: []tls.Certificate{cert}, + ClientAuth: acw.ClientAuth, + }, nil +} + +func (acw *APICoverageWebhook) getWebhookServer(handler http.Handler) (*http.Server, error) { + tlsConfig, err := acw.generateServerConfig() + if err != nil { + // generateServerConfig() is expected to provided explanatory error message. + return nil, err + } + + return &http.Server{ + Handler: handler, + Addr: fmt.Sprintf(":%d", acw.Port), + TLSConfig: tlsConfig, + }, nil +} + +func (acw *APICoverageWebhook) registerWebhook(rules []admissionregistrationv1beta1.RuleWithOperations, namespace string) error { + webhook := &admissionregistrationv1beta1.ValidatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: acw.WebhookName, + Namespace: namespace, + }, + Webhooks: []admissionregistrationv1beta1.Webhook{{ + Name: acw.WebhookName, + Rules: rules, + ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{ + Service: &admissionregistrationv1beta1.ServiceReference{ + Namespace: namespace, + Name: acw.ServiceName, + }, + CABundle: acw.CaCert, + }, + FailurePolicy: &acw.FailurePolicy, + }, + }, + } + + deployment, err := acw.KubeClient.ExtensionsV1beta1().Deployments(namespace).Get(acw.DeploymentName, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("Error retrieving Deployment Extension object: %v", err) + } + deploymentRef := metav1.NewControllerRef(deployment, deploymentKind) + webhook.OwnerReferences = append(webhook.OwnerReferences, *deploymentRef) + + _, err = acw.KubeClient.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Create(webhook) + if err != nil { + return fmt.Errorf("Error creating ValidatingWebhookConfigurations object: %v", err) + } + + return nil +} + +func (acw *APICoverageWebhook) getValidationRules(resources map[schema.GroupVersionKind]webhook.GenericCRD) []admissionregistrationv1beta1.RuleWithOperations { + var rules []admissionregistrationv1beta1.RuleWithOperations + for gvk := range resources { + plural := strings.ToLower(inflect.Pluralize(gvk.Kind)) + + rules = append(rules, admissionregistrationv1beta1.RuleWithOperations{ + Operations: []admissionregistrationv1beta1.OperationType{ + admissionregistrationv1beta1.Create, + admissionregistrationv1beta1.Update, + }, + Rule: admissionregistrationv1beta1.Rule{ + APIGroups: []string{gvk.Group}, + APIVersions: []string{gvk.Version}, + Resources: []string{plural}, + }, + }) + } + return rules +} + +// SetupWebhook sets up the webhook with the provided http.handler, resourcegroup Map, namespace and stop channel. +func (acw *APICoverageWebhook) SetupWebhook(handler http.Handler, resources map[schema.GroupVersionKind]webhook.GenericCRD, namespace string, stop <-chan struct{}) error { + server, err := acw.getWebhookServer(handler) + rules := acw.getValidationRules(resources) + if err != nil { + return fmt.Errorf("Webhook server object creation failed: %v", err) + } + + select { + case <-time.After(acw.RegistrationDelay): + err = acw.registerWebhook(rules, namespace) + if err != nil { + return fmt.Errorf("Webhook registration failed: %v", err) + } + acw.Logger.Info("Successfully registered webhook") + case <-stop: + return nil + } + + serverBootstrapErrCh := make(chan struct{}) + go func() { + if err := server.ListenAndServeTLS("", ""); err != nil { + acw.Logger.Error("ListenAndServeTLS for admission webhook returned error", zap.Error(err)) + close(serverBootstrapErrCh) + return + } + acw.Logger.Info("Successfully started webhook server") + }() + + select { + case <-stop: + return server.Close() + case <-serverBootstrapErrCh: + return errors.New("webhook server bootstrap failed") + } +} + +// BuildWebhookConfiguration builds the APICoverageWebhook object using the provided names. +func BuildWebhookConfiguration(componentCommonName string, webhookName string, namespace string) *APICoverageWebhook { + cm, err := configmap.Load("/etc/config-logging") + if err != nil { + log.Fatalf("Error loading logging configuration: %v", err) + } + + config, err := logging.NewConfigFromMap(cm) + if err != nil { + log.Fatalf("Error parsing logging configuration: %v", err) + } + logger, _ := logging.NewLoggerFromConfig(config, "webhook") + + clusterConfig, err := rest.InClusterConfig() + if err != nil { + log.Fatalf("Failed to get in cluster config: %v", err) + } + + kubeClient, err := kubernetes.NewForConfig(clusterConfig) + if err != nil { + log.Fatalf("Failed to get client set: %v", err) + } + + return &APICoverageWebhook{ + Logger: logger, + KubeClient: kubeClient, + FailurePolicy: admissionregistrationv1beta1.Fail, + ClientAuth: tls.NoClientCert, + RegistrationDelay: time.Second * 2, + Port: 443, + Namespace: namespace, + DeploymentName: componentCommonName, + ServiceName: componentCommonName, + WebhookName: webhookName, + } +}