public bind()

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 },
          }),
        ],
      ],
    };
  }