in src/package-sources/code-artifact.ts [37:205]
public bind(scope: Construct, { denyList, ingestion, licenseList, monitoring, queue }: PackageSourceBindOptions): PackageSourceBindResult {
const idPrefix = this.props.repository.node.path;
const repositoryId = `${this.props.repository.attrDomainOwner}:${this.props.repository.attrDomainName}/${this.props.repository.attrName}`;
const storageFactory = S3StorageFactory.getOrCreate(scope);
const bucket = this.props.bucket || storageFactory.newBucket(scope, `${idPrefix}/StagingBucket`, {
blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
enforceSSL: true,
lifecycleRules: [{ expiration: Duration.days(30) }],
});
bucket.grantRead(ingestion);
const dlq = new Queue(scope, `${idPrefix}/DLQ`, {
encryption: QueueEncryption.KMS_MANAGED,
retentionPeriod: Duration.days(14),
visibilityTimeout: Duration.minutes(15),
});
const forwarder = new CodeArtifactForwarder(scope, `${idPrefix}/Forwarder`, {
deadLetterQueue: dlq,
description: `[${scope.node.path}/CodeArtifact/${repositoryId}] Handle CodeArtifact EventBridge events`,
environment: {
AWS_EMF_ENVIRONMENT: 'Local',
BUCKET_NAME: bucket.bucketName,
QUEUE_URL: queue.queueUrl,
},
memorySize: 1024,
timeout: Duration.seconds(60),
tracing: Tracing.ACTIVE,
});
bucket.grantReadWrite(forwarder);
denyList?.grantRead(forwarder);
licenseList.grantRead(forwarder);
queue.grantSendMessages(forwarder);
forwarder.addToRolePolicy(new PolicyStatement({
effect: Effect.ALLOW,
actions: ['codeartifact:GetPackageVersionAsset'],
resources: [
Stack.of(scope).formatArn({
service: 'codeartifact',
resource: 'package',
arnFormat: ArnFormat.SLASH_RESOURCE_NAME,
resourceName: [
this.props.repository.attrDomainName,
this.props.repository.attrName,
'npm', // package format
'*', // namespace/package-name
].join('/'),
}),
],
}));
const rule = new Rule(scope, `${idPrefix}/EventBridge`, {
description: `${scope.node.path}/CodeArtifact/${repositoryId}/EventBridge`,
eventPattern: {
source: ['aws.codeartifact'],
detailType: ['CodeArtifact Package Version State Change'],
detail: {
domainOwner: this.props.repository.attrDomainOwner,
domainName: this.props.repository.attrDomainName,
repositoryName: this.props.repository.attrName,
packageFormat: 'npm',
},
},
targets: [new LambdaFunction(forwarder)],
});
const failureAlarm = forwarder.metricErrors().createAlarm(scope, `${idPrefix}/Forwarder/Failures`, {
alarmName: `${scope.node.path}/CodeArtifact/${repositoryId}/Forwarder`,
alarmDescription: [
`The CodeArtifact fowarder for ${repositoryId} is failing`,
'',
`Link to the lambda function: ${lambdaFunctionUrl(forwarder)}`,
].join('\n'),
comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
evaluationPeriods: 3,
threshold: 1,
treatMissingData: TreatMissingData.MISSING,
});
monitoring.addHighSeverityAlarm(`CodeArtifact:${this.props.repository.attrName} Failures`, failureAlarm);
const dlqNotEmptyAlarm = new MathExpression({
expression: 'mVisible + mHidden',
usingMetrics: {
mVisible: dlq.metricApproximateNumberOfMessagesVisible({ period: Duration.minutes(1) }),
mHidden: dlq.metricApproximateNumberOfMessagesNotVisible({ period: Duration.minutes(1) }),
},
}).createAlarm(scope, `${idPrefix}/Forwarder/DLQNotEmpty`, {
alarmName: `${scope.node.path}/CodeArtifact/${repositoryId}/DLQNotEmpty`,
alarmDescription: [
`The CodeArtifact fowarder for ${repositoryId} is failing`,
'',
`Link to the lambda function: ${lambdaFunctionUrl(forwarder)}`,
`Link to the dead letter queue: ${sqsQueueUrl(dlq)}`,
].join('/n'),
comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
evaluationPeriods: 1,
threshold: 1,
treatMissingData: TreatMissingData.NOT_BREACHING,
});
monitoring.addLowSeverityAlarm(`CodeArtifact/${repositoryId} DLQ Not Empty`, dlqNotEmptyAlarm);
rule.node.addDependency(failureAlarm, dlqNotEmptyAlarm);
return {
name: `CodeArtifact: ${repositoryId}`,
links: [{
name: 'CodeArtifact',
url: codeArtifactRepositoryUrl(this.props.repository),
primary: true,
}, {
name: 'Forwarder Function',
url: lambdaFunctionUrl(forwarder),
}, {
name: 'Search Log group',
url: lambdaSearchLogGroupUrl(forwarder),
}, {
name: 'DLQ',
url: sqsQueueUrl(dlq),
}],
dashboardWidgets: [
[
new GraphWidget({
height: 6,
width: 12,
title: 'Function Health',
left: [
fillMetric(forwarder.metricInvocations({ label: 'Invocations' })),
fillMetric(forwarder.metricErrors({ label: 'Errors' })),
],
leftYAxis: { min: 0 },
right: [
forwarder.metricDuration({ label: 'Duration' }),
],
rightYAxis: { min: 0 },
period: Duration.minutes(15),
}),
new GraphWidget({
height: 6,
width: 12,
title: 'Dead Letter Queue',
left: [
dlq.metricApproximateNumberOfMessagesVisible({ label: 'Visible Messages', period: Duration.minutes(1) }),
dlq.metricApproximateNumberOfMessagesNotVisible({ label: 'Hidden Messages', period: Duration.minutes(1) }),
],
leftYAxis: { min: 0 },
right: [
dlq.metricApproximateAgeOfOldestMessage({ label: 'Oldest Message Age', period: Duration.minutes(1) }),
],
rightYAxis: { min: 0 },
}),
],
[
new GraphWidget({
height: 6,
width: 12,
title: 'Quality Metrics',
left: [
fillMetric(this.metricNotJsiiEnabledCount({ label: 'Not a jsii package' }), 0),
fillMetric(this.metricIneligibleLicense({ label: 'Ineligible License' }), 0),
fillMetric(this.metricDenyListedCount({ label: 'Deny Listed' }), 0),
fillMetric(this.metricDeletedCount({ label: 'Deletion Events' }), 0),
],
leftYAxis: { min: 0 },
}),
],
],
};
}