Skip to content

Commit 07572b4

Browse files
committed
support cloud init on macOS
when using a RHEL VM cloud-init is preferred over ignitioni for customizing the VM. With crc-org/vfkit#208 vfkit will be able to accept raw config files to enable cloud-init. This patch adds the logic to create a user-data config file to register users + their ssh key by leveraging cloud-init Signed-off-by: Luca Stocchi <lstocchi@redhat.com>
1 parent ac31576 commit 07572b4

File tree

5 files changed

+111
-28
lines changed

5 files changed

+111
-28
lines changed

pkg/machine/apple/apple.go

Lines changed: 69 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/containers/common/pkg/strongunits"
1717
gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types"
1818
"github.com/containers/podman/v5/pkg/machine"
19+
"github.com/containers/podman/v5/pkg/machine/cloudinit"
1920
"github.com/containers/podman/v5/pkg/machine/define"
2021
"github.com/containers/podman/v5/pkg/machine/ignition"
2122
"github.com/containers/podman/v5/pkg/machine/sockets"
@@ -141,10 +142,6 @@ func GenerateSystemDFilesForVirtiofsMounts(mounts []machine.VirtIoFs) ([]ignitio
141142

142143
// StartGenericAppleVM is wrapped by apple provider methods and starts the vm
143144
func StartGenericAppleVM(mc *vmconfigs.MachineConfig, cmdBinary string, bootloader vfConfig.Bootloader, endpoint string) (func() error, func() error, error) {
144-
var (
145-
ignitionSocket *define.VMFile
146-
)
147-
148145
// Add networking
149146
netDevice, err := vfConfig.VirtioNetNew(applehvMACAddress)
150147
if err != nil {
@@ -209,11 +206,6 @@ func StartGenericAppleVM(mc *vmconfigs.MachineConfig, cmdBinary string, bootload
209206
return nil, nil, err
210207
}
211208

212-
machineDataDir, err := mc.DataDir()
213-
if err != nil {
214-
return nil, nil, err
215-
}
216-
217209
cmd.Args = append(cmd.Args, endpointArgs...)
218210

219211
firstBoot, err := mc.IsFirstBoot()
@@ -231,31 +223,18 @@ func StartGenericAppleVM(mc *vmconfigs.MachineConfig, cmdBinary string, bootload
231223
}
232224

233225
if firstBoot {
234-
// If this is the first boot of the vm, we need to add the vsock
235-
// device to vfkit so we can inject the ignition file
236-
socketName := fmt.Sprintf("%s-%s", mc.Name, ignitionSocketName)
237-
ignitionSocket, err = machineDataDir.AppendToNewVMFile(socketName, &socketName)
238-
if err != nil {
239-
return nil, nil, err
240-
}
241-
if err := ignitionSocket.Delete(); err != nil {
242-
logrus.Errorf("unable to delete ignition socket: %q", err)
226+
var firstBootCli []string
227+
if mc.CloudInit {
228+
firstBootCli, err = getFirstBootAppleVMCloudInit(mc)
229+
} else {
230+
firstBootCli, err = getFirstBootAppleVMIgnition(mc)
243231
}
244232

245-
ignitionVsockDeviceCLI, err := GetIgnitionVsockDeviceAsCLI(ignitionSocket.GetPath())
246233
if err != nil {
247234
return nil, nil, err
248235
}
249-
cmd.Args = append(cmd.Args, ignitionVsockDeviceCLI...)
250-
251236
logrus.Debug("first boot detected")
252-
logrus.Debugf("serving ignition file over %s", ignitionSocket.GetPath())
253-
go func() {
254-
if err := ServeIgnitionOverSock(ignitionSocket, mc); err != nil {
255-
logrus.Error(err)
256-
}
257-
logrus.Debug("ignition vsock server exited")
258-
}()
237+
cmd.Args = append(cmd.Args, firstBootCli...)
259238
}
260239

261240
logrus.Debugf("listening for ready on: %s", readySocket.GetPath())
@@ -351,6 +330,68 @@ func StartGenericAppleVM(mc *vmconfigs.MachineConfig, cmdBinary string, bootload
351330
return cmd.Process.Release, returnFunc, nil
352331
}
353332

333+
func getFirstBootAppleVMIgnition(mc *vmconfigs.MachineConfig) ([]string, error) {
334+
machineDataDir, err := mc.DataDir()
335+
if err != nil {
336+
return nil, err
337+
}
338+
339+
// If this is the first boot of the vm, we need to add the vsock
340+
// device to vfkit so we can inject the ignition file
341+
socketName := fmt.Sprintf("%s-%s", mc.Name, ignitionSocketName)
342+
ignitionSocket, err := machineDataDir.AppendToNewVMFile(socketName, &socketName)
343+
if err != nil {
344+
return nil, err
345+
}
346+
if err := ignitionSocket.Delete(); err != nil {
347+
logrus.Errorf("unable to delete ignition socket: %q", err)
348+
}
349+
350+
ignitionVsockDeviceCLI, err := GetIgnitionVsockDeviceAsCLI(ignitionSocket.GetPath())
351+
if err != nil {
352+
return nil, err
353+
}
354+
355+
logrus.Debugf("serving ignition file over %s", ignitionSocket.GetPath())
356+
go func() {
357+
if err := ServeIgnitionOverSock(ignitionSocket, mc); err != nil {
358+
logrus.Error(err)
359+
}
360+
logrus.Debug("ignition vsock server exited")
361+
}()
362+
363+
return ignitionVsockDeviceCLI, nil
364+
}
365+
366+
func getFirstBootAppleVMCloudInit(mc *vmconfigs.MachineConfig) ([]string, error) {
367+
sshKey, err := machine.GetSSHKeys(mc.SSH.IdentityPath)
368+
if err != nil {
369+
return nil, err
370+
}
371+
372+
tempDir, err := os.MkdirTemp("", "cloud-init-macadam-")
373+
if err != nil {
374+
return nil, err
375+
}
376+
377+
// we generate the user-data file
378+
userDataFile, err := cloudinit.GenerateUserData(tempDir, cloudinit.UserData{
379+
Users: []cloudinit.User{
380+
cloudinit.User{
381+
Name: mc.SSH.RemoteUsername,
382+
Sudo: "ALL=(ALL) NOPASSWD:ALL",
383+
Shell: "/bin/bash",
384+
Groups: "users",
385+
SSHKeys: []string{
386+
sshKey,
387+
},
388+
},
389+
},
390+
})
391+
392+
return []string{"--cloud-init", userDataFile}, nil
393+
}
394+
354395
// CheckProcessRunning checks non blocking if the pid exited
355396
// returns nil if process is running otherwise an error if not
356397
func CheckProcessRunning(processName string, pid int) error {

pkg/machine/cloudinit/cloudinit.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package cloudinit
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
7+
"github.com/sirupsen/logrus"
8+
"gopkg.in/yaml.v3"
9+
)
10+
11+
type User struct {
12+
Name string `yaml:"name"`
13+
Sudo string `yaml:"sudo"`
14+
Shell string `yaml:"shell"`
15+
Groups string `yaml:"groups"`
16+
SSHKeys []string `yaml:"ssh_authorized_keys"`
17+
}
18+
19+
type UserData struct {
20+
Users []User `yaml:"users"`
21+
}
22+
23+
func GenerateUserData(dir string, data UserData) (string, error) {
24+
yamlBytes, err := yaml.Marshal(&data)
25+
if err != nil {
26+
logrus.Errorf("Error marshaling to YAML: %v", err)
27+
}
28+
29+
headerLine := "#cloud-config\n"
30+
yamlBytes = append([]byte(headerLine), yamlBytes...)
31+
32+
path := filepath.Join(dir, "user-data")
33+
err = os.WriteFile(path, yamlBytes, 0644)
34+
if err != nil {
35+
logrus.Errorf("Error creating temp file: %v", err)
36+
}
37+
return path, nil
38+
}

pkg/machine/define/initopts.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ type InitOptions struct {
2121
UserModeNetworking *bool // nil = use backend/system default, false = disable, true = enable
2222
USBs []string
2323
ImagePuller ImagePuller
24+
CloudInit bool
2425
}

pkg/machine/vmconfigs/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ type MachineConfig struct {
5353
Starting bool
5454

5555
Rosetta bool
56+
57+
CloudInit bool
5658
}
5759

5860
type machineImage interface { //nolint:unused

pkg/machine/vmconfigs/machine.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ func NewMachineConfig(opts define.InitOptions, dirs *define.MachineDirs, sshIden
9696
mc.Created = time.Now()
9797

9898
mc.HostUser = HostUser{UID: getHostUID(), Rootful: opts.Rootful}
99+
mc.CloudInit = opts.CloudInit
99100

100101
return mc, nil
101102
}

0 commit comments

Comments
 (0)