constructor()

in source/infrastructure/lib/machine-to-cloud-connectivity-stack.ts [60:327]


  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // CFN template format version
    this.templateOptions.templateFormatVersion = '2010-09-09';

    // CFN Parameters
    // Admin E-mail parameter
    const userEmail = new CfnParameter(this, 'UserEmail', {
      type: 'String',
      description: 'The user E-Mail to access the UI',
      allowedPattern: '^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$',
      constraintDescription: 'User E-Mail must be a valid E-Mail address.'
    });

    // Logging level for Lambda and UI
    const loggingLevel = new CfnParameter(this, 'LoggingLevel', {
      type: 'String',
      description: 'The logging level of the Lambda functions and the UI',
      allowedValues: [
        'VERBOSE',
        'DEBUG',
        'INFO',
        'WARN',
        'ERROR'
      ],
      default: 'ERROR'
    });

    // ExistingGreengrassGroupID
    const existingGreengrassGroupIDParameter = new CfnParameter(this, 'ExistingGreengrassGroupID', {
      type: 'String',
      description: 'The Greengrass Group ID can be found in the Settings option of your Greengrass group in the console.',
      allowedPattern: '[a-zA-Z0-9-]*',
      constraintDescription: 'Greengrass group ID should match the allowed pattern: [a-zA-Z0-9-]'
    });

    // ExistingKinesisStreamName
    const existingKinesisStreamNameParameter = new CfnParameter(this, 'ExistingKinesisStreamName', {
      type: 'String',
      description: 'The Kinesis Data Stream Name can be found in the Data streams in the Amazon Kinesis console.',
      allowedPattern: '[a-zA-Z0-9-_.]*',
      constraintDescription: 'Kinesis Stream Name should match the allowed pattern: [a-zA-Z0-9-_.]'
    });

    // CFN Metadata
    this.templateOptions.metadata = {
      'AWS::CloudFormation::Interface': {
        ParameterGroups: [
          {
            Label: { default: 'Required parameters' },
            Parameters: [loggingLevel.logicalId, userEmail.logicalId]
          },
          {
            Label: { default: '(Optional) Using your existing resources' },
            Parameters: [existingGreengrassGroupIDParameter.logicalId, existingKinesisStreamNameParameter.logicalId]
          }
        ],
        ParameterLabels: {
          [loggingLevel.logicalId]: { default: '* Logging Level' },
          [userEmail.logicalId]: { default: '* Initial User Email' },
          [existingGreengrassGroupIDParameter.logicalId]: { default: 'ID of the Existing AWS IoT Greengrass Group' },
          [existingKinesisStreamNameParameter.logicalId]: { default: 'Name of the Existing Data Stream in Kinesis Data Streams' }
        }
      }
    };

    // CFN Mappings
    const solutionMapping = new CfnMapping(this, 'Solution', {
      mapping: {
        Config: {
          SolutionId: 'SO0070',
          Version: 'VERSION_PLACEHOLDER',
          SendAnonymousUsage: 'Yes',
          S3Bucket: 'BUCKET_NAME_PLACEHOLDER',
          KeyPrefix: 'SOLUTION_NAME_PLACEHOLDER/VERSION_PLACEHOLDER'
        }
      }
    });
    const sendAnonymousUsage = solutionMapping.findInMap('Config', 'SendAnonymousUsage');
    const solutionId = solutionMapping.findInMap('Config', 'SolutionId');
    const solutionVersion = solutionMapping.findInMap('Config', 'Version');
    const sourceCodeBucket = Fn.join('-', [solutionMapping.findInMap('Config', 'S3Bucket'), Aws.REGION]);
    const sourceCodePrefix = solutionMapping.findInMap('Config', 'KeyPrefix');

    // CFN Conditions
    const sendAnonymousUsageCondition = new CfnCondition(this, 'SendAnonymousUsage', {
      expression: Fn.conditionEquals(sendAnonymousUsage, 'Yes')
    });
    const createGreengrassResourcesCondition = new CfnCondition(this, 'CreateGreengrassResources', {
      expression: Fn.conditionEquals(existingGreengrassGroupIDParameter.valueAsString, '')
    });
    const createKinesisResourcesCondition = new CfnCondition(this, 'CreateKinesisResources', {
      expression: Fn.conditionEquals(existingKinesisStreamNameParameter.valueAsString, '')
    });

    // Common Resources
    const commonResources = new CommonResourcesConstruct(this, 'M2C2CommonResources', {
      sourceCodeBucket
    });

    // Kinesis Streams, Kinesis Firehose, S3
    const dataStream = new DataStreamConstruct(this, 'M2C2Data', {
      s3LoggingBucket: commonResources.s3LoggingBucket
    });
    Aspects.of(dataStream).add(new ConditionAspect(createKinesisResourcesCondition));
    this.kinesisStreamName = Fn.conditionIf(createKinesisResourcesCondition.logicalId,
      dataStream.kinesisStreamName,
      existingKinesisStreamNameParameter.valueAsString
    ).toString();

    // Custom Resources
    const customResources = new CustomResourcesConstruct(this, 'M2C2CustomResources', {
      cloudWatchLogsPolicy: commonResources.cloudWatchLogsPolicy,
      existingGreengrassGroup: existingGreengrassGroupIDParameter.valueAsString,
      existingKinesisStream: existingKinesisStreamNameParameter.valueAsString,
      sendAnonymousUsageCondition,
      solutionConfig: {
        loggingLevel: loggingLevel.valueAsString,
        solutionId,
        solutionVersion,
        sourceCodeBucket: commonResources.sourceCodeBucket,
        sourceCodePrefix
      }
    });

    // Greengrass resources
    const greengrassResources = new GreengrassConstruct(this, 'M2C2GreengrassResources', {
      kinesisStreamName: this.kinesisStreamName,
      s3LoggingBucket: commonResources.s3LoggingBucket,
      solutionConfig: {
        solutionId,
        solutionVersion,
        sourceCodeBucket: commonResources.sourceCodeBucket,
        sourceCodePrefix
      }
    });
    Aspects.of(greengrassResources).add(new ConditionAspect(createGreengrassResourcesCondition));

    // SQS message consumer resources
    const sqsMessageConsumer = new SQSMessageConsumerConstruct(this, 'M2C2SQSMessageConsumer', {
      solutionConfig: {
        loggingLevel: loggingLevel.valueAsString,
        solutionId,
        solutionVersion,
        sourceCodeBucket: commonResources.sourceCodeBucket,
        sourceCodePrefix
      }
    });

    // M2C2 IoT Sitewise
    const iotSitewise = new IoTSitewiseConstruct(this, 'M2C2IoTSitewise', {
      greengrassGroupId: Fn.conditionIf(createGreengrassResourcesCondition.logicalId,
        greengrassResources.greengrassGroupId,
        existingGreengrassGroupIDParameter.valueAsString).toString()
    });

    // Connection Builder resources
    const connectionBuilder = new ConnectionBuilderConstruct(this, 'M2C2ConnectionBuilder', {
      cloudWatchLogsPolicy: commonResources.cloudWatchLogsPolicy,
      greengrassGroupId: Fn.conditionIf(createGreengrassResourcesCondition.logicalId,
        greengrassResources.greengrassGroupId,
        existingGreengrassGroupIDParameter.valueAsString).toString(),
      iotEndpointAddress: customResources.iotEndpointAddress,
      iotSitewiseGateway: iotSitewise.iotSitewiseGateway,
      kinesisStreamName: this.kinesisStreamName,
      logsTableArn: sqsMessageConsumer.logsTable.tableArn,
      logsTableName: sqsMessageConsumer.logsTable.tableName,
      solutionConfig: {
        loggingLevel: loggingLevel.valueAsString,
        sendAnonymousUsage,
        solutionId,
        solutionVersion,
        sourceCodeBucket: commonResources.sourceCodeBucket,
        sourceCodePrefix
      },
      uuid: customResources.uuid
    });

    // M2C2 API
    const api = new ApiConstruct(this, 'M2C2Api', {
      connectionBuilderLambdaFunction: connectionBuilder.connectionBuilderLambdaFunction
    });
    connectionBuilder.connectionBuilderLambdaFunction.addEnvironment('API_ENDPOINT', `${api.apiId}.execute-api.${Aws.REGION}.amazonaws.com`);

    // M2C2 UI and UI custom resources for UI assets
    const ui = new UiConstruct(this, 'M2C2Ui', {
      apiId: api.apiId,
      s3LoggingBucket: commonResources.s3LoggingBucket,
      userEmail: userEmail.valueAsString
    });

    customResources.setupUi({
      apiEndpoint: api.apiEndpoint,
      identityPoolId: ui.identityPoolId,
      loggingLevel: loggingLevel.valueAsString,
      uiBucket: ui.uiBucket,
      userPoolId: ui.userPoolId,
      webClientId: ui.webClientId
    });

    // Define the outputs
    new CfnOutput(this, 'CertKeyPairS3URL', { // NOSONAR: typescript:S1848
      condition: createGreengrassResourcesCondition,
      description: 'The solution generated a certificate and key pair for your Greengrass instance. Use this URL to download the tar archive to install on your Greengrass instance.',
      value: greengrassResources.certKeyPairS3URL
    });
    new CfnOutput(this, 'CertificateId', { // NOSONAR: typescript:S1848
      condition: createGreengrassResourcesCondition,
      description: 'ID of certificate generated by the solution.',
      value: greengrassResources.certificateId
    });
    new CfnOutput(this, 'CertificateArn', { // NOSONAR: typescript:S1848
      condition: createGreengrassResourcesCondition,
      description: 'ARN of certificate generated by the solution.',
      value: greengrassResources.certificateArn
    });
    new CfnOutput(this, 'M2C2DeviceGatewayThing', { // NOSONAR: typescript:S1848
      condition: createGreengrassResourcesCondition,
      description: 'The name of the IoT Device Gateway',
      value: greengrassResources.m2c2DeviceGatewayThing
    });
    new CfnOutput(this, 'M2C2DeviceGatewayThingArn', { // NOSONAR: typescript:S1848
      condition: createGreengrassResourcesCondition,
      description: 'The ARN of the IoT Device Gateway',
      value: greengrassResources.m2c2DeviceGatewayThingArn
    });
    new CfnOutput(this, 'M2C2GreengrassGroup', { // NOSONAR: typescript:S1848
      description: 'Greengrass group that needs to be deployed to the on-premises gateway',
      value: Fn.conditionIf(createGreengrassResourcesCondition.logicalId,
        greengrassResources.greengrassGroupId,
        existingGreengrassGroupIDParameter.logicalId).toString()
    });
    new CfnOutput(this, 'M2C2ConnectionControlRequestTopic', { // NOSONAR: typescript:S1848
      description: 'IoT Topic where connection controls need to be submitted',
      value: 'm2c2/job/{connectionName}'
    });
    new CfnOutput(this, 'M2C2DataBucket', { // NOSONAR: typescript:S1848
      condition: createKinesisResourcesCondition,
      description: 'Bucket where the connection telemetry data will be stored',
      value: dataStream.dataBucketName
    });
    new CfnOutput(this, 'M2C2KinesisStream', { // NOSONAR: typescript:S1848
      description: 'The Kinesis Data Stream that sends Greengrass Stream Manager data',
      value: this.kinesisStreamName
    });
    new CfnOutput(this, 'M2C2GreengrassBucket', { // NOSONAR: typescript:S1848
      condition: createGreengrassResourcesCondition,
      description: 'Bucket where the Greengrass configuration tar file will be stored',
      value: greengrassResources.greengrassBucket.bucketName
    });
    new CfnOutput(this, 'M2C2ConnectionMetadataTable', { // NOSONAR: typescript:S1848
      description: 'The DynamoDB table where the connections metadata will be stored',
      value: connectionBuilder.connectionDynamodbTableName
    });
    new CfnOutput(this, 'M2C2LogsTable', { // NOSONAR: typescript:S1848
      description: 'The DynamoDB table where the IoT topic info or error logs will be stored',
      value: sqsMessageConsumer.logsTable.tableName
    });
    new CfnOutput(this, 'UUID', { // NOSONAR: typescript:S1848
      description: 'Solution UUID',
      value: customResources.uuid
    });
    new CfnOutput(this, 'M2C2UIDomainName', { // NOSONAR: typescript:S1848
      description: 'The UI domain name',
      value: `https://${ui.cloudFrontDomainName}`
    });
  }