support semver checkpoint formats · Entire

support semver checkpoint formats

1f1d021·

pfleidi·3w ago·3 files·+88 added/-28 removed

Parse checkpoint policy versions with golang.org/x/mod/semver so downgrade checks can compare minor, patch, and prerelease versions.

Keep read/write support explicit while canonicalizing equivalent branch-v1 forms through the format key.

Sessions

5b9a51224543View transcript

Changes

3

2 unmodified lines

3
4
5
6
6
7
8
9
10
11
4 unmodified lines

16
17
18
18
19
19
20
21
22
23
23
24
24
25
26
27
28
28
29
30
29
30
31
32
33
34
35
36
37
38
33
39
40
41
42
37
43
44
45
40
46
47
48
49
5 unmodified lines

55
56
57
52
58
59
60
61
16 unmodified lines

78
79
80
75
81
82
83
84

2 unmodified lines

import (
    "cmp"
    "fmt"
    "strconv"
    "strings"

"golang.org/x/mod/semver"
)

type CheckpointFamily string
4 unmodified lines

)

type CheckpointFormat struct {
    Family CheckpointFamily
    Major  int
    Family  CheckpointFamily
    Version string
}

func ParseFormat(raw string) (CheckpointFormat, error) {
    familyRaw, majorRaw, ok := strings.Cut(raw, "-v")
    if !ok || familyRaw == "" || majorRaw == "" {
        familyRaw, versionRaw, ok := strings.Cut(raw, "-")
        if !ok || familyRaw == "" || versionRaw == "" {
            return CheckpointFormat{}, fmt.Errorf("invalid checkpoint format %q", raw)
        }

major, err := strconv.Atoi(majorRaw)
        if err != nil || major <= 0 {
            return CheckpointFormat{}, fmt.Errorf("invalid checkpoint major %q", majorRaw)
        }
        if !semver.IsValid(versionRaw) {
            return CheckpointFormat{}, fmt.Errorf("invalid checkpoint version %q", versionRaw)
        }
        if semver.Major(versionRaw) == "v0" {
            return CheckpointFormat{}, fmt.Errorf("invalid checkpoint version %q", versionRaw)
        }
        if semver.Build(versionRaw) != "" {
            return CheckpointFormat{}, fmt.Errorf("checkpoint version %q must not include build metadata", versionRaw)
        }

return CheckpointFormat{Family: CheckpointFamily(familyRaw), Major: major}, nil
    }
    return CheckpointFormat{Family: CheckpointFamily(familyRaw), Version: semver.Canonical(versionRaw)}, nil
}

func (f CheckpointFormat) String() string {
    if f.Family == "" || f.Major == 0 {
        if f.Family == "" || f.Version == "" {
            return ""
        }
        return fmt.Sprintf("%s-v%d", f.Family, f.Major)
    }
    return fmt.Sprintf("%s-%s", f.Family, f.Version)
}

func Compare(a, b CheckpointFormat) int {
5 unmodified lines

if a.Family != b.Family {
        return cmp.Compare(string(a.Family), string(b.Family))
    }
    return cmp.Compare(a.Major, b.Major)
    return semver.Compare(a.Version, b.Version)
}

func CanRead(format CheckpointFormat) bool {

CheckpointFamilyRefs:   1,
}

var branchV1Format = CheckpointFormat{Family: CheckpointFamilyBranch, Major: 1}
var branchV1Format = CheckpointFormat{Family: CheckpointFamilyBranch, Version: semver.Canonical("v1")}

var (
    readFormats = map[CheckpointFormat]bool{

Mcmd/entire/cli/checkpointpolicy/format.go+19/-13


10 unmodified lines

11
12
13
14
15
16
17
14
15
16
17
18
19
19
20
21
22
23
24
20
21
22
23
24
25
26
27
28
29
30
31
4 unmodified lines

36
37
38
35
36
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
53
88
89
90
91
92
93
94
95
57
96
97
98
61
99

10 unmodified lines

func TestParseFormat(t *testing.T) {
    t.Parallel()
    tests := []struct {
        name    string
        input   string
        want    checkpointpolicy.CheckpointFormat
        wantErr string
        name       string
        input      string
        wantFamily checkpointpolicy.CheckpointFamily
        wantString string
        wantErr    string
    }{
        {name: "branch v1", input: "branch-v1", want: checkpointpolicy.CheckpointFormat{Family: checkpointpolicy.CheckpointFamilyBranch, Major: 1}},
        {name: "refs v2", input: "refs-v2", want: checkpointpolicy.CheckpointFormat{Family: checkpointpolicy.CheckpointFamilyRefs, Major: 2}},
        {name: "unknown family parses", input: "unknown-v1", want: checkpointpolicy.CheckpointFormat{Family: "unknown", Major: 1}},
        {name: "missing v", input: "branch-1", wantErr: "invalid checkpoint format"},
        {name: "zero major", input: "branch-v0", wantErr: "invalid checkpoint major"},
        {name: "non numeric major", input: "branch-vx", wantErr: "invalid checkpoint major"},
        {name: "branch v1", input: "branch-v1", wantFamily: checkpointpolicy.CheckpointFamilyBranch, wantString: "branch-v1.0.0"},
        {name: "branch v1 minor", input: "branch-v1.2", wantFamily: checkpointpolicy.CheckpointFamilyBranch, wantString: "branch-v1.2.0"},
        {name: "branch v1 patch", input: "branch-v1.2.3", wantFamily: checkpointpolicy.CheckpointFamilyBranch, wantString: "branch-v1.2.3"},
        {name: "refs prerelease", input: "refs-v2.0.0-rc.1", wantFamily: checkpointpolicy.CheckpointFamilyRefs, wantString: "refs-v2.0.0-rc.1"},
        {name: "unknown family parses", input: "unknown-v1", wantFamily: "unknown", wantString: "unknown-v1.0.0"},
        {name: "missing v", input: "branch-1", wantErr: "invalid checkpoint version"},
        {name: "zero major", input: "branch-v0", wantErr: "invalid checkpoint version"},
        {name: "non numeric major", input: "branch-vx", wantErr: "invalid checkpoint version"},
        {name: "build metadata", input: "branch-v1.2.3+build.1", wantErr: "must not include build metadata"},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
4 unmodified lines

return
        }
        require.NoError(t, err)
        require.Equal(t, tt.want, got)
        require.Equal(t, tt.input, got.String())
        require.Equal(t, tt.wantFamily, got.Family)
        require.Equal(t, tt.wantString, got.String())
        })
    }
}

func TestCompareFormats(t *testing.T) {
    t.Parallel()

branchV1, err := checkpointpolicy.ParseFormat(checkpoint.CheckpointVersionBranchV1)
    require.NoError(t, err)
    branchV1Canonical, err := checkpointpolicy.ParseFormat("branch-v1.0.0")
    require.NoError(t, err)
    branchV1Minor, err := checkpointpolicy.ParseFormat("branch-v1.1.0")
    require.NoError(t, err)
    branchV1Patch, err := checkpointpolicy.ParseFormat("branch-v1.1.1")
    require.NoError(t, err)
    branchV1Prerelease, err := checkpointpolicy.ParseFormat("branch-v1.1.1-rc.1")
    require.NoError(t, err)
    refsV1, err := checkpointpolicy.ParseFormat("refs-v1")
    require.NoError(t, err)
    unknownV1, err := checkpointpolicy.ParseFormat("unknown-v1")
    require.NoError(t, err)

require.Zero(t, checkpointpolicy.Compare(branchV1, branchV1Canonical))
    require.Negative(t, checkpointpolicy.Compare(branchV1, branchV1Minor))
    require.Negative(t, checkpointpolicy.Compare(branchV1Minor, branchV1Patch))
    require.Negative(t, checkpointpolicy.Compare(branchV1Prerelease, branchV1Patch))
    require.Negative(t, checkpointpolicy.Compare(branchV1Patch, refsV1))
    require.Negative(t, checkpointpolicy.Compare(refsV1, unknownV1))
}

func TestSupportedFormats(t *testing.T) {
    t.Parallel()

branchV1, err := checkpointpolicy.ParseFormat(checkpoint.CheckpointVersionBranchV1)
    require.NoError(t, err)
    branchV1Canonical, err := checkpointpolicy.ParseFormat("branch-v1.0.0")
    require.NoError(t, err)
    branchPrerelease, err := checkpointpolicy.ParseFormat("branch-v1.0.0-rc.1")
    require.NoError(t, err)
    refsV1, err := checkpointpolicy.ParseFormat("refs-v1")
    require.NoError(t, err)
    unknownV1, err := checkpointpolicy.ParseFormat("unknown-v1")
    require.NoError(t, err)

require.True(t, checkpointpolicy.CanRead(branchV1))
    require.True(t, checkpointpolicy.CanRead(branchV1Canonical))
    require.True(t, checkpointpolicy.CanWrite(branchV1))
    require.Equal(t, checkpoint.CheckpointVersionBranchV1, branchV1.String())
    require.True(t, checkpointpolicy.CanWrite(branchV1Canonical))
    require.Equal(t, "branch-v1.0.0", branchV1.String())

require.False(t, checkpointpolicy.CanRead(branchPrerelease))
    require.False(t, checkpointpolicy.CanWrite(branchPrerelease))

require.False(t, checkpointpolicy.CanRead(refsV1))
    require.False(t, checkpointpolicy.CanWrite(refsV1))
    require.Negative(t, checkpointpolicy.Compare(branchV1, refsV1))

require.False(t, checkpointpolicy.CanRead(unknownV1))
    require.False(t, checkpointpolicy.CanWrite(unknownV1))
    require.Negative(t, checkpointpolicy.Compare(refsV1, unknownV1))
}