Skip to content

Commit 997b353

Browse files
authored
Merge pull request #55 from embik/kubeconfigs-contexts
Create base/default contexts in kubeconfigs and extend samples
2 parents 1eb8595 + 8a11289 commit 997b353

File tree

8 files changed

+186
-60
lines changed

8 files changed

+186
-60
lines changed

config/samples/v1alpha1_frontproxy.yaml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,18 @@ spec:
99
rootShard:
1010
ref:
1111
name: shard-sample
12-
externalHostname: kcp.example.com
12+
serviceTemplate:
13+
spec:
14+
# hard code a specific cluster IP, e.g. for a kind setup.
15+
clusterIP: 10.96.100.100
16+
certificateTemplates:
17+
server:
18+
spec:
19+
dnsNames:
20+
# add localhost to the certificate.
21+
- localhost
22+
ipAddresses:
23+
# add localhost IPs to the server certificate.
24+
# this allows easy port-forward access.
25+
- 127.0.0.1
26+
- 127.0.0.2

config/samples/v1alpha1_kubeconfig.yaml renamed to config/samples/v1alpha1_kubeconfig_frontproxy.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ metadata:
44
labels:
55
app.kubernetes.io/name: kcp-operator
66
app.kubernetes.io/managed-by: kustomize
7-
name: kubeconfig-sample
7+
name: kubeconfig-kcp-admin
88
spec:
9-
username: user@kcp.io
9+
username: kcp-admin
1010
groups:
11-
- kcp-users
11+
- system:kcp:admin
1212
validity: 8766h
1313
secretRef:
1414
name: sample-kubeconfig
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
apiVersion: operator.kcp.io/v1alpha1
2+
kind: Kubeconfig
3+
metadata:
4+
labels:
5+
app.kubernetes.io/name: kcp-operator
6+
app.kubernetes.io/managed-by: kustomize
7+
name: kubeconfig-shard-root-admin
8+
spec:
9+
username: shard-root-admin
10+
groups:
11+
- system:kcp:admin
12+
validity: 8766h
13+
secretRef:
14+
name: kubeconfig-shard-root-admin
15+
target:
16+
rootShardRef:
17+
name: shard-sample
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
apiVersion: operator.kcp.io/v1alpha1
2+
kind: Kubeconfig
3+
metadata:
4+
labels:
5+
app.kubernetes.io/name: kcp-operator
6+
app.kubernetes.io/managed-by: kustomize
7+
name: kubeconfig-shard-secondary-admin
8+
spec:
9+
username: shard-root-admin
10+
groups:
11+
- system:kcp:admin
12+
validity: 8766h
13+
secretRef:
14+
name: kubeconfig-shard-secondary-admin
15+
target:
16+
shardRef:
17+
name: secondary-shard

config/samples/v1alpha1_rootshard.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,12 @@ spec:
2020
etcd:
2121
endpoints:
2222
- http://etcd.default.svc.cluster.local:2379
23+
deploymentTemplate:
24+
spec:
25+
template:
26+
spec:
27+
hostAliases:
28+
# add a hardcoded DNS override to the same IP as in v1alpha1_frontproxy.yaml.
29+
- ip: "10.96.100.100"
30+
hostnames:
31+
- "example.operator.kcp.io"

config/samples/v1alpha1_shard.yaml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ metadata:
44
labels:
55
app.kubernetes.io/name: kcp-operator
66
app.kubernetes.io/managed-by: kustomize
7-
name: shard-sample
7+
name: secondary-shard
88
spec:
9-
# TODO(user): Add fields here
9+
etcd:
10+
endpoints:
11+
- http://etcd-shard.default.svc.cluster.local:2379
12+
rootShard:
13+
ref:
14+
name: shard-sample

internal/controller/kubeconfig_controller.go

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ import (
2020
"context"
2121
"errors"
2222
"fmt"
23-
"net/url"
2423
"time"
2524

2625
certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
2726
certmanagermetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
2827
k8creconciling "k8c.io/reconciler/pkg/reconciling"
2928

3029
corev1 "k8s.io/api/core/v1"
30+
apierrors "k8s.io/apimachinery/pkg/api/errors"
3131
"k8s.io/apimachinery/pkg/runtime"
3232
"k8s.io/apimachinery/pkg/types"
3333
ctrl "sigs.k8s.io/controller-runtime"
@@ -70,45 +70,46 @@ func (r *KubeconfigReconciler) Reconcile(ctx context.Context, req ctrl.Request)
7070

7171
var kc operatorv1alpha1.Kubeconfig
7272
if err := r.Get(ctx, req.NamespacedName, &kc); err != nil {
73+
// object has been deleted.
74+
if apierrors.IsNotFound(err) {
75+
return ctrl.Result{}, nil
76+
}
7377
return ctrl.Result{}, err
7478
}
7579

80+
rootShard := &operatorv1alpha1.RootShard{}
81+
shard := &operatorv1alpha1.Shard{}
82+
7683
var (
77-
clientCertIssuer, serverCA, serverURL, serverName string
84+
clientCertIssuer string
85+
serverCA string
7886
)
7987

8088
switch {
8189
case kc.Spec.Target.RootShardRef != nil:
82-
var rootShard operatorv1alpha1.RootShard
83-
if err := r.Get(ctx, types.NamespacedName{Name: kc.Spec.Target.RootShardRef.Name, Namespace: req.Namespace}, &rootShard); err != nil {
90+
if err := r.Get(ctx, types.NamespacedName{Name: kc.Spec.Target.RootShardRef.Name, Namespace: req.Namespace}, rootShard); err != nil {
8491
return ctrl.Result{}, fmt.Errorf("failed to get RootShard: %w", err)
8592
}
8693

87-
clientCertIssuer = resources.GetRootShardCAName(&rootShard, operatorv1alpha1.ClientCA)
88-
serverCA = resources.GetRootShardCAName(&rootShard, operatorv1alpha1.ServerCA)
89-
serverURL = resources.GetRootShardBaseURL(&rootShard)
90-
serverName = rootShard.Name
94+
clientCertIssuer = resources.GetRootShardCAName(rootShard, operatorv1alpha1.ClientCA)
95+
serverCA = resources.GetRootShardCAName(rootShard, operatorv1alpha1.ServerCA)
9196

9297
case kc.Spec.Target.ShardRef != nil:
93-
var shard operatorv1alpha1.Shard
94-
if err := r.Get(ctx, types.NamespacedName{Name: kc.Spec.Target.ShardRef.Name, Namespace: req.Namespace}, &shard); err != nil {
98+
if err := r.Get(ctx, types.NamespacedName{Name: kc.Spec.Target.ShardRef.Name, Namespace: req.Namespace}, shard); err != nil {
9599
return ctrl.Result{}, fmt.Errorf("failed to get Shard: %w", err)
96100
}
97101

98102
ref := shard.Spec.RootShard.Reference
99103
if ref == nil || ref.Name == "" {
100104
return ctrl.Result{}, errors.New("the Shard does not reference a (valid) RootShard")
101105
}
102-
var rootShard operatorv1alpha1.RootShard
103-
if err := r.Get(ctx, types.NamespacedName{Name: ref.Name, Namespace: req.Namespace}, &rootShard); err != nil {
106+
if err := r.Get(ctx, types.NamespacedName{Name: ref.Name, Namespace: req.Namespace}, rootShard); err != nil {
104107
return ctrl.Result{}, fmt.Errorf("failed to get RootShard: %w", err)
105108
}
106109

107110
// The client CA is shared among all shards and owned by the root shard.
108-
clientCertIssuer = resources.GetRootShardCAName(&rootShard, operatorv1alpha1.ClientCA)
109-
serverCA = resources.GetRootShardCAName(&rootShard, operatorv1alpha1.ServerCA)
110-
serverURL = resources.GetShardBaseURL(&shard)
111-
serverName = shard.Name
111+
clientCertIssuer = resources.GetRootShardCAName(rootShard, operatorv1alpha1.ClientCA)
112+
serverCA = resources.GetRootShardCAName(rootShard, operatorv1alpha1.ServerCA)
112113

113114
case kc.Spec.Target.FrontProxyRef != nil:
114115
var frontProxy operatorv1alpha1.FrontProxy
@@ -120,15 +121,12 @@ func (r *KubeconfigReconciler) Reconcile(ctx context.Context, req ctrl.Request)
120121
if ref == nil || ref.Name == "" {
121122
return ctrl.Result{}, errors.New("the FrontProxy does not reference a (valid) RootShard")
122123
}
123-
var rootShard operatorv1alpha1.RootShard
124-
if err := r.Get(ctx, types.NamespacedName{Name: frontProxy.Spec.RootShard.Reference.Name, Namespace: req.Namespace}, &rootShard); err != nil {
124+
if err := r.Get(ctx, types.NamespacedName{Name: frontProxy.Spec.RootShard.Reference.Name, Namespace: req.Namespace}, rootShard); err != nil {
125125
return ctrl.Result{}, fmt.Errorf("failed to get RootShard: %w", err)
126126
}
127127

128-
clientCertIssuer = resources.GetRootShardCAName(&rootShard, operatorv1alpha1.FrontProxyClientCA)
129-
serverCA = resources.GetRootShardCAName(&rootShard, operatorv1alpha1.ServerCA)
130-
serverURL = fmt.Sprintf("https://%s:6443", rootShard.Spec.External.Hostname)
131-
serverName = rootShard.Spec.External.Hostname
128+
clientCertIssuer = resources.GetRootShardCAName(rootShard, operatorv1alpha1.FrontProxyClientCA)
129+
serverCA = resources.GetRootShardCAName(rootShard, operatorv1alpha1.ServerCA)
132130

133131
default:
134132
return ctrl.Result{}, fmt.Errorf("no valid target for kubeconfig found")
@@ -156,14 +154,12 @@ func (r *KubeconfigReconciler) Reconcile(ctx context.Context, req ctrl.Request)
156154
return ctrl.Result{RequeueAfter: time.Second * 5}, nil
157155
}
158156

159-
rootWSURL, err := url.JoinPath(serverURL, "clusters", "root")
157+
reconciler, err := kubeconfig.KubeconfigSecretReconciler(&kc, rootShard, shard, serverCASecret, clientCertSecret)
160158
if err != nil {
161159
return ctrl.Result{}, err
162160
}
163161

164-
if err := k8creconciling.ReconcileSecrets(ctx, []k8creconciling.NamedSecretReconcilerFactory{
165-
kubeconfig.KubeconfigSecretReconciler(&kc, serverCASecret, clientCertSecret, serverName, rootWSURL),
166-
}, req.Namespace, r.Client); err != nil {
162+
if err := k8creconciling.ReconcileSecrets(ctx, []k8creconciling.NamedSecretReconcilerFactory{reconciler}, req.Namespace, r.Client); err != nil {
167163
return ctrl.Result{}, err
168164
}
169165

internal/resources/kubeconfig/secret.go

Lines changed: 96 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,49 +18,117 @@ package kubeconfig
1818

1919
import (
2020
"fmt"
21+
"net/url"
2122

2223
"k8c.io/reconciler/pkg/reconciling"
2324

2425
corev1 "k8s.io/api/core/v1"
2526
"k8s.io/client-go/tools/clientcmd"
2627
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
2728

29+
"github.com/kcp-dev/kcp-operator/internal/resources"
2830
operatorv1alpha1 "github.com/kcp-dev/kcp-operator/sdk/apis/operator/v1alpha1"
2931
)
3032

31-
func KubeconfigSecretReconciler(kubeconfig *operatorv1alpha1.Kubeconfig, caSecret, certSecret *corev1.Secret, serverName, serverURL string) reconciling.NamedSecretReconcilerFactory {
32-
return func() (string, reconciling.SecretReconciler) {
33-
return kubeconfig.Spec.SecretRef.Name, func(secret *corev1.Secret) (*corev1.Secret, error) {
34-
var config *clientcmdapi.Config
33+
const (
34+
baseContext string = "base"
35+
shardBaseContext string = "shard-base"
36+
defaultContext string = "default"
37+
)
3538

36-
if secret.Data == nil {
37-
secret.Data = make(map[string][]byte)
38-
}
39+
func KubeconfigSecretReconciler(
40+
kubeconfig *operatorv1alpha1.Kubeconfig,
41+
rootShard *operatorv1alpha1.RootShard,
42+
shard *operatorv1alpha1.Shard,
43+
caSecret, certSecret *corev1.Secret,
44+
) (reconciling.NamedSecretReconcilerFactory, error) {
45+
config := &clientcmdapi.Config{
46+
Clusters: map[string]*clientcmdapi.Cluster{},
47+
Contexts: map[string]*clientcmdapi.Context{},
48+
AuthInfos: map[string]*clientcmdapi.AuthInfo{
49+
kubeconfig.Spec.Username: {
50+
ClientCertificateData: certSecret.Data["tls.crt"],
51+
ClientKeyData: certSecret.Data["tls.key"],
52+
},
53+
},
54+
}
3955

40-
config = &clientcmdapi.Config{}
56+
addCluster := func(clusterName, url string) {
57+
config.Clusters[clusterName] = &clientcmdapi.Cluster{
58+
Server: url,
59+
CertificateAuthorityData: caSecret.Data["tls.crt"],
60+
}
61+
}
62+
addContext := func(contextName, clusterName string) {
63+
config.Contexts[contextName] = &clientcmdapi.Context{
64+
Cluster: clusterName,
65+
AuthInfo: kubeconfig.Spec.Username,
66+
}
67+
}
4168

42-
config.Clusters = map[string]*clientcmdapi.Cluster{
43-
serverName: {
44-
Server: serverURL,
45-
CertificateAuthorityData: caSecret.Data["tls.crt"],
46-
},
47-
}
69+
switch {
70+
case kubeconfig.Spec.Target.RootShardRef != nil:
71+
if rootShard == nil {
72+
panic("RootShard must be provided when kubeconfig targets one.")
73+
}
4874

49-
contextName := fmt.Sprintf("%s:%s", serverName, kubeconfig.Spec.Username)
75+
serverURL := resources.GetRootShardBaseURL(rootShard)
76+
defaultURL, err := url.JoinPath(serverURL, "clusters", "root")
77+
if err != nil {
78+
return nil, err
79+
}
5080

51-
config.Contexts = map[string]*clientcmdapi.Context{
52-
contextName: {
53-
Cluster: serverName,
54-
AuthInfo: kubeconfig.Spec.Username,
55-
},
56-
}
57-
config.AuthInfos = map[string]*clientcmdapi.AuthInfo{
58-
kubeconfig.Spec.Username: {
59-
ClientCertificateData: certSecret.Data["tls.crt"],
60-
ClientKeyData: certSecret.Data["tls.key"],
61-
},
81+
addCluster(defaultContext, defaultURL)
82+
addContext(defaultContext, defaultContext)
83+
addCluster(baseContext, serverURL)
84+
addContext(baseContext, baseContext)
85+
addContext(shardBaseContext, baseContext)
86+
config.CurrentContext = defaultContext
87+
88+
case kubeconfig.Spec.Target.ShardRef != nil:
89+
if shard == nil {
90+
panic("Shard must be provided when kubeconfig targets one.")
91+
}
92+
93+
serverURL := resources.GetShardBaseURL(shard)
94+
defaultURL, err := url.JoinPath(serverURL, "clusters", "root")
95+
if err != nil {
96+
return nil, err
97+
}
98+
99+
addCluster(defaultContext, defaultURL)
100+
addContext(defaultContext, defaultContext)
101+
addCluster(baseContext, serverURL)
102+
addContext(baseContext, baseContext)
103+
addContext(shardBaseContext, baseContext)
104+
config.CurrentContext = defaultContext
105+
106+
case kubeconfig.Spec.Target.FrontProxyRef != nil:
107+
if rootShard == nil {
108+
panic("RootShard must be provided when kubeconfig targets a FrontProxy.")
109+
}
110+
111+
serverURL := fmt.Sprintf("https://%s:6443", rootShard.Spec.External.Hostname)
112+
defaultURL, err := url.JoinPath(serverURL, "clusters", "root")
113+
if err != nil {
114+
return nil, err
115+
}
116+
117+
addCluster(baseContext, serverURL)
118+
addCluster(defaultContext, defaultURL)
119+
addContext(defaultContext, defaultContext)
120+
addContext(baseContext, baseContext)
121+
config.CurrentContext = defaultContext
122+
123+
default:
124+
panic("Called reconciler for an invalid kubeconfig, this should not have happened.")
125+
}
126+
127+
return func() (string, reconciling.SecretReconciler) {
128+
return kubeconfig.Spec.SecretRef.Name, func(secret *corev1.Secret) (*corev1.Secret, error) {
129+
if secret.Data == nil {
130+
secret.Data = make(map[string][]byte)
62131
}
63-
config.CurrentContext = contextName
64132

65133
data, err := clientcmd.Write(*config)
66134
if err != nil {
@@ -71,5 +139,5 @@ func KubeconfigSecretReconciler(kubeconfig *operatorv1alpha1.Kubeconfig, caSecre
71139

72140
return secret, nil
73141
}
74-
}
142+
}, nil
75143
}

0 commit comments

Comments
 (0)