diff --git a/cmd/main.go b/cmd/main.go index 03ebd7e292b..060ed7f7849 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -620,6 +620,12 @@ func main() { InsecureIPXEURLs: generateInsecureIPXEURLs, }).SetupWithManager(ctrlMgr), "unable to create controller InfraEnv") + spokeClientFactory, err := spoke_k8s_client.NewFactory(). + SetLogger(log). + SetHubClient(c). + Build() + failOnError(err, "unable to create spoke client factory") + cluster_client := ctrlMgr.GetClient() cluster_reader := ctrlMgr.GetAPIReader() failOnError((&controllers.ClusterDeploymentsReconciler{ @@ -636,7 +642,7 @@ func main() { PullSecretHandler: controllers.NewPullSecretHandler(cluster_client, cluster_reader, bm), AuthType: Options.Auth.AuthType, VersionsHandler: versionHandler, - SpokeK8sClientFactory: spoke_k8s_client.NewSpokeK8sClientFactory(log), + SpokeK8sClientFactory: spokeClientFactory, MirrorRegistriesConfigBuilder: mirrorregistries.New(), }).SetupWithManager(ctrlMgr), "unable to create controller ClusterDeployment") @@ -649,7 +655,7 @@ func main() { CRDEventsHandler: crdEventsHandler, ServiceBaseURL: Options.BMConfig.ServiceBaseURL, AuthType: Options.Auth.AuthType, - SpokeK8sClientFactory: spoke_k8s_client.NewSpokeK8sClientFactory(log), + SpokeK8sClientFactory: spokeClientFactory, ApproveCsrsRequeueDuration: Options.ApproveCsrsRequeueDuration, AgentContainerImage: Options.BMConfig.AgentDockerImg, HostFSMountDir: hostFSMountDir, @@ -661,7 +667,7 @@ func main() { Log: log, Scheme: ctrlMgr.GetScheme(), Installer: bm, - SpokeK8sClientFactory: spoke_k8s_client.NewSpokeK8sClientFactory(log), + SpokeK8sClientFactory: spokeClientFactory, ConvergedFlowEnabled: useConvergedFlow, PauseProvisionedBMHs: Options.PauseProvisionedBMHs, Drainer: &controllers.KubectlDrainer{}, diff --git a/cmd/operator/main.go b/cmd/operator/main.go index 06d89648405..714ea856565 100644 --- a/cmd/operator/main.go +++ b/cmd/operator/main.go @@ -160,7 +160,14 @@ func main() { } log := logrus.New() - spokeClientFactory := spoke_k8s_client.NewSpokeK8sClientFactory(log) + spokeClientFactory, err := spoke_k8s_client.NewFactory(). + SetLogger(log). + SetHubClient(mgr.GetClient()). + Build() + if err != nil { + log.WithError(err).Error("failed to create spoke client factory") + os.Exit(1) + } spokeClientCache := controllers.NewSpokeClientCache(spokeClientFactory) c, err := client.New(mgr.GetConfig(), client.Options{Scheme: mgr.GetScheme()}) diff --git a/hack/start_db.sh b/hack/start_db.sh index a922e96d8e7..90ea804b65c 100755 --- a/hack/start_db.sh +++ b/hack/start_db.sh @@ -8,4 +8,11 @@ mkdir -p /tmp/postgres/data mkdir -p /tmp/postgres/sockets initdb -D /tmp/postgres/data -U postgres + +# We store the data files in the `/tmp` directory assuming that it will be a `tmpfs` and therefore +# very fast. But in some environments it may not be, as it may be mapped to a real persistent file +# system. Disabling `fsync` speeds things up in those environment, at the cost of risking data loss, +# but we don't really care about that, otherwise we woudn't be using `/tmp`. +echo "fsync=off" >> /tmp/postgres/data/postgresql.conf + pg_ctl -D /tmp/postgres/data -l /tmp/postgres/logfile -o'-k /tmp/postgres/sockets -p 5433' start diff --git a/internal/controller/controllers/agent_controller.go b/internal/controller/controllers/agent_controller.go index 5e159666857..2666e8f43f9 100644 --- a/internal/controller/controllers/agent_controller.go +++ b/internal/controller/controllers/agent_controller.go @@ -95,7 +95,7 @@ type AgentReconciler struct { CRDEventsHandler CRDEventsHandler ServiceBaseURL string AuthType auth.AuthType - SpokeK8sClientFactory spoke_k8s_client.SpokeK8sClientFactory + SpokeK8sClientFactory spoke_k8s_client.Factory ApproveCsrsRequeueDuration time.Duration AgentContainerImage string HostFSMountDir string @@ -360,7 +360,7 @@ func deleteBMHForMachine(ctx context.Context, spokeClient client.Client, machine // removeSpokeResources removes all relevant resources from the agent's spoke cluster // This includes all or some of the node, machine, BMH, and scaling the machineset depending on what is present -func removeSpokeResources(ctx context.Context, log logrus.FieldLogger, spokeClient spoke_k8s_client.SpokeK8sClient, nodeName string) error { +func removeSpokeResources(ctx context.Context, log logrus.FieldLogger, spokeClient spoke_k8s_client.Client, nodeName string) error { log = log.WithField("node", nodeName) nodeKey := client.ObjectKey{Name: nodeName} @@ -540,7 +540,7 @@ func (r *AgentReconciler) shouldApproveCSR(csr *certificatesv1.CertificateSignin return validateNodeCsr(agent, csr, x509CSR) } -func (r *AgentReconciler) approveAIHostsCSRs(ctx context.Context, clients spoke_k8s_client.SpokeK8sClient, agent *aiv1beta1.Agent, validateNodeCsr nodeCsrValidator) { +func (r *AgentReconciler) approveAIHostsCSRs(ctx context.Context, clients spoke_k8s_client.Client, agent *aiv1beta1.Agent, validateNodeCsr nodeCsrValidator) { csrList, err := clients.ListCsrs(ctx) if err != nil { r.Log.WithError(err).Errorf("Failed to get CSRs for agent %s/%s", agent.Namespace, agent.Name) @@ -565,18 +565,22 @@ func (r *AgentReconciler) approveAIHostsCSRs(ctx context.Context, clients spoke_ } } -func (r *AgentReconciler) spokeKubeClient(ctx context.Context, clusterRef *aiv1beta1.ClusterReference) (spoke_k8s_client.SpokeK8sClient, error) { +func (r *AgentReconciler) spokeKubeClient(ctx context.Context, clusterRef *aiv1beta1.ClusterReference) (spoke_k8s_client.Client, error) { secret, err := spokeKubeconfigSecret(ctx, r.Log, r.Client, r.APIReader, clusterRef) if err != nil { r.Log.WithError(err).Errorf("failed to get spoke secret for cluster %s/%s", clusterRef.Namespace, clusterRef.Name) return nil, err } - return r.SpokeK8sClientFactory.CreateFromSecret(secret) + clusterKey := types.NamespacedName{ + Namespace: clusterRef.Namespace, + Name: clusterRef.Name, + } + return r.SpokeK8sClientFactory.CreateFromSecret(ctx, clusterKey, secret) } // Attempt to approve CSRs for agent. If already approved then the node will be marked as done // requeue means that approval will be attempted again -func (r *AgentReconciler) tryApproveDay2CSRs(ctx context.Context, agent *aiv1beta1.Agent, node *corev1.Node, client spoke_k8s_client.SpokeK8sClient) { +func (r *AgentReconciler) tryApproveDay2CSRs(ctx context.Context, agent *aiv1beta1.Agent, node *corev1.Node, client spoke_k8s_client.Client) { r.Log.Infof("Approving CSRs for agent %s/%s", agent.Namespace, agent.Name) var validateNodeCsr nodeCsrValidator @@ -771,7 +775,7 @@ func marshalNodeLabels(nodeLabels map[string]string) (string, error) { return string(b), err } -func (r *AgentReconciler) applyDay2NodeLabels(ctx context.Context, log logrus.FieldLogger, agent *aiv1beta1.Agent, node *corev1.Node, client spoke_k8s_client.SpokeK8sClient) error { +func (r *AgentReconciler) applyDay2NodeLabels(ctx context.Context, log logrus.FieldLogger, agent *aiv1beta1.Agent, node *corev1.Node, client spoke_k8s_client.Client) error { if funk.IsEmpty(agent.Spec.NodeLabels) || !isNodeReady(node) { return nil } @@ -805,7 +809,7 @@ func (r *AgentReconciler) updateStatus(ctx context.Context, log logrus.FieldLogg var ( err error shouldAutoApproveCSRs bool - spokeClient spoke_k8s_client.SpokeK8sClient + spokeClient spoke_k8s_client.Client node *corev1.Node ) ret := ctrl.Result{} diff --git a/internal/controller/controllers/agent_controller_test.go b/internal/controller/controllers/agent_controller_test.go index e957f49b18d..777fc8d347e 100644 --- a/internal/controller/controllers/agent_controller_test.go +++ b/internal/controller/controllers/agent_controller_test.go @@ -89,7 +89,7 @@ var _ = Describe("agent reconcile", func() { sId strfmt.UUID backEndCluster *common.Cluster ignitionEndpointTokenSecretName = "ignition-endpoint-secret" - mockClientFactory *spoke_k8s_client.MockSpokeK8sClientFactory + mockClientFactory *spoke_k8s_client.MockFactory agentImage = "registry.example.com/assisted-installer/agent:latest" ) @@ -98,7 +98,7 @@ var _ = Describe("agent reconcile", func() { WithStatusSubresource(&v1beta1.Agent{}).Build() mockCtrl = gomock.NewController(GinkgoT()) mockInstallerInternal = bminventory.NewMockInstallerInternals(mockCtrl) - mockClientFactory = spoke_k8s_client.NewMockSpokeK8sClientFactory(mockCtrl) + mockClientFactory = spoke_k8s_client.NewMockFactory(mockCtrl) hr = &AgentReconciler{ Client: c, @@ -599,8 +599,8 @@ var _ = Describe("agent reconcile", func() { allowGetInfraEnvInternal(mockInstallerInternal, infraEnvId, "infraEnvName") Expect(c.Create(ctx, host)).To(BeNil()) createKubeconfigSecret(clusterDeployment.Name) - mockClient := spoke_k8s_client.NewMockSpokeK8sClient(mockCtrl) - mockClientFactory.EXPECT().CreateFromSecret(gomock.Any()).Return(mockClient, nil).AnyTimes() + mockClient := spoke_k8s_client.NewMockClient(mockCtrl) + mockClientFactory.EXPECT().CreateFromSecret(gomock.Any(), gomock.Any(), gomock.Any()).Return(mockClient, nil).AnyTimes() mockClient.EXPECT().GetNode(gomock.Any(), gomock.Any()).Return(nil, k8serrors.NewNotFound(schema.GroupResource{Group: "v1", Resource: "Node"}, commonHost.RequestedHostname)).Times(1) result, err := hr.Reconcile(ctx, newHostRequest(host)) @@ -629,8 +629,8 @@ var _ = Describe("agent reconcile", func() { } Expect(c.Create(ctx, host)).To(BeNil()) createKubeconfigSecret(clusterDeployment.Name) - mockClient := spoke_k8s_client.NewMockSpokeK8sClient(mockCtrl) - mockClientFactory.EXPECT().CreateFromSecret(gomock.Any()).Return(mockClient, nil).AnyTimes() + mockClient := spoke_k8s_client.NewMockClient(mockCtrl) + mockClientFactory.EXPECT().CreateFromSecret(gomock.Any(), gomock.Any(), gomock.Any()).Return(mockClient, nil).AnyTimes() node := &corev1.Node{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ @@ -677,8 +677,8 @@ var _ = Describe("agent reconcile", func() { } Expect(c.Create(ctx, host)).To(BeNil()) createKubeconfigSecret(clusterDeployment.Name) - mockClient := spoke_k8s_client.NewMockSpokeK8sClient(mockCtrl) - mockClientFactory.EXPECT().CreateFromSecret(gomock.Any()).Return(mockClient, nil).AnyTimes() + mockClient := spoke_k8s_client.NewMockClient(mockCtrl) + mockClientFactory.EXPECT().CreateFromSecret(gomock.Any(), gomock.Any(), gomock.Any()).Return(mockClient, nil).AnyTimes() node := &corev1.Node{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ @@ -976,8 +976,8 @@ var _ = Describe("agent reconcile", func() { createKubeconfigSecret() expectDBClusterWithKubeKeys() - mockClient := spoke_k8s_client.NewMockSpokeK8sClient(mockCtrl) - mockClientFactory.EXPECT().CreateFromSecret(gomock.Any()).Return(mockClient, nil).AnyTimes() + mockClient := spoke_k8s_client.NewMockClient(mockCtrl) + mockClientFactory.EXPECT().CreateFromSecret(gomock.Any(), gomock.Any(), gomock.Any()).Return(mockClient, nil).AnyTimes() mockClient.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.AssignableToTypeOf(&corev1.Namespace{})).Return(nil) mockClient.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.AssignableToTypeOf(&corev1.ServiceAccount{})).Return(nil) mockClient.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.AssignableToTypeOf(&authzv1.Role{})).Return(nil) @@ -1008,7 +1008,7 @@ var _ = Describe("agent reconcile", func() { createKubeconfigSecret() expectDBClusterWithKubeKeys() - mockClientFactory.EXPECT().CreateFromSecret(gomock.Any()).Return(nil, errors.New("failed to create client")) + mockClientFactory.EXPECT().CreateFromSecret(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("failed to create client")) mockInstallerInternal.EXPECT().UnbindHostInternal(gomock.Any(), gomock.Any(), false, bminventory.NonInteractive).Return(commonHost, nil) result, err := hr.Reconcile(ctx, newHostRequest(host)) @@ -1021,8 +1021,8 @@ var _ = Describe("agent reconcile", func() { createKubeconfigSecret() expectDBClusterWithKubeKeys() - mockClient := spoke_k8s_client.NewMockSpokeK8sClient(mockCtrl) - mockClientFactory.EXPECT().CreateFromSecret(gomock.Any()).Return(mockClient, nil).AnyTimes() + mockClient := spoke_k8s_client.NewMockClient(mockCtrl) + mockClientFactory.EXPECT().CreateFromSecret(gomock.Any(), gomock.Any(), gomock.Any()).Return(mockClient, nil).AnyTimes() mockClient.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.AssignableToTypeOf(&corev1.Namespace{})).Return(nil) mockClient.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.AssignableToTypeOf(&corev1.ServiceAccount{})).Return(nil) mockClient.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.AssignableToTypeOf(&authzv1.Role{})).Return(nil) @@ -1340,7 +1340,7 @@ var _ = Describe("agent reconcile", func() { clusterDeploymentName = "test-cluster" agent *v1beta1.Agent agentHostname = "agent.example.com" - spokeClient spoke_k8s_client.SpokeK8sClient + spokeClient spoke_k8s_client.Client host *common.Host ) @@ -1404,7 +1404,7 @@ var _ = Describe("agent reconcile", func() { schemes := GetKubeClientSchemes() fakeClient := fakeclient.NewClientBuilder().WithScheme(schemes).Build() spokeClient = fakeSpokeK8sClient{Client: fakeClient} - mockClientFactory.EXPECT().CreateFromSecret(gomock.Any()).Return(spokeClient, nil).AnyTimes() + mockClientFactory.EXPECT().CreateFromSecret(gomock.Any(), gomock.Any(), gomock.Any()).Return(spokeClient, nil).AnyTimes() node := &corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: agentHostname}} Expect(spokeClient.Create(ctx, node)).To(Succeed()) } @@ -1468,8 +1468,8 @@ var _ = Describe("agent reconcile", func() { } Expect(c.Create(ctx, agent)).To(Succeed()) - mockSpokeClient := spoke_k8s_client.NewMockSpokeK8sClient(mockCtrl) - mockClientFactory.EXPECT().CreateFromSecret(gomock.Any()).Return(mockSpokeClient, nil).AnyTimes() + mockSpokeClient := spoke_k8s_client.NewMockClient(mockCtrl) + mockClientFactory.EXPECT().CreateFromSecret(gomock.Any(), gomock.Any(), gomock.Any()).Return(mockSpokeClient, nil).AnyTimes() mockSpokeClient.EXPECT().Get(gomock.Any(), client.ObjectKey{Name: agentHostname}, gomock.Any()).Return(fmt.Errorf("get-failed")) result, err := hr.Reconcile(ctx, newHostRequest(agent)) @@ -2300,7 +2300,7 @@ VU1eS0RiS/Lz6HwRs2mATNY5FrpZOgdM3cI= agentKey types.NamespacedName hostId strfmt.UUID mockInstallerInternal *bminventory.MockInstallerInternals - mockClientFactory *spoke_k8s_client.MockSpokeK8sClientFactory + mockClientFactory *spoke_k8s_client.MockFactory commonHost *common.Host ) newAciWithUserManagedNetworkingNoSNO := func(name, namespace string) *hiveext.AgentClusterInstall { @@ -2497,7 +2497,7 @@ VU1eS0RiS/Lz6HwRs2mATNY5FrpZOgdM3cI= WithStatusSubresource(&v1beta1.Agent{}).Build() mockCtrl = gomock.NewController(GinkgoT()) mockInstallerInternal = bminventory.NewMockInstallerInternals(mockCtrl) - mockClientFactory = spoke_k8s_client.NewMockSpokeK8sClientFactory(mockCtrl) + mockClientFactory = spoke_k8s_client.NewMockFactory(mockCtrl) hr = &AgentReconciler{ Client: c, Scheme: scheme.Scheme, @@ -3084,8 +3084,8 @@ VU1eS0RiS/Lz6HwRs2mATNY5FrpZOgdM3cI= } Expect(c.Create(ctx, host)).To(BeNil()) if t.createClient { - mockClient := spoke_k8s_client.NewMockSpokeK8sClient(mockCtrl) - mockClientFactory.EXPECT().CreateFromSecret(gomock.Any()).Return(mockClient, nil) + mockClient := spoke_k8s_client.NewMockClient(mockCtrl) + mockClientFactory.EXPECT().CreateFromSecret(gomock.Any(), gomock.Any(), gomock.Any()).Return(mockClient, nil) mockClient.EXPECT().GetNode(gomock.Any(), gomock.Any()).Return(t.node, t.nodeError).Times(t.getNodeCount) if t.csrs != nil { mockClient.EXPECT().ListCsrs(gomock.Any()).Return(t.csrs, nil) @@ -3869,7 +3869,7 @@ var _ = Describe("spokeKubeClient", func() { cdSpec hivev1.ClusterDeploymentSpec hr *AgentReconciler mockCtrl *gomock.Controller - mockClientFactory *spoke_k8s_client.MockSpokeK8sClientFactory + mockClientFactory *spoke_k8s_client.MockFactory ref *v1beta1.ClusterReference ) @@ -3877,7 +3877,7 @@ var _ = Describe("spokeKubeClient", func() { c = fakeclient.NewClientBuilder().WithScheme(scheme.Scheme). WithStatusSubresource(&v1beta1.Agent{}).Build() mockCtrl = gomock.NewController(GinkgoT()) - mockClientFactory = spoke_k8s_client.NewMockSpokeK8sClientFactory(mockCtrl) + mockClientFactory = spoke_k8s_client.NewMockFactory(mockCtrl) hr = &AgentReconciler{ Client: c, Log: common.GetTestLog(), @@ -3915,8 +3915,8 @@ var _ = Describe("spokeKubeClient", func() { } Expect(c.Create(ctx, secret)).To(Succeed()) - mockClientFactory.EXPECT().CreateFromSecret(gomock.Any()).Do( - func(s *corev1.Secret) (spoke_k8s_client.SpokeK8sClient, error) { + mockClientFactory.EXPECT().CreateFromSecret(gomock.Any(), gomock.Any(), gomock.Any()).Do( + func(_ context.Context, _ types.NamespacedName, s *corev1.Secret) (spoke_k8s_client.Client, error) { Expect(s.Name).To(Equal(adminSecretName)) return nil, nil }, @@ -3950,8 +3950,8 @@ var _ = Describe("spokeKubeClient", func() { } Expect(c.Create(ctx, secret)).To(Succeed()) - mockClientFactory.EXPECT().CreateFromSecret(gomock.Any()).Do( - func(s *corev1.Secret) (spoke_k8s_client.SpokeK8sClient, error) { + mockClientFactory.EXPECT().CreateFromSecret(gomock.Any(), gomock.Any(), gomock.Any()).Do( + func(_ context.Context, _ types.NamespacedName, s *corev1.Secret) (spoke_k8s_client.Client, error) { Expect(s.Name).To(Equal(secretName)) return nil, nil }, @@ -3970,7 +3970,7 @@ var _ = Describe("handleAgentFinalizer", func() { mockCtrl *gomock.Controller mockClient *MockK8sClient mockInstallerInternal *bminventory.MockInstallerInternals - mockClientFactory *spoke_k8s_client.MockSpokeK8sClientFactory + mockClientFactory *spoke_k8s_client.MockFactory agent *v1beta1.Agent agentHostname = "host.example.com" ) @@ -3979,7 +3979,7 @@ var _ = Describe("handleAgentFinalizer", func() { mockCtrl = gomock.NewController(GinkgoT()) mockClient = NewMockK8sClient(mockCtrl) mockInstallerInternal = bminventory.NewMockInstallerInternals(mockCtrl) - mockClientFactory = spoke_k8s_client.NewMockSpokeK8sClientFactory(mockCtrl) + mockClientFactory = spoke_k8s_client.NewMockFactory(mockCtrl) r = &AgentReconciler{ Client: mockClient, @@ -4039,7 +4039,7 @@ var _ = Describe("handleAgentFinalizer", func() { Context("agent is being deleted with the finalizer set", func() { var ( - fakeSpokeClient spoke_k8s_client.SpokeK8sClient + fakeSpokeClient spoke_k8s_client.Client ) expectAgentFinalizerRemoved := func() { @@ -4089,8 +4089,8 @@ var _ = Describe("handleAgentFinalizer", func() { }, ).AnyTimes() - mockClientFactory.EXPECT().CreateFromSecret(gomock.AssignableToTypeOf(&corev1.Secret{})).DoAndReturn( - func(secret *corev1.Secret) (spoke_k8s_client.SpokeK8sClient, error) { + mockClientFactory.EXPECT().CreateFromSecret(gomock.Any(), gomock.Any(), gomock.AssignableToTypeOf(&corev1.Secret{})).DoAndReturn( + func(_ context.Context, _ types.NamespacedName, secret *corev1.Secret) (spoke_k8s_client.Client, error) { Expect(secret.Data["kubeconfig"]).To(Equal([]byte("definitely_a_kubeconfig"))) return fakeSpokeClient, nil }, @@ -4298,7 +4298,7 @@ var _ = Describe("Restore Host - Reconcile an Agent with missing Host", func() { mockInstallerInternal *bminventory.MockInstallerInternals sId strfmt.UUID backEndCluster *common.Cluster - mockClientFactory *spoke_k8s_client.MockSpokeK8sClientFactory + mockClientFactory *spoke_k8s_client.MockFactory agentImage = "registry.example.com/assisted-installer/agent:latest" ) @@ -4307,7 +4307,7 @@ var _ = Describe("Restore Host - Reconcile an Agent with missing Host", func() { WithStatusSubresource(&v1beta1.Agent{}).Build() mockCtrl = gomock.NewController(GinkgoT()) mockInstallerInternal = bminventory.NewMockInstallerInternals(mockCtrl) - mockClientFactory = spoke_k8s_client.NewMockSpokeK8sClientFactory(mockCtrl) + mockClientFactory = spoke_k8s_client.NewMockFactory(mockCtrl) hr = &AgentReconciler{ Client: c, diff --git a/internal/controller/controllers/bmh_agent_controller.go b/internal/controller/controllers/bmh_agent_controller.go index d207ca9688e..79804e1e52b 100644 --- a/internal/controller/controllers/bmh_agent_controller.go +++ b/internal/controller/controllers/bmh_agent_controller.go @@ -70,7 +70,7 @@ type BMACReconciler struct { Log logrus.FieldLogger Scheme *runtime.Scheme Installer bminventory.InstallerInternals - SpokeK8sClientFactory spoke_k8s_client.SpokeK8sClientFactory + SpokeK8sClientFactory spoke_k8s_client.Factory spokeClient client.Client ConvergedFlowEnabled bool PauseProvisionedBMHs bool @@ -1101,7 +1101,11 @@ func (r *BMACReconciler) reconcileSpokeBMH(ctx context.Context, log logrus.Field return reconcileError{err: err} } - spokeClient, err := r.getSpokeClient(secret) + spokeKey := types.NamespacedName{ + Namespace: agent.Spec.ClusterDeploymentName.Namespace, + Name: agent.Spec.ClusterDeploymentName.Name, + } + spokeClient, err := r.getSpokeClient(ctx, spokeKey, secret) if err != nil { log.WithError(err).Errorf("failed to create spoke kubeclient") return reconcileError{err: err} @@ -1398,7 +1402,11 @@ func (r *BMACReconciler) ensureMCSCert(ctx context.Context, log logrus.FieldLogg return reconcileError{err: err} } - spokeClient, err := r.getSpokeClient(secret) + spokeKey := types.NamespacedName{ + Namespace: agent.Spec.ClusterDeploymentName.Namespace, + Name: agent.Spec.ClusterDeploymentName.Name, + } + spokeClient, err := r.getSpokeClient(ctx, spokeKey, secret) if err != nil { log.WithError(err).Errorf("failed to create spoke kubeclient") return reconcileError{err: err} @@ -1565,7 +1573,8 @@ func (r *BMACReconciler) newSpokeMachine(bmh *bmh_v1alpha1.BareMetalHost, cluste return machine, mutateFn } -func (r *BMACReconciler) getSpokeClient(secret *corev1.Secret) (client.Client, error) { +func (r *BMACReconciler) getSpokeClient(ctx context.Context, clusterKey types.NamespacedName, + secret *corev1.Secret) (client.Client, error) { var err error // We only set `spokeClient` during tests. Do not // set it as it would cache the client, which would @@ -1574,7 +1583,7 @@ func (r *BMACReconciler) getSpokeClient(secret *corev1.Secret) (client.Client, e if r.spokeClient != nil { return r.spokeClient, err } - return r.SpokeK8sClientFactory.CreateFromSecret(secret) + return r.SpokeK8sClientFactory.CreateFromSecret(ctx, clusterKey, secret) } // Returns a list of BMH ReconcileRequests for a given Agent @@ -1852,7 +1861,11 @@ func (r *BMACReconciler) drainAgentNode(ctx context.Context, log logrus.FieldLog return false, err } - client, clientset, err := r.SpokeK8sClientFactory.ClientAndSetFromSecret(spokeSecret) + spokeKey := types.NamespacedName{ + Namespace: agent.Spec.ClusterDeploymentName.Namespace, + Name: agent.Spec.ClusterDeploymentName.Name, + } + client, clientset, err := r.SpokeK8sClientFactory.ClientAndSetFromSecret(ctx, spokeKey, spokeSecret) if err != nil { log.WithError(err).Error("failed to create spoke client") return false, err diff --git a/internal/controller/controllers/bmh_agent_controller_test.go b/internal/controller/controllers/bmh_agent_controller_test.go index 4f559a45eb7..e1ed22d5edc 100644 --- a/internal/controller/controllers/bmh_agent_controller_test.go +++ b/internal/controller/controllers/bmh_agent_controller_test.go @@ -2651,7 +2651,7 @@ var _ = Describe("handleBMHFinalizer", func() { mockCtrl *gomock.Controller mockClient *MockK8sClient mockDrainer *MockDrainer - mockClientFactory *spoke_k8s_client.MockSpokeK8sClientFactory + mockClientFactory *spoke_k8s_client.MockFactory bmh *bmh_v1alpha1.BareMetalHost ) @@ -2659,7 +2659,7 @@ var _ = Describe("handleBMHFinalizer", func() { mockCtrl = gomock.NewController(GinkgoT()) mockClient = NewMockK8sClient(mockCtrl) mockDrainer = NewMockDrainer(mockCtrl) - mockClientFactory = spoke_k8s_client.NewMockSpokeK8sClientFactory(mockCtrl) + mockClientFactory = spoke_k8s_client.NewMockFactory(mockCtrl) bmhr = &BMACReconciler{ Client: mockClient, APIReader: mockClient, @@ -2802,7 +2802,7 @@ var _ = Describe("handleBMHFinalizer", func() { Context("with a matching agent", func() { var ( agent *v1beta1.Agent - mockSpokeClient *spoke_k8s_client.MockSpokeK8sClient + mockSpokeClient *spoke_k8s_client.MockClient ) BeforeEach(func() { @@ -2811,7 +2811,7 @@ var _ = Describe("handleBMHFinalizer", func() { Hostname: "agent.example.com", } agent = newAgent("test-agent", testNamespace, agentSpec) - mockSpokeClient = spoke_k8s_client.NewMockSpokeK8sClient(mockCtrl) + mockSpokeClient = spoke_k8s_client.NewMockClient(mockCtrl) }) setupSpokeClient := func() { @@ -2841,8 +2841,8 @@ var _ = Describe("handleBMHFinalizer", func() { // mock client and clientset clientset := &kubernetes.Clientset{} - mockClientFactory.EXPECT().ClientAndSetFromSecret(gomock.AssignableToTypeOf(&corev1.Secret{})).DoAndReturn( - func(secret *corev1.Secret) (spoke_k8s_client.SpokeK8sClient, *kubernetes.Clientset, error) { + mockClientFactory.EXPECT().ClientAndSetFromSecret(gomock.Any(), gomock.Any(), gomock.AssignableToTypeOf(&corev1.Secret{})).DoAndReturn( + func(_ context.Context, _ types.NamespacedName, secret *corev1.Secret) (spoke_k8s_client.Client, *kubernetes.Clientset, error) { Expect(secret.Data["kubeconfig"]).To(Equal([]byte("definitely_a_kubeconfig"))) return mockSpokeClient, clientset, nil }, diff --git a/internal/controller/controllers/clusterdeployments_controller.go b/internal/controller/controllers/clusterdeployments_controller.go index c5d72609989..c3d600ac08d 100644 --- a/internal/controller/controllers/clusterdeployments_controller.go +++ b/internal/controller/controllers/clusterdeployments_controller.go @@ -110,7 +110,7 @@ type ClusterDeploymentsReconciler struct { PullSecretHandler AuthType auth.AuthType VersionsHandler versions.Handler - SpokeK8sClientFactory spoke_k8s_client.SpokeK8sClientFactory + SpokeK8sClientFactory spoke_k8s_client.Factory MirrorRegistriesConfigBuilder mirrorregistries.ServiceMirrorRegistriesConfigBuilder } @@ -460,7 +460,7 @@ func (r *ClusterDeploymentsReconciler) installDay1(ctx context.Context, log logr return r.updateStatus(ctx, log, clusterInstall, clusterDeployment, cluster, nil) } -func (r *ClusterDeploymentsReconciler) spokeKubeClient(ctx context.Context, clusterDeployment *hivev1.ClusterDeployment) (spoke_k8s_client.SpokeK8sClient, error) { +func (r *ClusterDeploymentsReconciler) spokeKubeClient(ctx context.Context, clusterDeployment *hivev1.ClusterDeployment) (spoke_k8s_client.Client, error) { adminKubeConfigSecretName := getClusterDeploymentAdminKubeConfigSecretName(clusterDeployment) namespacedName := types.NamespacedName{ @@ -477,7 +477,11 @@ func (r *ClusterDeploymentsReconciler) spokeKubeClient(ctx context.Context, clus r.Log.WithError(err).Errorf("failed to label kubeconfig secret %s", namespacedName) return nil, err } - return r.SpokeK8sClientFactory.CreateFromSecret(secret) + clusterKey := types.NamespacedName{ + Namespace: clusterDeployment.Namespace, + Name: clusterDeployment.Name, + } + return r.SpokeK8sClientFactory.CreateFromSecret(ctx, clusterKey, secret) } func (r *ClusterDeploymentsReconciler) updateWorkerMcpPaused(ctx context.Context, log logrus.FieldLogger, clusterInstall *hiveext.AgentClusterInstall, clusterDeployment *hivev1.ClusterDeployment) error { diff --git a/internal/controller/controllers/clusterdeployments_controller_test.go b/internal/controller/controllers/clusterdeployments_controller_test.go index ff5adcebe62..99e33d87f7d 100644 --- a/internal/controller/controllers/clusterdeployments_controller_test.go +++ b/internal/controller/controllers/clusterdeployments_controller_test.go @@ -4078,7 +4078,7 @@ var _ = Describe("cluster reconcile", func() { clusterDeployment *hivev1.ClusterDeployment aci *hiveext.AgentClusterInstall cluster *common.Cluster - mockClientFactory *spoke_k8s_client.MockSpokeK8sClientFactory + mockClientFactory *spoke_k8s_client.MockFactory ) createKubeconfigSecret := func() { secretName := fmt.Sprintf(adminKubeConfigStringTemplate, clusterDeployment.Name) @@ -4119,7 +4119,7 @@ var _ = Describe("cluster reconcile", func() { mockInstallerInternal.EXPECT().ValidatePullSecret(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() mockInstallerInternal.EXPECT().UpdateClusterNonInteractive(gomock.Any(), gomock.Any(), gomock.Any()).Return(cluster, nil) mockInstallerInternal.EXPECT().HostWithCollectedLogsExists(gomock.Any()).Return(false, nil) - mockClientFactory = spoke_k8s_client.NewMockSpokeK8sClientFactory(mockCtrl) + mockClientFactory = spoke_k8s_client.NewMockFactory(mockCtrl) cr.SpokeK8sClientFactory = mockClientFactory }) It("no hosts", func() { @@ -4194,8 +4194,8 @@ var _ = Describe("cluster reconcile", func() { Expect(c.Create(ctx, agent)).ToNot(HaveOccurred()) request := newClusterDeploymentRequest(clusterDeployment) createKubeconfigSecret() - mockClient := spoke_k8s_client.NewMockSpokeK8sClient(mockCtrl) - mockClientFactory.EXPECT().CreateFromSecret(gomock.Any()).Return(mockClient, nil).AnyTimes() + mockClient := spoke_k8s_client.NewMockClient(mockCtrl) + mockClientFactory.EXPECT().CreateFromSecret(gomock.Any(), gomock.Any(), gomock.Any()).Return(mockClient, nil).AnyTimes() mockClient.EXPECT().PatchMachineConfigPoolPaused(gomock.Any(), true, "worker").Return(nil) result, err := cr.Reconcile(ctx, request) Expect(err).To(BeNil()) @@ -4222,8 +4222,8 @@ var _ = Describe("cluster reconcile", func() { Expect(c.Create(ctx, agent)).ToNot(HaveOccurred()) request := newClusterDeploymentRequest(clusterDeployment) createKubeconfigSecret() - mockClient := spoke_k8s_client.NewMockSpokeK8sClient(mockCtrl) - mockClientFactory.EXPECT().CreateFromSecret(gomock.Any()).Return(mockClient, nil).AnyTimes() + mockClient := spoke_k8s_client.NewMockClient(mockCtrl) + mockClientFactory.EXPECT().CreateFromSecret(gomock.Any(), gomock.Any(), gomock.Any()).Return(mockClient, nil).AnyTimes() mockClient.EXPECT().PatchMachineConfigPoolPaused(gomock.Any(), false, "worker").Return(nil) result, err := cr.Reconcile(ctx, request) Expect(err).To(BeNil()) @@ -4252,8 +4252,8 @@ var _ = Describe("cluster reconcile", func() { } request := newClusterDeploymentRequest(clusterDeployment) createKubeconfigSecret() - mockClient := spoke_k8s_client.NewMockSpokeK8sClient(mockCtrl) - mockClientFactory.EXPECT().CreateFromSecret(gomock.Any()).Return(mockClient, nil).AnyTimes() + mockClient := spoke_k8s_client.NewMockClient(mockCtrl) + mockClientFactory.EXPECT().CreateFromSecret(gomock.Any(), gomock.Any(), gomock.Any()).Return(mockClient, nil).AnyTimes() mockClient.EXPECT().PatchMachineConfigPoolPaused(gomock.Any(), true, "worker").Return(nil) mockVersions.EXPECT().GetReleaseImageByURL(gomock.Any(), gomock.Any(), gomock.Any()).Return(releaseImage, nil).AnyTimes() diff --git a/internal/controller/controllers/hypershiftagentserviceconfig_controller.go b/internal/controller/controllers/hypershiftagentserviceconfig_controller.go index 4524908fde0..0dfeccc2e9e 100644 --- a/internal/controller/controllers/hypershiftagentserviceconfig_controller.go +++ b/internal/controller/controllers/hypershiftagentserviceconfig_controller.go @@ -167,7 +167,11 @@ func (hr *HypershiftAgentServiceConfigReconciler) Reconcile(origCtx context.Cont // Creating spoke client using specified kubeconfig secret reference log.Info("creating spoke client on namespace") - spokeClient, err := hr.createSpokeClient(ctx, log, instance.Spec.KubeconfigSecretRef.Name, asc) + spokeKey := types.NamespacedName{ + Namespace: "TODO", + Name: "TODO", + } + spokeClient, err := hr.createSpokeClient(ctx, spokeKey, log, instance.Spec.KubeconfigSecretRef.Name, asc) if err != nil { log.WithError(err).Errorf("Failed to create client using %s on namespace %s", instance.Spec.KubeconfigSecretRef.Name, req.NamespacedName) return ctrl.Result{Requeue: true}, err @@ -327,7 +331,8 @@ func (hr *HypershiftAgentServiceConfigReconciler) reconcileSpokeComponents(ctx c return ctrl.Result{}, nil } -func (hr *HypershiftAgentServiceConfigReconciler) createSpokeClient(ctx context.Context, log *logrus.Entry, kubeconfigSecretName string, asc ASC) (spoke_k8s_client.SpokeK8sClient, error) { +func (hr *HypershiftAgentServiceConfigReconciler) createSpokeClient(ctx context.Context, spokeKey types.NamespacedName, + log *logrus.Entry, kubeconfigSecretName string, asc ASC) (spoke_k8s_client.Client, error) { // Fetch kubeconfig secret by specified secret reference kubeconfigSecret, err := hr.getKubeconfigSecret(ctx, log, kubeconfigSecretName, asc.namespace) if err != nil { @@ -339,7 +344,7 @@ func (hr *HypershiftAgentServiceConfigReconciler) createSpokeClient(ctx context. } // Create spoke cluster client using kubeconfig secret - spokeClient, err := hr.SpokeClients.Get(kubeconfigSecret) + spokeClient, err := hr.SpokeClients.Get(ctx, spokeKey, kubeconfigSecret) if err != nil { reason := aiv1beta1.ReasonSpokeClientCreationFailure msg := fmt.Sprintf("Failed to create kubeconfig client: %s", err.Error()) diff --git a/internal/controller/controllers/hypershiftagentserviceconfig_controller_test.go b/internal/controller/controllers/hypershiftagentserviceconfig_controller_test.go index 4fb6b69e8f9..d62d0471cfe 100644 --- a/internal/controller/controllers/hypershiftagentserviceconfig_controller_test.go +++ b/internal/controller/controllers/hypershiftagentserviceconfig_controller_test.go @@ -42,7 +42,7 @@ var _ = Describe("HypershiftAgentServiceConfig reconcile", func() { imageServiceStatefulSet *appsv1.StatefulSet kubeconfigSecret *corev1.Secret mockCtrl *gomock.Controller - mockSpokeClient *spoke_k8s_client.MockSpokeK8sClient + mockSpokeClient *spoke_k8s_client.MockClient mockSpokeClientCache *MockSpokeClientCache fakeSpokeClient client.WithWatch ) @@ -249,7 +249,7 @@ var _ = Describe("HypershiftAgentServiceConfig reconcile", func() { fakeSpokeClient = fakeclient.NewClientBuilder().WithScheme(schemes).Build() client := fakeSpokeK8sClient{Client: fakeSpokeClient} - mockSpokeClientCache.EXPECT().Get(gomock.Any()).Return(client, nil) + mockSpokeClientCache.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(client, nil) res, err := hr.Reconcile(ctx, newHypershiftAgentServiceConfigRequest(hsc)) Expect(err).To(BeNil()) Expect(res).To(Equal(ctrl.Result{})) @@ -272,7 +272,7 @@ var _ = Describe("HypershiftAgentServiceConfig reconcile", func() { BeforeEach(func() { mockCtrl = gomock.NewController(GinkgoT()) - mockSpokeClient = spoke_k8s_client.NewMockSpokeK8sClient(mockCtrl) + mockSpokeClient = spoke_k8s_client.NewMockClient(mockCtrl) mockSpokeClientCache = NewMockSpokeClientCache(mockCtrl) hsc = newHSCDefault() @@ -288,7 +288,7 @@ var _ = Describe("HypershiftAgentServiceConfig reconcile", func() { }) It("runs without error", func() { - mockSpokeClientCache.EXPECT().Get(gomock.Any()).Return(mockSpokeClient, nil) + mockSpokeClientCache.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(mockSpokeClient, nil) mockSpokeClient.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() mockSpokeClient.EXPECT().Update(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() mockSpokeClient.EXPECT().List(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() @@ -329,7 +329,7 @@ var _ = Describe("HypershiftAgentServiceConfig reconcile", func() { }) It("fails due to an error getting client", func() { - mockSpokeClientCache.EXPECT().Get(gomock.Any()).Return(mockSpokeClient, errors.Errorf("error")) + mockSpokeClientCache.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(mockSpokeClient, errors.Errorf("error")) _, err := hr.Reconcile(ctx, newHypershiftAgentServiceConfigRequest(hsc)) Expect(err).ToNot(BeNil()) Expect(err.Error()).To(ContainSubstring("Failed to create client")) @@ -338,7 +338,7 @@ var _ = Describe("HypershiftAgentServiceConfig reconcile", func() { It("fails due to missing agent-install CRDs on management cluster", func() { Expect(hr.Client.Delete(ctx, crd)).To(Succeed()) - mockSpokeClientCache.EXPECT().Get(gomock.Any()).Return(mockSpokeClient, nil) + mockSpokeClientCache.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(mockSpokeClient, nil) _, err := hr.Reconcile(ctx, newHypershiftAgentServiceConfigRequest(hsc)) Expect(err).ToNot(BeNil()) Expect(err.Error()).To(ContainSubstring("agent-install CRDs are not available")) @@ -353,7 +353,7 @@ var _ = Describe("HypershiftAgentServiceConfig reconcile", func() { }) It("fails due to missing konnectivity deployment", func() { - mockSpokeClientCache.EXPECT().Get(gomock.Any()).Return(mockSpokeClient, nil) + mockSpokeClientCache.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(mockSpokeClient, nil) mockSpokeClient.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() mockSpokeClient.EXPECT().Update(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() mockSpokeClient.EXPECT().List(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() @@ -384,7 +384,7 @@ var _ = Describe("HypershiftAgentServiceConfig reconcile", func() { c := crd.DeepCopy() c.Labels["new"] = "label" Expect(hr.Client.Update(ctx, c)).To(Succeed()) - mockSpokeClientCache.EXPECT().Get(gomock.Any()).Return(fakeSpokeK8sClient{Client: fakeSpokeClient}, nil) + mockSpokeClientCache.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(fakeSpokeK8sClient{Client: fakeSpokeClient}, nil) res, err := hr.Reconcile(ctx, newHypershiftAgentServiceConfigRequest(hsc)) Expect(err).To(BeNil()) Expect(res).To(Equal(ctrl.Result{})) @@ -403,7 +403,7 @@ var _ = Describe("HypershiftAgentServiceConfig reconcile", func() { crdKey := client.ObjectKeyFromObject(crd) spokeCrd := apiextensionsv1.CustomResourceDefinition{} - mockSpokeClientCache.EXPECT().Get(gomock.Any()).Return(fakeSpokeK8sClient{Client: fakeSpokeClient}, nil) + mockSpokeClientCache.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(fakeSpokeK8sClient{Client: fakeSpokeClient}, nil) res, err := hr.Reconcile(ctx, newHypershiftAgentServiceConfigRequest(hsc)) Expect(err).To(BeNil()) Expect(res).To(Equal(ctrl.Result{})) @@ -411,7 +411,7 @@ var _ = Describe("HypershiftAgentServiceConfig reconcile", func() { }) It("successfully added kubeconfig resources to service deployment", func() { - mockSpokeClientCache.EXPECT().Get(gomock.Any()).Return(mockSpokeClient, nil) + mockSpokeClientCache.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(mockSpokeClient, nil) mockSpokeClient.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() mockSpokeClient.EXPECT().Update(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() mockSpokeClient.EXPECT().List(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() diff --git a/internal/controller/controllers/mock_spoke_client_cache.go b/internal/controller/controllers/mock_spoke_client_cache.go index 7d5a533bb02..bd68dad399c 100644 --- a/internal/controller/controllers/mock_spoke_client_cache.go +++ b/internal/controller/controllers/mock_spoke_client_cache.go @@ -5,11 +5,13 @@ package controllers import ( + context "context" reflect "reflect" gomock "github.com/golang/mock/gomock" spoke_k8s_client "github.com/openshift/assisted-service/internal/spoke_k8s_client" v1 "k8s.io/api/core/v1" + types "k8s.io/apimachinery/pkg/types" ) // MockSpokeClientCache is a mock of SpokeClientCache interface. @@ -36,16 +38,16 @@ func (m *MockSpokeClientCache) EXPECT() *MockSpokeClientCacheMockRecorder { } // Get mocks base method. -func (m *MockSpokeClientCache) Get(arg0 *v1.Secret) (spoke_k8s_client.SpokeK8sClient, error) { +func (m *MockSpokeClientCache) Get(arg0 context.Context, arg1 types.NamespacedName, arg2 *v1.Secret) (spoke_k8s_client.Client, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Get", arg0) - ret0, _ := ret[0].(spoke_k8s_client.SpokeK8sClient) + ret := m.ctrl.Call(m, "Get", arg0, arg1, arg2) + ret0, _ := ret[0].(spoke_k8s_client.Client) ret1, _ := ret[1].(error) return ret0, ret1 } // Get indicates an expected call of Get. -func (mr *MockSpokeClientCacheMockRecorder) Get(arg0 interface{}) *gomock.Call { +func (mr *MockSpokeClientCacheMockRecorder) Get(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockSpokeClientCache)(nil).Get), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockSpokeClientCache)(nil).Get), arg0, arg1, arg2) } diff --git a/internal/controller/controllers/spoke_client_cache.go b/internal/controller/controllers/spoke_client_cache.go index 5d761c6c188..ff8e9740392 100644 --- a/internal/controller/controllers/spoke_client_cache.go +++ b/internal/controller/controllers/spoke_client_cache.go @@ -1,6 +1,7 @@ package controllers import ( + "context" "crypto/sha256" "encoding/json" "fmt" @@ -14,30 +15,31 @@ import ( //go:generate mockgen --build_flags=--mod=mod -package=controllers -destination=mock_spoke_client_cache.go . SpokeClientCache type SpokeClientCache interface { - Get(secret *corev1.Secret) (spoke_k8s_client.SpokeK8sClient, error) + Get(ctx context.Context, clusterKey types.NamespacedName, secret *corev1.Secret) (spoke_k8s_client.Client, error) } type spokeClientCache struct { sync.Mutex - clientFactory spoke_k8s_client.SpokeK8sClientFactory + clientFactory spoke_k8s_client.Factory clientMap map[string]*spokeClient } type spokeClient struct { - spokeK8sClient *spoke_k8s_client.SpokeK8sClient + spokeK8sClient *spoke_k8s_client.Client secretHash string } -func NewSpokeClientCache(clientFactory spoke_k8s_client.SpokeK8sClientFactory) SpokeClientCache { +func NewSpokeClientCache(clientFactory spoke_k8s_client.Factory) SpokeClientCache { return &spokeClientCache{ clientFactory: clientFactory, - clientMap: make(map[string]*spokeClient), + clientMap: map[string]*spokeClient{}, } } // Get returns a SpokeK8sClient for the given secret. // The client is returned from cache, or, a new client is created if not available. -func (c *spokeClientCache) Get(secret *corev1.Secret) (spoke_k8s_client.SpokeK8sClient, error) { +func (c *spokeClientCache) Get(ctx context.Context, clusterKey types.NamespacedName, + secret *corev1.Secret) (spoke_k8s_client.Client, error) { c.Lock() defer c.Unlock() @@ -51,7 +53,7 @@ func (c *spokeClientCache) Get(secret *corev1.Secret) (spoke_k8s_client.SpokeK8s key := types.NamespacedName{Name: secret.Name, Namespace: secret.Namespace} client, present := c.clientMap[key.String()] if !present || client.secretHash != secretHash { - spokeK8sClient, err := c.clientFactory.CreateFromSecret(secret) + spokeK8sClient, err := c.clientFactory.CreateFromSecret(ctx, clusterKey, secret) if err != nil { return nil, errors.Wrapf(err, "Failed to create client using secret '%s'", secret.Name) } diff --git a/internal/controller/controllers/spoke_client_cache_test.go b/internal/controller/controllers/spoke_client_cache_test.go index a85e8d6ac16..f91dfc5424c 100644 --- a/internal/controller/controllers/spoke_client_cache_test.go +++ b/internal/controller/controllers/spoke_client_cache_test.go @@ -1,6 +1,7 @@ package controllers import ( + "context" "errors" "github.com/golang/mock/gomock" @@ -9,20 +10,24 @@ import ( "github.com/openshift/assisted-service/internal/spoke_k8s_client" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" ) var _ = Context("with kubeconfig test secret", func() { const ( + testClusterName = "test-cluster" testKubeconfigSecretName = "test-secret" testKubeconfigSecretNamespace = "test-namespace" ) var ( + ctx context.Context mockCtrl *gomock.Controller - mockSpokeClient *spoke_k8s_client.MockSpokeK8sClient - mockSpokeFactory *spoke_k8s_client.MockSpokeK8sClientFactory + mockSpokeClient *spoke_k8s_client.MockClient + mockSpokeFactory *spoke_k8s_client.MockFactory clientCache SpokeClientCache kubeconfigSecret *corev1.Secret + clusterKey types.NamespacedName ) newKubeconfigSecret := func() *corev1.Secret { @@ -38,12 +43,21 @@ var _ = Context("with kubeconfig test secret", func() { } } + newClusterKey := func() types.NamespacedName { + return types.NamespacedName{ + Namespace: testKubeconfigSecretNamespace, + Name: testClusterName, + } + } + BeforeEach(func() { + ctx = context.Background() mockCtrl = gomock.NewController(GinkgoT()) - mockSpokeClient = spoke_k8s_client.NewMockSpokeK8sClient(mockCtrl) - mockSpokeFactory = spoke_k8s_client.NewMockSpokeK8sClientFactory(mockCtrl) + mockSpokeClient = spoke_k8s_client.NewMockClient(mockCtrl) + mockSpokeFactory = spoke_k8s_client.NewMockFactory(mockCtrl) clientCache = NewSpokeClientCache(mockSpokeFactory) kubeconfigSecret = newKubeconfigSecret() + clusterKey = newClusterKey() }) AfterEach(func() { @@ -52,8 +66,8 @@ var _ = Context("with kubeconfig test secret", func() { Describe("Get", func() { It("successfully creates a new client", func() { - mockSpokeFactory.EXPECT().CreateFromSecret(kubeconfigSecret).Return(mockSpokeClient, nil) - client, err := clientCache.Get(kubeconfigSecret) + mockSpokeFactory.EXPECT().CreateFromSecret(gomock.Any(), gomock.Any(), kubeconfigSecret).Return(mockSpokeClient, nil) + client, err := clientCache.Get(ctx, clusterKey, kubeconfigSecret) Expect(err).ShouldNot(HaveOccurred()) Expect(client).To(Equal(mockSpokeClient)) @@ -61,37 +75,37 @@ var _ = Context("with kubeconfig test secret", func() { It("successfully returns an existing client", func() { // create a client - mockSpokeFactory.EXPECT().CreateFromSecret(kubeconfigSecret).Return(mockSpokeClient, nil) - client, err := clientCache.Get(kubeconfigSecret) + mockSpokeFactory.EXPECT().CreateFromSecret(gomock.Any(), gomock.Any(), kubeconfigSecret).Return(mockSpokeClient, nil) + client, err := clientCache.Get(ctx, clusterKey, kubeconfigSecret) Expect(err).ShouldNot(HaveOccurred()) Expect(client).To(Equal(mockSpokeClient)) // get created client - client, err = clientCache.Get(kubeconfigSecret) + client, err = clientCache.Get(ctx, clusterKey, kubeconfigSecret) Expect(err).ShouldNot(HaveOccurred()) Expect(client).To(Equal(mockSpokeClient)) }) It("successfully creates a new client on hash mismatch", func() { // create a client - mockSpokeFactory.EXPECT().CreateFromSecret(kubeconfigSecret).Return(mockSpokeClient, nil) - client, err := clientCache.Get(kubeconfigSecret) + mockSpokeFactory.EXPECT().CreateFromSecret(gomock.Any(), gomock.Any(), kubeconfigSecret).Return(mockSpokeClient, nil) + client, err := clientCache.Get(ctx, clusterKey, kubeconfigSecret) Expect(err).ShouldNot(HaveOccurred()) Expect(client).To(Equal(mockSpokeClient)) // create a client from a new kubeconfig newKubeconfigSecret := kubeconfigSecret.DeepCopy() newKubeconfigSecret.Data["kubeconfig"] = []byte("new") - mockSpokeFactory.EXPECT().CreateFromSecret(newKubeconfigSecret).Return(mockSpokeClient, nil) - client, err = clientCache.Get(newKubeconfigSecret) + mockSpokeFactory.EXPECT().CreateFromSecret(gomock.Any(), gomock.Any(), newKubeconfigSecret).Return(mockSpokeClient, nil) + client, err = clientCache.Get(ctx, clusterKey, newKubeconfigSecret) Expect(err).ShouldNot(HaveOccurred()) Expect(client).To(Equal(mockSpokeClient)) }) It("fails due failure on create client from secret", func() { - mockSpokeFactory.EXPECT().CreateFromSecret(gomock.Any()).Return(nil, errors.New("error")) + mockSpokeFactory.EXPECT().CreateFromSecret(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("error")) - _, err := clientCache.Get(kubeconfigSecret) + _, err := clientCache.Get(ctx, clusterKey, kubeconfigSecret) Expect(err).Should(HaveOccurred()) Expect(err.Error()).Should(ContainSubstring("Failed to create client using secret")) diff --git a/internal/spoke_k8s_client/spoke_k8s_client.go b/internal/spoke_k8s_client/client.go similarity index 96% rename from internal/spoke_k8s_client/spoke_k8s_client.go rename to internal/spoke_k8s_client/client.go index e807062effa..8edec57c335 100644 --- a/internal/spoke_k8s_client/spoke_k8s_client.go +++ b/internal/spoke_k8s_client/client.go @@ -31,8 +31,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -//go:generate mockgen --build_flags=--mod=mod -package=spoke_k8s_client -destination=mock_spoke_k8s_client.go . SpokeK8sClient -type SpokeK8sClient interface { +//go:generate mockgen --build_flags=--mod=mod -package=spoke_k8s_client -destination=mock_client.go . Client +type Client interface { client.Client ListCsrs(ctx context.Context) (*certificatesv1.CertificateSigningRequestList, error) ApproveCsr(ctx context.Context, csr *certificatesv1.CertificateSigningRequest) error @@ -47,7 +47,7 @@ type spokeK8sClient struct { csrClient cerv1.CertificateSigningRequestInterface sarClient authorizationv1interfaces.SelfSubjectAccessReviewInterface nodesClient typedcorev1.NodeInterface - log logrus.FieldLogger + logger logrus.FieldLogger } func (c *spokeK8sClient) ListCsrs(ctx context.Context) (*certificatesv1.CertificateSigningRequestList, error) { @@ -90,7 +90,7 @@ func (c *spokeK8sClient) PatchMachineConfigPoolPaused(ctx context.Context, pause return nil } pausePatch := []byte(fmt.Sprintf("{\"spec\":{\"paused\":%t}}", pause)) - c.log.Infof("Setting pause MCP %s to %t", mcpName, pause) + c.logger.Infof("Setting pause MCP %s to %t", mcpName, pause) return c.Patch(ctx, mcp, client.RawPatch(types.MergePatchType, pausePatch)) } diff --git a/internal/spoke_k8s_client/factory.go b/internal/spoke_k8s_client/factory.go index 23269d74ddd..553707839b7 100644 --- a/internal/spoke_k8s_client/factory.go +++ b/internal/spoke_k8s_client/factory.go @@ -1,90 +1,205 @@ package spoke_k8s_client import ( + "context" + "fmt" + "net/http" + "slices" + + hivev1 "github.com/openshift/hive/apis/hive/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" - client "sigs.k8s.io/controller-runtime/pkg/client" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" ) -//go:generate mockgen --build_flags=--mod=mod -package=spoke_k8s_client -destination=mock_spoke_k8s_client_factory.go . SpokeK8sClientFactory -type SpokeK8sClientFactory interface { - CreateFromSecret(secret *corev1.Secret) (SpokeK8sClient, error) - ClientAndSetFromSecret(secret *corev1.Secret) (SpokeK8sClient, *kubernetes.Clientset, error) +//go:generate mockgen --build_flags=--mod=mod -package=spoke_k8s_client -destination=mock_factory.go . Factory +type Factory interface { + CreateFromSecret(ctx context.Context, key types.NamespacedName, secret *corev1.Secret) (Client, error) + ClientAndSetFromSecret(ctx context.Context, key types.NamespacedName, secret *corev1.Secret) (Client, *kubernetes.Clientset, error) +} + +type FactoryBuilder struct { + logger logrus.FieldLogger + hubClient ctrlclient.Client + transportWrappers []func(http.RoundTripper) http.RoundTripper +} + +type factory struct { + logger logrus.FieldLogger + hubClient ctrlclient.Client + transportWrappers []func(http.RoundTripper) http.RoundTripper +} + +func NewFactory() *FactoryBuilder { + return &FactoryBuilder{} +} + +// SetLogger sets the object that will be used to write to the log. This is mandatory. +func (b *FactoryBuilder) SetLogger(value logrus.FieldLogger) *FactoryBuilder { + b.logger = value + return b } -type spokeK8sClientFactory struct { - log logrus.FieldLogger +// SetHubClient sets the client that will be used to call the API of the hub cluster. This is mandatory. +func (b *FactoryBuilder) SetHubClient(value ctrlclient.Client) *FactoryBuilder { + b.hubClient = value + return b } -func NewSpokeK8sClientFactory(log logrus.FieldLogger) SpokeK8sClientFactory { - return &spokeK8sClientFactory{ - log: log, +// AddTransportWrapper adds a function that will be called to potentially modify the original HTTP transport. This is +// optional and by default there are no wrappers. +func (b *FactoryBuilder) AddTransportWrapper(value func(http.RoundTripper) http.RoundTripper) *FactoryBuilder { + b.transportWrappers = append(b.transportWrappers, value) + return b +} + +// Build uses the data stored in the builder to create and configure a new factory. +func (b *FactoryBuilder) Build() (result Factory, err error) { + // Check parameters: + if b.logger == nil { + err = errors.New("logger is mandatory") + return + } + if b.hubClient == nil { + err = errors.New("hub client is mandatory") + return + } + + // Create and populate the object: + result = &factory{ + logger: b.logger, + hubClient: b.hubClient, + transportWrappers: slices.Clone(b.transportWrappers), } + return } -func (cf *spokeK8sClientFactory) CreateFromSecret(secret *corev1.Secret) (SpokeK8sClient, error) { - kubeconfigData, err := kubeconfigFromSecret(secret) +func (f *factory) CreateFromSecret(ctx context.Context, clusterKey types.NamespacedName, + secret *corev1.Secret) (result Client, err error) { + result, _, err = f.ClientAndSetFromSecret(ctx, clusterKey, secret) + return +} + +func (f *factory) ClientAndSetFromSecret(ctx context.Context, clusterKey types.NamespacedName, + secret *corev1.Secret) (result Client, clientSet *kubernetes.Clientset, err error) { + // Create the REST configuration from the content of the secret: + restConfig, err := f.restConfigFromSecret(secret) if err != nil { - return nil, err + return } - client, _, err := cf.clientAndSetForKubeconfig(kubeconfigData) - return client, err -} -func (cf *spokeK8sClientFactory) ClientAndSetFromSecret(secret *corev1.Secret) (SpokeK8sClient, *kubernetes.Clientset, error) { - kubeconfig, err := kubeconfigFromSecret(secret) + // If this is a HyperShift cluster then the API server address can be replaced by a `*.svc` address that uses + // the service network and therefore reduces the number of round trips and doesn't need to go via proxies. + isHyperShift, err := f.isHyperShift(ctx, clusterKey) if err != nil { - cf.log.WithError(err).Error("failed to get kubeconfig from secret") - return nil, nil, err + return + } + if isHyperShift { + err = f.modifyRestConfigForHyperShift(clusterKey, restConfig) + if err != nil { + return + } } - return cf.clientAndSetForKubeconfig(kubeconfig) -} + // Add the transport wrappers: + for _, transportWrapper := range f.transportWrappers { + restConfig.Wrap(transportWrapper) + } -func kubeconfigFromSecret(secret *corev1.Secret) ([]byte, error) { - if secret.Data == nil { - return nil, errors.Errorf("Secret %s/%s does not contain any data", secret.Namespace, secret.Name) + // Create the controller-runtime client: + client, err := f.clientFromRestConfig(restConfig) + if err != nil { + return } - kubeconfigData, ok := secret.Data["kubeconfig"] - if !ok || len(kubeconfigData) == 0 { - return nil, errors.Errorf("Secret data for %s/%s does not contain kubeconfig", secret.Namespace, secret.Name) + + // Create the client-go client: + clientSet, err = f.clientSetFromRestConfig(restConfig) + if err != nil { + return } - return kubeconfigData, nil + + // Create and populate the object: + result = &spokeK8sClient{ + logger: f.logger, + Client: client, + csrClient: clientSet.CertificatesV1().CertificateSigningRequests(), + sarClient: clientSet.AuthorizationV1().SelfSubjectAccessReviews(), + nodesClient: clientSet.CoreV1().Nodes(), + } + return } -func (cf *spokeK8sClientFactory) clientAndSetForKubeconfig(kubeconfig []byte) (SpokeK8sClient, *kubernetes.Clientset, error) { - clientConfig, err := clientcmd.NewClientConfigFromBytes(kubeconfig) +func (f *factory) restConfigFromSecret(secret *corev1.Secret) (result *rest.Config, err error) { + kubeConfig, err := f.kubeConfigFromSecret(secret) if err != nil { - return nil, nil, errors.Wrapf(err, "failed to get clientconfig from kubeconfig data") + return } - - restConfig, err := clientConfig.ClientConfig() + clientConfig, err := clientcmd.NewClientConfigFromBytes(kubeConfig) if err != nil { - return nil, nil, errors.Wrapf(err, "failed to get restconfig for kube client") + return } + result, err = clientConfig.ClientConfig() + return +} - clientset, err := kubernetes.NewForConfig(restConfig) - if err != nil { - cf.log.WithError(err).Warnf("Getting kuberenetes config for cluster") - return nil, nil, err +func (f *factory) kubeConfigFromSecret(secret *corev1.Secret) (result []byte, err error) { + if len(secret.Data) == 0 { + err = errors.Errorf( + "secret '%s/%s' is empty", + secret.Namespace, secret.Name, + ) + return + } + result, ok := secret.Data["kubeconfig"] + if !ok || len(result) == 0 { + err = errors.Errorf( + "secret '%s/%s' doesn't contain the 'kubeconfig' key", + secret.Namespace, secret.Name, + ) } + return +} +func (f *factory) clientFromRestConfig(restConfig *rest.Config) (result ctrlclient.Client, err error) { schemes := GetKubeClientSchemes() - targetClient, err := client.New(restConfig, client.Options{Scheme: schemes}) + result, err = ctrlclient.New(restConfig, ctrlclient.Options{Scheme: schemes}) + return +} + +func (f *factory) clientSetFromRestConfig(restConfig *rest.Config) (result *kubernetes.Clientset, + err error) { + result, err = kubernetes.NewForConfig(restConfig) + return +} + +func (f *factory) isHyperShift(ctx context.Context, clusterKey types.NamespacedName) (result bool, err error) { + clusterDeployment := &hivev1.ClusterDeployment{} + err = f.hubClient.Get(ctx, clusterKey, clusterDeployment) if err != nil { - cf.log.WithError(err).Warnf("failed to get spoke kube client") - return nil, nil, err + err = errors.Wrapf( + err, + "failed to check if '%s/%s' is a hosted cluster", + clusterKey.Namespace, clusterKey.Name, + ) + return } + _, result = clusterDeployment.Labels["agentClusterRef"] + return +} - spokeClient := &spokeK8sClient{ - Client: targetClient, - csrClient: clientset.CertificatesV1().CertificateSigningRequests(), - sarClient: clientset.AuthorizationV1().SelfSubjectAccessReviews(), - nodesClient: clientset.CoreV1().Nodes(), - log: cf.log, - } - return spokeClient, clientset, nil +func (f *factory) modifyRestConfigForHyperShift(clusterKey types.NamespacedName, restConfig *rest.Config) error { + originalHost := restConfig.Host + restConfig.Host = fmt.Sprintf("https://kube-apiserver.%s.svc:6443", clusterKey.Namespace) + f.logger.WithFields(logrus.Fields{ + "namespace": clusterKey.Namespace, + "name": clusterKey.Name, + "original": originalHost, + "modified": restConfig.Host, + }).Info("Modified hosted cluster API server address to connect via the service network") + return nil } diff --git a/internal/spoke_k8s_client/factory_test.go b/internal/spoke_k8s_client/factory_test.go index 6bb9fb59a7b..8151f61f61a 100644 --- a/internal/spoke_k8s_client/factory_test.go +++ b/internal/spoke_k8s_client/factory_test.go @@ -1,63 +1,277 @@ package spoke_k8s_client import ( + "net/http" + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" - "github.com/sirupsen/logrus" + "github.com/onsi/gomega/ghttp" + hivev1 "github.com/openshift/hive/apis/hive/v1" + "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + "github.com/openshift/assisted-service/internal/common" ) var _ = Describe("Factory", func() { - var logger *logrus.Logger - - BeforeEach(func() { - logger = logrus.New() - logger.SetOutput(GinkgoWriter) - }) + Describe("Creation", func() { + It("Can't be created without a logger", func() { + client, err := NewFactory(). + SetHubClient(hubClient). + Build() + Expect(err).To(MatchError("logger is mandatory")) + Expect(client).To(BeNil()) + }) - Describe("Create from secret", func() { - It("Fails if secret doesn't contain data", func() { - factory := NewSpokeK8sClientFactory(logger) - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "myns", - Name: "mysecret", - }, - Data: nil, - } - _, err := factory.CreateFromSecret(secret) - Expect(err).To(MatchError("Secret myns/mysecret does not contain any data")) + It("Can't be created without a hub client", func() { + client, err := NewFactory(). + SetLogger(logger). + Build() + Expect(err).To(MatchError("hub client is mandatory")) + Expect(client).To(BeNil()) }) - It("Fails if secret doesn't contain a 'kubeconfig' data item", func() { - factory := NewSpokeK8sClientFactory(logger) - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "myns", - Name: "mysecret", - }, - Data: map[string][]byte{ - "mydata": []byte("myvalue"), - }, - } - _, err := factory.CreateFromSecret(secret) - Expect(err).To(MatchError("Secret data for myns/mysecret does not contain kubeconfig")) + It("Can be created with logger and hub client", func() { + client, err := NewFactory(). + SetLogger(logger). + SetHubClient(hubClient). + Build() + Expect(err).ToNot(HaveOccurred()) + Expect(client).ToNot(BeNil()) }) + }) - It("Fails if secret contains a 'kubeconfig' data item with junk", func() { - factory := NewSpokeK8sClientFactory(logger) - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "myns", - Name: "mysecret", + Describe("Create spoke client from secret", func() { + DescribeTable( + "Fails if secret doesn't contain a valid kubeconfig", + func(data map[string][]byte, matcher OmegaMatcher) { + // Create the secret: + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: hubNamespace.Name, + Name: "admin-kubeconfig", + }, + Data: data, + } + err := hubClient.Create(ctx, secret) + Expect(err).ToNot(HaveOccurred()) + + // Create the factory: + factory, err := NewFactory(). + SetLogger(logger). + SetHubClient(hubClient). + Build() + Expect(err).ToNot(HaveOccurred()) + + // Check the error: + key := types.NamespacedName{ + Namespace: hubNamespace.Name, + Name: "mycluster", + } + _, err = factory.CreateFromSecret(ctx, key, secret) + Expect(err).To(MatchError(matcher)) + }, + Entry( + "Secret data is nil", + nil, + MatchRegexp("secret '.*/admin-kubeconfig' is empty"), + ), + Entry( + "Secret data is empty", + map[string][]byte{}, + MatchRegexp("secret '.*/admin-kubeconfig' is empty"), + ), + Entry( + "Secret data doesn't contain a 'kubeconfig' key", + map[string][]byte{ + "mydata": []byte("myvalue"), }, - Data: map[string][]byte{ + MatchRegexp("secret '.*/admin-kubeconfig' doesn't contain the 'kubeconfig' key"), + ), + Entry( + "Secret data contains a 'kubeconfig' data item with junk", + map[string][]byte{ "kubeconfig": []byte("junk"), }, - } - _, err := factory.CreateFromSecret(secret) - Expect(err).To(MatchError(ContainSubstring("cannot unmarshal"))) + ContainSubstring("cannot unmarshal"), + ), + ) + + When("Secret contains a valid kubeconfig", func() { + var secret *corev1.Secret + + BeforeEach(func() { + // Create the kubeconfig: + kubeconfig := common.Dedent(` + apiVersion: v1 + kind: Config + clusters: + - name: mycluster + cluster: + server: https://mylb:32132 + users: + - name: myuser + user: + username: myuser + password: mypassword + contexts: + - name: mycontext + context: + cluster: mycluster + user: myuser + current-context: mycontext + `) + + // Create the secret: + secret = &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: hubNamespace.Name, + Name: "admin-kubeconfig", + }, + Data: map[string][]byte{ + "kubeconfig": []byte(kubeconfig), + }, + } + err := hubClient.Create(ctx, secret) + Expect(err).ToNot(HaveOccurred()) + }) + + It("Fails if cluster deployment doesn't exist", func() { + // Create the factory: + factory, err := NewFactory(). + SetLogger(logger). + SetHubClient(hubClient). + Build() + Expect(err).ToNot(HaveOccurred()) + + // Get the client: + key := types.NamespacedName{ + Namespace: hubNamespace.Name, + Name: "mycluster", + } + client, err := factory.CreateFromSecret(ctx, key, secret) + Expect(err).To(MatchError(And( + ContainSubstring(hubNamespace.Name), + ContainSubstring("mycluster"), + ContainSubstring("clusterdeployment"), + ContainSubstring("not found"), + ))) + Expect(client).To(BeNil()) + }) + + It("Replaces API server address for HyperShift clusters", func() { + // For this test we need to create the factory with a transport wrapper that verifies + // that the address has been changed. Note also that this transport will always return + // an error, as we dont really care about the rest of the processing. + transport := ghttp.RoundTripperFunc( + func(request *http.Request) (response *http.Response, err error) { + address := request.URL.String() + Expect(address).To(HavePrefix( + "https://kube-apiserver.%s.svc:6443/", + hubNamespace.Name, + )) + err = errors.New("myerror") + return + }, + ) + factory, err := NewFactory(). + SetLogger(logger). + SetHubClient(hubClient). + AddTransportWrapper(func(_ http.RoundTripper) http.RoundTripper { + return transport + }). + Build() + Expect(err).ToNot(HaveOccurred()) + + // Create the cluster deployment, including the `agetClusteRef` label, as that is what + // indicates that it is a HyperShift cluster. + clusterDeployment := &hivev1.ClusterDeployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: hubNamespace.Name, + Name: "mycluster", + Labels: map[string]string{ + "agentClusterRef": "mycluster", + }, + }, + } + err = hubClient.Create(ctx, clusterDeployment) + Expect(err).ToNot(HaveOccurred()) + + // Get the client: + clusterDeploymentKey := types.NamespacedName{ + Namespace: clusterDeployment.Namespace, + Name: clusterDeployment.Name, + } + client, err := factory.CreateFromSecret(ctx, clusterDeploymentKey, secret) + Expect(err).ToNot(HaveOccurred()) + + // Send a request with the client. This will fail because our transport wrapper fails + // all requests, but it allows us to verify that the API server address has been + // changed. + configMap := &corev1.ConfigMap{} + configMapKey := types.NamespacedName{ + Namespace: hubNamespace.Name, + Name: "myconfig", + } + err = client.Get(ctx, configMapKey, configMap) + Expect(err).To(MatchError(ContainSubstring("myerror"))) + }) + + It("Doesn't replace API server address for regular clusters", func() { + // For this test we need to create the factory with a transport wrapper that verifies + // that the address hasn't been changed. Note also that this transport will always + // return an error, as we dont really care about the rest of the processing. + transport := ghttp.RoundTripperFunc( + func(request *http.Request) (response *http.Response, err error) { + address := request.URL.String() + Expect(address).To(HavePrefix("https://mylb:32132/")) + err = errors.New("myerror") + return + }, + ) + factory, err := NewFactory(). + SetLogger(logger). + SetHubClient(hubClient). + AddTransportWrapper(func(_ http.RoundTripper) http.RoundTripper { + return transport + }). + Build() + Expect(err).ToNot(HaveOccurred()) + + // Create the cluster deployment, without the `agetClusteRef` label, as that is what + // indicates that it isn't a HyperShift cluster. + clusterDeployment := &hivev1.ClusterDeployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: hubNamespace.Name, + Name: "mycluster", + Labels: nil, + }, + } + err = hubClient.Create(ctx, clusterDeployment) + Expect(err).ToNot(HaveOccurred()) + + // Get the client: + clusterDeploymentKey := types.NamespacedName{ + Namespace: clusterDeployment.Namespace, + Name: clusterDeployment.Name, + } + client, err := factory.CreateFromSecret(ctx, clusterDeploymentKey, secret) + Expect(err).ToNot(HaveOccurred()) + + // Send a request with the client. This will fail because our transport wrapper fails + // all requests, but it allows us to verify that the API server address has been + // changed. + configMap := &corev1.ConfigMap{} + configMapKey := types.NamespacedName{ + Namespace: hubNamespace.Name, + Name: "myconfig", + } + err = client.Get(ctx, configMapKey, configMap) + Expect(err).To(MatchError(ContainSubstring("myerror"))) + }) }) }) }) diff --git a/internal/spoke_k8s_client/mock_spoke_k8s_client.go b/internal/spoke_k8s_client/mock_client.go similarity index 57% rename from internal/spoke_k8s_client/mock_spoke_k8s_client.go rename to internal/spoke_k8s_client/mock_client.go index 5d9c767bb95..5bb0997e862 100644 --- a/internal/spoke_k8s_client/mock_spoke_k8s_client.go +++ b/internal/spoke_k8s_client/mock_client.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/openshift/assisted-service/internal/spoke_k8s_client (interfaces: SpokeK8sClient) +// Source: github.com/openshift/assisted-service/internal/spoke_k8s_client (interfaces: Client) // Package spoke_k8s_client is a generated GoMock package. package spoke_k8s_client @@ -18,31 +18,31 @@ import ( client "sigs.k8s.io/controller-runtime/pkg/client" ) -// MockSpokeK8sClient is a mock of SpokeK8sClient interface. -type MockSpokeK8sClient struct { +// MockClient is a mock of Client interface. +type MockClient struct { ctrl *gomock.Controller - recorder *MockSpokeK8sClientMockRecorder + recorder *MockClientMockRecorder } -// MockSpokeK8sClientMockRecorder is the mock recorder for MockSpokeK8sClient. -type MockSpokeK8sClientMockRecorder struct { - mock *MockSpokeK8sClient +// MockClientMockRecorder is the mock recorder for MockClient. +type MockClientMockRecorder struct { + mock *MockClient } -// NewMockSpokeK8sClient creates a new mock instance. -func NewMockSpokeK8sClient(ctrl *gomock.Controller) *MockSpokeK8sClient { - mock := &MockSpokeK8sClient{ctrl: ctrl} - mock.recorder = &MockSpokeK8sClientMockRecorder{mock} +// NewMockClient creates a new mock instance. +func NewMockClient(ctrl *gomock.Controller) *MockClient { + mock := &MockClient{ctrl: ctrl} + mock.recorder = &MockClientMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockSpokeK8sClient) EXPECT() *MockSpokeK8sClientMockRecorder { +func (m *MockClient) EXPECT() *MockClientMockRecorder { return m.recorder } // ApproveCsr mocks base method. -func (m *MockSpokeK8sClient) ApproveCsr(arg0 context.Context, arg1 *v1.CertificateSigningRequest) error { +func (m *MockClient) ApproveCsr(arg0 context.Context, arg1 *v1.CertificateSigningRequest) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ApproveCsr", arg0, arg1) ret0, _ := ret[0].(error) @@ -50,13 +50,13 @@ func (m *MockSpokeK8sClient) ApproveCsr(arg0 context.Context, arg1 *v1.Certifica } // ApproveCsr indicates an expected call of ApproveCsr. -func (mr *MockSpokeK8sClientMockRecorder) ApproveCsr(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) ApproveCsr(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApproveCsr", reflect.TypeOf((*MockSpokeK8sClient)(nil).ApproveCsr), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApproveCsr", reflect.TypeOf((*MockClient)(nil).ApproveCsr), arg0, arg1) } // Create mocks base method. -func (m *MockSpokeK8sClient) Create(arg0 context.Context, arg1 client.Object, arg2 ...client.CreateOption) error { +func (m *MockClient) Create(arg0 context.Context, arg1 client.Object, arg2 ...client.CreateOption) error { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { @@ -68,14 +68,14 @@ func (m *MockSpokeK8sClient) Create(arg0 context.Context, arg1 client.Object, ar } // Create indicates an expected call of Create. -func (mr *MockSpokeK8sClientMockRecorder) Create(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) Create(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockSpokeK8sClient)(nil).Create), varargs...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockClient)(nil).Create), varargs...) } // Delete mocks base method. -func (m *MockSpokeK8sClient) Delete(arg0 context.Context, arg1 client.Object, arg2 ...client.DeleteOption) error { +func (m *MockClient) Delete(arg0 context.Context, arg1 client.Object, arg2 ...client.DeleteOption) error { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { @@ -87,14 +87,14 @@ func (m *MockSpokeK8sClient) Delete(arg0 context.Context, arg1 client.Object, ar } // Delete indicates an expected call of Delete. -func (mr *MockSpokeK8sClientMockRecorder) Delete(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) Delete(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockSpokeK8sClient)(nil).Delete), varargs...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockClient)(nil).Delete), varargs...) } // DeleteAllOf mocks base method. -func (m *MockSpokeK8sClient) DeleteAllOf(arg0 context.Context, arg1 client.Object, arg2 ...client.DeleteAllOfOption) error { +func (m *MockClient) DeleteAllOf(arg0 context.Context, arg1 client.Object, arg2 ...client.DeleteAllOfOption) error { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { @@ -106,14 +106,14 @@ func (m *MockSpokeK8sClient) DeleteAllOf(arg0 context.Context, arg1 client.Objec } // DeleteAllOf indicates an expected call of DeleteAllOf. -func (mr *MockSpokeK8sClientMockRecorder) DeleteAllOf(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) DeleteAllOf(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllOf", reflect.TypeOf((*MockSpokeK8sClient)(nil).DeleteAllOf), varargs...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllOf", reflect.TypeOf((*MockClient)(nil).DeleteAllOf), varargs...) } // DeleteNode mocks base method. -func (m *MockSpokeK8sClient) DeleteNode(arg0 context.Context, arg1 string) error { +func (m *MockClient) DeleteNode(arg0 context.Context, arg1 string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DeleteNode", arg0, arg1) ret0, _ := ret[0].(error) @@ -121,13 +121,13 @@ func (m *MockSpokeK8sClient) DeleteNode(arg0 context.Context, arg1 string) error } // DeleteNode indicates an expected call of DeleteNode. -func (mr *MockSpokeK8sClientMockRecorder) DeleteNode(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) DeleteNode(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteNode", reflect.TypeOf((*MockSpokeK8sClient)(nil).DeleteNode), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteNode", reflect.TypeOf((*MockClient)(nil).DeleteNode), arg0, arg1) } // Get mocks base method. -func (m *MockSpokeK8sClient) Get(arg0 context.Context, arg1 types.NamespacedName, arg2 client.Object, arg3 ...client.GetOption) error { +func (m *MockClient) Get(arg0 context.Context, arg1 types.NamespacedName, arg2 client.Object, arg3 ...client.GetOption) error { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1, arg2} for _, a := range arg3 { @@ -139,14 +139,14 @@ func (m *MockSpokeK8sClient) Get(arg0 context.Context, arg1 types.NamespacedName } // Get indicates an expected call of Get. -func (mr *MockSpokeK8sClientMockRecorder) Get(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) Get(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1, arg2}, arg3...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockSpokeK8sClient)(nil).Get), varargs...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockClient)(nil).Get), varargs...) } // GetNode mocks base method. -func (m *MockSpokeK8sClient) GetNode(arg0 context.Context, arg1 string) (*v10.Node, error) { +func (m *MockClient) GetNode(arg0 context.Context, arg1 string) (*v10.Node, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetNode", arg0, arg1) ret0, _ := ret[0].(*v10.Node) @@ -155,13 +155,13 @@ func (m *MockSpokeK8sClient) GetNode(arg0 context.Context, arg1 string) (*v10.No } // GetNode indicates an expected call of GetNode. -func (mr *MockSpokeK8sClientMockRecorder) GetNode(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) GetNode(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNode", reflect.TypeOf((*MockSpokeK8sClient)(nil).GetNode), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNode", reflect.TypeOf((*MockClient)(nil).GetNode), arg0, arg1) } // GroupVersionKindFor mocks base method. -func (m *MockSpokeK8sClient) GroupVersionKindFor(arg0 runtime.Object) (schema.GroupVersionKind, error) { +func (m *MockClient) GroupVersionKindFor(arg0 runtime.Object) (schema.GroupVersionKind, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GroupVersionKindFor", arg0) ret0, _ := ret[0].(schema.GroupVersionKind) @@ -170,13 +170,13 @@ func (m *MockSpokeK8sClient) GroupVersionKindFor(arg0 runtime.Object) (schema.Gr } // GroupVersionKindFor indicates an expected call of GroupVersionKindFor. -func (mr *MockSpokeK8sClientMockRecorder) GroupVersionKindFor(arg0 interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) GroupVersionKindFor(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GroupVersionKindFor", reflect.TypeOf((*MockSpokeK8sClient)(nil).GroupVersionKindFor), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GroupVersionKindFor", reflect.TypeOf((*MockClient)(nil).GroupVersionKindFor), arg0) } // IsObjectNamespaced mocks base method. -func (m *MockSpokeK8sClient) IsObjectNamespaced(arg0 runtime.Object) (bool, error) { +func (m *MockClient) IsObjectNamespaced(arg0 runtime.Object) (bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "IsObjectNamespaced", arg0) ret0, _ := ret[0].(bool) @@ -185,13 +185,13 @@ func (m *MockSpokeK8sClient) IsObjectNamespaced(arg0 runtime.Object) (bool, erro } // IsObjectNamespaced indicates an expected call of IsObjectNamespaced. -func (mr *MockSpokeK8sClientMockRecorder) IsObjectNamespaced(arg0 interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) IsObjectNamespaced(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsObjectNamespaced", reflect.TypeOf((*MockSpokeK8sClient)(nil).IsObjectNamespaced), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsObjectNamespaced", reflect.TypeOf((*MockClient)(nil).IsObjectNamespaced), arg0) } // List mocks base method. -func (m *MockSpokeK8sClient) List(arg0 context.Context, arg1 client.ObjectList, arg2 ...client.ListOption) error { +func (m *MockClient) List(arg0 context.Context, arg1 client.ObjectList, arg2 ...client.ListOption) error { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { @@ -203,14 +203,14 @@ func (m *MockSpokeK8sClient) List(arg0 context.Context, arg1 client.ObjectList, } // List indicates an expected call of List. -func (mr *MockSpokeK8sClientMockRecorder) List(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) List(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockSpokeK8sClient)(nil).List), varargs...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockClient)(nil).List), varargs...) } // ListCsrs mocks base method. -func (m *MockSpokeK8sClient) ListCsrs(arg0 context.Context) (*v1.CertificateSigningRequestList, error) { +func (m *MockClient) ListCsrs(arg0 context.Context) (*v1.CertificateSigningRequestList, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListCsrs", arg0) ret0, _ := ret[0].(*v1.CertificateSigningRequestList) @@ -219,13 +219,13 @@ func (m *MockSpokeK8sClient) ListCsrs(arg0 context.Context) (*v1.CertificateSign } // ListCsrs indicates an expected call of ListCsrs. -func (mr *MockSpokeK8sClientMockRecorder) ListCsrs(arg0 interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) ListCsrs(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListCsrs", reflect.TypeOf((*MockSpokeK8sClient)(nil).ListCsrs), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListCsrs", reflect.TypeOf((*MockClient)(nil).ListCsrs), arg0) } // Patch mocks base method. -func (m *MockSpokeK8sClient) Patch(arg0 context.Context, arg1 client.Object, arg2 client.Patch, arg3 ...client.PatchOption) error { +func (m *MockClient) Patch(arg0 context.Context, arg1 client.Object, arg2 client.Patch, arg3 ...client.PatchOption) error { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1, arg2} for _, a := range arg3 { @@ -237,14 +237,14 @@ func (m *MockSpokeK8sClient) Patch(arg0 context.Context, arg1 client.Object, arg } // Patch indicates an expected call of Patch. -func (mr *MockSpokeK8sClientMockRecorder) Patch(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) Patch(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1, arg2}, arg3...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockSpokeK8sClient)(nil).Patch), varargs...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockClient)(nil).Patch), varargs...) } // PatchMachineConfigPoolPaused mocks base method. -func (m *MockSpokeK8sClient) PatchMachineConfigPoolPaused(arg0 context.Context, arg1 bool, arg2 string) error { +func (m *MockClient) PatchMachineConfigPoolPaused(arg0 context.Context, arg1 bool, arg2 string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PatchMachineConfigPoolPaused", arg0, arg1, arg2) ret0, _ := ret[0].(error) @@ -252,13 +252,13 @@ func (m *MockSpokeK8sClient) PatchMachineConfigPoolPaused(arg0 context.Context, } // PatchMachineConfigPoolPaused indicates an expected call of PatchMachineConfigPoolPaused. -func (mr *MockSpokeK8sClientMockRecorder) PatchMachineConfigPoolPaused(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) PatchMachineConfigPoolPaused(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PatchMachineConfigPoolPaused", reflect.TypeOf((*MockSpokeK8sClient)(nil).PatchMachineConfigPoolPaused), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PatchMachineConfigPoolPaused", reflect.TypeOf((*MockClient)(nil).PatchMachineConfigPoolPaused), arg0, arg1, arg2) } // PatchNodeLabels mocks base method. -func (m *MockSpokeK8sClient) PatchNodeLabels(arg0 context.Context, arg1, arg2 string) error { +func (m *MockClient) PatchNodeLabels(arg0 context.Context, arg1, arg2 string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PatchNodeLabels", arg0, arg1, arg2) ret0, _ := ret[0].(error) @@ -266,13 +266,13 @@ func (m *MockSpokeK8sClient) PatchNodeLabels(arg0 context.Context, arg1, arg2 st } // PatchNodeLabels indicates an expected call of PatchNodeLabels. -func (mr *MockSpokeK8sClientMockRecorder) PatchNodeLabels(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) PatchNodeLabels(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PatchNodeLabels", reflect.TypeOf((*MockSpokeK8sClient)(nil).PatchNodeLabels), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PatchNodeLabels", reflect.TypeOf((*MockClient)(nil).PatchNodeLabels), arg0, arg1, arg2) } // RESTMapper mocks base method. -func (m *MockSpokeK8sClient) RESTMapper() meta.RESTMapper { +func (m *MockClient) RESTMapper() meta.RESTMapper { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "RESTMapper") ret0, _ := ret[0].(meta.RESTMapper) @@ -280,13 +280,13 @@ func (m *MockSpokeK8sClient) RESTMapper() meta.RESTMapper { } // RESTMapper indicates an expected call of RESTMapper. -func (mr *MockSpokeK8sClientMockRecorder) RESTMapper() *gomock.Call { +func (mr *MockClientMockRecorder) RESTMapper() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RESTMapper", reflect.TypeOf((*MockSpokeK8sClient)(nil).RESTMapper)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RESTMapper", reflect.TypeOf((*MockClient)(nil).RESTMapper)) } // Scheme mocks base method. -func (m *MockSpokeK8sClient) Scheme() *runtime.Scheme { +func (m *MockClient) Scheme() *runtime.Scheme { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Scheme") ret0, _ := ret[0].(*runtime.Scheme) @@ -294,13 +294,13 @@ func (m *MockSpokeK8sClient) Scheme() *runtime.Scheme { } // Scheme indicates an expected call of Scheme. -func (mr *MockSpokeK8sClientMockRecorder) Scheme() *gomock.Call { +func (mr *MockClientMockRecorder) Scheme() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Scheme", reflect.TypeOf((*MockSpokeK8sClient)(nil).Scheme)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Scheme", reflect.TypeOf((*MockClient)(nil).Scheme)) } // Status mocks base method. -func (m *MockSpokeK8sClient) Status() client.SubResourceWriter { +func (m *MockClient) Status() client.SubResourceWriter { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Status") ret0, _ := ret[0].(client.SubResourceWriter) @@ -308,13 +308,13 @@ func (m *MockSpokeK8sClient) Status() client.SubResourceWriter { } // Status indicates an expected call of Status. -func (mr *MockSpokeK8sClientMockRecorder) Status() *gomock.Call { +func (mr *MockClientMockRecorder) Status() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockSpokeK8sClient)(nil).Status)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockClient)(nil).Status)) } // SubResource mocks base method. -func (m *MockSpokeK8sClient) SubResource(arg0 string) client.SubResourceClient { +func (m *MockClient) SubResource(arg0 string) client.SubResourceClient { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SubResource", arg0) ret0, _ := ret[0].(client.SubResourceClient) @@ -322,13 +322,13 @@ func (m *MockSpokeK8sClient) SubResource(arg0 string) client.SubResourceClient { } // SubResource indicates an expected call of SubResource. -func (mr *MockSpokeK8sClientMockRecorder) SubResource(arg0 interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) SubResource(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubResource", reflect.TypeOf((*MockSpokeK8sClient)(nil).SubResource), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubResource", reflect.TypeOf((*MockClient)(nil).SubResource), arg0) } // Update mocks base method. -func (m *MockSpokeK8sClient) Update(arg0 context.Context, arg1 client.Object, arg2 ...client.UpdateOption) error { +func (m *MockClient) Update(arg0 context.Context, arg1 client.Object, arg2 ...client.UpdateOption) error { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { @@ -340,8 +340,8 @@ func (m *MockSpokeK8sClient) Update(arg0 context.Context, arg1 client.Object, ar } // Update indicates an expected call of Update. -func (mr *MockSpokeK8sClientMockRecorder) Update(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) Update(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockSpokeK8sClient)(nil).Update), varargs...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockClient)(nil).Update), varargs...) } diff --git a/internal/spoke_k8s_client/mock_factory.go b/internal/spoke_k8s_client/mock_factory.go new file mode 100644 index 00000000000..f15eebeed4e --- /dev/null +++ b/internal/spoke_k8s_client/mock_factory.go @@ -0,0 +1,69 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/openshift/assisted-service/internal/spoke_k8s_client (interfaces: Factory) + +// Package spoke_k8s_client is a generated GoMock package. +package spoke_k8s_client + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + v1 "k8s.io/api/core/v1" + types "k8s.io/apimachinery/pkg/types" + kubernetes "k8s.io/client-go/kubernetes" +) + +// MockFactory is a mock of Factory interface. +type MockFactory struct { + ctrl *gomock.Controller + recorder *MockFactoryMockRecorder +} + +// MockFactoryMockRecorder is the mock recorder for MockFactory. +type MockFactoryMockRecorder struct { + mock *MockFactory +} + +// NewMockFactory creates a new mock instance. +func NewMockFactory(ctrl *gomock.Controller) *MockFactory { + mock := &MockFactory{ctrl: ctrl} + mock.recorder = &MockFactoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockFactory) EXPECT() *MockFactoryMockRecorder { + return m.recorder +} + +// ClientAndSetFromSecret mocks base method. +func (m *MockFactory) ClientAndSetFromSecret(arg0 context.Context, arg1 types.NamespacedName, arg2 *v1.Secret) (Client, *kubernetes.Clientset, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientAndSetFromSecret", arg0, arg1, arg2) + ret0, _ := ret[0].(Client) + ret1, _ := ret[1].(*kubernetes.Clientset) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// ClientAndSetFromSecret indicates an expected call of ClientAndSetFromSecret. +func (mr *MockFactoryMockRecorder) ClientAndSetFromSecret(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientAndSetFromSecret", reflect.TypeOf((*MockFactory)(nil).ClientAndSetFromSecret), arg0, arg1, arg2) +} + +// CreateFromSecret mocks base method. +func (m *MockFactory) CreateFromSecret(arg0 context.Context, arg1 types.NamespacedName, arg2 *v1.Secret) (Client, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateFromSecret", arg0, arg1, arg2) + ret0, _ := ret[0].(Client) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateFromSecret indicates an expected call of CreateFromSecret. +func (mr *MockFactoryMockRecorder) CreateFromSecret(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFromSecret", reflect.TypeOf((*MockFactory)(nil).CreateFromSecret), arg0, arg1, arg2) +} diff --git a/internal/spoke_k8s_client/mock_spoke_k8s_client_factory.go b/internal/spoke_k8s_client/mock_spoke_k8s_client_factory.go deleted file mode 100644 index c6ff9a93573..00000000000 --- a/internal/spoke_k8s_client/mock_spoke_k8s_client_factory.go +++ /dev/null @@ -1,67 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/openshift/assisted-service/internal/spoke_k8s_client (interfaces: SpokeK8sClientFactory) - -// Package spoke_k8s_client is a generated GoMock package. -package spoke_k8s_client - -import ( - reflect "reflect" - - gomock "github.com/golang/mock/gomock" - v1 "k8s.io/api/core/v1" - kubernetes "k8s.io/client-go/kubernetes" -) - -// MockSpokeK8sClientFactory is a mock of SpokeK8sClientFactory interface. -type MockSpokeK8sClientFactory struct { - ctrl *gomock.Controller - recorder *MockSpokeK8sClientFactoryMockRecorder -} - -// MockSpokeK8sClientFactoryMockRecorder is the mock recorder for MockSpokeK8sClientFactory. -type MockSpokeK8sClientFactoryMockRecorder struct { - mock *MockSpokeK8sClientFactory -} - -// NewMockSpokeK8sClientFactory creates a new mock instance. -func NewMockSpokeK8sClientFactory(ctrl *gomock.Controller) *MockSpokeK8sClientFactory { - mock := &MockSpokeK8sClientFactory{ctrl: ctrl} - mock.recorder = &MockSpokeK8sClientFactoryMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockSpokeK8sClientFactory) EXPECT() *MockSpokeK8sClientFactoryMockRecorder { - return m.recorder -} - -// ClientAndSetFromSecret mocks base method. -func (m *MockSpokeK8sClientFactory) ClientAndSetFromSecret(arg0 *v1.Secret) (SpokeK8sClient, *kubernetes.Clientset, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClientAndSetFromSecret", arg0) - ret0, _ := ret[0].(SpokeK8sClient) - ret1, _ := ret[1].(*kubernetes.Clientset) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// ClientAndSetFromSecret indicates an expected call of ClientAndSetFromSecret. -func (mr *MockSpokeK8sClientFactoryMockRecorder) ClientAndSetFromSecret(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientAndSetFromSecret", reflect.TypeOf((*MockSpokeK8sClientFactory)(nil).ClientAndSetFromSecret), arg0) -} - -// CreateFromSecret mocks base method. -func (m *MockSpokeK8sClientFactory) CreateFromSecret(arg0 *v1.Secret) (SpokeK8sClient, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateFromSecret", arg0) - ret0, _ := ret[0].(SpokeK8sClient) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateFromSecret indicates an expected call of CreateFromSecret. -func (mr *MockSpokeK8sClientFactoryMockRecorder) CreateFromSecret(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFromSecret", reflect.TypeOf((*MockSpokeK8sClientFactory)(nil).CreateFromSecret), arg0) -} diff --git a/internal/spoke_k8s_client/suite_test.go b/internal/spoke_k8s_client/suite_test.go index 8e24431ffa8..01a90e6c306 100644 --- a/internal/spoke_k8s_client/suite_test.go +++ b/internal/spoke_k8s_client/suite_test.go @@ -1,13 +1,88 @@ package spoke_k8s_client import ( + "context" + "path/filepath" "testing" + "github.com/bombsimon/logrusr/v3" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + hivev1 "github.com/openshift/hive/apis/hive/v1" + "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/klog/v2" + ctrl "sigs.k8s.io/controller-runtime" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + + . "github.com/openshift/assisted-service/internal/testing" ) func TestSpokeK8SClient(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "Spoke K8S client") + RunSpecs(t, "Spoke client") } + +var ( + ctx context.Context + logger *logrus.Logger + hubEnv *envtest.Environment + hubClient ctrlclient.Client + hubNamespace *corev1.Namespace +) + +var _ = BeforeSuite(func() { + // Create a context: + ctx = context.Background() + + // Create a logger that writes to the Ginkgo output: + logger = logrus.New() + logger.SetOutput(GinkgoWriter) + + // Configure the controller-runtime library to use our logger: + adapter := logrusr.New(logger) + ctrl.SetLogger(adapter) + klog.SetLogger(adapter) + + // Start the hub environment: + hubScheme := runtime.NewScheme() + scheme.AddToScheme(hubScheme) + corev1.AddToScheme(hubScheme) + hivev1.AddToScheme(hubScheme) + hubEnv = SetupEnvtest(&envtest.Environment{ + CRDDirectoryPaths: []string{ + filepath.Join("..", "..", "hack", "crds"), + }, + Scheme: hubScheme, + }) + hubConfig, err := hubEnv.Start() + Expect(err).ToNot(HaveOccurred()) + + // Create the hub client: + hubOpts := ctrlclient.Options{ + Scheme: hubScheme, + } + hubClient, err = ctrlclient.New(hubConfig, hubOpts) + Expect(err).ToNot(HaveOccurred()) +}) + +var _ = BeforeEach(func() { + // Create the namespace: + hubNamespace = &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "myns-", + }, + } + err := hubClient.Create(ctx, hubNamespace) + Expect(err).ToNot(HaveOccurred()) +}) + +var _ = AfterEach(func() { + // Delete the namespace: + err := hubClient.Delete(ctx, hubNamespace) + Expect(err).ToNot(HaveOccurred()) +})