Skip to content

Commit 604ae96

Browse files
pborn-ionoswikkykmcbenjemaa
authored
feat: allow vmtemplate selection based on tags (#343)
* feat: allow template selection based on tags * `sourceNode + templateID` and `templateSelector` are mutually exclusive * automatically detects both `sourceNode` + `templateID` * errors out if anything but one (1) VM template with desired flags was found * revert webhook changes * introduce TemplateSource * comment on TemplateSource * add testcase for VMTemplateNotFound * add test for SourceNode, TemplateID and TemplateSelector being unset * revert GetNode() changes * remove redundant TemplateSelector check * update TemplateSelector description * introduce ErrTemplateNotFound error * add FindVMTemplateByTags test-case with nil vmTags * Update api/v1alpha1/proxmoxmachine_types_test.go Co-authored-by: Vic Kerr <[email protected]> * make the linter happy * Update proxmoxmachine_types.go * Update advanced-setups.md * Update proxmoxmachine_types.go * refaormat --------- Co-authored-by: Vic Kerr <[email protected]> Co-authored-by: Mohamed Chiheb Ben Jemaa <[email protected]>
1 parent cf26c4f commit 604ae96

20 files changed

+763
-29
lines changed

api/v1alpha1/proxmoxcluster_types_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ func defaultCluster() *ProxmoxCluster {
9292
ProxmoxMachineSpec: map[string]ProxmoxMachineSpec{
9393
"controlPlane": {
9494
VirtualMachineCloneSpec: VirtualMachineCloneSpec{
95-
SourceNode: "pve1",
95+
TemplateSource: TemplateSource{
96+
SourceNode: "pve1",
97+
},
9698
},
9799
},
98100
},

api/v1alpha1/proxmoxmachine_types.go

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,8 @@ const (
155155
TargetStorageFormatVmdk TargetFileStorageFormat = "vmdk"
156156
)
157157

158-
// VirtualMachineCloneSpec is information used to clone a virtual machine.
159-
type VirtualMachineCloneSpec struct {
158+
// TemplateSource defines the source of the template VM.
159+
type TemplateSource struct {
160160
// SourceNode is the initially selected proxmox node.
161161
// This node will be used to locate the template VM, which will
162162
// be used for cloning operations.
@@ -173,12 +173,22 @@ type VirtualMachineCloneSpec struct {
173173
// will be cloned onto the same node as SourceNode.
174174
//
175175
// +kubebuilder:validation:MinLength=1
176-
SourceNode string `json:"sourceNode"`
176+
// +optional
177+
SourceNode string `json:"sourceNode,omitempty"`
177178

178179
// TemplateID the vm_template vmid used for cloning a new VM.
179180
// +optional
180181
TemplateID *int32 `json:"templateID,omitempty"`
181182

183+
// TemplateSelector defines MatchTags for looking up VM templates.
184+
// +optional
185+
TemplateSelector *TemplateSelector `json:"templateSelector,omitempty"`
186+
}
187+
188+
// VirtualMachineCloneSpec is information used to clone a virtual machine.
189+
type VirtualMachineCloneSpec struct {
190+
TemplateSource `json:",inline"`
191+
182192
// Description for the new VM.
183193
// +optional
184194
Description *string `json:"description,omitempty"`
@@ -213,6 +223,16 @@ type VirtualMachineCloneSpec struct {
213223
Target *string `json:"target,omitempty"`
214224
}
215225

226+
// TemplateSelector defines MatchTags for looking up VM templates.
227+
type TemplateSelector struct {
228+
// Specifies all tags to look for, when looking up the VM template.
229+
// Passed tags must be an exact 1:1 match with the tags on the template you want to use.
230+
// If multiple VM templates with the same set of tags are found, provisioning will fail.
231+
//
232+
// +kubebuilder:validation:MinItems=1
233+
MatchTags []string `json:"matchTags"`
234+
}
235+
216236
// NetworkSpec defines the virtual machine's network configuration.
217237
type NetworkSpec struct {
218238
// Default is the default network device,
@@ -526,6 +546,8 @@ type ProxmoxMachine struct {
526546
metav1.TypeMeta `json:",inline"`
527547
metav1.ObjectMeta `json:"metadata,omitempty"`
528548

549+
// +kubebuilder:validation:XValidation:rule="[has(self.sourceNode), has(self.templateSelector)].exists_one(c, c)",message="must define either SourceNode with TemplateID, OR TemplateSelector"
550+
// +kubebuilder:validation:XValidation:rule="[has(self.templateID), has(self.templateSelector)].exists_one(c, c)",message="must define either SourceNode with TemplateID, OR TemplateSelector."
529551
// +kubebuilder:validation:XValidation:rule="self.full && self.format != ''",message="Must set full=true when specifying format"
530552
Spec ProxmoxMachineSpec `json:"spec,omitempty"`
531553
Status ProxmoxMachineStatus `json:"status,omitempty"`
@@ -566,6 +588,14 @@ func (r *ProxmoxMachine) GetTemplateID() int32 {
566588
return -1
567589
}
568590

591+
// GetTemplateSelectorTags get the tags, the desired vm template should have.
592+
func (r *ProxmoxMachine) GetTemplateSelectorTags() []string {
593+
if r.Spec.TemplateSelector != nil {
594+
return r.Spec.TemplateSelector.MatchTags
595+
}
596+
return nil
597+
}
598+
569599
// GetNode get the Proxmox node used to provision this machine.
570600
func (r *ProxmoxMachine) GetNode() string {
571601
return r.Spec.SourceNode

api/v1alpha1/proxmoxmachine_types_test.go

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,14 @@ func defaultMachine() *ProxmoxMachine {
3434
Namespace: metav1.NamespaceDefault,
3535
},
3636
Spec: ProxmoxMachineSpec{
37-
VirtualMachineCloneSpec: VirtualMachineCloneSpec{
38-
SourceNode: "pve1",
39-
},
4037
ProviderID: ptr.To("proxmox://abcdef"),
4138
VirtualMachineID: ptr.To[int64](100),
39+
VirtualMachineCloneSpec: VirtualMachineCloneSpec{
40+
TemplateSource: TemplateSource{
41+
SourceNode: "pve1",
42+
TemplateID: ptr.To[int32](100),
43+
},
44+
},
4245
Disks: &Storage{
4346
BootVolume: &DiskSize{
4447
Disk: "scsi0",
@@ -56,18 +59,32 @@ var _ = Describe("ProxmoxMachine Test", func() {
5659
})
5760

5861
Context("VirtualMachineCloneSpec", func() {
59-
It("Should not allow empty source node", func() {
62+
It("Should not allow specifying format if full clone is disabled", func() {
6063
dm := defaultMachine()
61-
dm.Spec.SourceNode = ""
64+
dm.Spec.Full = ptr.To(false)
6265

63-
Expect(k8sClient.Create(context.Background(), dm)).Should(MatchError(ContainSubstring("should be at least 1 chars long")))
66+
Expect(k8sClient.Create(context.Background(), dm)).Should(MatchError(ContainSubstring("Must set full=true when specifying format")))
6467
})
6568

66-
It("Should not allow specifying format if full clone is disabled", func() {
69+
It("Should disallow absence of SourceNode, TemplateID and TemplateSelector", func() {
6770
dm := defaultMachine()
68-
dm.Spec.Full = ptr.To(false)
71+
dm.Spec.TemplateSource.SourceNode = ""
72+
dm.Spec.TemplateSource.TemplateID = nil
73+
dm.Spec.TemplateSelector = nil
74+
Expect(k8sClient.Create(context.Background(), dm)).Should(MatchError(ContainSubstring("must define either SourceNode with TemplateID, OR TemplateSelector")))
75+
})
6976

70-
Expect(k8sClient.Create(context.Background(), dm)).Should(MatchError(ContainSubstring("Must set full=true when specifying format")))
77+
It("Should not allow specifying TemplateSelector together with SourceNode and/or TemplateID", func() {
78+
dm := defaultMachine()
79+
dm.Spec.TemplateSelector = &TemplateSelector{MatchTags: []string{"test"}}
80+
Expect(k8sClient.Create(context.Background(), dm)).Should(MatchError(ContainSubstring("must define either SourceNode with TemplateID, OR TemplateSelector")))
81+
})
82+
83+
It("Should not allow specifying TemplateSelector with empty MatchTags", func() {
84+
dm := defaultMachine()
85+
dm.Spec.TemplateSelector = &TemplateSelector{MatchTags: []string{}}
86+
87+
Expect(k8sClient.Create(context.Background(), dm)).Should(MatchError(ContainSubstring("should have at least 1 items")))
7188
})
7289
})
7390

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 46 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/infrastructure.cluster.x-k8s.io_proxmoxclusters.yaml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,22 @@ spec:
559559
a new VM.
560560
format: int32
561561
type: integer
562+
templateSelector:
563+
description: TemplateSelector defines MatchTags for looking
564+
up VM templates.
565+
properties:
566+
matchTags:
567+
description: |-
568+
Specifies all tags to look for, when looking up the VM template.
569+
Passed tags must be an exact 1:1 match with the tags on the template you want to use.
570+
If multiple VM templates with the same set of tags are found, provisioning will fail.
571+
items:
572+
type: string
573+
minItems: 1
574+
type: array
575+
required:
576+
- matchTags
577+
type: object
562578
virtualMachineID:
563579
description: VirtualMachineID is the Proxmox identifier
564580
for the ProxmoxMachine VM.
@@ -590,8 +606,6 @@ spec:
590606
x-kubernetes-validations:
591607
- message: end should be greater than or equal to start
592608
rule: self.end >= self.start
593-
required:
594-
- sourceNode
595609
type: object
596610
type: object
597611
x-kubernetes-validations:

config/crd/bases/infrastructure.cluster.x-k8s.io_proxmoxclustertemplates.yaml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,22 @@ spec:
600600
for cloning a new VM.
601601
format: int32
602602
type: integer
603+
templateSelector:
604+
description: TemplateSelector defines MatchTags
605+
for looking up VM templates.
606+
properties:
607+
matchTags:
608+
description: |-
609+
Specifies all tags to look for, when looking up the VM template.
610+
Passed tags must be an exact 1:1 match with the tags on the template you want to use.
611+
If multiple VM templates with the same set of tags are found, provisioning will fail.
612+
items:
613+
type: string
614+
minItems: 1
615+
type: array
616+
required:
617+
- matchTags
618+
type: object
603619
virtualMachineID:
604620
description: VirtualMachineID is the Proxmox identifier
605621
for the ProxmoxMachine VM.
@@ -632,8 +648,6 @@ spec:
632648
- message: end should be greater than or equal to
633649
start
634650
rule: self.end >= self.start
635-
required:
636-
- sourceNode
637651
type: object
638652
type: object
639653
x-kubernetes-validations:

config/crd/bases/infrastructure.cluster.x-k8s.io_proxmoxmachines.yaml

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,22 @@ spec:
527527
VM.
528528
format: int32
529529
type: integer
530+
templateSelector:
531+
description: TemplateSelector defines MatchTags for looking up VM
532+
templates.
533+
properties:
534+
matchTags:
535+
description: |-
536+
Specifies all tags to look for, when looking up the VM template.
537+
Passed tags must be an exact 1:1 match with the tags on the template you want to use.
538+
If multiple VM templates with the same set of tags are found, provisioning will fail.
539+
items:
540+
type: string
541+
minItems: 1
542+
type: array
543+
required:
544+
- matchTags
545+
type: object
530546
virtualMachineID:
531547
description: VirtualMachineID is the Proxmox identifier for the ProxmoxMachine
532548
VM.
@@ -557,10 +573,14 @@ spec:
557573
x-kubernetes-validations:
558574
- message: end should be greater than or equal to start
559575
rule: self.end >= self.start
560-
required:
561-
- sourceNode
562576
type: object
563577
x-kubernetes-validations:
578+
- message: must define either SourceNode with TemplateID, OR TemplateSelector
579+
rule: '[has(self.sourceNode), has(self.templateSelector)].exists_one(c,
580+
c)'
581+
- message: must define either SourceNode with TemplateID, OR TemplateSelector.
582+
rule: '[has(self.templateID), has(self.templateSelector)].exists_one(c,
583+
c)'
564584
- message: Must set full=true when specifying format
565585
rule: self.full && self.format != ''
566586
status:

config/crd/bases/infrastructure.cluster.x-k8s.io_proxmoxmachinetemplates.yaml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,22 @@ spec:
559559
a new VM.
560560
format: int32
561561
type: integer
562+
templateSelector:
563+
description: TemplateSelector defines MatchTags for looking
564+
up VM templates.
565+
properties:
566+
matchTags:
567+
description: |-
568+
Specifies all tags to look for, when looking up the VM template.
569+
Passed tags must be an exact 1:1 match with the tags on the template you want to use.
570+
If multiple VM templates with the same set of tags are found, provisioning will fail.
571+
items:
572+
type: string
573+
minItems: 1
574+
type: array
575+
required:
576+
- matchTags
577+
type: object
562578
virtualMachineID:
563579
description: VirtualMachineID is the Proxmox identifier for
564580
the ProxmoxMachine VM.
@@ -589,8 +605,6 @@ spec:
589605
x-kubernetes-validations:
590606
- message: end should be greater than or equal to start
591607
rule: self.end >= self.start
592-
required:
593-
- sourceNode
594608
type: object
595609
required:
596610
- spec

docs/advanced-setups.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,14 @@ This behaviour can be configured in the `ProxmoxCluster` CR through the field `.
176176

177177
For example, setting it to `0` (zero), entirely disables scheduling based on memory. Alternatively, if you set it to any value greater than `0`, the scheduler will treat your host as it would have `${value}%` of memory. In real numbers that would mean, if you have a host with 64GB of memory and set the number to `300`, the scheduler would allow you to provision guests with a total of 192GB memory and therefore overprovision the host. (Use with caution! It's strongly suggested to have memory ballooning configured everywhere.). Or, if you were to set it to `95` for example, it would treat your host as it would only have 60,8GB of memory, and leave the remaining 3,2GB for the host.
178178

179+
## Template lookup based on Proxmox tags
180+
181+
Our provider is able to look up templates based on their attached tags, for `ProxmoxMachine` resources, that make use of an tag selector.
182+
183+
For example, you can set the `TEMPLATE_TAGS="tag1,tag2"` environment variable. Your custom image will then be used when using the [auto-image](https://github.com/ionos-cloud/cluster-api-provider-ionoscloud/blob/main/templates/cluster-template-auto-image.yaml) template.
184+
185+
Please note: Passed tags must be an exact 1:1 match with the tags on the template you want to use. The matched result must be unique. If multiple templates are found, provisioning will fail.
186+
179187
## Proxmox RBAC with least privileges
180188

181189
For the Proxmox API user/token you create for CAPMOX, these are the minimum required permissions.

envfile.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export PROXMOX_TOKEN=""
33
export PROXMOX_SECRET=""
44
export PROXMOX_SOURCENODE="pve"
55
export TEMPLATE_VMID=100
6+
export TEMPLATE_TAGS="tag1,tag2"
67
export VM_SSH_KEYS="ssh-ed25519 ..., ssh-ed25519 ..."
78
export KUBERNETES_VERSION="1.25.1"
89
export CONTROL_PLANE_ENDPOINT_IP=10.10.10.4

internal/service/vmservice/helpers_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,10 @@ func setupReconcilerTest(t *testing.T) (*scope.MachineScope, *proxmoxtest.MockCl
109109
},
110110
Spec: infrav1alpha1.ProxmoxMachineSpec{
111111
VirtualMachineCloneSpec: infrav1alpha1.VirtualMachineCloneSpec{
112-
SourceNode: "node1",
113-
TemplateID: ptr.To[int32](123),
112+
TemplateSource: infrav1alpha1.TemplateSource{
113+
SourceNode: "node1",
114+
TemplateID: ptr.To[int32](123),
115+
},
114116
},
115117
},
116118
}

0 commit comments

Comments
 (0)