diff --git a/docs/data-sources/ippool.md b/docs/data-sources/ippool.md new file mode 100644 index 00000000..da39f3a0 --- /dev/null +++ b/docs/data-sources/ippool.md @@ -0,0 +1,27 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "spectrocloud_ippool Data Source - terraform-provider-spectrocloud" +subcategory: "" +description: |- + +--- + +# spectrocloud_ippool (Data Source) + + + + + + +## Schema + +### Required + +- `name` (String) The name of the IP pool. +- `private_cloud_gateway_id` (String) The ID of the private cloud gateway. + +### Read-Only + +- `id` (String) The ID of this resource. + + diff --git a/docs/data-sources/private_cloud_gateway.md b/docs/data-sources/private_cloud_gateway.md new file mode 100644 index 00000000..1945c2ca --- /dev/null +++ b/docs/data-sources/private_cloud_gateway.md @@ -0,0 +1,23 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "spectrocloud_private_cloud_gateway Data Source - terraform-provider-spectrocloud" +subcategory: "" +description: |- + +--- + +# spectrocloud_private_cloud_gateway (Data Source) + + + + + + +## Schema + +### Optional + +- `id` (String) The ID of Private Cloud Gateway. +- `name` (String) The Name of Private Cloud Gateway. + + diff --git a/examples/data-sources/spectrocloud_ip_pool/datasource.tf b/examples/data-sources/spectrocloud_ip_pool/datasource.tf new file mode 100644 index 00000000..bd16fb4b --- /dev/null +++ b/examples/data-sources/spectrocloud_ip_pool/datasource.tf @@ -0,0 +1,16 @@ +data "spectrocloud_private_cloud_gateway" "gateway" { + name = "pcg-benoitcamp-vcenter" +} + +data "spectrocloud_ippool" "ippool" { + name = "IP Pool Jesse" + private_cloud_gateway_id = data.spectrocloud_private_cloud_gateway.gateway.id +} + +variable "private_cloud_gateway_id" { + type = string +} + +output "same" { + value = data.spectrocloud_ippool.ippool.id +} diff --git a/examples/data-sources/spectrocloud_ip_pool/providers.tf b/examples/data-sources/spectrocloud_ip_pool/providers.tf new file mode 100644 index 00000000..fc9f3803 --- /dev/null +++ b/examples/data-sources/spectrocloud_ip_pool/providers.tf @@ -0,0 +1,21 @@ +terraform { + required_providers { + spectrocloud = { + version = ">= 0.11.1" + source = "spectrocloud/spectrocloud" + } + } +} + +variable "sc_host" {} +variable "sc_username" {} +variable "sc_password" {} +variable "sc_project_name" {} + +provider "spectrocloud" { + host = var.sc_host + username = var.sc_username + password = var.sc_password + project_name = var.sc_project_name + trace = true +} diff --git a/examples/data-sources/spectrocloud_private_cloud_gateway/datasource.tf b/examples/data-sources/spectrocloud_private_cloud_gateway/datasource.tf new file mode 100644 index 00000000..30245cce --- /dev/null +++ b/examples/data-sources/spectrocloud_private_cloud_gateway/datasource.tf @@ -0,0 +1,7 @@ +data "spectrocloud_private_cloud_gateway" "gateway" { + name = "pcg-benoitcamp-vcenter" +} + +output "same" { + value = data.spectrocloud_private_cloud_gateway.gateway.id +} diff --git a/examples/data-sources/spectrocloud_private_cloud_gateway/providers.tf b/examples/data-sources/spectrocloud_private_cloud_gateway/providers.tf new file mode 100644 index 00000000..fc9f3803 --- /dev/null +++ b/examples/data-sources/spectrocloud_private_cloud_gateway/providers.tf @@ -0,0 +1,21 @@ +terraform { + required_providers { + spectrocloud = { + version = ">= 0.11.1" + source = "spectrocloud/spectrocloud" + } + } +} + +variable "sc_host" {} +variable "sc_username" {} +variable "sc_password" {} +variable "sc_project_name" {} + +provider "spectrocloud" { + host = var.sc_host + username = var.sc_username + password = var.sc_password + project_name = var.sc_project_name + trace = true +} diff --git a/examples/e2e/vsphere_static/README.md b/examples/e2e/vsphere_static/README.md new file mode 100644 index 00000000..445c6a3a --- /dev/null +++ b/examples/e2e/vsphere_static/README.md @@ -0,0 +1,50 @@ +# Basic Cluster demo + +End-to-end example of provisioning a new VMware K8s cluster with all of its dependencies. This terraform configuration +will provision the following resources on Spectro Cloud: +- VMware Cluster Profile +- VMware Cluster + +For SaaS deployments, there is a dependency to install the Private Cloud Gateway before VMware K8s clusters can be provisioned. +Please proceed with installation of this appliance by following the documentation: +[Spectro Cloud VMware Cluster](https://docs.spectrocloud.com/getting-started/?getting_started=vmware#yourfirstvmwarecluster). + +Once the private cloud gateway is running, please note the name of the created vSphere cloud account. Cloud accounts are managed +in Spectro Cloud Admin view: +- Login to Spectro Cloud UI (e.g for SaaS: https://console.spectrocloud.com) +- Switch to the Admin view (bottom left in the main-menu) +- Select _Settings_ in the main-menu +- Select _Cloud Accounts_ +- Note the name of the newly added vSphere cloud account + +This name will be specified as the `shared_Vmware_cloud_account_name` option in `terraform.tfvars`. + +Alternatively, look at using a `spectrocloud_cloud_account_vsphere` resource to have Terraform create +a dedicated cloud account for this e2e example. + +## Instructions: + +Clone this repository to a local directory, and change directory to `examples/e2e/vsphere`. Proceed with the following: +1. Follow the Spectro Cloud documentation to create a Private Cloud Gateway: +[VMware First Cluster](https://docs.spectrocloud.com/getting-started/?getting_started=vmware#yourfirstvmwarecluster). +2. From the current directory, copy the template variable file `terraform.template.tfvars` to a new file `terraform.tfvars`. +3. Specify and update all the placeholder values in the `terraform.tfvars` file. +4. Initialize and run terraform: `terraform init && terraform apply`. +5. Wait for the cluster creation to finish. + +Once the cluster is provisioned, the cluster _kubeconfig_ file is exported in the current working directly. + +Export the kubeconfig and check cluster pods: + +```shell +export KUBECONFIG=kubeconfig_vsphere-2 +kubectl get pod -A +``` + +## Clean up: + +Run the destroy operation: + +```shell +terraform destroy +``` diff --git a/examples/e2e/vsphere_static/datasource.tf b/examples/e2e/vsphere_static/datasource.tf new file mode 100644 index 00000000..e0a5a4c1 --- /dev/null +++ b/examples/e2e/vsphere_static/datasource.tf @@ -0,0 +1,15 @@ +data "spectrocloud_private_cloud_gateway" "gateway" { + name = var.gateway_name +} + +data "spectrocloud_ippool" "ippool" { + name = var.ippool_name + private_cloud_gateway_id = data.spectrocloud_private_cloud_gateway.gateway.id +} + + +data "spectrocloud_cluster_profile" "profile" { + name = "vmware-jben" + version = "1.0.0" + context = "project" +} \ No newline at end of file diff --git a/examples/e2e/vsphere_static/kubectl.tf b/examples/e2e/vsphere_static/kubectl.tf new file mode 100644 index 00000000..f613f0c4 --- /dev/null +++ b/examples/e2e/vsphere_static/kubectl.tf @@ -0,0 +1,6 @@ +resource "local_file" "kubeconfig" { + content = local.cluster_kubeconfig + filename = "kubeconfig_vsphere-2" + file_permission = "0644" + directory_permission = "0755" +} diff --git a/examples/e2e/vsphere_static/locals.tf b/examples/e2e/vsphere_static/locals.tf new file mode 100644 index 00000000..bcf0fee9 --- /dev/null +++ b/examples/e2e/vsphere_static/locals.tf @@ -0,0 +1,3 @@ +locals { + cluster_kubeconfig = spectrocloud_cluster_vsphere.cluster.kubeconfig +} diff --git a/examples/e2e/vsphere_static/outputs.tf b/examples/e2e/vsphere_static/outputs.tf new file mode 100644 index 00000000..d160d7eb --- /dev/null +++ b/examples/e2e/vsphere_static/outputs.tf @@ -0,0 +1,11 @@ +output "cluster_id" { + value = spectrocloud_cluster_vsphere.cluster.id +} + +output "cluster_kubeconfig" { + value = local.cluster_kubeconfig +} + +output "clusterprofile_id" { + value = data.spectrocloud_cluster_profile.profile.id +} diff --git a/examples/e2e/vsphere_static/providers.tf b/examples/e2e/vsphere_static/providers.tf new file mode 100644 index 00000000..9d52273f --- /dev/null +++ b/examples/e2e/vsphere_static/providers.tf @@ -0,0 +1,35 @@ +terraform { + required_providers { + spectrocloud = { + version = ">= 0.11.1" + source = "spectrocloud/spectrocloud" + } + } +} + +variable "sc_host" { + description = "Spectro Cloud Endpoint" + default = "api.spectrocloud.com" +} + +variable "sc_username" { + description = "Spectro Cloud Username" +} + +variable "sc_password" { + description = "Spectro Cloud Password" + sensitive = true +} + +variable "sc_project_name" { + description = "Spectro Cloud Project (e.g: Default)" + default = "Default" +} + +provider "spectrocloud" { + host = var.sc_host + username = var.sc_username + password = var.sc_password + project_name = var.sc_project_name + # ignore_insecure_tls_error = true +} diff --git a/examples/e2e/vsphere_static/resource_cloudaccount.tf b/examples/e2e/vsphere_static/resource_cloudaccount.tf new file mode 100644 index 00000000..b3823b71 --- /dev/null +++ b/examples/e2e/vsphere_static/resource_cloudaccount.tf @@ -0,0 +1,9 @@ +resource "spectrocloud_cloudaccount_vsphere" "account" { + name = var.new_vmware_cloud_account_name + context = "project" + private_cloud_gateway_id = data.spectrocloud_private_cloud_gateway.gateway.id + vsphere_vcenter = var.vsphere_vcenter + vsphere_username = var.vsphere_username + vsphere_password = var.vsphere_password + vsphere_ignore_insecure_error = true +} \ No newline at end of file diff --git a/examples/e2e/vsphere_static/resource_cluster.tf b/examples/e2e/vsphere_static/resource_cluster.tf new file mode 100644 index 00000000..cd8f69f3 --- /dev/null +++ b/examples/e2e/vsphere_static/resource_cluster.tf @@ -0,0 +1,42 @@ +resource "spectrocloud_cluster_vsphere" "cluster" { + name = "vsphere-static2" + cloud_account_id = spectrocloud_cloudaccount_vsphere.account.id + + cluster_profile { + id = data.spectrocloud_cluster_profile.profile.id + } + + cloud_config { + ssh_key = var.cluster_ssh_public_key + image_template_folder = var.vsphere_image_template_folder + + datacenter = var.vsphere_datacenter + folder = var.vsphere_folder + + static_ip = true + #network_type = "DDNS" + network_search_domain = var.cluster_network_search + } + + + machine_pool { + control_plane = true + control_plane_as_worker = true + name = "master-pool" + count = 3 + + placement { + cluster = var.vsphere_cluster + resource_pool = var.vsphere_resource_pool + datastore = var.vsphere_datastore + network = var.vsphere_network + static_ip_pool_id = data.spectrocloud_ippool.ippool.id + } + instance_type { + disk_size_gb = 40 + memory_mb = 8192 + cpu = 4 + } + } + +} diff --git a/examples/e2e/vsphere_static/terraform.template.tfvars b/examples/e2e/vsphere_static/terraform.template.tfvars new file mode 100644 index 00000000..46600340 --- /dev/null +++ b/examples/e2e/vsphere_static/terraform.template.tfvars @@ -0,0 +1,29 @@ +# Spectro Cloud credentials +sc_host = "{enter Spectro Cloud API endpoint}" #e.g: api.spectrocloud.com (for SaaS) +sc_username = "{enter Spectro Cloud username}" #e.g: user1@abc.com +sc_password = "{enter Spectro Cloud password}" #e.g: supereSecure1! +sc_project_name = "{enter Spectro Cloud project Name}" #e.g: Default + +# Cloud Account lookup by name +# See README.md for instructions how to obtain this name +shared_vmware_cloud_account_name = "{enter Spectro Cloud VMware Cloud Account name}" + +# SSH public key to inject into all K8s nodes +# Insert your public key between the EOT markers +# The public key starts with "ssh-rsa ...." +cluster_ssh_public_key = <<-EOT + {enter SSH Public Key} +EOT + +# For DHCP, the search domain +cluster_network_search = "{enter DHCP Search domain}" #e.g spectrocloud.local + +# VMware cluster placement properties +# All fields except _vsphere\_resource\_pool_ are required fields +vsphere_datacenter = "{enter vSphere Datacenter}" +vsphere_folder = "{enter vSphere Folder}" + +vsphere_cluster = "{enter vSphere ESX Cluster}" +vsphere_resource_pool = "{enter vSphere Resource Pool}" # Leave "" blank for Cluster Resource pool +vsphere_datastore = "{enter vSphere Datastore}" +vsphere_network = "{enter vSphere Network}" diff --git a/examples/e2e/vsphere_static/variables.tf b/examples/e2e/vsphere_static/variables.tf new file mode 100644 index 00000000..43d13cfb --- /dev/null +++ b/examples/e2e/vsphere_static/variables.tf @@ -0,0 +1,37 @@ +#cluster variables +variable "vsphere_image_template_folder" {} +variable "cluster_ssh_public_key" {} +variable "cluster_network_search" {} + +variable "vsphere_datacenter" {} +variable "vsphere_folder" {} + +variable "vsphere_cluster" {} +variable "vsphere_resource_pool" {} +variable "vsphere_datastore" {} +variable "vsphere_network" {} + +# common +variable "gateway_name" { + type = string +} + +variable "ippool_name" { + type = string +} + +# cloud account variables +variable "new_vmware_cloud_account_name" {} + +variable "vsphere_vcenter" { + type = string +} + +variable "vsphere_username" { + type = string +} + +variable "vsphere_password" { + type = string + sensitive = true +} \ No newline at end of file diff --git a/pkg/client/private_cloud_gateway.go b/pkg/client/private_cloud_gateway.go index 2af11de9..6d1ab0b5 100644 --- a/pkg/client/private_cloud_gateway.go +++ b/pkg/client/private_cloud_gateway.go @@ -1,6 +1,7 @@ package client import ( + "errors" "github.com/spectrocloud/hapi/apiutil" "github.com/spectrocloud/hapi/models" clusterC "github.com/spectrocloud/hapi/spectrocluster/client/v1" @@ -20,25 +21,48 @@ func (h *V1Client) CreateIpPool(pcgUID string, pool *models.V1IPPoolInputEntity) } } +// get ip pool by uid func (h *V1Client) GetIpPool(pcgUID, poolUID string) (*models.V1IPPoolEntity, error) { + pools, err := h.GetIpPools(pcgUID) + if err != nil { + return nil, err + } + for _, pool := range pools { + if pool.Metadata.UID == poolUID { + return pool, nil + } + } + return nil, nil +} + +// get ip pool by name +func (h *V1Client) GetIpPoolByName(pcgUID, poolName string) (*models.V1IPPoolEntity, error) { + pools, err := h.GetIpPools(pcgUID) + if err != nil { + return nil, err + } + for _, pool := range pools { + if pool.Metadata.Name == poolName { + return pool, nil + } + } + return nil, errors.New("ip pool not found: " + poolName) +} + +func (h *V1Client) GetIpPools(pcgUID string) ([]*models.V1IPPoolEntity, error) { client, err := h.GetClusterClient() if err != nil { return nil, err } params := clusterC.NewV1OverlordsUIDPoolsListParams().WithUID(pcgUID) - if listResp, err := client.V1OverlordsUIDPoolsList(params); err != nil { + listResp, err := client.V1OverlordsUIDPoolsList(params) + if err != nil { if v1Err := apiutil.ToV1ErrorObj(err); v1Err.Code != "ResourceNotFound" { return nil, err } - } else if listResp.Payload != nil && listResp.Payload.Items != nil { - for _, pool := range listResp.Payload.Items { - if p := *pool; pool.Metadata.UID == poolUID { - return &p, nil - } - } } - return nil, nil + return listResp.Payload.Items, nil } func (h *V1Client) UpdateIpPool(pcgUID, poolUID string, pool *models.V1IPPoolInputEntity) error { @@ -65,6 +89,22 @@ func (h *V1Client) DeleteIpPool(pcgUID, poolUID string) error { } func (h *V1Client) GetPrivateCloudGatewayID(name *string) (string, error) { - // Need to call overload api - return "{id}", nil + client, err := h.GetClusterClient() + if err != nil { + return "", err + } + + params := clusterC.NewV1OverlordsListParams() + listResp, err := client.V1OverlordsList(params) + if err != nil { + return "", err + } + + for _, pcg := range listResp.Payload.Items { + if pcg.Metadata.Name == *name { + return pcg.Metadata.UID, nil + } + } + // return not found error + return "", errors.New("Private Cloud Gateway not found: " + *name) } diff --git a/spectrocloud/data_source_pcg_ippool.go b/spectrocloud/data_source_pcg_ippool.go new file mode 100644 index 00000000..e1853aef --- /dev/null +++ b/spectrocloud/data_source_pcg_ippool.go @@ -0,0 +1,51 @@ +package spectrocloud + +import ( + "context" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/spectrocloud/terraform-provider-spectrocloud/pkg/client" +) + +func dataSourcePrivateCloudGatewayIpPool() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceIpPoolRead, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "The name of the IP pool.", + }, + "private_cloud_gateway_id": { + Type: schema.TypeString, + Required: true, + Description: "The ID of the private cloud gateway.", + }, + }, + } +} + +func dataSourceIpPoolRead(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + c := m.(*client.V1Client) + pcgUID := d.Get("private_cloud_gateway_id").(string) + name := d.Get("name").(string) + + pool, err := c.GetIpPoolByName(pcgUID, name) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(pool.Metadata.UID) + err = d.Set("private_cloud_gateway_id", pcgUID) + if err != nil { + return diag.FromErr(err) + } + err = d.Set("name", pool.Metadata.Name) + if err != nil { + return diag.FromErr(err) + } + return diags +} diff --git a/spectrocloud/data_source_private_cloud_gateway.go b/spectrocloud/data_source_private_cloud_gateway.go index d102564f..f68ced6d 100644 --- a/spectrocloud/data_source_private_cloud_gateway.go +++ b/spectrocloud/data_source_private_cloud_gateway.go @@ -17,13 +17,13 @@ func dataSourcePCG() *schema.Resource { Computed: true, Optional: true, ConflictsWith: []string{"name"}, - Description: "The ID of Private Cloud Gateways.", + Description: "The ID of Private Cloud Gateway.", }, "name": { Type: schema.TypeString, Computed: true, Optional: true, - Description: "The Name of Private Cloud Gateways.", + Description: "The Name of Private Cloud Gateway.", }, }, } diff --git a/spectrocloud/provider.go b/spectrocloud/provider.go index bc874783..6cac6d28 100644 --- a/spectrocloud/provider.go +++ b/spectrocloud/provider.go @@ -161,7 +161,9 @@ func New(_ string) func() *schema.Provider { "spectrocloud_cluster_group": dataSourceClusterGroup(), "spectrocloud_application_profile": dataSourceApplicationProfile(), "spectrocloud_workspace": dataSourceWorkspace(), + "spectrocloud_private_cloud_gateway": dataSourcePCG(), + "spectrocloud_ippool": dataSourcePrivateCloudGatewayIpPool(), }, ConfigureContextFunc: providerConfigure, } diff --git a/spectrocloud/resource_cloud_account_vsphere_negative_test.go b/spectrocloud/resource_cloud_account_vsphere_negative_test.go index 51503667..ff759885 100644 --- a/spectrocloud/resource_cloud_account_vsphere_negative_test.go +++ b/spectrocloud/resource_cloud_account_vsphere_negative_test.go @@ -70,7 +70,7 @@ func TestFlattenVsphereCloudAccountAttributes(t *testing.T) { continue } - // Create a new schema with only the current attribute + // Create a new schema skipping the current attribute newSchema := skipSchemaAttributes(originalSchema, []string{attrName}) resourceCloudAccountVsphereWithSkippedAttrs := &schema.Resource{ diff --git a/spectrocloud/resource_macros.go b/spectrocloud/resource_macros.go index 88951fd9..eab58a16 100644 --- a/spectrocloud/resource_macros.go +++ b/spectrocloud/resource_macros.go @@ -18,7 +18,7 @@ func resourceMacro() *schema.Resource { ReadContext: resourceMacrosRead, UpdateContext: resourceMacrosUpdate, DeleteContext: resourceMacrosDelete, - Description: "A resource for creating and managing service output variables and macros.", + Description: "A resource for creating and managing service output variables and macros.", Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(10 * time.Minute),