github.com/verrazzano/verrazzano@v1.7.1/tools/vz/pkg/helpers/vzcapture_test.go (about) 1 // Copyright (c) 2022, 2024, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package helpers 5 6 import ( 7 "bytes" 8 "encoding/json" 9 "fmt" 10 "io" 11 "os" 12 "path/filepath" 13 "regexp" 14 "testing" 15 "time" 16 17 certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" 18 "github.com/crossplane/oam-kubernetes-runtime/apis/core" 19 "github.com/stretchr/testify/assert" 20 appv1alpha1 "github.com/verrazzano/verrazzano/application-operator/apis/app/v1alpha1" 21 appclusterv1alpha1 "github.com/verrazzano/verrazzano/application-operator/apis/clusters/v1alpha1" 22 appoamv1alpha1 "github.com/verrazzano/verrazzano/application-operator/apis/oam/v1alpha1" 23 clusterv1alpha1 "github.com/verrazzano/verrazzano/cluster-operator/apis/clusters/v1alpha1" 24 vzconstants "github.com/verrazzano/verrazzano/pkg/constants" 25 "github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1beta1" 26 "github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/component/common" 27 "github.com/verrazzano/verrazzano/tools/vz/pkg/constants" 28 testhelpers "github.com/verrazzano/verrazzano/tools/vz/test/helpers" 29 corev1 "k8s.io/api/core/v1" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 32 "k8s.io/apimachinery/pkg/runtime" 33 "k8s.io/apimachinery/pkg/runtime/schema" 34 "k8s.io/cli-runtime/pkg/genericclioptions" 35 fakedynamic "k8s.io/client-go/dynamic/fake" 36 k8sfake "k8s.io/client-go/kubernetes/fake" 37 k8scheme "k8s.io/client-go/kubernetes/scheme" 38 "sigs.k8s.io/controller-runtime/pkg/client" 39 "sigs.k8s.io/controller-runtime/pkg/client/fake" 40 ) 41 42 const dummyIP1 = "0.0.0.0" 43 const dummyIP2 = "5.6.x.x" 44 45 // TestCreateReportArchive 46 // GIVEN a directory containing some files 47 // 48 // WHEN I call function CreateReportArchive with a report file 49 // THEN expect it to create the report file 50 func TestCreateReportArchive(t *testing.T) { 51 tmpDir, _ := os.MkdirTemp("", "bug-report") 52 defer cleanupTempDir(t, tmpDir) 53 54 captureDir := tmpDir + string(os.PathSeparator) + "test-report" 55 if err := os.Mkdir(captureDir, os.ModePerm); err != nil { 56 assert.Error(t, err) 57 } 58 59 // Create some files inside bugReport 60 _, err := os.Create(captureDir + string(os.PathSeparator) + "f1.txt") 61 if err != nil { 62 assert.Error(t, err) 63 } 64 65 _, err = os.Create(captureDir + string(os.PathSeparator) + "f2.txt") 66 if err != nil { 67 assert.Error(t, err) 68 } 69 70 _, err = os.Create(captureDir + string(os.PathSeparator) + "f3.txt") 71 if err != nil { 72 assert.Error(t, err) 73 } 74 75 bugReportFile, err := os.Create(tmpDir + string(os.PathSeparator) + "bug.tar.gz") 76 if err != nil { 77 assert.Error(t, err) 78 } 79 err = CreateReportArchive(captureDir, bugReportFile, true) 80 if err != nil { 81 assert.Error(t, err) 82 } 83 84 // Check file exists 85 assert.FileExists(t, bugReportFile.Name()) 86 } 87 88 // TestRemoveDuplicates 89 // GIVEN a string slice containing duplicates 90 // 91 // WHEN I call function RemoveDuplicate 92 // THEN expect it to remove the duplicate elements 93 func TestRemoveDuplicates(t *testing.T) { 94 testSlice := []string{"abc", "def", "abc"} 95 result := RemoveDuplicate(testSlice) 96 assert.True(t, true, len(result) == 2) 97 } 98 99 // TestGroupVersionResource 100 // 101 // WHEN I call functions to get the config schemes 102 // THEN expect it to return the expected resource 103 func TestGroupVersionResource(t *testing.T) { 104 assert.True(t, true, GetAppConfigScheme().Resource == constants.OAMAppConfigurations) 105 assert.True(t, true, GetComponentConfigScheme().Resource == constants.OAMComponents) 106 assert.True(t, true, GetMetricsTraitConfigScheme().Resource == constants.OAMMetricsTraits) 107 assert.True(t, true, GetIngressTraitConfigScheme().Resource == constants.OAMIngressTraits) 108 assert.True(t, true, GetMCComponentScheme().Resource == constants.OAMMCCompConfigurations) 109 assert.True(t, true, GetMCAppConfigScheme().Resource == constants.OAMMCAppConfigurations) 110 assert.True(t, true, GetVzProjectsConfigScheme().Resource == constants.OAMProjects) 111 assert.True(t, true, GetManagedClusterConfigScheme().Resource == constants.OAMManagedClusters) 112 } 113 114 // TestCaptureK8SResources 115 // 116 // WHEN I call functions to capture k8s resource 117 // THEN expect it to not throw any error 118 func TestCaptureK8SResources(t *testing.T) { 119 schemeForClient := k8scheme.Scheme 120 err := certmanagerv1.AddToScheme(schemeForClient) 121 assert.NoError(t, err) 122 k8sClient := k8sfake.NewSimpleClientset() 123 scheme := k8scheme.Scheme 124 AddCapiToScheme(scheme) 125 dynamicClient := fakedynamic.NewSimpleDynamicClient(scheme) 126 client := fake.NewClientBuilder().WithScheme(schemeForClient).Build() 127 captureDir, err := os.MkdirTemp("", "testcapture") 128 defer cleanupTempDir(t, captureDir) 129 assert.NoError(t, err) 130 buf := new(bytes.Buffer) 131 errBuf := new(bytes.Buffer) 132 tempFile, err := os.CreateTemp("", "testfile") 133 defer cleanupFile(t, tempFile) 134 assert.NoError(t, err) 135 SetMultiWriterOut(buf, tempFile) 136 SetMultiWriterErr(errBuf, tempFile) 137 rc := testhelpers.NewFakeRootCmdContext(genericclioptions.IOStreams{In: os.Stdin, Out: buf, ErrOut: errBuf}) 138 err = CaptureK8SResources(client, k8sClient, dynamicClient, constants.VerrazzanoInstall, captureDir, rc) 139 assert.NoError(t, err) 140 isError = false 141 } 142 143 // TestCaptureMultiClusterOAMResources tests the functionality to capture the multi cluster related resources 144 // 145 // WHEN I call functions to capture Verrazzano OAM resources in a multi cluster environment 146 // THEN expect it to not throw any error 147 func TestCaptureMultiClusterOAMResources(t *testing.T) { 148 scheme := k8scheme.Scheme 149 _ = v1beta1.AddToScheme(scheme) 150 _ = clusterv1alpha1.AddToScheme(scheme) 151 _ = appclusterv1alpha1.AddToScheme(scheme) 152 153 dynamicClient := fakedynamic.NewSimpleDynamicClient(scheme) 154 captureDir, err := os.MkdirTemp("", "testcapture") 155 defer cleanupTempDir(t, captureDir) 156 assert.NoError(t, err) 157 buf := new(bytes.Buffer) 158 errBuf := new(bytes.Buffer) 159 rc := testhelpers.NewFakeRootCmdContext(genericclioptions.IOStreams{In: os.Stdin, Out: buf, ErrOut: errBuf}) 160 assert.NoError(t, CaptureMultiClusterOAMResources(dynamicClient, []string{constants.VerrazzanoInstall}, captureDir, rc)) 161 } 162 163 // TestCaptureOAMResources tests the functionality to capture the OAM resources in the cluster 164 // 165 // WHEN I call functions to capture Verrazzano OAM resources 166 // THEN expect it to not throw any error 167 func TestCaptureOAMResources(t *testing.T) { 168 scheme := k8scheme.Scheme 169 _ = v1beta1.AddToScheme(scheme) 170 _ = clusterv1alpha1.AddToScheme(scheme) 171 _ = appclusterv1alpha1.AddToScheme(scheme) 172 _ = appv1alpha1.AddToScheme(scheme) 173 _ = appoamv1alpha1.AddToScheme(scheme) 174 _ = core.AddToScheme(scheme) 175 176 dynamicClient := fakedynamic.NewSimpleDynamicClient(scheme) 177 captureDir, err := os.MkdirTemp("", "testcapture") 178 defer cleanupTempDir(t, captureDir) 179 assert.NoError(t, err) 180 buf := new(bytes.Buffer) 181 errBuf := new(bytes.Buffer) 182 rc := testhelpers.NewFakeRootCmdContext(genericclioptions.IOStreams{In: os.Stdin, Out: buf, ErrOut: errBuf}) 183 assert.NoError(t, CaptureOAMResources(dynamicClient, []string{constants.VerrazzanoInstall}, captureDir, rc)) 184 } 185 186 // TestCapturePodLog tests the functionality to capture the logs of a given pod. 187 func TestCapturePodLog(t *testing.T) { 188 k8sClient := k8sfake.NewSimpleClientset() 189 captureDir, err := os.MkdirTemp("", "testcapture") 190 defer cleanupTempDir(t, captureDir) 191 assert.NoError(t, err) 192 buf := new(bytes.Buffer) 193 errBuf := new(bytes.Buffer) 194 rc := testhelpers.NewFakeRootCmdContext(genericclioptions.IOStreams{In: os.Stdin, Out: buf, ErrOut: errBuf}) 195 err = CapturePodLog(k8sClient, corev1.Pod{}, constants.VerrazzanoInstall, captureDir, rc, 0, false) 196 assert.NoError(t, err) 197 198 // GIVENT and empty k8s cluster, 199 // WHEN I call functions to capture VPO pod logs, 200 // THEN expect it to not throw any error. 201 err = CapturePodLog(k8sClient, corev1.Pod{ObjectMeta: metav1.ObjectMeta{ 202 Name: constants.VerrazzanoPlatformOperator, 203 Namespace: constants.VerrazzanoInstall, 204 }}, constants.VerrazzanoInstall, captureDir, rc, 0, false) 205 assert.NoError(t, err) 206 207 // GIVENT a k8s cluster with a VPO pod, 208 // WHEN I call functions to capture VPO pod logs, 209 // THEN expect it to not throw any error. 210 k8sClient = k8sfake.NewSimpleClientset(&corev1.Pod{ObjectMeta: metav1.ObjectMeta{ 211 Name: constants.VerrazzanoPlatformOperator, 212 Namespace: constants.VerrazzanoInstall, 213 }, Spec: corev1.PodSpec{ 214 Containers: []corev1.Container{ 215 { 216 Name: "testcontainer", 217 Image: "dummimage:notag", 218 }, 219 }, 220 }}) 221 err = CapturePodLog(k8sClient, corev1.Pod{ObjectMeta: metav1.ObjectMeta{ 222 Name: constants.VerrazzanoPlatformOperator, 223 Namespace: constants.VerrazzanoInstall, 224 }, Spec: corev1.PodSpec{ 225 Containers: []corev1.Container{ 226 { 227 Name: "testcontainer", 228 Image: "dummimage:notag", 229 }, 230 }, 231 }}, constants.VerrazzanoInstall, captureDir, rc, 300, false) 232 assert.NoError(t, err) 233 } 234 235 // TestGetPodList tests the functionality to return the list of pods with the given label 236 func TestGetPodList(t *testing.T) { 237 // GIVEN a k8s cluster with no VPO pods, 238 // WHEN I call functions to get the list of pods in the k8s cluster, 239 // THEN expect it to be an empty list. 240 pods, err := GetPodList(fake.NewClientBuilder().Build(), "app", constants.VerrazzanoPlatformOperator, constants.VerrazzanoInstall) 241 assert.NoError(t, err) 242 assert.Empty(t, pods) 243 244 // GIVEN a k8s cluster with a VPO pod, 245 // WHEN I call functions to get the list of pods in the k8s cluster, 246 // THEN expect it to be an empty list. 247 pods, err = GetPodList(fake.NewClientBuilder().WithObjects(&corev1.Pod{ 248 ObjectMeta: metav1.ObjectMeta{ 249 Name: constants.VerrazzanoPlatformOperator, 250 Namespace: constants.VerrazzanoInstall, 251 Labels: map[string]string{"app": constants.VerrazzanoPlatformOperator}, 252 }, 253 }).Build(), "app", constants.VerrazzanoPlatformOperator, constants.VerrazzanoInstall) 254 assert.NoError(t, err) 255 assert.NotEmpty(t, pods) 256 } 257 258 // TestCaptureVZResource tests the functionality to capture the Verrazzano resource. 259 func TestCaptureVZResource(t *testing.T) { 260 captureDir, err := os.MkdirTemp("", "testcapture") 261 defer cleanupTempDir(t, captureDir) 262 assert.NoError(t, err) 263 buf := new(bytes.Buffer) 264 errBuf := new(bytes.Buffer) 265 266 // GIVEN a k8s cluster with a user provided Verrazzano CR, 267 // WHEN I call functions to capture the Verrazzano CR, 268 // THEN expect the file to contain the JSON output of the Verrazzano CR. 269 vz := &v1beta1.Verrazzano{ 270 ObjectMeta: metav1.ObjectMeta{ 271 Namespace: "default", 272 Name: "myverrazzano", 273 }, 274 Spec: v1beta1.VerrazzanoSpec{ 275 Profile: v1beta1.Dev, 276 }, 277 } 278 tempFile, err := os.CreateTemp("", "testfile") 279 defer cleanupFile(t, tempFile) 280 assert.NoError(t, err) 281 SetMultiWriterOut(buf, tempFile) 282 SetMultiWriterErr(errBuf, tempFile) 283 SetVerboseOutput(true) 284 SetIsLiveCluster() 285 err = CaptureVZResource(captureDir, vz) 286 assert.NoError(t, err) 287 assert.NotNil(t, GetMultiWriterOut()) 288 assert.NotNil(t, GetMultiWriterErr()) 289 assert.True(t, GetIsLiveCluster()) 290 } 291 292 // TestCaptureVerrazzanoProjects tests the CaptureVerrazzanoProjects function 293 // GIVEN a dynamic client possibly containing VerrazzanoProject resources 294 // WHEN I call CaptureVerrazzanoProjects 295 // THEN expect it to detect the VerrazzanoProjects and appropriately create a JSON file 296 func TestCaptureVerrazzanoProjects(t *testing.T) { 297 scheme := k8scheme.Scheme 298 _ = appclusterv1alpha1.AddToScheme(scheme) 299 vzProject := appclusterv1alpha1.VerrazzanoProject{ 300 ObjectMeta: metav1.ObjectMeta{ 301 Namespace: vzconstants.VerrazzanoMultiClusterNamespace, 302 Name: "myvzproject", 303 }, 304 } 305 vzProject2 := appclusterv1alpha1.VerrazzanoProject{ 306 ObjectMeta: metav1.ObjectMeta{ 307 Namespace: vzconstants.VerrazzanoMultiClusterNamespace, 308 Name: "myvzproject2", 309 }, 310 } 311 312 tests := []struct { 313 name string 314 vzProjects []runtime.Object 315 }{ 316 { 317 "No VerrazzanoProjects exists", 318 nil, 319 }, 320 { 321 "A single existing VerrazzanoProject", 322 []runtime.Object{&vzProject}, 323 }, 324 { 325 "Two VerrazzanoProjects", 326 []runtime.Object{&vzProject, &vzProject2}, 327 }, 328 } 329 330 for _, tt := range tests { 331 t.Run(tt.name, func(t *testing.T) { 332 // create dynamic client 333 var dynamicClient *fakedynamic.FakeDynamicClient 334 if tt.vzProjects == nil { 335 dynamicClient = fakedynamic.NewSimpleDynamicClient(scheme) 336 } else { 337 dynamicClient = fakedynamic.NewSimpleDynamicClient(scheme, tt.vzProjects...) 338 } 339 340 // create capture directory 341 captureDir, err := os.MkdirTemp("", "testcapture") 342 defer cleanupTempDir(t, captureDir) 343 assert.NoError(t, err) 344 345 // stdout and stderr 346 buf := new(bytes.Buffer) 347 errBuf := new(bytes.Buffer) 348 tempFile, _ := os.CreateTemp("", "testfile") 349 defer cleanupFile(t, tempFile) 350 SetMultiWriterOut(buf, tempFile) 351 SetMultiWriterErr(errBuf, tempFile) 352 353 // Call the function we want to test 354 rc := testhelpers.NewFakeRootCmdContext(genericclioptions.IOStreams{In: os.Stdin, Out: buf, ErrOut: errBuf}) 355 assert.NoError(t, CaptureVerrazzanoProjects(dynamicClient, captureDir, rc)) 356 357 // Check if the VerrazzanoProject JSON file exists in the cluster snapshot as expected 358 expectedFilePath := filepath.Join(captureDir, vzconstants.VerrazzanoMultiClusterNamespace, constants.VzProjectsJSON) 359 _, err = os.Stat(expectedFilePath) 360 if len(tt.vzProjects) == 0 { 361 assert.Error(t, err) 362 } else { 363 assert.NoError(t, err) 364 vzProjectList := &appclusterv1alpha1.VerrazzanoProjectList{} 365 bytesToUnmarshall, err := returnBytesFromAFile(expectedFilePath) 366 assert.NoError(t, err) 367 assert.NoError(t, json.Unmarshal(bytesToUnmarshall, vzProjectList)) 368 assert.Len(t, vzProjectList.Items, len(tt.vzProjects), 369 "the file %s did not have the expected number of VerrazzanoProject resources listed", expectedFilePath) 370 } 371 }) 372 } 373 } 374 375 // TestCaptureVerrazzanoManagedCluster tests the CaptureVerrazzanoManagedCluster function 376 // GIVEN a dynamic client possibly containing VMC resources 377 // WHEN I call CaptureVerrazzanoManagedCluster 378 // THEN expect it to detect the VMCs and appropriately create a JSON file 379 func TestCaptureVerrazzanoManagedCluster(t *testing.T) { 380 scheme := k8scheme.Scheme 381 _ = clusterv1alpha1.AddToScheme(scheme) 382 vmc := clusterv1alpha1.VerrazzanoManagedCluster{ 383 ObjectMeta: metav1.ObjectMeta{ 384 Namespace: vzconstants.VerrazzanoMultiClusterNamespace, 385 Name: "myverrazzanomanagedcluster", 386 }, 387 } 388 vmc2 := clusterv1alpha1.VerrazzanoManagedCluster{ 389 ObjectMeta: metav1.ObjectMeta{ 390 Namespace: vzconstants.VerrazzanoMultiClusterNamespace, 391 Name: "myverrazzanomanagedcluster2", 392 }, 393 } 394 395 tests := []struct { 396 name string 397 vmcs []runtime.Object 398 }{ 399 { 400 "No VerrazzanoManagedClusters exists", 401 nil, 402 }, 403 { 404 "A single existing VerrazzanoManagedCluster", 405 []runtime.Object{&vmc}, 406 }, 407 { 408 "Two VerrazzanoManagedClusters", 409 []runtime.Object{&vmc, &vmc2}, 410 }, 411 } 412 413 for _, tt := range tests { 414 t.Run(tt.name, func(t *testing.T) { 415 // create dynamic client 416 var dynamicClient *fakedynamic.FakeDynamicClient 417 if tt.vmcs == nil { 418 dynamicClient = fakedynamic.NewSimpleDynamicClient(scheme) 419 } else { 420 dynamicClient = fakedynamic.NewSimpleDynamicClient(scheme, tt.vmcs...) 421 } 422 423 // create capture directory 424 captureDir, err := os.MkdirTemp("", "testcapture") 425 defer cleanupTempDir(t, captureDir) 426 assert.NoError(t, err) 427 428 // stdout and stderr 429 buf := new(bytes.Buffer) 430 errBuf := new(bytes.Buffer) 431 tempFile, _ := os.CreateTemp("", "testfile") 432 defer cleanupFile(t, tempFile) 433 SetMultiWriterOut(buf, tempFile) 434 SetMultiWriterErr(errBuf, tempFile) 435 436 // Call the function we want to test 437 rc := testhelpers.NewFakeRootCmdContext(genericclioptions.IOStreams{In: os.Stdin, Out: buf, ErrOut: errBuf}) 438 assert.NoError(t, CaptureVerrazzanoManagedCluster(dynamicClient, captureDir, rc)) 439 440 // Check if the VMC JSON file exists in the cluster snapshot as expected 441 expectedFilePath := filepath.Join(captureDir, vzconstants.VerrazzanoMultiClusterNamespace, constants.VmcJSON) 442 _, err = os.Stat(expectedFilePath) 443 if len(tt.vmcs) == 0 { 444 assert.Error(t, err) 445 } else { 446 assert.NoError(t, err) 447 vmcList := &clusterv1alpha1.VerrazzanoManagedClusterList{} 448 bytesToUnmarshall, err := returnBytesFromAFile(expectedFilePath) 449 assert.NoError(t, err) 450 assert.NoError(t, json.Unmarshal(bytesToUnmarshall, vmcList)) 451 assert.Len(t, vmcList.Items, len(tt.vmcs), 452 "the file %s did not have the expected number of VerrazzanoManagedCluster resources listed", expectedFilePath) 453 } 454 }) 455 } 456 } 457 458 // TestDoesNamespaceExist tests the functionality to check if a given namespace exists. 459 func TestDoesNamespaceExist(t *testing.T) { 460 buf := new(bytes.Buffer) 461 errBuf := new(bytes.Buffer) 462 rc := testhelpers.NewFakeRootCmdContext(genericclioptions.IOStreams{In: os.Stdin, Out: buf, ErrOut: errBuf}) 463 tempFile, _ := os.CreateTemp("", "testfile") 464 defer cleanupFile(t, tempFile) 465 SetMultiWriterOut(buf, tempFile) 466 SetMultiWriterErr(errBuf, tempFile) 467 SetVerboseOutput(true) 468 469 // GIVEN a k8s cluster with no namespaces, 470 // WHEN I call functions to check if a namespace with empty string exists, 471 // THEN expect it to return false and no error. 472 exists, err := DoesNamespaceExist(k8sfake.NewSimpleClientset(), "", rc) 473 assert.NoError(t, err) 474 assert.False(t, exists) 475 476 // GIVEN a k8s cluster with no namespaces, 477 // WHEN I call functions to check if a namespace verrazzano-install exists, 478 // THEN expect it to return false and an error. 479 exists, err = DoesNamespaceExist(k8sfake.NewSimpleClientset(), constants.VerrazzanoInstall, rc) 480 assert.Error(t, err) 481 assert.False(t, exists) 482 483 // GIVEN a k8s cluster with the required verrazzano-install namespace, 484 // WHEN I call functions to check if a namespace verrazzano-install exists, 485 // THEN expect it to return true and no error. 486 exists, err = DoesNamespaceExist(k8sfake.NewSimpleClientset(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ 487 Name: constants.VerrazzanoInstall, 488 }}), constants.VerrazzanoInstall, rc) 489 assert.NoError(t, err) 490 assert.True(t, exists) 491 } 492 493 // TestGetVZManagedNamespaces tests the functionality to return all namespaces managed by verrazzano 494 func TestGetVZManagedNamespaces(t *testing.T) { 495 namespaces := GetVZManagedNamespaces(k8sfake.NewSimpleClientset()) 496 assert.Empty(t, namespaces) 497 498 // GIVEN a k8s cluster with the required verrazzano-install namespace with label verrazzano-managed=true, 499 // WHEN I call functions to list the namespaces that are managed by Verrazzano, 500 // THEN expect it to return a single namespace verrazzano-install 501 namespaces = GetVZManagedNamespaces(k8sfake.NewSimpleClientset(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ 502 Name: constants.VerrazzanoInstall, 503 Labels: map[string]string{"verrazzano-managed": "true"}, 504 }})) 505 assert.NotEmpty(t, namespaces) 506 assert.Equal(t, 1, len(namespaces)) 507 assert.Equal(t, constants.VerrazzanoInstall, namespaces[0]) 508 } 509 510 // TestIsErrorReported tests the functionality to see if an error had been reported when capturing the k8s resources. 511 func TestIsErrorReported(t *testing.T) { 512 assert.False(t, IsErrorReported()) 513 LogError("dummy error msg") 514 assert.True(t, IsErrorReported()) 515 } 516 517 // TestCreateFile tests the functionality to create a file containing the Verrazzano Resource 518 func TestCreateFile(t *testing.T) { 519 // GIVEN a k8s cluster with a VPO pod, 520 // WHEN I call functions to create a JSON file for the pod, 521 // THEN expect it to write to the provided resource file, the JSON contents of the pod and no error should be returned. 522 captureDir, err := os.MkdirTemp("", "testcapture") 523 defer cleanupTempDir(t, captureDir) 524 assert.NoError(t, err) 525 defer cleanupTempDir(t, captureDir) 526 buf := new(bytes.Buffer) 527 errBuf := new(bytes.Buffer) 528 rc := testhelpers.NewFakeRootCmdContext(genericclioptions.IOStreams{In: os.Stdin, Out: buf, ErrOut: errBuf}) 529 err = createFile(corev1.Pod{ObjectMeta: metav1.ObjectMeta{ 530 Name: constants.VerrazzanoPlatformOperator, 531 Namespace: constants.VerrazzanoInstall, 532 }}, constants.VerrazzanoInstall, "test-file", captureDir, rc) 533 assert.NoError(t, err) 534 } 535 536 // cleanupTempDir cleans up the given temp directory after the test run 537 func cleanupTempDir(t *testing.T, dirName string) { 538 if err := os.RemoveAll(dirName); err != nil { 539 t.Fatalf("RemoveAll failed: %v", err) 540 } 541 } 542 543 // cleanupTempDir cleans up the given temp file after the test run 544 func cleanupFile(t *testing.T, file *os.File) { 545 if err := file.Close(); err != nil { 546 t.Fatalf("RemoveAll failed: %v", err) 547 } 548 } 549 550 // TestGetPodListAll tests the functionality to return the list of all pods 551 func TestGetPodListAll(t *testing.T) { 552 nsName := "test" 553 podLength := 5 554 var podList []client.Object 555 for i := 0; i < podLength; i++ { 556 podList = append(podList, &corev1.Pod{ 557 ObjectMeta: metav1.ObjectMeta{ 558 Name: nsName + fmt.Sprint(i), 559 Namespace: nsName, 560 Labels: map[string]string{"name": "myapp"}, 561 }, 562 }) 563 } 564 // GIVEN a k8s cluster with no pods, 565 // WHEN I call functions to get the list of pods in the k8s cluster, 566 // THEN expect it to be an empty list. 567 pods, err := GetPodListAll(fake.NewClientBuilder().Build(), nsName) 568 assert.NoError(t, err) 569 assert.Empty(t, pods) 570 571 // GIVEN a k8s cluster with 5 pods, 572 // WHEN I call functions to get the list of pods in the k8s cluster without label, 573 // THEN expect it to be list all pods. 574 pods, err = GetPodListAll(fake.NewClientBuilder().WithObjects(podList...).Build(), nsName) 575 assert.NoError(t, err) 576 assert.Equal(t, podLength, len(pods)) 577 } 578 579 // TestCreateInnoDBClusterFile tests that a InnoDBCluster file titled inno-db-cluster.json can be successfully written 580 // GIVEN a k8s cluster with a inno-db-cluster resources present in a namespace, 581 // WHEN I call functions to create a list of inno-db-cluster resources for the namespace 582 // THEN expect it to write to the provided resource file and no error should be returned 583 func TestCreateInnoDBClusterFile(t *testing.T) { 584 schemeForClient := runtime.NewScheme() 585 innoDBClusterGVK := schema.GroupVersionKind{ 586 Group: "mysql.oracle.com", 587 Version: "v2", 588 Kind: "InnoDBCluster", 589 } 590 innoDBCluster := unstructured.Unstructured{} 591 innoDBCluster.SetGroupVersionKind(innoDBClusterGVK) 592 innoDBCluster.SetNamespace("keycloak") 593 innoDBCluster.SetName("my-sql") 594 innoDBClusterStatusFields := []string{"status", "cluster", "status"} 595 err := unstructured.SetNestedField(innoDBCluster.Object, "ONLINE", innoDBClusterStatusFields...) 596 assert.NoError(t, err) 597 metav1Time := metav1.Time{ 598 Time: time.Now().UTC(), 599 } 600 innoDBCluster.SetDeletionTimestamp(&metav1Time) 601 cli := fake.NewClientBuilder().WithScheme(schemeForClient).WithObjects(&innoDBCluster).Build() 602 captureDir, err := os.MkdirTemp("", "testcaptureforinnodbclusters") 603 assert.Nil(t, err) 604 buf := new(bytes.Buffer) 605 errBuf := new(bytes.Buffer) 606 tempFile, err := os.CreateTemp(captureDir, "temporary-log-file-for-test") 607 assert.NoError(t, err) 608 SetMultiWriterOut(buf, tempFile) 609 rc := testhelpers.NewFakeRootCmdContext(genericclioptions.IOStreams{In: os.Stdin, Out: buf, ErrOut: errBuf}) 610 err = captureInnoDBClusterResources(cli, "keycloak", captureDir, rc) 611 assert.NoError(t, err) 612 innoDBClusterLocation := filepath.Join(captureDir, "keycloak", constants.InnoDBClusterJSON) 613 innoDBClusterListToUnmarshalInto := unstructured.UnstructuredList{} 614 bytesToUnmarshall, err := returnBytesFromAFile(innoDBClusterLocation) 615 assert.NoError(t, err) 616 innoDBClusterListToUnmarshalInto.UnmarshalJSON(bytesToUnmarshall) 617 assert.NoError(t, err) 618 innoDBClusterResource := innoDBClusterListToUnmarshalInto.Items[0] 619 statusOfCluster, _, err := unstructured.NestedString(innoDBClusterResource.Object, "status", "cluster", "status") 620 assert.NoError(t, err) 621 assert.Equal(t, statusOfCluster, "ONLINE") 622 623 } 624 625 // TestCreateCertificateFile tests that a certificate file titled certificates.json can be successfully written 626 // GIVEN a k8s cluster with certificates present in a namespace, 627 // WHEN I call functions to create a list of certificates for the namespace, 628 // THEN expect it to write to the provided resource file and no error should be returned. 629 func TestCreateCertificateFile(t *testing.T) { 630 schemeForClient := k8scheme.Scheme 631 err := certmanagerv1.AddToScheme(schemeForClient) 632 assert.NoError(t, err) 633 sampleCert := certmanagerv1.Certificate{ 634 ObjectMeta: metav1.ObjectMeta{Name: "testcertificate", Namespace: "cattle-system"}, 635 Spec: certmanagerv1.CertificateSpec{ 636 DNSNames: []string{"example.com", "www.example.com", "api.example.com"}, 637 IPAddresses: []string{dummyIP1, dummyIP2}, 638 }, 639 } 640 client := fake.NewClientBuilder().WithScheme(schemeForClient).WithObjects(&sampleCert).Build() 641 captureDir, err := os.MkdirTemp("", "testcaptureforcertificates") 642 assert.NoError(t, err) 643 t.Log(captureDir) 644 defer cleanupTempDir(t, captureDir) 645 buf := new(bytes.Buffer) 646 errBuf := new(bytes.Buffer) 647 tempFile, err := os.CreateTemp(captureDir, "temporary-log-file-for-test") 648 assert.NoError(t, err) 649 SetMultiWriterOut(buf, tempFile) 650 rc := testhelpers.NewFakeRootCmdContext(genericclioptions.IOStreams{In: os.Stdin, Out: buf, ErrOut: errBuf}) 651 err = captureCertificates(client, "cattle-system", captureDir, rc) 652 assert.NoError(t, err) 653 } 654 655 // TestCreateCaCrtInfoFile tests that a caCrtInfo file titled caCrtInfo.json can be successfully written 656 // GIVEN a k8s cluster with secrets containing caCrtInfo present in a namespace , 657 // WHEN I call functions to create a list of caCrt for the namespace, 658 // THEN expect it to write to the provided resource file and no error should be returned. 659 func TestCreateCaCrtJsonFile(t *testing.T) { 660 schemeForClient := k8scheme.Scheme 661 err := certmanagerv1.AddToScheme(schemeForClient) 662 assert.NoError(t, err) 663 certificateListForTest := certmanagerv1.CertificateList{} 664 sampleCert := certmanagerv1.Certificate{ 665 ObjectMeta: metav1.ObjectMeta{Name: "test-certificate-caCrt.json", Namespace: "cattle-system"}, 666 Spec: certmanagerv1.CertificateSpec{ 667 DNSNames: []string{"example.com", "www.example.com", "api.example.com"}, 668 IPAddresses: []string{dummyIP1, dummyIP2}, 669 SecretName: "test-secret-name", 670 }, 671 } 672 certificateListForTest.Items = append(certificateListForTest.Items, sampleCert) 673 sampleSecret := corev1.Secret{ 674 ObjectMeta: metav1.ObjectMeta{Name: "test-secret-name", Namespace: "cattle-system"}, 675 Data: map[string][]byte{ 676 "ca.crt": []byte("-----BEGIN CERTIFICATE-----\nMIIDvTCCAqWgAwIBAgIUJUr+YG3UuQJh6g4MKuRpZPnTVO0wDQYJKoZIhvcNAQEL\nBQAwbTELMAkGA1UEBhMCUEExDTALBgNVBAgMBFRlc3QxDTALBgNVBAcMBFRlc3Qx\nDTALBgNVBAoMBFRlc3QxDTALBgNVBAsMBFRlc3QxDTALBgNVBAMMBFRlc3QxEzAR\nBgkqhkiG9w0BCQEWBFRlc3QwIBcNMjMwODMwMTkxMjE4WhgPMzAyMjEyMzExOTEy\nMThaMG0xCzAJBgNVBAYTAlBBMQ0wCwYDVQQIDARUZXN0MQ0wCwYDVQQHDARUZXN0\nMQ0wCwYDVQQKDARUZXN0MQ0wCwYDVQQLDARUZXN0MQ0wCwYDVQQDDARUZXN0MRMw\nEQYJKoZIhvcNAQkBFgRUZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\nAQEA5R5EAPbPhrfRnpGtC49OX9q4XDVP11C/nHZ13z4QMPQn3eD+S5DODjo95wVD\nbZlmOUdGhas037W/G4rsEr+fg2DF3tNV3bNtDU5NG+PjRDcmFDKup0q7Lh7Yf2FP\naNxi6wlIgmm8Yi4lQmaBSN5LZalIbTO+tk7PRa1FY2LCIKDzzY9ipc0h9nDQWXIz\nEUtjQdQuZsdcv+br2L6b891Pu/fiZgJg1Vzx8N9bBbxMl3usI/CT8qmJy4E9fh4q\n0LQMFcOXeVSR4dhGLpctXP82AH2wgz0mLmgXlYe3koX+TlOxGIG3tUKBndvII8wm\nO03wILuk63XhXg30EFjpj0qZiQIDAQABo1MwUTAdBgNVHQ4EFgQUxkWW0nvivNEy\nLAPMJYgNwpSHQ5IwHwYDVR0jBBgwFoAUxkWW0nvivNEyLAPMJYgNwpSHQ5IwDwYD\nVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAtUjYhkDzJoFNx84Y9KXJ\nVM5BRtiI7YuvrKujwFmct1uCDEDXxZivwDf7khbUlI/GDg13LXsHQbxRaZNotcju\nibG9DNwInlBDpJ/grjlz/KG/LCmYrQE5RAnuqxVe812pc2ndSkTOGvcds7n7Gir/\n1S6zn2d5g2KeYtaMEYV1jArjzsFIdZ4M2R0ZTAsArcJy2ZGZ655j54Df7yzviNpD\nTz6nQQv1DHEpdogys+rOUTXrVhSpnsTacwztp/lvQsZl231THlCJcsySHRgMKmB+\nRKLLMfDfIaGeiZWRvEPEdurMWYkwWdYz9d+iEo3YTpWKy2QeCOEFZKMX5B2MXkdd\nNA==\n-----END CERTIFICATE-----\n"), 677 }, 678 } 679 client := fake.NewClientBuilder().WithScheme(schemeForClient).WithObjects(&sampleCert, &sampleSecret).Build() 680 captureDir, err := os.MkdirTemp("", "testcaptureforcaCrt.json") 681 assert.NoError(t, err) 682 t.Log(captureDir) 683 defer cleanupTempDir(t, captureDir) 684 buf := new(bytes.Buffer) 685 errBuf := new(bytes.Buffer) 686 tempFile, err := os.CreateTemp(captureDir, "temporary-log-file-for-ca-crt-test") 687 assert.NoError(t, err) 688 SetMultiWriterOut(buf, tempFile) 689 rc := testhelpers.NewFakeRootCmdContext(genericclioptions.IOStreams{In: os.Stdin, Out: buf, ErrOut: errBuf}) 690 err = captureCaCrtExpirationInfo(client, certificateListForTest, "cattle-system", captureDir, rc) 691 assert.NoError(t, err) 692 } 693 694 // TestRedactHostNamesForCertificates tests the captureCertificates function 695 // GIVEN when sample cert with DNSNames and IPAddresses which are sensitive information 696 // WHEN captureCertificates is called on certain namespace 697 // THEN it should obfuscate the known hostnames with hashed value 698 // AND the output certificates.json file should NOT contain any of the sensitive information from KnownHostNames 699 func TestRedactHostNamesForCertificates(t *testing.T) { 700 sampleCert := &certmanagerv1.Certificate{ 701 ObjectMeta: metav1.ObjectMeta{ 702 Name: "test-certificate", 703 Namespace: "cattle-system", 704 }, 705 Spec: certmanagerv1.CertificateSpec{ 706 DNSNames: []string{"example.com", "www.example.com", "api.example.com"}, 707 IPAddresses: []string{dummyIP1, dummyIP2}, 708 }, 709 } 710 711 schemeForClient := k8scheme.Scheme 712 err := certmanagerv1.AddToScheme(schemeForClient) 713 assert.NoError(t, err) 714 client := fake.NewClientBuilder().WithScheme(schemeForClient).WithObjects(sampleCert).Build() 715 captureDir, err := os.MkdirTemp("", "testcaptureforcertificates") 716 assert.NoError(t, err) 717 t.Log(captureDir) 718 defer cleanupTempDir(t, captureDir) 719 buf := new(bytes.Buffer) 720 errBuf := new(bytes.Buffer) 721 tempFile, err := os.CreateTemp(captureDir, "temporary-log-file-for-test") 722 assert.NoError(t, err) 723 SetMultiWriterOut(buf, tempFile) 724 rc := testhelpers.NewFakeRootCmdContext(genericclioptions.IOStreams{In: os.Stdin, Out: buf, ErrOut: errBuf}) 725 726 err = captureCertificates(client, common.CattleSystem, captureDir, rc) 727 assert.NoError(t, err) 728 729 // Check if the file is Sanitized as expected 730 certLocation := filepath.Join(captureDir, common.CattleSystem, constants.CertificatesJSON) 731 f, err := os.ReadFile(certLocation) 732 assert.NoError(t, err, "Should not error reading certificates.json file") 733 for k := range KnownHostNames { 734 keyMatch, err := regexp.Match(k, f) 735 assert.NoError(t, err, "Error while regex matching") 736 assert.Falsef(t, keyMatch, "%s should be obfuscated from certificates.json file %s", k, string(f)) 737 } 738 } 739 740 // TestCreateParentsIfNecessary tests that parent directories are only created when intended 741 // GIVEN a temporary directory and a string representing a filePath , 742 // WHEN I call a function to create a parent directory 743 // THEN expect it to only create the parent directories if it does not exist and if the file path contains a "/" 744 func TestCreateParentsIfNecessary(t *testing.T) { 745 err := os.Mkdir(constants.TestDirectory, 0700) 746 defer os.RemoveAll(constants.TestDirectory) 747 assert.Nil(t, err) 748 createParentsIfNecessary(constants.TestDirectory, "files.txt") 749 createParentsIfNecessary(constants.TestDirectory, "cluster-snapshot/files.txt") 750 _, err = os.Stat(constants.TestDirectory + "/files.txt") 751 assert.NotNil(t, err) 752 _, err = os.Stat(constants.TestDirectory + "/cluster-snapshot") 753 assert.Nil(t, err) 754 755 } 756 757 // TestCreateNamespaceFile tests that a namespace file titled namespace.json can be successfully written 758 // GIVEN a k8s cluster, 759 // WHEN I call functions to get the namespace resource for that namespace 760 // THEN expect it to write to the provided resource file with the correct information and no error should be returned. 761 func TestCreateNamespaceFile(t *testing.T) { 762 listOfFinalizers := []corev1.FinalizerName{"test-finalizer-name"} 763 sampleNamespace := corev1.Namespace{ 764 ObjectMeta: metav1.ObjectMeta{ 765 Name: "test-namespace", 766 }, 767 Spec: corev1.NamespaceSpec{ 768 Finalizers: listOfFinalizers, 769 }, 770 Status: corev1.NamespaceStatus{ 771 Phase: corev1.NamespaceTerminating, 772 }, 773 } 774 client := k8sfake.NewSimpleClientset(&sampleNamespace) 775 captureDir, err := os.MkdirTemp("", "testcapturefornamespaces") 776 assert.NoError(t, err) 777 t.Log(captureDir) 778 defer cleanupTempDir(t, captureDir) 779 buf := new(bytes.Buffer) 780 errBuf := new(bytes.Buffer) 781 tempFile, err := os.CreateTemp(captureDir, "temporary-log-file-for-test") 782 assert.NoError(t, err) 783 SetMultiWriterOut(buf, tempFile) 784 rc := testhelpers.NewFakeRootCmdContext(genericclioptions.IOStreams{In: os.Stdin, Out: buf, ErrOut: errBuf}) 785 err = captureNamespaces(client, "test-namespace", captureDir, rc) 786 assert.NoError(t, err) 787 namespaceLocation := filepath.Join(captureDir, "test-namespace", constants.NamespaceJSON) 788 namespaceObjectToUnmarshalInto := &corev1.Namespace{} 789 err = unmarshallFile(namespaceLocation, namespaceObjectToUnmarshalInto) 790 assert.NoError(t, err) 791 assert.True(t, namespaceObjectToUnmarshalInto.Status.Phase == corev1.NamespaceTerminating) 792 } 793 794 // TestCreateMetadataFile tests that a metadata file titled metadata.json can be successfully written 795 // GIVEN a k8s cluster, 796 // WHEN I call functions to record the metadata for the capture 797 // THEN expect it to write to the correct resource file with the correct information and no error should be returned. 798 func TestCreateMetadataFile(t *testing.T) { 799 captureDir, err := os.MkdirTemp("", "testcaptureformetadata") 800 assert.NoError(t, err) 801 t.Log(captureDir) 802 defer cleanupTempDir(t, captureDir) 803 buf := new(bytes.Buffer) 804 errBuf := new(bytes.Buffer) 805 tempFile, err := os.CreateTemp(captureDir, "temporary-log-file-for-test") 806 assert.NoError(t, err) 807 SetMultiWriterOut(buf, tempFile) 808 SetMultiWriterErr(errBuf, tempFile) 809 err = CaptureMetadata(captureDir) 810 assert.NoError(t, err) 811 metadataLocation := filepath.Join(captureDir, constants.MetadataJSON) 812 metadataObjectToUnmarshalInto := &Metadata{} 813 err = unmarshallFile(metadataLocation, metadataObjectToUnmarshalInto) 814 assert.NoError(t, err) 815 timeObject, err := time.Parse(time.RFC3339, metadataObjectToUnmarshalInto.Time) 816 assert.NoError(t, err) 817 assert.True(t, time.Now().UTC().Sub(timeObject).Minutes() < 60) 818 819 } 820 821 // unmarshallFile is a helper function that is used to place the contents of a file into a defined struct 822 func unmarshallFile(clusterPath string, object interface{}) error { 823 // Parse the json into local struct 824 file, err := os.Open(clusterPath) 825 if os.IsNotExist(err) { 826 // The file may not exist if the component is not installed. 827 return nil 828 } 829 if err != nil { 830 return fmt.Errorf("failed to open file %s from cluster snapshot: %s", clusterPath, err.Error()) 831 } 832 defer file.Close() 833 834 fileBytes, err := io.ReadAll(file) 835 if err != nil { 836 return fmt.Errorf("Failed reading Json file %s: %s", clusterPath, err.Error()) 837 } 838 // Unmarshall file contents into a struct 839 err = json.Unmarshal(fileBytes, object) 840 if err != nil { 841 return fmt.Errorf("Failed to unmarshal %s: %s", clusterPath, err.Error()) 842 } 843 844 return nil 845 } 846 847 // returnBytesFromAFile is a helper function that returns the bytes of a file 848 func returnBytesFromAFile(clusterPath string) ([]byte, error) { 849 var byteArray = []byte{} 850 // Parse the json into local struct 851 file, err := os.Open(clusterPath) 852 if os.IsNotExist(err) { 853 // The file may not exist if the component is not installed. 854 return byteArray, nil 855 } 856 if err != nil { 857 return byteArray, fmt.Errorf("failed to open file %s from cluster snapshot: %s", clusterPath, err.Error()) 858 } 859 defer file.Close() 860 861 byteArray, err = io.ReadAll(file) 862 if err != nil { 863 return byteArray, fmt.Errorf("Failed reading Json file %s: %s", clusterPath, err.Error()) 864 } 865 866 return byteArray, nil 867 }