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 3 years ago
parent 55bd93ff79
commit e8d6d5fe5c

@ -25,7 +25,6 @@ import (
"github.com/spf13/cobra"
_ "k8s.io/client-go/plugin/pkg/client/auth"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/manifestgen/install"
)
@ -127,18 +126,6 @@ func NewRootFlags() rootFlags {
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() {
log.SetFlags(0)
configureKubeconfig()

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

@ -13,23 +13,21 @@ import (
"text/template"
"time"
"github.com/fluxcd/flux2/internal/utils"
"github.com/google/go-cmp/cmp"
"github.com/mattn/go-shellwords"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/tools/clientcmd"
"sigs.k8s.io/controller-runtime/pkg/client"
fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
"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)
if err != nil {
return nil, err
}
objects := []client.Object{}
objects := []unstructured.Unstructured{}
reader := k8syaml.NewYAMLReader(bufio.NewReader(bytes.NewReader(obj)))
for {
doc, err := reader.Read()
@ -44,7 +42,7 @@ func readYamlObjects(objectFile string) ([]client.Object, error) {
if err != nil {
return nil, err
}
objects = append(objects, unstructuredObj)
objects = append(objects, *unstructuredObj)
}
return objects, nil
}
@ -59,9 +57,18 @@ func (m *testEnvKubeManager) NewClient(kubeconfig string, kubecontext string) (c
return m.client, nil
}
func (m *testEnvKubeManager) CreateObjects(clientObjects []client.Object) error {
func (m *testEnvKubeManager) CreateObjects(clientObjects []unstructured.Unstructured) error {
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 {
return err
}
@ -78,15 +85,11 @@ func (m *testEnvKubeManager) Stop() error {
func NewTestEnvKubeManager(testClusterMode TestClusterMode) (*testEnvKubeManager, error) {
switch testClusterMode {
case FakeClusterMode:
c := fakeclient.NewClientBuilder().WithScheme(utils.NewScheme()).Build()
return &testEnvKubeManager{
client: c,
}, nil
case TestEnvClusterMode:
useExistingCluster := false
testEnv := &envtest.Environment{
UseExistingCluster: &useExistingCluster,
CRDDirectoryPaths: []string{"manifests"},
}
cfg, err := testEnv.Start()
if err != nil {
@ -150,8 +153,7 @@ func NewTestEnvKubeManager(testClusterMode TestClusterMode) (*testEnvKubeManager
type TestClusterMode int
const (
FakeClusterMode = TestClusterMode(iota + 1)
TestEnvClusterMode
TestEnvClusterMode = TestClusterMode(iota + 1)
ExistingClusterMode
)
@ -181,7 +183,6 @@ func (cmd *cmdTestCase) runTestCmd(t *testing.T) {
}
if km != nil {
rootCtx.kubeManager = km
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
kind: Deployment
metadata:
labels:
app.kubernetes.io/name: podinfo
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: podinfo
helm.toolkit.fluxcd.io/name: podinfo
@ -11,11 +22,18 @@ metadata:
namespace: podinfo
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: podinfo
template:
metadata:
labels:
app.kubernetes.io/name: podinfo
spec:
containers:
- name: hello
command: [ "echo hello world" ]
image: busybox
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
@ -33,6 +51,7 @@ spec:
kind: HelmRepository
name: podinfo
namespace: flux-system
interval: 5m
status:
conditions:
- lastTransitionTime: "2021-07-16T15:42:20Z"
@ -56,11 +75,14 @@ spec:
kind: HelmRepository
name: podinfo
version: 6.0.0
interval: 5m
status:
artifact:
checksum: cf13ba96773d9a879cd052c86e73199b3f96c854
lastUpdateTime: "2021-08-01T04:42:55Z"
revision: 6.0.0
path: "example"
url: "example"
conditions:
- lastTransitionTime: "2021-07-16T15:32:09Z"
message: 'Fetched revision: 6.0.0'
@ -86,6 +108,8 @@ status:
checksum: 8411f23d07d3701f0e96e7d9e503b7936d7e1d56
lastUpdateTime: "2021-07-11T00:25:46Z"
revision: 8411f23d07d3701f0e96e7d9e503b7936d7e1d56
path: "example"
url: "example"
conditions:
- lastTransitionTime: "2021-07-11T00:25:46Z"
message: 'Fetched revision: 8411f23d07d3701f0e96e7d9e503b7936d7e1d56'
@ -104,6 +128,8 @@ spec:
kind: GitRepository
name: flux-system
validation: client
interval: 5m
prune: true
status:
conditions:
- lastTransitionTime: "2021-08-01T04:52:56Z"
@ -126,4 +152,5 @@ spec:
branch: main
secretRef:
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
kind: HelmRelease
metadata:
@ -15,6 +25,7 @@ spec:
kind: HelmRepository
name: podinfo
namespace: flux-system
interval: 5m
status:
conditions:
- lastTransitionTime: "2021-07-16T15:42:20Z"
@ -38,6 +49,8 @@ spec:
kind: GitRepository
name: flux-system
validation: client
interval: 5m
prune: false
status:
conditions:
- lastTransitionTime: "2021-08-01T04:52:56Z"
@ -62,14 +75,16 @@ spec:
secretRef:
name: flux-system
url: ssh://git@github.com/example/repo
interval: 5m
status:
artifact:
lastUpdateTime: "2021-08-01T04:28:42Z"
revision: main/696f056df216eea4f9401adbee0ff744d4df390f
path: "example"
url: "example"
conditions:
- lastTransitionTime: "2021-07-20T00:48:16Z"
message: 'Fetched revision: main/696f056df216eea4f9401adbee0ff744d4df390f'
reason: GitOperationSucceed
status: "True"
type: Ready

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

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

@ -131,36 +131,6 @@ func KubeConfig(kubeConfigPath string, kubeContext string) (*rest.Config, error)
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
// which can be shared by tests.
func NewScheme() *apiruntime.Scheme {
@ -180,9 +150,20 @@ func NewScheme() *apiruntime.Scheme {
}
func KubeClient(kubeConfigPath string, kubeContext string) (client.WithWatch, error) {
m := DefaultKubeManager()
kubeClient, err := m.NewClient(kubeConfigPath, kubeContext)
return kubeClient, err
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
}
// SplitKubeConfigPath splits the given KUBECONFIG path based on the runtime OS

Loading…
Cancel
Save