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  }