constructor()

in deployment/custom-deployment/lib/smart-product-solution-stack.ts [36:440]


  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    //=============================================================================================
    // DynamoDB Tables
    //=============================================================================================
    // Setting Table
    const settingsTable = new dynamodb.Table(this, 'SmartProductSettingsTable', {
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      serverSideEncryption: true,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      partitionKey: {
        name: 'settingId',
        type: dynamodb.AttributeType.STRING
      }
    })

    // Registration Table
    const registrationTable = new dynamodb.Table(this, 'SmartProductRegistrationTable', {
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      serverSideEncryption: true,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      partitionKey: {
        name: 'userId',
        type: dynamodb.AttributeType.STRING
      },
      sortKey: {
        name: 'deviceId',
        type: dynamodb.AttributeType.STRING
      }
    })

    registrationTable.addGlobalSecondaryIndex({
      indexName: 'deviceId-index',
      partitionKey: {
        name: 'deviceId',
        type: dynamodb.AttributeType.STRING
      },
      projectionType: dynamodb.ProjectionType.ALL
    })

    registrationTable.addGlobalSecondaryIndex({
      indexName: 'userId-deviceName-index',
      partitionKey: {
        name: 'userId',
        type: dynamodb.AttributeType.STRING
      },
      sortKey: {
        name: 'deviceName',
        type: dynamodb.AttributeType.STRING
      },
      projectionType: dynamodb.ProjectionType.ALL
    })

    // Command Table
    const commandTable = new dynamodb.Table(this, 'SmartProductCommandTable', {
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      serverSideEncryption: true,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      partitionKey: {
        name: 'deviceId',
        type: dynamodb.AttributeType.STRING
      },
      sortKey: {
        name: 'commandId',
        type: dynamodb.AttributeType.STRING
      }
    })

    commandTable.addGlobalSecondaryIndex({
      indexName: 'deviceId-updatedAt-index',
      partitionKey: {
        name: 'deviceId',
        type: dynamodb.AttributeType.STRING
      },
      sortKey: {
        name: 'updatedAt',
        type: dynamodb.AttributeType.STRING
      },
      projectionType: dynamodb.ProjectionType.ALL
    })

    // Event Table
    const eventsTable = new dynamodb.Table(this, 'SmartProductEventsTable', {
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      serverSideEncryption: true,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      partitionKey: {
        name: 'deviceId',
        type: dynamodb.AttributeType.STRING
      },
      sortKey: {
        name: 'id',
        type: dynamodb.AttributeType.STRING
      }
    })

    eventsTable.addGlobalSecondaryIndex({
      indexName: 'userId-timestamp-index',
      partitionKey: {
        name: 'userId',
        type: dynamodb.AttributeType.STRING
      },
      sortKey: {
        name: 'timestamp',
        type: dynamodb.AttributeType.NUMBER
      },
      projectionType: dynamodb.ProjectionType.ALL
    })

    eventsTable.addGlobalSecondaryIndex({
      indexName: 'deviceId-timestamp-index',
      partitionKey: {
        name: 'deviceId',
        type: dynamodb.AttributeType.STRING
      },
      sortKey: {
        name: 'timestamp',
        type: dynamodb.AttributeType.NUMBER
      },
      projectionType: dynamodb.ProjectionType.ALL
    })

    // Reference Table
    const referenceTable = new dynamodb.Table(this, 'SmartProductReferenceTable', {
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      serverSideEncryption: true,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      partitionKey: {
        name: 'deviceId',
        type: dynamodb.AttributeType.STRING
      },
      sortKey: {
        name: 'modelNumber',
        type: dynamodb.AttributeType.STRING
      }
    })

    //=============================================================================================
    //  Permissions and Policies
    //=============================================================================================
    // Helper Policy
    const helperPolicy = new iam.Policy(this, 'SmartProductHelperPolicy', {
      statements: [
        new iam.PolicyStatement({
          actions: [
            'logs:CreateLogGroup',
            'logs:CreateLogStream',
            'logs:PutLogEvents'
          ],
          resources: [`arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/lambda/*`]
        }),
        new iam.PolicyStatement({
          actions: [
            'iot:UpdateAccountAuditConfiguration',
            'iot:CreateScheduledAudit',
            'iot:DeleteAccountAuditConfiguration',
            'iot:DeleteScheduledAudit',
            'iot:DescribeEndpoint',
            'iot:DescribeAccountAuditConfiguration',
            'iotanalytics:ListChannels'
          ],
          resources: ['*']
        }),
        new iam.PolicyStatement({
          actions: [
            'iot:CreateThingType'
          ],
          resources: [`arn:aws:iot:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:thingtype/SmartProduct`]
        }),
        new iam.PolicyStatement({
          actions: [
            'iam:PassRole'
          ],
          resources: [`arn:aws:iam::${cdk.Aws.ACCOUNT_ID}:role/*`]
        })
      ]
    })

    const helperPolicyResource = helperPolicy.node.findChild('Resource') as iam.CfnPolicy;
    helperPolicyResource.cfnOptions.metadata = {
      cfn_nag: {
        rules_to_suppress: [{
          id: 'W12',
          reason: `The * resource allows exchange information with solution resources.`
        }]
      }
    }

    // Cognito Helper Policy
    const cognitoHelperPolicy = new iam.Policy(this, 'SmartProductCognitoHelperPolicy', {
      statements: [
        new iam.PolicyStatement({
          actions: [
            'logs:CreateLogGroup',
            'logs:CreateLogStream',
            'logs:PutLogEvents'
          ],
          resources: [`arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/lambda/*`]
        }),
        new iam.PolicyStatement({
          actions: [
            'dynamodb:PutItem'
          ],
          resources: [`${settingsTable.tableArn}`]
        })
      ]
    })

    const congintoHelperPolicyResource = cognitoHelperPolicy.node.findChild('Resource') as iam.CfnPolicy;
    congintoHelperPolicyResource.cfnOptions.metadata = {
      cfn_nag: {
        rules_to_suppress: [{
          id: 'W12',
          reason: `The * resource allows to access its own logs.`
        }]
      }
    }

    //=============================================================================================
    // Helper Function
    //=============================================================================================
    const s3BuildOutputBucket = s3.Bucket.fromBucketArn(this, 'BuildOutputBucket', `arn:aws:s3:::${process.env.BUILD_OUTPUT_BUCKET}`);

    const smartProductHelperLambdaRole = new iam.Role(this, 'SmartProductHelperRole', {
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
    })

    const smartProductHelperFunction = new lambda.SingletonFunction(this, 'SmartProductHelper', {
      uuid: 'helperFunction',
      functionName: 'SmartProductHelper',
      description: 'Smart Product Solution deployment helper',
      code: new lambda.S3Code(
        s3BuildOutputBucket,
        `smart-product-solution/${spConfig.default.version}/smart-product-helper.zip`
      ),
      handler: 'index.handler',
      runtime: lambda.Runtime.NODEJS_12_X,
      timeout: cdk.Duration.seconds(300),
      memorySize: 256,
      role: smartProductHelperLambdaRole,
      environment: {
        LOGGING_LEVEL: '2'
      }
    })

    const smartProductCognitoHelperLambdaRole = new iam.Role(this, 'SmartProductCognitoHelperRole', {
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
    })

    const smartProductCognitoHelperFunction = new lambda.Function(this, 'SmartProductCognitoHelper', {
      functionName: 'SmartProductCognitoHelper',
      description: 'Smart Product Solution Cognito trigger',
      code: new lambda.S3Code(
        s3BuildOutputBucket,
        `smart-product-solution/${spConfig.default.version}/smart-product-cognito.zip`
      ),
      handler: 'index.handler',
      runtime: lambda.Runtime.NODEJS_12_X,
      role: smartProductCognitoHelperLambdaRole,
      timeout: cdk.Duration.seconds(60),
      memorySize: 128,
      environment: {
        SETTINGS_TBL: `${settingsTable.tableName}`,
        LOGGING_LEVEL: '2'
      }
    })

    smartProductCognitoHelperLambdaRole.attachInlinePolicy(cognitoHelperPolicy)
    smartProductHelperLambdaRole.attachInlinePolicy(helperPolicy)

    //=============================================================================================
    // Check Requirements Event
    //=============================================================================================
    const checkRquirements = new cfn.CustomResource(this, 'SmartProductCheckRequirements', {
      provider: cfn.CustomResourceProvider.lambda(smartProductHelperFunction),
      resourceType: 'Custom::CheckRequirements',
      properties: {
        Region: `${this.region}`,
        DeploySmartProductTelemetry: spConfig.default.telemetry.deploy,
        DeploySmartProductEvent: spConfig.default.events.deploy,
        DeploySmartProductJitr: spConfig.default.jitr.deploy,
        DeploySmartProductApi: spConfig.default.api.deploy,
        DeploySmartProductSampleOwnerWebApp: spConfig.default.ownerapp.deploy,
        EnableSmartProductDefenderEnable: spConfig.default.defender.deploy,
        TelemetryTopic: (spConfig.default.telemetry.deploy) ? (spConfig.default.telemetry.env.telemetryTopic) : (''),
        EventTopic: (spConfig.default.events.deploy) ? (spConfig.default.events.env.eventTopic) : ('')
      }
    })
    checkRquirements.node.addDependency(helperPolicy.node.findChild('Resource') as cdk.Resource)

    const appUuid = new cfn.CustomResource(this, 'SmartProductAppUuid', {
      provider: cfn.CustomResourceProvider.lambda(smartProductHelperFunction),
      resourceType: 'Custom::CreateUuid',
      properties: {
        Region: `${this.region}`
      }
    })
    appUuid.node.addDependency(helperPolicy.node.findChild('Resource') as cdk.Resource)

    //=============================================================================================
    // Cognito
    //=============================================================================================
    const userPool = new cognito.UserPool(this, 'SmartProductUserPool', {
      userPoolName: 'smart-product-pool',
      signInType: cognito.SignInType.EMAIL,
      autoVerifiedAttributes: [cognito.UserPoolAttribute.EMAIL],
      lambdaTriggers: {
        postConfirmation: smartProductCognitoHelperFunction
      }
    })

    const userPoolClient = new cognito.UserPoolClient(this, 'SmartProductAppClient', {
      userPool: userPool,
      userPoolClientName: 'smart-product-app',
      generateSecret: false
    })

    this.apiEndpoint = '';
    //=============================================================================================
    // API Constructor
    //=============================================================================================
    if (spConfig.default.api.deploy) {
      const smartProductApi = new SmartProductApi(this, 'SmartProductApi', {
        helperFunction: cfn.CustomResourceProvider.lambda(smartProductHelperFunction),
        helperFunctionRole: smartProductHelperLambdaRole,
        userPool: userPool,
        userPoolClient: userPoolClient,
        settingsTable: settingsTable,
        registrationTable: registrationTable,
        commandTable: commandTable,
        eventsTable: eventsTable,
        referenceTable: referenceTable,
        solutionVersion: spConfig.default.version,
        solutionId: solutionId,
        solutionUuid: appUuid.getAtt('UUID').toString(),
        anonymousData: `${spConfig.default.sendAnonymousUsage}`
      });

      this.apiEndpoint = smartProductApi.apiEndpoint;
    }

    //=============================================================================================
    // Device Defender Constructor
    //=============================================================================================
    if (spConfig.default.defender.deploy) {
      new SmartProductDeviceDefender(this, 'SmartProductDeviceDefender', {
        helperFunction: cfn.CustomResourceProvider.lambda(smartProductHelperFunction),
        helperFunctionPolicy: helperPolicy
      })
    }

    //=============================================================================================
    // Event Constructor
    //=============================================================================================
    if (spConfig.default.events.deploy) {
      new SmartProductEvent(this, 'SmartProductEvent', {
        eventsTable: eventsTable,
        registrationTable: registrationTable,
        userPool: userPool,
        settingsTable: settingsTable,
        solutionVersion: spConfig.default.version,
        eventTopic: spConfig.default.events.env.eventTopic
      })
    }

    //=============================================================================================
    // JITR Constructor
    //=============================================================================================
    if (spConfig.default.jitr.deploy) {
      new SmartProductJITR(this, 'SmartProductJITR', {
        spTelemetryTopic: (spConfig.default.telemetry.deploy) ? (spConfig.default.telemetry.env.telemetryTopic) : (''),
        spEventTopic: (spConfig.default.events.deploy) ? (spConfig.default.events.env.eventTopic) : (''),
        solutionVersion: spConfig.default.version,
        registrationTable: registrationTable,
        solutionId: solutionId,
        solutionUuid: appUuid.getAtt('UUID').toString(),
        anonymousData: `${spConfig.default.sendAnonymousUsage}`
      })
    }

    //=============================================================================================
    // Owner Web App Constructor
    //=============================================================================================
    if (spConfig.default.ownerapp.deploy && spConfig.default.api.deploy) {
      new OwnerWebApp(this, 'SmartProductOwnerWebApp', {
        helperFunction: cfn.CustomResourceProvider.lambda(smartProductHelperFunction),
        helperFunctionRole: smartProductHelperLambdaRole,
        userPool: userPool,
        userPoolClient: userPoolClient,
        apiEndpoint: this.apiEndpoint,
        solutionVersion: spConfig.default.version
      })
    }

    //=============================================================================================
    // Telemetry Constructor
    //=============================================================================================
    if (spConfig.default.telemetry.deploy) {
      new SmartProductTelemetry(this, 'SmartProductTelemetry', {
        telemetryTopic: spConfig.default.telemetry.env.telemetryTopic,
        solutionVersion: spConfig.default.version
      })
    }
  }