diff --git a/cmd/flux/main.go b/cmd/flux/main.go index 3ef4abacce28ad4de6c92d0a985d548bb0c19c24..9295e83ea3ea7eb6daf56a62d46e666a44c10497 100644 --- a/cmd/flux/main.go +++ b/cmd/flux/main.go @@ -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() diff --git a/cmd/flux/main_e2e_test.go b/cmd/flux/main_e2e_test.go index 23f06f041dcf36f2bc184d173177c4eb8ab56e51..8851f19c3421ca97a3fd150747d2f42685dcb98c 100644 --- a/cmd/flux/main_e2e_test.go +++ b/cmd/flux/main_e2e_test.go @@ -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)) diff --git a/cmd/flux/main_test.go b/cmd/flux/main_test.go index 0f4df3b7a493cab6d07b26fdefc1a30c844e0027..70ab23a2bce645f24bad4b20e2732b8d8fab3e4a 100644 --- a/cmd/flux/main_test.go +++ b/cmd/flux/main_test.go @@ -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() } diff --git a/cmd/flux/testdata/trace/deployment.yaml b/cmd/flux/testdata/trace/deployment.yaml index 74fc5e46223ffbe49d6b379e624700566d762c05..b1eeb019d667ece14c57cbe0b9c4627c2dd7e6a6 100644 --- a/cmd/flux/testdata/trace/deployment.yaml +++ b/cmd/flux/testdata/trace/deployment.yaml @@ -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 diff --git a/cmd/flux/testdata/trace/helmrelease-missing-git-ref.txt b/cmd/flux/testdata/trace/helmrelease-missing-git-ref.txt deleted file mode 100644 index 7eb18c5012f1481fca2c665caf01a6d818a0d4c0..0000000000000000000000000000000000000000 --- a/cmd/flux/testdata/trace/helmrelease-missing-git-ref.txt +++ /dev/null @@ -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 diff --git a/cmd/flux/testdata/trace/helmrelease-missing-git-ref.yaml b/cmd/flux/testdata/trace/helmrelease-missing-git-ref.yaml deleted file mode 100644 index dee892aee291aeec6dc289b81acb3f6e69a1aad9..0000000000000000000000000000000000000000 --- a/cmd/flux/testdata/trace/helmrelease-missing-git-ref.yaml +++ /dev/null @@ -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 - diff --git a/cmd/flux/testdata/trace/helmrelease.yaml b/cmd/flux/testdata/trace/helmrelease.yaml index 4f0db83df72d8afe7f9558875a976f7a36c5f58f..90df86f458f08de4a2ce7666d5445803c3fa131c 100644 --- a/cmd/flux/testdata/trace/helmrelease.yaml +++ b/cmd/flux/testdata/trace/helmrelease.yaml @@ -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 - diff --git a/cmd/flux/trace.go b/cmd/flux/trace.go index c18449f131a2b638c871073922cbe84227388bb2..807cdaaf75cafe0a2d765f1685225d855a283b66 100644 --- a/cmd/flux/trace.go +++ b/cmd/flux/trace.go @@ -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 } diff --git a/cmd/flux/trace_test.go b/cmd/flux/trace_test.go index c0b538e31fe6d7fb9e896708f916c0db512c039b..c30951ef58f22fb48d174ff74a685f96a9785866 100644 --- a/cmd/flux/trace_test.go +++ b/cmd/flux/trace_test.go @@ -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) -} diff --git a/cmd/flux/version_test.go b/cmd/flux/version_test.go index 94419bc023965c24db78368a92cc38c97d095b5f..7dde5b0cd8ecacc11908386f88d4cdeb8df59f79 100644 --- a/cmd/flux/version_test.go +++ b/cmd/flux/version_test.go @@ -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) } diff --git a/internal/utils/utils.go b/internal/utils/utils.go index db323b4c90a48001d883e144be90522b73fce110..d06629e24c857d9a3bb9c3d0b82cdbd35af557da 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -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