Skip to content

Commit c4f12d0

Browse files
committed
Added secretResolver interface for secret resolution
1 parent 91e8b44 commit c4f12d0

File tree

5 files changed

+846
-302
lines changed

5 files changed

+846
-302
lines changed

lib/runner/extensions/item.command.js

Lines changed: 174 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ var _ = require('lodash'),
22
uuid = require('uuid'),
33
Response = require('postman-collection').Response,
44
visualizer = require('../../visualizer'),
5+
resolveSecrets = require('../resolve-secrets').resolveSecrets,
56

67
/**
78
* List of request properties which can be mutated via pre-request
@@ -121,7 +122,11 @@ module.exports = {
121122
stopOnFailure = this.options.stopOnFailure,
122123
delay = _.get(this.options, 'delay.item'),
123124

124-
ctxTemplate;
125+
ctxTemplate,
126+
self = this,
127+
secretResolvers = this.state.secretResolvers,
128+
secretResolutionPayload,
129+
executeItemFlow;
125130

126131
// validate minimum parameters required for the command to work
127132
if (!(item && coords)) {
@@ -134,160 +139,197 @@ module.exports = {
134139
// here we code to queue prerequest script, then make a request and then execute test script
135140
this.triggers.beforeItem(null, coords, item);
136141

137-
this.queueDelay(function () {
142+
// Build payload for secret resolution
143+
// Only scan environment, globals, and collectionVariables for secrets
144+
secretResolutionPayload = {
145+
item,
146+
environment,
147+
globals,
148+
collectionVariables
149+
};
150+
151+
/**
152+
* Execute the main item flow (delay, prerequest, request, test)
153+
*/
154+
executeItemFlow = function () {
155+
self.queueDelay(function () {
138156
// create the context object for scripts to run
139-
ctxTemplate = {
140-
collectionVariables: collectionVariables,
141-
vaultSecrets: vaultSecrets,
142-
_variables: _variables,
143-
globals: globals,
144-
environment: environment,
145-
data: data,
146-
request: item.request
147-
};
148-
149-
// @todo make it less nested by coding Instruction.thenQueue
150-
this.queue('event', {
151-
name: 'prerequest',
152-
item: item,
153-
coords: coords,
154-
context: ctxTemplate,
155-
// No need to include vaultSecrets here as runtime takes care of tracking internally
156-
trackContext: ['globals', 'environment', 'collectionVariables'],
157-
stopOnScriptError: stopOnError,
158-
stopOnFailure: stopOnFailure
159-
}).done(function (prereqExecutions, prereqExecutionError, shouldSkipExecution) {
160-
// if stop on error is marked and script executions had an error,
161-
// do not proceed with more commands, instead we bail out
162-
if ((stopOnError || stopOnFailure) && prereqExecutionError) {
163-
this.triggers.item(null, coords, item); // @todo - should this trigger receive error?
164-
165-
return callback && callback.call(this, prereqExecutionError, {
166-
prerequest: prereqExecutions
167-
});
168-
}
169-
170-
if (shouldSkipExecution) {
171-
this.triggers.item(prereqExecutionError, coords, item, null, { isSkipped: true });
172-
173-
return callback && callback.call(this, prereqExecutionError, {
174-
prerequest: prereqExecutions
175-
});
176-
}
177-
178-
// update allowed request mutation properties with the mutated context
179-
// @note from this point forward, make sure this mutated
180-
// request instance is used for upcoming commands.
181-
ALLOWED_REQUEST_MUTATIONS.forEach(function (property) {
182-
if (_.has(ctxTemplate, ['request', property])) {
183-
item.request[property] = ctxTemplate.request[property];
184-
}
185-
186-
// update property's parent reference
187-
if (item.request[property] && typeof item.request[property].setParent === 'function') {
188-
item.request[property].setParent(item.request);
189-
}
190-
});
191-
192-
this.queue('request', {
157+
ctxTemplate = {
158+
collectionVariables: collectionVariables,
159+
vaultSecrets: vaultSecrets,
160+
_variables: _variables,
161+
globals: globals,
162+
environment: environment,
163+
data: data,
164+
request: item.request
165+
};
166+
167+
// @todo make it less nested by coding Instruction.thenQueue
168+
this.queue('event', {
169+
name: 'prerequest',
193170
item: item,
194-
vaultSecrets: ctxTemplate.vaultSecrets,
195-
globals: ctxTemplate.globals,
196-
environment: ctxTemplate.environment,
197-
collectionVariables: ctxTemplate.collectionVariables,
198-
_variables: ctxTemplate._variables,
199-
data: ctxTemplate.data,
200171
coords: coords,
201-
source: 'collection',
202-
secretResolver: this.state.secretResolver
203-
}).done(function (result, requestError) {
204-
!result && (result = {});
205-
206-
var request = result.request,
207-
response = result.response,
208-
cookies = result.cookies;
209-
210-
if ((stopOnError || stopOnFailure) && requestError) {
172+
context: ctxTemplate,
173+
// No need to include vaultSecrets here as runtime takes care of tracking internally
174+
trackContext: ['globals', 'environment', 'collectionVariables'],
175+
stopOnScriptError: stopOnError,
176+
stopOnFailure: stopOnFailure
177+
}).done(function (prereqExecutions, prereqExecutionError, shouldSkipExecution) {
178+
// if stop on error is marked and script executions had an error,
179+
// do not proceed with more commands, instead we bail out
180+
if ((stopOnError || stopOnFailure) && prereqExecutionError) {
211181
this.triggers.item(null, coords, item); // @todo - should this trigger receive error?
212182

213-
return callback && callback.call(this, requestError, {
214-
request
183+
return callback && callback.call(this, prereqExecutionError, {
184+
prerequest: prereqExecutions
215185
});
216186
}
217187

218-
// also the test object requires the updated request object (since auth helpers may modify it)
219-
request && (ctxTemplate.request = request);
188+
if (shouldSkipExecution) {
189+
this.triggers.item(prereqExecutionError, coords, item, null, { isSkipped: true });
220190

221-
// @note convert response instance to plain object.
222-
// we want to avoid calling Response.toJSON() which triggers toJSON on Response.stream buffer.
223-
// Because that increases the size of stringified object by 3 times.
224-
// Also, that increases the total number of tokens (buffer.data) whereas Buffer.toString
225-
// generates a single string that is easier to stringify and sent over the UVM bridge.
226-
response && (ctxTemplate.response = getResponseJSON(response));
191+
return callback && callback.call(this, prereqExecutionError, {
192+
prerequest: prereqExecutions
193+
});
194+
}
227195

228-
// set cookies for this transaction
229-
cookies && (ctxTemplate.cookies = cookies);
196+
// update allowed request mutation properties with the mutated context
197+
// @note from this point forward, make sure this mutated
198+
// request instance is used for upcoming commands.
199+
ALLOWED_REQUEST_MUTATIONS.forEach(function (property) {
200+
if (_.has(ctxTemplate, ['request', property])) {
201+
item.request[property] = ctxTemplate.request[property];
202+
}
230203

231-
// the context template also has a test object to store assertions
232-
ctxTemplate.tests = {}; // @todo remove
204+
// update property's parent reference
205+
if (item.request[property] && typeof item.request[property].setParent === 'function') {
206+
item.request[property].setParent(item.request);
207+
}
208+
});
233209

234-
this.queue('event', {
235-
name: 'test',
210+
this.queue('request', {
236211
item: item,
212+
vaultSecrets: ctxTemplate.vaultSecrets,
213+
globals: ctxTemplate.globals,
214+
environment: ctxTemplate.environment,
215+
collectionVariables: ctxTemplate.collectionVariables,
216+
_variables: ctxTemplate._variables,
217+
data: ctxTemplate.data,
237218
coords: coords,
238-
context: ctxTemplate,
239-
// No need to include vaultSecrets here as runtime takes care of tracking internally
240-
trackContext: ['tests', 'globals', 'environment', 'collectionVariables'],
241-
stopOnScriptError: stopOnError,
242-
abortOnFailure: abortOnFailure,
243-
stopOnFailure: stopOnFailure
244-
}).done(function (testExecutions, testExecutionError) {
245-
var visualizerData = extractVisualizerData(prereqExecutions, testExecutions),
246-
visualizerResult;
247-
248-
if (visualizerData) {
249-
visualizer.processTemplate(visualizerData.template,
250-
visualizerData.data,
251-
visualizerData.options,
252-
function (err, processedTemplate) {
253-
visualizerResult = {
254-
// bubble up the errors while processing template through visualizer result
255-
error: err,
219+
source: 'collection'
220+
}).done(function (result, requestError) {
221+
!result && (result = {});
222+
223+
var request = result.request,
224+
response = result.response,
225+
cookies = result.cookies;
256226

257-
// add processed template and data to visualizer result
258-
processedTemplate: processedTemplate,
259-
data: visualizerData.data
260-
};
227+
if ((stopOnError || stopOnFailure) && requestError) {
228+
this.triggers.item(null, coords, item); // @todo - should this trigger receive error?
261229

262-
// trigger an event saying that item has been processed
263-
this.triggers.item(null, coords, item, visualizerResult);
264-
}.bind(this));
230+
return callback && callback.call(this, requestError, {
231+
request
232+
});
265233
}
266-
else {
234+
235+
// also the test object requires the updated request object
236+
// (since auth helpers may modify it)
237+
request && (ctxTemplate.request = request);
238+
239+
// @note convert response instance to plain object.
240+
// we want to avoid calling Response.toJSON() which triggers
241+
// toJSON on Response.stream buffer.
242+
// Because that increases the size of stringified object by 3 times.
243+
// Also, that increases the total number of tokens (buffer.data) whereas Buffer.toString
244+
// generates a single string that is easier to stringify and sent over the UVM bridge.
245+
response && (ctxTemplate.response = getResponseJSON(response));
246+
247+
// set cookies for this transaction
248+
cookies && (ctxTemplate.cookies = cookies);
249+
250+
// the context template also has a test object to store assertions
251+
ctxTemplate.tests = {}; // @todo remove
252+
253+
this.queue('event', {
254+
name: 'test',
255+
item: item,
256+
coords: coords,
257+
context: ctxTemplate,
258+
// No need to include vaultSecrets here as runtime takes care of tracking internally
259+
trackContext: ['tests', 'globals', 'environment', 'collectionVariables'],
260+
stopOnScriptError: stopOnError,
261+
abortOnFailure: abortOnFailure,
262+
stopOnFailure: stopOnFailure
263+
}).done(function (testExecutions, testExecutionError) {
264+
var visualizerData = extractVisualizerData(prereqExecutions, testExecutions),
265+
visualizerResult;
266+
267+
if (visualizerData) {
268+
visualizer.processTemplate(visualizerData.template,
269+
visualizerData.data,
270+
visualizerData.options,
271+
function (err, processedTemplate) {
272+
visualizerResult = {
273+
// bubble up the errors while processing template through visualizer result
274+
error: err,
275+
276+
// add processed template and data to visualizer result
277+
processedTemplate: processedTemplate,
278+
data: visualizerData.data
279+
};
280+
281+
// trigger an event saying that item has been processed
282+
this.triggers.item(null, coords, item, visualizerResult);
283+
}.bind(this));
284+
}
285+
else {
267286
// trigger an event saying that item has been processed
268287
// @todo - should this trigger receive error?
269-
this.triggers.item(null, coords, item, null);
270-
}
271-
272-
// reset mutated request with original request instance
273-
// @note request mutations are not persisted across iterations
274-
item.request = originalRequest;
275-
276-
callback && callback.call(this, ((stopOnError || stopOnFailure) && testExecutionError) ?
277-
testExecutionError : null, {
278-
prerequest: prereqExecutions,
279-
request: request,
280-
response: response,
281-
test: testExecutions
288+
this.triggers.item(null, coords, item, null);
289+
}
290+
291+
// reset mutated request with original request instance
292+
// @note request mutations are not persisted across iterations
293+
item.request = originalRequest;
294+
295+
callback && callback.call(this, ((stopOnError || stopOnFailure) && testExecutionError) ?
296+
testExecutionError : null, {
297+
prerequest: prereqExecutions,
298+
request: request,
299+
response: response,
300+
test: testExecutions
301+
});
282302
});
283303
});
284304
});
305+
}.bind(self), {
306+
time: delay,
307+
source: 'item',
308+
cursor: coords
309+
}, next);
310+
};
311+
312+
// Resolve secrets before pre-request scripts so scripts can access resolved values via related pm APIs
313+
if (secretResolvers) {
314+
resolveSecrets(secretResolutionPayload, secretResolvers, function (err) {
315+
if (err) {
316+
// Secret resolution failed - this is fatal for the current item
317+
// (same pattern as pre-request script skip)
318+
self.triggers.item(err, coords, item, null, { isSecretResolutionFailed: true });
319+
320+
callback && callback.call(self, err, {
321+
request: null
322+
});
323+
324+
return next();
325+
}
326+
327+
executeItemFlow();
285328
});
286-
}.bind(this), {
287-
time: delay,
288-
source: 'item',
289-
cursor: coords
290-
}, next);
329+
}
330+
else {
331+
executeItemFlow();
332+
}
291333
}
292334
}
293335
};

0 commit comments

Comments
 (0)