PowerAppsで簡単な翻訳アプリを作成する方法

今回はPowerAppsでMicrosoftのTranslatorというサービスを使用して簡単な翻訳アプリを作成してみようと思います。

  • PowerAppsとは
  • コネクタとは
  • PowerAppsでアプリを作成する
    • Power Apps Studioを開き、空のアプリの作成する
    • まずは基本的な機能を実装する
      • アイテムの配置
      • アクションを設定してみる
    • コネクタと連携してみる
      • アクションの編集
    • まとめ
    • 参考

PowerAppsとは

Power Apps は、ビジネス ニーズに合ったカスタム アプリを構築するために短時間で開発できる環境を提供するプラットフォームです。

Power Apps とは - Power Apps | Microsoft Learn

続きを読む

コンシューマ駆動契約テスト用ツールのPactを使う

以前の記事で、Spring BootのソースコードからOpenAPI仕様のフォーマットのAPI定義書のYamlファイルを生成するようにしました。このYamlファイルを受け取ったフロントエンド・エンジニアはバックエンドの仕様を確認しながら開発を開始できるようになります。

しかしながら、バックエンドの開発が進んでも、フロントエンド開発チームが想定している仕様が遵守される保証はありません。実際、多くの現場ではバックエンド開発チームが断りなくAPI仕様を変更したものをデプロイする事がよくあります。その結果、アプリケーションが動作しなくなり、原因究明のためフロントエンドの開発がストップしてしまいます。

このような事態を防ぐにはどうしたらよいのでしょうか? まず、フロントエンド(コンシューマ)側が想定しているAPI仕様に基づいてAPIのテストスイートを作成します。そして、バックエンド・サーバ(プロバイダ)はこのテストに合格するか、バックエンド開発チームがAPIの仕様変更をフロントエンド開発チームの了承を得ない限り、デプロイを禁止するようにすればよいのです。

この考えに基づくテストの事を、コンシューマ駆動契約(Consumer Driven Contract: CDC)テストと呼びます。

本記事では、CDCテスト・ツールであるPactを使い、コンシューマをTypeScriptで書かれたReactアプリケーション、プロバイダ側をJavaで書かれたSpring Bootアプリケーションとした場合の、CDCテストの例を解説します。

続きを読む

OpenAPIのAPI定義書をソースコードから自動生成する

ほとんどのWebサービスの開発では、Webサービスのフロントエンドを担当するフロントエンド・エンジニアとバックエンドを担当するバックエンド・エンジニアに分かれて開発します。フロントエンドは、バックエンド側が提供するHTTPベースのAPIを利用して構築されます。このため、フロントエンドの開発にはバックエンドのAPI仕様書が必須になります。

API仕様書は最近ではOpenAPI仕様のフォーマットに従って(ほとんどはyamlファイルで)書かれる事が多くなりました。ただし、バックエンドのプログラマとしてはAPIの仕様書を書くよりも、バックエンドのプログラムそのものを書きたいのが本音だと思います。そうは言っても、フロントエンド・エンジニアからはAPIの仕様を明文化してもらわないと開発しにくいとか、プロジェクトマネージャーから、インターフェースの部分の仕様だけでも先に決めてから実装すべきとかの圧力がかかる事が多いのも事実です。

そうやって仕方なく仕様書を書いていると、なぜ、Javaでプログラミングする時はJavadocで、Pythonでプログラミングする時はpydocでドキュメントを生成するのに、Web APIはエディタでyamlファイルを書いているのだろうと疑問に思えてきます。

実はSpring Bootでバックエンドの開発している場合は自動生成できるのです。本記事ではspringdoc-openapiを使って、OpenAPI仕様に従ったAPI定義書を自動生成する方法を、タスク管理管理サービスを例として解説します。

続きを読む

既存のCognitoにTerraformからLambdaトリガーを設定する

Terraform でインフラの構成構築を行っている際に、既存の CognitoにLambdaトリガーを取り付けたい、というシーンがありました。

Cognito IDP の Data Sourcesdata ブロックで表現される、Terraformの外部で定義された情報を参照できるリソース)を頼りにトリガーとなる Lambda の設定ができないかと Terraform のリファレンスを眺めていたのですが、残念ながら既存のCognitoに対して変更を行えそうなリソースが見つかりませんでした。

そこで、Terraform から AWS CLI のコマンドを実行して Lambda の取り付けができないかと考えたのですが、いくつか注意点があったのでその紹介です。

前提

Terraform 自体やシェルスクリプトの説明は行いません。

Lambdaの作成する準備

まず、検証用の Lambda 関数をデプロイするためのインフラコードを作成します。

CognitoへのLambdaの取り付けが確認できれば良いので、以下の Terraform のインフラコードで空っぽのファイルをzip圧縮したダミーのコードを持つ Lambda関数を作成します。

※ local.region はお使いのリージョンに読み替えてください。

locals {
  region = "us-west-2"
}
 
provider "aws" {
  region                   = local.region
  shared_credentials_files = ["~/.aws/credentials"]
  profile                  = "dev"
}
 
data "archive_file" "dummy" {
  type        = "zip"
  output_path = "${path.module}/dummy.zip"
  source {
    content  = "dummy"
    filename = "bootstrap"
  }
}
 
resource "aws_iam_role" "iam_for_lambda" {
  name = "iam_for_lambda"
 
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}
 
resource "aws_lambda_function" "cognito_trigger_lambda" {
  function_name = "cognito-trigger-lambda"
  role          = aws_iam_role.iam_for_lambda.arn
 
  filename = data.archive_file.dummy.output_path
 
  runtime = "nodejs14.x"
  handler = "index.test"
}

update-user-pool コマンド

AWS CLI のコマンド cognito-idp update-user-pool でCognitoにLambdaトリガーを取り付けることができますが、注意が必要です。

https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cognito-idp/update-user-pool.html

If you don’t provide a value for an attribute, it will be set to the default value.

リファレンスには「属性の値を指定しない場合は、デフォルト値に設定される」とあります。

実際、単純に以下のようにコマンドを実行してしまうと、Lambdaトリガーの取り付けが行われるものの、既になされている他の設定がデフォルト値に戻ってしまいます。

aws cognito-idp update-user-pool --user-pool-id "<ユーザープールID>" --lambda-config PreSignUp="<Lambda関数のARN>"

このことは aws/aws-cli の issue としても挙がっています。

https://github.com/aws/aws-cli/issues/3302

解決策としては、JSONプロセッサ jq を駆使して以下のように現在の設定を —cli-input-json で指定しつつ、新規に追加したい内容(ここではLambdaトリガー)を --lambda-config で指定する方法が提示されています。

user_pool_id="<ユーザープールID>"
function_arn="<Lambda関数のARN>"
 
# 利用可能な設定のキーを取得する
config_keys=$(aws cognito-idp update-user-pool --generate-cli-skeleton | jq 'keys')
 
# 現在のユーザープールの設定値を取得し、更新用データを作成する
# note: we delete AdminCreateUserConfig.UnusedAccountValidityDays because it is not supported when using Policies.PasswordPolicy.TemporaryPasswordValidityDays
current_config=$(aws cognito-idp describe-user-pool --user-pool-id "${user_pool_id}" | jq --argjson whitelist "${config_keys}" '.UserPool | . + {UserPoolId:.Id} | with_entries(select(.key as $a | any($whitelist[]; . == $a))) | del(.AdminCreateUserConfig.UnusedAccountValidityDays)')
 
# 現在のユーザープールの設定にLambdaトリガーの設定をマージする
aws cognito-idp update-user-pool --lambda-config "PostConfirmation=${function_arn}" --cli-input-json "${current_config}"

この方法で良さそうに思えたのですが、このままでは、まだ少し課題があることに気が付きました。

上記で取り付けている関数以外に、事前に取り付けられているLambda関数の設定がある場合、事前の設定が消失してしまいます。

例えば、Cognitoにサインアップ前に動作するLambdaトリガー(PreSignUp)が取り付けられている状態で、上記のように PostConfirmation の Lambdaトリガーの設定を行うと、PreSignUp に取り付けられている Lambdaの設定は無くなってしまいます。

これを回避するために以下のように追加したい内容をJSONで表現するようにして、既存の設定のJSONにマージして設定を行うことで、他の設定に影響を出さずに新規で意図したLambdaトリガーの取り付けが行えるようになります。

user_pool_id="<ユーザープールID>"
function_arn="<Lambda関数のARN>"
 
# 利用可能な設定のキーを取得する
config_keys=$(aws cognito-idp update-user-pool --generate-cli-skeleton | jq 'keys')
 
# 現在のユーザープールの設定値を取得し、更新用データを作成する
# note: we delete AdminCreateUserConfig.UnusedAccountValidityDays because it is not supported when using Policies.PasswordPolicy.TemporaryPasswordValidityDays
current_config=$(aws cognito-idp describe-user-pool --user-pool-id "${user_pool_id}" | jq --argjson whitelist "${config_keys}" '.UserPool | . + {UserPoolId:.Id} | with_entries(select(.key as $a | any($whitelist[]; . == $a))) | del(.AdminCreateUserConfig.UnusedAccountValidityDays)')
 
# 現在のユーザープールの設定にLambdaトリガーの設定をマージ
input_json=$(echo ${current_config} | jq --arg func_arn ${function_arn} '. * {"LambdaConfig":{"PostConfirmation": $func_arn}}')
 
aws cognito-idp update-user-pool --cli-input-json "${input_json}"

provisioner "local-exec"

ようやくAWS CLIのコマンドからLambdaトリガーの取り付けが行えるようになりましたが、今度はこれをTerraformから実行できるようにしてみたいと思います。

結論から書くと、冒頭の Lambda をデプロイするための Terraform のインフラコードに以下のコードを追加します。

locals {
  # Cognito ユーザープールID
  user_pool_id="<ユーザープールID>"
 
    # 新規に追加したいLambdaトリガーの定義
  additional_cognito_settings = jsonencode({
    "LambdaConfig" : {
      "PreSignUp" : "${resource.aws_lambda_function.cognito_trigger_lambda.arn}"
    }
  })
}
 
resource "null_resource" "attach_cognito_lambda_trigger" {
  provisioner "local-exec" {
    # 実行したいコマンド
    command = <<-EOT
      config_keys=$(aws cognito-idp update-user-pool --generate-cli-skeleton | jq 'keys')
      current_config=$(aws cognito-idp describe-user-pool --user-pool-id ${local.user_pool_id} | jq --argjson whitelist "$config_keys" '.UserPool | . + {UserPoolId:.Id} | with_entries(select(.key as $a | any($whitelist[]; . == $a))) | del(.AdminCreateUserConfig.UnusedAccountValidityDays)')
      input_json=$(echo $current_config | jq '. * ${local.additional_cognito_settings}')
      aws cognito-idp update-user-pool --cli-input-json "$input_json"
    EOT
 
    # Windows環境ではデフォルトでcmd(コマンドプロンプト)でコマンドが実行されてしまうため
    # 明示的に bash を指定
    interpreter=["bash", "-c"]
    # 作業ディレクトリは本モジュール(このインフラコード)と同じディレクトリに設定
    working_dir=path.module
  }
}

インスタンスの存在しないリソースの定義 null_resource に provisioner を記述してTerraformを実行しているマシン上で任意のコマンドを実行するようにしています。

Provisioners

https://www.terraform.io/language/resources/provisioners/syntax

Provisioners can be used to model specific actions on the local machine or on a remote machine in order to prepare servers or other infrastructure objects for service.

私訳)プロビジョナーを使用すると、ローカルマシンまたはリモートマシンで特定のアクションをモデル化して、サービス用のサーバーまたはその他のインフラストラクチャオブジェクトを準備できます。

local-exec Provisioner

https://www.terraform.io/language/resources/provisioners/local-exec

The local-exec provisioner invokes a local executable after a resource is created. This invokes a process on the machine running Terraform, not on the resource.

私訳)local-exec Provisioner は、リソースの作成後にローカル実行可能ファイルを呼び出します。これはリソース上ではなく、Terraformを実行しているマシン上でプロセスを起動します。

以上で、TerraformからLambdaをデプロイしCognitoのLambdaトリガーとして取り付けるところまでをインフラコードにすることができました。

また、上記の例では記載していませんが、 null_resourcetriggers を使用することで、コマンドが実行されるタイミングを制御することもできます。

特定のスクリプトファイルが更新されたタイミングでコマンドを実行したい場合は以下のように記述することで実現できるようです。

https://discuss.hashicorp.com/t/re-triggering-shell-script-in-terraform/2988/2

resource "null_resource" "run_script" {
  triggers = {
    script_hash = "${sha256(var.bash_script)}"
  }
...
}