0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-06-30 10:33:46 +00:00

stopped at clone

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
Pavel Boldyrev 2024-06-20 21:18:52 -04:00
parent b779317dba
commit 9eb497fa95
No known key found for this signature in database
GPG Key ID: 637146A2A6804C59
8 changed files with 231 additions and 76 deletions

View File

@ -123,14 +123,19 @@ func (e *Environment) AddTemplateVars(vars map[string]any) {
}
}
// RandomVMID returns a random VM ID.
func (e *Environment) RandomVMID() int {
return gofakeit.IntRange(100_000, 999_999)
}
// RenderConfig renders the given configuration with for the current test environment using template engine.
func (e *Environment) RenderConfig(cfg string) string {
tmpl, err := template.New("config").Parse("{{.ProviderConfig}}" + cfg)
require.NoError(e.t, err)
e.templateVars["RandomVMID"] = gofakeit.IntRange(100_000, 999_999)
e.templateVars["RandomVMID1"] = gofakeit.IntRange(100_000, 999_999)
e.templateVars["RandomVMID2"] = gofakeit.IntRange(100_000, 999_999)
e.templateVars["RandomVMID"] = e.RandomVMID()
e.templateVars["RandomVMID1"] = e.RandomVMID()
e.templateVars["RandomVMID2"] = e.RandomVMID()
var buf bytes.Buffer
err = tmpl.Execute(&buf, e.templateVars)

View File

@ -7,33 +7,17 @@
package cloudinit
import (
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
)
// Model represents the CPU model.
// Model represents the cloud-init model.
type Model struct {
DatastoreId types.String `tfsdk:"datastore_id"`
DatastoreID types.String `tfsdk:"datastore_id"`
Interface types.String `tfsdk:"interface"`
DNS DNSValue `tfsdk:"dns"`
DNS *ModelDNS `tfsdk:"dns"`
}
type ModelDNS struct {
Domain types.String `tfsdk:"domain"`
Servers types.List `tfsdk:"servers"`
}
func attributeTypes() map[string]attr.Type {
return map[string]attr.Type{
"datastore_id": types.StringType,
"interface": types.StringType,
"dns": types.ObjectType{}.WithAttributeTypes(attributeTypesDNS()),
}
}
func attributeTypesDNS() map[string]attr.Type {
return map[string]attr.Type{
"domain": types.StringType,
"servers": types.ListType{ElemType: types.StringType},
}
}

View File

@ -14,33 +14,28 @@ import (
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/bpg/terraform-provider-proxmox/fwprovider/attribute"
customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types"
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms"
)
// Value represents the type for CPU settings.
type Value = types.Object
type DNSValue = types.Object
// NewValue returns a new Value with the given CPU settings from the PVE API.
func NewValue(ctx context.Context, config *vms.GetResponseData, vmID int, diags *diag.Diagnostics) Value {
cloudinit := Model{}
func NewValue(ctx context.Context, config *vms.GetResponseData, vmID int, diags *diag.Diagnostics) *Model {
ci := Model{}
devices := config.CustomStorageDevices.Filter(func(device *vms.CustomStorageDevice) bool {
return device.IsCloudInitDrive(vmID)
})
if len(devices) != 1 {
return types.ObjectNull(attributeTypes())
return nil
}
for iface, device := range devices {
cloudinit.Interface = types.StringValue(iface)
cloudinit.DatastoreId = types.StringValue(device.GetDatastoreID())
ci.Interface = types.StringValue(iface)
ci.DatastoreID = types.StringValue(device.GetDatastoreID())
dns := ModelDNS{}
dns.Domain = types.StringPointerValue(config.CloudInitDNSDomain)
@ -56,41 +51,25 @@ func NewValue(ctx context.Context, config *vms.GetResponseData, vmID int, diags
}
if !reflect.DeepEqual(dns, ModelDNS{}) {
dnsObj, d := types.ObjectValueFrom(ctx, attributeTypesDNS(), dns)
diags.Append(d...)
cloudinit.DNS = dnsObj
ci.DNS = &dns
}
obj, d := types.ObjectValueFrom(ctx, attributeTypes(), cloudinit)
diags.Append(d...)
return obj
return &ci
}
return types.ObjectNull(attributeTypes())
return nil
}
func FillCreateBody(ctx context.Context, planValue Value, body *vms.CreateRequestBody, diags *diag.Diagnostics) {
var plan Model
if planValue.IsNull() || planValue.IsUnknown() {
return
}
d := planValue.As(ctx, &plan, basetypes.ObjectAsOptions{})
diags.Append(d...)
if d.HasError() {
// FillCreateBody fills the CreateRequestBody with the Cloud-Init settings from the Value.
func FillCreateBody(ctx context.Context, plan *Model, body *vms.CreateRequestBody) {
if plan == nil {
return
}
ci := vms.CustomCloudInitConfig{}
if !plan.DNS.IsUnknown() {
var dns ModelDNS
plan.DNS.As(ctx, &dns, basetypes.ObjectAsOptions{})
if plan.DNS != nil {
dns := *plan.DNS
if !dns.Domain.IsUnknown() {
ci.SearchDomain = dns.Domain.ValueStringPointer()
@ -98,6 +77,7 @@ func FillCreateBody(ctx context.Context, planValue Value, body *vms.CreateReques
if !dns.Servers.IsUnknown() {
var servers []string
dns.Servers.ElementsAs(ctx, &servers, false)
ci.Nameserver = ptr.Ptr(strings.Join(servers, " "))
@ -108,9 +88,67 @@ func FillCreateBody(ctx context.Context, planValue Value, body *vms.CreateReques
device := vms.CustomStorageDevice{
Enabled: true,
FileVolume: fmt.Sprintf("%s:cloudinit", plan.DatastoreId.ValueString()),
FileVolume: fmt.Sprintf("%s:cloudinit", plan.DatastoreID.ValueString()),
Media: ptr.Ptr("cdrom"),
}
body.AddCustomStorageDevice(plan.Interface.ValueString(), device)
}
// FillUpdateBody fills the UpdateRequestBody with the CPU settings from the Value.
func FillUpdateBody(
ctx context.Context,
plan, state *Model,
updateBody *vms.UpdateRequestBody,
isClone bool,
diags *diag.Diagnostics,
) {
if plan == nil || reflect.DeepEqual(plan, state) {
return
}
del := func(field ...string) {
updateBody.Delete = append(updateBody.Delete, field...)
}
// TODO: migrate cloud init to another datastore
if !reflect.DeepEqual(plan.DNS, state.DNS) {
if plan.DNS == nil && state.DNS != nil && !isClone {
del("searchdomain", "nameserver")
} else if plan.DNS != nil {
ci := vms.CustomCloudInitConfig{}
planDNS := plan.DNS
stateDNS := state.DNS
if !planDNS.Domain.Equal(stateDNS.Domain) {
if attribute.ShouldBeRemoved(planDNS.Domain, stateDNS.Domain, isClone) {
del("searchdomain")
} else if attribute.IsDefined(planDNS.Domain) {
ci.SearchDomain = planDNS.Domain.ValueStringPointer()
}
}
if !planDNS.Servers.Equal(stateDNS.Servers) {
if attribute.ShouldBeRemoved(planDNS.Servers, stateDNS.Servers, isClone) {
del("nameserver")
} else if attribute.IsDefined(planDNS.Servers) {
// TODO: duplicates code from FillCreateBody
var servers []string
planDNS.Servers.ElementsAs(ctx, &servers, false)
//// special case for the servers list, if we want to remove them during update
//if len(servers) == 0 {
// del("nameserver")
//} else {
ci.Nameserver = ptr.Ptr(strings.Join(servers, " "))
//}
}
}
updateBody.CloudInitConfig = &ci
}
}
}

View File

@ -9,7 +9,9 @@ package cloudinit
import (
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
customtypes "github.com/bpg/terraform-provider-proxmox/fwprovider/types"
@ -21,7 +23,6 @@ func ResourceSchema() schema.Attribute {
return schema.SingleNestedAttribute{
Description: "The cloud-init configuration.",
Optional: true,
Computed: true,
Attributes: map[string]schema.Attribute{
"datastore_id": schema.StringAttribute{
Description: "The identifier for the datastore to create the cloud-init disk in (defaults to `local-lvm`)",
@ -31,6 +32,10 @@ func ResourceSchema() schema.Attribute {
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
// TODO: add support for datastore migration
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"interface": schema.StringAttribute{
Description: "The hardware interface to connect the cloud-init image to.",
@ -45,22 +50,22 @@ func ResourceSchema() schema.Attribute {
Validators: []validator.String{
validators.CDROMInterface(),
},
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"dns": schema.SingleNestedAttribute{
Description: "The DNS configuration.",
Optional: true,
Computed: true,
Attributes: map[string]schema.Attribute{
"domain": schema.StringAttribute{
Description: "The domain name to use for the VM.",
Optional: true,
Computed: true,
},
"servers": schema.ListAttribute{
Description: "The list of DNS servers to use.",
ElementType: customtypes.IPAddrType{},
Optional: true,
Computed: true,
},
},
},

View File

@ -20,6 +20,9 @@ func TestAccResourceVM2CloudInit(t *testing.T) {
t.Parallel()
te := test.InitEnvironment(t)
te.AddTemplateVars(map[string]interface{}{
"UpdateVMID": te.RandomVMID(),
})
tests := []struct {
name string
@ -37,7 +40,129 @@ func TestAccResourceVM2CloudInit(t *testing.T) {
}
}
}`),
Check: resource.ComposeTestCheckFunc(
test.ResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"initialization.datastore_id": te.DatastoreID,
"initialization.interface": "ide2",
}),
),
}}},
{"update VM with cloud-init", []resource.TestStep{
//{
// Config: te.RenderConfig(`
// resource "proxmox_virtual_environment_vm2" "test_vm" {
// node_name = "{{.NodeName}}"
// id = {{.UpdateVMID}}
// name = "test-cloudinit"
// initialization = {
// dns = {
// domain = "example.com"
// }
// }
// }`),
// Destroy: false,
//},
//{
// Config: te.RenderConfig(`
// resource "proxmox_virtual_environment_vm2" "test_vm" {
// node_name = "{{.NodeName}}"
// id = {{.UpdateVMID}}
// name = "test-cloudinit"
// initialization = {
// dns = {
// domain = "example.com"
// servers = [
// "1.1.1.1",
// "8.8.8.8"
// ]
// }
// }
// }`),
// Destroy: false,
//},
//{
// Config: te.RenderConfig(`
// resource "proxmox_virtual_environment_vm2" "test_vm" {
// node_name = "{{.NodeName}}"
// id = {{.UpdateVMID}}
// name = "test-cloudinit"
// initialization = {
// dns = {
// domain = "another.domain.com"
// servers = [
// "8.8.8.8",
// "1.1.1.1"
// ]
// }
// }
// }`),
// Destroy: false,
//},
{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
id = {{.UpdateVMID}}
name = "test-cloudinit"
initialization = {
dns = {
servers = [
"1.1.1.1"
]
}
}
}`),
Destroy: false,
Check: resource.ComposeTestCheckFunc(
test.NoResourceAttributesSet("proxmox_virtual_environment_vm2.test_vm", []string{
"initialization.dns.domain",
}),
test.ResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
"initialization.dns.servers.#": "1",
}),
),
},
{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
id = {{.UpdateVMID}}
name = "test-cloudinit"
initialization = {
dns = {
//servers = []
}
}
}`),
Destroy: false,
Check: resource.ComposeTestCheckFunc(
test.NoResourceAttributesSet("proxmox_virtual_environment_vm2.test_vm", []string{
"initialization.dns.servers",
}),
),
},
{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
id = {{.UpdateVMID}}
name = "test-cloudinit"
initialization = {
dns = {}
}
}`),
Destroy: false,
},
{
Config: te.RenderConfig(`
resource "proxmox_virtual_environment_vm2" "test_vm" {
node_name = "{{.NodeName}}"
id = {{.UpdateVMID}}
name = "test-cloudinit"
initialization = {}
}`),
},
}},
}
for _, tt := range tests {

View File

@ -66,8 +66,6 @@ func NewValue(ctx context.Context, config *vms.GetResponseData, diags *diag.Diag
}
// FillCreateBody fills the CreateRequestBody with the CPU settings from the Value.
//
// In the 'create' context, v is the plan.
func FillCreateBody(ctx context.Context, planValue Value, body *vms.CreateRequestBody, diags *diag.Diagnostics) {
var plan Model
@ -128,8 +126,6 @@ func FillCreateBody(ctx context.Context, planValue Value, body *vms.CreateReques
}
// FillUpdateBody fills the UpdateRequestBody with the CPU settings from the Value.
//
// In the 'update' context, v is the plan and stateValue is the current state.
func FillUpdateBody(
ctx context.Context,
planValue, stateValue Value,

View File

@ -35,15 +35,16 @@ type Model struct {
ID types.Int64 `tfsdk:"id"`
Retries types.Int64 `tfsdk:"retries"`
} `tfsdk:"clone"`
CloudInit cloudinit.Value `tfsdk:"initialization"`
CPU cpu.Value `tfsdk:"cpu"`
ID types.Int64 `tfsdk:"id"`
Name types.String `tfsdk:"name"`
NodeName types.String `tfsdk:"node_name"`
StopOnDestroy types.Bool `tfsdk:"stop_on_destroy"`Tags stringset.Value `tfsdk:"tags"`
Template types.Bool `tfsdk:"template"`
Timeouts timeouts.Value `tfsdk:"timeouts"`
VGA vga.Value `tfsdk:"vga"`
CloudInit *cloudinit.Model `tfsdk:"initialization"`
CPU cpu.Value `tfsdk:"cpu"`
ID types.Int64 `tfsdk:"id"`
Name types.String `tfsdk:"name"`
NodeName types.String `tfsdk:"node_name"`
StopOnDestroy types.Bool `tfsdk:"stop_on_destroy"`
Tags stringset.Value `tfsdk:"tags"`
Template types.Bool `tfsdk:"template"`
Timeouts timeouts.Value `tfsdk:"timeouts"`
VGA vga.Value `tfsdk:"vga"`
}
// read retrieves the current state of the resource from the API and updates the state.

View File

@ -155,7 +155,7 @@ func (r *Resource) create(ctx context.Context, plan Model, diags *diag.Diagnosti
// fill out create body fields with values from other resource blocks
cdrom.FillCreateBody(ctx, plan.CDROM, createBody, diags)
cloudinit.FillCreateBody(ctx, plan.CloudInit, createBody, diags)
cloudinit.FillCreateBody(ctx, plan.CloudInit, createBody)
cpu.FillCreateBody(ctx, plan.CPU, createBody, diags)
vga.FillCreateBody(ctx, plan.VGA, createBody, diags)
@ -334,6 +334,7 @@ func (r *Resource) update(ctx context.Context, plan, state Model, isClone bool,
// fill out update body fields with values from other resource blocks
cdrom.FillUpdateBody(ctx, plan.CDROM, state.CDROM, updateBody, isClone, diags)
cloudinit.FillUpdateBody(ctx, plan.CloudInit, state.CloudInit, updateBody, isClone, diags)
cpu.FillUpdateBody(ctx, plan.CPU, state.CPU, updateBody, isClone, diags)
vga.FillUpdateBody(ctx, plan.VGA, state.VGA, updateBody, isClone, diags)