Skip to content

Commit 556ceb6

Browse files
authored
feat: initial release (#1)
1 parent ae5174a commit 556ceb6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2211
-383
lines changed

.github/workflows/pr-build.yaml

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,19 @@ on:
88
- reopened
99

1010
jobs:
11-
e2e:
11+
build:
1212
runs-on: ubuntu-latest
13+
outputs:
14+
profiles: ${{ steps.profiles.outputs.matrix }}
1315
steps:
1416
- name: Checkout
15-
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b #v3
17+
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c #v3
1618
- name: Setup Go
1719
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 #v3.5.0
1820
with:
1921
go-version: 1.20.x
2022
- name: Restore Go cache
21-
uses: actions/cache@4723a57e26efda3a62cbde1812113b730952852d #v3.2.2
23+
uses: actions/cache@627f0f41f6904a5b1efbaed9f96d9eb58e92e920 #v3.2.4
2224
with:
2325
path: ~/go/pkg/mod
2426
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
@@ -34,10 +36,10 @@ jobs:
3436
run: make test
3537
- name: build
3638
run: make build
37-
- name: Send go coverage report
38-
uses: shogo82148/actions-goveralls@31ee804b8576ae49f6dc3caa22591bc5080e7920 #v1.6.0
39-
with:
40-
path-to-profile: coverage.out
39+
#- name: Send go coverage report
40+
# uses: shogo82148/actions-goveralls@31ee804b8576ae49f6dc3caa22591bc5080e7920 #v1.6.0
41+
# with:
42+
# path-to-profile: coverage.out
4143
- name: Check if working tree is dirty
4244
run: |
4345
if [[ $(git diff --stat) != '' ]]; then
@@ -48,19 +50,56 @@ jobs:
4850
- name: Build container image
4951
run: |
5052
make docker-build
53+
- name: Create image tarball
54+
run: |
55+
docker save --output k8sgrowthbook-controller-container.tar k8sgrowthbook-controller:latest
56+
- name: Upload image
57+
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce #v3.1.2
58+
with:
59+
name: k8sgrowthbook-controller-container
60+
path: k8sgrowthbook-controller-container.tar
61+
- id: profiles
62+
name: Determine test profiles
63+
run: |
64+
profiles=$(ls config/tests/cases | jq -R -s -c 'split("\n")[:-1]')
65+
echo $profiles
66+
echo "::set-output name=matrix::$profiles"
67+
68+
e2e-tests:
69+
runs-on: ubuntu-latest
70+
needs:
71+
- build
72+
strategy:
73+
matrix:
74+
profile: ${{ fromJson(needs.build.outputs.profiles) }}
75+
steps:
76+
- name: Checkout
77+
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c #v3
78+
- name: Setup Go
79+
uses: actions/setup-go@d0a58c1c4d2b25278816e339b944508c875f3613 #v3.4.0
80+
with:
81+
go-version: 1.20.x
5182
- name: Setup Kubernetes
5283
uses: engineerd/setup-kind@aa272fe2a7309878ffc2a81c56cfe3ef108ae7d0 #v0.5.0
5384
with:
5485
version: v0.17.0
86+
- name: Download k8sgrowthbook-controller container
87+
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a #v3.0.2
88+
with:
89+
name: k8sgrowthbook-controller-container
90+
path: /tmp
91+
- name: Load images
92+
run: |
93+
docker load --input /tmp/k8sgrowthbook-controller-container.tar
94+
docker image ls -a
5595
- name: Setup Kustomize
5696
uses: imranismail/setup-kustomize@6691bdeb1b0a3286fb7f70fd1423c10e81e5375f # v2.0.0
5797
- name: Run test
5898
run: |
59-
make kind-test
99+
make kind-test TEST_PROFILE=${{ matrix.profile }}
60100
- name: Debug failure
61101
if: failure()
62102
run: |
63-
kubectl -n kube-system describe pods
64-
kubectl -n k8sreq-system describe pods
65-
kubectl -n k8sreq-system get all
66-
kubectl -n k8sreq-system logs deploy/k8sgrowthbook-controller manager
103+
kubectl -n k8sgrowthbook-system get pods
104+
kubectl -n k8sgrowthbook-system logs deploy/k8sgrowthbook-controller
105+
kubectl -n k8sgrowthbook-system get growthbookinstance -o yaml

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*.dylib
88
bin
99
/manager
10-
config/tests/.../charts
10+
**/charts
1111
coverage.out
1212

1313
# Test binary, build with `go test -c`

Makefile

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ help: ## Display this help.
4141

4242
.PHONY: manifests
4343
manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
44-
$(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/base/crd/bases
44+
$(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/base/crd/bases output:rbac:artifacts:config=config/base/rbac
4545
cp config/base/crd/bases/* chart/k8sgrowthbook-controller/crds/
4646

4747
.PHONY: generate
@@ -105,11 +105,10 @@ kind-test: docker-build ## Deploy including test
105105
kind load docker-image ${IMG} --name ${CLUSTER}
106106
kubectl -n k8sgrowthbook-system delete pods --all
107107
kustomize build config/tests/cases/${TEST_PROFILE} --enable-helm | kubectl --context kind-${CLUSTER} apply -f -
108-
kubectl --context kind-${CLUSTER} -n k8sgrowthbook-system wait --for=condition=Ready pods -l control-plane=controller-manager -l app.kubernetes.io/name=podinfo -l app.kubernetes.io/managed-by!=Helm --timeout=3m
109-
kill $$pid
108+
kubectl --context kind-${CLUSTER} -n k8sgrowthbook-system wait --for=condition=Ready pods -l control-plane=controller-manager -l app.kubernetes.io/managed-by!=Helm -l verify!=yes --timeout=3m
109+
kubectl --context kind-${CLUSTER} -n k8sgrowthbook-system wait --for=jsonpath='{.status.conditions[1].reason}'=PodCompleted pods -l control-plane=controller-manager -l app.kubernetes.io/managed-by!=Helm -l verify=yes --timeout=3m
110110

111111
##@ Deployment
112-
113112
ifndef ignore-not-found
114113
ignore-not-found = false
115114
endif
@@ -134,7 +133,7 @@ undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/confi
134133
CONTROLLER_GEN = $(GOBIN)/controller-gen
135134
.PHONY: controller-gen
136135
controller-gen: ## Download controller-gen locally if necessary.
137-
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.10.0)
136+
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.12.0)
138137

139138
GOLANGCI_LINT = $(GOBIN)/golangci-lint
140139
.PHONY: golangci-lint

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@
99
Kubernetes controller for managing growthbook.
1010

1111
Currently supported are `GrowthbookOrganization`, `GrowthbookUser`, `GrowthbookFeature`, `GrowthbookClient` and `GrowthbookInstance` while the later one is the main resource
12-
referencing organizations and users while organizations select further resources including clients, features and users (organization membership).
12+
referencing all other resources while organizations select further resources including clients, features and users (organization membership).
1313
Basically for deploying features and clients a `GrowthbookInstance` as well as at least one `GrowthbooKOrganization` resource needs to be created.
1414

1515
This controller does not deploy growthbook itself. It manages resources for an existing growthbook instance.
1616
Growthbook currently does not support managing features nor clients within the scope of the rest api. This controller
1717
bypasses their api and manages the resources on MongoDB directly.
1818

19+
## Resource relationship
20+
21+
![graph](https://github.com/DoodleScheduling/k8sgrowthbook-controller/blob/master/docs/resource-relationship.jpg.jpg?raw=true)
22+
1923
## Example Usage
2024

2125
The following manifests configure:
@@ -97,6 +101,7 @@ metadata:
97101
name: feature-a
98102
labels:
99103
growthbook-org: my-org
104+
growthbook-instance: my-instance
100105
namespace: growthbook
101106
spec:
102107
description: feature A
@@ -114,6 +119,7 @@ metadata:
114119
name: feature-b
115120
labels:
116121
growthbook-org: my-org
122+
growthbook-instance: my-instance
117123
namespace: growthbook
118124
spec:
119125
description: feature B
@@ -131,9 +137,11 @@ metadata:
131137
name: client-1
132138
labels:
133139
growthbook-org: my-org
140+
growthbook-instance: my-instance
134141
namespace: growthbook
135142
spec:
136143
description: feature B
144+
environment: production
137145
tags:
138146
- frontend
139147
tokenSecret:

api/v1beta1/growthbookclient_types.go

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,17 @@ import (
2424

2525
// GrowthbookClientSpec defines the desired state of GrowthbookClient
2626
type GrowthbookClientSpec struct {
27-
Languages []string `json:"languages,omitempty"`
28-
Name string `json:"name,omitempty"`
29-
Environment string `json:"environment,omitempty"`
30-
EncryptPayload bool `json:"encryptPayload,omitempty"`
31-
Project string `json:"project,omitempty"`
32-
IncludeVisualExperiments bool `json:"includeVisualExperiments,omitempty"`
33-
IncludeDraftExperiments bool `json:"includeDraftExperiments,omitempty"`
34-
IncludeExperimentNames bool `json:"includeExperimentNames,omitempty"`
35-
ID string `json:"id,omitempty"`
36-
TokenSecret TokenSecretReference `json:"tokenSecret,omitempty"`
27+
Languages []string `json:"languages,omitempty"`
28+
Name string `json:"name,omitempty"`
29+
// +kubebuilder:default:=dev
30+
Environment string `json:"environment,omitempty"`
31+
EncryptPayload bool `json:"encryptPayload,omitempty"`
32+
Project string `json:"project,omitempty"`
33+
IncludeVisualExperiments bool `json:"includeVisualExperiments,omitempty"`
34+
IncludeDraftExperiments bool `json:"includeDraftExperiments,omitempty"`
35+
IncludeExperimentNames bool `json:"includeExperimentNames,omitempty"`
36+
ID string `json:"id,omitempty"`
37+
TokenSecret *TokenSecretReference `json:"tokenSecret"`
3738
}
3839

3940
// GetID returns the client ID which is the resource name if not overwritten by spec.ID
@@ -51,16 +52,14 @@ func (c *GrowthbookClient) GetName() string {
5152
return c.Name
5253
}
5354

54-
return c.Spec.ID
55+
return c.Spec.Name
5556
}
5657

5758
// SecretReference is a named reference to a secret which contains user credentials
5859
type TokenSecretReference struct {
5960
// Name referrs to the name of the secret, must be located whithin the same namespace
60-
// +required
61-
Name string `json:"name,omitempty"`
61+
Name string `json:"name"`
6262

63-
// +optional
6463
// +kubebuilder:default:=token
6564
TokenField string `json:"tokenField,omitempty"`
6665
}

api/v1beta1/growthbookfeature_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ type GrowthbookFeatureSpec struct {
2929
Tags []string `json:"tags,omitempty"`
3030
DefaultValue string `json:"defaultValue,omitempty"`
3131
ValueType FeatureValueType `json:"valueType,omitempty"`
32-
// +kubebuilder:default:={{name: production, enabled: true}}
32+
// +kubebuilder:default:={{name: dev, enabled: true}}
3333
Environments []Environment `json:"environments,omitempty"`
3434
}
3535

api/v1beta1/growthbookinstance_types.go

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ type GrowthbookInstanceSpec struct {
2626
MongoDB GrowthbookInstanceMongoDB `json:"mongodb,omitempty"`
2727

2828
// Interval reconciliation
29-
// +optional
3029
Interval *metav1.Duration `json:"interval,omitempty"`
3130

31+
// Timeout while reconciling the instance
32+
// +kubebuilder:default:="5m"
33+
Timeout *metav1.Duration `json:"timeout,omitempty"`
34+
3235
// Suspend reconciliation
33-
// +optional
3436
Suspend bool `json:"suspend,omitempty"`
3537

3638
// ResourceSelector defines a selector to select Growthbook resources associated with this instance
@@ -43,22 +45,7 @@ type GrowthbookInstanceMongoDB struct {
4345
URI string `json:"uri,omitempty"`
4446

4547
// Secret is a secret refernece with the MongoDB credentials
46-
Secret *SecretReference `json:"rootSecret"`
47-
}
48-
49-
// SecretReference is a named reference to a secret which contains user credentials
50-
type SecretReference struct {
51-
// Name referrs to the name of the secret, must be located whithin the same namespace
52-
// +required
53-
Name string `json:"name"`
54-
55-
// +optional
56-
// +kubebuilder:default:=username
57-
UserField string `json:"userField"`
58-
59-
// +optional
60-
// +kubebuilder:default:=password
61-
PasswordField string `json:"passwordField"`
48+
Secret *SecretReference `json:"rootSecret,omitempty"`
6249
}
6350

6451
// GrowthbookInstanceStatus defines the observed state of GrowthbookInstance

api/v1beta1/growthbookorganization_types.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ limitations under the License.
1717
package v1beta1
1818

1919
import (
20+
"fmt"
21+
2022
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2123
)
2224

@@ -42,7 +44,7 @@ type GrowthbookOrganizationUser struct {
4244
// GetID returns the organization ID which is the resource name if not overwritten by spec.ID
4345
func (o *GrowthbookOrganization) GetID() string {
4446
if o.Spec.ID == "" {
45-
return o.Name
47+
return fmt.Sprintf("%s-%s", o.Name, o.Namespace)
4648
}
4749

4850
return o.Spec.ID
@@ -54,7 +56,7 @@ func (o *GrowthbookOrganization) GetName() string {
5456
return o.Name
5557
}
5658

57-
return o.Spec.ID
59+
return o.Spec.Name
5860
}
5961

6062
// +kubebuilder:object:root=true

api/v1beta1/growthbookuser_types.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ limitations under the License.
1717
package v1beta1
1818

1919
import (
20+
"fmt"
21+
2022
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2123
)
2224

@@ -31,21 +33,21 @@ type GrowthbookUserSpec struct {
3133
}
3234

3335
// GetID returns the organization ID which is the resource name if not overwritten by spec.ID
34-
func (o *GrowthbookUser) GetID() string {
35-
if o.Spec.ID == "" {
36-
return o.Name
36+
func (u *GrowthbookUser) GetID() string {
37+
if u.Spec.ID == "" {
38+
return fmt.Sprintf("%s-%s", u.Name, u.Namespace)
3739
}
3840

39-
return o.Spec.ID
41+
return u.Spec.ID
4042
}
4143

4244
// GetName returns the organization name which is the resource name if not overwritten by spec.Name
43-
func (o *GrowthbookUser) GetName() string {
44-
if o.Spec.Name == "" {
45-
return o.Name
45+
func (u *GrowthbookUser) GetName() string {
46+
if u.Spec.Name == "" {
47+
return u.Name
4648
}
4749

48-
return o.Spec.ID
50+
return u.Spec.Name
4951
}
5052

5153
// +kubebuilder:object:root=true

api/v1beta1/meta.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,17 @@ func setResourceCondition(resource conditionalResource, condition string, status
4747

4848
apimeta.SetStatusCondition(conditions, newCondition)
4949
}
50+
51+
// SecretReference is a named reference to a secret which contains user credentials
52+
type SecretReference struct {
53+
// Name referrs to the name of the secret, must be located whithin the same namespace
54+
Name string `json:"name"`
55+
56+
// +optional
57+
// +kubebuilder:default:=username
58+
UserField string `json:"userField,omitempty"`
59+
60+
// +optional
61+
// +kubebuilder:default:=password
62+
PasswordField string `json:"passwordField,omitempty"`
63+
}

0 commit comments

Comments
 (0)