github.com/verrazzano/verrazzano@v1.7.1/tests/e2e/quickcreate/context.go (about)

     1  // Copyright (c) 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 quickcreate
     5  
     6  import (
     7  	"context"
     8  	_ "embed"
     9  	"encoding/base64"
    10  	"errors"
    11  	"fmt"
    12  	"github.com/verrazzano/verrazzano/cluster-operator/controllers/quickcreate/controller/ocne"
    13  	"github.com/verrazzano/verrazzano/pkg/k8sutil"
    14  	"github.com/verrazzano/verrazzano/pkg/semver"
    15  	"github.com/verrazzano/verrazzano/tests/e2e/pkg"
    16  	corev1 "k8s.io/api/core/v1"
    17  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    18  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    19  	"k8s.io/apimachinery/pkg/types"
    20  	"os"
    21  	"sigs.k8s.io/cluster-api/api/v1beta1"
    22  	clipkg "sigs.k8s.io/controller-runtime/pkg/client"
    23  	"sigs.k8s.io/yaml"
    24  	"strings"
    25  )
    26  
    27  const (
    28  	Ocneoci     = "ocneoci"
    29  	Oke         = "oke"
    30  	Namespace   = "NAMESPACE"
    31  	VZFleetName = "VZFLEET_NAME"
    32  )
    33  
    34  var (
    35  	//go:embed templates/ocneoci.goyaml
    36  	ocneociTemplate []byte
    37  	//go:embed templates/oke.goyaml
    38  	okeTemplate []byte
    39  	//go:embed templates/ociclusteridentity.goyaml
    40  	ociClusterIdentity []byte
    41  
    42  	//go:embed templates/verrazzanofleet-mc-profile.goyaml
    43  	verrazzanoFleet []byte
    44  
    45  	clusterTemplateMap = map[string][]byte{
    46  		Ocneoci: ocneociTemplate,
    47  		Oke:     okeTemplate,
    48  	}
    49  
    50  	clusterName      string
    51  	clusterNamespace string
    52  	vzFleetName      string
    53  )
    54  
    55  type (
    56  	QCContext struct {
    57  		ClusterType string
    58  		Namespace   string
    59  		Client      clipkg.Client
    60  		RawObjects  []byte
    61  		Parameters  input
    62  	}
    63  	input map[string]interface{}
    64  )
    65  
    66  func newContext(cli clipkg.Client, clusterType string) (*QCContext, error) {
    67  	qc := &QCContext{
    68  		ClusterType: clusterType,
    69  		Namespace:   pkg.SimpleNameGenerator.New("qc-"),
    70  		Client:      cli,
    71  	}
    72  	if err := qc.setDynamicValues(); err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	return qc, nil
    77  }
    78  
    79  func (qc *QCContext) setDynamicValues() error {
    80  	rawObjects, parameters, err := qc.getInputValues()
    81  	if err != nil {
    82  		return fmt.Errorf("failed to load template: %v", err)
    83  	}
    84  	qc.Parameters = parameters
    85  	qc.RawObjects = rawObjects
    86  	qc.Parameters[Namespace] = qc.Namespace
    87  	if qc.isOCICluster() {
    88  		err = qc.Parameters.prepareOCI(qc.ClusterType)
    89  		if err != nil {
    90  			return err
    91  		}
    92  	}
    93  	return nil
    94  }
    95  
    96  func (qc *QCContext) setup() error {
    97  	if err := qc.Client.Create(context.Background(), qc.namespaceObject()); err != nil {
    98  		return err
    99  	}
   100  	if qc.isOCICluster() {
   101  		if err := qc.applyOCIClusterIdentity(); err != nil {
   102  			return err
   103  		}
   104  	}
   105  	return nil
   106  }
   107  
   108  func (qc *QCContext) applyOCIClusterIdentity() error {
   109  	return k8sutil.NewYAMLApplier(qc.Client, "").ApplyBT(ociClusterIdentity, qc.Parameters)
   110  }
   111  
   112  func (qc *QCContext) applyVerrazzanoFleet() error {
   113  	return k8sutil.NewYAMLApplier(qc.Client, "").ApplyBT(verrazzanoFleet, qc.Parameters)
   114  }
   115  
   116  func (qc *QCContext) applyCluster() error {
   117  	return k8sutil.NewYAMLApplier(qc.Client, "").ApplyBT(qc.RawObjects, qc.Parameters)
   118  }
   119  
   120  func (qc *QCContext) getInputValues() ([]byte, input, error) {
   121  	params, err := qc.newParameters()
   122  	clusterName = params[ClusterID].(string)
   123  	clusterNamespace = qc.Namespace
   124  	vzFleetName = params[VZFleetName].(string)
   125  	if err != nil {
   126  		return nil, nil, err
   127  	}
   128  	b, ok := clusterTemplateMap[qc.ClusterType]
   129  	if !ok {
   130  		return nil, nil, fmt.Errorf("invalid cluster type: %s", qc.ClusterType)
   131  	}
   132  	return b, params, nil
   133  }
   134  
   135  func (qc *QCContext) newParameters() (input, error) {
   136  	var i input = map[string]interface{}{
   137  		ClusterID:   pkg.SimpleNameGenerator.New("qc-"),
   138  		VZFleetName: pkg.SimpleNameGenerator.New("vzfleet-"),
   139  	}
   140  	if err := i.addFileContents(); err != nil {
   141  		return nil, err
   142  	}
   143  	if err := i.addLatestOCNEVersion(qc.Client, qc.ClusterType); err != nil {
   144  		return nil, err
   145  	}
   146  	return i, nil
   147  }
   148  
   149  func (qc *QCContext) deleteObject(o clipkg.Object) error {
   150  	err := qc.Client.Get(context.Background(), types.NamespacedName{
   151  		Namespace: o.GetNamespace(),
   152  		Name:      o.GetName(),
   153  	}, o)
   154  	if apierrors.IsNotFound(err) {
   155  		return nil
   156  	}
   157  	if err != nil {
   158  		return err
   159  	}
   160  	if !o.GetDeletionTimestamp().IsZero() {
   161  		return errors.New("object is being deleted")
   162  	}
   163  	if err := qc.Client.Delete(context.Background(), o); err != nil {
   164  		return err
   165  	}
   166  	return errors.New("deleting object")
   167  }
   168  
   169  func (qc *QCContext) cleanupCAPICluster() error {
   170  	name, ok := qc.Parameters[ClusterID].(string)
   171  	if !ok {
   172  		return nil
   173  	}
   174  	return qc.deleteObject(&v1beta1.Cluster{
   175  		ObjectMeta: metav1.ObjectMeta{
   176  			Name:      name,
   177  			Namespace: qc.Namespace,
   178  		},
   179  	})
   180  }
   181  
   182  func (qc *QCContext) namespaceObject() clipkg.Object {
   183  	return &corev1.Namespace{
   184  		ObjectMeta: metav1.ObjectMeta{
   185  			Name: qc.Namespace,
   186  		},
   187  	}
   188  }
   189  
   190  func (qc *QCContext) isClusterReady() error {
   191  	cluster := &v1beta1.Cluster{}
   192  	if err := qc.Client.Get(context.TODO(), types.NamespacedName{
   193  		Namespace: qc.Namespace,
   194  		Name:      qc.Parameters[ClusterID].(string),
   195  	}, cluster); err != nil {
   196  		return err
   197  	}
   198  	if !cluster.Status.InfrastructureReady || !cluster.Status.ControlPlaneReady || cluster.Status.Phase != string(v1beta1.ClusterPhaseProvisioned) {
   199  		return dumpClusterError(cluster)
   200  	}
   201  	return nil
   202  }
   203  
   204  func (i input) addLatestOCNEVersion(client clipkg.Client, clusterType string) error {
   205  	if clusterType != Ocneoci {
   206  		return nil
   207  	}
   208  	const (
   209  		ocneConfigMapName      = "ocne-metadata"
   210  		ocneConfigMapNamespace = "verrazzano-capi"
   211  	)
   212  	cm := &corev1.ConfigMap{}
   213  	if err := client.Get(context.Background(), types.NamespacedName{
   214  		Namespace: ocneConfigMapNamespace,
   215  		Name:      ocneConfigMapName,
   216  	}, cm); err != nil {
   217  		return err
   218  	}
   219  	mapping, ok := cm.Data["mapping"]
   220  	if !ok {
   221  		return errors.New("no OCNE version mapping")
   222  	}
   223  	versions := map[string]*ocne.VersionDefaults{}
   224  	if err := yaml.Unmarshal([]byte(mapping), &versions); err != nil {
   225  		return err
   226  	}
   227  	var v1 *semver.SemVersion
   228  	var ocneVersion string
   229  	for k8sVersion, defaults := range versions {
   230  		v2, err := semver.NewSemVersion(k8sVersion)
   231  		if err != nil {
   232  			return err
   233  		}
   234  		if v1 == nil || v2.IsGreaterThanOrEqualTo(v1) {
   235  			v1 = v2
   236  			ocneVersion = defaults.Release
   237  		}
   238  	}
   239  	i[OcneVersion] = ocneVersion
   240  	return nil
   241  }
   242  
   243  func (i input) addFileContents() error {
   244  	files := []string{
   245  		PubKey,
   246  		APIKey,
   247  	}
   248  	for _, filevar := range files {
   249  		path := os.Getenv(filevar)
   250  		if len(path) < 1 {
   251  			return fmt.Errorf("%s env var empty", filevar)
   252  		}
   253  		b, err := os.ReadFile(path)
   254  		if err != nil {
   255  			return err
   256  		}
   257  		i[filevar] = string(b)
   258  		if filevar == APIKey {
   259  			i.b64EncodeKV(APIKey, B64Key)
   260  		}
   261  	}
   262  	return nil
   263  }
   264  
   265  func (i input) b64EncodeKV(key, encodedKey string) {
   266  	i[encodedKey] = base64.StdEncoding.EncodeToString([]byte(i[key].(string)))
   267  }
   268  
   269  func (i input) addEnvValue(key string) error {
   270  	value := os.Getenv(key)
   271  	if len(value) < 1 {
   272  		return fmt.Errorf("no value found for environment key %s", key)
   273  	}
   274  	i[key] = value
   275  	return nil
   276  }
   277  
   278  func dumpClusterError(cluster *v1beta1.Cluster) error {
   279  	sb := strings.Builder{}
   280  	if cluster.Status.FailureMessage != nil {
   281  		sb.WriteString(fmt.Sprintf("message: %s", *cluster.Status.FailureMessage))
   282  	}
   283  	if !cluster.Status.ControlPlaneReady {
   284  		sb.WriteString("- control plane is not ready")
   285  	}
   286  	if !cluster.Status.InfrastructureReady {
   287  		sb.WriteString("- infrastructure is not ready")
   288  	}
   289  	for _, cond := range cluster.Status.Conditions {
   290  		if cond.Status != corev1.ConditionTrue {
   291  			sb.WriteString(fmt.Sprintf("- condition[%s]:", cond.Type))
   292  			sb.WriteString(fmt.Sprintf(" reason: %s", cond.Reason))
   293  			sb.WriteString(fmt.Sprintf(" message: %s", cond.Message))
   294  		}
   295  	}
   296  	return errors.New(sb.String())
   297  }