Skip to content

Commit

Permalink
fix: support experiment-ref (#98)
Browse files Browse the repository at this point in the history
  • Loading branch information
raffis committed Mar 18, 2024
1 parent c8fb07b commit 50955ca
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 54 deletions.
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,23 @@ spec:
environments:
- name: "production"
enabled: true
rules:
- type: force
enabled: true
description: |
Enterprise tiers
condition: |
{
"tier": {
"$elemMatch": {
"$in": [
"ENTERPRISE",
"FULL"
]
}
}
}
value: "999"
---
apiVersion: growthbook.infra.doodle.com/v1beta1
kind: GrowthbookFeature
Expand Down Expand Up @@ -186,4 +203,4 @@ The controller can be configured by cmd args:
--min-retry-delay duration The minimum amount of time for which an object being reconciled will have to wait before a retry. (default 750ms)
--watch-all-namespaces Watch for resources in all namespaces, if set to false it will only watch the runtime namespace. (default true)
--watch-label-selector string Watch for resources with matching labels e.g. 'sharding.fluxcd.io/shard=shard1'.
```
```
50 changes: 29 additions & 21 deletions api/v1beta1/growthbookfeature_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,33 +50,41 @@ var (
SavedGroupTargetingMatchAny SavedGroupTargetingMatch = "any"
)

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

var (
FeatureRuleTypeForce FeatureRuleType = "force"
FeatureRuleTypeRollout FeatureRuleType = "rollout"
FeatureRuleTypeExperiment FeatureRuleType = "experiment"
FeatureRuleTypeForce FeatureRuleType = "force"
FeatureRuleTypeRollout FeatureRuleType = "rollout"
FeatureRuleTypeExperiment FeatureRuleType = "experiment"
FeatureRuleTypeExperimentRef FeatureRuleType = "experiment-ref"
)

type FeatureRule struct {
Type FeatureRuleType `json:"type,omitempty"`
Description string `json:"description,omitempty"`
Condition string `json:"condition,omitempty"`
Enabled bool `json:"enabled,omitempty"`
ScheduleRules []ScheduleRule `json:"scheduleRules,omitempty"`
SavedGroups []SavedGroupTargeting `json:"savedGroups,omitempty"`
Prerequisites []FeaturePrerequisite `json:"prerequisites,omitempty"`
Value string `json:"value,omitempty"`
Coverage string `json:"coverage,omitempty"`
HashAttribute string `json:"hashAttribute,omitempty"`
TrackingKey string `json:"trackingKey,omitempty"`
FallbackAttribute *string `json:"fallbackAttribute,omitempty"`
DisableStickyBucketing *bool `json:"disableStickyBucketing,omitempty"`
BucketVersion *int64 `json:"bucketVersion,omitempty"`
MinBucketVersion *int64 `json:"minBucketVersion,omitempty"`
Namespace *NamespaceValue `json:"namespace,omitempty"`
Values []ExperimentValue `json:"values,omitempty"`
Type FeatureRuleType `json:"type,omitempty"`
Description string `json:"description,omitempty"`
Condition string `json:"condition,omitempty"`
Enabled bool `json:"enabled,omitempty"`
ScheduleRules []ScheduleRule `json:"scheduleRules,omitempty"`
SavedGroups []SavedGroupTargeting `json:"savedGroups,omitempty"`
Prerequisites []FeaturePrerequisite `json:"prerequisites,omitempty"`
Value string `json:"value,omitempty"`
Coverage string `json:"coverage,omitempty"`
HashAttribute string `json:"hashAttribute,omitempty"`
TrackingKey string `json:"trackingKey,omitempty"`
FallbackAttribute *string `json:"fallbackAttribute,omitempty"`
DisableStickyBucketing *bool `json:"disableStickyBucketing,omitempty"`
BucketVersion *int64 `json:"bucketVersion,omitempty"`
MinBucketVersion *int64 `json:"minBucketVersion,omitempty"`
Namespace *NamespaceValue `json:"namespace,omitempty"`
Values []ExperimentValue `json:"values,omitempty"`
ExperimentID string `json:"experimentId,omitempty"`
Variations []ExperimentRefVariation `json:"variations,omitempty"`
}

type ExperimentRefVariation struct {
VariationId string `json:"variationId,omitempty"`
Value string `json:"value,omitempty"`
}

type FeaturePrerequisite struct {
Expand Down
20 changes: 20 additions & 0 deletions api/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ spec:
type: boolean
enabled:
type: boolean
experimentId:
type: string
fallbackAttribute:
type: string
hashAttribute:
Expand Down Expand Up @@ -128,6 +130,7 @@ spec:
- force
- rollout
- experiment
- experiment-ref
type: string
value:
type: string
Expand All @@ -143,6 +146,15 @@ spec:
type: integer
type: object
type: array
variations:
items:
properties:
value:
type: string
variationId:
type: string
type: object
type: array
type: object
type: array
type: object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ spec:
type: boolean
enabled:
type: boolean
experimentId:
type: string
fallbackAttribute:
type: string
hashAttribute:
Expand Down Expand Up @@ -128,6 +130,7 @@ spec:
- force
- rollout
- experiment
- experiment-ref
type: string
value:
type: string
Expand All @@ -143,6 +146,15 @@ spec:
type: integer
type: object
type: array
variations:
items:
properties:
value:
type: string
variationId:
type: string
type: object
type: array
type: object
type: array
type: object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ metadata:
spec:
restartPolicy: OnFailure
containers:
- image: curlimages/curl:8.1.2
- image: jonlabelle/network-tools
imagePullPolicy: IfNotPresent
name: verify
command:
- /bin/sh
- "-c"
- |
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}}'
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"}]}}'
resources: {}
securityContext:
allowPrivilegeEscalation: false
Expand Down
50 changes: 29 additions & 21 deletions internal/growthbook/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,30 +52,38 @@ var (
type FeatureRuleType string

var (
FeatureRuleTypeForce FeatureRuleType = "force"
FeatureRuleTypeRollout FeatureRuleType = "rollout"
FeatureRuleTypeExperiment FeatureRuleType = "experiment"
FeatureRuleTypeForce FeatureRuleType = "force"
FeatureRuleTypeRollout FeatureRuleType = "rollout"
FeatureRuleTypeExperiment FeatureRuleType = "experiment"
FeatureRuleTypeExperimentRef FeatureRuleType = "experiment-ref"
)

type FeatureRule struct {
ID string `bson:"id,omitempty"`
Type FeatureRuleType `bson:"type,omitempty"`
Description string `bson:"description,omitempty"`
Condition string `bson:"condition,omitempty"`
Enabled bool `bson:"enabled,omitempty"`
ScheduleRules []ScheduleRule `bson:"scheduleRules,omitempty"`
SavedGroups []SavedGroupTargeting `bson:"savedGroups,omitempty"`
Prerequisites []FeaturePrerequisite `bson:"prerequisites,omitempty"`
Value string `bson:"value,omitempty"`
Coverage float64 `bson:"coverage,omitempty"`
HashAttribute string `bson:"hashAttribute,omitempty"`
TrackingKey string `bson:"trackingKey,omitempty"`
FallbackAttribute *string `bson:"fallbackAttribute,omitempty"`
DisableStickyBucketing *bool `bson:"disableStickyBucketing,omitempty"`
BucketVersion *int64 `bson:"bucketVersion,omitempty"`
MinBucketVersion *int64 `bson:"minBucketVersion,omitempty"`
Namespace *NamespaceValue `bson:"namespace,omitempty"`
Values []ExperimentValue `bson:"values,omitempty"`
ID string `bson:"id,omitempty"`
Type FeatureRuleType `bson:"type,omitempty"`
Description string `bson:"description,omitempty"`
Condition string `bson:"condition,omitempty"`
Enabled bool `bson:"enabled,omitempty"`
ScheduleRules []ScheduleRule `bson:"scheduleRules,omitempty"`
SavedGroups []SavedGroupTargeting `bson:"savedGroups,omitempty"`
Prerequisites []FeaturePrerequisite `bson:"prerequisites,omitempty"`
Value string `bson:"value,omitempty"`
Coverage float64 `bson:"coverage,omitempty"`
HashAttribute string `bson:"hashAttribute,omitempty"`
TrackingKey string `bson:"trackingKey,omitempty"`
FallbackAttribute *string `bson:"fallbackAttribute,omitempty"`
DisableStickyBucketing *bool `bson:"disableStickyBucketing,omitempty"`
BucketVersion *int64 `bson:"bucketVersion,omitempty"`
MinBucketVersion *int64 `bson:"minBucketVersion,omitempty"`
Namespace *NamespaceValue `bson:"namespace,omitempty"`
Values []ExperimentValue `bson:"values,omitempty"`
ExperimentID string `bson:"experimentId,omitempty"`
Variations []ExperimentRefVariation `bson:"variations,omitempty"`
}

type ExperimentRefVariation struct {
VariationId string `bson:"variationId,omitempty"`
Value string `bson:"value,omitempty"`
}

type FeaturePrerequisite struct {
Expand Down
19 changes: 10 additions & 9 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
ctrlcache "sigs.k8s.io/controller-runtime/pkg/cache"
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/metrics/server"
// +kubebuilder:scaffold:imports
)

Expand Down Expand Up @@ -91,28 +92,24 @@ func main() {
leaderElectionId = leaderelection.GenerateID(leaderElectionId, watchOptions.LabelSelector)
}

watchNamespace := ""
if !watchOptions.AllNamespaces {
watchNamespace = os.Getenv("RUNTIME_NAMESPACE")
}

watchSelector, err := helper.GetWatchSelector(watchOptions)
if err != nil {
setupLog.Error(err, "unable to configure watch label selector for manager")
os.Exit(1)
}

opts := ctrl.Options{
Scheme: scheme,
MetricsBindAddress: metricsAddr,
Scheme: scheme,
Metrics: server.Options{
BindAddress: metricsAddr,
},
HealthProbeBindAddress: healthAddr,
LeaderElection: leaderElectionOptions.Enable,
LeaderElectionReleaseOnCancel: leaderElectionOptions.ReleaseOnCancel,
LeaseDuration: &leaderElectionOptions.LeaseDuration,
RenewDeadline: &leaderElectionOptions.RenewDeadline,
RetryPeriod: &leaderElectionOptions.RetryPeriod,
GracefulShutdownTimeout: &gracefulShutdownTimeout,
Port: 9443,
LeaderElectionID: leaderElectionId,
Cache: ctrlcache.Options{
ByObject: map[ctrlclient.Object]ctrlcache.ByObject{
Expand All @@ -121,10 +118,14 @@ func main() {
&infrav1beta1.GrowthbookFeature{}: {Label: watchSelector},
&infrav1beta1.GrowthbookClient{}: {Label: watchSelector},
},
Namespaces: []string{watchNamespace},
},
}

if !watchOptions.AllNamespaces {
opts.Cache.DefaultNamespaces = make(map[string]ctrlcache.Config)
opts.Cache.DefaultNamespaces[os.Getenv("RUNTIME_NAMESPACE")] = ctrlcache.Config{}
}

mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), opts)
if err != nil {
setupLog.Error(err, "unable to start manager")
Expand Down

0 comments on commit 50955ca

Please sign in to comment.