diff --git a/cmd/limactl/start.go b/cmd/limactl/start.go index ba6652d7d1e..3120cc9eece 100644 --- a/cmd/limactl/start.go +++ b/cmd/limactl/start.go @@ -23,6 +23,7 @@ import ( "github.com/lima-vm/lima/pkg/store/filenames" "github.com/lima-vm/lima/pkg/templatestore" "github.com/lima-vm/lima/pkg/uiutil" + "github.com/lima-vm/lima/pkg/version" "github.com/lima-vm/lima/pkg/yqutil" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -332,6 +333,9 @@ func createInstance(ctx context.Context, st *creatorState, saveBrokenEditorBuffe if err := os.WriteFile(filePath, st.yBytes, 0o644); err != nil { return nil, err } + if err := os.WriteFile(filepath.Join(instDir, filenames.LimaVersion), []byte(version.Version), 0o444); err != nil { + return nil, err + } inst, err := store.Inspect(st.instName) if err != nil { diff --git a/pkg/store/filenames/filenames.go b/pkg/store/filenames/filenames.go index 466d552d0e1..6615b410feb 100644 --- a/pkg/store/filenames/filenames.go +++ b/pkg/store/filenames/filenames.go @@ -27,6 +27,7 @@ const ( const ( LimaYAML = "lima.yaml" + LimaVersion = "lima-version" // Lima version used to create instance CIDataISO = "cidata.iso" CIDataISODir = "cidata" BaseDisk = "basedisk" diff --git a/pkg/store/instance.go b/pkg/store/instance.go index 27e09d6e2c1..e01610320c8 100644 --- a/pkg/store/instance.go +++ b/pkg/store/instance.go @@ -16,12 +16,14 @@ import ( "text/template" "time" + "github.com/coreos/go-semver/semver" "github.com/docker/go-units" hostagentclient "github.com/lima-vm/lima/pkg/hostagent/api/client" "github.com/lima-vm/lima/pkg/limayaml" "github.com/lima-vm/lima/pkg/store/dirnames" "github.com/lima-vm/lima/pkg/store/filenames" "github.com/lima-vm/lima/pkg/textutil" + "github.com/sirupsen/logrus" ) type Status = string @@ -56,6 +58,7 @@ type Instance struct { Config *limayaml.LimaYAML `json:"config,omitempty"` SSHAddress string `json:"sshAddress,omitempty"` Protected bool `json:"protected"` + LimaVersion string `json:"limaVersion"` } func (inst *Instance) LoadYAML() (*limayaml.LimaYAML, error) { @@ -167,6 +170,16 @@ func Inspect(instName string) (*Instance, error) { } } } + + limaVersionFile := filepath.Join(instDir, filenames.LimaVersion) + if version, err := os.ReadFile(limaVersionFile); err == nil { + inst.LimaVersion = strings.TrimSpace(string(version)) + if _, err = parseLimaVersion(inst.LimaVersion); err != nil { + logrus.Warnf("treating lima version %q from %q as very latest release", inst.LimaVersion, limaVersionFile) + } + } else if !errors.Is(err, os.ErrNotExist) { + inst.Errors = append(inst.Errors, err) + } return inst, nil } @@ -423,3 +436,36 @@ func (inst *Instance) Unprotect() error { inst.Protected = false return nil } + +// parseLimaVersion parses a Lima version string by removing the leading "v" character and +// stripping everything from the first "-" forward (which are `git describe` artifacts and +// not semver pre-release markers). So "v0.19.1-16-gf3dc6ed.m" will be parsed as "0.19.1". +func parseLimaVersion(version string) (*semver.Version, error) { + version = strings.TrimPrefix(version, "v") + version, _, _ = strings.Cut(version, "-") + return semver.NewVersion(version) +} + +// LimaVersionGreaterThan returns true if the Lima version used to create an instance is greater +// than a specific older version. Always returns false if the Lima version is the empty string. +// Unparsable lima versions (like SHA1 commit ids) are treated as the latest version and return true. +// limaVersion is a `github describe` string, not a semantic version. So "0.19.1-16-gf3dc6ed.m" +// will be considered greater than "0.19.1". +func LimaVersionGreaterThan(limaVersion, oldVersion string) bool { + if limaVersion == "" { + return false + } + version, err := parseLimaVersion(limaVersion) + if err != nil { + return true + } + switch version.Compare(*semver.New(oldVersion)) { + case -1: + return false + case +1: + return true + case 0: + return strings.Contains(limaVersion, "-") + } + panic("unreachable") +} diff --git a/pkg/store/instance_test.go b/pkg/store/instance_test.go index bdbbba7d943..0d532673362 100644 --- a/pkg/store/instance_test.go +++ b/pkg/store/instance_test.go @@ -155,3 +155,12 @@ func TestPrintInstanceTableTwo(t *testing.T) { assert.NilError(t, err) assert.Equal(t, tableTwo, buf.String()) } + +func TestLimaVersionGreaterThan(t *testing.T) { + assert.Equal(t, LimaVersionGreaterThan("", "0.1.0"), false) + assert.Equal(t, LimaVersionGreaterThan("0.0.1", "0.1.0"), false) + assert.Equal(t, LimaVersionGreaterThan("0.1.0", "0.1.0"), false) + assert.Equal(t, LimaVersionGreaterThan("0.1.0-2", "0.1.0"), true) + assert.Equal(t, LimaVersionGreaterThan("0.2.0", "0.1.0"), true) + assert.Equal(t, LimaVersionGreaterThan("abacab", "0.1.0"), true) +} diff --git a/website/content/en/docs/dev/Internals/_index.md b/website/content/en/docs/dev/Internals/_index.md index 1089a83c66d..c1cd06ac7c8 100644 --- a/website/content/en/docs/dev/Internals/_index.md +++ b/website/content/en/docs/dev/Internals/_index.md @@ -30,6 +30,7 @@ having to specify an identity explicitly. An instance directory contains the following files: Metadata: +- `lima-version`: the Lima version used to create this instance - `lima.yaml`: the YAML - `protected`: empty file, used by `limactl protect`