@spottoai/types-package 1.0.2-beta.354 → 1.0.2-beta.356
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 +11 -0
- package/dist/aws/commitments-planning.d.ts +3 -0
- package/dist/aws/commitments-planning.d.ts.map +1 -0
- package/dist/aws/commitments-planning.js +6 -0
- package/dist/aws/commitments-planning.js.map +1 -0
- package/dist/aws/relationships.d.ts +4 -0
- package/dist/aws/relationships.d.ts.map +1 -0
- package/dist/aws/relationships.js +6 -0
- package/dist/aws/relationships.js.map +1 -0
- package/dist/esm/accounts/accounts.js +115 -0
- package/dist/esm/accounts/azureSpSetup.js +1 -0
- package/dist/esm/accounts/azureSpSetupDurable.js +1 -0
- package/dist/esm/accounts/index.js +5 -0
- package/dist/esm/accounts/readPermissions.js +198 -0
- package/dist/esm/accounts/validation.js +1 -0
- package/dist/esm/accounts/writePermissions.js +43 -0
- package/dist/esm/ai/index.js +2 -0
- package/dist/esm/assessments/artifact.js +1 -0
- package/dist/esm/assessments/catalogue.js +12 -0
- package/dist/esm/assessments/common.js +1 -0
- package/dist/esm/assessments/evidence.js +1 -0
- package/dist/esm/assessments/export.js +1 -0
- package/dist/esm/assessments/index.js +10 -0
- package/dist/esm/assessments/queue.js +2 -0
- package/dist/esm/assessments/review.js +1 -0
- package/dist/esm/assessments/run.js +1 -0
- package/dist/esm/assessments/sow.js +1 -0
- package/dist/esm/assessments/status.js +23 -0
- package/dist/esm/aws/commitments-planning.js +1 -0
- package/dist/esm/aws/commitmentsPlanningValidation.js +121 -0
- package/dist/esm/aws/estates.js +21 -0
- package/dist/esm/aws/index.js +18 -0
- package/dist/esm/aws/onboarding.js +1 -0
- package/dist/esm/aws/pluginPublicArtifactBodyValidation.js +217 -0
- package/dist/esm/aws/pluginPublicArtifactEvidence.js +1 -0
- package/dist/esm/aws/pluginPublicArtifactManifestValidation.js +255 -0
- package/dist/esm/aws/pluginPublicArtifactSectionValidation.js +464 -0
- package/dist/esm/aws/pluginPublicArtifactValidation.js +2 -0
- package/dist/esm/aws/pluginPublicArtifactValidationHelpers.js +216 -0
- package/dist/esm/aws/pluginPublicArtifacts.js +109 -0
- package/dist/esm/aws/portalCommitmentsPlanningPublicArtifactValidation.js +509 -0
- package/dist/esm/aws/portalCommitmentsPlanningPublicArtifacts.js +1 -0
- package/dist/esm/aws/portalPublicArtifactCurrentValidation.js +328 -0
- package/dist/esm/aws/portalPublicArtifactEvidenceValidation.js +294 -0
- package/dist/esm/aws/portalPublicArtifactHistoryAiValidation.js +368 -0
- package/dist/esm/aws/portalPublicArtifactHistoryTypes.js +40 -0
- package/dist/esm/aws/portalPublicArtifactNestedEvidence.js +1 -0
- package/dist/esm/aws/portalPublicArtifactNestedEvidenceValidation.js +397 -0
- package/dist/esm/aws/portalPublicArtifactValidation.js +2 -0
- package/dist/esm/aws/portalPublicArtifactValidationCommon.js +139 -0
- package/dist/esm/aws/portalPublicArtifacts.js +73 -0
- package/dist/esm/aws/portalRelationshipPublicArtifactValidation.js +334 -0
- package/dist/esm/aws/portalRelationshipPublicArtifacts.js +59 -0
- package/dist/esm/aws/portalRelationshipResourceNodeTypes.js +1 -0
- package/dist/esm/aws/portalRelationshipResourceNodeValidation.js +368 -0
- package/dist/esm/aws/publicArtifacts.js +13 -0
- package/dist/esm/aws/relationships.js +1 -0
- package/dist/esm/aws/requests.js +19 -0
- package/dist/esm/azure/activityLogs.js +2 -0
- package/dist/esm/azure/advisorScore.js +1 -0
- package/dist/esm/azure/benefits.js +1 -0
- package/dist/esm/azure/billingGeneration.js +1 -0
- package/dist/esm/azure/billingPlots.js +4 -0
- package/dist/esm/azure/budgets.js +1 -0
- package/dist/esm/azure/commitmentsPlanning.js +1 -0
- package/dist/esm/azure/common.js +1 -0
- package/dist/esm/azure/configs.js +1 -0
- package/dist/esm/azure/costComposition.js +1 -0
- package/dist/esm/azure/dataProtection.js +5 -0
- package/dist/esm/azure/governance.js +14 -0
- package/dist/esm/azure/licensing.js +1 -0
- package/dist/esm/azure/metrics.js +1 -0
- package/dist/esm/azure/payloads.js +1 -0
- package/dist/esm/azure/portalArtifacts.js +1 -0
- package/dist/esm/azure/prices.js +1 -0
- package/dist/esm/azure/publicIpAddresses.js +2 -0
- package/dist/esm/azure/recommendationAudit.js +16 -0
- package/dist/esm/azure/recommendationState.js +1 -0
- package/dist/esm/azure/recommendationTracks.js +40 -0
- package/dist/esm/azure/recommendationWorkflow.js +13 -0
- package/dist/esm/azure/recommendations.js +10 -0
- package/dist/esm/azure/regulatoryCompliance.js +6 -0
- package/dist/esm/azure/relationships.js +2 -0
- package/dist/esm/azure/reportingTemplates.js +1 -0
- package/dist/esm/azure/reports.js +12 -0
- package/dist/esm/azure/resourceHealth.js +12 -0
- package/dist/esm/azure/resourceOptimization.js +1 -0
- package/dist/esm/azure/resources.js +1 -0
- package/dist/esm/azure/reviewChecklist.js +1 -0
- package/dist/esm/azure/secureScore.js +1 -0
- package/dist/esm/azure/security.js +1 -0
- package/dist/esm/azure/serviceRetirement.js +1 -0
- package/dist/esm/azure/serviceRetirementState.js +1 -0
- package/dist/esm/azure/serviceRetirementWorkManagement.js +1 -0
- package/dist/esm/azure/storage-inventory.js +1 -0
- package/dist/esm/azure/subscriptions.js +1 -0
- package/dist/esm/azure/views.js +32 -0
- package/dist/esm/azure/workManagement.js +4 -0
- package/dist/esm/common/artifactGeneration.js +1 -0
- package/dist/esm/common/capabilityPassport.js +214 -0
- package/dist/esm/common/index.js +7 -0
- package/dist/esm/common/pagination.js +1 -0
- package/dist/esm/common/provider.js +11 -0
- package/dist/esm/common/providerSyncProgress.js +1 -0
- package/dist/esm/common/relationships.js +1 -0
- package/dist/esm/common/syncProgress.js +1 -0
- package/dist/esm/company/billingAccount.js +1 -0
- package/dist/esm/company/company.js +1 -0
- package/dist/esm/company/companyHierarchy.js +1 -0
- package/dist/esm/company/companyNotes.js +38 -0
- package/dist/esm/company/customProperties.js +1 -0
- package/dist/esm/company/index.js +6 -0
- package/dist/esm/company/notification.js +43 -0
- package/dist/esm/company/survey.js +1 -0
- package/dist/esm/customProperties/customProperties.js +1 -0
- package/dist/esm/customProperties/index.js +1 -0
- package/dist/esm/entries/aws.js +3 -0
- package/dist/esm/entries/commitments-planning.js +3 -0
- package/dist/esm/entries/provider.js +3 -0
- package/dist/esm/entries/recommendations.js +3 -0
- package/dist/esm/entries/relationships.js +3 -0
- package/dist/esm/entries/root.js +3 -0
- package/dist/esm/events/actionGroups.js +1 -0
- package/dist/esm/events/alertDefinitionRun.js +1 -0
- package/dist/esm/events/baseAlert.js +1 -0
- package/dist/esm/events/cost.js +1 -0
- package/dist/esm/events/costAnomalies.js +1 -0
- package/dist/esm/events/costViews.js +1 -0
- package/dist/esm/events/eventStatus.js +1 -0
- package/dist/esm/events/index.js +8 -0
- package/dist/esm/events/quickAlerts.js +2 -0
- package/dist/esm/features-and-permissions/access.js +1 -0
- package/dist/esm/features-and-permissions/index.js +1 -0
- package/dist/esm/feedbacks/feedback.js +1 -0
- package/dist/esm/identity/apikey.js +1 -0
- package/dist/esm/identity/index.js +1 -0
- package/dist/esm/index.js +54 -0
- package/dist/esm/index.vite.js +48 -0
- package/dist/esm/integrations/autotask.js +1 -0
- package/dist/esm/integrations/azureDevOps.js +1 -0
- package/dist/esm/integrations/github.js +1 -0
- package/dist/esm/integrations/halo.js +1 -0
- package/dist/esm/integrations/index.js +5 -0
- package/dist/esm/integrations/integrationTickets.js +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/scheduler/index.js +1 -0
- package/dist/esm/scheduler/scheduler.js +1 -0
- package/dist/esm/tags/index.js +1 -0
- package/dist/esm/tags/tags.js +6 -0
- package/dist/esm/unknown/services.js +1 -0
- package/dist/esm/users/companyUser.js +2 -0
- package/dist/esm/users/index.js +2 -0
- package/dist/esm/users/user.js +1 -0
- package/package.json +23 -6
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { ProviderName } from '../common/provider.js';
|
|
2
|
+
import { asRecord, assertAccount, assertExactKeys, assertValue } from './pluginPublicArtifactValidationHelpers.js';
|
|
3
|
+
const FORBIDDEN_AWS_COMMITMENTS_KEYS = new Set([
|
|
4
|
+
'billingScopeId',
|
|
5
|
+
'breakCostEstimate',
|
|
6
|
+
'cloudAccountId',
|
|
7
|
+
'companyId',
|
|
8
|
+
'credentialHealth',
|
|
9
|
+
'credentialId',
|
|
10
|
+
'managementGroupId',
|
|
11
|
+
'pricingQuote',
|
|
12
|
+
'quotePolicy',
|
|
13
|
+
'resourceGroupId',
|
|
14
|
+
'storageCapacity',
|
|
15
|
+
'storageDimensions',
|
|
16
|
+
'subscription',
|
|
17
|
+
'subscriptionId',
|
|
18
|
+
'tenantId',
|
|
19
|
+
'unlockFinancialLedger',
|
|
20
|
+
]);
|
|
21
|
+
/**
|
|
22
|
+
* Validates the security-sensitive AWS identity boundary of a commitments view.
|
|
23
|
+
* A full public-artifact validator should call this before publication or response.
|
|
24
|
+
*/
|
|
25
|
+
export function validateAwsCommitmentsPlanningViewIdentity(value, expectedAccountId) {
|
|
26
|
+
const view = asRecord(value, 'commitmentsPlanning');
|
|
27
|
+
const providerScope = asRecord(view.providerScope, 'commitmentsPlanning.providerScope');
|
|
28
|
+
assertExactKeys(providerScope, ['providerName', 'providerScopeId'], 'commitmentsPlanning.providerScope');
|
|
29
|
+
assertValue(providerScope.providerName, ProviderName.Aws, 'commitmentsPlanning.providerScope.providerName');
|
|
30
|
+
const accountId = String(providerScope.providerScopeId ?? '');
|
|
31
|
+
assertAccount(providerScope.providerScopeId, accountId, 'commitmentsPlanning.providerScope.providerScopeId');
|
|
32
|
+
if (expectedAccountId !== undefined) {
|
|
33
|
+
assertAccount(expectedAccountId, accountId, 'expectedAccountId');
|
|
34
|
+
}
|
|
35
|
+
validateInventory(view.inventory, accountId);
|
|
36
|
+
validatePurchaseRecommendations(view.purchaseRecommendations, accountId);
|
|
37
|
+
validateBoundIdentities(view, accountId, 'commitmentsPlanning');
|
|
38
|
+
}
|
|
39
|
+
function validateInventory(value, accountId) {
|
|
40
|
+
if (!Array.isArray(value))
|
|
41
|
+
throw new Error('commitmentsPlanning.inventory must be an array.');
|
|
42
|
+
value.forEach((item, index) => {
|
|
43
|
+
const field = `commitmentsPlanning.inventory[${index}]`;
|
|
44
|
+
const record = asRecord(item, field);
|
|
45
|
+
assertValue(record.sourceKind, 'aws-native', `${field}.sourceKind`);
|
|
46
|
+
assertValue(record.provider, ProviderName.Aws, `${field}.provider`);
|
|
47
|
+
assertValue(record.appliedScopeType, 'linked-account', `${field}.appliedScopeType`);
|
|
48
|
+
validateAppliedScope(record.appliedScopeProperties, accountId, `${field}.appliedScopeProperties`);
|
|
49
|
+
validateAwsShape(record.shape, `${field}.shape`);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function validatePurchaseRecommendations(value, accountId) {
|
|
53
|
+
if (value === undefined)
|
|
54
|
+
return;
|
|
55
|
+
if (!Array.isArray(value))
|
|
56
|
+
throw new Error('commitmentsPlanning.purchaseRecommendations must be an array.');
|
|
57
|
+
value.forEach((item, index) => {
|
|
58
|
+
const field = `commitmentsPlanning.purchaseRecommendations[${index}]`;
|
|
59
|
+
const record = asRecord(item, field);
|
|
60
|
+
assertValue(record.purchaseScope, 'linked-account', `${field}.purchaseScope`);
|
|
61
|
+
validateAppliedScope(record.appliedScopeProperties, accountId, `${field}.appliedScopeProperties`);
|
|
62
|
+
validateAwsSource(record.source, `${field}.source`);
|
|
63
|
+
validateAwsShape(record.currentShape, `${field}.currentShape`);
|
|
64
|
+
validateAwsShape(record.targetShape, `${field}.targetShape`, true);
|
|
65
|
+
if (record.eligibility !== undefined) {
|
|
66
|
+
const eligibility = asRecord(record.eligibility, `${field}.eligibility`);
|
|
67
|
+
validateAwsShape(eligibility.currentShape, `${field}.eligibility.currentShape`);
|
|
68
|
+
validateAwsShape(eligibility.targetShape, `${field}.eligibility.targetShape`);
|
|
69
|
+
if (eligibility.source !== undefined)
|
|
70
|
+
validateAwsSource(eligibility.source, `${field}.eligibility.source`);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function validateAppliedScope(value, accountId, field) {
|
|
75
|
+
const scope = asRecord(value, field);
|
|
76
|
+
assertExactKeys(scope, ['accountId', 'region', 'availabilityZone'], field);
|
|
77
|
+
assertAccount(scope.accountId, accountId, `${field}.accountId`);
|
|
78
|
+
}
|
|
79
|
+
function validateAwsSource(value, field) {
|
|
80
|
+
const source = asRecord(value, field);
|
|
81
|
+
assertValue(source.sourceKind, 'aws-native', `${field}.sourceKind`);
|
|
82
|
+
}
|
|
83
|
+
function validateAwsShape(value, field, required = false) {
|
|
84
|
+
if (value === undefined && !required)
|
|
85
|
+
return;
|
|
86
|
+
const shape = asRecord(value, field);
|
|
87
|
+
assertValue(shape.provider, 'aws', `${field}.provider`);
|
|
88
|
+
}
|
|
89
|
+
function validateBoundIdentities(value, accountId, field) {
|
|
90
|
+
if (Array.isArray(value)) {
|
|
91
|
+
value.forEach((item, index) => validateBoundIdentities(item, accountId, `${field}[${index}]`));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (!value || typeof value !== 'object') {
|
|
95
|
+
if (typeof value === 'string')
|
|
96
|
+
validateArnAccount(value, accountId, field);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
for (const [key, child] of Object.entries(value)) {
|
|
100
|
+
if (FORBIDDEN_AWS_COMMITMENTS_KEYS.has(key)) {
|
|
101
|
+
throw new Error(`${field}.${key} is not allowed in an AWS commitments artifact.`);
|
|
102
|
+
}
|
|
103
|
+
if (key === 'accountId')
|
|
104
|
+
assertAccount(child, accountId, `${field}.${key}`);
|
|
105
|
+
if (key === 'providerName')
|
|
106
|
+
assertValue(child, ProviderName.Aws, `${field}.${key}`);
|
|
107
|
+
if (key === 'provider')
|
|
108
|
+
assertValue(child, 'aws', `${field}.${key}`);
|
|
109
|
+
if (key === 'sourceKind' && child === 'azure-native') {
|
|
110
|
+
throw new Error(`${field}.${key} cannot contain Azure-native evidence.`);
|
|
111
|
+
}
|
|
112
|
+
validateBoundIdentities(child, accountId, `${field}.${key}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function validateArnAccount(value, accountId, field) {
|
|
116
|
+
if (!value.startsWith('arn:'))
|
|
117
|
+
return;
|
|
118
|
+
const arnAccountId = value.split(':', 6)[4];
|
|
119
|
+
if (arnAccountId)
|
|
120
|
+
assertAccount(arnAccountId, accountId, field);
|
|
121
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { AWS_FORBIDDEN_CREDENTIAL_FIELDS } from './requests.js';
|
|
2
|
+
/** Schema version for the API-owned AWS company estate manifest. */
|
|
3
|
+
export const AWS_ESTATES_MANIFEST_SCHEMA_VERSION = 1;
|
|
4
|
+
/** Public provider discriminator used by the AWS estate manifest API. */
|
|
5
|
+
export const AWS_ESTATES_MANIFEST_PROVIDER = 'AWS';
|
|
6
|
+
export const AWS_ESTATE_KINDS = ['standalone', 'organization'];
|
|
7
|
+
export const AWS_ESTATE_ACCOUNT_SOURCES = ['manual'];
|
|
8
|
+
export const AWS_ESTATE_ROLE_DEPLOYMENT_MODES = ['customer-managed'];
|
|
9
|
+
export const AWS_ESTATE_ACCOUNT_PURPOSES = ['resource-discovery', 'organization-discovery', 'billing-definition', 'billing-storage'];
|
|
10
|
+
/**
|
|
11
|
+
* Credential-shaped fields that manifest API boundaries must reject
|
|
12
|
+
* recursively before persisting untyped JSON.
|
|
13
|
+
*/
|
|
14
|
+
export const AWS_ESTATES_MANIFEST_FORBIDDEN_CREDENTIAL_FIELDS = [
|
|
15
|
+
...AWS_FORBIDDEN_CREDENTIAL_FIELDS,
|
|
16
|
+
'externalId',
|
|
17
|
+
'accessToken',
|
|
18
|
+
'connectionString',
|
|
19
|
+
'sasToken',
|
|
20
|
+
'storageCredential',
|
|
21
|
+
];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export * from './estates.js';
|
|
2
|
+
export * from './commitmentsPlanningValidation.js';
|
|
3
|
+
export * from './onboarding.js';
|
|
4
|
+
export * from './pluginPublicArtifacts.js';
|
|
5
|
+
export * from './pluginPublicArtifactEvidence.js';
|
|
6
|
+
export * from './pluginPublicArtifactValidation.js';
|
|
7
|
+
export * from './portalPublicArtifacts.js';
|
|
8
|
+
export * from './portalCommitmentsPlanningPublicArtifacts.js';
|
|
9
|
+
export * from './portalCommitmentsPlanningPublicArtifactValidation.js';
|
|
10
|
+
export * from './portalRelationshipPublicArtifacts.js';
|
|
11
|
+
export * from './portalRelationshipResourceNodeTypes.js';
|
|
12
|
+
export * from './portalRelationshipPublicArtifactValidation.js';
|
|
13
|
+
export * from './portalPublicArtifactHistoryTypes.js';
|
|
14
|
+
export * from './portalPublicArtifactNestedEvidence.js';
|
|
15
|
+
export * from './portalPublicArtifactNestedEvidenceValidation.js';
|
|
16
|
+
export * from './portalPublicArtifactValidation.js';
|
|
17
|
+
export * from './publicArtifacts.js';
|
|
18
|
+
export * from './requests.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { AWS_PLUGIN_PUBLIC_ARTIFACT_SCHEMA_VERSION, buildAwsPluginResourceLogicalName, buildAwsPluginSubscriptionLogicalName, sha256AwsPluginIdentity, } from './pluginPublicArtifacts.js';
|
|
2
|
+
import { AWS_PUBLIC_ARTIFACT_SCHEMA_VERSION } from './publicArtifacts.js';
|
|
3
|
+
import { asRecord, assertAccount, assertExactKeys, assertJsonEqual, assertPublicJson, assertValue, isoTimestamp, optionalEnum, requiredEnum, requiredString, sha256, stringArray, validateBilling, validateMetricWindow, validateSelector, validateTagRuleScope, } from './pluginPublicArtifactValidationHelpers.js';
|
|
4
|
+
import { validateResourceSections, validateSubscriptionSections } from './pluginPublicArtifactSectionValidation.js';
|
|
5
|
+
export { validateSourceBinding } from './pluginPublicArtifactSectionValidation.js';
|
|
6
|
+
/** Validates and returns one lossless AWS plugin subscription-detail body. */
|
|
7
|
+
export function validateAwsPluginSubscriptionDetailArtifact(value) {
|
|
8
|
+
const artifact = asRecord(value, 'artifact');
|
|
9
|
+
assertExactKeys(artifact, [
|
|
10
|
+
'schemaVersion',
|
|
11
|
+
'pluginSchemaVersion',
|
|
12
|
+
'provider',
|
|
13
|
+
'accountId',
|
|
14
|
+
'artifactType',
|
|
15
|
+
'artifactGeneration',
|
|
16
|
+
'generatedAt',
|
|
17
|
+
'logicalName',
|
|
18
|
+
'target',
|
|
19
|
+
'sections',
|
|
20
|
+
], 'artifact');
|
|
21
|
+
validateArtifactEnvelope(artifact, 'plugin-subscription');
|
|
22
|
+
const target = validateTarget(artifact.target, 'subscription-detail');
|
|
23
|
+
const accountId = requiredString(artifact.accountId, 'artifact.accountId');
|
|
24
|
+
assertAccount(target.accountId, accountId, 'artifact.target.accountId');
|
|
25
|
+
assertValue(artifact.logicalName, buildAwsPluginSubscriptionLogicalName(target.targetKeySha256), 'artifact.logicalName');
|
|
26
|
+
validateSubscriptionSections(artifact.sections, target, requiredString(artifact.generatedAt, 'artifact.generatedAt'));
|
|
27
|
+
assertPublicJson(artifact, 'artifact');
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
/** Validates and returns one lossless AWS plugin resource-detail body. */
|
|
31
|
+
export function validateAwsPluginResourceDetailArtifact(value) {
|
|
32
|
+
const artifact = asRecord(value, 'artifact');
|
|
33
|
+
assertExactKeys(artifact, [
|
|
34
|
+
'schemaVersion',
|
|
35
|
+
'pluginSchemaVersion',
|
|
36
|
+
'provider',
|
|
37
|
+
'accountId',
|
|
38
|
+
'artifactType',
|
|
39
|
+
'artifactGeneration',
|
|
40
|
+
'generatedAt',
|
|
41
|
+
'logicalName',
|
|
42
|
+
'target',
|
|
43
|
+
'selector',
|
|
44
|
+
'stableKey',
|
|
45
|
+
'sections',
|
|
46
|
+
], 'artifact');
|
|
47
|
+
validateArtifactEnvelope(artifact, 'plugin-resource');
|
|
48
|
+
const target = validateTarget(artifact.target, 'resource-detail');
|
|
49
|
+
const accountId = requiredString(artifact.accountId, 'artifact.accountId');
|
|
50
|
+
assertAccount(target.accountId, accountId, 'artifact.target.accountId');
|
|
51
|
+
assertValue(artifact.logicalName, buildAwsPluginResourceLogicalName(target.stableKeySha256, target.targetKeySha256), 'artifact.logicalName');
|
|
52
|
+
assertJsonEqual(artifact.selector, target.selector, 'artifact.selector');
|
|
53
|
+
assertValue(artifact.stableKey, target.stableKey, 'artifact.stableKey');
|
|
54
|
+
validateResourceSections(artifact.sections, target, requiredString(artifact.generatedAt, 'artifact.generatedAt'));
|
|
55
|
+
assertPublicJson(artifact, 'artifact');
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
function validateArtifactEnvelope(artifact, artifactType) {
|
|
59
|
+
assertValue(artifact.schemaVersion, AWS_PUBLIC_ARTIFACT_SCHEMA_VERSION, 'artifact.schemaVersion');
|
|
60
|
+
assertValue(artifact.pluginSchemaVersion, AWS_PLUGIN_PUBLIC_ARTIFACT_SCHEMA_VERSION, 'artifact.pluginSchemaVersion');
|
|
61
|
+
assertValue(artifact.provider, 'aws', 'artifact.provider');
|
|
62
|
+
requiredString(artifact.accountId, 'artifact.accountId');
|
|
63
|
+
assertValue(artifact.artifactType, artifactType, 'artifact.artifactType');
|
|
64
|
+
const generation = validateGeneration(artifact.artifactGeneration, 'artifact.artifactGeneration');
|
|
65
|
+
assertValue(isoTimestamp(artifact.generatedAt, 'artifact.generatedAt'), generation.generatedAt, 'artifact.generatedAt');
|
|
66
|
+
}
|
|
67
|
+
function validateGeneration(value, field) {
|
|
68
|
+
const generation = asRecord(value, field);
|
|
69
|
+
assertExactKeys(generation, ['runId', 'generatedAt'], field);
|
|
70
|
+
return {
|
|
71
|
+
runId: requiredString(generation.runId, `${field}.runId`),
|
|
72
|
+
generatedAt: isoTimestamp(generation.generatedAt, `${field}.generatedAt`),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export function validateTarget(value, kind) {
|
|
76
|
+
const target = asRecord(value, 'target');
|
|
77
|
+
assertValue(target.kind, kind, 'target.kind');
|
|
78
|
+
assertValue(target.provider, 'aws', 'target.provider');
|
|
79
|
+
requiredString(target.accountId, 'target.accountId');
|
|
80
|
+
requiredString(target.targetKey, 'target.targetKey');
|
|
81
|
+
sha256(target.targetKeySha256, 'target.targetKeySha256');
|
|
82
|
+
assertValue(target.targetKeySha256, sha256AwsPluginIdentity(requiredString(target.targetKey, 'target.targetKey')), 'target.targetKeySha256');
|
|
83
|
+
validateBilling(target.billing);
|
|
84
|
+
stringArray(target.resourceRegions, 'target.resourceRegions');
|
|
85
|
+
validateMetricWindow(target.metricTimeWindow, 'target.metricTimeWindow');
|
|
86
|
+
validateRecommendationScopes(target.recommendationScopes, requiredString(target.accountId, 'target.accountId'));
|
|
87
|
+
if (kind === 'subscription-detail') {
|
|
88
|
+
assertExactKeys(target, [
|
|
89
|
+
'kind',
|
|
90
|
+
'provider',
|
|
91
|
+
'accountId',
|
|
92
|
+
'targetKey',
|
|
93
|
+
'targetKeySha256',
|
|
94
|
+
'accountSummaryTargetKey',
|
|
95
|
+
'resourceCollectionTargetKey',
|
|
96
|
+
'recommendationsTargetKey',
|
|
97
|
+
'aiCostSummaryTargetKey',
|
|
98
|
+
'serviceRetirementTargetKey',
|
|
99
|
+
'commitmentAnalysisTargetKey',
|
|
100
|
+
'reliabilityTargetKey',
|
|
101
|
+
'accountGovernanceTargetKey',
|
|
102
|
+
'billing',
|
|
103
|
+
'resourceRegions',
|
|
104
|
+
'metricTimeWindow',
|
|
105
|
+
'aiCostSummaryAudience',
|
|
106
|
+
'serviceRetirementRegions',
|
|
107
|
+
'recommendationScopes',
|
|
108
|
+
'commitmentAnalysis',
|
|
109
|
+
'reliability',
|
|
110
|
+
'accountGovernance',
|
|
111
|
+
], 'target');
|
|
112
|
+
optionalEnum(target.aiCostSummaryAudience, ['portal', 'plugin'], 'target.aiCostSummaryAudience');
|
|
113
|
+
requiredString(target.accountSummaryTargetKey, 'target.accountSummaryTargetKey');
|
|
114
|
+
requiredString(target.resourceCollectionTargetKey, 'target.resourceCollectionTargetKey');
|
|
115
|
+
requiredString(target.recommendationsTargetKey, 'target.recommendationsTargetKey');
|
|
116
|
+
requiredString(target.serviceRetirementTargetKey, 'target.serviceRetirementTargetKey');
|
|
117
|
+
validateOptionalTargetKey(target.aiCostSummaryTargetKey, target.aiCostSummaryAudience !== undefined, 'target.aiCostSummaryTargetKey');
|
|
118
|
+
validateOptionalTargetKey(target.commitmentAnalysisTargetKey, target.commitmentAnalysis !== undefined, 'target.commitmentAnalysisTargetKey');
|
|
119
|
+
validateOptionalTargetKey(target.reliabilityTargetKey, target.reliability !== undefined, 'target.reliabilityTargetKey');
|
|
120
|
+
validateOptionalTargetKey(target.accountGovernanceTargetKey, target.accountGovernance !== undefined, 'target.accountGovernanceTargetKey');
|
|
121
|
+
stringArray(target.serviceRetirementRegions, 'target.serviceRetirementRegions');
|
|
122
|
+
validateCommitmentAnalysisTarget(target.commitmentAnalysis);
|
|
123
|
+
validateReliabilityTarget(target.reliability);
|
|
124
|
+
validateAccountGovernanceTarget(target.accountGovernance);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
assertExactKeys(target, [
|
|
128
|
+
'kind',
|
|
129
|
+
'provider',
|
|
130
|
+
'accountId',
|
|
131
|
+
'targetKey',
|
|
132
|
+
'targetKeySha256',
|
|
133
|
+
'resourceCollectionTargetKey',
|
|
134
|
+
'recommendationsTargetKey',
|
|
135
|
+
'billing',
|
|
136
|
+
'resourceRegions',
|
|
137
|
+
'metricTimeWindow',
|
|
138
|
+
'tagRuleScope',
|
|
139
|
+
'selector',
|
|
140
|
+
'stableKey',
|
|
141
|
+
'stableKeySha256',
|
|
142
|
+
'relationshipTargetKey',
|
|
143
|
+
'serviceRetirementTargetKey',
|
|
144
|
+
'recommendationScopes',
|
|
145
|
+
], 'target');
|
|
146
|
+
validateTagRuleScope(target.tagRuleScope);
|
|
147
|
+
requiredString(target.resourceCollectionTargetKey, 'target.resourceCollectionTargetKey');
|
|
148
|
+
requiredString(target.recommendationsTargetKey, 'target.recommendationsTargetKey');
|
|
149
|
+
validateSelector(target.selector, 'target.selector');
|
|
150
|
+
const stableKey = requiredString(target.stableKey, 'target.stableKey');
|
|
151
|
+
if (asRecord(target.selector, 'target.selector').kind === 'stable-key') {
|
|
152
|
+
assertValue(asRecord(target.selector, 'target.selector').stableKey, stableKey, 'target.selector.stableKey');
|
|
153
|
+
}
|
|
154
|
+
sha256(target.stableKeySha256, 'target.stableKeySha256');
|
|
155
|
+
assertValue(target.stableKeySha256, sha256AwsPluginIdentity(stableKey), 'target.stableKeySha256');
|
|
156
|
+
requiredString(target.relationshipTargetKey, 'target.relationshipTargetKey');
|
|
157
|
+
requiredString(target.serviceRetirementTargetKey, 'target.serviceRetirementTargetKey');
|
|
158
|
+
}
|
|
159
|
+
return value;
|
|
160
|
+
}
|
|
161
|
+
function validateOptionalTargetKey(value, required, field) {
|
|
162
|
+
if (required)
|
|
163
|
+
requiredString(value, field);
|
|
164
|
+
else if (value !== undefined)
|
|
165
|
+
throw new Error(`${field} requires its corresponding optional target.`);
|
|
166
|
+
}
|
|
167
|
+
function validateCommitmentAnalysisTarget(value) {
|
|
168
|
+
if (value === undefined)
|
|
169
|
+
return;
|
|
170
|
+
const target = asRecord(value, 'target.commitmentAnalysis');
|
|
171
|
+
assertExactKeys(target, ['resourceRegions', 'maxBenefitEntries'], 'target.commitmentAnalysis');
|
|
172
|
+
stringArray(target.resourceRegions, 'target.commitmentAnalysis.resourceRegions');
|
|
173
|
+
if (!Number.isSafeInteger(target.maxBenefitEntries) || Number(target.maxBenefitEntries) <= 0) {
|
|
174
|
+
throw new Error('target.commitmentAnalysis.maxBenefitEntries must be a positive safe integer.');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function validateReliabilityTarget(value) {
|
|
178
|
+
if (value === undefined)
|
|
179
|
+
return;
|
|
180
|
+
const target = asRecord(value, 'target.reliability');
|
|
181
|
+
assertExactKeys(target, ['includeTemplateContent', 'includeScoring', 'maxResultsPerSection', 'sectionScopeKeys'], 'target.reliability');
|
|
182
|
+
if (target.includeTemplateContent !== undefined)
|
|
183
|
+
assertValue(target.includeTemplateContent, true, 'target.reliability.includeTemplateContent');
|
|
184
|
+
if (target.includeScoring !== undefined)
|
|
185
|
+
assertValue(target.includeScoring, true, 'target.reliability.includeScoring');
|
|
186
|
+
if (!Number.isSafeInteger(target.maxResultsPerSection) || Number(target.maxResultsPerSection) <= 0) {
|
|
187
|
+
throw new Error('target.reliability.maxResultsPerSection must be a positive safe integer.');
|
|
188
|
+
}
|
|
189
|
+
stringArray(target.sectionScopeKeys, 'target.reliability.sectionScopeKeys');
|
|
190
|
+
}
|
|
191
|
+
function validateAccountGovernanceTarget(value) {
|
|
192
|
+
if (value === undefined)
|
|
193
|
+
return;
|
|
194
|
+
const target = asRecord(value, 'target.accountGovernance');
|
|
195
|
+
assertExactKeys(target, ['apiRegion'], 'target.accountGovernance');
|
|
196
|
+
requiredString(target.apiRegion, 'target.accountGovernance.apiRegion');
|
|
197
|
+
}
|
|
198
|
+
function validateRecommendationScopes(value, accountId) {
|
|
199
|
+
if (!Array.isArray(value))
|
|
200
|
+
throw new Error('target.recommendationScopes must be an array.');
|
|
201
|
+
const scopes = value;
|
|
202
|
+
scopes.forEach((value, index) => {
|
|
203
|
+
const field = `target.recommendationScopes[${index}]`;
|
|
204
|
+
const scope = asRecord(value, field);
|
|
205
|
+
assertExactKeys(scope, ['source', 'scope', 'accountId', 'scopeDiscriminator', 'computeOptimizerFamily', 'resourceRegions', 'targetKey', 'actionabilityTargetKey'], field);
|
|
206
|
+
requiredEnum(scope.source, ['custom', 'compute-optimizer', 'cost-optimization-hub', 'resilience-hub', 'security-hub', 'trusted-advisor', 'well-architected'], `${field}.source`);
|
|
207
|
+
assertValue(scope.scope, 'account', `${field}.scope`);
|
|
208
|
+
assertAccount(scope.accountId, accountId, `${field}.accountId`);
|
|
209
|
+
if (scope.scopeDiscriminator !== undefined)
|
|
210
|
+
requiredString(scope.scopeDiscriminator, `${field}.scopeDiscriminator`);
|
|
211
|
+
if (scope.computeOptimizerFamily !== undefined)
|
|
212
|
+
requiredString(scope.computeOptimizerFamily, `${field}.computeOptimizerFamily`);
|
|
213
|
+
stringArray(scope.resourceRegions, `${field}.resourceRegions`);
|
|
214
|
+
requiredString(scope.targetKey, `${field}.targetKey`);
|
|
215
|
+
requiredString(scope.actionabilityTargetKey, `${field}.actionabilityTargetKey`);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { AWS_PLUGIN_PUBLIC_ARTIFACT_SCHEMA_VERSION, buildAwsPluginResourceLogicalName, buildAwsPluginSubscriptionLogicalName, } from './pluginPublicArtifacts.js';
|
|
2
|
+
import { validateSourceBinding, validateTarget } from './pluginPublicArtifactBodyValidation.js';
|
|
3
|
+
import { validateSourceBindingForTarget } from './pluginPublicArtifactSectionValidation.js';
|
|
4
|
+
import { asRecord, assertAccount, assertExactKeys, assertJsonEqual, assertValue, isoTimestamp, requiredArray, requiredEnum, requiredString, sha256, validateDescriptor, } from './pluginPublicArtifactValidationHelpers.js';
|
|
5
|
+
/** Validates complete active-set identity and targeted carry-forward semantics. */
|
|
6
|
+
export function validateAwsPluginGenerationManifest(value) {
|
|
7
|
+
const manifest = asRecord(value, 'manifest');
|
|
8
|
+
assertExactKeys(manifest, [
|
|
9
|
+
'schemaVersion',
|
|
10
|
+
'status',
|
|
11
|
+
'provider',
|
|
12
|
+
'accountId',
|
|
13
|
+
'artifactGeneration',
|
|
14
|
+
'requestBinding',
|
|
15
|
+
'operation',
|
|
16
|
+
'previousGeneration',
|
|
17
|
+
'members',
|
|
18
|
+
'counts',
|
|
19
|
+
'completedAt',
|
|
20
|
+
], 'manifest');
|
|
21
|
+
assertValue(manifest.schemaVersion, AWS_PLUGIN_PUBLIC_ARTIFACT_SCHEMA_VERSION, 'manifest.schemaVersion');
|
|
22
|
+
assertValue(manifest.status, 'completed', 'manifest.status');
|
|
23
|
+
assertValue(manifest.provider, 'aws', 'manifest.provider');
|
|
24
|
+
const accountId = requiredString(manifest.accountId, 'manifest.accountId');
|
|
25
|
+
assertAccount(accountId, accountId, 'manifest.accountId');
|
|
26
|
+
const generation = validateGeneration(manifest.artifactGeneration);
|
|
27
|
+
const request = validateRequestBinding(manifest.requestBinding);
|
|
28
|
+
assertValue(request.requestedAt, generation.generatedAt, 'manifest.requestBinding.requestedAt');
|
|
29
|
+
const completedAt = isoTimestamp(manifest.completedAt, 'manifest.completedAt');
|
|
30
|
+
if (completedAt < generation.generatedAt)
|
|
31
|
+
throw new Error('manifest.completedAt cannot precede the output generation time.');
|
|
32
|
+
const operation = validateOperation(manifest.operation, accountId);
|
|
33
|
+
const previous = validatePreviousGeneration(manifest.previousGeneration, accountId);
|
|
34
|
+
const members = requiredArray(manifest.members, 'manifest.members').map((member, index) => validateMember(member, accountId, generation.generatedAt, `manifest.members[${index}]`));
|
|
35
|
+
assertUniqueLogicalNames(members);
|
|
36
|
+
validateCounts(manifest.counts, members);
|
|
37
|
+
validateTransition(operation, previous, members);
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
function validateGeneration(value) {
|
|
41
|
+
const generation = asRecord(value, 'manifest.artifactGeneration');
|
|
42
|
+
assertExactKeys(generation, ['runId', 'generatedAt'], 'manifest.artifactGeneration');
|
|
43
|
+
return {
|
|
44
|
+
runId: requiredString(generation.runId, 'manifest.artifactGeneration.runId'),
|
|
45
|
+
generatedAt: isoTimestamp(generation.generatedAt, 'manifest.artifactGeneration.generatedAt'),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function validateRequestBinding(value) {
|
|
49
|
+
const request = asRecord(value, 'manifest.requestBinding');
|
|
50
|
+
assertExactKeys(request, ['requestId', 'requestedAt'], 'manifest.requestBinding');
|
|
51
|
+
return {
|
|
52
|
+
requestId: requiredString(request.requestId, 'manifest.requestBinding.requestId'),
|
|
53
|
+
requestedAt: isoTimestamp(request.requestedAt, 'manifest.requestBinding.requestedAt'),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function validateOperation(value, accountId) {
|
|
57
|
+
const operation = asRecord(value, 'manifest.operation');
|
|
58
|
+
if (operation.kind === 'full') {
|
|
59
|
+
assertExactKeys(operation, ['kind'], 'manifest.operation');
|
|
60
|
+
return operation;
|
|
61
|
+
}
|
|
62
|
+
const kind = requiredEnum(operation.kind, ['targeted-replacement', 'targeted-retirement'], 'manifest.operation.kind');
|
|
63
|
+
assertExactKeys(operation, ['kind', 'target'], 'manifest.operation');
|
|
64
|
+
const targetRecord = asRecord(operation.target, 'manifest.operation.target');
|
|
65
|
+
const target = targetRecord.kind === 'subscription-detail'
|
|
66
|
+
? validateTarget(operation.target, 'subscription-detail')
|
|
67
|
+
: validateTarget(operation.target, 'resource-detail');
|
|
68
|
+
if (targetRecord.kind !== 'subscription-detail' && targetRecord.kind !== 'resource-detail')
|
|
69
|
+
throw new Error('manifest.operation.target.kind is not declared.');
|
|
70
|
+
assertAccount(target.accountId, accountId, 'manifest.operation.target.accountId');
|
|
71
|
+
return { kind, target };
|
|
72
|
+
}
|
|
73
|
+
function validateMember(value, accountId, outputGeneratedAt, field) {
|
|
74
|
+
const member = asRecord(value, field);
|
|
75
|
+
const common = ['state', 'publication', 'provider', 'accountId', 'logicalName', 'artifactType', 'target'];
|
|
76
|
+
assertValue(member.provider, 'aws', `${field}.provider`);
|
|
77
|
+
assertAccount(member.accountId, accountId, `${field}.accountId`);
|
|
78
|
+
const artifactType = requiredEnum(member.artifactType, ['plugin-subscription', 'plugin-resource'], `${field}.artifactType`);
|
|
79
|
+
const target = artifactType === 'plugin-subscription' ? validateTarget(member.target, 'subscription-detail') : validateTarget(member.target, 'resource-detail');
|
|
80
|
+
assertAccount(target.accountId, accountId, `${field}.target.accountId`);
|
|
81
|
+
const logicalName = validateLogicalName(member.logicalName, target, `${field}.logicalName`);
|
|
82
|
+
if (member.state === 'active') {
|
|
83
|
+
assertExactKeys(member, [...common, 'artifact', 'sourceGenerations'], field);
|
|
84
|
+
requiredEnum(member.publication, ['replaced', 'carried-forward'], `${field}.publication`);
|
|
85
|
+
validateDescriptor(member.artifact, `${field}.artifact`);
|
|
86
|
+
const descriptor = asRecord(member.artifact, `${field}.artifact`);
|
|
87
|
+
assertValue(descriptor.name, logicalName, `${field}.artifact.name`);
|
|
88
|
+
const sourceIdentities = new Set();
|
|
89
|
+
const sourceBindings = [];
|
|
90
|
+
requiredArray(member.sourceGenerations, `${field}.sourceGenerations`).forEach((source, index) => {
|
|
91
|
+
const sourceRecord = asRecord(source, `${field}.sourceGenerations[${index}]`);
|
|
92
|
+
const binding = validateSourceBinding(source, accountId, requiredString(sourceRecord.section, `${field}.sourceGenerations[${index}].section`), `${field}.sourceGenerations[${index}]`);
|
|
93
|
+
if (binding.generatedAt > outputGeneratedAt)
|
|
94
|
+
throw new Error(`${field}.sourceGenerations[${index}].generatedAt cannot exceed output time.`);
|
|
95
|
+
validateSourceBindingForTarget(binding, target, `${field}.sourceGenerations[${index}]`);
|
|
96
|
+
const identity = [binding.section, binding.sourceArtifactType, binding.generationId, binding.targetKey, binding.artifactSha256].join('|');
|
|
97
|
+
if (sourceIdentities.has(identity))
|
|
98
|
+
throw new Error(`${field}.sourceGenerations contains duplicate source bindings.`);
|
|
99
|
+
sourceIdentities.add(identity);
|
|
100
|
+
sourceBindings.push(binding);
|
|
101
|
+
});
|
|
102
|
+
validateCompleteSourceSet(sourceBindings, target, field);
|
|
103
|
+
}
|
|
104
|
+
else if (member.state === 'retired') {
|
|
105
|
+
assertExactKeys(member, [...common, 'priorArtifactSha256', 'retiredAt'], field);
|
|
106
|
+
assertValue(member.publication, 'retired', `${field}.publication`);
|
|
107
|
+
sha256(member.priorArtifactSha256, `${field}.priorArtifactSha256`);
|
|
108
|
+
isoTimestamp(member.retiredAt, `${field}.retiredAt`);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
throw new Error(`${field}.state is not declared.`);
|
|
112
|
+
}
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
function validateCompleteSourceSet(sources, target, field) {
|
|
116
|
+
const expected = [];
|
|
117
|
+
const add = (section, sourceType, targetKey) => {
|
|
118
|
+
if (targetKey)
|
|
119
|
+
expected.push(`${section}|${sourceType}|${targetKey}`);
|
|
120
|
+
};
|
|
121
|
+
if (target.kind === 'subscription-detail') {
|
|
122
|
+
add('account-summary', 'account-summary', target.accountSummaryTargetKey);
|
|
123
|
+
add('resources', 'resource-collection', target.resourceCollectionTargetKey);
|
|
124
|
+
add('service-retirement', 'lifecycle', target.serviceRetirementTargetKey);
|
|
125
|
+
add('ai-cost-summary', 'ai-cost-summary', target.aiCostSummaryTargetKey);
|
|
126
|
+
add('commitment-analysis', 'commitment-analysis', target.commitmentAnalysisTargetKey);
|
|
127
|
+
add('reliability', 'reliability', target.reliabilityTargetKey);
|
|
128
|
+
add('account-governance', 'account-governance', target.accountGovernanceTargetKey);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
add('resource', 'resource-collection', target.resourceCollectionTargetKey);
|
|
132
|
+
add('relationships', 'relationships', target.relationshipTargetKey);
|
|
133
|
+
add('service-retirement', 'lifecycle', target.serviceRetirementTargetKey);
|
|
134
|
+
}
|
|
135
|
+
target.recommendationScopes.forEach(scope => {
|
|
136
|
+
add('recommendations', 'recommendations', scope.targetKey);
|
|
137
|
+
add('recommendations', 'recommendation-actionability', scope.actionabilityTargetKey);
|
|
138
|
+
});
|
|
139
|
+
const actual = sources.map(source => `${source.section}|${source.sourceArtifactType}|${source.targetKey}`);
|
|
140
|
+
assertJsonEqual([...actual].sort(), [...expected].sort(), `${field}.sourceGenerations complete source set`);
|
|
141
|
+
}
|
|
142
|
+
function validatePreviousGeneration(value, accountId) {
|
|
143
|
+
if (value === undefined)
|
|
144
|
+
return new Map();
|
|
145
|
+
const previous = asRecord(value, 'manifest.previousGeneration');
|
|
146
|
+
assertExactKeys(previous, ['provider', 'accountId', 'runId', 'manifestSha256', 'members'], 'manifest.previousGeneration');
|
|
147
|
+
assertValue(previous.provider, 'aws', 'manifest.previousGeneration.provider');
|
|
148
|
+
assertAccount(previous.accountId, accountId, 'manifest.previousGeneration.accountId');
|
|
149
|
+
requiredString(previous.runId, 'manifest.previousGeneration.runId');
|
|
150
|
+
sha256(previous.manifestSha256, 'manifest.previousGeneration.manifestSha256');
|
|
151
|
+
const result = new Map();
|
|
152
|
+
requiredArray(previous.members, 'manifest.previousGeneration.members').forEach((value, index) => {
|
|
153
|
+
const field = `manifest.previousGeneration.members[${index}]`;
|
|
154
|
+
const member = asRecord(value, field);
|
|
155
|
+
assertExactKeys(member, ['provider', 'accountId', 'logicalName', 'artifactType', 'targetKeySha256', 'stableKeySha256', 'artifactSha256'], field);
|
|
156
|
+
assertValue(member.provider, 'aws', `${field}.provider`);
|
|
157
|
+
assertAccount(member.accountId, accountId, `${field}.accountId`);
|
|
158
|
+
const name = requiredString(member.logicalName, `${field}.logicalName`);
|
|
159
|
+
if (result.has(name))
|
|
160
|
+
throw new Error('manifest.previousGeneration.members has duplicate logicalName values.');
|
|
161
|
+
requiredEnum(member.artifactType, ['plugin-subscription', 'plugin-resource'], `${field}.artifactType`);
|
|
162
|
+
sha256(member.targetKeySha256, `${field}.targetKeySha256`);
|
|
163
|
+
if (member.stableKeySha256 !== undefined)
|
|
164
|
+
sha256(member.stableKeySha256, `${field}.stableKeySha256`);
|
|
165
|
+
sha256(member.artifactSha256, `${field}.artifactSha256`);
|
|
166
|
+
result.set(name, member);
|
|
167
|
+
});
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
function validateTransition(operation, previous, members) {
|
|
171
|
+
if (operation.kind === 'full')
|
|
172
|
+
return;
|
|
173
|
+
if (previous.size === 0)
|
|
174
|
+
throw new Error('Targeted publication requires previousGeneration.members.');
|
|
175
|
+
const target = operation.target;
|
|
176
|
+
const targetName = logicalNameForTarget(target);
|
|
177
|
+
const current = new Map(members.map(member => [member.logicalName, member]));
|
|
178
|
+
for (const [name, prior] of previous) {
|
|
179
|
+
const member = current.get(name);
|
|
180
|
+
if (!member)
|
|
181
|
+
throw new Error(`Targeted publication omitted prior member ${name}.`);
|
|
182
|
+
if (name === targetName) {
|
|
183
|
+
assertTargetPublication(operation.kind, member);
|
|
184
|
+
assertPriorIdentity(member, prior, name);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (member.state !== 'active' || member.publication !== 'carried-forward')
|
|
188
|
+
throw new Error(`Untargeted member ${name} must be carried forward.`);
|
|
189
|
+
assertPriorIdentity(member, prior, name);
|
|
190
|
+
}
|
|
191
|
+
const targetMember = current.get(targetName);
|
|
192
|
+
if (!targetMember)
|
|
193
|
+
throw new Error('Targeted publication omitted its target member.');
|
|
194
|
+
if (!previous.has(targetName)) {
|
|
195
|
+
if (operation.kind === 'targeted-retirement')
|
|
196
|
+
throw new Error('Cannot retire a target absent from the previous active set.');
|
|
197
|
+
assertTargetPublication(operation.kind, targetMember);
|
|
198
|
+
}
|
|
199
|
+
for (const member of members) {
|
|
200
|
+
if (!previous.has(member.logicalName) && member.logicalName !== targetName)
|
|
201
|
+
throw new Error(`Targeted publication contains undeclared member ${member.logicalName}.`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function assertPriorIdentity(member, prior, name) {
|
|
205
|
+
assertValue(member.artifactType, prior.artifactType, `${name}.artifactType`);
|
|
206
|
+
assertValue(member.target.targetKeySha256, prior.targetKeySha256, `${name}.target.targetKeySha256`);
|
|
207
|
+
if (member.target.kind === 'resource-detail') {
|
|
208
|
+
assertValue(member.target.stableKeySha256, prior.stableKeySha256, `${name}.target.stableKeySha256`);
|
|
209
|
+
}
|
|
210
|
+
else if (prior.stableKeySha256 !== undefined) {
|
|
211
|
+
throw new Error(`${name}.stableKeySha256 is ambiguous for a subscription artifact.`);
|
|
212
|
+
}
|
|
213
|
+
if (member.state === 'active') {
|
|
214
|
+
if (member.publication === 'carried-forward') {
|
|
215
|
+
assertValue(member.artifact.sha256, prior.artifactSha256, `${name}.artifact.sha256`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
assertValue(member.priorArtifactSha256, prior.artifactSha256, `${name}.priorArtifactSha256`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function assertTargetPublication(kind, member) {
|
|
223
|
+
const expected = kind === 'targeted-retirement' ? 'retired' : 'replaced';
|
|
224
|
+
if (member.publication !== expected)
|
|
225
|
+
throw new Error(`Target member publication must be ${expected}.`);
|
|
226
|
+
}
|
|
227
|
+
function validateCounts(value, members) {
|
|
228
|
+
const counts = asRecord(value, 'manifest.counts');
|
|
229
|
+
assertExactKeys(counts, ['active', 'replaced', 'carriedForward', 'retired'], 'manifest.counts');
|
|
230
|
+
const expected = {
|
|
231
|
+
active: members.filter(member => member.state === 'active').length,
|
|
232
|
+
replaced: members.filter(member => member.publication === 'replaced').length,
|
|
233
|
+
carriedForward: members.filter(member => member.publication === 'carried-forward').length,
|
|
234
|
+
retired: members.filter(member => member.state === 'retired').length,
|
|
235
|
+
};
|
|
236
|
+
for (const [key, count] of Object.entries(expected))
|
|
237
|
+
assertValue(counts[key], count, `manifest.counts.${key}`);
|
|
238
|
+
}
|
|
239
|
+
function validateLogicalName(value, target, field) {
|
|
240
|
+
const expected = logicalNameForTarget(target);
|
|
241
|
+
assertValue(value, expected, field);
|
|
242
|
+
if (/[\\/]/.test(expected) || expected.includes('..'))
|
|
243
|
+
throw new Error(`${field} must be path-free.`);
|
|
244
|
+
return expected;
|
|
245
|
+
}
|
|
246
|
+
function logicalNameForTarget(target) {
|
|
247
|
+
return target.kind === 'subscription-detail'
|
|
248
|
+
? buildAwsPluginSubscriptionLogicalName(target.targetKeySha256)
|
|
249
|
+
: buildAwsPluginResourceLogicalName(target.stableKeySha256, target.targetKeySha256);
|
|
250
|
+
}
|
|
251
|
+
function assertUniqueLogicalNames(members) {
|
|
252
|
+
const names = members.map(member => member.logicalName);
|
|
253
|
+
if (new Set(names).size !== names.length)
|
|
254
|
+
throw new Error('manifest.members contains duplicate logicalName values.');
|
|
255
|
+
}
|