Vendoring improved

* update dagger init with package manager downloading stdlib

Signed-off-by: Tihomir Jovicic <tihomir.jovicic.develop@gmail.com>

* split mod get and update functions

Signed-off-by: Tihomir Jovicic <tihomir.jovicic.develop@gmail.com>

* write to package checksum to dagger.sum when installing/updating

Signed-off-by: Tihomir Jovicic <tihomir.jovicic.develop@gmail.com>

* encure checksums are valid when compiling input

Signed-off-by: Tihomir Jovicic <tihomir.jovicic.develop@gmail.com>

* remove references to github.com/tjovicic in docs 1010 and 1011

Signed-off-by: Tihomir Jovicic <tihomir.jovicic.develop@gmail.com>

* refactor mod get command

Signed-off-by: Tihomir Jovicic <tihomir.jovicic.develop@gmail.com>

* update logic of moving dir when installing packages

Signed-off-by: Tihomir Jovicic <tihomir.jovicic.develop@gmail.com>

* fix linting errors

Signed-off-by: Tihomir Jovicic <tihomir.jovicic.develop@gmail.com>

* revert changes to 1010 docs

Signed-off-by: Tihomir Jovicic <tihomir.jovicic.develop@gmail.com>

* updating error log line in mod/get

Signed-off-by: Tihomir Jovicic <tihomir.jovicic.develop@gmail.com>

* fix ci tests when using vendoring

Signed-off-by: Tihomir Jovicic <tihomir.jovicic.develop@gmail.com>

* update alpha.dagger.io version to v0.1

Signed-off-by: Tihomir Jovicic <tihomir.jovicic.develop@gmail.com>

* fix mod repo test

Signed-off-by: Tihomir Jovicic <tihomir.jovicic.develop@gmail.com>

* return error if package already installed

Signed-off-by: Tihomir Jovicic <tihomir.jovicic.develop@gmail.com>

* remove already installed packages when installing

Signed-off-by: Tihomir Jovicic <tihomir.jovicic.develop@gmail.com>

* fix issue when vendoring stdlib

Signed-off-by: Tihomir Jovicic <tihomir.jovicic.develop@gmail.com>

* update mod command with filelock while installing

Signed-off-by: Tihomir Jovicic <tihomir.jovicic.develop@gmail.com>

* fix linting errors

Signed-off-by: Tihomir Jovicic <tihomir.jovicic.develop@gmail.com>

* fix path of mod lock file

Signed-off-by: Tihomir Jovicic <tihomir.jovicic.develop@gmail.com>

Co-authored-by: Tihomir Jovicic <tihomir.jovicic.develop@gmail.com>

Signed-off-by: Sam Alba <sam.alba@gmail.com>
This commit is contained in:
Sam Alba
2021-10-13 14:32:06 -07:00
committed by Sam Alba
parent 31255f75ad
commit e9ca8f38e6
22 changed files with 709 additions and 428 deletions

31
mod/checksum.go Normal file
View File

@@ -0,0 +1,31 @@
package mod
import (
"fmt"
"os"
"path"
"golang.org/x/mod/sumdb/dirhash"
)
func dirChecksum(dirPath string) (string, error) {
err := cleanDirForChecksum(dirPath)
if err != nil {
return "", err
}
checksum, err := dirhash.HashDir(dirPath, "", dirhash.DefaultHash)
if err != nil {
return "", err
}
return checksum, nil
}
func cleanDirForChecksum(dirPath string) error {
if err := os.RemoveAll(path.Join(dirPath, ".git")); err != nil {
return fmt.Errorf("error cleaning up .git directory")
}
return nil
}

293
mod/file.go Normal file
View File

@@ -0,0 +1,293 @@
package mod
import (
"bytes"
"errors"
"fmt"
"io"
"io/fs"
"io/ioutil"
"os"
"path"
"regexp"
"strings"
"github.com/spf13/viper"
)
const modFilePath = "./cue.mod/dagger.mod"
const sumFilePath = "./cue.mod/dagger.sum"
const lockFilePath = "./cue.mod/dagger.lock"
const destBasePath = "./cue.mod/pkg"
const tmpBasePath = "./cue.mod/tmp"
// file is the parsed, interpreted form of dagger.mod file.
type file struct {
requires []*Require
workspacePath string
}
func readPath(workspacePath string) (*file, error) {
pMod := path.Join(workspacePath, modFilePath)
fMod, err := os.Open(pMod)
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return nil, err
}
// dagger.mod doesn't exist, let's create an empty file
if fMod, err = os.Create(pMod); err != nil {
return nil, err
}
}
pSum := path.Join(workspacePath, sumFilePath)
fSum, err := os.Open(pSum)
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return nil, err
}
// dagger.sum doesn't exist, let's create an empty file
if fSum, err = os.Create(pSum); err != nil {
return nil, err
}
}
modFile, err := read(fMod, fSum)
if err != nil {
return nil, err
}
modFile.workspacePath = workspacePath
return modFile, nil
}
func read(fMod, fSum io.Reader) (*file, error) {
bMod, err := ioutil.ReadAll(fMod)
if err != nil {
return nil, err
}
bSum, err := ioutil.ReadAll(fSum)
if err != nil {
return nil, err
}
modLines := nonEmptyLines(bMod)
sumLines := nonEmptyLines(bSum)
if len(modLines) != len(sumLines) {
return nil, fmt.Errorf("length of dagger.mod and dagger.sum files differ")
}
requires := make([]*Require, 0, len(modLines))
for i := 0; i < len(modLines); i++ {
modSplit := strings.Split(modLines[i], " ")
if len(modSplit) != 2 {
return nil, fmt.Errorf("line in the mod file doesn't contain 2 elements")
}
sumSplit := strings.Split(sumLines[i], " ")
if len(sumSplit) != 2 {
return nil, fmt.Errorf("line in the sum file doesn't contain 2 elements")
}
if modSplit[0] != sumSplit[0] {
return nil, fmt.Errorf("repos in mod and sum line don't match: %s and %s", modSplit[0], sumSplit[0])
}
require, err := newRequire(modSplit[0])
if err != nil {
return nil, err
}
require.version = modSplit[1]
require.checksum = sumSplit[1]
requires = append(requires, require)
}
return &file{requires: requires}, nil
}
var spaceRgx = regexp.MustCompile(`\s+`)
func nonEmptyLines(b []byte) []string {
s := strings.ReplaceAll(string(b), "\r\n", "\n")
split := strings.Split(s, "\n")
lines := make([]string, 0, len(split))
for _, l := range split {
trimmed := strings.TrimSpace(l)
if trimmed == "" {
continue
}
trimmed = spaceRgx.ReplaceAllString(trimmed, " ")
lines = append(lines, trimmed)
}
return lines
}
func (f *file) install(req *Require) error {
// cleaning up possible leftovers
tmpPath := path.Join(f.workspacePath, tmpBasePath, req.fullPath())
defer os.RemoveAll(tmpPath)
// clone to a tmp directory
r, err := clone(req, tmpPath, viper.GetString("private-key-file"), viper.GetString("private-key-password"))
if err != nil {
return fmt.Errorf("error downloading package %s: %w", req, err)
}
destPath := path.Join(f.workspacePath, destBasePath, req.fullPath())
// requirement is new, so we should move the cloned files from tmp to pkg and add it to the mod file
existing := f.searchInstalledRequire(req)
if existing == nil {
if err = replace(req, tmpPath, destPath); err != nil {
return err
}
checksum, err := dirChecksum(destPath)
if err != nil {
return err
}
req.checksum = checksum
f.requires = append(f.requires, req)
return nil
}
// checkout the cloned repo to that tag, change the version in the existing requirement and
// replace the code in the /pkg folder
existing.version = req.version
if err = r.checkout(req.version); err != nil {
return err
}
if err = replace(req, tmpPath, destPath); err != nil {
return err
}
checksum, err := dirChecksum(destPath)
if err != nil {
return err
}
existing.checksum = checksum
return nil
}
func (f *file) updateToLatest(req *Require) (*Require, error) {
// check if it doesn't exist
existing := f.searchInstalledRequire(req)
if existing == nil {
return nil, fmt.Errorf("package %s isn't already installed", req.fullPath())
}
// cleaning up possible leftovers
tmpPath := path.Join(f.workspacePath, tmpBasePath, existing.fullPath())
defer os.RemoveAll(tmpPath)
// clone to a tmp directory
gitRepo, err := clone(existing, tmpPath, viper.GetString("private-key-file"), viper.GetString("private-key-password"))
if err != nil {
return nil, fmt.Errorf("error downloading package %s: %w", existing, err)
}
// checkout the latest tag
latestTag, err := gitRepo.latestTag()
if err != nil {
return nil, err
}
c, err := compareVersions(latestTag, existing.version)
if err != nil {
return nil, err
}
if c < 0 {
return nil, fmt.Errorf("latest git tag is less than the current version")
}
existing.version = latestTag
if err = gitRepo.checkout(existing.version); err != nil {
return nil, err
}
// move the package from tmp to pkg directory
destPath := path.Join(f.workspacePath, destBasePath, existing.fullPath())
if err = replace(existing, tmpPath, destPath); err != nil {
return nil, err
}
checksum, err := dirChecksum(destPath)
if err != nil {
return nil, err
}
req.checksum = checksum
return existing, nil
}
func (f *file) searchInstalledRequire(r *Require) *Require {
for _, existing := range f.requires {
if existing.fullPath() == r.fullPath() {
return existing
}
}
return nil
}
func (f *file) ensure() error {
for _, require := range f.requires {
requirePath := path.Join(f.workspacePath, destBasePath, require.fullPath())
checksum, err := dirChecksum(requirePath)
if err != nil {
return nil
}
if require.checksum != checksum {
return fmt.Errorf("wrong checksum for %s", require.fullPath())
}
}
return nil
}
func (f *file) write() error {
// write dagger.mod file
var bMod bytes.Buffer
for _, r := range f.requires {
bMod.WriteString(fmt.Sprintf("%s %s\n", r.fullPath(), r.version))
}
err := ioutil.WriteFile(path.Join(f.workspacePath, modFilePath), bMod.Bytes(), 0600)
if err != nil {
return err
}
// write dagger.sum file
var bSum bytes.Buffer
for _, r := range f.requires {
bSum.WriteString(fmt.Sprintf("%s %s\n", r.fullPath(), r.checksum))
}
err = ioutil.WriteFile(path.Join(f.workspacePath, sumFilePath), bSum.Bytes(), 0600)
if err != nil {
return err
}
return nil
}

54
mod/file_test.go Normal file
View File

@@ -0,0 +1,54 @@
package mod
import (
"strings"
"testing"
)
func TestReadFile(t *testing.T) {
cases := []struct {
name string
modFile string
sumFile string
want *file
}{
{
name: "module file with valid dependencies",
modFile: `
github.com/tjovicic/test xyz
github.com/bla/bla abc
`,
sumFile: `
github.com/tjovicic/test h1:hash
github.com/bla/bla h1:hash
`,
want: &file{
requires: []*Require{
{
repo: "github.com/tjovicic/test",
path: "",
version: "xyz",
},
{
repo: "github.com/bla/bla",
path: "",
version: "abc",
},
},
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got, err := read(strings.NewReader(c.modFile), strings.NewReader(c.sumFile))
if err != nil {
t.Error(err)
}
if len(got.requires) != len(c.want.requires) {
t.Errorf("requires length differs: want %d, got %d", len(c.want.requires), len(got.requires))
}
})
}
}

135
mod/mod.go Normal file
View File

@@ -0,0 +1,135 @@
package mod
import (
"path"
"github.com/gofrs/flock"
)
func InstallStdlib(workspace string) error {
_, err := Install(workspace, "alpha.dagger.io@v0.1")
return err
}
func Install(workspace, repoName string) (*Require, error) {
require, err := newRequire(repoName)
if err != nil {
return nil, err
}
modfile, err := readPath(workspace)
if err != nil {
return nil, err
}
fileLock := flock.New(path.Join(workspace, lockFilePath))
if err := fileLock.Lock(); err != nil {
return nil, err
}
err = modfile.install(require)
if err != nil {
return nil, err
}
if err = modfile.write(); err != nil {
return nil, err
}
if err := fileLock.Unlock(); err != nil {
return nil, err
}
return require, nil
}
func InstallAll(workspace string, repoNames []string) ([]*Require, error) {
installedRequires := make([]*Require, 0, len(repoNames))
var err error
for _, repoName := range repoNames {
var require *Require
if require, err = Install(workspace, repoName); err != nil {
continue
}
installedRequires = append(installedRequires, require)
}
return installedRequires, err
}
func Update(workspace, repoName string) (*Require, error) {
require, err := newRequire(repoName)
if err != nil {
return nil, err
}
modfile, err := readPath(workspace)
if err != nil {
return nil, err
}
fileLock := flock.New(path.Join(workspace, lockFilePath))
if err := fileLock.Lock(); err != nil {
return nil, err
}
updatedRequire, err := modfile.updateToLatest(require)
if err != nil {
return nil, err
}
if err = modfile.write(); err != nil {
return nil, err
}
if err := fileLock.Unlock(); err != nil {
return nil, err
}
return updatedRequire, nil
}
func UpdateAll(workspace string, repoNames []string) ([]*Require, error) {
updatedRequires := make([]*Require, 0, len(repoNames))
var err error
for _, repoName := range repoNames {
var require *Require
if require, err = Update(workspace, repoName); err != nil {
continue
}
updatedRequires = append(updatedRequires, require)
}
return updatedRequires, err
}
func UpdateInstalled(workspace string) ([]*Require, error) {
modfile, err := readPath(workspace)
if err != nil {
return nil, err
}
repoNames := make([]string, 0, len(modfile.requires))
for _, require := range modfile.requires {
repoNames = append(repoNames, require.String())
}
return UpdateAll(workspace, repoNames)
}
func Ensure(workspace string) error {
modfile, err := readPath(workspace)
if err != nil {
return err
}
return modfile.ensure()
}

124
mod/repo.go Normal file
View File

@@ -0,0 +1,124 @@
package mod
import (
"fmt"
"os"
"sort"
"strings"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/hashicorp/go-version"
)
type repo struct {
contents *git.Repository
}
func clone(require *Require, dir string, privateKeyFile, privateKeyPassword string) (*repo, error) {
if err := os.RemoveAll(dir); err != nil {
return nil, fmt.Errorf("error cleaning up tmp directory")
}
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, fmt.Errorf("error creating tmp dir for cloning package")
}
o := git.CloneOptions{
URL: fmt.Sprintf("https://%s", require.cloneRepo),
}
if privateKeyFile != "" {
publicKeys, err := ssh.NewPublicKeysFromFile("git", privateKeyFile, privateKeyPassword)
if err != nil {
return nil, err
}
o.Auth = publicKeys
o.URL = fmt.Sprintf("git@%s", strings.Replace(require.cloneRepo, "/", ":", 1))
}
r, err := git.PlainClone(dir, false, &o)
if err != nil {
return nil, err
}
rr := &repo{
contents: r,
}
if require.version == "" {
latestTag, err := rr.latestTag()
if err != nil {
return nil, err
}
require.version = latestTag
}
if err := rr.checkout(require.version); err != nil {
return nil, err
}
return rr, nil
}
func (r *repo) checkout(version string) error {
h, err := r.contents.ResolveRevision(plumbing.Revision(version))
if err != nil {
return err
}
w, err := r.contents.Worktree()
if err != nil {
return err
}
err = w.Checkout(&git.CheckoutOptions{
Hash: *h,
})
if err != nil {
return err
}
return nil
}
func (r *repo) listTags() ([]string, error) {
iter, err := r.contents.Tags()
if err != nil {
return nil, err
}
var tags []string
if err := iter.ForEach(func(ref *plumbing.Reference) error {
tags = append(tags, ref.Name().Short())
return nil
}); err != nil {
return nil, err
}
return tags, nil
}
func (r *repo) latestTag() (string, error) {
versionsRaw, err := r.listTags()
if err != nil {
return "", err
}
versions := make([]*version.Version, len(versionsRaw))
for i, raw := range versionsRaw {
v, _ := version.NewVersion(raw)
versions[i] = v
}
if len(versions) == 0 {
return "", fmt.Errorf("repo doesn't have any tags")
}
sort.Sort(version.Collection(versions))
return versions[len(versions)-1].Original(), nil
}

93
mod/repo_test.go Normal file
View File

@@ -0,0 +1,93 @@
package mod
import (
"io/ioutil"
"os"
"testing"
)
func TestClone(t *testing.T) {
cases := []struct {
name string
require Require
privateKeyFile string
privateKeyPassword string
}{
{
name: "resolving shorter hash version",
require: Require{
cloneRepo: "github.com/dagger/universe",
clonePath: "stdlib",
version: "24d7af3fc2a3e9c7cc2",
},
},
{
name: "resolving branch name",
require: Require{
cloneRepo: "github.com/dagger/universe",
clonePath: "stdlib",
version: "main",
},
},
{
name: "resolving tag",
require: Require{
cloneRepo: "github.com/dagger/universe",
clonePath: "stdlib",
version: "v0.1",
},
},
{
name: "dagger private repo",
require: Require{
cloneRepo: "github.com/dagger/test",
clonePath: "",
version: "main",
},
privateKeyFile: "./test-ssh-keys/id_ed25519_test",
privateKeyPassword: "",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "clone")
if err != nil {
t.Fatal("error creating tmp dir")
}
defer os.Remove(tmpDir)
_, err = clone(&c.require, tmpDir, c.privateKeyFile, c.privateKeyPassword)
if err != nil {
t.Error(err)
}
})
}
}
func TestListTags(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "clone")
if err != nil {
t.Fatal("error creating tmp dir")
}
defer os.Remove(tmpDir)
r, err := clone(&Require{
cloneRepo: "github.com/dagger/universe",
clonePath: "stdlib",
version: "",
}, tmpDir, "", "")
if err != nil {
t.Fatal(err)
}
tags, err := r.listTags()
if err != nil {
t.Error(err)
}
if len(tags) == 0 {
t.Errorf("could not list repo tags")
}
}

90
mod/require.go Normal file
View File

@@ -0,0 +1,90 @@
package mod
import (
"fmt"
"os"
"path"
"regexp"
"strings"
)
type Require struct {
repo string
path string
cloneRepo string
clonePath string
version string
checksum string
}
func newRequire(repoName string) (*Require, error) {
switch {
case strings.HasPrefix(repoName, "github.com"):
return parseGithubRepoName(repoName)
case strings.HasPrefix(repoName, "alpha.dagger.io"):
return parseDaggerRepoName(repoName)
default:
return nil, fmt.Errorf("repo name does not match suported providers")
}
}
var githubRepoNameRegex = regexp.MustCompile(`(github.com/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+)([a-zA-Z0-9/_.-]*)@?([0-9a-zA-Z.-]*)`)
func parseGithubRepoName(repoName string) (*Require, error) {
repoMatches := githubRepoNameRegex.FindStringSubmatch(repoName)
if len(repoMatches) < 4 {
return nil, fmt.Errorf("issue when parsing github repo")
}
return &Require{
repo: repoMatches[1],
path: repoMatches[2],
version: repoMatches[3],
cloneRepo: repoMatches[1],
clonePath: repoMatches[2],
}, nil
}
var daggerRepoNameRegex = regexp.MustCompile(`alpha.dagger.io([a-zA-Z0-9/_.-]*)@?([0-9a-zA-Z.-]*)`)
func parseDaggerRepoName(repoName string) (*Require, error) {
repoMatches := daggerRepoNameRegex.FindStringSubmatch(repoName)
if len(repoMatches) < 3 {
return nil, fmt.Errorf("issue when parsing dagger repo")
}
return &Require{
repo: "alpha.dagger.io",
path: repoMatches[1],
version: repoMatches[2],
cloneRepo: "github.com/dagger/universe",
clonePath: path.Join("/stdlib", repoMatches[1]),
}, nil
}
func (r *Require) String() string {
return fmt.Sprintf("%s@%s", r.fullPath(), r.version)
}
func (r *Require) fullPath() string {
return path.Join(r.repo, r.path)
}
func replace(r *Require, sourceRepoPath, destPath string) error {
// remove previous package directory
if err := os.RemoveAll(destPath); err != nil {
return err
}
if err := os.Rename(path.Join(sourceRepoPath, r.clonePath), destPath); err != nil {
return err
}
return nil
}

123
mod/require_test.go Normal file
View File

@@ -0,0 +1,123 @@
package mod
import (
"testing"
)
func TestParseArgument(t *testing.T) {
cases := []struct {
name string
in string
want *Require
hasError bool
}{
{
name: "Random",
in: "abcd/bla@:/xyz",
hasError: true,
},
{
name: "Dagger repo",
in: "github.com/dagger/dagger",
want: &Require{
repo: "github.com/dagger/dagger",
path: "",
version: "",
},
},
{
name: "Dagger repo with path",
in: "github.com/dagger/dagger/stdlib",
want: &Require{
repo: "github.com/dagger/dagger",
path: "/stdlib",
version: "",
},
},
{
name: "Dagger repo with longer path",
in: "github.com/dagger/dagger/stdlib/test/test",
want: &Require{
repo: "github.com/dagger/dagger",
path: "/stdlib/test/test",
version: "",
},
},
{
name: "Dagger repo with path and version",
in: "github.com/dagger/dagger/stdlib@v0.1",
want: &Require{
repo: "github.com/dagger/dagger",
path: "/stdlib",
version: "v0.1",
},
},
{
name: "Dagger repo with longer path and version tag",
in: "github.com/dagger/dagger/stdlib/test/test@v0.0.1",
want: &Require{
repo: "github.com/dagger/dagger",
path: "/stdlib/test/test",
version: "v0.0.1",
},
},
{
name: "Alpha Dagger repo with path",
in: "alpha.dagger.io/gcp/gke@v0.1.0-alpha.20",
want: &Require{
repo: "alpha.dagger.io",
path: "/gcp/gke",
version: "v0.1.0-alpha.20",
cloneRepo: "github.com/dagger/dagger",
clonePath: "/stdlib/gcp/gke",
},
},
{
name: "Alpha Dagger repo",
in: "alpha.dagger.io@v0.1.0-alpha.23",
want: &Require{
repo: "alpha.dagger.io",
path: "",
version: "v0.1.0-alpha.23",
cloneRepo: "github.com/dagger/dagger",
clonePath: "/stdlib",
},
},
{
name: "Dagger repo with longer path and commit version",
in: "github.com/dagger/dagger/stdlib/test/test@26a1d46d1b3c",
want: &Require{
repo: "github.com/dagger/dagger",
path: "/stdlib/test/test",
version: "26a1d46d1b3c",
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got, err := newRequire(c.in)
if err != nil && c.hasError {
return
}
if err != nil {
t.Fatal(err)
}
if got.repo != c.want.repo {
t.Errorf("repos differ: want %s, got %s", c.want.repo, got.repo)
}
if got.path != c.want.path {
t.Errorf("paths differ: want %s, got %s", c.want.path, got.path)
}
if got.version != c.want.version {
t.Errorf("versions differ: want %s, got %s", c.want.version, got.version)
}
})
}
}

View File

@@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACCpGsk8WLx7gXCXX1muGhKjlkqaqykF1X198WQMkBO2pwAAAKC5Ec8WuRHP
FgAAAAtzc2gtZWQyNTUxOQAAACCpGsk8WLx7gXCXX1muGhKjlkqaqykF1X198WQMkBO2pw
AAAEBXE9Uht+QHuyK7+yYcZFVWOJ3qkhUh/wn289nDKDPHKakayTxYvHuBcJdfWa4aEqOW
SpqrKQXVfX3xZAyQE7anAAAAGnRpaG9taXIuam92aWNpY0B0b3B0YWwuY29tAQID
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKkayTxYvHuBcJdfWa4aEqOWSpqrKQXVfX3xZAyQE7an tihomir.jovicic@toptal.com

27
mod/version.go Normal file
View File

@@ -0,0 +1,27 @@
package mod
import "github.com/hashicorp/go-version"
// compareVersions returns -1 if the first argument is less or 1 if it's greater than the second argument.
// It returns 0 if they are equal.
func compareVersions(reqV1, reqV2 string) (int, error) {
v1, err := version.NewVersion(reqV1)
if err != nil {
return 0, err
}
v2, err := version.NewVersion(reqV2)
if err != nil {
return 0, err
}
if v1.LessThan(v2) {
return -1, nil
}
if v1.Equal(v2) {
return 0, nil
}
return 1, nil
}