[AWS CDK] Amazon QuickSight で Athena の Dataset を作成してみた

2022.08.17

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、CX 事業本部 IoT 事業部の若槻です。

前回のエントリでは Amazon QuickSight のコンソールから Athena の Dataset を作成してみました。(そして数々のエラーに遭遇しとてもハマりました。)

今回は、AWS CDKで QuickSight の Athena の Dataset を作成してみました。また作成するのは前回と同様既存のユーザーがアクセスがアクセス可能な Dataset とします。

やってみた

ざっくりと次のような手順で実装を行いました。

  1. データソースとなる Resource の CDK Deploy
  2. データソースとなる Resource のアクセス権限を既存のユーザーに手動設定
  3. QuickSight の Dataset となる Resource の CDK Deploy

実装

AWS CDK v2(TypeScript)で Stack の記述を作成します。

まずデータソースとなる Glue Table およびデータ格納用 S3 Bucket を作成する Stack です。

lib/aws-app-stack.ts

import { Construct } from 'constructs';
import { aws_s3, Stack, StackProps, RemovalPolicy } from 'aws-cdk-lib';
import * as glue_alpha from '@aws-cdk/aws-glue-alpha';

export class AwsAppStack extends Stack {
  public readonly glueDatabase: glue_alpha.Database;
  public readonly glueTable: glue_alpha.Table;
  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id, props);

    // データ格納バケット
    const dataBucket = new aws_s3.Bucket(this, 'dataBucket', {
      removalPolicy: RemovalPolicy.DESTROY,
    });

    // Glueデータベース
    const glueDatabase = new glue_alpha.Database(this, 'glueDatabase', {
      databaseName: 'glue_database',
    });
    this.glueDatabase = glueDatabase;

    // Glueテーブル
    const glueTable = new glue_alpha.Table(this, 'glueTable', {
      tableName: 'glue_table',
      database: glueDatabase,
      bucket: dataBucket,
      s3Prefix: 'data/',
      dataFormat: glue_alpha.DataFormat.JSON,
      columns: [
        {
          name: 'id',
          type: glue_alpha.Schema.STRING,
        },
        {
          name: 'count',
          type: glue_alpha.Schema.FLOAT,
        },
        {
          name: 'area',
          type: glue_alpha.Schema.STRING,
        },
      ],
    });
    this.glueTable = glueTable;
  }
}

次に、QuickSight の Datasource および Dataset を作成する Stack です。Dataset の Resource にはpermissionsを設定し、既存のユーザーが Dataset にコンソールからアクセスできるようにしています。

lib/process-stack.ts

import { Construct } from 'constructs';
import { aws_quicksight, Stack, StackProps } from 'aws-cdk-lib';
import * as glue_alpha from '@aws-cdk/aws-glue-alpha';

interface ProcessStackProps extends StackProps {
  userName: string;
  glueDatabase: glue_alpha.Database;
  glueTable: glue_alpha.Table;
}

export class ProcessStack extends Stack {
  constructor(scope: Construct, id: string, props: ProcessStackProps) {
    super(scope, id, props);

    // QuickSightデータソース
    const quickSightAthenaDataSource = new aws_quicksight.CfnDataSource(
      this,
      'quickSightAthenaDataSource',
      {
        awsAccountId: this.account,
        dataSourceId: 'quickSightAthenaDataSource',
        name: 'quickSightAthenaDataSource',
        type: 'ATHENA',
        dataSourceParameters: {
          athenaParameters: {
            workGroup: 'primary',
          },
        },
      }
    );

    // QuickSightデータセット
    new aws_quicksight.CfnDataSet(this, 'quickSightAthenaDataSet', {
      name: 'quickSightAthenaDataSet',
      awsAccountId: this.account,
      dataSetId: 'quickSightAthenaDataSet',
      physicalTableMap: {
        quickSightAthenaDataSetPhysicalTableMap: {
          relationalTable: {
            dataSourceArn: quickSightAthenaDataSource.attrArn,
            catalog: 'AwsDataCatalog',
            schema: props.glueDatabase.databaseName,
            name: props.glueTable.tableName,
            inputColumns: [
              {
                name: 'id',
                type: 'STRING',
              },
              {
                name: 'count',
                type: 'DECIMAL',
              },
              {
                name: 'area',
                type: 'STRING',
              },
            ],
          },
        },
      },
      logicalTableMap: {
        quickSightAthenaDataSetPhysicalTableMap: {
          alias: props.glueTable.tableName,
          source: {
            physicalTableId: 'quickSightAthenaDataSetPhysicalTableMap',
          },
        },
      },
      importMode: 'SPICE',
      permissions: [
        {
          principal: `arn:aws:quicksight:${this.region}:${this.account}:user/default/${props.userName}`,
          actions: [
            'quicksight:PassDataSet',
            'quicksight:DescribeIngestion',
            'quicksight:CreateIngestion',
            'quicksight:UpdateDataSet',
            'quicksight:DeleteDataSet',
            'quicksight:DescribeDataSet',
            'quicksight:CancelIngestion',
            'quicksight:DescribeDataSetPermissions',
            'quicksight:ListIngestions',
            'quicksight:UpdateDataSetPermissions',
          ],
        },
      ],
    });
  }
}

App 層ではAwsAppStackからProcessStackへ Glue の Resource を渡しています。

bin/aws-cdk-v2-project.ts

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { ProcessStack } from '../lib/process-stack';
import { AwsAppStack } from '../lib/aws-app-stack';

const app = new cdk.App();

const userName = app.node.tryGetContext('userName');

const awsAppStack = new AwsAppStack(app, 'AwsAppStack', {});

new ProcessStack(app, 'ProcessStack', {
  userName: userName,
  glueDatabase: awsAppStack.glueDatabase,
  glueTable: awsAppStack.glueTable,
});

まずデータソース側の Stack を CDK Deploy します。

cdk deploy AwsAppStack

デプロイに成功したら、QuickSight access to AWS servicesを開きます。

[Amazon Athena]が未チェックであればチェックを入れます。

[Amazon Athena permissions]のダイアログが出たら[Skip]をクリックして閉じます。

[Select S3 buckets]をクリック。

先程 CDK Deploy で作成した Bucket にチェックを入れ、[Finish]をクリック。

[Save]をクリックしてアクセス権限の変更を保存します。

ここでManage usersコンソールにて Dataset を利用可能としたい既存のユーザーの Username を確認します。

Context に Username を指定して、QuickSight の Datasource および Dataset を CDK Deploy で作成します。

cdk deploy ProcessStack -c userName=${USER_NAME}

デプロイに成功したら、コンソールのDatasetsを見ると、Dataset が作成されていることが確認できました!

ここでデータソースの Bucket に次の JSON ファイルをアップロードします。

data1.json

{"id":"u001","count":3,"area":"Shinjuku"}
{"id":"u001","count":1,"area":"Akihabara"}
{"id":"u002","count":5,"area":"Ikebukuro"}
{"id":"u002","count":2,"area":"Akihabara"}
{"id":"u003","count":4,"area":"Shinjuku"}

aws s3 cp data1.json s3://${BUCKET_NAME}/data/data1.json

Dataset を開いて Refresh すると、レコードが読み込めています!

おわりに

AWS CDK で QuickSight の Athena の Dataset を作成して、既存のユーザーで使えるようにしてみました。

最初 CDK Stack で Dataset の Permission を設定していなかったため、作成されたはずの Dataset が既存のユーザーでコンソールから見れなくて焦りました。QuickSight ではコンソールにサインインするユーザーに権限を明示的に付与するということを CDK で実装する場合は意識する必要がありました。

以上