diff --git a/pkg/awsiamauth/installer.go b/pkg/awsiamauth/installer.go index e6441f169126..1b7e57aa01e7 100644 --- a/pkg/awsiamauth/installer.go +++ b/pkg/awsiamauth/installer.go @@ -9,6 +9,7 @@ import ( "github.com/aws/eks-anywhere/pkg/cluster" "github.com/aws/eks-anywhere/pkg/crypto" "github.com/aws/eks-anywhere/pkg/filewriter" + "github.com/aws/eks-anywhere/pkg/kubeconfig" "github.com/aws/eks-anywhere/pkg/logger" "github.com/aws/eks-anywhere/pkg/types" ) @@ -23,11 +24,12 @@ type KubernetesClient interface { // Installer provides the necessary behavior for installing the AWS IAM Authenticator. type Installer struct { - certgen crypto.CertificateGenerator - templateBuilder *TemplateBuilder - clusterID uuid.UUID - k8s KubernetesClient - writer filewriter.FileWriter + certgen crypto.CertificateGenerator + templateBuilder *TemplateBuilder + clusterID uuid.UUID + k8s KubernetesClient + writer filewriter.FileWriter + kubeconfigWriter kubeconfig.Writer } // NewInstaller creates a new installer instance. @@ -36,13 +38,15 @@ func NewInstaller( clusterID uuid.UUID, k8s KubernetesClient, writer filewriter.FileWriter, + kubeconfigWriter kubeconfig.Writer, ) *Installer { return &Installer{ - certgen: certgen, - templateBuilder: &TemplateBuilder{}, - clusterID: clusterID, - k8s: k8s, - writer: writer, + certgen: certgen, + templateBuilder: &TemplateBuilder{}, + clusterID: clusterID, + k8s: k8s, + writer: writer, + kubeconfigWriter: kubeconfigWriter, } } @@ -163,6 +167,17 @@ func (i *Installer) GenerateManagementAWSIAMKubeconfig( ) error { fileName := fmt.Sprintf("%s-aws.kubeconfig", cluster.Name) + fsOptions := []filewriter.FileOptionsFunc{filewriter.PersistentFile, filewriter.Permission0600} + fh, path, err := i.writer.Create( + fileName, + fsOptions..., + ) + if err != nil { + return err + } + + defer fh.Close() + decodedKubeconfigSecretValue, err := i.k8s.GetAWSIAMKubeconfigSecretValue( ctx, cluster, @@ -172,16 +187,11 @@ func (i *Installer) GenerateManagementAWSIAMKubeconfig( return fmt.Errorf("generating aws-iam-authenticator kubeconfig: %v", err) } - writtenFile, err := i.writer.Write( - fileName, - decodedKubeconfigSecretValue, - filewriter.PersistentFile, - filewriter.Permission0600, - ) + err = i.kubeconfigWriter.WriteKubeconfigContent(ctx, cluster.Name, decodedKubeconfigSecretValue, fh) if err != nil { - return fmt.Errorf("writing aws-iam-authenticator kubeconfig to %s: %v", writtenFile, err) + return fmt.Errorf("writing aws-iam-authenticator kubeconfig to %s: %v", path, err) } - logger.V(3).Info("Generated aws-iam-authenticator kubeconfig", "kubeconfig", writtenFile) + logger.V(3).Info("Generated aws-iam-authenticator kubeconfig", "kubeconfig", path) return nil } diff --git a/pkg/awsiamauth/installer_test.go b/pkg/awsiamauth/installer_test.go index ae11e2dc07ec..be044dd7b7b4 100644 --- a/pkg/awsiamauth/installer_test.go +++ b/pkg/awsiamauth/installer_test.go @@ -3,12 +3,15 @@ package awsiamauth_test import ( "context" "errors" + "fmt" + "os" "strings" "testing" "github.com/golang/mock/gomock" "github.com/google/uuid" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" "github.com/aws/eks-anywhere/internal/test" "github.com/aws/eks-anywhere/pkg/api/v1alpha1" @@ -17,6 +20,7 @@ import ( cryptomocks "github.com/aws/eks-anywhere/pkg/crypto/mocks" "github.com/aws/eks-anywhere/pkg/filewriter" filewritermock "github.com/aws/eks-anywhere/pkg/filewriter/mocks" + kubeconfigmocks "github.com/aws/eks-anywhere/pkg/kubeconfig/mocks" "github.com/aws/eks-anywhere/pkg/types" ) @@ -38,6 +42,7 @@ func TestInstallAWSIAMAuth(t *testing.T) { var kubeconfig []byte writer := filewritermock.NewMockFileWriter(ctrl) + kwriter := kubeconfigmocks.NewMockWriter(ctrl) writer.EXPECT().Write(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( func(fileName string, content []byte, f ...filewriter.FileOptionsFunc) (string, error) { kubeconfig = content @@ -80,7 +85,7 @@ func TestInstallAWSIAMAuth(t *testing.T) { }, } - installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer) + installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer, kwriter) err := installer.InstallAWSIAMAuth(context.Background(), &types.Cluster{}, &types.Cluster{}, spec) if err != nil { @@ -172,8 +177,8 @@ func TestInstallAWSIAMAuthErrors(t *testing.T) { }, }, } - - installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer) + kwriter := kubeconfigmocks.NewMockWriter(ctrl) + installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer, kwriter) err := installer.InstallAWSIAMAuth(context.Background(), &types.Cluster{}, &types.Cluster{}, spec) if err == nil { @@ -205,7 +210,8 @@ func TestCreateAndInstallAWSIAMAuthCASecret(t *testing.T) { certs := cryptomocks.NewMockCertificateGenerator(ctrl) certs.EXPECT().GenerateIamAuthSelfSignCertKeyPair().Return([]byte("ca-cert"), []byte("ca-key"), nil) - installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer) + kwriter := kubeconfigmocks.NewMockWriter(ctrl) + installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer, kwriter) err := installer.CreateAndInstallAWSIAMAuthCASecret(context.Background(), &types.Cluster{}, "test-cluster") if err != nil { @@ -248,7 +254,8 @@ func TestCreateAndInstallAWSIAMAuthCASecretErrors(t *testing.T) { certs := cryptomocks.NewMockCertificateGenerator(ctrl) certs.EXPECT().GenerateIamAuthSelfSignCertKeyPair().Return([]byte("ca-cert"), []byte("ca-key"), nil) - installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer) + kwriter := kubeconfigmocks.NewMockWriter(ctrl) + installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer, kwriter) err := installer.CreateAndInstallAWSIAMAuthCASecret(context.Background(), &types.Cluster{}, "test-cluster") @@ -280,7 +287,8 @@ func TestUpgradeAWSIAMAuth(t *testing.T) { }, ) - installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer) + kwriter := kubeconfigmocks.NewMockWriter(ctrl) + installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer, kwriter) spec := &cluster.Spec{ Config: &cluster.Config{ @@ -328,16 +336,26 @@ func TestGenerateManagementAWSIAMKubeconfig(t *testing.T) { ctrl := gomock.NewController(t) certs := cryptomocks.NewMockCertificateGenerator(ctrl) clusterID := uuid.MustParse("36db102f-9e1e-4ca4-8300-271d30b14161") + ctx := context.Background() k8s := NewMockKubernetesClient(ctrl) - k8s.EXPECT().GetAWSIAMKubeconfigSecretValue(gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte("kubeconfig"), nil) + secretValue := []byte("kubeconfig") + k8s.EXPECT().GetAWSIAMKubeconfigSecretValue(gomock.Any(), gomock.Any(), gomock.Any()).Return(secretValue, nil) + cluster := &types.Cluster{ + Name: "cluster-name", + } writer := filewritermock.NewMockFileWriter(ctrl) - writer.EXPECT().Write(gomock.Any(), gomock.Any(), gomock.Any()).Return("kubeconfig", nil) + fileName := fmt.Sprintf("%s-aws.kubeconfig", cluster.Name) + path := "testpath" + fileWriter := os.NewFile(uintptr(*pointer.Uint(0)), "test") - installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer) + writer.EXPECT().Create(fileName, gomock.AssignableToTypeOf([]filewriter.FileOptionsFunc{})).Return(fileWriter, path, nil) + kwriter := kubeconfigmocks.NewMockWriter(ctrl) + installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer, kwriter) + kwriter.EXPECT().WriteKubeconfigContent(ctx, cluster.Name, secretValue, fileWriter) - err := installer.GenerateManagementAWSIAMKubeconfig(context.Background(), &types.Cluster{}) + err := installer.GenerateManagementAWSIAMKubeconfig(context.Background(), cluster) if err != nil { t.Fatal(err) } @@ -349,13 +367,50 @@ func TestGenerateManagementAWSIAMKubeconfigError(t *testing.T) { clusterID := uuid.MustParse("36db102f-9e1e-4ca4-8300-271d30b14161") k8s := NewMockKubernetesClient(ctrl) - k8s.EXPECT().GetAWSIAMKubeconfigSecretValue(gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte{}, errors.New("test")) + k8s.EXPECT().GetAWSIAMKubeconfigSecretValue(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("test")) + + cluster := &types.Cluster{ + Name: "cluster-name", + } + writer := filewritermock.NewMockFileWriter(ctrl) + fileName := fmt.Sprintf("%s-aws.kubeconfig", cluster.Name) + path := "testpath" + fileWriter := os.NewFile(uintptr(*pointer.Uint(0)), "test") + + writer.EXPECT().Create(fileName, gomock.AssignableToTypeOf([]filewriter.FileOptionsFunc{})).Return(fileWriter, path, nil) + kwriter := kubeconfigmocks.NewMockWriter(ctrl) + installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer, kwriter) + + err := installer.GenerateManagementAWSIAMKubeconfig(context.Background(), cluster) + if err == nil { + t.Fatal(err) + } +} + +func TestGenerateAWSIAMKubeconfigError(t *testing.T) { + ctrl := gomock.NewController(t) + certs := cryptomocks.NewMockCertificateGenerator(ctrl) + clusterID := uuid.MustParse("36db102f-9e1e-4ca4-8300-271d30b14161") + ctx := context.Background() + + k8s := NewMockKubernetesClient(ctrl) + secretValue := []byte("kubeconfig") + k8s.EXPECT().GetAWSIAMKubeconfigSecretValue(gomock.Any(), gomock.Any(), gomock.Any()).Return(secretValue, nil) + cluster := &types.Cluster{ + Name: "cluster-name", + } writer := filewritermock.NewMockFileWriter(ctrl) + fileName := fmt.Sprintf("%s-aws.kubeconfig", cluster.Name) + path := "testpath" + fileWriter := os.NewFile(uintptr(*pointer.Uint(0)), "test") - installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer) + writer.EXPECT().Create(fileName, gomock.AssignableToTypeOf([]filewriter.FileOptionsFunc{})).Return(fileWriter, path, nil) + kwriter := kubeconfigmocks.NewMockWriter(ctrl) + installer := awsiamauth.NewInstaller(certs, clusterID, k8s, writer, kwriter) + kwriter.EXPECT().WriteKubeconfigContent(ctx, cluster.Name, secretValue, fileWriter).Return(errors.New("test")) - err := installer.GenerateManagementAWSIAMKubeconfig(context.Background(), &types.Cluster{}) + err := installer.GenerateManagementAWSIAMKubeconfig(context.Background(), cluster) if err == nil { t.Fatal(err) } diff --git a/pkg/dependencies/factory.go b/pkg/dependencies/factory.go index 8eb6e2126a54..4a2b98990d37 100644 --- a/pkg/dependencies/factory.go +++ b/pkg/dependencies/factory.go @@ -951,8 +951,9 @@ func (f *Factory) WithCiliumTemplater() *Factory { return f } -func (f *Factory) WithAwsIamAuth() *Factory { - f.WithKubectl().WithWriter() +// WithAwsIamAuth builds dependencies for AWS IAM Auth. +func (f *Factory) WithAwsIamAuth(clusterConfig *v1alpha1.Cluster) *Factory { + f.WithKubectl().WithWriter().WithKubeconfigWriter(clusterConfig) f.buildSteps = append(f.buildSteps, func(ctx context.Context) error { if f.dependencies.AwsIamAuth != nil { @@ -971,6 +972,7 @@ func (f *Factory) WithAwsIamAuth() *Factory { clusterId, awsiamauth.NewRetrierClient(f.dependencies.Kubectl, opts...), f.dependencies.Writer, + f.dependencies.KubeconfigWriter, ) return nil }) @@ -1067,7 +1069,7 @@ func (f *Factory) clusterManagerOpts(timeoutOpts *ClusterManagerTimeoutOptions) // WithClusterManager builds a cluster manager based on the cluster config and timeout options. func (f *Factory) WithClusterManager(clusterConfig *v1alpha1.Cluster, timeoutOpts *ClusterManagerTimeoutOptions) *Factory { - f.WithClusterctl().WithNetworking(clusterConfig).WithWriter().WithDiagnosticBundleFactory().WithAwsIamAuth().WithFileReader().WithUnAuthKubeClient().WithKubernetesRetrierClient().WithEKSAInstaller() + f.WithClusterctl().WithNetworking(clusterConfig).WithWriter().WithDiagnosticBundleFactory().WithAwsIamAuth(clusterConfig).WithFileReader().WithUnAuthKubeClient().WithKubernetesRetrierClient().WithEKSAInstaller() f.buildSteps = append(f.buildSteps, func(ctx context.Context) error { if f.dependencies.ClusterManager != nil { diff --git a/pkg/dependencies/factory_test.go b/pkg/dependencies/factory_test.go index a27b816d3311..c147c634067f 100644 --- a/pkg/dependencies/factory_test.go +++ b/pkg/dependencies/factory_test.go @@ -691,7 +691,7 @@ func TestFactoryBuildWithAwsIamAuthNoTimeout(t *testing.T) { deps, err := dependencies.NewFactory(). WithLocalExecutables(). WithNoTimeouts(). - WithAwsIamAuth(). + WithAwsIamAuth(tt.clusterSpec.Cluster). Build(context.Background()) tt.Expect(err).To(BeNil()) diff --git a/pkg/kubeconfig/kubeconfig.go b/pkg/kubeconfig/kubeconfig.go index 0f9330b26915..921872aa90b0 100644 --- a/pkg/kubeconfig/kubeconfig.go +++ b/pkg/kubeconfig/kubeconfig.go @@ -17,6 +17,7 @@ import ( // Writer reads the kubeconfig secret on a cluster and copies the contents to a writer. type Writer interface { WriteKubeconfig(ctx context.Context, clusterName, kubeconfig string, w io.Writer) error + WriteKubeconfigContent(ctx context.Context, clusterName string, content []byte, w io.Writer) error } // FromClusterFormat defines the format of the kubeconfig of the. diff --git a/pkg/kubeconfig/kubeconfig_writer.go b/pkg/kubeconfig/kubeconfig_writer.go index 288044c67583..0be1a8d3ae42 100644 --- a/pkg/kubeconfig/kubeconfig_writer.go +++ b/pkg/kubeconfig/kubeconfig_writer.go @@ -66,7 +66,16 @@ func (kr ClusterAPIKubeconfigSecretWriter) WriteKubeconfig(ctx context.Context, return err } - if _, err := io.Copy(w, bytes.NewReader(rawKubeconfig)); err != nil { + if err := kr.WriteKubeconfigContent(ctx, clusterName, rawKubeconfig, w); err != nil { + return err + } + + return nil +} + +// WriteKubeconfigContent copies a raw kubeconfig to an io.Writer. +func (kr ClusterAPIKubeconfigSecretWriter) WriteKubeconfigContent(ctx context.Context, clusterName string, content []byte, w io.Writer) error { + if _, err := io.Copy(w, bytes.NewReader(content)); err != nil { return err } diff --git a/pkg/kubeconfig/mocks/writer.go b/pkg/kubeconfig/mocks/writer.go index 3b28dd8b1d60..f9c77d74a45f 100644 --- a/pkg/kubeconfig/mocks/writer.go +++ b/pkg/kubeconfig/mocks/writer.go @@ -48,3 +48,17 @@ func (mr *MockWriterMockRecorder) WriteKubeconfig(ctx, clusterName, kubeconfig, mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteKubeconfig", reflect.TypeOf((*MockWriter)(nil).WriteKubeconfig), ctx, clusterName, kubeconfig, w) } + +// WriteKubeconfigContent mocks base method. +func (m *MockWriter) WriteKubeconfigContent(ctx context.Context, clusterName string, content []byte, w io.Writer) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WriteKubeconfigContent", ctx, clusterName, content, w) + ret0, _ := ret[0].(error) + return ret0 +} + +// WriteKubeconfigContent indicates an expected call of WriteKubeconfigContent. +func (mr *MockWriterMockRecorder) WriteKubeconfigContent(ctx, clusterName, content, w interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteKubeconfigContent", reflect.TypeOf((*MockWriter)(nil).WriteKubeconfigContent), ctx, clusterName, content, w) +} diff --git a/pkg/providers/docker/docker.go b/pkg/providers/docker/docker.go index d3778f7f2d6f..80f8837b9186 100644 --- a/pkg/providers/docker/docker.go +++ b/pkg/providers/docker/docker.go @@ -607,14 +607,23 @@ func (kr KubeconfigWriter) WriteKubeconfig(ctx context.Context, clusterName, kub return err } + if err := kr.WriteKubeconfigContent(ctx, clusterName, rawkubeconfig, w); err != nil { + return err + } + + return nil +} + +// WriteKubeconfigContent retrieves the contents of the specified cluster's kubeconfig from a secret and copies it to an io.Writer. +func (kr KubeconfigWriter) WriteKubeconfigContent(ctx context.Context, clusterName string, content []byte, w io.Writer) error { port, err := kr.docker.GetDockerLBPort(ctx, clusterName) if err != nil { return err } - updateKubeconfig(&rawkubeconfig, port) + updateKubeconfig(&content, port) - if _, err := io.Copy(w, bytes.NewReader(rawkubeconfig)); err != nil { + if _, err := io.Copy(w, bytes.NewReader(content)); err != nil { return err } diff --git a/pkg/workflows/management/create_test.go b/pkg/workflows/management/create_test.go index 15d6534bdcaa..cd642e32e3b0 100644 --- a/pkg/workflows/management/create_test.go +++ b/pkg/workflows/management/create_test.go @@ -532,29 +532,6 @@ func TestCreateSyncFailure(t *testing.T) { } } -func TestCreateAWSIAMFailure(t *testing.T) { - test := newCreateTest(t) - test.expectSetup() - test.expectPreflightValidationsToPass() - test.expectCreateBootstrap() - test.expectCAPIInstall(nil, nil, nil) - test.expectInstallEksaComponentsBootstrap(nil, nil, nil, nil) - test.clusterSpec.AWSIamConfig = &v1alpha1.AWSIamConfig{} - - test.clusterManager.EXPECT().CreateNamespace(test.ctx, test.bootstrapCluster, test.clusterSpec.Cluster.Namespace).Return(nil) - test.clusterCreator.EXPECT().CreateSync(test.ctx, test.clusterSpec, test.bootstrapCluster).Return(test.workloadCluster, nil) - test.clusterManager.EXPECT().GenerateAWSIAMKubeconfig(test.ctx, test.workloadCluster).Return(errors.New("test")) - - test.clusterManager.EXPECT().SaveLogsManagementCluster(test.ctx, test.clusterSpec, test.bootstrapCluster) - test.clusterManager.EXPECT().SaveLogsWorkloadCluster(test.ctx, test.provider, test.clusterSpec, test.workloadCluster) - test.writer.EXPECT().Write(fmt.Sprintf("%s-checkpoint.yaml", test.clusterSpec.Cluster.Name), gomock.Any()) - - err := test.run() - if err == nil { - t.Fatalf("Create.Run() expected to return an error %v", err) - } -} - func TestCreateEKSANamespaceFailure(t *testing.T) { test := newCreateTest(t) test.expectSetup() @@ -851,6 +828,39 @@ func TestCreateWriteConfigFailure(t *testing.T) { } } +func TestCreateWriteConfigAWSIAMFailure(t *testing.T) { + test := newCreateTest(t) + + test.expectSetup() + test.expectCreateBootstrap() + test.expectCAPIInstall(nil, nil, nil) + test.expectInstallEksaComponentsBootstrap(nil, nil, nil, nil) + test.expectCreateWorkload(nil, nil, nil, nil, nil, nil) + test.expectInstallResourcesOnManagementTask(nil) + test.expectPauseReconcile(nil) + test.expectMoveManagement(nil) + test.expectInstallEksaComponentsWorkload(nil, nil, nil) + test.expectInstallGitOpsManager() + test.expectPreflightValidationsToPass() + test.clusterSpec.AWSIamConfig = &v1alpha1.AWSIamConfig{} + test.expectWriteClusterConfig() + + test.clusterManager.EXPECT().GenerateAWSIAMKubeconfig(test.ctx, test.workloadCluster).Return(errors.New("test")) + + test.clusterManager.EXPECT().SaveLogsManagementCluster( + test.ctx, test.clusterSpec, test.bootstrapCluster, + ) + test.clusterManager.EXPECT().SaveLogsWorkloadCluster( + test.ctx, test.provider, test.clusterSpec, test.workloadCluster, + ) + test.writer.EXPECT().Write(fmt.Sprintf("%s-checkpoint.yaml", test.clusterSpec.Cluster.Name), gomock.Any()) + + err := test.run() + if err == nil { + t.Fatalf("Create.Run() err = %v, want err = nil", err) + } +} + func TestCreateRunDeleteBootstrapFailure(t *testing.T) { test := newCreateTest(t) test.expectSetup() diff --git a/pkg/workflows/management/create_workload.go b/pkg/workflows/management/create_workload.go index 298e38258305..5efc456282db 100644 --- a/pkg/workflows/management/create_workload.go +++ b/pkg/workflows/management/create_workload.go @@ -32,15 +32,6 @@ func (s *createWorkloadClusterTask) Run(ctx context.Context, commandContext *tas } commandContext.WorkloadCluster = workloadCluster - if commandContext.ClusterSpec.AWSIamConfig != nil { - logger.Info("Generating the aws iam kubeconfig file") - err = commandContext.ClusterManager.GenerateAWSIAMKubeconfig(ctx, commandContext.WorkloadCluster) - if err != nil { - commandContext.SetError(err) - return &workflows.CollectDiagnosticsTask{} - } - } - logger.Info("Creating EKS-A namespace") err = commandContext.ClusterManager.CreateEKSANamespace(ctx, commandContext.WorkloadCluster) if err != nil { diff --git a/pkg/workflows/management/write_cluster_config.go b/pkg/workflows/management/write_cluster_config.go index ed61d3114659..2904c9b39a01 100644 --- a/pkg/workflows/management/write_cluster_config.go +++ b/pkg/workflows/management/write_cluster_config.go @@ -49,6 +49,16 @@ func (s *writeCreateClusterConfig) Run(ctx context.Context, commandContext *task commandContext.SetError(err) return &workflows.CollectDiagnosticsTask{} } + + if commandContext.ClusterSpec.AWSIamConfig != nil { + logger.Info("Generating the aws iam kubeconfig file") + err = commandContext.ClusterManager.GenerateAWSIAMKubeconfig(ctx, commandContext.WorkloadCluster) + if err != nil { + commandContext.SetError(err) + return &workflows.CollectDiagnosticsTask{} + } + } + return &deleteBootstrapClusterTask{} }