Skip to content

Commit 90e42b7

Browse files
committed
CARRY: KCP example
1 parent a29f17f commit 90e42b7

File tree

13 files changed

+971
-2
lines changed

13 files changed

+971
-2
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@
2424
hack/tools/bin
2525

2626
junit-report.xml
27-
/artifacts
27+
/artifacts
28+
29+
examples/kcp/bin/*

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ modules: ## Runs go mod to ensure modules are up to date.
105105

106106
.PHONY: generate
107107
generate: $(CONTROLLER_GEN) ## Runs controller-gen for internal types for config file
108-
$(CONTROLLER_GEN) object paths="./pkg/config/v1alpha1/...;./examples/configfile/custom/v1alpha1/..."
108+
$(CONTROLLER_GEN) object paths="./pkg/config/v1alpha1/...;./examples/configfile/custom/v1alpha1/...;./examples/kcp/..."
109109

110110
## --------------------------------------
111111
## Cleanup / Verification

examples/builtins/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ func main() {
4242

4343
// Setup a Manager
4444
entryLog.Info("setting up manager")
45+
4546
mgr, err := manager.New(config.GetConfigOrDie(), manager.Options{})
4647
if err != nil {
4748
entryLog.Error(err, "unable to set up overall controller manager")

examples/kcp/Makefile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
LOCALBIN ?= $(shell pwd)/bin
3+
KCP ?= $(LOCALBIN)/kcp
4+
KCP_VERSION ?= 0.23.0
5+
6+
OS ?= $(shell go env GOOS )
7+
ARCH ?= $(shell go env GOARCH )
8+
9+
$(KCP): ## Download kcp locally if necessary.
10+
mkdir -p $(LOCALBIN)
11+
curl -L -s -o - https://github.com/kcp-dev/kcp/releases/download/v$(KCP_VERSION)/kcp_$(KCP_VERSION)_$(OS)_$(ARCH).tar.gz | tar --directory $(LOCALBIN)/../ -xvzf - bin/kcp
12+
touch $(KCP) # we download an "old" file, so make will re-download to refresh it unless we make it newer than the owning dir
13+
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
Copyright 2024.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Package v1alpha1 contains API Schema definitions for the data v1alpha1 API group
18+
// +kubebuilder:object:generate=true
19+
// +groupName=data.my.domain
20+
package v1alpha1
21+
22+
import (
23+
"k8s.io/apimachinery/pkg/runtime/schema"
24+
"sigs.k8s.io/controller-runtime/pkg/scheme"
25+
)
26+
27+
var (
28+
// GroupVersion is group version used to register these objects
29+
GroupVersion = schema.GroupVersion{Group: "data.my.domain", Version: "v1alpha1"}
30+
31+
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
32+
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
33+
34+
// AddToScheme adds the types in this group-version to the given scheme.
35+
AddToScheme = SchemeBuilder.AddToScheme
36+
)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
Copyright 2024.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1alpha1
18+
19+
import (
20+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
)
22+
23+
// WidgetSpec defines the desired state of Widget
24+
type WidgetSpec struct {
25+
Foo string `json:"foo,omitempty"`
26+
}
27+
28+
// WidgetStatus defines the observed state of Widget
29+
type WidgetStatus struct {
30+
Total int `json:"total,omitempty"`
31+
}
32+
33+
// +kubebuilder:object:root=true
34+
// +kubebuilder:subresource:status
35+
36+
// Widget is the Schema for the widgets API
37+
type Widget struct {
38+
metav1.TypeMeta `json:",inline"`
39+
metav1.ObjectMeta `json:"metadata,omitempty"`
40+
41+
Spec WidgetSpec `json:"spec,omitempty"`
42+
Status WidgetStatus `json:"status,omitempty"`
43+
}
44+
45+
// +kubebuilder:object:root=true
46+
47+
// WidgetList contains a list of Widget
48+
type WidgetList struct {
49+
metav1.TypeMeta `json:",inline"`
50+
metav1.ListMeta `json:"metadata,omitempty"`
51+
Items []Widget `json:"items"`
52+
}
53+
54+
func init() {
55+
SchemeBuilder.Register(&Widget{}, &WidgetList{})
56+
}

examples/kcp/api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 98 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
Copyright 2024 The KCP Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package controllers
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"time"
23+
24+
corev1 "k8s.io/api/core/v1"
25+
apierrors "k8s.io/apimachinery/pkg/api/errors"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/apimachinery/pkg/types"
28+
29+
"github.com/kcp-dev/logicalcluster/v3"
30+
31+
ctrl "sigs.k8s.io/controller-runtime"
32+
"sigs.k8s.io/controller-runtime/pkg/client"
33+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
34+
"sigs.k8s.io/controller-runtime/pkg/log"
35+
)
36+
37+
type ConfigMapReconciler struct {
38+
client.Client
39+
}
40+
41+
func (r *ConfigMapReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
42+
log := log.FromContext(ctx).WithValues("cluster", req.ClusterName)
43+
44+
ctx = logicalcluster.WithCluster(ctx, logicalcluster.New(req.ClusterName))
45+
46+
// Test get
47+
var configMap corev1.ConfigMap
48+
49+
if err := r.Get(ctx, req.NamespacedName, &configMap); err != nil {
50+
log.Error(err, "unable to get configmap")
51+
return ctrl.Result{}, nil
52+
}
53+
54+
log.Info("Get: retrieved configMap")
55+
labels := configMap.Labels
56+
57+
if labels["name"] != "" {
58+
response := fmt.Sprintf("hello-%s", labels["name"])
59+
60+
if labels["response"] != response {
61+
labels["response"] = response
62+
63+
// Test Update
64+
if err := r.Update(ctx, &configMap); err != nil {
65+
return ctrl.Result{}, err
66+
}
67+
log.Info("Update: updated configMap")
68+
return ctrl.Result{}, nil
69+
}
70+
}
71+
72+
// Test list
73+
var configMapList corev1.ConfigMapList
74+
if err := r.List(ctx, &configMapList); err != nil {
75+
log.Error(err, "unable to list configmaps")
76+
return ctrl.Result{}, nil
77+
}
78+
log.Info("List: got", "itemCount", len(configMapList.Items))
79+
found := false
80+
for _, cm := range configMapList.Items {
81+
if !logicalcluster.From(&cm).Empty() {
82+
log.Info("List: got", "clusterName", logicalcluster.From(&cm).String(), "namespace", cm.Namespace, "name", cm.Name)
83+
} else {
84+
if cm.Name == configMap.Name && cm.Namespace == configMap.Namespace {
85+
if found {
86+
return ctrl.Result{}, fmt.Errorf("there should be listed only one configmap with the given name '%s' for the given namespace '%s' when the clusterName is not available", cm.Name, cm.Namespace)
87+
}
88+
found = true
89+
log.Info("Found in listed configmaps", "namespace", cm.Namespace, "name", cm.Name)
90+
}
91+
}
92+
}
93+
94+
// If the configmap has a namespace field, create the corresponding namespace
95+
nsName, exists := configMap.Data["namespace"]
96+
if exists {
97+
var namespace corev1.Namespace
98+
nsKey := types.NamespacedName{Name: nsName}
99+
100+
if err := r.Get(ctx, nsKey, &namespace); err != nil {
101+
if !apierrors.IsNotFound(err) {
102+
log.Error(err, "unable to get namespace")
103+
return ctrl.Result{}, err
104+
}
105+
106+
// Need to create ns
107+
namespace.SetName(nsName)
108+
if err = r.Create(ctx, &namespace); err != nil {
109+
log.Error(err, "unable to create namespace")
110+
return ctrl.Result{}, err
111+
}
112+
log.Info("Create: created ", "namespace", nsName)
113+
return ctrl.Result{RequeueAfter: time.Second * 5}, nil
114+
}
115+
log.Info("Exists", "namespace", nsName)
116+
}
117+
118+
// If the configmap has a secretData field, create a secret in the same namespace
119+
// If the secret already exists but is out of sync, it will be non-destructively patched
120+
secretData, exists := configMap.Data["secretData"]
121+
if exists {
122+
var secret corev1.Secret
123+
124+
secret.SetName(configMap.GetName())
125+
secret.SetNamespace(configMap.GetNamespace())
126+
secret.SetOwnerReferences([]metav1.OwnerReference{metav1.OwnerReference{
127+
Name: configMap.GetName(),
128+
UID: configMap.GetUID(),
129+
APIVersion: "v1",
130+
Kind: "ConfigMap",
131+
Controller: func() *bool { x := true; return &x }(),
132+
}})
133+
secret.Data = map[string][]byte{"dataFromCM": []byte(secretData)}
134+
135+
operationResult, err := controllerutil.CreateOrPatch(ctx, r, &secret, func() error {
136+
secret.Data["dataFromCM"] = []byte(secretData)
137+
return nil
138+
})
139+
if err != nil {
140+
log.Error(err, "unable to create or patch secret")
141+
return ctrl.Result{}, err
142+
}
143+
log.Info(string(operationResult), "secret", secret.GetName())
144+
}
145+
146+
return ctrl.Result{}, nil
147+
}
148+
149+
func (r *ConfigMapReconciler) SetupWithManager(mgr ctrl.Manager) error {
150+
return ctrl.NewControllerManagedBy(mgr).
151+
For(&corev1.ConfigMap{}).
152+
Owns(&corev1.Secret{}).
153+
Complete(r)
154+
}

0 commit comments

Comments
 (0)