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 }