Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
🎁 Allow to override version via environmental variables to be used on…
Browse files Browse the repository at this point in the history
… CI (#22)

* Introducing version.Resolver type for compatibility with #20

* Impementing knative style version resolver using composite, ordered, and git resolvers.

* Adding additional tests and fixes

* More tests to pinpoint the failure

* Knative version resolver work properly

* Lint fix
  • Loading branch information
cardil committed Oct 29, 2021
1 parent 2578dcd commit 0bd3a66
Show file tree
Hide file tree
Showing 20 changed files with 578 additions and 146 deletions.
13 changes: 13 additions & 0 deletions pkg/cache/noop.go
@@ -0,0 +1,13 @@
package cache

// NoopCache do not do any caching. Probably, should be only used for testing
// purposes.
type NoopCache struct{}

func (n NoopCache) Compute(_ interface{}, provider func() (interface{}, error)) (interface{}, error) {
return provider()
}

func (n NoopCache) Drop(_ interface{}) interface{} {
return nil
}
56 changes: 56 additions & 0 deletions pkg/environment/types.go
@@ -0,0 +1,56 @@
package environment

import (
"os"
"strings"
)

// Key is an environment key.
type Key string

// Value is an environment value.
type Value string

// Pair holds a pair of environment key and value.
type Pair struct {
Key
Value
}

// Values holds environment values together with their keys.
type Values map[Key]Value

// ValuesSupplier is a func that supplies environmental values.
type ValuesSupplier func() Values

// Add a pair to environment values.
func (v Values) Add(pair Pair) {
v[pair.Key] = pair.Value
}

// New returns an environmental values bases on input compatible with the
// os.Environ function.
func New(environ ...string) Values {
vals := Values(map[Key]Value{})
for _, pair := range environ {
vals.Add(NewPair(pair))
}
return vals
}

// Current returns current environment values, from os.Environ method.
func Current() Values {
return New(os.Environ()...)
}

// NewPair creates a pair from os.Environ style string.
func NewPair(environ string) Pair {
parts := strings.SplitN(environ, "=", pairElements)
pair := Pair{Key: Key(parts[0])}
if len(parts) > 1 {
pair.Value = Value(parts[1])
}
return pair
}

const pairElements = 2
14 changes: 14 additions & 0 deletions pkg/environment/types_test.go
@@ -0,0 +1,14 @@
package environment_test

import (
"testing"

"github.com/wavesoftware/go-magetasks/pkg/environment"
"gotest.tools/v3/assert"
)

func TestNew(t *testing.T) {
e := environment.New("TAG=v4.5.6", "PUSH_RELEASE=1")
assert.Equal(t, e[environment.Key("TAG")], environment.Value("v4.5.6"))
assert.Equal(t, e[environment.Key("PUSH_RELEASE")], environment.Value("1"))
}
81 changes: 81 additions & 0 deletions pkg/environment/version.go
@@ -0,0 +1,81 @@
package environment

import (
"errors"
"fmt"
)

// ErrNotSupported when operation is not supported.
var ErrNotSupported = errors.New("not supported")

// NewVersionResolver creates a VersionResolver using options.
func NewVersionResolver(options ...VersionResolverOption) VersionResolver {
r := VersionResolver{}

for _, option := range options {
option(&r)
}

return r
}

// WithValuesSupplier allows to set the values supplier.
func WithValuesSupplier(vs ValuesSupplier) VersionResolverOption {
return func(r *VersionResolver) {
r.ValuesSupplier = vs
}
}

// VersionResolverOption is used to customize creation of VersionResolver.
type VersionResolverOption func(*VersionResolver)

// VersionResolver is used to resolve version information solely on
// environment variables.
type VersionResolver struct {
VersionKey Key
IsApplicable []Check
ValuesSupplier
}

// Check is used to verify environment values.
type Check Pair

func (e VersionResolver) Version() string {
values := e.environment()
if !e.isApplicable(values) {
return ""
}
return string(values[e.VersionKey])
}

func (e VersionResolver) IsLatest(versionRange string) (bool, error) {
return false, fmt.Errorf(
"%w: IsLatest(versionRange string) by environment.VersionResolver",
ErrNotSupported)
}

func (e VersionResolver) environment() Values {
supplier := Current
if e.ValuesSupplier != nil {
supplier = e.ValuesSupplier
}
return supplier()
}

func (e VersionResolver) isApplicable(values Values) bool {
for _, check := range e.IsApplicable {
if !check.test(values) {
return false
}
}
return true
}

func (c Check) test(values Values) bool {
if c.Value == "" {
_, ok := values[c.Key]
return ok
}
val, ok := values[c.Key]
return ok && val == c.Value
}
49 changes: 49 additions & 0 deletions pkg/environment/version_test.go
@@ -0,0 +1,49 @@
package environment_test

import (
"fmt"
"testing"

"github.com/wavesoftware/go-magetasks/pkg/environment"
"github.com/wavesoftware/go-magetasks/pkg/testing/errors"
"github.com/wavesoftware/go-magetasks/pkg/version"
"gotest.tools/v3/assert"
)

func TestVersionResolver(t *testing.T) {
tests := []testCase{{
environment: environment.New(),
}, {
environment: environment.New("TAG=v4.6.23", "TAG_RELEASE=1"),
version: "v4.6.23",
}, {
environment: environment.New("TAG=v6.23.0", "TAG_RELEASE=1", "is_auto_release=1"),
version: "v6.23.0",
}}
for i, tc := range tests {
tc := tc
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
resolver := tc.resolver()
assert.Equal(t, resolver.Version(), tc.version)
_, err := resolver.IsLatest(version.AnyVersion)
errors.Check(t, err, environment.ErrNotSupported)
})
}
}

type testCase struct {
environment environment.Values
version string
}

func (tc testCase) resolver() version.Resolver {
return environment.VersionResolver{
VersionKey: "TAG",
IsApplicable: []environment.Check{{
Key: "TAG_RELEASE", Value: "1",
}},
ValuesSupplier: func() environment.Values {
return tc.environment
},
}
}
80 changes: 51 additions & 29 deletions pkg/git/resolver.go
@@ -1,82 +1,104 @@
package git

import (
"errors"

"github.com/wavesoftware/go-ensure"
"github.com/wavesoftware/go-magetasks/config"
"github.com/wavesoftware/go-magetasks/pkg/cache"
"github.com/wavesoftware/go-magetasks/pkg/version"
)

// IsLatestStrategy is used to determine if current version is latest one.
type IsLatestStrategy func(Resolver) func(versionRange string) (bool, error)
type VersionResolverOption func(*VersionResolver)

// Remote represents a remote repository name and address.
type Remote struct {
Name string
URL string
func NewVersionResolver(options ...VersionResolverOption) VersionResolver {
r := VersionResolver{}

for _, option := range options {
option(&r)
}

return r
}

func WithCache(cache cache.Cache) VersionResolverOption {
return func(r *VersionResolver) {
r.Cache = cache
}
}

// Resolver implements version.Resolver for git SCM.
type Resolver struct {
func WithIsLatestStrategy(strategy IsLatestStrategy) VersionResolverOption {
return func(r *VersionResolver) {
r.IsLatestStrategy = strategy
}
}

func WithRepository(repository Repository) VersionResolverOption {
return func(r *VersionResolver) {
r.Repository = repository
}
}

func WithRemote(remote Remote) VersionResolverOption {
return func(r *VersionResolver) {
r.Remote = &remote
}
}

// VersionResolver implements version.Resolver for git SCM.
type VersionResolver struct {
Cache cache.Cache
IsLatestStrategy
Repository
*Remote
}

// Remote represents a remote repository name and address.
type Remote struct {
Name string
URL string
}

// IsLatestStrategy is used to determine if current version is latest one.
type IsLatestStrategy func(version.Resolver) func(string) (bool, error)

type cacheKey struct {
typee string
}

func (r Resolver) Version() string {
func (r VersionResolver) Version() string {
ver, err := r.cache().Compute(cacheKey{"version"}, func() (interface{}, error) {
return r.repository().Describe()
})
ensure.NoError(err)
return ver.(string)
}

func (r Resolver) IsLatest(versionRange string) (bool, error) {
strategy := TagBasedIsLatestStrategy
if r.IsLatestStrategy != nil {
strategy = r.IsLatestStrategy
}
fn := strategy(r)
latest, err := fn(versionRange)
if err != nil {
if !errors.Is(err, version.ErrVersionIsNotValid) {
return false, err
}
return false, nil
}
return latest, nil
func (r VersionResolver) IsLatest(versionRange string) (bool, error) {
return ResolveIsLatest(r, r, versionRange)
}

func (r Resolver) cache() cache.Cache {
func (r VersionResolver) cache() cache.Cache {
if r.Cache == nil {
return config.Cache()
}
return r.Cache
}

func (r Resolver) repository() Repository {
func (r VersionResolver) repository() Repository {
if r.Repository == nil {
return installedGitBinaryRepo{r.remote()}
}
return r.Repository
}

func (r Resolver) remote() Remote {
func (r VersionResolver) remote() Remote {
remote := Remote{Name: "origin"}
if r.Remote != nil {
remote = *r.Remote
}
return remote
}

func (r Resolver) resolveTags() []string {
func (r VersionResolver) resolveTags() []string {
tt, err := r.cache().Compute(cacheKey{"tags"}, func() (interface{}, error) {
return r.repository().Tags()
})
Expand Down

0 comments on commit 0bd3a66

Please sign in to comment.