Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ include::modules/selecting-migration-network-for-virt-provider.adoc[leveloffset=

include::modules/creating-plan-wizard-vmware.adoc[leveloffset=+2]

include::modules/pvc-naming-vmware-specific-template.adoc[leveloffset=+3]

Comment on lines +162 to +163
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
include::modules/pvc-naming-vmware-specific-template.adoc[leveloffset=+3]

include::modules/running-migration-plan.adoc[leveloffset=+2]

include::modules/migration-plan-options-ui.adoc[leveloffset=+2]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ include::../modules/selecting-migration-network-for-virt-provider.adoc[leveloffs

include::../modules/creating-plan-wizard-vmware.adoc[leveloffset=+1]

include::../modules/pvc-naming-vmware-specific-template.adoc[leveloffset=+2]

:!vmware:

Expand Down
5 changes: 2 additions & 3 deletions documentation/modules/creating-plan-wizard-vmware.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,8 @@ Variable names cannot exceed 63 characters.
*** Click the *Virtual Machines* tab.
*** Select the desired VMs.
*** Click the {kebab} of the VM.
*** Select *Edit PVC name template*.
*** Enter the template according to the instructions.
*** Select *Edit PVC name template*. For more details, see xref:pvc-naming-vmware-specific-template_vmware[PVC naming with {vmw}-specific template variables].
*** *** Enter the template according to the instructions.
*** Click *Save*.
+
[IMPORTANT]
Expand Down Expand Up @@ -337,4 +337,3 @@ The *Plan details* page also includes five additional tabs, which are described
|Editable specification of the network and storage maps used by your plan
|Updatable specification of the hooks used by your plan, if any
|===

140 changes: 140 additions & 0 deletions documentation/modules/pvc-naming-vmware-specific-template.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Module included in the following assemblies:
//
// * documentation/doc-Migration_Toolkit_for_Virtualization/master.adoc

:_content-type: CONCEPT
[id="pvc-naming-vmware-specific-template_{context}"]
= PVC naming with {vmw}-specific template variables

The {project-first} provides additional template variables for defining custom Persistent Volume Claim (PVC) names in migration plans, enhancing the manageability of migrated VM disks. These new variables, primarily applicable to {vmw} vSphere source providers, allow for more granular control over PVC naming by incorporating details such as Microsoft Windows drive letters and VMDK filenames.

[id="pvc-name-template-variables_{context}"]
== PVC name template variables

When defining a `Plan` Custom Resource (CR) for migrations, you can use the following new variables within the `spec.preserveStaticIPs.pvcNameTemplate` field to dynamically generate PVC names:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    // PVCNameTemplate is a template for generating PVC names for VM disks.
	// Generated names must be valid DNS-1123 labels (lowercase alphanumerics, '-' allowed, max 63 chars).
	// It follows Go template syntax and has access to the following variables:
	//   - .VmName: name of the VM in the source cluster (original source name)
	//   - .TargetVmName: final VM name in the target cluster (may equal .VmName if no rename/normalization)
	//   - .PlanName: name of the migration plan
	//   - .DiskIndex: initial volume index of the disk
	//   - .WinDriveLetter: Windows drive letter (lowercase, if applicable, e.g. "c", requires guest agent)
	//   - .RootDiskIndex: index of the root disk
	//   - .Shared: true if the volume is shared by multiple VMs, false otherwise
	//   - .FileName: name of the file in the source provider (VMware only, filename includes the .vmdk suffix)
	// Note:
	//   This template can be overridden at the individual VM level.
	// Examples:
	//   "{{.TargetVmName}}-disk-{{.DiskIndex}}"
	//   "{{if eq .DiskIndex .RootDiskIndex}}root{{else}}data{{end}}-{{.DiskIndex}}"
	//   "{{if .Shared}}shared-{{end}}{{.VmName | lower}}-{{.DiskIndex}}"
	// See:
	// 	 https://github.com/kubev2v/forklift/tree/main/pkg/templateutil for template functions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have more variables, maybe we should put them all in the same list ?


* *`.WinDriveLetter`*: This variable allows you to include the Windows drive letter of a disk (for example, "`c`") in the PVC name.

** *Requirement*: The use of `.WinDriveLetter` *requires the guest agent* to be installed and running on the source Windows VM for this information to be available.

* *`.FileName`*: This variable provides the name of the disk backing file from the source provider (e.g., `vm-disk.vmdk`).

** *Applicability*: This variable is *available for {vmw} vSphere source providers only*.

** *Requirement*: The `.FileName` value is derived directly from the vSphere API and *does not require a guest agent* on the VM.

These variables are part of the `PVCNameTemplateData` structure, which also includes other fields like `VmName`, `PlanName`, `DiskIndex`, `RootDiskIndex`, and `Shared`.

*Usage in a Migration Plan*: You can use these new variables in the `pvcNameTemplate` field within your `Plan` CR. For example, you might define a template that incorporates the VM name and the disk’s filename.

This functionality extends to the volume populator flow, ensuring PVC name templates are respected during data import.

Example Plan CR snippet using new PVC template variables:

[source,yaml]
----
apiVersion: forklift.konveyor.io/v1beta1
kind: Plan
metadata:
name: <plan>
namespace: <namespace>
spec:
# ... other plan configurations ...
preserveStaticIPs:
pvcNameTemplate: "{{ .VmName }}-{{ .FileName | regexReplaceAll `\\.vmdk$` `` | toLower }}" # Example using .FileName
# pvcNameTemplate: "{{ .VmName }}-disk-{{ .WinDriveLetter }}" # Example using .WinDriveLetter
pvcNameTemplateUseGenerateName: true # Or false, with caution
targetNamespace: <target_namespace>
vms:
- id: <source_vm1_moRef_or_UUID>
# ... other VM configurations ...
----

[NOTE]
====
The example above uses `regexReplaceAll` and `toLower` functions which might be available in the Go template syntax supported by {project-short}.
====

[id="pvc-naming-important-considerations-validations_{context}"]
== Important considerations and validation

* *Guest Agent for `WinDriveLetter`*: To accurately retrieve the Windows drive letter, ensure that {vmw} Tools (which includes the `qemu-guest-agent`) is installed and running on your source Windows VMs.

* *Filename Format*: The `.FileName` variable will include the `.vmdk` extension for {vmw} vSphere disks. You can use template functions (like string manipulation or regular expressions) to modify this as needed.

* *PVC Name Restrictions*: PVC names generated by templates must adhere to the Kubernetes DNS-1123 label format. This is enforced during validation because PVC names are often reused as Kubernetes labels in other parts of the codebase. Templates that produce whitespace-only or excessively long output (e.g., over 57 characters if `pvcNameTemplateUseGenerateName` is set to `true`) will fail validation.

* *Validation and Error Reporting*: {project-short} now validates the PVC name template earlier in the process using information from the plan’s VM inventory. Any template errors are detailed in the plan conditions and logged, allowing for quicker identification and troubleshooting.

* *Uniqueness of PVC Names*: If `pvcNameTemplateUseGenerateName` is set to `false`, it is crucial to ensure that your PVC naming template generates unique names to avoid conflicts during migration.

[id="pvc-name-regex-example-template-variables_{context}"]
== Regular expression examples for PVC name templates
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shdeshpa07 - please could you have a look at these examples


The {project-short} supports GoLang template syntax for Persistent Volume Claim (PVC) name templates, which can include regular expressions to create dynamic and descriptive names. This is useful for extracting specific information from source VM characteristics, such as {vmw} vSphere VMDK filenames.

* *Extracting Disk Number from {vmw} VMDK Filename*: This example uses the `.FileName` variable, which provides the name of the disk backing file from {vmw} vSphere, to extract a numeric identifier from the filename. This is especially helpful when the Windows drive letter might be encoded within the filename itself, in cases where the guest agent is not available or reliable for providing the drive letter directly.
+
[source,go]
----
disk-{{ mustRegexReplaceAll \".*[^0-9](+).vmdk\" .FileName \"\$1\" }}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @anarnold97 - I was trying to analyze this regex based on the explanation, and I think it is missing a . in this pattern (+). Should it be (.+) ? And the \ in the .vmdk\ should be at the start, like this \.vmdk. That way, it will:

  1. .* - match any characters
  2. [^0-9] - match one non-digit character
  3. (.+) - capture one or more characters
  4. .vmdk - match literal ".vmdk"

So for this example, disk-vm001.vmdk, the output will be vm001.
For this example, test-vm-001.vmdk, the output will be 001.

Does that make sense? Or am I over-complicating things? Please feel free to ignore if it does not make sense. Disclaimer: I did use Claude to test the example.

The rest of the section looks good. Thanks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree about the missing dot

.*[^0-9](.+).vmdk

with the file named: win_drive_letter_is_C.vmdk

wil get the C drive letter we want

----
+
*Explanation*: This template attempts to find a sequence of one or more digits (`{plus}`) immediately preceding `.vmdk` and preceded by a non-digit character (`++[++^0-9++]++`), capturing these digits (`++\++$1`) for use in the PVC name. This helps in generating PVC names that are based on specific disk identifiers from the original VMDK file.
+
It is important to note that while the `.WinDriveLetter` variable can directly provide the Windows drive letter (if a guest agent is installed on the source Windows VM), using regular expression with `.FileName` offers an alternative for scenarios where the drive letter is embedded in the filename.
+
When creating custom PVC name templates, ensure the generated names comply with Kubernetes DNS-1123 label format, which includes
restrictions on characters and length. {project-short} will validate the template during the plan creation process and report any errors in the plan conditions.


* *Sanitizing Illegal Characters*: This is a common use case to ensure the resulting PVC name is valid for Kubernetes, which has strict naming conventions.
+
[source,go]
----
{{ .VmName | regex.Replace "[^a-zA-Z0-9-]" "-" }}
----
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't support regex. syntax, we have a list of supported functions:
https://github.com/kubev2v/forklift/tree/main/pkg/templateutil

Function Description Example
lower Converts string to lowercase {{ lower "TEXT" }} → text
upper Converts string to uppercase {{ upper "text" }} → TEXT
contains Checks if string contains substring {{ contains "hello" "lo" }} → true
replace Replaces occurrences in a string {{"I Am Henry VIII" | replace " " "-"}} → I-Am-Henry-VIII
trim Removes whitespace from both ends {{ trim " text " }} → text
trimAll Removes specified characters from both ends {{ trimAll "$" "$5.00$" }} → 5.00
trimSuffix Removes suffix if present {{ trimSuffix ".go" "file.go" }} → file
trimPrefix Removes prefix if present {{ trimPrefix "go." "go.file" }} → file
title Converts to title case {{ title "hello world" }} → Hello World
untitle Converts to lowercase {{ untitle "Hello World" }} → hello world
repeat Repeats string n times {{ repeat 3 "abc" }} → abcabcabc
substr Extracts substring from start to end {{ substr 1 4 "abcdef" }} → bcd
nospace Removes all whitespace {{ nospace "a b c" }} → abc
trunc Truncates string to specified length {{ trunc 3 "abcdef" }} → abc
initials Extracts first letter of each word {{ initials "John Doe" }} → JD
hasPrefix Checks if string starts with prefix {{ hasPrefix "go" "golang" }} → true
hasSuffix Checks if string ends with suffix {{ hasSuffix "ing" "coding" }} → true
mustRegexReplaceAll Replaces matches using regex with submatch expansion {{ mustRegexReplaceAll "a(x*)b" "-ab-axxb-" "${1}W" }} → -W-xxW-

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function Description Example
add Sum numbers {{ add 1 2 3 }} → 6
add1 Increment by 1 {{ add1 5 }} → 6
sub Subtract second number from first {{ sub 5 3 }} → 2
div Integer division {{ div 10 3 }} → 3
mod Modulo operation {{ mod 10 3 }} → 1
mul Multiply numbers {{ mul 2 3 4 }} → 24
max Return largest integer {{ max 1 5 3 }} → 5
min Return smallest integer {{ min 1 5 3 }} → 1
floor Round down to nearest integer {{ floor 3.75 }} → 3.0
ceil Round up to nearest integer {{ ceil 3.25 }} → 4.0
round Round to specified decimal places {{ round 3.75159 2 }} → 3.75

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mustRegexReplaceAll - is the only regexp variant we currently support

+
*Explanation*: `{{ .VmName }}` is the input variable—the virtual machine's name. `| regex.Replace` is the function that performs the regular expression replacement. The pipe symbol | passes the .VmName value as the input to the function.
+
The `"[^a-zA-Z0-9-]"` regular expression pattern matches any character that is not a lowercase letter (a-z), an uppercase letter (A-Z), a digit (0-9), or a hyphen (-).
+
"-": This is the replacement string. Any character that matches the pattern is replaced with a hyphen.
+
If your VM is named `vm_name-with.dots`, the resulting PVC name will be `vm-name-with-dots`.

* *Extracting a Specific Part of a Name*: You can use regular expression to extract a piece of information, such as a drive letter, from a more complex filename:
+
[source,go]
----
{{ .FileName | regex.Find "[a-zA-Z]$" }}
----
+
*Explanation*: `{{ .FileName }}` is the input variable, which is the filename of the virtual disk from the source provider.
+
The `| regex.Find` function finds the first substring that matches the provided regular expression pattern.
+
The `"[a-zA-Z]$"` regular expression pattern matches any single letter ([a-zA-Z]) that appears at the end of the string ($).
+
If the disk's filename is `windows-disk-D`, this template will extract just the letter D.

* *Conditional Logic with regular expression*: For more advanced scenarios, you can combine regular expression matching with an if statement to apply logic based on whether a pattern exists:
+
[source,go]
----
{{ if .VmName | regex.Match "^db-" }}{{ .VmName | regex.Replace "^db-" "production-db-" }}{{ else }}{{ .VmName }}{{ end }}
----
+
*Explanation*: `{{ if ... }}` starts a conditional block.
+
`.VmName | regex.Match "^db-"` checks if the VM name starts with the prefix `db-`. The caret `^` anchors the match to the beginning of the string.
+
`{{ .VmName | regex.Replace ... }}` means that if the condition is *true*, this section is executed, replacing `db-` with `production-db-`.
+
`{{ else }}` means that if the condition is *false*, the template falls back to this section.
+
`{{ .VmName }}` means that the original VM name is used if it does not match the regular expression.
+
If your VM is named `db-mysql-primary`, the PVC name will become `production-db-mysql-primary`.
+
While if your VM is named `web-server-01`, the PVC name will remain `web-server-01`.