Skip to content

Commit

Permalink
feat: add lazy loading kubeconfigs for Test Steps (#540)
Browse files Browse the repository at this point in the history
Signed-off-by: Kumar Mallikarjuna <[email protected]>
  • Loading branch information
kumar-mallikarjuna authored Jul 5, 2024
1 parent 280792a commit 7160f43
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 9 deletions.
1 change: 1 addition & 0 deletions docs/testing/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ delete | list of object references | A list of objects to delete, if they do n
index | int | Override the test step's index.
commands | list of [Commands](#commands) | Commands to run prior at the beginning of the test step.
kubeconfig | string | The Kubeconfig file to use to run the included steps(s).
kubeconfigLoading | string | Specifies the mode for loading Kubeconfig and making a cluster connection: `Eager` (when loading the test definition) or `Lazy` (right before executing the step, makes it possible to generate the Kubeconfig in a preceding step). Defaults to `Eager`.
unitTest | bool | Indicates if the step is a unit test, safe to run without a real Kubernetes cluster.


Expand Down
8 changes: 8 additions & 0 deletions pkg/apis/testharness/v1beta1/test_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import (
"k8s.io/client-go/rest"
)

const KubeconfigLoadingEager = "Eager"
const KubeconfigLoadingLazy = "Lazy"

// Create embedded struct to implement custom DeepCopyInto method
type RestConfig struct {
RC *rest.Config
Expand Down Expand Up @@ -125,6 +128,11 @@ type TestStep struct {

// Kubeconfig to use when applying and asserting for this step.
Kubeconfig string `json:"kubeconfig,omitempty"`

// Specifies the mode for loading Kubeconfig: Eager/Lazy. Defaults to Eager.
// +kubebuilder:default=Eager
// +kubebuilder:validation:Enum=Eager;Lazy
KubeconfigLoading string `json:"kubeconfigLoading,omitempty"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
Expand Down
33 changes: 27 additions & 6 deletions pkg/test/case.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

"k8s.io/client-go/tools/clientcmd"

"github.com/kudobuilder/kuttl/pkg/apis/testharness/v1beta1"
"github.com/kudobuilder/kuttl/pkg/report"
testutils "github.com/kudobuilder/kuttl/pkg/test/utils"
)
Expand Down Expand Up @@ -333,11 +334,11 @@ func (t *Case) Run(test *testing.T, ts *report.Testsuite) {
clients := map[string]client.Client{"": cl}

for _, testStep := range t.Steps {
if clients[testStep.Kubeconfig] != nil {
if clients[testStep.Kubeconfig] != nil || testStep.KubeconfigLoading == v1beta1.KubeconfigLoadingLazy {
continue
}

cl, err := newClient(testStep.Kubeconfig)(false)
cl, err = newClient(testStep.Kubeconfig)(false)
if err != nil {
setupReport.Failure = report.NewFailure(err.Error(), nil)
ts.AddTestcase(setupReport)
Expand All @@ -347,9 +348,11 @@ func (t *Case) Run(test *testing.T, ts *report.Testsuite) {
clients[testStep.Kubeconfig] = cl
}

for _, c := range clients {
if err := t.CreateNamespace(test, c, ns); err != nil {
setupReport.Failure = report.NewFailure(err.Error(), nil)
for kc, c := range clients {
if err = t.CreateNamespace(test, c, ns); k8serrors.IsAlreadyExists(err) {
t.Logger.Logf("namespace %q already exists, using kubeconfig %q", ns.Name, kc)
} else if err != nil {
setupReport.Failure = report.NewFailure("failed to create test namespace", []error{err})
ts.AddTestcase(setupReport)
test.Fatal(err)
}
Expand All @@ -370,7 +373,25 @@ func (t *Case) Run(test *testing.T, ts *report.Testsuite) {
tc.Assertions += len(testStep.Asserts)
tc.Assertions += len(testStep.Errors)

errs := testStep.Run(test, ns.Name)
errs := []error{}

// Set-up client/namespace for lazy-loaded Kubeconfig
if testStep.KubeconfigLoading == v1beta1.KubeconfigLoadingLazy {
cl, err = testStep.Client(false)
if err != nil {
errs = append(errs, fmt.Errorf("failed to lazy-load kubeconfig: %w", err))
} else if err = t.CreateNamespace(test, cl, ns); k8serrors.IsAlreadyExists(err) {
t.Logger.Logf("namespace %q already exists", ns.Name)
} else if err != nil {
errs = append(errs, fmt.Errorf("failed to create test namespace: %w", err))
}
}

// Run test case only if no setup errors are encountered
if len(errs) == 0 {
errs = append(errs, testStep.Run(test, ns.Name)...)
}

if len(errs) > 0 {
caseErr := fmt.Errorf("failed in step %s", testStep.String())
tc.Failure = report.NewFailure(caseErr.Error(), errs)
Expand Down
14 changes: 11 additions & 3 deletions pkg/test/step.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,10 @@ type Step struct {

Timeout int

Kubeconfig string
Client func(forceNew bool) (client.Client, error)
DiscoveryClient func() (discovery.DiscoveryInterface, error)
Kubeconfig string
KubeconfigLoading string
Client func(forceNew bool) (client.Client, error)
DiscoveryClient func() (discovery.DiscoveryInterface, error)

Logger testutils.Logger
}
Expand Down Expand Up @@ -555,6 +556,13 @@ func (s *Step) LoadYAML(file string) error {
exKubeconfig := env.Expand(s.Step.Kubeconfig)
s.Kubeconfig = cleanPath(exKubeconfig, s.Dir)
}

switch s.Step.KubeconfigLoading {
case "", harness.KubeconfigLoadingEager, harness.KubeconfigLoadingLazy:
s.KubeconfigLoading = s.Step.KubeconfigLoading
default:
return fmt.Errorf("attribute 'kubeconfigLoading' has invalid value %q", s.Step.KubeconfigLoading)
}
} else {
applies = append(applies, obj)
}
Expand Down

0 comments on commit 7160f43

Please sign in to comment.