Skip to content

Commit 50955ca

Browse files
authored
fix: support experiment-ref (#98)
1 parent c8fb07b commit 50955ca

File tree

8 files changed

+132
-54
lines changed

8 files changed

+132
-54
lines changed

README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,23 @@ spec:
112112
environments:
113113
- name: "production"
114114
enabled: true
115+
rules:
116+
- type: force
117+
enabled: true
118+
description: |
119+
Enterprise tiers
120+
condition: |
121+
{
122+
"tier": {
123+
"$elemMatch": {
124+
"$in": [
125+
"ENTERPRISE",
126+
"FULL"
127+
]
128+
}
129+
}
130+
}
131+
value: "999"
115132
---
116133
apiVersion: growthbook.infra.doodle.com/v1beta1
117134
kind: GrowthbookFeature
@@ -186,4 +203,4 @@ The controller can be configured by cmd args:
186203
--min-retry-delay duration The minimum amount of time for which an object being reconciled will have to wait before a retry. (default 750ms)
187204
--watch-all-namespaces Watch for resources in all namespaces, if set to false it will only watch the runtime namespace. (default true)
188205
--watch-label-selector string Watch for resources with matching labels e.g. 'sharding.fluxcd.io/shard=shard1'.
189-
```
206+
```

api/v1beta1/growthbookfeature_types.go

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -50,33 +50,41 @@ var (
5050
SavedGroupTargetingMatchAny SavedGroupTargetingMatch = "any"
5151
)
5252

53-
// +kubebuilder:validation:Enum=force;rollout;experiment
53+
// +kubebuilder:validation:Enum=force;rollout;experiment;experiment-ref
5454
type FeatureRuleType string
5555

5656
var (
57-
FeatureRuleTypeForce FeatureRuleType = "force"
58-
FeatureRuleTypeRollout FeatureRuleType = "rollout"
59-
FeatureRuleTypeExperiment FeatureRuleType = "experiment"
57+
FeatureRuleTypeForce FeatureRuleType = "force"
58+
FeatureRuleTypeRollout FeatureRuleType = "rollout"
59+
FeatureRuleTypeExperiment FeatureRuleType = "experiment"
60+
FeatureRuleTypeExperimentRef FeatureRuleType = "experiment-ref"
6061
)
6162

6263
type FeatureRule struct {
63-
Type FeatureRuleType `json:"type,omitempty"`
64-
Description string `json:"description,omitempty"`
65-
Condition string `json:"condition,omitempty"`
66-
Enabled bool `json:"enabled,omitempty"`
67-
ScheduleRules []ScheduleRule `json:"scheduleRules,omitempty"`
68-
SavedGroups []SavedGroupTargeting `json:"savedGroups,omitempty"`
69-
Prerequisites []FeaturePrerequisite `json:"prerequisites,omitempty"`
70-
Value string `json:"value,omitempty"`
71-
Coverage string `json:"coverage,omitempty"`
72-
HashAttribute string `json:"hashAttribute,omitempty"`
73-
TrackingKey string `json:"trackingKey,omitempty"`
74-
FallbackAttribute *string `json:"fallbackAttribute,omitempty"`
75-
DisableStickyBucketing *bool `json:"disableStickyBucketing,omitempty"`
76-
BucketVersion *int64 `json:"bucketVersion,omitempty"`
77-
MinBucketVersion *int64 `json:"minBucketVersion,omitempty"`
78-
Namespace *NamespaceValue `json:"namespace,omitempty"`
79-
Values []ExperimentValue `json:"values,omitempty"`
64+
Type FeatureRuleType `json:"type,omitempty"`
65+
Description string `json:"description,omitempty"`
66+
Condition string `json:"condition,omitempty"`
67+
Enabled bool `json:"enabled,omitempty"`
68+
ScheduleRules []ScheduleRule `json:"scheduleRules,omitempty"`
69+
SavedGroups []SavedGroupTargeting `json:"savedGroups,omitempty"`
70+
Prerequisites []FeaturePrerequisite `json:"prerequisites,omitempty"`
71+
Value string `json:"value,omitempty"`
72+
Coverage string `json:"coverage,omitempty"`
73+
HashAttribute string `json:"hashAttribute,omitempty"`
74+
TrackingKey string `json:"trackingKey,omitempty"`
75+
FallbackAttribute *string `json:"fallbackAttribute,omitempty"`
76+
DisableStickyBucketing *bool `json:"disableStickyBucketing,omitempty"`
77+
BucketVersion *int64 `json:"bucketVersion,omitempty"`
78+
MinBucketVersion *int64 `json:"minBucketVersion,omitempty"`
79+
Namespace *NamespaceValue `json:"namespace,omitempty"`
80+
Values []ExperimentValue `json:"values,omitempty"`
81+
ExperimentID string `json:"experimentId,omitempty"`
82+
Variations []ExperimentRefVariation `json:"variations,omitempty"`
83+
}
84+
85+
type ExperimentRefVariation struct {
86+
VariationId string `json:"variationId,omitempty"`
87+
Value string `json:"value,omitempty"`
8088
}
8189

8290
type FeaturePrerequisite struct {

api/v1beta1/zz_generated.deepcopy.go

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

chart/growthbook-controller/crds/growthbook.infra.doodle.com_growthbookfeatures.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ spec:
6969
type: boolean
7070
enabled:
7171
type: boolean
72+
experimentId:
73+
type: string
7274
fallbackAttribute:
7375
type: string
7476
hashAttribute:
@@ -128,6 +130,7 @@ spec:
128130
- force
129131
- rollout
130132
- experiment
133+
- experiment-ref
131134
type: string
132135
value:
133136
type: string
@@ -143,6 +146,15 @@ spec:
143146
type: integer
144147
type: object
145148
type: array
149+
variations:
150+
items:
151+
properties:
152+
value:
153+
type: string
154+
variationId:
155+
type: string
156+
type: object
157+
type: array
146158
type: object
147159
type: array
148160
type: object

config/base/crd/bases/growthbook.infra.doodle.com_growthbookfeatures.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ spec:
6969
type: boolean
7070
enabled:
7171
type: boolean
72+
experimentId:
73+
type: string
7274
fallbackAttribute:
7375
type: string
7476
hashAttribute:
@@ -128,6 +130,7 @@ spec:
128130
- force
129131
- rollout
130132
- experiment
133+
- experiment-ref
131134
type: string
132135
value:
133136
type: string
@@ -143,6 +146,15 @@ spec:
143146
type: integer
144147
type: object
145148
type: array
149+
variations:
150+
items:
151+
properties:
152+
value:
153+
type: string
154+
variationId:
155+
type: string
156+
type: object
157+
type: array
146158
type: object
147159
type: array
148160
type: object

config/tests/cases/growthbook-v2.1.1-mongodb-v6-with-proxy/test/verify-proxy-get-features-clienttoken.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ metadata:
77
spec:
88
restartPolicy: OnFailure
99
containers:
10-
- image: curlimages/curl:8.1.2
10+
- image: jonlabelle/network-tools
1111
imagePullPolicy: IfNotPresent
1212
name: verify
1313
command:
1414
- /bin/sh
1515
- "-c"
1616
- |
17-
curl --fail -vvv http://growthbook-proxy/api/features/sdk-token | grep '{"status":200,"features":{"feature-b":{"defaultValue":true},"feature-a":{"defaultValue":false}}\|{"status":200,"features":{"feature-a":{"defaultValue":true},"feature-b":{"defaultValue":false}}'
17+
curl --fail -vvv http://growthbook-proxy/api/features/sdk-token | jq -c --sort-keys .features | grep '{"feature-a":{"defaultValue":true},"feature-b":{"defaultValue":false},"feature-c":{"defaultValue":false,"rules":\[{"condition":{"segments":{"$elemMatch":{"$eq":"TIER_ENTERPRISE"}}},"force":true},{"condition":{"locale":"de"},"coverage":0.1,"force":true,"hashAttribute":"userId"}]}}'
1818
resources: {}
1919
securityContext:
2020
allowPrivilegeEscalation: false

internal/growthbook/feature.go

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -52,30 +52,38 @@ var (
5252
type FeatureRuleType string
5353

5454
var (
55-
FeatureRuleTypeForce FeatureRuleType = "force"
56-
FeatureRuleTypeRollout FeatureRuleType = "rollout"
57-
FeatureRuleTypeExperiment FeatureRuleType = "experiment"
55+
FeatureRuleTypeForce FeatureRuleType = "force"
56+
FeatureRuleTypeRollout FeatureRuleType = "rollout"
57+
FeatureRuleTypeExperiment FeatureRuleType = "experiment"
58+
FeatureRuleTypeExperimentRef FeatureRuleType = "experiment-ref"
5859
)
5960

6061
type FeatureRule struct {
61-
ID string `bson:"id,omitempty"`
62-
Type FeatureRuleType `bson:"type,omitempty"`
63-
Description string `bson:"description,omitempty"`
64-
Condition string `bson:"condition,omitempty"`
65-
Enabled bool `bson:"enabled,omitempty"`
66-
ScheduleRules []ScheduleRule `bson:"scheduleRules,omitempty"`
67-
SavedGroups []SavedGroupTargeting `bson:"savedGroups,omitempty"`
68-
Prerequisites []FeaturePrerequisite `bson:"prerequisites,omitempty"`
69-
Value string `bson:"value,omitempty"`
70-
Coverage float64 `bson:"coverage,omitempty"`
71-
HashAttribute string `bson:"hashAttribute,omitempty"`
72-
TrackingKey string `bson:"trackingKey,omitempty"`
73-
FallbackAttribute *string `bson:"fallbackAttribute,omitempty"`
74-
DisableStickyBucketing *bool `bson:"disableStickyBucketing,omitempty"`
75-
BucketVersion *int64 `bson:"bucketVersion,omitempty"`
76-
MinBucketVersion *int64 `bson:"minBucketVersion,omitempty"`
77-
Namespace *NamespaceValue `bson:"namespace,omitempty"`
78-
Values []ExperimentValue `bson:"values,omitempty"`
62+
ID string `bson:"id,omitempty"`
63+
Type FeatureRuleType `bson:"type,omitempty"`
64+
Description string `bson:"description,omitempty"`
65+
Condition string `bson:"condition,omitempty"`
66+
Enabled bool `bson:"enabled,omitempty"`
67+
ScheduleRules []ScheduleRule `bson:"scheduleRules,omitempty"`
68+
SavedGroups []SavedGroupTargeting `bson:"savedGroups,omitempty"`
69+
Prerequisites []FeaturePrerequisite `bson:"prerequisites,omitempty"`
70+
Value string `bson:"value,omitempty"`
71+
Coverage float64 `bson:"coverage,omitempty"`
72+
HashAttribute string `bson:"hashAttribute,omitempty"`
73+
TrackingKey string `bson:"trackingKey,omitempty"`
74+
FallbackAttribute *string `bson:"fallbackAttribute,omitempty"`
75+
DisableStickyBucketing *bool `bson:"disableStickyBucketing,omitempty"`
76+
BucketVersion *int64 `bson:"bucketVersion,omitempty"`
77+
MinBucketVersion *int64 `bson:"minBucketVersion,omitempty"`
78+
Namespace *NamespaceValue `bson:"namespace,omitempty"`
79+
Values []ExperimentValue `bson:"values,omitempty"`
80+
ExperimentID string `bson:"experimentId,omitempty"`
81+
Variations []ExperimentRefVariation `bson:"variations,omitempty"`
82+
}
83+
84+
type ExperimentRefVariation struct {
85+
VariationId string `bson:"variationId,omitempty"`
86+
Value string `bson:"value,omitempty"`
7987
}
8088

8189
type FeaturePrerequisite struct {

main.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
ctrlcache "sigs.k8s.io/controller-runtime/pkg/cache"
3737
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
3838
"sigs.k8s.io/controller-runtime/pkg/healthz"
39+
"sigs.k8s.io/controller-runtime/pkg/metrics/server"
3940
// +kubebuilder:scaffold:imports
4041
)
4142

@@ -91,28 +92,24 @@ func main() {
9192
leaderElectionId = leaderelection.GenerateID(leaderElectionId, watchOptions.LabelSelector)
9293
}
9394

94-
watchNamespace := ""
95-
if !watchOptions.AllNamespaces {
96-
watchNamespace = os.Getenv("RUNTIME_NAMESPACE")
97-
}
98-
9995
watchSelector, err := helper.GetWatchSelector(watchOptions)
10096
if err != nil {
10197
setupLog.Error(err, "unable to configure watch label selector for manager")
10298
os.Exit(1)
10399
}
104100

105101
opts := ctrl.Options{
106-
Scheme: scheme,
107-
MetricsBindAddress: metricsAddr,
102+
Scheme: scheme,
103+
Metrics: server.Options{
104+
BindAddress: metricsAddr,
105+
},
108106
HealthProbeBindAddress: healthAddr,
109107
LeaderElection: leaderElectionOptions.Enable,
110108
LeaderElectionReleaseOnCancel: leaderElectionOptions.ReleaseOnCancel,
111109
LeaseDuration: &leaderElectionOptions.LeaseDuration,
112110
RenewDeadline: &leaderElectionOptions.RenewDeadline,
113111
RetryPeriod: &leaderElectionOptions.RetryPeriod,
114112
GracefulShutdownTimeout: &gracefulShutdownTimeout,
115-
Port: 9443,
116113
LeaderElectionID: leaderElectionId,
117114
Cache: ctrlcache.Options{
118115
ByObject: map[ctrlclient.Object]ctrlcache.ByObject{
@@ -121,10 +118,14 @@ func main() {
121118
&infrav1beta1.GrowthbookFeature{}: {Label: watchSelector},
122119
&infrav1beta1.GrowthbookClient{}: {Label: watchSelector},
123120
},
124-
Namespaces: []string{watchNamespace},
125121
},
126122
}
127123

124+
if !watchOptions.AllNamespaces {
125+
opts.Cache.DefaultNamespaces = make(map[string]ctrlcache.Config)
126+
opts.Cache.DefaultNamespaces[os.Getenv("RUNTIME_NAMESPACE")] = ctrlcache.Config{}
127+
}
128+
128129
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), opts)
129130
if err != nil {
130131
setupLog.Error(err, "unable to start manager")

0 commit comments

Comments
 (0)