Skip to content

Commit 84460fb

Browse files
support for tpm and secureboot flags for compute_instance (#452)
# Description Support for [TPM](https://openapi-v2.exoscale.com/operation/operation-create-instance#operation-create-instance-body-application-json-tpm-enabled) and [SecureBoot](https://openapi-v2.exoscale.com/operation/operation-create-instance#operation-create-instance-body-application-json-secureboot-enabled) flags for compute_instance ## Checklist (For exoscale contributors) * [x] Changelog updated (under *Unreleased* block) * [x] Acceptance tests OK * [x] For a new resource, datasource or new attributes: acceptance test added/updated ## Testing ``` # Providers # -> providers.tf # Customizable parameters locals { my_zone = "ch-gva-2" } data "exoscale_template" "my_template" { zone = "ch-gva-2" name = "Linux Ubuntu 22.04 LTS 64-bit" } resource "exoscale_compute_instance" "my_instance" { zone = "ch-gva-2" name = "my-instance-ho" enable_tpm = true enable_secure_boot = true template_id = data.exoscale_template.my_template.id type = "standard.medium" disk_size = 10 } ``` **CREATING** ``` terraform apply Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # exoscale_compute_instance.my_instance will be created + resource "exoscale_compute_instance" "my_instance" { + created_at = (known after apply) + disk_size = 10 + enable_secure_boot = true + enable_tpm = true + id = (known after apply) + ipv6 = false + ipv6_address = (known after apply) + mac_address = (known after apply) + name = "my-instance-ho" + private = false + private_network_ids = (known after apply) + public_ip_address = (known after apply) + state = (known after apply) + template_id = "f9950b1f-8c1b-4957-8be5-22061a0a3a0b" + type = "standard.medium" + zone = "ch-gva-2" } Plan: 1 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes exoscale_compute_instance.my_instance: Creating... exoscale_compute_instance.my_instance: Still creating... [10s elapsed] exoscale_compute_instance.my_instance: Creation complete after 13s [id=240e7f09-5d1b-415a-b230-afe7a37bf693] Apply complete! Resources: 1 added, 0 changed, 0 destroyed. ``` <img width="1498" height="817" alt="Screenshot 2025-07-21 at 14 45 46" src="https://github.com/user-attachments/assets/bb0e87bc-a81d-44ab-bb7a-4704ccafbdd0" /> **ENABLING TPM** ``` exoscale/terraform  terraform apply data.exoscale_template.my_template: Reading... data.exoscale_template.my_template: Read complete after 1s [id=f9950b1f-8c1b-4957-8be5-22061a0a3a0b] exoscale_compute_instance.my_instance: Refreshing state... [id=a53f01f2-597b-417d-8196-174cda068b23] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions: # exoscale_compute_instance.my_instance will be updated in-place ~ resource "exoscale_compute_instance" "my_instance" { ~ enable_tpm = false -> true id = "a53f01f2-597b-417d-8196-174cda068b23" name = "my-instance" ~ security_group_ids = [ - "757ee92c-d1a5-4161-bb7c-4adebe3d388d", ] # (16 unchanged attributes hidden) } Plan: 0 to add, 1 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes exoscale_compute_instance.my_instance: Modifying... [id=a53f01f2-597b-417d-8196-174cda068b23] exoscale_compute_instance.my_instance: Still modifying... [id=a53f01f2-597b-417d-8196-174cda068b23, 10s elapsed] exoscale_compute_instance.my_instance: Still modifying... [id=a53f01f2-597b-417d-8196-174cda068b23, 20s elapsed] exoscale_compute_instance.my_instance: Modifications complete after 26s [id=a53f01f2-597b-417d-8196-174cda068b23] ``` **DISABLING TPM** ``` exoscale/terraform  terraform apply data.exoscale_template.my_template: Reading... data.exoscale_template.my_template: Read complete after 1s [id=f9950b1f-8c1b-4957-8be5-22061a0a3a0b] exoscale_compute_instance.my_instance: Refreshing state... [id=43e251de-0da7-4022-a0ec-ee2e99bb9cba] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions: # exoscale_compute_instance.my_instance will be updated in-place ~ resource "exoscale_compute_instance" "my_instance" { ~ enable_tpm = true -> false id = "43e251de-0da7-4022-a0ec-ee2e99bb9cba" name = "my-instance" # (17 unchanged attributes hidden) } Plan: 0 to add, 1 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes exoscale_compute_instance.my_instance: Modifying... [id=43e251de-0da7-4022-a0ec-ee2e99bb9cba] ╷ │ Error: TPM can't be disabled │ │ with exoscale_compute_instance.my_instance, │ on main.tf line 14, in resource "exoscale_compute_instance" "my_instance": │ 14: resource "exoscale_compute_instance" "my_instance" { │ ╵ ``` **ENABLING_SECURE_BOOT** ``` exoscale/terraform  terraform apply data.exoscale_template.my_template: Reading... data.exoscale_template.my_template: Read complete after 1s [id=f9950b1f-8c1b-4957-8be5-22061a0a3a0b] exoscale_compute_instance.my_instance: Refreshing state... [id=43e251de-0da7-4022-a0ec-ee2e99bb9cba] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: -/+ destroy and then create replacement Terraform will perform the following actions: # exoscale_compute_instance.my_instance must be replaced -/+ resource "exoscale_compute_instance" "my_instance" { ~ created_at = "2025-08-12 11:24:52 +0000 UTC" -> (known after apply) ~ enable_secure_boot = false -> true # forces replacement ~ enable_tpm = true -> false ~ id = "43e251de-0da7-4022-a0ec-ee2e99bb9cba" -> (known after apply) + ipv6_address = (known after apply) - labels = {} -> null ~ mac_address = "06:f1:d6:00:00:6b" -> (known after apply) name = "my-instance" ~ private_network_ids = [] -> (known after apply) ~ public_ip_address = "159.100.241.212" -> (known after apply) - security_group_ids = [] -> null - ssh_keys = [] -> null ~ state = "running" -> (known after apply) # (8 unchanged attributes hidden) } Plan: 1 to add, 0 to change, 1 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes exoscale_compute_instance.my_instance: Destroying... [id=43e251de-0da7-4022-a0ec-ee2e99bb9cba] exoscale_compute_instance.my_instance: Destruction complete after 4s exoscale_compute_instance.my_instance: Creating... exoscale_compute_instance.my_instance: Still creating... [10s elapsed] exoscale_compute_instance.my_instance: Creation complete after 10s [id=b7b5a18e-9c79-4f4e-b7da-6691daa5222c] Apply complete! Resources: 1 added, 0 changed, 1 destroyed. ``` **DISABLING SECURE_BOOT** ``` exoscale/terraform  terraform apply data.exoscale_template.my_template: Reading... data.exoscale_template.my_template: Read complete after 1s [id=f9950b1f-8c1b-4957-8be5-22061a0a3a0b] exoscale_compute_instance.my_instance: Refreshing state... [id=b7b5a18e-9c79-4f4e-b7da-6691daa5222c] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: -/+ destroy and then create replacement Terraform will perform the following actions: # exoscale_compute_instance.my_instance must be replaced -/+ resource "exoscale_compute_instance" "my_instance" { ~ created_at = "2025-08-12 11:45:46 +0000 UTC" -> (known after apply) ~ enable_secure_boot = true -> false # forces replacement ~ id = "b7b5a18e-9c79-4f4e-b7da-6691daa5222c" -> (known after apply) + ipv6_address = (known after apply) - labels = {} -> null ~ mac_address = "06:86:2e:00:00:6b" -> (known after apply) name = "my-instance" ~ private_network_ids = [] -> (known after apply) ~ public_ip_address = "159.100.241.212" -> (known after apply) - security_group_ids = [ - "757ee92c-d1a5-4161-bb7c-4adebe3d388d", ] -> null - ssh_keys = [] -> null ~ state = "running" -> (known after apply) # (9 unchanged attributes hidden) } Plan: 1 to add, 0 to change, 1 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes exoscale_compute_instance.my_instance: Destroying... [id=b7b5a18e-9c79-4f4e-b7da-6691daa5222c] exoscale_compute_instance.my_instance: Destruction complete after 6s exoscale_compute_instance.my_instance: Creating... exoscale_compute_instance.my_instance: Still creating... [10s elapsed] exoscale_compute_instance.my_instance: Creation complete after 10s [id=16c98b85-960c-4eb8-8924-fcf8d64e0697] Apply complete! Resources: 1 added, 0 changed, 1 destroyed. ``` --------- Co-authored-by: Predrag Janosevic <[email protected]>
1 parent 905fb71 commit 84460fb

File tree

8 files changed

+81
-4
lines changed

8 files changed

+81
-4
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,12 @@ IMPROVEMENTS:
2525

2626
IMPROVEMENTS:
2727
- compute_instance: make disk_size a required field #419
28+
- compute_instance: support for tpm_enalbed and secure_boot_enabled flags #452
2829
- docs: note that domain_record.content format depnds on record type #429
2930
- sks-cluster: support for feature-gates #431
3031
- Bump go version in go.mod #433
3132
- Move DNS management to egoscale v3 #434
32-
- doc: fix broken links to community docs #440
33+
- doc: fix broken links to community docs #440
3334

3435
BUG FIXES:
3536

docs/data-sources/compute_instance.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ directory for complete configuration examples.
4848
- `deploy_target_id` (String) A deploy target ID.
4949
- `disk_size` (Number) The instance disk size (GiB).
5050
- `elastic_ip_ids` (Set of String) The list of attached [exoscale_elastic_ip](../resources/elastic_ip.md) (IDs).
51+
- `enable_secure_boot` (Boolean) Whether secure boot is enabled on the instance.
52+
- `enable_tpm` (Boolean) Whether TPM is enabled on the instance.
5153
- `ipv6` (Boolean) Whether IPv6 is enabled on the instance.
5254
- `ipv6_address` (String) The instance (main network interface) IPv6 address (if enabled).
5355
- `labels` (Map of String) A map of key/value labels.

docs/data-sources/compute_instance_list.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ Read-Only:
8080
- `deploy_target_id` (String)
8181
- `disk_size` (Number)
8282
- `elastic_ip_ids` (Set of String)
83+
- `enabled_secure_boot` (Boolean)
84+
- `enabled_tpm` (Boolean)
8385
- `id` (String)
8486
- `ipv6` (Boolean)
8587
- `ipv6_address` (String)

docs/resources/compute_instance.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ directory for complete configuration examples.
5555
- `deploy_target_id` (String) ❗ A deploy target ID.
5656
- `destroy_protected` (Boolean) Mark the instance as protected, the Exoscale API will refuse to delete the instance until the protection is removed (boolean; default: `false`).
5757
- `elastic_ip_ids` (Set of String) A list of [exoscale_elastic_ip](./elastic_ip.md) (IDs) to attach to the instance.
58+
- `enable_secure_boot` (Boolean) Indicates whether secure boot is enabled on the instance.
59+
- `enable_tpm` (Boolean) Indicates whether TPM is enabled on the instance.
5860
- `ipv6` (Boolean) Enable IPv6 on the instance (boolean; default: `false`).
5961
- `labels` (Map of String) A map of key/value labels.
6062
- `network_interface` (Block Set) Private network interfaces (may be specified multiple times). Structure is documented below. (see [below for nested schema](#nestedblock--network_interface))

pkg/resources/instance/attributes.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ const (
1111
AttrDestroyProtected = "destroy_protected"
1212
AttrDiskSize = "disk_size"
1313
AttrElasticIPIDs = "elastic_ip_ids"
14+
AttrEnableSecureBoot = "enable_secure_boot"
15+
AttrEnableTPM = "enable_tpm"
1416
AttrID = "id"
1517
AttrIPv6 = "ipv6"
1618
AttrIPv6Address = "ipv6_address"

pkg/resources/instance/datasource.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ func DataSourceSchema() map[string]*schema.Schema {
4848
Set: schema.HashString,
4949
Elem: &schema.Schema{Type: schema.TypeString},
5050
},
51+
AttrEnableSecureBoot: {
52+
Description: "Indicates if the instance has secure boot enabled.",
53+
Type: schema.TypeBool,
54+
Computed: true,
55+
},
56+
AttrEnableTPM: {
57+
Description: "Indicates if the instance has TPM enabled.",
58+
Type: schema.TypeBool,
59+
Computed: true,
60+
},
5161
AttrID: {
5262
Description: "The compute instance ID to match (conflicts with `name`).",
5363
Type: schema.TypeString,
@@ -274,6 +284,8 @@ func dsBuildData(instance *v3.Instance, zone string) (map[string]interface{}, er
274284
data[AttrName] = instance.Name
275285
data[AttrState] = instance.State
276286
data[AttrZone] = zone
287+
data[AttrEnableSecureBoot] = instance.SecurebootEnabled
288+
data[AttrEnableTPM] = instance.TpmEnabled
277289

278290
data[AttrIPv6] = func() bool { return instance.PublicIPAssignment == v3.PublicIPAssignmentDual }()
279291

pkg/resources/instance/resource.go

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,18 @@ func Resource() *schema.Resource {
5757
Set: schema.HashString,
5858
Elem: &schema.Schema{Type: schema.TypeString},
5959
},
60+
AttrEnableSecureBoot: {
61+
Description: "Enable secure boot on the instance (boolean; default: `false`). Can not be changed after the creation.",
62+
Type: schema.TypeBool,
63+
Optional: true,
64+
ForceNew: true,
65+
},
66+
AttrEnableTPM: {
67+
Description: "Enable TPM on the instance (boolean; default: `false`). Can not be disabled after the creation. **WARNING**: enabling this attribute stops/restarts the instance.",
68+
Type: schema.TypeBool,
69+
Optional: true,
70+
Default: false,
71+
},
6072
AttrIPv6: {
6173
Description: "Enable IPv6 on the instance (boolean; default: `false`).",
6274
Type: schema.TypeBool,
@@ -284,6 +296,16 @@ func rCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag
284296
instanceRequest.PublicIPAssignment = v3.PublicIPAssignment(t)
285297
}
286298

299+
if enableTPM, ok := d.GetOk(AttrEnableTPM); ok {
300+
tpmEnabledBool := enableTPM.(bool)
301+
instanceRequest.TpmEnabled = &tpmEnabledBool
302+
}
303+
304+
if enableSecureBoot, ok := d.GetOk(AttrEnableSecureBoot); ok {
305+
secureBootEnabledBool := enableSecureBoot.(bool)
306+
instanceRequest.SecurebootEnabled = &secureBootEnabledBool
307+
}
308+
287309
if l, ok := d.GetOk(AttrLabels); ok {
288310
labels := make(map[string]string)
289311
for k, v := range l.(map[string]interface{}) {
@@ -775,22 +797,23 @@ func rUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag
775797
}
776798
}
777799
}
778-
779800
if d.HasChanges(
780801
AttrState,
781802
AttrDiskSize,
782803
AttrType,
804+
AttrEnableTPM,
783805
) {
784806
// Check if size is below current size to prevent uneeded stop as API will prevent the scale operation
785807
if d.HasChange(AttrDiskSize) &&
786808
instance.DiskSize > int64(d.Get(AttrDiskSize).(int)) {
787809
return diag.Errorf("unable to scale down the disk size, use size > %v", instance.DiskSize)
788810
}
789811

790-
// Compute instance scaling/disk resizing API operations requires the instance to be stopped.
812+
// Compute instance scaling/disk resizing/TPM enabling API operations requires the instance to be stopped.
791813
if d.Get(AttrState) == "stopped" ||
792814
d.HasChange(AttrDiskSize) ||
793-
d.HasChange(AttrType) {
815+
d.HasChange(AttrType) ||
816+
(d.HasChange(AttrEnableTPM) && d.Get(AttrEnableTPM).(bool)) {
794817
op, err := client.StopInstance(ctx, instance.ID)
795818
if err != nil {
796819
return diag.Errorf("unable to stop instance: %s", err)
@@ -830,6 +853,20 @@ func rUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag
830853
}
831854
}
832855

856+
if d.HasChange(AttrEnableTPM) {
857+
if d.Get(AttrEnableTPM).(bool) {
858+
op, err := client.EnableTpm(ctx, instance.ID)
859+
if err != nil {
860+
return diag.Errorf("failed to enable TPM: %s", err)
861+
}
862+
if _, err = client.Wait(ctx, op, v3.OperationStateSuccess); err != nil {
863+
return diag.Errorf("failed to enable TPM: %s", err)
864+
}
865+
} else {
866+
return diag.Errorf("TPM can't be disabled")
867+
}
868+
}
869+
833870
if d.Get(AttrState) == "running" {
834871
op, err := client.StartInstance(ctx, instance.ID, v3.StartInstanceRequest{})
835872
if err != nil {
@@ -1079,6 +1116,18 @@ func rApply( //nolint:gocyclo
10791116
}
10801117
}
10811118

1119+
if instance.SecurebootEnabled != nil {
1120+
if err := d.Set(AttrEnableSecureBoot, instance.SecurebootEnabled); err != nil {
1121+
return diag.FromErr(err)
1122+
}
1123+
}
1124+
1125+
if instance.TpmEnabled != nil {
1126+
if err := d.Set(AttrEnableTPM, instance.TpmEnabled); err != nil {
1127+
return diag.FromErr(err)
1128+
}
1129+
}
1130+
10821131
rdns, err := clientV3.GetReverseDNSInstance(ctx, instance.ID)
10831132
if err != nil && !errors.Is(err, v3.ErrNotFound) {
10841133
return diag.Errorf("unable to retrieve instance reverse-dns: %s", err)

pkg/resources/instance/resource_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ resource "exoscale_compute_instance" "test" {
8383
disk_size = %d
8484
template_id = data.exoscale_template.ubuntu.id
8585
ipv6 = true
86+
enable_tpm = false
87+
enable_secure_boot = true
8688
anti_affinity_group_ids = [exoscale_anti_affinity_group.test.id]
8789
security_group_ids = [
8890
data.exoscale_security_group.default.id,
@@ -165,6 +167,8 @@ resource "exoscale_compute_instance" "test" {
165167
disk_size = %d
166168
template_id = data.exoscale_template.ubuntu.id
167169
ipv6 = true
170+
enable_tpm = true
171+
enable_secure_boot = true
168172
anti_affinity_group_ids = [exoscale_anti_affinity_group.test.id]
169173
security_group_ids = [data.exoscale_security_group.default.id]
170174
elastic_ip_ids = []
@@ -516,6 +520,8 @@ func testResource(t *testing.T) {
516520
instance.AttrElasticIPIDs + ".#": testutils.ValidateString("1"),
517521
instance.AttrIPv6: testutils.ValidateString("true"),
518522
instance.AttrIPv6Address: validation.ToDiagFunc(validation.IsIPv6Address),
523+
instance.AttrEnableTPM: testutils.ValidateString("false"),
524+
instance.AttrEnableSecureBoot: testutils.ValidateString("true"),
519525
instance.AttrLabels + ".test": testutils.ValidateString(rLabelValue),
520526
instance.AttrName: testutils.ValidateString(rName),
521527
instance.AttrMACAddress: validation.ToDiagFunc(validation.NoZeroValues),
@@ -570,6 +576,7 @@ func testResource(t *testing.T) {
570576
instance.AttrDiskSize: testutils.ValidateString(fmt.Sprint(rDiskSizeUpdated)),
571577
instance.AttrLabels + ".test": testutils.ValidateString(rLabelValueUpdated),
572578
instance.AttrName: testutils.ValidateString(rNameUpdated),
579+
instance.AttrEnableTPM: testutils.ValidateString("true"),
573580
instance.AttrSecurityGroupIDs + ".#": testutils.ValidateString("1"),
574581
instance.AttrState: testutils.ValidateString("stopped"),
575582
instance.AttrReverseDNS: testutils.ValidateString(rReverseDNSUpdated),

0 commit comments

Comments
 (0)