github.com/verrazzano/verrazzano@v1.7.1/tools/vz/pkg/helpers/vzhelper.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  	"context"
     8  	"fmt"
     9  	"io"
    10  	"net/http"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  
    15  	certv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
    16  	oam "github.com/crossplane/oam-kubernetes-runtime/apis/core"
    17  	"github.com/spf13/cobra"
    18  	"github.com/verrazzano/verrazzano/pkg/k8sutil"
    19  	"github.com/verrazzano/verrazzano/pkg/semver"
    20  	v1alpha1 "github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1alpha1"
    21  	"github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1beta1"
    22  	vpoconstants "github.com/verrazzano/verrazzano/platform-operator/constants"
    23  	"github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/component/registry"
    24  	vzconstants "github.com/verrazzano/verrazzano/tools/vz/pkg/constants"
    25  	"github.com/verrazzano/verrazzano/tools/vz/pkg/github"
    26  	istioclient "istio.io/client-go/pkg/apis/networking/v1alpha3"
    27  	adminv1 "k8s.io/api/admissionregistration/v1"
    28  	appv1 "k8s.io/api/apps/v1"
    29  	batchv1 "k8s.io/api/batch/v1"
    30  	corev1 "k8s.io/api/core/v1"
    31  	networkingv1 "k8s.io/api/networking/v1"
    32  	rbacv1 "k8s.io/api/rbac/v1"
    33  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    34  	"k8s.io/apimachinery/pkg/api/meta"
    35  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    36  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    37  	"k8s.io/apimachinery/pkg/runtime"
    38  	"k8s.io/apimachinery/pkg/runtime/schema"
    39  	"k8s.io/apimachinery/pkg/types"
    40  	"k8s.io/client-go/discovery"
    41  	"k8s.io/client-go/dynamic"
    42  	"k8s.io/client-go/kubernetes"
    43  	"sigs.k8s.io/controller-runtime/pkg/client"
    44  	"sigs.k8s.io/yaml"
    45  )
    46  
    47  type VZHelper interface {
    48  	GetOutputStream() io.Writer
    49  	GetErrorStream() io.Writer
    50  	GetInputStream() io.Reader
    51  	GetClient(cmd *cobra.Command) (client.Client, error)
    52  	GetKubeClient(cmd *cobra.Command) (kubernetes.Interface, error)
    53  	GetHTTPClient() *http.Client
    54  	GetDynamicClient(cmd *cobra.Command) (dynamic.Interface, error)
    55  	GetDiscoveryClient(cmd *cobra.Command) (discovery.DiscoveryInterface, error)
    56  	VerifyCLIArgsNil(cmd *cobra.Command) error
    57  }
    58  
    59  type ReportCtx struct {
    60  	ReportFile           string
    61  	ReportFormat         string
    62  	IncludeSupportData   bool
    63  	IncludeInfo          bool
    64  	IncludeActions       bool
    65  	MinConfidence        int
    66  	MinImpact            int
    67  	PrintReportToConsole bool
    68  }
    69  
    70  type ClusterSnapshotCtx struct {
    71  	BugReportDir         string
    72  	MoreNS               []string
    73  	PrintReportToConsole bool
    74  }
    75  
    76  const defaultVerrazzanoTmpl = `apiVersion: install.verrazzano.io/%s
    77  kind: Verrazzano
    78  metadata:
    79    name: verrazzano
    80    namespace: default`
    81  
    82  const v1beta1MinVersion = "1.4.0"
    83  
    84  var (
    85  	vzVer, k8sVer string
    86  )
    87  
    88  func NewVerrazzanoForVZVersion(version string) (schema.GroupVersion, client.Object, error) {
    89  	if version == "" {
    90  		// default to a v1beta1 compatible version if not specified
    91  		version = "1.5.0"
    92  	}
    93  	actualVersion, err := semver.NewSemVersion(version)
    94  	if err != nil {
    95  		return schema.GroupVersion{}, nil, err
    96  	}
    97  	minVersion, err := semver.NewSemVersion(v1beta1MinVersion)
    98  	if err != nil {
    99  		return schema.GroupVersion{}, nil, err
   100  	}
   101  	if actualVersion.IsLessThan(minVersion) {
   102  		o, err := newVerazzanoWithAPIVersion(v1alpha1.SchemeGroupVersion.Version)
   103  		return v1alpha1.SchemeGroupVersion, o, err
   104  	}
   105  	o, err := newVerazzanoWithAPIVersion(v1beta1.SchemeGroupVersion.Version)
   106  	return v1beta1.SchemeGroupVersion, o, err
   107  }
   108  
   109  func newVerazzanoWithAPIVersion(version string) (client.Object, error) {
   110  	vz := &unstructured.Unstructured{}
   111  	if err := yaml.Unmarshal([]byte(fmt.Sprintf(defaultVerrazzanoTmpl, version)), vz); err != nil {
   112  		return nil, err
   113  	}
   114  	return vz, nil
   115  }
   116  
   117  func NewVerrazzanoForGroupVersion(groupVersion schema.GroupVersion) func() interface{} {
   118  	switch groupVersion {
   119  	case v1alpha1.SchemeGroupVersion:
   120  		return func() interface{} {
   121  			return &v1alpha1.Verrazzano{}
   122  		}
   123  	default:
   124  		return func() interface{} {
   125  			return &v1beta1.Verrazzano{}
   126  		}
   127  	}
   128  }
   129  
   130  // FindVerrazzanoResource - find the single Verrazzano resource
   131  func FindVerrazzanoResource(client client.Client) (*v1beta1.Verrazzano, error) {
   132  	vzList := v1beta1.VerrazzanoList{}
   133  	err := client.List(context.TODO(), &vzList)
   134  	if err != nil {
   135  		// If v1beta1 resource version doesn't exist, try v1alpha1
   136  		if meta.IsNoMatchError(err) {
   137  			return findVerazzanoResourceV1Alpha1(client)
   138  		}
   139  		return nil, failedToFindResourceError(err)
   140  	}
   141  	if err := checkListLength(len(vzList.Items)); err != nil {
   142  		return nil, err
   143  	}
   144  	return &vzList.Items[0], nil
   145  }
   146  
   147  // GetVerrazzanoResource - get a Verrazzano resource
   148  func GetVerrazzanoResource(client client.Client, namespacedName types.NamespacedName) (*v1beta1.Verrazzano, error) {
   149  	vz := &v1beta1.Verrazzano{}
   150  	if err := client.Get(context.TODO(), namespacedName, vz); err != nil {
   151  		if meta.IsNoMatchError(err) {
   152  			return getVerrazzanoResourceV1Alpha1(client, namespacedName)
   153  		}
   154  		return nil, failedToGetResourceError(err)
   155  
   156  	}
   157  	return vz, nil
   158  }
   159  
   160  func UpdateVerrazzanoResource(client client.Client, vz *v1beta1.Verrazzano) error {
   161  	err := client.Update(context.TODO(), vz)
   162  	// upgrade version may not support v1beta1
   163  	if err != nil && (meta.IsNoMatchError(err) || apierrors.IsNotFound(err)) {
   164  		vzV1Alpha1 := &v1alpha1.Verrazzano{}
   165  		err = vzV1Alpha1.ConvertFrom(vz)
   166  		if err != nil {
   167  			return err
   168  		}
   169  		return client.Update(context.TODO(), vzV1Alpha1)
   170  	}
   171  	return err
   172  }
   173  
   174  // GetLatestReleaseVersion - get the version of the latest release of Verrazzano
   175  func GetLatestReleaseVersion(client *http.Client) (string, error) {
   176  	// Get the list of all Verrazzano releases
   177  	releases, err := github.ListReleases(client)
   178  	if err != nil {
   179  		return "", fmt.Errorf("Failed to get list of Verrazzano releases: %s", err.Error())
   180  	}
   181  	return getLatestReleaseVersion(releases)
   182  }
   183  
   184  // getLatestReleaseVersion - determine which release it the latest based on semver values
   185  func getLatestReleaseVersion(releases []string) (string, error) {
   186  	var latestRelease *semver.SemVersion
   187  	for _, tag := range releases {
   188  		tagSemver, err := semver.NewSemVersion(tag)
   189  		if err != nil {
   190  			return "", err
   191  		}
   192  		if latestRelease == nil {
   193  			// Initialize with the first tag
   194  			latestRelease = tagSemver
   195  		} else {
   196  			if tagSemver.IsGreatherThan(latestRelease) {
   197  				// Update the latest release found
   198  				latestRelease = tagSemver
   199  			}
   200  		}
   201  	}
   202  	return fmt.Sprintf("v%s", latestRelease.ToString()), nil
   203  }
   204  
   205  func NewScheme() *runtime.Scheme {
   206  	scheme := runtime.NewScheme()
   207  	_ = v1alpha1.AddToScheme(scheme)
   208  	_ = v1beta1.AddToScheme(scheme)
   209  	_ = corev1.SchemeBuilder.AddToScheme(scheme)
   210  	_ = adminv1.SchemeBuilder.AddToScheme(scheme)
   211  	_ = rbacv1.SchemeBuilder.AddToScheme(scheme)
   212  	_ = appv1.SchemeBuilder.AddToScheme(scheme)
   213  	_ = networkingv1.AddToScheme(scheme)
   214  	_ = oam.AddToScheme(scheme)
   215  	_ = batchv1.AddToScheme(scheme)
   216  	_ = certv1.AddToScheme(scheme)
   217  	_ = istioclient.AddToScheme(scheme)
   218  	return scheme
   219  }
   220  
   221  // GetNamespacesForAllComponents returns the list of unique namespaces of all the components included in the Verrazzano resource
   222  func GetNamespacesForAllComponents(vz *v1beta1.Verrazzano) []string {
   223  	var nsList []string
   224  	if vz == nil {
   225  		return nsList
   226  	}
   227  	allComponents := getAllComponents(vz)
   228  	for _, eachComp := range allComponents {
   229  		found, comp := registry.FindComponent(eachComp)
   230  		if found {
   231  			nsList = append(nsList, comp.Namespace())
   232  		}
   233  	}
   234  	if len(nsList) > 0 {
   235  		nsList = RemoveDuplicate(nsList)
   236  	}
   237  	return nsList
   238  }
   239  
   240  // getAllComponents returns the list of components from the Verrazzano resource
   241  func getAllComponents(vzRes *v1beta1.Verrazzano) []string {
   242  	var compSlice = make([]string, 0)
   243  
   244  	for _, compStatusDetail := range vzRes.Status.Components {
   245  		if compStatusDetail.State == v1beta1.CompStateDisabled {
   246  			continue
   247  		}
   248  		compSlice = append(compSlice, compStatusDetail.Name)
   249  	}
   250  	return compSlice
   251  }
   252  
   253  func findVerazzanoResourceV1Alpha1(client client.Client) (*v1beta1.Verrazzano, error) {
   254  	vzV1Alpha1List := v1alpha1.VerrazzanoList{}
   255  	err := client.List(context.TODO(), &vzV1Alpha1List)
   256  	if err != nil {
   257  		return nil, failedToFindResourceError(err)
   258  	}
   259  	if err := checkListLength(len(vzV1Alpha1List.Items)); err != nil {
   260  		return nil, err
   261  	}
   262  	vzConverted := &v1beta1.Verrazzano{}
   263  	err = vzV1Alpha1List.Items[0].ConvertTo(vzConverted)
   264  	return vzConverted, err
   265  }
   266  
   267  func getVerrazzanoResourceV1Alpha1(client client.Client, namespacedName types.NamespacedName) (*v1beta1.Verrazzano, error) {
   268  	vzV1Alpha1 := &v1alpha1.Verrazzano{}
   269  	if err := client.Get(context.TODO(), namespacedName, vzV1Alpha1); err != nil {
   270  		return nil, failedToGetResourceError(err)
   271  	}
   272  	vz := &v1beta1.Verrazzano{}
   273  	err := vzV1Alpha1.ConvertTo(vz)
   274  	return vz, err
   275  }
   276  
   277  func failedToFindResourceError(err error) error {
   278  	return fmt.Errorf("Failed to find any Verrazzano resources: %s", err.Error())
   279  }
   280  
   281  func failedToGetResourceError(err error) error {
   282  	return fmt.Errorf("Failed to get a Verrazzano install resource: %s", err.Error())
   283  }
   284  
   285  func checkListLength(length int) error {
   286  	if length == 0 {
   287  		return fmt.Errorf("Failed to find any Verrazzano resources")
   288  	}
   289  	if length != 1 {
   290  		return fmt.Errorf("Expected to only find one Verrazzano resource, but found %d", length)
   291  	}
   292  	return nil
   293  }
   294  
   295  // GetOperatorYaml returns Kubernetes manifests to deploy the Verrazzano platform operator
   296  // The return value is operator.yaml for releases earlier than 1.4.0 and verrazzano-platform-operator.yaml from release 1.4.0 onwards
   297  func GetOperatorYaml(version string) (string, error) {
   298  	vzVersion, err := semver.NewSemVersion(version)
   299  	if err != nil {
   300  		return "", fmt.Errorf("invalid Verrazzano version: %v", err.Error())
   301  	}
   302  	ver140, _ := semver.NewSemVersion("v" + vpoconstants.VerrazzanoVersion1_4_0)
   303  	var url string
   304  	if vzVersion.IsGreaterThanOrEqualTo(ver140) {
   305  		url = fmt.Sprintf(vzconstants.VerrazzanoPlatformOperatorURL, version)
   306  	} else {
   307  		url = fmt.Sprintf(vzconstants.VerrazzanoOperatorURL, version)
   308  	}
   309  	return url, nil
   310  }
   311  
   312  // SetK8sVer returns cluster Kubernetes version
   313  func SetK8sVer() error {
   314  	config, err := k8sutil.GetConfigFromController()
   315  	if err != nil {
   316  		return fmt.Errorf("error getting config from the Controller Runtime: %v", err.Error())
   317  	}
   318  
   319  	client, err := kubernetes.NewForConfig(config)
   320  	if err != nil {
   321  		return fmt.Errorf("error getting a clientset for the given config %v", err.Error())
   322  	}
   323  
   324  	versionInfo, err := client.ServerVersion()
   325  	if err != nil {
   326  		return fmt.Errorf("error getting kubernetes version %v", err.Error())
   327  	}
   328  
   329  	k8sVer = versionInfo.String()
   330  	return nil
   331  }
   332  
   333  // SetVzVer set verrazzano version
   334  func SetVzVer(client *client.Client) error {
   335  	vz, vzErr := FindVerrazzanoResource(*client)
   336  	if vzErr != nil {
   337  		return vzErr
   338  	}
   339  	vzVer = vz.Status.Version
   340  	return nil
   341  }
   342  
   343  // GetVersionOut returns the customised k8s and vz version string
   344  func GetVersionOut() string {
   345  	verOut := ""
   346  	if vzVer != "" {
   347  		verOut += fmt.Sprintf("\nVerrazzano Version: %s", vzVer)
   348  	}
   349  	if k8sVer != "" {
   350  		verOut += fmt.Sprintf("\nKubernetes Version: %s\n", k8sVer)
   351  	}
   352  	return verOut
   353  }
   354  
   355  // VerifyVzInstallNamespaceExists returns existence of verrazzano-install namespace
   356  func VerifyVzInstallNamespaceExists(kubeClient kubernetes.Interface) bool {
   357  	pods, err := kubeClient.CoreV1().Pods(vzconstants.VerrazzanoInstall).List(context.TODO(), metav1.ListOptions{})
   358  	if err != nil {
   359  		return false
   360  	}
   361  	for i := range pods.Items {
   362  		if strings.HasPrefix(pods.Items[i].Name, vzconstants.VerrazzanoPlatformOperator) {
   363  			return true
   364  		}
   365  	}
   366  	return false
   367  }
   368  
   369  // CheckAndRemoveBugReportExistsInDir checks vz bug report exists in dir or not
   370  func CheckAndRemoveBugReportExistsInDir(dir string) bool {
   371  	bugReportFilePattern := strings.Replace(vzconstants.BugReportFileDefaultValue, "-dt", "", 1)
   372  	if fileMatched, _ := filepath.Glob(dir + bugReportFilePattern); len(fileMatched) == 1 {
   373  		os.Remove(fileMatched[0])
   374  		return true
   375  	}
   376  	return false
   377  }
   378  
   379  // CheckAndRemoveBugReportAndRedactionFileExistsInDir checks that both a vz bug-report file and
   380  // a redacted values file exists in dir, with matching names
   381  func CheckAndRemoveBugReportAndRedactionFileExistsInDir(dir string) bool {
   382  	// Check for the bug report file
   383  	bugReportFilePattern := strings.Replace(vzconstants.BugReportFileDefaultValue, "-dt", "", 1)
   384  	var bugReportFilesMatched []string
   385  	if bugReportFilesMatched, _ = filepath.Glob(dir + bugReportFilePattern); len(bugReportFilesMatched) != 1 {
   386  		return false
   387  	}
   388  	existingBugReportFileName := bugReportFilesMatched[0]
   389  	defer os.Remove(existingBugReportFileName)
   390  
   391  	// Check the redacted values file exists with the expected name
   392  	expectedRedactionFileName := GenerateRedactionFileNameFromBugReportName(existingBugReportFileName)
   393  	if _, err := os.Stat(expectedRedactionFileName); err != nil {
   394  		return false
   395  	}
   396  	os.Remove(expectedRedactionFileName)
   397  	return true
   398  }
   399  
   400  // generateRedactionFileNameFromBugReportName returns a name for the redacted values file
   401  // to match the bugReportFileName.
   402  // For example, for a bugReportFileName of vz-bug-report-datetime-xxxx.tar.gz,
   403  // this function returns vz-bug-report-datetime-xxxx-sensitive-do-not-share-redaction-map.csv.
   404  func GenerateRedactionFileNameFromBugReportName(bugReportFileName string) string {
   405  	return strings.TrimSuffix(bugReportFileName, ".tar.gz") + vzconstants.RedactionFileSuffix
   406  }