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