From 6da1be357cc88cfca7fa5d51990f794babdee13b Mon Sep 17 00:00:00 2001 From: Lena Garber <114949949+lgarber-akamai@users.noreply.github.com> Date: Mon, 10 Jun 2024 14:17:17 -0400 Subject: [PATCH 01/25] new: Add support for Linode Disk Encryption (#1451) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Description This pull request adds support for Linode Disk Encryption to the following resources/data sources: * `linode_instance` - New `disk_encryption` and `lke_cluster_id` attributes * `linode_instance_disk` - New computed `disk_encryption` field (derived from parent instance) * `linode_lke_cluster` - New `pool.*.disk_encryption` attribute * `linode_lke_node_pool` - New `disk_encryption` attribute * `data.linode_instances` - New `disk_encryption` and `lke_cluster_id` attributes * `data.linode_lke_cluster` - New `pool.*.disk_encryption` attribute ## ✔️ How to Test The following test steps assume you have pulled down this PR locally and pointed your local environment at an API instance with support for disk encryption: ``` export LINODE_URL=... export LINODE_TOKEN=... ``` Additionally your account will need to be configured according to the description of the DX LDE epic (TPT-2789). ### Integration Testing ``` make PKG_NAME=linode/instance int-test make PKG_NAME=linode/instancedisk int-test make PKG_NAME=linode/lke int-test make PKG_NAME=linode/lkenodepool int-test ``` ### Manual Testing 1. In a Terraform provider sandbox environment (e.g. dx-devenv), apply the following configuration: ```terraform resource "linode_instance" "test" { label = "tf-lde-test" region = local.target_region type = "g6-nanode-1" image = "linode/alpine3.19" root_pass = "F00b4rp4ss!!11!!!v3rys3cure" disk_encryption = "disabled" booted = false } resource "linode_lke_cluster" "test" { label = "tf-lde-test" region = local.target_region k8s_version = "1.29" pool { count = 1 type = "g6-standard-1" } } data "linode_instances" "cluster-nodes" { depends_on = [linode_lke_cluster.test] filter { name = "lke_cluster_id" values = [linode_lke_cluster.test.id] } } output "linode_encrypted" { value = linode_instance.test.disk_encryption } output "lke_encrypted" { value = linode_lke_cluster.test.pool.0.disk_encryption } output "lke_node_encrypted" { value = data.linode_instances.cluster-nodes.instances.0.disk_encryption } # Resolve an LDE-enabled region locals { target_region = data.linode_regions.lde-enabled.regions.0.id } data "linode_regions" "lde-enabled" { filter { name = "capabilities" values = ["Disk Encryption"] } filter { name = "capabilities" values = ["Kubernetes"] } } ``` **NOTE: Due to availability issues you may see an `Invalid index error`. This can be resolved by waiting a bit for the LKE nodes to populate and re-applying the configuration.** 2. Ensure the output matches the following: ``` linode_encrypted = "disabled" lke_encrypted = "enabled" lke_node_encrypted = "enabled ``` 3. Change the `disk_encryption` attribute to enable and re-apply the configuration. 4. Ensure the instance was recreated and the output matches the following: ``` linode_encrypted = "enabled" lke_encrypted = "enabled" lke_node_encrypted = "enabled ``` --- docs/data-sources/instances.md | 4 + docs/data-sources/lke_cluster.md | 2 + docs/resources/instance.md | 4 + docs/resources/instance_disk.md | 2 + docs/resources/lke_cluster.md | 2 + docs/resources/lke_node_pool.md | 2 + go.mod | 3 + go.sum | 4 +- linode/instance/datasource.go | 12 ++- linode/instance/datasource_test.go | 1 + linode/instance/flatten.go | 2 + linode/instance/resource.go | 5 + linode/instance/resource_test.go | 93 ++++++++++++++++++- linode/instance/schema_datasource.go | 14 ++- linode/instance/schema_resource.go | 17 ++++ linode/instance/tmpl/template.go | 21 +++++ .../tmpl/templates/disk_encryption.gotf | 15 +++ linode/instancedisk/framework_model.go | 3 + .../instancedisk/framework_resource_schema.go | 4 + linode/instancedisk/resource_test.go | 5 + linode/lke/cluster.go | 11 ++- linode/lke/datasource_test.go | 1 + linode/lke/framework_datasource_schema.go | 4 + linode/lke/framework_models.go | 17 ++-- linode/lke/resource_test.go | 10 ++ linode/lke/schema_resource.go | 5 + linode/lke/tmpl/basic.gotf | 9 ++ linode/lkenodepool/framework_models.go | 16 ++-- .../lkenodepool/framework_models_unit_test.go | 8 +- .../lkenodepool/framework_resource_schema.go | 4 + linode/lkenodepool/framework_resource_test.go | 1 + 31 files changed, 270 insertions(+), 31 deletions(-) create mode 100644 linode/instance/tmpl/templates/disk_encryption.gotf diff --git a/docs/data-sources/instances.md b/docs/data-sources/instances.md index 7b82190af..4868eb8bd 100644 --- a/docs/data-sources/instances.md +++ b/docs/data-sources/instances.md @@ -104,6 +104,10 @@ Each Linode instance will be stored in the `instances` attribute and will export * `has_user_data` - Whether this Instance was created with user-data. +* `disk_encryption` - The disk encryption policy for this instance. + +* `lke_cluster_id` - If applicable, the ID of the LKE cluster this instance is a part of. + * `specs.0.disk` - The amount of storage space, in GB. this Linode has access to. A typical Linode will divide this space between a primary disk with an image deployed to it, and a swap disk, usually 512 MB. This is the default configuration created when deploying a Linode with an image through POST /linode/instances. * `specs.0.memory` - The amount of RAM, in MB, this Linode has access to. Typically a Linode will choose to boot with all of its available RAM, but this can be configured in a Config profile. diff --git a/docs/data-sources/lke_cluster.md b/docs/data-sources/lke_cluster.md index 084300786..128493aa6 100644 --- a/docs/data-sources/lke_cluster.md +++ b/docs/data-sources/lke_cluster.md @@ -54,6 +54,8 @@ In addition to all arguments above, the following attributes are exported: * `count` - The number of nodes in the Node Pool. + * `disk_encryption` - The disk encryption policy for nodes in this pool. + * `tags` - An array of tags applied to this object. Tags are case-insensitive and are for organizational purposes only. * `nodes` - The nodes in the Node Pool. diff --git a/docs/resources/instance.md b/docs/resources/instance.md index 7305051d8..b56456986 100644 --- a/docs/resources/instance.md +++ b/docs/resources/instance.md @@ -166,6 +166,8 @@ The following arguments are supported: * `firewall_id` - (Optional) The ID of the Firewall to attach to the instance upon creation. *Changing `firewall_id` forces the creation of a new Linode Instance.* +* `disk_encryption` - (Optional) The disk encryption policy for this instance. (`enabled`, `disabled`; default `enabled` in supported regions) + * `group` - (Optional, Deprecated) A deprecated property denoting a group label for this Linode. We recommend using the `tags` attribute instead. ### Simplified Resource Arguments @@ -335,6 +337,8 @@ This Linode Instance resource exports the following attributes: * `has_user_data` - Whether this Instance was created with user-data. +* `lke_cluster_id` - If applicable, the ID of the LKE cluster this instance is a part of. + * `specs.0.disk` - The amount of storage space, in GB. this Linode has access to. A typical Linode will divide this space between a primary disk with an image deployed to it, and a swap disk, usually 512 MB. This is the default configuration created when deploying a Linode with an image through POST /linode/instances. * `specs.0.memory` - The amount of RAM, in MB, this Linode has access to. Typically a Linode will choose to boot with all of its available RAM, but this can be configured in a Config profile. diff --git a/docs/resources/instance_disk.md b/docs/resources/instance_disk.md index 078facf6d..b6c086032 100644 --- a/docs/resources/instance_disk.md +++ b/docs/resources/instance_disk.md @@ -87,6 +87,8 @@ This resource exports the following attributes: * `created` - When this disk was created. +* `disk_encryption` - The disk encryption policy for this disk's parent instance. (`enabled`, `disabled`) + * `status` - A brief description of this Disk's current state. * `updated` - When this disk was last updated. diff --git a/docs/resources/lke_cluster.md b/docs/resources/lke_cluster.md index 1efd7a9da..43e685294 100644 --- a/docs/resources/lke_cluster.md +++ b/docs/resources/lke_cluster.md @@ -157,6 +157,8 @@ In addition to all arguments above, the following attributes are exported: * `id` - The ID of the Node Pool. + * `disk_encryption` - The disk encryption policy for nodes in this pool. + * [`nodes`](#nodes) - The nodes in the Node Pool. ### nodes diff --git a/docs/resources/lke_node_pool.md b/docs/resources/lke_node_pool.md index 04d2be9fe..d87b8f838 100644 --- a/docs/resources/lke_node_pool.md +++ b/docs/resources/lke_node_pool.md @@ -102,6 +102,8 @@ In addition to all arguments above, the following attributes are exported: * `id` - The ID of the Node Pool within LKE Cluster. +* `disk_encryption` - The disk encryption policy for nodes in this pool. + * [`nodes`](#nodes) - The nodes in the Node Pool. ### nodes diff --git a/go.mod b/go.mod index 28c985280..2b77e73fc 100644 --- a/go.mod +++ b/go.mod @@ -122,3 +122,6 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) + +// TODO: Remove replacement before merging project branch +replace github.com/linode/linodego => github.com/linode/linodego v1.34.1-0.20240523161713-6a6014f02272 diff --git a/go.sum b/go.sum index 728c3dda5..8ec575c98 100644 --- a/go.sum +++ b/go.sum @@ -189,8 +189,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/linode/linodego v1.34.0 h1:tBCwZzJTNh6Sr5xImkq/KQ/1rvUbH3aXGve5VuHEspQ= -github.com/linode/linodego v1.34.0/go.mod h1:JxuhOEAMfSxun6RU5/MgTKH2GGTmFrhKRj3wL1NFin0= +github.com/linode/linodego v1.34.1-0.20240523161713-6a6014f02272 h1:R8x88fA1JnEq+Dgzqk8u6Fa3UI1lgKXVYFjNfpLnof8= +github.com/linode/linodego v1.34.1-0.20240523161713-6a6014f02272/go.mod h1:JxuhOEAMfSxun6RU5/MgTKH2GGTmFrhKRj3wL1NFin0= github.com/linode/linodego/k8s v1.25.2 h1:PY6S0sAD3xANVvM9WY38bz9GqMTjIbytC8IJJ9Cv23o= github.com/linode/linodego/k8s v1.25.2/go.mod h1:DC1XCSRZRGsmaa/ggpDPSDUmOM6aK1bhSIP6+f9Cwhc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= diff --git a/linode/instance/datasource.go b/linode/instance/datasource.go index 671762496..1846a5d6c 100644 --- a/linode/instance/datasource.go +++ b/linode/instance/datasource.go @@ -11,17 +11,19 @@ import ( ) var filterConfig = helper.FilterConfig{ - "group": {APIFilterable: true, TypeFunc: helper.FilterTypeString}, - "id": {APIFilterable: true, TypeFunc: helper.FilterTypeInt}, - "image": {APIFilterable: true, TypeFunc: helper.FilterTypeString}, - "label": {APIFilterable: true, TypeFunc: helper.FilterTypeString}, - "region": {APIFilterable: true, TypeFunc: helper.FilterTypeString}, + "group": {APIFilterable: true, TypeFunc: helper.FilterTypeString}, + "id": {APIFilterable: true, TypeFunc: helper.FilterTypeInt}, + "image": {APIFilterable: true, TypeFunc: helper.FilterTypeString}, + "label": {APIFilterable: true, TypeFunc: helper.FilterTypeString}, + "region": {APIFilterable: true, TypeFunc: helper.FilterTypeString}, + "lke_cluster_id": {APIFilterable: true, TypeFunc: helper.FilterTypeInt}, // Tags must be filtered on the client "tags": {TypeFunc: helper.FilterTypeString}, "status": {TypeFunc: helper.FilterTypeString}, "type": {TypeFunc: helper.FilterTypeString}, "watchdog_enabled": {TypeFunc: helper.FilterTypeBool}, + "disk_encryption": {TypeFunc: helper.FilterTypeString}, } func dataSourceInstance() *schema.Resource { diff --git a/linode/instance/datasource_test.go b/linode/instance/datasource_test.go index cc0284fa5..86bdcbd17 100644 --- a/linode/instance/datasource_test.go +++ b/linode/instance/datasource_test.go @@ -34,6 +34,7 @@ func TestAccDataSourceInstances_basic(t *testing.T) { resource.TestCheckResourceAttr(resName, "instances.0.region", testRegion), resource.TestCheckResourceAttr(resName, "instances.0.group", "tf_test"), resource.TestCheckResourceAttr(resName, "instances.0.swap_size", "256"), + resource.TestCheckResourceAttrSet(resName, "instances.0.disk_encryption"), resource.TestCheckResourceAttr(resName, "instances.0.ipv4.#", "2"), resource.TestCheckResourceAttrSet(resName, "instances.0.ipv6"), resource.TestCheckResourceAttrSet(resName, "instances.0.host_uuid"), diff --git a/linode/instance/flatten.go b/linode/instance/flatten.go index 9e60c1ab6..c434f672e 100644 --- a/linode/instance/flatten.go +++ b/linode/instance/flatten.go @@ -49,6 +49,8 @@ func flattenInstance( result["image"] = instance.Image result["host_uuid"] = instance.HostUUID result["has_user_data"] = instance.HasUserData + result["disk_encryption"] = instance.DiskEncryption + result["lke_cluster_id"] = instance.LKEClusterID result["backups"] = flattenInstanceBackups(*instance) result["specs"] = flattenInstanceSpecs(*instance) diff --git a/linode/instance/resource.go b/linode/instance/resource.go index 55f73e5a8..34b5ddfb3 100644 --- a/linode/instance/resource.go +++ b/linode/instance/resource.go @@ -115,6 +115,8 @@ func readResource(ctx context.Context, d *schema.ResourceData, meta interface{}) d.Set("booted", isInstanceBooted(instance)) d.Set("host_uuid", instance.HostUUID) d.Set("has_user_data", instance.HasUserData) + d.Set("lke_cluster_id", instance.LKEClusterID) + d.Set("disk_encryption", instance.DiskEncryption) flatSpecs := flattenInstanceSpecs(*instance) flatAlerts := flattenInstanceAlerts(*instance) @@ -170,6 +172,9 @@ func createResource(ctx context.Context, d *schema.ResourceData, meta interface{ Group: d.Get("group").(string), BackupsEnabled: d.Get("backups_enabled").(bool), PrivateIP: d.Get("private_ip").(bool), + DiskEncryption: linodego.InstanceDiskEncryption( + d.Get("disk_encryption").(string), + ), } if tagsRaw, tagsOk := d.GetOk("tags"); tagsOk { diff --git a/linode/instance/resource_test.go b/linode/instance/resource_test.go index 936bd5adf..1e9507f46 100644 --- a/linode/instance/resource_test.go +++ b/linode/instance/resource_test.go @@ -30,7 +30,9 @@ func init() { F: sweep, }) - region, err := acceptance.GetRandomRegionWithCaps([]string{"Vlans", "VPCs"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{ + linodego.Vlans, linodego.VPCs, linodego.DiskEncryption, + }) if err != nil { log.Fatal(err) } @@ -2327,6 +2329,95 @@ func TestAccResourceInstance_migration(t *testing.T) { }) } +func TestAccResourceInstance_diskEncryption(t *testing.T) { + t.Parallel() + + resName := "linode_instance.foobar" + var instance linodego.Instance + instanceName := acctest.RandomWithPrefix("tf_test") + rootPass := acctest.RandString(16) + + // Resolve a region that supports disk encryption + targetRegion, err := acceptance.GetRandomRegionWithCaps( + []string{linodego.Linodes, linodego.DiskEncryption}, + ) + if err != nil { + t.Fatal(err) + } + + encryptionEnabled := linodego.InstanceDiskEncryptionEnabled + encryptionDisabled := linodego.InstanceDiskEncryptionDisabled + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, + CheckDestroy: acceptance.CheckInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: tmpl.DiskEncryption( + t, + instanceName, + targetRegion, + rootPass, + &encryptionEnabled, + ), + Check: resource.ComposeTestCheckFunc( + acceptance.CheckInstanceExists(resName, &instance), + resource.TestCheckResourceAttr(resName, "label", instanceName), + resource.TestCheckResourceAttr(resName, "type", "g6-nanode-1"), + resource.TestCheckResourceAttr(resName, "image", acceptance.TestImageLatest), + resource.TestCheckResourceAttr(resName, "region", testRegion), + resource.TestCheckResourceAttr(resName, "disk_encryption", "enabled"), + ), + }, + { + Config: tmpl.DiskEncryption( + t, + instanceName, + targetRegion, + rootPass, + &encryptionDisabled, + ), + Check: resource.ComposeTestCheckFunc( + acceptance.CheckInstanceExists(resName, &instance), + resource.TestCheckResourceAttr(resName, "label", instanceName), + resource.TestCheckResourceAttr(resName, "type", "g6-nanode-1"), + resource.TestCheckResourceAttr(resName, "image", acceptance.TestImageLatest), + resource.TestCheckResourceAttr(resName, "region", testRegion), + resource.TestCheckResourceAttr(resName, "disk_encryption", "disabled"), + ), + }, + + // Make sure the instance is not recreated when disk_encryption is not explicitly set. + // This is necessary to prevent instances created pre-disk-encryption from being recreated. + { + Config: tmpl.DiskEncryption( + t, + instanceName, + targetRegion, + rootPass, + nil, + ), + Check: resource.ComposeTestCheckFunc( + acceptance.CheckInstanceExists(resName, &instance), + resource.TestCheckResourceAttr(resName, "label", instanceName), + resource.TestCheckResourceAttr(resName, "type", "g6-nanode-1"), + resource.TestCheckResourceAttr(resName, "image", acceptance.TestImageLatest), + resource.TestCheckResourceAttr(resName, "region", testRegion), + resource.TestCheckResourceAttr(resName, "disk_encryption", "disabled"), + ), + }, + + { + ResourceName: resName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"root_pass", "authorized_keys", "image", "resize_disk", "migration_type"}, + }, + }, + }) +} + func checkInstancePrivateNetworkAttributes(n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] diff --git a/linode/instance/schema_datasource.go b/linode/instance/schema_datasource.go index 64c08a432..aac0d51f9 100644 --- a/linode/instance/schema_datasource.go +++ b/linode/instance/schema_datasource.go @@ -1,6 +1,8 @@ package instance -import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) var instanceDataSourceSchema = map[string]*schema.Schema{ "id": { @@ -102,6 +104,16 @@ var instanceDataSourceSchema = map[string]*schema.Schema{ Description: "Whether this Instance was created with user-data.", Computed: true, }, + "disk_encryption": { + Type: schema.TypeString, + Description: "The disk encryption policy for this Instance.", + Computed: true, + }, + "lke_cluster_id": { + Type: schema.TypeInt, + Description: "If applicable, the ID of the LKE cluster this Instance is a node of.", + Computed: true, + }, "specs": { Computed: true, Type: schema.TypeList, diff --git a/linode/instance/schema_resource.go b/linode/instance/schema_resource.go index b0498118b..354d3d87c 100644 --- a/linode/instance/schema_resource.go +++ b/linode/instance/schema_resource.go @@ -421,6 +421,23 @@ var resourceSchema = map[string]*schema.Schema{ Description: "Whether or not this Instance was created with user-data.", Computed: true, }, + "disk_encryption": { + Type: schema.TypeString, + Description: "The disk encryption policy for this Instance.", + Optional: true, + ForceNew: true, + ValidateDiagFunc: validation.ToDiagFunc( + validation.StringInSlice([]string{"enabled", "disabled"}, false), + ), + + // This is necessary to prevent instances created pre-disk-encryption from being recreated. + Computed: true, + }, + "lke_cluster_id": { + Type: schema.TypeInt, + Description: "If applicable, the ID of the LKE cluster this Instance is a node of.", + Computed: true, + }, "specs": { Computed: true, Description: "Information about the resources available to this Linode.", diff --git a/linode/instance/tmpl/template.go b/linode/instance/tmpl/template.go index c11b878ba..4fd25cc94 100644 --- a/linode/instance/tmpl/template.go +++ b/linode/instance/tmpl/template.go @@ -3,6 +3,8 @@ package tmpl import ( "testing" + "github.com/linode/linodego" + "github.com/linode/terraform-provider-linode/v2/linode/acceptance" ) @@ -22,6 +24,8 @@ type TemplateData struct { Booted bool ResizeDisk bool + + DiskEncryption *linodego.InstanceDiskEncryption } func Basic(t *testing.T, label, pubKey, region string, rootPass string) string { @@ -590,6 +594,23 @@ func UserData(t *testing.T, label, region string, rootPass string) string { }) } +func DiskEncryption( + t *testing.T, + label, + region, + rootPass string, + diskEncryption *linodego.InstanceDiskEncryption, +) string { + return acceptance.ExecuteTemplate(t, + "instance_disk_encryption", TemplateData{ + Label: label, + Image: acceptance.TestImageLatest, + Region: region, + RootPass: rootPass, + DiskEncryption: diskEncryption, + }) +} + func DataBasic(t *testing.T, label, region string, rootPass string) string { return acceptance.ExecuteTemplate(t, "instance_data_basic", TemplateData{ diff --git a/linode/instance/tmpl/templates/disk_encryption.gotf b/linode/instance/tmpl/templates/disk_encryption.gotf new file mode 100644 index 000000000..6fbe2129c --- /dev/null +++ b/linode/instance/tmpl/templates/disk_encryption.gotf @@ -0,0 +1,15 @@ +{{ define "instance_disk_encryption" }} + +resource "linode_instance" "foobar" { + label = "{{.Label}}" + type = "g6-nanode-1" + image = "{{.Image}}" + region = "{{ .Region }}" + root_pass = "{{ .RootPass }}" + +{{ if not (eq .DiskEncryption nil) }} + disk_encryption = "{{ .DiskEncryption }}" +{{ end }} +} + +{{ end }} \ No newline at end of file diff --git a/linode/instancedisk/framework_model.go b/linode/instancedisk/framework_model.go index 8e70e36c9..9ccd2e8d4 100644 --- a/linode/instancedisk/framework_model.go +++ b/linode/instancedisk/framework_model.go @@ -25,6 +25,7 @@ type ResourceModel struct { Created timetypes.RFC3339 `tfsdk:"created"` Updated timetypes.RFC3339 `tfsdk:"updated"` Status types.String `tfsdk:"status"` + DiskEncryption types.String `tfsdk:"disk_encryption"` Timeouts timeouts.Value `tfsdk:"timeouts"` } @@ -44,6 +45,7 @@ func (data *ResourceModel) FlattenDisk(disk *linodego.InstanceDisk, preserveKnow ) data.Status = helper.KeepOrUpdateString(data.Status, string(disk.Status), preserveKnown) + data.DiskEncryption = helper.KeepOrUpdateString(data.DiskEncryption, string(disk.DiskEncryption), preserveKnown) } func (data *ResourceModel) CopyFrom(other ResourceModel, preserveKnown bool) { @@ -68,4 +70,5 @@ func (data *ResourceModel) CopyFrom(other ResourceModel, preserveKnown bool) { data.Created = helper.KeepOrUpdateValue(data.Created, other.Created, preserveKnown) data.Updated = helper.KeepOrUpdateValue(data.Updated, other.Updated, preserveKnown) data.Status = helper.KeepOrUpdateValue(data.Status, other.Status, preserveKnown) + data.DiskEncryption = helper.KeepOrUpdateValue(data.DiskEncryption, other.DiskEncryption, preserveKnown) } diff --git a/linode/instancedisk/framework_resource_schema.go b/linode/instancedisk/framework_resource_schema.go index 7c3e91c64..334051dc6 100644 --- a/linode/instancedisk/framework_resource_schema.go +++ b/linode/instancedisk/framework_resource_schema.go @@ -145,5 +145,9 @@ var frameworkResourceSchema = schema.Schema{ Description: "A brief description of this Disk's current state.", Computed: true, }, + "disk_encryption": schema.StringAttribute{ + Description: "The disk encryption policy for this disk's parent Linode.", + Computed: true, + }, }, } diff --git a/linode/instancedisk/resource_test.go b/linode/instancedisk/resource_test.go index 3ff68616a..a89db7ffb 100644 --- a/linode/instancedisk/resource_test.go +++ b/linode/instancedisk/resource_test.go @@ -49,6 +49,11 @@ func TestAccResourceInstanceDisk_basic_smoke(t *testing.T) { resource.TestCheckResourceAttr(resName, "status", "ready"), resource.TestCheckResourceAttrSet(resName, "linode_id"), + + resource.TestCheckResourceAttrPair( + resName, "disk_encryption", + "linode_instance.foobar", "disk_encryption", + ), ), }, // Resize up diff --git a/linode/lke/cluster.go b/linode/lke/cluster.go index 59ae77171..77944435b 100644 --- a/linode/lke/cluster.go +++ b/linode/lke/cluster.go @@ -421,11 +421,12 @@ func flattenLKENodePools(pools []linodego.LKEClusterPool) []map[string]interface } flattened[i] = map[string]interface{}{ - "id": pool.ID, - "count": pool.Count, - "type": pool.Type, - "nodes": nodes, - "autoscaler": autoscaler, + "id": pool.ID, + "count": pool.Count, + "type": pool.Type, + "disk_encryption": pool.DiskEncryption, + "nodes": nodes, + "autoscaler": autoscaler, } } return flattened diff --git a/linode/lke/datasource_test.go b/linode/lke/datasource_test.go index afcdfa51d..f83e414a1 100644 --- a/linode/lke/datasource_test.go +++ b/linode/lke/datasource_test.go @@ -34,6 +34,7 @@ func TestAccDataSourceLKECluster_basic(t *testing.T) { resource.TestCheckResourceAttr(dataSourceClusterName, "pools.#", "1"), resource.TestCheckResourceAttr(dataSourceClusterName, "pools.0.type", "g6-standard-2"), resource.TestCheckResourceAttr(dataSourceClusterName, "pools.0.count", "3"), + resource.TestCheckResourceAttrSet(dataSourceClusterName, "pool.0.disk_encryption"), resource.TestCheckResourceAttr(dataSourceClusterName, "pools.0.nodes.#", "3"), resource.TestCheckResourceAttr(dataSourceClusterName, "pools.0.autoscaler.#", "0"), resource.TestCheckResourceAttr(dataSourceClusterName, "control_plane.0.high_availability", "false"), diff --git a/linode/lke/framework_datasource_schema.go b/linode/lke/framework_datasource_schema.go index b07ca2288..6db476b6f 100644 --- a/linode/lke/framework_datasource_schema.go +++ b/linode/lke/framework_datasource_schema.go @@ -121,6 +121,10 @@ var frameworkDataSourceSchema = schema.Schema{ Computed: true, Description: "An array of tags applied to this object. Tags are for organizational purposes only.", }, + "disk_encryption": schema.StringAttribute{ + Computed: true, + Description: "The disk encryption policy for the nodes in this pool.", + }, }, Blocks: map[string]schema.Block{ "nodes": schema.ListNestedBlock{ diff --git a/linode/lke/framework_models.go b/linode/lke/framework_models.go index a910b1405..c4163eb65 100644 --- a/linode/lke/framework_models.go +++ b/linode/lke/framework_models.go @@ -53,13 +53,14 @@ type LKEControlPlaneACLAddresses struct { } type LKENodePool struct { - ID types.Int64 `tfsdk:"id"` - Count types.Int64 `tfsdk:"count"` - Type types.String `tfsdk:"type"` - Tags types.List `tfsdk:"tags"` - Disks []LKENodePoolDisk `tfsdk:"disks"` - Nodes []LKENodePoolNode `tfsdk:"nodes"` - Autoscaler []LKENodePoolAutoscaler `tfsdk:"autoscaler"` + ID types.Int64 `tfsdk:"id"` + Count types.Int64 `tfsdk:"count"` + Type types.String `tfsdk:"type"` + Tags types.List `tfsdk:"tags"` + DiskEncryption types.String `tfsdk:"disk_encryption"` + Disks []LKENodePoolDisk `tfsdk:"disks"` + Nodes []LKENodePoolNode `tfsdk:"nodes"` + Autoscaler []LKENodePoolAutoscaler `tfsdk:"autoscaler"` } type LKENodePoolDisk struct { @@ -116,6 +117,8 @@ func (data *LKEDataModel) parseLKEAttributes( pool.ID = types.Int64Value(int64(p.ID)) pool.Count = types.Int64Value(int64(p.Count)) pool.Type = types.StringValue(p.Type) + pool.DiskEncryption = types.StringValue(string(p.DiskEncryption)) + tags, diags := types.ListValueFrom(ctx, types.StringType, p.Tags) if diags != nil { return nil, diags diff --git a/linode/lke/resource_test.go b/linode/lke/resource_test.go index 28426c018..7bf88040a 100644 --- a/linode/lke/resource_test.go +++ b/linode/lke/resource_test.go @@ -185,6 +185,7 @@ func TestAccResourceLKECluster_basic_smoke(t *testing.T) { resource.TestCheckResourceAttr(resourceClusterName, "pool.#", "1"), resource.TestCheckResourceAttr(resourceClusterName, "pool.0.type", "g6-standard-2"), resource.TestCheckResourceAttr(resourceClusterName, "pool.0.count", "3"), + resource.TestCheckResourceAttrSet(resourceClusterName, "pool.0.disk_encryption"), resource.TestCheckResourceAttr(resourceClusterName, "pool.0.nodes.#", "3"), resource.TestCheckResourceAttr(resourceClusterName, "control_plane.#", "1"), resource.TestCheckResourceAttr(resourceClusterName, "control_plane.0.high_availability", "false"), @@ -192,6 +193,15 @@ func TestAccResourceLKECluster_basic_smoke(t *testing.T) { resource.TestCheckResourceAttrSet(resourceClusterName, "pool.0.id"), resource.TestCheckResourceAttrSet(resourceClusterName, "kubeconfig"), resource.TestCheckResourceAttrSet(resourceClusterName, "dashboard_url"), + + // Ensure the lke_cluster_id field is populated on a sample + // node from the new cluster. + resource.TestCheckResourceAttrPair( + resourceClusterName, + "id", + "data.linode_instances.test", + "instances.0.lke_cluster_id", + ), ), }, }, diff --git a/linode/lke/schema_resource.go b/linode/lke/schema_resource.go index 037117d38..e3d20d243 100644 --- a/linode/lke/schema_resource.go +++ b/linode/lke/schema_resource.go @@ -79,6 +79,11 @@ var resourceSchema = map[string]*schema.Schema{ Description: "A Linode Type for all of the nodes in the Node Pool.", Required: true, }, + "disk_encryption": { + Type: schema.TypeString, + Description: "The disk encryption policy for the nodes in this pool.", + Computed: true, + }, "nodes": { Type: schema.TypeList, Elem: &schema.Resource{ diff --git a/linode/lke/tmpl/basic.gotf b/linode/lke/tmpl/basic.gotf index 53980a83d..1f7557d8a 100644 --- a/linode/lke/tmpl/basic.gotf +++ b/linode/lke/tmpl/basic.gotf @@ -12,4 +12,13 @@ resource "linode_lke_cluster" "test" { } } +data "linode_instances" "test" { + depends_on = [linode_lke_cluster.test] + + filter { + name = "id" + values = [linode_lke_cluster.test.pool.0.nodes.0.instance_id] + } +} + {{ end }} \ No newline at end of file diff --git a/linode/lkenodepool/framework_models.go b/linode/lkenodepool/framework_models.go index ed35711ca..f1d4e257c 100644 --- a/linode/lkenodepool/framework_models.go +++ b/linode/lkenodepool/framework_models.go @@ -14,13 +14,14 @@ import ( ) type NodePoolModel struct { - ID types.String `tfsdk:"id"` - ClusterID types.Int64 `tfsdk:"cluster_id"` - Count types.Int64 `tfsdk:"node_count"` - Type types.String `tfsdk:"type"` - Tags types.Set `tfsdk:"tags"` - Nodes types.List `tfsdk:"nodes"` - Autoscaler []NodePoolAutoscalerModel `tfsdk:"autoscaler"` + ID types.String `tfsdk:"id"` + ClusterID types.Int64 `tfsdk:"cluster_id"` + Count types.Int64 `tfsdk:"node_count"` + Type types.String `tfsdk:"type"` + DiskEncryption types.String `tfsdk:"disk_encryption"` + Tags types.Set `tfsdk:"tags"` + Nodes types.List `tfsdk:"nodes"` + Autoscaler []NodePoolAutoscalerModel `tfsdk:"autoscaler"` } type NodePoolAutoscalerModel struct { @@ -76,6 +77,7 @@ func (pool *NodePoolModel) FlattenLKENodePool( pool.ID = helper.KeepOrUpdateString(pool.ID, strconv.Itoa(p.ID), preserveKnown) pool.Count = helper.KeepOrUpdateInt64(pool.Count, int64(p.Count), preserveKnown) pool.Type = helper.KeepOrUpdateString(pool.Type, p.Type, preserveKnown) + pool.DiskEncryption = helper.KeepOrUpdateString(pool.DiskEncryption, string(p.DiskEncryption), preserveKnown) pool.Tags = helper.KeepOrUpdateStringSet(pool.Tags, p.Tags, preserveKnown, diags) if diags.HasError() { return diff --git a/linode/lkenodepool/framework_models_unit_test.go b/linode/lkenodepool/framework_models_unit_test.go index df2bfa75e..4e6905c65 100644 --- a/linode/lkenodepool/framework_models_unit_test.go +++ b/linode/lkenodepool/framework_models_unit_test.go @@ -14,9 +14,10 @@ import ( func TestParseNodePool(t *testing.T) { lkeNodePool := linodego.LKENodePool{ - ID: 123, - Count: 3, - Type: "g6-standard-2", + ID: 123, + Count: 3, + Type: "g6-standard-2", + DiskEncryption: linodego.InstanceDiskEncryptionEnabled, Disks: []linodego.LKENodePoolDisk{ {Size: 50, Type: "ssd"}, }, @@ -42,6 +43,7 @@ func TestParseNodePool(t *testing.T) { assert.Equal(t, "123", nodePoolModel.ID.ValueString()) assert.Equal(t, int64(3), nodePoolModel.Count.ValueInt64()) assert.Equal(t, "g6-standard-2", nodePoolModel.Type.ValueString()) + assert.Equal(t, "enabled", nodePoolModel.DiskEncryption.ValueString()) assert.Len(t, nodePoolModel.Nodes.Elements(), 3) tags := make([]string, len(nodePoolModel.Tags.Elements())) diff --git a/linode/lkenodepool/framework_resource_schema.go b/linode/lkenodepool/framework_resource_schema.go index d1afc6901..3494f1085 100644 --- a/linode/lkenodepool/framework_resource_schema.go +++ b/linode/lkenodepool/framework_resource_schema.go @@ -48,6 +48,10 @@ var resourceSchema = schema.Schema{ Description: "The type of node pool.", Required: true, }, + "disk_encryption": schema.StringAttribute{ + Description: "The disk encryption policy for nodes in this pool", + Computed: true, + }, "tags": schema.SetAttribute{ ElementType: types.StringType, Optional: true, diff --git a/linode/lkenodepool/framework_resource_test.go b/linode/lkenodepool/framework_resource_test.go index 7dcf26aef..991cb99c9 100644 --- a/linode/lkenodepool/framework_resource_test.go +++ b/linode/lkenodepool/framework_resource_test.go @@ -135,6 +135,7 @@ func TestAccResourceNodePool_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( checkNodePoolExists, resource.TestCheckResourceAttr(resName, "type", "g6-standard-1"), + resource.TestCheckResourceAttrSet(resName, "disk_encryption"), resource.TestCheckResourceAttr(resName, "tags.#", "2"), resource.TestCheckResourceAttr(resName, "tags.0", "external"), resource.TestCheckResourceAttr(resName, "tags.1", poolTag), From edd7bae5befc41264b68be0aae430a0d7a824ae8 Mon Sep 17 00:00:00 2001 From: Lena Garber Date: Mon, 8 Jul 2024 11:13:55 -0400 Subject: [PATCH 02/25] Bump linodego project branch replacement and make format --- go.mod | 2 +- go.sum | 4 ++-- linode/instance/tmpl/template.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 78d66ee91..0b90ce5f0 100644 --- a/go.mod +++ b/go.mod @@ -124,4 +124,4 @@ require ( ) // TODO: Remove replacement before merging project branch -replace github.com/linode/linodego => github.com/linode/linodego v1.34.1-0.20240523161713-6a6014f02272 +replace github.com/linode/linodego => github.com/linode/linodego v1.36.2-0.20240703205759-8d41d439317b diff --git a/go.sum b/go.sum index 0dbcea55f..8585c02a1 100644 --- a/go.sum +++ b/go.sum @@ -189,8 +189,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/linode/linodego v1.34.1-0.20240523161713-6a6014f02272 h1:R8x88fA1JnEq+Dgzqk8u6Fa3UI1lgKXVYFjNfpLnof8= -github.com/linode/linodego v1.34.1-0.20240523161713-6a6014f02272/go.mod h1:JxuhOEAMfSxun6RU5/MgTKH2GGTmFrhKRj3wL1NFin0= +github.com/linode/linodego v1.36.2-0.20240703205759-8d41d439317b h1:b+FsLIWKXZ1EXvj/DSX8wNQzbbEWAMRZ9YzALTBn7kk= +github.com/linode/linodego v1.36.2-0.20240703205759-8d41d439317b/go.mod h1:KyV4OO/9/tAxaLSjyjFyOQBcS9bYUdei1hwk3nl0UjI= github.com/linode/linodego/k8s v1.25.2 h1:PY6S0sAD3xANVvM9WY38bz9GqMTjIbytC8IJJ9Cv23o= github.com/linode/linodego/k8s v1.25.2/go.mod h1:DC1XCSRZRGsmaa/ggpDPSDUmOM6aK1bhSIP6+f9Cwhc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= diff --git a/linode/instance/tmpl/template.go b/linode/instance/tmpl/template.go index a99a6303d..d3d2ea537 100644 --- a/linode/instance/tmpl/template.go +++ b/linode/instance/tmpl/template.go @@ -26,7 +26,7 @@ type TemplateData struct { PlacementGroups []string AssignedGroup string - + DiskEncryption *linodego.InstanceDiskEncryption } From c0e02af3d8bce664b8628ab912f331257ef84dc0 Mon Sep 17 00:00:00 2001 From: Lena Garber Date: Tue, 23 Jul 2024 10:36:50 -0400 Subject: [PATCH 03/25] Add LDE LA disclaimers --- docs/data-sources/instances.md | 2 ++ docs/data-sources/lke_cluster.md | 2 ++ docs/resources/instance.md | 2 ++ docs/resources/instance_disk.md | 2 ++ docs/resources/lke_cluster.md | 2 ++ docs/resources/lke_node_pool.md | 2 ++ linode/instance/schema_datasource.go | 7 ++++--- linode/instance/schema_resource.go | 9 +++++---- linode/instancedisk/framework_resource_schema.go | 5 +++-- linode/lke/framework_datasource_schema.go | 5 +++-- linode/lke/schema_resource.go | 7 ++++--- linode/lkenodepool/framework_resource_schema.go | 5 +++-- 12 files changed, 34 insertions(+), 16 deletions(-) diff --git a/docs/data-sources/instances.md b/docs/data-sources/instances.md index 4868eb8bd..fe174a088 100644 --- a/docs/data-sources/instances.md +++ b/docs/data-sources/instances.md @@ -106,6 +106,8 @@ Each Linode instance will be stored in the `instances` attribute and will export * `disk_encryption` - The disk encryption policy for this instance. + * **NOTE: Disk encryption may not currently be available to all users.** + * `lke_cluster_id` - If applicable, the ID of the LKE cluster this instance is a part of. * `specs.0.disk` - The amount of storage space, in GB. this Linode has access to. A typical Linode will divide this space between a primary disk with an image deployed to it, and a swap disk, usually 512 MB. This is the default configuration created when deploying a Linode with an image through POST /linode/instances. diff --git a/docs/data-sources/lke_cluster.md b/docs/data-sources/lke_cluster.md index 13c95c090..b57fdcd3f 100644 --- a/docs/data-sources/lke_cluster.md +++ b/docs/data-sources/lke_cluster.md @@ -56,6 +56,8 @@ In addition to all arguments above, the following attributes are exported: * `disk_encryption` - The disk encryption policy for nodes in this pool. + * **NOTE: Disk encryption may not currently be available to all users.** + * `tags` - An array of tags applied to this object. Tags are case-insensitive and are for organizational purposes only. * `nodes` - The nodes in the Node Pool. diff --git a/docs/resources/instance.md b/docs/resources/instance.md index 2bdc8f3be..09050b108 100644 --- a/docs/resources/instance.md +++ b/docs/resources/instance.md @@ -192,6 +192,8 @@ The following arguments are supported: * `disk_encryption` - (Optional) The disk encryption policy for this instance. (`enabled`, `disabled`; default `enabled` in supported regions) + * **NOTE: Disk encryption may not currently be available to all users.** + * `group` - (Optional, Deprecated) A deprecated property denoting a group label for this Linode. We recommend using the `tags` attribute instead. ### Simplified Resource Arguments diff --git a/docs/resources/instance_disk.md b/docs/resources/instance_disk.md index b6c086032..88ac9ebd9 100644 --- a/docs/resources/instance_disk.md +++ b/docs/resources/instance_disk.md @@ -89,6 +89,8 @@ This resource exports the following attributes: * `disk_encryption` - The disk encryption policy for this disk's parent instance. (`enabled`, `disabled`) + * **NOTE: Disk encryption may not currently be available to all users.** + * `status` - A brief description of this Disk's current state. * `updated` - When this disk was last updated. diff --git a/docs/resources/lke_cluster.md b/docs/resources/lke_cluster.md index fdc8f9366..27232cd18 100644 --- a/docs/resources/lke_cluster.md +++ b/docs/resources/lke_cluster.md @@ -162,6 +162,8 @@ In addition to all arguments above, the following attributes are exported: * `disk_encryption` - The disk encryption policy for nodes in this pool. + * **NOTE: Disk encryption may not currently be available to all users.** + * [`nodes`](#nodes) - The nodes in the Node Pool. ### nodes diff --git a/docs/resources/lke_node_pool.md b/docs/resources/lke_node_pool.md index d87b8f838..1fd9915fc 100644 --- a/docs/resources/lke_node_pool.md +++ b/docs/resources/lke_node_pool.md @@ -104,6 +104,8 @@ In addition to all arguments above, the following attributes are exported: * `disk_encryption` - The disk encryption policy for nodes in this pool. + * **NOTE: Disk encryption may not currently be available to all users.** + * [`nodes`](#nodes) - The nodes in the Node Pool. ### nodes diff --git a/linode/instance/schema_datasource.go b/linode/instance/schema_datasource.go index aac0d51f9..6a69a7d33 100644 --- a/linode/instance/schema_datasource.go +++ b/linode/instance/schema_datasource.go @@ -105,9 +105,10 @@ var instanceDataSourceSchema = map[string]*schema.Schema{ Computed: true, }, "disk_encryption": { - Type: schema.TypeString, - Description: "The disk encryption policy for this Instance.", - Computed: true, + Type: schema.TypeString, + Description: "The disk encryption policy for this Instance." + + "NOTE: Disk encryption may not currently be available to all users.", + Computed: true, }, "lke_cluster_id": { Type: schema.TypeInt, diff --git a/linode/instance/schema_resource.go b/linode/instance/schema_resource.go index 13909ec82..eb3e5132c 100644 --- a/linode/instance/schema_resource.go +++ b/linode/instance/schema_resource.go @@ -474,10 +474,11 @@ var resourceSchema = map[string]*schema.Schema{ Computed: true, }, "disk_encryption": { - Type: schema.TypeString, - Description: "The disk encryption policy for this Instance.", - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Description: "The disk encryption policy for this Instance. " + + "NOTE: Disk encryption may not currently be available to all users.", + Optional: true, + ForceNew: true, ValidateDiagFunc: validation.ToDiagFunc( validation.StringInSlice([]string{"enabled", "disabled"}, false), ), diff --git a/linode/instancedisk/framework_resource_schema.go b/linode/instancedisk/framework_resource_schema.go index 334051dc6..7f16dcb2a 100644 --- a/linode/instancedisk/framework_resource_schema.go +++ b/linode/instancedisk/framework_resource_schema.go @@ -146,8 +146,9 @@ var frameworkResourceSchema = schema.Schema{ Computed: true, }, "disk_encryption": schema.StringAttribute{ - Description: "The disk encryption policy for this disk's parent Linode.", - Computed: true, + Description: "The disk encryption policy for this disk's parent Linode. " + + "NOTE: Disk encryption may not currently be available to all users.", + Computed: true, }, }, } diff --git a/linode/lke/framework_datasource_schema.go b/linode/lke/framework_datasource_schema.go index 6db476b6f..cc94bcc6e 100644 --- a/linode/lke/framework_datasource_schema.go +++ b/linode/lke/framework_datasource_schema.go @@ -122,8 +122,9 @@ var frameworkDataSourceSchema = schema.Schema{ Description: "An array of tags applied to this object. Tags are for organizational purposes only.", }, "disk_encryption": schema.StringAttribute{ - Computed: true, - Description: "The disk encryption policy for the nodes in this pool.", + Computed: true, + Description: "The disk encryption policy for the nodes in this pool. " + + "NOTE: Disk encryption may not currently be available to all users.", }, }, Blocks: map[string]schema.Block{ diff --git a/linode/lke/schema_resource.go b/linode/lke/schema_resource.go index e3d20d243..3b187df83 100644 --- a/linode/lke/schema_resource.go +++ b/linode/lke/schema_resource.go @@ -80,9 +80,10 @@ var resourceSchema = map[string]*schema.Schema{ Required: true, }, "disk_encryption": { - Type: schema.TypeString, - Description: "The disk encryption policy for the nodes in this pool.", - Computed: true, + Type: schema.TypeString, + Description: "The disk encryption policy for the nodes in this pool. " + + "NOTE: Disk encryption may not currently be available to all users.", + Computed: true, }, "nodes": { Type: schema.TypeList, diff --git a/linode/lkenodepool/framework_resource_schema.go b/linode/lkenodepool/framework_resource_schema.go index cc9ae7b69..84762a073 100644 --- a/linode/lkenodepool/framework_resource_schema.go +++ b/linode/lkenodepool/framework_resource_schema.go @@ -52,8 +52,9 @@ var resourceSchema = schema.Schema{ }, }, "disk_encryption": schema.StringAttribute{ - Description: "The disk encryption policy for nodes in this pool", - Computed: true, + Description: "The disk encryption policy for nodes in this pool. " + + "NOTE: Disk encryption may not currently be available to all users.", + Computed: true, }, "tags": schema.SetAttribute{ ElementType: types.StringType, From 8578245e1bdbbfa5abc2a597c3f1f85ef3b7a0bc Mon Sep 17 00:00:00 2001 From: Lena Garber Date: Tue, 23 Jul 2024 11:37:15 -0400 Subject: [PATCH 04/25] Fix indentation --- docs/data-sources/lke_cluster.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data-sources/lke_cluster.md b/docs/data-sources/lke_cluster.md index 856e60e3d..b2528dcb3 100644 --- a/docs/data-sources/lke_cluster.md +++ b/docs/data-sources/lke_cluster.md @@ -57,7 +57,7 @@ In addition to all arguments above, the following attributes are exported: * `disk_encryption` - The disk encryption policy for nodes in this pool. - * **NOTE: Disk encryption may not currently be available to all users.** + * **NOTE: Disk encryption may not currently be available to all users.** * `tags` - An array of tags applied to this object. Tags are case-insensitive and are for organizational purposes only. From a80647d7aec1fda6bb07279a3cb2defd4fad58aa Mon Sep 17 00:00:00 2001 From: Lena Garber Date: Tue, 23 Jul 2024 12:11:27 -0400 Subject: [PATCH 05/25] Bump linodego -> v1.38 --- go.mod | 5 +---- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 9ef3d440b..748f18cfc 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/hashicorp/terraform-plugin-mux v0.16.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 github.com/hashicorp/terraform-plugin-testing v1.9.0 - github.com/linode/linodego v1.37.0 + github.com/linode/linodego v1.38.0 github.com/linode/linodego/k8s v1.25.2 github.com/stretchr/testify v1.9.0 golang.org/x/crypto v0.25.0 @@ -121,6 +121,3 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) - -// TODO: Remove replacement before merging project branch -replace github.com/linode/linodego => github.com/linode/linodego v1.36.2-0.20240703205759-8d41d439317b diff --git a/go.sum b/go.sum index 1f4b8abe2..76d1394ba 100644 --- a/go.sum +++ b/go.sum @@ -185,8 +185,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/linode/linodego v1.36.2-0.20240703205759-8d41d439317b h1:b+FsLIWKXZ1EXvj/DSX8wNQzbbEWAMRZ9YzALTBn7kk= -github.com/linode/linodego v1.36.2-0.20240703205759-8d41d439317b/go.mod h1:KyV4OO/9/tAxaLSjyjFyOQBcS9bYUdei1hwk3nl0UjI= +github.com/linode/linodego v1.38.0 h1:wP3oW9OhGc6vhze8NPf2knbwH4TzSbrjzuCd9okjbTY= +github.com/linode/linodego v1.38.0/go.mod h1:L7GXKFD3PoN2xSEtFc04wIXP5WK65O10jYQx0PQISWQ= github.com/linode/linodego/k8s v1.25.2 h1:PY6S0sAD3xANVvM9WY38bz9GqMTjIbytC8IJJ9Cv23o= github.com/linode/linodego/k8s v1.25.2/go.mod h1:DC1XCSRZRGsmaa/ggpDPSDUmOM6aK1bhSIP6+f9Cwhc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= From 2c37869fc030cd9ec6b40ba3bb771b4c9b7f8a60 Mon Sep 17 00:00:00 2001 From: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> Date: Wed, 31 Jul 2024 03:38:32 -0400 Subject: [PATCH 06/25] Add workflow job to verify registry publication after release (#1527) * Add workflow job to verify registry publication after release * Read registry polling config from repo vars --- .github/workflows/release.yml | 66 +++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 124b4cfa4..73a4fdaf8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,3 +24,69 @@ jobs: gpg-private-key-passphrase: ${{ secrets.PASSPHRASE }} with: setup-go-version-file: 'go.mod' + + verify-publications: + needs: terraform-provider-release + runs-on: ubuntu-latest + name: Verifying TF Registry Publications + strategy: + matrix: + registry: + - "https://registry.opentofu.org/v1" + - "https://registry.terraform.io/v1" + steps: + - uses: actions/checkout@v4 + - uses: actions/github-script@v7 + env: + REGISTRY: ${{ matrix.registry }} + with: + script: | + async function verifyPublication(targetVersion, registry) { + const url = `${registry}/providers/linode/linode/versions`; + + const response = await fetch(url); + if (!response.ok) { + console.log(`Error response status: ${response.status}`); + } + + const json = await response.json(); + + return json.versions.find((v) => v.version == targetVersion) != null; + } + + let prefix = "refs/tags/v"; + if (!context.ref.startsWith(prefix)) { + throw new Error(`Invalid ref: ${context.ref}`); + } + + const TARGET_VERSION = context.ref.slice(prefix.length); + const REGISTRY = process.env.REGISTRY; + + // 1 retry request per minute, 3 hours in total + const REGISTRY_POLL_RETRIES = ${{ vars.REGISTRY_POLL_RETRIES }}; + const REGISTRY_POLL_INTERVAL = ${{ vars.REGISTRY_POLL_INTERVAL }}; + + console.log(`Verifying publication of v${TARGET_VERSION} on ${REGISTRY}`); + + let found = false; + let count = 0; + while (!found && count < REGISTRY_POLL_RETRIES) { + count++; + found = await verifyPublication(TARGET_VERSION, REGISTRY); + if (found) { + break; + } + console.log( + `Publication of v${TARGET_VERSION} on ${REGISTRY} isn't found, retrying in ${REGISTRY_POLL_INTERVAL} ms...` + ); + await new Promise((r) => setTimeout(r, REGISTRY_POLL_INTERVAL)); + } + if (found) { + console.log( + `Verified that Linode Provider v${TARGET_VERSION} has been successfully published on ${REGISTRY}.` + ); + } else { + throw new Error( + `Timeout waiting for Linode Provider v${TARGET_VERSION} publication on ${REGISTRY}` + ); + } From a1ac6a0845059ad7e5ece4e5c55dfabc8da834d2 Mon Sep 17 00:00:00 2001 From: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> Date: Wed, 31 Jul 2024 03:55:16 -0400 Subject: [PATCH 07/25] Support tags in nested node pool (#1530) * Support tags in nested node pool * Update linode/lke/schema_resource.go Co-authored-by: Lena Garber <114949949+lgarber-akamai@users.noreply.github.com> * gofumpt * Add tags check in pool matching by attributes * Update set comparison * Add not operator --------- Co-authored-by: Lena Garber <114949949+lgarber-akamai@users.noreply.github.com> --- linode/helper/compare.go | 18 ++++++++++++++++-- linode/lke/cluster.go | 10 +++++++++- linode/lke/resource.go | 1 + linode/lke/resource_test.go | 7 +++++++ linode/lke/schema_resource.go | 31 +++++++++++++++++++++++++++++++ linode/lke/tmpl/basic.gotf | 1 + linode/lke/tmpl/updates.gotf | 1 + 7 files changed, 66 insertions(+), 3 deletions(-) diff --git a/linode/helper/compare.go b/linode/helper/compare.go index 357e7aec0..172a45c79 100644 --- a/linode/helper/compare.go +++ b/linode/helper/compare.go @@ -51,12 +51,14 @@ func StringListElementsEqual(a, b []string) bool { return true } -// Check if `subset` is a subset of `superset`, or in other words, whether slice `superset` contains all elements of slice `subset`. +// Check if `subset` is a subset of `superset`, or in other words, assuming no duplicated items are in the sets, +// whether slice `superset` contains all elements of slice `subset`. func ValidateStringSubset(superset, subset []string) bool { return ValidateSubset(TypedSliceToAny(superset), TypedSliceToAny(subset)) } -// Check if `subset` is a subset of `superset`, or in other words, whether slice `superset` contains all elements of slice `subset`. +// Check if `subset` is a subset of `superset`, or in other words, whether slice `superset` contains all elements of slice `subset`, +// assuming no duplicated items are in the sets. func ValidateSubset(superset, subset []any) bool { for _, v := range subset { if !slices.Contains(superset, v) { @@ -67,6 +69,18 @@ func ValidateSubset(superset, subset []any) bool { return true } +// Check if two slices are equivalent without considering ordering, +// assuming no duplicated items are in the sets. +func CompareSets(a, b []any) bool { + return ValidateSubset(a, b) && ValidateSubset(b, a) +} + +// Check if two string slices are equivalent without considering ordering, +// assuming no duplicated items are in the sets. +func CompareStringSets(a, b []string) bool { + return CompareSets(TypedSliceToAny(a), TypedSliceToAny(b)) +} + func CompareScopes(s1, s2 string) bool { s1AccountScope := s1 == "*" s2AccountScope := s2 == "*" diff --git a/linode/lke/cluster.go b/linode/lke/cluster.go index 77944435b..8f08fe06e 100644 --- a/linode/lke/cluster.go +++ b/linode/lke/cluster.go @@ -15,6 +15,7 @@ import ( type NodePoolSpec struct { ID int + Tags []string Type string Count int AutoScalerEnabled bool @@ -110,6 +111,7 @@ func ReconcileLKENodePoolSpecs( updateOpts := linodego.LKENodePoolUpdateOptions{ Count: newSpec.Count, + Tags: &newSpecs[i].Tags, } // Only include the autoscaler if the autoscaler has updated @@ -335,6 +337,10 @@ func matchPoolsWithSchema(pools []linodego.LKENodePool, declaredPools []interfac continue } + if !helper.CompareStringSets(helper.ExpandStringSet(declaredPool["tags"].(*schema.Set)), apiPool.Tags) { + continue + } + // Pair the API pool with the declared pool result[i] = apiPool delete(apiPools, apiPool.ID) @@ -387,6 +393,7 @@ func expandLinodeLKENodePoolSpecs(pool []interface{}, preserveNoTarget bool) (po poolSpecs = append(poolSpecs, NodePoolSpec{ ID: specMap["id"].(int), Type: specMap["type"].(string), + Tags: helper.ExpandStringSet(specMap["tags"].(*schema.Set)), Count: specMap["count"].(int), AutoScalerEnabled: autoscaler.Enabled, AutoScalerMin: autoscaler.Min, @@ -396,7 +403,7 @@ func expandLinodeLKENodePoolSpecs(pool []interface{}, preserveNoTarget bool) (po return } -func flattenLKENodePools(pools []linodego.LKEClusterPool) []map[string]interface{} { +func flattenLKENodePools(pools []linodego.LKENodePool) []map[string]interface{} { flattened := make([]map[string]interface{}, len(pools)) for i, pool := range pools { @@ -424,6 +431,7 @@ func flattenLKENodePools(pools []linodego.LKEClusterPool) []map[string]interface "id": pool.ID, "count": pool.Count, "type": pool.Type, + "tags": pool.Tags, "disk_encryption": pool.DiskEncryption, "nodes": nodes, "autoscaler": autoscaler, diff --git a/linode/lke/resource.go b/linode/lke/resource.go index 8c723ab26..0b7111602 100644 --- a/linode/lke/resource.go +++ b/linode/lke/resource.go @@ -177,6 +177,7 @@ func createResource(ctx context.Context, d *schema.ResourceData, meta interface{ createOpts.NodePools = append(createOpts.NodePools, linodego.LKENodePoolCreateOptions{ Type: poolSpec["type"].(string), + Tags: helper.ExpandStringSet(poolSpec["tags"].(*schema.Set)), Count: count, Autoscaler: autoscaler, }) diff --git a/linode/lke/resource_test.go b/linode/lke/resource_test.go index 7bf88040a..e1b1dd70f 100644 --- a/linode/lke/resource_test.go +++ b/linode/lke/resource_test.go @@ -187,6 +187,8 @@ func TestAccResourceLKECluster_basic_smoke(t *testing.T) { resource.TestCheckResourceAttr(resourceClusterName, "pool.0.count", "3"), resource.TestCheckResourceAttrSet(resourceClusterName, "pool.0.disk_encryption"), resource.TestCheckResourceAttr(resourceClusterName, "pool.0.nodes.#", "3"), + resource.TestCheckResourceAttr(resourceClusterName, "pool.0.tags.#", "1"), + resource.TestCheckResourceAttr(resourceClusterName, "pool.0.tags.0", "test"), resource.TestCheckResourceAttr(resourceClusterName, "control_plane.#", "1"), resource.TestCheckResourceAttr(resourceClusterName, "control_plane.0.high_availability", "false"), resource.TestCheckResourceAttrSet(resourceClusterName, "id"), @@ -293,6 +295,8 @@ func TestAccResourceLKECluster_basicUpdates(t *testing.T) { resource.TestCheckResourceAttr(resourceClusterName, "label", clusterName), resource.TestCheckResourceAttr(resourceClusterName, "tags.#", "1"), resource.TestCheckResourceAttr(resourceClusterName, "pool.#", "1"), + resource.TestCheckResourceAttr(resourceClusterName, "pool.0.tags.#", "1"), + resource.TestCheckResourceAttr(resourceClusterName, "pool.0.tags.0", "test"), resource.TestCheckResourceAttr(resourceClusterName, "pool.0.count", "3"), ), }, @@ -301,6 +305,9 @@ func TestAccResourceLKECluster_basicUpdates(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceClusterName, "label", newClusterName), resource.TestCheckResourceAttr(resourceClusterName, "tags.#", "2"), + resource.TestCheckResourceAttr(resourceClusterName, "pool.0.tags.#", "2"), + resource.TestCheckResourceAttr(resourceClusterName, "pool.0.tags.0", "test"), + resource.TestCheckResourceAttr(resourceClusterName, "pool.0.tags.1", "test-2"), resource.TestCheckResourceAttr(resourceClusterName, "pool.0.count", "4"), ), }, diff --git a/linode/lke/schema_resource.go b/linode/lke/schema_resource.go index 3b187df83..9a6641bd9 100644 --- a/linode/lke/schema_resource.go +++ b/linode/lke/schema_resource.go @@ -1,8 +1,12 @@ package lke import ( + "strconv" + "strings" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/linode/terraform-provider-linode/v2/linode/helper" ) var resourceSchema = map[string]*schema.Schema{ @@ -79,6 +83,33 @@ var resourceSchema = map[string]*schema.Schema{ Description: "A Linode Type for all of the nodes in the Node Pool.", Required: true, }, + "tags": { + Type: schema.TypeSet, + Description: "A set of tags applied to this node pool.", + + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + DiffSuppressOnRefresh: true, + DiffSuppressFunc: func(k, oldValue, newValue string, d *schema.ResourceData) bool { + setAttrName := k[0:strings.LastIndex(k, ".")] + + index, err := strconv.Atoi(k[strings.LastIndex(k, "."):]) + if err != nil { + return false + } + + tags := helper.ExpandStringSet(d.Get(setAttrName).(*schema.Set)) + for i, item := range tags { + if i == index { + continue + } + if strings.EqualFold(item, oldValue) || strings.EqualFold(item, newValue) { + return true + } + } + return false + }, + }, "disk_encryption": { Type: schema.TypeString, Description: "The disk encryption policy for the nodes in this pool. " + diff --git a/linode/lke/tmpl/basic.gotf b/linode/lke/tmpl/basic.gotf index 1f7557d8a..0e1f8dca5 100644 --- a/linode/lke/tmpl/basic.gotf +++ b/linode/lke/tmpl/basic.gotf @@ -9,6 +9,7 @@ resource "linode_lke_cluster" "test" { pool { type = "g6-standard-2" count = 3 + tags = ["test"] } } diff --git a/linode/lke/tmpl/updates.gotf b/linode/lke/tmpl/updates.gotf index cfa133894..9b547aacb 100644 --- a/linode/lke/tmpl/updates.gotf +++ b/linode/lke/tmpl/updates.gotf @@ -9,6 +9,7 @@ resource "linode_lke_cluster" "test" { pool { type = "g6-standard-2" count = 4 + tags = ["test", "test-2"] } } From 365fced208d963783f0d77ac11280c1fabc2776c Mon Sep 17 00:00:00 2001 From: Erik Zilber Date: Wed, 31 Jul 2024 13:47:52 -0400 Subject: [PATCH 08/25] Resolved createTempKeys failure when bucket is deleted (#1524) * Resolved createTempKeys failure when bucket is deleted * Updated helper to use more general error message --- linode/helper/objects.go | 9 +++++++++ linode/obj/resource.go | 15 +++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/linode/helper/objects.go b/linode/helper/objects.go index 180379f7b..e009753d2 100644 --- a/linode/helper/objects.go +++ b/linode/helper/objects.go @@ -99,6 +99,15 @@ func IsObjNotFoundErr(err error) bool { return errors.As(err, &apiErr) && (apiErr.ErrorCode() == "NotFound" || apiErr.ErrorCode() == "Forbidden") } +// isBucketNotFoundError checks if the error is due to the bucket not being found. +func IsBucketNotFoundError(err error) bool { + tflog.Debug( + context.Background(), + fmt.Sprintf("received an error: %s, checking whether it's a bucket not found error", err), + ) + return strings.Contains(err.Error(), "Bucket not found") +} + // Purge all objects, wiping out all versions and delete markers for versioned objects. func PurgeAllObjects( ctx context.Context, diff --git a/linode/obj/resource.go b/linode/obj/resource.go index baf8c1a46..5c44d376a 100644 --- a/linode/obj/resource.go +++ b/linode/obj/resource.go @@ -52,14 +52,21 @@ func readResource(ctx context.Context, d *schema.ResourceData, meta any) diag.Di key := d.Get("key").(string) objKeys, diags, teardownKeysCleanUp := GetObjKeys(ctx, d, config, client, bucket, regionOrCluster, "read_only") - if teardownKeysCleanUp != nil { - defer teardownKeysCleanUp() - } - if diags != nil { + // Check if the error is due to the bucket being not found + if helper.IsBucketNotFoundError(fmt.Errorf(diags[0].Summary)) { + d.SetId("") + tflog.Warn(ctx, + "couldn't find the bucket, removing the object from the TF state") + return nil + } return diags } + if teardownKeysCleanUp != nil { + defer teardownKeysCleanUp() + } + s3client, err := helper.S3ConnectionFromData(ctx, d, meta, objKeys.AccessKey, objKeys.SecretKey) if err != nil { return diag.FromErr(err) From 60003c63812b35826537fd58125b01a922933aea Mon Sep 17 00:00:00 2001 From: Lena Garber <114949949+lgarber-akamai@users.noreply.github.com> Date: Wed, 31 Jul 2024 14:52:32 -0400 Subject: [PATCH 09/25] fix: Fix LDE-related test failures (#1533) * Fix LDE-related test failures * Add tags to create options during reconciliation --- linode/instance/resource_test.go | 4 ++-- linode/lke/cluster.go | 1 + linode/lke/datasource_test.go | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/linode/instance/resource_test.go b/linode/instance/resource_test.go index fcf21b124..3b5532761 100644 --- a/linode/instance/resource_test.go +++ b/linode/instance/resource_test.go @@ -31,7 +31,7 @@ func init() { }) region, err := acceptance.GetRandomRegionWithCaps([]string{ - linodego.Vlans, linodego.VPCs, linodego.DiskEncryption, + linodego.CapabilityVlans, linodego.CapabilityVPCs, linodego.CapabilityDiskEncryption, }) if err != nil { log.Fatal(err) @@ -2523,7 +2523,7 @@ func TestAccResourceInstance_diskEncryption(t *testing.T) { // Resolve a region that supports disk encryption targetRegion, err := acceptance.GetRandomRegionWithCaps( - []string{linodego.Linodes, linodego.DiskEncryption}, + []string{linodego.CapabilityLinodes, linodego.CapabilityDiskEncryption}, ) if err != nil { t.Fatal(err) diff --git a/linode/lke/cluster.go b/linode/lke/cluster.go index 8f08fe06e..7af29b364 100644 --- a/linode/lke/cluster.go +++ b/linode/lke/cluster.go @@ -42,6 +42,7 @@ func ReconcileLKENodePoolSpecs( createOpts := linodego.LKENodePoolCreateOptions{ Count: spec.Count, Type: spec.Type, + Tags: spec.Tags, } if createOpts.Count == 0 { diff --git a/linode/lke/datasource_test.go b/linode/lke/datasource_test.go index f83e414a1..ebf538fa8 100644 --- a/linode/lke/datasource_test.go +++ b/linode/lke/datasource_test.go @@ -34,7 +34,7 @@ func TestAccDataSourceLKECluster_basic(t *testing.T) { resource.TestCheckResourceAttr(dataSourceClusterName, "pools.#", "1"), resource.TestCheckResourceAttr(dataSourceClusterName, "pools.0.type", "g6-standard-2"), resource.TestCheckResourceAttr(dataSourceClusterName, "pools.0.count", "3"), - resource.TestCheckResourceAttrSet(dataSourceClusterName, "pool.0.disk_encryption"), + resource.TestCheckResourceAttrSet(dataSourceClusterName, "pools.0.disk_encryption"), resource.TestCheckResourceAttr(dataSourceClusterName, "pools.0.nodes.#", "3"), resource.TestCheckResourceAttr(dataSourceClusterName, "pools.0.autoscaler.#", "0"), resource.TestCheckResourceAttr(dataSourceClusterName, "control_plane.0.high_availability", "false"), From adef3fff09370b06fcbeacc33c29d430e94cd5bc Mon Sep 17 00:00:00 2001 From: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> Date: Mon, 5 Aug 2024 17:10:34 -0400 Subject: [PATCH 10/25] Add deprecate message to cluster attributes (#1534) --- linode/obj/schema_resource.go | 6 ++++-- linode/objbucket/framework_datasource_schema.go | 7 +++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/linode/obj/schema_resource.go b/linode/obj/schema_resource.go index f0256a57e..b21fbdfca 100644 --- a/linode/obj/schema_resource.go +++ b/linode/obj/schema_resource.go @@ -14,8 +14,10 @@ var resourceSchema = map[string]*schema.Schema{ ForceNew: true, }, "cluster": { - Type: schema.TypeString, - Description: "The target cluster that the bucket is in.", + Type: schema.TypeString, + Description: "The target cluster that the bucket is in.", + Deprecated: "The cluster attribute has been deprecated, please consider switching to the region attribute. " + + "For example, a cluster value of `us-mia-1` can be translated to a region value of `us-mia`.", Optional: true, ForceNew: true, ExactlyOneOf: []string{"cluster", "region"}, diff --git a/linode/objbucket/framework_datasource_schema.go b/linode/objbucket/framework_datasource_schema.go index e0cb1a768..85e8f2bcf 100644 --- a/linode/objbucket/framework_datasource_schema.go +++ b/linode/objbucket/framework_datasource_schema.go @@ -11,8 +11,11 @@ var frameworkDatasourceSchema = schema.Schema{ Attributes: map[string]schema.Attribute{ "cluster": schema.StringAttribute{ Description: "The ID of the Object Storage Cluster this bucket is in.", - Optional: true, - Computed: true, + DeprecationMessage: "The cluster attribute has been deprecated, please consider " + + "switching to the region attribute. For example, a cluster value of `us-mia-1` " + + "can be translated to a region value of `us-mia`.", + Optional: true, + Computed: true, Validators: []validator.String{ stringvalidator.ExactlyOneOf( path.MatchRelative().AtParent().AtName("region"), From 94ce45fca299129069eb6c38aa9b7f3cbf7ce5ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 21:25:51 -0400 Subject: [PATCH 11/25] build(deps): bump golang.org/x/sync from 0.7.0 to 0.8.0 (#1535) Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.7.0 to 0.8.0. - [Commits](https://github.com/golang/sync/compare/v0.7.0...v0.8.0) --- updated-dependencies: - dependency-name: golang.org/x/sync dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 748f18cfc..3599e7490 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/stretchr/testify v1.9.0 golang.org/x/crypto v0.25.0 golang.org/x/net v0.27.0 - golang.org/x/sync v0.7.0 + golang.org/x/sync v0.8.0 ) require ( diff --git a/go.sum b/go.sum index 76d1394ba..3be10aad1 100644 --- a/go.sum +++ b/go.sum @@ -295,8 +295,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From cb5a0b0f2c87b9ceafacf0f2a9bffb1b5a921cb0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Aug 2024 13:54:19 -0400 Subject: [PATCH 12/25] build(deps): bump github.com/go-resty/resty/v2 from 2.13.1 to 2.14.0 (#1538) Bumps [github.com/go-resty/resty/v2](https://github.com/go-resty/resty) from 2.13.1 to 2.14.0. - [Release notes](https://github.com/go-resty/resty/releases) - [Commits](https://github.com/go-resty/resty/compare/v2.13.1...v2.14.0) --- updated-dependencies: - dependency-name: github.com/go-resty/resty/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 20 ++++++++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 3599e7490..e17b3e886 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.4 github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 github.com/aws/smithy-go v1.20.3 - github.com/go-resty/resty/v2 v2.13.1 + github.com/go-resty/resty/v2 v2.14.0 github.com/google/go-cmp v0.6.0 github.com/hashicorp/go-cty v1.4.1-0.20200723130312-85980079f637 github.com/hashicorp/go-hclog v1.6.3 @@ -101,7 +101,7 @@ require ( golang.org/x/sys v0.22.0 // indirect golang.org/x/term v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect - golang.org/x/time v0.5.0 // indirect + golang.org/x/time v0.6.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect diff --git a/go.sum b/go.sum index 3be10aad1..239d56b22 100644 --- a/go.sum +++ b/go.sum @@ -79,8 +79,8 @@ github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2Kv github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-resty/resty/v2 v2.13.1 h1:x+LHXBI2nMB1vqndymf26quycC4aggYJ7DECYbiz03g= -github.com/go-resty/resty/v2 v2.13.1/go.mod h1:GznXlLxkq6Nh4sU59rPmUw3VtgpO3aS96ORAI6Q7d+0= +github.com/go-resty/resty/v2 v2.14.0 h1:/rhkzsAqGQkozwfKS5aFAbb6TyKd3zyFRWcdRXLPCAU= +github.com/go-resty/resty/v2 v2.14.0/go.mod h1:IW6mekUOsElt9C7oWr0XRt9BNSD6D5rr9mhk6NjmNHg= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= @@ -265,6 +265,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= @@ -273,6 +274,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -283,6 +286,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= @@ -295,6 +299,9 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -313,14 +320,17 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= @@ -332,18 +342,20 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From c0a854066598f9cec98854995328c6772e02ebdd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2024 09:03:04 -0400 Subject: [PATCH 13/25] build(deps): bump github.com/hashicorp/terraform-plugin-framework-timetypes (#1539) Bumps [github.com/hashicorp/terraform-plugin-framework-timetypes](https://github.com/hashicorp/terraform-plugin-framework-timetypes) from 0.4.0 to 0.5.0. - [Release notes](https://github.com/hashicorp/terraform-plugin-framework-timetypes/releases) - [Changelog](https://github.com/hashicorp/terraform-plugin-framework-timetypes/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/terraform-plugin-framework-timetypes/compare/v0.4.0...v0.5.0) --- updated-dependencies: - dependency-name: github.com/hashicorp/terraform-plugin-framework-timetypes dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index e17b3e886..e0cff3e8f 100644 --- a/go.mod +++ b/go.mod @@ -14,9 +14,9 @@ require ( github.com/hashicorp/go-cty v1.4.1-0.20200723130312-85980079f637 github.com/hashicorp/go-hclog v1.6.3 github.com/hashicorp/go-version v1.7.0 - github.com/hashicorp/terraform-plugin-framework v1.10.0 + github.com/hashicorp/terraform-plugin-framework v1.11.0 github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1 - github.com/hashicorp/terraform-plugin-framework-timetypes v0.4.0 + github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 github.com/hashicorp/terraform-plugin-framework-validators v0.13.0 github.com/hashicorp/terraform-plugin-go v0.23.0 github.com/hashicorp/terraform-plugin-log v0.9.0 diff --git a/go.sum b/go.sum index 239d56b22..ba91b5f2e 100644 --- a/go.sum +++ b/go.sum @@ -137,12 +137,12 @@ github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVW github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= -github.com/hashicorp/terraform-plugin-framework v1.10.0 h1:xXhICE2Fns1RYZxEQebwkB2+kXouLC932Li9qelozrc= -github.com/hashicorp/terraform-plugin-framework v1.10.0/go.mod h1:qBXLDn69kM97NNVi/MQ9qgd1uWWsVftGSnygYG1tImM= +github.com/hashicorp/terraform-plugin-framework v1.11.0 h1:M7+9zBArexHFXDx/pKTxjE6n/2UCXY6b8FIq9ZYhwfE= +github.com/hashicorp/terraform-plugin-framework v1.11.0/go.mod h1:qBXLDn69kM97NNVi/MQ9qgd1uWWsVftGSnygYG1tImM= github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1 h1:gm5b1kHgFFhaKFhm4h2TgvMUlNzFAtUqlcOWnWPm+9E= github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1/go.mod h1:MsjL1sQ9L7wGwzJ5RjcI6FzEMdyoBnw+XK8ZnOvQOLY= -github.com/hashicorp/terraform-plugin-framework-timetypes v0.4.0 h1:XLI93Oqw2/KTzYjgCXrUnm8LBkGAiHC/mDQg5g5Vob4= -github.com/hashicorp/terraform-plugin-framework-timetypes v0.4.0/go.mod h1:mGuieb3bqKFYwEYB4lCMt302Z3siyv4PFYk/41wAUps= +github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 h1:v3DapR8gsp3EM8fKMh6up9cJUFQ2iRaFsYLP8UJnCco= +github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0/go.mod h1:c3PnGE9pHBDfdEVG9t1S1C9ia5LW+gkFR0CygXlM8ak= github.com/hashicorp/terraform-plugin-framework-validators v0.13.0 h1:bxZfGo9DIUoLLtHMElsu+zwqI4IsMZQBRRy4iLzZJ8E= github.com/hashicorp/terraform-plugin-framework-validators v0.13.0/go.mod h1:wGeI02gEhj9nPANU62F2jCaHjXulejm/X+af4PdZaNo= github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/125t+AAurhqphJ2Co= From 33279a85e2d19ab82684de631b500f17bca0aa71 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2024 09:03:19 -0400 Subject: [PATCH 14/25] build(deps): bump golang.org/x/net from 0.27.0 to 0.28.0 (#1541) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.27.0 to 0.28.0. - [Commits](https://github.com/golang/net/compare/v0.27.0...v0.28.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 15 ++++++++++----- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index e0cff3e8f..8591475f7 100644 --- a/go.mod +++ b/go.mod @@ -26,8 +26,8 @@ require ( github.com/linode/linodego v1.38.0 github.com/linode/linodego/k8s v1.25.2 github.com/stretchr/testify v1.9.0 - golang.org/x/crypto v0.25.0 - golang.org/x/net v0.27.0 + golang.org/x/crypto v0.26.0 + golang.org/x/net v0.28.0 golang.org/x/sync v0.8.0 ) @@ -98,9 +98,9 @@ require ( github.com/zclconf/go-cty v1.14.4 // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/term v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/term v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect golang.org/x/time v0.6.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/appengine v1.6.8 // indirect diff --git a/go.sum b/go.sum index ba91b5f2e..0727178c8 100644 --- a/go.sum +++ b/go.sum @@ -268,8 +268,9 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -289,8 +290,9 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -323,8 +325,9 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -333,8 +336,9 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -345,8 +349,9 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 7d73f7092fbcfb28e2f6ea4424add88960944fd3 Mon Sep 17 00:00:00 2001 From: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> Date: Wed, 7 Aug 2024 15:58:15 -0400 Subject: [PATCH 15/25] Fix flatten image panic when error happens in image creation (#1543) --- linode/image/framework_resource.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/linode/image/framework_resource.go b/linode/image/framework_resource.go index 1218977e1..c85c127a0 100644 --- a/linode/image/framework_resource.go +++ b/linode/image/framework_resource.go @@ -213,6 +213,10 @@ func (r *Resource) Create( image = createResourceFromUpload(ctx, &plan, client, resp, timeoutSeconds) } + if resp.Diagnostics.HasError() { + return + } + plan.FlattenImage(image, true, &resp.Diagnostics) if resp.Diagnostics.HasError() { return From b62bee0dc05d3544fff27c78411bc9d13c0d058b Mon Sep 17 00:00:00 2001 From: Erik Zilber Date: Thu, 8 Aug 2024 11:05:25 -0400 Subject: [PATCH 16/25] Added missing placement_group field to linode_instances data source (#1529) * Added missing placement_group field to linode_instances data source * Added additional integration test * Addressed PR comments --------- Co-authored-by: Ye Chen <127243817+yec-akamai@users.noreply.github.com> --- docs/data-sources/instances.md | 16 +++++++- linode/instance/datasource_test.go | 37 +++++++++++++++++++ linode/instance/flatten.go | 22 ++++------- linode/instance/resource.go | 11 +++++- linode/instance/schema_datasource.go | 33 +++++++++++++++++ linode/instance/tmpl/template.go | 10 +++++ .../instance/tmpl/templates/data_with_pg.gotf | 28 ++++++++++++++ 7 files changed, 140 insertions(+), 17 deletions(-) create mode 100644 linode/instance/tmpl/templates/data_with_pg.gotf diff --git a/docs/data-sources/instances.md b/docs/data-sources/instances.md index 3b53bb341..57d796926 100644 --- a/docs/data-sources/instances.md +++ b/docs/data-sources/instances.md @@ -125,6 +125,8 @@ Each Linode instance will be stored in the `instances` attribute and will export * [`backups`](#backups) - Information about the Linode's backup status. +* [`placement_group`](#placement-groups) - Information about the Linode's Placement Groups. + ### Disks * `disk` @@ -220,7 +222,19 @@ The following arguments are available in an `ipv4` configuration block of an `in * `day` - The day of the week that your Linode's weekly Backup is taken. If not set manually, a day will be chosen for you. Backups are taken every day, but backups taken on this day are preferred when selecting backups to retain for a longer period. If not set manually, then when backups are initially enabled, this may come back as "Scheduling" until the day is automatically selected. * `window` - The window ('W0'-'W22') in which your backups will be taken, in UTC. A backups window is a two-hour span of time in which the backup may occur. For example, 'W10' indicates that your backups should be taken between 10:00 and 12:00. If you do not choose a backup window, one will be selected for you automatically. If not set manually, when backups are initially enabled this may come back as Scheduling until the window is automatically selected. - + +### Placement Groups + +* `placement_group` + + * `id` - The ID of the Placement Group in the Linode API. + + * `placement_group_type` - The placement group type to use when placing Linodes in this group. + + * `placement_group_policy` - Whether Linodes must be able to become compliant during assignment. (Default `strict`) + + * `label` - The label of the Placement Group. This field can only contain ASCII letters, digits and dashes. + ## Filterable Fields * `group` diff --git a/linode/instance/datasource_test.go b/linode/instance/datasource_test.go index 92ea7018d..c7c515230 100644 --- a/linode/instance/datasource_test.go +++ b/linode/instance/datasource_test.go @@ -43,6 +43,43 @@ func TestAccDataSourceInstances_basic(t *testing.T) { resource.TestCheckResourceAttr(resName, "instances.0.disk.#", "2"), resource.TestCheckResourceAttr(resName, "instances.0.config.#", "1"), resource.TestCheckResourceAttrSet(resName, "instances.0.config.0.id"), + resource.TestCheckResourceAttr(resName, "instances.0.placement_group.#", "0"), + ), + }, + }, + }) +} + +func TestAccDataSourceInstances_withPG(t *testing.T) { + t.Parallel() + + resName := "data.linode_instances.foobar" + instanceName := acctest.RandomWithPrefix("tf_test") + + pgIDs := []string{"foobar"} + + // Resolve a region with support for PGs + targetRegion, err := acceptance.GetRandomRegionWithCaps( + []string{"Linodes", "Placement Group"}, + ) + if err != nil { + t.Fatal(err) + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, + CheckDestroy: acceptance.CheckInstanceDestroy, + + Steps: []resource.TestStep{ + { + Config: tmpl.DataWithPG(t, instanceName, targetRegion, "foobar", pgIDs), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resName, "instances.#", "1"), + resource.TestCheckResourceAttr(resName, "instances.0.placement_group.#", "1"), + resource.TestCheckResourceAttrSet(resName, "instances.0.placement_group.0.id"), + resource.TestCheckResourceAttrSet(resName, "instances.0.placement_group.0.placement_group_type"), + resource.TestCheckResourceAttrSet(resName, "instances.0.placement_group.0.placement_group_policy"), ), }, }, diff --git a/linode/instance/flatten.go b/linode/instance/flatten.go index e31a47931..b3566bf07 100644 --- a/linode/instance/flatten.go +++ b/linode/instance/flatten.go @@ -4,8 +4,6 @@ import ( "context" "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/linode/linodego" "github.com/linode/terraform-provider-linode/v2/linode/helper" ) @@ -57,6 +55,7 @@ func flattenInstance( result["backups"] = flattenInstanceBackups(*instance) result["specs"] = flattenInstanceSpecs(*instance) result["alerts"] = flattenInstanceAlerts(*instance) + result["placement_group"] = flattenInstancePlacementGroup(*instance) instanceDisks, err := client.ListInstanceDisks(ctx, id, nil) if err != nil { @@ -225,23 +224,16 @@ func flattenInstanceSimple(instance *linodego.Instance) (map[string]interface{}, return result, nil } -// NOTE: The ResourceData needs to be passed down here because compliant_only is NOT returned -// by the API and instead needs to be carried over from the old state. -func flattenInstancePlacementGroup(d *schema.ResourceData, pg *linodego.InstancePlacementGroup) []map[string]any { - if pg == nil { +func flattenInstancePlacementGroup(instance linodego.Instance) []map[string]any { + if instance.PlacementGroup == nil { return nil } result := map[string]any{ - "id": pg.ID, - "label": pg.Label, - "placement_group_type": string(pg.PlacementGroupType), - "placement_group_policy": pg.PlacementGroupPolicy, - } - - // Inherit compliant_only if it already exists in state - if compliantOnly, ok := d.GetOk("placement_group.0.compliant_only"); ok { - result["compliant_only"] = compliantOnly.(bool) + "id": instance.PlacementGroup.ID, + "label": instance.PlacementGroup.Label, + "placement_group_type": instance.PlacementGroup.PlacementGroupType, + "placement_group_policy": instance.PlacementGroup.PlacementGroupPolicy, } return []map[string]any{result} diff --git a/linode/instance/resource.go b/linode/instance/resource.go index 6b9359be5..76c6c240f 100644 --- a/linode/instance/resource.go +++ b/linode/instance/resource.go @@ -128,7 +128,16 @@ func readResource(ctx context.Context, d *schema.ResourceData, meta interface{}) d.Set("specs", flatSpecs) d.Set("alerts", flatAlerts) - d.Set("placement_group", flattenInstancePlacementGroup(d, instance.PlacementGroup)) + var placementGroupMap map[string]interface{} + flattenedGroups := flattenInstancePlacementGroup(*instance) + if len(flattenedGroups) > 0 { + placementGroupMap = flattenedGroups[0] + // Inherit compliant_only if it already exists in state + if compliantOnly, ok := d.GetOk("placement_group.0.compliant_only"); ok { + placementGroupMap["compliant_only"] = compliantOnly.(bool) + } + d.Set("placement_group", []map[string]interface{}{placementGroupMap}) + } disks, swapSize := flattenInstanceDisks(instanceDisks) d.Set("disk", disks) diff --git a/linode/instance/schema_datasource.go b/linode/instance/schema_datasource.go index 6a69a7d33..66e344901 100644 --- a/linode/instance/schema_datasource.go +++ b/linode/instance/schema_datasource.go @@ -419,4 +419,37 @@ var instanceDataSourceSchema = map[string]*schema.Schema{ }, }, }, + "placement_group": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeInt, + Description: "The placement group's ID. You need to provide it for all operations impacting it.", + Computed: true, + }, + "label": { + Type: schema.TypeString, + Description: "The unique name set for the placement group.", + Computed: true, + }, + "placement_group_type": { + Type: schema.TypeString, + Description: "How compute instances are distributed in your placement group. " + + "anti-affinity:local places compute instances in separate fault domains, but still in the same region.", + Computed: true, + }, + "placement_group_policy": { + Type: schema.TypeString, + Description: "How the API enforces your placement_group_type. Set to strict, your group is strict. You can't " + + "add more compute instances to your placement group if your preferred container lacks capacity or is" + + " unavailable. Set to flexible, your group is flexible. You can add more compute instances to it even if " + + "they violate the placement_group_type. If you violate the placement_group_type your placement group becomes " + + "non-compliant and you need to wait for our assistance.", + Computed: true, + }, + }, + }, + }, } diff --git a/linode/instance/tmpl/template.go b/linode/instance/tmpl/template.go index d3d2ea537..2dd51a57d 100644 --- a/linode/instance/tmpl/template.go +++ b/linode/instance/tmpl/template.go @@ -623,6 +623,16 @@ func DataBasic(t *testing.T, label, region string, rootPass string) string { }) } +func DataWithPG(t *testing.T, label, region, assignedGroup string, groups []string) string { + return acceptance.ExecuteTemplate(t, + "instance_data_with_pg", TemplateData{ + Label: label, + Region: region, + PlacementGroups: groups, + AssignedGroup: assignedGroup, + }) +} + func DataMultiple(t *testing.T, label, tag, region string, rootPass string) string { return acceptance.ExecuteTemplate(t, "instance_data_multiple", TemplateData{ diff --git a/linode/instance/tmpl/templates/data_with_pg.gotf b/linode/instance/tmpl/templates/data_with_pg.gotf new file mode 100644 index 000000000..72adb239f --- /dev/null +++ b/linode/instance/tmpl/templates/data_with_pg.gotf @@ -0,0 +1,28 @@ +{{ define "instance_data_with_pg" }} + +{{ template "e2e_test_firewall" . }} + +resource "linode_placement_group" "foobar" { + label = "{{ $.Label }}" + region = "{{ $.Region }}" + placement_group_type = "anti_affinity:local" + placement_group_policy = "flexible" +} + +resource "linode_instance" "foobar" { + label = "{{ .Label }}" + type = "g6-nanode-1" + region = "{{ .Region }}" + placement_group { + id = linode_placement_group.foobar.id + } +} + +data "linode_instances" "foobar" { + filter { + name = "id" + values = [linode_instance.foobar.id] + } +} + +{{ end }} \ No newline at end of file From 96327ec3656e95c03119ea4179487755f2306bd2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 11:27:00 -0400 Subject: [PATCH 17/25] build(deps): bump github.com/hashicorp/terraform-plugin-testing (#1544) Bumps [github.com/hashicorp/terraform-plugin-testing](https://github.com/hashicorp/terraform-plugin-testing) from 1.9.0 to 1.10.0. - [Release notes](https://github.com/hashicorp/terraform-plugin-testing/releases) - [Changelog](https://github.com/hashicorp/terraform-plugin-testing/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/terraform-plugin-testing/compare/v1.9.0...v1.10.0) --- updated-dependencies: - dependency-name: github.com/hashicorp/terraform-plugin-testing dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 9 +++++---- go.sum | 17 ++++++++++------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 8591475f7..93a62de1e 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-mux v0.16.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 - github.com/hashicorp/terraform-plugin-testing v1.9.0 + github.com/hashicorp/terraform-plugin-testing v1.10.0 github.com/linode/linodego v1.38.0 github.com/linode/linodego/k8s v1.25.2 github.com/stretchr/testify v1.9.0 @@ -66,8 +66,9 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.6.0 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect - github.com/hashicorp/hc-install v0.7.0 // indirect + github.com/hashicorp/hc-install v0.8.0 // indirect github.com/hashicorp/hcl/v2 v2.21.0 // indirect github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.21.0 // indirect @@ -95,8 +96,8 @@ require ( github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/zclconf/go-cty v1.14.4 // indirect - golang.org/x/mod v0.17.0 // indirect + github.com/zclconf/go-cty v1.15.0 // indirect + golang.org/x/mod v0.19.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sys v0.23.0 // indirect golang.org/x/term v0.23.0 // indirect diff --git a/go.sum b/go.sum index 0727178c8..acd54004d 100644 --- a/go.sum +++ b/go.sum @@ -122,13 +122,15 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A= github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/hc-install v0.7.0 h1:Uu9edVqjKQxxuD28mR5TikkKDd/p55S8vzPC1659aBk= -github.com/hashicorp/hc-install v0.7.0/go.mod h1:ELmmzZlGnEcqoUMKUuykHaPCIR1sYLYX+KSggWSKZuA= +github.com/hashicorp/hc-install v0.8.0 h1:LdpZeXkZYMQhoKPCecJHlKvUkQFixN/nvyR1CdfOLjI= +github.com/hashicorp/hc-install v0.8.0/go.mod h1:+MwJYjDfCruSD/udvBmRB22Nlkwwkwf5sAB6uTIhSaU= github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit14= github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= @@ -153,8 +155,8 @@ github.com/hashicorp/terraform-plugin-mux v0.16.0 h1:RCzXHGDYwUwwqfYYWJKBFaS3fQs github.com/hashicorp/terraform-plugin-mux v0.16.0/go.mod h1:PF79mAsPc8CpusXPfEVa4X8PtkB+ngWoiUClMrNZlYo= github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 h1:kJiWGx2kiQVo97Y5IOGR4EMcZ8DtMswHhUuFibsCQQE= github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0/go.mod h1:sl/UoabMc37HA6ICVMmGO+/0wofkVIRxf+BMb/dnoIg= -github.com/hashicorp/terraform-plugin-testing v1.9.0 h1:xOsQRqqlHKXpFq6etTxih3ubdK3HVDtfE1IY7Rpd37o= -github.com/hashicorp/terraform-plugin-testing v1.9.0/go.mod h1:fhhVx/8+XNJZTD5o3b4stfZ6+q7z9+lIWigIYdT6/44= +github.com/hashicorp/terraform-plugin-testing v1.10.0 h1:2+tmRNhvnfE4Bs8rB6v58S/VpqzGC6RCh9Y8ujdn+aw= +github.com/hashicorp/terraform-plugin-testing v1.10.0/go.mod h1:iWRW3+loP33WMch2P/TEyCxxct/ZEcCGMquSLSCVsrc= github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI= github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM= github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= @@ -257,8 +259,8 @@ github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= -github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= +github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -277,8 +279,9 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= From 149123671ccf56c0b9a5bf3d45d6360914d569a6 Mon Sep 17 00:00:00 2001 From: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> Date: Mon, 12 Aug 2024 12:17:27 -0400 Subject: [PATCH 18/25] Handle unknown in config validate function (#1545) --- linode/objkey/framework_config_validation.go | 37 +++++++++++--------- linode/objkey/framework_resource.go | 2 ++ 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/linode/objkey/framework_config_validation.go b/linode/objkey/framework_config_validation.go index 3fbcb2bae..f116da090 100644 --- a/linode/objkey/framework_config_validation.go +++ b/linode/objkey/framework_config_validation.go @@ -6,34 +6,39 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" "github.com/linode/terraform-provider-linode/v2/linode/helper" ) func (r Resource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { - var config ResourceModel + validateRegionsAgainstBucketAccessEntries(ctx, req.Config, &resp.Diagnostics) +} - resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) +func validateRegionsAgainstBucketAccessEntries(ctx context.Context, config tfsdk.Config, diags *diag.Diagnostics) { + var bucketAccesses types.Set + var regions []string + var bucketRegions []string - if resp.Diagnostics.HasError() { + diags.Append(config.GetAttribute(ctx, path.Root("bucket_access"), &bucketAccesses)...) + diags.Append(config.GetAttribute(ctx, path.Root("regions"), ®ions)...) + + if diags.HasError() { return } - validateRegionsAgainstBucketAccessEntries(ctx, config, &resp.Diagnostics) -} - -func validateRegionsAgainstBucketAccessEntries(ctx context.Context, config ResourceModel, diags *diag.Diagnostics) { - // regions will be computed if not configured, so it's okay to be null. - if config.BucketAccess == nil || config.Regions.IsNull() { + if bucketAccesses.IsNull() || bucketAccesses.IsUnknown() || regions == nil { return } - var regions []string - var bucketRegions []string - - config.Regions.ElementsAs(ctx, ®ions, true) - - for _, ba := range config.BucketAccess { - bucketRegions = append(bucketRegions, ba.Region.ValueString()) + for _, ba := range bucketAccesses.Elements() { + if baObj, ok := ba.(types.Object); ok { + if region, ok := baObj.Attributes()["region"]; ok { + if regionStringValue, ok := region.(types.String); ok { + bucketRegions = append(bucketRegions, regionStringValue.ValueString()) + } + } + } } if !helper.ValidateStringSubset(regions, bucketRegions) { diff --git a/linode/objkey/framework_resource.go b/linode/objkey/framework_resource.go index 108e5097b..1c305baf2 100644 --- a/linode/objkey/framework_resource.go +++ b/linode/objkey/framework_resource.go @@ -13,6 +13,8 @@ import ( "github.com/linode/terraform-provider-linode/v2/linode/helper" ) +var _ resource.ResourceWithValidateConfig = &Resource{} + func NewResource() resource.Resource { return &Resource{ BaseResource: helper.NewBaseResource( From 8bab035a57ff3448f19e5f12787e0c18793c0452 Mon Sep 17 00:00:00 2001 From: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> Date: Tue, 13 Aug 2024 16:49:17 -0400 Subject: [PATCH 19/25] Fix Multiple Issues in Object Storage Bucket and Key Resources (#1547) * Fix issue that regions is not in key create request * Add a validator to prevent empty regions list * Update tests * Remove config validation * Add validation function for plans --- ...rce_test.go => frameowkr_resource_test.go} | 4 ++ linode/objkey/framework_config_validation.go | 51 ---------------- linode/objkey/framework_model.go | 21 +++++++ linode/objkey/framework_resource.go | 35 +++++------ linode/objkey/framework_resource_schema.go | 2 + linode/objkey/helpers.go | 61 +++++++++++++++++++ linode/objkey/tmpl/limited.gotf | 29 +++++---- 7 files changed, 118 insertions(+), 85 deletions(-) rename linode/objkey/{resource_test.go => frameowkr_resource_test.go} (97%) delete mode 100644 linode/objkey/framework_config_validation.go create mode 100644 linode/objkey/helpers.go diff --git a/linode/objkey/resource_test.go b/linode/objkey/frameowkr_resource_test.go similarity index 97% rename from linode/objkey/resource_test.go rename to linode/objkey/frameowkr_resource_test.go index 66c5547ae..dea0fc948 100644 --- a/linode/objkey/resource_test.go +++ b/linode/objkey/frameowkr_resource_test.go @@ -117,6 +117,8 @@ func TestAccResourceObjectKey_limited_cluster(t *testing.T) { resource.TestCheckResourceAttr(resName, "bucket_access.1.region", testRegion), resource.TestCheckResourceAttr(resName, "bucket_access.0.permissions", "read_only"), resource.TestCheckResourceAttr(resName, "bucket_access.1.permissions", "read_write"), + resource.TestCheckResourceAttr(resName, "regions.#", "1"), + resource.TestCheckResourceAttr(resName, "regions.0", testRegion), ), }, }, @@ -152,6 +154,8 @@ func TestAccResourceObjectKey_limited(t *testing.T) { resource.TestCheckResourceAttr(resName, "bucket_access.1.region", testRegion), resource.TestCheckResourceAttr(resName, "bucket_access.0.permissions", "read_only"), resource.TestCheckResourceAttr(resName, "bucket_access.1.permissions", "read_write"), + resource.TestCheckResourceAttr(resName, "regions.#", "1"), + resource.TestCheckResourceAttr(resName, "regions.0", testRegion), ), }, }, diff --git a/linode/objkey/framework_config_validation.go b/linode/objkey/framework_config_validation.go deleted file mode 100644 index f116da090..000000000 --- a/linode/objkey/framework_config_validation.go +++ /dev/null @@ -1,51 +0,0 @@ -package objkey - -import ( - "context" - - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/path" - "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/linode/terraform-provider-linode/v2/linode/helper" -) - -func (r Resource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { - validateRegionsAgainstBucketAccessEntries(ctx, req.Config, &resp.Diagnostics) -} - -func validateRegionsAgainstBucketAccessEntries(ctx context.Context, config tfsdk.Config, diags *diag.Diagnostics) { - var bucketAccesses types.Set - var regions []string - var bucketRegions []string - - diags.Append(config.GetAttribute(ctx, path.Root("bucket_access"), &bucketAccesses)...) - diags.Append(config.GetAttribute(ctx, path.Root("regions"), ®ions)...) - - if diags.HasError() { - return - } - - if bucketAccesses.IsNull() || bucketAccesses.IsUnknown() || regions == nil { - return - } - - for _, ba := range bucketAccesses.Elements() { - if baObj, ok := ba.(types.Object); ok { - if region, ok := baObj.Attributes()["region"]; ok { - if regionStringValue, ok := region.(types.String); ok { - bucketRegions = append(bucketRegions, regionStringValue.ValueString()) - } - } - } - } - - if !helper.ValidateStringSubset(regions, bucketRegions) { - diags.AddAttributeError( - path.Root("regions"), - "Incomplete Regions", - "All regions of the buckets defined in `bucket_access` blocks are expected in the `regions` attribute.", - ) - } -} diff --git a/linode/objkey/framework_model.go b/linode/objkey/framework_model.go index 72accf9d4..c23563811 100644 --- a/linode/objkey/framework_model.go +++ b/linode/objkey/framework_model.go @@ -52,6 +52,27 @@ func (plan ResourceModel) GetUpdateOptions( return } +func (plan ResourceModel) GetCreateOptions(ctx context.Context) (opts linodego.ObjectStorageKeyCreateOptions) { + opts.Label = plan.Label.ValueString() + + if plan.BucketAccess != nil { + accessSlice := make( + []linodego.ObjectStorageKeyBucketAccess, + len(plan.BucketAccess), + ) + + for i, v := range plan.BucketAccess { + accessSlice[i] = v.toLinodeObject() + } + + opts.BucketAccess = &accessSlice + } + + plan.Regions.ElementsAs(ctx, &opts.Regions, false) + + return +} + func getObjectStorageKeyRegionIDs(regions []linodego.ObjectStorageKeyRegion) []string { regionIDs := make([]string, len(regions)) for i, r := range regions { diff --git a/linode/objkey/framework_resource.go b/linode/objkey/framework_resource.go index 1c305baf2..982ded767 100644 --- a/linode/objkey/framework_resource.go +++ b/linode/objkey/framework_resource.go @@ -13,8 +13,6 @@ import ( "github.com/linode/terraform-provider-linode/v2/linode/helper" ) -var _ resource.ResourceWithValidateConfig = &Resource{} - func NewResource() resource.Resource { return &Resource{ BaseResource: helper.NewBaseResource( @@ -37,30 +35,20 @@ func (r *Resource) Create( resp *resource.CreateResponse, ) { tflog.Debug(ctx, "Create linode_object_storage_key") - var data ResourceModel + var plan ResourceModel client := r.Meta.Client - resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) if resp.Diagnostics.HasError() { return } - createOpts := linodego.ObjectStorageKeyCreateOptions{ - Label: data.Label.ValueString(), + validateRegionsAgainstBucketAccesses(ctx, plan, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return } - if data.BucketAccess != nil { - accessSlice := make( - []linodego.ObjectStorageKeyBucketAccess, - len(data.BucketAccess), - ) - - for i, v := range data.BucketAccess { - accessSlice[i] = v.toLinodeObject() - } - - createOpts.BucketAccess = &accessSlice - } + createOpts := plan.GetCreateOptions(ctx) tflog.Debug(ctx, "client.CreateObjectStorageKey(...)", map[string]any{ "options": createOpts, @@ -79,13 +67,13 @@ func (r *Resource) Create( "label": key.Label, }) - data.FlattenObjectStorageKey(ctx, key, true, &resp.Diagnostics) + plan.FlattenObjectStorageKey(ctx, key, true, &resp.Diagnostics) // IDs should always be overridden during creation (see #1085) // TODO: Remove when Crossplane empty string ID issue is resolved - data.ID = types.StringValue(strconv.Itoa(key.ID)) + plan.ID = types.StringValue(strconv.Itoa(key.ID)) - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } func (r *Resource) Read( @@ -154,6 +142,11 @@ func (r *Resource) Update( return } + validateRegionsAgainstBucketAccesses(ctx, plan, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + ctx = helper.SetLogFieldBulk(ctx, map[string]any{ "key_id": state.ID.ValueString(), "label": state.Label.ValueString(), diff --git a/linode/objkey/framework_resource_schema.go b/linode/objkey/framework_resource_schema.go index b4f92689d..333fed30e 100644 --- a/linode/objkey/framework_resource_schema.go +++ b/linode/objkey/framework_resource_schema.go @@ -1,6 +1,7 @@ package objkey import ( + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/path" @@ -61,6 +62,7 @@ var frameworkResourceSchema = schema.Schema{ Optional: true, Computed: true, ElementType: types.StringType, + Validators: []validator.Set{setvalidator.SizeAtLeast(1)}, }, "regions_details": schema.SetAttribute{ Description: "A set of objects containing the detailed info of the regions where " + diff --git a/linode/objkey/helpers.go b/linode/objkey/helpers.go new file mode 100644 index 000000000..f511a8abd --- /dev/null +++ b/linode/objkey/helpers.go @@ -0,0 +1,61 @@ +package objkey + +import ( + "context" + "fmt" + "slices" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/linode/terraform-provider-linode/v2/linode/helper" +) + +func getRegionFromCluster(cluster string) (string, error) { + s := strings.Split(cluster, "-") + if len(s) <= 2 { + return "", fmt.Errorf("failed to parse cluster %q", cluster) + } + return strings.Join(s[:2], "-"), nil +} + +func validateRegionsAgainstBucketAccesses(ctx context.Context, plan ResourceModel, diags *diag.Diagnostics) { + // regions will be computed if not configured, so it's okay to be null or unknown. + if plan.BucketAccess == nil || plan.Regions.IsNull() || plan.Regions.IsUnknown() { + return + } + + var regions []string + var bucketRegions []string + + plan.Regions.ElementsAs(ctx, ®ions, true) + + for _, ba := range plan.BucketAccess { + var bucketRegion string + var err error + + if ba.Region.IsNull() || ba.Region.IsUnknown() { + bucketRegion, err = getRegionFromCluster(ba.Cluster.ValueString()) + if err != nil { + diags.AddWarning("Failed to Parse Cluster", err.Error()) + continue + } + } else { + bucketRegion = ba.Region.ValueString() + } + + if !slices.Contains(bucketRegions, bucketRegion) { + bucketRegions = append(bucketRegions, bucketRegion) + } + } + + if !helper.ValidateStringSubset(regions, bucketRegions) { + diags.AddAttributeError( + path.Root("regions"), + "Incomplete Regions", + "All regions of the buckets defined in `bucket_access` blocks are expected in the `regions` set attribute.\n"+ + fmt.Sprintf("Regions in the `regions` set attribute: %v\n", regions)+ + fmt.Sprintf("Regions in the `bucket_access` blocks: %v\n", bucketRegions), + ) + } +} diff --git a/linode/objkey/tmpl/limited.gotf b/linode/objkey/tmpl/limited.gotf index fa2705fa3..6ddb5a06d 100644 --- a/linode/objkey/tmpl/limited.gotf +++ b/linode/objkey/tmpl/limited.gotf @@ -1,33 +1,36 @@ {{ define "object_key_limited" }} resource "linode_object_storage_bucket" "foobar" { - {{if .Region }} - region = "{{.Region}}" - {{else}} + {{ if .Region }} + region = "{{ .Region }}" + {{ else }} cluster = "{{ .Cluster }}" - {{end}} - label = "{{.Label}}-bucket" + {{ end }} + label = "{{ .Label }}-bucket" } resource "linode_object_storage_key" "foobar" { - label = "{{.Label}}_key" + label = "{{ .Label }}_key" bucket_access { - bucket_name = "{{.Label}}-bucket" + bucket_name = "{{ .Label }}-bucket" {{if .Region }} - region = "{{.Region}}" - {{else}} + region = "{{ .Region }}" + {{ else }} cluster = "{{ .Cluster }}" - {{end}} + {{ end }} permissions = "read_only" } bucket_access { bucket_name = linode_object_storage_bucket.foobar.label - {{if .Region }} + {{ if .Region }} region = linode_object_storage_bucket.foobar.region - {{else}} + {{ else }} cluster = linode_object_storage_bucket.foobar.cluster - {{end}} + {{ end }} permissions = "read_write" } + {{if .Region }} + regions = [ "{{ .Region }}" ] + {{ end }} } {{ end }} \ No newline at end of file From 8cacb846c9fdd0e333c3cbca0320bb0a3f822cd4 Mon Sep 17 00:00:00 2001 From: Youjung Kim <126618609+ykim-1@users.noreply.github.com> Date: Tue, 13 Aug 2024 15:04:05 -0700 Subject: [PATCH 20/25] test: Add RegionType to test region helper (#1548) * try # 1 * adding region type option to helper function * make format * add submodule checkout in e2e pr workflow * adding any as an option for regionType * giving default value for long running tests in integration_tests_pr.yml * getting rid of grep in Makefile for proper exit codes * Fix unit tests in cluster_test.go --- .github/workflows/integration_tests_pr.yml | 3 ++ Makefile | 8 ++-- linode/acceptance/util.go | 20 +++++++--- linode/accountavailability/datasource_test.go | 2 +- linode/backup/datasource_test.go | 2 +- .../databaseaccesscontrols/resource_test.go | 2 +- linode/databasebackups/datasource_test.go | 2 +- linode/databasemysql/resource_test.go | 2 +- .../databasemysqlbackups/datasource_test.go | 2 +- linode/databasepostgresql/resource_test.go | 2 +- linode/databases/datasource_test.go | 2 +- linode/firewall/resource_test.go | 2 +- linode/firewalldevice/resource_test.go | 2 +- linode/firewalls/datasource_test.go | 2 +- linode/image/resource_test.go | 2 +- linode/images/datasource_test.go | 2 +- linode/instance/resource_test.go | 14 +++---- linode/instanceconfig/resource_test.go | 2 +- linode/instancedisk/resource_test.go | 2 +- linode/instanceip/resource_test.go | 2 +- linode/instancenetworking/datasource_test.go | 2 +- linode/ipv6ranges/datasource_test.go | 2 +- linode/lke/cluster_test.go | 39 ++++++++++--------- linode/lke/resource_test.go | 2 +- linode/lkeclusters/datasource_test.go | 2 +- linode/lkenodepool/framework_resource_test.go | 2 +- linode/nb/resource_test.go | 2 +- linode/nbconfig/resource_test.go | 2 +- linode/nbconfigs/datasource_test.go | 2 +- linode/nbnode/resource_test.go | 2 +- linode/nbs/datasource_test.go | 2 +- linode/networkingip/datasource_test.go | 2 +- linode/obj/resource_test.go | 2 +- linode/objbucket/resource_test.go | 2 +- linode/objkey/frameowkr_resource_test.go | 2 +- linode/placementgroup/resource_test.go | 2 +- .../placementgroupassignment/resource_test.go | 2 +- linode/placementgroups/datasource_test.go | 2 +- linode/rdns/resource_test.go | 2 +- linode/region/datasource_test.go | 2 +- linode/vlan/datasource_test.go | 2 +- linode/volume/resource_test.go | 2 +- linode/volumes/datasource_test.go | 2 +- linode/vpc/resource_test.go | 2 +- linode/vpcs/datasource_test.go | 4 +- linode/vpcsubnet/resource_test.go | 2 +- linode/vpcsubnets/datasource_test.go | 4 +- 47 files changed, 93 insertions(+), 79 deletions(-) diff --git a/.github/workflows/integration_tests_pr.yml b/.github/workflows/integration_tests_pr.yml index ae1334652..26ab634f6 100644 --- a/.github/workflows/integration_tests_pr.yml +++ b/.github/workflows/integration_tests_pr.yml @@ -15,6 +15,7 @@ on: run_long_tests: description: 'Setting this value will skip long running tests (e.g. Database related cases): (true|false)' required: false + default: false name: Integration tests on PR @@ -49,6 +50,8 @@ jobs: uses: actions/checkout@v4 with: ref: ${{ inputs.sha }} + fetch-depth: 0 + submodules: 'recursive' - run: make deps diff --git a/Makefile b/Makefile index 2bc3696ce..7e3c93403 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ COUNT?=1 PARALLEL?=10 PKG_NAME=linode/... TIMEOUT?=240m -RUN_LONG_TESTS?=False +RUN_LONG_TESTS?=false SWEEP?="tf_test,tf-test" TEST_TAGS="integration" @@ -61,7 +61,7 @@ test: fmt-check smoke-test unit-test int-test .PHONY: unit-test unit-test: fmt-check - go test -v --tags=unit ./$(PKG_NAME) | grep -v "\[no test files\]" + go test -v --tags=unit ./$(PKG_NAME) .PHONY: int-test int-test: fmt-check generate-ip-env-fw-e2e include-env @@ -70,7 +70,7 @@ int-test: fmt-check generate-ip-env-fw-e2e include-env RUN_LONG_TESTS=$(RUN_LONG_TESTS) \ TF_VAR_ipv4_addr=${PUBLIC_IPV4} \ TF_VAR_ipv6_addr=${PUBLIC_IPV6} \ - go test --tags="$(TEST_TAGS)" -v ./$(PKG_NAME) -count $(COUNT) -timeout $(TIMEOUT) -ldflags="-X=github.com/linode/terraform-provider-linode/v2/version.ProviderVersion=acc" -parallel=$(PARALLEL) $(ARGS) | grep -v "\[no test files\]" + go test --tags="$(TEST_TAGS)" -v ./$(PKG_NAME) -count $(COUNT) -timeout $(TIMEOUT) -ldflags="-X=github.com/linode/terraform-provider-linode/v2/version.ProviderVersion=acc" -parallel=$(PARALLEL) $(ARGS) .PHONY: include-env include-env: $(IP_ENV_FILE) @@ -87,7 +87,7 @@ smoke-test: fmt-check TF_ACC=1 \ LINODE_API_VERSION="v4beta" \ RUN_LONG_TESTS=$(RUN_LONG_TESTS) \ - go test -v -run smoke ./linode/... -count $(COUNT) -timeout $(TIMEOUT) -parallel=$(PARALLEL) -ldflags="-X=github.com/linode/terraform-provider-linode/v2/version.ProviderVersion=acc" | grep -v "\[no test files\]" + go test -v -run smoke ./linode/... -count $(COUNT) -timeout $(TIMEOUT) -parallel=$(PARALLEL) -ldflags="-X=github.com/linode/terraform-provider-linode/v2/version.ProviderVersion=acc" .PHONY: docs-check docs-check: diff --git a/linode/acceptance/util.go b/linode/acceptance/util.go index 823ba0314..fbc0f47cc 100644 --- a/linode/acceptance/util.go +++ b/linode/acceptance/util.go @@ -570,8 +570,12 @@ func ModifyProviderMeta(provider *schema.Provider, modifier ProviderMetaModifier } } -// GetRegionsWithCaps returns a list of regions that support the given capabilities. -func GetRegionsWithCaps(capabilities []string, filters ...RegionFilterFunc) ([]string, error) { +// GetRegionsWithCaps returns a list of region IDs that support the given capabilities +// Parameters: +// - capabilities: Required capabilities that the regions must support. +// - siteType: The site type to filter by ("core" or "distributed" or "any"). +// - filters: Optional custom filters for additional criteria. +func GetRegionsWithCaps(capabilities []string, regionType string, filters ...RegionFilterFunc) ([]string, error) { client, err := GetTestClient() if err != nil { return nil, err @@ -582,8 +586,14 @@ func GetRegionsWithCaps(capabilities []string, filters ...RegionFilterFunc) ([]s return nil, err } - // Filter on capabilities + // Filter on capabilities and site type regionsWithCaps := slices.DeleteFunc(regions, func(region linodego.Region) bool { + // Check if the site type matches + // Skip site type check if "any" is passed + if !strings.EqualFold(regionType, "any") && !strings.EqualFold(region.SiteType, regionType) { + return true + } + capsMap := make(map[string]bool) for _, c := range region.Capabilities { @@ -620,8 +630,8 @@ func GetRegionsWithCaps(capabilities []string, filters ...RegionFilterFunc) ([]s } // GetRandomRegionWithCaps gets a random region given a list of region capabilities. -func GetRandomRegionWithCaps(capabilities []string, filters ...RegionFilterFunc) (string, error) { - regions, err := GetRegionsWithCaps(capabilities, filters...) +func GetRandomRegionWithCaps(capabilities []string, regionType string, filters ...RegionFilterFunc) (string, error) { + regions, err := GetRegionsWithCaps(capabilities, regionType, filters...) if err != nil { return "", err } diff --git a/linode/accountavailability/datasource_test.go b/linode/accountavailability/datasource_test.go index c2d8bb647..92fed0966 100644 --- a/linode/accountavailability/datasource_test.go +++ b/linode/accountavailability/datasource_test.go @@ -16,7 +16,7 @@ func TestAccDataSourceNodeBalancers_basic(t *testing.T) { resourceName := "data.linode_account_availability.foobar" - region, err := acceptance.GetRandomRegionWithCaps(nil) + region, err := acceptance.GetRandomRegionWithCaps(nil, "core") if err != nil { log.Fatal(err) } diff --git a/linode/backup/datasource_test.go b/linode/backup/datasource_test.go index b63d521a4..6db6e2259 100644 --- a/linode/backup/datasource_test.go +++ b/linode/backup/datasource_test.go @@ -18,7 +18,7 @@ import ( var testRegion string func init() { - region, err := acceptance.GetRandomRegionWithCaps(nil) + region, err := acceptance.GetRandomRegionWithCaps(nil, "core") if err != nil { log.Fatal(err) } diff --git a/linode/databaseaccesscontrols/resource_test.go b/linode/databaseaccesscontrols/resource_test.go index fa181bfa8..10255e818 100644 --- a/linode/databaseaccesscontrols/resource_test.go +++ b/linode/databaseaccesscontrols/resource_test.go @@ -44,7 +44,7 @@ func init() { postgresEngineVersion = v.ID - region, err := acceptance.GetRandomRegionWithCaps([]string{"Managed Databases"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"Managed Databases"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/databasebackups/datasource_test.go b/linode/databasebackups/datasource_test.go index 157674b57..48e9a67fd 100644 --- a/linode/databasebackups/datasource_test.go +++ b/linode/databasebackups/datasource_test.go @@ -34,7 +34,7 @@ func init() { engineVersion = v.ID - region, err := acceptance.GetRandomRegionWithCaps([]string{"Managed Databases"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"Managed Databases"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/databasemysql/resource_test.go b/linode/databasemysql/resource_test.go index 705061c64..ebdbb69ca 100644 --- a/linode/databasemysql/resource_test.go +++ b/linode/databasemysql/resource_test.go @@ -42,7 +42,7 @@ func init() { engineVersion = v.ID - region, err := acceptance.GetRandomRegionWithCaps([]string{"Managed Databases"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"Managed Databases"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/databasemysqlbackups/datasource_test.go b/linode/databasemysqlbackups/datasource_test.go index 9c5dfd78b..2d25b391f 100644 --- a/linode/databasemysqlbackups/datasource_test.go +++ b/linode/databasemysqlbackups/datasource_test.go @@ -33,7 +33,7 @@ func init() { engineVersion = v.ID - region, err := acceptance.GetRandomRegionWithCaps([]string{"Managed Databases"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"Managed Databases"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/databasepostgresql/resource_test.go b/linode/databasepostgresql/resource_test.go index c869b3318..afa66a1ec 100644 --- a/linode/databasepostgresql/resource_test.go +++ b/linode/databasepostgresql/resource_test.go @@ -42,7 +42,7 @@ func init() { engineVersion = v.ID - region, err := acceptance.GetRandomRegionWithCaps([]string{"Managed Databases"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"Managed Databases"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/databases/datasource_test.go b/linode/databases/datasource_test.go index e3e35d9ea..38a515e6c 100644 --- a/linode/databases/datasource_test.go +++ b/linode/databases/datasource_test.go @@ -32,7 +32,7 @@ func init() { engineVersion = v.ID - region, err := acceptance.GetRandomRegionWithCaps([]string{"Managed Databases"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"Managed Databases"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/firewall/resource_test.go b/linode/firewall/resource_test.go index d051628e6..38bc0934f 100644 --- a/linode/firewall/resource_test.go +++ b/linode/firewall/resource_test.go @@ -27,7 +27,7 @@ func init() { F: sweep, }) - region, err := acceptance.GetRandomRegionWithCaps([]string{"Cloud Firewall", "NodeBalancers"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"Cloud Firewall", "NodeBalancers"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/firewalldevice/resource_test.go b/linode/firewalldevice/resource_test.go index e38da295a..e595db960 100644 --- a/linode/firewalldevice/resource_test.go +++ b/linode/firewalldevice/resource_test.go @@ -21,7 +21,7 @@ import ( var testRegion string func init() { - region, err := acceptance.GetRandomRegionWithCaps([]string{"Cloud Firewall", "NodeBalancers"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"Cloud Firewall", "NodeBalancers"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/firewalls/datasource_test.go b/linode/firewalls/datasource_test.go index c4b5b1465..0c879c938 100644 --- a/linode/firewalls/datasource_test.go +++ b/linode/firewalls/datasource_test.go @@ -17,7 +17,7 @@ const testFirewallDataName = "data.linode_firewalls.test" var testRegion string func init() { - region, err := acceptance.GetRandomRegionWithCaps([]string{"Linodes"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"Linodes"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/image/resource_test.go b/linode/image/resource_test.go index 906c5bf4c..ab1bc44a7 100644 --- a/linode/image/resource_test.go +++ b/linode/image/resource_test.go @@ -43,7 +43,7 @@ func init() { F: sweep, }) - region, err := acceptance.GetRandomRegionWithCaps(nil) + region, err := acceptance.GetRandomRegionWithCaps(nil, "core") if err != nil { log.Fatal(err) } diff --git a/linode/images/datasource_test.go b/linode/images/datasource_test.go index 7c015de04..babdce8f4 100644 --- a/linode/images/datasource_test.go +++ b/linode/images/datasource_test.go @@ -15,7 +15,7 @@ import ( var testRegion string func init() { - region, err := acceptance.GetRandomRegionWithCaps(nil) + region, err := acceptance.GetRandomRegionWithCaps(nil, "core") if err != nil { log.Fatal(err) } diff --git a/linode/instance/resource_test.go b/linode/instance/resource_test.go index 3b5532761..8f5125eca 100644 --- a/linode/instance/resource_test.go +++ b/linode/instance/resource_test.go @@ -32,7 +32,7 @@ func init() { region, err := acceptance.GetRandomRegionWithCaps([]string{ linodego.CapabilityVlans, linodego.CapabilityVPCs, linodego.CapabilityDiskEncryption, - }) + }, "core") if err != nil { log.Fatal(err) } @@ -2063,7 +2063,7 @@ func TestAccResourceInstance_userData(t *testing.T) { var instance linodego.Instance instanceName := acctest.RandomWithPrefix("tf_test") - region, err := acceptance.GetRandomRegionWithCaps([]string{"Metadata"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"Metadata"}, "core") if err != nil { t.Fatal(err) } @@ -2165,7 +2165,7 @@ func TestAccResourceInstance_firewallOnCreation(t *testing.T) { var instance linodego.Instance instanceName := acctest.RandomWithPrefix("tf_test") - region, err := acceptance.GetRandomRegionWithCaps([]string{"Cloud Firewall"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"Cloud Firewall"}, "core") rootPass := acctest.RandString(12) if err != nil { t.Fatal(err) @@ -2329,7 +2329,7 @@ func TestAccResourceInstance_migration(t *testing.T) { // Resolve a region to migrate to targetRegion, err := acceptance.GetRandomRegionWithCaps( - []string{"Linodes"}, + []string{"Linodes"}, "core", func(v linodego.Region) bool { return v.ID != testRegion }, @@ -2386,7 +2386,7 @@ func TestAccResourceInstance_withPG(t *testing.T) { // Resolve a region with support for PGs targetRegion, err := acceptance.GetRandomRegionWithCaps( - []string{"Linodes", "Placement Group"}, + []string{"Linodes", "Placement Group"}, "core", ) if err != nil { t.Fatal(err) @@ -2433,7 +2433,7 @@ func TestAccResourceInstance_pgAssignment(t *testing.T) { // Resolve a region with support for PGs testRegion, err := acceptance.GetRandomRegionWithCaps( - []string{"Linodes", "Placement Group"}, + []string{"Linodes", "Placement Group"}, "core", ) if err != nil { t.Fatal(err) @@ -2523,7 +2523,7 @@ func TestAccResourceInstance_diskEncryption(t *testing.T) { // Resolve a region that supports disk encryption targetRegion, err := acceptance.GetRandomRegionWithCaps( - []string{linodego.CapabilityLinodes, linodego.CapabilityDiskEncryption}, + []string{linodego.CapabilityLinodes, linodego.CapabilityDiskEncryption}, "core", ) if err != nil { t.Fatal(err) diff --git a/linode/instanceconfig/resource_test.go b/linode/instanceconfig/resource_test.go index 1fe960744..bf55c05da 100644 --- a/linode/instanceconfig/resource_test.go +++ b/linode/instanceconfig/resource_test.go @@ -21,7 +21,7 @@ import ( var testRegion string func init() { - region, err := acceptance.GetRandomRegionWithCaps([]string{"vlans", "VPCs"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"vlans", "VPCs"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/instancedisk/resource_test.go b/linode/instancedisk/resource_test.go index a89db7ffb..9cf14d71e 100644 --- a/linode/instancedisk/resource_test.go +++ b/linode/instancedisk/resource_test.go @@ -21,7 +21,7 @@ import ( var testRegion string func init() { - region, err := acceptance.GetRandomRegionWithCaps(nil) + region, err := acceptance.GetRandomRegionWithCaps(nil, "core") if err != nil { log.Fatal(err) } diff --git a/linode/instanceip/resource_test.go b/linode/instanceip/resource_test.go index 284bd2283..5019e306a 100644 --- a/linode/instanceip/resource_test.go +++ b/linode/instanceip/resource_test.go @@ -18,7 +18,7 @@ const testInstanceIPResName = "linode_instance_ip.test" var testRegion string func init() { - region, err := acceptance.GetRandomRegionWithCaps(nil) + region, err := acceptance.GetRandomRegionWithCaps(nil, "core") if err != nil { log.Fatal(err) } diff --git a/linode/instancenetworking/datasource_test.go b/linode/instancenetworking/datasource_test.go index 58554c445..d09925106 100644 --- a/linode/instancenetworking/datasource_test.go +++ b/linode/instancenetworking/datasource_test.go @@ -18,7 +18,7 @@ const testInstanceNetworkResName = "data.linode_instance_networking.test" var testRegion string func init() { - region, err := acceptance.GetRandomRegionWithCaps([]string{"VPCs", "Linodes"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"VPCs", "Linodes"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/ipv6ranges/datasource_test.go b/linode/ipv6ranges/datasource_test.go index 511bcace1..c25027454 100644 --- a/linode/ipv6ranges/datasource_test.go +++ b/linode/ipv6ranges/datasource_test.go @@ -15,7 +15,7 @@ import ( var testRegion string func init() { - region, err := acceptance.GetRandomRegionWithCaps([]string{"Linodes"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"Linodes"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/lke/cluster_test.go b/linode/lke/cluster_test.go index 297e18c4c..4e1d93200 100644 --- a/linode/lke/cluster_test.go +++ b/linode/lke/cluster_test.go @@ -38,10 +38,10 @@ func TestReconcileLKENodePoolSpecs(t *testing.T) { {ID: 123, Type: "g6-standard-1", Count: 2}, }, newSpecs: []lke.NodePoolSpec{ - {ID: 123, Type: "g6-standard-1", Count: 3}, + {ID: 123, Type: "g6-standard-1", Count: 3, Tags: []string{"example"}}, }, expectedToUpdate: map[int]linodego.LKENodePoolUpdateOptions{ - 123: {Count: 3}, + 123: {Count: 3, Tags: &[]string{"example"}}, }, expectedToCreate: []linodego.LKENodePoolCreateOptions{}, expectedToDelete: []int{}, @@ -67,15 +67,15 @@ func TestReconcileLKENodePoolSpecs(t *testing.T) { {ID: 124, Type: "g6-standard-1", Count: 10}, }, newSpecs: []lke.NodePoolSpec{ - {ID: 123, Type: "g6-standard-1", Count: 9}, // bumped from 1 to 9 - {ID: 124, Type: "g6-standard-2", Count: 10}, // type changed + {ID: 123, Type: "g6-standard-1", Count: 9, Tags: []string{"example"}}, // bumped from 1 to 9 + {ID: 124, Type: "g6-standard-2", Count: 10, Tags: []string{"example"}}, // type changed }, expectedToDelete: []int{124}, expectedToUpdate: map[int]linodego.LKENodePoolUpdateOptions{ - 123: {Count: 9}, + 123: {Count: 9, Tags: &[]string{"example"}}, }, expectedToCreate: []linodego.LKENodePoolCreateOptions{ - {Type: "g6-standard-2", Count: 10}, + {Type: "g6-standard-2", Count: 10, Tags: []string{"example"}}, }, }, { @@ -87,15 +87,16 @@ func TestReconcileLKENodePoolSpecs(t *testing.T) { {ID: 127, Type: "g6-standard-3", Count: 2}, }, newSpecs: []lke.NodePoolSpec{ - {ID: 123, Type: "g6-standard-3", Count: 2}, - {ID: 124, Type: "g6-standard-3", Count: 9}, - {ID: 126, Type: "g6-standard-3", Count: 8}, - {ID: 127, Type: "g6-standard-3", Count: 2}, + {ID: 123, Type: "g6-standard-3", Count: 2, Tags: []string{"example"}}, + {ID: 124, Type: "g6-standard-3", Count: 9, Tags: []string{"example"}}, + {ID: 126, Type: "g6-standard-3", Count: 8, Tags: []string{"example"}}, + {ID: 127, Type: "g6-standard-3", Count: 2, Tags: []string{"example"}}, }, expectedToUpdate: map[int]linodego.LKENodePoolUpdateOptions{ - 123: {Count: 2}, - 124: {Count: 9}, - 126: {Count: 8}, + 123: {Count: 2, Tags: &[]string{"example"}}, + 124: {Count: 9, Tags: &[]string{"example"}}, + 126: {Count: 8, Tags: &[]string{"example"}}, + 127: {Count: 2, Tags: &[]string{"example"}}, }, expectedToDelete: []int{}, expectedToCreate: []linodego.LKENodePoolCreateOptions{}, @@ -106,10 +107,10 @@ func TestReconcileLKENodePoolSpecs(t *testing.T) { {ID: 123, Type: "g6-standard-3", Count: 3}, }, newSpecs: []lke.NodePoolSpec{ - {ID: 123, Type: "g6-standard-3", Count: 3, AutoScalerEnabled: true, AutoScalerMin: 3, AutoScalerMax: 7}, + {ID: 123, Type: "g6-standard-3", Count: 3, AutoScalerEnabled: true, AutoScalerMin: 3, AutoScalerMax: 7, Tags: []string{"example"}}, }, expectedToUpdate: map[int]linodego.LKENodePoolUpdateOptions{ - 123: {Count: 3, Autoscaler: &linodego.LKENodePoolAutoscaler{Enabled: true, Min: 3, Max: 7}}, + 123: {Count: 3, Autoscaler: &linodego.LKENodePoolAutoscaler{Enabled: true, Min: 3, Max: 7}, Tags: &[]string{"example"}}, }, expectedToDelete: []int{}, expectedToCreate: []linodego.LKENodePoolCreateOptions{}, @@ -120,10 +121,10 @@ func TestReconcileLKENodePoolSpecs(t *testing.T) { {ID: 123, Type: "g6-standard-3", Count: 3, AutoScalerEnabled: true, AutoScalerMin: 3, AutoScalerMax: 7}, }, newSpecs: []lke.NodePoolSpec{ - {ID: 123, Type: "g6-standard-3", Count: 3, AutoScalerEnabled: false}, + {ID: 123, Type: "g6-standard-3", Count: 3, AutoScalerEnabled: false, Tags: []string{"example"}}, }, expectedToUpdate: map[int]linodego.LKENodePoolUpdateOptions{ - 123: {Count: 3, Autoscaler: &linodego.LKENodePoolAutoscaler{Enabled: false, Min: 0, Max: 0}}, + 123: {Count: 3, Autoscaler: &linodego.LKENodePoolAutoscaler{Enabled: false, Min: 0, Max: 0}, Tags: &[]string{"example"}}, }, expectedToDelete: []int{}, expectedToCreate: []linodego.LKENodePoolCreateOptions{}, @@ -134,10 +135,10 @@ func TestReconcileLKENodePoolSpecs(t *testing.T) { {ID: 123, Type: "g6-standard-3", Count: 3, AutoScalerEnabled: true, AutoScalerMin: 3, AutoScalerMax: 7}, }, newSpecs: []lke.NodePoolSpec{ - {ID: 123, Type: "g6-standard-3", Count: 3, AutoScalerEnabled: true, AutoScalerMin: 5, AutoScalerMax: 10}, + {ID: 123, Type: "g6-standard-3", Count: 3, AutoScalerEnabled: true, AutoScalerMin: 5, AutoScalerMax: 10, Tags: []string{"example"}}, }, expectedToUpdate: map[int]linodego.LKENodePoolUpdateOptions{ - 123: {Count: 3, Autoscaler: &linodego.LKENodePoolAutoscaler{Enabled: true, Min: 5, Max: 10}}, + 123: {Count: 3, Autoscaler: &linodego.LKENodePoolAutoscaler{Enabled: true, Min: 5, Max: 10}, Tags: &[]string{"example"}}, }, expectedToDelete: []int{}, expectedToCreate: []linodego.LKENodePoolCreateOptions{}, diff --git a/linode/lke/resource_test.go b/linode/lke/resource_test.go index e1b1dd70f..588af3e24 100644 --- a/linode/lke/resource_test.go +++ b/linode/lke/resource_test.go @@ -68,7 +68,7 @@ func init() { k8sVersionPrevious = k8sVersions[len(k8sVersions)-2] } - region, err := acceptance.GetRandomRegionWithCaps([]string{"kubernetes"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"kubernetes"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/lkeclusters/datasource_test.go b/linode/lkeclusters/datasource_test.go index a97f132db..ce4932463 100644 --- a/linode/lkeclusters/datasource_test.go +++ b/linode/lkeclusters/datasource_test.go @@ -44,7 +44,7 @@ func init() { k8sVersionLatest = k8sVersions[len(k8sVersions)-1] - region, err := acceptance.GetRandomRegionWithCaps([]string{"kubernetes"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"kubernetes"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/lkenodepool/framework_resource_test.go b/linode/lkenodepool/framework_resource_test.go index a26a679b4..cab315852 100644 --- a/linode/lkenodepool/framework_resource_test.go +++ b/linode/lkenodepool/framework_resource_test.go @@ -62,7 +62,7 @@ func init() { k8sVersion = k8sVersions[len(k8sVersions)-1] - region, err := acceptance.GetRandomRegionWithCaps([]string{"kubernetes"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"kubernetes"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/nb/resource_test.go b/linode/nb/resource_test.go index af516d452..a31f1a2f3 100644 --- a/linode/nb/resource_test.go +++ b/linode/nb/resource_test.go @@ -30,7 +30,7 @@ func init() { F: sweep, }) - region, err := acceptance.GetRandomRegionWithCaps([]string{"nodebalancers"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"nodebalancers"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/nbconfig/resource_test.go b/linode/nbconfig/resource_test.go index 2e59dba62..10d8571f7 100644 --- a/linode/nbconfig/resource_test.go +++ b/linode/nbconfig/resource_test.go @@ -24,7 +24,7 @@ import ( var testRegion string func init() { - region, err := acceptance.GetRandomRegionWithCaps([]string{"nodebalancers"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"nodebalancers"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/nbconfigs/datasource_test.go b/linode/nbconfigs/datasource_test.go index b04c35d61..02175b225 100644 --- a/linode/nbconfigs/datasource_test.go +++ b/linode/nbconfigs/datasource_test.go @@ -19,7 +19,7 @@ func TestAccDataSourceNodeBalancerConfigs_basic(t *testing.T) { resourceName := "data.linode_nodebalancer_configs.foo" nbLabel := acctest.RandomWithPrefix("tf_test") - nbRegion, err := acceptance.GetRandomRegionWithCaps([]string{"NodeBalancers"}) + nbRegion, err := acceptance.GetRandomRegionWithCaps([]string{"NodeBalancers"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/nbnode/resource_test.go b/linode/nbnode/resource_test.go index 2d4fe7b74..9c5b35055 100644 --- a/linode/nbnode/resource_test.go +++ b/linode/nbnode/resource_test.go @@ -21,7 +21,7 @@ import ( var testRegion string func init() { - region, err := acceptance.GetRandomRegionWithCaps([]string{"nodebalancers"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"nodebalancers"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/nbs/datasource_test.go b/linode/nbs/datasource_test.go index 665d8bbf7..f7c0c7093 100644 --- a/linode/nbs/datasource_test.go +++ b/linode/nbs/datasource_test.go @@ -18,7 +18,7 @@ func TestAccDataSourceNodeBalancers_basic(t *testing.T) { resourceName := "data.linode_nodebalancers.nbs" nbLabel := acctest.RandomWithPrefix("tf_test") - nbRegion, err := acceptance.GetRandomRegionWithCaps([]string{"NodeBalancers"}) + nbRegion, err := acceptance.GetRandomRegionWithCaps([]string{"NodeBalancers"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/networkingip/datasource_test.go b/linode/networkingip/datasource_test.go index c1079b04d..ae4b2e013 100644 --- a/linode/networkingip/datasource_test.go +++ b/linode/networkingip/datasource_test.go @@ -16,7 +16,7 @@ import ( var testRegion string func init() { - region, err := acceptance.GetRandomRegionWithCaps([]string{"linodes"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"linodes"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/obj/resource_test.go b/linode/obj/resource_test.go index 805cff04b..95de2fa79 100644 --- a/linode/obj/resource_test.go +++ b/linode/obj/resource_test.go @@ -26,7 +26,7 @@ var ( ) func init() { - region, err := acceptance.GetRandomRegionWithCaps([]string{"Object Storage"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"Object Storage"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/objbucket/resource_test.go b/linode/objbucket/resource_test.go index 92fa3562d..2671a5b24 100644 --- a/linode/objbucket/resource_test.go +++ b/linode/objbucket/resource_test.go @@ -45,7 +45,7 @@ var ( ) func init() { - region, err := acceptance.GetRandomRegionWithCaps([]string{"Object Storage"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"Object Storage"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/objkey/frameowkr_resource_test.go b/linode/objkey/frameowkr_resource_test.go index dea0fc948..1ec80623d 100644 --- a/linode/objkey/frameowkr_resource_test.go +++ b/linode/objkey/frameowkr_resource_test.go @@ -30,7 +30,7 @@ func init() { F: sweep, }) - region, err := acceptance.GetRandomRegionWithCaps([]string{"Object Storage"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"Object Storage"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/placementgroup/resource_test.go b/linode/placementgroup/resource_test.go index d3f5990b7..65dfb5fb3 100644 --- a/linode/placementgroup/resource_test.go +++ b/linode/placementgroup/resource_test.go @@ -27,7 +27,7 @@ func init() { }) var err error - testRegion, err = acceptance.GetRandomRegionWithCaps([]string{"Placement Group"}) + testRegion, err = acceptance.GetRandomRegionWithCaps([]string{"Placement Group"}, "core") if err != nil { log.Fatal(fmt.Errorf("Error getting region: %s", err)) } diff --git a/linode/placementgroupassignment/resource_test.go b/linode/placementgroupassignment/resource_test.go index 9027d9ad8..5431faddf 100644 --- a/linode/placementgroupassignment/resource_test.go +++ b/linode/placementgroupassignment/resource_test.go @@ -19,7 +19,7 @@ import ( var testRegion string func init() { - region, err := acceptance.GetRandomRegionWithCaps([]string{"Placement Group"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"Placement Group"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/placementgroups/datasource_test.go b/linode/placementgroups/datasource_test.go index 6d36b521e..7a5865b91 100644 --- a/linode/placementgroups/datasource_test.go +++ b/linode/placementgroups/datasource_test.go @@ -21,7 +21,7 @@ func TestAccDataSourcePlacementGroups_basic(t *testing.T) { baseLabel := acctest.RandomWithPrefix("tf-test") - testRegion, err := acceptance.GetRandomRegionWithCaps([]string{"Placement Group"}) + testRegion, err := acceptance.GetRandomRegionWithCaps([]string{"Placement Group"}, "core") if err != nil { t.Error(fmt.Errorf("failed to get region with PG capability: %w", err)) } diff --git a/linode/rdns/resource_test.go b/linode/rdns/resource_test.go index 45ecc410a..0b0ab9a2a 100644 --- a/linode/rdns/resource_test.go +++ b/linode/rdns/resource_test.go @@ -25,7 +25,7 @@ func init() { F: sweep, }) - region, err := acceptance.GetRandomRegionWithCaps([]string{"linodes"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"linodes"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/region/datasource_test.go b/linode/region/datasource_test.go index 95b301842..a379ed68a 100644 --- a/linode/region/datasource_test.go +++ b/linode/region/datasource_test.go @@ -18,7 +18,7 @@ var ( ) func init() { - region, err := acceptance.GetRandomRegionWithCaps([]string{"linodes"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"linodes"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/vlan/datasource_test.go b/linode/vlan/datasource_test.go index 5a874de44..0143fd516 100644 --- a/linode/vlan/datasource_test.go +++ b/linode/vlan/datasource_test.go @@ -20,7 +20,7 @@ import ( var testRegion string func init() { - region, err := acceptance.GetRandomRegionWithCaps([]string{"vlans"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"vlans"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/volume/resource_test.go b/linode/volume/resource_test.go index 232fcc0a3..c117f4f6b 100644 --- a/linode/volume/resource_test.go +++ b/linode/volume/resource_test.go @@ -24,7 +24,7 @@ func init() { F: sweep, }) - region, err := acceptance.GetRandomRegionWithCaps([]string{"Block Storage"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"Block Storage"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/volumes/datasource_test.go b/linode/volumes/datasource_test.go index b5f1ccd16..baacaa094 100644 --- a/linode/volumes/datasource_test.go +++ b/linode/volumes/datasource_test.go @@ -22,7 +22,7 @@ func init() { F: sweep, }) - region, err := acceptance.GetRandomRegionWithCaps([]string{"Block Storage"}) + region, err := acceptance.GetRandomRegionWithCaps([]string{"Block Storage"}, "core") if err != nil { log.Fatal(err) } diff --git a/linode/vpc/resource_test.go b/linode/vpc/resource_test.go index 22afc82d8..31a884941 100644 --- a/linode/vpc/resource_test.go +++ b/linode/vpc/resource_test.go @@ -29,7 +29,7 @@ func init() { var err error - testRegion, err = acceptance.GetRandomRegionWithCaps([]string{"VPCs"}) + testRegion, err = acceptance.GetRandomRegionWithCaps([]string{"VPCs"}, "core") if err != nil { log.Fatal(fmt.Errorf("Error getting region: %s", err)) } diff --git a/linode/vpcs/datasource_test.go b/linode/vpcs/datasource_test.go index f2cc6726e..075e6e55e 100644 --- a/linode/vpcs/datasource_test.go +++ b/linode/vpcs/datasource_test.go @@ -18,7 +18,7 @@ func TestAccDataSourceVPCs_basic_smoke(t *testing.T) { resourceName := "data.linode_vpcs.foobar" vpcLabel := acctest.RandomWithPrefix("tf-test") - testRegion, err := acceptance.GetRandomRegionWithCaps([]string{"VPCs"}) + testRegion, err := acceptance.GetRandomRegionWithCaps([]string{"VPCs"}, "core") if err != nil { t.Error(fmt.Errorf("failed to get region with VPC capability: %w", err)) } @@ -46,7 +46,7 @@ func TestAccDataSourceVPCs_filterByLabel(t *testing.T) { resourceName := "data.linode_vpcs.foobar" vpcLabel := acctest.RandomWithPrefix("tf-test") - testRegion, err := acceptance.GetRandomRegionWithCaps([]string{"VPCs"}) + testRegion, err := acceptance.GetRandomRegionWithCaps([]string{"VPCs"}, "core") if err != nil { log.Fatal(fmt.Errorf("Error getting region: %s", err)) } diff --git a/linode/vpcsubnet/resource_test.go b/linode/vpcsubnet/resource_test.go index 58f60b80f..4fea47abc 100644 --- a/linode/vpcsubnet/resource_test.go +++ b/linode/vpcsubnet/resource_test.go @@ -22,7 +22,7 @@ import ( var testRegion string func init() { - r, err := acceptance.GetRandomRegionWithCaps([]string{"VPCs"}) + r, err := acceptance.GetRandomRegionWithCaps([]string{"VPCs"}, "core") if err != nil { log.Fatal(fmt.Errorf("Error getting region: %s", err)) } diff --git a/linode/vpcsubnets/datasource_test.go b/linode/vpcsubnets/datasource_test.go index 444957a8b..8d1b6f249 100644 --- a/linode/vpcsubnets/datasource_test.go +++ b/linode/vpcsubnets/datasource_test.go @@ -18,7 +18,7 @@ func TestAccDataSourceVPCSubnets_basic_smoke(t *testing.T) { resourceName := "data.linode_vpc_subnets.foobar" vpcLabel := acctest.RandomWithPrefix("tf-test") - testRegion, err := acceptance.GetRandomRegionWithCaps([]string{"VPCs"}) + testRegion, err := acceptance.GetRandomRegionWithCaps([]string{"VPCs"}, "core") if err != nil { log.Fatal(fmt.Errorf("Error getting region: %s", err)) } @@ -51,7 +51,7 @@ func TestAccDataSourceVPCSubnets_filterByLabel(t *testing.T) { resourceName := "data.linode_vpc_subnets.foobar" vpcLabel := acctest.RandomWithPrefix("tf-test") - testRegion, err := acceptance.GetRandomRegionWithCaps([]string{"VPCs"}) + testRegion, err := acceptance.GetRandomRegionWithCaps([]string{"VPCs"}, "core") if err != nil { log.Fatal(fmt.Errorf("Error getting region: %s", err)) } From 68872e50bb5a7aca0a4b76bf606afa8a1be36be8 Mon Sep 17 00:00:00 2001 From: Ye Chen <127243817+yec-akamai@users.noreply.github.com> Date: Wed, 14 Aug 2024 13:37:34 -0400 Subject: [PATCH 21/25] Project: Image Services Gen2 (#1531) * new: Support image gen2 features (#1513) * image gen2 * unit test * doc fix * fix test * add an error check * rename to replica_regions; add replication tests; add LA disclamier * Allow wait for replications and update replica_regions requirements (#1551) * allow wait for replications; require one available region for replication * fix wait for image ready * change wait for image ready * fix file_hash * update docs * test cases import fix * fix doc --- docs/data-sources/image.md | 8 + docs/data-sources/images.md | 10 + docs/resources/image.md | 35 ++- go.mod | 4 +- go.sum | 8 +- linode/image/datasource_test.go | 43 ++++ linode/image/framework_datasource.go | 5 +- linode/image/framework_models.go | 151 ++++++++++--- linode/image/framework_models_unit_test.go | 21 +- linode/image/framework_resource.go | 216 +++++++++++++++++-- linode/image/framework_schema_datasource.go | 28 +++ linode/image/framework_schema_resource.go | 37 +++- linode/image/resource_test.go | 73 ++++++- linode/image/tmpl/basic.gotf | 1 + linode/image/tmpl/data_replicate.gotf | 9 + linode/image/tmpl/no_replica_regions.gotf | 11 + linode/image/tmpl/replicate.gotf | 13 ++ linode/image/tmpl/template.go | 50 ++++- linode/image/tmpl/updates.gotf | 1 + linode/image/tmpl/upload.gotf | 1 + linode/images/datasource_test.go | 8 +- linode/images/framework_datasource.go | 5 +- linode/images/framework_models.go | 13 +- linode/images/framework_models_unit_test.go | 3 +- linode/images/framework_schema_datasource.go | 3 + linode/images/tmpl/data_base.gotf | 2 + linode/images/tmpl/data_basic.gotf | 5 + 27 files changed, 682 insertions(+), 82 deletions(-) create mode 100644 linode/image/tmpl/data_replicate.gotf create mode 100644 linode/image/tmpl/no_replica_regions.gotf create mode 100644 linode/image/tmpl/replicate.gotf diff --git a/docs/data-sources/image.md b/docs/data-sources/image.md index 0169b2d73..854c4c106 100644 --- a/docs/data-sources/image.md +++ b/docs/data-sources/image.md @@ -48,3 +48,11 @@ The Linode Image resource exports the following attributes: * `type` - How the Image was created. Manual Images can be created at any time. "Automatic" Images are created automatically from a deleted Linode. (`manual`, `automatic`) * `vendor` - The upstream distribution vendor. `None` for private Images. + +* `tags` - A list of customized tags. + +* `total_size` - The total size of the image in all available regions. + +* `replications` - A list of image replication regions and corresponding status. + * `region` - The region of an image replica. + * `status` - The status of an image replica. diff --git a/docs/data-sources/images.md b/docs/data-sources/images.md index 8df4e4c20..5f2d3f6ea 100644 --- a/docs/data-sources/images.md +++ b/docs/data-sources/images.md @@ -87,6 +87,14 @@ Each Linode image will be stored in the `images` attribute and will export the f * `vendor` - The upstream distribution vendor. `None` for private Images. +* `tags` - A list of customized tags. + +* `total_size` - The total size of the image in all available regions. + +* `replications` - A list of image replication regions and corresponding status. + * `region` - The region of an image replica. + * `status` - The status of an image replica. + ## Filterable Fields * `created_by` @@ -106,3 +114,5 @@ Each Linode image will be stored in the `images` attribute and will export the f * `status` * `vendor` + +* `tags` diff --git a/docs/resources/image.md b/docs/resources/image.md index 1388890b7..88ff02066 100644 --- a/docs/resources/image.md +++ b/docs/resources/image.md @@ -27,6 +27,7 @@ resource "linode_image" "bar" { description = "Image taken from foo" disk_id = linode_instance.foo.disk.0.id linode_id = linode_instance.foo.id + tags = ["image-tag", "test"] } resource "linode_instance" "bar_based" { @@ -43,12 +44,30 @@ resource "linode_image" "foobar" { label = "foobar-image" description = "An image uploaded from Terraform!" region = "us-southeast" + tags = ["image-tag", "test"] file_path = "path/to/image.img.gz" file_hash = filemd5("path/to/image.img.gz") } ``` +Upload and replicate an image from a local file: + +```hcl +resource "linode_image" "foobar" { + label = "foobar-image" + description = "An image uploaded from Terraform!" + region = "us-southeast" + tags = ["image-tag", "test"] + + file_path = "path/to/image.img.gz" + file_hash = filemd5("path/to/image.img.gz") + + // Note: Image replication may not be available to all users. + replica_regions = ["us-southeast", "us-east", "eu-west"] +} +``` + ## Argument Reference The following arguments are supported: @@ -57,6 +76,12 @@ The following arguments are supported: * `description` - (Optional) A detailed description of this Image. +* `tags` - (Optional) A list of customized tags. + +* `replica_regions` - (Optional) A list of regions that customer wants to replicate this image in. At least one valid region is required and only core regions allowed. Existing images in the regions not passed will be removed. **Note:** Image replication may not be available to all users. See Replicate an Image [here](https://techdocs.akamai.com/linode-api/reference/post-replicate-image) for more details. + +* `wait_for_replications` - (Optional) Whether to wait for all image replications become `available`. Default to false. + - - - The following arguments apply to creating an image from an existing Linode Instance: @@ -75,13 +100,13 @@ The following arguments apply to uploading an image: * `file_hash` - (Optional) The MD5 hash of the file to be uploaded. This is used to trigger file updates. -* `region` - (Required) The region of the image. See all regions [here](https://api.linode.com/v4/regions). +* `region` - (Required) The region of the image. See all regions [here](https://techdocs.akamai.com/linode-api/reference/get-regions). ### Timeouts The `timeouts` block allows you to specify [timeouts](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts) for certain actions: -* `create` - (Defaults to 20 mins) Used when creating the instance image (until the instance is available) +* `create` - (Defaults to 30 mins) Used when creating the instance image (until the instance is available) ## Attributes Reference @@ -105,6 +130,12 @@ This resource exports the following attributes: * `vendor` - The upstream distribution vendor. Nil for private Images. +* `total_size` - The total size of the image in all available regions. + +* `replications` - A list of image replications region and corresponding status. + * `region` - The region of an image replica. + * `status` - The status of an image replica. + ## Import Linodes Images can be imported using the Linode Image `id`, e.g. diff --git a/go.mod b/go.mod index 93a62de1e..4956d58ee 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/hashicorp/terraform-plugin-mux v0.16.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 github.com/hashicorp/terraform-plugin-testing v1.10.0 - github.com/linode/linodego v1.38.0 + github.com/linode/linodego v1.39.0 github.com/linode/linodego/k8s v1.25.2 github.com/stretchr/testify v1.9.0 golang.org/x/crypto v0.26.0 @@ -98,7 +98,7 @@ require ( github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/zclconf/go-cty v1.15.0 // indirect golang.org/x/mod v0.19.0 // indirect - golang.org/x/oauth2 v0.21.0 // indirect + golang.org/x/oauth2 v0.22.0 // indirect golang.org/x/sys v0.23.0 // indirect golang.org/x/term v0.23.0 // indirect golang.org/x/text v0.17.0 // indirect diff --git a/go.sum b/go.sum index acd54004d..ec7eb6d9e 100644 --- a/go.sum +++ b/go.sum @@ -187,8 +187,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/linode/linodego v1.38.0 h1:wP3oW9OhGc6vhze8NPf2knbwH4TzSbrjzuCd9okjbTY= -github.com/linode/linodego v1.38.0/go.mod h1:L7GXKFD3PoN2xSEtFc04wIXP5WK65O10jYQx0PQISWQ= +github.com/linode/linodego v1.39.0 h1:gRsj2PXX+HTO3eYQaXEuQGsLeeLFDSBDontC5JL3Nn8= +github.com/linode/linodego v1.39.0/go.mod h1:da8KzAQKSm5obwa06yXk5CZSDFMP9Wb08GA/O+aR9W0= github.com/linode/linodego/k8s v1.25.2 h1:PY6S0sAD3xANVvM9WY38bz9GqMTjIbytC8IJJ9Cv23o= github.com/linode/linodego/k8s v1.25.2/go.mod h1:DC1XCSRZRGsmaa/ggpDPSDUmOM6aK1bhSIP6+f9Cwhc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -296,8 +296,8 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/linode/image/datasource_test.go b/linode/image/datasource_test.go index d8d044184..bd9115bc0 100644 --- a/linode/image/datasource_test.go +++ b/linode/image/datasource_test.go @@ -3,8 +3,10 @@ package image_test import ( + "os" "testing" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/linode/terraform-provider-linode/v2/linode/acceptance" "github.com/linode/terraform-provider-linode/v2/linode/image/tmpl" @@ -36,3 +38,44 @@ func TestAccDataSourceImage_basic(t *testing.T) { }, }) } + +func TestAccDataSourceImage_replicate(t *testing.T) { + t.Parallel() + + resourceName := "data.linode_image.foobar" + imageName := acctest.RandomWithPrefix("tf_test") + // TODO: Use random region once image gen2 works globally or with specific capabilities + replicateRegion := "eu-west" + + file, err := createTempFile("tf-test-image-data-replicate-file", testImageBytes) + if err != nil { + t.Fatal(err) + } + defer os.Remove(file.Name()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, + + CheckDestroy: checkImageDestroy, + Steps: []resource.TestStep{ + { + Config: tmpl.DataReplicate(t, imageName, file.Name(), testRegion, replicateRegion), + Check: resource.ComposeTestCheckFunc( + checkImageExists(resourceName, nil), + resource.TestCheckResourceAttr(resourceName, "label", imageName), + resource.TestCheckResourceAttr(resourceName, "description", "really descriptive text"), + resource.TestCheckResourceAttrSet(resourceName, "created"), + resource.TestCheckResourceAttrSet(resourceName, "created_by"), + resource.TestCheckResourceAttrSet(resourceName, "size"), + resource.TestCheckResourceAttr(resourceName, "type", "manual"), + resource.TestCheckResourceAttr(resourceName, "is_public", "false"), + resource.TestCheckResourceAttrSet(resourceName, "deprecated"), + resource.TestCheckResourceAttrSet(resourceName, "tags.#"), + resource.TestCheckResourceAttrSet(resourceName, "total_size"), + resource.TestCheckResourceAttr(resourceName, "replications.#", "2"), + ), + }, + }, + }) +} diff --git a/linode/image/framework_datasource.go b/linode/image/framework_datasource.go index d56439d9e..dedb7c753 100644 --- a/linode/image/framework_datasource.go +++ b/linode/image/framework_datasource.go @@ -66,6 +66,9 @@ func (d *DataSource) Read( return } - data.ParseImage(image) + resp.Diagnostics.Append(data.ParseImage(ctx, image)...) + if resp.Diagnostics.HasError() { + return + } resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } diff --git a/linode/image/framework_models.go b/linode/image/framework_models.go index 517b354ff..bb969c95a 100644 --- a/linode/image/framework_models.go +++ b/linode/image/framework_models.go @@ -1,6 +1,7 @@ package image import ( + "context" "time" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" @@ -12,29 +13,39 @@ import ( ) type ResourceModel struct { - ID types.String `tfsdk:"id"` - Label types.String `tfsdk:"label"` - DiskID types.Int64 `tfsdk:"disk_id"` - LinodeID types.Int64 `tfsdk:"linode_id"` - FilePath types.String `tfsdk:"file_path"` - Region types.String `tfsdk:"region"` - FileHash types.String `tfsdk:"file_hash"` - Description types.String `tfsdk:"description"` - CloudInit types.Bool `tfsdk:"cloud_init"` - Capabilities types.List `tfsdk:"capabilities"` - Created timetypes.RFC3339 `tfsdk:"created"` - CreatedBy types.String `tfsdk:"created_by"` - Deprecated types.Bool `tfsdk:"deprecated"` - IsPublic types.Bool `tfsdk:"is_public"` - Size types.Int64 `tfsdk:"size"` - Status types.String `tfsdk:"status"` - Type types.String `tfsdk:"type"` - Expiry timetypes.RFC3339 `tfsdk:"expiry"` - Vendor types.String `tfsdk:"vendor"` - Timeouts timeouts.Value `tfsdk:"timeouts"` + ID types.String `tfsdk:"id"` + Label types.String `tfsdk:"label"` + DiskID types.Int64 `tfsdk:"disk_id"` + LinodeID types.Int64 `tfsdk:"linode_id"` + FilePath types.String `tfsdk:"file_path"` + Region types.String `tfsdk:"region"` + FileHash types.String `tfsdk:"file_hash"` + Description types.String `tfsdk:"description"` + CloudInit types.Bool `tfsdk:"cloud_init"` + Capabilities types.List `tfsdk:"capabilities"` + Created timetypes.RFC3339 `tfsdk:"created"` + CreatedBy types.String `tfsdk:"created_by"` + Deprecated types.Bool `tfsdk:"deprecated"` + IsPublic types.Bool `tfsdk:"is_public"` + Size types.Int64 `tfsdk:"size"` + Status types.String `tfsdk:"status"` + Type types.String `tfsdk:"type"` + Expiry timetypes.RFC3339 `tfsdk:"expiry"` + Vendor types.String `tfsdk:"vendor"` + Timeouts timeouts.Value `tfsdk:"timeouts"` + Tags types.List `tfsdk:"tags"` + TotalSize types.Int64 `tfsdk:"total_size"` + ReplicaRegions types.List `tfsdk:"replica_regions"` + Replications types.List `tfsdk:"replications"` + WaitForReplications types.Bool `tfsdk:"wait_for_replications"` } -func (data *ResourceModel) FlattenImage(image *linodego.Image, preserveKnown bool, diags *diag.Diagnostics) { +func (data *ResourceModel) FlattenImage( + ctx context.Context, + image *linodego.Image, + preserveKnown bool, + diags *diag.Diagnostics, +) { data.ID = helper.KeepOrUpdateString(data.ID, image.ID, preserveKnown) data.Label = helper.KeepOrUpdateString(data.Label, image.Label, preserveKnown) data.Description = helper.KeepOrUpdateString( @@ -64,6 +75,23 @@ func (data *ResourceModel) FlattenImage(image *linodego.Image, preserveKnown boo data.Expiry, timetypes.NewRFC3339TimePointerValue(image.Expiry), preserveKnown, ) data.Vendor = helper.KeepOrUpdateString(data.Vendor, image.Vendor, preserveKnown) + data.TotalSize = helper.KeepOrUpdateInt64(data.TotalSize, int64(image.TotalSize), preserveKnown) + + tags, newDiags := types.ListValue(types.StringType, helper.StringSliceToFrameworkValueSlice(image.Tags)) + diags.Append(newDiags...) + if diags.HasError() { + return + } + + data.Tags = helper.KeepOrUpdateValue(data.Tags, tags, preserveKnown) + + replications, newDiags := flattenReplications(ctx, image.Regions) + diags.Append(newDiags...) + if diags.HasError() { + return + } + + data.Replications = helper.KeepOrUpdateValue(data.Replications, *replications, preserveKnown) } func (data *ResourceModel) CopyFrom(other ResourceModel, preserveKnown bool) { @@ -87,29 +115,44 @@ func (data *ResourceModel) CopyFrom(other ResourceModel, preserveKnown bool) { data.Expiry = helper.KeepOrUpdateValue(data.Expiry, other.Expiry, preserveKnown) data.Vendor = helper.KeepOrUpdateValue(data.Vendor, other.Vendor, preserveKnown) data.Timeouts = helper.KeepOrUpdateValue(data.Timeouts, other.Timeouts, preserveKnown) + data.Tags = helper.KeepOrUpdateValue(data.Tags, other.Tags, preserveKnown) + data.TotalSize = helper.KeepOrUpdateValue(data.TotalSize, other.TotalSize, preserveKnown) + data.ReplicaRegions = helper.KeepOrUpdateValue(data.ReplicaRegions, other.ReplicaRegions, preserveKnown) + data.Replications = helper.KeepOrUpdateValue(data.Replications, other.Replications, preserveKnown) + data.WaitForReplications = helper.KeepOrUpdateValue(data.WaitForReplications, other.WaitForReplications, preserveKnown) } // ImageModel describes the Terraform resource data model to match the // resource schema. type ImageModel struct { - ID types.String `tfsdk:"id"` - Label types.String `tfsdk:"label"` - Description types.String `tfsdk:"description"` - Capabilities []types.String `tfsdk:"capabilities"` - Created types.String `tfsdk:"created"` - CreatedBy types.String `tfsdk:"created_by"` - Deprecated types.Bool `tfsdk:"deprecated"` - IsPublic types.Bool `tfsdk:"is_public"` - Size types.Int64 `tfsdk:"size"` - Status types.String `tfsdk:"status"` - Type types.String `tfsdk:"type"` - Expiry types.String `tfsdk:"expiry"` - Vendor types.String `tfsdk:"vendor"` + ID types.String `tfsdk:"id"` + Label types.String `tfsdk:"label"` + Description types.String `tfsdk:"description"` + Capabilities []types.String `tfsdk:"capabilities"` + Created types.String `tfsdk:"created"` + CreatedBy types.String `tfsdk:"created_by"` + Deprecated types.Bool `tfsdk:"deprecated"` + IsPublic types.Bool `tfsdk:"is_public"` + Size types.Int64 `tfsdk:"size"` + Status types.String `tfsdk:"status"` + Type types.String `tfsdk:"type"` + Expiry types.String `tfsdk:"expiry"` + Vendor types.String `tfsdk:"vendor"` + Tags types.List `tfsdk:"tags"` + TotalSize types.Int64 `tfsdk:"total_size"` + Replications []ReplicationModel `tfsdk:"replications"` +} + +// ReplicationModel describes an image replication. +type ReplicationModel struct { + Region types.String `tfsdk:"region"` + Status types.String `tfsdk:"status"` } func (data *ImageModel) ParseImage( + ctx context.Context, image *linodego.Image, -) { +) diag.Diagnostics { data.ID = types.StringValue(image.ID) data.Label = types.StringValue(image.Label) @@ -132,4 +175,42 @@ func (data *ImageModel) ParseImage( data.Status = types.StringValue(string(image.Status)) data.Type = types.StringValue(image.Type) data.Vendor = types.StringValue(image.Vendor) + data.TotalSize = types.Int64Value(int64(image.TotalSize)) + + tags, diags := types.ListValueFrom(ctx, types.StringType, image.Tags) + if diags.HasError() { + return diags + } + data.Tags = tags + + data.Replications = parseReplicationModels(image.Regions) + + return nil +} + +func parseReplicationModels( + regions []linodego.ImageRegion, +) []ReplicationModel { + replications := make([]ReplicationModel, len(regions)) + + for i, r := range regions { + replications[i].Region = types.StringValue(r.Region) + replications[i].Status = types.StringValue(string(r.Status)) + } + + return replications +} + +func flattenReplications( + ctx context.Context, + regions []linodego.ImageRegion, +) (*types.List, diag.Diagnostics) { + replications := parseReplicationModels(regions) + + result, diags := types.ListValueFrom(ctx, replicationObjType, replications) + if diags.HasError() { + return nil, diags + } + + return &result, nil } diff --git a/linode/image/framework_models_unit_test.go b/linode/image/framework_models_unit_test.go index f5b5c1e1f..12d51136f 100644 --- a/linode/image/framework_models_unit_test.go +++ b/linode/image/framework_models_unit_test.go @@ -3,6 +3,7 @@ package image import ( + "context" "testing" "time" @@ -28,10 +29,22 @@ func TestParseImage(t *testing.T) { Deprecated: false, Created: createdTime, Expiry: nil, + TotalSize: 2500, + Tags: []string{"test"}, + Regions: []linodego.ImageRegion{ + { + Region: "us-east", + Status: linodego.ImageRegionStatus("available"), + }, + { + Region: "us-west", + Status: linodego.ImageRegionStatus("pending replication"), + }, + }, } var imageModel ImageModel - imageModel.ParseImage(&mockImage) + imageModel.ParseImage(context.Background(), &mockImage) assert.Equal(t, types.StringValue("linode/debian11"), imageModel.ID) assert.Equal(t, types.StringValue("linode"), imageModel.CreatedBy) @@ -46,4 +59,10 @@ func TestParseImage(t *testing.T) { assert.Equal(t, types.BoolValue(false), imageModel.Deprecated) assert.Equal(t, imageModel.Created, types.StringValue(createdTimeFormatted)) assert.Empty(t, imageModel.Expiry) + assert.Equal(t, types.Int64Value(2500), imageModel.TotalSize) + assert.Equal(t, types.StringValue("us-east"), imageModel.Replications[0].Region) + assert.Equal(t, types.StringValue("available"), imageModel.Replications[0].Status) + assert.Equal(t, types.StringValue("us-west"), imageModel.Replications[1].Region) + assert.Equal(t, types.StringValue("pending replication"), imageModel.Replications[1].Status) + assert.Contains(t, imageModel.Tags.String(), "test") } diff --git a/linode/image/framework_resource.go b/linode/image/framework_resource.go index c85c127a0..0f7724c65 100644 --- a/linode/image/framework_resource.go +++ b/linode/image/framework_resource.go @@ -21,7 +21,7 @@ import ( ) const ( - DefaultVolumeCreateTimeout = 30 * time.Minute + DefaultImageCreateTimeout = 30 * time.Minute ) func NewResource() resource.Resource { @@ -66,6 +66,11 @@ func createResourceFromUpload( CloudInit: plan.CloudInit.ValueBool(), } + resp.Diagnostics.Append(plan.Tags.ElementsAs(ctx, &createOpts.Tags, true)...) + if resp.Diagnostics.HasError() { + return nil + } + tflog.Trace(ctx, "client.CreateImageUpload(...)", map[string]any{ "options": createOpts, }) @@ -92,19 +97,8 @@ func createResourceFromUpload( return image } - tflog.Debug(ctx, "Waiting for a single image to be ready") - tflog.Trace(ctx, "client.WaitForImageStatus(...)", map[string]any{ - "status": linodego.ImageStatusAvailable, - }) - - image, err = client.WaitForImageStatus( - ctx, - image.ID, - linodego.ImageStatusAvailable, - timeoutSeconds, - ) - if err != nil { - resp.Diagnostics.AddError("Failed to Wait for Image to be Available", err.Error()) + image = waitForImageToBeAvailable(ctx, client, image.ID, timeoutSeconds, &resp.Diagnostics) + if resp.Diagnostics.HasError() { return image } @@ -141,6 +135,12 @@ func createResourceFromLinode( Description: plan.Description.ValueString(), CloudInit: plan.CloudInit.ValueBool(), } + + resp.Diagnostics.Append(plan.Tags.ElementsAs(ctx, &createOpts.Tags, true)...) + if resp.Diagnostics.HasError() { + return nil + } + tflog.Trace(ctx, "client.CreateImage(...)", map[string]any{ "options": createOpts, }) @@ -195,7 +195,7 @@ func (r *Resource) Create( return } - createTimeout, diags := plan.Timeouts.Create(ctx, DefaultVolumeCreateTimeout) + createTimeout, diags := plan.Timeouts.Create(ctx, DefaultImageCreateTimeout) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return @@ -217,7 +217,25 @@ func (r *Resource) Create( return } - plan.FlattenImage(image, true, &resp.Diagnostics) + if !plan.ReplicaRegions.IsNull() && !plan.ReplicaRegions.IsUnknown() { + plan.ID = types.StringValue(image.ID) + + // make sure image is ready for replication + waitForImageToBeAvailable(ctx, client, image.ID, timeoutSeconds, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + // Refresh image from replication + image, diags = replicateImage(ctx, &plan, client) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } + + plan.FlattenImage(ctx, image, true, &resp.Diagnostics) + if resp.Diagnostics.HasError() { return } @@ -267,7 +285,7 @@ func (r *Resource) Read( return } - state.FlattenImage(image, true, &resp.Diagnostics) + state.FlattenImage(ctx, image, true, &resp.Diagnostics) if resp.Diagnostics.HasError() { return } @@ -308,6 +326,14 @@ func (r *Resource) Update( shouldUpdate = true } + if !state.Tags.Equal(plan.Tags) { + resp.Diagnostics.Append(plan.Tags.ElementsAs(ctx, &updateOpts.Tags, true)...) + if resp.Diagnostics.HasError() { + return + } + shouldUpdate = true + } + if shouldUpdate { tflog.Debug(ctx, "client.UpdateImage(...)", map[string]any{ "options": updateOpts, @@ -318,7 +344,52 @@ func (r *Resource) Update( resp.Diagnostics.AddError("Failed to Update Image", err.Error()) return } - plan.FlattenImage(image, true, &resp.Diagnostics) + plan.FlattenImage(ctx, image, true, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + } + + if !state.ReplicaRegions.Equal(plan.ReplicaRegions) { + isAvailableRegionLeft, diags := atLeastOneAvailableRegion(ctx, &plan, &state) + if diags != nil { + resp.Diagnostics.Append(diags...) + return + } + + if plan.ReplicaRegions.IsNull() || plan.ReplicaRegions.IsUnknown() || !isAvailableRegionLeft { + resp.Diagnostics.AddError( + "Invalid regions to replicate.", + "At least one available region must be specified. "+ + "Note: Image is not allowed to be deleted by sending an empty regions list.") + return + } + + createTimeout, diags := plan.Timeouts.Create(ctx, DefaultImageCreateTimeout) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + timeoutSeconds := helper.FrameworkSafeFloat64ToInt(createTimeout.Seconds(), &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + // make sure image is ready for replication + waitForImageToBeAvailable(ctx, client, plan.ID.ValueString(), timeoutSeconds, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + image, diags := replicateImage(ctx, &plan, client) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Refresh image from replication + plan.FlattenImage(ctx, image, true, &resp.Diagnostics) if resp.Diagnostics.HasError() { return } @@ -431,3 +502,112 @@ func uploadImageAndStoreHash( plan.FileHash = types.StringValue(hex.EncodeToString(hash.Sum(nil))) } + +func replicateImage( + ctx context.Context, + plan *ResourceModel, + client *linodego.Client, +) (*linodego.Image, diag.Diagnostics) { + var replicationOpts linodego.ImageReplicateOptions + diags := plan.ReplicaRegions.ElementsAs(ctx, &replicationOpts.Regions, false) + if diags.HasError() { + return nil, diags + } + + tflog.Debug(ctx, "client.ReplicateImage(...)", map[string]any{ + "opts": replicationOpts, + }) + + imageID := plan.ID.ValueString() + image, err := client.ReplicateImage(ctx, imageID, replicationOpts) + if err != nil { + diags.AddError( + fmt.Sprintf("Failed to replicate image %v", imageID), + err.Error(), + ) + return nil, diags + } + + if plan.WaitForReplications.ValueBool() { + var replicaRegionWaitList []string + + image, err = client.GetImage(ctx, imageID) + for _, region := range image.Regions { + // remove pending deletion replicas from the wait list + if region.Status != linodego.ImageRegionStatusPendingDeletion { + replicaRegionWaitList = append(replicaRegionWaitList, region.Region) + } + } + + tflog.Trace(ctx, "client.WaitForImageRegionStatus(...)", map[string]any{ + "status": linodego.ImageRegionStatusAvailable, + }) + + for _, region := range replicaRegionWaitList { + image, err = client.WaitForImageRegionStatus(ctx, imageID, region, linodego.ImageRegionStatusAvailable) + if err != nil { + diags.AddError( + fmt.Sprintf("Failed to get image %v replication status in region %v", imageID, region), + err.Error(), + ) + return nil, diags + } + } + } + + return image, diags +} + +func atLeastOneAvailableRegion( + ctx context.Context, + plan *ResourceModel, + state *ResourceModel, +) (bool, diag.Diagnostics) { + var planRegions, stateRegions []string + diags := plan.ReplicaRegions.ElementsAs(ctx, &planRegions, true) + if diags.HasError() { + return false, diags + } + diags = state.ReplicaRegions.ElementsAs(ctx, &stateRegions, true) + if diags.HasError() { + return false, diags + } + + set := make(map[string]bool) + for _, v := range stateRegions { + set[v] = true + } + + for _, v := range planRegions { + if set[v] { + return true, nil + } + } + + return false, nil +} + +func waitForImageToBeAvailable( + ctx context.Context, + client *linodego.Client, + imageID string, + timeoutSeconds int, + diags *diag.Diagnostics, +) *linodego.Image { + tflog.Debug(ctx, "Waiting for a single image to be ready") + tflog.Trace(ctx, "client.WaitForImageStatus(...)", map[string]any{ + "status": linodego.ImageStatusAvailable, + }) + + image, err := client.WaitForImageStatus( + ctx, + imageID, + linodego.ImageStatusAvailable, + timeoutSeconds, + ) + if err != nil { + diags.AddError("Failed to Wait for Image to be Available", err.Error()) + } + + return image +} diff --git a/linode/image/framework_schema_datasource.go b/linode/image/framework_schema_datasource.go index d5e4fc2f9..19f5997a0 100644 --- a/linode/image/framework_schema_datasource.go +++ b/linode/image/framework_schema_datasource.go @@ -60,8 +60,36 @@ var ImageAttributes = map[string]schema.Attribute{ Description: "The upstream distribution vendor. Nil for private Images.", Computed: true, }, + "tags": schema.ListAttribute{ + Description: "The customized tags for the image.", + Computed: true, + ElementType: types.StringType, + }, + "total_size": schema.Int64Attribute{ + Description: "The total size of the image in all available regions.", + Computed: true, + }, +} + +var ReplicationsBlock = map[string]schema.Block{ + "replications": schema.ListNestedBlock{ + Description: "A list of image replications region and corresponding status.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "region": schema.StringAttribute{ + Description: "The region of an image replica.", + Computed: true, + }, + "status": schema.StringAttribute{ + Description: "The status of an image replica.", + Computed: true, + }, + }, + }, + }, } var frameworkDatasourceSchema = schema.Schema{ Attributes: ImageAttributes, + Blocks: ReplicationsBlock, } diff --git a/linode/image/framework_schema_resource.go b/linode/image/framework_schema_resource.go index 5cc247f63..301bd763b 100644 --- a/linode/image/framework_schema_resource.go +++ b/linode/image/framework_schema_resource.go @@ -4,6 +4,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" @@ -16,6 +17,13 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) +var replicationObjType = types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "region": types.StringType, + "status": types.StringType, + }, +} + var frameworkResourceSchema = schema.Schema{ Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ @@ -94,7 +102,6 @@ var frameworkResourceSchema = schema.Schema{ }, "file_hash": schema.StringAttribute{ Description: "The MD5 hash of the image file.", - Computed: true, Optional: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), @@ -187,5 +194,33 @@ var frameworkResourceSchema = schema.Schema{ listplanmodifier.UseStateForUnknown(), }, }, + "tags": schema.ListAttribute{ + Description: "The customized tags for the image.", + Computed: true, + Optional: true, + ElementType: types.StringType, + }, + "total_size": schema.Int64Attribute{ + Description: "The total size of the image in all available regions.", + Computed: true, + }, + "replica_regions": schema.ListAttribute{ + Description: "A list of regions that customer wants to replicate this image in. " + + "At least one available region is required and only core regions allowed. " + + "Existing images in the regions not passed will be removed.", + Optional: true, + ElementType: types.StringType, + }, + "replications": schema.ListAttribute{ + Description: "A list of image replications region and corresponding status.", + Computed: true, + ElementType: replicationObjType, + }, + "wait_for_replications": schema.BoolAttribute{ + Description: "Whether to wait for all image replications become `available`.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, }, } diff --git a/linode/image/resource_test.go b/linode/image/resource_test.go index ab1bc44a7..3eb565377 100644 --- a/linode/image/resource_test.go +++ b/linode/image/resource_test.go @@ -7,6 +7,7 @@ import ( "fmt" "log" "os" + "regexp" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" @@ -89,7 +90,7 @@ func TestAccImage_basic(t *testing.T) { CheckDestroy: checkImageDestroy, Steps: []resource.TestStep{ { - Config: tmpl.Basic(t, imageName, testRegion, label), + Config: tmpl.Basic(t, imageName, testRegion, label, "test-tag"), Check: resource.ComposeTestCheckFunc( checkImageExists(resName, nil), resource.TestCheckResourceAttr(resName, "label", imageName), @@ -101,6 +102,7 @@ func TestAccImage_basic(t *testing.T) { resource.TestCheckResourceAttr(resName, "is_public", "false"), resource.TestCheckResourceAttr(resName, "capabilities.0", "cloud-init"), resource.TestCheckResourceAttrSet(resName, "deprecated"), + resource.TestCheckResourceAttr(resName, "tags.#", "1"), ), }, { @@ -127,16 +129,18 @@ func TestAccImage_update(t *testing.T) { Steps: []resource.TestStep{ { - Config: tmpl.Basic(t, imageName, testRegion, label), + Config: tmpl.Basic(t, imageName, testRegion, label, "test-tag"), Check: resource.ComposeTestCheckFunc( checkImageExists(resName, nil), resource.TestCheckResourceAttr(resName, "label", imageName), resource.TestCheckResourceAttr(resName, "description", "descriptive text"), resource.TestCheckResourceAttrSet(resName, "capabilities.#"), + resource.TestCheckResourceAttr(resName, "tags.#", "1"), + resource.TestCheckResourceAttr(resName, "tags.0", "test-tag"), ), }, { - Config: tmpl.Updates(t, imageName, testRegion, label), + Config: tmpl.Updates(t, imageName, testRegion, label, "updated-tag"), Check: resource.ComposeTestCheckFunc( checkImageExists(resName, nil), resource.TestCheckResourceAttr(resName, "label", fmt.Sprintf("%s_renamed", imageName)), @@ -147,6 +151,8 @@ func TestAccImage_update(t *testing.T) { resource.TestCheckResourceAttr(resName, "type", "manual"), resource.TestCheckResourceAttr(resName, "is_public", "false"), resource.TestCheckResourceAttrSet(resName, "deprecated"), + resource.TestCheckResourceAttr(resName, "tags.#", "1"), + resource.TestCheckResourceAttr(resName, "tags.0", "updated-tag"), ), }, { @@ -179,7 +185,7 @@ func TestAccImage_uploadFile(t *testing.T) { CheckDestroy: checkImageDestroy, Steps: []resource.TestStep{ { - Config: tmpl.Upload(t, imageName, file.Name(), testRegion), + Config: tmpl.Upload(t, imageName, file.Name(), testRegion, "test-tag"), Check: resource.ComposeTestCheckFunc( checkImageExists(resName, &image), resource.TestCheckResourceAttr(resName, "label", imageName), @@ -192,13 +198,14 @@ func TestAccImage_uploadFile(t *testing.T) { resource.TestCheckResourceAttrSet(resName, "deprecated"), resource.TestCheckResourceAttr(resName, "file_hash", testImageMD5), resource.TestCheckResourceAttr(resName, "status", string(linodego.ImageStatusAvailable)), + resource.TestCheckResourceAttr(resName, "tags.#", "1"), ), }, { PreConfig: func() { file.Write(testImageBytesNew) }, - Config: tmpl.Upload(t, imageName, file.Name(), testRegion), + Config: tmpl.Upload(t, imageName, file.Name(), testRegion, "test-tag"), Check: resource.ComposeTestCheckFunc( checkImageExists(resName, &image), resource.TestCheckResourceAttr(resName, "status", string(linodego.ImageStatusAvailable)), @@ -208,6 +215,62 @@ func TestAccImage_uploadFile(t *testing.T) { }) } +func TestAccImage_replicate(t *testing.T) { + t.Parallel() + + resName := "linode_image.foobar" + imageName := acctest.RandomWithPrefix("tf_test") + // Override the testRegion to be a fixed value because we need to make sure these three regions are different + testRegion = "us-east" + replicateRegion := "eu-west" + replicateNewRegion := "us-central" + + file, err := createTempFile("tf-test-image-replicate-file", testImageBytes) + if err != nil { + t.Fatal(err) + } + defer os.Remove(file.Name()) + + var image linodego.Image + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, + CheckDestroy: checkImageDestroy, + Steps: []resource.TestStep{ + { + Config: tmpl.Replicate(t, imageName, file.Name(), testRegion, replicateRegion), + Check: resource.ComposeTestCheckFunc( + checkImageExists(resName, &image), + resource.TestCheckResourceAttr(resName, "label", imageName), + resource.TestCheckResourceAttr(resName, "replications.#", "2"), + ), + }, + { + // Remove the one of the available region and replicate the image in a new region + Config: tmpl.Replicate(t, imageName, file.Name(), testRegion, replicateNewRegion), + Check: resource.ComposeTestCheckFunc( + checkImageExists(resName, &image), + resource.TestCheckResourceAttr(resName, "label", imageName), + resource.TestCheckResourceAttr(resName, "replications.#", "2"), + ), + }, + { + // Remove all available region and replicate the image in new regions + Config: tmpl.Replicate(t, imageName, file.Name(), "us-west", "us-mia"), + ExpectError: regexp.MustCompile( + "At least one available region must be specified"), + }, + { + // Remove all available region + Config: tmpl.NoReplicaRegions(t, imageName, file.Name(), testRegion), + ExpectError: regexp.MustCompile( + "At least one available region must be specified"), + }, + }, + }) +} + func checkImageExists(name string, image *linodego.Image) resource.TestCheckFunc { return func(s *terraform.State) error { client := acceptance.TestAccProvider.Meta().(*helper.ProviderMeta).Client diff --git a/linode/image/tmpl/basic.gotf b/linode/image/tmpl/basic.gotf index 338cc1a3b..0d6c5dcb9 100644 --- a/linode/image/tmpl/basic.gotf +++ b/linode/image/tmpl/basic.gotf @@ -21,6 +21,7 @@ resource "linode_image" "foobar" { label = "{{.Image}}" description = "descriptive text" cloud_init = true + tags = ["{{.Tag}}"] } {{ end }} \ No newline at end of file diff --git a/linode/image/tmpl/data_replicate.gotf b/linode/image/tmpl/data_replicate.gotf new file mode 100644 index 000000000..5b4a86938 --- /dev/null +++ b/linode/image/tmpl/data_replicate.gotf @@ -0,0 +1,9 @@ +{{ define "image_data_replicate" }} + +{{ template "image_replicate" . }} + +data "linode_image" "foobar" { + id = linode_image.foobar.id +} + +{{ end }} \ No newline at end of file diff --git a/linode/image/tmpl/no_replica_regions.gotf b/linode/image/tmpl/no_replica_regions.gotf new file mode 100644 index 000000000..9cda202db --- /dev/null +++ b/linode/image/tmpl/no_replica_regions.gotf @@ -0,0 +1,11 @@ +{{ define "image_no_replica_regions" }} + +resource "linode_image" "foobar" { + label = "{{.Image}}" + file_path = "{{.FilePath}}" + file_hash = filemd5("{{.FilePath}}") + region = "{{ .Region }}" + description = "really descriptive text" +} + +{{ end }} \ No newline at end of file diff --git a/linode/image/tmpl/replicate.gotf b/linode/image/tmpl/replicate.gotf new file mode 100644 index 000000000..3e23476c9 --- /dev/null +++ b/linode/image/tmpl/replicate.gotf @@ -0,0 +1,13 @@ +{{ define "image_replicate" }} + +resource "linode_image" "foobar" { + label = "{{.Image}}" + file_path = "{{.FilePath}}" + file_hash = filemd5("{{.FilePath}}") + region = "{{ .Region }}" + description = "really descriptive text" + replica_regions = ["{{ .Region }}", "{{ .ReplicaRegion }}"] + wait_for_replications = true +} + +{{ end }} \ No newline at end of file diff --git a/linode/image/tmpl/template.go b/linode/image/tmpl/template.go index a99428257..7e4e5ae7d 100644 --- a/linode/image/tmpl/template.go +++ b/linode/image/tmpl/template.go @@ -7,37 +7,61 @@ import ( ) type TemplateData struct { - Image string - ID string - FilePath string - Region string - Label string + Image string + ID string + FilePath string + Region string + Label string + Tag string + ReplicaRegion string } -func Basic(t *testing.T, image, region, label string) string { +func Basic(t *testing.T, image, region, label, tag string) string { return acceptance.ExecuteTemplate(t, "image_basic", TemplateData{ Image: image, Region: region, Label: label, + Tag: tag, }) } -func Updates(t *testing.T, image, region, label string) string { +func Updates(t *testing.T, image, region, label, tag string) string { return acceptance.ExecuteTemplate(t, "image_updates", TemplateData{ Image: image, Region: region, Label: label, + Tag: tag, }) } -func Upload(t *testing.T, image, upload, region string) string { +func Upload(t *testing.T, image, upload, region, tag string) string { return acceptance.ExecuteTemplate(t, "image_upload", TemplateData{ Image: image, FilePath: upload, Region: region, + Tag: tag, + }) +} + +func Replicate(t *testing.T, image, upload, region, replicaRegion string) string { + return acceptance.ExecuteTemplate(t, + "image_replicate", TemplateData{ + Image: image, + Region: region, + FilePath: upload, + ReplicaRegion: replicaRegion, + }) +} + +func NoReplicaRegions(t *testing.T, image, upload, region string) string { + return acceptance.ExecuteTemplate(t, + "image_no_replica_regions", TemplateData{ + Image: image, + Region: region, + FilePath: upload, }) } @@ -45,3 +69,13 @@ func DataBasic(t *testing.T, id string) string { return acceptance.ExecuteTemplate(t, "image_data_basic", TemplateData{ID: id}) } + +func DataReplicate(t *testing.T, image, upload, region, replicaRegion string) string { + return acceptance.ExecuteTemplate(t, + "image_data_replicate", TemplateData{ + Image: image, + Region: region, + FilePath: upload, + ReplicaRegion: replicaRegion, + }) +} diff --git a/linode/image/tmpl/updates.gotf b/linode/image/tmpl/updates.gotf index 17d84ccf8..33f24cc4f 100644 --- a/linode/image/tmpl/updates.gotf +++ b/linode/image/tmpl/updates.gotf @@ -20,6 +20,7 @@ resource "linode_image" "foobar" { disk_id = "${linode_instance.foobar.disk.0.id}" label = "{{.Image}}_renamed" description = "more descriptive text" + tags = ["{{.Tag}}"] } {{ end }} \ No newline at end of file diff --git a/linode/image/tmpl/upload.gotf b/linode/image/tmpl/upload.gotf index 7b72919cf..9f6d10904 100644 --- a/linode/image/tmpl/upload.gotf +++ b/linode/image/tmpl/upload.gotf @@ -6,6 +6,7 @@ resource "linode_image" "foobar" { file_hash = filemd5("{{.FilePath}}") region = "{{ .Region }}" description = "really descriptive text" + tags = ["{{.Tag}}"] } {{ end }} \ No newline at end of file diff --git a/linode/images/datasource_test.go b/linode/images/datasource_test.go index babdce8f4..2109e2a39 100644 --- a/linode/images/datasource_test.go +++ b/linode/images/datasource_test.go @@ -42,20 +42,26 @@ func TestAccDataSourceImages_basic_smoke(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "images.0.description", "descriptive text"), resource.TestCheckResourceAttr(resourceName, "images.0.is_public", "false"), resource.TestCheckResourceAttr(resourceName, "images.0.type", "manual"), + acceptance.CheckListContains(resourceName, "images.0.tags", "test"), resource.TestCheckResourceAttrSet(resourceName, "images.0.created"), resource.TestCheckResourceAttrSet(resourceName, "images.0.created_by"), resource.TestCheckResourceAttrSet(resourceName, "images.0.size"), resource.TestCheckResourceAttrSet(resourceName, "images.0.deprecated"), resource.TestCheckResourceAttrSet(resourceName, "images.0.capabilities.#"), + resource.TestCheckResourceAttrSet(resourceName, "images.0.total_size"), + resource.TestCheckResourceAttrSet(resourceName, "images.0.replications.#"), resource.TestCheckResourceAttr(resourceName, "images.1.label", imageName), resource.TestCheckResourceAttr(resourceName, "images.1.description", "descriptive text"), resource.TestCheckResourceAttr(resourceName, "images.1.is_public", "false"), resource.TestCheckResourceAttr(resourceName, "images.1.type", "manual"), + acceptance.CheckListContains(resourceName, "images.1.tags", "test"), resource.TestCheckResourceAttrSet(resourceName, "images.1.created"), resource.TestCheckResourceAttrSet(resourceName, "images.1.created_by"), resource.TestCheckResourceAttrSet(resourceName, "images.1.size"), resource.TestCheckResourceAttrSet(resourceName, "images.1.deprecated"), - resource.TestCheckResourceAttrSet(resourceName, "images.0.capabilities.#"), + resource.TestCheckResourceAttrSet(resourceName, "images.1.capabilities.#"), + resource.TestCheckResourceAttrSet(resourceName, "images.1.total_size"), + resource.TestCheckResourceAttrSet(resourceName, "images.1.replications.#"), ), }, diff --git a/linode/images/framework_datasource.go b/linode/images/framework_datasource.go index b2ede85f7..6e3cf19b9 100644 --- a/linode/images/framework_datasource.go +++ b/linode/images/framework_datasource.go @@ -62,7 +62,10 @@ func (d *DataSource) Read( } } - data.parseImages(helper.AnySliceToTyped[linodego.Image](result)) + resp.Diagnostics.Append(data.parseImages(ctx, helper.AnySliceToTyped[linodego.Image](result))...) + if resp.Diagnostics.HasError() { + return + } resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } diff --git a/linode/images/framework_models.go b/linode/images/framework_models.go index fa319dca7..4bc6b4448 100644 --- a/linode/images/framework_models.go +++ b/linode/images/framework_models.go @@ -1,6 +1,9 @@ package images import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/linode/linodego" "github.com/linode/terraform-provider-linode/v2/linode/helper/frameworkfilter" @@ -19,14 +22,20 @@ type ImageFilterModel struct { } func (data *ImageFilterModel) parseImages( + ctx context.Context, images []linodego.Image, -) { +) diag.Diagnostics { result := make([]image.ImageModel, len(images)) for i := range images { var imgData image.ImageModel - imgData.ParseImage(&images[i]) + diags := imgData.ParseImage(ctx, &images[i]) + if diags.HasError() { + return diags + } result[i] = imgData } data.Images = result + + return nil } diff --git a/linode/images/framework_models_unit_test.go b/linode/images/framework_models_unit_test.go index 036fa8f29..72009e1fd 100644 --- a/linode/images/framework_models_unit_test.go +++ b/linode/images/framework_models_unit_test.go @@ -3,6 +3,7 @@ package images import ( + "context" "testing" "time" @@ -49,7 +50,7 @@ func TestParseImages(t *testing.T) { data := ImageFilterModel{} - data.parseImages(images) + data.parseImages(context.Background(), images) assert.Len(t, data.Images, len(images)) diff --git a/linode/images/framework_schema_datasource.go b/linode/images/framework_schema_datasource.go index 76cbab49b..625156bd9 100644 --- a/linode/images/framework_schema_datasource.go +++ b/linode/images/framework_schema_datasource.go @@ -14,6 +14,8 @@ var filterConfig = frameworkfilter.Config{ "type": {APIFilterable: true, TypeFunc: frameworkfilter.FilterTypeString}, "vendor": {APIFilterable: true, TypeFunc: frameworkfilter.FilterTypeString}, + // TODO: check if tags become API filterable + "tags": {TypeFunc: frameworkfilter.FilterTypeString}, "created_by": {TypeFunc: frameworkfilter.FilterTypeString}, "id": {TypeFunc: frameworkfilter.FilterTypeString}, "status": {TypeFunc: frameworkfilter.FilterTypeString}, @@ -39,6 +41,7 @@ var frameworkDatasourceSchema = schema.Schema{ Description: "The returned list of Images.", NestedObject: schema.NestedBlockObject{ Attributes: image.ImageAttributes, + Blocks: image.ReplicationsBlock, }, }, }, diff --git a/linode/images/tmpl/data_base.gotf b/linode/images/tmpl/data_base.gotf index 0088a002c..f5426b2e0 100644 --- a/linode/images/tmpl/data_base.gotf +++ b/linode/images/tmpl/data_base.gotf @@ -17,6 +17,7 @@ resource "linode_image" "foobar" { disk_id = "${linode_instance.foobar.disk.0.id}" label = "{{.Image}}" description = "descriptive text" + tags = ["test"] } resource "linode_image" "foobar2" { @@ -24,6 +25,7 @@ resource "linode_image" "foobar2" { disk_id = "${linode_instance.foobar.disk.0.id}" label = "{{.Image}}" description = "descriptive text" + tags = ["test", "gen2"] } {{ end }} \ No newline at end of file diff --git a/linode/images/tmpl/data_basic.gotf b/linode/images/tmpl/data_basic.gotf index 8aeb8f0b7..227bed5dd 100644 --- a/linode/images/tmpl/data_basic.gotf +++ b/linode/images/tmpl/data_basic.gotf @@ -12,6 +12,11 @@ data "linode_images" "foobar" { name = "is_public" values = ["false"] } + + filter { + name = "tags" + values = ["test"] + } } {{ end }} \ No newline at end of file From ab00c712d4f3fa18e623d6a29334d5861646b43b Mon Sep 17 00:00:00 2001 From: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:09:22 -0400 Subject: [PATCH 22/25] Refactor `parseLKEAttributes` function and reduce size of test linode instances for LKE (#1546) * Refactor parseLKEAttributes function for LKE data source * use cheaper instances for testing --- linode/lke/datasource_test.go | 2 +- linode/lke/framework_models.go | 37 ++++++++++------------ linode/lke/resource_test.go | 2 +- linode/lke/tmpl/autoscaler_many_pools.gotf | 8 ++--- linode/lke/tmpl/autoscaler_updates.gotf | 2 +- linode/lke/tmpl/basic.gotf | 2 +- linode/lke/tmpl/many_pools.gotf | 4 +-- linode/lke/tmpl/no_count.gotf | 2 +- linode/lke/tmpl/updates.gotf | 2 +- 9 files changed, 28 insertions(+), 33 deletions(-) diff --git a/linode/lke/datasource_test.go b/linode/lke/datasource_test.go index ebf538fa8..53613e53f 100644 --- a/linode/lke/datasource_test.go +++ b/linode/lke/datasource_test.go @@ -32,7 +32,7 @@ func TestAccDataSourceLKECluster_basic(t *testing.T) { resource.TestCheckResourceAttr(dataSourceClusterName, "status", "ready"), resource.TestCheckResourceAttr(dataSourceClusterName, "tags.#", "1"), resource.TestCheckResourceAttr(dataSourceClusterName, "pools.#", "1"), - resource.TestCheckResourceAttr(dataSourceClusterName, "pools.0.type", "g6-standard-2"), + resource.TestCheckResourceAttr(dataSourceClusterName, "pools.0.type", "g6-standard-1"), resource.TestCheckResourceAttr(dataSourceClusterName, "pools.0.count", "3"), resource.TestCheckResourceAttrSet(dataSourceClusterName, "pools.0.disk_encryption"), resource.TestCheckResourceAttr(dataSourceClusterName, "pools.0.nodes.#", "3"), diff --git a/linode/lke/framework_models.go b/linode/lke/framework_models.go index c4163eb65..67fe88531 100644 --- a/linode/lke/framework_models.go +++ b/linode/lke/framework_models.go @@ -125,35 +125,30 @@ func (data *LKEDataModel) parseLKEAttributes( } pool.Tags = tags - poolNodes := make([]LKENodePoolNode, len(p.Linodes)) - for j, n := range p.Linodes { - var node LKENodePoolNode - node.ID = types.StringValue(n.ID) - node.InstanceID = types.Int64Value(int64(n.InstanceID)) - node.Status = types.StringValue(string(n.Status)) - - poolNodes[j] = node + pool.Nodes = make([]LKENodePoolNode, len(p.Linodes)) + for i, linode := range p.Linodes { + pool.Nodes[i].ID = types.StringValue(linode.ID) + pool.Nodes[i].InstanceID = types.Int64Value(int64(linode.InstanceID)) + pool.Nodes[i].Status = types.StringValue(string(linode.Status)) } - pool.Nodes = poolNodes // Only parse the autoscaler when it's enabled in order to keep returning // the same list result of SDKv2. if p.Autoscaler.Enabled { - var autoscaler LKENodePoolAutoscaler - autoscaler.Enabled = types.BoolValue(p.Autoscaler.Enabled) - autoscaler.Min = types.Int64Value(int64(p.Autoscaler.Min)) - autoscaler.Max = types.Int64Value(int64(p.Autoscaler.Max)) - pool.Autoscaler = []LKENodePoolAutoscaler{autoscaler} + pool.Autoscaler = []LKENodePoolAutoscaler{ + { + Enabled: types.BoolValue(p.Autoscaler.Enabled), + Min: types.Int64Value(int64(p.Autoscaler.Min)), + Max: types.Int64Value(int64(p.Autoscaler.Max)), + }, + } } - poolDisks := make([]LKENodePoolDisk, len(p.Disks)) - for k, d := range p.Disks { - var poolDisk LKENodePoolDisk - poolDisk.Size = types.Int64Value(int64(d.Size)) - poolDisk.Type = types.StringValue(d.Type) - poolDisks[k] = poolDisk + pool.Disks = make([]LKENodePoolDisk, len(p.Disks)) + for i, d := range p.Disks { + pool.Disks[i].Size = types.Int64Value(int64(d.Size)) + pool.Disks[i].Type = types.StringValue(d.Type) } - pool.Disks = poolDisks lkePools[i] = pool } diff --git a/linode/lke/resource_test.go b/linode/lke/resource_test.go index 588af3e24..5c75a6cd5 100644 --- a/linode/lke/resource_test.go +++ b/linode/lke/resource_test.go @@ -183,7 +183,7 @@ func TestAccResourceLKECluster_basic_smoke(t *testing.T) { resource.TestCheckResourceAttr(resourceClusterName, "status", "ready"), resource.TestCheckResourceAttr(resourceClusterName, "tags.#", "1"), resource.TestCheckResourceAttr(resourceClusterName, "pool.#", "1"), - resource.TestCheckResourceAttr(resourceClusterName, "pool.0.type", "g6-standard-2"), + resource.TestCheckResourceAttr(resourceClusterName, "pool.0.type", "g6-standard-1"), resource.TestCheckResourceAttr(resourceClusterName, "pool.0.count", "3"), resource.TestCheckResourceAttrSet(resourceClusterName, "pool.0.disk_encryption"), resource.TestCheckResourceAttr(resourceClusterName, "pool.0.nodes.#", "3"), diff --git a/linode/lke/tmpl/autoscaler_many_pools.gotf b/linode/lke/tmpl/autoscaler_many_pools.gotf index 93ef113bf..a74b13774 100644 --- a/linode/lke/tmpl/autoscaler_many_pools.gotf +++ b/linode/lke/tmpl/autoscaler_many_pools.gotf @@ -1,16 +1,16 @@ {{ define "lke_cluster_autoscaler_many_pools" }} resource "linode_lke_cluster" "test" { - label = "{{.Label}}" + label = "{{ .Label }}" region = "{{ .Region }}" - k8s_version = "{{.K8sVersion}}" + k8s_version = "{{ .K8sVersion }}" tags = ["test"] pool { autoscaler { min = 3 max = 8 } - type = "g6-standard-4" + type = "g6-standard-1" count = 5 } pool { @@ -18,7 +18,7 @@ resource "linode_lke_cluster" "test" { min = 1 max = 8 } - type = "g6-standard-2" + type = "g6-standard-1" count = 3 } } diff --git a/linode/lke/tmpl/autoscaler_updates.gotf b/linode/lke/tmpl/autoscaler_updates.gotf index 09b0cecb4..41297b2d7 100644 --- a/linode/lke/tmpl/autoscaler_updates.gotf +++ b/linode/lke/tmpl/autoscaler_updates.gotf @@ -10,7 +10,7 @@ resource "linode_lke_cluster" "test" { min = 1 max = 8 } - type = "g6-standard-2" + type = "g6-standard-1" count = 3 } } diff --git a/linode/lke/tmpl/basic.gotf b/linode/lke/tmpl/basic.gotf index 0e1f8dca5..a288ba1dc 100644 --- a/linode/lke/tmpl/basic.gotf +++ b/linode/lke/tmpl/basic.gotf @@ -7,7 +7,7 @@ resource "linode_lke_cluster" "test" { tags = ["test"] pool { - type = "g6-standard-2" + type = "g6-standard-1" count = 3 tags = ["test"] } diff --git a/linode/lke/tmpl/many_pools.gotf b/linode/lke/tmpl/many_pools.gotf index 1f1bce9ad..250c3e775 100644 --- a/linode/lke/tmpl/many_pools.gotf +++ b/linode/lke/tmpl/many_pools.gotf @@ -7,12 +7,12 @@ resource "linode_lke_cluster" "test" { tags = ["test"] pool { - type = "g6-standard-2" + type = "g6-standard-1" count = 1 } pool { - type = "g6-standard-2" + type = "g6-standard-1" count = 1 } } diff --git a/linode/lke/tmpl/no_count.gotf b/linode/lke/tmpl/no_count.gotf index cf3e3ed75..3fe976199 100644 --- a/linode/lke/tmpl/no_count.gotf +++ b/linode/lke/tmpl/no_count.gotf @@ -7,7 +7,7 @@ resource "linode_lke_cluster" "test" { k8s_version = "{{.K8sVersion}}" pool { - type = "g6-standard-2" + type = "g6-standard-1" } } diff --git a/linode/lke/tmpl/updates.gotf b/linode/lke/tmpl/updates.gotf index 9b547aacb..e6e985dbb 100644 --- a/linode/lke/tmpl/updates.gotf +++ b/linode/lke/tmpl/updates.gotf @@ -7,7 +7,7 @@ resource "linode_lke_cluster" "test" { tags = ["test", "new_tag"] pool { - type = "g6-standard-2" + type = "g6-standard-1" count = 4 tags = ["test", "test-2"] } From 5e09192c06593233f58aaf30432da3c7ccb01d64 Mon Sep 17 00:00:00 2001 From: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:48:30 -0400 Subject: [PATCH 23/25] Fix test build (#1552) --- linode/instance/datasource_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/linode/instance/datasource_test.go b/linode/instance/datasource_test.go index c7c515230..5a9258a3e 100644 --- a/linode/instance/datasource_test.go +++ b/linode/instance/datasource_test.go @@ -61,6 +61,7 @@ func TestAccDataSourceInstances_withPG(t *testing.T) { // Resolve a region with support for PGs targetRegion, err := acceptance.GetRandomRegionWithCaps( []string{"Linodes", "Placement Group"}, + "core", ) if err != nil { t.Fatal(err) From c5c20420f0de1519bcf25e11cde30b9fa353b5f1 Mon Sep 17 00:00:00 2001 From: Lena Garber <114949949+lgarber-akamai@users.noreply.github.com> Date: Wed, 14 Aug 2024 16:17:19 -0400 Subject: [PATCH 24/25] ref: Temporarily revert Linode Disk Encryption project merge (#1554) * Revert "fix: Fix LDE-related test failures (#1533)" This reverts commit 60003c63812b35826537fd58125b01a922933aea. * Revert "Merge pull request #1504 from linode/proj/disk-encryption" This reverts commit f91f8ded8155459f0b2bf6a2805b77281fc3603b, reversing changes made to 08748bed77dbd87b31cda1dc591d5481c3437ec3. * Fix accidental tag removal --- docs/data-sources/instances.md | 6 -- docs/data-sources/lke_cluster.md | 4 - docs/resources/instance.md | 6 -- docs/resources/instance_disk.md | 4 - docs/resources/lke_cluster.md | 4 - docs/resources/lke_node_pool.md | 4 - linode/instance/datasource.go | 12 ++- linode/instance/datasource_test.go | 1 - linode/instance/flatten.go | 2 - linode/instance/resource.go | 5 -- linode/instance/resource_test.go | 89 ------------------- linode/instance/schema_datasource.go | 15 +--- linode/instance/schema_resource.go | 18 ---- linode/instance/tmpl/template.go | 20 ----- .../tmpl/templates/disk_encryption.gotf | 15 ---- linode/instancedisk/framework_model.go | 3 - .../instancedisk/framework_resource_schema.go | 5 -- linode/instancedisk/resource_test.go | 5 -- linode/lke/cluster.go | 13 ++- linode/lke/datasource_test.go | 1 - linode/lke/framework_datasource_schema.go | 5 -- linode/lke/framework_models.go | 17 ++-- linode/lke/resource_test.go | 10 --- linode/lke/schema_resource.go | 6 -- linode/lke/tmpl/basic.gotf | 9 -- linode/lkenodepool/framework_models.go | 16 ++-- .../lkenodepool/framework_models_unit_test.go | 8 +- .../lkenodepool/framework_resource_schema.go | 5 -- linode/lkenodepool/framework_resource_test.go | 1 - 29 files changed, 29 insertions(+), 280 deletions(-) delete mode 100644 linode/instance/tmpl/templates/disk_encryption.gotf diff --git a/docs/data-sources/instances.md b/docs/data-sources/instances.md index 57d796926..e4dd579ce 100644 --- a/docs/data-sources/instances.md +++ b/docs/data-sources/instances.md @@ -105,12 +105,6 @@ Each Linode instance will be stored in the `instances` attribute and will export * `has_user_data` - Whether this Instance was created with user-data. -* `disk_encryption` - The disk encryption policy for this instance. - - * **NOTE: Disk encryption may not currently be available to all users.** - -* `lke_cluster_id` - If applicable, the ID of the LKE cluster this instance is a part of. - * `specs.0.disk` - The amount of storage space, in GB. this Linode has access to. A typical Linode will divide this space between a primary disk with an image deployed to it, and a swap disk, usually 512 MB. This is the default configuration created when deploying a Linode with an image through POST /linode/instances. * `specs.0.memory` - The amount of RAM, in MB, this Linode has access to. Typically a Linode will choose to boot with all of its available RAM, but this can be configured in a Config profile. diff --git a/docs/data-sources/lke_cluster.md b/docs/data-sources/lke_cluster.md index b2528dcb3..0af50e9d9 100644 --- a/docs/data-sources/lke_cluster.md +++ b/docs/data-sources/lke_cluster.md @@ -55,10 +55,6 @@ In addition to all arguments above, the following attributes are exported: * `count` - The number of nodes in the Node Pool. - * `disk_encryption` - The disk encryption policy for nodes in this pool. - - * **NOTE: Disk encryption may not currently be available to all users.** - * `tags` - An array of tags applied to this object. Tags are case-insensitive and are for organizational purposes only. * `nodes` - The nodes in the Node Pool. diff --git a/docs/resources/instance.md b/docs/resources/instance.md index a40ec4cf3..196e9e420 100644 --- a/docs/resources/instance.md +++ b/docs/resources/instance.md @@ -190,10 +190,6 @@ The following arguments are supported: * `firewall_id` - (Optional) The ID of the Firewall to attach to the instance upon creation. *Changing `firewall_id` forces the creation of a new Linode Instance.* -* `disk_encryption` - (Optional) The disk encryption policy for this instance. (`enabled`, `disabled`; default `enabled` in supported regions) - - * **NOTE: Disk encryption may not currently be available to all users.** - * `group` - (Optional, Deprecated) A deprecated property denoting a group label for this Linode. We recommend using the `tags` attribute instead. ### Simplified Resource Arguments @@ -363,8 +359,6 @@ This Linode Instance resource exports the following attributes: * `has_user_data` - Whether this Instance was created with user-data. -* `lke_cluster_id` - If applicable, the ID of the LKE cluster this instance is a part of. - * `specs.0.disk` - The amount of storage space, in GB. this Linode has access to. A typical Linode will divide this space between a primary disk with an image deployed to it, and a swap disk, usually 512 MB. This is the default configuration created when deploying a Linode with an image through POST /linode/instances. * `specs.0.memory` - The amount of RAM, in MB, this Linode has access to. Typically a Linode will choose to boot with all of its available RAM, but this can be configured in a Config profile. diff --git a/docs/resources/instance_disk.md b/docs/resources/instance_disk.md index d45162fb7..0b9e73c2f 100644 --- a/docs/resources/instance_disk.md +++ b/docs/resources/instance_disk.md @@ -88,10 +88,6 @@ This resource exports the following attributes: * `created` - When this disk was created. -* `disk_encryption` - The disk encryption policy for this disk's parent instance. (`enabled`, `disabled`) - - * **NOTE: Disk encryption may not currently be available to all users.** - * `status` - A brief description of this Disk's current state. * `updated` - When this disk was last updated. diff --git a/docs/resources/lke_cluster.md b/docs/resources/lke_cluster.md index 6737eef7c..56800057e 100644 --- a/docs/resources/lke_cluster.md +++ b/docs/resources/lke_cluster.md @@ -161,10 +161,6 @@ In addition to all arguments above, the following attributes are exported: * `id` - The ID of the Node Pool. - * `disk_encryption` - The disk encryption policy for nodes in this pool. - - * **NOTE: Disk encryption may not currently be available to all users.** - * [`nodes`](#nodes) - The nodes in the Node Pool. ### nodes diff --git a/docs/resources/lke_node_pool.md b/docs/resources/lke_node_pool.md index 676bab34f..13bfb8008 100644 --- a/docs/resources/lke_node_pool.md +++ b/docs/resources/lke_node_pool.md @@ -103,10 +103,6 @@ In addition to all arguments above, the following attributes are exported: * `id` - The ID of the Node Pool within LKE Cluster. -* `disk_encryption` - The disk encryption policy for nodes in this pool. - - * **NOTE: Disk encryption may not currently be available to all users.** - * [`nodes`](#nodes) - The nodes in the Node Pool. ### nodes diff --git a/linode/instance/datasource.go b/linode/instance/datasource.go index 1846a5d6c..671762496 100644 --- a/linode/instance/datasource.go +++ b/linode/instance/datasource.go @@ -11,19 +11,17 @@ import ( ) var filterConfig = helper.FilterConfig{ - "group": {APIFilterable: true, TypeFunc: helper.FilterTypeString}, - "id": {APIFilterable: true, TypeFunc: helper.FilterTypeInt}, - "image": {APIFilterable: true, TypeFunc: helper.FilterTypeString}, - "label": {APIFilterable: true, TypeFunc: helper.FilterTypeString}, - "region": {APIFilterable: true, TypeFunc: helper.FilterTypeString}, - "lke_cluster_id": {APIFilterable: true, TypeFunc: helper.FilterTypeInt}, + "group": {APIFilterable: true, TypeFunc: helper.FilterTypeString}, + "id": {APIFilterable: true, TypeFunc: helper.FilterTypeInt}, + "image": {APIFilterable: true, TypeFunc: helper.FilterTypeString}, + "label": {APIFilterable: true, TypeFunc: helper.FilterTypeString}, + "region": {APIFilterable: true, TypeFunc: helper.FilterTypeString}, // Tags must be filtered on the client "tags": {TypeFunc: helper.FilterTypeString}, "status": {TypeFunc: helper.FilterTypeString}, "type": {TypeFunc: helper.FilterTypeString}, "watchdog_enabled": {TypeFunc: helper.FilterTypeBool}, - "disk_encryption": {TypeFunc: helper.FilterTypeString}, } func dataSourceInstance() *schema.Resource { diff --git a/linode/instance/datasource_test.go b/linode/instance/datasource_test.go index 5a9258a3e..220305749 100644 --- a/linode/instance/datasource_test.go +++ b/linode/instance/datasource_test.go @@ -35,7 +35,6 @@ func TestAccDataSourceInstances_basic(t *testing.T) { resource.TestCheckResourceAttr(resName, "instances.0.region", testRegion), resource.TestCheckResourceAttr(resName, "instances.0.group", "tf_test"), resource.TestCheckResourceAttr(resName, "instances.0.swap_size", "256"), - resource.TestCheckResourceAttrSet(resName, "instances.0.disk_encryption"), resource.TestCheckResourceAttr(resName, "instances.0.ipv4.#", "2"), resource.TestCheckResourceAttrSet(resName, "instances.0.ipv6"), resource.TestCheckResourceAttrSet(resName, "instances.0.host_uuid"), diff --git a/linode/instance/flatten.go b/linode/instance/flatten.go index b3566bf07..656d4d92f 100644 --- a/linode/instance/flatten.go +++ b/linode/instance/flatten.go @@ -49,8 +49,6 @@ func flattenInstance( result["image"] = instance.Image result["host_uuid"] = instance.HostUUID result["has_user_data"] = instance.HasUserData - result["disk_encryption"] = instance.DiskEncryption - result["lke_cluster_id"] = instance.LKEClusterID result["backups"] = flattenInstanceBackups(*instance) result["specs"] = flattenInstanceSpecs(*instance) diff --git a/linode/instance/resource.go b/linode/instance/resource.go index 76c6c240f..1a5e66d67 100644 --- a/linode/instance/resource.go +++ b/linode/instance/resource.go @@ -115,8 +115,6 @@ func readResource(ctx context.Context, d *schema.ResourceData, meta interface{}) d.Set("booted", isInstanceBooted(instance)) d.Set("host_uuid", instance.HostUUID) d.Set("has_user_data", instance.HasUserData) - d.Set("lke_cluster_id", instance.LKEClusterID) - d.Set("disk_encryption", instance.DiskEncryption) flatSpecs := flattenInstanceSpecs(*instance) flatAlerts := flattenInstanceAlerts(*instance) @@ -183,9 +181,6 @@ func createResource(ctx context.Context, d *schema.ResourceData, meta interface{ Group: d.Get("group").(string), BackupsEnabled: d.Get("backups_enabled").(bool), PrivateIP: d.Get("private_ip").(bool), - DiskEncryption: linodego.InstanceDiskEncryption( - d.Get("disk_encryption").(string), - ), } if tagsRaw, tagsOk := d.GetOk("tags"); tagsOk { diff --git a/linode/instance/resource_test.go b/linode/instance/resource_test.go index 8f5125eca..6214e1dfa 100644 --- a/linode/instance/resource_test.go +++ b/linode/instance/resource_test.go @@ -2513,95 +2513,6 @@ func TestAccResourceInstance_pgAssignment(t *testing.T) { }) } -func TestAccResourceInstance_diskEncryption(t *testing.T) { - t.Parallel() - - resName := "linode_instance.foobar" - var instance linodego.Instance - instanceName := acctest.RandomWithPrefix("tf_test") - rootPass := acctest.RandString(16) - - // Resolve a region that supports disk encryption - targetRegion, err := acceptance.GetRandomRegionWithCaps( - []string{linodego.CapabilityLinodes, linodego.CapabilityDiskEncryption}, "core", - ) - if err != nil { - t.Fatal(err) - } - - encryptionEnabled := linodego.InstanceDiskEncryptionEnabled - encryptionDisabled := linodego.InstanceDiskEncryptionDisabled - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acceptance.PreCheck(t) }, - ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, - CheckDestroy: acceptance.CheckInstanceDestroy, - Steps: []resource.TestStep{ - { - Config: tmpl.DiskEncryption( - t, - instanceName, - targetRegion, - rootPass, - &encryptionEnabled, - ), - Check: resource.ComposeTestCheckFunc( - acceptance.CheckInstanceExists(resName, &instance), - resource.TestCheckResourceAttr(resName, "label", instanceName), - resource.TestCheckResourceAttr(resName, "type", "g6-nanode-1"), - resource.TestCheckResourceAttr(resName, "image", acceptance.TestImageLatest), - resource.TestCheckResourceAttr(resName, "region", testRegion), - resource.TestCheckResourceAttr(resName, "disk_encryption", "enabled"), - ), - }, - { - Config: tmpl.DiskEncryption( - t, - instanceName, - targetRegion, - rootPass, - &encryptionDisabled, - ), - Check: resource.ComposeTestCheckFunc( - acceptance.CheckInstanceExists(resName, &instance), - resource.TestCheckResourceAttr(resName, "label", instanceName), - resource.TestCheckResourceAttr(resName, "type", "g6-nanode-1"), - resource.TestCheckResourceAttr(resName, "image", acceptance.TestImageLatest), - resource.TestCheckResourceAttr(resName, "region", testRegion), - resource.TestCheckResourceAttr(resName, "disk_encryption", "disabled"), - ), - }, - - // Make sure the instance is not recreated when disk_encryption is not explicitly set. - // This is necessary to prevent instances created pre-disk-encryption from being recreated. - { - Config: tmpl.DiskEncryption( - t, - instanceName, - targetRegion, - rootPass, - nil, - ), - Check: resource.ComposeTestCheckFunc( - acceptance.CheckInstanceExists(resName, &instance), - resource.TestCheckResourceAttr(resName, "label", instanceName), - resource.TestCheckResourceAttr(resName, "type", "g6-nanode-1"), - resource.TestCheckResourceAttr(resName, "image", acceptance.TestImageLatest), - resource.TestCheckResourceAttr(resName, "region", testRegion), - resource.TestCheckResourceAttr(resName, "disk_encryption", "disabled"), - ), - }, - - { - ResourceName: resName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"root_pass", "authorized_keys", "image", "resize_disk", "migration_type"}, - }, - }, - }) -} - func checkInstancePrivateNetworkAttributes(n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] diff --git a/linode/instance/schema_datasource.go b/linode/instance/schema_datasource.go index 66e344901..430a3a644 100644 --- a/linode/instance/schema_datasource.go +++ b/linode/instance/schema_datasource.go @@ -1,8 +1,6 @@ package instance -import ( - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) +import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" var instanceDataSourceSchema = map[string]*schema.Schema{ "id": { @@ -104,17 +102,6 @@ var instanceDataSourceSchema = map[string]*schema.Schema{ Description: "Whether this Instance was created with user-data.", Computed: true, }, - "disk_encryption": { - Type: schema.TypeString, - Description: "The disk encryption policy for this Instance." + - "NOTE: Disk encryption may not currently be available to all users.", - Computed: true, - }, - "lke_cluster_id": { - Type: schema.TypeInt, - Description: "If applicable, the ID of the LKE cluster this Instance is a node of.", - Computed: true, - }, "specs": { Computed: true, Type: schema.TypeList, diff --git a/linode/instance/schema_resource.go b/linode/instance/schema_resource.go index cb65f77ae..d1e08006c 100644 --- a/linode/instance/schema_resource.go +++ b/linode/instance/schema_resource.go @@ -475,24 +475,6 @@ var resourceSchema = map[string]*schema.Schema{ Description: "Whether or not this Instance was created with user-data.", Computed: true, }, - "disk_encryption": { - Type: schema.TypeString, - Description: "The disk encryption policy for this Instance. " + - "NOTE: Disk encryption may not currently be available to all users.", - Optional: true, - ForceNew: true, - ValidateDiagFunc: validation.ToDiagFunc( - validation.StringInSlice([]string{"enabled", "disabled"}, false), - ), - - // This is necessary to prevent instances created pre-disk-encryption from being recreated. - Computed: true, - }, - "lke_cluster_id": { - Type: schema.TypeInt, - Description: "If applicable, the ID of the LKE cluster this Instance is a node of.", - Computed: true, - }, "specs": { Computed: true, Description: "Information about the resources available to this Linode.", diff --git a/linode/instance/tmpl/template.go b/linode/instance/tmpl/template.go index 2dd51a57d..faf50a2c8 100644 --- a/linode/instance/tmpl/template.go +++ b/linode/instance/tmpl/template.go @@ -3,7 +3,6 @@ package tmpl import ( "testing" - "github.com/linode/linodego" "github.com/linode/terraform-provider-linode/v2/linode/acceptance" ) @@ -26,8 +25,6 @@ type TemplateData struct { PlacementGroups []string AssignedGroup string - - DiskEncryption *linodego.InstanceDiskEncryption } func Basic(t *testing.T, label, pubKey, region string, rootPass string) string { @@ -596,23 +593,6 @@ func UserData(t *testing.T, label, region string, rootPass string) string { }) } -func DiskEncryption( - t *testing.T, - label, - region, - rootPass string, - diskEncryption *linodego.InstanceDiskEncryption, -) string { - return acceptance.ExecuteTemplate(t, - "instance_disk_encryption", TemplateData{ - Label: label, - Image: acceptance.TestImageLatest, - Region: region, - RootPass: rootPass, - DiskEncryption: diskEncryption, - }) -} - func DataBasic(t *testing.T, label, region string, rootPass string) string { return acceptance.ExecuteTemplate(t, "instance_data_basic", TemplateData{ diff --git a/linode/instance/tmpl/templates/disk_encryption.gotf b/linode/instance/tmpl/templates/disk_encryption.gotf deleted file mode 100644 index 6fbe2129c..000000000 --- a/linode/instance/tmpl/templates/disk_encryption.gotf +++ /dev/null @@ -1,15 +0,0 @@ -{{ define "instance_disk_encryption" }} - -resource "linode_instance" "foobar" { - label = "{{.Label}}" - type = "g6-nanode-1" - image = "{{.Image}}" - region = "{{ .Region }}" - root_pass = "{{ .RootPass }}" - -{{ if not (eq .DiskEncryption nil) }} - disk_encryption = "{{ .DiskEncryption }}" -{{ end }} -} - -{{ end }} \ No newline at end of file diff --git a/linode/instancedisk/framework_model.go b/linode/instancedisk/framework_model.go index 9ccd2e8d4..8e70e36c9 100644 --- a/linode/instancedisk/framework_model.go +++ b/linode/instancedisk/framework_model.go @@ -25,7 +25,6 @@ type ResourceModel struct { Created timetypes.RFC3339 `tfsdk:"created"` Updated timetypes.RFC3339 `tfsdk:"updated"` Status types.String `tfsdk:"status"` - DiskEncryption types.String `tfsdk:"disk_encryption"` Timeouts timeouts.Value `tfsdk:"timeouts"` } @@ -45,7 +44,6 @@ func (data *ResourceModel) FlattenDisk(disk *linodego.InstanceDisk, preserveKnow ) data.Status = helper.KeepOrUpdateString(data.Status, string(disk.Status), preserveKnown) - data.DiskEncryption = helper.KeepOrUpdateString(data.DiskEncryption, string(disk.DiskEncryption), preserveKnown) } func (data *ResourceModel) CopyFrom(other ResourceModel, preserveKnown bool) { @@ -70,5 +68,4 @@ func (data *ResourceModel) CopyFrom(other ResourceModel, preserveKnown bool) { data.Created = helper.KeepOrUpdateValue(data.Created, other.Created, preserveKnown) data.Updated = helper.KeepOrUpdateValue(data.Updated, other.Updated, preserveKnown) data.Status = helper.KeepOrUpdateValue(data.Status, other.Status, preserveKnown) - data.DiskEncryption = helper.KeepOrUpdateValue(data.DiskEncryption, other.DiskEncryption, preserveKnown) } diff --git a/linode/instancedisk/framework_resource_schema.go b/linode/instancedisk/framework_resource_schema.go index 7f16dcb2a..7c3e91c64 100644 --- a/linode/instancedisk/framework_resource_schema.go +++ b/linode/instancedisk/framework_resource_schema.go @@ -145,10 +145,5 @@ var frameworkResourceSchema = schema.Schema{ Description: "A brief description of this Disk's current state.", Computed: true, }, - "disk_encryption": schema.StringAttribute{ - Description: "The disk encryption policy for this disk's parent Linode. " + - "NOTE: Disk encryption may not currently be available to all users.", - Computed: true, - }, }, } diff --git a/linode/instancedisk/resource_test.go b/linode/instancedisk/resource_test.go index 9cf14d71e..f168440db 100644 --- a/linode/instancedisk/resource_test.go +++ b/linode/instancedisk/resource_test.go @@ -49,11 +49,6 @@ func TestAccResourceInstanceDisk_basic_smoke(t *testing.T) { resource.TestCheckResourceAttr(resName, "status", "ready"), resource.TestCheckResourceAttrSet(resName, "linode_id"), - - resource.TestCheckResourceAttrPair( - resName, "disk_encryption", - "linode_instance.foobar", "disk_encryption", - ), ), }, // Resize up diff --git a/linode/lke/cluster.go b/linode/lke/cluster.go index 7af29b364..dcb7ea86e 100644 --- a/linode/lke/cluster.go +++ b/linode/lke/cluster.go @@ -429,13 +429,12 @@ func flattenLKENodePools(pools []linodego.LKENodePool) []map[string]interface{} } flattened[i] = map[string]interface{}{ - "id": pool.ID, - "count": pool.Count, - "type": pool.Type, - "tags": pool.Tags, - "disk_encryption": pool.DiskEncryption, - "nodes": nodes, - "autoscaler": autoscaler, + "id": pool.ID, + "count": pool.Count, + "type": pool.Type, + "tags": pool.Tags, + "nodes": nodes, + "autoscaler": autoscaler, } } return flattened diff --git a/linode/lke/datasource_test.go b/linode/lke/datasource_test.go index 53613e53f..b56a5de7e 100644 --- a/linode/lke/datasource_test.go +++ b/linode/lke/datasource_test.go @@ -34,7 +34,6 @@ func TestAccDataSourceLKECluster_basic(t *testing.T) { resource.TestCheckResourceAttr(dataSourceClusterName, "pools.#", "1"), resource.TestCheckResourceAttr(dataSourceClusterName, "pools.0.type", "g6-standard-1"), resource.TestCheckResourceAttr(dataSourceClusterName, "pools.0.count", "3"), - resource.TestCheckResourceAttrSet(dataSourceClusterName, "pools.0.disk_encryption"), resource.TestCheckResourceAttr(dataSourceClusterName, "pools.0.nodes.#", "3"), resource.TestCheckResourceAttr(dataSourceClusterName, "pools.0.autoscaler.#", "0"), resource.TestCheckResourceAttr(dataSourceClusterName, "control_plane.0.high_availability", "false"), diff --git a/linode/lke/framework_datasource_schema.go b/linode/lke/framework_datasource_schema.go index cc94bcc6e..b07ca2288 100644 --- a/linode/lke/framework_datasource_schema.go +++ b/linode/lke/framework_datasource_schema.go @@ -121,11 +121,6 @@ var frameworkDataSourceSchema = schema.Schema{ Computed: true, Description: "An array of tags applied to this object. Tags are for organizational purposes only.", }, - "disk_encryption": schema.StringAttribute{ - Computed: true, - Description: "The disk encryption policy for the nodes in this pool. " + - "NOTE: Disk encryption may not currently be available to all users.", - }, }, Blocks: map[string]schema.Block{ "nodes": schema.ListNestedBlock{ diff --git a/linode/lke/framework_models.go b/linode/lke/framework_models.go index 67fe88531..da58b18a2 100644 --- a/linode/lke/framework_models.go +++ b/linode/lke/framework_models.go @@ -53,14 +53,13 @@ type LKEControlPlaneACLAddresses struct { } type LKENodePool struct { - ID types.Int64 `tfsdk:"id"` - Count types.Int64 `tfsdk:"count"` - Type types.String `tfsdk:"type"` - Tags types.List `tfsdk:"tags"` - DiskEncryption types.String `tfsdk:"disk_encryption"` - Disks []LKENodePoolDisk `tfsdk:"disks"` - Nodes []LKENodePoolNode `tfsdk:"nodes"` - Autoscaler []LKENodePoolAutoscaler `tfsdk:"autoscaler"` + ID types.Int64 `tfsdk:"id"` + Count types.Int64 `tfsdk:"count"` + Type types.String `tfsdk:"type"` + Tags types.List `tfsdk:"tags"` + Disks []LKENodePoolDisk `tfsdk:"disks"` + Nodes []LKENodePoolNode `tfsdk:"nodes"` + Autoscaler []LKENodePoolAutoscaler `tfsdk:"autoscaler"` } type LKENodePoolDisk struct { @@ -117,8 +116,6 @@ func (data *LKEDataModel) parseLKEAttributes( pool.ID = types.Int64Value(int64(p.ID)) pool.Count = types.Int64Value(int64(p.Count)) pool.Type = types.StringValue(p.Type) - pool.DiskEncryption = types.StringValue(string(p.DiskEncryption)) - tags, diags := types.ListValueFrom(ctx, types.StringType, p.Tags) if diags != nil { return nil, diags diff --git a/linode/lke/resource_test.go b/linode/lke/resource_test.go index 5c75a6cd5..1260618bf 100644 --- a/linode/lke/resource_test.go +++ b/linode/lke/resource_test.go @@ -185,7 +185,6 @@ func TestAccResourceLKECluster_basic_smoke(t *testing.T) { resource.TestCheckResourceAttr(resourceClusterName, "pool.#", "1"), resource.TestCheckResourceAttr(resourceClusterName, "pool.0.type", "g6-standard-1"), resource.TestCheckResourceAttr(resourceClusterName, "pool.0.count", "3"), - resource.TestCheckResourceAttrSet(resourceClusterName, "pool.0.disk_encryption"), resource.TestCheckResourceAttr(resourceClusterName, "pool.0.nodes.#", "3"), resource.TestCheckResourceAttr(resourceClusterName, "pool.0.tags.#", "1"), resource.TestCheckResourceAttr(resourceClusterName, "pool.0.tags.0", "test"), @@ -195,15 +194,6 @@ func TestAccResourceLKECluster_basic_smoke(t *testing.T) { resource.TestCheckResourceAttrSet(resourceClusterName, "pool.0.id"), resource.TestCheckResourceAttrSet(resourceClusterName, "kubeconfig"), resource.TestCheckResourceAttrSet(resourceClusterName, "dashboard_url"), - - // Ensure the lke_cluster_id field is populated on a sample - // node from the new cluster. - resource.TestCheckResourceAttrPair( - resourceClusterName, - "id", - "data.linode_instances.test", - "instances.0.lke_cluster_id", - ), ), }, }, diff --git a/linode/lke/schema_resource.go b/linode/lke/schema_resource.go index 9a6641bd9..a3c4356d6 100644 --- a/linode/lke/schema_resource.go +++ b/linode/lke/schema_resource.go @@ -110,12 +110,6 @@ var resourceSchema = map[string]*schema.Schema{ return false }, }, - "disk_encryption": { - Type: schema.TypeString, - Description: "The disk encryption policy for the nodes in this pool. " + - "NOTE: Disk encryption may not currently be available to all users.", - Computed: true, - }, "nodes": { Type: schema.TypeList, Elem: &schema.Resource{ diff --git a/linode/lke/tmpl/basic.gotf b/linode/lke/tmpl/basic.gotf index a288ba1dc..8d55bd96f 100644 --- a/linode/lke/tmpl/basic.gotf +++ b/linode/lke/tmpl/basic.gotf @@ -13,13 +13,4 @@ resource "linode_lke_cluster" "test" { } } -data "linode_instances" "test" { - depends_on = [linode_lke_cluster.test] - - filter { - name = "id" - values = [linode_lke_cluster.test.pool.0.nodes.0.instance_id] - } -} - {{ end }} \ No newline at end of file diff --git a/linode/lkenodepool/framework_models.go b/linode/lkenodepool/framework_models.go index f1d4e257c..ed35711ca 100644 --- a/linode/lkenodepool/framework_models.go +++ b/linode/lkenodepool/framework_models.go @@ -14,14 +14,13 @@ import ( ) type NodePoolModel struct { - ID types.String `tfsdk:"id"` - ClusterID types.Int64 `tfsdk:"cluster_id"` - Count types.Int64 `tfsdk:"node_count"` - Type types.String `tfsdk:"type"` - DiskEncryption types.String `tfsdk:"disk_encryption"` - Tags types.Set `tfsdk:"tags"` - Nodes types.List `tfsdk:"nodes"` - Autoscaler []NodePoolAutoscalerModel `tfsdk:"autoscaler"` + ID types.String `tfsdk:"id"` + ClusterID types.Int64 `tfsdk:"cluster_id"` + Count types.Int64 `tfsdk:"node_count"` + Type types.String `tfsdk:"type"` + Tags types.Set `tfsdk:"tags"` + Nodes types.List `tfsdk:"nodes"` + Autoscaler []NodePoolAutoscalerModel `tfsdk:"autoscaler"` } type NodePoolAutoscalerModel struct { @@ -77,7 +76,6 @@ func (pool *NodePoolModel) FlattenLKENodePool( pool.ID = helper.KeepOrUpdateString(pool.ID, strconv.Itoa(p.ID), preserveKnown) pool.Count = helper.KeepOrUpdateInt64(pool.Count, int64(p.Count), preserveKnown) pool.Type = helper.KeepOrUpdateString(pool.Type, p.Type, preserveKnown) - pool.DiskEncryption = helper.KeepOrUpdateString(pool.DiskEncryption, string(p.DiskEncryption), preserveKnown) pool.Tags = helper.KeepOrUpdateStringSet(pool.Tags, p.Tags, preserveKnown, diags) if diags.HasError() { return diff --git a/linode/lkenodepool/framework_models_unit_test.go b/linode/lkenodepool/framework_models_unit_test.go index 4e6905c65..df2bfa75e 100644 --- a/linode/lkenodepool/framework_models_unit_test.go +++ b/linode/lkenodepool/framework_models_unit_test.go @@ -14,10 +14,9 @@ import ( func TestParseNodePool(t *testing.T) { lkeNodePool := linodego.LKENodePool{ - ID: 123, - Count: 3, - Type: "g6-standard-2", - DiskEncryption: linodego.InstanceDiskEncryptionEnabled, + ID: 123, + Count: 3, + Type: "g6-standard-2", Disks: []linodego.LKENodePoolDisk{ {Size: 50, Type: "ssd"}, }, @@ -43,7 +42,6 @@ func TestParseNodePool(t *testing.T) { assert.Equal(t, "123", nodePoolModel.ID.ValueString()) assert.Equal(t, int64(3), nodePoolModel.Count.ValueInt64()) assert.Equal(t, "g6-standard-2", nodePoolModel.Type.ValueString()) - assert.Equal(t, "enabled", nodePoolModel.DiskEncryption.ValueString()) assert.Len(t, nodePoolModel.Nodes.Elements(), 3) tags := make([]string, len(nodePoolModel.Tags.Elements())) diff --git a/linode/lkenodepool/framework_resource_schema.go b/linode/lkenodepool/framework_resource_schema.go index 84762a073..c7272d770 100644 --- a/linode/lkenodepool/framework_resource_schema.go +++ b/linode/lkenodepool/framework_resource_schema.go @@ -51,11 +51,6 @@ var resourceSchema = schema.Schema{ stringplanmodifier.RequiresReplace(), }, }, - "disk_encryption": schema.StringAttribute{ - Description: "The disk encryption policy for nodes in this pool. " + - "NOTE: Disk encryption may not currently be available to all users.", - Computed: true, - }, "tags": schema.SetAttribute{ ElementType: types.StringType, Optional: true, diff --git a/linode/lkenodepool/framework_resource_test.go b/linode/lkenodepool/framework_resource_test.go index cab315852..c9515b620 100644 --- a/linode/lkenodepool/framework_resource_test.go +++ b/linode/lkenodepool/framework_resource_test.go @@ -135,7 +135,6 @@ func TestAccResourceNodePool_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( checkNodePoolExists, resource.TestCheckResourceAttr(resName, "type", "g6-standard-1"), - resource.TestCheckResourceAttrSet(resName, "disk_encryption"), resource.TestCheckResourceAttr(resName, "tags.#", "2"), resource.TestCheckResourceAttr(resName, "tags.0", "external"), resource.TestCheckResourceAttr(resName, "tags.1", poolTag), From dc811bad9db044d2a0594333410a254cd5489a5a Mon Sep 17 00:00:00 2001 From: Ye Chen <127243817+yec-akamai@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:45:04 -0400 Subject: [PATCH 25/25] fix test case (#1555) --- linode/instance/tmpl/templates/private_image.gotf | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/linode/instance/tmpl/templates/private_image.gotf b/linode/instance/tmpl/templates/private_image.gotf index d8c5e96ff..ad2746716 100644 --- a/linode/instance/tmpl/templates/private_image.gotf +++ b/linode/instance/tmpl/templates/private_image.gotf @@ -7,11 +7,7 @@ resource "linode_instance" "foobar-orig" { group = "tf_test" type = "g6-nanode-1" region = "{{ .Region }}" - disk { - label = "disk" - size = 1000 - filesystem = "ext4" - } + image = "linode/alpine3.19" firewall_id = linode_firewall.e2e_test_firewall.id }