Add base features flag mechanism

parent cde4a2d1
......@@ -460,6 +460,11 @@ func (b *Build) GetDefaultVariables() JobVariables {
}
}
func (b *Build) GetDefaultFeatureFlagsVariables() JobVariables {
return JobVariables{
}
}
func (b *Build) GetSharedEnvVariable() JobVariable {
env := JobVariable{Value: "true", Public: true, Internal: true, File: false}
if b.IsSharedEnv() {
......@@ -509,6 +514,7 @@ func (b *Build) GetAllVariables() JobVariables {
variables = append(variables, b.Runner.GetVariables()...)
}
variables = append(variables, b.GetDefaultVariables()...)
variables = append(variables, b.GetDefaultFeatureFlagsVariables()...)
variables = append(variables, b.GetCITLSVariables()...)
variables = append(variables, b.Variables...)
variables = append(variables, b.GetSharedEnvVariable())
......@@ -647,3 +653,23 @@ func (b *Build) GetCacheRequestTimeout() int {
func (b *Build) Duration() time.Duration {
return time.Since(b.startedAt)
}
func (b *Build) IsFeatureFlagOn(name string) bool {
ffValue := b.GetAllVariables().Get(name)
if ffValue == "" {
return false
}
on, err := strconv.ParseBool(ffValue)
if err != nil {
logrus.WithError(err).
WithField("ffName", name).
WithField("ffValue", ffValue).
Error("Error while parsing the value of feature flag")
return false
}
return on
}
package common
import (
"errors"
"fmt"
"os"
"testing"
"errors"
"github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
func init() {
......@@ -590,3 +591,73 @@ func TestGetRemoteURL(t *testing.T) {
assert.Equal(t, tc.result, build.GetRemoteURL())
}
}
type featureFlagOnTestCase struct {
value string
expectedStatus bool
expectedError bool
}
func TestIsFeatureFlagOn(t *testing.T) {
hook := test.NewGlobal()
tests := map[string]featureFlagOnTestCase{
"no value": {
value: "",
expectedStatus: false,
expectedError: false,
},
"true": {
value: "true",
expectedStatus: true,
expectedError: false,
},
"1": {
value: "1",
expectedStatus: true,
expectedError: false,
},
"false": {
value: "false",
expectedStatus: false,
expectedError: false,
},
"0": {
value: "0",
expectedStatus: false,
expectedError: false,
},
"invalid value": {
value: "test",
expectedStatus: false,
expectedError: true,
},
}
for name, testCase := range tests {
t.Run(name, func(t *testing.T) {
build := new(Build)
build.Variables = JobVariables{
{Key: "FF_TEST_FEATURE", Value: testCase.value},
}
status := build.IsFeatureFlagOn("FF_TEST_FEATURE")
assert.Equal(t, testCase.expectedStatus, status)
entry := hook.LastEntry()
if testCase.expectedError {
require.NotNil(t, entry)
logrusOutput, err := entry.String()
require.NoError(t, err)
assert.Contains(t, logrusOutput, "Error while parsing the value of feature flag")
} else {
assert.Nil(t, entry)
}
hook.Reset()
})
}
}
......@@ -5,6 +5,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestVariablesJSON(t *testing.T) {
......@@ -93,3 +94,60 @@ func TestSpecialVariablesExpansion(t *testing.T) {
assert.Equal(t, "aabb", expanded.Get("key3"))
assert.Equal(t, "aabb", expanded.Get("key4"))
}
type multipleKeyUsagesTestCase struct {
variables JobVariables
expectedValue string
}
func TestMultipleUsageOfAKey(t *testing.T) {
getVariable := func(value string) JobVariable {
return JobVariable{Key: "key", Value: value}
}
tests := map[string]multipleKeyUsagesTestCase{
"defined at job level": {
variables: JobVariables{
getVariable("from-job"),
},
expectedValue: "from-job",
},
"defined at default and job level": {
variables: JobVariables{
getVariable("from-default"),
getVariable("from-job"),
},
expectedValue: "from-job",
},
"defined at config, default and job level": {
variables: JobVariables{
getVariable("from-config"),
getVariable("from-default"),
getVariable("from-job"),
},
expectedValue: "from-job",
},
"defined at config and default level": {
variables: JobVariables{
getVariable("from-config"),
getVariable("from-default"),
},
expectedValue: "from-default",
},
"defined at config level": {
variables: JobVariables{
getVariable("from-config"),
},
expectedValue: "from-config",
},
}
for name, testCase := range tests {
t.Run(name, func(t *testing.T) {
for i := 0; i < 100; i++ {
require.Equal(t, testCase.expectedValue, testCase.variables.Get("key"))
}
})
}
}
# Feature flags
Starting with GitLab Runner 11.4 we added a base support for feature flags in GitLab Runner.
These flags may be used:
1. For beta features that we want to make available for volunteers but don't want to enable publicly yet.
A user who wants to use such feature and accepts the risk, can enable it explicitly while the wide
group of users will be unaffected by potential bugs and regressions.
1. For breaking changes that need a deprecation and removal after few releases.
While the product evolves some features may be removed or changed. Sometimes it may be even something
that is generally considered as a bug, but users already managed to find some workarounds for it
and a fix could affect their configurations.
In that cases the feature flag is used to switch from old behavior to the new wan on demand. Such
fix such ensure that the old behavior is deprecated and marked for removal together with the feature
flag that protects the new behavior.
At this moment feature flags mechanism is based on environment variables. To make the change hidden behind
the feature flag active a corresponding environment variable should be set to `true` or `1`. To make the
change hidden behind the feature flag disabled a corresponding environment variable should be set to
`false` or `0`.
## Available feature flags
| Feature flag | Default value | Deprecated | To be removed with | Description |
|--------------------------------------|---------------|------------|--------------------|-------------|
......@@ -123,6 +123,7 @@ To jump into the specific documentation of each executor, visit:
- [Runner monitoring](monitoring/README.md) Learn how to monitor the Runner's behavior.
- [Cleanup the Docker images automatically](https://gitlab.com/gitlab-org/gitlab-runner-docker-cleanup) A simple Docker application that automatically garbage collects the GitLab Runner caches and images when running low on disk space.
- [Configure GitLab Runner to run behind a proxy](configuration/proxy.md) Learn how to set up a Linux proxy and configure GitLab Runner. Especially useful for the Docker executor.
- [Feature Flags](configuration/feature-flags.md) Learn how to use feature flags to get access to features in beta stage or to enable breaking changes before the full deprecation and replacement is handled.
## Troubleshooting
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment