Skip to content

Commit 3553afe

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 <[email protected]>
1 parent ac31576 commit 3553afe

File tree

5 files changed

+122
-28
lines changed

5 files changed

+122
-28
lines changed

pkg/machine/apple/apple.go

+78-28
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ import (
99
"net"
1010
"os"
1111
"os/exec"
12+
"path/filepath"
1213
"syscall"
1314
"time"
1415

1516
"github.com/containers/common/pkg/config"
1617
"github.com/containers/common/pkg/strongunits"
1718
gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types"
1819
"github.com/containers/podman/v5/pkg/machine"
20+
"github.com/containers/podman/v5/pkg/machine/cloudinit"
1921
"github.com/containers/podman/v5/pkg/machine/define"
2022
"github.com/containers/podman/v5/pkg/machine/ignition"
2123
"github.com/containers/podman/v5/pkg/machine/sockets"
@@ -141,10 +143,6 @@ func GenerateSystemDFilesForVirtiofsMounts(mounts []machine.VirtIoFs) ([]ignitio
141143

142144
// StartGenericAppleVM is wrapped by apple provider methods and starts the vm
143145
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-
148146
// Add networking
149147
netDevice, err := vfConfig.VirtioNetNew(applehvMACAddress)
150148
if err != nil {
@@ -209,11 +207,6 @@ func StartGenericAppleVM(mc *vmconfigs.MachineConfig, cmdBinary string, bootload
209207
return nil, nil, err
210208
}
211209

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

219212
firstBoot, err := mc.IsFirstBoot()
@@ -231,31 +224,18 @@ func StartGenericAppleVM(mc *vmconfigs.MachineConfig, cmdBinary string, bootload
231224
}
232225

233226
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)
227+
var firstBootCli []string
228+
if mc.CloudInit {
229+
firstBootCli, err = getFirstBootAppleVMCloudInit(mc)
230+
} else {
231+
firstBootCli, err = getFirstBootAppleVMIgnition(mc)
243232
}
244233

245-
ignitionVsockDeviceCLI, err := GetIgnitionVsockDeviceAsCLI(ignitionSocket.GetPath())
246234
if err != nil {
247235
return nil, nil, err
248236
}
249-
cmd.Args = append(cmd.Args, ignitionVsockDeviceCLI...)
250-
251237
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-
}()
238+
cmd.Args = append(cmd.Args, firstBootCli...)
259239
}
260240

261241
logrus.Debugf("listening for ready on: %s", readySocket.GetPath())
@@ -351,6 +331,76 @@ func StartGenericAppleVM(mc *vmconfigs.MachineConfig, cmdBinary string, bootload
351331
return cmd.Process.Release, returnFunc, nil
352332
}
353333

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

pkg/machine/cloudinit/cloudinit.go

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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+
return "", err
28+
}
29+
30+
headerLine := "#cloud-config\n"
31+
yamlBytes = append([]byte(headerLine), yamlBytes...)
32+
33+
path := filepath.Join(dir, "user-data")
34+
err = os.WriteFile(path, yamlBytes, 0644)
35+
if err != nil {
36+
logrus.Errorf("Error creating temp file: %v", err)
37+
return "", err
38+
}
39+
return path, nil
40+
}

pkg/machine/define/initopts.go

+1
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

+2
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

+1
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)