diff --git a/docs/resources/baremetal_server.md b/docs/resources/baremetal_server.md index 80a90bcbc..8c4595e7e 100644 --- a/docs/resources/baremetal_server.md +++ b/docs/resources/baremetal_server.md @@ -75,6 +75,56 @@ resource "scaleway_baremetal_server" "base" { } ``` +### With cloud-init + +```terraform +data "scaleway_iam_ssh_key" "my_ssh_key" { + name = "main" +} + +data "scaleway_baremetal_offer" "my_offer" { + zone = "fr-par-2" + name = "EM-I220E-NVME" +} + +resource "scaleway_baremetal_server" "my_server_ci" { + zone = "fr-par-2" + offer = data.scaleway_baremetal_offer.my_offer.offer_id + os = "d17d6872-0412-45d9-a198-af82c34d3c5c" + ssh_key_ids = [data.scaleway_iam_ssh_key.my_ssh_key.id] + + cloud_init = file("userdata.yaml") +} +``` + +```terraform +data "scaleway_iam_ssh_key" "my_ssh_key" { + name = "main" +} + +data "scaleway_baremetal_offer" "my_offer" { + zone = "fr-par-2" + name = "EM-I220E-NVME" +} + +resource "scaleway_baremetal_server" "my_server_ci" { + zone = "fr-par-2" + offer = data.scaleway_baremetal_offer.my_offer.offer_id + os = "d17d6872-0412-45d9-a198-af82c34d3c5c" + ssh_key_ids = [data.scaleway_iam_ssh_key.my_ssh_key.id] + + cloud_init = < /home/ubuntu/message.txt +EOF +} +``` + ### With private network ```terraform @@ -305,6 +355,7 @@ The following arguments are supported: - `ipam_ip_ids` - (Optional) List of IPAM IP IDs to assign to the server in the requested private network. - `zone` - (Defaults to [provider](../index.md#zone) `zone`) The [zone](../guides/regions_and_zones.md#zones) in which the server should be created. - `partitioning` (Optional) The partitioning schema in JSON format +- `cloud_init` - (Optional) Configuration data to pass to cloud-init such as a YAML cloud config or a user-data script. Accepts either a string containing the content or a path to a file (for example `file("cloud-init.yml")`). Max length: 127998 characters. Updates to `cloud_init` will update the server user-data via the API and do not trigger a reinstall; however, a reboot of the server is required for the OS to re-run cloud-init and apply the changes. Only supported for OSes that have cloud-init enabled. - `protected` - (Optional) Set to true to activate server protection option. - `project_id` - (Defaults to [provider](../index.md#project_id) `project_id`) The ID of the project the server is associated with. diff --git a/internal/acctest/validate_cassettes_test.go b/internal/acctest/validate_cassettes_test.go index 979cd720e..62fadc8f6 100644 --- a/internal/acctest/validate_cassettes_test.go +++ b/internal/acctest/validate_cassettes_test.go @@ -25,20 +25,21 @@ const servicesDir = "../services" func exceptionsCassettesCases() map[string]struct{} { return map[string]struct{}{ - "../services/mnq/testdata/sns-topic-basic.cassette.yaml": {}, - "../services/mnq/testdata/sns-topic-subscription-basic.cassette.yaml": {}, - "../services/mnq/testdata/sqs-already-activated.cassette.yaml": {}, - "../services/object/testdata/bucket-cors-empty-origin.cassette.yaml": {}, - "../services/object/testdata/bucket-destroy-force.cassette.yaml": {}, - "../services/rdb/testdata/data-source-privilege-basic.cassette.yaml": {}, - "../services/rdb/testdata/privilege-basic.cassette.yaml": {}, - "../services/object/testdata/object-bucket-destroy-force.cassette.yaml": {}, - "../services/secret/testdata/secret-protected.cassette.yaml": {}, - "../services/secret/testdata/secret-version-type.cassette.yaml": {}, - "../services/file/testdata/file-system-invalid-size-granularity-fails.cassette.yaml": {}, - "../services/file/testdata/file-system-size-too-small-fails.cassette.yaml": {}, - "../services/container/testdata/namespace-vpc-integration.cassette.yaml": {}, - "../services/function/testdata/function-namespace-vpc-integration.cassette.yaml": {}, + "../services/mnq/testdata/sns-topic-basic.cassette.yaml": {}, + "../services/mnq/testdata/sns-topic-subscription-basic.cassette.yaml": {}, + "../services/mnq/testdata/sqs-already-activated.cassette.yaml": {}, + "../services/object/testdata/bucket-cors-empty-origin.cassette.yaml": {}, + "../services/object/testdata/bucket-destroy-force.cassette.yaml": {}, + "../services/rdb/testdata/data-source-privilege-basic.cassette.yaml": {}, + "../services/rdb/testdata/privilege-basic.cassette.yaml": {}, + "../services/object/testdata/object-bucket-destroy-force.cassette.yaml": {}, + "../services/secret/testdata/secret-protected.cassette.yaml": {}, + "../services/secret/testdata/secret-version-type.cassette.yaml": {}, + "../services/file/testdata/file-system-invalid-size-granularity-fails.cassette.yaml": {}, + "../services/file/testdata/file-system-size-too-small-fails.cassette.yaml": {}, + "../services/container/testdata/namespace-vpc-integration.cassette.yaml": {}, + "../services/function/testdata/function-namespace-vpc-integration.cassette.yaml": {}, + "../services/baremetal/testdata/server-cloud-init-not-compatible-offer.cassette.yaml": {}, } } diff --git a/internal/services/baremetal/server.go b/internal/services/baremetal/server.go index ac572a37a..55fabf3a1 100644 --- a/internal/services/baremetal/server.go +++ b/internal/services/baremetal/server.go @@ -297,6 +297,13 @@ If this behaviour is wanted, please set 'reinstall_on_ssh_key_changes' argument }, }, }, + "cloud_init": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "Configuration data to pass to cloud-init such as a YAML cloud config data or a user-data script", + ValidateFunc: validation.StringLenBetween(0, 127998), + }, }, CustomizeDiff: customdiff.Sequence( customDiffOffer(), @@ -367,6 +374,11 @@ func ResourceServerCreate(ctx context.Context, d *schema.ResourceData, m any) di Protected: d.Get("protected").(bool), } + if cloudInit, ok := d.GetOk("cloud_init"); ok { + cloudInitStr := []byte(cloudInit.(string)) + req.UserData = &cloudInitStr + } + partitioningSchema := baremetal.Schema{} if file, ok := d.GetOk("partitioning"); ok || !d.Get("install_config_afterward").(bool) { @@ -515,6 +527,13 @@ func ResourceServerRead(ctx context.Context, d *schema.ResourceData, m any) diag _ = d.Set("ipv6", flattenIPv6s(server.IPs)) _ = d.Set("protected", server.Protected) + var cloudInit string + if server.UserData != nil { + cloudInit = string(*server.UserData) + } + + _ = d.Set("cloud_init", cloudInit) + if server.Install != nil { _ = d.Set("os", zonal.NewIDString(server.Zone, os.ID)) _ = d.Set("os_name", os.Name) @@ -696,6 +715,13 @@ func ResourceServerUpdate(ctx context.Context, d *schema.ResourceData, m any) di hasChanged := false + if d.HasChange("cloud_init") { + cloudInit, _ := d.Get("cloud_init").(string) + cloudInitStr := []byte(cloudInit) + req.UserData = &cloudInitStr + hasChanged = true + } + if d.HasChange("name") { req.Name = types.ExpandUpdatedStringPtr(d.Get("name")) hasChanged = true diff --git a/internal/services/baremetal/server_test.go b/internal/services/baremetal/server_test.go index 17699c47a..e7274421e 100644 --- a/internal/services/baremetal/server_test.go +++ b/internal/services/baremetal/server_test.go @@ -116,6 +116,179 @@ func TestAccServer_Basic(t *testing.T) { }) } +func TestAccServer_CloudInit(t *testing.T) { + tt := acctest.NewTestTools(t) + defer tt.Cleanup() + + if !IsOfferAvailable(OfferName, scw.Zone(Zone), tt) { + t.Skip("Offer is out of stock") + } + + SSHKeyName := "TestAccServer_CloudInit" + name := "TestAccServer_CloudInit" + + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: tt.ProviderFactories, + CheckDestroy: baremetalchecks.CheckServerDestroy(tt), + + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` + variable "cloud_init" { + type = string + default = < /home/ubuntu/message.txt +EOF +} +``` + ### With private network ```terraform @@ -306,6 +356,7 @@ The following arguments are supported: - `ipam_ip_ids` - (Optional) List of IPAM IP IDs to assign to the server in the requested private network. - `zone` - (Defaults to [provider](../index.md#zone) `zone`) The [zone](../guides/regions_and_zones.md#zones) in which the server should be created. - `partitioning` (Optional) The partitioning schema in JSON format +- `cloud_init` - (Optional) Configuration data to pass to cloud-init such as a YAML cloud config or a user-data script. Accepts either a string containing the content or a path to a file (for example `file("cloud-init.yml")`). Max length: 127998 characters. Updates to `cloud_init` will update the server user-data via the API and do not trigger a reinstall; however, a reboot of the server is required for the OS to re-run cloud-init and apply the changes. Only supported for OSes that have cloud-init enabled. - `protected` - (Optional) Set to true to activate server protection option. - `project_id` - (Defaults to [provider](../index.md#project_id) `project_id`) The ID of the project the server is associated with.