Skip to content

Commit

Permalink
Produce state.yaml files on install, upgrade and reset commands (#278)
Browse files Browse the repository at this point in the history
This commit makes upgrade|reset|install to create and upgrade
`state.yaml` file including system wide data (deployed images,
partition labels, etc.)

It introduces the concept of installation state and stores such
a data in `state.yaml` file in two different locations, state partition root
and recovery partition root.

The purpose of this duplication is to be able to always find the
state.yaml file in a known location regardless of the image we are
booting.
  • Loading branch information
davidcassany committed Jul 6, 2022
1 parent a81c51f commit 02bb111
Show file tree
Hide file tree
Showing 25 changed files with 1,359 additions and 150 deletions.
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,20 @@ test_deps:
go install -mod=mod github.com/onsi/ginkgo/v2/ginkgo

test: $(GINKGO)
ginkgo run --label-filter '!root' --fail-fast --slow-spec-threshold 30s --race --covermode=atomic --coverprofile=coverage.txt -p -r ${PKG}
ginkgo run --label-filter '!root' --fail-fast --slow-spec-threshold 30s --race --covermode=atomic --coverprofile=coverage.txt --coverpkg=github.com/rancher/elemental-cli/... -p -r ${PKG}

test_root: $(GINKGO)
ifneq ($(shell id -u), 0)
@echo "This tests require root/sudo to run."
@exit 1
else
ginkgo run --label-filter root --fail-fast --slow-spec-threshold 30s --race --covermode=atomic --coverprofile=coverage_root.txt -procs=1 -r ${PKG}
ginkgo run --label-filter root --fail-fast --slow-spec-threshold 30s --race --covermode=atomic --coverprofile=coverage_root.txt --coverpkg=github.com/rancher/elemental-cli/... -procs=1 -r ${PKG}
endif

# Useful test run for local dev. It does not run tests that require root and it does not run tests that require systemctl checks
# which results in a escalation prompt for privileges. This can block a run until a password or the prompt is cancelled
test_no_root_no_systemctl:
ginkgo run --label-filter '!root && !systemctl' --fail-fast --slow-spec-threshold 30s --race --covermode=atomic --coverprofile=coverage.txt -p -r ${PKG}
ginkgo run --label-filter '!root && !systemctl' --fail-fast --slow-spec-threshold 30s --race --covermode=atomic --coverprofile=coverage.txt --coverpkg=github.com/rancher/elemental-cli/... -p -r ${PKG}


license-check:
Expand Down
2 changes: 1 addition & 1 deletion cmd/build-iso_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ var _ = Describe("BuidISO", Label("iso", "cmd"), func() {
It("Errors out if rootfs is a non valid argument", Label("flags"), func() {
_, _, err := executeCommandC(rootCmd, "build-iso", "/no/image/reference")
Expect(err).ToNot(BeNil())
Expect(err.Error()).To(ContainSubstring("unknown source type for"))
Expect(err.Error()).To(ContainSubstring("invalid image reference"))
})
It("Errors out if overlay roofs path does not exist", Label("flags"), func() {
_, _, err := executeCommandC(
Expand Down
6 changes: 6 additions & 0 deletions cmd/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,12 @@ var _ = Describe("Config", Label("config"), func() {
mainDisk := block.Disk{
Name: "device",
Partitions: []*block.Partition{
{
Name: "device1",
FilesystemLabel: "COS_RECOVERY",
Type: "ext4",
MountPoint: constants.RunningStateDir,
},
{
Name: "device2",
FilesystemLabel: "COS_STATE",
Expand Down
2 changes: 1 addition & 1 deletion cmd/pull-image.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func NewPullImageCmd(root *cobra.Command, addCheckRoot bool) *cobra.Command {

l := luet.NewLuet(luet.WithLogger(cfg.Logger), luet.WithAuth(auth), luet.WithPlugins(plugins...))
l.VerifyImageUnpack = verify
err = l.Unpack(destination, image, local)
_, err = l.Unpack(destination, image, local)

if err != nil {
cfg.Logger.Error(err.Error())
Expand Down
2 changes: 1 addition & 1 deletion pkg/action/build-disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func BuildDiskRun(cfg *v1.BuildConfig, spec *v1.RawDiskArchEntry, imgType string
cfg.Logger.Error(err)
return err
}
err = e.DumpSource(
_, err = e.DumpSource(
filepath.Join(baseDir, pkg.Target),
imgSource,
)
Expand Down
2 changes: 1 addition & 1 deletion pkg/action/build-iso.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ func (b BuildISOAction) burnISO(root string) error {

func (b BuildISOAction) applySources(target string, sources ...*v1.ImageSource) error {
for _, src := range sources {
err := b.e.DumpSource(target, src)
_, err := b.e.DumpSource(target, src)
if err != nil {
return err
}
Expand Down
10 changes: 5 additions & 5 deletions pkg/action/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,21 +92,21 @@ var _ = Describe("Runtime Actions", func() {
imageSrc, _ := v1.NewSrcFromURI("channel:live/bootloader")
iso.Image = []*v1.ImageSource{imageSrc}

luet.UnpackSideEffect = func(target string, image string, local bool) error {
luet.UnpackSideEffect = func(target string, image string, local bool) (*v1.DockerImageMeta, error) {
bootDir := filepath.Join(target, "boot")
err := utils.MkdirAll(fs, bootDir, constants.DirPerm)
if err != nil {
return err
return nil, err
}
_, err = fs.Create(filepath.Join(bootDir, "vmlinuz"))
if err != nil {
return err
return nil, err
}
_, err = fs.Create(filepath.Join(bootDir, "initrd"))
if err != nil {
return err
return nil, err
}
return nil
return nil, nil
}

buildISO := action.NewBuildISOAction(cfg, iso)
Expand Down
77 changes: 74 additions & 3 deletions pkg/action/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package action

import (
"fmt"
"path/filepath"
"time"

cnst "github.com/rancher/elemental-cli/pkg/constants"
"github.com/rancher/elemental-cli/pkg/elemental"
Expand All @@ -41,6 +43,69 @@ func (i *InstallAction) installHook(hook string, chroot bool) error {
return Hook(&i.cfg.Config, hook, i.cfg.Strict, i.cfg.CloudInitPaths...)
}

func (i *InstallAction) createInstallStateYaml(sysMeta, recMeta interface{}) error {
if i.spec.Partitions.State == nil || i.spec.Partitions.Recovery == nil {
return fmt.Errorf("undefined state or recovery partition")
}

// If recovery image is a copyied file from active reuse the same source and metadata
recSource := i.spec.Recovery.Source
if i.spec.Recovery.Source.IsFile() && i.spec.Active.File == i.spec.Recovery.Source.Value() {
recMeta = sysMeta
recSource = i.spec.Active.Source
}

installState := &v1.InstallState{
Date: time.Now().Format(time.RFC3339),
Partitions: map[string]*v1.PartitionState{
cnst.StatePartName: {
FSLabel: i.spec.Partitions.State.FilesystemLabel,
Images: map[string]*v1.ImageState{
cnst.ActiveImgName: {
Source: i.spec.Active.Source,
SourceMetadata: sysMeta,
Label: i.spec.Active.Label,
FS: i.spec.Active.FS,
},
cnst.PassiveImgName: {
Source: i.spec.Active.Source,
SourceMetadata: sysMeta,
Label: i.spec.Passive.Label,
FS: i.spec.Passive.FS,
},
},
},
cnst.RecoveryPartName: {
FSLabel: i.spec.Partitions.Recovery.FilesystemLabel,
Images: map[string]*v1.ImageState{
cnst.RecoveryImgName: {
Source: recSource,
SourceMetadata: recMeta,
Label: i.spec.Recovery.Label,
FS: i.spec.Recovery.FS,
},
},
},
},
}
if i.spec.Partitions.OEM != nil {
installState.Partitions[cnst.OEMPartName] = &v1.PartitionState{
FSLabel: i.spec.Partitions.OEM.FilesystemLabel,
}
}
if i.spec.Partitions.Persistent != nil {
installState.Partitions[cnst.PersistentPartName] = &v1.PartitionState{
FSLabel: i.spec.Partitions.Persistent.FilesystemLabel,
}
}

return i.cfg.WriteInstallState(
installState,
filepath.Join(i.spec.Partitions.State.MountPoint, cnst.InstallStateFile),
filepath.Join(i.spec.Partitions.Recovery.MountPoint, cnst.InstallStateFile),
)
}

type InstallAction struct {
cfg *v1.RunConfig
spec *v1.InstallSpec
Expand Down Expand Up @@ -103,7 +168,7 @@ func (i InstallAction) Run() (err error) {
})

// Deploy active image
err = e.DeployImage(&i.spec.Active, true)
systemMeta, err := e.DeployImage(&i.spec.Active, true)
if err != nil {
return err
}
Expand Down Expand Up @@ -164,12 +229,12 @@ func (i InstallAction) Run() (err error) {
return err
}
// Install Recovery
err = e.DeployImage(&i.spec.Recovery, false)
recoveryMeta, err := e.DeployImage(&i.spec.Recovery, false)
if err != nil {
return err
}
// Install Passive
err = e.DeployImage(&i.spec.Passive, false)
_, err = e.DeployImage(&i.spec.Passive, false)
if err != nil {
return err
}
Expand All @@ -179,6 +244,12 @@ func (i InstallAction) Run() (err error) {
return err
}

// Add state.yaml file on state and recovery partitions
err = i.createInstallStateYaml(systemMeta, recoveryMeta)
if err != nil {
return err
}

// Do not reboot/poweroff on cleanup errors
err = cleanup.Cleanup(err)
if err != nil {
Expand Down
74 changes: 68 additions & 6 deletions pkg/action/reset.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ limitations under the License.
package action

import (
"fmt"
"path/filepath"
"time"

cnst "github.com/rancher/elemental-cli/pkg/constants"
"github.com/rancher/elemental-cli/pkg/elemental"
v1 "github.com/rancher/elemental-cli/pkg/types/v1"
Expand Down Expand Up @@ -48,6 +52,60 @@ func NewResetAction(cfg *v1.RunConfig, spec *v1.ResetSpec) *ResetAction {
return &ResetAction{cfg: cfg, spec: spec}
}

func (r *ResetAction) updateInstallState(e *elemental.Elemental, cleanup *utils.CleanStack, meta interface{}) error {
if r.spec.Partitions.Recovery == nil || r.spec.Partitions.State == nil {
return fmt.Errorf("undefined state or recovery partition")
}

installState := &v1.InstallState{
Date: time.Now().Format(time.RFC3339),
Partitions: map[string]*v1.PartitionState{
cnst.StatePartName: {
FSLabel: r.spec.Partitions.State.FilesystemLabel,
Images: map[string]*v1.ImageState{
cnst.ActiveImgName: {
Source: r.spec.Active.Source,
SourceMetadata: meta,
Label: r.spec.Active.Label,
FS: r.spec.Active.FS,
},
cnst.PassiveImgName: {
Source: r.spec.Active.Source,
SourceMetadata: meta,
Label: r.spec.Passive.Label,
FS: r.spec.Passive.FS,
},
},
},
},
}
if r.spec.Partitions.OEM != nil {
installState.Partitions[cnst.OEMPartName] = &v1.PartitionState{
FSLabel: r.spec.Partitions.OEM.FilesystemLabel,
}
}
if r.spec.Partitions.Persistent != nil {
installState.Partitions[cnst.PersistentPartName] = &v1.PartitionState{
FSLabel: r.spec.Partitions.Persistent.FilesystemLabel,
}
}
if r.spec.State != nil && r.spec.State.Partitions != nil {
installState.Partitions[cnst.RecoveryPartName] = r.spec.State.Partitions[cnst.RecoveryPartName]
}

umount, err := e.MountRWPartition(r.spec.Partitions.Recovery)
if err != nil {
return err
}
cleanup.Push(umount)

return r.cfg.WriteInstallState(
installState,
filepath.Join(r.spec.Partitions.State.MountPoint, cnst.InstallStateFile),
filepath.Join(r.spec.Partitions.Recovery.MountPoint, cnst.InstallStateFile),
)
}

// ResetRun will reset the cos system to by following several steps
func (r ResetAction) Run() (err error) {
e := elemental.NewElemental(&r.cfg.Config)
Expand All @@ -60,7 +118,7 @@ func (r ResetAction) Run() (err error) {
}

// Unmount partitions if any is already mounted before formatting
err = e.UnmountPartitions(r.spec.Partitions.PartitionsByMountPoint(true))
err = e.UnmountPartitions(r.spec.Partitions.PartitionsByMountPoint(true, r.spec.Partitions.Recovery))
if err != nil {
return err
}
Expand All @@ -80,7 +138,6 @@ func (r ResetAction) Run() (err error) {
return err
}
}

}

// Reformat OEM
Expand All @@ -94,16 +151,16 @@ func (r ResetAction) Run() (err error) {
}
}
// Mount configured partitions
err = e.MountPartitions(r.spec.Partitions.PartitionsByMountPoint(false))
err = e.MountPartitions(r.spec.Partitions.PartitionsByMountPoint(false, r.spec.Partitions.Recovery))
if err != nil {
return err
}
cleanup.Push(func() error {
return e.UnmountPartitions(r.spec.Partitions.PartitionsByMountPoint(true))
return e.UnmountPartitions(r.spec.Partitions.PartitionsByMountPoint(true, r.spec.Partitions.Recovery))
})

// Deploy active image
err = e.DeployImage(&r.spec.Active, true)
meta, err := e.DeployImage(&r.spec.Active, true)
if err != nil {
return err
}
Expand Down Expand Up @@ -163,7 +220,7 @@ func (r ResetAction) Run() (err error) {
}

// Install Passive
err = e.DeployImage(&r.spec.Passive, false)
_, err = e.DeployImage(&r.spec.Passive, false)
if err != nil {
return err
}
Expand All @@ -173,6 +230,11 @@ func (r ResetAction) Run() (err error) {
return err
}

err = r.updateInstallState(e, cleanup, meta)
if err != nil {
return err
}

// Do not reboot/poweroff on cleanup errors
err = cleanup.Cleanup(err)
if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions pkg/action/reset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ var _ = Describe("Reset action tests", func() {
FilesystemLabel: "COS_OEM",
Type: "ext4",
},
{
Name: "device5",
FilesystemLabel: "COS_RECOVERY",
Type: "ext4",
},
},
}
ghwTest = v1mock.GhwMock{}
Expand Down

0 comments on commit 02bb111

Please sign in to comment.