diff --git a/CHANGELOG.md b/CHANGELOG.md index 57aa4bdb7..45d4d7244 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ - Add bootvolume_selector to `https://docs.ionos.com/ - Add servers to `https://docs.ionos.com/` - Add cube server and vcpu server to `https://docs.ionos.com/` +### Enhancement +- Add `allow_replace` to `ionoscloud_server` and `ionoscloud_cube_server` resources, which allows the update of immutable server fields by destroying and then re-creating the resource. This field should be used with care, understanding the risks. +### Fixes +- All `id` and `name` fields in data sources need to be computed, so value can be read on first apply. +- Refactor `ionoscloud_share` and `ionoscloud_nic` data sources +### Testing +- Fix template test +- Remove cpu_family from server test +- Add basic NFS tests ## 6.6.2 ### Features @@ -24,14 +33,6 @@ - Add import tests for VPN Gateway resources - Add `security_groups_ids` to `ionoscloud_server`, `ionoscloud_cube_server`, `ionoscloud_nic`, `ionoscloud_vcpu_server` resources and data sources -### Fixes -- All `id` and `name` fields in data sources need to be computed, so value can be read on first apply. -- Refactor `ionoscloud_share` and `ionoscloud_nic` data sources - -### Testing -- Fix template test -- Remove cpu_family from server test - ### New Product - **Network Security Groups**: - `Resources`: - [ionoscloud_nsg](docs/resources/nsg.md) diff --git a/README.md b/README.md index 6318840ba..7304b7cc3 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,6 @@ The IonosCloud provider gives the ability to deploy and configure resources using the IonosCloud APIs. ---- -### Warning: API Basic Authentication Deprecation Notice -Effective March 15, 2024, IONOS account holders using 2-Factor Authentication will no longer be able to utilize Basic Authentication for accessing our APIs, SDKs, and all related tools. Token creation and deletion via APIs and ionosCTL will also be restricted. - -Affected users are required to switch to token-based authorization. These tokens will be accessible through our new Token Manager in the Data Center Designer, launching at the beginning of February 2024. More information can be found [here](https://docs.ionos.com/cloud/getting-started/basic-tutorials/deprecation-basic-authentication/basic-authentication-deprecation-faqs). - --- ## Requirements @@ -248,4 +242,7 @@ now you can see the response body incl. api error message: ## Frequently Asked Questions ### How can I find out the IP for the added NIC on a K8s nodepool? - Please check out this [module](https://github.com/ionos-cloud/terraform-ionoscloud-kube-lan-ip). \ No newline at end of file + Please check out this [module](https://github.com/ionos-cloud/terraform-ionoscloud-kube-lan-ip). + + ### For more complex examples on how to use the provider + Please check out the [examples](examples/) directory. \ No newline at end of file diff --git a/docs/resources/cube_server.md b/docs/resources/cube_server.md index b57002db8..576fdc22c 100644 --- a/docs/resources/cube_server.md +++ b/docs/resources/cube_server.md @@ -136,6 +136,9 @@ resource "random_password" "server_image_password" { - `ssh_key_path` - (Optional)[list] List of paths to files containing a public SSH key that will be injected into IonosCloud provided Linux images. Required for IonosCloud Linux images. Required if `image_password` is not provided. - `image_password` - (Optional)[string] Required if `ssh_key_path` is not provided. - `security_groups_ids` - (Optional) The list of Security Group IDs for the resource. +- `allow_replace` - (Optional)[bool] When set to true, allows the update of immutable fields by first destroying and then re-creating the server. + +⚠️ **_Warning: `allow_replace` - lets you update immutable fields, but it first destroys and then re-creates the server in order to do it. This field should be used with care, understanding the risks._** > **⚠ WARNING** > diff --git a/docs/resources/server.md b/docs/resources/server.md index 3f24cde61..040cef4c1 100644 --- a/docs/resources/server.md +++ b/docs/resources/server.md @@ -288,6 +288,9 @@ resource "ionoscloud_server" "test" { - `value` - (Required)[string] The value of the label. - `inline_volume_ids` - (Computed) A list with the IDs for the volumes that are defined inside the server resource. - `security_groups_ids` - (Optional) The list of Security Group IDs for the +- `allow_replace` - (Optional)[bool] When set to true, allows the update of immutable fields by first destroying and then re-creating the server. + +⚠️ **_Warning: `allow_replace` - lets you update immutable fields, but it first destroys and then re-creates the server in order to do it. This field should be used with care, understanding the risks._** > **⚠ WARNING** > diff --git a/gitbook_docs/README.md b/gitbook_docs/README.md index 884edc356..c0b694c7c 100644 --- a/gitbook_docs/README.md +++ b/gitbook_docs/README.md @@ -85,4 +85,7 @@ now you can see the response body incl. api error message: "message" : "[VDC-yy-xxxx] Operation cannot be executed since this Kubernetes Nodepool is already marked for deletion. Current state of the resource is FAILED_DESTROYING." }] } -``` \ No newline at end of file +``` + +### For more complex examples on how to use the provider +Please check out the [examples](examples/) directory. \ No newline at end of file diff --git a/ionoscloud/import_server_test.go b/ionoscloud/import_server_test.go index c397fad01..0afb0011c 100644 --- a/ionoscloud/import_server_test.go +++ b/ionoscloud/import_server_test.go @@ -28,7 +28,7 @@ func TestAccServerImportBasic(t *testing.T) { ImportStateIdFunc: testAccServerImportStateIdWithNicAndFw, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"image_password", "ssh_key_path.#", "image_name", "volume.0.user_data", "volume.0.backup_unit_id", "firewallrule_id", "primary_nic", "inline_volume_ids"}, + ImportStateVerifyIgnore: []string{"image_password", "ssh_key_path.#", "image_name", "volume.0.user_data", "volume.0.backup_unit_id", "firewallrule_id", "primary_nic", "inline_volume_ids", "allow_replace"}, }, }, }) diff --git a/ionoscloud/resource_cube_server.go b/ionoscloud/resource_cube_server.go index 611beaa3c..17b20c185 100644 --- a/ionoscloud/resource_cube_server.go +++ b/ionoscloud/resource_cube_server.go @@ -39,6 +39,7 @@ func resourceCubeServer() *schema.Resource { "template_uuid": { Type: schema.TypeString, Required: true, + ForceNew: true, }, "name": { Type: schema.TypeString, @@ -56,6 +57,7 @@ func resourceCubeServer() *schema.Resource { Type: schema.TypeString, Optional: true, Computed: true, + ForceNew: true, ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"AUTO", "ZONE_1", "ZONE_2"}, true)), }, "boot_volume": { @@ -103,6 +105,7 @@ func resourceCubeServer() *schema.Resource { Type: schema.TypeString, Optional: true, Computed: true, + ForceNew: true, }, "ssh_key_path": { Type: schema.TypeList, @@ -126,6 +129,7 @@ func resourceCubeServer() *schema.Resource { "disk_type": { Type: schema.TypeString, Required: true, + ForceNew: true, ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotWhiteSpace), }, "image_password": { @@ -180,6 +184,7 @@ func resourceCubeServer() *schema.Resource { Type: schema.TypeString, Optional: true, Computed: true, + ForceNew: true, ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"AUTO", "ZONE_1", "ZONE_2", "ZONE_3"}, true)), }, "cpu_hot_plug": { @@ -215,12 +220,14 @@ func resourceCubeServer() *schema.Resource { Description: "The uuid of the Backup Unit that user has access to. The property is immutable and is only allowed to be set on a new volume creation. It is mandatory to provide either 'public image' or 'imageAlias' in conjunction with this property.", Optional: true, Computed: true, + ForceNew: true, }, "user_data": { Type: schema.TypeString, Description: "The cloud-init configuration for the volume as base64 encoded string. The property is immutable and is only allowed to be set on a new volume creation. It is mandatory to provide either 'public image' or 'imageAlias' that has cloud-init compatibility in conjunction with this property.", Optional: true, Computed: true, + ForceNew: true, }, "pci_slot": { Type: schema.TypeInt, @@ -375,6 +382,12 @@ func resourceCubeServer() *schema.Resource { Type: schema.TypeString, }, }, + "allow_replace": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "When set to true, allows the update of immutable fields by destroying and re-creating the resource.", + }, }, Timeouts: &resourceDefaultTimeouts, } diff --git a/ionoscloud/resource_networkloadbalancer_test.go b/ionoscloud/resource_networkloadbalancer_test.go index e2ac91ef9..e41c6ffb2 100644 --- a/ionoscloud/resource_networkloadbalancer_test.go +++ b/ionoscloud/resource_networkloadbalancer_test.go @@ -54,7 +54,7 @@ func TestAccNetworkLoadBalancerBasic(t *testing.T) { resource.TestCheckResourceAttr(networkLoadBalancerResource, "name", constant.NetworkLoadBalancerTestResource), resource.TestCheckResourceAttr(networkLoadBalancerResource, "ips.0", "10.12.118.224"), resource.TestCheckResourceAttr(networkLoadBalancerResource, "lb_private_ips.0", "10.13.72.225/24"), - resource.TestCheckResourceAttr(networkLoadBalancerResource, "central_logging", true), + resource.TestCheckResourceAttr(networkLoadBalancerResource, "central_logging", "true"), resource.TestCheckResourceAttr(networkLoadBalancerResource, "logging_format", `%{+Q}o %{-Q}ci - - [%trg] %r %ST %B "" "" %cp %ms %ft %b %s %TR %Tw %Tc %Tr %Ta %tsc %ac %fc %bc %sc %rc %sq %bq %CC %CS %hrl %hsl`), resource.TestCheckResourceAttrPair(networkLoadBalancerResource, "listener_lan", constant.LanResource+".nlb_lan_1", "id"), resource.TestCheckResourceAttrPair(networkLoadBalancerResource, "target_lan", constant.LanResource+".nlb_lan_2", "id"), @@ -72,7 +72,7 @@ func TestAccNetworkLoadBalancerBasic(t *testing.T) { resource.TestCheckResourceAttrPair(networkLoadBalancerResource, "ips", dataSourceNetworkLoadBalancerId, "ips"), resource.TestCheckResourceAttrPair(networkLoadBalancerResource, "target_lan", dataSourceNetworkLoadBalancerId, "target_lan"), resource.TestCheckResourceAttrPair(networkLoadBalancerResource, "lb_private_ips", dataSourceNetworkLoadBalancerId, "lb_private_ips"), - resource.TestCheckResourceAttrPair(networkLoadBalancerResource, "central_logging", true, "central_logging"), + resource.TestCheckResourceAttrPair(networkLoadBalancerResource, "central_logging", "true", "central_logging"), resource.TestCheckResourceAttrPair(networkLoadBalancerResource, "logging_format", `%{+Q}o %{-Q}ci - - [%trg] %r %ST %B "" "" %cp %ms %ft %b %s %TR %Tw %Tc %Tr %Ta %tsc %ac %fc %bc %sc %rc %sq %bq %CC %CS %hrl %hsl`, "logging_format"), resource.TestCheckResourceAttrPair(dataSourceNetworkLoadBalancerId, "flowlog.0.name", networkLoadBalancerResource, "flowlog.0.name"), resource.TestCheckResourceAttrPair(dataSourceNetworkLoadBalancerId, "flowlog.0.action", networkLoadBalancerResource, "flowlog.0.action"), @@ -102,7 +102,7 @@ func TestAccNetworkLoadBalancerBasic(t *testing.T) { resource.TestCheckResourceAttr(networkLoadBalancerResource, "ips.1", "10.12.119.224"), resource.TestCheckResourceAttr(networkLoadBalancerResource, "lb_private_ips.0", "10.13.72.225/24"), resource.TestCheckResourceAttr(networkLoadBalancerResource, "lb_private_ips.1", "10.13.73.225/24"), - resource.TestCheckResourceAttr(networkLoadBalancerResource, "central_logging", false), + resource.TestCheckResourceAttr(networkLoadBalancerResource, "central_logging", "false"), resource.TestCheckResourceAttr(networkLoadBalancerResource, "logging_format", `%{+Q}o %{-Q}ci - - [%trg] %r %ST %B "" "" %cp %ms %ft %b %s %TR %Tw %Tc %Tr %Ta %tsc %ac %fc %bc %sc %rc %sq %bq %CC %CS %hrl %hsl`), resource.TestCheckResourceAttrPair(networkLoadBalancerResource, "listener_lan", constant.LanResource+".nlb_lan_3", "id"), resource.TestCheckResourceAttrPair(networkLoadBalancerResource, "target_lan", constant.LanResource+".nlb_lan_4", "id"), diff --git a/ionoscloud/resource_nfs_cluster_test.go b/ionoscloud/resource_nfs_cluster_test.go new file mode 100644 index 000000000..cef74b804 --- /dev/null +++ b/ionoscloud/resource_nfs_cluster_test.go @@ -0,0 +1,219 @@ +//go:build all || nfs || nfs_cluster + +package ionoscloud + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/ionos-cloud/terraform-provider-ionoscloud/v6/services" + "github.com/ionos-cloud/terraform-provider-ionoscloud/v6/utils/constant" +) + +func TestAccNFSClusterBasic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + ExternalProviders: map[string]resource.ExternalProvider{ + "random": { + VersionConstraint: "3.4.3", + Source: "hashicorp/random", + }, + "time": { + Source: "hashicorp/time", + VersionConstraint: "0.11.1", + }, + }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckNFSClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckNFSClusterConfigBasic, + Check: resource.ComposeTestCheckFunc( + testAccCheckNFSClusterExists("ionoscloud_nfs_cluster.example"), + resource.TestCheckResourceAttr("ionoscloud_nfs_cluster.example", "name", "example"), + resource.TestCheckResourceAttr("ionoscloud_nfs_cluster.example", "location", "de/txl"), + resource.TestCheckResourceAttr("ionoscloud_nfs_cluster.example", "size", "2"), + resource.TestCheckResourceAttr("ionoscloud_nfs_cluster.example", "nfs.0.min_version", "4.2"), + ), + }, + { + Config: testAccCheckNFSClusterConfigUpdate, + Check: resource.ComposeTestCheckFunc( + testAccCheckNFSClusterExists("ionoscloud_nfs_cluster.example"), + resource.TestCheckResourceAttr("ionoscloud_nfs_cluster.example", "name", "example_updated"), + resource.TestCheckResourceAttr("ionoscloud_nfs_cluster.example", "location", "de/txl"), + resource.TestCheckResourceAttr("ionoscloud_nfs_cluster.example", "size", "2"), + resource.TestCheckResourceAttr("ionoscloud_nfs_cluster.example", "nfs.0.min_version", "4.2"), + ), + }, + { + Config: testAccDataSourceNFSClusterMatchName, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair("data.ionoscloud_nfs_cluster.data_with_name", "name", "ionoscloud_nfs_cluster.example", "name"), + resource.TestCheckResourceAttrPair("data.ionoscloud_nfs_cluster.data_with_name", "location", "ionoscloud_nfs_cluster.example", "location"), + ), + }, + { + Config: testAccDataSourceNFSClusterPartialMatchName, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair("data.ionoscloud_nfs_cluster.data_with_name", "name", "ionoscloud_nfs_cluster.example", "name"), + resource.TestCheckResourceAttrPair("data.ionoscloud_nfs_cluster.data_with_name", "location", "ionoscloud_nfs_cluster.example", "location"), + ), + }, + }, + }) +} + +func testAccCheckNFSClusterDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(services.SdkBundle).NFSClient + + for _, rs := range s.RootModule().Resources { + if rs.Type != constant.NFSClusterResource { + continue + } + + _, resp, err := client.GetNFSClusterByID(context.Background(), rs.Primary.ID, rs.Primary.Attributes["location"]) + if resp != nil && resp.StatusCode != 404 { + return fmt.Errorf("NFS Cluster still exists: %s", rs.Primary.ID) + } + if err != nil { + return fmt.Errorf("error fetching NFS Cluster with ID %s: %v", rs.Primary.ID, err) + } + } + + return nil +} + +func testAccCheckNFSClusterExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := testAccProvider.Meta().(services.SdkBundle).NFSClient + + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("not found: %s", n) + } + if rs.Primary.ID == "" { + return fmt.Errorf("no ID is set") + } + + ctx, cancel := context.WithTimeout(context.Background(), *resourceDefaultTimeouts.Default) + defer cancel() + + found, _, err := client.GetNFSClusterByID(ctx, rs.Primary.ID, rs.Primary.Attributes["location"]) + if err != nil { + return fmt.Errorf("an error occurred while fetching NFS Cluster with ID: %v, error: %w", rs.Primary.ID, err) + } + if *found.Id != rs.Primary.ID { + return fmt.Errorf("resource not found") + } + + return nil + } +} + +const testAccCheckNFSClusterConfig = ` +resource "ionoscloud_datacenter" "nfs_dc" { + name = "NFS Datacenter" + location = "de/txl" + description = "Datacenter Description" + sec_auth_protection = false +} + +resource "ionoscloud_lan" "nfs_lan" { + datacenter_id = ionoscloud_datacenter.nfs_dc.id + public = false + name = "Lan for NFS" +} + +data "ionoscloud_image" "HDD_image" { + image_alias = "ubuntu:20.04" + type = "HDD" + cloud_init = "V1" + location = "de/txl" +} + +resource "random_password" "password" { + length = 16 + special = false +} + +resource "ionoscloud_server" "nfs_server" { + name = "Server for NFS" + datacenter_id = ionoscloud_datacenter.nfs_dc.id + cores = 1 + ram = 2048 + availability_zone = "ZONE_1" + cpu_family = "INTEL_SKYLAKE" + image_name = data.ionoscloud_image.HDD_image.id + image_password = random_password.password.result + + volume { + name = "system" + size = 14 + disk_type = "SSD" + } + + nic { + name = "NIC A" + lan = ionoscloud_lan.nfs_lan.id + dhcp = true + firewall_active = true + } +} +` + +const testAccCheckNFSClusterConfigBasic = testAccCheckNFSClusterConfig + ` +resource "ionoscloud_nfs_cluster" "example" { + name = "example" + location = "de/txl" + size = 2 + + nfs { + min_version = "4.2" + } + + connections { + datacenter_id = ionoscloud_datacenter.nfs_dc.id + ip_address = format("%s/24", ionoscloud_server.nfs_server.nic[0].ips[0]) + lan = ionoscloud_lan.nfs_lan.id + } +} +` + +const testAccCheckNFSClusterConfigUpdate = testAccCheckNFSClusterConfig + ` +resource "ionoscloud_nfs_cluster" "example" { + name = "example_updated" + location = "de/txl" + size = 2 + + nfs { + min_version = "4.2" + } + + connections { + datacenter_id = ionoscloud_datacenter.nfs_dc.id + ip_address = format("%s/24", ionoscloud_server.nfs_server.nic[0].ips[0]) + lan = ionoscloud_lan.nfs_lan.id + } +} +` + +const testAccDataSourceNFSClusterMatchName = testAccCheckNFSClusterConfigUpdate + ` +data "ionoscloud_nfs_cluster" "data_with_name" { + location = ionoscloud_nfs_cluster.example.location + name = "example_updated" +} +` + +const testAccDataSourceNFSClusterPartialMatchName = testAccCheckNFSClusterConfigUpdate + ` +data "ionoscloud_nfs_cluster" "data_with_name" { + location = ionoscloud_nfs_cluster.example.location + name = "example_" + partial_match = true +} +` diff --git a/ionoscloud/resource_nfs_share_test.go b/ionoscloud/resource_nfs_share_test.go new file mode 100644 index 000000000..36d5d5e6d --- /dev/null +++ b/ionoscloud/resource_nfs_share_test.go @@ -0,0 +1,171 @@ +//go:build all || nfs || nfs_share + +package ionoscloud + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/ionos-cloud/terraform-provider-ionoscloud/v6/services" +) + +func TestAccNFSShareBasic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + ExternalProviders: map[string]resource.ExternalProvider{ + "random": { + VersionConstraint: "3.4.3", + Source: "hashicorp/random", + }, + "time": { + Source: "hashicorp/time", + VersionConstraint: "0.11.1", + }, + }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckNFSShareDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckNFSShareConfigBasic, + Check: resource.ComposeTestCheckFunc( + testAccCheckNFSShareExists("ionoscloud_nfs_share.example"), + resource.TestCheckResourceAttr("ionoscloud_nfs_share.example", "name", "example-share"), + resource.TestCheckResourceAttr("ionoscloud_nfs_share.example", "quota", "512"), + resource.TestCheckResourceAttr("ionoscloud_nfs_share.example", "gid", "512"), + resource.TestCheckResourceAttr("ionoscloud_nfs_share.example", "uid", "512"), + resource.TestCheckResourceAttr("ionoscloud_nfs_share.example", "client_groups.0.description", "Client Group 1"), + resource.TestCheckResourceAttr("ionoscloud_nfs_share.example", "client_groups.0.ip_networks.0", "10.234.50.0/24"), + resource.TestCheckResourceAttr("ionoscloud_nfs_share.example", "client_groups.0.hosts.0", "10.234.62.123"), + resource.TestCheckResourceAttr("ionoscloud_nfs_share.example", "client_groups.0.nfs.0.squash", "all-anonymous"), + ), + }, + { + Config: testAccCheckNFSShareConfigUpdate, + Check: resource.ComposeTestCheckFunc( + testAccCheckNFSShareExists("ionoscloud_nfs_share.example"), + resource.TestCheckResourceAttr("ionoscloud_nfs_share.example", "name", "example-share-updated"), + resource.TestCheckResourceAttr("ionoscloud_nfs_share.example", "quota", "1024"), + resource.TestCheckResourceAttr("ionoscloud_nfs_share.example", "gid", "1024"), + resource.TestCheckResourceAttr("ionoscloud_nfs_share.example", "uid", "1024"), + resource.TestCheckResourceAttr("ionoscloud_nfs_share.example", "client_groups.0.description", "Client Group 1 Updated"), + resource.TestCheckResourceAttr("ionoscloud_nfs_share.example", "client_groups.0.ip_networks.0", "10.234.50.0/24"), + resource.TestCheckResourceAttr("ionoscloud_nfs_share.example", "client_groups.0.hosts.0", "10.234.62.124"), + resource.TestCheckResourceAttr("ionoscloud_nfs_share.example", "client_groups.0.nfs.0.squash", "root-anonymous"), + ), + }, + { + Config: testAccDataSourceNFSShareMatchId, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair("data.ionoscloud_nfs_share.share_data_example", "name", "ionoscloud_nfs_share.example", "name"), + resource.TestCheckResourceAttrPair("data.ionoscloud_nfs_share.share_data_example", "quota", "ionoscloud_nfs_share.example", "quota"), + resource.TestCheckResourceAttrPair("data.ionoscloud_nfs_share.share_data_example", "gid", "ionoscloud_nfs_share.example", "gid"), + resource.TestCheckResourceAttrPair("data.ionoscloud_nfs_share.share_data_example", "uid", "ionoscloud_nfs_share.example", "uid"), + ), + }, + }, + }) +} + +func testAccCheckNFSShareDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(services.SdkBundle).NFSClient + + for _, rs := range s.RootModule().Resources { + if rs.Type != "ionoscloud_nfs_share" { + continue + } + + _, resp, err := client.GetNFSShareByID(context.Background(), rs.Primary.Attributes["cluster_id"], rs.Primary.ID, rs.Primary.Attributes["location"]) + if !resp.HttpNotFound() { + return fmt.Errorf("NFS Share still exists: %s, clusterID %s", rs.Primary.ID, rs.Primary.Attributes["cluster_id"]) + } + if err != nil { + return fmt.Errorf("error fetching NFS Share with ID %s: %v", rs.Primary.ID, err) + } + } + + return nil +} + +func testAccCheckNFSShareExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := testAccProvider.Meta().(services.SdkBundle).NFSClient + + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("not found: %s", n) + } + if rs.Primary.ID == "" { + return fmt.Errorf("no ID is set") + } + + ctx, cancel := context.WithTimeout(context.Background(), *resourceDefaultTimeouts.Default) + defer cancel() + + found, _, err := client.GetNFSShareByID(ctx, rs.Primary.Attributes["cluster_id"], rs.Primary.ID, rs.Primary.Attributes["location"]) + if err != nil { + return fmt.Errorf("an error occurred while fetching NFS Share with ID: %v, error: %w", rs.Primary.ID, err) + } + if *found.Id != rs.Primary.ID { + return fmt.Errorf("resource not found") + } + + return nil + } +} + +const testAccCheckNFSShareConfigBasic = testAccCheckNFSClusterConfigBasic + ` +resource "ionoscloud_nfs_share" "example" { + location = ionoscloud_nfs_cluster.example.location + cluster_id = ionoscloud_nfs_cluster.example.id + + name = "example-share" + quota = 512 + gid = 512 + uid = 512 + + client_groups { + description = "Client Group 1" + ip_networks = ["10.234.50.0/24"] + hosts = ["10.234.62.123"] + + nfs { + squash = "all-anonymous" + } + } +} +` + +const testAccCheckNFSShareConfigUpdate = testAccCheckNFSClusterConfigBasic + ` +resource "ionoscloud_nfs_share" "example" { + location = ionoscloud_nfs_cluster.example.location + cluster_id = ionoscloud_nfs_cluster.example.id + + name = "example-share-updated" + quota = 1024 + gid = 1024 + uid = 1024 + + client_groups { + description = "Client Group 1 Updated" + ip_networks = ["10.234.50.0/24"] + hosts = ["10.234.62.124"] + + nfs { + squash = "root-anonymous" + } + } +} +` + +const testAccDataSourceNFSShareMatchId = testAccCheckNFSShareConfigUpdate + ` +data "ionoscloud_nfs_share" "share_data_example" { + location = ionoscloud_nfs_cluster.example.location + cluster_id = ionoscloud_nfs_cluster.example.id + id = ionoscloud_nfs_share.example.id +} +` diff --git a/ionoscloud/resource_server.go b/ionoscloud/resource_server.go index b01c529e0..71050e274 100644 --- a/ionoscloud/resource_server.go +++ b/ionoscloud/resource_server.go @@ -40,6 +40,7 @@ func resourceServer() *schema.Resource { "template_uuid": { Type: schema.TypeString, Optional: true, + ForceNew: true, }, "name": { Type: schema.TypeString, @@ -67,6 +68,7 @@ func resourceServer() *schema.Resource { Type: schema.TypeString, Optional: true, Computed: true, + ForceNew: true, ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"AUTO", "ZONE_1", "ZONE_2"}, true)), }, "boot_volume": { @@ -138,6 +140,7 @@ func resourceServer() *schema.Resource { Type: schema.TypeString, Optional: true, Computed: true, + ForceNew: true, }, "ssh_key_path": { Type: schema.TypeList, @@ -192,6 +195,7 @@ func resourceServer() *schema.Resource { "disk_type": { Type: schema.TypeString, Required: true, + ForceNew: true, ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotWhiteSpace), }, "image_password": { @@ -273,6 +277,7 @@ func resourceServer() *schema.Resource { Type: schema.TypeString, Optional: true, Computed: true, + ForceNew: true, ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"AUTO", "ZONE_1", "ZONE_2", "ZONE_3"}, true)), }, "cpu_hot_plug": { @@ -308,12 +313,14 @@ func resourceServer() *schema.Resource { Description: "The uuid of the Backup Unit that user has access to. The property is immutable and is only allowed to be set on a new volume creation. It is mandatory to provide either 'public image' or 'imageAlias' in conjunction with this property.", Optional: true, Computed: true, + ForceNew: true, }, "user_data": { Type: schema.TypeString, Description: "The cloud-init configuration for the volume as base64 encoded string. The property is immutable and is only allowed to be set on a new volume creation. It is mandatory to provide either 'public image' or 'imageAlias' that has cloud-init compatibility in conjunction with this property.", Optional: true, Computed: true, + ForceNew: true, }, "pci_slot": { Type: schema.TypeInt, @@ -493,13 +500,23 @@ func resourceServer() *schema.Resource { Type: schema.TypeString, }, }, + "allow_replace": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "When set to true, allows the update of immutable fields by destroying and re-creating the resource.", + }, }, Timeouts: &resourceDefaultTimeouts, } } func checkServerImmutableFields(_ context.Context, diff *schema.ResourceDiff, _ interface{}) error { - + allowReplace := diff.Get("allow_replace").(bool) + // allows the immutable fields to be updated + if allowReplace { + return nil + } // we do not want to check in case of resource creation if diff.Id() == "" { return nil