diff --git a/civo/loadbalancer/datasource_loadbalancer.go b/civo/loadbalancer/datasource_loadbalancer.go index 63674af9..cb5f4670 100644 --- a/civo/loadbalancer/datasource_loadbalancer.go +++ b/civo/loadbalancer/datasource_loadbalancer.go @@ -165,25 +165,3 @@ func dataSourceLoadBalancerRead(_ context.Context, d *schema.ResourceData, m int return nil } - -// function to flatten the load balancer backend when is coming from the api -func flattenLoadBalancerBackend(backend []civogo.LoadBalancerBackend) []interface{} { - if backend == nil { - return nil - } - - flattenedBackend := make([]interface{}, len(backend)) - for i, back := range backend { - rawRule := map[string]interface{}{ - "ip": back.IP, - "protocol": back.Protocol, - "source_port": back.SourcePort, - "target_port": back.TargetPort, - "health_check_port": back.HealthCheckPort, - } - - flattenedBackend[i] = rawRule - } - - return flattenedBackend -} diff --git a/civo/loadbalancer/resource_loadbalancer.go b/civo/loadbalancer/resource_loadbalancer.go new file mode 100644 index 00000000..8d2a1d48 --- /dev/null +++ b/civo/loadbalancer/resource_loadbalancer.go @@ -0,0 +1,530 @@ +package loadbalancer + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "log" + "net" + "time" + + "github.com/civo/civogo" + "github.com/civo/terraform-provider-civo/internal/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +// ResourceLoadBalancer represent a load balancer in the system +func ResourceLoadBalancer() *schema.Resource { + return &schema.Resource{ + Description: "Provides a Civo load balancer resource. This can be used to create, modify, and delete load balancers.", + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "Name of the load balancer.", + ValidateFunc: validation.StringIsNotEmpty, + }, + "service_name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "Name of the service associated with the load balancer.", + ValidateFunc: validation.StringIsNotEmpty, + }, + "network_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "Network ID associated with the load balancer.", + ValidateFunc: utils.ValidateUUID, + }, + "algorithm": { + Type: schema.TypeString, + Optional: true, + Default: "round_robin", + Description: "Load balancing algorithm, either 'round_robin' or 'least_connections'.", + ValidateFunc: validation.StringInSlice([]string{"round_robin", "least_connections"}, false), + }, + "external_traffic_policy": { + Type: schema.TypeString, + Optional: true, + Description: "External traffic policy, either 'Cluster' or 'Local'.", + ValidateFunc: validation.StringInSlice([]string{"Cluster", "Local"}, false), + }, + "session_affinity": { + Type: schema.TypeString, + Optional: true, + Description: "Session affinity setting, either 'ClientIP' or 'None'.", + ValidateFunc: validation.StringInSlice([]string{"ClientIP", "None"}, false), + }, + "session_affinity_config_timeout": { + Type: schema.TypeInt, + Optional: true, + Description: "Timeout for session affinity in seconds.", + ValidateFunc: validation.IntAtLeast(0), + }, + "enable_proxy_protocol": { + Type: schema.TypeString, + Optional: true, + Description: "Enable proxy protocol, options are '', 'send-proxy', 'send-proxy-v2'.", + ValidateFunc: validation.StringInSlice([]string{"", "send-proxy", "send-proxy-v2"}, false), + }, + "max_concurrent_requests": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + Description: "Maximum concurrent requests the load balancer can handle.", + ValidateFunc: validation.IntAtLeast(1), + }, + "cluster_id": { + Type: schema.TypeString, + Optional: true, + Description: "ID of the cluster associated with the load balancer.", + ValidateFunc: utils.ValidateUUID, + }, + "firewall_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "ID of the firewall associated with the load balancer.", + ValidateFunc: utils.ValidateUUID, + }, + "firewall_rule": { + Type: schema.TypeString, + Optional: true, + Description: "Firewall rules for the load balancer (e.g., 'all', '80,443', '40-80,90-120').", + }, + "backend": { + Type: schema.TypeList, + Optional: true, + Description: "List of backend servers to be load balanced.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip": { + Type: schema.TypeString, + Required: true, + Description: "IP address of the backend server.", + ValidateFunc: validateIPAddress, + }, + "protocol": { + Type: schema.TypeString, + Optional: true, + Default: "TCP", + Description: "Protocol for backend server communication (TCP or UDP).", + ValidateFunc: validation.StringInSlice([]string{"TCP", "UDP"}, false), + }, + "source_port": { + Type: schema.TypeInt, + Required: true, + Description: "Source port for backend server.", + ValidateFunc: validation.IntBetween(1, 65535), + }, + "target_port": { + Type: schema.TypeInt, + Required: true, + Description: "Target port for backend server.", + ValidateFunc: validation.IntBetween(1, 65535), + }, + "health_check_port": { + Type: schema.TypeInt, + Optional: true, + Description: "Port used for health checks on the backend server.", + ValidateFunc: validation.IntBetween(1, 65535), + }, + }, + }, + }, + "instance_pool": { + Type: schema.TypeList, + Optional: true, + Description: "List of instance pools for the load balancer.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "tags": { + Type: schema.TypeList, + Optional: true, + Description: "List of tags for instances in the pool.", + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "names": { + Type: schema.TypeList, + Optional: true, + Description: "List of instance names in the pool.", + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "protocol": { + Type: schema.TypeString, + Optional: true, + Default: "TCP", + Description: "Protocol for instance pool communication (TCP or UDP).", + ValidateFunc: validation.StringInSlice([]string{"TCP", "UDP"}, false), + }, + "source_port": { + Type: schema.TypeInt, + Required: true, + Description: "Source port for instance pool.", + ValidateFunc: validation.IntBetween(1, 65535), + }, + "target_port": { + Type: schema.TypeInt, + Required: true, + Description: "Target port for instance pool.", + ValidateFunc: validation.IntBetween(1, 65535), + }, + "health_check": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Description: "Health check configuration for instance pool.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "port": { + Type: schema.TypeInt, + Required: true, + Description: "Port used for health checks.", + ValidateFunc: validation.IntBetween(1, 65535), + }, + "path": { + Type: schema.TypeString, + Required: true, + Description: "Path used for HTTP health checks.", + }, + }, + }, + }, + }, + }, + }, + }, + CreateContext: resourceLoadBalancerCreate, + ReadContext: resourceLoadBalancerRead, + UpdateContext: resourceLoadBalancerUpdate, + DeleteContext: resourceLoadBalancerDelete, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + } +} + +// function to create a new load balancer +func resourceLoadBalancerCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*civogo.Client) + + name := d.Get("name").(string) + + // Check that either backend or instance_pool is provided + backends, backendSet := d.GetOk("backend") + instancePools, instancePoolSet := d.GetOk("instance_pool") + + if !backendSet && !instancePoolSet { + return diag.Errorf("one of 'backend' or 'instance_pool' must be specified") + } + + // Prepare the load balancer create request + conf := &civogo.LoadBalancerConfig{ + Name: name, + } + + if v, ok := d.GetOk("service_name"); ok { + conf.ServiceName = v.(string) + } + + if v, ok := d.GetOk("firewall_id"); ok { + conf.FirewallID = v.(string) + } + + if v, ok := d.GetOk("network_id"); ok { + conf.NetworkID = v.(string) + } + + if v, ok := d.GetOk("algorithm"); ok { + conf.Algorithm = v.(string) + } + + if v, ok := d.GetOk("external_traffic_policy"); ok { + conf.ExternalTrafficPolicy = v.(string) + } + + if v, ok := d.GetOk("session_affinity"); ok { + conf.SessionAffinity = v.(string) + } + + if v, ok := d.GetOk("session_affinity_config_timeout"); ok { + conf.SessionAffinityConfigTimeout = v.(int32) + } + + if v, ok := d.GetOk("enable_proxy_protocol"); ok { + conf.EnableProxyProtocol = v.(string) + } + + if v, ok := d.GetOk("cluster_id"); ok { + conf.ClusterID = v.(string) + } + + //if v, ok := d.GetOk("max_concurrent_requests"); ok { + // conf.MaxConcurrentRequests = v.(int) + //} + + // Set backend configurations if provided + if backendSet { + for _, backend := range backends.([]interface{}) { + b := backend.(map[string]interface{}) + conf.Backends = append(conf.Backends, civogo.LoadBalancerBackendConfig{ + IP: b["ip"].(string), + Protocol: b["protocol"].(string), + SourcePort: int32(b["source_port"].(int)), + TargetPort: int32(b["target_port"].(int)), + HealthCheckPort: int32(b["health_check_port"].(int)), + }) + } + } + + // Set instance pool configurations if provided + if instancePoolSet { + for _, instancePool := range instancePools.([]interface{}) { + ip := instancePool.(map[string]interface{}) + + pool := civogo.LoadBalancerInstancePoolConfig{ + Tags: convertStringList(ip["tags"].([]interface{})), + Names: convertStringList(ip["names"].([]interface{})), + Protocol: ip["protocol"].(string), + SourcePort: int32(ip["source_port"].(int)), + TargetPort: int32(ip["target_port"].(int)), + HealthCheck: civogo.HealthCheck{ + Port: int32(ip["health_check"].([]interface{})[0].(map[string]interface{})["port"].(int)), + Path: ip["health_check"].([]interface{})[0].(map[string]interface{})["path"].(string), + }, + } + conf.InstancePools = append(conf.InstancePools, pool) + } + } + + // Send create request to API + loadBalancer, err := client.CreateLoadBalancer(conf) + if err != nil { + return diag.Errorf("error creating load balancer: %s", err) + } + + // Set the ID for the Terraform state + d.SetId(loadBalancer.ID) + + return resourceLoadBalancerRead(ctx, d, m) +} + +// function to read the load balancer +func resourceLoadBalancerRead(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*civogo.Client) + + // Retrieve the load balancer information from the API + loadBalancer, err := client.GetLoadBalancer(d.Id()) + if err != nil { + return diag.Errorf("error fetching load balancer: %s", err) + } + + // Set basic load balancer attributes + d.Set("name", loadBalancer.Name) + d.Set("service_name", loadBalancer.ServiceName) + d.Set("network_id", loadBalancer.NetworkID) + d.Set("algorithm", loadBalancer.Algorithm) + d.Set("external_traffic_policy", loadBalancer.ExternalTrafficPolicy) + d.Set("session_affinity", loadBalancer.SessionAffinity) + d.Set("session_affinity_config_timeout", loadBalancer.SessionAffinityConfigTimeout) + d.Set("enable_proxy_protocol", loadBalancer.EnableProxyProtocol) + d.Set("max_concurrent_requests", loadBalancer.MaxConcurrentRequests) + d.Set("cluster_id", loadBalancer.ClusterID) + d.Set("firewall_id", loadBalancer.FirewallID) + + fmt.Println("FIREEEE ID", loadBalancer.FirewallID) + + if err := d.Set("backend", flattenLoadBalancerBackend(loadBalancer.Backends)); err != nil { + return diag.Errorf("error setting backend: %s", err) + } + + if loadBalancer.InstancePool != nil { + flattenedInstancePool := flattenInstancePool(loadBalancer.InstancePool) + if err := d.Set("instance_pool", flattenedInstancePool); err != nil { + return diag.Errorf("error setting instance_pool: %s", err) + } + } + + return nil +} + +// function to update the load balancer +func resourceLoadBalancerUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*civogo.Client) + loadBalancerID := d.Id() + + // Initialize an update request + updateRequest := &civogo.LoadBalancerUpdateConfig{ + Name: d.Get("name").(string), + } + + // Check if any relevant fields have changed and update the request accordingly + if d.HasChange("service_name") { + updateRequest.ServiceName = d.Get("service_name").(string) + } + + if d.HasChange("firewall_id") { + updateRequest.FirewallID = d.Get("firewall_id").(string) + } + + if d.HasChange("algorithm") { + updateRequest.Algorithm = d.Get("algorithm").(string) + } + + if d.HasChange("external_traffic_policy") { + updateRequest.ExternalTrafficPolicy = d.Get("external_traffic_policy").(string) + } + + if d.HasChange("session_affinity") { + updateRequest.SessionAffinity = d.Get("session_affinity").(string) + } + + if d.HasChange("session_affinity_config_timeout") { + updateRequest.SessionAffinityConfigTimeout = int32(d.Get("session_affinity_config_timeout").(int)) + } + + if d.HasChange("enable_proxy_protocol") { + updateRequest.EnableProxyProtocol = d.Get("enable_proxy_protocol").(string) + } + + // If backend configuration has changed, update backends in the request + if d.HasChange("backend") { + backends := d.Get("backend").([]interface{}) + updateRequest.Backends = make([]civogo.LoadBalancerBackendConfig, len(backends)) + + for i, backend := range backends { + b := backend.(map[string]interface{}) + updateRequest.Backends[i] = civogo.LoadBalancerBackendConfig{ + IP: b["ip"].(string), + Protocol: b["protocol"].(string), + SourcePort: int32(b["source_port"].(int)), + TargetPort: int32(b["target_port"].(int)), + HealthCheckPort: int32(b["health_check_port"].(int)), + } + } + } + + // If instance pool configuration has changed, update instance pools in the request + if d.HasChange("instance_pool") { + instancePools := d.Get("instance_pool").([]interface{}) + updateRequest.InstancePools = make([]civogo.LoadBalancerInstancePoolConfig, len(instancePools)) + + for i, instancePool := range instancePools { + p := instancePool.(map[string]interface{}) + healthCheck := p["health_check"].([]interface{})[0].(map[string]interface{}) + + updateRequest.InstancePools[i] = civogo.LoadBalancerInstancePoolConfig{ + Tags: convertStringList(p["tags"].([]interface{})), + Names: convertStringList(p["names"].([]interface{})), + Protocol: p["protocol"].(string), + SourcePort: int32(p["source_port"].(int)), + TargetPort: int32(p["target_port"].(int)), + HealthCheck: civogo.HealthCheck{ + Port: healthCheck["port"].(int32), + Path: healthCheck["path"].(string), + }, + } + } + } + + // Send the update request to the Civo API + _, err := client.UpdateLoadBalancer(loadBalancerID, updateRequest) + if err != nil { + return diag.Errorf("error updating load balancer: %s", err) + } + + return resourceLoadBalancerRead(ctx, d, m) +} + +// function to delete the load balancer +func resourceLoadBalancerDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + apiClient := m.(*civogo.Client) + + log.Printf("[INFO] deleting the load balancer %s", d.Id()) + _, err := apiClient.DeleteLoadBalancer(d.Id()) + if err != nil { + return diag.Errorf("[ERR] an error occurred while trying to delete load balancer %s", d.Id()) + } + return nil +} + +// flattenLoadBalancerBackend converts a slice of LoadBalancerBackend structs into a slice of maps for Terraform state. +func flattenLoadBalancerBackend(backends []civogo.LoadBalancerBackend) []interface{} { + // Return nil if there are no backends + if len(backends) == 0 { + return nil + } + + // Create a slice to store each backend configuration as a map + flattenedBackends := make([]interface{}, len(backends)) + for i, backend := range backends { + // Convert each backend into a map format + backendMap := map[string]interface{}{ + "ip": backend.IP, + "protocol": backend.Protocol, + "source_port": backend.SourcePort, + "target_port": backend.TargetPort, + "health_check_port": backend.HealthCheckPort, + } + flattenedBackends[i] = backendMap + } + + return flattenedBackends +} + +// flattenInstancePool converts a slice of InstancePool structs into a slice of maps for Terraform state. +func flattenInstancePool(instancePools []civogo.InstancePool) []interface{} { + // Return nil if there are no instance pools + if len(instancePools) == 0 { + return nil + } + + // Create a slice to store each instance pool configuration as a map + flattenedInstancePools := make([]interface{}, len(instancePools)) + for i, pool := range instancePools { + // Convert HealthCheck struct into a map + healthCheckMap := map[string]interface{}{ + "port": pool.HealthCheck.Port, + "path": pool.HealthCheck.Path, + } + + // Convert each instance pool into a map format + poolMap := map[string]interface{}{ + "tags": pool.Tags, + "names": pool.Names, + "protocol": pool.Protocol, + "source_port": pool.SourcePort, + "target_port": pool.TargetPort, + "health_check": []interface{}{healthCheckMap}, // Wrap in a slice to match Terraform's nested object structure + } + flattenedInstancePools[i] = poolMap + } + + return flattenedInstancePools +} + +// Helper function for IP address validation +func validateIPAddress(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if ip := net.ParseIP(value); ip == nil { + errors = append(errors, fmt.Errorf("%q must be a valid IP address", k)) + } + return +} + +// convertStringList converts a list of interface{} to a slice of strings. +func convertStringList(input []interface{}) []string { + strList := make([]string, len(input)) + for i, v := range input { + strList[i] = v.(string) + } + return strList +} diff --git a/civo/loadbalancer/resource_loadbalancer.go.disabled b/civo/loadbalancer/resource_loadbalancer.go.disabled deleted file mode 100644 index c58d47a0..00000000 --- a/civo/loadbalancer/resource_loadbalancer.go.disabled +++ /dev/null @@ -1,302 +0,0 @@ -package civo - -import ( - "fmt" - "log" - - "github.com/civo/civogo" - "github.com/civo/terraform-provider-civo/internal/utils" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" -) - -// This resource represent a load balancer in the system -func resourceLoadBalancer() *schema.Resource { - return &schema.Resource{ - Description: "Provides a Civo load balancer resource. This can be used to create, modify, and delete load balancers.", - Schema: map[string]*schema.Schema{ - "hostname": { - Type: schema.TypeString, - Required: true, - Description: "the hostname to receive traffic for, e.g. www.example.com (optional: sets hostname to loadbalancer-uuid.civo.com if blank)", - ValidateFunc: utils.ValidateName, - }, - "protocol": { - Type: schema.TypeString, - Required: true, - Description: "either http or https. If you specify https then you must also provide the next two fields, the default is http", - ValidateFunc: validation.StringInSlice([]string{ - "http", - "https", - }, false), - }, - "tls_certificate": { - Type: schema.TypeString, - Optional: true, - Description: "if your protocol is https then you should send the TLS certificate in Base64-encoded PEM format", - }, - "tls_key": { - Type: schema.TypeString, - Optional: true, - Description: "if your protocol is https then you should send the TLS private key in Base64-encoded PEM format", - }, - "port": { - Type: schema.TypeInt, - Required: true, - Description: "you can listen on any port, the default is 80 to match the default protocol of http," + - "if not you must specify it here (commonly 80 for HTTP or 443 for HTTPS)", - ValidateFunc: validation.NoZeroValues, - }, - "max_request_size": { - Type: schema.TypeInt, - Required: true, - Description: "the size in megabytes of the maximum request content that will be accepted, defaults to 20", - ValidateFunc: validation.IntAtLeast(20), - }, - "policy": { - Type: schema.TypeString, - Required: true, - Description: "one of: least_conn (sends new requests to the least busy server), " + - "random (sends new requests to a random backend), round_robin (sends new requests to the next backend in order), " + - "ip_hash (sends requests from a given IP address to the same backend), default is random", - ValidateFunc: validation.StringInSlice([]string{ - "least_conn", - "random", - "round_robin", - "ip_hash", - }, false), - }, - "health_check_path": { - Type: schema.TypeString, - Optional: true, - Description: "what URL should be used on the backends to determine if it's OK (2xx/3xx status), defaults to /", - }, - "fail_timeout": { - Type: schema.TypeInt, - Required: true, - Description: "how long to wait in seconds before determining a backend has failed, defaults to 30", - ValidateFunc: validation.IntAtLeast(30), - }, - "max_conns": { - Type: schema.TypeInt, - Required: true, - Description: "how many concurrent connections can each backend handle, defaults to 10", - ValidateFunc: validation.IntAtLeast(10), - }, - "ignore_invalid_backend_tls": { - Type: schema.TypeBool, - Optional: true, - Description: "should self-signed/invalid certificates be ignored from the backend servers, defaults to true", - }, - "backend": { - Type: schema.TypeSet, - Required: true, - Description: "a list of backend instances, each containing an instance_id, protocol (http or https) and port", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "instance_id": { - Type: schema.TypeString, - Required: true, - ValidateFunc: utils.ValidateName, - }, - "protocol": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - "http", - "https", - }, false), - }, - "port": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.NoZeroValues, - }, - }, - }, - }, - }, - Create: resourceLoadBalancerCreate, - Read: resourceLoadBalancerRead, - Update: resourceLoadBalancerUpdate, - Delete: resourceLoadBalancerDelete, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - } -} - -// function to create a new load balancer -func resourceLoadBalancerCreate(d *schema.ResourceData, m interface{}) error { - apiClient := m.(*civogo.Client) - - log.Printf("[INFO] configuring the load balancer %s", d.Get("hostname").(string)) - conf := &civogo.LoadBalancerConfig{ - Hostname: d.Get("hostname").(string), - Protocol: d.Get("protocol").(string), - Port: d.Get("port").(int), - MaxRequestSize: d.Get("max_request_size").(int), - Policy: d.Get("policy").(string), - MaxConns: d.Get("max_conns").(int), - Backends: expandLoadBalancerBackend(d.Get("backend").(*schema.Set).List()), - } - - if v, ok := d.GetOk("tls_certificate"); ok { - conf.TLSCertificate = v.(string) - } - - if v, ok := d.GetOk("tls_key"); ok { - conf.TLSKey = v.(string) - } - - if v, ok := d.GetOk("health_check_path"); ok { - conf.HealthCheckPath = v.(string) - } - - if v, ok := d.GetOk("fail_timeout"); ok { - conf.FailTimeout = v.(int) - } - - if v, ok := d.GetOk("ignore_invalid_backend_tls"); ok { - conf.IgnoreInvalidBackendTLS = v.(bool) - } - - log.Printf("[INFO] creating the load balancer %s", d.Get("hostname").(string)) - lb, err := apiClient.CreateLoadBalancer(conf) - if err != nil { - return fmt.Errorf("[ERR] failed to create a new load balancer: %s", err) - } - - d.SetId(lb.ID) - - return resourceLoadBalancerRead(d, m) -} - -// function to read the load balancer -func resourceLoadBalancerRead(d *schema.ResourceData, m interface{}) error { - apiClient := m.(*civogo.Client) - - log.Printf("[INFO] retrieving the load balancer %s", d.Id()) - resp, err := apiClient.FindLoadBalancer(d.Id()) - if err != nil { - if resp == nil { - d.SetId("") - return nil - } - return fmt.Errorf("[ERR] error retrieving load balancer: %s", err) - } - - d.Set("hostname", resp.Hostname) - d.Set("protocol", resp.Protocol) - d.Set("tls_certificate", resp.TLSCertificate) - d.Set("tls_key", resp.TLSKey) - d.Set("port", resp.Port) - d.Set("max_request_size", resp.MaxRequestSize) - d.Set("policy", resp.Policy) - d.Set("health_check_path", resp.HealthCheckPath) - d.Set("fail_timeout", resp.FailTimeout) - d.Set("max_conns", resp.MaxConns) - d.Set("ignore_invalid_backend_tls", resp.IgnoreInvalidBackendTLS) - - if err := d.Set("backend", flattenLoadBalancerBackend(resp.Backends)); err != nil { - return fmt.Errorf("[ERR] error retrieving the backend for load balancer error: %#v", err) - } - - return nil -} - -// function to update the load balancer -func resourceLoadBalancerUpdate(d *schema.ResourceData, m interface{}) error { - apiClient := m.(*civogo.Client) - - log.Printf("[INFO] configuring the load balancer to update %s", d.Id()) - conf := &civogo.LoadBalancerConfig{ - Hostname: d.Get("hostname").(string), - Protocol: d.Get("protocol").(string), - Port: d.Get("port").(int), - MaxRequestSize: d.Get("max_request_size").(int), - Policy: d.Get("policy").(string), - MaxConns: d.Get("max_conns").(int), - FailTimeout: d.Get("fail_timeout").(int), - Backends: expandLoadBalancerBackend(d.Get("backend").(*schema.Set).List()), - } - - if d.HasChange("tls_certificate") { - conf.TLSCertificate = d.Get("tls_certificate").(string) - } - - if d.HasChange("tls_key") { - conf.TLSKey = d.Get("tls_key").(string) - } - - if d.HasChange("health_check_path") { - conf.HealthCheckPath = d.Get("health_check_path").(string) - } - - if d.HasChange("ignore_invalid_backend_tls") { - conf.IgnoreInvalidBackendTLS = d.Get("ignore_invalid_backend_tls").(bool) - } - - log.Printf("[INFO] updating the load balancer %s", d.Id()) - _, err := apiClient.UpdateLoadBalancer(d.Id(), conf) - if err != nil { - return fmt.Errorf("[ERR] failed to update load balancer: %s", err) - } - - return resourceLoadBalancerRead(d, m) - -} - -// function to delete the load balancer -func resourceLoadBalancerDelete(d *schema.ResourceData, m interface{}) error { - apiClient := m.(*civogo.Client) - - log.Printf("[INFO] deleting the load balancer %s", d.Id()) - _, err := apiClient.DeleteLoadBalancer(d.Id()) - if err != nil { - return fmt.Errorf("[ERR] an error occurred while trying to delete load balancer %s", d.Id()) - } - return nil -} - -// function to expand the load balancer backend to send to the api -func expandLoadBalancerBackend(backend []interface{}) []civogo.LoadBalancerBackendConfig { - expandedBackend := make([]civogo.LoadBalancerBackendConfig, 0, len(backend)) - for _, rawBackend := range backend { - - rule := rawBackend.(map[string]interface{}) - - r := civogo.LoadBalancerBackendConfig{ - Protocol: rule["protocol"].(string), - InstanceID: rule["instance_id"].(string), - Port: rule["port"].(int), - } - - expandedBackend = append(expandedBackend, r) - } - return expandedBackend -} - -// function to flatten the load balancer backend when is coming from the api -func flattenLoadBalancerBackend(backend []civogo.LoadBalancerBackend) []interface{} { - if backend == nil { - return nil - } - - flattenedBackend := make([]interface{}, len(backend)) - for i, back := range backend { - instanceID := back.InstanceID - protocol := back.Protocol - port := back.Port - - rawRule := map[string]interface{}{ - "instance_id": instanceID, - "protocol": protocol, - "port": port, - } - - flattenedBackend[i] = rawRule - } - - return flattenedBackend -} diff --git a/civo/loadbalancer/resource_loadbalancer_test.go.disabled b/civo/loadbalancer/resource_loadbalancer_test.go.disabled deleted file mode 100644 index 2eddc4fb..00000000 --- a/civo/loadbalancer/resource_loadbalancer_test.go.disabled +++ /dev/null @@ -1,199 +0,0 @@ -package civo - -import ( - "fmt" - "testing" - - "github.com/civo/civogo" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" -) - -var domainName = acctest.RandString(10) - -// example.Widget represents a concrete Go type that represents an API resource -func CivoLoadBalancer_basic(t *testing.T) { - var loadBalancer civogo.LoadBalancer - - // generate a random name for each test run - resName := "civo_loadbalancer.foobar" - - resource.Test(t, resource.TestCase{ - PreCheck: func() {PreCheck(t) }, - Providers: Providers, - CheckDestroy:CheckCivoLoadBalancerDestroy, - Steps: []resource.TestStep{ - { - // use a dynamic configuration with the random name from above - Config:CheckCivoLoadBalancerConfigBasic(domainName), - // compose a basic test, checking both remote and local values - Check: resource.ComposeTestCheckFunc( - // query the API to retrieve the widget object - testAccCheckCivoLoadBalancerResourceExists(resName, &loadBalancer), - // verify remote values - testAccCheckCivoLoadBalancerValues(&loadBalancer, domainName), - // verify local values - resource.TestCheckResourceAttr(resName, "protocol", "http"), - resource.TestCheckResourceAttr(resName, "port", "80"), - resource.TestCheckResourceAttr(resName, "max_request_size", "30"), - resource.TestCheckResourceAttr(resName, "policy", "round_robin"), - resource.TestCheckResourceAttr(resName, "health_check_path", "/"), - resource.TestCheckResourceAttr(resName, "max_conns", "10"), - ), - }, - }, - }) -} - -func CivoLoadBalancer_update(t *testing.T) { - var loadBalancer civogo.LoadBalancer - - // generate a random name for each test run - resName := "civo_loadbalancer.foobar" - - resource.Test(t, resource.TestCase{ - PreCheck: func() {PreCheck(t) }, - Providers: Providers, - CheckDestroy:CheckCivoFirewallRuleDestroy, - Steps: []resource.TestStep{ - { - Config:CheckCivoLoadBalancerConfigBasic(domainName), - Check: resource.ComposeTestCheckFunc( - testAccCheckCivoLoadBalancerResourceExists(resName, &loadBalancer), - resource.TestCheckResourceAttr(resName, "protocol", "http"), - resource.TestCheckResourceAttr(resName, "port", "80"), - resource.TestCheckResourceAttr(resName, "max_request_size", "30"), - resource.TestCheckResourceAttr(resName, "policy", "round_robin"), - resource.TestCheckResourceAttr(resName, "health_check_path", "/"), - resource.TestCheckResourceAttr(resName, "max_conns", "10"), - ), - }, - { - // use a dynamic configuration with the random name from above - Config:CheckCivoLoadBalancerConfigUpdates(domainName), - Check: resource.ComposeTestCheckFunc( - testAccCheckCivoLoadBalancerResourceExists(resName, &loadBalancer), - testAccCheckCivoLoadBalancerUpdated(&loadBalancer, domainName), - resource.TestCheckResourceAttr(resName, "protocol", "http"), - resource.TestCheckResourceAttr(resName, "port", "80"), - resource.TestCheckResourceAttr(resName, "max_request_size", "50"), - resource.TestCheckResourceAttr(resName, "policy", "round_robin"), - resource.TestCheckResourceAttr(resName, "health_check_path", "/"), - resource.TestCheckResourceAttr(resName, "max_conns", "100"), - ), - }, - }, - }) -} - -funcCheckCivoLoadBalancerValues(loadBalancer *civogo.LoadBalancer, name string) resource.TestCheckFunc { - return func(s *terraform.State) error { - if loadBalancer.Hostname != name { - return fmt.Errorf("bad protocol, expected \"%s\", got: %#v", name, loadBalancer.Hostname) - } - return nil - } -} - -//CheckExampleResourceExists queries the API and retrieves the matching Widget. -funcCheckCivoLoadBalancerResourceExists(n string, loadBalancer *civogo.LoadBalancer) resource.TestCheckFunc { - return func(s *terraform.State) error { - // find the corresponding state object - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - // retrieve the configured client from the test setup - client :=Provider.Meta().(*civogo.Client) - resp, err := client.FindLoadBalancer(rs.Primary.ID) - if err != nil { - return fmt.Errorf("LoadBalancer not found: (%s) %s", rs.Primary.ID, err) - } - - // If no error, assign the response Widget attribute to the widget pointer - *loadBalancer = *resp - - // return fmt.Errorf("Domain (%s) not found", rs.Primary.ID) - return nil - } -} - -funcCheckCivoLoadBalancerUpdated(loadBalancer *civogo.LoadBalancer, name string) resource.TestCheckFunc { - return func(s *terraform.State) error { - if loadBalancer.Hostname != fmt.Sprintf("rename-%s", name) { - return fmt.Errorf("bad protocol, expected \"%s\", got: %#v", fmt.Sprintf("rename-%s", name), loadBalancer.Hostname) - } - return nil - } -} - -funcCheckCivoLoadBalancerDestroy(s *terraform.State) error { - client :=Provider.Meta().(*civogo.Client) - - for _, rs := range s.RootModule().Resources { - if rs.Type != "civo_loadbalancer" { - continue - } - - _, err := client.FindLoadBalancer(rs.Primary.ID) - if err == nil { - return fmt.Errorf("LoadBlanacer still exists") - } - } - - return nil -} - -funcCheckCivoLoadBalancerConfigBasic(name string) string { - return fmt.Sprintf(` -resource "civo_instance" "vm" { - hostname = "instance-%s" -} - -resource "civo_loadbalancer" "foobar" { - hostname = "%s" - protocol = "http" - port = 80 - max_request_size = 30 - policy = "round_robin" - health_check_path = "/" - max_conns = 10 - fail_timeout = 40 - depends_on = [civo_instance.vm] - - backend { - instance_id = civo_instance.vm.id - protocol = "http" - port = 80 - } -} -`, name, name) -} - -funcCheckCivoLoadBalancerConfigUpdates(name string) string { - return fmt.Sprintf(` -resource "civo_instance" "vm" { - hostname = "instance-%s" -} - -resource "civo_loadbalancer" "foobar" { - hostname = "rename-%s" - protocol = "http" - port = 80 - max_request_size = 50 - policy = "round_robin" - health_check_path = "/" - max_conns = 100 - fail_timeout = 40 - depends_on = [civo_instance.vm] - - backend { - instance_id = civo_instance.vm.id - protocol = "http" - port = 80 - } -} -`, name, name) -} diff --git a/civo/provider.go b/civo/provider.go index 7c6f662d..a69df91c 100644 --- a/civo/provider.go +++ b/civo/provider.go @@ -107,6 +107,7 @@ func Provider() *schema.Provider { "civo_object_store": objectstorage.ResourceObjectStore(), "civo_object_store_credential": objectstorage.ResourceObjectStoreCredential(), "civo_database": database.ResourceDatabase(), + "civo_load_balancer": loadbalancer.ResourceLoadBalancer(), }, ConfigureFunc: providerConfigure, } diff --git a/go.mod b/go.mod index 0aa0bd4e..ac95c54e 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/civo/terraform-provider-civo require ( - github.com/civo/civogo v0.3.85 + github.com/civo/civogo v0.3.86 github.com/google/uuid v1.3.1 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-sdk/v2 v2.31.0 @@ -78,4 +78,6 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) +replace github.com/civo/civogo => /Users/uzair/Work/civogo + go 1.21 diff --git a/go.sum b/go.sum index 4f0bc2f4..560fefa2 100644 --- a/go.sum +++ b/go.sum @@ -12,10 +12,6 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmms github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/civo/civogo v0.3.84 h1:jf5IT7VJFPaReO6g8B0zqKhsYCIizaGo4PjDLY7Sl6Y= -github.com/civo/civogo v0.3.84/go.mod h1:7UCYX+qeeJbrG55E1huv+0ySxcHTqq/26FcHLVelQJM= -github.com/civo/civogo v0.3.85 h1:rXRbDT9B60Ocp/IxAoAs90yAJog2la1MajQqgXETxd4= -github.com/civo/civogo v0.3.85/go.mod h1:7UCYX+qeeJbrG55E1huv+0ySxcHTqq/26FcHLVelQJM= github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=