From 39c86199c815c07c6d9cb4add81ffd53e824f843 Mon Sep 17 00:00:00 2001 From: gazarenkov Date: Wed, 17 Jun 2026 17:13:29 +0300 Subject: [PATCH 1/3] Do not allow multi entry app-config ConfigMap --- pkg/model/appconfig.go | 8 ++++---- pkg/model/appconfig_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/pkg/model/appconfig.go b/pkg/model/appconfig.go index 873611bbc..433d356d6 100644 --- a/pkg/model/appconfig.go +++ b/pkg/model/appconfig.go @@ -107,10 +107,10 @@ func (b *AppConfig) setMetaInfo(backstage api.Backstage, scheme *runtime.Scheme) // updatePodWithAppConfig contributes to Volumes, container.VolumeMounts and container.Args func updatePodWithAppConfig(bsd *BackstageDeployment, cmName, mountPath, key string, withSubPath bool, cmData []string) error { - // TODO, enable this check - //if len(cmData) > 1 { - // return fmt.Errorf("multiple fields is not allowed for app-config ConfigMap %s", cmName) - //} + // allow only single entry configMap to ensure predictable order in app-config chain + if len(cmData) > 1 { + return fmt.Errorf("multiple entries (%d) not allowed for app-config ConfigMap: %s", len(cmData), cmName) + } _ = bsd.mountFilesFrom(containersFilter{}, ConfigMapObjectKind, cmName, mountPath, key, withSubPath, cmData) diff --git a/pkg/model/appconfig_test.go b/pkg/model/appconfig_test.go index 47d91f6c7..03910b900 100644 --- a/pkg/model/appconfig_test.go +++ b/pkg/model/appconfig_test.go @@ -146,3 +146,28 @@ func TestDefaultAndSpecifiedAppConfig(t *testing.T) { deployment.container().VolumeMounts[0].Name) } + +// TestMultiEntryAppConfigNotAllowed verifies that ConfigMaps with multiple entries +// are rejected to ensure predictable order in the app-config chain. +func TestMultiEntryAppConfigNotAllowed(t *testing.T) { + bs := *appConfigTestBackstage.DeepCopy() + + // Reference a ConfigMap with multiple entries + multiEntryCmName := "multi-entry-config" + bs.Spec.Application.AppConfig.ConfigMaps = []api.FileObjectRef{ + {Name: multiEntryCmName}, + } + + testObj := createBackstageTest(bs).withDefaultConfig(true) + + // Simulate a ConfigMap with multiple data entries + testObj.externalConfig.AppConfigKeys = map[string][]string{ + multiEntryCmName: {"config1.yaml", "config2.yaml"}, // Multiple entries - should fail + } + + _, err := InitObjects(context.TODO(), bs, testObj.externalConfig, platform.Default, testObj.scheme) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "multiple entries") + assert.Contains(t, err.Error(), multiEntryCmName) +} From 23bf090f20f4992dc41712cddbfc522a9a1055ad Mon Sep 17 00:00:00 2001 From: gazarenkov Date: Wed, 17 Jun 2026 17:29:13 +0300 Subject: [PATCH 2/3] fix docs --- docs/configuration.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 284a50a3a..542464cac 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -458,8 +458,7 @@ The ConfigMap key/value defines the file name and content, and this app-config w --config /my/path/my-app-config.yaml ``` -**Note**: It is possible to define several **app-config** files inside one ConfigMap, but the keys are sorted alphabetically to ensure a deterministic order in the pod spec across reconciliations (otherwise, the iteration order would not be guaranteed). This may not match the intended merge order. -Since Backstage merges the chain of **app-config** files from first to last and order matters, keeping several **app-config** files inside one ConfigMap is **NOT recommended**. For this case consider defining several one-entry ConfigMaps instead. +**Important**: Each app-config ConfigMap must contain exactly one data entry. This ensures predictable merge order, as Kubernetes does not guarantee iteration order for ConfigMap data entries. ConfigMaps with multiple entries will be rejected with an error. If you need multiple app-config files, define separate single-entry ConfigMaps and reference them in the desired order. [Includes and Dynamic Data](https://backstage.io/docs/conf/writing/#includes-and-dynamic-data) (including [extra files](#extra-files) and [extra environment variables](#extra-environment-variables)) support configuring additional ConfigMaps and Secrets. From e7ddbd184bd1cb8c4a33cee27f697016822ae03c Mon Sep 17 00:00:00 2001 From: Gennady Azarenkov Date: Thu, 18 Jun 2026 10:52:56 +0300 Subject: [PATCH 3/3] Update pkg/model/appconfig.go Co-authored-by: Armel Soro --- pkg/model/appconfig.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/model/appconfig.go b/pkg/model/appconfig.go index 433d356d6..b9b6bafc4 100644 --- a/pkg/model/appconfig.go +++ b/pkg/model/appconfig.go @@ -109,7 +109,7 @@ func updatePodWithAppConfig(bsd *BackstageDeployment, cmName, mountPath, key str // allow only single entry configMap to ensure predictable order in app-config chain if len(cmData) > 1 { - return fmt.Errorf("multiple entries (%d) not allowed for app-config ConfigMap: %s", len(cmData), cmName) + return fmt.Errorf("multiple entries (%d) not allowed for app-config ConfigMap: %s; split into separate single-entry ConfigMaps", len(cmData), cmName) } _ = bsd.mountFilesFrom(containersFilter{}, ConfigMapObjectKind,