aws / aws-cdk-rfcs

RFCs for the AWS CDK

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

RUM AppMonitor L2 Construct

WinterYukky opened this issue · comments

Description

I am delighted to be able to create a RUM Monitor with CloudFormation.
However, RUM AppMonitor L1 Construct doesn't provide code snippet and AppMonitorID, so
to embed client code user must access management console or run AWS CLI for get AppMonitorID after created RUM AppMonitor by RUM AppMonitor L1 Construct. This is a bit far from full automation, so I propose an L2 construct that can solve these problems..

Overview

Define AppMonitor

Define AppMonitor to your stack:

const appMonitor = new AppMonitor(this, 'AppMonitor', {
  domain: 'my-website.com'
});

Authorizer

To use CloudWatch RUM, your application must have authorization.

You have three options to set up authorization:

  • Let CloudWatch RUM create a new Amazon Cognito identity pool for the application. This method requires the least effort to set up. It's the default option.
    The identity pool will contain an unauthenticated identity. This allows the CloudWatch RUM web client to send data to CloudWatch RUM without authenticating the user of the application.
    The Amazon Cognito identity pool has an attached IAM role. The Amazon Cognito unauthenticated identity allows the web client to assume the IAM role that is authorized to send data to CloudWatch RUM.
  • Use an existing Amazon Cognito identity pool. In this case, you must pass the IAM role as well that is attached to the identity pool.
  • Use authentication from an existing identity provider that you have already set up. In this case, you must get credentials from the identity provider and your application must forward these credentials to the RUM web client.

Creates a new Amazon Cognito identity pool

By default, AppMonitor creates a new Amazon Cognito identity pool.
This is the simplest option to set up, and if you choose this no further setup steps are required. You must have administrative permissions to use this option. For more information, see IAM policies to use CloudWatch RUM.

const appMonitor = new AppMonitor(this, 'AppMonitor', {
  domain: 'my-website.com'
});

Use an existing Amazon Cognito identity pool

If you want to use an existing Amazon Cognito identity pool,
you need to pass the identityPool and the role that associated with your identity pool.

import * as identitypool from '@aws-cdk/aws-cognito-identitypool';
import * as iam from '@aws-cdk/aws-iam';

const identityPool = identitypool.IdentityPool.fromIdentityPoolId(this, 'IdentityPool', 'us-east-1:dj2823ryiwuhef937');
const role = iam.Role.fromRoleName(this, 'Role', 'UnauthenticatedRole');

const appMonitor = new AppMonitor(this, 'AppMonitor', {
  domain: 'my-website.com',
  identityPool,
  role
});

Use Third-party provider

If you want to use third-party authenticator, you can only pass a role that associated with your identity pool.

import * as iam from '@aws-cdk/aws-iam';

declare const role: iam.IRole;

const appMonitor = new AppMonitor(this, 'AppMonitor', {
  domain: 'my-website.com',
  role
});

Add the following to your application to have it pass the credentials from your provider to CloudWatch RUM. Insert the line so that it runs after a user has signed in to your application and the application has received the credentials to use to access AWS.

cwr('setAwsCredentials', {/* Credentials or CredentialProvider */});

For more information, see Amazon Amazon CloudWatch User Guide for to use Third-party provider.

Code Snippet

AppMonitor generates code snippet as you would create them on the management console, except that there are no <script> tags.
The reason there is no <script> tag in the code snippet is that it is intended to be loaded as a regular JavaScript file from your HTML document.
This allows you to seamlessly embed RUM into your application without having to rewrite your HTML document when deploying.

The code snippet is integrated with aws-s3-deployment,
so you can deploy directly to any S3 bucket using BucketDeployment.

An example of deploying a static website and code snippet to S3 is shown below.

import * as s3 from '@aws-cdk/aws-s3';
import * as s3deployment from '@aws-cdk/aws-s3-deployment';

declare const myWebSiteBucket: s3.Bucket;
const website = s3deployment.Source.asset(path.join(__dirname, 'my-website'));

const appMonitor = new AppMonitor(this, 'AppMonitor', {
  domain: 'my-website.com'
});

new s3deployment.BucketDeployment(this, 'BucketDeployment', {
  sources: [website, appMonitor.codeSnippet],
  destinationBucket: myWebSiteBucket
});

Your website must load the code snippet with the object key (default: rum.js).

<html>
  <head>
    <!-- add next line -->
    <script src="/rum.js" async="true"></script>
  </head>
  <body>Hello RUM</body>
</html>

If you want to use another name for will generates code snippet, then you can pass the objectKey to addCodeSnippet argument.

declare const appMonitor: AppMonitor;

const codeSnippet = appMonitor.addCodeSnippet('CodeSnippet', {
  objectKey: 'my-rum.js'
});

RUM web client configuration

If you want to use RUM web client configuration (e.g pageIdFormat), you can pass options to addCodeSnippet argument.

const codeSnippet = appMonitor.addCodeSnippet('CodeSnippet', {
  applicationVersion: '1.1.0',
  pageIdFormat: PageIdFormat.HASH,
});

Class Diagram

 classDiagram
      IAppMonitor <|.. AppMonitorBase
      AppMonitorBase <|-- AppMonitor
      ISource <|.. CodeSnippet
      IAppMonitor -- CodeSnippet
      class IAppMonitor{
          <<interface>>
          +string appMonitorId*
          +string appMonitorArn*
          +string appMonitorName*
          +CodeSnippet codeSnippet*
          +CodeSnippet addCodeSnippet()*
      }
      class AppMonitorBase{
          <<abstract>>
          +string appMonitorId
          +string appMonitorArn
          +string appMonitorName
          +CodeSnippet codeSnippet
          +CodeSnippet addCodeSnippet()
      }
      class AppMonitor{
      }
      class CodeSnippet{
          +string value
          +SourceConfig bind()
      }

Implementation

RUM AppMonitor L1 Construct doesn't provide an AppMonitorID so we need to use custom resource for rum:GetAppMonitor. And generate code snippet also needs custom resource because to avoid XSS.

  • I may be able to implement this feature request
  • This feature might incur a breaking change

Roles

Role User
Proposed by @WinterYukky
Author(s) @alias, @alias, @alias
API Bar Raiser @madeline-k
Stakeholders @alias, @alias, @alias

See RFC Process for details

Workflow

  • Tracking issue created (label: status/proposed)
  • API bar raiser assigned (ping us at #aws-cdk-rfcs if needed)
  • Kick off meeting
  • RFC pull request submitted (label: status/review)
  • Community reach out (via Slack and/or Twitter)
  • API signed-off (label api-approved applied to pull request)
  • Final comments period (label: status/final-comments-period)
  • Approved and merged (label: status/approved)
  • Execution plan submitted (label: status/planning)
  • Plan approved and merged (label: status/implementing)
  • Implementation complete (label: status/done)

Author is responsible to progress the RFC according to this checklist, and
apply the relevant labels to this issue so that the RFC table in README gets
updated.

@madeline-k will be API bar raiser for this one.

@madeline-k will be API bar raiser for this one.

Thank you, @madeline-k.
I will send you a PR in the near future 🕊️

Hello, @WinterYukky. I have some feedback and questions about the proposed design here.

  1. For the proposed CognitoIdentityPoolAuthorizer construct, we should integrate with the existing IdentityPool construct. The CognitoIdentityPoolAuthorizer class is probably not necessary, and the AppMonitor constructor can just take an IdentityPool in the props. If there are features needed that are missing in the IdentityPool L2, then we should implement more functionality in that module, instead of in aws-rum.
  2. Can you explain in a bit more detail how this will fully automate injecting the RUM code snippet into the user's web application? This part is the biggest value-add for this L2. We might need to use an AwsCustomResource to get the code snippet after the AppMonitor is deployed.
  3. It would be ideal if the customer never has to call appmonitor.generateCodeSnippet(), and instead we just generate the code snippet and insert it into the right place for them.

I will take a look at the PR you opened as well, it's possible you've already addressed these questions there.

Also, if you haven't seen it yet, here's a link to the design guidelines: https://github.com/aws/aws-cdk/blob/master/docs/DESIGN_GUIDELINES.md. These should be helpful in both the design and execution phases.

Thanks for your review.

  1. For the proposed CognitoIdentityPoolAuthorizer construct, we should integrate with the existing IdentityPool construct. The CognitoIdentityPoolAuthorizer class is probably not necessary, and the AppMonitor constructor can just take an IdentityPool in the props. If there are features needed that are missing in the IdentityPool L2, then we should implement more functionality in that module, instead of in aws-rum.

Yes, I strongly agree with your suggestion.
I tried to use IIdentityPool interface as props before I created this PR. Then I got this lint error [no-experimental-dependencies] so I used identityPoolId.
Can I fix this lint error?

[edited 2022/03/24]
I find how to fix this. I can add aws-cognito-identitypool to rule.ts.
This will allowed by you?

error log
$ yarn build
yarn run v1.22.17
$ cdk-build
warning JSII6: A "peerDependency" on "constructs" at "^3.3.69" means you should take a "devDependency" on "constructs" at "3.3.69" (found "^3.3.69")"
In package package.json
- [no-experimental-dependencies] It is not allowed to depend on experimental modules. @aws-cdk/aws-rum added a dependency on experimental module @aws-cdk/aws-cognito-identitypool
Error: Some package.json files had errors
    at main (/workspaces/aws-cdk/tools/@aws-cdk/pkglint/bin/pkglint.js:30:15)
    at Object.<anonymous> (/workspaces/aws-cdk/tools/@aws-cdk/pkglint/bin/pkglint.js:33:1)
    at Module._compile (internal/modules/cjs/loader.js:1085:14)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
    at Module.load (internal/modules/cjs/loader.js:950:32)
    at Function.Module._load (internal/modules/cjs/loader.js:790:12)
    at Module.require (internal/modules/cjs/loader.js:974:19)
    at require (internal/modules/cjs/helpers.js:101:18)
    at Object.<anonymous> (/workspaces/aws-cdk/tools/@aws-cdk/pkglint/bin/pkglint:2:1)
    at Module._compile (internal/modules/cjs/loader.js:1085:14)
  1. Can you explain in a bit more detail how this will fully automate injecting the RUM code snippet into the user's web application? This part is the biggest value-add for this L2. We might need to use an AwsCustomResource to get the code snippet after the AppMonitor is deployed.

OK, I merge and fixed description of code snippet.
We need to use CustomResourse not a AwsCustomResource to validate value.

  1. It would be ideal if the customer never has to call appmonitor.generateCodeSnippet(), and instead we just generate the code snippet and insert it into the right place for them.

Could you be more specific?
I prefer to use generateCodeSnippet for more flexibility, but this may be because I don't understand your idea.

Method calls are still required, but I have tried to make it possible to retrieve code snippets as s3-deployment.Source.

Also, if you haven't seen it yet, here's a link to the design guidelines: https://github.com/aws/aws-cdk/blob/master/docs/DESIGN_GUIDELINES.md. These should be helpful in both the design and execution phases.

I was looking for this! Thank you!

We'd love to see this. Any update?

whats the status here? I am refactoring something and would have been the time to remove a double deploy step from my cdn stack. #sadface

Any update?