mirror of https://github.com/knative/caching.git
Auto-update dependencies (#68)
Produced via: `dep ensure -update knative.dev/test-infra knative.dev/pkg` /assign @mattmoor
This commit is contained in:
parent
2c1a329543
commit
63dd62a744
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
23
vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/README.md
vendored
Normal file
23
vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/README.md
vendored
Normal file
|
@ -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.
|
40
vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/calculator.go
vendored
Normal file
40
vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/calculator.go
vendored
Normal file
|
@ -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
|
||||
}
|
51
vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/coveragedata.go
vendored
Normal file
51
vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/coveragedata.go
vendored
Normal file
|
@ -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"`
|
||||
}
|
81
vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/ignorefields.go
vendored
Normal file
81
vendor/knative.dev/pkg/test/webhook-apicoverage/coveragecalculator/ignorefields.go
vendored
Normal file
|
@ -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
|
||||
}
|
|
@ -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.
|
71
vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/arraykindnode.go
vendored
Normal file
71
vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/arraykindnode.go
vendored
Normal file
|
@ -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
|
||||
}
|
95
vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/basictypekindnode.go
vendored
Normal file
95
vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/basictypekindnode.go
vendored
Normal file
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
53
vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/otherkindnode.go
vendored
Normal file
53
vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/otherkindnode.go
vendored
Normal file
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
80
vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/resourceforest.go
vendored
Normal file
80
vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/resourceforest.go
vendored
Normal file
|
@ -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
|
||||
}
|
97
vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/resourcetree.go
vendored
Normal file
97
vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/resourcetree.go
vendored
Normal file
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
109
vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/structkindnode.go
vendored
Normal file
109
vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/structkindnode.go
vendored
Normal file
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
57
vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/timetypenode.go
vendored
Normal file
57
vendor/knative.dev/pkg/test/webhook-apicoverage/resourcetree/timetypenode.go
vendored
Normal file
|
@ -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
|
||||
}
|
|
@ -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.
|
|
@ -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
|
||||
}
|
|
@ -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: <PackageName>
|
||||
Type: <TypeName>
|
||||
{
|
||||
<FieldName> <Ignored>/<Coverage:TrueorFalse> [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: <Number of total fields>
|
||||
Covered Fields: <Number of fields covered>
|
||||
Ignored Fields: <Number of fields ignored>
|
||||
Coverage Percentage: <Percentage value of coverage>
|
||||
```
|
|
@ -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(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<style type="text/css">
|
||||
<!--
|
||||
|
||||
.tab { margin-left: 50px; }
|
||||
|
||||
.styleheader {color: white; size: A4}
|
||||
|
||||
.covered {color: green; size: A3}
|
||||
|
||||
.notcovered {color: red; size: A3}
|
||||
|
||||
.ignored {color: white; size: A4}
|
||||
|
||||
.values {color: yellow; size: A3}
|
||||
|
||||
table, th, td { border: 1px solid white; text-align: center}
|
||||
|
||||
.braces {color: white; size: A3}
|
||||
-->
|
||||
</style>
|
||||
<body style="background-color:rgb(0,0,0); font-family: Arial">
|
||||
`)
|
||||
|
||||
// HTMLFooter closes the tags for the HTML page.
|
||||
var HTMLFooter = fmt.Sprintf(`
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
|
||||
// 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(`<div class="styleheader">`))
|
||||
buffer.WriteString(fmt.Sprintf(`<br>Package: %s`, packageName))
|
||||
buffer.WriteString(fmt.Sprintf(`<br>Type: %s`, typeName))
|
||||
buffer.WriteString(fmt.Sprint(`<br></div>`))
|
||||
buffer.WriteString(fmt.Sprint(`<div class="braces"><br>{</div>`))
|
||||
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(`<div class="braces">}</div>`))
|
||||
buffer.WriteString(fmt.Sprint(`<br>`))
|
||||
|
||||
}
|
||||
|
||||
buffer.WriteString(HTMLFooter)
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func defaultHTMLTypeDisplay(field *coveragecalculator.FieldCoverage) string {
|
||||
var buffer strings.Builder
|
||||
if field.Ignored {
|
||||
buffer.WriteString(fmt.Sprintf(`<div class="ignored tab">%s</div>`, field.Field))
|
||||
} else if field.Coverage {
|
||||
buffer.WriteString(fmt.Sprintf(`<div class="covered tab">%s`, field.Field))
|
||||
if len(field.Values) > 0 && !strings.Contains(strings.ToLower(field.Field), "uid") {
|
||||
buffer.WriteString(fmt.Sprintf(`    <span class="values">Values: [%s]</span>`, strings.Join(field.GetValues(), ", ")))
|
||||
}
|
||||
buffer.WriteString(fmt.Sprint(`</div>`))
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf(`<div class="notcovered tab">%s</div>`, 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(`<br>`))
|
||||
buffer.WriteString(fmt.Sprint(`<div class="styleheader">Coverage Values</div>`))
|
||||
buffer.WriteString(fmt.Sprint(`<br>`))
|
||||
buffer.WriteString(fmt.Sprint(` <table style="width: 30%">`))
|
||||
buffer.WriteString(fmt.Sprintf(`<tr class="styleheader"><td>Total Fields</td><td>%d</td></tr>`, coverageValues.TotalFields))
|
||||
buffer.WriteString(fmt.Sprintf(`<tr class="styleheader"><td>Covered Fields</td><td>%d</td></tr>`, coverageValues.CoveredFields))
|
||||
buffer.WriteString(fmt.Sprintf(`<tr class="styleheader"><td>Ignored Fields</td><td>%d</td></tr>`, coverageValues.IgnoredFields))
|
||||
|
||||
percentCoverage := 0.0
|
||||
if coverageValues.TotalFields > 0 {
|
||||
percentCoverage = (float64(coverageValues.CoveredFields) / float64(coverageValues.TotalFields-coverageValues.IgnoredFields)) * 100
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf(`<tr class="styleheader"><td>Coverage Percentage</td><td>%f</td></tr>`, percentCoverage))
|
||||
buffer.WriteString(fmt.Sprint(`</table>`))
|
||||
return buffer.String()
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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.
|
208
vendor/knative.dev/pkg/test/webhook-apicoverage/webhook/apicoverage_recorder.go
vendored
Normal file
208
vendor/knative.dev/pkg/test/webhook-apicoverage/webhook/apicoverage_recorder.go
vendored
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue