Skip to content

Commit 2394b54

Browse files
committed
preserve array order in serverless config diffs
1 parent 27d261f commit 2394b54

6 files changed

Lines changed: 243 additions & 222 deletions

File tree

packages/base/src/helpers/serverless/__tests__/common.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ describe('generateConfigDiff', () => {
5050
expect(result).toContain('No changes detected.')
5151
})
5252

53+
test('should show arrays in the order they will be written', () => {
54+
const original = {command: []}
55+
const updated = {command: ['CMD-SHELL', '/probe.sh']}
56+
57+
const expected = `+ "command": [
58+
+ "CMD-SHELL",
59+
+ "/probe.sh"
60+
+ ]`
61+
const result = generateConfigDiff(original, updated)
62+
expect(result).toContain(expected)
63+
})
64+
5365
test('should obfuscate sensitive values', () => {
5466
const original = {api_key: 'abc123'}
5567
const updated = {api_key: '1234567890abcdef1234567890abcdef'}

packages/base/src/helpers/serverless/common.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,25 +89,25 @@ export const getBaseEnvVars = (config: ServerlessConfigOptions): Record<string,
8989
}
9090

9191
/**
92-
* Recursively sort object keys to ensure consistent ordering
92+
* Recursively sort object keys, and optionally array elements, to ensure consistent ordering
9393
*/
94-
const sortObject = (obj: any): any => {
94+
const sortRecursively = (obj: any, sortArrays: boolean): any => {
9595
if (!obj) {
9696
return obj
9797
}
9898

9999
if (Array.isArray(obj)) {
100-
return obj.map(sortObject).sort((a, b) => {
101-
return JSON.stringify(a).localeCompare(JSON.stringify(b))
102-
})
100+
const items = obj.map((item) => sortRecursively(item, sortArrays))
101+
102+
return sortArrays ? items.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b))) : items
103103
}
104104

105105
if (typeof obj === 'object') {
106106
const sorted: any = {}
107107
Object.keys(obj)
108108
.sort()
109109
.forEach((key) => {
110-
sorted[key] = sortObject(obj[key])
110+
sorted[key] = sortRecursively(obj[key], sortArrays)
111111
})
112112

113113
return sorted
@@ -116,6 +116,18 @@ const sortObject = (obj: any): any => {
116116
return obj
117117
}
118118

119+
/**
120+
* Sorts keys only. Array order is meaningful in a configuration -- a container's `command` or
121+
* `entryPoint` says something different when it is reordered -- so it is left alone.
122+
*/
123+
const sortKeys = (obj: any): any => sortRecursively(obj, false)
124+
125+
/**
126+
* Sorts keys and array elements, so that two configurations differing only in the order they list
127+
* things compare as equal.
128+
*/
129+
const sortObject = (obj: any): any => sortRecursively(obj, true)
130+
119131
export const sortedEqual = (a: any, b: any): boolean => {
120132
const sortedA = sortObject(a)
121133
const sortedB = sortObject(b)
@@ -140,9 +152,10 @@ const obfuscateSensitiveValues = (line: string): string => {
140152
*/
141153

142154
export const generateConfigDiff = (original: any, updated: any): string => {
143-
// Sort keys consistently before comparison
144-
const sortedOriginal = sortObject(original)
145-
const sortedUpdated = sortObject(updated)
155+
// Keys are sorted so the two sides line up, but array order is preserved: the diff is read as a
156+
// preview of what will be written, so it has to show the order that will actually be sent.
157+
const sortedOriginal = sortKeys(original)
158+
const sortedUpdated = sortKeys(updated)
146159

147160
const originalJson = JSON.stringify(sortedOriginal, undefined, 2)
148161
const updatedJson = JSON.stringify(sortedUpdated, undefined, 2)

packages/plugin-cloud-run/src/__tests__/__snapshots__/instrument.test.ts.snap

Lines changed: 62 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -25,36 +25,36 @@ exports[`InstrumentCommand snapshot tests interactive mode 1`] = `
2525
{
2626
"env": [
2727
+ {
28-
+ "name": "DD_API_KEY",
29-
+ "value": "PLACEHOLDER"
30-
+ },
31-
+ {
32-
+ "name": "DD_HEALTH_PORT",
33-
+ "value": "5555"
28+
+ "name": "DD_SITE",
29+
+ "value": "datadoghq.com"
3430
+ },
3531
+ {
3632
+ "name": "DD_LOGS_INJECTION",
3733
+ "value": "true"
3834
+ },
3935
+ {
40-
+ "name": "DD_SERVERLESS_LOG_PATH",
41-
+ "value": "/shared-volume/logs/*.log"
36+
+ "name": "DD_TRACE_ENABLED",
37+
+ "value": "true"
4238
+ },
4339
+ {
44-
+ "name": "DD_SERVICE",
45-
+ "value": "interactive-service"
40+
+ "name": "DD_HEALTH_PORT",
41+
+ "value": "5555"
42+
+ },
43+
{
44+
"name": "NODE_ENV",
45+
"value": "production"
4646
+ },
4747
+ {
48-
+ "name": "DD_SITE",
49-
+ "value": "datadoghq.com"
48+
+ "name": "DD_API_KEY",
49+
+ "value": "PLACEHOLDER"
5050
+ },
5151
+ {
52-
+ "name": "DD_TRACE_ENABLED",
53-
+ "value": "true"
52+
+ "name": "DD_SERVICE",
53+
+ "value": "interactive-service"
5454
+ },
55-
{
56-
"name": "NODE_ENV",
57-
"value": "production"
55+
+ {
56+
+ "name": "DD_SERVERLESS_LOG_PATH",
57+
+ "value": "/shared-volume/logs/*.log"
5858
}
5959
],
6060
"image": "gcr.io/test-project/test-app:latest",
@@ -70,32 +70,32 @@ exports[`InstrumentCommand snapshot tests interactive mode 1`] = `
7070
+ {
7171
+ "env": [
7272
+ {
73-
+ "name": "DD_API_KEY",
74-
+ "value": "PLACEHOLDER"
73+
+ "name": "DD_SITE",
74+
+ "value": "datadoghq.com"
7575
+ },
7676
+ {
77-
+ "name": "DD_HEALTH_PORT",
78-
+ "value": "5555"
77+
+ "name": "DD_LOGS_INJECTION",
78+
+ "value": "true"
7979
+ },
8080
+ {
81-
+ "name": "DD_LOGS_INJECTION",
81+
+ "name": "DD_TRACE_ENABLED",
8282
+ "value": "true"
8383
+ },
8484
+ {
85-
+ "name": "DD_SERVERLESS_LOG_PATH",
86-
+ "value": "/shared-volume/logs/*.log"
85+
+ "name": "DD_HEALTH_PORT",
86+
+ "value": "5555"
8787
+ },
8888
+ {
89-
+ "name": "DD_SERVICE",
90-
+ "value": "interactive-service"
89+
+ "name": "DD_API_KEY",
90+
+ "value": "PLACEHOLDER"
9191
+ },
9292
+ {
93-
+ "name": "DD_SITE",
94-
+ "value": "datadoghq.com"
93+
+ "name": "DD_SERVICE",
94+
+ "value": "interactive-service"
9595
+ },
9696
+ {
97-
+ "name": "DD_TRACE_ENABLED",
98-
+ "value": "true"
97+
+ "name": "DD_SERVERLESS_LOG_PATH",
98+
+ "value": "/shared-volume/logs/*.log"
9999
+ }
100100
+ ],
101101
+ "image": "gcr.io/datadoghq/serverless-init:latest",
@@ -166,44 +166,44 @@ exports[`InstrumentCommand snapshot tests prints dry run data with basic flags 1
166166
{
167167
"env": [
168168
+ {
169-
+ "name": "DD_API_KEY",
170-
+ "value": "PLACEHOLDER"
169+
+ "name": "DD_SITE",
170+
+ "value": "datadoghq.com"
171171
+ },
172172
+ {
173-
+ "name": "DD_ENV",
174-
+ "value": "staging"
173+
+ "name": "DD_LOGS_INJECTION",
174+
+ "value": "true"
175175
+ },
176176
+ {
177+
+ "name": "DD_TRACE_ENABLED",
178+
+ "value": "true"
179+
+ },
180+
{
177181
+ "name": "DD_HEALTH_PORT",
178182
+ "value": "5555"
179183
+ },
180184
+ {
181-
+ "name": "DD_LOGS_INJECTION",
182-
+ "value": "true"
185+
"name": "NODE_ENV",
186+
"value": "production"
183187
+ },
184188
+ {
185-
+ "name": "DD_SERVERLESS_LOG_PATH",
186-
+ "value": "/shared-volume/logs/*.log"
189+
+ "name": "DD_API_KEY",
190+
+ "value": "PLACEHOLDER"
187191
+ },
188192
+ {
189193
+ "name": "DD_SERVICE",
190194
+ "value": "test-service"
191-
+ },
192-
{
193-
+ "name": "DD_SITE",
194-
+ "value": "datadoghq.com"
195195
+ },
196196
+ {
197-
+ "name": "DD_TRACE_ENABLED",
198-
+ "value": "true"
197+
+ "name": "DD_ENV",
198+
+ "value": "staging"
199199
+ },
200200
+ {
201201
+ "name": "DD_VERSION",
202202
+ "value": "1.0.0"
203203
+ },
204204
+ {
205-
"name": "NODE_ENV",
206-
"value": "production"
205+
+ "name": "DD_SERVERLESS_LOG_PATH",
206+
+ "value": "/shared-volume/logs/*.log"
207207
}
208208
],
209209
"image": "gcr.io/test-project/test-app:latest",
@@ -219,40 +219,40 @@ exports[`InstrumentCommand snapshot tests prints dry run data with basic flags 1
219219
+ {
220220
+ "env": [
221221
+ {
222-
+ "name": "DD_API_KEY",
223-
+ "value": "PLACEHOLDER"
222+
+ "name": "DD_SITE",
223+
+ "value": "datadoghq.com"
224224
+ },
225225
+ {
226-
+ "name": "DD_ENV",
227-
+ "value": "staging"
226+
+ "name": "DD_LOGS_INJECTION",
227+
+ "value": "true"
228228
+ },
229229
+ {
230-
+ "name": "DD_HEALTH_PORT",
231-
+ "value": "5555"
230+
+ "name": "DD_TRACE_ENABLED",
231+
+ "value": "true"
232232
+ },
233233
+ {
234-
+ "name": "DD_LOGS_INJECTION",
235-
+ "value": "true"
234+
+ "name": "DD_HEALTH_PORT",
235+
+ "value": "5555"
236236
+ },
237237
+ {
238-
+ "name": "DD_SERVERLESS_LOG_PATH",
239-
+ "value": "/shared-volume/logs/*.log"
238+
+ "name": "DD_API_KEY",
239+
+ "value": "PLACEHOLDER"
240240
+ },
241241
+ {
242242
+ "name": "DD_SERVICE",
243243
+ "value": "test-service"
244244
+ },
245245
+ {
246-
+ "name": "DD_SITE",
247-
+ "value": "datadoghq.com"
248-
+ },
249-
+ {
250-
+ "name": "DD_TRACE_ENABLED",
251-
+ "value": "true"
246+
+ "name": "DD_ENV",
247+
+ "value": "staging"
252248
+ },
253249
+ {
254250
+ "name": "DD_VERSION",
255251
+ "value": "1.0.0"
252+
+ },
253+
+ {
254+
+ "name": "DD_SERVERLESS_LOG_PATH",
255+
+ "value": "/shared-volume/logs/*.log"
256256
+ }
257257
+ ],
258258
+ "image": "gcr.io/datadoghq/serverless-init:latest",

0 commit comments

Comments
 (0)