diff --git a/lib/compose.ts b/lib/compose.ts index f815e2e..f49069a 100644 --- a/lib/compose.ts +++ b/lib/compose.ts @@ -413,6 +413,18 @@ function normalizeService( // Delete env_file, as compose-go adds env_file vars to service.environment delete service.env_file; + // Coerce null/undefined env var values to empty string. + // `KEY: null` is valid in the Compose spec, but the API + // rejects null values for env vars, so we coerce to '' to match + // @balena/compose parser behavior prior to integrating compose-parser. + if (service.environment) { + for (const [key, value] of Object.entries(service.environment)) { + if (value == null) { + service.environment[key] = ''; + } + } + } + // Delete label_file, as compose-go adds label_file labels to service.labels delete service.label_file; diff --git a/test/compose.spec.ts b/test/compose.spec.ts index ea5ee26..9913721 100644 --- a/test/compose.spec.ts +++ b/test/compose.spec.ts @@ -458,6 +458,34 @@ describe('compose-go parsing & validation', () => { }); }); + it('should coerce null/undefined environment values to empty string', async () => { + const composition = await parse( + 'test/fixtures/compose/services/null_env.yml', + ); + expect(composition).to.deep.equal({ + services: { + main: { + image: 'alpine:latest', + command: ['sh', '-c', 'sleep infinity'], + environment: { + EXPLICIT_VALUE: 'hello', + EXPLICIT_EMPTY: '', + NULL_DICT_FORM: '', + IMPLICIT_NULL_DICT_FORM: '', + }, + networks: { + default: null, + }, + }, + }, + networks: { + default: { + ipam: {}, + }, + }, + }); + }); + it('should merge services from extends config', async () => { const composition = await parse( 'test/fixtures/compose/services/extends.yml', diff --git a/test/fixtures/compose/services/null_env.yml b/test/fixtures/compose/services/null_env.yml new file mode 100644 index 0000000..ab71d12 --- /dev/null +++ b/test/fixtures/compose/services/null_env.yml @@ -0,0 +1,9 @@ +services: + main: + image: alpine:latest + command: sh -c "sleep infinity" + environment: + EXPLICIT_VALUE: hello + EXPLICIT_EMPTY: "" + NULL_DICT_FORM: null + IMPLICIT_NULL_DICT_FORM: