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
3 changes: 1 addition & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
8 changes: 4 additions & 4 deletions pkg/model/appconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -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; split into separate single-entry ConfigMaps", len(cmData), cmName)
}

_ = bsd.mountFilesFrom(containersFilter{}, ConfigMapObjectKind,
cmName, mountPath, key, withSubPath, cmData)
Expand Down
25 changes: 25 additions & 0 deletions pkg/model/appconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Loading