@nitra/cfr 0.5.0 → 0.6.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/README.md +14 -4
- package/lib/get-resources.mjs +147 -25
- package/lib/k8s-rest.mjs +20 -4
- package/lib/kcc-inventory.mjs +3 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -93,10 +93,16 @@ heard of, and never will until someone points it out.
|
|
|
93
93
|
|
|
94
94
|
`kcc-inventory` is that someone. Per namespace (any namespace carrying the
|
|
95
95
|
annotation `cnrm.cloud.google.com/project-id`), it compares what's live in
|
|
96
|
-
the GCP project against what's declared under KCC,
|
|
97
|
-
|
|
98
|
-
`
|
|
99
|
-
|
|
96
|
+
the GCP project against what's declared under KCC. Besides IAM, GKE,
|
|
97
|
+
Artifact Registry, buckets, addresses and Cloud DNS, it includes Cloud Run
|
|
98
|
+
(`RunService`, `RunJob`), `CloudSchedulerJob`, `EventarcTrigger`, Pub/Sub,
|
|
99
|
+
Secret Manager, VPC Access, KMS, and the Cloud Run HTTP(S) load-balancer
|
|
100
|
+
chain (network, subnetwork, backend service, serverless NEG, URL map,
|
|
101
|
+
target HTTPS proxy, global forwarding rule, and SSL certificates).
|
|
102
|
+
|
|
103
|
+
Location-scoped resources use the canonical `location/name` ID, preventing
|
|
104
|
+
resources with the same name in different regions from being merged. IAM
|
|
105
|
+
bindings on a `RunService` are normalized to the same identity.
|
|
100
106
|
|
|
101
107
|
Read-only — it reports, it doesn't touch anything.
|
|
102
108
|
|
|
@@ -189,6 +195,10 @@ carries the same stale-cache/GKE-managed notes described above.
|
|
|
189
195
|
Same requirements as `kcc-inventory` — no `gcloud`/`kubectl` needed, GKE
|
|
190
196
|
only, `--include-system` to see GCP-managed noise.
|
|
191
197
|
|
|
198
|
+
## Changelog
|
|
199
|
+
|
|
200
|
+
See [CHANGELOG.md](https://github.com/nitra/cfr/blob/main/CHANGELOG.md).
|
|
201
|
+
|
|
192
202
|
## License
|
|
193
203
|
|
|
194
204
|
MIT
|
package/lib/get-resources.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Сирий збір фактів для одного KCC namespace: що є "живого" в GCP-проєкті і
|
|
2
|
-
// що заявлено як Config Connector CR у цьому namespace — для кожного з
|
|
2
|
+
// що заявлено як Config Connector CR у цьому namespace — для кожного з
|
|
3
3
|
// видів ресурсів. Жодного diff-у, жодного форматування, жодного I/O в
|
|
4
4
|
// консоль: тільки звернення до GCP REST API (gcp-rest.mjs) і Kubernetes
|
|
5
5
|
// REST API (k8s-rest.mjs), і нормалізація результату в плаский список.
|
|
@@ -24,6 +24,23 @@ export const KIND_ORDER = [
|
|
|
24
24
|
'ComputeAddress',
|
|
25
25
|
'DNSManagedZone',
|
|
26
26
|
'DNSRecordSet',
|
|
27
|
+
'RunService',
|
|
28
|
+
'RunJob',
|
|
29
|
+
'CloudSchedulerJob',
|
|
30
|
+
'EventarcTrigger',
|
|
31
|
+
'PubSubTopic',
|
|
32
|
+
'PubSubSubscription',
|
|
33
|
+
'SecretManagerSecret',
|
|
34
|
+
'VPCAccessConnector',
|
|
35
|
+
'ComputeNetwork',
|
|
36
|
+
'ComputeSubnetwork',
|
|
37
|
+
'KMSCryptoKey',
|
|
38
|
+
'ComputeBackendService',
|
|
39
|
+
'ComputeNetworkEndpointGroup',
|
|
40
|
+
'ComputeURLMap',
|
|
41
|
+
'ComputeTargetHTTPSProxy',
|
|
42
|
+
'ComputeGlobalForwardingRule',
|
|
43
|
+
'ComputeSSLCertificate',
|
|
27
44
|
'IAMPolicyMember',
|
|
28
45
|
];
|
|
29
46
|
|
|
@@ -62,7 +79,7 @@ async function listRegionalAddressIds(project) {
|
|
|
62
79
|
for (const [scopeKey, val] of Object.entries(data.items || {})) {
|
|
63
80
|
if (!val.addresses) continue;
|
|
64
81
|
const region = scopeKey.replace(/^regions\//, '');
|
|
65
|
-
for (const a of val.addresses) out.push(`${a.name}
|
|
82
|
+
for (const a of val.addresses) out.push(`${region}/${a.name}`);
|
|
66
83
|
}
|
|
67
84
|
pageToken = data.nextPageToken;
|
|
68
85
|
} while (pageToken);
|
|
@@ -71,13 +88,46 @@ async function listRegionalAddressIds(project) {
|
|
|
71
88
|
|
|
72
89
|
async function listGlobalAddressIds(project) {
|
|
73
90
|
const items = await paginate(`${COMPUTE_API}/projects/${project}/global/addresses`, { pageSize: 500 }, 'items');
|
|
74
|
-
return items.map((a) =>
|
|
91
|
+
return items.map((a) => `global/${a.name}`);
|
|
75
92
|
}
|
|
76
93
|
|
|
77
94
|
function stripPrefix(s, prefix) {
|
|
78
95
|
return s.startsWith(prefix) ? s.slice(prefix.length) : s;
|
|
79
96
|
}
|
|
80
97
|
|
|
98
|
+
// Один ID для всіх location/region/zone ресурсів: location/name. Це не дає
|
|
99
|
+
// однаковим іменам у різних регіонах зливатися в один inventory запис.
|
|
100
|
+
function assetScopedId(asset, collection) {
|
|
101
|
+
const m = (asset.name || '').match(new RegExp(`/(?:locations|regions|zones)/([^/]+)/${collection}/([^/]+)$`));
|
|
102
|
+
return m && `${m[1]}/${m[2]}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function kccScopedId(item, { defaultLocation, name = (i) => i.spec && i.spec.resourceID } = {}) {
|
|
106
|
+
const spec = item.spec || {};
|
|
107
|
+
const resourceName = name(item) || (item.metadata && item.metadata.name);
|
|
108
|
+
const location = spec.location || spec.region || spec.zone || defaultLocation;
|
|
109
|
+
return location && resourceName ? `${location}/${resourceName}` : null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function resourceId(item) {
|
|
113
|
+
return (item.spec && item.spec.resourceID) || (item.metadata && item.metadata.name);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function liveScopedIds(assets, type, collection) {
|
|
117
|
+
return assets.filter((a) => a.assetType === type).map((a) => assetScopedId(a, collection)).filter(Boolean);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function assetGlobalOrScopedId(asset, collection) {
|
|
121
|
+
const scoped = assetScopedId(asset, collection);
|
|
122
|
+
if (scoped) return scoped;
|
|
123
|
+
const global = (asset.name || '').match(new RegExp(`/global/${collection}/([^/]+)$`));
|
|
124
|
+
return global && `global/${global[1]}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function liveGlobalOrScopedIds(assets, type, collection) {
|
|
128
|
+
return assets.filter((a) => a.assetType === type).map((a) => assetGlobalOrScopedId(a, collection)).filter(Boolean);
|
|
129
|
+
}
|
|
130
|
+
|
|
81
131
|
// --- фільтри GCP-системного шуму (див. includeSystem) ----------------------
|
|
82
132
|
|
|
83
133
|
const SA_SYSTEM = [
|
|
@@ -121,6 +171,8 @@ function assetResId(entry, project) {
|
|
|
121
171
|
const mAr = res.match(/\/repositories\/([^/]+)$/);
|
|
122
172
|
if (mAr && assetType === 'artifactregistry.googleapis.com/Repository') return `ar/${mAr[1]}`;
|
|
123
173
|
if (assetType === 'storage.googleapis.com/Bucket') return 'bucket/' + stripPrefix(res, '//storage.googleapis.com/');
|
|
174
|
+
const mRunService = res.match(/\/projects\/[^/]+\/locations\/([^/]+)\/services\/([^/]+)$/);
|
|
175
|
+
if (mRunService && assetType === 'run.googleapis.com/Service') return `run-service/${mRunService[1]}/${mRunService[2]}`;
|
|
124
176
|
return 'other/' + stripPrefix(res, '//');
|
|
125
177
|
}
|
|
126
178
|
|
|
@@ -141,7 +193,7 @@ function kccSaId(ref, project) {
|
|
|
141
193
|
return '?';
|
|
142
194
|
}
|
|
143
195
|
|
|
144
|
-
function kccPolicyMemberId(item, project) {
|
|
196
|
+
function kccPolicyMemberId(item, project, runServiceRefs) {
|
|
145
197
|
const ref = (item.spec && item.spec.resourceRef) || {};
|
|
146
198
|
switch (ref.kind) {
|
|
147
199
|
case 'Project':
|
|
@@ -152,6 +204,12 @@ function kccPolicyMemberId(item, project) {
|
|
|
152
204
|
return 'ar/' + kccArId(ref);
|
|
153
205
|
case 'StorageBucket':
|
|
154
206
|
return 'bucket/' + (ref.external || ref.name || '?');
|
|
207
|
+
case 'RunService': {
|
|
208
|
+
const external = ref.external || '';
|
|
209
|
+
const m = external.match(/projects\/[^/]+\/locations\/([^/]+)\/services\/([^/]+)$/);
|
|
210
|
+
if (m) return `run-service/${m[1]}/${m[2]}`;
|
|
211
|
+
return runServiceRefs.get(ref.name) || `run-service/?/${ref.name || '?'}`;
|
|
212
|
+
}
|
|
155
213
|
default:
|
|
156
214
|
return 'other/' + (ref.external || ref.name || '?');
|
|
157
215
|
}
|
|
@@ -227,39 +285,36 @@ export async function collectNamespace(namespace, { includeSystem = false } = {}
|
|
|
227
285
|
)).map((n) => n.split('/').pop()), 'kcc');
|
|
228
286
|
|
|
229
287
|
// --- ArtifactRegistryRepository -------------------------------------------
|
|
230
|
-
let liveAr =
|
|
231
|
-
.map((a) => {
|
|
232
|
-
const m = (a.name || '').match(/\/repositories\/([^/]+)$/);
|
|
233
|
-
return m && m[1];
|
|
234
|
-
})
|
|
235
|
-
.filter(Boolean);
|
|
288
|
+
let liveAr = liveScopedIds(assets, 'artifactregistry.googleapis.com/Repository', 'repositories');
|
|
236
289
|
if (!includeSystem) liveAr = liveAr.filter((r) => !isSystem(AR_SYSTEM, r));
|
|
237
290
|
push('ArtifactRegistryRepository', liveAr, 'gcp');
|
|
238
291
|
push('ArtifactRegistryRepository', await kccField(
|
|
239
292
|
'ArtifactRegistryRepository', namespace,
|
|
240
|
-
(i) => i
|
|
293
|
+
(i) => kccScopedId(i),
|
|
241
294
|
), 'kcc');
|
|
242
295
|
|
|
243
296
|
// --- ContainerCluster + ContainerNodePool ---------------------------------
|
|
244
|
-
push('ContainerCluster',
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
), 'kcc');
|
|
297
|
+
push('ContainerCluster', liveScopedIds(assets, 'container.googleapis.com/Cluster', 'clusters'), 'gcp');
|
|
298
|
+
const containerClusterItems = await listCustomObjects('ContainerCluster', namespace);
|
|
299
|
+
push('ContainerCluster', containerClusterItems.map((i) => kccScopedId(i)).filter(Boolean), 'kcc');
|
|
300
|
+
const clusterRefs = new Map(containerClusterItems.map((i) => [i.metadata && i.metadata.name, kccScopedId(i)]));
|
|
249
301
|
|
|
250
302
|
const livePool = [];
|
|
251
303
|
for (const a of byType('container.googleapis.com/NodePool')) {
|
|
252
304
|
const m = (a.name || '').match(/\/clusters\/([^/]+)\/nodePools\/([^/]+)$/);
|
|
253
305
|
if (!m) continue;
|
|
254
306
|
const [, cluster, pool] = m;
|
|
255
|
-
|
|
307
|
+
const location = (a.name || '').match(/\/locations\/([^/]+)\/clusters\/[^/]+\/nodePools\/[^/]+$/)?.[1];
|
|
308
|
+
if (includeSystem || !NODEPOOL_SYSTEM.test(pool)) livePool.push(location ? `${location}/${cluster}/${pool}` : `${cluster}/${pool}`);
|
|
256
309
|
}
|
|
257
310
|
push('ContainerNodePool', livePool, 'gcp');
|
|
258
311
|
push('ContainerNodePool', await kccField(
|
|
259
312
|
'ContainerNodePool', namespace,
|
|
260
313
|
(i) => {
|
|
261
314
|
const ref = (i.spec && i.spec.clusterRef) || {};
|
|
262
|
-
|
|
315
|
+
const external = ref.external || '';
|
|
316
|
+
const m = external.match(/\/locations\/([^/]+)\/clusters\/([^/]+)$/);
|
|
317
|
+
return m ? `${m[1]}/${m[2]}/${resourceId(i)}` : `${clusterRefs.get(ref.name) || ref.name || external}/${resourceId(i)}`;
|
|
263
318
|
},
|
|
264
319
|
), 'kcc');
|
|
265
320
|
|
|
@@ -286,14 +341,14 @@ export async function collectNamespace(namespace, { includeSystem = false } = {}
|
|
|
286
341
|
const liveAddrIds = new Set([...regionalAddrIds, ...globalAddrIds]);
|
|
287
342
|
const staleAddrCount0 = byType('compute.googleapis.com/Address', 'compute.googleapis.com/GlobalAddress').length;
|
|
288
343
|
const liveAddr = byType('compute.googleapis.com/Address', 'compute.googleapis.com/GlobalAddress')
|
|
289
|
-
.map((a) => `${a.
|
|
344
|
+
.map((a) => `${a.location === 'global' ? 'global' : a.location}/${a.displayName}`)
|
|
290
345
|
.filter((id) => liveAddrIds.has(id));
|
|
291
346
|
const staleAddrCount = staleAddrCount0 - liveAddr.length;
|
|
292
347
|
if (staleAddrCount) diagnostics.push({ kind: 'ComputeAddress', type: 'stale-cache', count: staleAddrCount });
|
|
293
348
|
push('ComputeAddress', liveAddr, 'gcp');
|
|
294
349
|
push('ComputeAddress', await kccField(
|
|
295
350
|
'ComputeAddress', namespace,
|
|
296
|
-
(i) =>
|
|
351
|
+
(i) => kccScopedId(i, { defaultLocation: 'global' }),
|
|
297
352
|
), 'kcc');
|
|
298
353
|
|
|
299
354
|
// --- DNSManagedZone + DNSRecordSet -------------------------------------------
|
|
@@ -352,6 +407,72 @@ export async function collectNamespace(namespace, { includeSystem = false } = {}
|
|
|
352
407
|
},
|
|
353
408
|
), 'kcc');
|
|
354
409
|
|
|
410
|
+
// --- Cloud Run та його тригери/залежності ---------------------------------
|
|
411
|
+
push('RunService', liveScopedIds(assets, 'run.googleapis.com/Service', 'services'), 'gcp');
|
|
412
|
+
const runServiceItems = await listCustomObjects('RunService', namespace);
|
|
413
|
+
push('RunService', runServiceItems.map((i) => kccScopedId(i)).filter(Boolean), 'kcc');
|
|
414
|
+
|
|
415
|
+
push('RunJob', liveScopedIds(assets, 'run.googleapis.com/Job', 'jobs'), 'gcp');
|
|
416
|
+
push('RunJob', await kccField('RunJob', namespace, (i) => kccScopedId(i)), 'kcc');
|
|
417
|
+
|
|
418
|
+
push('CloudSchedulerJob', liveScopedIds(assets, 'cloudscheduler.googleapis.com/Job', 'jobs'), 'gcp');
|
|
419
|
+
push('CloudSchedulerJob', await kccField('CloudSchedulerJob', namespace, (i) => kccScopedId(i)), 'kcc');
|
|
420
|
+
|
|
421
|
+
push('EventarcTrigger', liveScopedIds(assets, 'eventarc.googleapis.com/Trigger', 'triggers'), 'gcp');
|
|
422
|
+
push('EventarcTrigger', await kccField('EventarcTrigger', namespace, (i) => kccScopedId(i)), 'kcc');
|
|
423
|
+
|
|
424
|
+
push('PubSubTopic', byType('pubsub.googleapis.com/Topic').map((a) => a.displayName), 'gcp');
|
|
425
|
+
push('PubSubTopic', await kccField('PubSubTopic', namespace, resourceId), 'kcc');
|
|
426
|
+
push('PubSubSubscription', byType('pubsub.googleapis.com/Subscription').map((a) => a.displayName), 'gcp');
|
|
427
|
+
push('PubSubSubscription', await kccField('PubSubSubscription', namespace, resourceId), 'kcc');
|
|
428
|
+
|
|
429
|
+
push('SecretManagerSecret', byType('secretmanager.googleapis.com/Secret').map((a) => a.displayName), 'gcp');
|
|
430
|
+
push('SecretManagerSecret', await kccField('SecretManagerSecret', namespace, resourceId), 'kcc');
|
|
431
|
+
|
|
432
|
+
push('VPCAccessConnector', liveScopedIds(assets, 'vpcaccess.googleapis.com/Connector', 'connectors'), 'gcp');
|
|
433
|
+
push('VPCAccessConnector', await kccField('VPCAccessConnector', namespace, (i) => kccScopedId(i)), 'kcc');
|
|
434
|
+
push('ComputeNetwork', liveGlobalOrScopedIds(assets, 'compute.googleapis.com/Network', 'networks'), 'gcp');
|
|
435
|
+
push('ComputeNetwork', await kccField('ComputeNetwork', namespace, (i) => kccScopedId(i, { defaultLocation: 'global' })), 'kcc');
|
|
436
|
+
push('ComputeSubnetwork', liveScopedIds(assets, 'compute.googleapis.com/Subnetwork', 'subnetworks'), 'gcp');
|
|
437
|
+
push('ComputeSubnetwork', await kccField('ComputeSubnetwork', namespace, (i) => kccScopedId(i)), 'kcc');
|
|
438
|
+
|
|
439
|
+
// KMS key ідентифікується не лише ім'ям: keyRing теж є частиною URI.
|
|
440
|
+
const cryptoKeyId = (a) => {
|
|
441
|
+
const m = (a.name || '').match(/\/locations\/([^/]+)\/keyRings\/([^/]+)\/cryptoKeys\/([^/]+)$/);
|
|
442
|
+
return m && `${m[1]}/${m[2]}/${m[3]}`;
|
|
443
|
+
};
|
|
444
|
+
push('KMSCryptoKey', byType('cloudkms.googleapis.com/CryptoKey').map(cryptoKeyId).filter(Boolean), 'gcp');
|
|
445
|
+
push('KMSCryptoKey', await kccField('KMSCryptoKey', namespace, (i) => {
|
|
446
|
+
const ref = (i.spec && i.spec.keyRingRef) || {};
|
|
447
|
+
const m = (ref.external || '').match(/\/locations\/([^/]+)\/keyRings\/([^/]+)$/);
|
|
448
|
+
return m ? `${m[1]}/${m[2]}/${resourceId(i)}` : null;
|
|
449
|
+
}), 'kcc');
|
|
450
|
+
|
|
451
|
+
// --- HTTP(S) LB, що часто стоїть перед Cloud Run ---------------------------
|
|
452
|
+
push('ComputeBackendService', liveGlobalOrScopedIds(assets, 'compute.googleapis.com/BackendService', 'backendServices'), 'gcp');
|
|
453
|
+
push('ComputeBackendService', await kccField('ComputeBackendService', namespace, (i) => kccScopedId(i, { defaultLocation: 'global' })), 'kcc');
|
|
454
|
+
const isServerlessNeg = (a) => (a.additionalAttributes && a.additionalAttributes.networkEndpointType) === 'SERVERLESS'
|
|
455
|
+
|| (a.resource && a.resource.data && a.resource.data.networkEndpointType) === 'SERVERLESS';
|
|
456
|
+
push('ComputeNetworkEndpointGroup', byType('compute.googleapis.com/NetworkEndpointGroup')
|
|
457
|
+
.filter(isServerlessNeg).map((a) => assetGlobalOrScopedId(a, 'networkEndpointGroups')).filter(Boolean), 'gcp');
|
|
458
|
+
push('ComputeNetworkEndpointGroup', await kccField('ComputeNetworkEndpointGroup', namespace, (i) => {
|
|
459
|
+
const type = i.spec && i.spec.networkEndpointType;
|
|
460
|
+
return type === 'SERVERLESS' ? kccScopedId(i, { defaultLocation: 'global' }) : null;
|
|
461
|
+
}), 'kcc');
|
|
462
|
+
push('ComputeURLMap', liveGlobalOrScopedIds(assets, 'compute.googleapis.com/UrlMap', 'urlMaps'), 'gcp');
|
|
463
|
+
push('ComputeURLMap', await kccField('ComputeURLMap', namespace, (i) => kccScopedId(i, { defaultLocation: 'global' })), 'kcc');
|
|
464
|
+
push('ComputeTargetHTTPSProxy', liveGlobalOrScopedIds(assets, 'compute.googleapis.com/TargetHttpsProxy', 'targetHttpsProxies'), 'gcp');
|
|
465
|
+
push('ComputeTargetHTTPSProxy', await kccField('ComputeTargetHTTPSProxy', namespace, (i) => kccScopedId(i, { defaultLocation: 'global' })), 'kcc');
|
|
466
|
+
push('ComputeGlobalForwardingRule', byType('compute.googleapis.com/ForwardingRule')
|
|
467
|
+
.map((a) => assetGlobalOrScopedId(a, 'forwardingRules')).filter((id) => id && id.startsWith('global/')), 'gcp');
|
|
468
|
+
push('ComputeGlobalForwardingRule', await kccField('ComputeGlobalForwardingRule', namespace, (i) => kccScopedId(i, { defaultLocation: 'global' })), 'kcc');
|
|
469
|
+
push('ComputeSSLCertificate', liveGlobalOrScopedIds(assets, 'compute.googleapis.com/SslCertificate', 'sslCertificates'), 'gcp');
|
|
470
|
+
const sslKcc = await Promise.all([
|
|
471
|
+
kccField('ComputeSSLCertificate', namespace, (i) => kccScopedId(i, { defaultLocation: 'global' })),
|
|
472
|
+
kccField('ComputeManagedSSLCertificate', namespace, (i) => kccScopedId(i, { defaultLocation: 'global' })),
|
|
473
|
+
]);
|
|
474
|
+
push('ComputeSSLCertificate', sslKcc.flat(), 'kcc');
|
|
475
|
+
|
|
355
476
|
// --- IAMPolicyMember ---------------------------------------------------------
|
|
356
477
|
// search-all-iam-policies — усі біндинги проєкту одразу, на будь-якому
|
|
357
478
|
// типі ресурсу, не тільки на трьох раніше підтримуваних (Project/SA/AR).
|
|
@@ -369,10 +490,11 @@ export async function collectNamespace(namespace, { includeSystem = false } = {}
|
|
|
369
490
|
}
|
|
370
491
|
push('IAMPolicyMember', liveIam, 'gcp');
|
|
371
492
|
|
|
493
|
+
const runServiceRefs = new Map(runServiceItems.map((i) => [i.metadata && i.metadata.name, kccScopedId(i)]));
|
|
372
494
|
push('IAMPolicyMember', await kccField(
|
|
373
495
|
'IAMPolicyMember', namespace,
|
|
374
496
|
(item) => {
|
|
375
|
-
const rid = kccPolicyMemberId(item, project);
|
|
497
|
+
const rid = kccPolicyMemberId(item, project, runServiceRefs);
|
|
376
498
|
const spec = item.spec || {};
|
|
377
499
|
return `${rid}/${spec.role}/${spec.member}`;
|
|
378
500
|
},
|
|
@@ -396,10 +518,10 @@ The mechanism kcc-inventory is built on, callable on its own: per KCC
|
|
|
396
518
|
namespace (namespace carrying the annotation
|
|
397
519
|
cnrm.cloud.google.com/project-id), lists every resource found live in the
|
|
398
520
|
GCP project (source: gcp) and every one declared as a Config Connector
|
|
399
|
-
custom resource in that namespace
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
521
|
+
custom resource in that namespace. It covers IAM, Artifact Registry, GKE,
|
|
522
|
+
Cloud Run (services and jobs), Scheduler, Eventarc, Pub/Sub, Secret Manager,
|
|
523
|
+
VPC Access, selected network/LB resources, KMS, Cloud DNS, and
|
|
524
|
+
IAMPolicyMember. No drift/orphan diff — that's
|
|
403
525
|
\`cfr kcc-inventory\`.
|
|
404
526
|
|
|
405
527
|
By default GCP-managed system noise is filtered out (Google-owned service
|
package/lib/k8s-rest.mjs
CHANGED
|
@@ -112,10 +112,8 @@ export async function listNamespaces() {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
// group + множина для кожного виду KCC-ресурсу, який сканує
|
|
115
|
-
// get-resources.mjs. Версія скрізь v1beta1 —
|
|
116
|
-
|
|
117
|
-
// це стандарт самого KCC, не наш вибір.
|
|
118
|
-
const CRD_GVR = {
|
|
115
|
+
// get-resources.mjs. Версія скрізь v1beta1 — стандарт KCC, не наш вибір.
|
|
116
|
+
export const CRD_GVR = {
|
|
119
117
|
IAMServiceAccount: { group: 'iam.cnrm.cloud.google.com', plural: 'iamserviceaccounts' },
|
|
120
118
|
IAMServiceAccountKey: { group: 'iam.cnrm.cloud.google.com', plural: 'iamserviceaccountkeys' },
|
|
121
119
|
ArtifactRegistryRepository: { group: 'artifactregistry.cnrm.cloud.google.com', plural: 'artifactregistryrepositories' },
|
|
@@ -125,6 +123,24 @@ const CRD_GVR = {
|
|
|
125
123
|
ComputeAddress: { group: 'compute.cnrm.cloud.google.com', plural: 'computeaddresses' },
|
|
126
124
|
DNSManagedZone: { group: 'dns.cnrm.cloud.google.com', plural: 'dnsmanagedzones' },
|
|
127
125
|
DNSRecordSet: { group: 'dns.cnrm.cloud.google.com', plural: 'dnsrecordsets' },
|
|
126
|
+
RunService: { group: 'run.cnrm.cloud.google.com', plural: 'runservices' },
|
|
127
|
+
RunJob: { group: 'run.cnrm.cloud.google.com', plural: 'runjobs' },
|
|
128
|
+
CloudSchedulerJob: { group: 'cloudscheduler.cnrm.cloud.google.com', plural: 'cloudschedulerjobs' },
|
|
129
|
+
EventarcTrigger: { group: 'eventarc.cnrm.cloud.google.com', plural: 'eventarctriggers' },
|
|
130
|
+
PubSubTopic: { group: 'pubsub.cnrm.cloud.google.com', plural: 'pubsubtopics' },
|
|
131
|
+
PubSubSubscription: { group: 'pubsub.cnrm.cloud.google.com', plural: 'pubsubsubscriptions' },
|
|
132
|
+
SecretManagerSecret: { group: 'secretmanager.cnrm.cloud.google.com', plural: 'secretmanagersecrets' },
|
|
133
|
+
VPCAccessConnector: { group: 'vpcaccess.cnrm.cloud.google.com', plural: 'vpcaccessconnectors' },
|
|
134
|
+
ComputeNetwork: { group: 'compute.cnrm.cloud.google.com', plural: 'computenetworks' },
|
|
135
|
+
ComputeSubnetwork: { group: 'compute.cnrm.cloud.google.com', plural: 'computesubnetworks' },
|
|
136
|
+
KMSCryptoKey: { group: 'kms.cnrm.cloud.google.com', plural: 'kmscryptokeys' },
|
|
137
|
+
ComputeBackendService: { group: 'compute.cnrm.cloud.google.com', plural: 'computebackendservices' },
|
|
138
|
+
ComputeNetworkEndpointGroup: { group: 'compute.cnrm.cloud.google.com', plural: 'computenetworkendpointgroups' },
|
|
139
|
+
ComputeURLMap: { group: 'compute.cnrm.cloud.google.com', plural: 'computeurlmaps' },
|
|
140
|
+
ComputeTargetHTTPSProxy: { group: 'compute.cnrm.cloud.google.com', plural: 'computetargethttpsproxies' },
|
|
141
|
+
ComputeGlobalForwardingRule: { group: 'compute.cnrm.cloud.google.com', plural: 'computeglobalforwardingrules' },
|
|
142
|
+
ComputeSSLCertificate: { group: 'compute.cnrm.cloud.google.com', plural: 'computesslcertificates' },
|
|
143
|
+
ComputeManagedSSLCertificate: { group: 'compute.cnrm.cloud.google.com', plural: 'computemanagedsslcertificates' },
|
|
128
144
|
IAMPolicyMember: { group: 'iam.cnrm.cloud.google.com', plural: 'iampolicymembers' },
|
|
129
145
|
};
|
|
130
146
|
|
package/lib/kcc-inventory.mjs
CHANGED
|
@@ -14,10 +14,9 @@ Usage:
|
|
|
14
14
|
Compares, per KCC namespace (namespace carrying the annotation
|
|
15
15
|
cnrm.cloud.google.com/project-id), what actually exists in the GCP project
|
|
16
16
|
against what's declared as Config Connector custom resources in that
|
|
17
|
-
namespace
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
IAMPolicyMember.
|
|
17
|
+
namespace. It covers IAM, Artifact Registry, GKE, Cloud Run, Scheduler,
|
|
18
|
+
Eventarc, Pub/Sub, Secret Manager, VPC Access, selected network/LB
|
|
19
|
+
resources, KMS, Cloud DNS, and IAMPolicyMember.
|
|
21
20
|
|
|
22
21
|
Reports two directions per kind:
|
|
23
22
|
DRIFT — live in GCP, not declared under KCC (adopt it or delete it)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nitra/cfr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "A handful of small k8s/GitOps CLI utilities: `check` verifies every YAML file in a Kustomize directory is listed in kustomization.yaml's explicit resources: (and vice versa); `kcc-inventory` diffs a GCP Config Connector namespace against the live project to find drift; `get-resources` is that same scan without the diff.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"kustomize",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
|
-
"test": "node --test test
|
|
39
|
+
"test": "node --test test/*.test.mjs"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"google-auth-library": "^10.9.1",
|