github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/cse_internal.go (about)

     1  package govcd
     2  
     3  import (
     4  	"bytes"
     5  	"embed"
     6  	"encoding/json"
     7  	"fmt"
     8  	"strconv"
     9  	"strings"
    10  	"text/template"
    11  )
    12  
    13  // This collection of files contains all the Go Templates and resources required for the Container Service Extension (CSE) methods
    14  // to work.
    15  //
    16  //go:embed cse
    17  var cseFiles embed.FS
    18  
    19  // getUnmarshalledRdePayload gets the unmarshalled JSON payload to create the Runtime Defined Entity that represents
    20  // a CSE Kubernetes cluster, by using the receiver information. This method uses all the Go Templates stored in cseFiles
    21  func (clusterSettings *cseClusterSettingsInternal) getUnmarshalledRdePayload() (map[string]interface{}, error) {
    22  	if clusterSettings == nil {
    23  		return nil, fmt.Errorf("the receiver CSE Kubernetes cluster settings object is nil")
    24  	}
    25  	capiYaml, err := clusterSettings.generateCapiYamlAsJsonString()
    26  	if err != nil {
    27  		return nil, err
    28  	}
    29  
    30  	templateArgs := map[string]string{
    31  		"Name":               clusterSettings.Name,
    32  		"Org":                clusterSettings.OrganizationName,
    33  		"VcdUrl":             clusterSettings.VcdUrl,
    34  		"Vdc":                clusterSettings.VdcName,
    35  		"Delete":             "false",
    36  		"ForceDelete":        "false",
    37  		"AutoRepairOnErrors": strconv.FormatBool(clusterSettings.AutoRepairOnErrors),
    38  		"ApiToken":           clusterSettings.ApiToken,
    39  		"CapiYaml":           capiYaml,
    40  	}
    41  
    42  	if clusterSettings.DefaultStorageClass.StorageProfileName != "" {
    43  		templateArgs["DefaultStorageClassStorageProfile"] = clusterSettings.DefaultStorageClass.StorageProfileName
    44  		templateArgs["DefaultStorageClassName"] = clusterSettings.DefaultStorageClass.Name
    45  		templateArgs["DefaultStorageClassUseDeleteReclaimPolicy"] = strconv.FormatBool(clusterSettings.DefaultStorageClass.UseDeleteReclaimPolicy)
    46  		templateArgs["DefaultStorageClassFileSystem"] = clusterSettings.DefaultStorageClass.Filesystem
    47  	}
    48  
    49  	rdeTemplate, err := getCseTemplate(clusterSettings.CseVersion, "rde")
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  
    54  	rdePayload := template.Must(template.New(clusterSettings.Name).Parse(rdeTemplate))
    55  	buf := &bytes.Buffer{}
    56  	if err := rdePayload.Execute(buf, templateArgs); err != nil {
    57  		return nil, fmt.Errorf("could not render the Go template with the RDE JSON: %s", err)
    58  	}
    59  
    60  	var result interface{}
    61  	err = json.Unmarshal(buf.Bytes(), &result)
    62  	if err != nil {
    63  		return nil, fmt.Errorf("could not generate a correct RDE payload: %s", err)
    64  	}
    65  
    66  	return result.(map[string]interface{}), nil
    67  }
    68  
    69  // generateCapiYamlAsJsonString generates the "capiYaml" property of the RDE that represents a Kubernetes cluster. This
    70  // "capiYaml" property is a YAML encoded as a JSON string. This method uses the Go Templates stored in cseFiles.
    71  func (clusterSettings *cseClusterSettingsInternal) generateCapiYamlAsJsonString() (string, error) {
    72  	if clusterSettings == nil {
    73  		return "", fmt.Errorf("the receiver cluster settings is nil")
    74  	}
    75  
    76  	capiYamlTemplate, err := getCseTemplate(clusterSettings.CseVersion, "capiyaml_cluster")
    77  	if err != nil {
    78  		return "", err
    79  	}
    80  
    81  	// This YAML snippet contains special strings, such as "%,", that render wrong using the Go template engine
    82  	sanitizedCapiYamlTemplate := strings.NewReplacer("%", "%%").Replace(capiYamlTemplate)
    83  	capiYaml := template.Must(template.New(clusterSettings.Name + "-cluster").Parse(sanitizedCapiYamlTemplate))
    84  
    85  	nodePoolYaml, err := clusterSettings.generateWorkerPoolsYaml()
    86  	if err != nil {
    87  		return "", err
    88  	}
    89  
    90  	memoryHealthCheckYaml, err := clusterSettings.generateMachineHealthCheckYaml()
    91  	if err != nil {
    92  		return "", err
    93  	}
    94  
    95  	templateArgs := map[string]interface{}{
    96  		"ClusterName":                 clusterSettings.Name,
    97  		"TargetNamespace":             clusterSettings.Name + "-ns",
    98  		"TkrVersion":                  clusterSettings.TkgVersionBundle.TkrVersion,
    99  		"TkgVersion":                  clusterSettings.TkgVersionBundle.TkgVersion,
   100  		"PodCidr":                     clusterSettings.PodCidr,
   101  		"ServiceCidr":                 clusterSettings.ServiceCidr,
   102  		"VcdSite":                     clusterSettings.VcdUrl,
   103  		"Org":                         clusterSettings.OrganizationName,
   104  		"OrgVdc":                      clusterSettings.VdcName,
   105  		"OrgVdcNetwork":               clusterSettings.NetworkName,
   106  		"Catalog":                     clusterSettings.CatalogName,
   107  		"VAppTemplate":                clusterSettings.KubernetesTemplateOvaName,
   108  		"ControlPlaneSizingPolicy":    clusterSettings.ControlPlane.SizingPolicyName,
   109  		"ControlPlanePlacementPolicy": clusterSettings.ControlPlane.PlacementPolicyName,
   110  		"ControlPlaneStorageProfile":  clusterSettings.ControlPlane.StorageProfileName,
   111  		"ControlPlaneDiskSize":        fmt.Sprintf("%dGi", clusterSettings.ControlPlane.DiskSizeGi),
   112  		"ControlPlaneMachineCount":    strconv.Itoa(clusterSettings.ControlPlane.MachineCount),
   113  		"ControlPlaneEndpoint":        clusterSettings.ControlPlane.Ip,
   114  		"DnsVersion":                  clusterSettings.TkgVersionBundle.CoreDnsVersion,
   115  		"EtcdVersion":                 clusterSettings.TkgVersionBundle.EtcdVersion,
   116  		"ContainerRegistryUrl":        clusterSettings.VcdKeConfig.ContainerRegistryUrl,
   117  		"KubernetesVersion":           clusterSettings.TkgVersionBundle.KubernetesVersion,
   118  		"SshPublicKey":                clusterSettings.SshPublicKey,
   119  		"VirtualIpSubnet":             clusterSettings.VirtualIpSubnet,
   120  		"Base64Certificates":          clusterSettings.VcdKeConfig.Base64Certificates,
   121  	}
   122  
   123  	buf := &bytes.Buffer{}
   124  	if err := capiYaml.Execute(buf, templateArgs); err != nil {
   125  		return "", fmt.Errorf("could not generate a correct CAPI YAML: %s", err)
   126  	}
   127  
   128  	// The final "pretty" YAML. To embed it in the final payload it must be marshaled into a one-line JSON string
   129  	prettyYaml := ""
   130  	if memoryHealthCheckYaml != "" {
   131  		prettyYaml += fmt.Sprintf("%s\n---\n", memoryHealthCheckYaml)
   132  	}
   133  	prettyYaml += fmt.Sprintf("%s\n---\n%s", nodePoolYaml, buf.String())
   134  
   135  	// We don't use a standard json.Marshal() as the YAML contains special characters that are not encoded properly, such as '<'.
   136  	buf.Reset()
   137  	enc := json.NewEncoder(buf)
   138  	enc.SetEscapeHTML(false)
   139  	err = enc.Encode(prettyYaml)
   140  	if err != nil {
   141  		return "", fmt.Errorf("could not encode the CAPI YAML into a JSON string: %s", err)
   142  	}
   143  
   144  	// Removes trailing quotes from the final JSON string
   145  	return strings.Trim(strings.TrimSpace(buf.String()), "\""), nil
   146  }
   147  
   148  // generateWorkerPoolsYaml generates YAML blocks corresponding to the cluster Worker Pools. The blocks are separated by
   149  // the standard YAML separator (---), but does not add one at the end.
   150  func (clusterSettings *cseClusterSettingsInternal) generateWorkerPoolsYaml() (string, error) {
   151  	if clusterSettings == nil {
   152  		return "", fmt.Errorf("the receiver CSE Kubernetes cluster settings object is nil")
   153  	}
   154  
   155  	workerPoolsTemplate, err := getCseTemplate(clusterSettings.CseVersion, "capiyaml_workerpool")
   156  	if err != nil {
   157  		return "", err
   158  	}
   159  
   160  	workerPools := template.Must(template.New(clusterSettings.Name + "-worker-pool").Parse(workerPoolsTemplate))
   161  	resultYaml := ""
   162  	buf := &bytes.Buffer{}
   163  
   164  	// We can have many Worker Pools, we build a YAML object for each one of them.
   165  	for i, wp := range clusterSettings.WorkerPools {
   166  
   167  		// Check the correctness of the Compute Policies in the node pool block
   168  		if wp.PlacementPolicyName != "" && wp.VGpuPolicyName != "" {
   169  			return "", fmt.Errorf("the Worker Pool '%s' should have either a Placement Policy or a vGPU Policy, not both", wp.Name)
   170  		}
   171  		placementPolicy := wp.PlacementPolicyName
   172  		if wp.VGpuPolicyName != "" {
   173  			// For convenience, we just use one of the variables as both cannot be set at same time
   174  			placementPolicy = wp.VGpuPolicyName
   175  		}
   176  
   177  		if err := workerPools.Execute(buf, map[string]string{
   178  			"ClusterName":             clusterSettings.Name,
   179  			"NodePoolName":            wp.Name,
   180  			"TargetNamespace":         clusterSettings.Name + "-ns",
   181  			"Catalog":                 clusterSettings.CatalogName,
   182  			"VAppTemplate":            clusterSettings.KubernetesTemplateOvaName,
   183  			"NodePoolSizingPolicy":    wp.SizingPolicyName,
   184  			"NodePoolPlacementPolicy": placementPolicy, // Can be either Placement or vGPU policy
   185  			"NodePoolStorageProfile":  wp.StorageProfileName,
   186  			"NodePoolDiskSize":        fmt.Sprintf("%dGi", wp.DiskSizeGi),
   187  			"NodePoolEnableGpu":       strconv.FormatBool(wp.VGpuPolicyName != ""),
   188  			"NodePoolMachineCount":    strconv.Itoa(wp.MachineCount),
   189  			"KubernetesVersion":       clusterSettings.TkgVersionBundle.KubernetesVersion,
   190  		}); err != nil {
   191  			return "", fmt.Errorf("could not generate a correct Worker Pool '%s' YAML block: %s", wp.Name, err)
   192  		}
   193  		resultYaml += fmt.Sprintf("%s\n", buf.String())
   194  		if i < len(clusterSettings.WorkerPools)-1 {
   195  			resultYaml += "---\n"
   196  		}
   197  		buf.Reset()
   198  	}
   199  	return resultYaml, nil
   200  }
   201  
   202  // generateMachineHealthCheckYaml generates a YAML block corresponding to the cluster Machine Health Check.
   203  // The generated YAML does not contain a separator (---) at the end.
   204  func (clusterSettings *cseClusterSettingsInternal) generateMachineHealthCheckYaml() (string, error) {
   205  	if clusterSettings == nil {
   206  		return "", fmt.Errorf("the receiver CSE Kubernetes cluster settings object is nil")
   207  	}
   208  
   209  	if clusterSettings.VcdKeConfig.NodeStartupTimeout == "" &&
   210  		clusterSettings.VcdKeConfig.NodeUnknownTimeout == "" &&
   211  		clusterSettings.VcdKeConfig.NodeNotReadyTimeout == "" &&
   212  		clusterSettings.VcdKeConfig.MaxUnhealthyNodesPercentage == 0 {
   213  		return "", nil
   214  	}
   215  
   216  	mhcTemplate, err := getCseTemplate(clusterSettings.CseVersion, "capiyaml_mhc")
   217  	if err != nil {
   218  		return "", err
   219  	}
   220  
   221  	machineHealthCheck := template.Must(template.New(clusterSettings.Name + "-mhc").Parse(mhcTemplate))
   222  	buf := &bytes.Buffer{}
   223  
   224  	if err := machineHealthCheck.Execute(buf, map[string]string{
   225  		"ClusterName":     clusterSettings.Name,
   226  		"TargetNamespace": clusterSettings.Name + "-ns",
   227  		// With the 'percentage' suffix
   228  		"MaxUnhealthyNodePercentage": fmt.Sprintf("%.0f%%", clusterSettings.VcdKeConfig.MaxUnhealthyNodesPercentage),
   229  		// These values coming from VCDKEConfig (CSE Server settings) may have an "s" suffix. We make sure we don't duplicate it
   230  		"NodeStartupTimeout":  fmt.Sprintf("%ss", strings.ReplaceAll(clusterSettings.VcdKeConfig.NodeStartupTimeout, "s", "")),
   231  		"NodeUnknownTimeout":  fmt.Sprintf("%ss", strings.ReplaceAll(clusterSettings.VcdKeConfig.NodeUnknownTimeout, "s", "")),
   232  		"NodeNotReadyTimeout": fmt.Sprintf("%ss", strings.ReplaceAll(clusterSettings.VcdKeConfig.NodeNotReadyTimeout, "s", "")),
   233  	}); err != nil {
   234  		return "", fmt.Errorf("could not generate a correct Machine Health Check YAML: %s", err)
   235  	}
   236  	return fmt.Sprintf("%s\n", buf.String()), nil
   237  
   238  }