diff --git a/mantle/cmd/kola/options.go b/mantle/cmd/kola/options.go index bb1e7be9dd..d8f6d1850d 100644 --- a/mantle/cmd/kola/options.go +++ b/mantle/cmd/kola/options.go @@ -186,6 +186,10 @@ func syncOptionsImpl(useCosa bool) error { } } + if kola.Options.IgnitionVersion == "" && kola.QEMUOptions.DiskImage != "" { + kola.Options.IgnitionVersion = sdk.TargetIgnitionVersionFromName(kola.QEMUOptions.DiskImage) + } + units, _ := root.PersistentFlags().GetStringSlice("debug-systemd-units") for _, unit := range units { kola.Options.SystemdDropins = append(kola.Options.SystemdDropins, platform.SystemdDropin{ @@ -234,8 +238,6 @@ func syncCosaOptions() error { if kola.Options.IgnitionVersion == "" { if kola.CosaBuild != nil { kola.Options.IgnitionVersion = sdk.TargetIgnitionVersion(kola.CosaBuild) - } else if kola.QEMUOptions.DiskImage != "" { - kola.Options.IgnitionVersion = sdk.TargetIgnitionVersionFromName(kola.QEMUOptions.DiskImage) } } diff --git a/mantle/cmd/kola/qemuexec.go b/mantle/cmd/kola/qemuexec.go index c53b8f6258..1b68d54180 100644 --- a/mantle/cmd/kola/qemuexec.go +++ b/mantle/cmd/kola/qemuexec.go @@ -17,11 +17,19 @@ package main import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + + ignconverter "github.com/coreos/ign-converter" + v3 "github.com/coreos/ignition/v2/config/v3_0" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/coreos/mantle/kola" "github.com/coreos/mantle/platform" + "github.com/coreos/mantle/platform/conf" ) var ( @@ -41,6 +49,8 @@ var ( ignition string knetargs string + ignitionFragments []string + forceConfigInjection bool ) @@ -48,14 +58,91 @@ func init() { root.AddCommand(cmdQemuExec) cmdQemuExec.Flags().StringVarP(&knetargs, "knetargs", "", "", "Arguments for Ignition networking on kernel commandline") cmdQemuExec.Flags().BoolVarP(&usernet, "usernet", "U", false, "Enable usermode networking") + cmdQemuExec.Flags().StringSliceVar(&ignitionFragments, "add-ignition", nil, "Append well-known Ignition fragment: [\"autologin\"]") cmdQemuExec.Flags().StringVarP(&hostname, "hostname", "", "", "Set hostname via DHCP") cmdQemuExec.Flags().IntVarP(&memory, "memory", "m", 0, "Memory in MB") cmdQemuExec.Flags().StringVarP(&ignition, "ignition", "i", "", "Path to ignition config") cmdQemuExec.Flags().BoolVarP(&forceConfigInjection, "inject-ignition", "", false, "Force injecting Ignition config using guestfs") } +func renderFragments() (string, error) { + buf, err := ioutil.ReadFile(ignition) + if err != nil { + return "", err + } + config, _, err := v3.Parse(buf) + for _, fragtype := range ignitionFragments { + switch fragtype { + case "autologin": + newconf := v3.Merge(config, conf.GetAutologin()) + config = newconf + break + default: + return "", fmt.Errorf("Unknown fragment: %s", fragtype) + } + } + + newbuf, err := json.Marshal(config) + if err != nil { + return "", err + } + tmpf, err := ioutil.TempFile("", "qemuexec-ign") + if err != nil { + return "", err + } + if _, err := tmpf.Write(newbuf); err != nil { + return "", err + } + + return tmpf.Name(), nil +} + func runQemuExec(cmd *cobra.Command, args []string) error { var err error + cleanupIgnition := false + if len(ignitionFragments) > 0 { + newconfig, err := renderFragments() + if err != nil { + return errors.Wrapf(err, "rendering fragments") + } + ignition = newconfig + cleanupIgnition = true + } + if kola.Options.IgnitionVersion == "v2" { + buf, err := ioutil.ReadFile(ignition) + if err != nil { + return err + } + config, _, err := v3.Parse(buf) + if err != nil { + return errors.Wrapf(err, "parsing %s", ignition) + } + ignc2, err := ignconverter.Translate3to2(config) + if err != nil { + return err + } + ignc2buf, err := json.Marshal(ignc2) + if err != nil { + return err + } + tmpf, err := ioutil.TempFile("", "qemuexec-ign-conv") + if err != nil { + return err + } + if _, err := tmpf.Write(ignc2buf); err != nil { + return err + } + if cleanupIgnition { + os.Remove(ignition) + } + cleanupIgnition = true + ignition = tmpf.Name() + } + defer func() { + if cleanupIgnition { + os.Remove(ignition) + } + }() builder := platform.NewBuilder(ignition, forceConfigInjection) if len(knetargs) > 0 { diff --git a/mantle/platform/conf/conf.go b/mantle/platform/conf/conf.go index b0c7af8c0a..33a64a4afc 100644 --- a/mantle/platform/conf/conf.go +++ b/mantle/platform/conf/conf.go @@ -984,3 +984,32 @@ func (c *Conf) IsIgnition() bool { func (c *Conf) IsEmpty() bool { return !c.IsIgnition() && c.cloudconfig == nil && c.script == "" } + +func getAutologinFragment(name, args string) v3types.Unit { + contents := fmt.Sprintf(`[Service] +ExecStart= +ExecStart=-/sbin/agetty --autologin core -o '-p -f core' %s %%I $TERM +`, args) + return v3types.Unit{ + Name: name, + Dropins: []v3types.Dropin{ + { + Name: "10-autologin.conf", + Contents: &contents, + }, + }, + } +} + +// GetAutologin returns an Ignition config to automatic login on consoles +func GetAutologin() v3types.Config { + conf := v3types.Config{ + Ignition: v3types.Ignition{ + Version: "3.0.0", + }, + Systemd: v3types.Systemd{}, + } + conf.Systemd.Units = append(conf.Systemd.Units, getAutologinFragment("getty@.service", "--noclear")) + conf.Systemd.Units = append(conf.Systemd.Units, getAutologinFragment("serial-getty@.service", "--keep-baud 115200,38400,9600")) + return conf +} diff --git a/src/cmd-run b/src/cmd-run index 26ad53866f..44049e7a16 100755 --- a/src/cmd-run +++ b/src/cmd-run @@ -183,16 +183,6 @@ EOF rowcol=$(stty -a | tr ';' '\n' | grep -e 'rows\|columns' | tr '\n' ' ' ) rowcol=$(echo "stty ${rowcol}" | base64 --wrap 0) -append_systemd_unit "{ - \"name\": \"serial-getty@${DEFAULT_TERMINAL}.service\", - \"dropins\": [ - { - \"name\": \"autologin-core.conf\", - \"contents\": \"[Service]\\nTTYVTDisallocate=no\\nExecStart=\\nExecStart=-/usr/sbin/agetty --autologin ${TARGET_USER} --noclear %I \$TERM\\n\" - } - ] -}" - f=$(mktemp) cat > "${f}" <