github.com/verrazzano/verrazzano@v1.7.0/tools/vz/pkg/helpers/vzcapture.go (about)

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