podman/pkg/machine/ignition.go

222 lines
5.4 KiB
Go

// +build amd64,linux arm64,linux amd64,darwin arm64,darwin
package machine
import (
"encoding/json"
"fmt"
"io/ioutil"
)
/*
If this file gets too nuts, we can perhaps use existing go code
to create ignition files. At this point, the file is so simple
that I chose to use structs and not import any code as I was
concerned (unsubstantiated) about too much bloat coming in.
https://github.com/openshift/machine-config-operator/blob/master/pkg/server/server.go
*/
// Convenience function to convert int to ptr
func intToPtr(i int) *int {
return &i
}
// Convenience function to convert string to ptr
func strToPtr(s string) *string {
return &s
}
// Convenience function to convert bool to ptr
func boolToPtr(b bool) *bool {
return &b
}
func getNodeUsr(usrName string) NodeUser {
return NodeUser{Name: &usrName}
}
func getNodeGrp(grpName string) NodeGroup {
return NodeGroup{Name: &grpName}
}
type DynamicIgnition struct {
Name string
Key string
VMName string
WritePath string
}
// NewIgnitionFile
func NewIgnitionFile(ign DynamicIgnition) error {
if len(ign.Name) < 1 {
ign.Name = DefaultIgnitionUserName
}
ignVersion := Ignition{
Version: "3.2.0",
}
ignPassword := Passwd{
Users: []PasswdUser{
{
Name: ign.Name,
SSHAuthorizedKeys: []SSHAuthorizedKey{SSHAuthorizedKey(ign.Key)},
},
{
Name: "root",
SSHAuthorizedKeys: []SSHAuthorizedKey{SSHAuthorizedKey(ign.Key)},
},
},
}
ignStorage := Storage{
Directories: getDirs(ign.Name),
Files: getFiles(ign.Name),
Links: getLinks(ign.Name),
}
// ready is a unit file that sets up the virtual serial device
// where when the VM is done configuring, it will send an ack
// so a listening host knows it can being interacting with it
ready := `[Unit]
Requires=dev-virtio\\x2dports-%s.device
OnFailure=emergency.target
OnFailureJobMode=isolate
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/sh -c '/usr/bin/echo Ready >/dev/%s'
[Install]
RequiredBy=multi-user.target
`
_ = ready
ignSystemd := Systemd{
Units: []Unit{
{
Enabled: boolToPtr(true),
Name: "podman.socket",
},
{
Enabled: boolToPtr(true),
Name: "ready.service",
Contents: strToPtr(fmt.Sprintf(ready, "vport1p1", "vport1p1")),
},
}}
ignConfig := Config{
Ignition: ignVersion,
Passwd: ignPassword,
Storage: ignStorage,
Systemd: ignSystemd,
}
b, err := json.Marshal(ignConfig)
if err != nil {
return err
}
return ioutil.WriteFile(ign.WritePath, b, 0644)
}
func getDirs(usrName string) []Directory {
// Ignition has a bug/feature? where if you make a series of dirs
// in one swoop, then the leading dirs are creates as root.
newDirs := []string{
"/home/" + usrName + "/.config",
"/home/" + usrName + "/.config/containers",
"/home/" + usrName + "/.config/systemd",
"/home/" + usrName + "/.config/systemd/user",
"/home/" + usrName + "/.config/systemd/user/default.target.wants",
}
var (
dirs = make([]Directory, len(newDirs))
)
for i, d := range newDirs {
newDir := Directory{
Node: Node{
Group: getNodeGrp(usrName),
Path: d,
User: getNodeUsr(usrName),
},
DirectoryEmbedded1: DirectoryEmbedded1{Mode: intToPtr(493)},
}
dirs[i] = newDir
}
return dirs
}
func getFiles(usrName string) []File {
var (
files []File
)
// Add a fake systemd service to get the user socket rolling
files = append(files, File{
Node: Node{
Group: getNodeGrp(usrName),
Path: "/home/" + usrName + "/.config/systemd/user/linger-example.service",
User: getNodeUsr(usrName),
},
FileEmbedded1: FileEmbedded1{
Append: nil,
Contents: Resource{
Source: strToPtr("data:,%5BUnit%5D%0ADescription%3DA%20systemd%20user%20unit%20demo%0AAfter%3Dnetwork-online.target%0AWants%3Dnetwork-online.target%20podman.socket%0A%5BService%5D%0AExecStart%3D%2Fusr%2Fbin%2Fsleep%20infinity%0A"),
},
Mode: intToPtr(484),
},
})
// Set containers.conf up for core user to use cni networks
// by default
files = append(files, File{
Node: Node{
Group: getNodeGrp(usrName),
Path: "/home/" + usrName + "/.config/containers/containers.conf",
User: getNodeUsr(usrName),
},
FileEmbedded1: FileEmbedded1{
Append: nil,
Contents: Resource{
Source: strToPtr("data:,%5Bcontainers%5D%0D%0Anetns%3D%22bridge%22%0D%0Arootless_networking%3D%22cni%22"),
},
Mode: intToPtr(484),
},
})
// Add a file into linger
files = append(files, File{
Node: Node{
Group: getNodeGrp(usrName),
Path: "/var/lib/systemd/linger/core",
User: getNodeUsr(usrName),
},
FileEmbedded1: FileEmbedded1{Mode: intToPtr(420)},
})
// Set machine_enabled to true to indicate we're in a VM
files = append(files, File{
Node: Node{
Group: getNodeGrp("root"),
Path: "/etc/containers/containers.conf",
User: getNodeUsr("root"),
},
FileEmbedded1: FileEmbedded1{
Append: nil,
Contents: Resource{
Source: strToPtr("data:,%5Bengine%5D%0Amachine_enabled%3Dtrue%0A"),
},
Mode: intToPtr(420),
},
})
return files
}
func getLinks(usrName string) []Link {
return []Link{{
Node: Node{
Group: getNodeGrp(usrName),
Path: "/home/" + usrName + "/.config/systemd/user/default.target.wants/linger-example.service",
User: getNodeUsr(usrName),
},
LinkEmbedded1: LinkEmbedded1{
Hard: boolToPtr(false),
Target: "/home/" + usrName + "/.config/systemd/user/linger-example.service",
},
}}
}