@izara_project/izara-core-generate-service-code 1.0.54 → 1.0.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/codeGenerators/app/initial_setup/InitialSetupGenerator.js +223 -123
- package/src/codeGenerators/app/initial_setup/templates/InitialSetup_LambdaRole.ejs +6 -6
- package/src/codeGenerators/app/sls_yaml/FindDataYamlGenerator.js +1 -1
- package/src/codeGenerators/app/sls_yaml/FunctionYamlGenerator.js +303 -201
- package/src/codeGenerators/app/sls_yaml/RoleNameConfigGenerator.js +84 -60
- package/src/codeGenerators/app/sls_yaml/SharedResourceYamlGenerator.js +304 -271
- package/src/codeGenerators/app/sls_yaml/__tests__/SharedResourceYamlGenerator.test.js +91 -32
- package/src/codeGenerators/app/sls_yaml/templates/SharedResource_Yaml.ejs +2 -2
- package/src/codeGenerators/app/src/generatedCode/Flow/{FlowEndpoints → FlowObjects}/EndpointsGenerator.js +1 -1
- package/src/codeGenerators/app/src/generatedCode/Flow/FlowRbac/templates/rbac/FlowRbac_Main.ejs +252 -0
- package/src/codeGenerators/app/src/generatedCode/Flow/{FlowRelationshipEndpoints → FlowRelationships}/RelationshipFlowGenerator.js +1 -1
- package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/StatusFieldGenerator.js +1 -8
- package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/WebSocketGenerator.js +1 -7
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/BaseSqsHandler.js +3 -3
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowClassifier.js +3 -2
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowMainFunctionBase.js +12 -5
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/triggerCacheBase.js +1 -7
- package/src/codeGenerators/app/src/generatedCode/SystemFlowSchemas/RegisterGenerator.js +1 -7
- package/src/codeGenerators/app/src/generatedCode/libs/templates/Consts.ejs +23 -23
- package/src/codeGenerators/resource/sls_yaml/DynamoDBGenerator.js +8 -8
- package/src/codeGenerators/resource/sls_yaml/FlowOutGenerator.js +57 -51
- package/src/codeGenerators/resource/sls_yaml/FlowResourceYamlGenerator.js +164 -175
- package/src/codeGenerators/resource/sls_yaml/templates/SystemDynamoDB_Yaml.ejs +64 -0
- package/src/codeGenerators/resource/sls_yaml/templates/crud/ResourceYaml.ejs +10 -10
- package/src/generate.js +1 -1
- package/src/generateCode.js +181 -149
- package/src/generateSchema.js +1 -1
- package/src/schemaGenerators/app/src/schemas/FlowSchemas/FlowSchemaGenerator.js +0 -1
- package/src/schemaGenerators/app/src/schemas/FlowSchemas/RbacFlowSchemaGenerator.js +16 -2
- package/src/schemaGenerators/app/src/schemas/FlowSchemas/templates/DynamicFlowSchemaTemplate.ejs +3 -1
- package/src/schemaGenerators/app/src/schemas/FlowSchemas/templates/DynamicRbacFlowSchemaTemplate.ejs +1 -0
- package/src/schemaGenerators/app/src/schemas/FlowSchemas/templates/RelationshipFlowSchemaTemplate.ejs +6 -5
- package/src/schemaGenerators/app/src/schemas/FlowSchemas/templates/UserRbacFlowSchemaTemplate.ejs +22 -0
- /package/src/codeGenerators/app/src/generatedCode/Flow/{FlowEndpoints → FlowObjects}/.gitkeep +0 -0
- /package/src/codeGenerators/app/src/generatedCode/Flow/{FlowEndpoints → FlowObjects}/templates/FlowEndpointBeforeLogical_Main.ejs +0 -0
- /package/src/codeGenerators/app/src/generatedCode/Flow/{FlowEndpoints → FlowObjects}/templates/crud/CreateEndpoint_Main.ejs +0 -0
- /package/src/codeGenerators/app/src/generatedCode/Flow/{FlowEndpoints → FlowObjects}/templates/crud/DeleteEndpoint_Main.ejs +0 -0
- /package/src/codeGenerators/app/src/generatedCode/Flow/{FlowEndpoints → FlowObjects}/templates/crud/GetEndpoint_Main.ejs +0 -0
- /package/src/codeGenerators/app/src/generatedCode/Flow/{FlowEndpoints → FlowObjects}/templates/crud/UpdateEndpoint_Main.ejs +0 -0
- /package/src/codeGenerators/app/src/generatedCode/Flow/{FlowRelationshipEndpoints → FlowRelationships}/templates/relationship/ProcessChangeRelationshipComplete_Main.ejs +0 -0
- /package/src/codeGenerators/app/src/generatedCode/Flow/{FlowRelationshipEndpoints → FlowRelationships}/templates/relationship/ProcessChangeRelationship_Main.ejs +0 -0
- /package/src/codeGenerators/app/src/generatedCode/Flow/{FlowRelationshipEndpoints → FlowRelationships}/templates/relationship/ProcessCreateRelationshipComplete_Main.ejs +0 -0
- /package/src/codeGenerators/app/src/generatedCode/Flow/{FlowRelationshipEndpoints → FlowRelationships}/templates/relationship/ProcessCreateRelationship_Main.ejs +0 -0
- /package/src/codeGenerators/app/src/generatedCode/Flow/{FlowRelationshipEndpoints → FlowRelationships}/templates/relationship/ProcessDeleteRelationshipComplete_Main.ejs +0 -0
- /package/src/codeGenerators/app/src/generatedCode/Flow/{FlowRelationshipEndpoints → FlowRelationships}/templates/relationship/ProcessDeleteRelationship_Main.ejs +0 -0
- /package/src/codeGenerators/app/src/generatedCode/Flow/{FlowRelationshipEndpoints → FlowRelationships}/templates/relationship/ProcessGetRelationshipComplete_Main.ejs +0 -0
- /package/src/codeGenerators/app/src/generatedCode/Flow/{FlowRelationshipEndpoints → FlowRelationships}/templates/relationship/ProcessGetRelationship_Main.ejs +0 -0
- /package/src/codeGenerators/app/src/generatedCode/Flow/{FlowRelationshipEndpoints → FlowRelationships}/templates/relationship/ProcessMoveRelationshipComplete_Main.ejs +0 -0
- /package/src/codeGenerators/app/src/generatedCode/Flow/{FlowRelationshipEndpoints → FlowRelationships}/templates/relationship/ProcessMoveRelationship_Main.ejs +0 -0
- /package/src/codeGenerators/app/src/generatedCode/Flow/{FlowRelationshipEndpoints → FlowRelationships}/templates/relationship/ProcessUpdateRelationshipComplete_Main.ejs +0 -0
- /package/src/codeGenerators/app/src/generatedCode/Flow/{FlowRelationshipEndpoints → FlowRelationships}/templates/relationship/ProcessUpdateRelationship_Main.ejs +0 -0
|
@@ -11,14 +11,29 @@ const __dirname = path.dirname(__filename);
|
|
|
11
11
|
const APP_SOURCE_FILES = [
|
|
12
12
|
'find-data.yml',
|
|
13
13
|
'flow-schema.yml',
|
|
14
|
-
'
|
|
14
|
+
'flow-rbac.yml',
|
|
15
|
+
'flow-objects.yml',
|
|
15
16
|
'process-logical.yml',
|
|
16
|
-
'
|
|
17
|
+
'flow-relationships.yml'
|
|
17
18
|
];
|
|
18
19
|
|
|
20
|
+
const FLOW_OBJECTS_ROLE = 'FlowObjectsRole';
|
|
21
|
+
|
|
19
22
|
const QUEUE_OWNERSHIP_PREFIXES = {
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
[FLOW_OBJECTS_ROLE]: ['Create', 'Update', 'Delete', 'Get'],
|
|
24
|
+
FlowRbacRole: [
|
|
25
|
+
'CreateTargetRole',
|
|
26
|
+
'ListTargetRole',
|
|
27
|
+
'DeleteTargetRole',
|
|
28
|
+
'CreateRolePermissions',
|
|
29
|
+
'ListRolePermissions',
|
|
30
|
+
'DeleteRolePermissions',
|
|
31
|
+
'CreateUserRole',
|
|
32
|
+
'ListUserInRoles',
|
|
33
|
+
'DeleteUserFromRole',
|
|
34
|
+
'UserRbacFlow'
|
|
35
|
+
],
|
|
36
|
+
FlowRelationshipRole: [
|
|
22
37
|
'CreateRelationship',
|
|
23
38
|
'UpdateRelationship',
|
|
24
39
|
'DeleteRelationship',
|
|
@@ -31,6 +46,54 @@ const QUEUE_OWNERSHIP_PREFIXES = {
|
|
|
31
46
|
WebSocketMainRole: ['WebSocket']
|
|
32
47
|
};
|
|
33
48
|
|
|
49
|
+
const COMMON_S3_STATEMENTS = [
|
|
50
|
+
{
|
|
51
|
+
action: 's3:GetObject',
|
|
52
|
+
resource:
|
|
53
|
+
'arn:aws:s3:::${self:custom.iz_serviceSchemaBucketName}/perServiceSchemas/*'
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
action: 's3:GetObjectVersion',
|
|
57
|
+
resource:
|
|
58
|
+
'arn:aws:s3:::${self:custom.iz_serviceSchemaBucketName}/perServiceSchemas/*'
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
action: 's3:GetObject',
|
|
62
|
+
resource:
|
|
63
|
+
'arn:aws:s3:::${self:custom.iz_serviceSchemaBucketName}/serviceConfig/GraphServerTags.json'
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
action: 's3:GetObjectVersion',
|
|
67
|
+
resource:
|
|
68
|
+
'arn:aws:s3:::${self:custom.iz_serviceSchemaBucketName}/serviceConfig/GraphServerTags.json'
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
action: 's3:ListBucket',
|
|
72
|
+
resource: 'arn:aws:s3:::${self:custom.iz_serviceSchemaBucketName}'
|
|
73
|
+
}
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
const DEFAULT_DYNAMO_ACTIONS = [
|
|
77
|
+
'dynamodb:DeleteItem',
|
|
78
|
+
'dynamodb:GetItem',
|
|
79
|
+
'dynamodb:PutItem',
|
|
80
|
+
'dynamodb:Query',
|
|
81
|
+
'dynamodb:UpdateItem'
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
const DYNAMO_ACTIONS_BY_ROLE = {
|
|
85
|
+
RegisterRole: [],
|
|
86
|
+
WebSocketMainRole: ['dynamodb:DeleteItem', 'dynamodb:PutItem', 'dynamodb:Query']
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const SQS_CONSUMER_ACTIONS = [
|
|
90
|
+
'sqs:DeleteMessage',
|
|
91
|
+
'sqs:DeleteMessageBatch',
|
|
92
|
+
'sqs:GetQueueAttributes',
|
|
93
|
+
'sqs:GetQueueUrl',
|
|
94
|
+
'sqs:ReceiveMessage'
|
|
95
|
+
];
|
|
96
|
+
|
|
34
97
|
function upperCase(str) {
|
|
35
98
|
if (!str) return str;
|
|
36
99
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
@@ -46,74 +109,12 @@ async function readTextIfExists(filePath) {
|
|
|
46
109
|
|
|
47
110
|
function collectMatches(text, regex) {
|
|
48
111
|
const out = new Set();
|
|
112
|
+
|
|
49
113
|
for (const match of text.matchAll(regex)) {
|
|
50
114
|
if (match[1]) out.add(match[1]);
|
|
51
115
|
}
|
|
52
|
-
return out;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function collectExplicitResourceNames(outputPath) {
|
|
56
|
-
const appSourceDir = path.join(
|
|
57
|
-
outputPath,
|
|
58
|
-
'app',
|
|
59
|
-
'sls_yaml',
|
|
60
|
-
'generatedCode',
|
|
61
|
-
'source'
|
|
62
|
-
);
|
|
63
|
-
const resourceSourceDir = path.join(
|
|
64
|
-
outputPath,
|
|
65
|
-
'resource',
|
|
66
|
-
'sls_yaml',
|
|
67
|
-
'generatedCode',
|
|
68
|
-
'source'
|
|
69
|
-
);
|
|
70
116
|
|
|
71
|
-
|
|
72
|
-
readTextIfExists(path.join(resourceSourceDir, 'generated-dynamoDB-table.yml')),
|
|
73
|
-
readTextIfExists(path.join(resourceSourceDir, 'generated-sns-in-sqs.yml')),
|
|
74
|
-
readTextIfExists(path.join(resourceSourceDir, 'generated-sns-out.yml')),
|
|
75
|
-
...APP_SOURCE_FILES.map(fileName =>
|
|
76
|
-
readTextIfExists(path.join(appSourceDir, fileName))
|
|
77
|
-
)
|
|
78
|
-
]);
|
|
79
|
-
|
|
80
|
-
const [dynamoText, snsInSqsText, snsOutText, ...appSourceTexts] = resourceFiles;
|
|
81
|
-
const appJoinedText = appSourceTexts.join('\n');
|
|
82
|
-
|
|
83
|
-
const tableNames = new Set([
|
|
84
|
-
...collectMatches(
|
|
85
|
-
dynamoText,
|
|
86
|
-
/TableName:\s+\$\{self:custom\.iz_resourcePrefix\}([A-Za-z0-9]+)/g
|
|
87
|
-
)
|
|
88
|
-
]);
|
|
89
|
-
|
|
90
|
-
const queueNames = new Set([
|
|
91
|
-
...collectMatches(
|
|
92
|
-
snsInSqsText,
|
|
93
|
-
/QueueName:\s+\$\{self:custom\.iz_resourcePrefix\}([A-Za-z0-9]+)/g
|
|
94
|
-
),
|
|
95
|
-
...collectMatches(
|
|
96
|
-
appJoinedText,
|
|
97
|
-
/arn:aws:sqs:\$\{self:custom\.iz_region\}:\$\{self:custom\.iz_accountId\}:\$\{self:custom\.iz_resourcePrefix\}([A-Za-z0-9]+)/g
|
|
98
|
-
)
|
|
99
|
-
]);
|
|
100
|
-
|
|
101
|
-
const topicNames = new Set([
|
|
102
|
-
...collectMatches(
|
|
103
|
-
snsInSqsText,
|
|
104
|
-
/TopicName:\s+\$\{self:custom\.iz_serviceTag\}_\$\{self:custom\.iz_stage\}_([A-Za-z0-9]+)/g
|
|
105
|
-
),
|
|
106
|
-
...collectMatches(
|
|
107
|
-
snsOutText,
|
|
108
|
-
/TopicName:\s+\$\{self:custom\.iz_serviceTag\}_\$\{self:custom\.iz_stage\}_([A-Za-z0-9]+)/g
|
|
109
|
-
)
|
|
110
|
-
]);
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
tableNames: Array.from(tableNames).sort(),
|
|
114
|
-
queueNames: Array.from(queueNames).sort(),
|
|
115
|
-
topicNames: Array.from(topicNames).sort()
|
|
116
|
-
};
|
|
117
|
+
return out;
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
function splitTopLevelBlocks(text) {
|
|
@@ -127,6 +128,7 @@ function splitTopLevelBlocks(text) {
|
|
|
127
128
|
current = [line];
|
|
128
129
|
continue;
|
|
129
130
|
}
|
|
131
|
+
|
|
130
132
|
current.push(line);
|
|
131
133
|
}
|
|
132
134
|
|
|
@@ -135,35 +137,148 @@ function splitTopLevelBlocks(text) {
|
|
|
135
137
|
}
|
|
136
138
|
|
|
137
139
|
function rolePrefixes(roleName) {
|
|
138
|
-
if (QUEUE_OWNERSHIP_PREFIXES[roleName])
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (roleName.endsWith('Role')) {
|
|
143
|
-
return [roleName.replace(/Role$/, '')];
|
|
144
|
-
}
|
|
145
|
-
|
|
140
|
+
if (QUEUE_OWNERSHIP_PREFIXES[roleName]) return QUEUE_OWNERSHIP_PREFIXES[roleName];
|
|
141
|
+
if (roleName.endsWith('Role')) return [roleName.replace(/Role$/, '')];
|
|
146
142
|
return [roleName];
|
|
147
143
|
}
|
|
148
144
|
|
|
149
145
|
function topicBelongsToRole(roleName, topicName) {
|
|
146
|
+
if (topicName.endsWith('Complete_In')) return false;
|
|
147
|
+
|
|
150
148
|
const prefixes = rolePrefixes(roleName);
|
|
151
149
|
|
|
152
|
-
if (roleName ===
|
|
150
|
+
if (roleName === FLOW_OBJECTS_ROLE) {
|
|
151
|
+
const reservedPrefixes = [
|
|
152
|
+
...QUEUE_OWNERSHIP_PREFIXES.FlowRbacRole,
|
|
153
|
+
...QUEUE_OWNERSHIP_PREFIXES.FlowRelationshipRole
|
|
154
|
+
];
|
|
155
|
+
|
|
153
156
|
return (
|
|
154
157
|
prefixes.some(prefix => topicName.startsWith(prefix)) &&
|
|
158
|
+
!reservedPrefixes.some(prefix => topicName.startsWith(prefix)) &&
|
|
155
159
|
!topicName.includes('Relationship')
|
|
156
160
|
);
|
|
157
161
|
}
|
|
158
162
|
|
|
159
|
-
|
|
160
|
-
|
|
163
|
+
return prefixes.some(prefix => topicName.startsWith(prefix));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function buildDynamoArn(tableName) {
|
|
167
|
+
return (
|
|
168
|
+
'arn:aws:dynamodb:${self:custom.iz_region}:${self:custom.iz_accountId}:table/' +
|
|
169
|
+
'${self:custom.iz_resourcePrefix}' +
|
|
170
|
+
tableName
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function buildQueueArn(queueName) {
|
|
175
|
+
return (
|
|
176
|
+
'arn:aws:sqs:${self:custom.iz_region}:${self:custom.iz_accountId}:' +
|
|
177
|
+
'${self:custom.iz_resourcePrefix}' +
|
|
178
|
+
queueName
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function buildTopicArn(topicName) {
|
|
183
|
+
return (
|
|
184
|
+
'arn:aws:sns:${self:custom.iz_region}:${self:custom.iz_accountId}:' +
|
|
185
|
+
'${self:custom.iz_serviceTag}_${self:custom.iz_stage}_' +
|
|
186
|
+
topicName
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function buildWebSocketArn() {
|
|
191
|
+
return (
|
|
192
|
+
'arn:aws:execute-api:${self:custom.iz_region}:${self:custom.iz_accountId}:' +
|
|
193
|
+
'${self:custom.iz_webSocketHostId}/*'
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function detectRoleNamesFromSchemas(allSchemas = {}) {
|
|
198
|
+
const roleNames = new Set(['ProcFindDataRole', 'RegisterRole']);
|
|
199
|
+
const flows = getFlowsForGeneration(allSchemas);
|
|
200
|
+
|
|
201
|
+
if ((allSchemas.objects || []).length > 0) roleNames.add(FLOW_OBJECTS_ROLE);
|
|
202
|
+
if ((allSchemas.relationships || []).length > 0) roleNames.add('FlowRelationshipRole');
|
|
203
|
+
|
|
204
|
+
for (const flow of flows) {
|
|
205
|
+
if (!flow.flowTag) continue;
|
|
206
|
+
|
|
207
|
+
const upperFlowTag = upperCase(flow.flowTag);
|
|
208
|
+
|
|
209
|
+
if (QUEUE_OWNERSHIP_PREFIXES.FlowRbacRole.includes(upperFlowTag)) {
|
|
210
|
+
roleNames.add('FlowRbacRole');
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (upperFlowTag.endsWith('RbacFlow')) {
|
|
215
|
+
roleNames.add('FlowRbacRole');
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (QUEUE_OWNERSHIP_PREFIXES.FlowRelationshipRole.includes(upperFlowTag)) {
|
|
220
|
+
roleNames.add('FlowRelationshipRole');
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (QUEUE_OWNERSHIP_PREFIXES[FLOW_OBJECTS_ROLE].includes(upperFlowTag)) {
|
|
225
|
+
roleNames.add(FLOW_OBJECTS_ROLE);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if ((flow.event || []).includes('ownTopic')) {
|
|
230
|
+
roleNames.add('WebSocketMainRole');
|
|
231
|
+
// ponytail: removed continue; so custom flows with ownTopic get their own specific role
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
roleNames.add(`${upperFlowTag}Role`);
|
|
161
235
|
}
|
|
162
236
|
|
|
163
|
-
return
|
|
237
|
+
return roleNames;
|
|
164
238
|
}
|
|
165
239
|
|
|
166
|
-
async function
|
|
240
|
+
async function readRoleNames(outputPath, allSchemas) {
|
|
241
|
+
const roleConfigPath = path.join(
|
|
242
|
+
outputPath,
|
|
243
|
+
'app',
|
|
244
|
+
'sls_yaml',
|
|
245
|
+
'generatedCode',
|
|
246
|
+
'source',
|
|
247
|
+
'role-name-config.yml'
|
|
248
|
+
);
|
|
249
|
+
const roleConfigText = await readTextIfExists(roleConfigPath);
|
|
250
|
+
const fromFile = collectMatches(
|
|
251
|
+
roleConfigText,
|
|
252
|
+
/\$\{self:custom\.iz_resourcePrefix\}([A-Za-z0-9]+Role)/g
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
if (fromFile.size > 0) return new Set(fromFile);
|
|
256
|
+
return detectRoleNamesFromSchemas(allSchemas);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function collectExplicitResourceNames(outputPath) {
|
|
260
|
+
const resourceSourceDir = path.join(
|
|
261
|
+
outputPath,
|
|
262
|
+
'resource',
|
|
263
|
+
'sls_yaml',
|
|
264
|
+
'generatedCode',
|
|
265
|
+
'source'
|
|
266
|
+
);
|
|
267
|
+
const dynamoText = await readTextIfExists(
|
|
268
|
+
path.join(resourceSourceDir, 'generated-dynamoDB-table.yml')
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
tableNames: Array.from(
|
|
273
|
+
collectMatches(
|
|
274
|
+
dynamoText,
|
|
275
|
+
/TableName:\s+\$\{self:custom\.iz_resourcePrefix\}([A-Za-z0-9]+)/g
|
|
276
|
+
)
|
|
277
|
+
).sort()
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async function collectRoleScopedResourceNames(outputPath, allowedRoles) {
|
|
167
282
|
const appSourceDir = path.join(
|
|
168
283
|
outputPath,
|
|
169
284
|
'app',
|
|
@@ -190,142 +305,111 @@ async function collectRoleScopedResourceNames(outputPath) {
|
|
|
190
305
|
const appTexts = texts.slice(0, APP_SOURCE_FILES.length);
|
|
191
306
|
const snsInSqsText = texts[APP_SOURCE_FILES.length];
|
|
192
307
|
const snsOutText = texts[APP_SOURCE_FILES.length + 1];
|
|
308
|
+
const allTopics = new Set([
|
|
309
|
+
...collectMatches(
|
|
310
|
+
snsInSqsText,
|
|
311
|
+
/TopicName:\s+\$\{self:custom\.iz_serviceTag\}_\$\{self:custom\.iz_stage\}_([A-Za-z0-9_]+)/g
|
|
312
|
+
),
|
|
313
|
+
...collectMatches(
|
|
314
|
+
snsOutText,
|
|
315
|
+
/TopicName:\s+\$\{self:custom\.iz_serviceTag\}_\$\{self:custom\.iz_stage\}_([A-Za-z0-9_]+)/g
|
|
316
|
+
)
|
|
317
|
+
]);
|
|
193
318
|
|
|
194
319
|
const roleQueues = new Map();
|
|
195
320
|
|
|
321
|
+
for (const roleName of allowedRoles) {
|
|
322
|
+
roleQueues.set(roleName, new Set());
|
|
323
|
+
}
|
|
324
|
+
|
|
196
325
|
for (const text of appTexts) {
|
|
197
326
|
for (const block of splitTopLevelBlocks(text)) {
|
|
198
327
|
const roleMatch = block.match(/role:\s+([A-Za-z0-9]+)/);
|
|
199
|
-
if (!roleMatch) continue;
|
|
200
|
-
|
|
201
|
-
const
|
|
328
|
+
if (!roleMatch || !allowedRoles.has(roleMatch[1])) continue;
|
|
329
|
+
|
|
330
|
+
const queueNames = collectMatches(
|
|
202
331
|
block,
|
|
203
332
|
/arn:aws:sqs:\$\{self:custom\.iz_region\}:\$\{self:custom\.iz_accountId\}:\$\{self:custom\.iz_resourcePrefix\}([A-Za-z0-9]+)/g
|
|
204
333
|
);
|
|
205
|
-
|
|
206
|
-
for (const queueName of
|
|
207
|
-
roleQueues.get(
|
|
334
|
+
|
|
335
|
+
for (const queueName of queueNames) {
|
|
336
|
+
roleQueues.get(roleMatch[1]).add(queueName);
|
|
208
337
|
}
|
|
209
338
|
}
|
|
210
339
|
}
|
|
211
340
|
|
|
212
|
-
const topicToQueuePairs = [
|
|
213
|
-
...snsInSqsText.matchAll(
|
|
214
|
-
/TopicName:\s+\$\{self:custom\.iz_serviceTag\}_\$\{self:custom\.iz_stage\}_([A-Za-z0-9]+)[\s\S]*?Endpoint:\s+"arn:aws:sqs:\$\{self:custom\.iz_region\}:\$\{self:custom\.iz_accountId\}:\$\{self:custom\.iz_resourcePrefix\}([A-Za-z0-9]+)"/g
|
|
215
|
-
)
|
|
216
|
-
].map(match => ({
|
|
217
|
-
topicName: match[1],
|
|
218
|
-
queueName: match[2]
|
|
219
|
-
}));
|
|
220
|
-
|
|
221
|
-
const outTopics = Array.from(
|
|
222
|
-
collectMatches(
|
|
223
|
-
snsOutText,
|
|
224
|
-
/TopicName:\s+\$\{self:custom\.iz_serviceTag\}_\$\{self:custom\.iz_stage\}_([A-Za-z0-9]+)/g
|
|
225
|
-
)
|
|
226
|
-
);
|
|
227
|
-
|
|
228
341
|
const scoped = new Map();
|
|
229
342
|
|
|
230
|
-
for (const
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if (queues.has(queueName)) topics.add(topicName);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
for (const topicName of outTopics) {
|
|
239
|
-
if (topicBelongsToRole(roleName, topicName)) {
|
|
240
|
-
topics.add(topicName);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
343
|
+
for (const roleName of allowedRoles) {
|
|
344
|
+
const queueNames = Array.from(roleQueues.get(roleName) || []).sort();
|
|
345
|
+
const topicNames = Array.from(allTopics).filter(topicName => {
|
|
346
|
+
return topicBelongsToRole(roleName, topicName);
|
|
347
|
+
});
|
|
243
348
|
|
|
244
349
|
scoped.set(roleName, {
|
|
245
|
-
queueNames
|
|
246
|
-
topicNames:
|
|
350
|
+
queueNames,
|
|
351
|
+
topicNames: topicNames.sort()
|
|
247
352
|
});
|
|
248
353
|
}
|
|
249
354
|
|
|
250
355
|
return scoped;
|
|
251
356
|
}
|
|
252
357
|
|
|
253
|
-
function
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
{
|
|
271
|
-
|
|
272
|
-
resource:
|
|
273
|
-
'arn:aws:s3:::${self:custom.iz_serviceSchemaBucketName}/serviceConfig/GraphServerTags.json'
|
|
274
|
-
},
|
|
275
|
-
{
|
|
276
|
-
action: 's3:ListBucket',
|
|
277
|
-
resource: 'arn:aws:s3:::${self:custom.iz_serviceSchemaBucketName}'
|
|
358
|
+
function tableNamesForRole(roleName, tableNames) {
|
|
359
|
+
if (roleName === 'RegisterRole') return [];
|
|
360
|
+
|
|
361
|
+
if (roleName === 'WebSocketMainRole') {
|
|
362
|
+
const filtered = tableNames.filter(tableName => /websocket|upload/i.test(tableName));
|
|
363
|
+
// ponytail: fall back to all tables if websocket tables are not named consistently yet.
|
|
364
|
+
return filtered.length > 0 ? filtered : tableNames;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ponytail: least privilege for ProcFindDataRole, exclude system internal tables
|
|
368
|
+
if (roleName === 'ProcFindDataRole') {
|
|
369
|
+
return tableNames.filter(tableName => !/Awaiting|WebSocket|Upload/i.test(tableName));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const isFlowRole = roleName.endsWith('Role');
|
|
373
|
+
if (isFlowRole) {
|
|
374
|
+
const flowTables = tableNames.filter(tableName => !/FindDataMain|LogicalResults|RegisterRecords|Upload/i.test(tableName));
|
|
375
|
+
if (roleName === 'FlowRbacRole') {
|
|
376
|
+
return [...new Set([...flowTables, 'TargetRoles', 'RolePermissions', 'RoleUsers', 'UserRoles'])];
|
|
278
377
|
}
|
|
279
|
-
|
|
378
|
+
return flowTables;
|
|
379
|
+
}
|
|
280
380
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
381
|
+
return tableNames;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function dynamoActionsForRole(roleName) {
|
|
385
|
+
return DYNAMO_ACTIONS_BY_ROLE[roleName] || DEFAULT_DYNAMO_ACTIONS;
|
|
386
|
+
}
|
|
285
387
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
'dynamodb:DeleteItem',
|
|
294
|
-
'dynamodb:GetItem',
|
|
295
|
-
'dynamodb:PutItem',
|
|
296
|
-
'dynamodb:Query',
|
|
297
|
-
'dynamodb:UpdateItem'
|
|
298
|
-
]) {
|
|
299
|
-
statements.push({ action, resource: arn });
|
|
388
|
+
function buildRoleStatements(roleName, resources, scopedResources) {
|
|
389
|
+
const statements = [...COMMON_S3_STATEMENTS];
|
|
390
|
+
const scoped = scopedResources.get(roleName) || { queueNames: [], topicNames: [] };
|
|
391
|
+
|
|
392
|
+
for (const tableName of tableNamesForRole(roleName, resources.tableNames)) {
|
|
393
|
+
for (const action of dynamoActionsForRole(roleName)) {
|
|
394
|
+
statements.push({ action, resource: buildDynamoArn(tableName) });
|
|
300
395
|
}
|
|
301
396
|
}
|
|
302
397
|
|
|
303
|
-
for (const queueName of
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
'${self:custom.iz_resourcePrefix}' +
|
|
307
|
-
queueName;
|
|
308
|
-
|
|
309
|
-
for (const action of [
|
|
310
|
-
'sqs:DeleteMessage',
|
|
311
|
-
'sqs:DeleteMessageBatch',
|
|
312
|
-
'sqs:GetQueueAttributes',
|
|
313
|
-
'sqs:GetQueueUrl',
|
|
314
|
-
'sqs:ReceiveMessage',
|
|
315
|
-
'sqs:SendMessage'
|
|
316
|
-
]) {
|
|
317
|
-
statements.push({ action, resource: arn });
|
|
398
|
+
for (const queueName of scoped.queueNames) {
|
|
399
|
+
for (const action of SQS_CONSUMER_ACTIONS) {
|
|
400
|
+
statements.push({ action, resource: buildQueueArn(queueName) });
|
|
318
401
|
}
|
|
319
402
|
}
|
|
320
403
|
|
|
321
|
-
for (const topicName of
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
'${self:custom.iz_serviceTag}_${self:custom.iz_stage}_' +
|
|
325
|
-
topicName;
|
|
404
|
+
for (const topicName of scoped.topicNames) {
|
|
405
|
+
statements.push({ action: 'sns:Publish', resource: buildTopicArn(topicName) });
|
|
406
|
+
}
|
|
326
407
|
|
|
327
|
-
|
|
328
|
-
statements.push({
|
|
408
|
+
if (roleName === 'WebSocketMainRole') {
|
|
409
|
+
statements.push({
|
|
410
|
+
action: 'execute-api:ManageConnections',
|
|
411
|
+
resource: buildWebSocketArn()
|
|
412
|
+
});
|
|
329
413
|
}
|
|
330
414
|
|
|
331
415
|
return statements;
|
|
@@ -335,20 +419,16 @@ function groupStatementsByResources(statements) {
|
|
|
335
419
|
const grouped = new Map();
|
|
336
420
|
|
|
337
421
|
for (const statement of statements) {
|
|
338
|
-
const resources =
|
|
422
|
+
const resources = Array.isArray(statement.resource)
|
|
423
|
+
? [...statement.resource].sort()
|
|
424
|
+
: [statement.resource];
|
|
339
425
|
const key = JSON.stringify(resources);
|
|
340
426
|
|
|
341
427
|
if (!grouped.has(key)) {
|
|
342
|
-
grouped.set(key, {
|
|
343
|
-
action: new Set(),
|
|
344
|
-
resource: resources
|
|
345
|
-
});
|
|
428
|
+
grouped.set(key, { action: new Set(), resource: resources });
|
|
346
429
|
}
|
|
347
430
|
|
|
348
|
-
|
|
349
|
-
for (const action of statement.action || []) {
|
|
350
|
-
entry.action.add(action);
|
|
351
|
-
}
|
|
431
|
+
grouped.get(key).action.add(statement.action);
|
|
352
432
|
}
|
|
353
433
|
|
|
354
434
|
return [...grouped.values()].map(entry => ({
|
|
@@ -359,7 +439,7 @@ function groupStatementsByResources(statements) {
|
|
|
359
439
|
|
|
360
440
|
export async function generateSharedResourceYaml(allSchemas, options) {
|
|
361
441
|
console.log(
|
|
362
|
-
'
|
|
442
|
+
' [SharedResourceYamlGenerator] Generating Shared Resource YAML (IAM Roles)...'
|
|
363
443
|
);
|
|
364
444
|
|
|
365
445
|
const slsYamlDir = path.join(
|
|
@@ -371,101 +451,54 @@ export async function generateSharedResourceYaml(allSchemas, options) {
|
|
|
371
451
|
);
|
|
372
452
|
await fs.mkdir(slsYamlDir, { recursive: true });
|
|
373
453
|
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
roleNameConfigs.add('PerActionEndpointRole');
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
if (allSchemas.relationships && allSchemas.relationships.length > 0) {
|
|
384
|
-
roleNameConfigs.add('RelationshipRole');
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
const flows = getFlowsForGeneration(allSchemas);
|
|
388
|
-
if (
|
|
389
|
-
flows.length > 0 &&
|
|
390
|
-
flows.some(flow => flow.event && flow.event.includes('ownTopic'))
|
|
391
|
-
) {
|
|
392
|
-
roleNameConfigs.add('WebSocketMainRole');
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const standardRoles = [
|
|
396
|
-
'PerActionEndpointRole',
|
|
397
|
-
'RelationshipRole',
|
|
398
|
-
'WebSocketMainRole',
|
|
399
|
-
'ProcFindDataRole',
|
|
400
|
-
'RegisterRole'
|
|
401
|
-
];
|
|
402
|
-
|
|
403
|
-
for (const flow of flows) {
|
|
404
|
-
if (!flow.flowTag) continue;
|
|
405
|
-
const upperFlowTag = upperCase(flow.flowTag);
|
|
406
|
-
if (QUEUE_OWNERSHIP_PREFIXES.RelationshipRole.includes(upperFlowTag)) {
|
|
407
|
-
continue;
|
|
408
|
-
}
|
|
409
|
-
if (!standardRoles.includes(`${upperFlowTag}Role`)) {
|
|
410
|
-
roleNameConfigs.add(`${upperFlowTag}Role`);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
const allResources = await collectExplicitResourceNames(options.outputPath);
|
|
415
|
-
const scopedResources = await collectRoleScopedResourceNames(options.outputPath);
|
|
454
|
+
const allowedRoles = await readRoleNames(options.outputPath, allSchemas);
|
|
455
|
+
const resources = await collectExplicitResourceNames(options.outputPath);
|
|
456
|
+
const scopedResources = await collectRoleScopedResourceNames(
|
|
457
|
+
options.outputPath,
|
|
458
|
+
allowedRoles
|
|
459
|
+
);
|
|
416
460
|
|
|
417
|
-
for (const roleName of
|
|
461
|
+
for (const roleName of allowedRoles) {
|
|
418
462
|
policyRegistry.addMany(
|
|
419
463
|
roleName,
|
|
420
|
-
|
|
464
|
+
buildRoleStatements(roleName, resources, scopedResources)
|
|
421
465
|
);
|
|
422
|
-
|
|
423
|
-
if (roleName === 'WebSocketMainRole') {
|
|
424
|
-
policyRegistry.addMany(roleName, [
|
|
425
|
-
{
|
|
426
|
-
action: 'execute-api:ManageConnections',
|
|
427
|
-
resource:
|
|
428
|
-
'arn:aws:execute-api:${self:custom.iz_region}:${self:custom.iz_accountId}:${self:custom.iz_webSocketHostId}/*'
|
|
429
|
-
},
|
|
430
|
-
{
|
|
431
|
-
action: 'execute-api:Invoke',
|
|
432
|
-
resource:
|
|
433
|
-
'arn:aws:execute-api:${self:custom.iz_region}:${self:custom.iz_accountId}:${self:custom.iz_webSocketHostId}/*'
|
|
434
|
-
}
|
|
435
|
-
]);
|
|
436
|
-
}
|
|
437
466
|
}
|
|
438
467
|
|
|
439
468
|
const groupedStatements = policyRegistry.toGroupedByRole();
|
|
440
|
-
const
|
|
441
|
-
roleName
|
|
442
|
-
|
|
443
|
-
groupedStatements[roleName]?.statements || []
|
|
444
|
-
)
|
|
445
|
-
}));
|
|
469
|
+
const unexpectedRoles = Object.keys(groupedStatements).filter(
|
|
470
|
+
roleName => !allowedRoles.has(roleName)
|
|
471
|
+
);
|
|
446
472
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
} catch (error) {
|
|
452
|
-
console.error('Error reading SharedResource YAML template:', error);
|
|
453
|
-
return;
|
|
473
|
+
if (unexpectedRoles.length > 0) {
|
|
474
|
+
console.warn(
|
|
475
|
+
` [SharedResourceYamlGenerator] Ignoring roles not present in role-name-config.yml: ${unexpectedRoles.join(', ')}`
|
|
476
|
+
);
|
|
454
477
|
}
|
|
455
478
|
|
|
479
|
+
const roles = Array.from(allowedRoles)
|
|
480
|
+
.sort()
|
|
481
|
+
.map(roleName => ({
|
|
482
|
+
roleName,
|
|
483
|
+
statements: groupStatementsByResources(groupedStatements[roleName]?.statements || [])
|
|
484
|
+
}));
|
|
485
|
+
|
|
486
|
+
const templatePath = path.join(__dirname, 'templates', 'SharedResource_Yaml.ejs');
|
|
487
|
+
const templateString = await fs.readFile(templatePath, 'utf-8');
|
|
456
488
|
const fileContent = ejs.render(templateString, { roles });
|
|
457
489
|
let normalizedContent = fileContent
|
|
458
490
|
.split('\n')
|
|
459
491
|
.filter(line => line.trim() !== '')
|
|
460
492
|
.join('\n');
|
|
461
|
-
|
|
462
|
-
normalizedContent =
|
|
463
|
-
|
|
464
|
-
|
|
493
|
+
|
|
494
|
+
normalizedContent =
|
|
495
|
+
normalizedContent
|
|
496
|
+
.replace(/^ [A-Za-z0-9]+:/gm, '\n\n$&')
|
|
497
|
+
.replace(/^Resources:\n+ /, 'Resources:\n\n ') + '\n';
|
|
465
498
|
|
|
466
499
|
const outputPath = path.join(slsYamlDir, 'generate-shared-resource.yml');
|
|
467
500
|
await fs.writeFile(outputPath, normalizedContent, 'utf-8');
|
|
468
501
|
console.log(
|
|
469
|
-
`
|
|
502
|
+
` [SharedResourceYamlGenerator] Wrote generate-shared-resource.yml to ${outputPath}`
|
|
470
503
|
);
|
|
471
504
|
}
|