github.com/verrazzano/verrazzano@v1.7.0/tools/vz/pkg/helpers/vzcapture.go (about) 1 // Copyright (c) 2022, 2023, 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 "archive/tar" 8 "bufio" 9 "compress/gzip" 10 "context" 11 "crypto/x509" 12 "encoding/json" 13 "encoding/pem" 14 "fmt" 15 "io" 16 "os" 17 "path/filepath" 18 "strings" 19 "time" 20 21 v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" 22 oamcore "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" 23 clustersv1alpha1 "github.com/verrazzano/verrazzano/application-operator/apis/clusters/v1alpha1" 24 vzoamapi "github.com/verrazzano/verrazzano/application-operator/apis/oam/v1alpha1" 25 vzconstants "github.com/verrazzano/verrazzano/pkg/constants" 26 "github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1beta1" 27 "github.com/verrazzano/verrazzano/tools/vz/pkg/constants" 28 corev1 "k8s.io/api/core/v1" 29 "k8s.io/apimachinery/pkg/api/errors" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/labels" 32 "k8s.io/apimachinery/pkg/runtime/schema" 33 "k8s.io/apimachinery/pkg/selection" 34 "k8s.io/client-go/dynamic" 35 "k8s.io/client-go/kubernetes" 36 clipkg "sigs.k8s.io/controller-runtime/pkg/client" 37 ) 38 39 var errBugReport = "an error occurred while creating the bug report: %s" 40 var createFileError = "an error occurred while creating the file %s: %s" 41 42 var containerStartLog = "==== START logs for container %s of pod %s/%s ====\n" 43 var containerEndLog = "==== END logs for container %s of pod %s/%s ====\n" 44 45 var isError bool 46 var isLiveCluster bool 47 var isVerbose bool 48 49 var multiWriterOut io.Writer 50 var multiWriterErr io.Writer 51 52 type CaCrtInfo struct { 53 Name string `json:"name"` 54 Expired bool `json:"expired"` 55 } 56 57 // CreateReportArchive creates the .tar.gz file specified by bugReportFile, from the files in captureDir 58 func CreateReportArchive(captureDir string, bugRepFile *os.File) error { 59 60 // Create new Writers for gzip and tar 61 gzipWriter := gzip.NewWriter(bugRepFile) 62 defer gzipWriter.Close() 63 64 tarWriter := tar.NewWriter(gzipWriter) 65 defer tarWriter.Close() 66 67 walkFn := func(path string, fileInfo os.FileInfo, err error) error { 68 if fileInfo.Mode().IsDir() { 69 return nil 70 } 71 // make cluster-snapshot as the root directory in the archive, to support existing analysis tool 72 filePath := constants.BugReportRoot + path[len(captureDir):] 73 fileReader, err := os.Open(path) 74 if err != nil { 75 return fmt.Errorf(errBugReport, err.Error()) 76 } 77 defer fileReader.Close() 78 79 fih, err := tar.FileInfoHeader(fileInfo, filePath) 80 if err != nil { 81 return fmt.Errorf(errBugReport, err.Error()) 82 } 83 84 fih.Name = filePath 85 err = tarWriter.WriteHeader(fih) 86 if err != nil { 87 return fmt.Errorf(errBugReport, err.Error()) 88 } 89 _, err = io.Copy(tarWriter, fileReader) 90 if err != nil { 91 return fmt.Errorf(errBugReport, err.Error()) 92 } 93 return nil 94 } 95 96 if err := filepath.Walk(captureDir, walkFn); err != nil { 97 return err 98 } 99 return nil 100 } 101 102 // CaptureK8SResources collects the Workloads (Deployment and ReplicaSet, StatefulSet, Daemonset), pods, events, ingress 103 // services, and cert-manager certificates from the specified namespace, as JSON files 104 func CaptureK8SResources(client clipkg.Client, kubeClient kubernetes.Interface, dynamicClient dynamic.Interface, namespace, captureDir string, vzHelper VZHelper) error { 105 if err := captureWorkLoads(kubeClient, namespace, captureDir, vzHelper); err != nil { 106 return err 107 } 108 if err := capturePods(kubeClient, namespace, captureDir, vzHelper); err != nil { 109 return err 110 } 111 if err := captureEvents(kubeClient, namespace, captureDir, vzHelper); err != nil { 112 return err 113 } 114 if err := captureIngress(kubeClient, namespace, captureDir, vzHelper); err != nil { 115 return err 116 } 117 if err := captureServices(kubeClient, namespace, captureDir, vzHelper); err != nil { 118 return err 119 } 120 if err := captureCapiNamespacedResources(dynamicClient, namespace, captureDir, vzHelper); err != nil { 121 return err 122 } 123 if err := captureRancherNamespacedResources(dynamicClient, namespace, captureDir, vzHelper); err != nil { 124 return err 125 } 126 if err := captureCertificates(client, namespace, captureDir, vzHelper); err != nil { 127 return err 128 } 129 return nil 130 } 131 132 // GetPodList returns list of pods matching the label in the given namespace 133 func GetPodList(client clipkg.Client, appLabel, appName, namespace string) ([]corev1.Pod, error) { 134 aLabel, _ := labels.NewRequirement(appLabel, selection.Equals, []string{appName}) 135 labelSelector := labels.NewSelector() 136 labelSelector = labelSelector.Add(*aLabel) 137 podList := corev1.PodList{} 138 err := client.List( 139 context.TODO(), 140 &podList, 141 &clipkg.ListOptions{ 142 Namespace: namespace, 143 LabelSelector: labelSelector, 144 }) 145 if err != nil { 146 return nil, fmt.Errorf("an error while listing pods: %s", err.Error()) 147 } 148 return podList.Items, nil 149 } 150 151 // GetPodListAll returns list of pods in the given namespace 152 // Will be used to fetch all pods in additional namespace 153 func GetPodListAll(client clipkg.Client, namespace string) ([]corev1.Pod, error) { 154 podList := corev1.PodList{} 155 err := client.List( 156 context.TODO(), 157 &podList, 158 &clipkg.ListOptions{ 159 Namespace: namespace, 160 }) 161 if err != nil { 162 return nil, fmt.Errorf("an error while listing pods: %s", err.Error()) 163 } 164 switch namespace { 165 case vzconstants.VerrazzanoInstallNamespace: 166 return removePod(podList.Items, constants.VerrazzanoPlatformOperator), nil 167 case vzconstants.VerrazzanoSystemNamespace: 168 return removePods(podList.Items, []string{constants.VerrazzanoApplicationOperator, constants.VerrazzanoMonitoringOperator}), nil 169 case vzconstants.CertManager: 170 return removePod(podList.Items, vzconstants.ExternalDNS), nil 171 } 172 return podList.Items, nil 173 } 174 175 // CaptureVZResource captures Verrazzano resources as a JSON file 176 func CaptureVZResource(captureDir string, vz *v1beta1.Verrazzano) error { 177 var vzRes = filepath.Join(captureDir, constants.VzResource) 178 f, err := os.OpenFile(vzRes, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) 179 if err != nil { 180 return fmt.Errorf(createFileError, vzRes, err.Error()) 181 } 182 defer f.Close() 183 184 LogMessage("Verrazzano resource ...\n") 185 vzJSON, err := json.MarshalIndent(vz, constants.JSONPrefix, constants.JSONIndent) 186 if err != nil { 187 LogError(fmt.Sprintf("An error occurred while creating JSON encoding of %s: %s\n", vzRes, err.Error())) 188 return err 189 } 190 _, err = f.WriteString(SanitizeString(string(vzJSON))) 191 if err != nil { 192 LogError(fmt.Sprintf("An error occurred while writing the file %s: %s\n", vzRes, err.Error())) 193 return err 194 } 195 return nil 196 } 197 198 // captureEvents captures the events in the given namespace, as a JSON file 199 func captureEvents(kubeClient kubernetes.Interface, namespace, captureDir string, vzHelper VZHelper) error { 200 events, err := kubeClient.CoreV1().Events(namespace).List(context.TODO(), metav1.ListOptions{}) 201 if err != nil { 202 LogError(fmt.Sprintf("An error occurred while getting the Events in namespace %s: %s\n", namespace, err.Error())) 203 } 204 if len(events.Items) > 0 { 205 LogMessage(fmt.Sprintf("Events in namespace: %s ...\n", namespace)) 206 if err = createFile(events, namespace, constants.EventsJSON, captureDir, vzHelper); err != nil { 207 return err 208 } 209 } 210 return nil 211 } 212 213 // capturePods captures the pods in the given namespace, as a JSON file 214 func capturePods(kubeClient kubernetes.Interface, namespace, captureDir string, vzHelper VZHelper) error { 215 pods, err := kubeClient.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{}) 216 if err != nil { 217 LogError(fmt.Sprintf("An error occurred while getting the Pods in namespace %s: %s\n", namespace, err.Error())) 218 } 219 if len(pods.Items) > 0 { 220 LogMessage(fmt.Sprintf("Pods in namespace: %s ...\n", namespace)) 221 if err = createFile(pods, namespace, constants.PodsJSON, captureDir, vzHelper); err != nil { 222 return err 223 } 224 } 225 return nil 226 } 227 228 // captureIngress captures the ingresses in the given namespace, as a JSON file 229 func captureIngress(kubeClient kubernetes.Interface, namespace, captureDir string, vzHelper VZHelper) error { 230 ingressList, err := kubeClient.NetworkingV1().Ingresses(namespace).List(context.TODO(), metav1.ListOptions{}) 231 if err != nil { 232 LogError(fmt.Sprintf("An error occurred while getting the Ingress in namespace %s: %s\n", namespace, err.Error())) 233 } 234 if len(ingressList.Items) > 0 { 235 LogMessage(fmt.Sprintf("Ingresses in namespace: %s ...\n", namespace)) 236 if err = createFile(ingressList, namespace, constants.IngressJSON, captureDir, vzHelper); err != nil { 237 return err 238 } 239 } 240 return nil 241 } 242 243 // captureServices captures the services in the given namespace, as a JSON file 244 func captureServices(kubeClient kubernetes.Interface, namespace, captureDir string, vzHelper VZHelper) error { 245 serviceList, err := kubeClient.CoreV1().Services(namespace).List(context.TODO(), metav1.ListOptions{}) 246 if err != nil { 247 LogError(fmt.Sprintf("An error occurred while getting the Services in namespace %s: %s\n", namespace, err.Error())) 248 } 249 if len(serviceList.Items) > 0 { 250 LogMessage(fmt.Sprintf("Services in namespace: %s ...\n", namespace)) 251 if err = createFile(serviceList, namespace, constants.ServicesJSON, captureDir, vzHelper); err != nil { 252 return err 253 } 254 } 255 return nil 256 } 257 258 // captureWorkLoads captures the Deployment and ReplicaSet, StatefulSet, Daemonset in the given namespace 259 func captureWorkLoads(kubeClient kubernetes.Interface, namespace, captureDir string, vzHelper VZHelper) error { 260 deployments, err := kubeClient.AppsV1().Deployments(namespace).List(context.TODO(), metav1.ListOptions{}) 261 if err != nil { 262 LogError(fmt.Sprintf("An error occurred while getting the Deployments in namespace %s: %s\n", namespace, err.Error())) 263 } 264 if len(deployments.Items) > 0 { 265 LogMessage(fmt.Sprintf("Deployments in namespace: %s ...\n", namespace)) 266 if err = createFile(deployments, namespace, constants.DeploymentsJSON, captureDir, vzHelper); err != nil { 267 return err 268 } 269 } 270 271 replicaSets, err := kubeClient.AppsV1().ReplicaSets(namespace).List(context.TODO(), metav1.ListOptions{}) 272 if err != nil { 273 LogError(fmt.Sprintf("An error occurred while getting the ReplicaSets in namespace %s: %s\n", namespace, err.Error())) 274 } 275 if len(replicaSets.Items) > 0 { 276 LogMessage(fmt.Sprintf("Replicasets in namespace: %s ...\n", namespace)) 277 if err = createFile(replicaSets, namespace, constants.ReplicaSetsJSON, captureDir, vzHelper); err != nil { 278 return err 279 } 280 } 281 282 daemonSets, err := kubeClient.AppsV1().DaemonSets(namespace).List(context.TODO(), metav1.ListOptions{}) 283 if err != nil { 284 LogError(fmt.Sprintf("An error occurred while getting the DaemonSets in namespace %s: %s\n", namespace, err.Error())) 285 } 286 if len(daemonSets.Items) > 0 { 287 LogMessage(fmt.Sprintf("DaemonSets in namespace: %s ...\n", namespace)) 288 if err = createFile(daemonSets, namespace, constants.DaemonSetsJSON, captureDir, vzHelper); err != nil { 289 return err 290 } 291 } 292 293 statefulSets, err := kubeClient.AppsV1().StatefulSets(namespace).List(context.TODO(), metav1.ListOptions{}) 294 if err != nil { 295 LogError(fmt.Sprintf("An error occurred while getting the StatefulSets in namespace %s: %s\n", namespace, err.Error())) 296 } 297 if len(statefulSets.Items) > 0 { 298 LogMessage(fmt.Sprintf("StatefulSets in namespace: %s ...\n", namespace)) 299 if err = createFile(statefulSets, namespace, constants.StatefulSetsJSON, captureDir, vzHelper); err != nil { 300 return err 301 } 302 } 303 return nil 304 } 305 306 // captureCertificates finds the certificates from the client for the current namespace, returns an error, and outputs the objects to a certificates.json file, if certificates are present in that namespace. 307 func captureCertificates(client clipkg.Client, namespace, captureDir string, vzHelper VZHelper) error { 308 certificateList := v1.CertificateList{} 309 err := client.List(context.TODO(), &certificateList, &clipkg.ListOptions{Namespace: namespace}) 310 if err != nil { 311 LogError(fmt.Sprintf("An error occurred while getting the Certificates in namespace %s: %s\n", namespace, err.Error())) 312 } 313 collectHostNames(certificateList) 314 if len(certificateList.Items) > 0 { 315 LogMessage(fmt.Sprintf("Certificates in namespace: %s ...\n", namespace)) 316 if err = createFile(certificateList, namespace, constants.CertificatesJSON, captureDir, vzHelper); err != nil { 317 return err 318 } 319 captureCaCrtExpirationInfo(client, certificateList, namespace, captureDir, vzHelper) 320 } 321 return nil 322 } 323 324 // captureCaCrtExpirationInfo is a helper function of the captureCertificates function that loops through the certificates in a particular namespace and outputs a file containing information about the ca.crt of each certificate 325 func captureCaCrtExpirationInfo(client clipkg.Client, certificateList v1.CertificateList, namespace string, captureDir string, vzHelper VZHelper) error { 326 caCrtList := []CaCrtInfo{} 327 for _, cert := range certificateList.Items { 328 caCrtInfoForCert, isFound, err := isCaExpired(client, cert, namespace) 329 330 if err != nil { 331 return err 332 } 333 if isFound { 334 caCrtList = append(caCrtList, *caCrtInfoForCert) 335 } 336 337 } 338 if len(caCrtList) > 0 { 339 LogMessage(fmt.Sprintf("ca.crts in namespace: %s ...\n", namespace)) 340 if err := createFile(caCrtList, namespace, "caCrtInfo.json", captureDir, vzHelper); err != nil { 341 return err 342 } 343 344 } 345 return nil 346 } 347 348 func collectHostNames(certificateList v1.CertificateList) { 349 for _, cert := range certificateList.Items { 350 for _, hostname := range cert.Spec.DNSNames { 351 putIntoHostNamesIfNotPresent(hostname) 352 } 353 } 354 for _, cert := range certificateList.Items { 355 for _, ipAddress := range cert.Spec.IPAddresses { 356 putIntoHostNamesIfNotPresent(ipAddress) 357 } 358 } 359 } 360 361 func putIntoHostNamesIfNotPresent(inputKey string) { 362 knownHostNamesMutex.Lock() 363 keyInMap := KnownHostNames[inputKey] 364 if !keyInMap { 365 KnownHostNames[inputKey] = true 366 } 367 knownHostNamesMutex.Unlock() 368 } 369 370 // CapturePodLog captures the log from the pod in the captureDir 371 func CapturePodLog(kubeClient kubernetes.Interface, pod corev1.Pod, namespace, captureDir string, vzHelper VZHelper, duration int64) error { 372 podName := pod.Name 373 if len(podName) == 0 { 374 return nil 375 } 376 377 // Create directory for the namespace and the pod, under the root level directory containing the bug report 378 var folderPath = filepath.Join(captureDir, namespace, podName) 379 err := os.MkdirAll(folderPath, os.ModePerm) 380 if err != nil { 381 return fmt.Errorf("an error occurred while creating the directory %s: %s", folderPath, err.Error()) 382 } 383 384 // Create logs.txt under the directory for the namespace 385 var logPath = filepath.Join(folderPath, constants.LogFile) 386 f, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) 387 if err != nil { 388 return fmt.Errorf(createFileError, logPath, err.Error()) 389 } 390 defer f.Close() 391 392 // Capture logs for both init containers and containers 393 var cs []corev1.Container 394 var podLogOptions corev1.PodLogOptions 395 if duration != 0 { 396 podLogOptions.SinceSeconds = &duration 397 } 398 cs = append(cs, pod.Spec.InitContainers...) 399 cs = append(cs, pod.Spec.Containers...) 400 // Write the log from all the containers to a single file, with lines differentiating the logs from each of the containers 401 for _, c := range cs { 402 writeToFile := func(contName string) error { 403 podLogOptions.Container = contName 404 podLogOptions.InsecureSkipTLSVerifyBackend = true 405 podLog, err := kubeClient.CoreV1().Pods(namespace).GetLogs(podName, &podLogOptions).Stream(context.TODO()) 406 if err != nil { 407 LogError(fmt.Sprintf("An error occurred while reading the logs from pod %s: %s\n", podName, err.Error())) 408 return nil 409 } 410 defer podLog.Close() 411 412 reader := bufio.NewScanner(podLog) 413 f.WriteString(fmt.Sprintf(containerStartLog, contName, namespace, podName)) 414 for reader.Scan() { 415 f.WriteString(SanitizeString(reader.Text() + "\n")) 416 } 417 f.WriteString(fmt.Sprintf(containerEndLog, contName, namespace, podName)) 418 return nil 419 } 420 writeToFile(c.Name) 421 } 422 return nil 423 } 424 425 // createFile creates file from a workload, as a JSON file 426 func createFile(v interface{}, namespace, resourceFile, captureDir string, vzHelper VZHelper) error { 427 var folderPath = filepath.Join(captureDir, namespace) 428 429 if _, err := os.Stat(folderPath); os.IsNotExist(err) { 430 err := os.MkdirAll(folderPath, os.ModePerm) 431 if err != nil { 432 return fmt.Errorf("an error occurred while creating the directory %s: %s", folderPath, err.Error()) 433 } 434 } 435 436 var res = filepath.Join(folderPath, resourceFile) 437 f, err := os.OpenFile(res, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) 438 if err != nil { 439 return fmt.Errorf(createFileError, res, err.Error()) 440 } 441 defer f.Close() 442 443 resJSON, _ := json.MarshalIndent(v, constants.JSONPrefix, constants.JSONIndent) 444 _, err = f.WriteString(SanitizeString(string(resJSON))) 445 if err != nil { 446 LogError(fmt.Sprintf("An error occurred while writing the file %s: %s\n", res, err.Error())) 447 } 448 return nil 449 } 450 451 // CaptureOAMResources captures OAM resources in the given list of namespaces 452 func CaptureOAMResources(dynamicClient dynamic.Interface, nsList []string, captureDir string, vzHelper VZHelper) error { 453 for _, ns := range nsList { 454 if err := captureAppConfigurations(dynamicClient, ns, captureDir, vzHelper); err != nil { 455 return err 456 } 457 if err := captureComponents(dynamicClient, ns, captureDir, vzHelper); err != nil { 458 return err 459 } 460 if err := captureIngressTraits(dynamicClient, ns, captureDir, vzHelper); err != nil { 461 return err 462 } 463 if err := captureMetricsTraits(dynamicClient, ns, captureDir, vzHelper); err != nil { 464 return err 465 } 466 } 467 return nil 468 } 469 470 // CaptureMultiClusterOAMResources captures OAM resources in multi-cluster environment 471 func CaptureMultiClusterOAMResources(dynamicClient dynamic.Interface, nsList []string, captureDir string, vzHelper VZHelper) error { 472 for _, ns := range nsList { 473 // Capture multi-cluster components and application configurations 474 if err := captureMCComponents(dynamicClient, ns, captureDir, vzHelper); err != nil { 475 return err 476 } 477 478 if err := captureMCAppConfigurations(dynamicClient, ns, captureDir, vzHelper); err != nil { 479 return err 480 } 481 } 482 return nil 483 } 484 485 // DoesNamespaceExist checks whether the namespace exists in the cluster 486 func DoesNamespaceExist(kubeClient kubernetes.Interface, namespace string, vzHelper VZHelper) (bool, error) { 487 if namespace == "" { 488 fmt.Fprintf(vzHelper.GetErrorStream(), "Ignoring empty namespace\n") 489 return false, nil 490 } 491 ns, err := kubeClient.CoreV1().Namespaces().Get(context.TODO(), namespace, metav1.GetOptions{}) 492 493 if err != nil && errors.IsNotFound(err) { 494 fmt.Fprintf(GetMultiWriterOut(), "Namespace %s not found in the cluster, so will be ignored.\n", namespace) 495 return false, err 496 } 497 if err != nil { 498 LogError(fmt.Sprintf("An error occurred while getting the namespace %s: %s\n", namespace, err.Error())) 499 return false, err 500 } 501 return ns != nil && len(ns.Name) > 0, nil 502 } 503 504 // GetVZManagedNamespaces returns the namespaces with label verrazzano-managed=true 505 func GetVZManagedNamespaces(kubeClient kubernetes.Interface) []string { 506 var appNS []string 507 nsList, err := kubeClient.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{LabelSelector: constants.VerrazzanoManagedLabel}) 508 if err != nil { 509 LogError(fmt.Sprintf("An error occurred while listing the namespaces with label verrazzano-managed=true: %s\n", err.Error())) 510 return appNS 511 } 512 513 for _, ns := range nsList.Items { 514 appNS = append(appNS, ns.Name) 515 } 516 return appNS 517 } 518 519 // RemoveDuplicate removes duplicates from origSlice 520 func RemoveDuplicate(origSlice []string) []string { 521 allKeys := make(map[string]bool) 522 var returnSlice []string 523 for _, item := range origSlice { 524 if _, value := allKeys[item]; !value { 525 allKeys[item] = true 526 returnSlice = append(returnSlice, item) 527 } 528 } 529 return returnSlice 530 } 531 532 // captureAppConfigurations captures the OAM application configurations in the given namespace, as a JSON file 533 func captureAppConfigurations(dynamicClient dynamic.Interface, namespace, captureDir string, vzHelper VZHelper) error { 534 appConfigs, err := dynamicClient.Resource(GetAppConfigScheme()).Namespace(namespace).List(context.TODO(), metav1.ListOptions{}) 535 if err != nil && errors.IsNotFound(err) { 536 return nil 537 } 538 if err != nil { 539 LogError(fmt.Sprintf("An error occurred while getting the ApplicationConfigurations in namespace %s: %s\n", namespace, err.Error())) 540 return nil 541 } 542 if len(appConfigs.Items) > 0 { 543 LogMessage(fmt.Sprintf("ApplicationConfigurations in namespace: %s ...\n", namespace)) 544 if err = createFile(appConfigs, namespace, constants.AppConfigJSON, captureDir, vzHelper); err != nil { 545 return err 546 } 547 } 548 return nil 549 } 550 551 // captureComponents captures the OAM components in the given namespace, as a JSON file 552 func captureComponents(dynamicClient dynamic.Interface, namespace, captureDir string, vzHelper VZHelper) error { 553 comps, err := dynamicClient.Resource(GetComponentConfigScheme()).Namespace(namespace).List(context.TODO(), metav1.ListOptions{}) 554 if err != nil && errors.IsNotFound(err) { 555 return nil 556 } 557 if err != nil { 558 LogError(fmt.Sprintf("An error occurred while getting the Components in namespace %s: %s\n", namespace, err.Error())) 559 return nil 560 } 561 if len(comps.Items) > 0 { 562 LogMessage(fmt.Sprintf("Components in namespace: %s ...\n", namespace)) 563 if err = createFile(comps, namespace, constants.ComponentJSON, captureDir, vzHelper); err != nil { 564 return err 565 } 566 } 567 return nil 568 } 569 570 // captureIngressTraits captures the ingress traits in the given namespace, as a JSON file 571 func captureIngressTraits(dynamicClient dynamic.Interface, namespace, captureDir string, vzHelper VZHelper) error { 572 ingTraits, err := dynamicClient.Resource(GetIngressTraitConfigScheme()).Namespace(namespace).List(context.TODO(), metav1.ListOptions{}) 573 if err != nil && errors.IsNotFound(err) { 574 return nil 575 } 576 if err != nil { 577 LogError(fmt.Sprintf("An error occurred while getting the IngressTraits in namespace %s: %s\n", namespace, err.Error())) 578 return nil 579 } 580 if len(ingTraits.Items) > 0 { 581 LogMessage(fmt.Sprintf("IngressTraits in namespace: %s ...\n", namespace)) 582 if err = createFile(ingTraits, namespace, constants.IngressTraitJSON, captureDir, vzHelper); err != nil { 583 return err 584 } 585 } 586 return nil 587 } 588 589 // captureMetricsTraits captures the metrics traits in the given namespace, as a JSON file 590 func captureMetricsTraits(dynamicClient dynamic.Interface, namespace, captureDir string, vzHelper VZHelper) error { 591 metricsTraits, err := dynamicClient.Resource(GetMetricsTraitConfigScheme()).Namespace(namespace).List(context.TODO(), metav1.ListOptions{}) 592 if err != nil && errors.IsNotFound(err) { 593 return nil 594 } 595 if err != nil { 596 LogError(fmt.Sprintf("An error occurred while getting the MetricsTraits in namespace %s: %s\n", namespace, err.Error())) 597 return nil 598 } 599 if len(metricsTraits.Items) > 0 { 600 LogMessage(fmt.Sprintf("MetricsTraits in namespace: %s ...\n", namespace)) 601 if err = createFile(metricsTraits, namespace, constants.MetricsTraitJSON, captureDir, vzHelper); err != nil { 602 return err 603 } 604 } 605 return nil 606 } 607 608 // captureMCComponents captures the MulticlusterComponent in the given namespace, as a JSON file 609 func captureMCComponents(dynamicClient dynamic.Interface, namespace, captureDir string, vzHelper VZHelper) error { 610 mcComps, err := dynamicClient.Resource(GetMCComponentScheme()).Namespace(namespace).List(context.TODO(), metav1.ListOptions{}) 611 if err != nil && errors.IsNotFound(err) { 612 return nil 613 } 614 if err != nil { 615 LogError(fmt.Sprintf("An error occurred while getting the MulticlusterComponent in namespace %s: %s\n", namespace, err.Error())) 616 return nil 617 } 618 if len(mcComps.Items) > 0 { 619 LogMessage(fmt.Sprintf("MulticlusterComponent in namespace: %s ...\n", namespace)) 620 if err = createFile(mcComps, namespace, constants.McComponentJSON, captureDir, vzHelper); err != nil { 621 return err 622 } 623 } 624 return nil 625 } 626 627 // captureMCComponents captures the MultiClusterApplicationConfiguration in the given namespace, as a JSON file 628 func captureMCAppConfigurations(dynamicClient dynamic.Interface, namespace, captureDir string, vzHelper VZHelper) error { 629 mcAppConfigs, err := dynamicClient.Resource(GetMCAppConfigScheme()).Namespace(namespace).List(context.TODO(), metav1.ListOptions{}) 630 if err != nil && errors.IsNotFound(err) { 631 return nil 632 } 633 if err != nil { 634 LogError(fmt.Sprintf("An error occurred while getting the MultiClusterApplicationConfiguration in namespace %s: %s\n", namespace, err.Error())) 635 return nil 636 } 637 if len(mcAppConfigs.Items) > 0 { 638 LogMessage(fmt.Sprintf("MultiClusterApplicationConfiguration in namespace: %s ...\n", namespace)) 639 if err = createFile(mcAppConfigs, namespace, constants.McAppConfigJSON, captureDir, vzHelper); err != nil { 640 return err 641 } 642 } 643 return nil 644 } 645 646 // CaptureVerrazzanoProjects captures the Verrazzano projects in the verrazzano-mc namespace, as a JSON file 647 func CaptureVerrazzanoProjects(dynamicClient dynamic.Interface, captureDir string, vzHelper VZHelper) error { 648 vzProjectConfigs, err := dynamicClient.Resource(GetVzProjectsConfigScheme()).Namespace(vzconstants.VerrazzanoMultiClusterNamespace).List(context.TODO(), metav1.ListOptions{}) 649 if err != nil && errors.IsNotFound(err) { 650 return nil 651 } 652 if err != nil { 653 LogError(fmt.Sprintf("An error occurred while getting the VerrazzanoProjects in namespace %s: %s\n", vzconstants.VerrazzanoMultiClusterNamespace, err.Error())) 654 return nil 655 } 656 if len(vzProjectConfigs.Items) > 0 { 657 LogMessage(fmt.Sprintf("VerrazzanoProjects in namespace: %s ...\n", vzconstants.VerrazzanoMultiClusterNamespace)) 658 if err = createFile(vzProjectConfigs, vzconstants.VerrazzanoMultiClusterNamespace, constants.VzProjectsJSON, captureDir, vzHelper); err != nil { 659 return err 660 } 661 } 662 return nil 663 } 664 665 // CaptureVerrazzanoManagedCluster captures VerrazzanoManagedCluster in verrazzano-mc namespace, as a JSON file 666 func CaptureVerrazzanoManagedCluster(dynamicClient dynamic.Interface, captureDir string, vzHelper VZHelper) error { 667 vmcConfigs, err := dynamicClient.Resource(GetManagedClusterConfigScheme()).Namespace(vzconstants.VerrazzanoMultiClusterNamespace).List(context.TODO(), metav1.ListOptions{}) 668 if err != nil && errors.IsNotFound(err) { 669 return nil 670 } 671 if err != nil { 672 LogError(fmt.Sprintf("An error occurred while getting the VerrazzanoManagedClusters in namespace %s: %s\n", vzconstants.VerrazzanoMultiClusterNamespace, err.Error())) 673 return nil 674 } 675 if len(vmcConfigs.Items) > 0 { 676 LogMessage(fmt.Sprintf("VerrazzanoManagedClusters in namespace: %s ...\n", vzconstants.VerrazzanoMultiClusterNamespace)) 677 if err = createFile(vmcConfigs, vzconstants.VerrazzanoMultiClusterNamespace, constants.VmcJSON, captureDir, vzHelper); err != nil { 678 return err 679 } 680 } 681 return nil 682 } 683 684 // GetAppConfigScheme returns GroupVersionResource for ApplicationConfiguration 685 func GetAppConfigScheme() schema.GroupVersionResource { 686 return schema.GroupVersionResource{ 687 Group: oamcore.Group, 688 Version: oamcore.Version, 689 Resource: constants.OAMAppConfigurations, 690 } 691 } 692 693 // GetComponentConfigScheme returns GroupVersionResource for Component 694 func GetComponentConfigScheme() schema.GroupVersionResource { 695 return schema.GroupVersionResource{ 696 Group: oamcore.Group, 697 Version: oamcore.Version, 698 Resource: constants.OAMComponents, 699 } 700 } 701 702 // GetMetricsTraitConfigScheme returns GroupVersionResource for MetricsTrait 703 func GetMetricsTraitConfigScheme() schema.GroupVersionResource { 704 return schema.GroupVersionResource{ 705 Group: vzoamapi.SchemeGroupVersion.Group, 706 Version: vzoamapi.SchemeGroupVersion.Version, 707 Resource: constants.OAMMetricsTraits, 708 } 709 } 710 711 // GetIngressTraitConfigScheme returns GroupVersionResource for IngressTrait 712 func GetIngressTraitConfigScheme() schema.GroupVersionResource { 713 return schema.GroupVersionResource{ 714 Group: vzoamapi.SchemeGroupVersion.Group, 715 Version: vzoamapi.SchemeGroupVersion.Version, 716 Resource: constants.OAMIngressTraits, 717 } 718 } 719 720 // GetMCComponentScheme returns GroupVersionResource for MulticlusterComponent 721 func GetMCComponentScheme() schema.GroupVersionResource { 722 return schema.GroupVersionResource{ 723 Group: clustersv1alpha1.SchemeGroupVersion.Group, 724 Version: clustersv1alpha1.SchemeGroupVersion.Version, 725 Resource: constants.OAMMCCompConfigurations, 726 } 727 } 728 729 // GetMCAppConfigScheme returns GroupVersionResource for MulticlusterApplicationConfiguration 730 func GetMCAppConfigScheme() schema.GroupVersionResource { 731 return schema.GroupVersionResource{ 732 Group: clustersv1alpha1.SchemeGroupVersion.Group, 733 Version: clustersv1alpha1.SchemeGroupVersion.Version, 734 Resource: constants.OAMMCAppConfigurations, 735 } 736 } 737 738 // GetVzProjectsConfigScheme returns GroupVersionResource for VerrazzanoProject 739 func GetVzProjectsConfigScheme() schema.GroupVersionResource { 740 return schema.GroupVersionResource{ 741 Group: clustersv1alpha1.SchemeGroupVersion.Group, 742 Version: clustersv1alpha1.SchemeGroupVersion.Version, 743 Resource: constants.OAMProjects, 744 } 745 } 746 747 // GetManagedClusterConfigScheme returns GroupVersionResource for VerrazzanoManagedCluster 748 func GetManagedClusterConfigScheme() schema.GroupVersionResource { 749 return schema.GroupVersionResource{ 750 Group: clustersv1alpha1.SchemeGroupVersion.Group, 751 Version: clustersv1alpha1.SchemeGroupVersion.Version, 752 Resource: constants.OAMManagedClusters, 753 } 754 } 755 756 // LogError logs a message to the standard error 757 func LogError(msg string) { 758 isError = true 759 fmt.Fprintf(GetMultiWriterErr(), msg) 760 } 761 762 // IsErrorReported returns true when the command logs at least one error to the standard error 763 func IsErrorReported() bool { 764 return isError 765 } 766 767 // SetMultiWriterOut sets MultiWriter for standard output 768 func SetMultiWriterOut(outStream io.Writer, outFile *os.File) { 769 // When verbose output is disabled, log the resources captured to outFile alone 770 if isVerbose { 771 multiWriterOut = io.MultiWriter(outStream, outFile) 772 } else { 773 multiWriterOut = io.MultiWriter(outFile) 774 } 775 } 776 777 // GetMultiWriterOut returns the MultiWriter for standard output 778 func GetMultiWriterOut() io.Writer { 779 return multiWriterOut 780 } 781 782 // SetMultiWriterErr sets MultiWriter for standard error 783 func SetMultiWriterErr(errStream io.Writer, errFile *os.File) { 784 // When verbose output is disabled, log the error capturing resources to errFile alone 785 if isVerbose { 786 multiWriterErr = io.MultiWriter(errStream, errFile) 787 } else { 788 multiWriterErr = io.MultiWriter(errFile) 789 } 790 } 791 792 // GetMultiWriterErr returns the MultiWriter for standard error 793 func GetMultiWriterErr() io.Writer { 794 return multiWriterErr 795 } 796 797 // SetIsLiveCluster sets true to isLiveCluster, indicating the live cluster analysis usage 798 func SetIsLiveCluster() { 799 isLiveCluster = true 800 } 801 802 // GetIsLiveCluster returns a boolean indicating whether it is live cluster analysis 803 func GetIsLiveCluster() bool { 804 return isLiveCluster 805 } 806 807 // LogMessage logs a message to the standard output 808 func LogMessage(msg string) { 809 msgPrefix := constants.BugReportMsgPrefix 810 if isLiveCluster { 811 msgPrefix = constants.AnalysisMsgPrefix 812 } 813 fmt.Fprintf(GetMultiWriterOut(), msgPrefix+msg) 814 } 815 816 // SetVerboseOutput sets the verbose output for the commands bug-report and analyze 817 func SetVerboseOutput(enableVerbose bool) { 818 isVerbose = enableVerbose 819 } 820 821 // removePod removes given podName from PodList 822 func removePod(podList []corev1.Pod, podName string) []corev1.Pod { 823 returnList := make([]corev1.Pod, 0) 824 for index, pod := range podList { 825 if strings.Contains(pod.Name, podName) { 826 returnList = append(returnList, podList[:index]...) 827 return append(returnList, podList[index+1:]...) 828 } 829 } 830 return nil 831 } 832 833 // removePods removes pods from PodList 834 func removePods(podList []corev1.Pod, pods []string) []corev1.Pod { 835 for _, p := range pods { 836 podList = removePod(podList, p) 837 } 838 return podList 839 } 840 841 func isCaExpired(client clipkg.Client, cert v1.Certificate, namespace string) (*CaCrtInfo, bool, error) { 842 correspondingSecretName := cert.Spec.SecretName 843 secretForCertificate := &corev1.Secret{} 844 err := client.Get(context.Background(), clipkg.ObjectKey{ 845 Namespace: namespace, 846 Name: correspondingSecretName, 847 }, secretForCertificate) 848 if err != nil { 849 return nil, false, err 850 } 851 caCrtData, ok := secretForCertificate.Data["ca.crt"] 852 if !ok { 853 return nil, false, nil 854 } 855 caCrtDataPemDecoded, _ := pem.Decode(caCrtData) 856 if caCrtDataPemDecoded == nil { 857 return nil, false, fmt.Errorf("Failure to PEM Decode Certificate") 858 } 859 certificate, err := x509.ParseCertificate(caCrtDataPemDecoded.Bytes) 860 if err != nil { 861 return nil, false, err 862 } 863 caCrtInfoForCert := CaCrtInfo{Name: correspondingSecretName, Expired: false} 864 expirationDateOfCert := certificate.NotAfter 865 866 if time.Now().Unix() > expirationDateOfCert.Unix() { 867 caCrtInfoForCert.Expired = true 868 869 } 870 return &caCrtInfoForCert, true, nil 871 }