github.com/verrazzano/verrazzano@v1.7.1/tools/vz/pkg/helpers/vzcapture.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 "archive/tar" 8 "bufio" 9 "bytes" 10 "compress/gzip" 11 "context" 12 "crypto/x509" 13 "encoding/json" 14 "encoding/pem" 15 errors2 "errors" 16 "fmt" 17 "io" 18 "os" 19 "path/filepath" 20 "strings" 21 "sync" 22 "time" 23 24 v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" 25 oamcore "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" 26 clustersv1alpha1 "github.com/verrazzano/verrazzano/application-operator/apis/clusters/v1alpha1" 27 vzoamapi "github.com/verrazzano/verrazzano/application-operator/apis/oam/v1alpha1" 28 vzconstants "github.com/verrazzano/verrazzano/pkg/constants" 29 "github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1beta1" 30 "github.com/verrazzano/verrazzano/tools/vz/pkg/constants" 31 corev1 "k8s.io/api/core/v1" 32 "k8s.io/apimachinery/pkg/api/errors" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 35 "k8s.io/apimachinery/pkg/labels" 36 "k8s.io/apimachinery/pkg/runtime/schema" 37 "k8s.io/apimachinery/pkg/selection" 38 "k8s.io/client-go/dynamic" 39 "k8s.io/client-go/kubernetes" 40 clipkg "sigs.k8s.io/controller-runtime/pkg/client" 41 ) 42 43 const ( 44 failureToCreateDirectoryMessage = "an error occurred while creating the directory %s: %s" 45 ) 46 47 var errBugReport = "an error occurred while creating the bug report: %s" 48 var createFileError = "an error occurred while creating the file %s: %s" 49 var writeFileError = "an error occurred while writing the file %s: %s\n" 50 var isErrorMutex = &sync.Mutex{} 51 var controllerRuntimeClient = &sync.Mutex{} 52 53 var containerStartLog = "==== START logs for container %s of pod %s/%s ====\n" 54 var containerEndLog = "==== END logs for container %s of pod %s/%s ====\n" 55 56 var previousTerminatedError = "previous terminated container" 57 58 var isError bool 59 var isLiveCluster bool 60 var isVerbose bool 61 62 var multiWriterOut io.Writer 63 var multiWriterErr io.Writer 64 65 type CaCrtInfo struct { 66 Name string `json:"name"` 67 Expired bool `json:"expired"` 68 } 69 type Metadata struct { 70 Time string `json:"time"` 71 } 72 73 type ErrorsChannelLogs struct { 74 PodName string `json:"podName"` 75 ErrorMessage string `json:"errorMessage"` 76 } 77 78 type ErrorsChannel struct { 79 ErrorMessage string `json:"errorMessage"` 80 } 81 82 type Pods struct { 83 Namespace string 84 PodList []corev1.Pod 85 } 86 type PodLogs struct { 87 IsPodLog bool 88 IsPrevious bool 89 Duration int64 90 } 91 92 // CreateReportArchive creates the .tar.gz file specified by bugReportFile, from the files in captureDir 93 // If the addClusterSnapshot value is set to true, a value of "cluster-snapshot" prefixes every file path that is put into the archive 94 func CreateReportArchive(captureDir string, bugRepFile *os.File, addClusterSnapshot bool) error { 95 96 // Create new Writers for gzip and tar 97 gzipWriter := gzip.NewWriter(bugRepFile) 98 defer gzipWriter.Close() 99 100 tarWriter := tar.NewWriter(gzipWriter) 101 defer tarWriter.Close() 102 103 walkFn := func(path string, fileInfo os.FileInfo, err error) error { 104 if fileInfo.Mode().IsDir() { 105 return nil 106 } 107 var filePath string 108 // make cluster-snapshot as the root directory in the archive, to support existing analysis tool 109 if addClusterSnapshot { 110 filePath = constants.BugReportRoot + path[len(captureDir):] 111 } else { 112 filePath = path[len(captureDir):] 113 } 114 fileReader, err := os.Open(path) 115 if err != nil { 116 return fmt.Errorf(errBugReport, err.Error()) 117 } 118 defer fileReader.Close() 119 120 fih, err := tar.FileInfoHeader(fileInfo, filePath) 121 if err != nil { 122 return fmt.Errorf(errBugReport, err.Error()) 123 } 124 125 fih.Name = filePath 126 err = tarWriter.WriteHeader(fih) 127 if err != nil { 128 return fmt.Errorf(errBugReport, err.Error()) 129 } 130 _, err = io.Copy(tarWriter, fileReader) 131 if err != nil { 132 return fmt.Errorf(errBugReport, err.Error()) 133 } 134 return nil 135 } 136 137 if err := filepath.Walk(captureDir, walkFn); err != nil { 138 return err 139 } 140 return nil 141 } 142 143 // UntarArchive untars the specified file and puts it in the capture directory 144 func UntarArchive(captureDir string, tarFile *os.File) error { 145 var tarReader *tar.Reader 146 // If it is compressed, we need to decompress it 147 if strings.HasSuffix(tarFile.Name(), ".tgz") || strings.HasSuffix(tarFile.Name(), ".tar.gz") { 148 uncompressedTarFile, err := gzip.NewReader(tarFile) 149 if err != nil { 150 return err 151 } 152 defer uncompressedTarFile.Close() 153 tarReader = tar.NewReader(uncompressedTarFile) 154 } else if strings.HasSuffix(tarFile.Name(), ".tar") { 155 tarReader = tar.NewReader(tarFile) 156 } else { 157 return fmt.Errorf("the file given as input is not in .tar, .tgz, or .tar.gz format") 158 } 159 // This loops through each entry in the tar archive 160 err := writeFilesFromArchive(captureDir, tarReader) 161 return err 162 } 163 164 func copyDataInByteChunks(dst io.Writer, src io.Reader, chunkSize int64) error { 165 for { 166 _, err := io.CopyN(dst, src, chunkSize) 167 if err == io.EOF { 168 break 169 } else if err != nil { 170 return err 171 } 172 } 173 return nil 174 } 175 176 // This function loops through a tar reader and writes its contents to disk 177 func writeFilesFromArchive(captureDir string, tarReader *tar.Reader) error { 178 for { 179 header, err := tarReader.Next() 180 // This means that we have reached the end of the archive, so we break out of the loop 181 if err == io.EOF { 182 break 183 } 184 if err != nil { 185 return err 186 } 187 188 // This means that it is a regular file that we write to disk 189 if header.Typeflag == 48 { 190 if err = writeFileFromArchive(captureDir, tarReader, header); err != nil { 191 return err 192 } 193 194 } 195 // This means that is a directory that is written 196 if header.Typeflag == 53 { 197 err = os.Mkdir(captureDir+string(os.PathSeparator)+header.Name, os.FileMode(header.Mode)) 198 if err != nil { 199 return err 200 } 201 } 202 203 } 204 return nil 205 } 206 207 // writeTarFileToDisk writes a tar file at a specified captureDir using a tar Reader and a tar Header for that file 208 func writeFileFromArchive(captureDir string, tarReader *tar.Reader, header *tar.Header) error { 209 if err := createParentsIfNecessary(captureDir, header.Name); err != nil { 210 return err 211 } 212 fileToWrite, err := os.Create(captureDir + string(os.PathSeparator) + header.Name) 213 if err != nil { 214 return err 215 } 216 if err = fileToWrite.Chmod(os.FileMode(header.Mode)); err != nil { 217 return err 218 } 219 if err = copyDataInByteChunks(fileToWrite, tarReader, int64(2048)); err != nil { 220 return err 221 } 222 return nil 223 224 } 225 226 // createParentsIfNecessary determines if a path of a file references directories that have not been created and then creates those directories 227 func createParentsIfNecessary(captureDir string, filePath string) error { 228 filePathSplitByPathSeperatorList := strings.Split(filePath, string(os.PathSeparator)) 229 if len(filePathSplitByPathSeperatorList) == 1 { 230 return nil 231 } 232 listOfDirectories := filePathSplitByPathSeperatorList[:len(filePathSplitByPathSeperatorList)-1] 233 directoryString := "" 234 for i := range listOfDirectories { 235 directoryString = directoryString + listOfDirectories[i] + string(os.PathSeparator) 236 } 237 if _, err := os.Stat(captureDir + string(os.PathSeparator) + directoryString); errors2.Is(err, os.ErrNotExist) { 238 if err = os.MkdirAll(captureDir+string(os.PathSeparator)+directoryString, 0700); err != nil { 239 return err 240 } 241 } else if err != nil { 242 return err 243 } 244 return nil 245 } 246 247 // CaptureK8SResources collects the Workloads (Deployment and ReplicaSet, StatefulSet, Daemonset), pods, events, ingress 248 // services, and cert-manager certificates from the specified namespace, as JSON files 249 func CaptureK8SResources(client clipkg.Client, kubeClient kubernetes.Interface, dynamicClient dynamic.Interface, namespace, captureDir string, vzHelper VZHelper) error { 250 if err := captureWorkLoads(kubeClient, namespace, captureDir, vzHelper); err != nil { 251 return err 252 } 253 if err := capturePods(kubeClient, namespace, captureDir, vzHelper); err != nil { 254 return err 255 } 256 if err := captureEvents(kubeClient, namespace, captureDir, vzHelper); err != nil { 257 return err 258 } 259 if err := captureIngress(kubeClient, namespace, captureDir, vzHelper); err != nil { 260 return err 261 } 262 if err := captureServices(kubeClient, namespace, captureDir, vzHelper); err != nil { 263 return err 264 } 265 if err := captureCapiNamespacedResources(dynamicClient, namespace, captureDir, vzHelper); err != nil { 266 return err 267 } 268 if err := captureRancherNamespacedResources(dynamicClient, namespace, captureDir, vzHelper); err != nil { 269 return err 270 } 271 if err := captureCertificates(client, namespace, captureDir, vzHelper); err != nil { 272 return err 273 } 274 if err := captureNamespaces(kubeClient, namespace, captureDir, vzHelper); err != nil { 275 return err 276 } 277 if err := captureInnoDBClusterResources(client, namespace, captureDir, vzHelper); err != nil { 278 return err 279 } 280 return nil 281 } 282 283 // captureMetadata gets the current time in UTC on the user's system and outputs it in RFC 3339 format to the user's system 284 func CaptureMetadata(captureDir string) error { 285 timetoCaptureString := time.Now().UTC().Format(time.RFC3339) 286 metadataFilename := filepath.Join(captureDir, constants.MetadataJSON) 287 LogMessage("Capturing Time In RFC 3339 Format ...\n") 288 timeStructToWrite := Metadata{Time: timetoCaptureString} 289 metadataJSON, err := json.MarshalIndent(timeStructToWrite, constants.JSONPrefix, constants.JSONIndent) 290 if err != nil { 291 LogError(fmt.Sprintf("An error occurred while creating JSON encoding of %s: %s\n", metadataFilename, err.Error())) 292 return err 293 } 294 sanitizedDataInBytes := []byte(SanitizeString(string(metadataJSON), nil)) 295 err = os.WriteFile(metadataFilename, sanitizedDataInBytes, 0600) 296 if err != nil { 297 LogError(fmt.Sprintf(writeFileError, metadataFilename, err.Error())) 298 return err 299 } 300 return nil 301 302 } 303 304 // GetPodList returns list of pods matching the label in the given namespace 305 func GetPodList(client clipkg.Client, appLabel, appName, namespace string) ([]corev1.Pod, error) { 306 aLabel, _ := labels.NewRequirement(appLabel, selection.Equals, []string{appName}) 307 labelSelector := labels.NewSelector() 308 labelSelector = labelSelector.Add(*aLabel) 309 podList := corev1.PodList{} 310 err := client.List( 311 context.TODO(), 312 &podList, 313 &clipkg.ListOptions{ 314 Namespace: namespace, 315 LabelSelector: labelSelector, 316 }) 317 if err != nil { 318 return nil, fmt.Errorf("an error while listing pods: %s", err.Error()) 319 } 320 return podList.Items, nil 321 } 322 323 // GetPodListAll returns list of pods in the given namespace 324 // Will be used to fetch all pods in additional namespace 325 func GetPodListAll(client clipkg.Client, namespace string) ([]corev1.Pod, error) { 326 podList := corev1.PodList{} 327 err := client.List( 328 context.TODO(), 329 &podList, 330 &clipkg.ListOptions{ 331 Namespace: namespace, 332 }) 333 if err != nil { 334 return nil, fmt.Errorf("an error while listing pods: %s", err.Error()) 335 } 336 switch namespace { 337 case vzconstants.VerrazzanoInstallNamespace: 338 return removePod(podList.Items, constants.VerrazzanoPlatformOperator), nil 339 case vzconstants.VerrazzanoSystemNamespace: 340 return removePods(podList.Items, []string{constants.VerrazzanoApplicationOperator, constants.VerrazzanoMonitoringOperator}), nil 341 case vzconstants.CertManager: 342 return removePod(podList.Items, vzconstants.ExternalDNS), nil 343 } 344 return podList.Items, nil 345 } 346 347 // CaptureVZResource captures Verrazzano resources as a JSON file 348 func CaptureVZResource(captureDir string, vz *v1beta1.Verrazzano) error { 349 var vzRes = filepath.Join(captureDir, constants.VzResource) 350 f, err := os.OpenFile(vzRes, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) 351 if err != nil { 352 return fmt.Errorf(createFileError, vzRes, err.Error()) 353 } 354 defer f.Close() 355 356 LogMessage("Verrazzano resource ...\n") 357 vzJSON, err := json.MarshalIndent(vz, constants.JSONPrefix, constants.JSONIndent) 358 if err != nil { 359 LogError(fmt.Sprintf("An error occurred while creating JSON encoding of %s: %s\n", vzRes, err.Error())) 360 return err 361 } 362 _, err = f.WriteString(SanitizeString(string(vzJSON), nil)) 363 if err != nil { 364 LogError(fmt.Sprintf(writeFileError, vzRes, err.Error())) 365 return err 366 } 367 return nil 368 } 369 370 // captureEvents captures the events in the given namespace, as a JSON file 371 func captureEvents(kubeClient kubernetes.Interface, namespace, captureDir string, vzHelper VZHelper) error { 372 events, err := kubeClient.CoreV1().Events(namespace).List(context.TODO(), metav1.ListOptions{}) 373 if err != nil { 374 LogError(fmt.Sprintf("An error occurred while getting the Events in namespace %s: %s\n", namespace, err.Error())) 375 } 376 if len(events.Items) > 0 { 377 LogMessage(fmt.Sprintf("Events in namespace: %s ...\n", namespace)) 378 if err = createFile(events, namespace, constants.EventsJSON, captureDir, vzHelper); err != nil { 379 return err 380 } 381 } 382 return nil 383 } 384 385 // capturePods captures the pods in the given namespace, as a JSON file 386 func capturePods(kubeClient kubernetes.Interface, namespace, captureDir string, vzHelper VZHelper) error { 387 pods, err := kubeClient.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{}) 388 if err != nil { 389 LogError(fmt.Sprintf("An error occurred while getting the Pods in namespace %s: %s\n", namespace, err.Error())) 390 } 391 if len(pods.Items) > 0 { 392 LogMessage(fmt.Sprintf("Pods in namespace: %s ...\n", namespace)) 393 if err = createFile(pods, namespace, constants.PodsJSON, captureDir, vzHelper); err != nil { 394 return err 395 } 396 } 397 return nil 398 } 399 400 // captureNamespaces captures the namespace resource for a given namespace, as a JSON file 401 func captureNamespaces(kubeClient kubernetes.Interface, namespace, captureDir string, vzHelper VZHelper) error { 402 namespaceResource, err := kubeClient.CoreV1().Namespaces().Get(context.TODO(), namespace, metav1.GetOptions{}) 403 if err != nil { 404 LogError(fmt.Sprintf("An error occurred while getting the namespace resource in namespace %s: %s\n", namespace, err.Error())) 405 } 406 if err = createFile(namespaceResource, namespace, constants.NamespaceJSON, captureDir, vzHelper); err != nil { 407 return err 408 } 409 return nil 410 } 411 412 // captureIngress captures the ingresses in the given namespace, as a JSON file 413 func captureIngress(kubeClient kubernetes.Interface, namespace, captureDir string, vzHelper VZHelper) error { 414 ingressList, err := kubeClient.NetworkingV1().Ingresses(namespace).List(context.TODO(), metav1.ListOptions{}) 415 if err != nil { 416 LogError(fmt.Sprintf("An error occurred while getting the Ingress in namespace %s: %s\n", namespace, err.Error())) 417 } 418 if len(ingressList.Items) > 0 { 419 LogMessage(fmt.Sprintf("Ingresses in namespace: %s ...\n", namespace)) 420 if err = createFile(ingressList, namespace, constants.IngressJSON, captureDir, vzHelper); err != nil { 421 return err 422 } 423 } 424 return nil 425 } 426 427 // captureServices captures the services in the given namespace, as a JSON file 428 func captureServices(kubeClient kubernetes.Interface, namespace, captureDir string, vzHelper VZHelper) error { 429 serviceList, err := kubeClient.CoreV1().Services(namespace).List(context.TODO(), metav1.ListOptions{}) 430 if err != nil { 431 LogError(fmt.Sprintf("An error occurred while getting the Services in namespace %s: %s\n", namespace, err.Error())) 432 } 433 if len(serviceList.Items) > 0 { 434 LogMessage(fmt.Sprintf("Services in namespace: %s ...\n", namespace)) 435 if err = createFile(serviceList, namespace, constants.ServicesJSON, captureDir, vzHelper); err != nil { 436 return err 437 } 438 } 439 return nil 440 } 441 442 // captureWorkLoads captures the Deployment and ReplicaSet, StatefulSet, Daemonset in the given namespace 443 func captureWorkLoads(kubeClient kubernetes.Interface, namespace, captureDir string, vzHelper VZHelper) error { 444 deployments, err := kubeClient.AppsV1().Deployments(namespace).List(context.TODO(), metav1.ListOptions{}) 445 if err != nil { 446 LogError(fmt.Sprintf("An error occurred while getting the Deployments in namespace %s: %s\n", namespace, err.Error())) 447 } 448 if len(deployments.Items) > 0 { 449 LogMessage(fmt.Sprintf("Deployments in namespace: %s ...\n", namespace)) 450 if err = createFile(deployments, namespace, constants.DeploymentsJSON, captureDir, vzHelper); err != nil { 451 return err 452 } 453 } 454 455 replicaSets, err := kubeClient.AppsV1().ReplicaSets(namespace).List(context.TODO(), metav1.ListOptions{}) 456 if err != nil { 457 LogError(fmt.Sprintf("An error occurred while getting the ReplicaSets in namespace %s: %s\n", namespace, err.Error())) 458 } 459 if len(replicaSets.Items) > 0 { 460 LogMessage(fmt.Sprintf("Replicasets in namespace: %s ...\n", namespace)) 461 if err = createFile(replicaSets, namespace, constants.ReplicaSetsJSON, captureDir, vzHelper); err != nil { 462 return err 463 } 464 } 465 466 daemonSets, err := kubeClient.AppsV1().DaemonSets(namespace).List(context.TODO(), metav1.ListOptions{}) 467 if err != nil { 468 LogError(fmt.Sprintf("An error occurred while getting the DaemonSets in namespace %s: %s\n", namespace, err.Error())) 469 } 470 if len(daemonSets.Items) > 0 { 471 LogMessage(fmt.Sprintf("DaemonSets in namespace: %s ...\n", namespace)) 472 if err = createFile(daemonSets, namespace, constants.DaemonSetsJSON, captureDir, vzHelper); err != nil { 473 return err 474 } 475 } 476 477 statefulSets, err := kubeClient.AppsV1().StatefulSets(namespace).List(context.TODO(), metav1.ListOptions{}) 478 if err != nil { 479 LogError(fmt.Sprintf("An error occurred while getting the StatefulSets in namespace %s: %s\n", namespace, err.Error())) 480 } 481 if len(statefulSets.Items) > 0 { 482 LogMessage(fmt.Sprintf("StatefulSets in namespace: %s ...\n", namespace)) 483 if err = createFile(statefulSets, namespace, constants.StatefulSetsJSON, captureDir, vzHelper); err != nil { 484 return err 485 } 486 } 487 return nil 488 } 489 490 // captureInnoDBClusterResources finds the InnoDBCluster resource from the client for the current namespace, returns an error, and outputs the objects to a inno_db_cluster.json file, if InnoDBCluster resources are present in that namespace. 491 func captureInnoDBClusterResources(client clipkg.Client, namespace, captureDir string, vzHelper VZHelper) error { 492 innoDBClusterList := unstructured.UnstructuredList{} 493 innoDBClusterGVK := schema.GroupVersionKind{ 494 Group: "mysql.oracle.com", 495 Version: "v2", 496 Kind: "InnoDBClusterList", 497 } 498 innoDBClusterList.SetGroupVersionKind(innoDBClusterGVK) 499 controllerRuntimeClient.Lock() 500 err := client.List(context.TODO(), &innoDBClusterList, &clipkg.ListOptions{Namespace: namespace}) 501 controllerRuntimeClient.Unlock() 502 if err != nil { 503 LogError(fmt.Sprintf("An error occurred while getting the InnoDBCluster resource in namespace %s: %s\n", namespace, err.Error())) 504 } 505 if len(innoDBClusterList.Items) > 0 { 506 LogMessage(fmt.Sprintf("InnoDBCluster resources in namespace: %s ...\n", namespace)) 507 if err = createFileFromUnstructuredList(innoDBClusterList, namespace, constants.InnoDBClusterJSON, captureDir, vzHelper); err != nil { 508 return err 509 } 510 } 511 return nil 512 } 513 514 // 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. 515 func captureCertificates(client clipkg.Client, namespace, captureDir string, vzHelper VZHelper) error { 516 certificateList := v1.CertificateList{} 517 controllerRuntimeClient.Lock() 518 err := client.List(context.TODO(), &certificateList, &clipkg.ListOptions{Namespace: namespace}) 519 controllerRuntimeClient.Unlock() 520 if err != nil { 521 LogError(fmt.Sprintf("An error occurred while getting the Certificates in namespace %s: %s\n", namespace, err.Error())) 522 } 523 collectHostNames(certificateList) 524 if len(certificateList.Items) > 0 { 525 LogMessage(fmt.Sprintf("Certificates in namespace: %s ...\n", namespace)) 526 if err = createFile(certificateList, namespace, constants.CertificatesJSON, captureDir, vzHelper); err != nil { 527 return err 528 } 529 captureCaCrtExpirationInfo(client, certificateList, namespace, captureDir, vzHelper) 530 } 531 return nil 532 } 533 534 // 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 535 func captureCaCrtExpirationInfo(client clipkg.Client, certificateList v1.CertificateList, namespace string, captureDir string, vzHelper VZHelper) error { 536 caCrtList := []CaCrtInfo{} 537 for _, cert := range certificateList.Items { 538 caCrtInfoForCert, isFound, err := isCaExpired(client, cert, namespace) 539 540 if err != nil { 541 return err 542 } 543 if isFound { 544 caCrtList = append(caCrtList, *caCrtInfoForCert) 545 } 546 547 } 548 if len(caCrtList) > 0 { 549 LogMessage(fmt.Sprintf("ca.crts in namespace: %s ...\n", namespace)) 550 if err := createFile(caCrtList, namespace, "caCrtInfo.json", captureDir, vzHelper); err != nil { 551 return err 552 } 553 554 } 555 return nil 556 } 557 558 func collectHostNames(certificateList v1.CertificateList) { 559 for _, cert := range certificateList.Items { 560 for _, hostname := range cert.Spec.DNSNames { 561 putIntoHostNamesIfNotPresent(hostname) 562 } 563 } 564 for _, cert := range certificateList.Items { 565 for _, ipAddress := range cert.Spec.IPAddresses { 566 putIntoHostNamesIfNotPresent(ipAddress) 567 } 568 } 569 } 570 571 func putIntoHostNamesIfNotPresent(inputKey string) { 572 knownHostNamesMutex.Lock() 573 keyInMap := KnownHostNames[inputKey] 574 if !keyInMap { 575 KnownHostNames[inputKey] = true 576 } 577 knownHostNamesMutex.Unlock() 578 } 579 580 // CapturePodLog captures the log from the pod in the captureDir 581 func CapturePodLog(kubeClient kubernetes.Interface, pod corev1.Pod, namespace, captureDir string, vzHelper VZHelper, duration int64, previous bool) error { 582 podName := pod.Name 583 if len(podName) == 0 { 584 return nil 585 } 586 587 // Create directory for the namespace and the pod, under the root level directory containing the bug report 588 var folderPath = filepath.Join(captureDir, namespace, podName) 589 err := os.MkdirAll(folderPath, os.ModePerm) 590 if err != nil { 591 return fmt.Errorf(failureToCreateDirectoryMessage, folderPath, err.Error()) 592 } 593 594 // Capture logs for both init containers and containers 595 var cs []corev1.Container 596 var podLogOptions corev1.PodLogOptions 597 if duration != 0 { 598 podLogOptions.SinceSeconds = &duration 599 } 600 var logPath string 601 if !previous { 602 logPath = filepath.Join(folderPath, constants.LogFile) 603 } else { 604 logPath = filepath.Join(folderPath, constants.PreviousLogFile) 605 podLogOptions.Previous = true 606 } 607 cs = append(cs, pod.Spec.InitContainers...) 608 cs = append(cs, pod.Spec.Containers...) 609 610 // Create logs.txt or previous-logs.txt under the directory for the namespace 611 f, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) 612 if err != nil { 613 return fmt.Errorf(createFileError, logPath, err.Error()) 614 } 615 defer f.Close() 616 617 // Write the log from all the containers to a single file, with lines differentiating the logs from each of the containers 618 for _, c := range cs { 619 writeToFile := func(contName string) error { 620 podLogOptions.Container = contName 621 podLogOptions.InsecureSkipTLSVerifyBackend = true 622 podLog, err := kubeClient.CoreV1().Pods(namespace).GetLogs(podName, &podLogOptions).Stream(context.TODO()) 623 if err != nil { 624 if strings.Contains(err.Error(), previousTerminatedError) { 625 return nil 626 } 627 LogError(fmt.Sprintf("An error occurred while reading the logs from pod %s: %s\n", podName, err.Error())) 628 return nil 629 } 630 defer podLog.Close() 631 632 reader := bufio.NewScanner(podLog) 633 f.WriteString(fmt.Sprintf(containerStartLog, contName, namespace, podName)) 634 for reader.Scan() { 635 f.WriteString(SanitizeString(reader.Text()+"\n", nil)) 636 } 637 f.WriteString(fmt.Sprintf(containerEndLog, contName, namespace, podName)) 638 return nil 639 } 640 writeToFile(c.Name) 641 } 642 return nil 643 } 644 645 // createFile creates file from a workload, as a JSON file 646 func createFile(v interface{}, namespace, resourceFile, captureDir string, vzHelper VZHelper) error { 647 var folderPath = filepath.Join(captureDir, namespace) 648 649 if _, err := os.Stat(folderPath); os.IsNotExist(err) { 650 err := os.MkdirAll(folderPath, os.ModePerm) 651 if err != nil { 652 return fmt.Errorf(failureToCreateDirectoryMessage, folderPath, err.Error()) 653 } 654 } 655 656 var res = filepath.Join(folderPath, resourceFile) 657 f, err := os.OpenFile(res, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) 658 if err != nil { 659 return fmt.Errorf(createFileError, res, err.Error()) 660 } 661 defer f.Close() 662 663 resJSON, _ := json.MarshalIndent(v, constants.JSONPrefix, constants.JSONIndent) 664 _, err = f.WriteString(SanitizeString(string(resJSON), nil)) 665 if err != nil { 666 LogError(fmt.Sprintf(writeFileError, res, err.Error())) 667 } 668 return nil 669 } 670 671 // createFileFromUnstructured creates a file from an unstructured list 672 func createFileFromUnstructuredList(v unstructured.UnstructuredList, namespace, resourceFile, captureDir string, vzHelper VZHelper) error { 673 var folderPath = filepath.Join(captureDir, namespace) 674 675 if _, err := os.Stat(folderPath); os.IsNotExist(err) { 676 err := os.MkdirAll(folderPath, os.ModePerm) 677 if err != nil { 678 return fmt.Errorf(failureToCreateDirectoryMessage, folderPath, err.Error()) 679 } 680 } 681 682 var res = filepath.Join(folderPath, resourceFile) 683 f, err := os.OpenFile(res, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) 684 if err != nil { 685 return fmt.Errorf(createFileError, res, err.Error()) 686 } 687 defer f.Close() 688 689 listJSON, err := v.MarshalJSON() 690 var prettyJSON bytes.Buffer 691 json.Indent(&prettyJSON, listJSON, "", " ") 692 _, err = f.WriteString(SanitizeString(prettyJSON.String(), nil)) 693 if err != nil { 694 LogError(fmt.Sprintf(writeFileError, res, err.Error())) 695 } 696 return nil 697 } 698 699 // CaptureOAMResources captures OAM resources in the given list of namespaces 700 func CaptureOAMResources(dynamicClient dynamic.Interface, nsList []string, captureDir string, vzHelper VZHelper) error { 701 for _, ns := range nsList { 702 if err := captureAppConfigurations(dynamicClient, ns, captureDir, vzHelper); err != nil { 703 return err 704 } 705 if err := captureComponents(dynamicClient, ns, captureDir, vzHelper); err != nil { 706 return err 707 } 708 if err := captureIngressTraits(dynamicClient, ns, captureDir, vzHelper); err != nil { 709 return err 710 } 711 if err := captureMetricsTraits(dynamicClient, ns, captureDir, vzHelper); err != nil { 712 return err 713 } 714 } 715 return nil 716 } 717 718 // CaptureMultiClusterOAMResources captures OAM resources in multi-cluster environment 719 func CaptureMultiClusterOAMResources(dynamicClient dynamic.Interface, nsList []string, captureDir string, vzHelper VZHelper) error { 720 for _, ns := range nsList { 721 // Capture multi-cluster components and application configurations 722 if err := captureMCComponents(dynamicClient, ns, captureDir, vzHelper); err != nil { 723 return err 724 } 725 726 if err := captureMCAppConfigurations(dynamicClient, ns, captureDir, vzHelper); err != nil { 727 return err 728 } 729 } 730 return nil 731 } 732 733 // DoesNamespaceExist checks whether the namespace exists in the cluster 734 func DoesNamespaceExist(kubeClient kubernetes.Interface, namespace string, vzHelper VZHelper) (bool, error) { 735 if namespace == "" { 736 fmt.Fprintf(vzHelper.GetErrorStream(), "Ignoring empty namespace\n") 737 return false, nil 738 } 739 ns, err := kubeClient.CoreV1().Namespaces().Get(context.TODO(), namespace, metav1.GetOptions{}) 740 741 if err != nil && errors.IsNotFound(err) { 742 fmt.Fprintf(GetMultiWriterOut(), "Namespace %s not found in the cluster, so will be ignored.\n", namespace) 743 return false, err 744 } 745 if err != nil { 746 LogError(fmt.Sprintf("An error occurred while getting the namespace %s: %s\n", namespace, err.Error())) 747 return false, err 748 } 749 return ns != nil && len(ns.Name) > 0, nil 750 } 751 752 // GetVZManagedNamespaces returns the namespaces with label verrazzano-managed=true 753 func GetVZManagedNamespaces(kubeClient kubernetes.Interface) []string { 754 var appNS []string 755 nsList, err := kubeClient.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{LabelSelector: constants.VerrazzanoManagedLabel}) 756 if err != nil { 757 LogError(fmt.Sprintf("An error occurred while listing the namespaces with label verrazzano-managed=true: %s\n", err.Error())) 758 return appNS 759 } 760 761 for _, ns := range nsList.Items { 762 appNS = append(appNS, ns.Name) 763 } 764 return appNS 765 } 766 767 // RemoveDuplicate removes duplicates from origSlice 768 func RemoveDuplicate(origSlice []string) []string { 769 allKeys := make(map[string]bool) 770 var returnSlice []string 771 for _, item := range origSlice { 772 if _, value := allKeys[item]; !value { 773 allKeys[item] = true 774 returnSlice = append(returnSlice, item) 775 } 776 } 777 return returnSlice 778 } 779 780 // captureAppConfigurations captures the OAM application configurations in the given namespace, as a JSON file 781 func captureAppConfigurations(dynamicClient dynamic.Interface, namespace, captureDir string, vzHelper VZHelper) error { 782 appConfigs, err := dynamicClient.Resource(GetAppConfigScheme()).Namespace(namespace).List(context.TODO(), metav1.ListOptions{}) 783 if err != nil && errors.IsNotFound(err) { 784 return nil 785 } 786 if err != nil { 787 LogError(fmt.Sprintf("An error occurred while getting the ApplicationConfigurations in namespace %s: %s\n", namespace, err.Error())) 788 return nil 789 } 790 if len(appConfigs.Items) > 0 { 791 LogMessage(fmt.Sprintf("ApplicationConfigurations in namespace: %s ...\n", namespace)) 792 if err = createFile(appConfigs, namespace, constants.AppConfigJSON, captureDir, vzHelper); err != nil { 793 return err 794 } 795 } 796 return nil 797 } 798 799 // captureComponents captures the OAM components in the given namespace, as a JSON file 800 func captureComponents(dynamicClient dynamic.Interface, namespace, captureDir string, vzHelper VZHelper) error { 801 comps, err := dynamicClient.Resource(GetComponentConfigScheme()).Namespace(namespace).List(context.TODO(), metav1.ListOptions{}) 802 if err != nil && errors.IsNotFound(err) { 803 return nil 804 } 805 if err != nil { 806 LogError(fmt.Sprintf("An error occurred while getting the Components in namespace %s: %s\n", namespace, err.Error())) 807 return nil 808 } 809 if len(comps.Items) > 0 { 810 LogMessage(fmt.Sprintf("Components in namespace: %s ...\n", namespace)) 811 if err = createFile(comps, namespace, constants.ComponentJSON, captureDir, vzHelper); err != nil { 812 return err 813 } 814 } 815 return nil 816 } 817 818 // captureIngressTraits captures the ingress traits in the given namespace, as a JSON file 819 func captureIngressTraits(dynamicClient dynamic.Interface, namespace, captureDir string, vzHelper VZHelper) error { 820 ingTraits, err := dynamicClient.Resource(GetIngressTraitConfigScheme()).Namespace(namespace).List(context.TODO(), metav1.ListOptions{}) 821 if err != nil && errors.IsNotFound(err) { 822 return nil 823 } 824 if err != nil { 825 LogError(fmt.Sprintf("An error occurred while getting the IngressTraits in namespace %s: %s\n", namespace, err.Error())) 826 return nil 827 } 828 if len(ingTraits.Items) > 0 { 829 LogMessage(fmt.Sprintf("IngressTraits in namespace: %s ...\n", namespace)) 830 if err = createFile(ingTraits, namespace, constants.IngressTraitJSON, captureDir, vzHelper); err != nil { 831 return err 832 } 833 } 834 return nil 835 } 836 837 // captureMetricsTraits captures the metrics traits in the given namespace, as a JSON file 838 func captureMetricsTraits(dynamicClient dynamic.Interface, namespace, captureDir string, vzHelper VZHelper) error { 839 metricsTraits, err := dynamicClient.Resource(GetMetricsTraitConfigScheme()).Namespace(namespace).List(context.TODO(), metav1.ListOptions{}) 840 if err != nil && errors.IsNotFound(err) { 841 return nil 842 } 843 if err != nil { 844 LogError(fmt.Sprintf("An error occurred while getting the MetricsTraits in namespace %s: %s\n", namespace, err.Error())) 845 return nil 846 } 847 if len(metricsTraits.Items) > 0 { 848 LogMessage(fmt.Sprintf("MetricsTraits in namespace: %s ...\n", namespace)) 849 if err = createFile(metricsTraits, namespace, constants.MetricsTraitJSON, captureDir, vzHelper); err != nil { 850 return err 851 } 852 } 853 return nil 854 } 855 856 // captureMCComponents captures the MulticlusterComponent in the given namespace, as a JSON file 857 func captureMCComponents(dynamicClient dynamic.Interface, namespace, captureDir string, vzHelper VZHelper) error { 858 mcComps, err := dynamicClient.Resource(GetMCComponentScheme()).Namespace(namespace).List(context.TODO(), metav1.ListOptions{}) 859 if err != nil && errors.IsNotFound(err) { 860 return nil 861 } 862 if err != nil { 863 LogError(fmt.Sprintf("An error occurred while getting the MulticlusterComponent in namespace %s: %s\n", namespace, err.Error())) 864 return nil 865 } 866 if len(mcComps.Items) > 0 { 867 LogMessage(fmt.Sprintf("MulticlusterComponent in namespace: %s ...\n", namespace)) 868 if err = createFile(mcComps, namespace, constants.McComponentJSON, captureDir, vzHelper); err != nil { 869 return err 870 } 871 } 872 return nil 873 } 874 875 // captureMCComponents captures the MultiClusterApplicationConfiguration in the given namespace, as a JSON file 876 func captureMCAppConfigurations(dynamicClient dynamic.Interface, namespace, captureDir string, vzHelper VZHelper) error { 877 mcAppConfigs, err := dynamicClient.Resource(GetMCAppConfigScheme()).Namespace(namespace).List(context.TODO(), metav1.ListOptions{}) 878 if err != nil && errors.IsNotFound(err) { 879 return nil 880 } 881 if err != nil { 882 LogError(fmt.Sprintf("An error occurred while getting the MultiClusterApplicationConfiguration in namespace %s: %s\n", namespace, err.Error())) 883 return nil 884 } 885 if len(mcAppConfigs.Items) > 0 { 886 LogMessage(fmt.Sprintf("MultiClusterApplicationConfiguration in namespace: %s ...\n", namespace)) 887 if err = createFile(mcAppConfigs, namespace, constants.McAppConfigJSON, captureDir, vzHelper); err != nil { 888 return err 889 } 890 } 891 return nil 892 } 893 894 // CaptureLogs collects the logs from platform operator, application operator and monitoring operator in parallel 895 func CaptureLogs(wg *sync.WaitGroup, ec chan ErrorsChannelLogs, kubeClient kubernetes.Interface, pod Pods, bugReportDir string, vzHelper VZHelper, podLog PodLogs) { 896 defer wg.Done() 897 if len(pod.PodList) == 0 { 898 return 899 } 900 // This won't work when there are more than one pods for the same app label 901 LogMessage(fmt.Sprintf("log from pod %s in %s namespace ...\n", pod.PodList[0].Name, pod.Namespace)) 902 err := CapturePodLog(kubeClient, pod.PodList[0], pod.Namespace, bugReportDir, vzHelper, podLog.Duration, podLog.IsPrevious) 903 if err != nil { 904 ec <- ErrorsChannelLogs{PodName: pod.PodList[0].Name, ErrorMessage: err.Error()} 905 } 906 907 } 908 909 // CaptureVerrazzanoProjects captures the Verrazzano projects in the verrazzano-mc namespace, as a JSON file 910 func CaptureVerrazzanoProjects(dynamicClient dynamic.Interface, captureDir string, vzHelper VZHelper) error { 911 vzProjectConfigs, err := dynamicClient.Resource(GetVzProjectsConfigScheme()).Namespace(vzconstants.VerrazzanoMultiClusterNamespace).List(context.TODO(), metav1.ListOptions{}) 912 if err != nil && errors.IsNotFound(err) { 913 return nil 914 } 915 if err != nil { 916 LogError(fmt.Sprintf("An error occurred while getting the VerrazzanoProjects in namespace %s: %s\n", vzconstants.VerrazzanoMultiClusterNamespace, err.Error())) 917 return nil 918 } 919 if len(vzProjectConfigs.Items) > 0 { 920 LogMessage(fmt.Sprintf("VerrazzanoProjects in namespace: %s ...\n", vzconstants.VerrazzanoMultiClusterNamespace)) 921 if err = createFile(vzProjectConfigs, vzconstants.VerrazzanoMultiClusterNamespace, constants.VzProjectsJSON, captureDir, vzHelper); err != nil { 922 return err 923 } 924 } 925 return nil 926 } 927 928 // CaptureVerrazzanoManagedCluster captures VerrazzanoManagedCluster in verrazzano-mc namespace, as a JSON file 929 func CaptureVerrazzanoManagedCluster(dynamicClient dynamic.Interface, captureDir string, vzHelper VZHelper) error { 930 vmcConfigs, err := dynamicClient.Resource(GetManagedClusterConfigScheme()).Namespace(vzconstants.VerrazzanoMultiClusterNamespace).List(context.TODO(), metav1.ListOptions{}) 931 if err != nil && errors.IsNotFound(err) { 932 return nil 933 } 934 if err != nil { 935 LogError(fmt.Sprintf("An error occurred while getting the VerrazzanoManagedClusters in namespace %s: %s\n", vzconstants.VerrazzanoMultiClusterNamespace, err.Error())) 936 return nil 937 } 938 if len(vmcConfigs.Items) > 0 { 939 LogMessage(fmt.Sprintf("VerrazzanoManagedClusters in namespace: %s ...\n", vzconstants.VerrazzanoMultiClusterNamespace)) 940 if err = createFile(vmcConfigs, vzconstants.VerrazzanoMultiClusterNamespace, constants.VmcJSON, captureDir, vzHelper); err != nil { 941 return err 942 } 943 } 944 return nil 945 } 946 947 // GetAppConfigScheme returns GroupVersionResource for ApplicationConfiguration 948 func GetAppConfigScheme() schema.GroupVersionResource { 949 return schema.GroupVersionResource{ 950 Group: oamcore.Group, 951 Version: oamcore.Version, 952 Resource: constants.OAMAppConfigurations, 953 } 954 } 955 956 // GetComponentConfigScheme returns GroupVersionResource for Component 957 func GetComponentConfigScheme() schema.GroupVersionResource { 958 return schema.GroupVersionResource{ 959 Group: oamcore.Group, 960 Version: oamcore.Version, 961 Resource: constants.OAMComponents, 962 } 963 } 964 965 // GetMetricsTraitConfigScheme returns GroupVersionResource for MetricsTrait 966 func GetMetricsTraitConfigScheme() schema.GroupVersionResource { 967 return schema.GroupVersionResource{ 968 Group: vzoamapi.SchemeGroupVersion.Group, 969 Version: vzoamapi.SchemeGroupVersion.Version, 970 Resource: constants.OAMMetricsTraits, 971 } 972 } 973 974 // GetIngressTraitConfigScheme returns GroupVersionResource for IngressTrait 975 func GetIngressTraitConfigScheme() schema.GroupVersionResource { 976 return schema.GroupVersionResource{ 977 Group: vzoamapi.SchemeGroupVersion.Group, 978 Version: vzoamapi.SchemeGroupVersion.Version, 979 Resource: constants.OAMIngressTraits, 980 } 981 } 982 983 // GetMCComponentScheme returns GroupVersionResource for MulticlusterComponent 984 func GetMCComponentScheme() schema.GroupVersionResource { 985 return schema.GroupVersionResource{ 986 Group: clustersv1alpha1.SchemeGroupVersion.Group, 987 Version: clustersv1alpha1.SchemeGroupVersion.Version, 988 Resource: constants.OAMMCCompConfigurations, 989 } 990 } 991 992 // GetMCAppConfigScheme returns GroupVersionResource for MulticlusterApplicationConfiguration 993 func GetMCAppConfigScheme() schema.GroupVersionResource { 994 return schema.GroupVersionResource{ 995 Group: clustersv1alpha1.SchemeGroupVersion.Group, 996 Version: clustersv1alpha1.SchemeGroupVersion.Version, 997 Resource: constants.OAMMCAppConfigurations, 998 } 999 } 1000 1001 // GetVzProjectsConfigScheme returns GroupVersionResource for VerrazzanoProject 1002 func GetVzProjectsConfigScheme() schema.GroupVersionResource { 1003 return schema.GroupVersionResource{ 1004 Group: clustersv1alpha1.SchemeGroupVersion.Group, 1005 Version: clustersv1alpha1.SchemeGroupVersion.Version, 1006 Resource: constants.OAMProjects, 1007 } 1008 } 1009 1010 // GetManagedClusterConfigScheme returns GroupVersionResource for VerrazzanoManagedCluster 1011 func GetManagedClusterConfigScheme() schema.GroupVersionResource { 1012 return schema.GroupVersionResource{ 1013 Group: clustersv1alpha1.SchemeGroupVersion.Group, 1014 Version: clustersv1alpha1.SchemeGroupVersion.Version, 1015 Resource: constants.OAMManagedClusters, 1016 } 1017 } 1018 1019 // LogError logs a message to the standard error 1020 func LogError(msg string) { 1021 isErrorMutex.Lock() 1022 isError = true 1023 fmt.Fprintf(GetMultiWriterErr(), msg) 1024 isErrorMutex.Unlock() 1025 } 1026 1027 // IsErrorReported returns true when the command logs at least one error to the standard error 1028 func IsErrorReported() bool { 1029 return isError 1030 } 1031 1032 // SetMultiWriterOut sets MultiWriter for standard output 1033 func SetMultiWriterOut(outStream io.Writer, outFile *os.File) { 1034 // When verbose output is disabled, log the resources captured to outFile alone 1035 if isVerbose { 1036 multiWriterOut = io.MultiWriter(outStream, outFile) 1037 } else { 1038 multiWriterOut = io.MultiWriter(outFile) 1039 } 1040 } 1041 1042 // GetMultiWriterOut returns the MultiWriter for standard output 1043 func GetMultiWriterOut() io.Writer { 1044 return multiWriterOut 1045 } 1046 1047 // SetMultiWriterErr sets MultiWriter for standard error 1048 func SetMultiWriterErr(errStream io.Writer, errFile *os.File) { 1049 // When verbose output is disabled, log the error capturing resources to errFile alone 1050 if isVerbose { 1051 multiWriterErr = io.MultiWriter(errStream, errFile) 1052 } else { 1053 multiWriterErr = io.MultiWriter(errFile) 1054 } 1055 } 1056 1057 // GetMultiWriterErr returns the MultiWriter for standard error 1058 func GetMultiWriterErr() io.Writer { 1059 return multiWriterErr 1060 } 1061 1062 // SetIsLiveCluster sets true to isLiveCluster, indicating the live cluster analysis usage 1063 func SetIsLiveCluster() { 1064 isLiveCluster = true 1065 } 1066 1067 // GetIsLiveCluster returns a boolean indicating whether it is live cluster analysis 1068 func GetIsLiveCluster() bool { 1069 return isLiveCluster 1070 } 1071 1072 // LogMessage logs a message to the standard output 1073 func LogMessage(msg string) { 1074 msgPrefix := constants.BugReportMsgPrefix 1075 if isLiveCluster { 1076 msgPrefix = constants.AnalysisMsgPrefix 1077 } 1078 fmt.Fprintf(GetMultiWriterOut(), msgPrefix+msg) 1079 } 1080 1081 // SetVerboseOutput sets the verbose output for the commands bug-report and analyze 1082 func SetVerboseOutput(enableVerbose bool) { 1083 isVerbose = enableVerbose 1084 } 1085 1086 // removePod removes given podName from PodList 1087 func removePod(podList []corev1.Pod, podName string) []corev1.Pod { 1088 returnList := make([]corev1.Pod, 0) 1089 for index, pod := range podList { 1090 if strings.Contains(pod.Name, podName) { 1091 returnList = append(returnList, podList[:index]...) 1092 return append(returnList, podList[index+1:]...) 1093 } 1094 } 1095 return nil 1096 } 1097 1098 // removePods removes pods from PodList 1099 func removePods(podList []corev1.Pod, pods []string) []corev1.Pod { 1100 for _, p := range pods { 1101 podList = removePod(podList, p) 1102 } 1103 return podList 1104 } 1105 1106 func isCaExpired(client clipkg.Client, cert v1.Certificate, namespace string) (*CaCrtInfo, bool, error) { 1107 correspondingSecretName := cert.Spec.SecretName 1108 secretForCertificate := &corev1.Secret{} 1109 err := client.Get(context.Background(), clipkg.ObjectKey{ 1110 Namespace: namespace, 1111 Name: correspondingSecretName, 1112 }, secretForCertificate) 1113 if err != nil { 1114 return nil, false, err 1115 } 1116 caCrtData, ok := secretForCertificate.Data["ca.crt"] 1117 if !ok { 1118 return nil, false, nil 1119 } 1120 caCrtDataPemDecoded, _ := pem.Decode(caCrtData) 1121 if caCrtDataPemDecoded == nil { 1122 return nil, false, fmt.Errorf("Failure to PEM Decode Certificate") 1123 } 1124 certificate, err := x509.ParseCertificate(caCrtDataPemDecoded.Bytes) 1125 if err != nil { 1126 return nil, false, err 1127 } 1128 caCrtInfoForCert := CaCrtInfo{Name: correspondingSecretName, Expired: false} 1129 expirationDateOfCert := certificate.NotAfter 1130 1131 if time.Now().Unix() > expirationDateOfCert.Unix() { 1132 caCrtInfoForCert.Expired = true 1133 1134 } 1135 return &caCrtInfoForCert, true, nil 1136 }