Skip to content

Commit

Permalink
feat: added check-version subcommand
Browse files Browse the repository at this point in the history
Signed-off-by: Ben Meier <[email protected]>
  • Loading branch information
astromechza committed Jul 9, 2024
1 parent b3d7fe1 commit fa90270
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 6 deletions.
36 changes: 36 additions & 0 deletions internal/command/check_version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package command

import (
"github.com/spf13/cobra"

"github.com/score-spec/score-compose/internal/version"
)

var checkVersionCmd = &cobra.Command{
Use: "check-version [constraint]",
Short: "Assert that the version of score-compose matches the required constraint",
Long: `score-compose is commonly used in Makefiles and CI pipelines which may depend on a particular functionality
or a particular default provisioner provided by score-compose init. This command provides a common way to check that
the version of score-compose matches a required version.
`,
Example: `
# check that the version is exactly 1.2.3
score-compose check-version =v1.2.3
# check that the version is 1.3.0 or greater
score-compose check-version >v1.2
# check that the version is equal or greater to 1.2.3
score-compose check-version >=1.2.3`,
Args: cobra.ExactArgs(1),
SilenceErrors: true,
CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true},
RunE: func(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true
return version.AssertVersion(args[0], version.Version)
},
}

func init() {
rootCmd.AddCommand(checkVersionCmd)
}
58 changes: 58 additions & 0 deletions internal/command/check_version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package command

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
)

func TestCheckVersionHelp(t *testing.T) {
stdout, stderr, err := executeAndResetCommand(context.Background(), rootCmd, []string{"check-version", "--help"})
assert.NoError(t, err)
assert.Equal(t, `score-compose is commonly used in Makefiles and CI pipelines which may depend on a particular functionality
or a particular default provisioner provided by score-compose init. This command provides a common way to check that
the version of score-compose matches a required version.
Usage:
score-compose check-version [constraint] [flags]
Examples:
# check that the version is exactly 1.2.3
score-compose check-version =v1.2.3
# check that the version is 1.3.0 or greater
score-compose check-version >v1.2
# check that the version is equal or greater to 1.2.3
score-compose check-version >=1.2.3
Flags:
-h, --help help for check-version
Global Flags:
--quiet Mute any logging output
-v, --verbose count Increase log verbosity and detail by specifying this flag one or more times
`, stdout)
assert.Equal(t, "", stderr)

stdout2, stderr, err := executeAndResetCommand(context.Background(), rootCmd, []string{"help", "check-version"})
assert.NoError(t, err)
assert.Equal(t, stdout, stdout2)
assert.Equal(t, "", stderr)
}

func TestCheckVersionPass(t *testing.T) {
stdout, stderr, err := executeAndResetCommand(context.Background(), rootCmd, []string{"check-version", ">=0.0.0"})
assert.NoError(t, err)
assert.Equal(t, stdout, "")
assert.Equal(t, "", stderr)
}

func TestCheckVersionFail(t *testing.T) {
stdout, stderr, err := executeAndResetCommand(context.Background(), rootCmd, []string{"check-version", ">99"})
assert.EqualError(t, err, "current version 0.0.0 does not match requested constraint >99")
assert.Equal(t, stdout, "")
assert.Equal(t, "", stderr)
}
11 changes: 6 additions & 5 deletions internal/command/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@ Usage:
score-compose [command]
Available Commands:
completion Generate the autocompletion script for the specified shell
generate Convert one or more Score files into a Docker compose manifest
help Help about any command
init Initialise a new score-compose project with local state directory and score file
resources Subcommands related to provisioned resources
check-version Assert that the version of score-compose matches the required constraint
completion Generate the autocompletion script for the specified shell
generate Convert one or more Score files into a Docker compose manifest
help Help about any command
init Initialise a new score-compose project with local state directory and score file
resources Subcommands related to provisioned resources
Flags:
-h, --help help for score-compose
Expand Down
49 changes: 48 additions & 1 deletion internal/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ package version

import (
"fmt"
"regexp"
"runtime/debug"
"strconv"
)

var (
Version string = "0.0.0"
Version string = "0.0.0"
semverPattern = regexp.MustCompile(`^(?:v?)(\d+)(?:\.(\d+))?(?:\.(\d+))?$`)
constraintAndSemver = regexp.MustCompile("^(>|>=|=)?" + semverPattern.String()[1:])
)

// BuildVersionString constructs a version string by looking at the build metadata injected at build time.
Expand All @@ -39,3 +43,46 @@ func BuildVersionString() string {
}
return fmt.Sprintf("%s (build: %s, sha: %s%s)", versionNumber, buildTime, gitSha, isDirtySuffix)
}

func semverToI(x string) (int, error) {
cpm := semverPattern.FindStringSubmatch(x)
if cpm == nil {
return 0, fmt.Errorf("invalid version: %s", x)
}
major, _ := strconv.Atoi(cpm[1])
minor, patch := 999, 999
if len(cpm) > 2 {
minor, _ = strconv.Atoi(cpm[2])
if len(cpm) > 3 {
patch, _ = strconv.Atoi(cpm[3])
}
}
return (major*1_000+minor)*1_000 + patch, nil
}

func AssertVersion(constraint string, current string) error {
if currentI, err := semverToI(current); err != nil {
return fmt.Errorf("current version is missing or invalid '%s'", current)
} else if m := constraintAndSemver.FindStringSubmatch(constraint); m == nil {
return fmt.Errorf("invalid constraint '%s'", constraint)
} else {
op := m[1]
compareI, err := semverToI(m[0][len(op):])
if err != nil {
return fmt.Errorf("failed to parse constraint: %w", err)
}
match := false
switch op {
case ">":
match = currentI > compareI
case ">=":
match = currentI >= compareI
case "=":
match = currentI == compareI
}
if !match {
return fmt.Errorf("current version %s does not match requested constraint %s", current, constraint)
}
return nil
}
}
37 changes: 37 additions & 0 deletions internal/version/version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package version

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func TestAssertVersion_good(t *testing.T) {
for _, tup := range [][2]string{
{"=1.2.3", "v1.2.3"},
{">=1.2.3", "v1.2.3"},
{">=1.2.3", "v1.2.4"},
{">1.2.3", "v1.2.4"},
{">=1.1", "1.1.0"},
{">=1.1", "1.2.0"},
{">=1", "1.0.0"},
{">1", "2.0.0"},
} {
t.Run(fmt.Sprintf("%v", tup), func(t *testing.T) {
assert.NoError(t, AssertVersion(tup[0], tup[1]))
})
}
}

func TestAssertVersion_bad(t *testing.T) {
for _, tup := range [][3]string{
{"=1.2.3", "v1.2.0", "current version v1.2.0 does not match requested constraint =1.2.3"},
{">2", "v1.2.0", "current version v1.2.0 does not match requested constraint >2"},
{">1.2", "v1.2.0", "current version v1.2.0 does not match requested constraint >1.2"},
} {
t.Run(fmt.Sprintf("%v", tup), func(t *testing.T) {
assert.EqualError(t, AssertVersion(tup[0], tup[1]), tup[2])
})
}
}

0 comments on commit fa90270

Please sign in to comment.