Skip to content

Commit 0d5fc0c

Browse files
authored
Merge pull request #641 from zirkelc/jsonata
feat(jsonata): support `Arguments` for DynamoDB
2 parents 7d82a84 + fcc00ea commit 0d5fc0c

File tree

2 files changed

+85
-81
lines changed

2 files changed

+85
-81
lines changed

lib/deploy/stepFunctions/compileIamRole.js

+47-11
Original file line numberDiff line numberDiff line change
@@ -196,25 +196,61 @@ function getEcsPermissions() {
196196
}];
197197
}
198198

199+
function isJsonPathParameter(state, key) {
200+
const jsonPath = `${key}.$`;
201+
return state.Parameters && state.Parameters[jsonPath];
202+
}
203+
204+
function isJsonataArgument(state, key) {
205+
return state.Arguments && state.Arguments[key] && typeof state.Arguments[key] === 'string' && state.Arguments[key].trim().startsWith('{%');
206+
}
207+
208+
function getParameterOrArgument(state, key) {
209+
if (state.QueryLanguage === 'JSONata') return state.Arguments && state.Arguments[key];
210+
211+
if (state.QueryLanguage === 'JSONPath') return state.Parameters && state.Parameters[key];
212+
213+
if (state.Parameters && !state.Arguments) return state.Parameters[key];
214+
215+
if (state.Arguments && !state.Parameters) return state.Arguments[key];
216+
217+
return undefined;
218+
}
219+
220+
function hasParameterOrArgument(state, key) {
221+
if (state.QueryLanguage === 'JSONata') return state.Arguments && state.Arguments[key];
222+
223+
if (state.QueryLanguage === 'JSONPath') return state.Parameters && state.Parameters[key];
224+
225+
// If no query language is specified, we would need to go to the top-level definition
226+
// and check if the key is present at the state machine definition
227+
// As workaround, we will simply check if eitehr Parameters or Arguments is present
228+
if (state.Parameters && !state.Arguments) return state.Parameters[key];
229+
230+
if (state.Arguments && !state.Parameters) return state.Arguments[key];
231+
232+
return false;
233+
}
234+
199235
function getDynamoDBPermissions(action, state) {
200236
let resource;
201237

202-
if (state.Parameters['TableName.$']) {
238+
if (isJsonPathParameter(state, 'TableName') || isJsonataArgument(state, 'TableName')) {
203239
// When the TableName is only known at runtime, we
204240
// have to provide * permissions during deployment.
205241
resource = '*';
206-
} else if (state.Parameters['IndexName.$'] || state.Parameters.IndexName) {
242+
} else if (isJsonPathParameter(state, 'IndexName') || isJsonataArgument(state, 'IndexName')) {
243+
// We must provide * here instead of state.Parameters['IndexName.$'], because we don't know
244+
// which index will be targeted when we the step function runs
245+
resource = getDynamoDBArn(`${getParameterOrArgument(state, 'TableName')}/index/*`);
246+
} else if (hasParameterOrArgument(state, 'IndexName')) {
207247
// When the Parameters contain an IndexName, we have to build a
208248
// longer arn that includes the index.
209-
const indexName = state.Parameters['IndexName.$']
210-
// We must provide * here instead of state.Parameters['IndexName.$'], because we don't know
211-
// which index will be targeted when we the step function runs
212-
? '*'
213-
: state.Parameters.IndexName;
249+
const indexName = getParameterOrArgument(state, 'IndexName');
214250

215-
resource = getDynamoDBArn(`${state.Parameters.TableName}/index/${indexName}`);
251+
resource = getDynamoDBArn(`${getParameterOrArgument(state, 'TableName')}/index/${indexName}`);
216252
} else {
217-
resource = getDynamoDBArn(state.Parameters.TableName);
253+
resource = getDynamoDBArn(getParameterOrArgument(state, 'TableName'));
218254
}
219255

220256
return [{
@@ -224,7 +260,7 @@ function getDynamoDBPermissions(action, state) {
224260
}
225261

226262
function getBatchDynamoDBPermissions(action, state) {
227-
if (state.Parameters['RequestItems.$']) {
263+
if (isJsonPathParameter(state, 'RequestItems') || isJsonataArgument(state, 'RequestItems')) {
228264
// When the RequestItems object is only known at runtime,
229265
// we have to provide * permissions during deployment.
230266
return [{
@@ -236,7 +272,7 @@ function getBatchDynamoDBPermissions(action, state) {
236272
// table names as keys. We can use these to generate roles
237273
// whether the array of requests for that table is known
238274
// at deploy time or not
239-
const tableNames = Object.keys(state.Parameters.RequestItems);
275+
const tableNames = Object.keys(getParameterOrArgument(state, 'RequestItems'));
240276

241277
return tableNames.map(tableName => ({
242278
action,

lib/deploy/stepFunctions/compileIamRole.test.js

+38-70
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
'use strict';
22

33
const _ = require('lodash');
4+
const itParam = require('mocha-param');
45
const expect = require('chai').expect;
56
const sinon = require('sinon');
67
const Serverless = require('serverless/lib/Serverless');
78
const AwsProvider = require('serverless/lib/plugins/aws/provider');
89
const ServerlessStepFunctions = require('./../../index');
910

11+
function getParamsOrArgs(queryLanguage, params, args) {
12+
return queryLanguage === 'JSONPath'
13+
? { Parameters: params }
14+
: { Arguments: args === undefined ? params : args };
15+
}
16+
1017
describe('#compileIamRole', () => {
1118
let serverless;
1219
let serverlessStepFunctions;
@@ -536,7 +543,7 @@ describe('#compileIamRole', () => {
536543
expect(policy.PolicyDocument.Statement[0].Resource).to.have.lengthOf(0);
537544
});
538545

539-
it('should give dynamodb permission for only tables referenced by state machine', () => {
546+
itParam('should give dynamodb permission for only tables referenced by state machine: ${value}', ['JSONPath', 'JSONata'], (queryLanguage) => {
540547
const helloTable = 'hello';
541548
const helloTableArn = {
542549
'Fn::Join': [
@@ -554,50 +561,42 @@ describe('#compileIamRole', () => {
554561
id,
555562
definition: {
556563
StartAt: 'A',
564+
QueryLanguage: queryLanguage,
557565
States: {
558566
A: {
559567
Type: 'Task',
560568
Resource: resources[0],
561-
Parameters: {
562-
TableName: tableName,
563-
},
569+
...getParamsOrArgs(queryLanguage, { TableName: tableName }),
564570
Next: 'B',
565571
},
566572
B: {
567573
Type: 'Task',
568574
Resource: resources[1],
569-
Parameters: {
570-
TableName: tableName,
571-
},
575+
...getParamsOrArgs(queryLanguage, { TableName: tableName }),
572576
Next: 'C',
573577
},
574578
C: {
575579
Type: 'Task',
576580
Resource: 'arn:aws:states:::dynamodb:getItem',
577-
Parameters: {
578-
TableName: tableName,
579-
},
581+
...getParamsOrArgs(queryLanguage, { TableName: tableName }),
580582
Next: 'D',
581583
},
582584
D: {
583585
Type: 'Task',
584586
Resource: 'arn:aws:states:::dynamodb:deleteItem',
585-
Parameters: {
586-
TableName: tableName,
587-
},
587+
...getParamsOrArgs(queryLanguage, { TableName: tableName }),
588588
End: true,
589589
},
590590
E: {
591591
Type: 'Task',
592592
Resource: 'arn:aws:states:::aws-sdk:dynamodb:updateTable',
593-
Parameters: {
594-
TableName: tableName,
595-
},
593+
...getParamsOrArgs(queryLanguage, { TableName: tableName }),
596594
End: true,
597595
},
598596
},
599597
},
600598
});
599+
601600
serverless.service.stepFunctions = {
602601
stateMachines: {
603602
myStateMachine1: genStateMachine('StateMachine1', helloTable, ['arn:aws:states:::dynamodb:updateItem', 'arn:aws:states:::dynamodb:putItem']),
@@ -636,7 +635,7 @@ describe('#compileIamRole', () => {
636635
.to.be.deep.equal([worldTableArn]);
637636
});
638637

639-
it('should give dynamodb permission for table name imported from external stack', () => {
638+
itParam('should give dynamodb permission for table name imported from external stack', ['JSONPath', 'JSONata'], (queryLanguage) => {
640639
// Necessary to convince the region is in the gov cloud infrastructure.
641640
const externalHelloTable = { 'Fn::ImportValue': 'HelloStack:Table:Name' };
642641
const helloTableArn = {
@@ -660,33 +659,25 @@ describe('#compileIamRole', () => {
660659
A: {
661660
Type: 'Task',
662661
Resource: resources[0],
663-
Parameters: {
664-
TableName: tableName,
665-
},
662+
...getParamsOrArgs(queryLanguage, { TableName: tableName }),
666663
Next: 'B',
667664
},
668665
B: {
669666
Type: 'Task',
670667
Resource: resources[1],
671-
Parameters: {
672-
TableName: tableName,
673-
},
668+
...getParamsOrArgs(queryLanguage, { TableName: tableName }),
674669
Next: 'C',
675670
},
676671
C: {
677672
Type: 'Task',
678673
Resource: 'arn:aws:states:::dynamodb:getItem',
679-
Parameters: {
680-
TableName: tableName,
681-
},
674+
...getParamsOrArgs(queryLanguage, { TableName: tableName }),
682675
Next: 'D',
683676
},
684677
D: {
685678
Type: 'Task',
686679
Resource: 'arn:aws:states:::dynamodb:deleteItem',
687-
Parameters: {
688-
TableName: tableName,
689-
},
680+
...getParamsOrArgs(queryLanguage, { TableName: tableName }),
690681
End: true,
691682
},
692683
},
@@ -731,7 +722,7 @@ describe('#compileIamRole', () => {
731722
.to.be.deep.equal([worldTableArn]);
732723
});
733724

734-
it('should give dynamodb permission to index table whenever IndexName is provided', () => {
725+
itParam('should give dynamodb permission to index table whenever IndexName is provided: ${value}', ['JSONPath', 'JSONata'], (queryLanguage) => {
735726
const helloTable = 'hello';
736727

737728
const genStateMachine = (id, tableName) => ({
@@ -742,18 +733,13 @@ describe('#compileIamRole', () => {
742733
A: {
743734
Type: 'Task',
744735
Resource: 'arn:aws:states:::aws-sdk:dynamodb:query',
745-
Parameters: {
746-
TableName: tableName,
747-
},
736+
...getParamsOrArgs(queryLanguage, { TableName: tableName }),
748737
Next: 'B',
749738
},
750739
B: {
751740
Type: 'Task',
752741
Resource: 'arn:aws:states:::aws-sdk:dynamodb:query',
753-
Parameters: {
754-
TableName: tableName,
755-
IndexName: 'GSI1',
756-
},
742+
...getParamsOrArgs(queryLanguage, { TableName: tableName, IndexName: 'GSI1' }),
757743
End: true,
758744
},
759745
},
@@ -783,7 +769,7 @@ describe('#compileIamRole', () => {
783769
});
784770
});
785771

786-
it('should give dynamodb permission to * whenever TableName.$ is seen', () => {
772+
itParam('should give dynamodb permission to * whenever TableName.$ is seen: ${value}', ['JSONPath', 'JSONata'], (queryLanguage) => {
787773
const helloTable = 'hello';
788774

789775
const genStateMachine = (id, tableName) => ({
@@ -794,17 +780,13 @@ describe('#compileIamRole', () => {
794780
A: {
795781
Type: 'Task',
796782
Resource: 'arn:aws:states:::dynamodb:updateItem',
797-
Parameters: {
798-
TableName: tableName,
799-
},
783+
...getParamsOrArgs(queryLanguage, { TableName: tableName }),
800784
Next: 'B',
801785
},
802786
B: {
803787
Type: 'Task',
804788
Resource: 'arn:aws:states:::dynamodb:updateItem',
805-
Parameters: {
806-
'TableName.$': '$.tableName',
807-
},
789+
...getParamsOrArgs(queryLanguage, { 'TableName.$': '$.tableName' }, { TableName: '{% $tableName %}' }),
808790
End: true,
809791
},
810792
},
@@ -830,7 +812,7 @@ describe('#compileIamRole', () => {
830812
expect(policy.PolicyDocument.Statement[0].Resource).to.equal('*');
831813
});
832814

833-
it('should give dynamodb permission to table/TableName/index/* when IndexName.$ is seen', () => {
815+
itParam('should give dynamodb permission to table/TableName/index/* when IndexName.$ is seen: ${value}', ['JSONPath', 'JSONata'], (queryLanguage) => {
834816
const helloTable = 'hello';
835817

836818
const genStateMachine = (id, tableName) => ({
@@ -841,10 +823,10 @@ describe('#compileIamRole', () => {
841823
A: {
842824
Type: 'Task',
843825
Resource: 'arn:aws:states:::aws-sdk:dynamodb:query',
844-
Parameters: {
826+
...getParamsOrArgs(queryLanguage, {
845827
TableName: tableName,
846828
'IndexName.$': '$.myDynamicIndexName',
847-
},
829+
}, { TableName: tableName, IndexName: '{% $myDynamicIndexName %}' }),
848830
End: true,
849831
},
850832
},
@@ -870,7 +852,7 @@ describe('#compileIamRole', () => {
870852
expect(policy.PolicyDocument.Statement[0].Resource[0]['Fn::Join'][1][5]).to.equal('table/hello/index/*');
871853
});
872854

873-
it('should give dynamodb permission to table/* whenever TableName.$ and IndexName.$ are seen', () => {
855+
itParam('should give dynamodb permission to table/* whenever TableName.$ and IndexName.$ are seen: ${value}', ['JSONPath', 'JSONata'], (queryLanguage) => {
874856
const genStateMachine = id => ({
875857
id,
876858
definition: {
@@ -879,10 +861,10 @@ describe('#compileIamRole', () => {
879861
A: {
880862
Type: 'Task',
881863
Resource: 'arn:aws:states:::aws-sdk:dynamodb:query',
882-
Parameters: {
864+
...getParamsOrArgs(queryLanguage, {
883865
'TableName.$': '$.myDynamicTableName',
884866
'IndexName.$': '$.myDynamicIndexName',
885-
},
867+
}, { TableName: '{% $myDynamicTableName %}', IndexName: '{% $myDynamicIndexName %}' }),
886868
End: true,
887869
},
888870
},
@@ -908,7 +890,7 @@ describe('#compileIamRole', () => {
908890
expect(policy.PolicyDocument.Statement[0].Resource[0]).to.equal('*');
909891
});
910892

911-
it('should give batch dynamodb permission for only tables referenced by state machine', () => {
893+
itParam('should give batch dynamodb permission for only tables referenced by state machine: ${value}', ['JSONPath', 'JSONata'], (queryLanguage) => {
912894
const helloTable = 'hello';
913895
const helloTableArn = {
914896
'Fn::Join': [
@@ -930,21 +912,13 @@ describe('#compileIamRole', () => {
930912
A: {
931913
Type: 'Task',
932914
Resource: 'arn:aws:states:::aws-sdk:dynamodb:batchWriteItem',
933-
Parameters: {
934-
RequestItems: {
935-
[tableName]: [],
936-
},
937-
},
915+
...getParamsOrArgs(queryLanguage, { RequestItems: { [tableName]: [] } }),
938916
Next: 'B',
939917
},
940918
B: {
941919
Type: 'Task',
942920
Resource: 'arn:aws:states:::aws-sdk:dynamodb:batchGetItem',
943-
Parameters: {
944-
RequestItems: {
945-
[tableName]: {},
946-
},
947-
},
921+
...getParamsOrArgs(queryLanguage, { RequestItems: { [tableName]: {} } }),
948922
End: true,
949923
},
950924
},
@@ -977,7 +951,7 @@ describe('#compileIamRole', () => {
977951
.to.be.deep.equal([worldTableArn]);
978952
});
979953

980-
it('should give batch dynamodb permission to * whenever RequestItems.$ is seen', () => {
954+
itParam('should give batch dynamodb permission to * whenever RequestItems.$ is seen: ${value}', ['JSONPath', 'JSONata'], (queryLanguage) => {
981955
const genStateMachine = id => ({
982956
id,
983957
definition: {
@@ -986,19 +960,13 @@ describe('#compileIamRole', () => {
986960
A: {
987961
Type: 'Task',
988962
Resource: 'arn:aws:states:::aws-sdk:dynamodb:batchWriteItem',
989-
Parameters: {
990-
RequestItems: {
991-
tableName: [],
992-
},
993-
},
963+
...getParamsOrArgs(queryLanguage, { RequestItems: { tableName: [] } }),
994964
Next: 'B',
995965
},
996966
B: {
997967
Type: 'Task',
998968
Resource: 'arn:aws:states:::aws-sdk:dynamodb:batchWriteItem',
999-
Parameters: {
1000-
'RequestItems.$': '$.requestItems',
1001-
},
969+
...getParamsOrArgs(queryLanguage, { 'RequestItems.$': '$.requestItems' }, { RequestItems: '{% $requestItems %}' }),
1002970
End: true,
1003971
},
1004972
},

0 commit comments

Comments
 (0)