Skip to content

feat(vm-import): add VMware VM import via Forklift - #1982

Open
Matthieu ROBIN (matthieu-robin) wants to merge 53 commits into
cozystack:mainfrom
matthieu-robin:vmware-import
Open

feat(vm-import): add VMware VM import via Forklift#1982
Matthieu ROBIN (matthieu-robin) wants to merge 53 commits into
cozystack:mainfrom
matthieu-robin:vmware-import

Conversation

@matthieu-robin

@matthieu-robin Matthieu ROBIN (matthieu-robin) commented Feb 4, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Integrate Forklift v2.11.5 as the KubeVirt-native migration toolkit and add a tenant-facing vm-import app to migrate virtual machines from VMware vSphere into Cozystack. VMs are imported through Forklift (virt-v2v guest conversion, or VDDK raw-copy) and then adopted as managed VMInstance / VMDisk resources by a dedicated vm-adoption-controller, so a migrated VM becomes a dashboard-managed workload in the user's own tenant. Cold and warm migrations and network/storage mapping are supported.

New packages

  • packages/system/forklift-operator/ — Forklift operator (CRDs + deployment); component images are pinned to immutable digests
  • packages/system/forklift/ — ForkliftController CR
  • packages/system/vm-adoption-controller/ — adopts Forklift-imported VMs as Cozystack VMInstances
  • packages/apps/vm-import/ — tenant-facing VM import application
  • packages/system/vm-import-rd/ — ApplicationDefinition for the dashboard (IaaS category)
  • PackageSources for the above plus the bundles/iaas.yaml wiring

Isolation

vm-import is tenant-facing, so cross-namespace targets are guarded on both the raw-copy chart render (Plan.spec.targetNamespace) and the adoption controller: a release inside a tenant-* namespace is confined to its own namespace and cannot direct an import at another tenant.

Test plan

  • helm unittest for vm-import, vm-disk, forklift-operator, vm-adoption-controller
  • go test for the adoption controller (the cross-tenant guard, plan/migration resolution, adoption gating)
  • Validated end-to-end on a live cluster: a VMware UEFI AlmaLinux 9 VM migrated and booted inside a tenant, on both the virt-v2v and VDDK raw-copy paths
feat(vm-import): add VMware vSphere VM import via Forklift, adopted as managed Cozystack VMInstances

Summary by CodeRabbit

  • New Features

    • VM import from VMware vSphere to KubeVirt via Forklift, with network/storage mapping, warm/cold migration, and automatic adoption into Cozystack (toggleable).
  • Documentation

    • Extensive user guides, examples (simple/advanced/monitoring), adoption guide, and an adoption helper script.
  • Tests

    • New end-to-end tests covering import workflows and adoption scenarios.
  • Packaging

    • New charts and package manifests to install VM import and adoption components.

@dosubot dosubot Bot added size/XL This PR changes 500-999 lines, ignoring generated files kind/feature Categorizes issue or PR as related to a new feature labels Feb 4, 2026
@coderabbitai

coderabbitai Bot commented Feb 4, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a vm-import application and vm-adoption-controller plus Forklift integration, Helm charts, RD registration, docs, examples, E2E tests, and minor vm-instance template/schema updates to support DataVolume naming for VMware→KubeVirt imports and automatic adoption into Cozystack.

Changes

Cohort / File(s) Summary
vm-import App (chart & config)
packages/apps/vm-import/Chart.yaml, packages/apps/vm-import/values.yaml, packages/apps/vm-import/values.schema.json, packages/apps/vm-import/.helmignore, packages/apps/vm-import/Makefile
New Helm chart, values and schema for VM import, build targets, and packaging ignores.
vm-import Templates & library
packages/apps/vm-import/templates/*, packages/apps/vm-import/charts/cozy-lib
Helm templates for Provider, Plan, Migration, NetworkMap, StorageMap, dashboard RBAC, WorkloadMonitor; references cozy-lib library.
vm-import Documentation, examples & scripts
packages/apps/vm-import/README.md, packages/apps/vm-import/ADOPTION_DESIGN.md, packages/apps/vm-import/docs/*, packages/apps/vm-import/docs/scripts/adopt-vm.sh, packages/apps/vm-import/docs/examples/*
Extensive user docs, design notes, examples (simple/advanced/monitoring), adoption guide, and an adopt-vm.sh helper script.
Resource Definition (RD) & packaging
packages/system/vm-import-rd/*, packages/system/vm-import-rd/cozyrds/vm-import.yaml
ApplicationDefinition and templates registering VMImport with dashboard metadata and OpenAPI schema.
vm-adoption-controller (system)
packages/system/vm-adoption-controller/Chart.yaml, .../Makefile, .../README.md, .../images/controller/*, .../templates/*, .../values.yaml
New Go controller (main.go), Dockerfile, module, Helm chart and manifests (SA, ClusterRole/Binding, Deployment), and values for running an automated adoption controller.
Platform package sources & bundles
packages/core/platform/sources/*, packages/core/platform/templates/bundles/iaas.yaml
Adds PackageSource entries for forklift, vm-adoption-controller, vm-import-application and includes them in the iaas bundle.
Forklift charts & system packages
packages/system/forklift-operator/*, packages/system/forklift/*, packages/system/vm-import-rd/*, packages/system/vm-import-rd/templates/cozyrd.yaml
New Helm chart metadata and Makefiles for forklift operator and forklift system packages; includes a prepared forklift CR.
E2E tests
hack/e2e-apps/vm-import.bats
Bats test suite covering full, minimal, and adoption-disabled VMImport scenarios with resource validations and cleanup.
vm-instance changes
packages/apps/vm-instance/templates/vm.yaml, packages/apps/vm-instance/values.schema.json, packages/apps/vm-instance/values.yaml
Optional disk field dvName added (schema/docs) and template adjusted to use dvName or default vm-disk- for DataVolume references.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant VMware as vSphere
    participant Forklift as Forklift<br/>Operator
    participant KubeVirt as KubeVirt
    participant Adoption as VM<br/>AdoptionController
    participant Cozystack as Cozystack<br/>Dashboard

    User->>KubeVirt: Create Secret (vCenter creds)
    User->>KubeVirt: Apply VMImport CR
    KubeVirt->>Forklift: Create Provider / Plan / Migration
    Forklift->>VMware: Connect & transfer VM disks
    Forklift->>KubeVirt: Create VirtualMachines & DataVolumes
    Forklift->>KubeVirt: Label VMs (plan reference)
    Adoption->>KubeVirt: Watch labeled VMs
    Adoption->>KubeVirt: Get Plan (check adoption enabled)
    alt adoption enabled
        Adoption->>KubeVirt: Create VMInstance CR
        Adoption->>KubeVirt: Label VM as adopted
        KubeVirt->>Cozystack: VMInstance sync -> HelmRelease
        Cozystack->>User: Show VM in dashboard
    else adoption disabled
        Adoption->>Adoption: Skip VM
    end
Loading
sequenceDiagram
    participant Forklift as Forklift
    participant Controller as VM Adoption<br/>Controller
    participant API as Kubernetes API
    participant VM as VirtualMachine

    Forklift->>VM: Create with forklift labels
    Forklift->>API: Annotate Plan (adoption enabled/disabled)
    loop reconcile (interval)
        Controller->>API: List VMs with plan label
        Controller->>API: Get Plan resource
        alt adoption enabled
            Controller->>API: Create VMInstance from VM spec
            rect rgba(76, 175, 80, 0.5)
            Note over Controller,API: extract state, disks, networks, SSH keys
            end
            Controller->>API: Patch VM with adopted labels
        else
            Note over Controller: skip adoption
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐇 I nibbled bytes from vSphere's vine,

Forklift ferried disks so fine,
I stitched them into Cozystack's lair,
Labeled and adopted with diligent care,
Hooray — migrations hopped right there!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(vm-import): add VMware VM import via Forklift' clearly and specifically describes the main change—adding VMware VM import functionality via Forklift integration.
Linked Issues check ✅ Passed The PR fully satisfies issue #250 objectives by delivering VMware-to-KubeVirt migration tooling with Forklift integration, network/storage mappings, and vm-adoption-controller for dashboard visibility.
Out of Scope Changes check ✅ Passed All changes align with PR objectives: vm-import app, Forklift operator/controller, vm-adoption-controller, dashboard integration, and vm-instance enhancements for dvName support are all in scope.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello Matthieu ROBIN (@matthieu-robin), I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the platform's virtualization capabilities by introducing a robust solution for migrating existing VMware virtual machines to KubeVirt. It provides a comprehensive application for users to manage the entire migration lifecycle, from source configuration to network and storage mapping, supporting various migration strategies. This integration streamlines the process of moving workloads from traditional VMware environments to a Kubernetes-native virtualization platform.

Highlights

  • Forklift Integration: Integrated Forklift v2.10.4 as the KubeVirt-native migration toolkit, enabling the import of Virtual Machines from VMware vSphere.
  • New VM Import Application: Introduced a new vm-import application package, providing a user-facing interface to configure VMware sources, select VMs for migration, and map networks and storage.
  • Migration Capabilities: Added support for both cold and warm migrations, including automatic guest conversion via virt-v2v for seamless integration with KubeVirt.
  • New Package Structure: Created several new packages: forklift-operator, forklift, vm-import, vm-import-rd, and corresponding PackageSource definitions for Forklift and the VM import application.
  • IaaS Bundle Update: Modified the iaas.yaml bundle to include the newly added Forklift and vm-import components, making them part of the Infrastructure as a Service offering.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • packages/apps/vm-import/.helmignore
    • Added a .helmignore file to specify patterns for files to ignore when packaging the Helm chart.
  • packages/apps/vm-import/Chart.yaml
    • Added the Helm chart definition for the vm-import application, including its name, description, icon, and cozy-lib dependency.
  • packages/apps/vm-import/Makefile
    • Added a Makefile for generating values schema and updating CRDs for the vm-import application.
  • packages/apps/vm-import/README.md
    • Added comprehensive documentation for the vm-import application, detailing usage instructions, credential creation, VM identification, migration monitoring, and parameter descriptions for VMware source, migration plan, network mapping, and storage mapping.
  • packages/apps/vm-import/charts/cozy-lib
    • Added a reference to the cozy-lib library within the vm-import application's charts directory.
  • packages/apps/vm-import/logos/vm-import.svg
    • Added an SVG icon for the vm-import application.
  • packages/apps/vm-import/templates/dashboard-resourcemap.yaml
    • Added RBAC definitions (Role and RoleBinding) to allow the dashboard to access Forklift resources like providers, plans, and migrations.
  • packages/apps/vm-import/templates/migration.yaml
    • Added a Helm template to create a Forklift Migration custom resource, conditionally based on the presence of VMs in the values.
  • packages/apps/vm-import/templates/networkmap.yaml
    • Added a Helm template to create a Forklift NetworkMap custom resource, enabling mapping of source VMware networks to destination Kubernetes pod or Multus networks.
  • packages/apps/vm-import/templates/plan.yaml
    • Added a Helm template to create a Forklift Plan custom resource, defining the migration strategy, source/destination providers, and VM list.
  • packages/apps/vm-import/templates/provider.yaml
    • Added a Helm template to create Forklift Provider custom resources for both VMware vSphere (source) and OpenShift/KubeVirt (destination).
  • packages/apps/vm-import/templates/storagemap.yaml
    • Added a Helm template to create a Forklift StorageMap custom resource, allowing mapping of source VMware datastores to destination Kubernetes StorageClasses.
  • packages/apps/vm-import/templates/workloadmonitor.yaml
    • Added a WorkloadMonitor resource definition for the vm-import application.
  • packages/apps/vm-import/values.schema.json
    • Added the JSON schema for the vm-import Helm chart's configurable values, including network and storage mappings, source details, and VM list.
  • packages/apps/vm-import/values.yaml
    • Added the default values file for the vm-import Helm chart, providing initial configurations and descriptions for all parameters.
  • packages/core/platform/sources/forklift.yaml
    • Added a PackageSource definition for the Forklift operator and controller, specifying its dependencies and installation details.
  • packages/core/platform/sources/vm-import-application.yaml
    • Added a PackageSource definition for the vm-import application, outlining its components, libraries, and dependencies.
  • packages/core/platform/templates/bundles/iaas.yaml
    • Modified the IaaS bundle template to include the new cozystack.forklift and cozystack.vm-import-application packages.
  • packages/system/forklift-operator/Chart.yaml
    • Added the Helm chart definition for the cozy-forklift-operator.
  • packages/system/forklift-operator/Makefile
    • Added a Makefile for the Forklift operator, including commands to update its manifests from the upstream Forklift repository.
  • packages/system/forklift/Chart.yaml
    • Added the Helm chart definition for the cozy-forklift controller.
  • packages/system/forklift/Makefile
    • Added a Makefile for the forklift controller package.
  • packages/system/forklift/templates/forklift-cr.yaml
    • Added a Helm template to create the ForkliftController custom resource, configuring various Forklift features.
  • packages/system/vm-import-rd/Chart.yaml
    • Added the Helm chart definition for the vm-import-rd (VM Import ApplicationDefinition).
  • packages/system/vm-import-rd/Makefile
    • Added a Makefile for the vm-import-rd package.
  • packages/system/vm-import-rd/cozyrds/vm-import.yaml
    • Added the ApplicationDefinition for vm-import, integrating it into the dashboard with its OpenAPI schema, category, and display properties.
  • packages/system/vm-import-rd/templates/cozyrd.yaml
    • Added a Helm template to include all ApplicationDefinition resources from the cozyrds directory.
  • packages/system/vm-import-rd/values.yaml
    • Added an empty default values file for the vm-import-rd Helm chart.
Activity
  • The pull request content was generated using Claude Code, indicating an AI-assisted development process.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment Gemini (@gemini-code-assist) Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on Gemini (@gemini-code-assist) comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces the VMware VM import feature via Forklift, integrating it seamlessly into the existing CozyStack platform. The new packages for forklift-operator, forklift, vm-import, and vm-import-rd are well-structured and follow Helm best practices. The Makefile for forklift-operator includes robust logic for namespace handling and removal of kind: Namespace definitions, which is a good practice for Helm chart generation from upstream Kustomize. The README.md provides clear and comprehensive instructions for users, and the values.schema.json accurately defines the configuration parameters. Overall, the changes are well-implemented and align with the stated objectives of the pull request.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@packages/apps/vm-import/Makefile`:
- Around line 1-5: Add a .PHONY declaration for the custom Makefile target to
match other packages: declare ".PHONY: generate" in the vm-import Makefile so
the "generate" target (the cozyvalues-gen and update-crd invocation) is treated
as phony; do not add duplicates for targets already provided by hack/package.mk
(e.g., all, clean, test).

In `@packages/apps/vm-import/README.md`:
- Around line 7-8: The README uses the incorrect capitalization "Github:";
update the text to the proper brand capitalization "GitHub:" by replacing the
string "Github:" with "GitHub:" in the README entry (the line containing the
link label currently showing "Github: <https://github.com/kubev2v/forklift>").

In `@packages/apps/vm-import/templates/networkmap.yaml`:
- Around line 14-22: The template currently treats any non-"pod" value as multus
and may render an empty destinationName; change the conditional to explicitly
check for "multus" (use else if eq .destinationType "multus") and only render
the multus block when that is true, and inside that block assert/require a
non-empty .destinationName (e.g., wrap name: {{ .destinationName | quote }} with
an if .destinationName check or use the template's required helper to fail
fast), leaving a safe default or no output for other destinationType values;
reference the symbols destinationType and destinationName in networkmap.yaml and
update the pod/multus conditional accordingly.

In `@packages/apps/vm-import/templates/plan.yaml`:
- Around line 17-25: The template currently gates the entire map block on
.Values.networkMap causing storage-only configs to omit map.storage; change the
outer condition to render map when either .Values.networkMap or
.Values.storageMap is set (use Helm's or: {{- if or .Values.networkMap
.Values.storageMap }}), then inside that block render network and storage
subsections independently using {{- if .Values.networkMap }} ... {{- end }} and
{{- if .Values.storageMap }} ... {{- end }}, keeping the existing fields (name:
{{ .Release.Name }}, namespace: {{ .Release.Namespace }}) for each subsection.
🧹 Nitpick comments (3)
packages/system/forklift/Chart.yaml (1)

1-3: LGTM!

Standard Chart.yaml structure following the cozy- prefix convention for system packages.

Consider adding a description field for documentation clarity:

 apiVersion: v2
 name: cozy-forklift
+description: Forklift controller for VM migration
 version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process
packages/system/forklift-operator/Makefile (1)

6-14: Consider extracting the inline Python filter into a small script for readability. It will make the update target easier to maintain and debug.

packages/apps/vm-import/values.schema.json (1)

16-27: Tighten schema: enum destinationType and require destinationName for multus.

This will prevent invalid values from reaching rendering/runtime.

Suggested schema update
         "properties": {
           "destinationName": {
             "description": "Name of the destination network (required if type is `multus`).",
             "type": "string"
           },
           "destinationNamespace": {
             "description": "Namespace of the destination network.",
             "type": "string"
           },
           "destinationType": {
             "description": "Destination type: `pod` for pod network, or `multus` for a Multus network.",
-            "type": "string"
+            "type": "string",
+            "enum": ["pod", "multus"]
           },
           "sourceId": {
             "description": "The managed object reference ID of the source network in vSphere.",
             "type": "string"
           }
-        }
+        },
+        "allOf": [
+          {
+            "if": {
+              "properties": { "destinationType": { "const": "multus" } }
+            },
+            "then": { "required": ["destinationName"] }
+          }
+        ]
       }

Comment thread packages/apps/vm-import/Makefile
Comment thread packages/apps/vm-import/README.md Outdated
Comment thread packages/apps/vm-import/templates/networkmap.yaml
Comment thread packages/apps/vm-import/templates/plan.yaml Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/apps/vm-import/templates/plan.yaml`:
- Line 9: The template sets "warm: {{ .Values.warm }}" which yields an
empty/invalid YAML when .Values.warm is undefined; change it to use Helm's
default function so the key always renders a valid boolean, e.g. replace the
expression with {{ default false .Values.warm }} (keep it unquoted) so the warm
field will be a proper boolean false when no value is provided.

Comment thread packages/apps/vm-import/templates/plan.yaml Outdated
@kvaps

Copy link
Copy Markdown
Member

Hey Matthieu ROBIN (@matthieu-robin),

With the vm-import application, it’s possible to specify which VMs to import via:

vms: []

What is the expected behavior here?

My understanding is that Forklift will import the VMs as lower-level KubeVirt objects. However, this wouldn’t create the higher-level virtualmachine objects in Cozystack, so they wouldn’t be visible in the dashboard or to the end user.

Am I mistaken?

Also, what is expected to happen when the vm-import application is removed? Should the imported VMs be deleted as well?

I think we need to clarify this workflow.

@dosubot dosubot Bot added size/XXL This PR changes 1000+ lines, ignoring generated files and removed size/XL This PR changes 500-999 lines, ignoring generated files labels Feb 6, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 13

🤖 Fix all issues with AI agents
In `@packages/apps/vm-import/ADOPTION_DESIGN.md`:
- Around line 54-84: Update the design doc to match the implemented behavior of
the vm-import-adoption-controller: replace the ConfigMap-based tracking section
with a clear description of the VMInstance CRD creation (including the CRD name
VMInstance and the fields populated) and add a short note explaining the
historical reason for the change from ConfigMap to VMInstance (or,
alternatively, modify the controller to create ConfigMaps instead if you prefer
the original design); ensure the doc references the controller name
vm-import-adoption-controller and the concrete resource (VMInstance) so readers
can find main.go and the CRD implementation.
- Around line 114-139: Update the HelmRelease manifest to use the stable API
version by replacing the apiVersion value "helm.toolkit.fluxcd.io/v2beta1" with
"helm.toolkit.fluxcd.io/v2" in the HelmRelease example; locate the manifest
block that defines kind: HelmRelease and change the apiVersion accordingly so
the example uses the GA Flux Helm toolkit API.

In `@packages/apps/vm-import/docs/examples/advanced-import.yaml`:
- Line 18: The thumbprint value under the thumbprint key is one octet short (19
colon-separated hex pairs) and must be a full 20-byte SHA-1 fingerprint; update
the thumbprint string in advanced-import.yaml (the thumbprint key) to contain 20
colon-separated two-digit hex pairs (matching the format used in the
simple-import example) so the example shows a valid SHA-1 fingerprint.

In `@packages/apps/vm-import/docs/scripts/adopt-vm.sh`:
- Around line 63-76: The post-increment expression ((DISK_COUNT++)) inside the
while loop causes a non-zero exit status when DISK_COUNT is 0 under set -e;
change the increment to a safe form that always returns 0 such as using
((DISK_COUNT+=1)) or DISK_COUNT=$((DISK_COUNT+1)) in the loop that reads
DISK_NAMES so the script doesn't exit early; update references to DISK_COUNT in
the same block (the while IFS= read -r disk; do ... done <<< "$DISK_NAMES"
section) and ensure DISKS_YAML building and the final echo remain unchanged.
- Around line 90-92: The step label for the network check is missing the
progress prefix; update the echo that prints "Checking network configuration..."
to follow the same step counter format (e.g., change it to "echo '[5/5] Checking
network configuration...'" or renumber all other step labels to use a 6-step
sequence) so it matches the existing pattern; locate the echo that precedes the
NETWORKS variable assignment (the block referencing VM_NAME and NAMESPACE) and
apply the consistent `[N/5]` or updated `[N/6]` prefix.

In `@packages/apps/vm-import/TEST_REPORT.md`:
- Line 106: The fenced code blocks in TEST_REPORT.md are missing language
specifiers (violates MD040); update each backtick-fenced block that currently
starts with ``` to include an explicit language hint (for example change ``` to
```text or ```bash as appropriate) so both occurrences are annotated and
markdownlint MD040 is satisfied.
- Around line 1-6: Remove the AI-authored test report file TEST_REPORT.md
(authored by "Claude Code") from the commit and do not commit AI-generated test
evidence as source-of-truth; either delete the file or move its contents to a
CI-generated artifact (e.g., capture helm template output as pipeline artifacts)
and replace the repository file with a human-verified test plan; also ensure any
references like "Ready to merge", "Kubernetes v1.34.3", "kubectl v1.35.0", or
mixed French/English notes are removed or rewritten by a human reviewer before
committing.

In `@packages/core/platform/sources/vm-adoption-controller.yaml`:
- Around line 1-14: The PackageSource uses the wrong API and flat structure;
change apiVersion to cozystack.io/v1alpha1 and restructure to the canonical
pattern: add spec.sourceRef referencing the cozystack-packages OCIRepository,
move the existing component under spec.variants[] -> components with the
vm-adoption-controller entry, and move dependsOn into the same spec.variants[]
as a list of package names (strings) instead of objects; ensure the kind stays
PackageSource and preserve namespace and path values when migrating them into
the spec.variants component definition.

In `@packages/system/vm-adoption-controller/images/controller/go.mod`:
- Around line 3-10: Update the module Go version and Kubernetes client
libraries: change the go directive from "go 1.23" to "go 1.25" and bump the k8s
libs in the require block—specifically update "k8s.io/client-go", "k8s.io/api",
and "k8s.io/apimachinery" to v0.35.0 (keep or reconcile k8s.io/klog/v2 and
kubevirt.io/api as needed for compatibility). After editing the go.mod entries,
run "go get k8s.io/client-go@v0.35.0" (and corresponding get commands for
api/apimachinery if needed) and "go mod tidy" to resolve transitive versions,
then rebuild/tests to ensure there are no breaking API changes.

In `@packages/system/vm-adoption-controller/images/controller/main.go`:
- Around line 226-251: The code currently defaults to adoption=true when the
Plan lookup via c.dynamicClient.Resource(gvr).Namespace(namespace).Get(...)
fails; change this to default to false: when Get returns an error, set enabled =
false, store that value in c.planCache[cacheKey]
(PlanCacheEntry.AdoptionEnabled) under c.cacheMutex, and log the failure at
warning level indicating adoption is being defaulted to disabled; ensure the
function returns false instead of true so adoption only proceeds when a Plan is
explicitly confirmed.
- Around line 268-272: The current call to unstructured.NestedMap (used with
vmUnstructured and assigned to spec) can return err==nil and found==false which
results in fmt.Errorf wrapping a nil error; change the check so you handle the
two cases separately: if err != nil return fmt.Errorf("failed to get VM spec:
%w", err) and if !found return a clear non-wrapped error (e.g., errors.New("VM
spec not found") or fmt.Errorf("VM spec not found")) so you never wrap a nil
error when calling unstructured.NestedMap.

In `@packages/system/vm-adoption-controller/README.md`:
- Around line 37-49: The YAML sample has duplicate top-level controller: blocks
so only the last one applies; merge the three blocks into a single controller
mapping containing watchInterval, watchNamespace, and namePrefix together
(preserve comments) so all keys take effect; update the README.md example to
show a single controller: block with watchInterval, watchNamespace, and
namePrefix fields.
🧹 Nitpick comments (12)
packages/system/vm-adoption-controller/templates/serviceaccount.yaml (1)

1-5: Hardcoded resource name prevents multiple releases and missing standard Helm labels.

The name vm-adoption-controller is hardcoded rather than derived from the release (e.g., {{ .Release.Name }}-vm-adoption-controller or a fullname helper). Since the corresponding ClusterRoleBinding (cluster-scoped) also uses this same hardcoded name, deploying two releases will cause a conflict.

Additionally, standard app.kubernetes.io labels are missing on all resources in this chart, which reduces observability and makes it harder to identify resources belonging to a specific release.

Suggested improvement
 apiVersion: v1
 kind: ServiceAccount
 metadata:
-  name: vm-adoption-controller
+  name: {{ include "vm-adoption-controller.fullname" . }}
   namespace: {{ .Release.Namespace }}
+  labels:
+    app.kubernetes.io/name: vm-adoption-controller
+    app.kubernetes.io/instance: {{ .Release.Name }}
+    app.kubernetes.io/managed-by: {{ .Release.Service }}

This applies equally to the ClusterRole, ClusterRoleBinding, Deployment, and ServiceAccount templates in this chart.

packages/system/vm-adoption-controller/images/controller/Dockerfile (3)

9-10: Only main.go is copied — consider copying all source files.

Currently only main.go is copied. If internal packages or additional .go files are added in the future, the build will silently exclude them. A more resilient pattern:

Suggested fix
 # Copy source
-COPY main.go .
+COPY . .

This is safe because go.mod and go.sum are already copied in the previous step (layer caching for dependency download is preserved).


1-1: Upgrade Go version to match repo standards. The Dockerfile correctly uses Go 1.23 from go.mod, but Go 1.23 is outdated—most other modules in the system use Go 1.25. Align this package with the latest version used across the codebase.


13-13: Hardcoded GOARCH=amd64 deviates from project multi-architecture build pattern.

The codebase consistently uses ARG TARGETOS and ARG TARGETARCH across other controllers (backup-controller, cozystack-controller, flux-plunger, kamaji, and others). Align this Dockerfile with the established pattern to enable multi-architecture builds.

Suggested fix
+ARG TARGETOS=linux
+ARG TARGETARCH=amd64
-RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o controller main.go
+RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -a -o controller main.go
packages/system/vm-adoption-controller/values.yaml (1)

3-5: Empty image.repository and image.tag defaults — will the deployment work out of the box?

Both repository and tag default to empty strings. If a user installs this chart without overriding these values, the deployment will reference an invalid image ("":"" or similar). Typically, a default image reference is provided so the chart is functional without mandatory overrides. If this is intentionally left for the packaging system to fill in, consider adding a comment noting that, or add a validation check in the deployment template.

packages/system/vm-adoption-controller/templates/deployment.yaml (1)

19-43: Consider adding a liveness probe.

The controller runs a polling loop with no health endpoint. If the reconciliation loop hangs (e.g., blocked on an API call without timeout), Kubernetes won't detect the failure and won't restart the pod. A minimal /healthz endpoint in the Go binary or a file-touch–based probe would improve resilience.

hack/e2e-apps/vm-import.bats (1)

86-92: Test cleanup only runs on success; resource leak on mid-test failure.

If an assertion fails before the cleanup block (lines 86-91), the VMImport, Secret, and HelmRelease will remain in the cluster, potentially causing subsequent test runs to fail or collide. Consider using a teardown function (Bats teardown) or a trap to ensure cleanup runs regardless of test outcome.

♻️ Example using Bats teardown or trap

You could add a file-level teardown or use setup/teardown per test:

# At the top of each `@test`, or use a file-level teardown
teardown() {
  kubectl -n tenant-test delete vmimport.apps.cozystack.io test --ignore-not-found 2>/dev/null || true
  kubectl -n tenant-test delete secret vmware-credentials-test --ignore-not-found 2>/dev/null || true
  kubectl -n tenant-test delete vmimport.apps.cozystack.io test-minimal --ignore-not-found 2>/dev/null || true
  kubectl -n tenant-test delete secret vmware-credentials-test-minimal --ignore-not-found 2>/dev/null || true
  kubectl -n tenant-test delete vmimport.apps.cozystack.io test-no-adoption --ignore-not-found 2>/dev/null || true
  kubectl -n tenant-test delete secret vmware-credentials-test-no-adoption --ignore-not-found 2>/dev/null || true
}
packages/apps/vm-import/ADOPTION_DESIGN.md (1)

1-10: Design document is written in French while the rest of the codebase is in English.

This limits accessibility for non-French-speaking contributors and reviewers. Consider translating to English for consistency, or at minimum adding an English summary at the top.

packages/system/vm-adoption-controller/images/controller/main.go (4)

43-46: Dead code: clientset field and vmDisk* constants are never used.

  • vmDiskGroup, vmDiskVersion, vmDiskKind (lines 43-46) are declared but never referenced.
  • clientset (line 97) is created in main() and stored in the struct but never accessed by any method — only dynamicClient is used.

This adds unnecessary dependencies and confusion.

♻️ Proposed cleanup
-	// VMDisk GVR
-	vmDiskGroup   = "apps.cozystack.io"
-	vmDiskVersion = "v1alpha1"
-	vmDiskKind    = "VMDisk"
-
 type AdoptionController struct {
-	clientset     *kubernetes.Clientset
 	dynamicClient dynamic.Interface
 	planCache     map[string]*PlanCacheEntry
 	cacheMutex    sync.RWMutex
 }

And remove the kubernetes.NewForConfig(config) call and the "k8s.io/client-go/kubernetes" import in main().

Also applies to: 74-78, 96-101


52-94: No signal handling — graceful shutdown is unreachable.

context.Background() is never cancelled, so ctx.Done() in Run() (line 119) will never fire. When Kubernetes sends SIGTERM, the controller ignores it and is eventually SIGKILL'd.

For a stateless polling controller this is functionally safe, but it prevents clean log messages and could leave mid-flight API calls interrupted.

♻️ Proposed signal handling
+	"os"
+	"os/signal"
+	"syscall"
 ...
 	// Run controller
-	ctx := context.Background()
+	ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
+	defer cancel()
 	controller.Run(ctx)

Also applies to: 108-126


328-331: Use strings.TrimPrefix for idiomatic prefix stripping.

Manual length check + slice is fragile and harder to read.

♻️ Proposed fix

Add "strings" to imports, then:

-		// Extract disk name (remove vm-disk- prefix if present)
-		if len(diskName) > 8 && diskName[:8] == "vm-disk-" {
-			diskName = diskName[8:]
-		}
+		// Extract disk name (remove vm-disk- prefix if present)
+		diskName = strings.TrimPrefix(diskName, "vm-disk-")

147-204: Each VM is fetched twice — once in getForkliftVMs (unstructured list) and again in adoptVM (get by name).

The full unstructured data is already available in item (line 172) but is discarded. Passing the unstructured.Unstructured directly to adoptVM would eliminate one API call per VM.

Comment thread packages/apps/vm-import/ADOPTION_DESIGN.md Outdated
Comment on lines +114 to +139
# Crée la HelmRelease avec adoption
cat <<EOF | kubectl apply -f -
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: $VM_NAME
namespace: $NAMESPACE
spec:
chart:
spec:
chart: vm-instance
sourceRef:
kind: HelmRepository
name: cozystack
install:
createNamespace: false
# N'essaie pas de créer la VM (elle existe déjà)
disableWait: true
upgrade:
# Ne modifie pas la VM existante
force: false
values:
# Valeurs extraites de la VM existante
...
EOF
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Flux HelmRelease v2beta1 deprecated v2 stable version

💡 Result:

HelmRelease v2beta1 (helm.toolkit.fluxcd.io/v2beta1) is deprecated and (in Flux) effectively removed starting with Flux v2.2.0—clusters upgraded to Flux v2.2+ must stop using v2beta1 manifests. [4], [5]

What to use instead (stable)

Use the stable (GA) API:

  • apiVersion: helm.toolkit.fluxcd.io/v2 (kind: HelmRelease) [2], [3]

(v2beta2 existed as an intermediate beta, but it’s also deprecated and slated for removal in a future release.) [1]

Minimal migration

  1. Update your manifests:
    • helm.toolkit.fluxcd.io/v2beta1helm.toolkit.fluxcd.io/v2 [2], [3]
  2. Make sure your Flux/helm-controller installation is upgraded and CRDs are updated (so the cluster has the v2 HelmRelease CRD installed). [1], [3]

Notable schema removals in the v2 API (watch for these if you used them)

Some deprecated fields were removed from the HelmRelease v2 API (e.g. valuesFilevaluesFiles, and older kustomize post-render patch fields → patches). [1]

Sources: Flux v2.3 announcement (deprecations/removals) [1], Helm API reference v2 [2], ControlPlane/Flux API version matrices showing HelmRelease GA as v2 [3], Flux v2.2.0 notes about v2beta1 deprecation/removal [4], Flux controller API versioning/deprecation policy [5].


Replace v2beta1 with the stable v2 API version in the HelmRelease manifest.

Line 116 uses the deprecated helm.toolkit.fluxcd.io/v2beta1 API version. Update it to helm.toolkit.fluxcd.io/v2 (stable GA since Flux 2.1.0, with v2beta1 removal enforced in Flux v2.2.0+). Anyone following this documentation example should use the current stable API.

🤖 Prompt for AI Agents
In `@packages/apps/vm-import/ADOPTION_DESIGN.md` around lines 114 - 139, Update
the HelmRelease manifest to use the stable API version by replacing the
apiVersion value "helm.toolkit.fluxcd.io/v2beta1" with
"helm.toolkit.fluxcd.io/v2" in the HelmRelease example; locate the manifest
block that defines kind: HelmRelease and change the apiVersion accordingly so
the example uses the GA Flux Helm toolkit API.

Comment thread packages/apps/vm-import/docs/examples/advanced-import.yaml Outdated
Comment thread packages/apps/vm-import/docs/scripts/adopt-vm.sh
Comment on lines +90 to +92
# Check for networks
echo "Checking network configuration..."
NETWORKS=$(kubectl get vm "$VM_NAME" -n "$NAMESPACE" -o json 2>/dev/null | \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent step label — missing [X/5] prefix.

Line 91 uses a bare "Checking network configuration..." while all other steps use the [N/5] counter format. This is the implicit step between 4 and 5.

Proposed fix
-echo "Checking network configuration..."
+echo -e "${YELLOW}[4.5/5]${NC} Checking network configuration..."

Alternatively, renumber all steps to account for 6 total steps.

🤖 Prompt for AI Agents
In `@packages/apps/vm-import/docs/scripts/adopt-vm.sh` around lines 90 - 92, The
step label for the network check is missing the progress prefix; update the echo
that prints "Checking network configuration..." to follow the same step counter
format (e.g., change it to "echo '[5/5] Checking network configuration...'" or
renumber all other step labels to use a 6-step sequence) so it matches the
existing pattern; locate the echo that precedes the NETWORKS variable assignment
(the block referencing VM_NAME and NAMESPACE) and apply the consistent `[N/5]`
or updated `[N/6]` prefix.

Comment on lines +3 to +10
go 1.23

require (
k8s.io/api v0.31.0
k8s.io/apimachinery v0.31.0
k8s.io/client-go v0.31.0
k8s.io/klog/v2 v2.130.1
kubevirt.io/api v1.3.1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

latest Go version February 2026

💡 Result:

As of February 6, 2026, the latest stable Go release is Go 1.25.7 (released February 4, 2026). [1]

Latest major series: Go 1.25 (Go 1.25.0 released August 12, 2025). [1]

Source(s)
[1] Go Release History (go.dev) (go.dev)

Citations:


🌐 Web query:

Kubernetes client-go latest release version 2026

💡 Result:

As of Feb 6, 2026, the latest stable k8s.io/client-go release tag is v0.35.0 (published Dec 17, 2025, aligned with Kubernetes v1.35.0). [1][2]


🌐 Web query:

Go 1.23 end of life support timeline

💡 Result:

Go 1.23 support follows Go’s release policy: a major release is supported until there are two newer major releases (i.e., only the latest two major versions are supported) [1]. The security policy likewise prepares fixes for the two most recent major releases [2].

Go 1.23 end-of-life (end of upstream support) timeline

  • Go 1.23.0 released: August 13, 2024 [3]
  • Last 1.23 patch release: Go 1.23.12 (August 6, 2025) [4]
  • End of upstream support (EOL): August 12, 2025 — when Go 1.25.0 shipped as the second newer major release after 1.23 (with 1.24 in between) [1][5]. After this date, upstream no longer backports critical/security fixes to 1.23 [1][2].

(As of February 6, 2026, the supported major releases are Go 1.24 and 1.25 [1].)

Sources

  1. Go Release Policy (go.dev release history page) [1]
  2. Go Security Policy (two most recent major releases) [2]
  3. Go 1.23 release announcement [3]
  4. Go 1.23.12 release entry [4]
  5. Go 1.25.0 release entry/date [5]

Upgrade Go and Kubernetes client libraries — current versions are unsupported.

Go 1.23 reached end-of-life on August 12, 2025, and no longer receives security patches or critical fixes. The Kubernetes client libraries at v0.31.0 (August 2024) are four minor versions behind the current stable v0.35.0 and similarly lack security updates.

Bump to Go 1.25.x (latest stable: 1.25.7 as of February 2026) and Kubernetes client-go v0.35.0 to restore security support and access current bug fixes.

🤖 Prompt for AI Agents
In `@packages/system/vm-adoption-controller/images/controller/go.mod` around lines
3 - 10, Update the module Go version and Kubernetes client libraries: change the
go directive from "go 1.23" to "go 1.25" and bump the k8s libs in the require
block—specifically update "k8s.io/client-go", "k8s.io/api", and
"k8s.io/apimachinery" to v0.35.0 (keep or reconcile k8s.io/klog/v2 and
kubevirt.io/api as needed for compatibility). After editing the go.mod entries,
run "go get k8s.io/client-go@v0.35.0" (and corresponding get commands for
api/apimachinery if needed) and "go mod tidy" to resolve transitive versions,
then rebuild/tests to ensure there are no breaking API changes.

Comment thread packages/system/vm-adoption-controller/images/controller/main.go
Comment thread packages/system/vm-adoption-controller/images/controller/main.go
Comment thread packages/system/vm-adoption-controller/images/controller/main.go Outdated
Comment thread packages/system/vm-adoption-controller/README.md
@matthieu-robin

Copy link
Copy Markdown
Collaborator Author

Hey Andrei Kvapil (@kvaps),

You're right — the original implementation had several gaps preventing imported VMs from being visible in the dashboard. Here's a clarification of the intended workflow, along with the fixes I've pushed.

Workflow: how imported VMs become visible in the dashboard

  1. Forklift imports VMs from VMware → creates native KubeVirt VirtualMachine + DataVolume resources
  2. The vm-adoption-controller (deployed as a platform package in the IaaS bundle) detects VMs with the forklift.konveyor.io/plan label
  3. It checks the Plan annotation vm-import.cozystack.io/adoption-enabled: "true"
  4. If enabled, it:
    • Annotates the existing VM with Helm metadata (meta.helm.sh/release-name, app.kubernetes.io/managed-by: Helm) so Helm can adopt it
    • Creates a VMInstance CRD via the aggregated API, with fullnameOverride set to the original VM name
    • Extracts disks (via dvName to reference Forklift DataVolumes directly) and Multus subnets from the existing VM
  5. The standard cozystack-controller picks up the VMInstance → creates a HelmRelease
  6. Helm adopts the existing VM (thanks to the pre-applied annotations) instead of creating a duplicate
  7. The VM appears in the dashboard as a regular VM Instance

This happens automatically within ~30 seconds of import completion.

What happens when vm-import is deleted

Deleted (temporary migration objects):

  • Migration, Plan, NetworkMap, StorageMap

Preserved:

  • Providers (source/destination) — kept via helm.sh/resource-policy: keep, reusable for future imports
  • All imported VMs — not part of the Helm release, never deleted
  • All DataVolumes and disks — never deleted
  • VMInstance CRDs and their HelmReleases — VMs remain visible and manageable in the dashboard

Fixes in this update

Architectural (the adoption flow was fundamentally broken)

Fix Detail
VM duplication Helm was creating vm-instance-<name> instead of adopting the existing <name>. Fixed by passing fullnameOverride and pre-annotating the VM with Helm ownership metadata
Disk lookup failure vm-instance template expected vm-disk-<name> but Forklift DVs have arbitrary names. Added dvName field to the disk spec for direct DataVolume references
Networks lost Controller was passing subnets: [], losing all Multus networks. Now extracts Multus interfaces from the existing VM
running vs runStrategy Controller used deprecated running boolean. Now converts to runStrategy enum (Always/Halted)
cloud-init re-execution cloudInitSeed: "imported-vm" changed the firmware UUID, risking cloud-init re-run. Changed to cloudInitSeed: "" which preserves the existing UUID

Infrastructure

Fix Detail
PackageSource wrong API vm-adoption-controller.yaml used source.toolkit.fluxcd.io/v1 instead of cozystack.io/v1alpha1. Rewrote following the forklift.yaml pattern
Missing from IaaS bundle vm-adoption-controller was never deployed. Added to iaas.yaml before vm-import-application
Default adoption=true on Plan lookup failure Changed to false — a failed lookup should not trigger adoption

Functional

Fix Detail
((DISK_COUNT++)) crash Fails with set -e when DISK_COUNT=0. Changed to DISK_COUNT=$((DISK_COUNT + 1)) in adopt-vm.sh
fmt.Errorf wrapping nil Separated err != nil and !found cases for clear error messages

Cleanup

  • Removed AI-generated TEST_REPORT.md
  • Rewrote ADOPTION_DESIGN.md to reflect actual implementation (VMInstance CRD, not ConfigMap)
  • Fixed duplicate YAML keys in vm-adoption-controller/README.md

Files changed

  • packages/core/platform/sources/vm-adoption-controller.yaml — rewritten
  • packages/core/platform/templates/bundles/iaas.yaml — added controller
  • packages/system/vm-adoption-controller/images/controller/main.go — Helm adoption, dvName, subnets, runStrategy
  • packages/apps/vm-instance/templates/vm.yamldvName support in disk lookup + volume reference
  • packages/apps/vm-instance/values.schema.json — added optional dvName field
  • packages/apps/vm-import/docs/scripts/adopt-vm.sh — DISK_COUNT fix
  • packages/apps/vm-import/ADOPTION_DESIGN.md — rewritten
  • packages/apps/vm-import/TEST_REPORT.md — deleted
  • packages/system/vm-adoption-controller/README.md — fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
packages/apps/vm-import/docs/examples/simple-import.yaml (1)

13-17: Use explicit placeholder tokens for secret values in the example.

Line 14 and Line 17 are valid samples, but <REPLACE_...> style placeholders are safer for copy/paste usage in docs.

Small docs hardening tweak
-  user: administrator@vsphere.local
-  password: MySecurePassword123!
+  user: "<VCENTER_USERNAME>"
+  password: "<VCENTER_PASSWORD>"
@@
-  thumbprint: "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD"
+  thumbprint: "<VCENTER_TLS_SHA1_FINGERPRINT>"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/apps/vm-import/docs/examples/simple-import.yaml` around lines 13 -
17, Replace the hard-coded example secrets in the YAML (the values for keys
user, password, and thumbprint) with explicit placeholder tokens so readers
don't accidentally copy real credentials; update the "user" value to something
like <REPLACE_USERNAME>, "password" to <REPLACE_PASSWORD>, and "thumbprint" to
<REPLACE_THUMBPRINT> (keep the comment showing how to obtain a real thumbprint
intact), ensuring examples in the file reference these placeholder tokens
instead of real-looking secrets.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/apps/vm-import/ADOPTION_DESIGN.md`:
- Around line 93-105: The fenced code block that begins with "Forklift imports
VM" is untyped; update that block in ADOPTION_DESIGN.md by adding a language tag
(e.g., change ``` to ```text) so the code fence is typed (```text ... ```),
ensuring markdownlint MD040 is satisfied for the architecture diagram block.

In `@packages/apps/vm-import/docs/adoption.md`:
- Around line 17-40: The docs contradict each other about enableAdoption
behavior: update the "Method 1: Automatic Labeling (Default)" section to match
the controller implementation by explicitly stating whether enableAdoption:true
only applies labels or also creates VMInstance resources for full management;
reference the actual controller flow symbols (enableAdoption, VMInstance,
ADOPTION_DESIGN and README adoption descriptions) and either (a) change the text
to say enableAdoption:true performs only labeling with the listed read-only
limitations, or (b) document the distinct modes/flags (label-only vs full
adoption) and the exact flag/field that triggers VMInstance creation so
operators know when Cozystack will take full lifecycle control.

In `@packages/apps/vm-import/README.md`:
- Around line 186-189: Add a brief note to README.md stating that setting vms:
[] (the default) produces no migration plan and is a no-op for execution; update
the table description for the `vms` entry and add a short sentence in the “How
to use” section clarifying that the Plan template only renders when `vms` is
non-empty so an empty `vms` list results in no migration actions.

In `@packages/apps/vm-instance/templates/vm.yaml`:
- Around line 67-68: The template uses the computed variable $dvName and the
lookup call (lookup "cdi.kubevirt.io/v1beta1" "DataVolume" $.Release.Namespace
$dvName) which depends on .Values.disks[].dvName being present in the generated
schema; add a dvName field to the disks entries in values.yaml (e.g., include
dvName: "" or null for each disk item) so the chart schema generator will pick
up and expose .Values.disks[].dvName in values.schema.json, ensuring the lookup
override works in the UI/validation.

In `@packages/apps/vm-instance/values.schema.json`:
- Around line 34-37: The dvName property was manually added to
values.schema.json (which is auto-generated) and will be overwritten; instead
add a dvName entry under the disks items in values.yaml with the same
description/comment so cozyvalues-gen will include it during generation, then
run make generate to regenerate values.schema.json; reference the dvName
property, the disks items structure in values.yaml, the cozyvalues-gen
generator, and the make generate step when making the change.

---

Nitpick comments:
In `@packages/apps/vm-import/docs/examples/simple-import.yaml`:
- Around line 13-17: Replace the hard-coded example secrets in the YAML (the
values for keys user, password, and thumbprint) with explicit placeholder tokens
so readers don't accidentally copy real credentials; update the "user" value to
something like <REPLACE_USERNAME>, "password" to <REPLACE_PASSWORD>, and
"thumbprint" to <REPLACE_THUMBPRINT> (keep the comment showing how to obtain a
real thumbprint intact), ensuring examples in the file reference these
placeholder tokens instead of real-looking secrets.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4104caed-4116-4600-aabf-3f7cd86ba5a9

📥 Commits

Reviewing files that changed from the base of the PR and between ff737ca and 8f3f95b.

⛔ Files ignored due to path filters (1)
  • packages/apps/vm-import/logos/vm-import.svg is excluded by !**/*.svg
📒 Files selected for processing (23)
  • hack/e2e-apps/vm-import.bats
  • packages/apps/vm-import/.helmignore
  • packages/apps/vm-import/ADOPTION_DESIGN.md
  • packages/apps/vm-import/Chart.yaml
  • packages/apps/vm-import/Makefile
  • packages/apps/vm-import/README.md
  • packages/apps/vm-import/charts/cozy-lib
  • packages/apps/vm-import/docs/adoption.md
  • packages/apps/vm-import/docs/examples/advanced-import.yaml
  • packages/apps/vm-import/docs/examples/monitoring-import.md
  • packages/apps/vm-import/docs/examples/simple-import.yaml
  • packages/apps/vm-import/docs/scripts/adopt-vm.sh
  • packages/apps/vm-import/templates/dashboard-resourcemap.yaml
  • packages/apps/vm-import/templates/migration.yaml
  • packages/apps/vm-import/templates/networkmap.yaml
  • packages/apps/vm-import/templates/plan.yaml
  • packages/apps/vm-import/templates/provider.yaml
  • packages/apps/vm-import/templates/storagemap.yaml
  • packages/apps/vm-import/templates/workloadmonitor.yaml
  • packages/apps/vm-import/values.schema.json
  • packages/apps/vm-import/values.yaml
  • packages/apps/vm-instance/templates/vm.yaml
  • packages/apps/vm-instance/values.schema.json
✅ Files skipped from review due to trivial changes (2)
  • packages/apps/vm-import/docs/examples/advanced-import.yaml
  • packages/apps/vm-import/docs/examples/monitoring-import.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/apps/vm-import/Chart.yaml
  • packages/apps/vm-import/docs/scripts/adopt-vm.sh
  • packages/apps/vm-import/templates/workloadmonitor.yaml
  • hack/e2e-apps/vm-import.bats
  • packages/apps/vm-import/.helmignore
  • packages/apps/vm-import/values.yaml

Comment thread packages/apps/vm-import/ADOPTION_DESIGN.md Outdated
Comment thread packages/apps/vm-import/docs/adoption.md Outdated
Comment thread packages/apps/vm-import/README.md Outdated
Comment on lines +67 to +68
{{- $dvName := default (printf "vm-disk-%s" $disk.name) $disk.dvName }}
{{- $dv := lookup "cdi.kubevirt.io/v1beta1" "DataVolume" $.Release.Namespace $dvName }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Template logic is sound, but depends on schema change that won't persist.

The dvName variable and lookup logic are correctly implemented—defaulting to vm-disk-<name> while allowing explicit overrides for imported VMs. The error message correctly references the computed name.

However, this template relies on .Values.disks[].dvName being available, which depends on the schema change in values.schema.json. Since that file is auto-generated and the manual edit gets removed (per pipeline warning), .dvName may not be exposed to users through the UI/validation.

Ensure the corresponding dvName field is added to values.yaml so the schema regeneration includes it.

Also applies to: 80-80

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/apps/vm-instance/templates/vm.yaml` around lines 67 - 68, The
template uses the computed variable $dvName and the lookup call (lookup
"cdi.kubevirt.io/v1beta1" "DataVolume" $.Release.Namespace $dvName) which
depends on .Values.disks[].dvName being present in the generated schema; add a
dvName field to the disks entries in values.yaml (e.g., include dvName: "" or
null for each disk item) so the chart schema generator will pick up and expose
.Values.disks[].dvName in values.schema.json, ensuring the lookup override works
in the UI/validation.

Comment on lines +34 to +37
"dvName": {
"description": "Explicit DataVolume name to reference. If set, used instead of the default vm-disk-<name> convention.",
"type": "string"
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Manual edit to auto-generated file will be lost.

The pipeline warning confirms that dvName was removed during automated generation. This file is produced by cozyvalues-gen from values.yaml and regenerated on make generate.

To persist this field, add the dvName property to values.yaml (under the disks items structure) with appropriate description comments. The generator will then include it in the schema automatically.

Based on learnings: "In reviews for cozystack, treat any values.schema.json files as auto-generated: they are produced from values.yaml by the cozyvalues-gen tool and regenerated on make generate. Do not suggest manual edits to these files."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/apps/vm-instance/values.schema.json` around lines 34 - 37, The
dvName property was manually added to values.schema.json (which is
auto-generated) and will be overwritten; instead add a dvName entry under the
disks items in values.yaml with the same description/comment so cozyvalues-gen
will include it during generation, then run make generate to regenerate
values.schema.json; reference the dvName property, the disks items structure in
values.yaml, the cozyvalues-gen generator, and the make generate step when
making the change.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/apps/vm-import/README.md (1)

1-213: Do not manually edit this README—it is auto-generated by cozyvalues-gen from values.yaml and will be overwritten on make generate.

This README is produced by the cozyvalues-gen tool (see packages/apps/vm-import/Makefile). Any manual edits to documentation, parameter descriptions, or the parameters table will be lost during regeneration. To update documentation, modify the parameter descriptions in values.yaml instead, and they will be included in the regenerated README on the next build.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/apps/vm-import/README.md` around lines 1 - 213, The README is
missing the auto-generated notice; add a clear top-of-file warning that this
file is auto-generated by cozyvalues-gen from values.yaml and will be
overwritten by `make generate` (matching the wording in the review), so any
edits should be made in values.yaml or the generator; reference the generator
and the Makefile target (cozyvalues-gen, values.yaml, Makefile) in the notice
and place it immediately under the main title so it appears first in
packages/apps/vm-import/README.md.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/apps/vm-import/README.md`:
- Around line 147-148: The README examples use the deprecated spec.running
field; update the kubectl patch vm examples that reference spec.running to use
spec.runStrategy instead (set runStrategy:"Always" to start continuously and
runStrategy:"Halted" to stop), i.e., locate the kubectl patch vm lines that
mention spec.running and replace them to patch spec.runStrategy with the
appropriate string values so examples align with KubeVirt v1.3+ and cozystack
runStrategy migration.

---

Nitpick comments:
In `@packages/apps/vm-import/README.md`:
- Around line 1-213: The README is missing the auto-generated notice; add a
clear top-of-file warning that this file is auto-generated by cozyvalues-gen
from values.yaml and will be overwritten by `make generate` (matching the
wording in the review), so any edits should be made in values.yaml or the
generator; reference the generator and the Makefile target (cozyvalues-gen,
values.yaml, Makefile) in the notice and place it immediately under the main
title so it appears first in packages/apps/vm-import/README.md.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2cdb45cf-be46-4021-be9e-3985cc4b5b28

📥 Commits

Reviewing files that changed from the base of the PR and between 8f3f95b and 95a8b76.

📒 Files selected for processing (4)
  • packages/apps/vm-import/ADOPTION_DESIGN.md
  • packages/apps/vm-import/README.md
  • packages/apps/vm-import/docs/adoption.md
  • packages/apps/vm-instance/values.yaml
✅ Files skipped from review due to trivial changes (1)
  • packages/apps/vm-import/ADOPTION_DESIGN.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/apps/vm-import/docs/adoption.md

Comment thread packages/apps/vm-import/README.md Outdated
@github-actions github-actions Bot added the area/virtualization Issues or PRs related to virtualization (kubevirt, cdi, vmi, vm-import) label May 12, 2026
@matthieu-robin

Copy link
Copy Markdown
Collaborator Author

Andrei Kvapil (@kvaps) Hello,

I did some modifications, could you please tell me what do you think about?

Thanks a lot

@matthieu-robin
Matthieu ROBIN (matthieu-robin) force-pushed the vmware-import branch 2 times, most recently from ac1e442 to c075a6f Compare June 22, 2026 20:42
Matthieu added 25 commits August 12, 2026 18:55
Drop the hardcoded GOARCH=amd64 (which silently mis-built arm64) and use
buildx-provided TARGETOS/TARGETARCH; bump the builder to golang:1.26 to
match sibling images.

Signed-off-by: Matthieu <matthieu@hidora.com>
…ease channel

These two images were substituted at the mutable :latest tag while every
other Forklift image is pinned to the release channel. Pin them to
$(FORKLIFT_CHANNEL) for reproducibility, and add a helm-unittest that
asserts the rendered deployment carries the pinned tags.

Signed-off-by: Matthieu <matthieu@hidora.com>
Every comparable chart ships a tests/ directory and a test: target; the
import charts had none. Add render/values suites (Plan target-namespace
and adoption annotations, controller securityContext, ForkliftController
feature flags, the vm-import ApplicationDefinition) and the matching
test: targets.

Signed-off-by: Matthieu <matthieu@hidora.com>
Replace company.com with example.com and drop the literal 8.8.8.8 from
the connectivity example.

Signed-off-by: Matthieu <matthieu@hidora.com>
The source.pvc field and the storageClass immutability/x-cozystack-options
annotations were in values.yaml and types.go intent, but the generated
artifacts predated the current cozyvalues-gen (v1.6.0) and controller-gen
conventions, so CI drift-checks failed. Regenerate values.schema.json, the
vm-disk-rd openAPISchema, the SourcePVC deepcopy methods, and the
storageClass XValidation marker.

Signed-off-by: Matthieu <matthieu@hidora.com>
The controller module declared go 1.23 while its Dockerfile builder and every other module in the repo use 1.26.4; align it so the go directive matches the toolchain that actually builds it.

Signed-off-by: Matthieu <matthieu@hidora.com>
…abel

The advanced-import example thumbprint was one octet short of a valid SHA-1 fingerprint (19 vs 20); pad it. Indent the network-configuration check in adopt-vm.sh so it reads as a sub-step of [4/5] instead of an unnumbered step.

Signed-off-by: Matthieu <matthieu@hidora.com>
Keep the local French-language project summary out of the PR alongside the tracked English docs.

Signed-off-by: Matthieu <matthieu@hidora.com>
…ant import

The raw-copy branch (skipGuestConversion=true) set Plan.spec.targetNamespace directly from the tenant-supplied tenantNamespace, bypassing the resolveTargetNamespace guard the adoption-controller path relies on. A tenant could then direct a Forklift import into another tenant's namespace.

Mirror the controller guard in the chart: honor a cross-namespace tenantNamespace only for an admin-directed import (a release outside a tenant-* namespace); a tenant-* release is confined to its own namespace, on both the target-namespace annotation and the raw-copy spec.targetNamespace. Rework the isolation tests into a negative regression plus an admin positive case, and align the tenantNamespace schema description.

Signed-off-by: Matthieu <matthieu@hidora.com>
Remove ~155 lines of dead Helm-adoption/rollback code (prepareVMForHelmAdoption, prepareDataVolumesForHelmAdoption, removeHelmLabelsFromVM, removeHelmLabelsFromDataVolumes) and the unused dvNames tracking, left over from the superseded Helm-takeover approach.

Adoption outcomes were log-only and invisible to tenants and operators. Wire an EventRecorder and emit a Warning (AdoptionFailed) on failure and a Normal (Adopted) on success, referencing the source VM; grant events create/patch RBAC. Add fake-client unit tests covering getTargetNamespace (the cross-tenant guard end-to-end via the Plan annotation), resolvePlan, isMigrationComplete and isAdoptionEnabled.

Signed-off-by: Matthieu <matthieu@hidora.com>
…Renovate

The operator/component images resolved to a floating release-2.11 channel tag with no digest, so pulls were non-reproducible and air-gapped installs could not resolve them; must-gather and console-plugin were pinned to a release-2.11 tag that does not exist upstream (they version independently: v2.7.1 / release-v2.6.7).

Pin all 16 deployed image refs to immutable @sha256 digests in both the Makefile substitution and the rendered manifest, and add a Renovate custom manager over both so the digests stay fresh. Document mirroring for air-gapped installs and update the image-pinning test.

Signed-off-by: Matthieu <matthieu@hidora.com>
vm-disk is tenant-facing and rendered DataVolumes are applied by the privileged cozy-fluxcd/flux service account (tenant HelmReleases carry no serviceAccountName and helm-controller sets no default), which is authorized to create datavolumes/source in any namespace. A tenant setting source.pvc.namespace to another tenant's namespace therefore clones foreign data, bypassing CDI's cross-namespace clone authorization — confirmed on a live cluster.

Remove source.pvc.namespace from the tenant-facing API (values/schema/README/types/cozyrd) and always render the clone source in the release namespace. The adoption controller, which legitimately needs a cross-namespace source on the virt-v2v path, now performs that clone itself (privileged) into the target namespace first and then creates a same-namespace VMDisk, so the tenant API never carries a cross-namespace pointer.

Signed-off-by: Matthieu <matthieu@hidora.com>
networkMap[].destinationNamespace was the last tenant-supplied namespace
in the chart rendered verbatim: a tenant could set destinationType=multus
with a NetworkAttachmentDefinition living in another tenant's namespace
and have the imported VM's NIC attached there. Multus resolves
namespace/name NAD references cross-namespace by default, and with
enableAdoption=false the Forklift VM is never adopted, so the attachment
is permanent.

Apply the same guard plan.yaml already uses for targetNamespace: a
release inside a tenant-* namespace is confined to its own namespace,
while an admin-directed import from a privileged namespace still needs
to point at the tenant's NAD and keeps honoring the value.

The namespace field is now always rendered rather than emitted only when
the value is set, so the confinement is visible in the manifest.

Signed-off-by: Matthieu <matthieu@hidora.com>
…mespace

dashboard-resourcemap.yaml called cozy-lib.rbac.subjectsForTenantAndAccessLevel
with .Release.Namespace unconditionally, and cozy-lib rejects any namespace
without the tenant- prefix as a tenant identifier. Rendering the chart in
cozy-vm-import therefore died with

  'cozy-vm-import' is not a valid tenant identifier

which made the documented admin-directed cross-namespace import unreachable:
from a tenant namespace the targetNamespace guard confines the import locally,
and from a privileged namespace the chart did not render at all.

A privileged deployment has no tenant subjects to bind, so skip the Role and
RoleBinding there instead of failing. The plan.yaml guard is unchanged; it now
has a namespace it can actually be exercised from.

The existing suites each restrict rendering to one template, so add a suite for
dashboard-resourcemap.yaml itself — without the gate it errors out with the
message above.

Signed-off-by: Matthieu <matthieu@hidora.com>
The controller selected any VirtualMachine carrying the bare "plan" label,
resolved that UID to a Plan cluster-wide, and derived the adoption target
namespace from the resolved Plan — never from the VM's own namespace, and
with no check that the VM belongs to the Plan at all.

cozy:tenant:super-admin:base grants tenant super-admins full access to
kubevirt VirtualMachines, so a tenant could create a raw VM labelled with
the UID of another tenant's Plan and have this cluster-privileged
controller create VMDisks and a VMInstance inside the victim's namespace,
against the victim's quota. resolveTargetNamespace does not defend against
this: it confines adoption to the resolved Plan's namespace, and the
attacker picks which Plan that is.

Require the VM to live either in the Plan's namespace or in the namespace
the Plan targets — the only two places Forklift ever creates it. As defense
in depth, when the VM carries the Forklift vmID label, require that id to
appear in the Plan's spec.vms; a VM without the label still adopts, so this
does not depend on which Forklift release stamped it.

resolvePlan now returns the Plan itself, since the validation needs its
spec rather than just its name and namespace.

Signed-off-by: Matthieu <matthieu@hidora.com>
…he source VM

Same-namespace adoption deleted the Forklift VM first, to clear the name
the managed VMInstance renders its VirtualMachine under, and only then
created the VMInstance. A create rejected by a webhook, a tenant quota or
a transient API error therefore destroyed the imported VM for good:
getForkliftVMs keys reconciliation on that VM, so the next loop never saw
it again. The end state was a running VM gone, orphaned VMDisks, no
VMInstance, and a Warning event attached to a deleted object.

Create the VMInstance first and release the source VM only once it exists.
A failed create now leaves everything in place and retries on the next
loop. Releasing the source VM moves into releaseSourceVM, which the
idempotency path also calls, so a delete that fails after a successful
create is replayed instead of leaving the VM re-listed every 15s forever.

The cost is a brief window where the VirtualMachine rendered by the
vm-instance HelmRelease shares a name with the Forklift VM still standing.
The delete follows immediately, well before cozystack-engine materialises
the HelmRelease, and if helm-controller does reconcile in between the
install converges once the source VM is gone — a Flux retry rather than a
destroyed VM.

Signed-off-by: Matthieu <matthieu@hidora.com>
The replicas value was operator-settable on a controller that reconciles by
mutating cluster state — deleting Forklift VMs, creating VMInstances,
cloning DataVolumes — with no leader election. The default of 1 was safe,
but any value above it puts two controllers on the same VM, racing to
delete, create and clone it twice.

Pin it until leader election exists.

Signed-off-by: Matthieu <matthieu@hidora.com>
Both Providers carried helm.sh/resource-policy: keep, justified as making
them reusable for future imports. They are named after the release, so no
other release ever reuses them; all the annotation achieves is leaving the
source Provider and its credential-Secret reference behind on uninstall,
and making a VMImport of the same name unreinstallable — Helm refuses to
adopt a resource it left behind, with invalid ownership metadata.

Signed-off-by: Matthieu <matthieu@hidora.com>
…the controller does

`set -e` was defeated on every read: `|| echo` and `2>/dev/null` turned an
auth or API failure into empty output, so the script happily wrote a values
file with no disks, no networks and a wrong run state instead of aborting.

Read the VM once into a variable, without a fallback, and add pipefail.
Then match the controller on what it extracts: runStrategy is authoritative
with .spec.running as the deprecated fallback, and disks come from either a
dataVolume or the persistentVolumeClaim the CDI volume populator backs them
with — the shape Forklift actually produces when it creates the VM itself,
which the script ignored entirely.

The generated values file now emits runStrategy rather than the removed
`running` field.

Signed-off-by: Matthieu <matthieu@hidora.com>
…ts on

The docs and the helper script read `forklift.konveyor.io/plan` and treated
its value as a Plan name, while the controller selects on the bare `plan`
key whose value is the Plan UID — the label Forklift release-2.11 actually
stamps, and the one validated end-to-end. Every documented `kubectl get vm
-l ...` command therefore matched nothing.

Resolve the UID first in the examples, and while in these files correct the
neighbouring claims that no longer hold: the controller polls rather than
watches, it verifies a VM belongs to the Plan it claims, it deletes the
source VM on same-namespace adoption rather than always labelling it, the
adopted VMInstance carries `vm-import.cozystack.io/plan`, and the RBAC list
was missing the VM delete, the clone source authorization and the events
the controller now needs.

Signed-off-by: Matthieu <matthieu@hidora.com>
The Secret example named administrator@vsphere.local and nothing said what
privileges a migration actually requires, so the path of least resistance
was to hand Forklift a vCenter Administrator. That credential is read by
the cluster-privileged Forklift controller, so the account's reach is the
credential's blast radius.

Name a dedicated account in the examples and add a prerequisites section
explaining why a read-only account is not sufficient either: reading disks
through the VDDK, powering the source VM off at cutover, and the snapshots
a warm migration takes are all writes, while only inventory collection is
genuinely read-only. A read-only account therefore lists the VMs and then
fails at transfer time.

The section points at the MTV Prerequisites chapter for the authoritative
privilege list rather than restating it here, since that list tracks the
Forklift release and would rot in this repository. It also covers the
second credential set migrationHosts introduces against the ESXi hosts.

Added as 3.7 rather than inserted earlier so the existing cross-references
into section 3 keep pointing at the right subsections.

Signed-off-by: Matthieu <matthieu@hidora.com>
fullnameOverride reached the tenant-facing schema so the adoption
controller can name the rendered VirtualMachine after the VM it replaces.
It feeds virtual-machine.fullname, which is metadata.name on the
VirtualMachine, and nothing stopped a tenant from editing it afterwards:
Helm would then prune the VirtualMachine and recreate it under the new
name, re-rolling a running VM. Disks survive, since they are DataVolumes
owned by their own vm-disk releases, so this is disruption rather than
data loss.

Keeping the field out of the schema is not an option — the API rejects
what the schema does not declare, and the controller has to set it — so
guard it with a CEL transition rule instead, the same mechanism vm-disk
uses for storageClass. A transition rule is not evaluated when the old
object lacks the field, so the controller can still stamp it at creation
and only later edits are refused.

Signed-off-by: Matthieu <matthieu@hidora.com>
…ates

The dashboard Role covered providers, plans, migrations and the two maps,
but not hosts. A tenant using migrationHosts to route disk transfer over
a per-ESXi-host address therefore created Host objects its own dashboard
could not display.

Adding the resource alone would not have been enough: the rule is scoped
by resourceNames, which takes no patterns, and host.yaml names each Host
<release>-<id> rather than one of the three names already listed. Range
over the declared migrationHosts to enumerate them.

Signed-off-by: Matthieu <matthieu@hidora.com>
Plans are searched cluster-wide because a Plan may live in a different
namespace than the VM it migrated, so resolvePlan listed every Plan in
the cluster and scanned it linearly. It runs once per candidate VM in
getForkliftVMs and again in adoptVM, which made a reconcile pass cost
O(pending VMs x all Plans) — repeated every 15 seconds.

Build the UID to Plan index from a single List the first time a pass
needs it, and drop it at the top of reconcile so a Plan edited between
ticks is still picked up. Lookups after the first are then free, adoptVM
included.

The lookup stays lazy rather than eager so resolvePlan keeps working
when called outside a reconcile pass, which is how the tests exercise it.

Signed-off-by: Matthieu <matthieu@hidora.com>
PROJECT_SUMMARY.md was 245 lines of development narrative — what was
built, what was fixed along the way, what is left to productize — shipped
inside an application package that should carry operator documentation.
Its "fixes made" section already duplicated section 6 of the migration
guide, and its follow-up list duplicated section 7.

One part was not narrative and not duplicated: the measured comparison of
the two transfer paths, which is what tells an operator whether
skipGuestConversion is worth arranging. Move that table into the guide,
next to the architecture it compares, and delete the rest.

Signed-off-by: Matthieu <matthieu@hidora.com>
The package still called $(call settag,$(TAG)), a macro removed from main in
ed0297e; it expands to nothing, so buildx was handed a bare "repo:" and
failed with "invalid reference format". Every Build job on the PR has been red
on this since, and the recent rebase did not clear it because the stale call
lives in this Makefile rather than in the shared includes.

Switch to image-tags/cache-args like the sibling controllers.

Assisted-By: Claude
Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
Installing Forklift unconditionally means every existing iaas cluster picks up
a large operator manifest, its own CRDs and broad RBAC on upgrade, plus an
adoption controller holding cluster-wide create on VMInstances -- for a feature
only a cluster migrating off vSphere needs.

Gate the three packages behind bundles.enabledPackages instead, alongside
gpu-operator and vm-default-images.

The accompanying test pins the rendered package count: absence of a single
document cannot be asserted directly here, because a documentSelector matching
nothing fails rather than counting zero, and containsDocument with not passes
whenever any one document differs.

Assisted-By: Claude
Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
…place

Forklift owns every migrated PVC by the VirtualMachine it creates -- EnsureVM
patches each one with a blockOwnerDeletion ownerReference, upstream comment
"so that they'll be cleaned up when the VirtualMachine is removed". Adoption
then deleted that VM to free the name the VMInstance takes, and the garbage
collector took the disk with it: the clone lost its source mid-flight, the
DataVolume sat in CloneWithoutSource and the VM in ErrorPvcNotFound, with a
completed transfer destroyed by the adoption meant to consume it.

The code assumed the opposite -- "KubeVirt does not delete PVCs with a VM" --
which holds for an ordinary VM but not for a Forklift import.

Two changes, both scoped to same-namespace adoption:

Detach before deleting. Only the volumes this adoption consumes, and only
ownerReferences pointing at this VM, so an import that never reaches adoption
keeps Forklift's cleanup of abandoned migrations.

Stop cloning. On the raw-copy path Forklift already wrote the disk into the
target namespace with the right size, StorageClass and content, so the clone
copied a whole disk to stand still and left two full copies behind for good.
The disks keep referencing the imported PVC; vm-instance already falls back to
a bare PersistentVolumeClaim when no DataVolume of that name exists. The
cross-namespace virt-v2v path is unchanged -- there the copy is the point.

Assisted-By: Claude
Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
@kvaps

Copy link
Copy Markdown
Member

On the enableAdoption default

enableAdoption defaults to true (plan.yaml:19, and isAdoptionEnabled defaults the same way), and same-namespace adoption deletes the Forklift VM to free the name the VMInstance takes.

Forklift puts an ownerReference on each migrated PVC pointing at that VM — EnsureVM in pkg/controller/plan/kubevirt.go patches them with blockOwnerDeletion, described upstream as being there "so that they'll be cleaned up when the VirtualMachine is removed". So deleting the VM has the garbage collector remove the imported PVCs along with it.

Is the intent that an operator running a first production migration sets enableAdoption: false and adopts afterwards via adopt-vm.sh, or should the default itself be false until the ownership handover is in place? Asking because the default is the path most people will take.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/virtualization Issues or PRs related to virtualization (kubevirt, cdi, vmi, vm-import) kind/feature Categorizes issue or PR as related to a new feature size/XXL This PR changes 1000+ lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants