Skip to content

Commit 8f95b60

Browse files
authored
feat(baremetal): migration to monthly (#3008)
* feat(baremetal): migration to monthly * fix stash * fix linter * fix test and customDiff logic * update tests * fix helper * fix linter * fix logic helper * fix logic * fix getOfferInformation * fix linter * roll back diffSuppressFunc * fix linter * update cassette * add documentation migration * update cassettes * variable name
1 parent 0c921c5 commit 8f95b60

File tree

7 files changed

+8125
-1788
lines changed

7 files changed

+8125
-1788
lines changed

docs/resources/baremetal_server.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,47 @@ resource "scaleway_baremetal_server" "base" {
226226
227227
```
228228

229+
### Migrate from hourly to monthly plan
230+
231+
To migrate from an hourly to a monthly subscription for a Scaleway Baremetal server, it is important to understand that the migration can only be done by using the data source.
232+
You cannot directly modify the subscription_period of an existing scaleway_baremetal_offer resource. Instead, you must define the monthly offer using the data source and then update the server configuration accordingly.
233+
234+
#### Hourly Plan Example
235+
236+
```terraform
237+
data "scaleway_baremetal_offer" "my_offer" {
238+
zone = "fr-par-1"
239+
name = "EM-B220E-NVME"
240+
subscription_period = "hourly"
241+
}
242+
243+
resource "scaleway_baremetal_server" "server01" {
244+
name = "UpdateSubscriptionPeriod"
245+
offer = data.scaleway_baremetal_offer.my_offer.offer_id
246+
zone = "%s"
247+
install_config_afterward = true
248+
}
249+
```
250+
251+
#### Monthly Plan Example
252+
253+
```terraform
254+
data "scaleway_baremetal_offer" "my_offer" {
255+
zone = "fr-par-1"
256+
name = "EM-B220E-NVME"
257+
subscription_period = "monthly"
258+
}
259+
260+
resource "scaleway_baremetal_server" "server01" {
261+
name = "UpdateSubscriptionPeriod"
262+
offer = data.scaleway_baremetal_offer.my_offer.offer_id
263+
zone = "fr-par-1"
264+
install_config_afterward = true
265+
}
266+
```
267+
268+
**Important** Once you migrate to a monthly subscription, you cannot downgrade back to an hourly plan. Ensure that the monthly plan meets your needs before making the switch.
269+
229270
## Argument Reference
230271

231272
The following arguments are supported:

internal/services/baremetal/helpers.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ import (
1212
"github.com/scaleway/scaleway-sdk-go/api/baremetal/v1"
1313
baremetalV3 "github.com/scaleway/scaleway-sdk-go/api/baremetal/v3"
1414
"github.com/scaleway/scaleway-sdk-go/scw"
15+
"github.com/scaleway/scaleway-sdk-go/validation"
1516
"github.com/scaleway/terraform-provider-scaleway/v2/internal/httperrors"
1617
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality"
18+
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/regional"
1719
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/zonal"
1820
"github.com/scaleway/terraform-provider-scaleway/v2/internal/meta"
1921
"github.com/scaleway/terraform-provider-scaleway/v2/internal/types"
@@ -224,3 +226,51 @@ func privateNetworkSetHash(v interface{}) int {
224226

225227
return schema.HashString(buf.String())
226228
}
229+
230+
func getOfferInformations(ctx context.Context, offer interface{}, id string, i interface{}) (*baremetal.Offer, error) {
231+
api, zone, err := NewAPIWithZoneAndID(i, id)
232+
if err != nil {
233+
return nil, err
234+
}
235+
236+
if validation.IsUUID(regional.ExpandID(offer.(string)).ID) {
237+
offerID := regional.ExpandID(offer.(string))
238+
239+
return FindOfferByID(ctx, api, zone.Zone, offerID.ID)
240+
} else {
241+
return api.GetOfferByName(&baremetal.GetOfferByNameRequest{
242+
OfferName: offer.(string),
243+
Zone: zone.Zone,
244+
})
245+
}
246+
}
247+
248+
func customDiffOffer() func(ctx context.Context, diff *schema.ResourceDiff, i interface{}) error {
249+
return func(ctx context.Context, diff *schema.ResourceDiff, i interface{}) error {
250+
if diff.Get("offer") == "" || !diff.HasChange("offer") || diff.Id() == "" {
251+
return nil
252+
}
253+
254+
oldOffer, newOffer := diff.GetChange("offer")
255+
256+
oldOfferInfo, err := getOfferInformations(ctx, oldOffer, diff.Id(), i)
257+
if err != nil {
258+
return errors.New(err.Error())
259+
}
260+
261+
newOfferInfo, err := getOfferInformations(ctx, newOffer, diff.Id(), i)
262+
if err != nil {
263+
return errors.New(err.Error())
264+
}
265+
266+
if oldOfferInfo.Name != newOfferInfo.Name {
267+
return diff.ForceNew("offer")
268+
}
269+
270+
if oldOfferInfo.SubscriptionPeriod == baremetal.OfferSubscriptionPeriodMonthly && newOfferInfo.SubscriptionPeriod == baremetal.OfferSubscriptionPeriodHourly {
271+
return errors.New("invalid plan transition: you cannot transition from a monthly plan to an hourly plan. Only the reverse (hourly to monthly) is supported. Please update your configuration accordingly")
272+
}
273+
274+
return nil
275+
}
276+
}

internal/services/baremetal/server.go

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/scaleway/terraform-provider-scaleway/v2/internal/dsf"
1919
"github.com/scaleway/terraform-provider-scaleway/v2/internal/httperrors"
2020
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality"
21+
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/regional"
2122
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/zonal"
2223
"github.com/scaleway/terraform-provider-scaleway/v2/internal/services/account"
2324
"github.com/scaleway/terraform-provider-scaleway/v2/internal/types"
@@ -54,10 +55,10 @@ func ResourceServer() *schema.Resource {
5455
Description: "Hostname of the server",
5556
},
5657
"offer": {
57-
Type: schema.TypeString,
58-
Required: true,
59-
ForceNew: true,
60-
Description: "ID or name of the server offer",
58+
Type: schema.TypeString,
59+
Required: true,
60+
Description: "ID or name of the server offer",
61+
ValidateDiagFunc: verify.IsUUIDOrNameOffer(),
6162
DiffSuppressFunc: func(_, oldValue, newValue string, d *schema.ResourceData) bool {
6263
// remove the locality from the IDs when checking diff
6364
if locality.ExpandID(newValue) == locality.ExpandID(oldValue) {
@@ -68,7 +69,6 @@ func ResourceServer() *schema.Resource {
6869

6970
return ok && newValue == offerName
7071
},
71-
ValidateDiagFunc: verify.IsUUIDOrNameOffer(),
7272
},
7373
"offer_id": {
7474
Type: schema.TypeString,
@@ -264,8 +264,8 @@ If this behaviour is wanted, please set 'reinstall_on_ssh_key_changes' argument
264264
Description: "The partitioning schema in json format",
265265
},
266266
},
267-
268267
CustomizeDiff: customdiff.Sequence(
268+
customDiffOffer(),
269269
cdf.LocalityCheck("private_network.#.id"),
270270
customDiffPrivateNetworkOption(),
271271
),
@@ -529,6 +529,26 @@ func ResourceServerUpdate(ctx context.Context, d *schema.ResourceData, m interfa
529529
var serverGetOptionIDs []*baremetal.ServerOption
530530
serverGetOptionIDs = append(serverGetOptionIDs, server.Options...)
531531

532+
if d.HasChange("offer") {
533+
ServerID := regional.ExpandID(server.ID)
534+
535+
_, err = api.MigrateServerToMonthlyOffer(&baremetal.MigrateServerToMonthlyOfferRequest{
536+
Zone: zonedID.Zone,
537+
ServerID: ServerID.ID,
538+
}, scw.WithContext(ctx))
539+
if err != nil {
540+
return diag.FromErr(err)
541+
}
542+
543+
_, err := api.WaitForServer(&baremetal.WaitForServerRequest{
544+
Zone: zonedID.Zone,
545+
ServerID: ServerID.ID,
546+
})
547+
if err != nil {
548+
return diag.FromErr(err)
549+
}
550+
}
551+
532552
if d.HasChange("options") {
533553
options, err := expandOptions(d.Get("options"))
534554
if err != nil {

internal/services/baremetal/server_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,6 +1039,100 @@ func TestAccServer_WithIPAMPrivateNetwork(t *testing.T) {
10391039
})
10401040
}
10411041

1042+
func TestAccServer_UpdateSubscriptionPeriod(t *testing.T) {
1043+
tt := acctest.NewTestTools(t)
1044+
defer tt.Cleanup()
1045+
1046+
newOffer := "EM-B320E-NVME"
1047+
1048+
if !IsOfferAvailable(OfferID, Zone, tt) {
1049+
t.Skip("Offer is out of stock")
1050+
}
1051+
1052+
resource.ParallelTest(t, resource.TestCase{
1053+
PreCheck: func() { acctest.PreCheck(t) },
1054+
ProviderFactories: tt.ProviderFactories,
1055+
CheckDestroy: resource.ComposeTestCheckFunc(
1056+
baremetalchecks.CheckServerDestroy(tt),
1057+
),
1058+
Steps: []resource.TestStep{
1059+
{
1060+
Config: fmt.Sprintf(`
1061+
data "scaleway_baremetal_offer" "my_offer" {
1062+
zone = "%s"
1063+
name = "%s"
1064+
subscription_period = "hourly"
1065+
}
1066+
1067+
resource "scaleway_baremetal_server" "server01" {
1068+
name = "TestAccServer_UpdateSubscriptionPeriod"
1069+
offer = data.scaleway_baremetal_offer.my_offer.offer_id
1070+
zone = "%s"
1071+
install_config_afterward = true
1072+
}`, Zone, OfferName, Zone),
1073+
Check: resource.ComposeTestCheckFunc(
1074+
resource.TestCheckResourceAttr("scaleway_baremetal_server.server01", "zone", Zone),
1075+
resource.TestCheckResourceAttrPair("scaleway_baremetal_server.server01", "offer_id", "data.scaleway_baremetal_offer.my_offer", "offer_id"),
1076+
),
1077+
},
1078+
{
1079+
Config: fmt.Sprintf(`
1080+
data "scaleway_baremetal_offer" "my_offer" {
1081+
zone = "%s"
1082+
name = "%s"
1083+
subscription_period = "monthly"
1084+
}
1085+
1086+
resource "scaleway_baremetal_server" "server01" {
1087+
name = "TestAccServer_UpdateSubscriptionPeriod"
1088+
offer = data.scaleway_baremetal_offer.my_offer.offer_id
1089+
zone = "%s"
1090+
install_config_afterward = true
1091+
}`, Zone, OfferName, Zone),
1092+
Check: resource.ComposeTestCheckFunc(
1093+
resource.TestCheckResourceAttr("scaleway_baremetal_server.server01", "zone", Zone),
1094+
resource.TestCheckResourceAttrPair("scaleway_baremetal_server.server01", "offer_id", "data.scaleway_baremetal_offer.my_offer", "offer_id"),
1095+
),
1096+
},
1097+
{
1098+
Config: fmt.Sprintf(`
1099+
data "scaleway_baremetal_offer" "my_offer" {
1100+
zone = "%s"
1101+
name = "%s"
1102+
subscription_period = "hourly"
1103+
}
1104+
1105+
resource "scaleway_baremetal_server" "server01" {
1106+
name = "TestAccServer_UpdateSubscriptionPeriod"
1107+
offer = data.scaleway_baremetal_offer.my_offer.offer_id
1108+
zone = "%s"
1109+
install_config_afterward = true
1110+
}`, Zone, OfferName, Zone),
1111+
ExpectError: regexp.MustCompile(`invalid plan transition: you cannot transition from a monthly plan to an hourly plan. Only the reverse \(hourly to monthly\) is supported. Please update your configuration accordingly`),
1112+
},
1113+
{
1114+
Config: fmt.Sprintf(`
1115+
data "scaleway_baremetal_offer" "my_offer" {
1116+
zone = "%s"
1117+
name = "%s"
1118+
subscription_period = "hourly"
1119+
}
1120+
1121+
resource "scaleway_baremetal_server" "server01" {
1122+
name = "Test_UpdateSubscriptionPeriod"
1123+
offer = data.scaleway_baremetal_offer.my_offer.offer_id
1124+
zone = "%s"
1125+
install_config_afterward = true
1126+
}`, Zone, newOffer, Zone),
1127+
Check: resource.ComposeTestCheckFunc(
1128+
resource.TestCheckResourceAttr("scaleway_baremetal_server.server01", "zone", "fr-par-1"),
1129+
resource.TestCheckResourceAttrPair("scaleway_baremetal_server.server01", "offer_id", "data.scaleway_baremetal_offer.my_offer", "offer_id"),
1130+
),
1131+
},
1132+
},
1133+
})
1134+
}
1135+
10421136
func testAccCheckBaremetalServerExists(tt *acctest.TestTools, n string) resource.TestCheckFunc {
10431137
return func(s *terraform.State) error {
10441138
rs, ok := s.RootModule().Resources[n]

0 commit comments

Comments
 (0)