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:
parent
b779317dba
commit
9eb497fa95
@ -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)
|
||||
|
@ -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},
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user