Skip to content

Commit 235f6d5

Browse files
saratahabigkevmcdfoot
authored
Watch gitopscluster CRs (#11)
* Watch GitOps clusters * Configure git for private modules * Configure docker for private modules * Add environment variables to docker build step * Update readme Co-authored-by: Kevin McDermott <[email protected]> Co-authored-by: Simon Howe <[email protected]>
1 parent fec0dbb commit 235f6d5

14 files changed

+209
-245
lines changed

.github/workflows/build.yaml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ name: "Build and push docker container"
33
on:
44
pull_request:
55
workflow_dispatch:
6-
6+
7+
env:
8+
GOPRIVATE: github.com/weaveworks/cluster-controller
9+
710
jobs:
811
cluster-bootstrap-controller:
912
runs-on: ubuntu-latest
@@ -17,7 +20,15 @@ jobs:
1720
with:
1821
fetch-depth: 0
1922
ref: ${{ github.event.pull_request.head.sha }}
23+
- name: Configure git for private modules
24+
env:
25+
GITHUB_BUILD_USERNAME: ${{ secrets.BUILD_BOT_USER }}
26+
GITHUB_BUILD_TOKEN: ${{ secrets.BUILD_BOT_PERSONAL_ACCESS_TOKEN }}
27+
run: git config --global url."https://${GITHUB_BUILD_USERNAME}:${GITHUB_BUILD_TOKEN}@github.com".insteadOf "https://github.com"
2028
- name: Build docker image
29+
env:
30+
GITHUB_BUILD_USERNAME: ${{ secrets.BUILD_BOT_USER }}
31+
GITHUB_BUILD_TOKEN: ${{ secrets.BUILD_BOT_PERSONAL_ACCESS_TOKEN }}
2132
run: |
2233
make docker-build
2334
- name: Login to Docker Hub

Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Build the manager binary
22
FROM golang:1.17 as builder
33

4+
ARG GITHUB_BUILD_USERNAME
5+
ARG GITHUB_BUILD_TOKEN
6+
RUN git config --global url."https://${GITHUB_BUILD_USERNAME}:${GITHUB_BUILD_TOKEN}@github.com".insteadOf "https://github.com"
7+
48
WORKDIR /workspace
59
# Copy the Go Modules manifests
610
COPY go.mod go.mod

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,11 @@ run: manifests generate fmt vet ## Run a controller from your host.
100100
go run ./main.go
101101

102102
docker-build: test ## Build docker image with the manager.
103-
docker build -t ${IMG} .
103+
docker build \
104+
--build-arg=GITHUB_BUILD_TOKEN=$(GITHUB_BUILD_TOKEN) \
105+
--build-arg=GITHUB_BUILD_USERNAME=$(GITHUB_BUILD_USERNAME) \
106+
-t ${IMG} . \
107+
104108

105109
docker-push: ## Push docker image with the manager.
106110
docker push ${IMG}

README.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# cluster-bootstrap-controller
22

3-
This is a controller that tracks [CAPI](https://github.com/kubernetes-sigs/cluster-api) [Cluster](https://cluster-api.sigs.k8s.io/developer/architecture/controllers/cluster.html) objects.
3+
This is a controller that tracks [GitopsCluster] objects.
44

55
It provides a CR for a `ClusterBootstrapConfig` which provides a [Job](https://kubernetes.io/docs/concepts/workloads/controllers/job/) template.
66

7-
When a CAPI Cluster is "provisioned" a Job is created from the template, the template can access multiple fields.
7+
When a GitopsCluster is "Ready" a Job is created from the template, the template can access multiple fields.
88

99
```yaml
1010
apiVersion: capi.weave.works/v1alpha1
@@ -34,7 +34,7 @@ spec:
3434
secretName: '{{ .ObjectMeta.Name }}-kubeconfig'
3535
```
3636
37-
This is using Go [templating](https://pkg.go.dev/text/template) and the `Cluster` object is provided as the context, this means that expressions like `{{ .ObjectMeta.Name }}` will get the _name_ of the Cluster that has transitioned to "provisioned".
37+
This is using Go [templating](https://pkg.go.dev/text/template) and the `GitopsCluster` object is provided as the context, this means that expressions like `{{ .ObjectMeta.Name }}` will get the _name_ of the GitopsCluster that has transitioned to "Ready".
3838

3939
## Annotations
4040

@@ -55,8 +55,6 @@ e.g.
5555

5656
## Installation
5757

58-
You will need to have CAPI installed first, see the [CAPI Quick Start](https://cluster-api.sigs.k8s.io/user/quick-start.html).
59-
6058
Release files are available https://github.com/weaveworks/cluster-bootstrap-controller/releases
6159

6260
You can install these e.g.

controllers/bootstrap.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import (
44
"context"
55
"fmt"
66

7+
gitopsv1alpha1 "github.com/weaveworks/cluster-controller/api/v1alpha1"
78
batchv1 "k8s.io/api/batch/v1"
89
corev1 "k8s.io/api/core/v1"
910
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10-
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
1111
"sigs.k8s.io/controller-runtime/pkg/client"
1212
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
1313

@@ -16,7 +16,7 @@ import (
1616
)
1717

1818
// bootstrapCluster applies the jobs from a ClusterBootstrapConfig to a cluster.
19-
func bootstrapClusterWithConfig(ctx context.Context, logger logr.Logger, c client.Client, cl *clusterv1.Cluster, bc *capiv1alpha1.ClusterBootstrapConfig) error {
19+
func bootstrapClusterWithConfig(ctx context.Context, logger logr.Logger, c client.Client, cl *gitopsv1alpha1.GitopsCluster, bc *capiv1alpha1.ClusterBootstrapConfig) error {
2020
job, err := renderTemplates(cl, jobFromTemplate(cl, bc.Spec.Template))
2121
if err != nil {
2222
return fmt.Errorf("failed to render job from template: %w", err)
@@ -31,7 +31,7 @@ func bootstrapClusterWithConfig(ctx context.Context, logger logr.Logger, c clien
3131
return nil
3232
}
3333

34-
func jobFromTemplate(cl *clusterv1.Cluster, jt capiv1alpha1.JobTemplate) *batchv1.Job {
34+
func jobFromTemplate(cl *gitopsv1alpha1.GitopsCluster, jt capiv1alpha1.JobTemplate) *batchv1.Job {
3535
return &batchv1.Job{
3636
ObjectMeta: metav1.ObjectMeta{
3737
GenerateName: jt.GenerateName,

controllers/bootstrap_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import (
77
"github.com/go-logr/logr"
88
"github.com/google/go-cmp/cmp"
99
"github.com/google/go-cmp/cmp/cmpopts"
10+
gitopsv1alpha1 "github.com/weaveworks/cluster-controller/api/v1alpha1"
1011
batchv1 "k8s.io/api/batch/v1"
1112
corev1 "k8s.io/api/core/v1"
1213
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1314
"k8s.io/apimachinery/pkg/runtime"
1415
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
1516
ptrutils "k8s.io/utils/pointer"
16-
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
1717
"sigs.k8s.io/controller-runtime/pkg/client"
1818
"sigs.k8s.io/controller-runtime/pkg/client/fake"
1919

@@ -76,8 +76,8 @@ func Test_bootstrapClusterWithConfig_sets_owner(t *testing.T) {
7676

7777
want := []metav1.OwnerReference{
7878
{
79-
APIVersion: "cluster.x-k8s.io/v1beta1",
80-
Kind: "Cluster",
79+
APIVersion: "gitops.weave.works/v1alpha1",
80+
Kind: "GitopsCluster",
8181
Name: testClusterName,
8282
},
8383
}
@@ -90,7 +90,7 @@ func Test_bootstrapClusterWithConfig_fail_to_create_job(t *testing.T) {
9090
// This is a hacky test for making Create fail because of an unregistered
9191
// type.
9292
s := runtime.NewScheme()
93-
test.AssertNoError(t, clusterv1.AddToScheme(s))
93+
test.AssertNoError(t, gitopsv1alpha1.AddToScheme(s))
9494
tc := fake.NewClientBuilder().WithScheme(s).Build()
9595
bc := makeTestClusterBootstrapConfig()
9696
cl := makeTestCluster()
@@ -120,13 +120,13 @@ func makeTestPodSpecWithVolumes(volumes ...corev1.Volume) corev1.PodSpec {
120120
}
121121
}
122122

123-
func makeTestCluster(opts ...func(*clusterv1.Cluster)) *clusterv1.Cluster {
124-
c := &clusterv1.Cluster{
123+
func makeTestCluster(opts ...func(*gitopsv1alpha1.GitopsCluster)) *gitopsv1alpha1.GitopsCluster {
124+
c := &gitopsv1alpha1.GitopsCluster{
125125
ObjectMeta: metav1.ObjectMeta{
126126
Name: testClusterName,
127127
Namespace: testNamespace,
128128
},
129-
Spec: clusterv1.ClusterSpec{},
129+
Spec: gitopsv1alpha1.GitopsClusterSpec{},
130130
}
131131
for _, o := range opts {
132132
o(c)
@@ -188,8 +188,8 @@ func makeTestClientAndScheme(t *testing.T, objs ...runtime.Object) (*runtime.Sch
188188
s := runtime.NewScheme()
189189
test.AssertNoError(t, clientgoscheme.AddToScheme(s))
190190
test.AssertNoError(t, capiv1alpha1.AddToScheme(s))
191-
test.AssertNoError(t, clusterv1.AddToScheme(s))
192191
test.AssertNoError(t, batchv1.AddToScheme(s))
192+
test.AssertNoError(t, gitopsv1alpha1.AddToScheme(s))
193193
return s, fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
194194
}
195195

controllers/clusterbootstrapconfig_controller.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ import (
2121
"encoding/json"
2222
"fmt"
2323

24+
gitopsv1alpha1 "github.com/weaveworks/cluster-controller/api/v1alpha1"
2425
corev1 "k8s.io/api/core/v1"
2526
apierrors "k8s.io/apimachinery/pkg/api/errors"
2627
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2728
"k8s.io/apimachinery/pkg/labels"
2829
"k8s.io/apimachinery/pkg/runtime"
2930
"k8s.io/apimachinery/pkg/types"
3031
"k8s.io/client-go/tools/clientcmd"
31-
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
3232
ctrl "sigs.k8s.io/controller-runtime"
3333
"sigs.k8s.io/controller-runtime/pkg/client"
3434
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
@@ -131,13 +131,13 @@ func (r *ClusterBootstrapConfigReconciler) SetupWithManager(mgr ctrl.Manager) er
131131
return ctrl.NewControllerManagedBy(mgr).
132132
For(&capiv1alpha1.ClusterBootstrapConfig{}).
133133
Watches(
134-
&source.Kind{Type: &clusterv1.Cluster{}},
134+
&source.Kind{Type: &gitopsv1alpha1.GitopsCluster{}},
135135
handler.EnqueueRequestsFromMapFunc(r.clusterToClusterBootstrapConfig),
136136
).
137137
Complete(r)
138138
}
139139

140-
func (r *ClusterBootstrapConfigReconciler) getClustersBySelector(ctx context.Context, ns string, ls metav1.LabelSelector) ([]*clusterv1.Cluster, error) {
140+
func (r *ClusterBootstrapConfigReconciler) getClustersBySelector(ctx context.Context, ns string, ls metav1.LabelSelector) ([]*gitopsv1alpha1.GitopsCluster, error) {
141141
logger := ctrl.LoggerFrom(ctx)
142142
selector, err := metav1.LabelSelectorAsSelector(&ls)
143143
if err != nil {
@@ -148,17 +148,24 @@ func (r *ClusterBootstrapConfigReconciler) getClustersBySelector(ctx context.Con
148148
logger.Info("empty ClusterBootstrapConfig selector: no clusters are selected")
149149
return nil, nil
150150
}
151-
clusterList := &clusterv1.ClusterList{}
151+
clusterList := &gitopsv1alpha1.GitopsClusterList{}
152152
if err := r.Client.List(ctx, clusterList, client.InNamespace(ns), client.MatchingLabelsSelector{Selector: selector}); err != nil {
153153
return nil, fmt.Errorf("failed to list clusters: %w", err)
154154
}
155155

156156
logger.Info("identified clusters with selector", "selector", selector, "count", len(clusterList.Items))
157-
clusters := []*clusterv1.Cluster{}
157+
clusters := []*gitopsv1alpha1.GitopsCluster{}
158158
for i := range clusterList.Items {
159159
c := &clusterList.Items[i]
160-
if clusterv1.ClusterPhase(c.Status.Phase) != clusterv1.ClusterPhaseProvisioned {
161-
logger.Info("cluster discarded - not provisioned", "phase", c.Status.Phase)
160+
161+
clusterFound := false
162+
for _, condition := range c.Status.Conditions {
163+
if condition.Type == "Ready" && condition.Status == metav1.ConditionTrue {
164+
clusterFound = true
165+
}
166+
}
167+
if !clusterFound {
168+
logger.Info("cluster discarded - not provisioned", "phase", c.Status)
162169
continue
163170
}
164171
if metav1.HasAnnotation(c.ObjectMeta, capiv1alpha1.BootstrappedAnnotation) {
@@ -175,7 +182,7 @@ func (r *ClusterBootstrapConfigReconciler) getClustersBySelector(ctx context.Con
175182
// ClusterBootstrapConfig.
176183
func (r *ClusterBootstrapConfigReconciler) clusterToClusterBootstrapConfig(o client.Object) []ctrl.Request {
177184
result := []ctrl.Request{}
178-
cluster, ok := o.(*clusterv1.Cluster)
185+
cluster, ok := o.(*gitopsv1alpha1.GitopsCluster)
179186
if !ok {
180187
panic(fmt.Sprintf("Expected a Cluster but got a %T", o))
181188
}

controllers/clusterbootstrapconfig_controller_test.go

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ import (
1515
"k8s.io/apimachinery/pkg/types"
1616
"k8s.io/client-go/tools/clientcmd"
1717
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
18-
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
1918
ctrl "sigs.k8s.io/controller-runtime"
2019
"sigs.k8s.io/controller-runtime/pkg/client"
2120

2221
capiv1alpha1 "github.com/weaveworks/cluster-bootstrap-controller/api/v1alpha1"
2322
"github.com/weaveworks/cluster-bootstrap-controller/test"
23+
gitopsv1alpha1 "github.com/weaveworks/cluster-controller/api/v1alpha1"
2424
)
2525

2626
const testWaitDuration = time.Second * 55
@@ -35,9 +35,9 @@ func TestReconcile_when_cluster_not_ready(t *testing.T) {
3535
"node-role.kubernetes.io/control-plane": "",
3636
}, corev1.NodeCondition{Type: "Ready", Status: "False", LastHeartbeatTime: metav1.Now(), LastTransitionTime: metav1.Now(), Reason: "KubeletReady", Message: "kubelet is posting ready status"})
3737

38-
cl := makeTestCluster(func(c *clusterv1.Cluster) {
38+
cl := makeTestCluster(func(c *gitopsv1alpha1.GitopsCluster) {
3939
c.ObjectMeta.Labels = bc.Spec.ClusterSelector.MatchLabels
40-
c.Status.Phase = string(clusterv1.ClusterPhaseProvisioned)
40+
c.Status.Conditions = append(c.Status.Conditions, makeReadyCondition())
4141
})
4242
secret := makeTestSecret(types.NamespacedName{
4343
Name: cl.GetName() + "-kubeconfig",
@@ -69,9 +69,9 @@ func TestReconcile_when_cluster_secret_not_available(t *testing.T) {
6969
bc := makeTestClusterBootstrapConfig(func(c *capiv1alpha1.ClusterBootstrapConfig) {
7070
c.Spec.RequireClusterReady = true
7171
})
72-
cl := makeTestCluster(func(c *clusterv1.Cluster) {
72+
cl := makeTestCluster(func(c *gitopsv1alpha1.GitopsCluster) {
7373
c.ObjectMeta.Labels = bc.Spec.ClusterSelector.MatchLabels
74-
c.Status.Phase = string(clusterv1.ClusterPhaseProvisioned)
74+
c.Status.Conditions = append(c.Status.Conditions, makeReadyCondition())
7575
})
7676
reconciler := makeTestReconciler(t, bc, cl)
7777

@@ -104,9 +104,9 @@ func TestReconcile_when_cluster_ready(t *testing.T) {
104104
}, corev1.NodeCondition{
105105
Type: "Ready", Status: "True", LastHeartbeatTime: metav1.Now(), LastTransitionTime: metav1.Now(), Reason: "KubeletReady", Message: "kubelet is posting ready status"})
106106

107-
cl := makeTestCluster(func(c *clusterv1.Cluster) {
107+
cl := makeTestCluster(func(c *gitopsv1alpha1.GitopsCluster) {
108108
c.ObjectMeta.Labels = bc.Spec.ClusterSelector.MatchLabels
109-
c.Status.Phase = string(clusterv1.ClusterPhaseProvisioned)
109+
c.Status.Conditions = append(c.Status.Conditions, makeReadyCondition())
110110
})
111111
secret := makeTestSecret(types.NamespacedName{
112112
Name: cl.GetName() + "-kubeconfig",
@@ -142,11 +142,11 @@ func TestReconcile_when_cluster_no_matching_labels(t *testing.T) {
142142
bc := makeTestClusterBootstrapConfig(func(c *capiv1alpha1.ClusterBootstrapConfig) {
143143
c.Spec.RequireClusterReady = true
144144
})
145-
cl := makeTestCluster(func(c *clusterv1.Cluster) {
145+
cl := makeTestCluster(func(c *gitopsv1alpha1.GitopsCluster) {
146146
c.ObjectMeta.Labels = map[string]string{
147147
"will-not-match": "",
148148
}
149-
c.Status.Phase = string(clusterv1.ClusterPhaseProvisioned)
149+
c.Status.Conditions = append(c.Status.Conditions, makeReadyCondition())
150150
})
151151
// This cheats by using the local client as the remote client to simplify
152152
// getting the value from the remote client.
@@ -178,11 +178,11 @@ func TestReconcile_when_empty_label_selector(t *testing.T) {
178178
}
179179

180180
})
181-
cl := makeTestCluster(func(c *clusterv1.Cluster) {
181+
cl := makeTestCluster(func(c *gitopsv1alpha1.GitopsCluster) {
182182
c.ObjectMeta.Labels = map[string]string{
183183
"will-not-match": "",
184184
}
185-
c.Status.Phase = string(clusterv1.ClusterPhaseProvisioned)
185+
c.Status.Conditions = append(c.Status.Conditions, makeReadyCondition())
186186
})
187187
// This cheats by using the local client as the remote client to simplify
188188
// getting the value from the remote client.
@@ -214,9 +214,9 @@ func TestReconcile_when_cluster_ready_and_old_label(t *testing.T) {
214214
LastTransitionTime: metav1.Now(), Reason: "KubeletReady",
215215
Message: "kubelet is posting ready status"})
216216

217-
cl := makeTestCluster(func(c *clusterv1.Cluster) {
217+
cl := makeTestCluster(func(c *gitopsv1alpha1.GitopsCluster) {
218218
c.ObjectMeta.Labels = bc.Spec.ClusterSelector.MatchLabels
219-
c.Status.Phase = string(clusterv1.ClusterPhaseProvisioned)
219+
c.Status.Conditions = append(c.Status.Conditions, makeReadyCondition())
220220
})
221221
secret := makeTestSecret(types.NamespacedName{
222222
Name: cl.GetName() + "-kubeconfig",
@@ -313,6 +313,13 @@ func Test_kubeConfigBytesToClient_with_invalidkubeconfig(t *testing.T) {
313313
}
314314
}
315315

316+
func makeReadyCondition() metav1.Condition {
317+
return metav1.Condition{
318+
Type: "Ready",
319+
Status: metav1.ConditionTrue,
320+
}
321+
}
322+
316323
func makeTestReconciler(t *testing.T, objs ...runtime.Object) *ClusterBootstrapConfigReconciler {
317324
s, tc := makeTestClientAndScheme(t, objs...)
318325
return NewClusterBootstrapConfigReconciler(tc, s)

controllers/templating.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import (
55
"fmt"
66
"text/template"
77

8+
gitopsv1alpha1 "github.com/weaveworks/cluster-controller/api/v1alpha1"
89
batchv1 "k8s.io/api/batch/v1"
9-
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
1010
"sigs.k8s.io/yaml"
1111
)
1212

@@ -16,14 +16,14 @@ func lookup(m map[string]string) func(s string) string {
1616
}
1717
}
1818

19-
func makeFuncMap(cl *clusterv1.Cluster) template.FuncMap {
19+
func makeFuncMap(cl *gitopsv1alpha1.GitopsCluster) template.FuncMap {
2020
return template.FuncMap{
2121
"annotation": lookup(cl.ObjectMeta.GetAnnotations()),
2222
"label": lookup(cl.ObjectMeta.GetLabels()),
2323
}
2424
}
2525

26-
func renderTemplates(cl *clusterv1.Cluster, j *batchv1.Job) (*batchv1.Job, error) {
26+
func renderTemplates(cl *gitopsv1alpha1.GitopsCluster, j *batchv1.Job) (*batchv1.Job, error) {
2727
raw, err := yaml.Marshal(j)
2828
if err != nil {
2929
return nil, fmt.Errorf("failed to parse job as YAML: %w", err)

0 commit comments

Comments
 (0)