Remove fakeclient and use testenv for flux cmd tests

Remove use of the fake client, and replace with a real client connected to the
testEnv.

This required fixes to the yaml files as the testEnv has stricter verifcation
of objects. This also meant it was not possible to test a GitRepository with
a missing artifact since that is not a valid state.

The tests are slower than before, taking around 7-10 seconds each because the
 testEnv is setup and destroyed for every test. These will be sped up in a
 follow up PR.

Signed-off-by: Allen Porter <allen@thebends.org>
pull/1726/head
Allen Porter 4 years ago
parent 55bd93ff79
commit e8d6d5fe5c

@ -25,7 +25,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
_ "k8s.io/client-go/plugin/pkg/client/auth" _ "k8s.io/client-go/plugin/pkg/client/auth"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/manifestgen/install" "github.com/fluxcd/flux2/pkg/manifestgen/install"
) )
@ -127,18 +126,6 @@ func NewRootFlags() rootFlags {
return rf return rf
} }
type rootContext struct {
kubeManager utils.KubeManager
}
var rootCtx = NewRootContext()
func NewRootContext() rootContext {
var rc rootContext
rc.kubeManager = utils.DefaultKubeManager()
return rc
}
func main() { func main() {
log.SetFlags(0) log.SetFlags(0)
configureKubeconfig() configureKubeconfig()

@ -15,12 +15,12 @@ func TestMain(m *testing.M) {
// Ensure tests print consistent timestamps regardless of timezone // Ensure tests print consistent timestamps regardless of timezone
os.Setenv("TZ", "UTC") os.Setenv("TZ", "UTC")
// Install Flux // Install Flux.
km, err := NewTestEnvKubeManager(ExistingClusterMode) // Creating the test env manager sets rootArgs client flags
_, err := NewTestEnvKubeManager(ExistingClusterMode)
if err != nil { if err != nil {
panic(fmt.Errorf("error creating kube manager: '%w'", err)) panic(fmt.Errorf("error creating kube manager: '%w'", err))
} }
rootCtx.kubeManager = km
output, err := executeCommand("install --components-extra=image-reflector-controller,image-automation-controller") output, err := executeCommand("install --components-extra=image-reflector-controller,image-automation-controller")
if err != nil { if err != nil {
panic(fmt.Errorf("install falied: %s error:'%w'", output, err)) panic(fmt.Errorf("install falied: %s error:'%w'", output, err))

@ -13,23 +13,21 @@ import (
"text/template" "text/template"
"time" "time"
"github.com/fluxcd/flux2/internal/utils"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/mattn/go-shellwords" "github.com/mattn/go-shellwords"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
k8syaml "k8s.io/apimachinery/pkg/util/yaml" k8syaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/envtest" "sigs.k8s.io/controller-runtime/pkg/envtest"
) )
func readYamlObjects(objectFile string) ([]client.Object, error) { func readYamlObjects(objectFile string) ([]unstructured.Unstructured, error) {
obj, err := os.ReadFile(objectFile) obj, err := os.ReadFile(objectFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
objects := []client.Object{} objects := []unstructured.Unstructured{}
reader := k8syaml.NewYAMLReader(bufio.NewReader(bytes.NewReader(obj))) reader := k8syaml.NewYAMLReader(bufio.NewReader(bytes.NewReader(obj)))
for { for {
doc, err := reader.Read() doc, err := reader.Read()
@ -44,7 +42,7 @@ func readYamlObjects(objectFile string) ([]client.Object, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
objects = append(objects, unstructuredObj) objects = append(objects, *unstructuredObj)
} }
return objects, nil return objects, nil
} }
@ -59,9 +57,18 @@ func (m *testEnvKubeManager) NewClient(kubeconfig string, kubecontext string) (c
return m.client, nil return m.client, nil
} }
func (m *testEnvKubeManager) CreateObjects(clientObjects []client.Object) error { func (m *testEnvKubeManager) CreateObjects(clientObjects []unstructured.Unstructured) error {
for _, obj := range clientObjects { for _, obj := range clientObjects {
err := m.client.Create(context.Background(), obj) // First create the object then set its status if present in the
// yaml file. Make a copy first since creating an object may overwrite
// the status.
createObj := obj.DeepCopy()
err := m.client.Create(context.Background(), createObj)
if err != nil {
return err
}
obj.SetResourceVersion(createObj.GetResourceVersion())
err = m.client.Status().Update(context.Background(), &obj)
if err != nil { if err != nil {
return err return err
} }
@ -78,15 +85,11 @@ func (m *testEnvKubeManager) Stop() error {
func NewTestEnvKubeManager(testClusterMode TestClusterMode) (*testEnvKubeManager, error) { func NewTestEnvKubeManager(testClusterMode TestClusterMode) (*testEnvKubeManager, error) {
switch testClusterMode { switch testClusterMode {
case FakeClusterMode:
c := fakeclient.NewClientBuilder().WithScheme(utils.NewScheme()).Build()
return &testEnvKubeManager{
client: c,
}, nil
case TestEnvClusterMode: case TestEnvClusterMode:
useExistingCluster := false useExistingCluster := false
testEnv := &envtest.Environment{ testEnv := &envtest.Environment{
UseExistingCluster: &useExistingCluster, UseExistingCluster: &useExistingCluster,
CRDDirectoryPaths: []string{"manifests"},
} }
cfg, err := testEnv.Start() cfg, err := testEnv.Start()
if err != nil { if err != nil {
@ -150,8 +153,7 @@ func NewTestEnvKubeManager(testClusterMode TestClusterMode) (*testEnvKubeManager
type TestClusterMode int type TestClusterMode int
const ( const (
FakeClusterMode = TestClusterMode(iota + 1) TestEnvClusterMode = TestClusterMode(iota + 1)
TestEnvClusterMode
ExistingClusterMode ExistingClusterMode
) )
@ -181,7 +183,6 @@ func (cmd *cmdTestCase) runTestCmd(t *testing.T) {
} }
if km != nil { if km != nil {
rootCtx.kubeManager = km
defer km.Stop() defer km.Stop()
} }

@ -1,8 +1,19 @@
--- ---
apiVersion: v1
kind: Namespace
metadata:
name: flux-system
---
apiVersion: v1
kind: Namespace
metadata:
name: podinfo
---
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
labels: labels:
app.kubernetes.io/name: podinfo
app.kubernetes.io/managed-by: Helm app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: podinfo app.kubernetes.io/name: podinfo
helm.toolkit.fluxcd.io/name: podinfo helm.toolkit.fluxcd.io/name: podinfo
@ -11,11 +22,18 @@ metadata:
namespace: podinfo namespace: podinfo
spec: spec:
replicas: 1 replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: podinfo
template: template:
metadata: metadata:
labels: labels:
app.kubernetes.io/name: podinfo app.kubernetes.io/name: podinfo
spec: spec:
containers:
- name: hello
command: [ "echo hello world" ]
image: busybox
--- ---
apiVersion: helm.toolkit.fluxcd.io/v2beta1 apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease kind: HelmRelease
@ -33,6 +51,7 @@ spec:
kind: HelmRepository kind: HelmRepository
name: podinfo name: podinfo
namespace: flux-system namespace: flux-system
interval: 5m
status: status:
conditions: conditions:
- lastTransitionTime: "2021-07-16T15:42:20Z" - lastTransitionTime: "2021-07-16T15:42:20Z"
@ -56,11 +75,14 @@ spec:
kind: HelmRepository kind: HelmRepository
name: podinfo name: podinfo
version: 6.0.0 version: 6.0.0
interval: 5m
status: status:
artifact: artifact:
checksum: cf13ba96773d9a879cd052c86e73199b3f96c854 checksum: cf13ba96773d9a879cd052c86e73199b3f96c854
lastUpdateTime: "2021-08-01T04:42:55Z" lastUpdateTime: "2021-08-01T04:42:55Z"
revision: 6.0.0 revision: 6.0.0
path: "example"
url: "example"
conditions: conditions:
- lastTransitionTime: "2021-07-16T15:32:09Z" - lastTransitionTime: "2021-07-16T15:32:09Z"
message: 'Fetched revision: 6.0.0' message: 'Fetched revision: 6.0.0'
@ -86,6 +108,8 @@ status:
checksum: 8411f23d07d3701f0e96e7d9e503b7936d7e1d56 checksum: 8411f23d07d3701f0e96e7d9e503b7936d7e1d56
lastUpdateTime: "2021-07-11T00:25:46Z" lastUpdateTime: "2021-07-11T00:25:46Z"
revision: 8411f23d07d3701f0e96e7d9e503b7936d7e1d56 revision: 8411f23d07d3701f0e96e7d9e503b7936d7e1d56
path: "example"
url: "example"
conditions: conditions:
- lastTransitionTime: "2021-07-11T00:25:46Z" - lastTransitionTime: "2021-07-11T00:25:46Z"
message: 'Fetched revision: 8411f23d07d3701f0e96e7d9e503b7936d7e1d56' message: 'Fetched revision: 8411f23d07d3701f0e96e7d9e503b7936d7e1d56'
@ -104,6 +128,8 @@ spec:
kind: GitRepository kind: GitRepository
name: flux-system name: flux-system
validation: client validation: client
interval: 5m
prune: true
status: status:
conditions: conditions:
- lastTransitionTime: "2021-08-01T04:52:56Z" - lastTransitionTime: "2021-08-01T04:52:56Z"
@ -126,4 +152,5 @@ spec:
branch: main branch: main
secretRef: secretRef:
name: flux-system name: flux-system
interval: 5m
url: ssh://git@github.com/example/repo

@ -1,18 +0,0 @@
Object: HelmRelease/podinfo
Namespace: podinfo
Status: Managed by Flux
---
Kustomization: infrastructure
Namespace: flux-system
Path: ./infrastructure
Revision: main/696f056df216eea4f9401adbee0ff744d4df390f
Status: Last reconciled at 2021-08-01 04:52:56 +0000 UTC
Message: Applied revision: main/696f056df216eea4f9401adbee0ff744d4df390f
---
GitRepository: flux-system
Namespace: flux-system
URL: ssh://git@github.com/example/repo
Revision: main/696f056df216eea4f9401adbee0ff744d4df390f
Status: Last reconciled at 2021-07-20 00:48:16 +0000 UTC
Message: Fetched revision: main/696f056df216eea4f9401adbee0ff744d4df390f

@ -1,73 +0,0 @@
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
labels:
kustomize.toolkit.fluxcd.io/name: infrastructure
kustomize.toolkit.fluxcd.io/namespace: flux-system
name: podinfo
namespace: podinfo
spec:
chart:
spec:
chart: podinfo
sourceRef:
kind: HelmRepository
name: podinfo
namespace: flux-system
status:
conditions:
- lastTransitionTime: "2021-07-16T15:42:20Z"
message: Release reconciliation succeeded
reason: ReconciliationSucceeded
status: "True"
type: Ready
helmChart: flux-system/podinfo-podinfo
lastAppliedRevision: 6.0.0
lastAttemptedRevision: 6.0.0
lastAttemptedValuesChecksum: c31db75d05b7515eba2eef47bd71038c74b2e531
---
apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
kind: Kustomization
metadata:
name: infrastructure
namespace: flux-system
spec:
path: ./infrastructure
sourceRef:
kind: GitRepository
name: flux-system
validation: client
status:
conditions:
- lastTransitionTime: "2021-08-01T04:52:56Z"
message: 'Applied revision: main/696f056df216eea4f9401adbee0ff744d4df390f'
reason: ReconciliationSucceeded
status: "True"
type: Ready
lastAppliedRevision: main/696f056df216eea4f9401adbee0ff744d4df390f
---
apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: GitRepository
metadata:
labels:
kustomize.toolkit.fluxcd.io/name: flux-system
kustomize.toolkit.fluxcd.io/namespace: flux-system
name: flux-system
namespace: flux-system
spec:
gitImplementation: go-git
secretRef:
name: flux-system
url: ssh://git@github.com/example/repo
status:
artifact:
lastUpdateTime: "2021-08-01T04:28:42Z"
revision: main/696f056df216eea4f9401adbee0ff744d4df390f
conditions:
- lastTransitionTime: "2021-07-20T00:48:16Z"
message: 'Fetched revision: main/696f056df216eea4f9401adbee0ff744d4df390f'
reason: GitOperationSucceed
status: "True"
type: Ready

@ -1,4 +1,14 @@
--- ---
apiVersion: v1
kind: Namespace
metadata:
name: flux-system
---
apiVersion: v1
kind: Namespace
metadata:
name: podinfo
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1 apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease kind: HelmRelease
metadata: metadata:
@ -15,6 +25,7 @@ spec:
kind: HelmRepository kind: HelmRepository
name: podinfo name: podinfo
namespace: flux-system namespace: flux-system
interval: 5m
status: status:
conditions: conditions:
- lastTransitionTime: "2021-07-16T15:42:20Z" - lastTransitionTime: "2021-07-16T15:42:20Z"
@ -38,6 +49,8 @@ spec:
kind: GitRepository kind: GitRepository
name: flux-system name: flux-system
validation: client validation: client
interval: 5m
prune: false
status: status:
conditions: conditions:
- lastTransitionTime: "2021-08-01T04:52:56Z" - lastTransitionTime: "2021-08-01T04:52:56Z"
@ -62,14 +75,16 @@ spec:
secretRef: secretRef:
name: flux-system name: flux-system
url: ssh://git@github.com/example/repo url: ssh://git@github.com/example/repo
interval: 5m
status: status:
artifact: artifact:
lastUpdateTime: "2021-08-01T04:28:42Z" lastUpdateTime: "2021-08-01T04:28:42Z"
revision: main/696f056df216eea4f9401adbee0ff744d4df390f revision: main/696f056df216eea4f9401adbee0ff744d4df390f
path: "example"
url: "example"
conditions: conditions:
- lastTransitionTime: "2021-07-20T00:48:16Z" - lastTransitionTime: "2021-07-20T00:48:16Z"
message: 'Fetched revision: main/696f056df216eea4f9401adbee0ff744d4df390f' message: 'Fetched revision: main/696f056df216eea4f9401adbee0ff744d4df390f'
reason: GitOperationSucceed reason: GitOperationSucceed
status: "True" status: "True"
type: Ready type: Ready

@ -89,7 +89,7 @@ func traceCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
kubeClient, err := rootCtx.kubeManager.NewClient(rootArgs.kubeconfig, rootArgs.kubecontext) kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
} }

@ -9,7 +9,7 @@ import (
func TestTraceNoArgs(t *testing.T) { func TestTraceNoArgs(t *testing.T) {
cmd := cmdTestCase{ cmd := cmdTestCase{
args: "trace", args: "trace",
testClusterMode: FakeClusterMode, testClusterMode: TestEnvClusterMode,
wantError: true, wantError: true,
goldenValue: "object name is required", goldenValue: "object name is required",
} }
@ -19,7 +19,7 @@ func TestTraceNoArgs(t *testing.T) {
func TestTraceDeployment(t *testing.T) { func TestTraceDeployment(t *testing.T) {
cmd := cmdTestCase{ cmd := cmdTestCase{
args: "trace podinfo -n podinfo --kind deployment --api-version=apps/v1", args: "trace podinfo -n podinfo --kind deployment --api-version=apps/v1",
testClusterMode: FakeClusterMode, testClusterMode: TestEnvClusterMode,
wantError: false, wantError: false,
goldenFile: "testdata/trace/deployment.txt", goldenFile: "testdata/trace/deployment.txt",
objectFile: "testdata/trace/deployment.yaml", objectFile: "testdata/trace/deployment.yaml",
@ -30,21 +30,10 @@ func TestTraceDeployment(t *testing.T) {
func TestTraceHelmRelease(t *testing.T) { func TestTraceHelmRelease(t *testing.T) {
cmd := cmdTestCase{ cmd := cmdTestCase{
args: "trace podinfo -n podinfo --kind HelmRelease --api-version=helm.toolkit.fluxcd.io/v2beta1", args: "trace podinfo -n podinfo --kind HelmRelease --api-version=helm.toolkit.fluxcd.io/v2beta1",
testClusterMode: FakeClusterMode, testClusterMode: TestEnvClusterMode,
wantError: false, wantError: false,
goldenFile: "testdata/trace/helmrelease.txt", goldenFile: "testdata/trace/helmrelease.txt",
objectFile: "testdata/trace/helmrelease.yaml", objectFile: "testdata/trace/helmrelease.yaml",
} }
cmd.runTestCmd(t) cmd.runTestCmd(t)
} }
func TestTraceHelmReleaseMissingGitRef(t *testing.T) {
cmd := cmdTestCase{
args: "trace podinfo -n podinfo --kind HelmRelease --api-version=helm.toolkit.fluxcd.io/v2beta1",
testClusterMode: FakeClusterMode,
wantError: false,
goldenFile: "testdata/trace/helmrelease-missing-git-ref.txt",
objectFile: "testdata/trace/helmrelease-missing-git-ref.yaml",
}
cmd.runTestCmd(t)
}

@ -8,8 +8,9 @@ import (
func TestVersion(t *testing.T) { func TestVersion(t *testing.T) {
cmd := cmdTestCase{ cmd := cmdTestCase{
args: "--version", args: "--version",
goldenValue: "flux version 0.0.0-dev.0\n", testClusterMode: TestEnvClusterMode,
goldenValue: "flux version 0.0.0-dev.0\n",
} }
cmd.runTestCmd(t) cmd.runTestCmd(t)
} }

@ -131,36 +131,6 @@ func KubeConfig(kubeConfigPath string, kubeContext string) (*rest.Config, error)
return cfg, nil return cfg, nil
} }
// KubeManger creates a Kubernetes client.Client. This interface exists to
// facilitate unit testing and provide a fake client.
type KubeManager interface {
NewClient(string, string) (client.WithWatch, error)
}
type defaultKubeManager struct{}
func DefaultKubeManager() KubeManager {
var manager defaultKubeManager
return manager
}
func (m defaultKubeManager) NewClient(kubeConfigPath string, kubeContext string) (client.WithWatch, error) {
cfg, err := KubeConfig(kubeConfigPath, kubeContext)
if err != nil {
return nil, fmt.Errorf("kubernetes client initialization failed: %w", err)
}
scheme := NewScheme()
kubeClient, err := client.NewWithWatch(cfg, client.Options{
Scheme: scheme,
})
if err != nil {
return nil, fmt.Errorf("kubernetes client initialization failed: %w", err)
}
return kubeClient, nil
}
// Create the Scheme, methods for serializing and deserializing API objects // Create the Scheme, methods for serializing and deserializing API objects
// which can be shared by tests. // which can be shared by tests.
func NewScheme() *apiruntime.Scheme { func NewScheme() *apiruntime.Scheme {
@ -180,9 +150,20 @@ func NewScheme() *apiruntime.Scheme {
} }
func KubeClient(kubeConfigPath string, kubeContext string) (client.WithWatch, error) { func KubeClient(kubeConfigPath string, kubeContext string) (client.WithWatch, error) {
m := DefaultKubeManager() cfg, err := KubeConfig(kubeConfigPath, kubeContext)
kubeClient, err := m.NewClient(kubeConfigPath, kubeContext) if err != nil {
return kubeClient, err return nil, fmt.Errorf("kubernetes client initialization failed: %w", err)
}
scheme := NewScheme()
kubeClient, err := client.NewWithWatch(cfg, client.Options{
Scheme: scheme,
})
if err != nil {
return nil, fmt.Errorf("kubernetes client initialization failed: %w", err)
}
return kubeClient, nil
} }
// SplitKubeConfigPath splits the given KUBECONFIG path based on the runtime OS // SplitKubeConfigPath splits the given KUBECONFIG path based on the runtime OS

Loading…
Cancel
Save