Skip to content

Commit

Permalink
Merge pull request #2283 from afbjorklund/ansible
Browse files Browse the repository at this point in the history
Support ansible provision mode for remote playbook
  • Loading branch information
AkihiroSuda authored Jun 19, 2024
2 parents e623b00 + f59b361 commit f7108eb
Show file tree
Hide file tree
Showing 12 changed files with 142 additions and 32 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@ jobs:
sudo modprobe kvm
# `sudo usermod -aG kvm $(whoami)` does not take an effect on GHA
sudo chown $(whoami) /dev/kvm
- name: Install ansible-playbook
run: |
sudo apt-get install -y --no-install-recommends ansible
if: matrix.template == '../hack/test-templates/test-misc.yaml'
- name: "Show cache"
run: ./hack/debug-cache.sh
- name: "Test"
Expand Down
6 changes: 6 additions & 0 deletions examples/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,12 @@ containerd:
# #!/bin/bash
# dnf config-manager --add-repo ...
# dnf install ...
# # `ansible` is executed after other scripts are complete
# # It requires `ansible-playbook` command to be installed.
# # Environment variables such as ANSIBLE_CONFIG can be used, to control the behavior of the playbook execution.
# # See ansible docs, and `ansible-config`, for more info https://docs.ansible.com/ansible/latest/playbook_guide/
# - mode: ansible
# playbook: playbook.yaml

# Probe scripts to check readiness.
# 🟢 Builtin default: null
Expand Down
6 changes: 6 additions & 0 deletions hack/ansible-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
- hosts: all
tasks:
- name: Create test file
file:
path: /tmp/ansible
state: touch
7 changes: 7 additions & 0 deletions hack/test-templates.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ declare -A CHECKS=(
["disk"]=""
["user-v2"]=""
["mount-path-with-spaces"]=""
["provision-ansible"]=""
)

case "$NAME" in
Expand Down Expand Up @@ -62,6 +63,7 @@ case "$NAME" in
CHECKS["snapshot-online"]="1"
CHECKS["snapshot-offline"]="1"
CHECKS["mount-path-with-spaces"]="1"
CHECKS["provision-ansible"]="1"
;;
"net-user-v2")
CHECKS["port-forwards"]=""
Expand Down Expand Up @@ -143,6 +145,11 @@ if [[ -n ${CHECKS["mount-path-with-spaces"]} ]]; then
[ "$(limactl shell "$NAME" cat "/tmp/lima test dir with spaces/test file")" = "test file content" ]
fi

if [[ -n ${CHECKS["provision-ansible"]} ]]; then
INFO 'Testing that /tmp/ansible was created successfully on provision'
limactl shell "$NAME" test -e /tmp/ansible
fi

INFO "Testing proxy settings are imported"
got=$(limactl shell "$NAME" env | grep FTP_PROXY)
# Expected: FTP_PROXY is set in addition to ftp_proxy, localhost is replaced
Expand Down
4 changes: 4 additions & 0 deletions hack/test-templates/test-misc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ mounts:
- location: "/tmp/lima"
writable: true

provision:
- mode: ansible
playbook: ./hack/ansible-test.yaml

# in order to use this example, you must first create the disk "data". run:
# $ limactl disk create data --size 10G
additionalDisks:
Expand Down
2 changes: 2 additions & 0 deletions pkg/cidata/cidata.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,8 @@ func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort
})
case limayaml.ProvisionModeBoot:
continue
case limayaml.ProvisionModeAnsible:
continue
default:
return fmt.Errorf("unknown provision mode %q", f.Mode)
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/limayaml/limayaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,14 @@ const (
ProvisionModeUser ProvisionMode = "user"
ProvisionModeBoot ProvisionMode = "boot"
ProvisionModeDependency ProvisionMode = "dependency"
ProvisionModeAnsible ProvisionMode = "ansible"
)

type Provision struct {
Mode ProvisionMode `yaml:"mode" json:"mode"` // default: "system"
SkipDefaultDependencyResolution *bool `yaml:"skipDefaultDependencyResolution,omitempty" json:"skipDefaultDependencyResolution,omitempty"`
Script string `yaml:"script" json:"script"`
Playbook string `yaml:"playbook,omitempty" json:"playbook,omitempty"`
}

type Containerd struct {
Expand Down
5 changes: 3 additions & 2 deletions pkg/limayaml/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,10 @@ func Validate(y *LimaYAML, warn bool) error {
i, ProvisionModeDependency)
}
case ProvisionModeDependency:
case ProvisionModeAnsible:
default:
return fmt.Errorf("field `provision[%d].mode` must one of %q, %q, %q, or %q",
i, ProvisionModeSystem, ProvisionModeUser, ProvisionModeBoot, ProvisionModeDependency)
return fmt.Errorf("field `provision[%d].mode` must one of %q, %q, %q, %q, or %q",
i, ProvisionModeSystem, ProvisionModeUser, ProvisionModeBoot, ProvisionModeDependency, ProvisionModeAnsible)
}
if strings.Contains(p.Script, "LIMA_CIDATA") {
logrus.Warn("provisioning scripts should not reference the LIMA_CIDATA variables")
Expand Down
66 changes: 66 additions & 0 deletions pkg/start/ansible.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package start

import (
"context"
"os"
"os/exec"
"path/filepath"

"github.com/goccy/go-yaml"
"github.com/lima-vm/lima/pkg/limayaml"
"github.com/lima-vm/lima/pkg/store"
"github.com/lima-vm/lima/pkg/store/filenames"
"github.com/sirupsen/logrus"
)

func runAnsibleProvision(ctx context.Context, inst *store.Instance) error {
y, err := inst.LoadYAML()
if err != nil {
return err
}
for _, f := range y.Provision {
if f.Mode == limayaml.ProvisionModeAnsible {
logrus.Infof("Waiting for ansible playbook %q", f.Playbook)
if err := runAnsiblePlaybook(ctx, inst, f.Playbook); err != nil {
return err
}
}
}
return nil
}

func runAnsiblePlaybook(ctx context.Context, inst *store.Instance, playbook string) error {
inventory, err := createAnsibleInventory(inst)
if err != nil {
return err
}
logrus.Debugf("ansible-playbook -i %q %q", inventory, playbook)
args := []string{"-i", inventory, playbook}
cmd := exec.CommandContext(ctx, "ansible-playbook", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}

func createAnsibleInventory(inst *store.Instance) (string, error) {
vars := map[string]interface{}{
"ansible_connection": "ssh",
"ansible_host": "lima-" + inst.Name,
"ansible_ssh_common_args": "-F " + inst.SSHConfigFile,
}
hosts := map[string]interface{}{
inst.Name: vars,
}
group := "lima"
data := map[string]interface{}{
group: map[string]interface{}{
"hosts": hosts,
},
}
bytes, err := yaml.Marshal(data)
if err != nil {
return "", err
}
inventory := filepath.Join(inst.Dir, filenames.AnsibleInventoryYAML)
return inventory, os.WriteFile(inventory, bytes, 0o644)
}
4 changes: 4 additions & 0 deletions pkg/start/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,10 @@ func watchHostAgentEvents(ctx context.Context, inst *store.Instance, haStdoutPat
return true
}

if xerr := runAnsibleProvision(ctx, inst); xerr != nil {
err = xerr
return true
}
if *inst.Config.Plain {
logrus.Infof("READY. Run `ssh -F %q lima-%s` to open the shell.", inst.SSHConfigFile, inst.Name)
} else {
Expand Down
61 changes: 31 additions & 30 deletions pkg/store/filenames/filenames.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,36 +26,37 @@ const (
// Filenames that may appear under an instance directory

const (
LimaYAML = "lima.yaml"
LimaVersion = "lima-version" // Lima version used to create instance
CIDataISO = "cidata.iso"
CIDataISODir = "cidata"
BaseDisk = "basedisk"
DiffDisk = "diffdisk"
Kernel = "kernel"
KernelCmdline = "kernel.cmdline"
Initrd = "initrd"
QMPSock = "qmp.sock"
SerialLog = "serial.log" // default serial (ttyS0, but ttyAMA0 on qemu-system-{arm,aarch64})
SerialSock = "serial.sock"
SerialPCILog = "serialp.log" // pci serial (ttyS0 on qemu-system-{arm,aarch64})
SerialPCISock = "serialp.sock"
SerialVirtioLog = "serialv.log" // virtio serial
SerialVirtioSock = "serialv.sock"
SSHSock = "ssh.sock"
SSHConfig = "ssh.config"
VhostSock = "virtiofsd-%d.sock"
VNCDisplayFile = "vncdisplay"
VNCPasswordFile = "vncpassword"
GuestAgentSock = "ga.sock"
VirtioPort = "io.lima-vm.guest_agent.0"
HostAgentPID = "ha.pid"
HostAgentSock = "ha.sock"
HostAgentStdoutLog = "ha.stdout.log"
HostAgentStderrLog = "ha.stderr.log"
VzIdentifier = "vz-identifier"
VzEfi = "vz-efi" // efi variable store
QemuEfiCodeFD = "qemu-efi-code.fd" // efi code; not always created
LimaYAML = "lima.yaml"
LimaVersion = "lima-version" // Lima version used to create instance
CIDataISO = "cidata.iso"
CIDataISODir = "cidata"
BaseDisk = "basedisk"
DiffDisk = "diffdisk"
Kernel = "kernel"
KernelCmdline = "kernel.cmdline"
Initrd = "initrd"
QMPSock = "qmp.sock"
SerialLog = "serial.log" // default serial (ttyS0, but ttyAMA0 on qemu-system-{arm,aarch64})
SerialSock = "serial.sock"
SerialPCILog = "serialp.log" // pci serial (ttyS0 on qemu-system-{arm,aarch64})
SerialPCISock = "serialp.sock"
SerialVirtioLog = "serialv.log" // virtio serial
SerialVirtioSock = "serialv.sock"
SSHSock = "ssh.sock"
SSHConfig = "ssh.config"
VhostSock = "virtiofsd-%d.sock"
VNCDisplayFile = "vncdisplay"
VNCPasswordFile = "vncpassword"
GuestAgentSock = "ga.sock"
VirtioPort = "io.lima-vm.guest_agent.0"
HostAgentPID = "ha.pid"
HostAgentSock = "ha.sock"
HostAgentStdoutLog = "ha.stdout.log"
HostAgentStderrLog = "ha.stderr.log"
VzIdentifier = "vz-identifier"
VzEfi = "vz-efi" // efi variable store
QemuEfiCodeFD = "qemu-efi-code.fd" // efi code; not always created
AnsibleInventoryYAML = "ansible-inventory.yaml"

// SocketDir is the default location for forwarded sockets with a relative paths in HostSocket.
SocketDir = "sock"
Expand Down
7 changes: 7 additions & 0 deletions website/content/en/docs/dev/Internals/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ Metadata:
cloud-init:
- `cidata.iso`: cloud-init ISO9660 image. See [`cidata.iso`](#cidataiso).

Ansible:
- `ansible-inventory.yaml`: the Ansible node inventory. See [ansible](#ansible).

disk:
- `basedisk`: the base image
- `diffdisk`: the diff image (QCOW2)
Expand Down Expand Up @@ -143,6 +146,10 @@ The directory contains the following files:
- `$QEMU_SYSTEM_ARM`: path of `qemu-system-arm`
- Default: `qemu-system-arm` in `$PATH`

## Ansible
The instance directory contains an inventory file, that might be used with Ansible playbooks and commands.
See [Building Ansible inventories](https://docs.ansible.com/ansible/latest/inventory_guide/) about dynamic inventories.

## `cidata.iso`
`cidata.iso` contains the following files:

Expand Down

0 comments on commit f7108eb

Please sign in to comment.