diff --git a/pkg/daemon/daemon.go b/pkg/daemon/daemon.go index 0a86e0b54a..e2cdd738f2 100644 --- a/pkg/daemon/daemon.go +++ b/pkg/daemon/daemon.go @@ -873,6 +873,10 @@ func (dn *Daemon) LogSystemData() { } else { glog.Info("systemd service state: OK") } + + if err := checkNodeRpmOstreeVersion(); err != nil { + glog.Errorf("Failed to check rpm-ostree version: %v", err) + } } const ( diff --git a/pkg/daemon/rpm-ostree.go b/pkg/daemon/rpm-ostree.go index f35ecaccdd..a9fc3b93dc 100644 --- a/pkg/daemon/rpm-ostree.go +++ b/pkg/daemon/rpm-ostree.go @@ -5,10 +5,12 @@ import ( "fmt" "os" "os/exec" + "strconv" "strings" "time" "github.com/containers/image/v5/types" + yaml "github.com/ghodss/yaml" "github.com/golang/glog" "github.com/opencontainers/go-digest" pivotutils "github.com/openshift/machine-config-operator/pkg/daemon/pivot/utils" @@ -20,8 +22,21 @@ const ( numRetriesNetCommands = 5 // Pull secret. Written by the machine-config-operator kubeletAuthFile = "/var/lib/kubelet/config.json" + + // rpmOstreeVersionMinimum is the minimum required version + rpmOstreeVersionMinimum = "2021.14" ) +// rpmOstreeVersionOuter is YAML output by `rpm-ostree --version` +type rpmOstreeVersionOuter struct { + Root rpmOstreeVersionData `json:"rpm-ostree"` +} + +type rpmOstreeVersionData struct { + Version string `json:"Version"` + Features []string `json:"Features"` +} + // rpmOstreeState houses zero or more RpmOstreeDeployments // Subset of `rpm-ostree status --json` // https://github.com/projectatomic/rpm-ostree/blob/bce966a9812df141d38e3290f845171ec745aa4e/src/daemon/rpmostreed-deployment-utils.c#L227 @@ -100,6 +115,60 @@ func (r *RpmOstreeClient) loadStatus() (*rpmOstreeState, error) { return &rosState, nil } +func parseVer(s string) ([]int, error) { + r := []int{} + for _, s := range strings.Split(s, ".") { + n, err := strconv.Atoi(s) + if err != nil { + return nil, fmt.Errorf("Failed to parse %s: %v", s, err) + } + r = append(r, n) + } + return r, nil +} + +func validateVersion(current rpmOstreeVersionOuter) error { + requiredVer, err := parseVer(rpmOstreeVersionMinimum) + if err != nil { + return err + } + curVer, err := parseVer(current.Root.Version) + if err != nil { + return err + } + if len(curVer) < len(requiredVer) { + return fmt.Errorf("Too few components in %s, expected to match %s", current.Root.Version, rpmOstreeVersionMinimum) + } + for i, v := range requiredVer { + if curVer[i] < v { + return fmt.Errorf("Too old %s, expected to match %s", current.Root.Version, rpmOstreeVersionMinimum) + } + // Shortcut for e.g. 2022 > 2021 + if curVer[i] > v { + return nil + } + } + + return nil +} + +func checkNodeRpmOstreeVersion() error { + versionBytes, err := runGetOut("rpm-ostree", "--version") + if err != nil { + return err + } + var versionData rpmOstreeVersionOuter + if err := yaml.Unmarshal(versionBytes, &versionData); err != nil { + return fmt.Errorf("failed to parse rpm-ostree --version as YAML: %v", err) + } + + if err := validateVersion(versionData); err != nil { + return fmt.Errorf("Too old rpm-ostree: %v", err) + } + + return nil +} + func (r *RpmOstreeClient) Initialize() error { // This replicates https://github.com/coreos/rpm-ostree/pull/2945 // and can be removed when we have a new enough rpm-ostree with diff --git a/pkg/daemon/rpm-ostree_test.go b/pkg/daemon/rpm-ostree_test.go index 6159d3d2bf..703df4db17 100644 --- a/pkg/daemon/rpm-ostree_test.go +++ b/pkg/daemon/rpm-ostree_test.go @@ -1,5 +1,13 @@ package daemon +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/util/yaml" +) + /* * This file contains test code for the rpm-ostree client. It is meant to be used when * testing the daemon and mocking the responses that would normally be executed by the @@ -46,3 +54,39 @@ func (r RpmOstreeClientMock) GetStatus() (string, error) { func (r RpmOstreeClientMock) GetBootedDeployment() (*RpmOstreeDeployment, error) { return &RpmOstreeDeployment{}, nil } + +func TestParseVersion(t *testing.T) { + s := ` +rpm-ostree: + Version: '2021.14' + Git: v2021.14 + Features: + - bin-unit-tests + - compose + - rust + - fedora-integration + ` + var outer rpmOstreeVersionOuter + assert.Nil(t, yaml.Unmarshal([]byte(s), &outer)) + fmt.Printf("%v", outer) + assert.Equal(t, outer.Root.Version, "2021.14") +} + +func TestValidateVersion(t *testing.T) { + for _, old := range []string{"2019.5", "2021.6"} { + v := rpmOstreeVersionOuter{ + Root: rpmOstreeVersionData{ + Version: old, + }, + } + assert.NotNil(t, validateVersion(v)) + } + for _, newver := range []string{"2021.14", "2021.15", "2022.1"} { + v := rpmOstreeVersionOuter{ + Root: rpmOstreeVersionData{ + Version: newver, + }, + } + assert.Nil(t, validateVersion(v)) + } +}