diff --git a/src/config/config.go b/src/config/config.go index 6c40d73b37..5984d45ee5 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -40,6 +40,7 @@ type Config struct { SkipInstallationDiskCleanup bool EnableSkipMcoReboot bool NotifyNumReboots bool + CoreosImage string } func printHelpAndExit(err error) { @@ -79,6 +80,7 @@ func (c *Config) ProcessArgs(args []string) { flagSet.BoolVar(&c.SkipInstallationDiskCleanup, "skip-installation-disk-cleanup", false, "Skip installation disk cleanup gives disk management to coreos-installer in case needed") flagSet.BoolVar(&c.EnableSkipMcoReboot, "enable-skip-mco-reboot", false, "indicate assisted installer to generate settings to match MCO requirements for skipping reboot after firstboot") flagSet.BoolVar(&c.NotifyNumReboots, "notify-num-reboots", false, "indicate number of reboots should be notified as event") + flagSet.StringVar(&c.CoreosImage, "coreos-image", "", "CoreOS image to install to the existing root") var installerArgs string flagSet.StringVar(&installerArgs, "installer-args", "", "JSON array of additional coreos-installer arguments") diff --git a/src/config/config_test.go b/src/config/config_test.go index f479a7e2e1..e5b4587149 100644 --- a/src/config/config_test.go +++ b/src/config/config_test.go @@ -66,6 +66,12 @@ var _ = Describe("ProcessArgs", func() { Expect(config.InfraEnvID).To(Equal("9f2a26d7-10a6-4be0-b1c2-e895ad3b04b8")) }) + It("should set coreos image when provided", func() { + config := &Config{} + arguments := []string{"--role", string(models.HostRoleBootstrap), "--infra-env-id", "9f2a26d7-10a6-4be0-b1c2-e895ad3b04b8", "--cluster-id", "0ae63135-5f7c-431e-9c72-0efaf2cb83b8", "--coreos-image", "example.com/coreos/os:latest"} + config.ProcessArgs(arguments) + Expect(config.CoreosImage).To(Equal("example.com/coreos/os:latest")) + }) }) var _ = Describe("SetInstallerArgs", func() { diff --git a/src/installer/installer.go b/src/installer/installer.go index c582085220..9a66049364 100644 --- a/src/installer/installer.go +++ b/src/installer/installer.go @@ -155,14 +155,17 @@ func (i *installer) InstallNode() error { } } - if i.EnableSkipMcoReboot { - i.skipMcoReboot(ignitionPath) - } + // we can't do any of this if we're installing to the existing root + if i.Config.CoreosImage == "" { + if i.EnableSkipMcoReboot { + i.skipMcoReboot(ignitionPath) + } - if err = i.ops.SetBootOrder(i.Device); err != nil { - i.log.WithError(err).Warnf("Failed to set boot order") - // Ignore the error for now so it doesn't fail the installation in case it fails - //return err + if err = i.ops.SetBootOrder(i.Device); err != nil { + i.log.WithError(err).Warnf("Failed to set boot order") + // Ignore the error for now so it doesn't fail the installation in case it fails + //return err + } } if isBootstrap { @@ -321,7 +324,7 @@ func convertToOverwriteKargs(args []string) []string { } func (i *installer) cleanupInstallDevice() { - if i.DryRunEnabled || i.Config.SkipInstallationDiskCleanup { + if i.DryRunEnabled || i.Config.SkipInstallationDiskCleanup || i.Config.CoreosImage != "" { i.log.Infof("skipping installation disk cleanup") } else { err := i.cleanupDevice.CleanupInstallDevice(i.Config.Device) @@ -365,7 +368,11 @@ func (i *installer) writeImageToDisk(ignitionPath string) error { interval := time.Second liveLogger := coreos_logger.NewCoreosInstallerLogWriter(i.log, i.inventoryClient, i.Config.InfraEnvID, i.Config.HostID) err := utils.Retry(3, interval, i.log, func() error { - return i.ops.WriteImageToDisk(liveLogger, ignitionPath, i.Device, i.Config.InstallerArgs) + if i.Config.CoreosImage == "" { + return i.ops.WriteImageToDisk(liveLogger, ignitionPath, i.Device, i.Config.InstallerArgs) + } else { + return i.ops.WriteImageToExistingRoot(liveLogger, ignitionPath) + } }) if err != nil { i.log.WithError(err).Error("Failed to write image to disk") @@ -1044,7 +1051,11 @@ func RunInstaller(installerConfig *config.Config, logger *logrus.Logger) error { ) // Try to format requested disks. May fail formatting some disks, this is not an error. - ai.FormatDisks() + if installerConfig.CoreosImage == "" { + ai.FormatDisks() + } else { + logger.Info("not attempting disk format when installing to boot device") + } if err = ai.InstallNode(); err != nil { ai.UpdateHostInstallProgress(models.HostStageFailed, err.Error()) diff --git a/src/installer/installer_test.go b/src/installer/installer_test.go index 2fac4a11f1..0f4361a9b4 100644 --- a/src/installer/installer_test.go +++ b/src/installer/installer_test.go @@ -114,7 +114,7 @@ var _ = Describe("installer HostRoleMaster role", func() { mockops.EXPECT().WriteImageToDisk(gomock.Any(), filepath.Join(InstallDir, "master-host-id.ign"), device, extra).Return(nil).Times(1) } - setBootOrderSuccess := func(extra interface{}) { + setBootOrderSuccess := func() { mockops.EXPECT().SetBootOrder(device).Return(nil).Times(1) } @@ -389,7 +389,7 @@ var _ = Describe("installer HostRoleMaster role", func() { downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign") writeToDiskSuccess(gomock.Any()) reportLogProgressSuccess() - setBootOrderSuccess(gomock.Any()) + setBootOrderSuccess() uploadLogsSuccess(true) ironicAgentDoesntExist() rebootSuccess() @@ -425,7 +425,7 @@ var _ = Describe("installer HostRoleMaster role", func() { downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign") writeToDiskSuccess(gomock.Any()) reportLogProgressSuccess() - setBootOrderSuccess(gomock.Any()) + setBootOrderSuccess() uploadLogsSuccess(true) ironicAgentDoesntExist() rebootSuccess() @@ -449,7 +449,7 @@ var _ = Describe("installer HostRoleMaster role", func() { //HostRoleMaster flow: downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign") writeToDiskSuccess(gomock.Any()) - setBootOrderSuccess(gomock.Any()) + setBootOrderSuccess() getEncapsulatedMcSuccess(nil) overwriteImageSuccess() ret := installerObj.InstallNode() @@ -480,7 +480,7 @@ var _ = Describe("installer HostRoleMaster role", func() { downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign") writeToDiskSuccess(gomock.Any()) reportLogProgressSuccess() - setBootOrderSuccess(gomock.Any()) + setBootOrderSuccess() uploadLogsSuccess(true) ironicAgentDoesntExist() rebootSuccess() @@ -513,7 +513,7 @@ var _ = Describe("installer HostRoleMaster role", func() { //HostRoleMaster flow: downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign") writeToDiskSuccess(gomock.Any()) - setBootOrderSuccess(gomock.Any()) + setBootOrderSuccess() uploadLogsSuccess(true) reportLogProgressSuccess() ironicAgentDoesntExist() @@ -554,7 +554,7 @@ var _ = Describe("installer HostRoleMaster role", func() { downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign") writeToDiskSuccess(gomock.Any()) reportLogProgressSuccess() - setBootOrderSuccess(gomock.Any()) + setBootOrderSuccess() uploadLogsSuccess(true) ironicAgentDoesntExist() rebootSuccess() @@ -584,7 +584,7 @@ var _ = Describe("installer HostRoleMaster role", func() { //HostRoleMaster flow: downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign") writeToDiskSuccess(gomock.Any()) - setBootOrderSuccess(gomock.Any()) + setBootOrderSuccess() getEncapsulatedMcSuccess(nil) overwriteImageSuccess() ret := installerObj.InstallNode() @@ -615,7 +615,7 @@ var _ = Describe("installer HostRoleMaster role", func() { //HostRoleMaster flow: downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign") writeToDiskSuccess(gomock.Any()) - setBootOrderSuccess(gomock.Any()) + setBootOrderSuccess() uploadLogsSuccess(true) reportLogProgressSuccess() ironicAgentDoesntExist() @@ -637,7 +637,7 @@ var _ = Describe("installer HostRoleMaster role", func() { downloadFileSuccess(bootstrapIgn) downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign") writeToDiskSuccess(gomock.Any()) - setBootOrderSuccess(gomock.Any()) + setBootOrderSuccess() extractSecretFromIgnitionSuccess() getEncapsulatedMcSuccess(nil) overwriteImageSuccess() @@ -661,7 +661,7 @@ var _ = Describe("installer HostRoleMaster role", func() { //HostRoleMaster flow: downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign") writeToDiskSuccess(gomock.Any()) - setBootOrderSuccess(gomock.Any()) + setBootOrderSuccess() getEncapsulatedMcSuccess(nil) overwriteImageSuccess() ret := installerObj.InstallNode() @@ -748,7 +748,7 @@ var _ = Describe("installer HostRoleMaster role", func() { //HostRoleMaster flow: downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign") writeToDiskSuccess(gomock.Any()) - setBootOrderSuccess(gomock.Any()) + setBootOrderSuccess() uploadLogsSuccess(true) reportLogProgressSuccess() ironicAgentDoesntExist() @@ -847,18 +847,23 @@ var _ = Describe("installer HostRoleMaster role", func() { }) }) Context("Master role", func() { - installerArgs := []string{"-n", "--append-karg", "nameserver=8.8.8.8"} - conf := config.Config{Role: string(models.HostRoleMaster), - ClusterID: "cluster-id", - InfraEnvID: "infra-env-id", - HostID: "host-id", - Device: "/dev/vda", - URL: "https://assisted-service.com:80", - OpenshiftVersion: openShiftVersion, - InstallerArgs: installerArgs, - EnableSkipMcoReboot: withEnableSkipMcoReboot, - } + var ( + conf config.Config + installerArgs []string + ) + BeforeEach(func() { + installerArgs = []string{"-n", "--append-karg", "nameserver=8.8.8.8"} + conf = config.Config{Role: string(models.HostRoleMaster), + ClusterID: "cluster-id", + InfraEnvID: "infra-env-id", + HostID: "host-id", + Device: "/dev/vda", + URL: "https://assisted-service.com:80", + OpenshiftVersion: openShiftVersion, + InstallerArgs: installerArgs, + EnableSkipMcoReboot: withEnableSkipMcoReboot, + } installerObj = NewAssistedInstaller(l, conf, mockops, mockbmclient, k8sBuilder, mockIgnition, cleanupDevice) evaluateDiskSymlinkSuccess() @@ -873,7 +878,7 @@ var _ = Describe("installer HostRoleMaster role", func() { mkdirSuccess(InstallDir) downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign") writeToDiskSuccess(installerArgs) - setBootOrderSuccess(gomock.Any()) + setBootOrderSuccess() uploadLogsSuccess(false) reportLogProgressSuccess() ironicAgentDoesntExist() @@ -884,6 +889,28 @@ var _ = Describe("installer HostRoleMaster role", func() { Expect(ret).Should(BeNil()) }) + It("installs to the existing root and does not skip reboot, clean install device, or set boot order when coreosImage is set", func() { + installerObj.Config.CoreosImage = "example.com/coreos/os:latest" + updateProgressSuccess([][]string{{string(models.HostStageStartingInstallation), conf.Role}, + {string(models.HostStageInstalling), conf.Role}, + {string(models.HostStageWritingImageToDisk)}, + {string(models.HostStageRebooting)}, + }) + cleanupDevice.EXPECT().CleanupInstallDevice(device).Times(0) + mkdirSuccess(InstallDir) + downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign") + mockops.EXPECT().WriteImageToExistingRoot(gomock.Any(), filepath.Join(InstallDir, "master-host-id.ign")).Return(nil).Times(1) + mockops.EXPECT().SetBootOrder(device).Times(0) + uploadLogsSuccess(false) + reportLogProgressSuccess() + ironicAgentDoesntExist() + rebootSuccess() + mockops.EXPECT().GetEncapsulatedMC(gomock.Any()).Times(0) + mockops.EXPECT().OverwriteOsImage(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) + ret := installerObj.InstallNode() + Expect(ret).Should(BeNil()) + }) + It("HostRoleMaster role happy flow with skipping disk cleanup", func() { installerObj.Config.SkipInstallationDiskCleanup = true cleanupDevice.EXPECT().CleanupInstallDevice(gomock.Any()).Return(nil).Times(0) @@ -896,7 +923,7 @@ var _ = Describe("installer HostRoleMaster role", func() { mkdirSuccess(InstallDir) downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign") writeToDiskSuccess(installerArgs) - setBootOrderSuccess(gomock.Any()) + setBootOrderSuccess() uploadLogsSuccess(false) reportLogProgressSuccess() ironicAgentDoesntExist() @@ -963,7 +990,7 @@ var _ = Describe("installer HostRoleMaster role", func() { mkdirSuccess(InstallDir) downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign") writeToDiskSuccess(installerArgs) - setBootOrderSuccess(gomock.Any()) + setBootOrderSuccess() uploadLogsSuccess(false) reportLogProgressSuccess() getEncapsulatedMcSuccess(nil) @@ -1018,7 +1045,7 @@ var _ = Describe("installer HostRoleMaster role", func() { uploadLogsSuccess(false) reportLogProgressSuccess() writeToDiskSuccess(installerArgs) - setBootOrderSuccess(gomock.Any()) + setBootOrderSuccess() getEncapsulatedMcSuccess(nil) overwriteImageSuccess() ironicAgentDoesntExist() @@ -1070,7 +1097,7 @@ var _ = Describe("installer HostRoleMaster role", func() { mkdirSuccess(InstallDir) downloadHostIgnitionSuccess(infraEnvId, hostId, "worker-host-id.ign") mockops.EXPECT().WriteImageToDisk(gomock.Any(), filepath.Join(InstallDir, "worker-host-id.ign"), device, nil).Return(nil).Times(1) - setBootOrderSuccess(gomock.Any()) + setBootOrderSuccess() // failure must do nothing reportLogProgressSuccess() mockops.EXPECT().UploadInstallationLogs(false).Return("", errors.Errorf("Dummy")).Times(1) @@ -1176,7 +1203,7 @@ var _ = Describe("installer HostRoleMaster role", func() { singleNodeMergeIgnitionSuccess() downloadHostIgnitionSuccess(infraEnvId, hostId, "master-host-id.ign") mockops.EXPECT().WriteImageToDisk(gomock.Any(), singleNodeMasterIgnitionPath, device, nil).Return(nil).Times(1) - setBootOrderSuccess(gomock.Any()) + setBootOrderSuccess() uploadLogsSuccess(true) reportLogProgressSuccess() ironicAgentDoesntExist() diff --git a/src/ops/mock_ops.go b/src/ops/mock_ops.go index 9b497b813a..8aff008345 100644 --- a/src/ops/mock_ops.go +++ b/src/ops/mock_ops.go @@ -399,3 +399,17 @@ func (mr *MockOpsMockRecorder) WriteImageToDisk(liveLogger, ignitionPath, device mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteImageToDisk", reflect.TypeOf((*MockOps)(nil).WriteImageToDisk), liveLogger, ignitionPath, device, extraArgs) } + +// WriteImageToExistingRoot mocks base method. +func (m *MockOps) WriteImageToExistingRoot(liveLogger io.Writer, ignitionPath string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WriteImageToExistingRoot", liveLogger, ignitionPath) + ret0, _ := ret[0].(error) + return ret0 +} + +// WriteImageToExistingRoot indicates an expected call of WriteImageToExistingRoot. +func (mr *MockOpsMockRecorder) WriteImageToExistingRoot(liveLogger, ignitionPath any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteImageToExistingRoot", reflect.TypeOf((*MockOps)(nil).WriteImageToExistingRoot), liveLogger, ignitionPath) +} diff --git a/src/ops/ops.go b/src/ops/ops.go index 058fffe733..a1b99545f3 100644 --- a/src/ops/ops.go +++ b/src/ops/ops.go @@ -38,6 +38,7 @@ import ( ) const ( + dockerConfigFile = "/root/.docker/config.json" coreosInstallerExecutable = "coreos-installer" dryRunCoreosInstallerExecutable = "dry-installer" encapsulatedMachineConfigFile = "/etc/ignition-machine-config-encapsulated.json" @@ -49,6 +50,7 @@ const ( type Ops interface { Mkdir(dirName string) error WriteImageToDisk(liveLogger io.Writer, ignitionPath string, device string, extraArgs []string) error + WriteImageToExistingRoot(liveLogger io.Writer, ignitionPath string) error Reboot(delay string) error SetBootOrder(device string) error ExtractFromIgnition(ignitionPath string, fileToExtract string) error @@ -125,6 +127,71 @@ func (o *ops) SystemctlAction(action string, args ...string) error { return errors.Wrapf(err, "Failed executing systemctl %s %s", action, args) } +var ostreeOutputRegex = regexp.MustCompile(`Imported: (\w+)`) + +func (o *ops) importOSTreeCommit(liveLogger io.Writer) (string, error) { + ostreeReleasePullSpec := fmt.Sprintf("ostree-unverified-registry:%s", o.installerConfig.CoreosImage) + out, err := o.ExecPrivilegeCommand(liveLogger, "ostree", "container", "unencapsulate", "--authfile", dockerConfigFile, "--quiet", "--repo", "/ostree/repo", ostreeReleasePullSpec) + if err != nil { + return "", errors.Wrapf(err, "failed to unencapsulate rhcos payload image: %s", out) + } + + matches := ostreeOutputRegex.FindStringSubmatch(out) + if matches == nil { + return "", fmt.Errorf("got unexpected output from unencapsulate: \"%s\"", out) + } + + return matches[1], nil +} + +func (o *ops) WriteImageToExistingRoot(liveLogger io.Writer, ignitionPath string) error { + out, err := o.ExecPrivilegeCommand(liveLogger, "mount", "/sysroot", "-o", "remount,rw") + if err != nil { + return errors.Wrapf(err, "failed to remount sysroot: %s", out) + } + out, err = o.ExecPrivilegeCommand(liveLogger, "mount", "/boot", "-o", "remount,rw") + if err != nil { + return errors.Wrapf(err, "failed to remount boot: %s", out) + } + + out, err = o.ExecPrivilegeCommand(liveLogger, "ostree", "admin", "stateroot-init", "install") + if err != nil { + return errors.Wrapf(err, "failed creating new stateroot: %s", out) + } + + commit, err := o.importOSTreeCommit(liveLogger) + if err != nil { + return err + } + o.log.Infof("imported commit %s", commit) + + out, err = o.ExecPrivilegeCommand(liveLogger, "ostree", "admin", "deploy", + "--stateroot", "install", + "--karg", "$ignition_firstboot", + "--karg", defaultIgnitionPlatformId, + commit) + if err != nil { + return errors.Wrapf(err, "failed to deploy commit to stateroot: %s", out) + } + + out, err = o.ExecPrivilegeCommand(liveLogger, "mkdir", "/boot/ignition") + if err != nil { + return errors.Wrapf(err, "failed to create ignition directory: %s", out) + } + + out, err = o.ExecPrivilegeCommand(liveLogger, "cp", ignitionPath, "/boot/ignition/config.ign") + if err != nil { + return errors.Wrapf(err, "failed to copy ignition file: %s", out) + } + + out, err = o.ExecPrivilegeCommand(liveLogger, "touch", "/boot/ignition.firstboot") + if err != nil { + return errors.Wrapf(err, "failed to write ignition marker file: %s", out) + } + + return nil +} + func (o *ops) WriteImageToDisk(liveLogger io.Writer, ignitionPath string, device string, extraArgs []string) error { allArgs := installerArgs(ignitionPath, device, extraArgs) o.log.Infof("Writing image and ignition to disk with arguments: %v", allArgs) @@ -952,7 +1019,7 @@ func (o *ops) OverwriteOsImage(osImage, device string, extraArgs []string) error "--sysroot", "/mnt", "--authfile", - "/root/.docker/config.json", + dockerConfigFile, "--imgref", "ostree-unverified-registry:"+osImage, "--karg", diff --git a/src/ops/ops_test.go b/src/ops/ops_test.go index 82d4b066d6..3cc2e89a43 100644 --- a/src/ops/ops_test.go +++ b/src/ops/ops_test.go @@ -13,6 +13,7 @@ import ( "reflect" "github.com/openshift/assisted-installer/src/ops/execute" + "github.com/openshift/assisted-installer/src/utils" "github.com/coreos/ignition/v2/config/v3_2/types" "github.com/go-openapi/swag" @@ -525,3 +526,83 @@ var _ = Describe("get number of reboots", func() { Expect(err).To(HaveOccurred()) }) }) + +var _ = Describe("importOSTreeCommit", func() { + const osImage = "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:d21f2ed754a66d18b0a13a59434fa4dc36abd4320e78f3be83a3e29e21e3c2f9" + var ( + l = logrus.New() + ctrl *gomock.Controller + execMock *execute.MockExecute + o *ops + ) + + BeforeEach(func() { + ctrl = gomock.NewController(GinkgoT()) + execMock = execute.NewMockExecute(ctrl) + o = &ops{ + log: l, + logWriter: utils.NewLogWriter(l), + installerConfig: &config.Config{ + CoreosImage: osImage, + }, + executor: execMock, + } + }) + + It("returns the commit when successful", func() { + pullSpec := "ostree-unverified-registry:" + osImage + execMock.EXPECT().ExecCommand(gomock.Any(), + "nsenter", "--target", "1", "--cgroup", "--mount", "--ipc", "--pid", "--", + "ostree", "container", "unencapsulate", + "--authfile", "/root/.docker/config.json", + "--quiet", + "--repo", "/ostree/repo", + pullSpec, + ).Return("Imported: bd58af6b04f8ed7e3e72d1af34439fb03a5640a516edd200fb26775df346ae25\n", nil) + commit, err := o.importOSTreeCommit(io.Discard) + Expect(err).NotTo(HaveOccurred()) + Expect(commit).To(Equal("bd58af6b04f8ed7e3e72d1af34439fb03a5640a516edd200fb26775df346ae25")) + }) + + It("fails when the command fails", func() { + pullSpec := "ostree-unverified-registry:" + osImage + execMock.EXPECT().ExecCommand(gomock.Any(), + "nsenter", "--target", "1", "--cgroup", "--mount", "--ipc", "--pid", "--", + "ostree", "container", "unencapsulate", + "--authfile", "/root/.docker/config.json", + "--quiet", + "--repo", "/ostree/repo", + pullSpec, + ).Return("", fmt.Errorf("failed")) + _, err := o.importOSTreeCommit(io.Discard) + Expect(err).To(HaveOccurred()) + }) + + It("fails when the output is not as expected", func() { + pullSpec := "ostree-unverified-registry:" + osImage + execMock.EXPECT().ExecCommand(gomock.Any(), + "nsenter", "--target", "1", "--cgroup", "--mount", "--ipc", "--pid", "--", + "ostree", "container", "unencapsulate", + "--authfile", "/root/.docker/config.json", + "--quiet", + "--repo", "/ostree/repo", + pullSpec, + ).Return("commit bd58af6b04f8ed7e3e72d1af34439fb03a5640a516edd200fb26775df346ae25\n", nil) + _, err := o.importOSTreeCommit(io.Discard) + Expect(err).To(HaveOccurred()) + }) + + It("fails when the output cannot be parsed", func() { + pullSpec := "ostree-unverified-registry:" + osImage + execMock.EXPECT().ExecCommand(gomock.Any(), + "nsenter", "--target", "1", "--cgroup", "--mount", "--ipc", "--pid", "--", + "ostree", "container", "unencapsulate", + "--authfile", "/root/.docker/config.json", + "--quiet", + "--repo", "/ostree/repo", + pullSpec, + ).Return("some nonsense here", nil) + _, err := o.importOSTreeCommit(io.Discard) + Expect(err).To(HaveOccurred()) + }) +})