Skip to content
5 changes: 5 additions & 0 deletions fleetconfig-controller/api/v1alpha1/fleetconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,11 @@ func (j *JoinedSpoke) conditionName() string {

// Klusterlet is the configuration for a klusterlet.
type Klusterlet struct {
// Annotations to apply to the spoke cluster. If not present, the 'agent.open-cluster-management.io/' prefix is added to each key.
// Each annotation is added to klusterlet.spec.registrationConfiguration.clusterAnnotations on the spoke and subsequently to the ManagedCluster on the hub.
// +optional
Annotations map[string]string `json:"annotations,omitempty"`

// A set of comma-separated pairs of the form 'key1=value1,key2=value2' that describe feature gates for alpha/experimental features.
// Options are:
// - AddonManagement (ALPHA - default=true)
Expand Down
7 changes: 7 additions & 0 deletions fleetconfig-controller/api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

68 changes: 35 additions & 33 deletions fleetconfig-controller/charts/fleetconfig-controller/README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,13 @@ spec:
default: {}
description: Klusterlet configuration.
properties:
annotations:
additionalProperties:
type: string
description: |-
Annotations to apply to the spoke cluster. If not present, the 'agent.open-cluster-management.io/' prefix is added to each key.
Each annotation is added to klusterlet.spec.registrationConfiguration.clusterAnnotations on the spoke and subsequently to the ManagedCluster on the hub.
type: object
featureGates:
default: AddonManagement=true,ClusterClaim=true
description: |-
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ spec:
bundleVersion: {{ .Values.fleetConfig.source.bundleVersion }}
registry: {{ .Values.fleetConfig.source.registry }}
{{- end }}
{{- if .Values.fleetConfig.hub.singleton }}
singleton: {{- toYaml .Values.fleetConfig.hub.singleton | nindent 6 }}
{{- if .Values.fleetConfig.hub.singletonControlPlane }}
singleton: {{- toYaml .Values.fleetConfig.hub.singletonControlPlane | nindent 6 }}
{{- end }}
createNamespace: {{ .Values.fleetConfig.hub.createNamespace }}
force: {{ .Values.fleetConfig.hub.force }}
Expand All @@ -53,6 +53,13 @@ spec:
clusterARN: {{ .clusterARN | quote }}
{{- end }}
klusterlet:
{{- if and .klusterlet.annotations $.Values.fleetConfig.spokeAnnotations }}
annotations: {{- toYaml (merge .klusterlet.annotations $.Values.fleetConfig.spokeAnnotations) | nindent 10 }}
{{- else if .klusterlet.annotations }}
annotations: {{- toYaml .klusterlet.annotations | nindent 10 }}
{{- else if $.Values.fleetConfig.spokeAnnotations }}
annotations: {{- toYaml $.Values.fleetConfig.spokeAnnotations | nindent 10 }}
{{- end }}
mode: {{ .klusterlet.mode | quote }}
purgeOperator: {{ .klusterlet.purgeOperator }}
featureGates: {{ include "featureGates" (dict "dict" $spokeFeatureGates) | quote }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
fleetConfig:
## @param fleetConfig.enabled Whether to create a FleetConfig resource.
enabled: true
## @param fleetConfig.spokeAnnotations Global annotations to apply to all spoke clusters. If not present, the 'agent.open-cluster-management.io/' prefix is added to each key. Each annotation is added to klusterlet.spec.registrationConfiguration.clusterAnnotations on every spoke and subsequently to the ManagedClusters on the hub. Per-spoke annotations take precedence over the global annotations.
spokeAnnotations: {}
## @descriptionStart
## ### Spoke Feature Gates
## Uncomment and configure `fleetConfig.spokeFeatureGates` to enable feature gates for the Klusterlet on each Spoke.
Expand Down Expand Up @@ -157,6 +159,7 @@ fleetConfig:
## @param fleetConfig.spokes[0].ca Hub cluster CA certificate, optional.
## @param fleetConfig.spokes[0].proxyCa Proxy CA certificate, optional.
## @param fleetConfig.spokes[0].proxyUrl URL of a forward proxy server used by agents to connect to the Hub cluster, optional.
## @param fleetConfig.spokes[0].klusterlet.annotations Annotations to apply to the spoke cluster. If not present, the 'agent.open-cluster-management.io/' prefix is added to each key. Each annotation is added to klusterlet.spec.registrationConfiguration.clusterAnnotations on the spoke and subsequently to the ManagedCluster on the hub. These annotations take precedence over the global spoke annotations.
## @param fleetConfig.spokes[0].klusterlet.mode Deployment mode for klusterlet. Options: Default (agents run on spoke cluster) | Hosted (agents run on hub cluster).
## @param fleetConfig.spokes[0].klusterlet.purgeOperator If set, the klusterlet operator will be purged and all open-cluster-management namespaces deleted when the klusterlet is unjoined from its Hub cluster.
## @param fleetConfig.spokes[0].klusterlet.forceInternalEndpointLookup If true, the klusterlet agent will start the cluster registration process by looking for the
Expand Down Expand Up @@ -187,6 +190,7 @@ fleetConfig:
proxyUrl: ""
## Configuration for the Klusterlet on the Spoke cluster.
klusterlet:
annotations: {}
mode: "Default"
purgeOperator: true
# Reference the internal endpoint from the cluster-info ConfigMap in the Hub cluster instead of using the Hub's
Expand Down Expand Up @@ -269,7 +273,7 @@ imageRegistry: ""
## @param image.pullPolicy Image pull policy
image:
repository: quay.io/open-cluster-management/fleetconfig-controller
tag: v0.0.5
tag: v0.0.6
pullPolicy: IfNotPresent

## @param imagePullSecrets Image pull secrets
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,13 @@ spec:
default: {}
description: Klusterlet configuration.
properties:
annotations:
additionalProperties:
type: string
description: |-
Annotations to apply to the spoke cluster. If not present, the 'agent.open-cluster-management.io/' prefix is added to each key.
Each annotation is added to klusterlet.spec.registrationConfiguration.clusterAnnotations on the spoke and subsequently to the ManagedCluster on the hub.
type: object
featureGates:
default: AddonManagement=true,ClusterClaim=true
description: |-
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,12 @@ func (r *FleetConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request)
}

// Handle Hub cluster: initialization and/or upgrade
hubInitializedCond := fc.GetCondition(v1alpha1.FleetConfigHubInitialized)
if err := handleHub(ctx, r.Client, fc); err != nil {
logger.Error(err, "Failed to handle hub operations")
fc.Status.Phase = v1alpha1.FleetConfigUnhealthy
}
if hubInitializedCond.Status == metav1.ConditionFalse {
hubInitializedCond := fc.GetCondition(v1alpha1.FleetConfigHubInitialized)
if hubInitializedCond == nil || hubInitializedCond.Status == metav1.ConditionFalse {
return ret(ctx, ctrl.Result{Requeue: true}, nil)
}

Expand Down
4 changes: 4 additions & 0 deletions fleetconfig-controller/internal/controller/spoke.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,10 @@ func joinSpoke(ctx context.Context, kClient client.Client, spec v1alpha1.FleetCo
"--image-registry", spoke.Klusterlet.Source.Registry,
}

for k, v := range spoke.Klusterlet.Annotations {
joinArgs = append(joinArgs, fmt.Sprintf("--klusterlet-annotation=%s=%s", k, v))
}

// resources args
joinArgs = append(joinArgs, common.PrepareResources(spoke.Klusterlet.Resources)...)

Expand Down
5 changes: 5 additions & 0 deletions fleetconfig-controller/test/data/fleetconfig-values.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
fleetConfig:
spokeAnnotations:
foo: "not-bar"
baz: "quux"
spokes:
- name: hub-as-spoke
createNamespace: true
Expand All @@ -21,6 +24,8 @@ fleetConfig:
namespace: "default"
kubeconfigKey: "value"
klusterlet:
annotations:
foo: "bar"
mode: "Default"
purgeOperator: true
forceInternalEndpointLookup: true
Expand Down
17 changes: 17 additions & 0 deletions fleetconfig-controller/test/e2e/fleetconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
kerrs "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ktypes "k8s.io/apimachinery/pkg/types"
operatorv1 "open-cluster-management.io/api/operator/v1"

"github.com/open-cluster-management-io/lab/fleetconfig-controller/api/v1alpha1"
"github.com/open-cluster-management-io/lab/fleetconfig-controller/pkg/common"
Expand Down Expand Up @@ -72,6 +73,22 @@ var _ = Describe("fleetconfig", Label("fleetconfig"), Ordered, func() {
Expect(err).NotTo(HaveOccurred())
})

It("should verify spoke cluster annotations", func() {
EventuallyWithOffset(1, func() error {
klusterlet := &operatorv1.Klusterlet{}
if err := tc.kClientSpoke.Get(tc.ctx, klusterletNN, klusterlet); err != nil {
return err
}
if err := assertKlusterletAnnotation(klusterlet, "foo", "bar"); err != nil {
return err
}
if err := assertKlusterletAnnotation(klusterlet, "baz", "quux"); err != nil {
return err
}
return nil
}, 1*time.Minute, 1*time.Second).Should(Succeed())
})

It("should successfully create a namespace in the hub-as-spoke cluster", func() {

By("creating a ManifestWork in the hub-as-spoke cluster namespace")
Expand Down
30 changes: 23 additions & 7 deletions fleetconfig-controller/test/e2e/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

clusterv1beta1 "open-cluster-management.io/api/cluster/v1beta1"
clusterv1beta2 "open-cluster-management.io/api/cluster/v1beta2"
operatorv1 "open-cluster-management.io/api/operator/v1"
workv1 "open-cluster-management.io/api/work/v1"

"github.com/open-cluster-management-io/lab/fleetconfig-controller/api/v1alpha1"
Expand All @@ -29,13 +30,14 @@ import (
)

const (
fcNamespace = "fleetconfig-system"
spokeSecretName = "test-fleetconfig-kubeconfig"
devspaceLocal = "local"
devspaceCI = "ci"
kubeconfigSecretKey = "value"
hubAsSpokeName = v1alpha1.ManagedClusterTypeHubAsSpoke
spokeName = v1alpha1.ManagedClusterTypeSpoke
fcNamespace = "fleetconfig-system"
spokeSecretName = "test-fleetconfig-kubeconfig"
klusterletAnnotationPrefix = "agent.open-cluster-management.io"
devspaceLocal = "local"
devspaceCI = "ci"
kubeconfigSecretKey = "value"
hubAsSpokeName = v1alpha1.ManagedClusterTypeHubAsSpoke
spokeName = v1alpha1.ManagedClusterTypeSpoke
)

var (
Expand All @@ -45,6 +47,7 @@ var (

// global test variables
fleetConfigNN = ktypes.NamespacedName{Name: "fleetconfig", Namespace: fcNamespace}
klusterletNN = ktypes.NamespacedName{Name: "klusterlet"}
)

// E2EContext holds all the test-specific state.
Expand Down Expand Up @@ -121,6 +124,7 @@ func setupTestEnvironment() *E2EContext {
Expect(v1alpha1.AddToScheme(scheme.Scheme)).To(Succeed())
Expect(clusterv1beta1.AddToScheme(scheme.Scheme)).To(Succeed())
Expect(clusterv1beta2.AddToScheme(scheme.Scheme)).To(Succeed())
Expect(operatorv1.AddToScheme(scheme.Scheme)).To(Succeed())
Expect(workv1.AddToScheme(scheme.Scheme)).To(Succeed())

By("creating a kubernetes client for the hub cluster")
Expand Down Expand Up @@ -329,3 +333,15 @@ func assertNamespace(ctx context.Context, cluster string, kClient client.Client)
utils.Info("Namespace %s is now created in cluster '%s'", namespaceName, cluster)
return nil
}

func assertKlusterletAnnotation(klusterlet *operatorv1.Klusterlet, key, expectedValue string) error {
expectedKey := fmt.Sprintf("%s/%s", klusterletAnnotationPrefix, key)
v, ok := klusterlet.Spec.RegistrationConfiguration.ClusterAnnotations[expectedKey]
if !ok {
return fmt.Errorf("expected annotation, %s, not found", expectedKey)
}
if v != expectedValue {
return fmt.Errorf("expected %s=%s, got %s", expectedKey, expectedValue, v)
}
return nil
}
Loading