連載 [第6回] :
今日からはじめる Pulumiでカンタン インフラ運用・管理「Pulumi Automation API」でPulumi CLIの機能をコード化しよう
2023年8月8日(火)
第6回となる今回は、Automation APIの概要を解説し、実際にAutomation APIを使ったRESTful APIの静的Webサーバーを構築するハンズオンを実践していきます。
Pulumi Program + Automation API コード編集
PythonファイルにPulumi ProgramとAutomation APIコードを記載します。
- 作業用ディレクトリを作成します。
$ mkdir pulumi_over_http && cd pulumi_over_http $ pwd /****/pulumi_over_http
- pythonファイル(app.py)を新規で作成し、以下コードを記載します。
$ vi app.py
import pulumi from pulumi import automation as auto from pulumi_aws import s3 from flask import Flask, request, make_response, jsonify def ensure_plugins(): ws = auto.LocalWorkspace() ws.install_plugin("aws", "v4.0.0") ensure_plugins() app = Flask(__name__) project_name = "pulumi_over_http" # Pulumi Program (静的Webサーバーの作成->ユーザーからのHTTPリクエストで読み込まれる) def create_pulumi_program(content: str): # S3 Bucket作成 site_bucket = s3.Bucket( "s3-website-bucket", website=s3.BucketWebsiteArgs(index_document="index.html") ) index_content = content # Bucket Public Access Block作成(Objectパブリック公開のための設定) s3.BucketPublicAccessBlock( "bucket-public-access-block", bucket=site_bucket.id, block_public_acls=False, block_public_policy=False, ignore_public_acls=False, restrict_public_buckets=False ) # Bucket Object作成 s3.BucketObject( "index", bucket=site_bucket.id, content=index_content, key="index.html", content_type="text/html; charset=utf-8" ) # Bucket Policy作成 s3.BucketPolicy( "bucket-policy", bucket=site_bucket.id, policy={ "Version": "2012-10-17", "Statement": { "Effect": "Allow", "Principal": "*", "Action": ["s3:GetObject","s3:PutObject"], "Resource": [pulumi.Output.concat("arn:aws:s3:::", site_bucket.id, "/*")] } } ) # WebサイトURLのエクスポート pulumi.export("website_url", site_bucket.website_endpoint) # コンテンツ新規作成のルーティング @app.route("/sites", methods=["POST"]) def create_handler(): """creates new sites""" stack_name = request.json.get('id') content = request.json.get('content') try: def pulumi_program(): return create_pulumi_program(content) # Automation API (Stackの新規作成) stack = auto.create_stack( stack_name=stack_name, project_name=project_name, program=pulumi_program ) stack.set_config("aws:region", auto.ConfigValue("ap-northeast-1")) # Automation API (Stackのデプロイ) up_res = stack.up(on_output=print) return jsonify(id=stack_name, url=up_res.outputs['website_url'].value) except auto.StackAlreadyExistsError: return make_response(f"stack '{stack_name}' already exists", 409) except Exception as exn: return make_response(str(exn), 500) # コンテンツ一覧取得のルーティング @app.route("/sites", methods=["GET"]) def list_handler(): """lists all sites""" try: ws = auto.LocalWorkspace(project_settings=auto.ProjectSettings(name=project_name, runtime="python")) stacks = ws.list_stacks() return jsonify(ids=[stack.name for stack in stacks]) except Exception as exn: return make_response(str(exn), 500) # コンテンツURL取得のルーティング @app.route("/sites/
コードの主要な箇所について解説します。", methods=["GET"]) def get_handler(id: str): stack_name = id try: # Automation API (Stackの選択) stack = auto.select_stack( stack_name=stack_name, project_name=project_name, program=lambda *args: None ) outs = stack.outputs() return jsonify(id=stack_name, url=outs["website_url"].value) except auto.StackNotFoundError: return make_response(f"stack '{stack_name}' does not exist", 404) except Exception as exn: return make_response(str(exn), 500) # コンテンツ内容更新のルーティング @app.route("/sites/ ", methods=["PUT"]) def update_handler(id: str): stack_name = id content = request.json.get('content') try: def pulumi_program(): create_pulumi_program(content) # Automation API (Stackの選択) stack = auto.select_stack( stack_name=stack_name, project_name=project_name, program=pulumi_program ) stack.set_config("aws:region", auto.ConfigValue("ap-northeast-1")) # Automation API (Stackのデプロイ) up_res = stack.up(on_output=print) return jsonify(id=stack_name, url=up_res.outputs["website_url"].value) except auto.StackNotFoundError: return make_response(f"stack '{stack_name}' does not exist", 404) except auto.ConcurrentUpdateError: return make_response(f"stack '{stack_name}' already has update in progress", 409) except Exception as exn: return make_response(str(exn), 500) # コンテンツ削除のルーティング @app.route("/sites/ ", methods=["DELETE"]) def delete_handler(id: str): stack_name = id try: # Automation API (Stackの選択) stack = auto.select_stack( stack_name=stack_name, project_name=project_name, program=lambda *args: None ) # Automation API (Stackのリソース削除) stack.destroy(on_output=print) # Automation API (Stack自体の削除) stack.workspace.remove_stack(stack_name) return jsonify(message=f"stack '{stack_name}' successfully removed!") except auto.StackNotFoundError: return make_response(f"stack '{stack_name}' does not exist", 404) except auto.ConcurrentUpdateError: return make_response(f"stack '{stack_name}' already has update in progress", 409) except Exception as exn: return make_response(str(exn), 500) def create_pulumi_program(content: str): ~~~ s3.Bucket(***) ~~~ s3.BucketPublicAccessBlock(***) ~~~ s3.BucketObject(***) ~~~ s3.BucketPolicy(***)
- Pulumi Programの内容を定義する関数です。ユーザーからのHTTPリクエストが発生した際に読み込まれます(コンテンツ新規作成及びコンテンツ内容更新時に読み込まれます)
Programでは、S3Bucket/BucketObject/BucketPolicy/BucketPublicAccessBlockのリソースが作成されます。BucketObjectを作成するためのコンテンツの内容は、ユーザーのHTTPリクエスト(POSTメソッド)のbodyから取得します。BucketObjectに対するパブリックアクセスを可能にするため、BucketPublicAccessBlockリソースで「パブリックアクセスのブロック設定」を無効化します。
# コンテンツ新規作成のルーティング @app.route("/sites", methods=["POST"]) def create_handler(): ~~~ # コンテンツ一覧取得のルーティング @app.route("/sites", methods=["GET"]) def list_handler(): ~~~ # コンテンツURL取得のルーティング @app.route("/sites/<string:id>", methods=["GET"]) def get_handler(id: str): ~~~ # コンテンツ内容更新のルーティング @app.route("/sites/<string:id>", methods=["PUT"]) def update_handler(id: str): ~~~ # コンテンツ削除のルーティング @app.route("/sites/<string:id>", methods=["DELETE"]) def delete_handler(id: str):
- ユーザーからのHTTPリクエストに応じたルーティングの作成とcaller関数の設定です。ルーティングは「コンテンツ新規作成/コンテンツ一覧取得/コンテンツ内容取得/コンテンツ内容更新/コンテンツ削除」の5つ分を作成しています。
# Automation API (Stackの新規作成) stack = auto.create_stack( stack_name=stack_name, project_name=project_name, program=pulumi_program ) ~~~ # Automation API (Stackのデプロイ) up_res = stack.up(on_output=print) ~~~ # Automation API (Stackの選択) stack = auto.select_stack( stack_name=stack_name, project_name=project_name, program=pulumi_program ) ~~~ # Automation API (Stackのリソース削除) stack.destroy(on_output=print) ~~~ # Automation API (Stack自体の削除) stack.workspace.remove_stack(stack_name)
- Automation APIのコードです。上記の記載により「Pulumi CLI」を介さずに、Stackの作成/デプロイ/削除などが可能になります。
- Pulumi Programの内容を定義する関数です。ユーザーからのHTTPリクエストが発生した際に読み込まれます(コンテンツ新規作成及びコンテンツ内容更新時に読み込まれます)
- requirements.txtを作成して、installするPythonパッケージのバージョンを指定します。
$ vi requirements.txt
pulumi>=3.0.0,<4.0.0 pulumi-aws>=4.0.0,<5.0.0 flask>=1.1.2,<2.0.0 markupsafe==2.0.1
Python仮想環境のセットアップ
作成したアプリケーションのPython仮想実行環境を構築します。
- Python 仮想環境を作成します。
$ pwd /****/pulumi_over_http $ python3 -m venv venv
- Pythonパッケージインストールのユーティリティ(pip)をインストールします
$ venv/bin/python3 -m pip install --upgrade pip — Collecting pip Using cached pip-23.2.1-py3-none-any.whl (2.1 MB) Installing collected packages: pip Attempting uninstall: pip Found existing installation: pip 20.2.3 Uninstalling pip-20.2.3: Successfully uninstalled pip-20.2.3 Successfully installed pip-23.2.1
- Pythonパッケージをインストールします
$ venv/bin/pip install -r requirements.txt — Collecting pulumi<4.0.0,>=3.0.0 (from -r requirements.txt (line 1)) Obtaining dependency information for pulumi<4.0.0,>=3.0.0 from https://files.pythonhosted.org/packages/4e/a2/a59532ee8c0e760ae99a4026e69c 292d29c137ea0364913b211095d74200/pulumi-3.76.1-py3-none-any.whl.metadata Downloading pulumi-3.76.1-py3-none-any.whl.metadata (11 kB) ~~~
連載バックナンバー
Think ITメルマガ会員登録受付中
Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。
全文検索エンジンによるおすすめ記事
- PulumiでAWSリソースをデプロイしよう
- TerraformからPulumiへの移行
- 既に存在するリソースをPulumiで管理してみよう
- Policy as Codeでインフラのコンプライアンスを自動実現! 「Pulumi CrossGuard」を活用してみよう
- Pulumi Kubernetes Operatorを活用してPulumiのCI/CDを実現しよう
- 「Pulumi Stack」とは ー Pulumiによるマルチステージ環境の構築方法
- SecretもPulumiで使いこなしたい! PulumiのSecurityを試してみよう
- Pulumiの最新機能「Pulumi ESC」を使ってみよう
- Oracle Cloud Hangout Cafe Season7 #2「IaC のベストプラクティス」(2023年7月5日開催)
- CloudサービスとRPAの連携