Skip to content

Commit 951c279

Browse files
authored
fix(instance): avoid ForceNew if server.Image.ID matches the image label (#2097)
1 parent 1e971d6 commit 951c279

File tree

68 files changed

+29176
-28499
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+29176
-28499
lines changed

scaleway/resource_instance_server.go

+51-4
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ func resourceScalewayInstanceServer() *schema.Resource {
4747
"image": {
4848
Type: schema.TypeString,
4949
Optional: true,
50-
ForceNew: true,
5150
Description: "The UUID or the label of the base image used by the server",
5251
DiffSuppressFunc: diffSuppressFuncLocality,
5352
ExactlyOneOf: []string{"image", "root_volume.0.volume_id"},
@@ -284,6 +283,7 @@ func resourceScalewayInstanceServer() *schema.Resource {
284283
"ip_id",
285284
),
286285
customDiffInstanceServerType,
286+
customDiffInstanceServerImage,
287287
),
288288
}
289289
}
@@ -558,8 +558,6 @@ func resourceScalewayInstanceServerRead(ctx context.Context, d *schema.ResourceD
558558
// Image could be empty in an import context.
559559
image := expandRegionalID(d.Get("image").(string))
560560
if server.Image != nil && (image.ID == "" || scwvalidation.IsUUID(image.ID)) {
561-
// TODO: If image is a label, check that server.Image.ID match the label.
562-
// It could be useful if the user edit the image with another tool.
563561
_ = d.Set("image", newZonedID(zone, server.Image.ID).String())
564562
}
565563

@@ -999,7 +997,7 @@ func resourceScalewayInstanceServerDelete(ctx context.Context, d *schema.Resourc
999997
}
1000998

1001999
_, err = waitForInstanceServer(ctx, instanceAPI, zone, id, d.Timeout(schema.TimeoutDelete))
1002-
if err != nil {
1000+
if err != nil && !is404Error(err) {
10031001
return diag.FromErr(err)
10041002
}
10051003

@@ -1097,6 +1095,55 @@ func customDiffInstanceServerType(_ context.Context, diff *schema.ResourceDiff,
10971095
return nil
10981096
}
10991097

1098+
func customDiffInstanceServerImage(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) error {
1099+
if diff.Get("image") == "" || !diff.HasChange("image") || diff.Id() == "" {
1100+
return nil
1101+
}
1102+
1103+
// We get the server to fetch the UUID of the image
1104+
instanceAPI, zone, id, err := instanceAPIWithZoneAndID(meta, diff.Id())
1105+
if err != nil {
1106+
return err
1107+
}
1108+
server, err := instanceAPI.GetServer(&instance.GetServerRequest{
1109+
Zone: zone,
1110+
ServerID: id,
1111+
}, scw.WithContext(ctx))
1112+
if err != nil {
1113+
return err
1114+
}
1115+
1116+
// If 'image' field is defined by the user and server.Image is empty, we should create a new server
1117+
if server.Server.Image == nil {
1118+
return diff.ForceNew("image")
1119+
}
1120+
1121+
// We get the image as it is defined by the user
1122+
image := expandRegionalID(diff.Get("image").(string))
1123+
if scwvalidation.IsUUID(image.ID) {
1124+
if image.ID == expandZonedID(server.Server.Image.ID).ID {
1125+
return nil
1126+
}
1127+
}
1128+
1129+
// If image is a label, we check that server.Image.ID matches the label in case the user has edited
1130+
// the image with another tool.
1131+
marketplaceAPI := marketplace.NewAPI(meta.(*Meta).scwClient)
1132+
if err != nil {
1133+
return err
1134+
}
1135+
marketplaceImage, err := marketplaceAPI.GetLocalImage(&marketplace.GetLocalImageRequest{
1136+
LocalImageID: server.Server.Image.ID,
1137+
}, scw.WithContext(ctx))
1138+
if err != nil {
1139+
return err
1140+
}
1141+
if marketplaceImage.Label != image.ID {
1142+
return diff.ForceNew("image")
1143+
}
1144+
return nil
1145+
}
1146+
11001147
func resourceScalewayInstanceServerMigrate(ctx context.Context, d *schema.ResourceData, instanceAPI *instance.API, zone scw.Zone, id string) error {
11011148
server, err := waitForInstanceServer(ctx, instanceAPI, zone, id, d.Timeout(schema.TimeoutUpdate))
11021149
if err != nil {

scaleway/resource_instance_server_test.go

+130
Original file line numberDiff line numberDiff line change
@@ -1404,3 +1404,133 @@ func TestAccScalewayInstanceServer_MigrateInvalidLocalVolumeSize(t *testing.T) {
14041404
},
14051405
})
14061406
}
1407+
1408+
func TestAccScalewayInstanceServer_CustomDiffImage(t *testing.T) {
1409+
tt := NewTestTools(t)
1410+
defer tt.Cleanup()
1411+
resource.ParallelTest(t, resource.TestCase{
1412+
PreCheck: func() { testAccPreCheck(t) },
1413+
ProviderFactories: tt.ProviderFactories,
1414+
CheckDestroy: testAccCheckScalewayInstanceServerDestroy(tt),
1415+
Steps: []resource.TestStep{
1416+
{
1417+
Config: `
1418+
resource "scaleway_instance_server" "main" {
1419+
image = "ubuntu_jammy"
1420+
type = "DEV1-S"
1421+
state = "stopped"
1422+
}
1423+
`,
1424+
Check: resource.ComposeTestCheckFunc(
1425+
testAccCheckScalewayInstanceServerExists(tt, "scaleway_instance_server.main"),
1426+
resource.TestCheckResourceAttr("scaleway_instance_server.main", "image", "ubuntu_jammy"),
1427+
),
1428+
},
1429+
{
1430+
Config: `
1431+
resource "scaleway_instance_server" "main" {
1432+
image = "ubuntu_jammy"
1433+
type = "DEV1-S"
1434+
state = "stopped"
1435+
}
1436+
resource "scaleway_instance_server" "copy" {
1437+
image = "ubuntu_jammy"
1438+
type = "DEV1-S"
1439+
state = "stopped"
1440+
}
1441+
`,
1442+
Check: resource.ComposeTestCheckFunc(
1443+
testAccCheckScalewayInstanceServerExists(tt, "scaleway_instance_server.main"),
1444+
testAccCheckScalewayInstanceServerExists(tt, "scaleway_instance_server.copy"),
1445+
resource.TestCheckResourceAttr("scaleway_instance_server.main", "image", "ubuntu_jammy"),
1446+
resource.TestCheckResourceAttr("scaleway_instance_server.copy", "image", "ubuntu_jammy"),
1447+
resource.TestCheckResourceAttrPair("scaleway_instance_server.main", "id", "scaleway_instance_server.copy", "id"),
1448+
),
1449+
ResourceName: "scaleway_instance_server.copy",
1450+
ImportState: true,
1451+
ImportStateIdFunc: func(state *terraform.State) (string, error) {
1452+
return state.RootModule().Resources["scaleway_instance_server.main"].Primary.ID, nil
1453+
},
1454+
ImportStatePersist: true,
1455+
},
1456+
{
1457+
Config: `
1458+
data "scaleway_marketplace_image" "jammy" {
1459+
label = "ubuntu_jammy"
1460+
}
1461+
resource "scaleway_instance_server" "main" {
1462+
image = data.scaleway_marketplace_image.jammy.id
1463+
type = "DEV1-S"
1464+
state = "stopped"
1465+
}
1466+
resource "scaleway_instance_server" "copy" {
1467+
image = "ubuntu_jammy"
1468+
type = "DEV1-S"
1469+
state = "stopped"
1470+
}
1471+
`,
1472+
Check: resource.ComposeTestCheckFunc(
1473+
testAccCheckScalewayInstanceServerExists(tt, "scaleway_instance_server.main"),
1474+
testAccCheckScalewayInstanceServerExists(tt, "scaleway_instance_server.copy"),
1475+
resource.TestCheckResourceAttrPair("scaleway_instance_server.main", "image", "data.scaleway_marketplace_image.jammy", "id"),
1476+
resource.TestCheckResourceAttrPair("scaleway_instance_server.main", "id", "scaleway_instance_server.copy", "id"),
1477+
),
1478+
},
1479+
{
1480+
RefreshState: true,
1481+
Check: resource.ComposeTestCheckFunc(
1482+
testAccCheckScalewayInstanceServerExists(tt, "scaleway_instance_server.main"),
1483+
testAccCheckScalewayInstanceServerExists(tt, "scaleway_instance_server.copy"),
1484+
resource.TestCheckResourceAttrPair("scaleway_instance_server.main", "image", "data.scaleway_marketplace_image.jammy", "id"),
1485+
resource.TestCheckResourceAttrPair("scaleway_instance_server.main", "id", "scaleway_instance_server.copy", "id"),
1486+
),
1487+
},
1488+
{
1489+
Config: `
1490+
data "scaleway_marketplace_image" "focal" {
1491+
label = "ubuntu_focal"
1492+
}
1493+
resource "scaleway_instance_server" "main" {
1494+
image = data.scaleway_marketplace_image.focal.id
1495+
type = "DEV1-S"
1496+
state = "stopped"
1497+
}
1498+
resource "scaleway_instance_server" "copy" {
1499+
image = "ubuntu_jammy"
1500+
type = "DEV1-S"
1501+
state = "stopped"
1502+
}
1503+
`,
1504+
PlanOnly: true,
1505+
ExpectNonEmptyPlan: true,
1506+
Check: resource.ComposeTestCheckFunc(
1507+
testAccCheckScalewayInstanceServerExists(tt, "scaleway_instance_server.main"),
1508+
resource.TestCheckResourceAttrPair("scaleway_instance_server.main", "image", "data.scaleway_marketplace_image.focal", "id"),
1509+
resource.TestCheckResourceAttr("scaleway_instance_server.copy", "image", "ubuntu_jammy"),
1510+
testAccCheckScalewayInstanceServerIDsAreDifferent("scaleway_instance_server.main", "scaleway_instance_server.copy"),
1511+
),
1512+
},
1513+
},
1514+
})
1515+
}
1516+
1517+
func testAccCheckScalewayInstanceServerIDsAreDifferent(nameFirst, nameSecond string) resource.TestCheckFunc {
1518+
return func(s *terraform.State) error {
1519+
rs, ok := s.RootModule().Resources[nameFirst]
1520+
if !ok {
1521+
return fmt.Errorf("resource was not found: %s", nameFirst)
1522+
}
1523+
idFirst := rs.Primary.ID
1524+
1525+
rs, ok = s.RootModule().Resources[nameSecond]
1526+
if !ok {
1527+
return fmt.Errorf("resource was not found: %s", nameSecond)
1528+
}
1529+
idSecond := rs.Primary.ID
1530+
1531+
if idFirst == idSecond {
1532+
return fmt.Errorf("IDs of both resources were equal when they should not have been (%s and %s)", nameFirst, nameSecond)
1533+
}
1534+
return nil
1535+
}
1536+
}

0 commit comments

Comments
 (0)