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
5 changes: 4 additions & 1 deletion pkg/controllers/workgenerator/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -1280,8 +1280,11 @@ func extractResFromConfigMap(uConfigMap *unstructured.Unstructured) ([]fleetv1be
klog.ErrorS(unMarshallErr, "manifest has invalid content", "manifestKey", key, "envelopeResource", klog.KObj(uConfigMap))
return nil, fmt.Errorf("the object with manifest key `%s` in envelope config `%s` is malformatted, err: %w", key, klog.KObj(uConfigMap), unMarshallErr)
}
if len(uManifest.GetNamespace()) == 0 {
Copy link

Copilot AI Apr 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new check preventing wrapping of cluster-scoped resources is correctly implemented with a clear error message. Consider adding a dedicated unit test case to explicitly verify that cluster-scoped objects are blocked.

Copilot uses AI. Check for mistakes.
// Block cluster-scoped resources.
return nil, fmt.Errorf("cannot wrap cluster-scoped resource %s in the envelope %s", uManifest.GetName(), klog.KObj(uConfigMap))
}
if len(uManifest.GetNamespace()) != 0 && uManifest.GetNamespace() != configMap.Namespace {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with the new condition added, this is just

Suggested change
if len(uManifest.GetNamespace()) != 0 && uManifest.GetNamespace() != configMap.Namespace {
if uManifest.GetNamespace() != configMap.Namespace {

// if the manifest is a namespaced object but no namespace is specified, it will fail at the apply time instead of here.
return nil, fmt.Errorf("the namespaced object `%s` in envelope config `%s` is placed in a different namespace `%s` ", uManifest.GetName(), klog.KObj(uConfigMap), uManifest.GetNamespace())
}
manifests = append(manifests, fleetv1beta1.Manifest{
Expand Down
3 changes: 1 addition & 2 deletions pkg/controllers/workgenerator/controller_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,6 @@ var _ = Describe("Test Work Generator Controller", func() {
Workload: placementv1beta1.WorkloadTemplate{
Manifests: []placementv1beta1.Manifest{
{RawExtension: runtime.RawExtension{Raw: testEnvelopeResourceQuota}},
{RawExtension: runtime.RawExtension{Raw: testEnvelopeWebhook}},
},
},
},
Expand Down Expand Up @@ -823,7 +822,7 @@ var _ = Describe("Test Work Generator Controller", func() {
Spec: placementv1beta1.WorkSpec{
Workload: placementv1beta1.WorkloadTemplate{
Manifests: []placementv1beta1.Manifest{
{RawExtension: runtime.RawExtension{Raw: testEnvelopeWebhook}},
{RawExtension: runtime.RawExtension{Raw: testEnvelopeResourceQuota2}},
},
},
},
Expand Down
26 changes: 20 additions & 6 deletions pkg/controllers/workgenerator/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,23 @@ func TestExtractResFromConfigMap(t *testing.T) {
want: nil,
wantErr: true,
},
"config map with cluster scoped resource should fail": {
uConfigMap: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "test-config",
"namespace": "default",
},
"data": map[string]interface{}{
"resource": `{"apiVersion": "admissionregistration.k8s.io/v1", "kind": "ValidatingWebhookConfiguration", "metadata": {"name": "test-webhook"}}`,
},
},
},
want: nil,
wantErr: true,
},
"config map with valid and invalid entries should fail": {
uConfigMap: &unstructured.Unstructured{
Object: map[string]interface{}{
Expand All @@ -226,7 +243,7 @@ func TestExtractResFromConfigMap(t *testing.T) {
want: nil,
wantErr: true,
},
"config map with cluster and namespace scoped data in the correct namespace should pass": {
"config map with cluster and namespace scoped data in the correct namespace should fail": {
uConfigMap: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
Expand All @@ -241,11 +258,8 @@ func TestExtractResFromConfigMap(t *testing.T) {
},
},
},
want: []fleetv1beta1.Manifest{
{RawExtension: runtime.RawExtension{Raw: []byte(`{"apiVersion": "v1", "kind": "Pod", "metadata": {"name": "test-pod", "namespace": "default"}}`)}},
{RawExtension: runtime.RawExtension{Raw: []byte(`{"apiVersion": "v1", "kind": "ClusterRole", "metadata": {"name": "test-role"}}`)}},
},
wantErr: false,
want: nil,
wantErr: true,
},
"config map with cluster scoped and cross namespaced resources data in a different namespace should fail": {
uConfigMap: &unstructured.Unstructured{
Expand Down
11 changes: 11 additions & 0 deletions pkg/controllers/workgenerator/manifests/resourcequota2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: v1
kind: ResourceQuota
metadata:
name: mem-cpu-demo
namespace: app
spec:
hard:
requests.cpu: "2"
requests.memory: 2Gi
limits.cpu: "4"
limits.memory: 4Gi
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,3 @@ data:
requests.memory: 1Gi
limits.cpu: "2"
limits.memory: 2Gi
webhook.yaml: |
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
creationTimestamp: null
labels:
azure-workload-identity.io/system: "true"
name: azure-wi-webhook-mutating-webhook-configuration
webhooks:
- admissionReviewVersions:
- v1
- v1beta1
clientConfig:
service:
name: azure-wi-webhook-webhook-service
namespace: app
path: /mutate-v1-pod
failurePolicy: Fail
matchPolicy: Equivalent
name: mutation.azure-workload-identity.io
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- pods
sideEffects: None
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,15 @@ metadata:
annotations:
kubernetes-fleet.io/envelope-configmap: "true"
data:
webhook.yaml: |
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
resourceQuota.yaml: |
apiVersion: v1
kind: ResourceQuota
metadata:
creationTimestamp: null
labels:
azure-workload-identity.io/system: "true"
name: azure-wi-webhook-mutating-webhook-configuration
webhooks:
- admissionReviewVersions:
- v1
- v1beta1
clientConfig:
service:
name: azure-wi-webhook-webhook-service
namespace: app
path: /mutate-v1-pod
failurePolicy: Fail
matchPolicy: Equivalent
name: mutation.azure-workload-identity.io
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- pods
sideEffects: None
name: mem-cpu-demo
namespace: app
spec:
hard:
requests.cpu: "2"
requests.memory: 2Gi
limits.cpu: "4"
limits.memory: 4Gi
14 changes: 7 additions & 7 deletions pkg/controllers/workgenerator/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ var (
wantOverriddenTestResource []byte

// the content of the enveloped resources
testEnvelopeWebhook, testEnvelopeResourceQuota []byte
testEnvelopeResourceQuota, testEnvelopeResourceQuota2 []byte
)

func TestAPIs(t *testing.T) {
Expand Down Expand Up @@ -365,15 +365,15 @@ func readTestManifests() {
testPdb, err = yaml.ToJSON(rawByte)
Expect(err).Should(Succeed())

By("Read EnvelopeWebhook")
rawByte, err = os.ReadFile("manifests/webhook.yaml")
Expect(err).Should(Succeed())
testEnvelopeWebhook, err = yaml.ToJSON(rawByte)
Expect(err).Should(Succeed())

By("Read ResourceQuota")
rawByte, err = os.ReadFile("manifests/resourcequota.yaml")
Expect(err).Should(Succeed())
testEnvelopeResourceQuota, err = yaml.ToJSON(rawByte)
Expect(err).Should(Succeed())

By("Read ResourceQuota2")
rawByte, err = os.ReadFile("manifests/resourcequota2.yaml")
Expect(err).Should(Succeed())
testEnvelopeResourceQuota2, err = yaml.ToJSON(rawByte)
Expect(err).Should(Succeed())
}
21 changes: 15 additions & 6 deletions test/e2e/actuals_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,11 @@ func resourcePlacementOverrideFailedConditions(generation int64) []metav1.Condit
}
}

func resourcePlacementWorkSynchronizedFailedConditions(generation int64) []metav1.Condition {
func resourcePlacementWorkSynchronizedFailedConditions(generation int64, hasOverrides bool) []metav1.Condition {
overridenCondReason := condition.OverrideNotSpecifiedReason
if hasOverrides {
overridenCondReason = condition.OverriddenSucceededReason
}
return []metav1.Condition{
{
Type: string(placementv1beta1.ResourceScheduledConditionType),
Expand All @@ -631,7 +635,7 @@ func resourcePlacementWorkSynchronizedFailedConditions(generation int64) []metav
Type: string(placementv1beta1.ResourceOverriddenConditionType),
Status: metav1.ConditionTrue,
ObservedGeneration: generation,
Reason: condition.OverriddenSucceededReason,
Reason: overridenCondReason,
},
{
Type: string(placementv1beta1.ResourceWorkSynchronizedConditionType),
Expand All @@ -642,7 +646,11 @@ func resourcePlacementWorkSynchronizedFailedConditions(generation int64) []metav
}
}

func crpWorkSynchronizedFailedConditions(generation int64) []metav1.Condition {
func crpWorkSynchronizedFailedConditions(generation int64, hasOverrides bool) []metav1.Condition {
overridenCondReason := condition.OverrideNotSpecifiedReason
if hasOverrides {
overridenCondReason = condition.OverriddenSucceededReason
}
return []metav1.Condition{
{
Type: string(placementv1beta1.ClusterResourcePlacementScheduledConditionType),
Expand All @@ -659,7 +667,7 @@ func crpWorkSynchronizedFailedConditions(generation int64) []metav1.Condition {
{
Type: string(placementv1beta1.ClusterResourcePlacementOverriddenConditionType),
Status: metav1.ConditionTrue,
Reason: condition.OverriddenSucceededReason,
Reason: overridenCondReason,
ObservedGeneration: generation,
},
{
Expand Down Expand Up @@ -782,17 +790,18 @@ func crpStatusWithWorkSynchronizedUpdatedFailedActual(
}

var wantPlacementStatus []placementv1beta1.ResourcePlacementStatus
hasOverrides := len(wantResourceOverrides) > 0 || len(wantClusterResourceOverrides) > 0
for _, name := range wantSelectedClusters {
wantPlacementStatus = append(wantPlacementStatus, placementv1beta1.ResourcePlacementStatus{
ClusterName: name,
Conditions: resourcePlacementWorkSynchronizedFailedConditions(crp.Generation),
Conditions: resourcePlacementWorkSynchronizedFailedConditions(crp.Generation, hasOverrides),
ApplicableResourceOverrides: wantResourceOverrides,
ApplicableClusterResourceOverrides: wantClusterResourceOverrides,
})
}

wantStatus := placementv1beta1.ClusterResourcePlacementStatus{
Conditions: crpWorkSynchronizedFailedConditions(crp.Generation),
Conditions: crpWorkSynchronizedFailedConditions(crp.Generation, hasOverrides),
PlacementStatuses: wantPlacementStatus,
SelectedResources: wantSelectedResourceIdentifiers,
ObservedResourceIndex: wantObservedResourceIndex,
Expand Down
Loading
Loading