Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add import for cloudavenue_vm #320

Merged
merged 5 commits into from
Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .changelog/320.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
```release-note:breaking-change
`cloudavenue_vm` - The default value for attribute `deploy_os.accept_all_eulas` has been removed.
```

```release-note:note
`cloudavenue_vm` - The attributes `settings.customization.force`, `settings.customization.change_sid`, `settings.customization.allow_local_admin_password`, `settings.customization.must_change_password_on_first_login`, `settings.customization.join_domain` and `settings.customization.join_org_domain` have now a default value of `false`.
```

```release-note:feature
`cloudavenue_vm` - Add import of VM.
```

```release-note:bug
`cloudavenue_vm` - Fix bugs in `settings.customization` and fix the ability to perform actions on multiple VMs simultaneously.
```
24 changes: 17 additions & 7 deletions docs/resources/vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ See below for [Advanced VM Examples](#advanced--examples).

Optional:

- `accept_all_eulas` (Boolean) Automatically accept EULA if OVA has it. Value defaults to `true`.
- `accept_all_eulas` (Boolean) Automatically accept EULA if OVA has it.
- `boot_image_id` (String) (ForceNew) The ID of the boot image to use for the VM. Ensure that if an attribute is set, these are not set: "[<.vapp_template_id]".
- `vapp_template_id` (String) (ForceNew) The ID of the vApp template to use for the VM. Ensure that if an attribute is set, these are not set: "[<.boot_image_id]".
- `vm_name_in_template` (String) (ForceNew) The name of the VM in the vApp template. Ensure that if an attribute is set, these are not set: "[<.boot_image_id]".
Expand Down Expand Up @@ -211,20 +211,20 @@ Optional:
Optional:

- `admin_password` (String, Sensitive) The admin password for the VM. Ensure that one and only one attribute from this collection is set : `admin_password`, `auto_generate_password`.
- `allow_local_admin_password` (Boolean) Whether to allow the local admin password to be changed.
- `allow_local_admin_password` (Boolean) Whether to allow the local admin password to be changed. Value defaults to `false`.
- `auto_generate_password` (Boolean) Whether to auto-generate the password. Ensure that one and only one attribute from this collection is set : `admin_password`, `auto_generate_password`.
- `change_sid` (Boolean) Whether to change the SID of the VM. Applicable only for Windows VMs.
- `change_sid` (Boolean) Whether to change the SID of the VM. Applicable only for Windows VMs. Value defaults to `false`.
- `enabled` (Boolean) Whether guest customization is enabled or not. Value defaults to `false`.
- `force` (Boolean) `true` value will cause the VM to reboot on every `apply` operation.
- `force` (Boolean) `true` value will cause the VM to reboot on every `apply` operation. Value defaults to `false`.
- `hostname` (String) Computer name to assign to this virtual machine. Default is the value of attribute `name`. Must be between 1 and 80 characters long and can contain only letters, numbers and hyphen. It must not contain only digits.
- `init_script` (String) The init script to run.
- `join_domain` (Boolean) Enable this VM to join a domain.
- `join_domain` (Boolean) Enable this VM to join a domain. Value defaults to `false`.
- `join_domain_account_ou` (String) The domain account OU to join.
- `join_domain_name` (String) The domain name to join.
- `join_domain_password` (String, Sensitive) The domain password to join.
- `join_domain_user` (String) The domain user to join.
- `join_org_domain` (Boolean) Use organization's domain for joining.
- `must_change_password_on_first_login` (Boolean) Whether the password must be changed on first login.
- `join_org_domain` (Boolean) Use organization's domain for joining. Value defaults to `false`.
- `must_change_password_on_first_login` (Boolean) Whether the password must be changed on first login. Value defaults to `false`.
- `number_of_auto_logons` (Number) The number of times the VM should auto-login. Value must be at least 0.


Expand All @@ -240,7 +240,17 @@ Read-Only:

- `status` (String) The status of the VM.

## Import

Import is supported using the following syntax:
```shell
# If `vDC` is not specified, the default `vDC` will be used
# The `myVMID` is the ID of the VM.
terraform import cloudavenue_vm.example myVAPP.myVMID

# or you can specify the vDC
terraform import cloudavenue_vm.example myVDC.myVAPP.myVMID
```

<a id="advanced--examples"></a>
## Advanced Examples
Expand Down
6 changes: 6 additions & 0 deletions examples/resources/cloudavenue_vm/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# If `vDC` is not specified, the default `vDC` will be used
# The `myVMID` is the ID of the VM.
terraform import cloudavenue_vm.example myVAPP.myVMID

# or you can specify the vDC
terraform import cloudavenue_vm.example myVDC.myVAPP.myVMID
15 changes: 8 additions & 7 deletions internal/provider/common/vm/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,19 +84,20 @@ func (v VM) SettingsRead(ctx context.Context, stateCustomization any) (settings
return nil, fmt.Errorf("unable to read affinity rule ID: %w", err)
}

var (
customization types.Object
ok bool
)
customization, err := v.CustomizationRead(ctx)
if err != nil {
return nil, fmt.Errorf("unable to read customization: %w", err)
}

switch custo := stateCustomization.(type) {
case *VMResourceModelSettingsCustomization:
customization = custo.ToPlan(ctx)
customization.Force = custo.Force
case attr.Value:
customization, ok = custo.(types.Object)
x, ok := custo.(types.Object)
if !ok {
return nil, fmt.Errorf("unable to convert state customization to basetypes.ObjectType")
}
customization.Force = x.Attributes()["force"].(types.Bool)
}

return &VMResourceModelSettings{
Expand All @@ -105,6 +106,6 @@ func (v VM) SettingsRead(ctx context.Context, stateCustomization any) (settings
StorageProfile: utils.StringValueOrNull(v.GetStorageProfileName()),
GuestProperties: guestProperties.ToPlan(ctx),
AffinityRuleID: utils.StringValueOrNull(affinityRuleID),
Customization: customization,
Customization: customization.ToPlan(ctx),
}, nil
}
29 changes: 28 additions & 1 deletion internal/provider/common/vm/settings_customization.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,39 @@ func (s *VMResourceModelSettingsCustomization) toAttrValues() map[string]attr.Va
// ToPlan returns the value of the SettingsCustomization attribute, if set, as a types.Object.
func (s *VMResourceModelSettingsCustomization) ToPlan(_ context.Context) types.Object {
if s == nil {
return types.Object{}
return types.ObjectNull(s.AttrTypes())
}

return types.ObjectValueMust(s.AttrTypes(), s.toAttrValues())
}

// CustomizationRead reads the customization fields from a VM.
func (v *VM) CustomizationRead(ctx context.Context) (*VMResourceModelSettingsCustomization, error) {
customization, err := v.GetCustomization()
if err != nil {
return nil, err
}

return &VMResourceModelSettingsCustomization{
Force: types.BoolValue(false),
Enabled: types.BoolValue(*customization.Enabled),
ChangeSID: types.BoolValue(*customization.ChangeSid),
AllowLocalAdminPassword: types.BoolValue(*customization.AdminPasswordEnabled),
MustChangePasswordOnFirstLogin: types.BoolValue(*customization.ResetPasswordRequired),
AdminPassword: utils.StringValueOrNull(customization.AdminPassword),
AutoGeneratePassword: types.BoolValue(*customization.AdminPasswordAuto),
NumberOfAutoLogons: types.Int64Value(int64(customization.AdminAutoLogonCount)),
JoinDomain: types.BoolValue(*customization.JoinDomainEnabled),
JoinOrgDomain: types.BoolValue(*customization.UseOrgSettings),
JoinDomainName: utils.StringValueOrNull(customization.DomainName),
JoinDomainUser: utils.StringValueOrNull(customization.DomainUserName),
JoinDomainPassword: utils.StringValueOrNull(customization.DomainUserPassword),
JoinDomainAccountOU: utils.StringValueOrNull(customization.MachineObjectOU),
InitScript: utils.StringValueOrNull(customization.CustomizationScript),
Hostname: utils.StringValueOrNull(customization.ComputerName),
}, nil
}

// GetCustomizationSection returns the value of the SettingsCustomization attribute, if set, as a *types.CustomizationSection.
func (s *VMResourceModelSettingsCustomization) GetCustomizationSection(vmName string) *govcdtypes.GuestCustomizationSection {
if s.Hostname.ValueString() != "" {
Expand Down
4 changes: 4 additions & 0 deletions internal/provider/common/vm/settings_guest_properties.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ func (v VM) GuestPropertiesRead() (guestProperties *VMResourceModelSettingsGuest

guestProperties = &VMResourceModelSettingsGuestProperties{}

if guest.ProductSection == nil {
return
}

for _, guestProperty := range guest.ProductSection.Property {
if guestProperty.Value != nil {
(*guestProperties)[guestProperty.Key] = guestProperty.Value.Value
Expand Down
83 changes: 78 additions & 5 deletions internal/provider/vm/vm_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,13 @@ func (r *vmResource) Create(ctx context.Context, req resource.CreateRequest, res
return
}

resp.Diagnostics.Append(r.vapp.LockVAPP(ctx)...)
if resp.Diagnostics.HasError() {
return
}

defer r.vapp.UnlockVAPP(ctx)

// * Create VM with Template
if !deployOS.VappTemplateID.IsNull() {
vmCreated, d = r.createVMWithTemplate(ctx, *plan)
Expand Down Expand Up @@ -268,7 +275,7 @@ func (r *vmResource) Read(ctx context.Context, req resource.ReadRequest, resp *r
return
}

plan, d := r.read(ctx, state)
plan, d := r.read(ctx, state, state)
resp.Diagnostics.Append(d...)
if resp.Diagnostics.HasError() {
return
Expand Down Expand Up @@ -296,6 +303,13 @@ func (r *vmResource) Update(ctx context.Context, req resource.UpdateRequest, res
return
}

resp.Diagnostics.Append(r.vapp.LockVAPP(ctx)...)
if resp.Diagnostics.HasError() {
return
}

defer r.vapp.UnlockVAPP(ctx)

/*
Implement the resource update here
*/
Expand All @@ -311,6 +325,13 @@ func (r *vmResource) Update(ctx context.Context, req resource.UpdateRequest, res
return
}

resp.Diagnostics.Append(r.vm.LockVM(ctx)...)
if resp.Diagnostics.HasError() {
return
}

defer r.vm.UnlockVM(ctx)

/*
Update VM was 2 major steps:
1. Hot update (VM must be powered on)
Expand Down Expand Up @@ -476,6 +497,22 @@ func (r *vmResource) Update(ctx context.Context, req resource.UpdateRequest, res
}
}

// * Customization
if !allStructsPlan.Settings.Customization.Equal(allStructsState.Settings.Customization) {
// Detected change on customization
customizationFromPlan, d := allStructsPlan.Settings.CustomizationFromPlan(ctx)
resp.Diagnostics.Append(d...)
if resp.Diagnostics.HasError() {
return
}

customization := customizationFromPlan.GetCustomizationSection(plan.Name.ValueString())
if err := r.vm.SetCustomization(customization); err != nil {
resp.Diagnostics.AddError("Error updating customization", err.Error())
return
}
}

// ! Hot Update

// ! Cold Update
Expand Down Expand Up @@ -663,7 +700,7 @@ func (r *vmResource) Update(ctx context.Context, req resource.UpdateRequest, res
}
}

newPlan, d := r.read(ctx, state)
newPlan, d := r.read(ctx, state, plan)
resp.Diagnostics.Append(d...)
if resp.Diagnostics.HasError() {
return
Expand Down Expand Up @@ -693,6 +730,13 @@ func (r *vmResource) Delete(ctx context.Context, req resource.DeleteRequest, res
Implement the resource deletion here
*/

resp.Diagnostics.Append(r.vapp.LockVAPP(ctx)...)
if resp.Diagnostics.HasError() {
return
}

defer r.vapp.UnlockVAPP(ctx)

var d diag.Diagnostics

r.vm, d = vm.Init(r.client, r.vapp, vm.GetVMOpts{
Expand Down Expand Up @@ -744,7 +788,36 @@ func (r *vmResource) Delete(ctx context.Context, req resource.DeleteRequest, res
}

func (r *vmResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
idParts := strings.Split(req.ID, ".")

if len(idParts) != 3 && len(idParts) != 2 {
resp.Diagnostics.AddError(
"Unexpected Import Identifier",
fmt.Sprintf("Expected import identifier with format: vdc.vapp_name.vm_id or vapp_name.vm_id. Got: %q", req.ID),
)
return
}

var id string

if len(idParts) == 3 {
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("vdc"), idParts[0])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("vapp_name"), idParts[1])...)
id = idParts[2]
} else {
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("vapp_name"), idParts[0])...)
id = idParts[1]
}

// if ID not contains urn:vcloud:vm add it
if !strings.Contains(id, "urn:vcloud:vm") {
id = "urn:vcloud:vm:" + id
}

resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), "urn:vcloud:vm:"+id)...)

// dOS := vm.VMResourceModelDeployOS{}
// resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("deploy_os"), types.ObjectNull(dOS.AttrTypes()))...)
}

func (r *vmResource) createVMWithTemplate(ctx context.Context, rm vm.VMResourceModel) (vmCreated vm.VM, diags diag.Diagnostics) {
Expand Down Expand Up @@ -1157,7 +1230,7 @@ func (r *vmResource) vmPowerOn(ctx context.Context, rm vm.VMResourceModel) (diag
}

// read is a common function for VM read. It is called in Update and Read.
func (r *vmResource) read(ctx context.Context, rm *vm.VMResourceModel) (plan *vm.VMResourceModel, diags diag.Diagnostics) {
func (r *vmResource) read(ctx context.Context, rm, rmPlan *vm.VMResourceModel) (plan *vm.VMResourceModel, diags diag.Diagnostics) {
if err := r.vm.Refresh(); err != nil {
diags.AddError("Error refreshing VM", err.Error())
return
Expand Down Expand Up @@ -1186,7 +1259,7 @@ func (r *vmResource) read(ctx context.Context, rm *vm.VMResourceModel) (plan *vm
}

// ? Settings
settings, err := r.vm.SettingsRead(ctx, rm.Settings.Attributes()["customization"])
settings, err := r.vm.SettingsRead(ctx, rmPlan.Settings.Attributes()["customization"])
if err != nil {
diags.AddError(
"Unable to get VM settings",
Expand Down
Loading