Add feature manager (#2633)

* add feature manager
pull/2651/head
Irena Rindos 3 years ago committed by GitHub
parent df02501dcc
commit 6315821c9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,87 @@
package version
import (
"strings"
gvers "github.com/hashicorp/go-version"
)
type Metadata int
const (
OSS Metadata = iota
HCP
)
type MetadataConstraint struct {
MetaInfo []Metadata
Constraints gvers.Constraints
}
type Feature int
const (
UnknownFeature Feature = iota
MultiHopSessionFeature
)
var featureMap map[Feature]MetadataConstraint
func init() {
if featureMap == nil {
featureMap = make(map[Feature]MetadataConstraint)
}
/*
Add constraints here following this format after adding a Feature to the Feature iota:
featureConstraint, err := gvers.NewConstraint(">= 0.1.0") // This feature exists at 0.1.0 and above
featureMap[FEATURE] = MetadataConstraint{
MetaInfo: []Metadata{OSS, HCP},
Constraints: featureConstraint,
}
*/
}
func metadataStringToMetadata(m string) Metadata {
if strings.Contains(strings.ToLower(m), "hcp") {
return HCP
}
return OSS
}
// Check returns a bool indicating if a version meets the metadata constraint for a feature
func (m MetadataConstraint) Check(version *gvers.Version) bool {
binaryMeta := metadataStringToMetadata(version.Metadata())
for _, v := range m.MetaInfo {
if v == binaryMeta {
return true
}
}
return false
}
// Check returns a bool indicating if a version satisfies the feature constraints
func Check(binaryVersion *gvers.Version, featureConstraint MetadataConstraint) bool {
if !featureConstraint.Check(binaryVersion) {
return false
}
return featureConstraint.Constraints.Check(binaryVersion)
}
// SupportsFeature return a bool indicating whether or not this version supports the given feature
func SupportsFeature(version *gvers.Version, feature Feature) bool {
featureVersion, found := featureMap[feature]
if !found {
return false
}
return Check(version, featureVersion)
}
// GetReleaseVersion returns a go-version of this binary's Boundary version
func GetReleaseVersion() (*gvers.Version, error) {
ver := Get()
return gvers.NewVersion(ver.Version)
}

@ -0,0 +1,140 @@
package version
import (
"testing"
gvers "github.com/hashicorp/go-version"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestHasFeature(t *testing.T) {
t.Parallel()
DeprecatedFeature := Feature(999)
HCPOnlyFeature := Feature(998)
deprecatedFeatureConstraint, _ := gvers.NewConstraint(">= 0.10.0, < 0.10.1")
featureMap[DeprecatedFeature] = MetadataConstraint{
MetaInfo: []Metadata{OSS, HCP},
Constraints: deprecatedFeatureConstraint,
}
hcpOnlyFeature, _ := gvers.NewConstraint(">= 0.12.0+hcp")
featureMap[HCPOnlyFeature] = MetadataConstraint{
MetaInfo: []Metadata{HCP},
Constraints: hcpOnlyFeature,
}
tests := []struct {
name string
version string
feature Feature
wantResult bool
}{
{
name: "does-not-have-multihop-ENT",
version: "0.11.1+hcp",
feature: HCPOnlyFeature,
wantResult: false,
},
{
name: "has-multihop-ENT",
version: "0.12.0+hcp",
feature: HCPOnlyFeature,
wantResult: true,
},
{
name: "has-multihop-worker-ENT",
version: "0.12.0+hcp.int",
feature: HCPOnlyFeature,
wantResult: true,
},
{
name: "does-not-have-multihop-OSS",
version: "0.12.0",
feature: HCPOnlyFeature,
wantResult: false,
},
{
name: "deprecated-feature-before-deprecation",
version: "0.10.0",
feature: DeprecatedFeature,
wantResult: true,
},
{
name: "deprecated-feature-after-deprecation",
version: "0.12.0",
feature: DeprecatedFeature,
wantResult: false,
},
{
name: "bogus-feature",
version: "0.12.0",
feature: Feature(-1),
wantResult: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
testVersion, err := gvers.NewVersion(tt.version)
assert.NoError(err)
got := SupportsFeature(testVersion, tt.feature)
require.Equal(tt.wantResult, got)
})
}
delete(featureMap, DeprecatedFeature)
_, ok := featureMap[DeprecatedFeature]
require.False(t, ok)
delete(featureMap, HCPOnlyFeature)
_, ok = featureMap[HCPOnlyFeature]
require.False(t, ok)
}
func TestEnableFeatureForTest(t *testing.T) {
t.Parallel()
FutureFeature := Feature(997)
futureVersionFeature, _ := gvers.NewConstraint(">= 99.99.99+hcp")
featureMap[FutureFeature] = MetadataConstraint{
MetaInfo: []Metadata{HCP},
Constraints: futureVersionFeature,
}
tests := []struct {
name string
version string
feature Feature
wantResult bool
wantErr bool
}{
{
name: "has-future-feature",
version: "0.11.1+hcp",
feature: FutureFeature,
wantResult: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
testVersion, err := gvers.NewVersion(tt.version)
assert.NoError(err)
testFunc := func() bool {
EnableFeatureOnVersionForTest(t, testVersion, tt.feature)
got := SupportsFeature(testVersion, tt.feature)
return got
}
// Test that the feature was enabled
got := testFunc()
require.Equal(tt.wantResult, got)
})
}
delete(featureMap, FutureFeature)
_, ok := featureMap[FutureFeature]
require.False(t, ok)
}

@ -0,0 +1,43 @@
package version
import (
"fmt"
"testing"
gvers "github.com/hashicorp/go-version"
"github.com/stretchr/testify/require"
)
// EnableFeatureForTest enables a feature for the current binary version
func EnableFeatureForTest(t *testing.T, feature Feature) {
require := require.New(t)
version, err := GetReleaseVersion()
require.NoError(err)
EnableFeatureOnVersionForTest(t, version, feature)
}
// EnableFeatureForTest modifies the feature map to enable a feature for a version.
// This is intended to be used for testing before release of a version
// Test cleanup will reset the feature map to the original feature constraint
// Note: running any tests in parallel while using this function WILL result in surprising
// behavior because this modifies the global feature map
func EnableFeatureOnVersionForTest(t *testing.T, version *gvers.Version, feature Feature) {
featConstraint, ok := featureMap[feature]
require := require.New(t)
require.True(ok)
versionNumber := version.String()
newConstraint, err := gvers.NewConstraint(fmt.Sprintf(">= %s", versionNumber))
require.NoError(err)
meta := metadataStringToMetadata(version.Metadata())
featureMap[feature] = MetadataConstraint{
MetaInfo: []Metadata{meta},
Constraints: newConstraint,
}
resetFunc := func() {
featureMap[feature] = featConstraint
}
t.Cleanup(resetFunc)
}
Loading…
Cancel
Save