mirror of
https://github.com/lima-vm/lima.git
synced 2026-10-28 11:44:35 +08:00
Support provision feature on Windows VM
Current support has some caveats: - boot mode isn't supported since Windows VM doesn't have an equivalent step to Cloud-init's init stage - yq mode isn't supported since Windows VM doesn't have yq engine by default - all data mode files are stored under C:\ProgramData\Lima\provision\provision.data - skipDefaultDependencyResolution in dependency mode is currently ignored Signed-off-by: mie313 <mie313.tanaka@gmail.com>
This commit is contained in:
1 parent
af91a4b1e0
commit
d14213b186
12 files changed
+359
-63
No files matched your search
+41
-24
@@ -432,30 +432,9 @@ func GenerateISO9660(ctx context.Context, drv driver.Driver, instDir, name strin
|
||||
})
|
||||
}
|
||||
|
||||
for i, f := range instConfig.Provision {
|
||||
switch f.Mode {
|
||||
case limatype.ProvisionModeSystem, limatype.ProvisionModeUser, limatype.ProvisionModeDependency:
|
||||
layout = append(layout, iso9660util.Entry{
|
||||
Path: fmt.Sprintf("provision.%s/%08d", f.Mode, i),
|
||||
Reader: strings.NewReader(*f.Script),
|
||||
})
|
||||
case limatype.ProvisionModeData:
|
||||
layout = append(layout, iso9660util.Entry{
|
||||
Path: fmt.Sprintf("provision.%s/%08d", f.Mode, i),
|
||||
Reader: strings.NewReader(*f.Content),
|
||||
})
|
||||
case limatype.ProvisionModeYQ:
|
||||
layout = append(layout, iso9660util.Entry{
|
||||
Path: fmt.Sprintf("provision.%s/%08d", f.Mode, i),
|
||||
Reader: strings.NewReader(*f.Expression),
|
||||
})
|
||||
case limatype.ProvisionModeBoot:
|
||||
continue
|
||||
case limatype.ProvisionModeAnsible:
|
||||
continue
|
||||
default:
|
||||
return "", fmt.Errorf("unknown provision mode %#q", f.Mode)
|
||||
}
|
||||
layout, err = appendProvisionEntries(layout, instConfig.Provision)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if guestAgentBinary != "" {
|
||||
@@ -582,6 +561,39 @@ func writeCIDataDir(rootPath string, layout []iso9660util.Entry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func appendProvisionEntries(layout []iso9660util.Entry, provisions []limatype.Provision) ([]iso9660util.Entry, error) {
|
||||
for i, f := range provisions {
|
||||
switch f.Mode {
|
||||
case limatype.ProvisionModeSystem, limatype.ProvisionModeUser, limatype.ProvisionModeDependency:
|
||||
layout = append(layout, iso9660util.Entry{
|
||||
Path: fmt.Sprintf("provision.%s/%08d", f.Mode, i),
|
||||
Reader: strings.NewReader(*f.Script),
|
||||
})
|
||||
// Boot commands are not treated as script files in cidata.iso.
|
||||
// Instead of that, they are stored in TemplateArgs.BootCmds then embedded in bootCmd field in user-data.
|
||||
// For Windows VM, there is no boot step where we can run commands.
|
||||
case limatype.ProvisionModeBoot:
|
||||
continue
|
||||
case limatype.ProvisionModeData:
|
||||
layout = append(layout, iso9660util.Entry{
|
||||
Path: fmt.Sprintf("provision.%s/%08d", f.Mode, i),
|
||||
Reader: strings.NewReader(*f.Content),
|
||||
})
|
||||
case limatype.ProvisionModeYQ:
|
||||
layout = append(layout, iso9660util.Entry{
|
||||
Path: fmt.Sprintf("provision.%s/%08d", f.Mode, i),
|
||||
Reader: strings.NewReader(*f.Expression),
|
||||
})
|
||||
case limatype.ProvisionModeAnsible:
|
||||
continue
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown provision mode %#q", f.Mode)
|
||||
}
|
||||
}
|
||||
|
||||
return layout, nil
|
||||
}
|
||||
|
||||
func GenerateWindowsISO(ctx context.Context, instDir, name string, instConfig *limatype.LimaYAML, udpDNSLocalPort, tcpDNSLocalPort, vsockPort int, virtioPort string, noCloudInit, rosettaEnabled, rosettaBinFmt bool) (string, error) {
|
||||
args, err := templateArgs(ctx, true, instDir, name, instConfig, udpDNSLocalPort, tcpDNSLocalPort, vsockPort, virtioPort, noCloudInit, rosettaEnabled, rosettaBinFmt)
|
||||
if err != nil {
|
||||
@@ -608,6 +620,11 @@ func GenerateWindowsISO(ctx context.Context, instDir, name string, instConfig *l
|
||||
return "", fmt.Errorf("failed to create Windows ISO entries: %w", err)
|
||||
}
|
||||
|
||||
layout, err = appendProvisionEntries(layout, instConfig.Provision)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
layout = append(layout, iso9660util.Entry{
|
||||
Path: "ssh_authorized_keys",
|
||||
Reader: strings.NewReader(strings.Join(args.SSHPubKeys, "\n")),
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
package cidata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
@@ -11,6 +13,8 @@ import (
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
"github.com/lima-vm/lima/v2/pkg/iso9660util"
|
||||
"github.com/lima-vm/lima/v2/pkg/limatype"
|
||||
"github.com/lima-vm/lima/v2/pkg/networks"
|
||||
)
|
||||
|
||||
@@ -60,3 +64,50 @@ func TestSetupInvalidEnv(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, envs[envKey], envValue)
|
||||
}
|
||||
|
||||
func TestAppendProvisionEntries(t *testing.T) {
|
||||
scriptBoot := "Write-Host boot"
|
||||
scriptSystem := "Write-Host system"
|
||||
contentData := "hello"
|
||||
exprYQ := ".foo=1"
|
||||
provisions := []limatype.Provision{
|
||||
{Mode: limatype.ProvisionModeBoot, Script: &scriptBoot},
|
||||
{Mode: limatype.ProvisionModeSystem, Script: &scriptSystem},
|
||||
{Mode: limatype.ProvisionModeData, ProvisionData: limatype.ProvisionData{Content: &contentData}},
|
||||
{Mode: limatype.ProvisionModeYQ, Expression: &exprYQ},
|
||||
}
|
||||
|
||||
layout, err := appendProvisionEntries(nil, provisions)
|
||||
assert.NilError(t, err)
|
||||
// Boot file is not added into the ISO file.
|
||||
assert.Assert(t, !hasEntryPath(layout, "provision.boot/00000000"))
|
||||
assert.Assert(t, hasEntryPath(layout, "provision.data/00000002"))
|
||||
assert.Assert(t, hasEntryPath(layout, "provision.yq/00000003"))
|
||||
|
||||
gotData, err := readEntry(layout, "provision.data/00000002")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, gotData, contentData)
|
||||
}
|
||||
|
||||
func hasEntryPath(layout []iso9660util.Entry, path string) bool {
|
||||
for _, e := range layout {
|
||||
if e.Path == path {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func readEntry(layout []iso9660util.Entry, path string) (string, error) {
|
||||
for _, e := range layout {
|
||||
if e.Path != path {
|
||||
continue
|
||||
}
|
||||
b, err := io.ReadAll(e.Reader)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
return "", fmt.Errorf("entry not found: %s", path)
|
||||
}
|
||||
+7
-39
@@ -208,12 +208,12 @@ func ExecuteTemplateCloudConfig(args *TemplateArgs) ([]byte, error) {
|
||||
return textutil.ExecuteTemplate(cloudConfigYaml, args)
|
||||
}
|
||||
|
||||
func ExecuteTemplateCIDataISO(args *TemplateArgs) ([]iso9660util.Entry, error) {
|
||||
func executeTemplateWalkDir(args *TemplateArgs, fsys fs.FS, root string) ([]iso9660util.Entry, error) {
|
||||
if err := ValidateTemplateArgs(args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fsys, err := fs.Sub(templateFS, templateFSRoot)
|
||||
fsys, err := fs.Sub(fsys, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -251,42 +251,10 @@ func ExecuteTemplateCIDataISO(args *TemplateArgs) ([]iso9660util.Entry, error) {
|
||||
return layout, nil
|
||||
}
|
||||
|
||||
func ExecuteTemplateCIDataISO(args *TemplateArgs) ([]iso9660util.Entry, error) {
|
||||
return executeTemplateWalkDir(args, templateFS, templateFSRoot)
|
||||
}
|
||||
|
||||
func ExecuteTemplateWindowsISO(args *TemplateArgs) ([]iso9660util.Entry, error) {
|
||||
fs := windowsTemplateFS
|
||||
root := windowsTemplateFSRoot
|
||||
|
||||
// Execute template for autounattend.xml
|
||||
xmlTemplate, err := fs.ReadFile(path.Join(root, "autounattend.xml"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
xmlfile, err := textutil.ExecuteTemplate(string(xmlTemplate), args)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to render autounattend.xml: %w", err)
|
||||
}
|
||||
|
||||
// Execute template for powershell script file
|
||||
ps1Template, err := fs.ReadFile(path.Join(root, "first_logon.ps1"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ps1file, err := textutil.ExecuteTemplate(string(ps1Template), args)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to render ps1 file: %w", err)
|
||||
}
|
||||
|
||||
layout := []iso9660util.Entry{
|
||||
{
|
||||
Path: "autounattend.xml",
|
||||
Reader: bytes.NewReader(xmlfile),
|
||||
},
|
||||
{
|
||||
Path: "first_logon.ps1",
|
||||
Reader: bytes.NewReader(ps1file),
|
||||
},
|
||||
}
|
||||
|
||||
return layout, nil
|
||||
return executeTemplateWalkDir(args, windowsTemplateFS, windowsTemplateFSRoot)
|
||||
}
|
||||
@@ -224,6 +224,10 @@ func TestExecuteTemplateWindowsISO(t *testing.T) {
|
||||
name: "windows server 2025",
|
||||
args: &TemplateArgs{
|
||||
Name: "windows",
|
||||
UID: 501,
|
||||
Home: "/home/foo.guest",
|
||||
Shell: "powershell.exe",
|
||||
SSHPubKeys: []string{"ssh-rsa dummy foo@example.com"},
|
||||
Arch: limatype.X8664,
|
||||
User: "windows-user",
|
||||
WindowsInitialPassword: "dummy-password",
|
||||
@@ -245,6 +249,10 @@ func TestExecuteTemplateWindowsISO(t *testing.T) {
|
||||
name: "windows 11 x86_64",
|
||||
args: &TemplateArgs{
|
||||
Name: "windows",
|
||||
UID: 501,
|
||||
Home: "/home/foo.guest",
|
||||
Shell: "powershell.exe",
|
||||
SSHPubKeys: []string{"ssh-rsa dummy foo@example.com"},
|
||||
Arch: limatype.X8664,
|
||||
User: "windows-user",
|
||||
WindowsInitialPassword: "dummy-password",
|
||||
@@ -265,6 +273,10 @@ func TestExecuteTemplateWindowsISO(t *testing.T) {
|
||||
name: "legacyBIOS",
|
||||
args: &TemplateArgs{
|
||||
Name: "windows",
|
||||
UID: 501,
|
||||
Home: "/home/foo.guest",
|
||||
Shell: "powershell.exe",
|
||||
SSHPubKeys: []string{"ssh-rsa dummy foo@example.com"},
|
||||
Arch: limatype.X8664,
|
||||
User: "windows-user",
|
||||
WindowsInitialPassword: "dummy-password",
|
||||
@@ -287,6 +299,10 @@ func TestExecuteTemplateWindowsISO(t *testing.T) {
|
||||
name: "disable TPM on Windows 11",
|
||||
args: &TemplateArgs{
|
||||
Name: "windows",
|
||||
UID: 501,
|
||||
Home: "/home/foo.guest",
|
||||
Shell: "powershell.exe",
|
||||
SSHPubKeys: []string{"ssh-rsa dummy foo@example.com"},
|
||||
Arch: limatype.X8664,
|
||||
User: "windows-user",
|
||||
WindowsInitialPassword: "dummy-password",
|
||||
@@ -308,6 +324,10 @@ func TestExecuteTemplateWindowsISO(t *testing.T) {
|
||||
name: "Windows 11 arm64",
|
||||
args: &TemplateArgs{
|
||||
Name: "windows",
|
||||
UID: 501,
|
||||
Home: "/home/foo.guest",
|
||||
Shell: "powershell.exe",
|
||||
SSHPubKeys: []string{"ssh-rsa dummy foo@example.com"},
|
||||
Arch: limatype.AARCH64,
|
||||
User: "windows-user",
|
||||
WindowsInitialPassword: "dummy-password",
|
||||
|
||||
@@ -76,6 +76,20 @@ icacls $pubkeyLocation /grant "Administrators:F"
|
||||
$elapsed = (Get-Date) - $sectionStart
|
||||
Write-Output "SSH setting completed in $($elapsed.TotalSeconds) seconds"
|
||||
|
||||
# Install provision runner and set task scheduler.
|
||||
$root = Split-Path -Path $PSScriptRoot -Qualifier
|
||||
powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$root\install_provision.ps1" -Root "$root\"
|
||||
|
||||
# Execute provision scripts here for the current boot.
|
||||
$runnerPath = "C:\ProgramData\Lima\provision\startup_runner.ps1"
|
||||
|
||||
# Dependency scripts are only run here, not scheduled.
|
||||
powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$runnerPath" -Mode "dependency"
|
||||
# Startup System scripts are run here, and run in startup of a next boot.
|
||||
powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$runnerPath" -Mode "system"
|
||||
# Startup User scripts are run here, and run in startup of a next boot.
|
||||
powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$runnerPath" -Mode "user"
|
||||
|
||||
# Finally, it creates a marker file. Host agent will check the file.
|
||||
$bootDoneDir = 'C:\ProgramData\Lima'
|
||||
New-Item -ItemType Directory -Path $bootDoneDir -Force | Out-Null
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
param(
|
||||
[string]$Root = "F:\"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Copy-Provision {
|
||||
param(
|
||||
[string]$Src,
|
||||
[string]$Dst,
|
||||
[bool]$Extension
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $Src)) {
|
||||
return
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Path $Dst -Force | Out-Null
|
||||
foreach ($item in Get-ChildItem -LiteralPath $Src) {
|
||||
$dst = $Dst
|
||||
if ($Extension) {
|
||||
# `.ps1` extension is necessary for script otherwise Powershell returns an error.
|
||||
$dst = Join-Path $Dst ("{0}.ps1" -f $item.BaseName)
|
||||
}
|
||||
Copy-Item -LiteralPath $item.FullName -Destination $dst
|
||||
}
|
||||
}
|
||||
|
||||
$provisionDir = "C:\ProgramData\Lima\provision"
|
||||
New-Item -ItemType Directory -Path $provisionDir -Force | Out-Null
|
||||
|
||||
# Copy startup runner and System scripts
|
||||
$startupSystemRunnerSrc = Join-Path $Root "startup_runner.ps1"
|
||||
$startupSystemRunnerDst = Join-Path $provisionDir "startup_runner.ps1"
|
||||
Copy-Item -LiteralPath $startupSystemRunnerSrc -Destination $startupSystemRunnerDst
|
||||
|
||||
$startupSystemScriptsSrc = Join-Path $Root "provision.system"
|
||||
$startupSystemScriptsDst = Join-Path $provisionDir "provision.system"
|
||||
Copy-Provision -Src $startupSystemScriptsSrc -Dst $startupSystemScriptsDst -Extension
|
||||
|
||||
# Copy startup User scripts
|
||||
$startupUserScriptsSrc = Join-Path $Root "provision.user"
|
||||
$startupUserScriptsDst = Join-Path $provisionDir "provision.user"
|
||||
Copy-Provision -Src $startupUserScriptsSrc -Dst $startupUserScriptsDst -Extension
|
||||
|
||||
# Copy dependency scripts
|
||||
$startupDependencyScriptsSrc = Join-Path $Root "provision.dependency"
|
||||
$startupDependencyScriptsDst = Join-Path $provisionDir "provision.dependency"
|
||||
Copy-Provision -Src $startupDependencyScriptsSrc -Dst $startupDependencyScriptsDst -Extension
|
||||
|
||||
|
||||
# Copy data files
|
||||
$startupDataFilesSrc = Join-Path $Root "provision.data"
|
||||
$startupDataFilesDst = Join-Path $provisionDir "provision.data"
|
||||
Copy-Provision -Src $startupDataFilesSrc -Dst $startupDataFilesDst
|
||||
|
||||
|
||||
# Set startup System task
|
||||
$startupTrigger = New-ScheduledTaskTrigger -AtStartup
|
||||
$startupActionSystem = New-ScheduledTaskAction -Execute "powershell.exe" -Argument ("-NoProfile -NonInteractive -ExecutionPolicy Bypass -File $startupSystemRunnerDst -Mode system")
|
||||
$startupPrincipalSystem = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
|
||||
Register-ScheduledTask -TaskName "LimaProvisionStartupSystem" -TaskPath "\Lima\" -Action $startupActionSystem -Trigger $startupTrigger -Principal $startupPrincipalSystem -Force | Out-Null
|
||||
|
||||
# Set startup User task
|
||||
$startupActionUser = New-ScheduledTaskAction -Execute "powershell.exe" -Argument ("-NoProfile -NonInteractive -ExecutionPolicy Bypass -File $startupSystemRunnerDst -Mode user")
|
||||
$startupPrincipalUser = New-ScheduledTaskPrincipal -UserId "{{.User}}" -LogonType S4U -RunLevel Limited
|
||||
Register-ScheduledTask -TaskName "LimaProvisionStartupUser" -TaskPath "\Lima\" -Action $startupActionUser -Trigger $startupTrigger -Principal $startupPrincipalUser -Force | Out-Null
|
||||
@@ -0,0 +1,37 @@
|
||||
param(
|
||||
[string]$Mode = "system"
|
||||
)
|
||||
|
||||
function Get-ProvisionFiles {
|
||||
param(
|
||||
[string]$Root,
|
||||
[string]$Mode
|
||||
)
|
||||
$dir = Join-Path $Root ("provision.{0}" -f $Mode)
|
||||
if (-not (Test-Path -LiteralPath $dir)) {
|
||||
return @()
|
||||
}
|
||||
return @(Get-ChildItem -LiteralPath $dir -File | Sort-Object Name)
|
||||
}
|
||||
|
||||
function Invoke-ProvisionScriptFile {
|
||||
param(
|
||||
[System.IO.FileInfo]$File
|
||||
)
|
||||
|
||||
powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $File.FullName
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error ("Provision script exited with code {0}: {1}" -f $LASTEXITCODE, $File.FullName)
|
||||
}
|
||||
return $LASTEXITCODE
|
||||
}
|
||||
|
||||
$baseDir = "C:\ProgramData\Lima\provision"
|
||||
$exitCode = 0
|
||||
foreach ($f in (Get-ProvisionFiles -Root $baseDir -Mode $Mode)) {
|
||||
$code = Invoke-ProvisionScriptFile -File $f
|
||||
if ($code -ne 0) {
|
||||
$exitCode = $code
|
||||
}
|
||||
}
|
||||
exit $exitCode
|
||||
@@ -208,6 +208,10 @@ func Validate(y *limatype.LimaYAML, warn bool) error {
|
||||
i, limatype.ProvisionModeDependency))
|
||||
}
|
||||
|
||||
if *y.OS == limatype.WINDOWS && (p.Mode == limatype.ProvisionModeAnsible || p.Mode == limatype.ProvisionModeBoot || p.Mode == limatype.ProvisionModeYQ) {
|
||||
errs = errors.Join(errs, fmt.Errorf("provision mode %#q is not supported on Windows VM", p.Mode))
|
||||
}
|
||||
|
||||
// This can lead to fatal Panic if p.Path is nil, better to return an error here
|
||||
switch p.Mode {
|
||||
case limatype.ProvisionModeData, limatype.ProvisionModeYQ:
|
||||
|
||||
@@ -147,6 +147,73 @@ func TestValidateProvisionMode(t *testing.T) {
|
||||
"field `provision[0].script` must not be empty")
|
||||
}
|
||||
|
||||
func TestValidateProvisionModeOnWindows(t *testing.T) {
|
||||
windowsBase := `
|
||||
os: Windows
|
||||
arch: x86_64
|
||||
vmType: qemu
|
||||
images:
|
||||
- location: /
|
||||
osOpts:
|
||||
Windows:
|
||||
virtioWin:
|
||||
- location: /
|
||||
arch: "x86_64"
|
||||
`
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
provision string
|
||||
wantErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "ansible not supported on windows",
|
||||
provision: `provision: [{mode: ansible}]`,
|
||||
wantErrMsg: "provision mode `ansible` is not supported on Windows VM",
|
||||
},
|
||||
{
|
||||
name: "boot not supported on windows",
|
||||
provision: `provision: [{mode: boot, script: "echo hi"}]`,
|
||||
wantErrMsg: "provision mode `boot` is not supported on Windows VM",
|
||||
},
|
||||
{
|
||||
name: "yq not supported on windows",
|
||||
provision: `provision: [{mode: yq, expression: ".x=1", path: /tmp}]`,
|
||||
wantErrMsg: "provision mode `yq` is not supported on Windows VM",
|
||||
},
|
||||
{
|
||||
name: "system is supported on windows",
|
||||
provision: `provision: [{mode: system, script: "echo hi"}]`,
|
||||
},
|
||||
{
|
||||
name: "user is supported on windows",
|
||||
provision: `provision: [{mode: user, script: "echo hi"}]`,
|
||||
},
|
||||
{
|
||||
name: "dependency is supported on windows",
|
||||
provision: `provision: [{mode: dependency, script: "echo hi"}]`,
|
||||
},
|
||||
{
|
||||
name: "data is supported on windows",
|
||||
provision: `provision: [{mode: data, path: /tmp, content: hello}]`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
y, err := Load(t.Context(), []byte(windowsBase+"\n"+tt.provision), "template.yaml")
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = Validate(y, false)
|
||||
if tt.wantErrMsg == "" {
|
||||
assert.NilError(t, err)
|
||||
} else {
|
||||
assert.ErrorContains(t, err, tt.wantErrMsg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateProvisionData(t *testing.T) {
|
||||
images := `images: [{location: /}]`
|
||||
validData := `provision: [{mode: data, path: /tmp, content: hello}]`
|
||||
|
||||
@@ -33,3 +33,28 @@ plain: true
|
||||
video:
|
||||
# This is for development purpose. You can set `none`.
|
||||
display: default
|
||||
|
||||
# Windows VM doesn't support boot mode because there is no phase which is equivalent to Cloud-init's 'init' stage.
|
||||
# Currently Windows VM doesn't support yq mode.
|
||||
# provision:
|
||||
# - mode: system
|
||||
# script: |
|
||||
# Write-Output "this is system mode"
|
||||
# - mode: user
|
||||
# script: |
|
||||
# Write-Output "this is user mode"
|
||||
# # dependency is only run at once, when the guest is installed.
|
||||
# # Currently Windows VM does not support skipDefaultDependencyResolution.
|
||||
# - mode: dependency
|
||||
# skipDefaultDependencyResolution: false
|
||||
# script: |
|
||||
# Write-Output "this is dependency mode"
|
||||
# # All data mode files are copied in `C:\ProgramData\Lima\provision\provision.data\`, and `path` is ignored.
|
||||
# - mode: data
|
||||
# path: /this-field-is-ignored
|
||||
# content: |
|
||||
# FOO=bar
|
||||
# - mode: data
|
||||
# path: /this-field-is-ignored
|
||||
# content: |
|
||||
# BAR=foo
|
||||
@@ -25,3 +25,28 @@ plain: true
|
||||
video:
|
||||
# This is for development purpose. You can set `none`.
|
||||
display: default
|
||||
|
||||
# Windows VM doesn't support boot mode because there is no phase which is equivalent to Cloud-init's 'init' stage.
|
||||
# Currently Windows VM doesn't support yq mode.
|
||||
# provision:
|
||||
# - mode: system
|
||||
# script: |
|
||||
# Write-Output "this is system mode"
|
||||
# - mode: user
|
||||
# script: |
|
||||
# Write-Output "this is user mode"
|
||||
# # dependency is only run at once, when the guest is installed.
|
||||
# # Currently Windows VM does not support skipDefaultDependencyResolution.
|
||||
# - mode: dependency
|
||||
# skipDefaultDependencyResolution: false
|
||||
# script: |
|
||||
# Write-Output "this is dependency mode"
|
||||
# # All data mode files are copied in `C:\ProgramData\Lima\provision\provision.data\`, and `path` is ignored.
|
||||
# - mode: data
|
||||
# path: /this-field-is-ignored
|
||||
# content: |
|
||||
# FOO=bar
|
||||
# - mode: data
|
||||
# path: /this-field-is-ignored
|
||||
# content: |
|
||||
# BAR=foo
|
||||
@@ -34,4 +34,5 @@ For Windows server 2025, TPM emulation is disabled by default. However, there ar
|
||||
## Caveats
|
||||
- For Windows 11 guest, you need to download the installer ISO manually from [here](https://www.microsoft.com/en-us/software-download/windows11)
|
||||
- QEMU is the only VM driver that supports Windows guests
|
||||
- provision feature is limited support (no `boot`, `yq` modes, and `data` and `dependency` modes have limitations)
|
||||
- Only plain mode is supported (no file mount, no dynamic port-forwarding)
|
||||
Reference in new issue
Block a user