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 }