Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/controller/shard/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ func (r *ShardReconciler) reconcileStatus(ctx context.Context, oldShard *operato

availableCond := apimeta.FindStatusCondition(newShard.Status.Conditions, string(operatorv1alpha1.ConditionTypeAvailable))
switch {
case availableCond.Status == metav1.ConditionTrue:
case availableCond != nil && availableCond.Status == metav1.ConditionTrue:
newShard.Status.Phase = operatorv1alpha1.ShardPhaseRunning

case newShard.DeletionTimestamp != nil:
Expand Down
130 changes: 125 additions & 5 deletions internal/controller/shard/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ import (
certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
"github.com/stretchr/testify/require"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client"
ctrlruntimefakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
Expand All @@ -37,9 +40,11 @@ func TestReconciling(t *testing.T) {
const namespace = "shard-tests"

testcases := []struct {
name string
rootShard *operatorv1alpha1.RootShard
shard *operatorv1alpha1.Shard
name string
rootShard *operatorv1alpha1.RootShard
shard *operatorv1alpha1.Shard
extraObjects []ctrlruntimeclient.Object
checkFunc func(t *testing.T, client ctrlruntimeclient.Client, shard *operatorv1alpha1.Shard)
}{
{
name: "vanilla",
Expand Down Expand Up @@ -78,20 +83,131 @@ func TestReconciling(t *testing.T) {
},
},
},
extraObjects: nil,
checkFunc: func(t *testing.T, client ctrlruntimeclient.Client, shard *operatorv1alpha1.Shard) {
// Check that the external logical cluster admin kubeconfig uses the server CA
_ = api.Config{}
kubeconfigSecret := &corev1.Secret{}
err := client.Get(context.Background(), ctrlruntimeclient.ObjectKey{
Name: shard.Name + "-external-logical-cluster-admin-kubeconfig",
Namespace: shard.Namespace,
}, kubeconfigSecret)
require.NoError(t, err)

kubeconfigData, exists := kubeconfigSecret.Data["kubeconfig"]
require.True(t, exists, "kubeconfig data should exist")

config, err := clientcmd.Load(kubeconfigData)
require.NoError(t, err)

cluster, exists := config.Clusters["external-logical-cluster:admin"]
require.True(t, exists, "external-logical-cluster:admin cluster should exist")

require.Equal(t, "/etc/kcp/tls/ca/server/tls.crt", cluster.CertificateAuthority, "should use server CA path")
},
},
{
name: "with-ca-bundle-secret-ref",
rootShard: &operatorv1alpha1.RootShard{
ObjectMeta: metav1.ObjectMeta{
Name: "rooty-ca",
Namespace: namespace,
},
Spec: operatorv1alpha1.RootShardSpec{
External: operatorv1alpha1.ExternalConfig{
Hostname: "example.kcp.io",
Port: 6443,
},
CommonShardSpec: operatorv1alpha1.CommonShardSpec{
Etcd: operatorv1alpha1.EtcdConfig{
Endpoints: []string{"https://localhost:2379"},
},
},
},
},
shard: &operatorv1alpha1.Shard{
ObjectMeta: metav1.ObjectMeta{
Name: "shardy-ca",
Namespace: namespace,
},
Spec: operatorv1alpha1.ShardSpec{
CommonShardSpec: operatorv1alpha1.CommonShardSpec{
Etcd: operatorv1alpha1.EtcdConfig{
Endpoints: []string{"https://localhost:2379"},
},
CABundleSecretRef: &corev1.LocalObjectReference{
Name: "custom-ca",
},
},
RootShard: operatorv1alpha1.RootShardConfig{
Reference: &corev1.LocalObjectReference{
Name: "rooty-ca",
},
},
},
},
extraObjects: []ctrlruntimeclient.Object{
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "custom-ca",
Namespace: namespace,
},
Data: map[string][]byte{
"tls.crt": []byte("custom-ca-cert"),
},
},
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "shardy-ca-server",
Namespace: namespace,
},
Data: map[string][]byte{
"tls.crt": []byte("server-ca-cert"),
},
},
},
checkFunc: func(t *testing.T, client ctrlruntimeclient.Client, shard *operatorv1alpha1.Shard) {
// Check that the external logical cluster admin kubeconfig uses the merged CA bundle
_ = api.Config{}
kubeconfigSecret := &corev1.Secret{}
err := client.Get(context.Background(), ctrlruntimeclient.ObjectKey{
Name: shard.Name + "-external-logical-cluster-admin-kubeconfig",
Namespace: shard.Namespace,
}, kubeconfigSecret)
require.NoError(t, err)

kubeconfigData, exists := kubeconfigSecret.Data["kubeconfig"]
require.True(t, exists, "kubeconfig data should exist")

config, err := clientcmd.Load(kubeconfigData)
require.NoError(t, err)

cluster, exists := config.Clusters["external-logical-cluster:admin"]
require.True(t, exists, "external-logical-cluster:admin cluster should exist")

require.Equal(t, "/etc/kcp/tls/ca/ca-bundle/tls.crt", cluster.CertificateAuthority, "should use merged CA bundle path")
},
},
}

scheme := runtime.NewScheme()
require.Nil(t, corev1.AddToScheme(scheme))
require.Nil(t, appsv1.AddToScheme(scheme))
require.Nil(t, operatorv1alpha1.AddToScheme(scheme))
require.Nil(t, certmanagerv1.AddToScheme(scheme))

for _, testcase := range testcases {
t.Run(testcase.name, func(t *testing.T) {
objects := []ctrlruntimeclient.Object{testcase.rootShard, testcase.shard}
if testcase.extraObjects != nil {
objects = append(objects, testcase.extraObjects...)
}

client := ctrlruntimefakeclient.
NewClientBuilder().
WithScheme(scheme).
WithStatusSubresource(testcase.rootShard, testcase.shard).
WithObjects(testcase.rootShard, testcase.shard).
WithObjects(objects...).
Build()

ctx := context.Background()
Expand All @@ -102,9 +218,13 @@ func TestReconciling(t *testing.T) {
}

_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: ctrlruntimeclient.ObjectKeyFromObject(testcase.rootShard),
NamespacedName: ctrlruntimeclient.ObjectKeyFromObject(testcase.shard),
})
require.NoError(t, err)

if testcase.checkFunc != nil {
testcase.checkFunc(t, client, testcase.shard)
}
})
}
}
10 changes: 8 additions & 2 deletions internal/resources/shard/kubeconfigs.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ func ExternalLogicalClusterAdminKubeconfigReconciler(shard *operatorv1alpha1.Sha
config = &clientcmdapi.Config{
Clusters: map[string]*clientcmdapi.Cluster{
serverName: {
Server: fmt.Sprintf("https://%s:%d", rootShard.Spec.External.Hostname, rootShard.Spec.External.Port),
CertificateAuthority: getCAMountPath(operatorv1alpha1.ServerCA) + "/tls.crt",
Server: fmt.Sprintf("https://%s:%d", rootShard.Spec.External.Hostname, rootShard.Spec.External.Port),
// CertificateAuthority will be configured below to respect CABundleSecretRef property in the shard spec
},
},
Contexts: map[string]*clientcmdapi.Context{
Expand All @@ -168,6 +168,12 @@ func ExternalLogicalClusterAdminKubeconfigReconciler(shard *operatorv1alpha1.Sha
CurrentContext: contextName,
}

if shard.Spec.CABundleSecretRef == nil {
config.Clusters[serverName].CertificateAuthority = getCAMountPath(operatorv1alpha1.ServerCA) + "/tls.crt"
} else {
config.Clusters[serverName].CertificateAuthority = getCAMountPath(operatorv1alpha1.CABundleCA) + "/tls.crt"
}

data, err := clientcmd.Write(*config)
if err != nil {
return nil, err
Expand Down