Skip to content

Commit

Permalink
Feature gate CLI flag and visibility changes (#4368)
Browse files Browse the repository at this point in the history
* Expose feature gate registry type and its methods

Signed-off-by: Anthony J Mirabella <a9@aneurysm9.com>

* Add CLI flags for feature gates

Signed-off-by: Anthony J Mirabella <a9@aneurysm9.com>

* Add accessor for global feature gate registry

Signed-off-by: Anthony J Mirabella <a9@aneurysm9.com>

* add canonical import comment

Signed-off-by: Anthony J Mirabella <a9@aneurysm9.com>

* Revert "Add accessor for global feature gate registry"

This reverts commit 23c957c.

* Revert "Expose feature gate registry type and its methods"

This reverts commit df64ad2.

Signed-off-by: Anthony J Mirabella <a9@aneurysm9.com>

* Add CHANGELOG entry, update docblock for Flags()

Signed-off-by: Anthony J Mirabella <a9@aneurysm9.com>
  • Loading branch information
Aneurysm9 authored Nov 23, 2021
1 parent 62a6552 commit 7059878
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@

- Add semconv 1.7.0 and 1.8.0 (#4452)

## 💡 Enhancements 💡

- Added `feature-gates` CLI flag for controlling feature gate state. (#4368)

## v0.39.0 Beta

## 🛑 Breaking changes 🛑
Expand Down
2 changes: 2 additions & 0 deletions service/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/spf13/cobra"

"go.opentelemetry.io/collector/config/configmapprovider"
"go.opentelemetry.io/collector/service/featuregate"
)

// NewCommand constructs a new cobra.Command using the given Collector.
Expand All @@ -28,6 +29,7 @@ func NewCommand(set CollectorSettings) *cobra.Command {
Version: set.BuildInfo.Version,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
featuregate.Apply(featuregate.GetFlags())
if set.ConfigMapProvider == nil {
set.ConfigMapProvider = configmapprovider.NewDefault(getConfigFlag(), getSetFlag())
}
Expand Down
96 changes: 96 additions & 0 deletions service/featuregate/flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package featuregate // import "go.opentelemetry.io/collector/service/featuregate"

import (
"flag"
"sort"
"strings"
)

const gatesListCfg = "feature-gates"

var gatesList = FlagValue{}

// Flags adds CLI flags for managing feature gates to the provided FlagSet
// Feature gates can be configured with `--feature-gates=foo,-bar`. This would
// enable the `foo` feature gate and disable the `bar` feature gate.
func Flags(flags *flag.FlagSet) {
flags.Var(
gatesList,
gatesListCfg,
"Comma-delimited list of feature gate identifiers. Prefix with '-' to disable the feature. '+' or no prefix will enable the feature.")
}

// GetFlags returns the FlagValue used with Flags()
func GetFlags() FlagValue {
return gatesList
}

var _ flag.Value = (*FlagValue)(nil)

// FlagValue implements the flag.Value interface and provides a mechanism for applying feature
// gate statuses to a Registry
type FlagValue map[string]bool

// String returns a string representing the FlagValue
func (f FlagValue) String() string {
var t []string
for k, v := range f {
if v {
t = append(t, k)
} else {
t = append(t, "-"+k)
}
}

// Sort the list of identifiers for consistent results
sort.Strings(t)
return strings.Join(t, ",")
}

// Set applies the FlagValue encoded in the input string
func (f FlagValue) Set(s string) error {
return f.SetSlice(strings.Split(s, ","))
}

// SetSlice applies the feature gate statuses in the input slice to the FlagValue
func (f FlagValue) SetSlice(s []string) error {
for _, v := range s {
var id string
var val bool
switch v[0] {
case '-':
id = v[1:]
val = false
case '+':
id = v[1:]
val = true
default:
id = v
val = true
}

if _, exists := f[id]; exists {
// If the status has already been set, ignore it
// This allows CLI flags, which are processed first
// to take precedence over config settings
continue
}
f[id] = val
}

return nil
}
93 changes: 93 additions & 0 deletions service/featuregate/flags_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package featuregate

import (
"flag"
"testing"

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

func TestFlags(t *testing.T) {
fs := flag.NewFlagSet("test", flag.ContinueOnError)
Flags(fs)
assert.Equal(t, gatesList, fs.Lookup(gatesListCfg).Value)
}

func TestGetFlags(t *testing.T) {
assert.Equal(t, gatesList, GetFlags())
}

func TestFlagValue_basic(t *testing.T) {
for _, tc := range []struct {
name string
expected string
input FlagValue
}{
{
name: "single item",
input: FlagValue{"foo": true},
expected: "foo",
},
{
name: "single disabled item",
input: FlagValue{"foo": false},
expected: "-foo",
},
{
name: "multiple items",
input: FlagValue{"foo": true, "bar": false},
expected: "-bar,foo",
},
} {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, tc.input.String())
v := FlagValue{}
assert.NoError(t, v.Set(tc.expected))
assert.Equal(t, tc.input, v)
})
}
}

func TestFlagValue_SetSlice(t *testing.T) {
for _, tc := range []struct {
name string
input []string
expected FlagValue
}{
{
name: "single item",
input: []string{"foo"},
expected: FlagValue{"foo": true},
},
{
name: "multiple items",
input: []string{"foo", "-bar", "+baz"},
expected: FlagValue{"foo": true, "bar": false, "baz": true},
},
{
name: "repeated items",
input: []string{"foo", "-bar", "-foo"},
expected: FlagValue{"foo": true, "bar": false},
},
} {
t.Run(tc.name, func(t *testing.T) {
v := FlagValue{}
assert.NoError(t, v.SetSlice(tc.input))
assert.Equal(t, tc.expected, v)
})
}
}
2 changes: 2 additions & 0 deletions service/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"strings"

"go.opentelemetry.io/collector/config/configtelemetry"
"go.opentelemetry.io/collector/service/featuregate"
)

var (
Expand Down Expand Up @@ -52,6 +53,7 @@ func (s *stringArrayValue) String() string {
func flags() *flag.FlagSet {
flagSet := new(flag.FlagSet)
configtelemetry.Flags(flagSet)
featuregate.Flags(flagSet)

// At least until we can use a generic, i.e.: OpenCensus, metrics exporter
// we default to Prometheus at port 8888, if not otherwise specified.
Expand Down

0 comments on commit 7059878

Please sign in to comment.