Skip to content

Commit

Permalink
feat: fix(AS): AS configuration support new field `instance_config.0.…
Browse files Browse the repository at this point in the history
…admin_pass` (#5122)
  • Loading branch information
deer-hang authored Jul 3, 2024
1 parent 148fdc4 commit b0a9806
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 18 deletions.
82 changes: 75 additions & 7 deletions docs/resources/as_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,61 @@ resource "huaweicloud_as_configuration" "my_as_config" {
}
```

### AS Configuration uses password authentication for Linux ECS

```hcl
variable "flavor_id" {}
variable "ecs_image_id" {}
variable "security_group_id" {}
resource "huaweicloud_as_configuration" "my_as_config" {
scaling_configuration_name = "my_as_config"
instance_config {
flavor = var.flavor_id
image = var.ecs_image_id
security_group_ids = [var.security_group_id]
user_data = <<EOT
#! /bin/bash
echo 'root:$6$V6azyeLwcD3CHlpY$BN3VVq18fmCkj66B4zdHLWevqcxlig' | chpasswd -e
EOT
disk {
size = 40
volume_type = "SSD"
disk_type = "SYS"
}
}
}
```

### AS Configuration uses password authentication for Windows ECS

```hcl
variable "flavor_id" {}
variable "windows_image_id" {}
variable "security_group_id" {}
variable "admin_pass" {}
resource "huaweicloud_as_configuration" "my_as_config" {
scaling_configuration_name = "my_as_config"
instance_config {
flavor = var.flavor_id
image = var.windows_image_id
security_group_ids = [var.security_group_id]
admin_pass = var.admin_pass
disk {
size = 40
volume_type = "SSD"
disk_type = "SYS"
}
}
}
```

### AS Configuration uses the existing instance specifications as the template

```hcl
Expand Down Expand Up @@ -185,6 +240,10 @@ The `instance_config` block supports:
<br/>If this parameter is not specified, the system automatically selects the DeH with the maximum available memory
size from the DeHs that meet specifications requirements to create the ECSs, thereby balancing load of the DeHs.

* `public_ip` - (Optional, List, ForceNew) Specifies the EIP of the ECS instance.
The [public_ip](#instance_config_public_ip_object) structure is documented below.
Changing this will create a new resource.

* `key_name` - (Optional, String, ForceNew) Specifies the name of the SSH key pair used to log in to the instance.
Changing this will create a new resource.

Expand All @@ -203,11 +262,19 @@ The `instance_config` block supports:
(1) The value ranges from `8` to `26` characters. (2) The value contains at least three of the following character
types: uppercase letters, lowercase letters, digits, and special characters `!@$%^-_=+[{}]:,./?`.

~> Fields `key_name` and `user_data` cannot be empty together.
* `admin_pass` - (Optional, String, ForceNew) Specifies the initial login password of the administrator account for
logging in to an ECS using password authentication. The Windows administrator is `Administrator`.

* `public_ip` - (Optional, List, ForceNew) Specifies the EIP of the ECS instance.
The [public_ip](#instance_config_public_ip_object) structure is documented below.
Changing this will create a new resource.
-> Password complexity requirements:
<br/>1. Consists of `8` to `26` characters.
<br/>2. Contains at least three of the following character types: uppercase letters, lowercase letters, digits, and
special characters `!@$%^-_=+[{}]:,./?`.
<br/>3. The password cannot contain the username or the username in reversed order.
<br/>4. The Windows ECS password cannot contain the username, the username in reversed order, or more than two
consecutive characters in the username.

~> Field `admin_pass` is used for Windows system password authentication, and `user_data` is used for Linux system
password authentication.

* `metadata` - (Optional, Map, ForceNew) Specifies the key/value pairs to make available from within the instance.
Changing this will create a new resource.
Expand Down Expand Up @@ -340,15 +407,16 @@ AS configurations can be imported by their `id`, e.g.
$ terraform import huaweicloud_as_configuration.test 18518c8a-9d15-416b-8add-2ee874751d18
```

Note that the imported state may not be identical to your resource definition, due to `instance_config.0.instance_id`
is missing from the API response. You can ignore changes after importing an AS configuration as below.
Note that the imported state may not be identical to your resource definition, due to `instance_config.0.instance_id`,
`instance_config.0.admin_pass`, and `instance_config.0.metadata` are missing from the API response.
You can ignore changes after importing an AS configuration as below.

```
resource "huaweicloud_as_configuration" "test" {
...
lifecycle {
ignore_changes = [ instance_config.0.instance_id ]
ignore_changes = [ instance_config.0.instance_id, instance_config.0.admin_pass, instance_config.0.metadata ]
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,16 @@ func TestAccASConfiguration_basic(t *testing.T) {
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"instance_config.0.metadata"},
},
},
})
}

func TestAccASConfiguration_spot(t *testing.T) {
func TestAccASConfiguration_spot_ecsPassword(t *testing.T) {
var (
obj interface{}
rName = acceptance.RandomAccResourceName()
Expand Down Expand Up @@ -117,9 +118,57 @@ func TestAccASConfiguration_spot(t *testing.T) {
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"instance_config.0.metadata"},
},
},
})
}

func TestAccASConfiguration_windowsPassword(t *testing.T) {
var (
obj interface{}
rName = acceptance.RandomAccResourceName()
resourceName = "huaweicloud_as_configuration.acc_as_config"
)

rc := acceptance.InitResourceCheck(
resourceName,
&obj,
getASConfigurationResourceFunc,
)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acceptance.TestAccPreCheck(t) },
ProviderFactories: acceptance.TestAccProviderFactories,
CheckDestroy: rc.CheckResourceDestroy(),
Steps: []resource.TestStep{
{
Config: testAccASConfiguration_windowsPassword(rName),
Check: resource.ComposeTestCheckFunc(
rc.CheckResourceExists(),
resource.TestCheckResourceAttr(resourceName, "scaling_configuration_name", rName),
resource.TestCheckResourceAttrPair(resourceName, "instance_config.0.image",
"data.huaweicloud_images_image.windows_test", "id"),
resource.TestCheckResourceAttrPair(resourceName, "instance_config.0.flavor",
"data.huaweicloud_compute_flavors.test", "ids.0"),
resource.TestCheckResourceAttrPair(resourceName, "instance_config.0.security_group_ids.0",
"huaweicloud_networking_secgroup.test", "id"),
resource.TestCheckResourceAttr(resourceName, "instance_config.0.key_name", ""),
resource.TestCheckResourceAttr(resourceName, "instance_config.0.user_data", ""),
resource.TestCheckResourceAttr(resourceName, "instance_config.0.admin_pass", "testTT123!"),
resource.TestCheckResourceAttr(resourceName, "instance_config.0.personality.0.path", "fbbo"),
resource.TestCheckResourceAttr(resourceName, "instance_config.0.personality.0.content", utils.Base64EncodeString("test content")),
resource.TestCheckResourceAttr(resourceName, "instance_config.0.disk.#", "1"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"instance_config.0.admin_pass"},
},
},
})
Expand Down Expand Up @@ -460,6 +509,39 @@ EOT
`, testAccASConfiguration_base(rName), rName)
}

func testAccASConfiguration_windowsPassword(rName string) string {
return fmt.Sprintf(`
%[1]s
data "huaweicloud_images_image" "windows_test" {
name = "Windows Server 2019 Datacenter 64bit English"
visibility = "public"
most_recent = true
}
resource "huaweicloud_as_configuration" "acc_as_config"{
scaling_configuration_name = "%[2]s"
instance_config {
image = data.huaweicloud_images_image.windows_test.id
flavor = data.huaweicloud_compute_flavors.test.ids[0]
security_group_ids = [huaweicloud_networking_secgroup.test.id]
admin_pass = "testTT123!"
disk {
size = 40
volume_type = "SSD"
disk_type = "SYS"
}
personality {
path = "fbbo"
content = base64encode("test content")
}
}
}
`, testAccASConfiguration_base(rName), rName)
}

func testAccASConfiguration_instance(rName string) string {
return fmt.Sprintf(`
%s
Expand Down
34 changes: 30 additions & 4 deletions huaweicloud/services/as/resource_huaweicloud_as_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,12 @@ func ResourceASConfiguration() *schema.Resource {
StateFunc: utils.HashAndHexEncode,
DiffSuppressFunc: utils.SuppressUserData,
},
"admin_pass": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Sensitive: true,
},
"metadata": {
Type: schema.TypeMap,
Optional: true,
Expand Down Expand Up @@ -365,6 +371,24 @@ func buildSecurityGroupIDsOpts(secGroups []interface{}) []configurations.Securit
return res
}

func buildMetadataOpts(configDataMap map[string]interface{}) map[string]interface{} {
metadataMap := configDataMap["metadata"].(map[string]interface{})
adminPass := configDataMap["admin_pass"].(string)
if metadataMap == nil && adminPass == "" {
return nil
}

resultMap := make(map[string]interface{})
if adminPass != "" {
resultMap["admin_pass"] = adminPass
}

for k, v := range metadataMap {
resultMap[k] = v
}
return resultMap
}

func buildInstanceConfig(configDataMap map[string]interface{}) configurations.InstanceConfigOpts {
instanceConfigOpts := configurations.InstanceConfigOpts{
InstanceID: configDataMap["instance_id"].(string),
Expand All @@ -376,7 +400,7 @@ func buildInstanceConfig(configDataMap map[string]interface{}) configurations.In
Tenancy: configDataMap["tenancy"].(string),
DedicatedHostID: configDataMap["dedicated_host_id"].(string),
UserData: []byte(configDataMap["user_data"].(string)),
Metadata: configDataMap["metadata"].(map[string]interface{}),
Metadata: buildMetadataOpts(configDataMap),
SecurityGroups: buildSecurityGroupIDsOpts(configDataMap["security_group_ids"].([]interface{})),
Personality: buildPersonalityOpts(configDataMap["personality"].([]interface{})),
Disk: buildDiskOpts(configDataMap["disk"].([]interface{})),
Expand Down Expand Up @@ -452,7 +476,7 @@ func resourceASConfigurationRead(_ context.Context, d *schema.ResourceData, meta
mErr := multierror.Append(nil,
d.Set("region", region),
d.Set("scaling_configuration_name", asConfig.Name),
d.Set("instance_config", flattenInstanceConfig(asConfig.InstanceConfig)),
d.Set("instance_config", flattenInstanceConfig(asConfig.InstanceConfig, d)),
d.Set("status", normalizeConfigurationStatus(asConfig.ScalingGroupID)),
)

Expand Down Expand Up @@ -502,7 +526,8 @@ func getASGroupsByConfiguration(asClient *golangsdk.ServiceClient, configuration
return gs, err
}

func flattenInstanceConfig(instanceConfig configurations.InstanceConfig) []map[string]interface{} {
// In order to avoid triggering force_new changes, write `admin_pass` and `metadata` in local files.
func flattenInstanceConfig(instanceConfig configurations.InstanceConfig, d *schema.ResourceData) []map[string]interface{} {
return []map[string]interface{}{
{
"charging_mode": normalizeConfigurationChargingMode(instanceConfig.MarketType),
Expand All @@ -515,7 +540,8 @@ func flattenInstanceConfig(instanceConfig configurations.InstanceConfig) []map[s
"tenancy": instanceConfig.Tenancy,
"dedicated_host_id": instanceConfig.DedicatedHostID,
"user_data": instanceConfig.UserData,
"metadata": instanceConfig.Metadata,
"admin_pass": d.Get("instance_config.0.admin_pass"),
"metadata": d.Get("instance_config.0.metadata"),
"disk": flattenInstanceDisks(instanceConfig.Disk),
"public_ip": flattenInstancePublicIP(instanceConfig.PublicIp.Eip),
"security_group_ids": flattenSecurityGroupIDs(instanceConfig.SecurityGroups),
Expand Down

0 comments on commit b0a9806

Please sign in to comment.