Skip to content

Commit bc67281

Browse files
committed
🔨 overhaul featureflags: configuration + activation + metadata
This is a full overhaul of featureflags. They have now moved to features.yaml and can be configured with the new featureflags utility. This utility makes sure we never mix up IDs for features, sets up default and available features, and generates some helper go-code around featureflags. The reason we are adding this now is: - the use of default featureflags was inconsistent between different code paths - the use of feature-loading from CLI and env variables was inconsistent as well - a series of features that should have been set as default features by now have not been set - they were simply overlooked - the list of features still contains a lot of names that have been builtin or sunset now i.e. setting them has no effect at all -> we can now identify what is usable and what is not Signed-off-by: Dominik Richter <dominik.richter@gmail.com>
1 parent bbada67 commit bc67281

10 files changed

Lines changed: 459 additions & 197 deletions

File tree

apps/cnquery/cmd/features.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) Mondoo, Inc.
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
package cmd
5+
6+
import (
7+
"fmt"
8+
9+
"github.com/spf13/cobra"
10+
"go.mondoo.com/cnquery/v12/cli/config"
11+
)
12+
13+
// featuresCmd represents the version command
14+
var featuresCmd = &cobra.Command{
15+
Hidden: true,
16+
Use: "features",
17+
Short: "Display cnquery features",
18+
Run: func(cmd *cobra.Command, args []string) {
19+
// prerequisite: features must be initialized via config on the root command
20+
// otherwise config.Features won't contain anything useful
21+
fmt.Println("Active features: " + config.Features.String())
22+
},
23+
}
24+
25+
func init() {
26+
rootCmd.AddCommand(featuresCmd)
27+
}

cli/config/config.go

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -40,39 +40,17 @@ const (
4040
// Init initializes and loads the mondoo config
4141
func Init(rootCmd *cobra.Command) {
4242
cobra.OnInitialize(InitViperConfig, func() {
43-
Features = getFeatures()
43+
var err error
44+
Features, err = cnquery.InitFeatures(viper.GetStringSlice("features")...)
45+
if err != nil {
46+
log.Error().Msg(err.Error())
47+
}
48+
// by default we don't print the list of active features, at least not for now...
4449
})
4550
// persistent flags are global for the application
4651
rootCmd.PersistentFlags().StringVar(&UserProvidedPath, "config", "", "Set config file path (default $HOME/.config/mondoo/mondoo.yml)")
4752
}
4853

49-
func getFeatures() cnquery.Features {
50-
bitSet := make([]bool, 256)
51-
flags := []byte{}
52-
53-
for _, f := range cnquery.DefaultFeatures {
54-
if !bitSet[f] {
55-
bitSet[f] = true
56-
flags = append(flags, f)
57-
}
58-
}
59-
60-
envFeatures := viper.GetStringSlice("features")
61-
for _, name := range envFeatures {
62-
flag, ok := cnquery.FeaturesValue[name]
63-
if ok {
64-
if !bitSet[byte(flag)] {
65-
bitSet[byte(flag)] = true
66-
flags = append(flags, byte(flag))
67-
}
68-
} else {
69-
log.Warn().Str("feature", name).Msg("could not parse feature")
70-
}
71-
}
72-
73-
return cnquery.Features(flags)
74-
}
75-
7654
func InitViperConfig() {
7755
viper.SetConfigType("yaml")
7856
// Effectively, we disable using a key delimiter in viper. So you cannot do something like

featureflags.go

Lines changed: 61 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -18,119 +18,17 @@
1818

1919
package cnquery
2020

21+
//go:generate go run utils/featureflags/main.go features.yaml -type=Feature -out=features.go
2122
//go:generate go run golang.org/x/tools/cmd/stringer -type=Feature
2223

2324
import (
2425
"bytes"
2526
"context"
2627
"encoding/base64"
28+
"errors"
29+
"strings"
2730
)
2831

29-
const (
30-
// For all features, use this format:
31-
// desc: A description of this feature and what it does...
32-
// start: vX.x (the version when it is introduced)
33-
// end: vZ.0 (the version when this flag will be removed)
34-
35-
// Feature flags:
36-
37-
// MassQueries feature flag
38-
// desc: Resolve similar queries the same way. If 100 assets have the same
39-
// dependent queries and overrides, they create the same resolved
40-
// plan. Cannot be used with old resolver at the same time for asset.
41-
// start: v3.x, available at v4.x, default at v5.x
42-
// end: v6.0 => default now, no need to set it anymore
43-
MassQueries Feature = iota + 1
44-
45-
// PiperCode feature flag
46-
// desc: Allows MQL to use variable references across blocks. Fully changes
47-
// the compiled code.
48-
// start: v5.x
49-
// end: v7.0 => default now, no need to set it anymore
50-
PiperCode
51-
52-
// BoolAssertions feature flag
53-
// desc: Only boolean results are checked when evaluating a query for success
54-
//
55-
// start: v6.x
56-
// end: v8.0
57-
BoolAssertions
58-
59-
// K8sNodeDiscovery feature flag
60-
// desc: Enables discovery of Kubernetes cluster nodes as individual assets
61-
//
62-
// start: v6.12
63-
// end: unknown
64-
K8sNodeDiscovery
65-
66-
// MQLAssetContext feature flag
67-
//
68-
// start: v7.0
69-
// updated: v11.0 We default to embed flags and re-use this feature for actual asset context
70-
// end: v12.0
71-
MQLAssetContext
72-
73-
// ErrorsAsFailures feature flag
74-
// desc: Errors are treated as failures
75-
// See https://www.notion.so/mondoo/Errors-and-Scoring-5dc554348aad4118a1dbf35123368329
76-
// start: v8.x
77-
// end: v9.0
78-
ErrorsAsFailures
79-
80-
// StoreResourcesData feature flag
81-
// desc: Stores recording-like data with upstream
82-
// start: v10.x
83-
// end: tbd (candidate: v11.0)
84-
StoreResourcesData
85-
86-
// FineGrainedAssets feature flag
87-
// desc: Enables fine-grained assets discovery
88-
// start: v11.x
89-
// end: tbd (candidate: v12.0)
90-
FineGrainedAssets
91-
92-
// SerialNumberAsID feature flag
93-
// desc: Use serial number as the asset ID
94-
// start: v11.x
95-
// end: tbd (candidate: v12.0)
96-
SerialNumberAsID
97-
98-
// ForceShellCompletion feature flag
99-
// desc: Forces shell completion to be enabled (for windows)
100-
// start: v11.x
101-
ForceShellCompletion
102-
103-
// ResourceContext feature flag
104-
// desc: Automatically add resource context to results and prints it
105-
// start: v11.x
106-
ResourceContext
107-
108-
// FailIfNoEntryPoints feature flag
109-
// desc: Fail if no entrypoints or datapoints are found in the query.
110-
FailIfNoEntryPoints
111-
)
112-
113-
// FeaturesValue is a map from feature name to feature flag
114-
var FeaturesValue = map[string]Feature{
115-
MassQueries.String(): MassQueries,
116-
PiperCode.String(): PiperCode,
117-
BoolAssertions.String(): BoolAssertions,
118-
MQLAssetContext.String(): MQLAssetContext,
119-
ErrorsAsFailures.String(): ErrorsAsFailures,
120-
StoreResourcesData.String(): StoreResourcesData,
121-
FineGrainedAssets.String(): FineGrainedAssets,
122-
SerialNumberAsID.String(): SerialNumberAsID,
123-
ForceShellCompletion.String(): ForceShellCompletion,
124-
ResourceContext.String(): ResourceContext,
125-
FailIfNoEntryPoints.String(): FailIfNoEntryPoints,
126-
}
127-
128-
// DefaultFeatures are a set of default flags that are active
129-
var DefaultFeatures = Features{
130-
byte(MassQueries),
131-
byte(PiperCode),
132-
}
133-
13432
// Features is a collection of activated features
13533
type Features []byte
13634

@@ -147,6 +45,15 @@ func (f Features) Encode() string {
14745
return base64.StdEncoding.EncodeToString(f)
14846
}
14947

48+
// String returns a list of features into human-readable form
49+
func (f Features) String() string {
50+
all := make([]string, len(f))
51+
for i, cur := range f {
52+
all[i] = Feature(cur).String()
53+
}
54+
return strings.Join(all, ", ")
55+
}
56+
15057
// DecodeFeatures that were previously encoded
15158
func DecodeFeatures(s string) (Features, error) {
15259
data, err := base64.StdEncoding.DecodeString(s)
@@ -169,3 +76,52 @@ func GetFeatures(ctx context.Context) Features {
16976
}
17077
return f
17178
}
79+
80+
// InitFeatures initialized everything using the default features
81+
// and can turn individual features on and off based on the
82+
// strings that are provided. To turn a feature on just use its
83+
// name. To turn it off use the "no" prefix in front of its name.
84+
// Feature names are case-sensitive
85+
func InitFeatures(features ...string) (Features, error) {
86+
bitSet := make([]bool, MAX_FEATURES)
87+
88+
for _, f := range DefaultFeatures {
89+
if !bitSet[f] {
90+
bitSet[f] = true
91+
}
92+
}
93+
94+
var failing []string
95+
for _, name := range features {
96+
flag, ok := FeaturesValue[name]
97+
if ok {
98+
bitSet[byte(flag)] = true
99+
continue
100+
}
101+
102+
rest, found := strings.CutPrefix(name, "no")
103+
if found {
104+
flag, ok = FeaturesValue[rest]
105+
if ok {
106+
bitSet[byte(flag)] = false
107+
continue
108+
}
109+
}
110+
111+
failing = append(failing, name)
112+
}
113+
114+
flags := []byte{}
115+
for i := 1; i < int(MAX_FEATURES); i++ {
116+
if bitSet[i] {
117+
flags = append(flags, byte(i))
118+
}
119+
}
120+
121+
var err error
122+
if len(failing) != 0 {
123+
err = errors.New("Failed to parse feature-flags: " + strings.Join(failing, ", "))
124+
}
125+
126+
return Features(flags), err
127+
}

features.go

Lines changed: 107 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)