From d4db9703c5bf79c4b8934bb8a614dcfa384a9e84 Mon Sep 17 00:00:00 2001 From: Mohamed Mahmoud Date: Mon, 2 Oct 2023 15:42:04 -0400 Subject: [PATCH] update e2e to use new cluster create/destory apis Signed-off-by: Mohamed Mahmoud --- e2e/basic/flow_test.go | 1 + e2e/cluster/kind.go | 12 +- e2e/kafka/kafka_test.go | 3 +- e2e/kafka/manifests/11-kafka-cluster.yml | 2 - go.mod | 2 +- go.sum | 4 +- vendor/k8s.io/utils/pointer/OWNERS | 10 + vendor/k8s.io/utils/pointer/README.md | 3 + vendor/k8s.io/utils/pointer/pointer.go | 410 ++++++++++++++++++ vendor/modules.txt | 10 +- .../controllerutil/controllerutil.go | 394 +++++++++++++++++ .../pkg/controller/controllerutil/doc.go | 20 + .../e2e-framework/klient/client.go | 14 + .../e2e-framework/klient/conf/config.go | 31 +- .../e2e-framework/klient/decoder/decoder.go | 355 +++++++++++++++ .../klient/k8s/resources/resources.go | 175 +++++++- .../e2e-framework/klient/k8s/watcher/watch.go | 132 ++++++ .../klient/wait/conditions/conditions.go | 90 ++-- .../e2e-framework/klient/wait/wait.go | 30 +- .../e2e-framework/pkg/env/action.go | 36 +- .../sigs.k8s.io/e2e-framework/pkg/env/env.go | 311 ++++++++----- .../e2e-framework/pkg/envconf/config.go | 93 +++- .../e2e-framework/pkg/envfuncs/kind_funcs.go | 186 +------- .../pkg/envfuncs/provider_funcs.go | 189 ++++++++ .../pkg/envfuncs/resource_funcs.go | 51 +++ .../e2e-framework/pkg/features/builder.go | 38 +- .../e2e-framework/pkg/features/feature.go | 37 +- .../e2e-framework/pkg/features/table.go | 19 +- .../e2e-framework/pkg/flags/flags.go | 174 ++++++-- .../e2e-framework/pkg/internal/types/types.go | 23 +- .../e2e-framework/support/kind/kind.go | 249 +++++++---- .../e2e-framework/support/types.go | 105 +++++ .../e2e-framework/support/utils/command.go | 84 ++++ 33 files changed, 2775 insertions(+), 518 deletions(-) create mode 100644 vendor/k8s.io/utils/pointer/OWNERS create mode 100644 vendor/k8s.io/utils/pointer/README.md create mode 100644 vendor/k8s.io/utils/pointer/pointer.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil/controllerutil.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil/doc.go create mode 100644 vendor/sigs.k8s.io/e2e-framework/klient/decoder/decoder.go create mode 100644 vendor/sigs.k8s.io/e2e-framework/klient/k8s/watcher/watch.go create mode 100644 vendor/sigs.k8s.io/e2e-framework/pkg/envfuncs/provider_funcs.go create mode 100644 vendor/sigs.k8s.io/e2e-framework/pkg/envfuncs/resource_funcs.go create mode 100644 vendor/sigs.k8s.io/e2e-framework/support/types.go create mode 100644 vendor/sigs.k8s.io/e2e-framework/support/utils/command.go diff --git a/e2e/basic/flow_test.go b/e2e/basic/flow_test.go index 05559466e..dd93bbde6 100644 --- a/e2e/basic/flow_test.go +++ b/e2e/basic/flow_test.go @@ -14,6 +14,7 @@ import ( "github.com/mariomac/guara/pkg/test" "github.com/netobserv/netobserv-ebpf-agent/e2e/cluster" "github.com/netobserv/netobserv-ebpf-agent/e2e/cluster/tester" + "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/e2e/cluster/kind.go b/e2e/cluster/kind.go index 3b4816838..5f5be7249 100644 --- a/e2e/cluster/kind.go +++ b/e2e/cluster/kind.go @@ -20,6 +20,7 @@ import ( rt2 "runtime" "github.com/netobserv/netobserv-ebpf-agent/e2e/cluster/tester" + "github.com/sirupsen/logrus" "github.com/vladimirvivien/gexe" "k8s.io/apimachinery/pkg/api/meta" @@ -34,6 +35,7 @@ import ( "sigs.k8s.io/e2e-framework/pkg/env" "sigs.k8s.io/e2e-framework/pkg/envconf" "sigs.k8s.io/e2e-framework/pkg/envfuncs" + "sigs.k8s.io/e2e-framework/support/kind" ) // DeployOrder specifies the order in which a Deployment must be executed, from lower to higher @@ -170,9 +172,11 @@ func NewKind(kindClusterName, baseDir string, options ...Option) *Kind { // Run the Kind cluster for the later execution of tests. func (k *Kind) Run(m *testing.M) { envFuncs := []env.Func{ - envfuncs.CreateKindClusterWithConfig(k.clusterName, - kindImage, - path.Join(packageDir(), "base", "00-kind.yml")), + withTimeout(envfuncs.CreateClusterWithConfig( + kind.NewProvider(), + k.clusterName, + path.Join(packageDir(), "base", "00-kind.yml"), + kind.WithImage(kindImage)), k.timeout), k.loadLocalImage(), } // Deploy base cluster dependencies and wait for readiness (if needed) @@ -195,7 +199,7 @@ func (k *Kind) Run(m *testing.M) { code := k.testEnv.Setup(envFuncs...). Finish( k.exportLogs(), - envfuncs.DestroyKindCluster(k.clusterName), + withTimeout(envfuncs.DestroyCluster(k.clusterName), k.timeout), ).Run(m) log.WithField("returnCode", code).Info("tests finished run") } diff --git a/e2e/kafka/kafka_test.go b/e2e/kafka/kafka_test.go index ae9194728..6e0c5faf7 100644 --- a/e2e/kafka/kafka_test.go +++ b/e2e/kafka/kafka_test.go @@ -10,6 +10,7 @@ import ( "github.com/netobserv/netobserv-ebpf-agent/e2e/basic" "github.com/netobserv/netobserv-ebpf-agent/e2e/cluster" + "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -61,7 +62,7 @@ func TestMain(m *testing.M) { ResourceMatch(&kfk, func(object k8s.Object) bool { return object.(*Kafka).Status.Ready() }), - wait.WithTimeout(testTimeout), + wait.WithInterval(5*time.Second), wait.WithTimeout(testTimeout), ); err != nil { return fmt.Errorf("waiting for kafka cluster to be ready: %w", err) } diff --git a/e2e/kafka/manifests/11-kafka-cluster.yml b/e2e/kafka/manifests/11-kafka-cluster.yml index 2654748e2..aa54d7fea 100644 --- a/e2e/kafka/manifests/11-kafka-cluster.yml +++ b/e2e/kafka/manifests/11-kafka-cluster.yml @@ -4,7 +4,6 @@ metadata: name: kafka-cluster spec: kafka: - version: 3.1.0 replicas: 1 listeners: - name: plain @@ -25,7 +24,6 @@ spec: transaction.state.log.min.isr: 1 default.replication.factor: 1 min.insync.replicas: 1 - inter.broker.protocol.version: "3.1" storage: type: ephemeral resources: diff --git a/go.mod b/go.mod index 00c0e6d31..bc2ce77b7 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( k8s.io/api v0.28.4 k8s.io/apimachinery v0.28.4 k8s.io/client-go v0.28.4 - sigs.k8s.io/e2e-framework v0.0.6 + sigs.k8s.io/e2e-framework v0.3.0 ) require ( diff --git a/go.sum b/go.sum index 4fe3c640f..523e4db2a 100644 --- a/go.sum +++ b/go.sum @@ -294,8 +294,8 @@ k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrC k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/controller-runtime v0.15.1 h1:9UvgKD4ZJGcj24vefUFgZFP3xej/3igL9BsOUTb/+4c= sigs.k8s.io/controller-runtime v0.15.1/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= -sigs.k8s.io/e2e-framework v0.0.6 h1:mGalOzsc25nz4GBOD6oVWBFKFcAanMso6oj3+4wiCFw= -sigs.k8s.io/e2e-framework v0.0.6/go.mod h1:XSknNb1ovbtOyNNjV8DKuY9Nr4rta4wwtnZq3IRGMl0= +sigs.k8s.io/e2e-framework v0.3.0 h1:eqQALBtPCth8+ulTs6lcPK7ytV5rZSSHJzQHZph4O7U= +sigs.k8s.io/e2e-framework v0.3.0/go.mod h1:C+ef37/D90Dc7Xq1jQnNbJYscrUGpxrWog9bx2KIa+c= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= diff --git a/vendor/k8s.io/utils/pointer/OWNERS b/vendor/k8s.io/utils/pointer/OWNERS new file mode 100644 index 000000000..0d6392752 --- /dev/null +++ b/vendor/k8s.io/utils/pointer/OWNERS @@ -0,0 +1,10 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: +- apelisse +- stewart-yu +- thockin +reviewers: +- apelisse +- stewart-yu +- thockin diff --git a/vendor/k8s.io/utils/pointer/README.md b/vendor/k8s.io/utils/pointer/README.md new file mode 100644 index 000000000..2ca8073dc --- /dev/null +++ b/vendor/k8s.io/utils/pointer/README.md @@ -0,0 +1,3 @@ +# Pointer + +This package provides some functions for pointer-based operations. diff --git a/vendor/k8s.io/utils/pointer/pointer.go b/vendor/k8s.io/utils/pointer/pointer.go new file mode 100644 index 000000000..b8103223a --- /dev/null +++ b/vendor/k8s.io/utils/pointer/pointer.go @@ -0,0 +1,410 @@ +/* +Copyright 2018 The Kubernetes 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 pointer + +import ( + "fmt" + "reflect" + "time" +) + +// AllPtrFieldsNil tests whether all pointer fields in a struct are nil. This is useful when, +// for example, an API struct is handled by plugins which need to distinguish +// "no plugin accepted this spec" from "this spec is empty". +// +// This function is only valid for structs and pointers to structs. Any other +// type will cause a panic. Passing a typed nil pointer will return true. +func AllPtrFieldsNil(obj interface{}) bool { + v := reflect.ValueOf(obj) + if !v.IsValid() { + panic(fmt.Sprintf("reflect.ValueOf() produced a non-valid Value for %#v", obj)) + } + if v.Kind() == reflect.Ptr { + if v.IsNil() { + return true + } + v = v.Elem() + } + for i := 0; i < v.NumField(); i++ { + if v.Field(i).Kind() == reflect.Ptr && !v.Field(i).IsNil() { + return false + } + } + return true +} + +// Int returns a pointer to an int +func Int(i int) *int { + return &i +} + +// IntPtr is a function variable referring to Int. +// +// Deprecated: Use Int instead. +var IntPtr = Int // for back-compat + +// IntDeref dereferences the int ptr and returns it if not nil, or else +// returns def. +func IntDeref(ptr *int, def int) int { + if ptr != nil { + return *ptr + } + return def +} + +// IntPtrDerefOr is a function variable referring to IntDeref. +// +// Deprecated: Use IntDeref instead. +var IntPtrDerefOr = IntDeref // for back-compat + +// Int32 returns a pointer to an int32. +func Int32(i int32) *int32 { + return &i +} + +// Int32Ptr is a function variable referring to Int32. +// +// Deprecated: Use Int32 instead. +var Int32Ptr = Int32 // for back-compat + +// Int32Deref dereferences the int32 ptr and returns it if not nil, or else +// returns def. +func Int32Deref(ptr *int32, def int32) int32 { + if ptr != nil { + return *ptr + } + return def +} + +// Int32PtrDerefOr is a function variable referring to Int32Deref. +// +// Deprecated: Use Int32Deref instead. +var Int32PtrDerefOr = Int32Deref // for back-compat + +// Int32Equal returns true if both arguments are nil or both arguments +// dereference to the same value. +func Int32Equal(a, b *int32) bool { + if (a == nil) != (b == nil) { + return false + } + if a == nil { + return true + } + return *a == *b +} + +// Uint returns a pointer to an uint +func Uint(i uint) *uint { + return &i +} + +// UintPtr is a function variable referring to Uint. +// +// Deprecated: Use Uint instead. +var UintPtr = Uint // for back-compat + +// UintDeref dereferences the uint ptr and returns it if not nil, or else +// returns def. +func UintDeref(ptr *uint, def uint) uint { + if ptr != nil { + return *ptr + } + return def +} + +// UintPtrDerefOr is a function variable referring to UintDeref. +// +// Deprecated: Use UintDeref instead. +var UintPtrDerefOr = UintDeref // for back-compat + +// Uint32 returns a pointer to an uint32. +func Uint32(i uint32) *uint32 { + return &i +} + +// Uint32Ptr is a function variable referring to Uint32. +// +// Deprecated: Use Uint32 instead. +var Uint32Ptr = Uint32 // for back-compat + +// Uint32Deref dereferences the uint32 ptr and returns it if not nil, or else +// returns def. +func Uint32Deref(ptr *uint32, def uint32) uint32 { + if ptr != nil { + return *ptr + } + return def +} + +// Uint32PtrDerefOr is a function variable referring to Uint32Deref. +// +// Deprecated: Use Uint32Deref instead. +var Uint32PtrDerefOr = Uint32Deref // for back-compat + +// Uint32Equal returns true if both arguments are nil or both arguments +// dereference to the same value. +func Uint32Equal(a, b *uint32) bool { + if (a == nil) != (b == nil) { + return false + } + if a == nil { + return true + } + return *a == *b +} + +// Int64 returns a pointer to an int64. +func Int64(i int64) *int64 { + return &i +} + +// Int64Ptr is a function variable referring to Int64. +// +// Deprecated: Use Int64 instead. +var Int64Ptr = Int64 // for back-compat + +// Int64Deref dereferences the int64 ptr and returns it if not nil, or else +// returns def. +func Int64Deref(ptr *int64, def int64) int64 { + if ptr != nil { + return *ptr + } + return def +} + +// Int64PtrDerefOr is a function variable referring to Int64Deref. +// +// Deprecated: Use Int64Deref instead. +var Int64PtrDerefOr = Int64Deref // for back-compat + +// Int64Equal returns true if both arguments are nil or both arguments +// dereference to the same value. +func Int64Equal(a, b *int64) bool { + if (a == nil) != (b == nil) { + return false + } + if a == nil { + return true + } + return *a == *b +} + +// Uint64 returns a pointer to an uint64. +func Uint64(i uint64) *uint64 { + return &i +} + +// Uint64Ptr is a function variable referring to Uint64. +// +// Deprecated: Use Uint64 instead. +var Uint64Ptr = Uint64 // for back-compat + +// Uint64Deref dereferences the uint64 ptr and returns it if not nil, or else +// returns def. +func Uint64Deref(ptr *uint64, def uint64) uint64 { + if ptr != nil { + return *ptr + } + return def +} + +// Uint64PtrDerefOr is a function variable referring to Uint64Deref. +// +// Deprecated: Use Uint64Deref instead. +var Uint64PtrDerefOr = Uint64Deref // for back-compat + +// Uint64Equal returns true if both arguments are nil or both arguments +// dereference to the same value. +func Uint64Equal(a, b *uint64) bool { + if (a == nil) != (b == nil) { + return false + } + if a == nil { + return true + } + return *a == *b +} + +// Bool returns a pointer to a bool. +func Bool(b bool) *bool { + return &b +} + +// BoolPtr is a function variable referring to Bool. +// +// Deprecated: Use Bool instead. +var BoolPtr = Bool // for back-compat + +// BoolDeref dereferences the bool ptr and returns it if not nil, or else +// returns def. +func BoolDeref(ptr *bool, def bool) bool { + if ptr != nil { + return *ptr + } + return def +} + +// BoolPtrDerefOr is a function variable referring to BoolDeref. +// +// Deprecated: Use BoolDeref instead. +var BoolPtrDerefOr = BoolDeref // for back-compat + +// BoolEqual returns true if both arguments are nil or both arguments +// dereference to the same value. +func BoolEqual(a, b *bool) bool { + if (a == nil) != (b == nil) { + return false + } + if a == nil { + return true + } + return *a == *b +} + +// String returns a pointer to a string. +func String(s string) *string { + return &s +} + +// StringPtr is a function variable referring to String. +// +// Deprecated: Use String instead. +var StringPtr = String // for back-compat + +// StringDeref dereferences the string ptr and returns it if not nil, or else +// returns def. +func StringDeref(ptr *string, def string) string { + if ptr != nil { + return *ptr + } + return def +} + +// StringPtrDerefOr is a function variable referring to StringDeref. +// +// Deprecated: Use StringDeref instead. +var StringPtrDerefOr = StringDeref // for back-compat + +// StringEqual returns true if both arguments are nil or both arguments +// dereference to the same value. +func StringEqual(a, b *string) bool { + if (a == nil) != (b == nil) { + return false + } + if a == nil { + return true + } + return *a == *b +} + +// Float32 returns a pointer to a float32. +func Float32(i float32) *float32 { + return &i +} + +// Float32Ptr is a function variable referring to Float32. +// +// Deprecated: Use Float32 instead. +var Float32Ptr = Float32 + +// Float32Deref dereferences the float32 ptr and returns it if not nil, or else +// returns def. +func Float32Deref(ptr *float32, def float32) float32 { + if ptr != nil { + return *ptr + } + return def +} + +// Float32PtrDerefOr is a function variable referring to Float32Deref. +// +// Deprecated: Use Float32Deref instead. +var Float32PtrDerefOr = Float32Deref // for back-compat + +// Float32Equal returns true if both arguments are nil or both arguments +// dereference to the same value. +func Float32Equal(a, b *float32) bool { + if (a == nil) != (b == nil) { + return false + } + if a == nil { + return true + } + return *a == *b +} + +// Float64 returns a pointer to a float64. +func Float64(i float64) *float64 { + return &i +} + +// Float64Ptr is a function variable referring to Float64. +// +// Deprecated: Use Float64 instead. +var Float64Ptr = Float64 + +// Float64Deref dereferences the float64 ptr and returns it if not nil, or else +// returns def. +func Float64Deref(ptr *float64, def float64) float64 { + if ptr != nil { + return *ptr + } + return def +} + +// Float64PtrDerefOr is a function variable referring to Float64Deref. +// +// Deprecated: Use Float64Deref instead. +var Float64PtrDerefOr = Float64Deref // for back-compat + +// Float64Equal returns true if both arguments are nil or both arguments +// dereference to the same value. +func Float64Equal(a, b *float64) bool { + if (a == nil) != (b == nil) { + return false + } + if a == nil { + return true + } + return *a == *b +} + +// Duration returns a pointer to a time.Duration. +func Duration(d time.Duration) *time.Duration { + return &d +} + +// DurationDeref dereferences the time.Duration ptr and returns it if not nil, or else +// returns def. +func DurationDeref(ptr *time.Duration, def time.Duration) time.Duration { + if ptr != nil { + return *ptr + } + return def +} + +// DurationEqual returns true if both arguments are nil or both arguments +// dereference to the same value. +func DurationEqual(a, b *time.Duration) bool { + if (a == nil) != (b == nil) { + return false + } + if a == nil { + return true + } + return *a == *b +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 2ed2450a7..27352f630 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -737,18 +737,22 @@ k8s.io/utils/clock/testing k8s.io/utils/integer k8s.io/utils/internal/third_party/forked/golang/net k8s.io/utils/net +k8s.io/utils/pointer k8s.io/utils/strings/slices # sigs.k8s.io/controller-runtime v0.15.1 ## explicit; go 1.20 sigs.k8s.io/controller-runtime/pkg/client sigs.k8s.io/controller-runtime/pkg/client/apiutil +sigs.k8s.io/controller-runtime/pkg/controller/controllerutil sigs.k8s.io/controller-runtime/pkg/log -# sigs.k8s.io/e2e-framework v0.0.6 -## explicit; go 1.17 +# sigs.k8s.io/e2e-framework v0.3.0 +## explicit; go 1.19 sigs.k8s.io/e2e-framework/klient sigs.k8s.io/e2e-framework/klient/conf +sigs.k8s.io/e2e-framework/klient/decoder sigs.k8s.io/e2e-framework/klient/k8s sigs.k8s.io/e2e-framework/klient/k8s/resources +sigs.k8s.io/e2e-framework/klient/k8s/watcher sigs.k8s.io/e2e-framework/klient/wait sigs.k8s.io/e2e-framework/klient/wait/conditions sigs.k8s.io/e2e-framework/pkg/env @@ -757,7 +761,9 @@ sigs.k8s.io/e2e-framework/pkg/envfuncs sigs.k8s.io/e2e-framework/pkg/features sigs.k8s.io/e2e-framework/pkg/flags sigs.k8s.io/e2e-framework/pkg/internal/types +sigs.k8s.io/e2e-framework/support sigs.k8s.io/e2e-framework/support/kind +sigs.k8s.io/e2e-framework/support/utils # sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd ## explicit; go 1.18 sigs.k8s.io/json diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil/controllerutil.go b/vendor/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil/controllerutil.go new file mode 100644 index 000000000..344abcd28 --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil/controllerutil.go @@ -0,0 +1,394 @@ +/* +Copyright 2018 The Kubernetes 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 controllerutil + +import ( + "context" + "fmt" + "reflect" + + "k8s.io/apimachinery/pkg/api/equality" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" +) + +// AlreadyOwnedError is an error returned if the object you are trying to assign +// a controller reference is already owned by another controller Object is the +// subject and Owner is the reference for the current owner. +type AlreadyOwnedError struct { + Object metav1.Object + Owner metav1.OwnerReference +} + +func (e *AlreadyOwnedError) Error() string { + return fmt.Sprintf("Object %s/%s is already owned by another %s controller %s", e.Object.GetNamespace(), e.Object.GetName(), e.Owner.Kind, e.Owner.Name) +} + +func newAlreadyOwnedError(obj metav1.Object, owner metav1.OwnerReference) *AlreadyOwnedError { + return &AlreadyOwnedError{ + Object: obj, + Owner: owner, + } +} + +// SetControllerReference sets owner as a Controller OwnerReference on controlled. +// This is used for garbage collection of the controlled object and for +// reconciling the owner object on changes to controlled (with a Watch + EnqueueRequestForOwner). +// Since only one OwnerReference can be a controller, it returns an error if +// there is another OwnerReference with Controller flag set. +func SetControllerReference(owner, controlled metav1.Object, scheme *runtime.Scheme) error { + // Validate the owner. + ro, ok := owner.(runtime.Object) + if !ok { + return fmt.Errorf("%T is not a runtime.Object, cannot call SetControllerReference", owner) + } + if err := validateOwner(owner, controlled); err != nil { + return err + } + + // Create a new controller ref. + gvk, err := apiutil.GVKForObject(ro, scheme) + if err != nil { + return err + } + ref := metav1.OwnerReference{ + APIVersion: gvk.GroupVersion().String(), + Kind: gvk.Kind, + Name: owner.GetName(), + UID: owner.GetUID(), + BlockOwnerDeletion: pointer.Bool(true), + Controller: pointer.Bool(true), + } + + // Return early with an error if the object is already controlled. + if existing := metav1.GetControllerOf(controlled); existing != nil && !referSameObject(*existing, ref) { + return newAlreadyOwnedError(controlled, *existing) + } + + // Update owner references and return. + upsertOwnerRef(ref, controlled) + return nil +} + +// SetOwnerReference is a helper method to make sure the given object contains an object reference to the object provided. +// This allows you to declare that owner has a dependency on the object without specifying it as a controller. +// If a reference to the same object already exists, it'll be overwritten with the newly provided version. +func SetOwnerReference(owner, object metav1.Object, scheme *runtime.Scheme) error { + // Validate the owner. + ro, ok := owner.(runtime.Object) + if !ok { + return fmt.Errorf("%T is not a runtime.Object, cannot call SetOwnerReference", owner) + } + if err := validateOwner(owner, object); err != nil { + return err + } + + // Create a new owner ref. + gvk, err := apiutil.GVKForObject(ro, scheme) + if err != nil { + return err + } + ref := metav1.OwnerReference{ + APIVersion: gvk.GroupVersion().String(), + Kind: gvk.Kind, + UID: owner.GetUID(), + Name: owner.GetName(), + } + + // Update owner references and return. + upsertOwnerRef(ref, object) + return nil +} + +func upsertOwnerRef(ref metav1.OwnerReference, object metav1.Object) { + owners := object.GetOwnerReferences() + if idx := indexOwnerRef(owners, ref); idx == -1 { + owners = append(owners, ref) + } else { + owners[idx] = ref + } + object.SetOwnerReferences(owners) +} + +// indexOwnerRef returns the index of the owner reference in the slice if found, or -1. +func indexOwnerRef(ownerReferences []metav1.OwnerReference, ref metav1.OwnerReference) int { + for index, r := range ownerReferences { + if referSameObject(r, ref) { + return index + } + } + return -1 +} + +func validateOwner(owner, object metav1.Object) error { + ownerNs := owner.GetNamespace() + if ownerNs != "" { + objNs := object.GetNamespace() + if objNs == "" { + return fmt.Errorf("cluster-scoped resource must not have a namespace-scoped owner, owner's namespace %s", ownerNs) + } + if ownerNs != objNs { + return fmt.Errorf("cross-namespace owner references are disallowed, owner's namespace %s, obj's namespace %s", owner.GetNamespace(), object.GetNamespace()) + } + } + return nil +} + +// Returns true if a and b point to the same object. +func referSameObject(a, b metav1.OwnerReference) bool { + aGV, err := schema.ParseGroupVersion(a.APIVersion) + if err != nil { + return false + } + + bGV, err := schema.ParseGroupVersion(b.APIVersion) + if err != nil { + return false + } + + return aGV.Group == bGV.Group && a.Kind == b.Kind && a.Name == b.Name +} + +// OperationResult is the action result of a CreateOrUpdate call. +type OperationResult string + +const ( // They should complete the sentence "Deployment default/foo has been ..." + // OperationResultNone means that the resource has not been changed. + OperationResultNone OperationResult = "unchanged" + // OperationResultCreated means that a new resource is created. + OperationResultCreated OperationResult = "created" + // OperationResultUpdated means that an existing resource is updated. + OperationResultUpdated OperationResult = "updated" + // OperationResultUpdatedStatus means that an existing resource and its status is updated. + OperationResultUpdatedStatus OperationResult = "updatedStatus" + // OperationResultUpdatedStatusOnly means that only an existing status is updated. + OperationResultUpdatedStatusOnly OperationResult = "updatedStatusOnly" +) + +// CreateOrUpdate creates or updates the given object in the Kubernetes +// cluster. The object's desired state must be reconciled with the existing +// state inside the passed in callback MutateFn. +// +// The MutateFn is called regardless of creating or updating an object. +// +// It returns the executed operation and an error. +func CreateOrUpdate(ctx context.Context, c client.Client, obj client.Object, f MutateFn) (OperationResult, error) { + key := client.ObjectKeyFromObject(obj) + if err := c.Get(ctx, key, obj); err != nil { + if !apierrors.IsNotFound(err) { + return OperationResultNone, err + } + if err := mutate(f, key, obj); err != nil { + return OperationResultNone, err + } + if err := c.Create(ctx, obj); err != nil { + return OperationResultNone, err + } + return OperationResultCreated, nil + } + + existing := obj.DeepCopyObject() + if err := mutate(f, key, obj); err != nil { + return OperationResultNone, err + } + + if equality.Semantic.DeepEqual(existing, obj) { + return OperationResultNone, nil + } + + if err := c.Update(ctx, obj); err != nil { + return OperationResultNone, err + } + return OperationResultUpdated, nil +} + +// CreateOrPatch creates or patches the given object in the Kubernetes +// cluster. The object's desired state must be reconciled with the before +// state inside the passed in callback MutateFn. +// +// The MutateFn is called regardless of creating or updating an object. +// +// It returns the executed operation and an error. +func CreateOrPatch(ctx context.Context, c client.Client, obj client.Object, f MutateFn) (OperationResult, error) { + key := client.ObjectKeyFromObject(obj) + if err := c.Get(ctx, key, obj); err != nil { + if !apierrors.IsNotFound(err) { + return OperationResultNone, err + } + if f != nil { + if err := mutate(f, key, obj); err != nil { + return OperationResultNone, err + } + } + if err := c.Create(ctx, obj); err != nil { + return OperationResultNone, err + } + return OperationResultCreated, nil + } + + // Create patches for the object and its possible status. + objPatch := client.MergeFrom(obj.DeepCopyObject().(client.Object)) + statusPatch := client.MergeFrom(obj.DeepCopyObject().(client.Object)) + + // Create a copy of the original object as well as converting that copy to + // unstructured data. + before, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj.DeepCopyObject()) + if err != nil { + return OperationResultNone, err + } + + // Attempt to extract the status from the resource for easier comparison later + beforeStatus, hasBeforeStatus, err := unstructured.NestedFieldCopy(before, "status") + if err != nil { + return OperationResultNone, err + } + + // If the resource contains a status then remove it from the unstructured + // copy to avoid unnecessary patching later. + if hasBeforeStatus { + unstructured.RemoveNestedField(before, "status") + } + + // Mutate the original object. + if f != nil { + if err := mutate(f, key, obj); err != nil { + return OperationResultNone, err + } + } + + // Convert the resource to unstructured to compare against our before copy. + after, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + return OperationResultNone, err + } + + // Attempt to extract the status from the resource for easier comparison later + afterStatus, hasAfterStatus, err := unstructured.NestedFieldCopy(after, "status") + if err != nil { + return OperationResultNone, err + } + + // If the resource contains a status then remove it from the unstructured + // copy to avoid unnecessary patching later. + if hasAfterStatus { + unstructured.RemoveNestedField(after, "status") + } + + result := OperationResultNone + + if !reflect.DeepEqual(before, after) { + // Only issue a Patch if the before and after resources (minus status) differ + if err := c.Patch(ctx, obj, objPatch); err != nil { + return result, err + } + result = OperationResultUpdated + } + + if (hasBeforeStatus || hasAfterStatus) && !reflect.DeepEqual(beforeStatus, afterStatus) { + // Only issue a Status Patch if the resource has a status and the beforeStatus + // and afterStatus copies differ + if result == OperationResultUpdated { + // If Status was replaced by Patch before, set it to afterStatus + objectAfterPatch, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + return result, err + } + if err = unstructured.SetNestedField(objectAfterPatch, afterStatus, "status"); err != nil { + return result, err + } + // If Status was replaced by Patch before, restore patched structure to the obj + if err = runtime.DefaultUnstructuredConverter.FromUnstructured(objectAfterPatch, obj); err != nil { + return result, err + } + } + if err := c.Status().Patch(ctx, obj, statusPatch); err != nil { + return result, err + } + if result == OperationResultUpdated { + result = OperationResultUpdatedStatus + } else { + result = OperationResultUpdatedStatusOnly + } + } + + return result, nil +} + +// mutate wraps a MutateFn and applies validation to its result. +func mutate(f MutateFn, key client.ObjectKey, obj client.Object) error { + if err := f(); err != nil { + return err + } + if newKey := client.ObjectKeyFromObject(obj); key != newKey { + return fmt.Errorf("MutateFn cannot mutate object name and/or object namespace") + } + return nil +} + +// MutateFn is a function which mutates the existing object into its desired state. +type MutateFn func() error + +// AddFinalizer accepts an Object and adds the provided finalizer if not present. +// It returns an indication of whether it updated the object's list of finalizers. +func AddFinalizer(o client.Object, finalizer string) (finalizersUpdated bool) { + f := o.GetFinalizers() + for _, e := range f { + if e == finalizer { + return false + } + } + o.SetFinalizers(append(f, finalizer)) + return true +} + +// RemoveFinalizer accepts an Object and removes the provided finalizer if present. +// It returns an indication of whether it updated the object's list of finalizers. +func RemoveFinalizer(o client.Object, finalizer string) (finalizersUpdated bool) { + f := o.GetFinalizers() + for i := 0; i < len(f); i++ { + if f[i] == finalizer { + f = append(f[:i], f[i+1:]...) + i-- + finalizersUpdated = true + } + } + o.SetFinalizers(f) + return +} + +// ContainsFinalizer checks an Object that the provided finalizer is present. +func ContainsFinalizer(o client.Object, finalizer string) bool { + f := o.GetFinalizers() + for _, e := range f { + if e == finalizer { + return true + } + } + return false +} + +// Object allows functions to work indistinctly with any resource that +// implements both Object interfaces. +// +// Deprecated: Use client.Object instead. +type Object = client.Object diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil/doc.go b/vendor/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil/doc.go new file mode 100644 index 000000000..ab386b29c --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2018 The Kubernetes 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 controllerutil contains utility functions for working with and implementing Controllers. +*/ +package controllerutil diff --git a/vendor/sigs.k8s.io/e2e-framework/klient/client.go b/vendor/sigs.k8s.io/e2e-framework/klient/client.go index 680e7ad05..67314adec 100644 --- a/vendor/sigs.k8s.io/e2e-framework/klient/client.go +++ b/vendor/sigs.k8s.io/e2e-framework/klient/client.go @@ -17,7 +17,11 @@ limitations under the License. package klient import ( + "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" + "k8s.io/klog/v2" + cr "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/e2e-framework/klient/conf" "sigs.k8s.io/e2e-framework/klient/k8s/resources" ) @@ -38,6 +42,12 @@ type client struct { resources *resources.Resources } +// NewControllerRuntimeClient provides an instance of the Controller runtime client with +// the provided rest config and custom runtime scheme. +func NewControllerRuntimeClient(cfg *rest.Config, scheme *runtime.Scheme) (cr.Client, error) { + return cr.New(cfg, cr.Options{Scheme: scheme}) +} + // New returns a new Client value func New(cfg *rest.Config) (Client, error) { res, err := resources.New(cfg) @@ -74,3 +84,7 @@ func (c *client) Resources(namespace ...string) *resources.Resources { panic("too many namespaces provided") } } + +func init() { + log.SetLogger(klog.NewKlogr()) +} diff --git a/vendor/sigs.k8s.io/e2e-framework/klient/conf/config.go b/vendor/sigs.k8s.io/e2e-framework/klient/conf/config.go index 74a739db9..5e57f80fb 100644 --- a/vendor/sigs.k8s.io/e2e-framework/klient/conf/config.go +++ b/vendor/sigs.k8s.io/e2e-framework/klient/conf/config.go @@ -18,6 +18,7 @@ limitations under the License. package conf import ( + "errors" "flag" "os" "os/user" @@ -33,14 +34,32 @@ var DefaultClusterContext = "" // New returns Kubernetes configuration value of type *rest.Config. // filename is kubeconfig file func New(fileName string) (*rest.Config, error) { - // if filename is not provided assume in-cluster-config + var resolvedKubeConfigFile string + kubeContext := ResolveClusterContext() + + // resolve the kubeconfig file + resolvedKubeConfigFile = fileName if fileName == "" { - return rest.InClusterConfig() + resolvedKubeConfigFile = ResolveKubeConfigFile() + } + + // if resolvedKubeConfigFile is still empty, assume in-cluster config + if resolvedKubeConfigFile == "" { + if kubeContext == "" { + return rest.InClusterConfig() + } + // if in-cluster can't use the --kubeContext flag + return nil, errors.New("cannot use a cluster context without a valid kubeconfig file") + } + + // set the desired context if provided + if kubeContext != "" { + return NewWithContextName(resolvedKubeConfigFile, kubeContext) } - // create the config object from k8s config path + // create the config object from resolvedKubeConfigFile without a context return clientcmd.NewNonInteractiveDeferredLoadingClientConfig( - &clientcmd.ClientConfigLoadingRules{ExplicitPath: fileName}, &clientcmd.ConfigOverrides{}).ClientConfig() + &clientcmd.ClientConfigLoadingRules{ExplicitPath: resolvedKubeConfigFile}, &clientcmd.ConfigOverrides{}).ClientConfig() } // NewWithContextName returns k8s config value of type *rest.Config @@ -110,9 +129,9 @@ func ResolveKubeConfigFile() string { // ResolveClusterContext returns cluster context name based on --context flag. func ResolveClusterContext() string { - // If a flag --kube-context is specified use that + // If a flag --context is specified use that if flag.Parsed() { - f := flag.Lookup("kube-context") + f := flag.Lookup("context") if f != nil { return f.Value.String() } diff --git a/vendor/sigs.k8s.io/e2e-framework/klient/decoder/decoder.go b/vendor/sigs.k8s.io/e2e-framework/klient/decoder/decoder.go new file mode 100644 index 000000000..db58973c3 --- /dev/null +++ b/vendor/sigs.k8s.io/e2e-framework/klient/decoder/decoder.go @@ -0,0 +1,355 @@ +/* +Copyright 2022 The Kubernetes 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 decoder + +import ( + "bufio" + "bytes" + "context" + "errors" + "fmt" + "io" + "io/fs" + "os" + "strings" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/e2e-framework/klient/k8s" + "sigs.k8s.io/e2e-framework/klient/k8s/resources" +) + +// Options are a set of configurations used to instruct the decoding process and otherwise +// alter the output of decoding operations. +type Options struct { + DefaultGVK *schema.GroupVersionKind + MutateFuncs []MutateFunc +} + +// DecodeOption is a function that alters the configuration Options used to decode and optionally mutate objects via MutateFuncs +type DecodeOption func(*Options) + +// MutateFunc is a function executed after an object is decoded to alter its state in a pre-defined way, and can be used to apply defaults. +// Returning an error halts decoding of any further objects. +type MutateFunc func(obj k8s.Object) error + +// HandlerFunc is a function executed after an object has been decoded and patched. If an error is returned, further decoding is halted. +type HandlerFunc func(ctx context.Context, obj k8s.Object) error + +// DecodeEachFile resolves files at the filesystem matching the pattern, decoding JSON or YAML files. Supports multi-document files. +// +// If handlerFn returns an error, decoding is halted. +// Options may be provided to configure the behavior of the decoder. +func DecodeEachFile(ctx context.Context, fsys fs.FS, pattern string, handlerFn HandlerFunc, options ...DecodeOption) error { + files, err := fs.Glob(fsys, pattern) + if err != nil { + return err + } + for _, file := range files { + f, err := fsys.Open(file) + if err != nil { + return err + } + defer f.Close() + if err := DecodeEach(ctx, f, handlerFn, options...); err != nil { + return err + } + if err := f.Close(); err != nil { + return err + } + } + return nil +} + +// DecodeAllFiles resolves files at the filesystem matching the pattern, decoding JSON or YAML files. Supports multi-document files. +// Falls back to the unstructured.Unstructured type if a matching type cannot be found for the Kind. +// Options may be provided to configure the behavior of the decoder. +func DecodeAllFiles(ctx context.Context, fsys fs.FS, pattern string, options ...DecodeOption) ([]k8s.Object, error) { + objects := []k8s.Object{} + err := DecodeEachFile(ctx, fsys, pattern, func(ctx context.Context, obj k8s.Object) error { + objects = append(objects, obj) + return nil + }, options...) + return objects, err +} + +// ApplyWithManifestDir resolves all the files in the Directory dirPath against the globbing pattern and creates a kubernetes +// resource for each of the resources found under the manifest directory. +func ApplyWithManifestDir(ctx context.Context, r *resources.Resources, dirPath, pattern string, createOptions []resources.CreateOption, options ...DecodeOption) error { + err := DecodeEachFile(ctx, os.DirFS(dirPath), pattern, CreateHandler(r, createOptions...), options...) + return err +} + +// DeleteWithManifestDir does the reverse of ApplyUsingManifestDir does. This will resolve all files in the dirPath against the pattern and then +// delete those kubernetes resources found under the manifest directory. +func DeleteWithManifestDir(ctx context.Context, r *resources.Resources, dirPath, pattern string, deleteOptions []resources.DeleteOption, options ...DecodeOption) error { + err := DecodeEachFile(ctx, os.DirFS(dirPath), pattern, DeleteHandler(r, deleteOptions...), options...) + return err +} + +// Decode a stream of documents of any Kind using either the innate typing of the scheme. +// Falls back to the unstructured.Unstructured type if a matching type cannot be found for the Kind. +// +// If handlerFn returns an error, decoding is halted. +// Options may be provided to configure the behavior of the decoder. +func DecodeEach(ctx context.Context, manifest io.Reader, handlerFn HandlerFunc, options ...DecodeOption) error { + decoder := yaml.NewYAMLReader(bufio.NewReader(manifest)) + for { + b, err := decoder.Read() + if errors.Is(err, io.EOF) { + break + } else if err != nil { + return err + } + obj, err := DecodeAny(bytes.NewReader(b), options...) + if err != nil { + return err + } + if err := handlerFn(ctx, obj); err != nil { + return err + } + } + return nil +} + +// DecodeAll is a stream of documents of any Kind using either the innate typing of the scheme. +// Falls back to the unstructured.Unstructured type if a matching type cannot be found for the Kind. +// Options may be provided to configure the behavior of the decoder. +func DecodeAll(ctx context.Context, manifest io.Reader, options ...DecodeOption) ([]k8s.Object, error) { + objects := []k8s.Object{} + err := DecodeEach(ctx, manifest, func(ctx context.Context, obj k8s.Object) error { + objects = append(objects, obj) + return nil + }, options...) + return objects, err +} + +// DecodeAny decodes any single-document YAML or JSON input using either the innate typing of the scheme. +// Falls back to the unstructured.Unstructured type if a matching type cannot be found for the Kind. +// Options may be provided to configure the behavior of the decoder. +func DecodeAny(manifest io.Reader, options ...DecodeOption) (k8s.Object, error) { + decodeOpt := &Options{} + for _, opt := range options { + opt(decodeOpt) + } + + k8sDecoder := serializer.NewCodecFactory(scheme.Scheme).UniversalDeserializer().Decode + b, err := io.ReadAll(manifest) + if err != nil { + return nil, err + } + runtimeObj, _, err := k8sDecoder(b, decodeOpt.DefaultGVK, nil) + if runtime.IsNotRegisteredError(err) { + // fallback to the unstructured.Unstructured type if a type is not registered for the Object to be decoded + runtimeObj = &unstructured.Unstructured{} + if err := yaml.Unmarshal(b, runtimeObj); err != nil { + return nil, err + } + } else if err != nil { + return nil, err + } + obj, ok := runtimeObj.(k8s.Object) + if !ok { + return nil, err + } + for _, patch := range decodeOpt.MutateFuncs { + if err := patch(obj); err != nil { + return nil, err + } + } + return obj, nil +} + +// Decode a single-document YAML or JSON file into the provided object. Patches are applied +// after decoding to the object to update the loaded resource. +func Decode(manifest io.Reader, obj k8s.Object, options ...DecodeOption) error { + decodeOpt := &Options{} + for _, opt := range options { + opt(decodeOpt) + } + if err := yaml.NewYAMLOrJSONDecoder(manifest, 1024).Decode(obj); err != nil { + return err + } + for _, patch := range decodeOpt.MutateFuncs { + if err := patch(obj); err != nil { + return err + } + } + return nil +} + +// DecodeFile decodes a single-document YAML or JSON file into the provided object. Patches are applied +// after decoding to the object to update the loaded resource. +func DecodeFile(fsys fs.FS, manifestPath string, obj k8s.Object, options ...DecodeOption) error { + f, err := fsys.Open(manifestPath) + if err != nil { + return err + } + defer f.Close() + return Decode(f, obj, options...) +} + +// DecodeString decodes a single-document YAML or JSON string into the provided object. Patches are applied +// after decoding to the object to update the loaded resource. +func DecodeString(rawManifest string, obj k8s.Object, options ...DecodeOption) error { + return Decode(strings.NewReader(rawManifest), obj, options...) +} + +// DefaultGVK instructs the decoder to use the given type to look up the appropriate Go type to decode into +// instead of its default behavior of deciding this by decoding the Group, Version, and Kind fields. +func DefaultGVK(defaults *schema.GroupVersionKind) DecodeOption { + return func(do *Options) { + do.DefaultGVK = defaults + } +} + +// MutateOption can be used to add a custom MutateFunc to the DecodeOption +// used to configure the decoding of objects +func MutateOption(m MutateFunc) DecodeOption { + return func(do *Options) { + do.MutateFuncs = append(do.MutateFuncs, m) + } +} + +// MutateLabels is an optional parameter to decoding functions that will patch an objects metadata.labels +func MutateLabels(overrides map[string]string) DecodeOption { + return MutateOption(func(obj k8s.Object) error { + labels := obj.GetLabels() + if labels == nil { + labels = make(map[string]string) + obj.SetLabels(labels) + } + for key, value := range overrides { + labels[key] = value + } + return nil + }) +} + +// MutateAnnotations is an optional parameter to decoding functions that will patch an objects metadata.annotations +func MutateAnnotations(overrides map[string]string) DecodeOption { + return MutateOption(func(obj k8s.Object) error { + annotations := obj.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string) + obj.SetLabels(annotations) + } + for key, value := range overrides { + annotations[key] = value + } + return nil + }) +} + +// MutateOwnerAnnotations is an optional parameter to decoding functions that will patch objects using the given owner object +func MutateOwnerAnnotations(owner k8s.Object) DecodeOption { + return MutateOption(func(obj k8s.Object) error { + return controllerutil.SetOwnerReference(owner, obj, scheme.Scheme) + }) +} + +// MutateNamespace is an optional parameter to decoding functions that will patch objects with the given namespace name +func MutateNamespace(namespace string) DecodeOption { + return MutateOption(func(obj k8s.Object) error { + obj.SetNamespace(namespace) + return nil + }) +} + +// CreateHandler returns a HandlerFunc that will create objects +func CreateHandler(r *resources.Resources, opts ...resources.CreateOption) HandlerFunc { + return func(ctx context.Context, obj k8s.Object) error { + return r.Create(ctx, obj, opts...) + } +} + +// ReadHandler returns a HandlerFunc that will use the provided object's Kind / Namespace / Name to retrieve +// the current state of the object using the provided Resource client. +// This helper makes it easy to use a stale reference to an object to retrieve its current version. +func ReadHandler(r *resources.Resources, handler HandlerFunc) HandlerFunc { + return func(ctx context.Context, obj k8s.Object) error { + name := obj.GetName() + namespace := obj.GetNamespace() + // use scheme.Scheme to generate a new, empty object to use as a base for decoding into + gvk := obj.GetObjectKind().GroupVersionKind() + o, err := scheme.Scheme.New(gvk) + if err != nil { + return fmt.Errorf("resources: GroupVersionKind not found in scheme: %s", gvk.String()) + } + obj, ok := o.(k8s.Object) + if !ok { + return fmt.Errorf("resources: unexpected type %T does not satisfy k8s.Object", obj) + } + if err := r.Get(ctx, name, namespace, obj); err != nil { + return err + } + return handler(ctx, obj) + } +} + +// UpdateHandler returns a HandlerFunc that will update objects +func UpdateHandler(r *resources.Resources, opts ...resources.UpdateOption) HandlerFunc { + return func(ctx context.Context, obj k8s.Object) error { + return r.Update(ctx, obj, opts...) + } +} + +// DeleteHandler returns a HandlerFunc that will delete objects +func DeleteHandler(r *resources.Resources, opts ...resources.DeleteOption) HandlerFunc { + return func(ctx context.Context, obj k8s.Object) error { + return r.Delete(ctx, obj, opts...) + } +} + +// IgnoreErrorHandler returns a HandlerFunc that will ignore the provided error if the errorMatcher returns true +func IgnoreErrorHandler(handler HandlerFunc, errorMatcher func(err error) bool) HandlerFunc { + return func(ctx context.Context, obj k8s.Object) error { + if err := handler(ctx, obj); err != nil && !errorMatcher(err) { + return err + } + return nil + } +} + +// NoopHandler returns a Handler func that only returns nil +func NoopHandler(_ *resources.Resources, _ ...resources.DeleteOption) HandlerFunc { + return func(ctx context.Context, obj k8s.Object) error { + return nil + } +} + +// CreateIgnoreAlreadyExists returns a HandlerFunc that will create objects if they do not already exist +func CreateIgnoreAlreadyExists(r *resources.Resources, opts ...resources.CreateOption) HandlerFunc { + return IgnoreErrorHandler(CreateHandler(r, opts...), apierrors.IsAlreadyExists) +} + +// DeleteIgnoreNotFound returns a HandlerFunc that will delete objects if they do not already exist +func DeleteIgnoreNotFound(r *resources.Resources, opts ...resources.DeleteOption) HandlerFunc { + return IgnoreErrorHandler(DeleteHandler(r, opts...), apierrors.IsNotFound) +} + +func init() { + log.SetLogger(klog.NewKlogr()) +} diff --git a/vendor/sigs.k8s.io/e2e-framework/klient/k8s/resources/resources.go b/vendor/sigs.k8s.io/e2e-framework/klient/k8s/resources/resources.go index 3942b0730..49cd29cef 100644 --- a/vendor/sigs.k8s.io/e2e-framework/klient/k8s/resources/resources.go +++ b/vendor/sigs.k8s.io/e2e-framework/klient/k8s/resources/resources.go @@ -17,17 +17,26 @@ limitations under the License. package resources import ( + "bytes" "context" "errors" "time" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/rest" - + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/remotecommand" + "k8s.io/klog/v2" cr "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/e2e-framework/klient/k8s" + "sigs.k8s.io/e2e-framework/klient/k8s/watcher" ) type Resources struct { @@ -67,6 +76,11 @@ func New(cfg *rest.Config) (*Resources, error) { return res, nil } +// GetConfig hepls to get config type *rest.Config +func (r *Resources) GetConfig() *rest.Config { + return r.config +} + func (r *Resources) WithNamespace(ns string) *Resources { r.namespace = ns return r @@ -84,7 +98,11 @@ func (r *Resources) Create(ctx context.Context, obj k8s.Object, opts ...CreateOp fn(createOptions) } - o := &cr.CreateOptions{Raw: createOptions} + o := &cr.CreateOptions{ + Raw: createOptions, + DryRun: createOptions.DryRun, + FieldManager: createOptions.FieldManager, + } return r.client.Create(ctx, obj, o) } @@ -97,10 +115,31 @@ func (r *Resources) Update(ctx context.Context, obj k8s.Object, opts ...UpdateOp fn(updateOptions) } - o := &cr.UpdateOptions{Raw: updateOptions} + o := &cr.UpdateOptions{ + Raw: updateOptions, + DryRun: updateOptions.DryRun, + FieldManager: updateOptions.FieldManager, + } return r.client.Update(ctx, obj, o) } +// UpdateSubresource updates the subresource of the object +func (r *Resources) UpdateSubresource(ctx context.Context, obj k8s.Object, subresource string, opts ...UpdateOption) error { + updateOptions := &metav1.UpdateOptions{} + for _, fn := range opts { + fn(updateOptions) + } + + uo := cr.UpdateOptions{Raw: updateOptions} + o := &cr.SubResourceUpdateOptions{UpdateOptions: uo} + return r.client.SubResource(subresource).Update(ctx, obj, o) +} + +// UpdateStatus updates the status of the object +func (r *Resources) UpdateStatus(ctx context.Context, obj k8s.Object, opts ...UpdateOption) error { + return r.UpdateSubresource(ctx, obj, "status", opts...) +} + type DeleteOption func(*metav1.DeleteOptions) func (r *Resources) Delete(ctx context.Context, obj k8s.Object, opts ...DeleteOption) error { @@ -109,7 +148,13 @@ func (r *Resources) Delete(ctx context.Context, obj k8s.Object, opts ...DeleteOp fn(deleteOptions) } - o := &cr.DeleteOptions{Raw: deleteOptions} + o := &cr.DeleteOptions{ + Raw: deleteOptions, + GracePeriodSeconds: deleteOptions.GracePeriodSeconds, + Preconditions: deleteOptions.Preconditions, + PropagationPolicy: deleteOptions.PropagationPolicy, + DryRun: deleteOptions.DryRun, + } return r.client.Delete(ctx, obj, o) } @@ -132,7 +177,22 @@ func (r *Resources) List(ctx context.Context, objs k8s.ObjectList, opts ...ListO fn(listOptions) } - o := &cr.ListOptions{Raw: listOptions} + ls, err := labels.Parse(listOptions.LabelSelector) + if err != nil { + return err + } + fs, err := fields.ParseSelector(listOptions.FieldSelector) + if err != nil { + return err + } + + o := &cr.ListOptions{ + Raw: listOptions, + FieldSelector: fs, + LabelSelector: ls, + Continue: listOptions.Continue, + Limit: listOptions.Limit, + } if r.namespace != "" { o.Namespace = r.namespace } @@ -156,8 +216,8 @@ func WithTimeout(to time.Duration) ListOption { // PatchOption is used to provide additional arguments to the Patch call. type PatchOption func(*metav1.PatchOptions) -// Patch patches portion of object `orig` with data from object `patch` -func (r *Resources) Patch(ctx context.Context, objs k8s.Object, patch k8s.Patch, opts ...PatchOption) error { +// Patch patches portion of object `obj` with data from object `patch` +func (r *Resources) Patch(ctx context.Context, obj k8s.Object, patch k8s.Patch, opts ...PatchOption) error { patchOptions := &metav1.PatchOptions{} for _, fn := range opts { @@ -166,8 +226,33 @@ func (r *Resources) Patch(ctx context.Context, objs k8s.Object, patch k8s.Patch, p := cr.RawPatch(patch.PatchType, patch.Data) - o := &cr.PatchOptions{Raw: patchOptions} - return r.client.Patch(ctx, objs, p, o) + o := &cr.PatchOptions{ + Raw: patchOptions, + DryRun: patchOptions.DryRun, + Force: patchOptions.Force, + FieldManager: patchOptions.FieldManager, + } + return r.client.Patch(ctx, obj, p, o) +} + +// PatchSubresource patches portion of object `obj` with data from object `patch` +func (r *Resources) PatchSubresource(ctx context.Context, obj k8s.Object, subresource string, patch k8s.Patch, opts ...PatchOption) error { + patchOptions := &metav1.PatchOptions{} + + for _, fn := range opts { + fn(patchOptions) + } + + p := cr.RawPatch(patch.PatchType, patch.Data) + + po := cr.PatchOptions{Raw: patchOptions} + o := &cr.SubResourcePatchOptions{PatchOptions: po} + return r.client.SubResource(subresource).Patch(ctx, obj, p, o) +} + +// PatchStatus patches portion of object `obj` with data from object `patch` +func (r *Resources) PatchStatus(ctx context.Context, objs k8s.Object, patch k8s.Patch, opts ...PatchOption) error { + return r.PatchSubresource(ctx, objs, "status", patch, opts...) } // Annotate attach annotations to an existing resource objec @@ -179,3 +264,71 @@ func (r *Resources) Annotate(obj k8s.Object, annotation map[string]string) { func (r *Resources) Label(obj k8s.Object, label map[string]string) { obj.SetLabels(label) } + +func (r *Resources) GetScheme() *runtime.Scheme { + return r.scheme +} + +// GetClient return the controller-runtime client instance +func (r *Resources) GetControllerRuntimeClient() cr.Client { + return r.client +} + +func (r *Resources) Watch(object k8s.ObjectList, opts ...ListOption) *watcher.EventHandlerFuncs { + listOptions := &metav1.ListOptions{} + + for _, fn := range opts { + fn(listOptions) + } + + o := &cr.ListOptions{Raw: listOptions} + + return &watcher.EventHandlerFuncs{ + ListOptions: o, + K8sObject: object, + Cfg: r.GetConfig(), + } +} + +func (r *Resources) ExecInPod(ctx context.Context, namespaceName, podName, containerName string, command []string, stdout, stderr *bytes.Buffer) error { + clientset, err := kubernetes.NewForConfig(r.config) + if err != nil { + return err + } + + req := clientset.CoreV1().RESTClient().Post(). + Resource("pods"). + Name(podName). + Namespace(namespaceName). + SubResource("exec") + newScheme := runtime.NewScheme() + if err := v1.AddToScheme(newScheme); err != nil { + return err + } + parameterCodec := runtime.NewParameterCodec(newScheme) + req.VersionedParams(&v1.PodExecOptions{ + Container: containerName, + Command: command, + Stdout: true, + Stderr: true, + }, parameterCodec) + + exec, err := remotecommand.NewSPDYExecutor(r.config, "POST", req.URL()) + if err != nil { + panic(err) + } + + err = exec.StreamWithContext(ctx, remotecommand.StreamOptions{ + Stdout: stdout, + Stderr: stderr, + }) + if err != nil { + return err + } + + return nil +} + +func init() { + log.SetLogger(klog.NewKlogr()) +} diff --git a/vendor/sigs.k8s.io/e2e-framework/klient/k8s/watcher/watch.go b/vendor/sigs.k8s.io/e2e-framework/klient/k8s/watcher/watch.go new file mode 100644 index 000000000..596fb1402 --- /dev/null +++ b/vendor/sigs.k8s.io/e2e-framework/klient/k8s/watcher/watch.go @@ -0,0 +1,132 @@ +/* +Copyright 2022 The Kubernetes 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 watcher + +import ( + "context" + + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/rest" + "k8s.io/klog/v2" + cr "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/e2e-framework/klient/k8s" +) + +// EventHandlerFuncs is an adaptor to let you easily specify as many or +// as few of functions to invoke while getting notification from watcher +type EventHandlerFuncs struct { + addFunc func(obj interface{}) + updateFunc func(newObj interface{}) + deleteFunc func(obj interface{}) + watcher watch.Interface + ListOptions *cr.ListOptions + K8sObject k8s.ObjectList + Cfg *rest.Config +} + +// EventHandler can handle notifications for events that happen to a resource. +// Start will be waiting for the events notification which is responsible +// for invoking the registered user defined functions. +// Stop used to stop the watcher. +type EventHandler interface { + Start(ctx context.Context) + Stop() +} + +// Start triggers the registered methods based on the event received for +// particular k8s resources +func (e *EventHandlerFuncs) Start(ctx context.Context) error { + // check if context is valid and that it has not been cancelled. + if ctx.Err() != nil { + return ctx.Err() + } + + cl, err := cr.NewWithWatch(e.Cfg, cr.Options{}) + if err != nil { + return err + } + + w, err := cl.Watch(ctx, e.K8sObject, e.ListOptions) + if err != nil { + return err + } + + // set watcher object + e.watcher = w + + go func() { + for { + select { + case <-ctx.Done(): + if ctx.Err() != nil { + return + } + case event := <-e.watcher.ResultChan(): + // retrieve the event type + eventType := event.Type + + switch eventType { + case watch.Added: + // calls AddFunc if it's not nil. + if e.addFunc != nil { + e.addFunc(event.Object) + } + case watch.Modified: + // calls UpdateFunc if it's not nil. + if e.updateFunc != nil { + e.updateFunc(event.Object) + } + case watch.Deleted: + // calls DeleteFunc if it's not nil. + if e.deleteFunc != nil { + e.deleteFunc(event.Object) + } + } + } + } + }() + + return nil +} + +// Stop triggers stopping a particular k8s watch resources +func (e *EventHandlerFuncs) Stop() { + e.watcher.Stop() +} + +// SetAddFunc used to set action on create event +func (e *EventHandlerFuncs) WithAddFunc(addfn func(obj interface{})) *EventHandlerFuncs { + e.addFunc = addfn + return e +} + +// SetUpdateFunc sets action for any update events +func (e *EventHandlerFuncs) WithUpdateFunc(updatefn func(updated interface{})) *EventHandlerFuncs { + e.updateFunc = updatefn + return e +} + +// SetDeleteFunc sets action for delete events +func (e *EventHandlerFuncs) WithDeleteFunc(deletefn func(obj interface{})) *EventHandlerFuncs { + e.deleteFunc = deletefn + return e +} + +func init() { + log.SetLogger(klog.NewKlogr()) +} diff --git a/vendor/sigs.k8s.io/e2e-framework/klient/wait/conditions/conditions.go b/vendor/sigs.k8s.io/e2e-framework/klient/wait/conditions/conditions.go index bcab5d538..6e81962e7 100644 --- a/vendor/sigs.k8s.io/e2e-framework/klient/wait/conditions/conditions.go +++ b/vendor/sigs.k8s.io/e2e-framework/klient/wait/conditions/conditions.go @@ -27,6 +27,7 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" apimachinerywait "k8s.io/apimachinery/pkg/util/wait" "sigs.k8s.io/e2e-framework/klient/k8s" @@ -50,10 +51,10 @@ func (c *Condition) namespacedName(obj k8s.Object) string { // ResourceScaled is a helper function used to check if the resource under question has a pre-defined number of // replicas. This can be leveraged for checking cases such as scaling up and down a deployment or STS and any // other scalable resources. -func (c *Condition) ResourceScaled(obj k8s.Object, scaleFetcher func(object k8s.Object) int32, replica int32) apimachinerywait.ConditionFunc { - return func() (done bool, err error) { +func (c *Condition) ResourceScaled(obj k8s.Object, scaleFetcher func(object k8s.Object) int32, replica int32) apimachinerywait.ConditionWithContextFunc { + return func(ctx context.Context) (done bool, err error) { log.V(4).InfoS("Checking for resource to be scaled", "resource", c.namespacedName(obj), "replica", replica) - if err := c.resources.Get(context.TODO(), obj.GetName(), obj.GetNamespace(), obj); err != nil { + if err := c.resources.Get(ctx, obj.GetName(), obj.GetNamespace(), obj); err != nil { return false, nil } return scaleFetcher(obj) == replica, nil @@ -62,9 +63,9 @@ func (c *Condition) ResourceScaled(obj k8s.Object, scaleFetcher func(object k8s. // ResourceMatch is a helper function used to check if the resource under question has met a pre-defined state. This can // be leveraged for checking fields on a resource that may not be immediately present upon creation. -func (c *Condition) ResourceMatch(obj k8s.Object, matchFetcher func(object k8s.Object) bool) apimachinerywait.ConditionFunc { - return func() (done bool, err error) { - if err := c.resources.Get(context.TODO(), obj.GetName(), obj.GetNamespace(), obj); err != nil { +func (c *Condition) ResourceMatch(obj k8s.Object, matchFetcher func(object k8s.Object) bool) apimachinerywait.ConditionWithContextFunc { + return func(ctx context.Context) (done bool, err error) { + if err := c.resources.Get(ctx, obj.GetName(), obj.GetNamespace(), obj); err != nil { return false, nil } return matchFetcher(obj), nil @@ -73,15 +74,15 @@ func (c *Condition) ResourceMatch(obj k8s.Object, matchFetcher func(object k8s.O // ResourceListN is a helper function that can be used to check for a minimum number of returned objects in a list. This function // accepts list options that can be used to adjust the set of objects queried for in the List resource operation. -func (c *Condition) ResourceListN(list k8s.ObjectList, n int, listOptions ...resources.ListOption) apimachinerywait.ConditionFunc { +func (c *Condition) ResourceListN(list k8s.ObjectList, n int, listOptions ...resources.ListOption) apimachinerywait.ConditionWithContextFunc { return c.ResourceListMatchN(list, n, func(object k8s.Object) bool { return true }, listOptions...) } // ResourceListMatchN is a helper function that can be used to check for a minimum number of returned objects in a list. This function // accepts list options and a match function that can be used to adjust the set of objects queried for in the List resource operation. -func (c *Condition) ResourceListMatchN(list k8s.ObjectList, n int, matchFetcher func(object k8s.Object) bool, listOptions ...resources.ListOption) apimachinerywait.ConditionFunc { - return func() (done bool, err error) { - if err := c.resources.List(context.TODO(), list, listOptions...); err != nil { +func (c *Condition) ResourceListMatchN(list k8s.ObjectList, n int, matchFetcher func(object k8s.Object) bool, listOptions ...resources.ListOption) apimachinerywait.ConditionWithContextFunc { + return func(ctx context.Context) (done bool, err error) { + if err = c.resources.List(ctx, list, listOptions...); err != nil { return false, nil } var found int @@ -102,22 +103,22 @@ func (c *Condition) ResourceListMatchN(list k8s.ObjectList, n int, matchFetcher // ResourcesFound is a helper function that can be used to check for a set of objects. This function accepts a list // of named objects and will wait until it is able to retrieve each. -func (c *Condition) ResourcesFound(list k8s.ObjectList) apimachinerywait.ConditionFunc { +func (c *Condition) ResourcesFound(list k8s.ObjectList) apimachinerywait.ConditionWithContextFunc { return c.ResourcesMatch(list, func(object k8s.Object) bool { return true }) } // ResourcesMatch is a helper function that can be used to check for a set of objects. This function accepts a list // of named objects and a match function, and will wait until it is able to retrieve each while passing the match validation. -func (c *Condition) ResourcesMatch(list k8s.ObjectList, matchFetcher func(object k8s.Object) bool) apimachinerywait.ConditionFunc { +func (c *Condition) ResourcesMatch(list k8s.ObjectList, matchFetcher func(object k8s.Object) bool) apimachinerywait.ConditionWithContextFunc { metaList, err := meta.ExtractList(list) if err != nil { - return func() (done bool, err error) { return false, err } + return func(ctx context.Context) (done bool, err error) { return false, err } } objects := make(map[k8s.Object]bool) for _, o := range metaList { obj, ok := o.(k8s.Object) if !ok { - return func() (done bool, err error) { + return func(ctx context.Context) (done bool, err error) { return false, fmt.Errorf("condition: unexpected type %T in list, does not satisfy k8s.Object", obj) } } @@ -125,11 +126,11 @@ func (c *Condition) ResourcesMatch(list k8s.ObjectList, matchFetcher func(object objects[obj] = false } } - return func() (done bool, err error) { + return func(ctx context.Context) (done bool, err error) { found := 0 for obj, created := range objects { if !created { - if err := c.resources.Get(context.TODO(), obj.GetName(), obj.GetNamespace(), obj); errors.IsNotFound(err) { + if err := c.resources.Get(ctx, obj.GetName(), obj.GetNamespace(), obj); errors.IsNotFound(err) { continue } else if err != nil { return false, err @@ -147,16 +148,16 @@ func (c *Condition) ResourcesMatch(list k8s.ObjectList, matchFetcher func(object // ResourcesDeleted is a helper function that can be used to check for if a set of objects has been deleted. This function // accepts a list of named objects and will wait until it is not able to find each. -func (c *Condition) ResourcesDeleted(list k8s.ObjectList) apimachinerywait.ConditionFunc { +func (c *Condition) ResourcesDeleted(list k8s.ObjectList) apimachinerywait.ConditionWithContextFunc { metaList, err := meta.ExtractList(list) if err != nil { - return func() (done bool, err error) { return false, err } + return func(ctx context.Context) (done bool, err error) { return false, err } } objects := make(map[k8s.Object]bool) for _, o := range metaList { obj, ok := o.(k8s.Object) if !ok { - return func() (done bool, err error) { + return func(ctx context.Context) (done bool, err error) { return false, fmt.Errorf("condition: unexpected type %T in list, does not satisfy k8s.Object", obj) } } @@ -164,10 +165,11 @@ func (c *Condition) ResourcesDeleted(list k8s.ObjectList) apimachinerywait.Condi objects[obj] = true } } - return func() (done bool, err error) { + return func(ctx context.Context) (done bool, err error) { for obj, created := range objects { if created { - if err := c.resources.Get(context.TODO(), obj.GetName(), obj.GetNamespace(), obj); errors.IsNotFound(err) { + log.V(4).InfoS("Checking for resource to be garbage collected", "resource", c.namespacedName(obj)) + if err := c.resources.Get(ctx, obj.GetName(), obj.GetNamespace(), obj); errors.IsNotFound(err) { delete(objects, obj) } else if err != nil { return false, err @@ -184,8 +186,8 @@ func (c *Condition) ResourcesDeleted(list k8s.ObjectList) apimachinerywait.Condi // // This method can be leveraged against any Kubernetes resource to check the deletion workflow and it does so by // checking the resource and waiting until it obtains a v1.StatusReasonNotFound error from the API -func (c *Condition) ResourceDeleted(obj k8s.Object) apimachinerywait.ConditionFunc { - return func() (done bool, err error) { +func (c *Condition) ResourceDeleted(obj k8s.Object) apimachinerywait.ConditionWithContextFunc { + return func(ctx context.Context) (done bool, err error) { log.V(4).InfoS("Checking for resource to be garbage collected", "resource", c.namespacedName(obj)) if err := c.resources.Get(context.Background(), obj.GetName(), obj.GetNamespace(), obj); err != nil { if errors.IsNotFound(err) { @@ -200,10 +202,10 @@ func (c *Condition) ResourceDeleted(obj k8s.Object) apimachinerywait.ConditionFu // JobConditionMatch is a helper function that can be used to check the Job Completion or runtime status against a // specific condition. This function accepts both conditionType and conditionState as argument and hence you can use this // to match both positive or negative cases with suitable values passed to the arguments. -func (c *Condition) JobConditionMatch(job k8s.Object, conditionType batchv1.JobConditionType, conditionState v1.ConditionStatus) apimachinerywait.ConditionFunc { - return func() (done bool, err error) { +func (c *Condition) JobConditionMatch(job k8s.Object, conditionType batchv1.JobConditionType, conditionState v1.ConditionStatus) apimachinerywait.ConditionWithContextFunc { + return func(ctx context.Context) (done bool, err error) { log.V(4).InfoS("Checking for condition match", "resource", c.namespacedName(job), "state", conditionState, "conditionType", conditionType) - if err := c.resources.Get(context.TODO(), job.GetName(), job.GetNamespace(), job); err != nil { + if err := c.resources.Get(ctx, job.GetName(), job.GetNamespace(), job); err != nil { return false, err } status := job.(*batchv1.Job).Status @@ -218,9 +220,9 @@ func (c *Condition) JobConditionMatch(job k8s.Object, conditionType batchv1.JobC } // DeploymentConditionMatch is a helper function that can be used to check a specific condition match for the Deployment in question. -func (c *Condition) DeploymentConditionMatch(deployment k8s.Object, conditionType appsv1.DeploymentConditionType, conditionState v1.ConditionStatus) apimachinerywait.ConditionFunc { - return func() (done bool, err error) { - if err := c.resources.Get(context.TODO(), deployment.GetName(), deployment.GetNamespace(), deployment); err != nil { +func (c *Condition) DeploymentConditionMatch(deployment k8s.Object, conditionType appsv1.DeploymentConditionType, conditionState v1.ConditionStatus) apimachinerywait.ConditionWithContextFunc { + return func(ctx context.Context) (done bool, err error) { + if err := c.resources.Get(ctx, deployment.GetName(), deployment.GetNamespace(), deployment); err != nil { return false, err } for _, cond := range deployment.(*appsv1.Deployment).Status.Conditions { @@ -234,10 +236,10 @@ func (c *Condition) DeploymentConditionMatch(deployment k8s.Object, conditionTyp // PodConditionMatch is a helper function that can be used to check a specific condition match for the Pod in question. // This is extended into a few simplified match helpers such as PodReady and ContainersReady as well. -func (c *Condition) PodConditionMatch(pod k8s.Object, conditionType v1.PodConditionType, conditionState v1.ConditionStatus) apimachinerywait.ConditionFunc { - return func() (done bool, err error) { +func (c *Condition) PodConditionMatch(pod k8s.Object, conditionType v1.PodConditionType, conditionState v1.ConditionStatus) apimachinerywait.ConditionWithContextFunc { + return func(ctx context.Context) (done bool, err error) { log.V(4).InfoS("Checking for condition match", "resource", c.namespacedName(pod), "state", conditionState, "conditionType", conditionType) - if err := c.resources.Get(context.TODO(), pod.GetName(), pod.GetNamespace(), pod); err != nil { + if err := c.resources.Get(ctx, pod.GetName(), pod.GetNamespace(), pod); err != nil { return false, err } status := pod.(*v1.Pod).Status @@ -254,8 +256,8 @@ func (c *Condition) PodConditionMatch(pod k8s.Object, conditionType v1.PodCondit // PodPhaseMatch is a helper function that is used to check and see if the Pod Has reached a specific Phase of the // runtime. This can be combined with PodConditionMatch to check if a specific condition and phase has been met. // This will enable validation such as checking against CLB of a POD. -func (c *Condition) PodPhaseMatch(pod k8s.Object, phase v1.PodPhase) apimachinerywait.ConditionFunc { - return func() (done bool, err error) { +func (c *Condition) PodPhaseMatch(pod k8s.Object, phase v1.PodPhase) apimachinerywait.ConditionWithContextFunc { + return func(ctx context.Context) (done bool, err error) { log.V(4).InfoS("Checking for phase match", "resource", c.namespacedName(pod), "phase", phase) if err := c.resources.Get(context.Background(), pod.GetName(), pod.GetNamespace(), pod); err != nil { return false, err @@ -266,28 +268,38 @@ func (c *Condition) PodPhaseMatch(pod k8s.Object, phase v1.PodPhase) apimachiner } // PodReady is a helper function used to check if the pod condition v1.PodReady has reached v1.ConditionTrue state -func (c *Condition) PodReady(pod k8s.Object) apimachinerywait.ConditionFunc { +func (c *Condition) PodReady(pod k8s.Object) apimachinerywait.ConditionWithContextFunc { return c.PodConditionMatch(pod, v1.PodReady, v1.ConditionTrue) } // ContainersReady is a helper function used to check if the pod condition v1.ContainersReady has reached v1.ConditionTrue -func (c *Condition) ContainersReady(pod k8s.Object) apimachinerywait.ConditionFunc { +func (c *Condition) ContainersReady(pod k8s.Object) apimachinerywait.ConditionWithContextFunc { return c.PodConditionMatch(pod, v1.ContainersReady, v1.ConditionTrue) } // PodRunning is a helper function used to check if the pod.Status.Phase attribute of the Pod has reached v1.PodRunning -func (c *Condition) PodRunning(pod k8s.Object) apimachinerywait.ConditionFunc { +func (c *Condition) PodRunning(pod k8s.Object) apimachinerywait.ConditionWithContextFunc { return c.PodPhaseMatch(pod, v1.PodRunning) } // JobCompleted is a helper function used to check if the Job has been completed successfully by checking if the // batchv1.JobCompleted has reached the v1.ConditionTrue state -func (c *Condition) JobCompleted(job k8s.Object) apimachinerywait.ConditionFunc { +func (c *Condition) JobCompleted(job k8s.Object) apimachinerywait.ConditionWithContextFunc { return c.JobConditionMatch(job, batchv1.JobComplete, v1.ConditionTrue) } // JobFailed is a helper function used to check if the Job has failed by checking if the batchv1.JobFailed has reached // v1.ConditionTrue state -func (c *Condition) JobFailed(job k8s.Object) apimachinerywait.ConditionFunc { +func (c *Condition) JobFailed(job k8s.Object) apimachinerywait.ConditionWithContextFunc { return c.JobConditionMatch(job, batchv1.JobFailed, v1.ConditionTrue) } + +// DeploymentAvailable is a helper function used to check if the deployment condition appsv1.DeploymentAvailable +// has reached v1.ConditionTrue state +func (c *Condition) DeploymentAvailable(name, namespace string) apimachinerywait.ConditionWithContextFunc { + return c.DeploymentConditionMatch( + &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}}, + appsv1.DeploymentAvailable, + v1.ConditionTrue, + ) +} diff --git a/vendor/sigs.k8s.io/e2e-framework/klient/wait/wait.go b/vendor/sigs.k8s.io/e2e-framework/klient/wait/wait.go index 4b219f34d..930f97947 100644 --- a/vendor/sigs.k8s.io/e2e-framework/klient/wait/wait.go +++ b/vendor/sigs.k8s.io/e2e-framework/klient/wait/wait.go @@ -17,6 +17,7 @@ limitations under the License. package wait import ( + "context" "time" apimachinerywait "k8s.io/apimachinery/pkg/util/wait" @@ -34,7 +35,7 @@ type Options struct { // to be met. Timeout time.Duration // StopChan is used to setup a wait mechanism using the apimachinerywait.PollUntil method - StopChan chan struct{} + Ctx context.Context // Immediate is used to indicate if the apimachinerywait's immediate wait method are to be // called instead of the regular one Immediate bool @@ -59,13 +60,12 @@ func WithInterval(interval time.Duration) Option { } } -// WithStopChannel provides a way to configure a Stop channel that can be used to run wait condition checks -// either until the condition has been successfully met or until the channel has been closed. This will enable +// WithContext provides a way to configure a context that can be used to cancel the wait condition checks. This will enable // end users to write test in cases where the max timeout is not really predictable or is a factor of a different // configuration or event. -func WithStopChannel(stopChan chan struct{}) Option { +func WithContext(ctx context.Context) Option { return func(options *Options) { - options.StopChan = stopChan + options.Ctx = ctx } } @@ -84,28 +84,24 @@ func WithImmediate() Option { // The conditions sub-packages provides a series of pre-defined wait functions that can be used by the developers // or a custom wait function can be passed as an argument to get a similar functionality if the check required // for your test is not already provided by the helper utility. -func For(conditionFunc apimachinerywait.ConditionFunc, opts ...Option) error { +func For(conditionFunc apimachinerywait.ConditionWithContextFunc, opts ...Option) error { options := &Options{ Interval: defaultPollInterval, Timeout: defaultPollTimeout, - StopChan: nil, + Ctx: nil, Immediate: false, } - + var cancel context.CancelFunc for _, fn := range opts { fn(options) } - // Setting the options.StopChan will force the usage of `PollUntil` - if options.StopChan != nil { - if options.Immediate { - return apimachinerywait.PollImmediateUntil(options.Interval, conditionFunc, options.StopChan) - } - return apimachinerywait.PollUntil(options.Interval, conditionFunc, options.StopChan) + if options.Ctx == nil { + options.Ctx, cancel = context.WithTimeout(context.Background(), options.Timeout) + defer cancel() } - if options.Immediate { - return apimachinerywait.PollImmediate(options.Interval, options.Timeout, conditionFunc) + return apimachinerywait.PollUntilContextCancel(options.Ctx, options.Interval, true, conditionFunc) } - return apimachinerywait.Poll(options.Interval, options.Timeout, conditionFunc) + return apimachinerywait.PollUntilContextCancel(options.Ctx, options.Interval, false, conditionFunc) } diff --git a/vendor/sigs.k8s.io/e2e-framework/pkg/env/action.go b/vendor/sigs.k8s.io/e2e-framework/pkg/env/action.go index 25f707fa6..1d236c7a3 100644 --- a/vendor/sigs.k8s.io/e2e-framework/pkg/env/action.go +++ b/vendor/sigs.k8s.io/e2e-framework/pkg/env/action.go @@ -21,12 +21,15 @@ import ( "fmt" "testing" + "k8s.io/klog/v2" "sigs.k8s.io/e2e-framework/pkg/envconf" "sigs.k8s.io/e2e-framework/pkg/internal/types" ) +type actionRole uint8 + const ( - roleSetup = iota + roleSetup actionRole = iota roleBeforeTest roleBeforeFeature roleAfterFeature @@ -34,6 +37,25 @@ const ( roleFinish ) +func (r actionRole) String() string { + switch r { + case roleSetup: + return "Setup" + case roleBeforeTest: + return "BeforeEachTest" + case roleBeforeFeature: + return "BeforeEachFeature" + case roleAfterFeature: + return "AfterEachFeature" + case roleAfterTest: + return "AfterEachTest" + case roleFinish: + return "Finish" + default: + panic("unknown role") // this should never happen + } +} + // action a group env functions type action struct { role actionRole @@ -52,6 +74,10 @@ type action struct { func (a *action) runWithT(ctx context.Context, cfg *envconf.Config, t *testing.T) (context.Context, error) { switch a.role { case roleBeforeTest, roleAfterTest: + if cfg.DryRunMode() { + klog.V(2).Info("Skipping execution of roleBeforeTest and roleAfterTest due to framework being in dry-run mode") + return ctx, nil + } for _, f := range a.testFuncs { if f == nil { continue @@ -74,6 +100,10 @@ func (a *action) runWithT(ctx context.Context, cfg *envconf.Config, t *testing.T func (a *action) runWithFeature(ctx context.Context, cfg *envconf.Config, t *testing.T, fi types.Feature) (context.Context, error) { switch a.role { case roleBeforeFeature, roleAfterFeature: + if cfg.DryRunMode() { + klog.V(2).Info("Skipping execution of roleBeforeFeature and roleAfterFeature due to framework being in dry-run mode") + return ctx, nil + } for _, f := range a.featureFuncs { if f == nil { continue @@ -92,6 +122,10 @@ func (a *action) runWithFeature(ctx context.Context, cfg *envconf.Config, t *tes } func (a *action) run(ctx context.Context, cfg *envconf.Config) (context.Context, error) { + if cfg.DryRunMode() { + klog.V(2).InfoS("Skipping processing of action due to framework being in dry-run mode") + return ctx, nil + } for _, f := range a.funcs { if f == nil { continue diff --git a/vendor/sigs.k8s.io/e2e-framework/pkg/env/env.go b/vendor/sigs.k8s.io/e2e-framework/pkg/env/env.go index 1d3119a93..72e1a0511 100644 --- a/vendor/sigs.k8s.io/e2e-framework/pkg/env/env.go +++ b/vendor/sigs.k8s.io/e2e-framework/pkg/env/env.go @@ -21,12 +21,12 @@ package env import ( "context" "fmt" - "math/rand" + "regexp" + "runtime/debug" "sync" "testing" - "time" - log "k8s.io/klog/v2" + "k8s.io/klog/v2" "sigs.k8s.io/e2e-framework/pkg/envconf" "sigs.k8s.io/e2e-framework/pkg/features" @@ -37,15 +37,13 @@ type ( Environment = types.Environment Func = types.EnvFunc FeatureFunc = types.FeatureEnvFunc - - actionRole uint8 + TestFunc = types.TestEnvFunc ) type testEnv struct { ctx context.Context cfg *envconf.Config actions []action - rnd rand.Source } // New creates a test environment with no config attached. @@ -64,6 +62,15 @@ func NewWithConfig(cfg *envconf.Config) types.Environment { return env } +// NewFromFlags creates a test environment using configuration values from CLI flags +func NewFromFlags() (types.Environment, error) { + cfg, err := envconf.NewFromFlags() + if err != nil { + return nil, err + } + return NewWithConfig(cfg), nil +} + // NewWithKubeConfig creates an environment using an Environment Configuration value // and the given kubeconfig. func NewWithKubeConfig(kubeconfigfile string) types.Environment { @@ -97,7 +104,6 @@ func newTestEnv() *testEnv { return &testEnv{ ctx: context.Background(), cfg: envconf.New(), - rnd: rand.NewSource(time.Now().UnixNano()), } } @@ -182,40 +188,44 @@ func (e *testEnv) panicOnMissingContext() { // processTestActions is used to run a series of test action that were configured as // BeforeEachTest or AfterEachTest -func (e *testEnv) processTestActions(t *testing.T, actions []action) { +func (e *testEnv) processTestActions(ctx context.Context, t *testing.T, actions []action) context.Context { var err error + out := ctx for _, action := range actions { - if e.ctx, err = action.runWithT(e.ctx, e.cfg, t); err != nil { - t.Fatalf("BeforeEachTest failure: %s", err) + out, err = action.runWithT(ctx, e.cfg, t) + if err != nil { + t.Fatalf("%s failure: %s", action.role, err) } } + return out } // processTestFeature is used to trigger the execution of the actual feature. This function wraps the entire // workflow of orchestrating the feature execution be running the action configured by BeforeEachFeature / // AfterEachFeature. -func (e *testEnv) processTestFeature(t *testing.T, featureName string, feature types.Feature) { - var err error - - // execute each feature - beforeFeatureActions := e.getBeforeFeatureActions() - afterFeatureActions := e.getAfterFeatureActions() - - for _, action := range beforeFeatureActions { - if e.ctx, err = action.runWithFeature(e.ctx, e.cfg, t, deepCopyFeature(feature)); err != nil { - t.Fatalf("BeforeEachTest failure: %s", err) - } - } +func (e *testEnv) processTestFeature(ctx context.Context, t *testing.T, featureName string, feature types.Feature) context.Context { + // execute beforeEachFeature actions + ctx = e.processFeatureActions(ctx, t, feature, e.getBeforeFeatureActions()) // execute feature test - e.ctx = e.execFeature(e.ctx, t, featureName, feature) + ctx = e.execFeature(ctx, t, featureName, feature) - // execute beforeFeature actions - for _, action := range afterFeatureActions { - if e.ctx, err = action.runWithFeature(e.ctx, e.cfg, t, deepCopyFeature(feature)); err != nil { - t.Fatalf("BeforeEachTest failure: %s", err) + // execute afterEachFeature actions + return e.processFeatureActions(ctx, t, feature, e.getAfterFeatureActions()) +} + +// processFeatureActions is used to run a series of feature action that were configured as +// BeforeEachFeature or AfterEachFeature +func (e *testEnv) processFeatureActions(ctx context.Context, t *testing.T, feature types.Feature, actions []action) context.Context { + var err error + out := ctx + for _, action := range actions { + out, err = action.runWithFeature(out, e.cfg, t, deepCopyFeature(feature)) + if err != nil { + t.Fatalf("%s failure: %s", action.role, err) } } + return out } // processTests is a wrapper function that can be invoked by either Test or TestInParallel methods. @@ -224,43 +234,54 @@ func (e *testEnv) processTestFeature(t *testing.T, featureName string, feature t // // In case if the parallel run of test features are enabled, this function will invoke the processTestFeature // as a go-routine to get them to run in parallel -func (e *testEnv) processTests(t *testing.T, enableParallelRun bool, testFeatures ...types.Feature) { - e.panicOnMissingContext() +func (e *testEnv) processTests(ctx context.Context, t *testing.T, enableParallelRun bool, testFeatures ...types.Feature) context.Context { + if e.cfg.DryRunMode() { + klog.V(2).Info("e2e-framework is being run in dry-run mode. This will skip all the before/after step functions configured around your test assessments and features") + } + if ctx == nil { + panic("nil context") // this should never happen + } if len(testFeatures) == 0 { t.Log("No test testFeatures provided, skipping test") - return + return ctx } beforeTestActions := e.getBeforeTestActions() afterTestActions := e.getAfterTestActions() - e.processTestActions(t, beforeTestActions) - runInParallel := e.cfg.ParallelTestEnabled() && enableParallelRun if runInParallel { - log.V(4).Info("Running test features in parallel") + klog.V(4).Info("Running test features in parallel") } + ctx = e.processTestActions(ctx, t, beforeTestActions) + var wg sync.WaitGroup for i, feature := range testFeatures { + featureCopy := feature featName := feature.Name() if featName == "" { featName = fmt.Sprintf("Feature-%d", i+1) } if runInParallel { wg.Add(1) - go func(w *sync.WaitGroup) { + go func(ctx context.Context, w *sync.WaitGroup, featName string, f types.Feature) { defer w.Done() - e.processTestFeature(t, featName, feature) - }(&wg) + _ = e.processTestFeature(ctx, t, featName, f) + }(ctx, &wg, featName, featureCopy) } else { - e.processTestFeature(t, featName, feature) + ctx = e.processTestFeature(ctx, t, featName, featureCopy) + // In case if the feature under test has failed, skip reset of the features + // that are part of the same test + if e.cfg.FailFast() && t.Failed() { + break + } } } if runInParallel { wg.Wait() } - e.processTestActions(t, afterTestActions) + return e.processTestActions(ctx, t, afterTestActions) } // TestInParallel executes a series a feature tests from within a @@ -281,8 +302,8 @@ func (e *testEnv) processTests(t *testing.T, enableParallelRun bool, testFeature // set of features being passed to this call while the feature themselves // are executed in parallel to avoid duplication of action that might happen // in BeforeTest and AfterTest actions -func (e *testEnv) TestInParallel(t *testing.T, testFeatures ...types.Feature) { - e.processTests(t, true, testFeatures...) +func (e *testEnv) TestInParallel(t *testing.T, testFeatures ...types.Feature) context.Context { + return e.processTests(e.ctx, t, true, testFeatures...) } // Test executes a feature test from within a TestXXX function. @@ -297,8 +318,8 @@ func (e *testEnv) TestInParallel(t *testing.T, testFeatures ...types.Feature) { // // BeforeTest and AfterTest operations are executed before and after // the feature is tested respectively. -func (e *testEnv) Test(t *testing.T, testFeatures ...types.Feature) { - e.processTests(t, false, testFeatures...) +func (e *testEnv) Test(t *testing.T, testFeatures ...types.Feature) context.Context { + return e.processTests(e.ctx, t, false, testFeatures...) } // Finish registers funcs that are executed at the end of the @@ -317,35 +338,47 @@ func (e *testEnv) Finish(funcs ...Func) types.Environment { // package. This method will all Env.Setup operations prior to // starting the tests and run all Env.Finish operations after // before completing the suite. -// func (e *testEnv) Run(m *testing.M) int { - if e.ctx == nil { - panic("context not set") // something is terribly wrong. - } + e.panicOnMissingContext() + ctx := e.ctx setups := e.getSetupActions() // fail fast on setup, upon err exit var err error - for _, setup := range setups { - // context passed down to each setup - if e.ctx, err = setup.run(e.ctx, e.cfg); err != nil { - log.Fatal(err) + + defer func() { + // Recover and see if the panic handler is disabled. If it is disabled, panic and stop the workflow. + // Otherwise, log and continue with running the Finish steps of the Test suite + rErr := recover() + if rErr != nil { + if e.cfg.DisableGracefulTeardown() { + panic(rErr) + } + klog.Errorf("Recovering from panic and running finish actions: %s, stack: %s", rErr, string(debug.Stack())) } - } - exitCode := m.Run() // exec test suite + finishes := e.getFinishActions() + // attempt to gracefully clean up. + // Upon error, log and continue. + for _, fin := range finishes { + // context passed down to each finish step + if ctx, err = fin.run(ctx, e.cfg); err != nil { + klog.V(2).ErrorS(err, "Cleanup failed", "action", fin.role) + } + } + e.ctx = ctx + }() - finishes := e.getFinishActions() - // attempt to gracefully clean up. - // Upon error, log and continue. - for _, fin := range finishes { - // context passed down to each finish step - if e.ctx, err = fin.run(e.ctx, e.cfg); err != nil { - log.V(2).ErrorS(err, "Finish action handlers") + for _, setup := range setups { + // context passed down to each setup + if ctx, err = setup.run(ctx, e.cfg); err != nil { + klog.Fatalf("%s failure: %s", setup.role, err) } } + e.ctx = ctx - return exitCode + // Execute the test suite + return m.Run() } func (e *testEnv) getActionsByRole(r actionRole) []action { @@ -387,78 +420,156 @@ func (e *testEnv) getFinishActions() []action { return e.getActionsByRole(roleFinish) } +func (e *testEnv) executeSteps(ctx context.Context, t *testing.T, steps []types.Step) context.Context { + if e.cfg.DryRunMode() { + return ctx + } + for _, setup := range steps { + ctx = setup.Func()(ctx, t, e.cfg) + } + return ctx +} + func (e *testEnv) execFeature(ctx context.Context, t *testing.T, featName string, f types.Feature) context.Context { // feature-level subtest - t.Run(featName, func(t *testing.T) { - // skip feature which matches with --skip-feature - if e.cfg.SkipFeatureRegex() != nil && e.cfg.SkipFeatureRegex().MatchString(featName) { - t.Skipf(`Skipping feature "%s": name matched`, featName) - } - - // skip feature which does not match with --feature - if e.cfg.FeatureRegex() != nil && !e.cfg.FeatureRegex().MatchString(featName) { - t.Skipf(`Skipping feature "%s": name not matched`, featName) - } - - // skip if labels does not match - // run tests if --labels values matches the feature labels - for k, v := range e.cfg.Labels() { - if f.Labels()[k] != v { - t.Skipf(`Skipping feature "%s": unmatched label "%s=%s"`, featName, k, f.Labels()[k]) - } + t.Run(featName, func(newT *testing.T) { + skipped, message := e.requireFeatureProcessing(f) + if skipped { + newT.Skipf(message) } - // skip running a feature if labels matches with --skip-labels - for k, v := range e.cfg.SkipLabels() { - if f.Labels()[k] == v { - t.Skipf(`Skipping feature "%s": matched label provided in --skip-lables "%s=%s"`, featName, k, f.Labels()[k]) - } + if fDescription, ok := f.(types.DescribableFeature); ok && fDescription.Description() != "" { + t.Logf("Processing Feature: %s", fDescription.Description()) } // setups run at feature-level setups := features.GetStepsByLevel(f.Steps(), types.LevelSetup) - for _, setup := range setups { - ctx = setup.Func()(ctx, t, e.cfg) - } + ctx = e.executeSteps(ctx, newT, setups) // assessments run as feature/assessment sub level assessments := features.GetStepsByLevel(f.Steps(), types.LevelAssess) + failed := false for i, assess := range assessments { assessName := assess.Name() + if dAssess, ok := assess.(types.DescribableStep); ok && dAssess.Description() != "" { + t.Logf("Processing Assessment: %s", dAssess.Description()) + } if assessName == "" { assessName = fmt.Sprintf("Assessment-%d", i+1) } - t.Run(assessName, func(t *testing.T) { - // skip assessments which matches with --skip-assessments - if e.cfg.SkipAssessmentRegex() != nil && e.cfg.SkipAssessmentRegex().MatchString(assess.Name()) { - t.Skipf(`Skipping assessment "%s": name matched`, assess.Name()) - } - - // skip assessments which does not matches with --assess - if e.cfg.AssessmentRegex() != nil && !e.cfg.AssessmentRegex().MatchString(assess.Name()) { - t.Skipf(`Skipping assessment "%s": name not matched`, assess.Name()) + newT.Run(assessName, func(internalT *testing.T) { + skipped, message := e.requireAssessmentProcessing(assess, i+1) + if skipped { + internalT.Skipf(message) } - ctx = assess.Func()(ctx, t, e.cfg) + ctx = e.executeSteps(ctx, internalT, []types.Step{assess}) }) + // Check if the Test assessment under question performed a `t.Fail()` or `t.Failed()` invocation. + // We need to track that and stop the next set of assessment in the feature under test from getting + // executed + if e.cfg.FailFast() && newT.Failed() { + failed = true + break + } + } + + // Let us fail the test fast and not run the teardown in case if the framework specific fail-fast mode is + // invoked to make sure we leave the traces of the failed test behind to enable better debugging for the + // test developers + if e.cfg.FailFast() && failed { + newT.FailNow() } // teardowns run at feature-level teardowns := features.GetStepsByLevel(f.Steps(), types.LevelTeardown) - for _, teardown := range teardowns { - ctx = teardown.Func()(ctx, t, e.cfg) - } + ctx = e.executeSteps(ctx, newT, teardowns) }) return ctx } +// requireFeatureProcessing is a wrapper around the requireProcessing function to process the feature level validation +func (e *testEnv) requireFeatureProcessing(f types.Feature) (skip bool, message string) { + requiredRegexp := e.cfg.FeatureRegex() + skipRegexp := e.cfg.SkipFeatureRegex() + return e.requireProcessing("feature", f.Name(), requiredRegexp, skipRegexp, f.Labels()) +} + +// requireAssessmentProcessing is a wrapper around the requireProcessing function to process the Assessment level validation +func (e *testEnv) requireAssessmentProcessing(a types.Step, assessmentIndex int) (skip bool, message string) { + requiredRegexp := e.cfg.AssessmentRegex() + skipRegexp := e.cfg.SkipAssessmentRegex() + assessmentName := a.Name() + if assessmentName == "" { + assessmentName = fmt.Sprintf("Assessment-%d", assessmentIndex) + } + return e.requireProcessing("assessment", assessmentName, requiredRegexp, skipRegexp, nil) +} + +// requireProcessing is a utility function that can be used to make a decision on if a specific Test assessment or feature needs to be +// processed or not. +// testName argument indicate the Feature Name or test Name that can be mapped against the skip or include regex flags +// to decide if the entity in question will need processing. +// This function also perform a label check against include/skip labels to make sure only those features to make sure +// we can filter out all the non-required features during the test execution +func (e *testEnv) requireProcessing(kind, testName string, requiredRegexp, skipRegexp *regexp.Regexp, labels types.Labels) (skip bool, message string) { + if requiredRegexp != nil && !requiredRegexp.MatchString(testName) { + skip = true + message = fmt.Sprintf(`Skipping %s "%s": name not matched`, kind, testName) + return skip, message + } + if skipRegexp != nil && skipRegexp.MatchString(testName) { + skip = true + message = fmt.Sprintf(`Skipping %s: "%s": name matched`, kind, testName) + return skip, message + } + + if labels != nil { + // only run a feature if all its label keys and values match those specified + // with --labels + matches := 0 + for key, vals := range e.cfg.Labels() { + for _, v := range vals { + if labels.Contains(key, v) { + matches++ + break // continue with next key + } + } + } + + if len(e.cfg.Labels()) != matches { + skip = true + var kvs []string + for k, v := range labels { + kvs = append(kvs, fmt.Sprintf("%s=%s", k, v)) // prettify output + } + message = fmt.Sprintf(`Skipping feature "%s": unmatched labels "%s"`, testName, kvs) + return skip, message + } + + // skip running a feature if labels matches with --skip-labels + for key, vals := range e.cfg.SkipLabels() { + for _, v := range vals { + if labels.Contains(key, v) { + skip = true + message = fmt.Sprintf(`Skipping feature "%s": matched label provided in --skip-lables "%s=%s"`, testName, key, labels[key]) + return skip, message + } + } + } + } + return skip, message +} + // deepCopyFeature just copies the values from the Feature but creates a deep // copy to avoid mutation when we just want an informational copy. func deepCopyFeature(f types.Feature) types.Feature { fcopy := features.New(f.Name()) - for k, v := range f.Labels() { - fcopy = fcopy.WithLabel(k, v) + for k, vals := range f.Labels() { + for _, v := range vals { + fcopy = fcopy.WithLabel(k, v) + } } f.Steps() for _, step := range f.Steps() { diff --git a/vendor/sigs.k8s.io/e2e-framework/pkg/envconf/config.go b/vendor/sigs.k8s.io/e2e-framework/pkg/envconf/config.go index 4b154487c..1534c5646 100644 --- a/vendor/sigs.k8s.io/e2e-framework/pkg/envconf/config.go +++ b/vendor/sigs.k8s.io/e2e-framework/pkg/envconf/config.go @@ -31,16 +31,20 @@ import ( // Config represents and environment configuration type Config struct { - client klient.Client - kubeconfig string - namespace string - assessmentRegex *regexp.Regexp - featureRegex *regexp.Regexp - labels map[string]string - skipFeatureRegex *regexp.Regexp - skipLabels map[string]string - skipAssessmentRegex *regexp.Regexp - parallelTests bool + client klient.Client + kubeconfig string + namespace string + assessmentRegex *regexp.Regexp + featureRegex *regexp.Regexp + labels flags.LabelsMap + skipFeatureRegex *regexp.Regexp + skipLabels flags.LabelsMap + skipAssessmentRegex *regexp.Regexp + parallelTests bool + dryRun bool + failFast bool + disableGracefulTeardown bool + kubeContext string } // New creates and initializes an empty environment configuration @@ -79,6 +83,10 @@ func NewFromFlags() (*Config, error) { } e.skipLabels = envFlags.SkipLabels() e.parallelTests = envFlags.Parallel() + e.dryRun = envFlags.DryRun() + e.failFast = envFlags.FailFast() + e.disableGracefulTeardown = envFlags.DisableGracefulTeardown() + e.kubeContext = envFlags.KubeContext() return e, nil } @@ -112,12 +120,13 @@ func (c *Config) NewClient() (klient.Client, error) { return nil, fmt.Errorf("envconfig: client failed: %w", err) } c.client = client + return c.client, nil } // Client is a constructor function that returns a previously -// created klient.Client or create a new one based on configuration -// previously set. Willpanic on any error so it recommended that you +// created klient.Client or creates a new one based on configuration +// previously set. Will panic on any error so it is recommended that you // are confident in the configuration or call NewClient() to ensure its // safe creation. func (c *Config) Client() klient.Client { @@ -196,36 +205,88 @@ func (c *Config) SkipFeatureRegex() *regexp.Regexp { } // WithLabels sets the environment label filters -func (c *Config) WithLabels(lbls map[string]string) *Config { +func (c *Config) WithLabels(lbls map[string][]string) *Config { c.labels = lbls return c } // Labels returns the environment's label filters -func (c *Config) Labels() map[string]string { +func (c *Config) Labels() map[string][]string { return c.labels } // WithSkipLabels sets the environment label filters -func (c *Config) WithSkipLabels(lbls map[string]string) *Config { +func (c *Config) WithSkipLabels(lbls map[string][]string) *Config { c.skipLabels = lbls return c } // SkipLabels returns the environment's label filters -func (c *Config) SkipLabels() map[string]string { +func (c *Config) SkipLabels() map[string][]string { return c.skipLabels } +// WithParallelTestEnabled can be used to enable parallel run of the test +// features func (c *Config) WithParallelTestEnabled() *Config { c.parallelTests = true return c } +// ParallelTestEnabled indicates if the test features are being run in +// parallel or not func (c *Config) ParallelTestEnabled() bool { return c.parallelTests } +func (c *Config) WithDryRunMode() *Config { + c.dryRun = true + return c +} + +func (c *Config) DryRunMode() bool { + return c.dryRun +} + +// WithFailFast can be used to enable framework specific fail fast mode +// that controls the test execution of the features and assessments under +// test +func (c *Config) WithFailFast() *Config { + c.failFast = true + return c +} + +// FailFast indicate if the framework is running in fail fast mode. This +// controls the behavior of how the assessments and features are handled +// if a test encounters a failure result +func (c *Config) FailFast() bool { + return c.failFast +} + +// WithDisableGracefulTeardown can be used to programmatically disabled the panic +// recovery enablement on test startup. This will prevent test Finish steps +// from being executed on panic +func (c *Config) WithDisableGracefulTeardown() *Config { + c.disableGracefulTeardown = true + return c +} + +// DisableGracefulTeardown is used to check the panic recovery handler should be enabled +func (c *Config) DisableGracefulTeardown() bool { + return c.disableGracefulTeardown +} + +// WithKubeContext is used to set the kubeconfig context +func (c *Config) WithKubeContext(kubeContext string) *Config { + c.kubeContext = kubeContext + return c +} + +// WithKubeContext is used to get the kubeconfig context +func (c *Config) KubeContext() string { + return c.kubeContext +} + func randNS() string { return RandomName("testns-", 32) } diff --git a/vendor/sigs.k8s.io/e2e-framework/pkg/envfuncs/kind_funcs.go b/vendor/sigs.k8s.io/e2e-framework/pkg/envfuncs/kind_funcs.go index a70e563a8..66f621bb5 100644 --- a/vendor/sigs.k8s.io/e2e-framework/pkg/envfuncs/kind_funcs.go +++ b/vendor/sigs.k8s.io/e2e-framework/pkg/envfuncs/kind_funcs.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2023 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,184 +18,38 @@ package envfuncs import ( "context" - "fmt" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/e2e-framework/klient" - "sigs.k8s.io/e2e-framework/klient/k8s/resources" - "sigs.k8s.io/e2e-framework/klient/wait" - "sigs.k8s.io/e2e-framework/klient/wait/conditions" "sigs.k8s.io/e2e-framework/pkg/env" - "sigs.k8s.io/e2e-framework/pkg/envconf" "sigs.k8s.io/e2e-framework/support/kind" ) -type kindContextKey string - -// CreateKindCluster returns an env.Func that is used to -// create a kind cluster that is then injected in the context -// using the name as a key. -// -// NOTE: the returned function will update its env config with the -// kubeconfig file for the config client. -// -func CreateKindCluster(clusterName string) env.Func { - return func(ctx context.Context, cfg *envconf.Config) (context.Context, error) { - k := kind.NewCluster(clusterName) - kubecfg, err := k.Create() - if err != nil { - return ctx, err - } - - // update envconfig with kubeconfig - cfg.WithKubeconfigFile(kubecfg) - - // stall, wait for pods initializations - if err := waitForControlPlane(cfg.Client()); err != nil { - return ctx, err - } - - // store entire cluster value in ctx for future access using the cluster name - return context.WithValue(ctx, kindContextKey(clusterName), k), nil +// Deprecated: This handler has been deprecated in favor of GetClusterFromContext +func GetKindClusterFromContext(ctx context.Context, clusterName string) (*kind.Cluster, bool) { + provider, ok := GetClusterFromContext(ctx, clusterName) + if ok { + return provider.(*kind.Cluster), ok } + return nil, ok } -// CreateKindClusterWithConfig returns an env.Func that is used to -// create a kind cluster that is then injected in the context -// using the name as a key. -// -// NOTE: the returned function will update its env config with the -// kubeconfig file for the config client. -// -func CreateKindClusterWithConfig(clusterName, image, configFilePath string) env.Func { - return func(ctx context.Context, cfg *envconf.Config) (context.Context, error) { - k := kind.NewCluster(clusterName) - kubecfg, err := k.CreateWithConfig(image, configFilePath) - if err != nil { - return ctx, err - } - - // update envconfig with kubeconfig - cfg.WithKubeconfigFile(kubecfg) - - // stall, wait for pods initializations - if err := waitForControlPlane(cfg.Client()); err != nil { - return ctx, err - } - - // store entire cluster value in ctx for future access using the cluster name - return context.WithValue(ctx, kindContextKey(clusterName), k), nil - } +// Deprecated: This handler has been deprecated in favor of CreateCluster which can now accept +// support.ClusterProvider type as input in order to setup the cluster using right providers +func CreateKindCluster(clusterName string) env.Func { + return CreateCluster(kind.NewProvider(), clusterName) } -func waitForControlPlane(client klient.Client) error { - r, err := resources.New(client.RESTConfig()) - if err != nil { - return err - } - selector, err := metav1.LabelSelectorAsSelector( - &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - {Key: "component", Operator: metav1.LabelSelectorOpIn, Values: []string{"etcd", "kube-apiserver", "kube-controller-manager", "kube-scheduler"}}, - }, - }, - ) - if err != nil { - return err - } - // a kind cluster with one control-plane node will have 4 pods running the core apiserver components - err = wait.For(conditions.New(r).ResourceListN(&v1.PodList{}, 4, resources.WithLabelSelector(selector.String()))) - if err != nil { - return err - } - selector, err = metav1.LabelSelectorAsSelector( - &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - {Key: "k8s-app", Operator: metav1.LabelSelectorOpIn, Values: []string{"kindnet", "kube-dns", "kube-proxy"}}, - }, - }, - ) - if err != nil { - return err - } - // a kind cluster with one control-plane node will have 4 k8s-app pods running networking components - err = wait.For(conditions.New(r).ResourceListN(&v1.PodList{}, 4, resources.WithLabelSelector(selector.String()))) - if err != nil { - return err - } - return nil +// Deprecated: This handler has been deprecated in favor of CreateClusterWithConfig which can now accept +// support.ClusterProvider type as input in order to setup the cluster using right providers +func CreateKindClusterWithConfig(clusterName, image, configFilePath string) env.Func { + return CreateClusterWithConfig(kind.NewProvider(), clusterName, configFilePath, kind.WithImage(image)) } -// DestroyKindCluster returns an EnvFunc that -// retrieves a previously saved kind Cluster in the context (using the name), then deletes it. -// -// NOTE: this should be used in a Environment.Finish step. -// +// Deprecated: This handler has been deprecated in favor of DestroyCluster func DestroyKindCluster(name string) env.Func { - return func(ctx context.Context, cfg *envconf.Config) (context.Context, error) { - clusterVal := ctx.Value(kindContextKey(name)) - if clusterVal == nil { - return ctx, fmt.Errorf("destroy kind cluster func: context cluster is nil") - } - - cluster, ok := clusterVal.(*kind.Cluster) - if !ok { - return ctx, fmt.Errorf("destroy kind cluster func: unexpected type for cluster value") - } - - if err := cluster.Destroy(); err != nil { - return ctx, fmt.Errorf("destroy kind cluster: %w", err) - } - - return ctx, nil - } + return DestroyCluster(name) } -// LoadDockerImageToCluster returns an EnvFunc that -// retrieves a previously saved kind Cluster in the context (using the name), and then loads a docker image -// from the host into the cluster. -// -func LoadDockerImageToCluster(name, image string) env.Func { - return func(ctx context.Context, cfg *envconf.Config) (context.Context, error) { - clusterVal := ctx.Value(kindContextKey(name)) - if clusterVal == nil { - return ctx, fmt.Errorf("load docker image func: context cluster is nil") - } - - cluster, ok := clusterVal.(*kind.Cluster) - if !ok { - return ctx, fmt.Errorf("load docker image func: unexpected type for cluster value") - } - - if err := cluster.LoadDockerImage(image); err != nil { - return ctx, fmt.Errorf("load docker image: %w", err) - } - - return ctx, nil - } -} - -// LoadImageArchiveToCluster returns an EnvFunc that -// retrieves a previously saved kind Cluster in the context (using the name), and then loads a docker image TAR archive -// from the host into the cluster. -// -func LoadImageArchiveToCluster(name, imageArchive string) env.Func { - return func(ctx context.Context, cfg *envconf.Config) (context.Context, error) { - clusterVal := ctx.Value(kindContextKey(name)) - if clusterVal == nil { - return ctx, fmt.Errorf("load image archive func: context cluster is nil") - } - - cluster, ok := clusterVal.(*kind.Cluster) - if !ok { - return ctx, fmt.Errorf("load image archive func: unexpected type for cluster value") - } - - if err := cluster.LoadImageArchive(imageArchive); err != nil { - return ctx, fmt.Errorf("load image archive: %w", err) - } - - return ctx, nil - } +// Deprecated: This handler has been deprecated in favor of ExportClusterLogs +func ExportKindClusterLogs(name, dest string) env.Func { + return ExportClusterLogs(name, dest) } diff --git a/vendor/sigs.k8s.io/e2e-framework/pkg/envfuncs/provider_funcs.go b/vendor/sigs.k8s.io/e2e-framework/pkg/envfuncs/provider_funcs.go new file mode 100644 index 000000000..6fd612f7c --- /dev/null +++ b/vendor/sigs.k8s.io/e2e-framework/pkg/envfuncs/provider_funcs.go @@ -0,0 +1,189 @@ +/* +Copyright 2021 The Kubernetes 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 envfuncs + +import ( + "context" + "fmt" + + "sigs.k8s.io/e2e-framework/pkg/env" + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/support" +) + +type clusterNameContextKey string + +var LoadDockerImageToCluster = LoadImageToCluster + +// GetClusterFromContext helps extract the E2EClusterProvider object from the context. +// This can be used to setup and run tests of multi cluster e2e Prioviders. +func GetClusterFromContext(ctx context.Context, clusterName string) (support.E2EClusterProvider, bool) { + c := ctx.Value(clusterNameContextKey(clusterName)) + if c == nil { + return nil, false + } + cluster, ok := c.(support.E2EClusterProvider) + return cluster, ok +} + +// CreateCluster returns an env.Func that is used to +// create an E2E provider cluster that is then injected in the context +// using the name as a key. +// +// NOTE: the returned function will update its env config with the +// kubeconfig file for the config client. +func CreateCluster(p support.E2EClusterProvider, clusterName string) env.Func { + return func(ctx context.Context, cfg *envconf.Config) (context.Context, error) { + k := p.SetDefaults().WithName(clusterName) + kubecfg, err := k.Create(ctx) + if err != nil { + return ctx, err + } + + // update envconfig with kubeconfig + cfg.WithKubeconfigFile(kubecfg) + + // stall, wait for pods initializations + if err := k.WaitForControlPlane(ctx, cfg.Client()); err != nil { + return ctx, err + } + + // store entire cluster value in ctx for future access using the cluster name + return context.WithValue(ctx, clusterNameContextKey(clusterName), k), nil + } +} + +// CreateClusterWithConfig returns an env.Func that is used to +// create a e2e provider cluster that is then injected in the context +// using the name as a key. +// +// NOTE: the returned function will update its env config with the +// kubeconfig file for the config client. +func CreateClusterWithConfig(p support.E2EClusterProvider, clusterName, configFilePath string, opts ...support.ClusterOpts) env.Func { + return func(ctx context.Context, cfg *envconf.Config) (context.Context, error) { + k := p.SetDefaults().WithName(clusterName).WithOpts(opts...) + kubecfg, err := k.CreateWithConfig(ctx, configFilePath) + if err != nil { + return ctx, err + } + + cfg.Client().RESTConfig() + // update envconfig with kubeconfig + cfg.WithKubeconfigFile(kubecfg) + + // stall, wait for pods initializations + if err := k.WaitForControlPlane(ctx, cfg.Client()); err != nil { + return ctx, err + } + + // store entire cluster value in ctx for future access using the cluster name + return context.WithValue(ctx, clusterNameContextKey(clusterName), k), nil + } +} + +// DestroyCluster returns an EnvFunc that +// retrieves a previously saved e2e provider Cluster in the context (using the name), then deletes it. +// +// NOTE: this should be used in a Environment.Finish step. +func DestroyCluster(name string) env.Func { + return func(ctx context.Context, cfg *envconf.Config) (context.Context, error) { + clusterVal := ctx.Value(clusterNameContextKey(name)) + if clusterVal == nil { + return ctx, fmt.Errorf("destroy e2e provider cluster func: context cluster is nil") + } + + cluster, ok := clusterVal.(support.E2EClusterProvider) + if !ok { + return ctx, fmt.Errorf("destroy e2e provider cluster func: unexpected type for cluster value") + } + + if err := cluster.Destroy(ctx); err != nil { + return ctx, fmt.Errorf("destroy e2e provider cluster: %w", err) + } + + return ctx, nil + } +} + +// LoadImageToCluster returns an EnvFunc that +// retrieves a previously saved e2e provider Cluster in the context (using the name), and then loads a container image +// from the host into the cluster. +func LoadImageToCluster(name, image string) env.Func { + return func(ctx context.Context, cfg *envconf.Config) (context.Context, error) { + clusterVal := ctx.Value(clusterNameContextKey(name)) + if clusterVal == nil { + return ctx, fmt.Errorf("load image func: context cluster is nil") + } + + cluster, ok := clusterVal.(support.E2EClusterProviderWithImageLoader) + if !ok { + return ctx, fmt.Errorf("load image archive func: cluster provider does not support LoadImage helper") + } + + if err := cluster.LoadImage(ctx, image); err != nil { + return ctx, fmt.Errorf("load image: %w", err) + } + + return ctx, nil + } +} + +// LoadImageArchiveToCluster returns an EnvFunc that +// retrieves a previously saved e2e provider Cluster in the context (using the name), and then loads a container image TAR archive +// from the host into the cluster. +func LoadImageArchiveToCluster(name, imageArchive string) env.Func { + return func(ctx context.Context, cfg *envconf.Config) (context.Context, error) { + clusterVal := ctx.Value(clusterNameContextKey(name)) + if clusterVal == nil { + return ctx, fmt.Errorf("load image archive func: context cluster is nil") + } + + cluster, ok := clusterVal.(support.E2EClusterProviderWithImageLoader) + if !ok { + return ctx, fmt.Errorf("load image archive func: cluster provider does not support LoadImageArchive helper") + } + + if err := cluster.LoadImageArchive(ctx, imageArchive); err != nil { + return ctx, fmt.Errorf("load image archive: %w", err) + } + + return ctx, nil + } +} + +// ExportClusterLogs returns an EnvFunc that +// retrieves a previously saved e2e provider Cluster in the context (using the name), and then export cluster logs +// in the provided destination. +func ExportClusterLogs(name, dest string) env.Func { + return func(ctx context.Context, cfg *envconf.Config) (context.Context, error) { + clusterVal := ctx.Value(clusterNameContextKey(name)) + if clusterVal == nil { + return ctx, fmt.Errorf("export e2e provider cluster logs: context cluster is nil") + } + + cluster, ok := clusterVal.(support.E2EClusterProvider) + if !ok { + return ctx, fmt.Errorf("export e2e provider cluster logs: unexpected type for cluster value") + } + + if err := cluster.ExportLogs(ctx, dest); err != nil { + return ctx, fmt.Errorf("load image archive: %w", err) + } + + return ctx, nil + } +} diff --git a/vendor/sigs.k8s.io/e2e-framework/pkg/envfuncs/resource_funcs.go b/vendor/sigs.k8s.io/e2e-framework/pkg/envfuncs/resource_funcs.go new file mode 100644 index 000000000..54a5f565a --- /dev/null +++ b/vendor/sigs.k8s.io/e2e-framework/pkg/envfuncs/resource_funcs.go @@ -0,0 +1,51 @@ +/* +Copyright 2022 The Kubernetes 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 envfuncs + +import ( + "context" + + "sigs.k8s.io/e2e-framework/klient/decoder" + "sigs.k8s.io/e2e-framework/klient/k8s/resources" + "sigs.k8s.io/e2e-framework/pkg/env" + "sigs.k8s.io/e2e-framework/pkg/envconf" +) + +// SetupCRDs is provided as a helper env.Func handler that can be used to setup the CRDs that are required +// to process your controller code for testing. For additional control on resource creation handling, please +// use the decoder.ApplyWithManifestDir directly with suitable arguments to customize the behavior +func SetupCRDs(crdPath, pattern string) env.Func { + return func(ctx context.Context, c *envconf.Config) (context.Context, error) { + r, err := resources.New(c.Client().RESTConfig()) + if err != nil { + return ctx, err + } + return ctx, decoder.ApplyWithManifestDir(ctx, r, crdPath, pattern, []resources.CreateOption{}) + } +} + +// TeardownCRDs is provided as a handler function that can be hooked into your test's teardown sequence to +// make sure that you can cleanup the CRDs that were setup as part of the SetupCRDs hook +func TeardownCRDs(crdPath, pattern string) env.Func { + return func(ctx context.Context, c *envconf.Config) (context.Context, error) { + r, err := resources.New(c.Client().RESTConfig()) + if err != nil { + return ctx, err + } + return ctx, decoder.DeleteWithManifestDir(ctx, r, crdPath, pattern, []resources.DeleteOption{}) + } +} diff --git a/vendor/sigs.k8s.io/e2e-framework/pkg/features/builder.go b/vendor/sigs.k8s.io/e2e-framework/pkg/features/builder.go index 47f56cfb3..7a79b604b 100644 --- a/vendor/sigs.k8s.io/e2e-framework/pkg/features/builder.go +++ b/vendor/sigs.k8s.io/e2e-framework/pkg/features/builder.go @@ -29,12 +29,16 @@ type FeatureBuilder struct { } func New(name string) *FeatureBuilder { - return &FeatureBuilder{feat: newDefaultFeature(name)} + return &FeatureBuilder{feat: newDefaultFeature(name, "")} +} + +func NewWithDescription(name, description string) *FeatureBuilder { + return &FeatureBuilder{feat: newDefaultFeature(name, description)} } // WithLabel adds a test label key/value pair func (b *FeatureBuilder) WithLabel(key, value string) *FeatureBuilder { - b.feat.labels[key] = value + b.feat.labels[key] = append(b.feat.labels[key], value) return b } @@ -44,22 +48,40 @@ func (b *FeatureBuilder) WithStep(name string, level Level, fn Func) *FeatureBui return b } +func (b *FeatureBuilder) WithStepDescription(name, description string, level Level, fn Func) *FeatureBuilder { + b.feat.steps = append(b.feat.steps, newStepWithDescription(name, description, level, fn)) + return b +} + // Setup adds a new setup step that will be applied prior to feature test. func (b *FeatureBuilder) Setup(fn Func) *FeatureBuilder { - b.feat.steps = append(b.feat.steps, newStep(fmt.Sprintf("%s-setup", b.feat.name), types.LevelSetup, fn)) - return b + return b.WithSetup(fmt.Sprintf("%s-setup", b.feat.name), fn) +} + +// WithSetup adds a new setup step with a pre-defined setup name instead of automating +// the setup name generation. This can make tests more readable. +func (b *FeatureBuilder) WithSetup(name string, fn Func) *FeatureBuilder { + return b.WithStep(name, types.LevelSetup, fn) } // Teardown adds a new teardown step that will be applied after feature test. func (b *FeatureBuilder) Teardown(fn Func) *FeatureBuilder { - b.feat.steps = append(b.feat.steps, newStep(fmt.Sprintf("%s-teardown", b.feat.name), types.LevelTeardown, fn)) - return b + return b.WithTeardown(fmt.Sprintf("%s-teardown", b.feat.name), fn) +} + +// WithTeardown adds a new teardown step with a pre-defined name instead of an +// auto-generated one +func (b *FeatureBuilder) WithTeardown(name string, fn Func) *FeatureBuilder { + return b.WithStep(name, types.LevelTeardown, fn) } // Assess adds an assessment step to the feature test. func (b *FeatureBuilder) Assess(desc string, fn Func) *FeatureBuilder { - b.feat.steps = append(b.feat.steps, newStep(desc, types.LevelAssess, fn)) - return b + return b.WithStep(desc, types.LevelAssess, fn) +} + +func (b *FeatureBuilder) AssessWithDescription(name, description string, fn Func) *FeatureBuilder { + return b.WithStepDescription(name, description, types.LevelAssess, fn) } // Feature returns a feature configured by builder. diff --git a/vendor/sigs.k8s.io/e2e-framework/pkg/features/feature.go b/vendor/sigs.k8s.io/e2e-framework/pkg/features/feature.go index 99ba1032c..df46f9dd1 100644 --- a/vendor/sigs.k8s.io/e2e-framework/pkg/features/feature.go +++ b/vendor/sigs.k8s.io/e2e-framework/pkg/features/feature.go @@ -31,13 +31,14 @@ type ( ) type defaultFeature struct { - name string - labels types.Labels - steps []types.Step + name string + description string + labels types.Labels + steps []types.Step } -func newDefaultFeature(name string) *defaultFeature { - return &defaultFeature{name: name, labels: make(types.Labels)} +func newDefaultFeature(name, description string) *defaultFeature { + return &defaultFeature{name: name, description: description, labels: make(types.Labels), steps: make([]types.Step, 0)} } func (f *defaultFeature) Name() string { @@ -52,17 +53,27 @@ func (f *defaultFeature) Steps() []types.Step { return f.steps } +func (f *defaultFeature) Description() string { + return f.description +} + type testStep struct { - name string - level Level - fn Func + name string + description string + level Level + fn Func } func newStep(name string, level Level, fn Func) *testStep { + return newStepWithDescription(name, "", level, fn) +} + +func newStepWithDescription(name, description string, level Level, fn Func) *testStep { return &testStep{ - name: name, - level: level, - fn: fn, + name: name, + description: description, + level: level, + fn: fn, } } @@ -78,6 +89,10 @@ func (s *testStep) Func() Func { return s.fn } +func (s *testStep) Description() string { + return s.description +} + func GetStepsByLevel(steps []types.Step, l types.Level) []types.Step { if steps == nil { return nil diff --git a/vendor/sigs.k8s.io/e2e-framework/pkg/features/table.go b/vendor/sigs.k8s.io/e2e-framework/pkg/features/table.go index d2afed90c..520e89a6b 100644 --- a/vendor/sigs.k8s.io/e2e-framework/pkg/features/table.go +++ b/vendor/sigs.k8s.io/e2e-framework/pkg/features/table.go @@ -23,26 +23,31 @@ import ( // Table provides a structure for table-driven tests. // Each entry in the table represents an executable assessment. type Table []struct { - Name string - Assessment Func + Name string + Description string + Assessment Func } // Build converts the defined test steps in the table // into a FeatureBuilder which can be used to add additional attributes // to the feature before it's exercised. Build takes an optional feature name // if omitted will be generated. -func (table Table) Build(featureName ...string) *FeatureBuilder { +func (table Table) Build(args ...string) *FeatureBuilder { var name string - if len(featureName) > 0 { - name = featureName[0] + var description string + if len(args) > 0 { + name = args[0] } - f := New(name) + if len(args) > 1 { + description = args[1] + } + f := NewWithDescription(name, description) for i, test := range table { if test.Name == "" { test.Name = fmt.Sprintf("Assessment-%d", i) } if test.Assessment != nil { - f.Assess(test.Name, test.Assessment) + f.AssessWithDescription(test.Name, test.Description, test.Assessment) } } return f diff --git a/vendor/sigs.k8s.io/e2e-framework/pkg/flags/flags.go b/vendor/sigs.k8s.io/e2e-framework/pkg/flags/flags.go index a83e474ae..763dd2d85 100644 --- a/vendor/sigs.k8s.io/e2e-framework/pkg/flags/flags.go +++ b/vendor/sigs.k8s.io/e2e-framework/pkg/flags/flags.go @@ -26,15 +26,19 @@ import ( ) const ( - flagNamespaceName = "namespace" - flagKubecofigName = "kubeconfig" - flagFeatureName = "feature" - flagAssessName = "assess" - flagLabelsName = "labels" - flagSkipLabelName = "skip-labels" - flagSkipFeatureName = "skip-features" - flagSkipAssessmentName = "skip-assessment" - flagParallelTestsName = "parallel" + flagNamespaceName = "namespace" + flagKubecofigName = "kubeconfig" + flagFeatureName = "feature" + flagAssessName = "assess" + flagLabelsName = "labels" + flagSkipLabelName = "skip-labels" + flagSkipFeatureName = "skip-features" + flagSkipAssessmentName = "skip-assessment" + flagParallelTestsName = "parallel" + flagDryRunName = "dry-run" + flagFailFast = "fail-fast" + flagDisableGracefulTeardown = "disable-graceful-teardown" + flagContext = "context" ) // Supported flag definitions @@ -75,19 +79,39 @@ var ( Name: flagParallelTestsName, Usage: "Run test features in parallel", } + dryRunFlag = flag.Flag{ + Name: flagDryRunName, + Usage: "Run Test suite in dry-run mode. This will list the tests to be executed without actually running them", + } + failFastFlag = flag.Flag{ + Name: flagFailFast, + Usage: "Fail immediately and stop running untested code", + } + disableGracefulTeardownFlag = flag.Flag{ + Name: flagDisableGracefulTeardown, + Usage: "Ignore panic recovery while running tests. This will prevent test finish steps from getting executed on panic", + } + contextFlag = flag.Flag{ + Name: flagContext, + Usage: "The name of the kubeconfig context to use", + } ) // EnvFlags surfaces all resolved flag values for the testing framework type EnvFlags struct { - feature string - assess string - labels LabelsMap - kubeconfig string - namespace string - skiplabels LabelsMap - skipFeatures string - skipAssessments string - parallelTests bool + feature string + assess string + labels LabelsMap + kubeconfig string + namespace string + skiplabels LabelsMap + skipFeatures string + skipAssessments string + parallelTests bool + dryRun bool + failFast bool + disableGracefulTeardown bool + kubeContext string } // Feature returns value for `-feature` flag @@ -110,14 +134,21 @@ func (f *EnvFlags) Namespace() string { return f.namespace } +// SkipFeatures is used to get a RegExp pattern that can be used +// to skip test features from getting executed func (f *EnvFlags) SkipFeatures() string { return f.skipFeatures } +// SkipAssessment is used to track the RegExp pattern that can be +// used to skip certain assessments of the current feature being +// executed func (f *EnvFlags) SkipAssessment() string { return f.skipAssessments } +// SkipLabels is used to define a series of labels that can be used +// to skip test cases during execution func (f *EnvFlags) SkipLabels() LabelsMap { return f.skiplabels } @@ -127,26 +158,54 @@ func (f *EnvFlags) Kubeconfig() string { return f.kubeconfig } +// Parallel is used to indicate if the test features should be run in parallel +// under a go-routine func (f *EnvFlags) Parallel() bool { return f.parallelTests } +func (f *EnvFlags) DryRun() bool { + return f.dryRun +} + +// FailFast is used to indicate if the failure of an assessment should continue +// assessing the rest of the features or skip it and continue to the next one. +// This is set to false by default. +func (f *EnvFlags) FailFast() bool { + return f.failFast +} + +// DisableGracefulTeardown is used to indicate that the panic handlers should not be registered while +// starting the test execution. This will prevent the test Finish steps from getting executed +func (f *EnvFlags) DisableGracefulTeardown() bool { + return f.disableGracefulTeardown +} + // Parse parses defined CLI args os.Args[1:] func Parse() (*EnvFlags, error) { return ParseArgs(os.Args[1:]) } +// Context returns an optional kubeconfig context to use +func (f *EnvFlags) KubeContext() string { + return f.kubeContext +} + // ParseArgs parses the specified args from global flag.CommandLine // and returns a set of environment flag values. func ParseArgs(args []string) (*EnvFlags, error) { var ( - feature string - assess string - namespace string - kubeconfig string - skipFeature string - skipAssessment string - parallelTests bool + feature string + assess string + namespace string + kubeconfig string + skipFeature string + skipAssessment string + parallelTests bool + dryRun bool + failFast bool + disableGracefulTeardown bool + kubeContext string ) labels := make(LabelsMap) @@ -188,6 +247,22 @@ func ParseArgs(args []string) (*EnvFlags, error) { flag.BoolVar(¶llelTests, parallelTestsFlag.Name, false, parallelTestsFlag.Usage) } + if flag.Lookup(dryRunFlag.Name) == nil { + flag.BoolVar(&dryRun, dryRunFlag.Name, false, dryRunFlag.Usage) + } + + if flag.Lookup(failFastFlag.Name) == nil { + flag.BoolVar(&failFast, failFastFlag.Name, false, failFastFlag.Usage) + } + + if flag.Lookup(disableGracefulTeardownFlag.Name) == nil { + flag.BoolVar(&disableGracefulTeardown, disableGracefulTeardownFlag.Name, false, disableGracefulTeardownFlag.Usage) + } + + if flag.Lookup(contextFlag.Name) == nil { + flag.StringVar(&kubeContext, contextFlag.Name, contextFlag.DefValue, contextFlag.Usage) + } + // Enable klog/v2 flag integration klog.InitFlags(nil) @@ -195,23 +270,37 @@ func ParseArgs(args []string) (*EnvFlags, error) { return nil, fmt.Errorf("flags parsing: %w", err) } + // Hook into the default test.list of the `go test` and integrate that with the `--dry-run` behavior. Treat them the same way + if !dryRun && flag.Lookup("test.list") != nil && flag.Lookup("test.list").Value.String() == "true" { + klog.V(2).Info("Enabling dry-run mode as the tests were invoked in list mode") + dryRun = true + } + + if failFast && parallelTests { + panic(fmt.Errorf("--fail-fast and --parallel are mutually exclusive options")) + } + return &EnvFlags{ - feature: feature, - assess: assess, - labels: labels, - namespace: namespace, - kubeconfig: kubeconfig, - skiplabels: skipLabels, - skipFeatures: skipFeature, - skipAssessments: skipAssessment, - parallelTests: parallelTests, + feature: feature, + assess: assess, + labels: labels, + namespace: namespace, + kubeconfig: kubeconfig, + skiplabels: skipLabels, + skipFeatures: skipFeature, + skipAssessments: skipAssessment, + parallelTests: parallelTests, + dryRun: dryRun, + failFast: failFast, + disableGracefulTeardown: disableGracefulTeardown, + kubeContext: kubeContext, }, nil } -type LabelsMap map[string]string +type LabelsMap map[string][]string func (m LabelsMap) String() string { - i := map[string]string(m) + i := map[string][]string(m) return fmt.Sprint(i) } @@ -223,8 +312,19 @@ func (m LabelsMap) Set(val string) error { if len(kv) != 2 { return fmt.Errorf("label format error: %s", label) } - m[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1]) + k := strings.TrimSpace(kv[0]) + v := strings.TrimSpace(kv[1]) + m[k] = append(m[k], v) } return nil } + +func (m LabelsMap) Contains(key, val string) bool { + for _, v := range m[key] { + if val == v { + return true + } + } + return false +} diff --git a/vendor/sigs.k8s.io/e2e-framework/pkg/internal/types/types.go b/vendor/sigs.k8s.io/e2e-framework/pkg/internal/types/types.go index 4a2073bc4..ed4fef4bd 100644 --- a/vendor/sigs.k8s.io/e2e-framework/pkg/internal/types/types.go +++ b/vendor/sigs.k8s.io/e2e-framework/pkg/internal/types/types.go @@ -21,6 +21,7 @@ import ( "testing" "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/flags" ) // EnvFunc represents a user-defined operation that @@ -67,12 +68,12 @@ type Environment interface { // Test executes a test feature defined in a TestXXX function // This method surfaces context for further updates. - Test(*testing.T, ...Feature) + Test(*testing.T, ...Feature) context.Context // TestInParallel executes a series of test features defined in a // TestXXX function in parallel. This works the same way Test method // does with the caveat that the features will all be run in parallel - TestInParallel(*testing.T, ...Feature) + TestInParallel(*testing.T, ...Feature) context.Context // AfterEachTest registers environment funcs that are executed // after each Env.Test(...). @@ -86,7 +87,7 @@ type Environment interface { Run(*testing.M) int } -type Labels map[string]string +type Labels = flags.LabelsMap type Feature interface { // Name is a descriptive text for the feature @@ -118,3 +119,19 @@ type Step interface { // Func is the operation for the step Func() StepFunc } + +type DescribableStep interface { + Step + // Description is the Readable test description indicating the purpose behind the test that + // can add more context to the test under question + Description() string +} + +type DescribableFeature interface { + Feature + + // Description is used to provide a readable context for the test feature. This can be used + // to provide more context for the test being performed and the assessment under each of the + // feature. + Description() string +} diff --git a/vendor/sigs.k8s.io/e2e-framework/support/kind/kind.go b/vendor/sigs.k8s.io/e2e-framework/support/kind/kind.go index 65ea251c5..abba976df 100644 --- a/vendor/sigs.k8s.io/e2e-framework/support/kind/kind.go +++ b/vendor/sigs.k8s.io/e2e-framework/support/kind/kind.go @@ -18,52 +18,109 @@ package kind import ( "bytes" + "context" "fmt" "io" - "io/ioutil" "os" "strings" + v1 "k8s.io/api/core/v1" + "k8s.io/client-go/rest" log "k8s.io/klog/v2" - - "github.com/vladimirvivien/gexe" + "sigs.k8s.io/e2e-framework/klient" + "sigs.k8s.io/e2e-framework/klient/conf" + "sigs.k8s.io/e2e-framework/klient/k8s/resources" + "sigs.k8s.io/e2e-framework/klient/wait" + "sigs.k8s.io/e2e-framework/klient/wait/conditions" + "sigs.k8s.io/e2e-framework/support" + "sigs.k8s.io/e2e-framework/support/utils" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -var kindVersion = "v0.11.0" +var kindVersion = "v0.17.0" type Cluster struct { + path string name string - e *gexe.Echo kubecfgFile string version string + image string + rc *rest.Config } +// Enforce Type check always to avoid future breaks +var _ support.E2EClusterProvider = &Cluster{} + func NewCluster(name string) *Cluster { - return &Cluster{name: name, e: gexe.New()} + return &Cluster{name: name} +} + +func NewProvider() support.E2EClusterProvider { + return &Cluster{} +} + +func WithImage(image string) support.ClusterOpts { + return func(c support.E2EClusterProvider) { + k, ok := c.(*Cluster) + if ok { + k.image = image + } + } +} + +func WithPath(path string) support.ClusterOpts { + return func(c support.E2EClusterProvider) { + k, ok := c.(*Cluster) + if ok { + k.path = path + } + } +} + +func (k *Cluster) SetDefaults() support.E2EClusterProvider { + if k.path == "" { + k.path = "kind" + } + return k +} + +func (k *Cluster) WithName(name string) support.E2EClusterProvider { + k.name = name + return k +} + +func (k *Cluster) WithPath(path string) support.E2EClusterProvider { + k.path = path + return k } -// WithVersion set kind version -func (k *Cluster) WithVersion(ver string) *Cluster { +func (k *Cluster) WithVersion(ver string) support.E2EClusterProvider { k.version = ver return k } +func (k *Cluster) WithOpts(opts ...support.ClusterOpts) support.E2EClusterProvider { + for _, o := range opts { + o(k) + } + return k +} + func (k *Cluster) getKubeconfig() (string, error) { kubecfg := fmt.Sprintf("%s-kubecfg", k.name) - p := k.e.StartProc(fmt.Sprintf(`kind get kubeconfig --name %s`, k.name)) + p := utils.RunCommand(fmt.Sprintf(`%s get kubeconfig --name %s`, k.path, k.name)) if p.Err() != nil { return "", fmt.Errorf("kind get kubeconfig: %w", p.Err()) } + var stdout bytes.Buffer - if _, err := stdout.ReadFrom(p.StdOut()); err != nil { + if _, err := stdout.ReadFrom(p.Out()); err != nil { return "", fmt.Errorf("kind kubeconfig stdout bytes: %w", err) } - if p.Wait().Err() != nil { - return "", fmt.Errorf("kind get kubeconfig: %s: %w", p.Result(), p.Err()) - } - file, err := ioutil.TempFile("", fmt.Sprintf("kind-cluser-%s", kubecfg)) + file, err := os.CreateTemp("", fmt.Sprintf("kind-cluser-%s", kubecfg)) if err != nil { return "", fmt.Errorf("kind kubeconfig file: %w", err) } @@ -79,7 +136,7 @@ func (k *Cluster) getKubeconfig() (string, error) { } func (k *Cluster) clusterExists(name string) (string, bool) { - clusters := k.e.Run("kind get clusters") + clusters := utils.FetchCommandOutput(fmt.Sprintf("%s get clusters", k.path)) for _, c := range strings.Split(clusters, "\n") { if c == name { return clusters, true @@ -88,13 +145,17 @@ func (k *Cluster) clusterExists(name string) (string, bool) { return clusters, false } -func (k *Cluster) CreateWithConfig(imageName, kindConfigFile string) (string, error) { - return k.Create("--image", imageName, "--config", kindConfigFile) +func (k *Cluster) CreateWithConfig(ctx context.Context, kindConfigFile string) (string, error) { + args := []string{"--config", kindConfigFile} + if k.image != "" { + args = append(args, "--image", k.image) + } + return k.Create(ctx, args...) } -func (k *Cluster) Create(args ...string) (string, error) { +func (k *Cluster) Create(ctx context.Context, args ...string) (string, error) { log.V(4).Info("Creating kind cluster ", k.name) - if err := k.findOrInstallKind(k.e); err != nil { + if err := k.findOrInstallKind(); err != nil { return "", err } @@ -103,120 +164,140 @@ func (k *Cluster) Create(args ...string) (string, error) { return k.getKubeconfig() } - command := fmt.Sprintf(`kind create cluster --name %s`, k.name) + command := fmt.Sprintf(`%s create cluster --name %s`, k.path, k.name) if len(args) > 0 { command = fmt.Sprintf("%s %s", command, strings.Join(args, " ")) } log.V(4).Info("Launching:", command) - p := k.e.RunProc(command) + p := utils.RunCommand(command) if p.Err() != nil { - return "", fmt.Errorf("failed to create kind cluster: %s : %s", p.Err(), p.Result()) + // Print the output data as well so that it can be useful to debug cluster bringup failures + var data []byte + b := bytes.NewBuffer(data) + _, err := io.Copy(b, p.Out()) + if err != nil { + log.ErrorS(err, "failed to read data from the kind create process output due to an error") + } + return "", fmt.Errorf("failed to create kind cluster: %s : %s: %s", p.Err(), p.Result(), b.String()) } - clusters, ok := k.clusterExists(k.name) if !ok { return "", fmt.Errorf("kind Cluster.Create: cluster %v still not in 'cluster list' after creation: %v", k.name, clusters) } log.V(4).Info("kind clusters available: ", clusters) - // Grab kubeconfig file for cluster. - return k.getKubeconfig() + kConfig, err := k.getKubeconfig() + if err != nil { + return "", err + } + return kConfig, k.initKubernetesAccessClients() +} + +func (k *Cluster) initKubernetesAccessClients() error { + cfg, err := conf.New(k.kubecfgFile) + if err != nil { + return err + } + k.rc = cfg + return nil } -// GetKubeconfig returns the path of the kubeconfig file -// associated with this kind cluster func (k *Cluster) GetKubeconfig() string { return k.kubecfgFile } -func (k *Cluster) GetKubeCtlContext() string { +func (k *Cluster) GetKubectlContext() string { return fmt.Sprintf("kind-%s", k.name) } -func (k *Cluster) Destroy() error { - log.V(4).Info("Destroying kind cluster ", k.name) - if err := k.findOrInstallKind(k.e); err != nil { +// ExportLogs export all cluster logs to the provided path. +func (k *Cluster) ExportLogs(ctx context.Context, dest string) error { + log.V(4).Info("Exporting kind cluster logs to ", dest) + if err := k.findOrInstallKind(); err != nil { return err } - p := k.e.RunProc(fmt.Sprintf(`kind delete cluster --name %s`, k.name)) + p := utils.RunCommand(fmt.Sprintf(`%s export logs %s --name %s`, k.path, dest, k.name)) if p.Err() != nil { - return fmt.Errorf("kind: delete cluster failed: %s: %s", p.Err(), p.Result()) - } - - log.V(4).Info("Removing kubeconfig file ", k.kubecfgFile) - if err := os.RemoveAll(k.kubecfgFile); err != nil { - return fmt.Errorf("kind: remove kubefconfig failed: %w", err) + return fmt.Errorf("kind: export cluster %v logs failed: %s: %s", k.name, p.Err(), p.Result()) } return nil } -func (k *Cluster) findOrInstallKind(e *gexe.Echo) error { - if e.Prog().Avail("kind") == "" { - log.V(4).Infof(`kind not found, installing with GO111MODULE="on" go get sigs.k8s.io/kind@%s`, kindVersion) - if err := k.installKind(e); err != nil { - return err - } - } - return nil -} - -func (k *Cluster) installKind(e *gexe.Echo) error { - if k.version != "" { - kindVersion = k.version +func (k *Cluster) Destroy(ctx context.Context) error { + log.V(4).Info("Destroying kind cluster ", k.name) + if err := k.findOrInstallKind(); err != nil { + return err } - log.V(4).Infof("Installing: go get sigs.k8s.io/kind@%s", kindVersion) - p := e.SetEnv("GO111MODULE", "on").RunProc(fmt.Sprintf("go get sigs.k8s.io/kind@%s", kindVersion)) + p := utils.RunCommand(fmt.Sprintf(`%s delete cluster --name %s`, k.path, k.name)) if p.Err() != nil { - return fmt.Errorf("failed to install kind: %s", p.Err()) + return fmt.Errorf("kind: delete cluster %v failed: %s: %s", k.name, p.Err(), p.Result()) } - if !p.IsSuccess() || p.ExitCode() != 0 { - return fmt.Errorf("failed to install kind: %s", p.Result()) + log.V(4).Info("Removing kubeconfig file ", k.kubecfgFile) + if err := os.RemoveAll(k.kubecfgFile); err != nil { + return fmt.Errorf("kind: remove kubefconfig %v failed: %w", k.kubecfgFile, err) } - // PATH may already be set to include $GOPATH/bin so we don't need to. - if kindPath := e.Prog().Avail("kind"); kindPath != "" { - log.V(4).Info("Installed kind at", kindPath) - return nil - } + return nil +} - p = e.RunProc("ls $GOPATH/bin") - if p.Err() != nil { - return fmt.Errorf("failed to install kind: %s", p.Err()) +func (k *Cluster) findOrInstallKind() error { + if k.version != "" { + kindVersion = k.version } - - p = e.RunProc("echo $PATH:$GOPATH/bin") - if p.Err() != nil { - return fmt.Errorf("failed to install kind: %s", p.Err()) + path, err := utils.FindOrInstallGoBasedProvider(k.path, "kind", "sigs.k8s.io/kind", kindVersion) + if path != "" { + k.path = path } + return err +} - log.V(4).Info(`Setting path to include $GOPATH/bin:`, p.Result()) - e.SetEnv("PATH", p.Result()) - - if kindPath := e.Prog().Avail("kind"); kindPath != "" { - log.V(4).Info("Installed kind at", kindPath) - return nil +func (k *Cluster) LoadImage(ctx context.Context, image string) error { + p := utils.RunCommand(fmt.Sprintf(`%s load docker-image --name %s %s`, k.path, k.name, image)) + if p.Err() != nil { + return fmt.Errorf("kind: load docker-image %v failed: %s: %s", image, p.Err(), p.Result()) } - return fmt.Errorf("kind not available even after installation") + return nil } -// LoadDockerImage loads a docker image from the host into the kind cluster -func (k *Cluster) LoadDockerImage(image string) error { - p := k.e.RunProc(fmt.Sprintf(`kind load docker-image --name %s %s`, k.name, image)) +func (k *Cluster) LoadImageArchive(ctx context.Context, imageArchive string) error { + p := utils.RunCommand(fmt.Sprintf(`%s load image-archive --name %s %s`, k.path, k.name, imageArchive)) if p.Err() != nil { - return fmt.Errorf("kind: load docker-image failed: %s: %s", p.Err(), p.Result()) + return fmt.Errorf("kind: load image-archive %v failed: %s: %s", imageArchive, p.Err(), p.Result()) } return nil } -// LoadImageArchive loads a docker image TAR archive from the host into the kind cluster -func (k *Cluster) LoadImageArchive(imageArchive string) error { - p := k.e.RunProc(fmt.Sprintf(`kind load image-archive --name %s %s`, k.name, imageArchive)) - if p.Err() != nil { - return fmt.Errorf("kind: load image-archive failed: %s: %s", p.Err(), p.Result()) +func (k *Cluster) WaitForControlPlane(ctx context.Context, client klient.Client) error { + r, err := resources.New(client.RESTConfig()) + if err != nil { + return err + } + for _, sl := range []metav1.LabelSelectorRequirement{ + {Key: "component", Operator: metav1.LabelSelectorOpIn, Values: []string{"etcd", "kube-apiserver", "kube-controller-manager", "kube-scheduler"}}, + {Key: "k8s-app", Operator: metav1.LabelSelectorOpIn, Values: []string{"kindnet", "kube-dns", "kube-proxy"}}, + } { + selector, err := metav1.LabelSelectorAsSelector( + &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + sl, + }, + }, + ) + if err != nil { + return err + } + err = wait.For(conditions.New(r).ResourceListN(&v1.PodList{}, len(sl.Values), resources.WithLabelSelector(selector.String()))) + if err != nil { + return err + } } return nil } + +func (k *Cluster) KubernetesRestConfig() *rest.Config { + return k.rc +} diff --git a/vendor/sigs.k8s.io/e2e-framework/support/types.go b/vendor/sigs.k8s.io/e2e-framework/support/types.go new file mode 100644 index 000000000..25fcae263 --- /dev/null +++ b/vendor/sigs.k8s.io/e2e-framework/support/types.go @@ -0,0 +1,105 @@ +/* +Copyright 2023 The Kubernetes 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 support + +import ( + "context" + + "k8s.io/client-go/rest" + "sigs.k8s.io/e2e-framework/klient" +) + +type ClusterOpts func(c E2EClusterProvider) + +type E2EClusterProvider interface { + // WithName is used to configure the cluster Name that should be used while setting up the cluster. Might + // Not apply for all providers. + WithName(name string) E2EClusterProvider + + // WithVersion helps you override the default version used while using the cluster provider. + // This can be useful in providing a mechanism to the end users where they want to test their + // code against a certain specific version of k8s that is not the default one configured + // for the provider + WithVersion(version string) E2EClusterProvider + + // WithPath heps you customize the executable binary that is used to back the cluster provider. + // This is useful in cases where your binary is present in a non standard location output of the + // PATH variable and you want to use that instead of framework trying to install one on it's own. + WithPath(path string) E2EClusterProvider + + // WithOpts provides a way to customize the options that can be used while setting up the + // cluster using the providers such as kind or kwok or anything else. These helpers can be + // leveraged to setup arguments or configuration values that can be provided while performing + // the cluster bring up + WithOpts(opts ...ClusterOpts) E2EClusterProvider + + // Create Provides an interface to start the cluster creation workflow using the selected provider + Create(ctx context.Context, args ...string) (string, error) + + // CreateWithConfig is used to provide a mechanism where cluster providers that take an input config + // file and then setup the cluster accordingly. This can be used to provide input such as kind config + CreateWithConfig(ctx context.Context, configFile string) (string, error) + + // GetKubeconfig provides a way to extract the kubeconfig file associated with the cluster in question + // using the cluster provider native way + GetKubeconfig() string + + // GetKubectlContext is used to extract the kubectl context to be used while performing the operation + GetKubectlContext() string + + // ExportLogs is used to export the cluster logs via the cluster provider native workflow. This + // can be used to export logs from the cluster after test failures for example to analyze the test + // failures better after the fact. + ExportLogs(ctx context.Context, dest string) error + + // Destroy is used to cleanup a cluster brought up as part of the test workflow + Destroy(ctx context.Context) error + + // SetDefaults is a handler function invoked after creating an object of type E2EClusterProvider. This method is + // invoked as the first step after creating an object in order to make sure the default values for required + // attributes are setup accordingly if any. + SetDefaults() E2EClusterProvider + + // WaitForControlPlane is a helper function that can be used to indiate the Provider based cluster create workflow + // that the control plane is fully up and running. This method is invoked after the Create/CreateWithConfig handlers + // and is expected to return an error if the control plane doesn't stabilize. If the provider being implemented + // does not have a clear mechanism to identify the Control plane readiness or is not required to wait for the control + // plane to be ready, such providers can simply add a no-op workflow for this function call. + // Returning an error message from this handler will stop the workflow of e2e-framework as returning an error from this + // is considered as failure to provision a cluster + WaitForControlPlane(ctx context.Context, client klient.Client) error + + // KubernetesRestConfig is a helper function that provides an instance of rest.Config which can then be used to + // create your own clients if you chose to do so. + KubernetesRestConfig() *rest.Config +} + +type E2EClusterProviderWithImageLoader interface { + E2EClusterProvider + + // LoadImage is used to load a set of Docker images to the cluster via the cluster provider native workflow + // Not every provider will have a mechanism like this/need to do this. So, providers that do not have this support + // can just provide a no-op implementation to be compliant with the interface + LoadImage(ctx context.Context, image string) error + + // LoadImageArchive is used to provide a mechanism where a tar.gz archive containing the docker images used + // by the services running on the cluster can be imported and loaded into the cluster prior to the execution of + // test if required. + // Not every provider will have a mechanism like this/need to do this. So, providers that do not have this support + // can just provide a no-op implementation to be compliant with the interface + LoadImageArchive(ctx context.Context, archivePath string) error +} diff --git a/vendor/sigs.k8s.io/e2e-framework/support/utils/command.go b/vendor/sigs.k8s.io/e2e-framework/support/utils/command.go new file mode 100644 index 000000000..860140c36 --- /dev/null +++ b/vendor/sigs.k8s.io/e2e-framework/support/utils/command.go @@ -0,0 +1,84 @@ +/* +Copyright 2023 The Kubernetes 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 utils + +import ( + "fmt" + + "github.com/vladimirvivien/gexe" + "github.com/vladimirvivien/gexe/exec" + log "k8s.io/klog/v2" +) + +var commandRunner = gexe.New() + +// FindOrInstallGoBasedProvider check if the provider specified by the pPath executable exists or not. +// If it exists, it returns the path with no error and if not, it uses the `go install` capabilities to +// install the provider and setup the required binaries to perform the tests. In case if the install +// is done by this helper, it will return the value for installed binary as provider which can then +// be set in the in the invoker to make sure the right path is used for the binaries while invoking +// rest of the workfow after this helper is triggered. +func FindOrInstallGoBasedProvider(pPath, provider, module, version string) (string, error) { + if commandRunner.Prog().Avail(pPath) != "" { + log.V(4).InfoS("Found Provider tooling already installed on the machine", "command", pPath) + return pPath, nil + } + + installCommand := fmt.Sprintf("go install %s@%s", module, version) + log.V(4).InfoS("Installing provider tooling using go install", "command", installCommand) + p := commandRunner.RunProc(installCommand) + if p.Err() != nil { + return "", fmt.Errorf("failed to install %s: %s", pPath, p.Err()) + } + + if !p.IsSuccess() || p.ExitCode() != 0 { + return "", fmt.Errorf("failed to install %s: %s", pPath, p.Result()) + } + + if providerPath := commandRunner.Prog().Avail(provider); providerPath != "" { + log.V(4).Infof("Installed %s at", pPath, providerPath) + return provider, nil + } + + p = commandRunner.RunProc("ls $GOPATH/bin") + if p.Err() != nil { + return "", fmt.Errorf("failed to install %s: %s", pPath, p.Err()) + } + + p = commandRunner.RunProc("echo $PATH:$GOPATH/bin") + if p.Err() != nil { + return "", fmt.Errorf("failed to install %s: %s", pPath, p.Err()) + } + + log.V(4).Info(`Setting path to include $GOPATH/bin:`, p.Result()) + commandRunner.SetEnv("PATH", p.Result()) + + if providerPath := commandRunner.Prog().Avail(provider); providerPath != "" { + log.V(4).Infof("Installed %s at", pPath, providerPath) + return provider, nil + } + + return "", fmt.Errorf("%s not available even after installation", provider) +} + +func RunCommand(command string) *exec.Proc { + return commandRunner.RunProc(command) +} + +func FetchCommandOutput(command string) string { + return commandRunner.Run(command) +}