PR 1040 review

harvester-ui-extension PR 1040 (Copilot, driven by a110605) at head 535a606, reviewed 2026-11-05. It blocks saving a VM or a template version when User Data or Network Data doesn't parse as YAML. Fixes harvester 10980. Nothing is posted.

Preview: harvesterpr1040r.mf-created-dashboard.ui.rancher.space. Built from adc1ea4, which is head 535a606 merged onto main 12c28c0 in a local clone, backend Harvester .118 (v1.9.0-rc4). PR + fix: a0ed496, local only, not pushed. Baseline: the shared harvester-main, rebuilt today at 12c28c0.

Verdict: request changes

Test it yourself

These previews write to the real .118. My runs stopped writes in the browser, but yours won't. A save that goes through creates a real VM, so use Cancel, or delete it afterwards.
  1. Log in as admin with HARVESTER_PASSWORD from .env, and open PR 1040: Create VM.
  2. Fill in the name, CPU, memory and an image, then go to Advanced Options.
  3. Replace User Data with the script below and click Create. PR: "Invalid User Data YAML syntax". On PR + fix and main the same click really creates the VM, so delete it afterwards.
  4. Network Data: type a few words and click Create. PR + fix: "Invalid Network Data YAML syntax". On the PR and main this really creates the VM too.
#!/bin/bash
apt-get update
# start it
systemctl enable --now nginx

No VM on .118 holds a script, so I reproduced the edit lock by rewriting the secret's GET response in the browser. ttt itself wasn't changed.

Findings
1
Blocker

Both of main's outcomes matter here. In all 8 script and format cases I ran, what main sends intact is what fails to parse as YAML, because getUserData falls into its catch and returns the text as typed. So this PR blocks exactly the non-YAML content that works today, and lets through the scripts main already changes (FYI 1).

PR: the running VM <code>ttt</code> holding a script, Save clicked with nothing changed. Blocked, no request sent. (Form cropped to the banner and the editor.)
PR: the running VM ttt holding a script, Save clicked with nothing changed. Blocked, no request sent. (Form cropped to the banner and the editor.)
PR: a Windows VM with Cloudbase-Init <code>#ps1_sysnative</code> user data. Blocked. (Form cropped.)
PR: a Windows VM with Cloudbase-Init #ps1_sysnative user data. Blocked. (Form cropped.)

Suggested inline comment, on line 432

User data isn't always YAML. Cloud-init picks the format from the first line, and #! scripts, Content-Type: multipart/mixed, ## template: jinja and Cloudbase-Init's #ps1_sysnative are all valid. The tip above this editor also says it can run a script. main sends these as typed, and here they're blocked with "Invalid User Data YAML syntax".

It reaches existing VMs too. A VM with a script like this can't be saved anymore, even when nothing in its user data was touched.

Can we parse only when the first line is #cloud-config and let everything else through as it does today? hasCloudConfigComment looks close, but it also matches a #cloud-config inside a MIME part, so checking the first line is safer.

Attach the two screenshots above to this comment. Docs: cloud-init user-data formats, Cloudbase-Init userdata. KubeVirt's startup scripts page also shows a #!/bin/bash script as cloudInitNoCloud user data.

2
Should fix

The issue says "input arbitrary words". The check only catches words that break the surrounding structure, like the issue video, where they follow the default template's mapping. Network Data starts empty, so words there are a valid YAML string and go through as typed.

Suggested inline comment, on line 440

jsyaml.load doesn't throw on plain words, because they're a valid YAML string. So words in Network Data, which starts empty, still save, and that's the issue's case. The same goes for user data with #cloud-config and only words. Checking that the result is a mapping (empty is fine) covers both.

3

The same method is in both pages, with no unit test

Suggestion

Suggested inline comment, on line 251 of the template page

This is the same method as in the VM page. Both pages already use VM_MIXIN, which imports js-yaml too. Could it live there? The changes above then go in one place, and it's easy to add a unit test. This path has none yet.

The fix I tested, in the mixin (24 lines added, 38 removed across the three files)
validateCloudInit() {
  const isMapping = (text) => {
    try {
      const doc = jsyaml.load(text);

      return doc === undefined || doc === null || (typeof doc === 'object' && !Array.isArray(doc));
    } catch (e) {
      return false;
    }
  };

  if (/^\s*#cloud-config(\s|$)/.test(this.userScript || '') && !isMapping(this.userScript)) {
    this.errors.push(this.t('harvester.virtualMachine.cloudConfig.user.invalidYaml'));
  }

  if (this.networkScript && !isMapping(this.networkScript)) {
    this.errors.push(this.t('harvester.virtualMachine.cloudConfig.network.invalidYaml'));
  }
},
4

The description points QA at the wrong behaviour

PR body: Changes, Test Steps
Description

This goes in the main comment because it has no line to anchor to. It matters because QA works from these steps.

What each build sends (18 cases through the real form)
What was typedmainPRPR + fix
Should block
Default template plus a word (issue video)Sent as typedBlockedBlocked
#cloud-config plus only wordsSent as typedSent as typedBlocked
Network Data: words (the box starts empty)Sent as typedSent as typedBlocked
Network Data: bad indentationSent as typedBlockedBlocked
Windows: bad cloud-config (votdev's ask)Sent as typedBlockedBlocked
Should save
Valid cloud-configSent as typedSent as typedSent as typed
Network Data: valid v1Sent as typedSent as typedSent as typed
Windows: valid cloud-configSent as typedSent as typedSent as typed
#!/bin/bash, comment between commandsSent as typedBlockedSent as typed
#!/bin/bash with a heredocSent as typedBlockedSent as typed
MIME multipartSent as typedBlockedSent as typed
## template: jinja with an if blockSent as typedBlockedSent as typed
Windows: #ps1_sysnativeSent as typedBlockedSent as typed
Not the ask here (see FYI)
PR step 3 snippet. It is valid YAML, so no check can block itSent, changedSent, changedSent, changed
Only words, no header. Cloud-init can't tell what it isSent, changedSent, changedSent, changed
#!/bin/bash, comment before commandsSent, changedSent, changedSent, changed
#!/bin/bash with a colon in a stringSent, changedSent, changedSent, changed
Windows: rem cmdSent, changedSent, changedSent, changed

Green is the right result, red the wrong one, grey not judged. "Sent, changed" means the form altered the text before sending it.

Other paths
PathmainPRPR + fix
Existing VM with a script in its secret, Save with nothing changedSentBlockedSent
Template version: default template plus a wordSentBlockedBlocked
Template version: a scriptSentBlockedSent
Template version: valid cloud-configSentSentSent
Template version: blocked, fixed, saved againNo block to retrySaved, payload same as a direct saveSaved, payload same as a direct save
VM: blocked, fixed, saved againNo block to retrySavedSaved
VM: bad user data, then Edit as YAML and CreateSentSentSent
PR: the case from the issue video, the default template plus a word. Blocked, as the issue asks. (Form cropped.)
PR: the case from the issue video, the default template plus a word. Blocked, as the issue asks. (Form cropped.)
FYI, left out of the suggested text
1

main corrupts scripts that happen to parse as YAML

Same on main

When a script happens to parse as YAML, for example one with no comment between its commands, the form adds #cloud-config and can fold its lines. Cloud-init then won't run it as a script. Example sent by main, the PR and the fix alike:

#cloud-config
#!/bin/bash
# install nginx
apt-get update systemctl enable --now nginx

Not this PR's bug, and the fix leaves it alone. It's worth its own issue, and I can file one if you want.

2

Edit as YAML skips the check

CruResource yaml mode
Same on main

With bad user data, Edit as YAML then Create sends the VM on all three builds. The other form validators don't run there either, so I wouldn't ask for it in this PR. The YAML only shows a secretRef, and no secret write showed up in my stubbed run. I didn't work out whether that's the stub or the app.

3

Cloud Configuration Templates aren't validated

harvesterhci.io.cloudtemplate.vue, VirtualMachineCloudConfig/index.vue
Not run

From reading the code, that page and the "create new" modal save the text without parsing it. Bad text can still become a template, and it only blocks later at VM save. The issue is about VMs, so this is out of scope.

4

Smaller notes

Note
Suggested review (request changes)

Main comment

I tested this merged onto current main, on a real v1.9 cluster. The case from the issue is blocked now, and the form saves again once the YAML is fixed, on both the VM and template forms. The check is too broad for user data and misses plain words though, see the comments.

Could you also update the description? It still says Windows is skipped, which 535a606 removed. The snippet in step 3 is valid YAML, so it doesn't block and saves password: test - invalid indentation. Adding a word after the default template, like in the issue video, does block.

Plus the three inline comments above. No preview link and no code blocks, as usual.

What was and wasn't checked