Skip to content

Commit f7108eb

Browse files
authored
Merge pull request #2283 from afbjorklund/ansible
Support ansible provision mode for remote playbook
2 parents e623b00 + f59b361 commit f7108eb

File tree

12 files changed

+142
-32
lines changed

12 files changed

+142
-32
lines changed

.github/workflows/test.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,10 @@ jobs:
240240
sudo modprobe kvm
241241
# `sudo usermod -aG kvm $(whoami)` does not take an effect on GHA
242242
sudo chown $(whoami) /dev/kvm
243+
- name: Install ansible-playbook
244+
run: |
245+
sudo apt-get install -y --no-install-recommends ansible
246+
if: matrix.template == '../hack/test-templates/test-misc.yaml'
243247
- name: "Show cache"
244248
run: ./hack/debug-cache.sh
245249
- name: "Test"

examples/default.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,12 @@ containerd:
227227
# #!/bin/bash
228228
# dnf config-manager --add-repo ...
229229
# dnf install ...
230+
# # `ansible` is executed after other scripts are complete
231+
# # It requires `ansible-playbook` command to be installed.
232+
# # Environment variables such as ANSIBLE_CONFIG can be used, to control the behavior of the playbook execution.
233+
# # See ansible docs, and `ansible-config`, for more info https://docs.ansible.com/ansible/latest/playbook_guide/
234+
# - mode: ansible
235+
# playbook: playbook.yaml
230236

231237
# Probe scripts to check readiness.
232238
# 🟢 Builtin default: null

hack/ansible-test.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
- hosts: all
2+
tasks:
3+
- name: Create test file
4+
file:
5+
path: /tmp/ansible
6+
state: touch

hack/test-templates.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ declare -A CHECKS=(
3535
["disk"]=""
3636
["user-v2"]=""
3737
["mount-path-with-spaces"]=""
38+
["provision-ansible"]=""
3839
)
3940

4041
case "$NAME" in
@@ -62,6 +63,7 @@ case "$NAME" in
6263
CHECKS["snapshot-online"]="1"
6364
CHECKS["snapshot-offline"]="1"
6465
CHECKS["mount-path-with-spaces"]="1"
66+
CHECKS["provision-ansible"]="1"
6567
;;
6668
"net-user-v2")
6769
CHECKS["port-forwards"]=""
@@ -143,6 +145,11 @@ if [[ -n ${CHECKS["mount-path-with-spaces"]} ]]; then
143145
[ "$(limactl shell "$NAME" cat "/tmp/lima test dir with spaces/test file")" = "test file content" ]
144146
fi
145147

148+
if [[ -n ${CHECKS["provision-ansible"]} ]]; then
149+
INFO 'Testing that /tmp/ansible was created successfully on provision'
150+
limactl shell "$NAME" test -e /tmp/ansible
151+
fi
152+
146153
INFO "Testing proxy settings are imported"
147154
got=$(limactl shell "$NAME" env | grep FTP_PROXY)
148155
# Expected: FTP_PROXY is set in addition to ftp_proxy, localhost is replaced

hack/test-templates/test-misc.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ mounts:
2626
- location: "/tmp/lima"
2727
writable: true
2828

29+
provision:
30+
- mode: ansible
31+
playbook: ./hack/ansible-test.yaml
32+
2933
# in order to use this example, you must first create the disk "data". run:
3034
# $ limactl disk create data --size 10G
3135
additionalDisks:

pkg/cidata/cidata.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,8 @@ func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort
334334
})
335335
case limayaml.ProvisionModeBoot:
336336
continue
337+
case limayaml.ProvisionModeAnsible:
338+
continue
337339
default:
338340
return fmt.Errorf("unknown provision mode %q", f.Mode)
339341
}

pkg/limayaml/limayaml.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,12 +180,14 @@ const (
180180
ProvisionModeUser ProvisionMode = "user"
181181
ProvisionModeBoot ProvisionMode = "boot"
182182
ProvisionModeDependency ProvisionMode = "dependency"
183+
ProvisionModeAnsible ProvisionMode = "ansible"
183184
)
184185

185186
type Provision struct {
186187
Mode ProvisionMode `yaml:"mode" json:"mode"` // default: "system"
187188
SkipDefaultDependencyResolution *bool `yaml:"skipDefaultDependencyResolution,omitempty" json:"skipDefaultDependencyResolution,omitempty"`
188189
Script string `yaml:"script" json:"script"`
190+
Playbook string `yaml:"playbook,omitempty" json:"playbook,omitempty"`
189191
}
190192

191193
type Containerd struct {

pkg/limayaml/validate.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,10 @@ func Validate(y *LimaYAML, warn bool) error {
183183
i, ProvisionModeDependency)
184184
}
185185
case ProvisionModeDependency:
186+
case ProvisionModeAnsible:
186187
default:
187-
return fmt.Errorf("field `provision[%d].mode` must one of %q, %q, %q, or %q",
188-
i, ProvisionModeSystem, ProvisionModeUser, ProvisionModeBoot, ProvisionModeDependency)
188+
return fmt.Errorf("field `provision[%d].mode` must one of %q, %q, %q, %q, or %q",
189+
i, ProvisionModeSystem, ProvisionModeUser, ProvisionModeBoot, ProvisionModeDependency, ProvisionModeAnsible)
189190
}
190191
if strings.Contains(p.Script, "LIMA_CIDATA") {
191192
logrus.Warn("provisioning scripts should not reference the LIMA_CIDATA variables")

pkg/start/ansible.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package start
2+
3+
import (
4+
"context"
5+
"os"
6+
"os/exec"
7+
"path/filepath"
8+
9+
"github.com/goccy/go-yaml"
10+
"github.com/lima-vm/lima/pkg/limayaml"
11+
"github.com/lima-vm/lima/pkg/store"
12+
"github.com/lima-vm/lima/pkg/store/filenames"
13+
"github.com/sirupsen/logrus"
14+
)
15+
16+
func runAnsibleProvision(ctx context.Context, inst *store.Instance) error {
17+
y, err := inst.LoadYAML()
18+
if err != nil {
19+
return err
20+
}
21+
for _, f := range y.Provision {
22+
if f.Mode == limayaml.ProvisionModeAnsible {
23+
logrus.Infof("Waiting for ansible playbook %q", f.Playbook)
24+
if err := runAnsiblePlaybook(ctx, inst, f.Playbook); err != nil {
25+
return err
26+
}
27+
}
28+
}
29+
return nil
30+
}
31+
32+
func runAnsiblePlaybook(ctx context.Context, inst *store.Instance, playbook string) error {
33+
inventory, err := createAnsibleInventory(inst)
34+
if err != nil {
35+
return err
36+
}
37+
logrus.Debugf("ansible-playbook -i %q %q", inventory, playbook)
38+
args := []string{"-i", inventory, playbook}
39+
cmd := exec.CommandContext(ctx, "ansible-playbook", args...)
40+
cmd.Stdout = os.Stdout
41+
cmd.Stderr = os.Stderr
42+
return cmd.Run()
43+
}
44+
45+
func createAnsibleInventory(inst *store.Instance) (string, error) {
46+
vars := map[string]interface{}{
47+
"ansible_connection": "ssh",
48+
"ansible_host": "lima-" + inst.Name,
49+
"ansible_ssh_common_args": "-F " + inst.SSHConfigFile,
50+
}
51+
hosts := map[string]interface{}{
52+
inst.Name: vars,
53+
}
54+
group := "lima"
55+
data := map[string]interface{}{
56+
group: map[string]interface{}{
57+
"hosts": hosts,
58+
},
59+
}
60+
bytes, err := yaml.Marshal(data)
61+
if err != nil {
62+
return "", err
63+
}
64+
inventory := filepath.Join(inst.Dir, filenames.AnsibleInventoryYAML)
65+
return inventory, os.WriteFile(inventory, bytes, 0o644)
66+
}

pkg/start/start.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,10 @@ func watchHostAgentEvents(ctx context.Context, inst *store.Instance, haStdoutPat
291291
return true
292292
}
293293

294+
if xerr := runAnsibleProvision(ctx, inst); xerr != nil {
295+
err = xerr
296+
return true
297+
}
294298
if *inst.Config.Plain {
295299
logrus.Infof("READY. Run `ssh -F %q lima-%s` to open the shell.", inst.SSHConfigFile, inst.Name)
296300
} else {

pkg/store/filenames/filenames.go

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -26,36 +26,37 @@ const (
2626
// Filenames that may appear under an instance directory
2727

2828
const (
29-
LimaYAML = "lima.yaml"
30-
LimaVersion = "lima-version" // Lima version used to create instance
31-
CIDataISO = "cidata.iso"
32-
CIDataISODir = "cidata"
33-
BaseDisk = "basedisk"
34-
DiffDisk = "diffdisk"
35-
Kernel = "kernel"
36-
KernelCmdline = "kernel.cmdline"
37-
Initrd = "initrd"
38-
QMPSock = "qmp.sock"
39-
SerialLog = "serial.log" // default serial (ttyS0, but ttyAMA0 on qemu-system-{arm,aarch64})
40-
SerialSock = "serial.sock"
41-
SerialPCILog = "serialp.log" // pci serial (ttyS0 on qemu-system-{arm,aarch64})
42-
SerialPCISock = "serialp.sock"
43-
SerialVirtioLog = "serialv.log" // virtio serial
44-
SerialVirtioSock = "serialv.sock"
45-
SSHSock = "ssh.sock"
46-
SSHConfig = "ssh.config"
47-
VhostSock = "virtiofsd-%d.sock"
48-
VNCDisplayFile = "vncdisplay"
49-
VNCPasswordFile = "vncpassword"
50-
GuestAgentSock = "ga.sock"
51-
VirtioPort = "io.lima-vm.guest_agent.0"
52-
HostAgentPID = "ha.pid"
53-
HostAgentSock = "ha.sock"
54-
HostAgentStdoutLog = "ha.stdout.log"
55-
HostAgentStderrLog = "ha.stderr.log"
56-
VzIdentifier = "vz-identifier"
57-
VzEfi = "vz-efi" // efi variable store
58-
QemuEfiCodeFD = "qemu-efi-code.fd" // efi code; not always created
29+
LimaYAML = "lima.yaml"
30+
LimaVersion = "lima-version" // Lima version used to create instance
31+
CIDataISO = "cidata.iso"
32+
CIDataISODir = "cidata"
33+
BaseDisk = "basedisk"
34+
DiffDisk = "diffdisk"
35+
Kernel = "kernel"
36+
KernelCmdline = "kernel.cmdline"
37+
Initrd = "initrd"
38+
QMPSock = "qmp.sock"
39+
SerialLog = "serial.log" // default serial (ttyS0, but ttyAMA0 on qemu-system-{arm,aarch64})
40+
SerialSock = "serial.sock"
41+
SerialPCILog = "serialp.log" // pci serial (ttyS0 on qemu-system-{arm,aarch64})
42+
SerialPCISock = "serialp.sock"
43+
SerialVirtioLog = "serialv.log" // virtio serial
44+
SerialVirtioSock = "serialv.sock"
45+
SSHSock = "ssh.sock"
46+
SSHConfig = "ssh.config"
47+
VhostSock = "virtiofsd-%d.sock"
48+
VNCDisplayFile = "vncdisplay"
49+
VNCPasswordFile = "vncpassword"
50+
GuestAgentSock = "ga.sock"
51+
VirtioPort = "io.lima-vm.guest_agent.0"
52+
HostAgentPID = "ha.pid"
53+
HostAgentSock = "ha.sock"
54+
HostAgentStdoutLog = "ha.stdout.log"
55+
HostAgentStderrLog = "ha.stderr.log"
56+
VzIdentifier = "vz-identifier"
57+
VzEfi = "vz-efi" // efi variable store
58+
QemuEfiCodeFD = "qemu-efi-code.fd" // efi code; not always created
59+
AnsibleInventoryYAML = "ansible-inventory.yaml"
5960

6061
// SocketDir is the default location for forwarded sockets with a relative paths in HostSocket.
6162
SocketDir = "sock"

website/content/en/docs/dev/Internals/_index.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ Metadata:
3939
cloud-init:
4040
- `cidata.iso`: cloud-init ISO9660 image. See [`cidata.iso`](#cidataiso).
4141

42+
Ansible:
43+
- `ansible-inventory.yaml`: the Ansible node inventory. See [ansible](#ansible).
44+
4245
disk:
4346
- `basedisk`: the base image
4447
- `diffdisk`: the diff image (QCOW2)
@@ -143,6 +146,10 @@ The directory contains the following files:
143146
- `$QEMU_SYSTEM_ARM`: path of `qemu-system-arm`
144147
- Default: `qemu-system-arm` in `$PATH`
145148

149+
## Ansible
150+
The instance directory contains an inventory file, that might be used with Ansible playbooks and commands.
151+
See [Building Ansible inventories](https://docs.ansible.com/ansible/latest/inventory_guide/) about dynamic inventories.
152+
146153
## `cidata.iso`
147154
`cidata.iso` contains the following files:
148155

0 commit comments

Comments
 (0)