@cloud-copilot/iam-expand 0.1.0
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/LICENSE.txt +674 -0
- package/README.md +269 -0
- package/dist/cjs/cli.d.ts +3 -0
- package/dist/cjs/cli.d.ts.map +1 -0
- package/dist/cjs/cli.js +76 -0
- package/dist/cjs/cli.js.map +1 -0
- package/dist/cjs/cli_utils.d.ts +27 -0
- package/dist/cjs/cli_utils.d.ts.map +1 -0
- package/dist/cjs/cli_utils.js +69 -0
- package/dist/cjs/cli_utils.js.map +1 -0
- package/dist/cjs/expand.d.ts +69 -0
- package/dist/cjs/expand.d.ts.map +1 -0
- package/dist/cjs/expand.js +118 -0
- package/dist/cjs/expand.js.map +1 -0
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +18 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/stdin.d.ts +7 -0
- package/dist/cjs/stdin.d.ts.map +1 -0
- package/dist/cjs/stdin.js +34 -0
- package/dist/cjs/stdin.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +67 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli_utils.d.ts +27 -0
- package/dist/cli_utils.d.ts.map +1 -0
- package/dist/cli_utils.js +57 -0
- package/dist/cli_utils.js.map +1 -0
- package/dist/cli_utils.test.d.ts +2 -0
- package/dist/cli_utils.test.d.ts.map +1 -0
- package/dist/cli_utils.test.js +90 -0
- package/dist/cli_utils.test.js.map +1 -0
- package/dist/esm/cli.d.ts +3 -0
- package/dist/esm/cli.d.ts.map +1 -0
- package/dist/esm/cli.js +74 -0
- package/dist/esm/cli.js.map +1 -0
- package/dist/esm/cli_utils.d.ts +27 -0
- package/dist/esm/cli_utils.d.ts.map +1 -0
- package/dist/esm/cli_utils.js +63 -0
- package/dist/esm/cli_utils.js.map +1 -0
- package/dist/esm/expand.d.ts +69 -0
- package/dist/esm/expand.d.ts.map +1 -0
- package/dist/esm/expand.js +114 -0
- package/dist/esm/expand.js.map +1 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/stdin.d.ts +7 -0
- package/dist/esm/stdin.d.ts.map +1 -0
- package/dist/esm/stdin.js +31 -0
- package/dist/esm/stdin.js.map +1 -0
- package/dist/expand.d.ts +55 -0
- package/dist/expand.d.ts.map +1 -0
- package/dist/expand.js +94 -0
- package/dist/expand.js.map +1 -0
- package/dist/expand.test.d.ts +2 -0
- package/dist/expand.test.d.ts.map +1 -0
- package/dist/expand.test.js +382 -0
- package/dist/expand.test.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/stdin.d.ts +7 -0
- package/dist/stdin.d.ts.map +1 -0
- package/dist/stdin.js +35 -0
- package/dist/stdin.js.map +1 -0
- package/package.json +33 -0
- package/postbuild.sh +13 -0
- package/src/cli.ts +82 -0
- package/src/cli_utils.test.ts +131 -0
- package/src/cli_utils.ts +78 -0
- package/src/expand.test.ts +523 -0
- package/src/expand.ts +185 -0
- package/src/index.ts +1 -0
- package/src/stdin.ts +34 -0
- package/tsconfig.cjs.json +11 -0
- package/tsconfig.esm.json +14 -0
- package/tsconfig.json +22 -0
package/src/expand.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { iamActionExists, iamActionsForService, iamServiceExists, iamServiceKeys } from '@cloud-copilot/iam-data'
|
|
2
|
+
|
|
3
|
+
export enum InvalidActionBehavior {
|
|
4
|
+
Remove = "Remove",
|
|
5
|
+
Error = "Error",
|
|
6
|
+
Include = "Include",
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Options for the expand function
|
|
11
|
+
*
|
|
12
|
+
*/
|
|
13
|
+
export interface ExpandIamActionsOptions {
|
|
14
|
+
/**
|
|
15
|
+
* If true, a single `*` will be expanded to all actions for all services
|
|
16
|
+
* If false, a single `*` will be returned as is
|
|
17
|
+
* Default: false
|
|
18
|
+
*/
|
|
19
|
+
expandAsterik: boolean
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* If true, `service:*` will be expanded to all actions for that service
|
|
23
|
+
* If false, `service:*` will be returned as is
|
|
24
|
+
* Default: false
|
|
25
|
+
*/
|
|
26
|
+
expandServiceAsterik: boolean
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* If true, an error will be thrown if the action string is not in the correct format
|
|
30
|
+
* If false, an empty array will be returned
|
|
31
|
+
* Default: false
|
|
32
|
+
*/
|
|
33
|
+
errorOnInvalidFormat: boolean
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* If true, an error will be thrown if the service in the action string does not exist
|
|
37
|
+
* If false, an empty array will be returned
|
|
38
|
+
* Default: false
|
|
39
|
+
*/
|
|
40
|
+
errorOnMissingService: boolean
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* If true, only unique values will be returned, while maintaining order
|
|
44
|
+
* If false, all values will be returned, even if they are duplicates
|
|
45
|
+
* Default: false
|
|
46
|
+
*/
|
|
47
|
+
distinct: boolean
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The behavior to use when an invalid action is encountered without wildcards
|
|
52
|
+
* @{InvalidActionBehavior.Remove} will remove the invalid action from the output
|
|
53
|
+
* @{InvalidActionBehavior.Error} will throw an error if an invalid action is encountered
|
|
54
|
+
* @{InvalidActionBehavior.Include} will include the invalid action in the output
|
|
55
|
+
*
|
|
56
|
+
* Default: InvalidActionBehavior.Remove
|
|
57
|
+
*/
|
|
58
|
+
invalidActionBehavior: InvalidActionBehavior
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* If true, the returned array will be sorted
|
|
62
|
+
* If false, the returned array will be in the order they were expanded
|
|
63
|
+
* Default: false
|
|
64
|
+
*/
|
|
65
|
+
sort: boolean
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const defaultOptions: ExpandIamActionsOptions = {
|
|
69
|
+
expandAsterik: false,
|
|
70
|
+
expandServiceAsterik: false,
|
|
71
|
+
errorOnInvalidFormat: false,
|
|
72
|
+
errorOnMissingService: false,
|
|
73
|
+
invalidActionBehavior: InvalidActionBehavior.Remove,
|
|
74
|
+
distinct: false,
|
|
75
|
+
sort: false
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const allAsteriksPattern = /^\*+$/i
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Expands an IAM action string that contains wildcards.
|
|
82
|
+
* If the action string contains no wildcards, it is returned as is.
|
|
83
|
+
* @see {@link ExpandIamActionsOptions} for options to customize behavior
|
|
84
|
+
*
|
|
85
|
+
* If any options are set to throw an error, the function will throw an error if validation fails for a single value.
|
|
86
|
+
*
|
|
87
|
+
* @param actionStringOrStrings An IAM action or array IAM action(s) that may contain wildcards
|
|
88
|
+
* @param overrideOptions Options to override the default behavior
|
|
89
|
+
* @returns An array of expanded action strings flattend to a single array
|
|
90
|
+
*/
|
|
91
|
+
export function expandIamActions(actionStringOrStrings: string | string[], overrideOptions?: Partial<ExpandIamActionsOptions>): string[] {
|
|
92
|
+
const options = {...defaultOptions, ...overrideOptions}
|
|
93
|
+
|
|
94
|
+
if(!actionStringOrStrings) {
|
|
95
|
+
//Just in case the user passes in null or undefined
|
|
96
|
+
return []
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if(Array.isArray(actionStringOrStrings)) {
|
|
100
|
+
let allMatches = actionStringOrStrings.flatMap(actionString => expandIamActions(actionString, options))
|
|
101
|
+
if(options.distinct) {
|
|
102
|
+
const aSet = new Set<string>()
|
|
103
|
+
allMatches = allMatches.filter((value) => {
|
|
104
|
+
if(aSet.has(value)) {
|
|
105
|
+
return false
|
|
106
|
+
}
|
|
107
|
+
aSet.add(value)
|
|
108
|
+
return true
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
if(options.sort) {
|
|
112
|
+
allMatches.sort()
|
|
113
|
+
}
|
|
114
|
+
return allMatches
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const actionString = actionStringOrStrings.trim()
|
|
118
|
+
|
|
119
|
+
if(actionString.match(allAsteriksPattern)) {
|
|
120
|
+
if(options.expandAsterik) {
|
|
121
|
+
//If that's really what you want...
|
|
122
|
+
return iamServiceKeys().flatMap(
|
|
123
|
+
service => iamActionsForService(service).map(action => `${service}:${action}`)
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
return ['*']
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if(!actionString.includes(':')) {
|
|
130
|
+
if(options.errorOnInvalidFormat) {
|
|
131
|
+
throw new Error(`Invalid action format: ${actionString}`)
|
|
132
|
+
}
|
|
133
|
+
return []
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const parts = actionString.split(':')
|
|
137
|
+
if(parts.length !== 2) {
|
|
138
|
+
if(options.errorOnInvalidFormat) {
|
|
139
|
+
throw new Error(`Invalid action format: ${actionString}`)
|
|
140
|
+
}
|
|
141
|
+
return []
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const [service, wildcardActions] = parts.map(part => part.toLowerCase())
|
|
145
|
+
if(!iamServiceExists(service)) {
|
|
146
|
+
if(options.errorOnMissingService) {
|
|
147
|
+
throw new Error(`Service not found: ${service}`)
|
|
148
|
+
}
|
|
149
|
+
return []
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if(wildcardActions.match(allAsteriksPattern)) {
|
|
153
|
+
if(options.expandServiceAsterik) {
|
|
154
|
+
return iamActionsForService(service).map(action => `${service}:${action}`)
|
|
155
|
+
}
|
|
156
|
+
return [`${service}:*`]
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if(!actionString.includes('*')) {
|
|
160
|
+
const actionExists = iamActionExists(service, wildcardActions)
|
|
161
|
+
if(actionExists) {
|
|
162
|
+
return [actionString]
|
|
163
|
+
}
|
|
164
|
+
if(options.invalidActionBehavior === InvalidActionBehavior.Remove) {
|
|
165
|
+
return []
|
|
166
|
+
} else if(options.invalidActionBehavior === InvalidActionBehavior.Include) {
|
|
167
|
+
return [actionString]
|
|
168
|
+
} else if(options.invalidActionBehavior === InvalidActionBehavior.Error) {
|
|
169
|
+
throw new Error(`Invalid action: ${actionString}`)
|
|
170
|
+
} else {
|
|
171
|
+
//This should never happen
|
|
172
|
+
throw new Error(`Invalid invalidActionBehavior: ${options.invalidActionBehavior}`)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const allActions = iamActionsForService(service)
|
|
177
|
+
const pattern = "^" + wildcardActions.replace(/\*/g, '.*?') + "$"
|
|
178
|
+
const regex = new RegExp(pattern, 'i')
|
|
179
|
+
const matchingActions = allActions.filter(action => regex.test(action)).map(action => `${service}:${action}`)
|
|
180
|
+
if(options.sort) {
|
|
181
|
+
matchingActions.sort()
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return matchingActions
|
|
185
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './expand.js';
|
package/src/stdin.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { stdin } from 'process';
|
|
2
|
+
/**
|
|
3
|
+
* Read from stdin until the stream ends, timeout, or an error occurs
|
|
4
|
+
*
|
|
5
|
+
* @returns the string input from stdin
|
|
6
|
+
*/
|
|
7
|
+
export async function readStdin(readWait: number | undefined): Promise<string> {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
// If the input is not a TTY, we are most likely receiving data from a pipe.
|
|
10
|
+
const definitelyReceivingData = !process.stdin.isTTY
|
|
11
|
+
if(!readWait || readWait <= 0) {
|
|
12
|
+
readWait = definitelyReceivingData ? 10_000 : 20
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let data = '';
|
|
16
|
+
setTimeout(() => {
|
|
17
|
+
if(data.length === 0) {
|
|
18
|
+
resolve(data)
|
|
19
|
+
}
|
|
20
|
+
}, readWait)
|
|
21
|
+
|
|
22
|
+
stdin.on('data', (chunk) => {
|
|
23
|
+
data += chunk;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
stdin.on('end', () => {
|
|
27
|
+
resolve(data);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
stdin.on('error', (err) => {
|
|
31
|
+
reject(err);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "commonjs",
|
|
4
|
+
"target": "es2022",
|
|
5
|
+
"outDir": "dist",
|
|
6
|
+
"rootDir": "src",
|
|
7
|
+
"sourceMap": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"declarationMap": true,
|
|
11
|
+
"lib": ["es2023", "DOM"],
|
|
12
|
+
"noUnusedLocals": false,
|
|
13
|
+
"noUnusedParameters": false,
|
|
14
|
+
"noImplicitReturns": true,
|
|
15
|
+
"noFallthroughCasesInSwitch": false,
|
|
16
|
+
"experimentalDecorators": true,
|
|
17
|
+
"emitDecoratorMetadata": true,
|
|
18
|
+
"esModuleInterop": false,
|
|
19
|
+
"forceConsistentCasingInFileNames": true,
|
|
20
|
+
},
|
|
21
|
+
"exclude": ["tests", "test", "dist", "bin", "**/bin", "**/dist", "node_modules", "cdk.out"]
|
|
22
|
+
}
|