
GitHub Actions を Slack workflow から実行する仕組みを AWS + Terraform で作ってみた
はじめに
アプリボットSREチームです。
弊社では、運用するいくつかのWebサイトのデプロイにGitHub Actionsを利用しています。
コンテンツの更新などは、エンジニアではないメンバーが担当することが多いのですが、普段慣れていないGitHub Actionsの画面内で操作を行うことには抵抗があるようでした。
その為、本番反映の時だけ、反映(デプロイ)のみを行う人としてサポートするということが度々発生していました。
そこで、作業者が誰でも簡単にデプロイを行える環境を作れないか?を検討してみたところ、AWS Chatbot + GitHub Actions + Terraformで簡単に実現することができましたので、紹介させていただきます。
※Terraform、GitHub Actionsのサンプルコードを下記にまとめていますので、よろしかったら試してみてください。
構成図

概要
| 項目 | 説明 |
|---|---|
| Slackワークフロー | Slackチャンネルのショートカット「ワークフロー」→「Run command」より2回のクリックでデプロイできます |
| AWS Chatbot | Slack チャンネルに投稿されたawsコマンド(lambda)を実行します |
| AWS Lambda | GitHub ActionsのWorkflow dispatchを実行しています(GitHub Token必須) |
| GitHub Actions | on Workflow dispatchトリガーで動作します |
| Slack通知 | GitHub Actionsの成功、失敗通知をします |
実行結果イメージ

Terraformの構成
各リソースについて説明します。
AWS Lambda
初回時のみnull_resourceを使用してmain.goをビルドしています。
# Lambda Function
resource "aws_lambda_function" "lambda" {
description = "Official Site Release"
filename = "${path.module}/lambda/archive/main.zip"
function_name = local.function_name
role = aws_iam_role.lambda.arn
handler = "main"
source_code_hash = data.archive_file.lambda.output_base64sha256
runtime = "go1.x"
memory_size = 128
timeout = 15
publish = true
environment {
variables = {
BRANCH = local.variables_BRANCH
REPO_NAME = local.variables_REPO_NAME
REPO_OWNER = local.variables_REPO_OWNER
WORKFLOW_NAME = local.variables_WORKFLOW_NAME
PARAMETER_STORE = local.variables_PARAMETER_STORE
}
}
}
resource "null_resource" "lambda" {
triggers = {
file_content = md5(file("${path.module}/lambda/source/main.go"))
}
provisioner "local-exec" {
command = "GOOS=linux GOARCH=amd64 go build -o ${path.module}/lambda/bin/main ${path.module}/lambda/source/main.go"
}
}
data "archive_file" "lambda" {
depends_on = [null_resource.lambda]
type = "zip"
source_dir = "${path.module}/lambda/bin/"
output_path = "${path.module}/lambda/archive/main.zip"
output_file_mode = "0666"
}
# IAM Role
resource "aws_iam_role" "lambda" {
name = local.lambda_role_name
path = "/service-role/"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow"
}
]
}
EOF
}
resource "aws_iam_role_policy_attachment" "lambda_execution" {
role = aws_iam_role.lambda.id
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
resource "aws_iam_role_policy" "lambda_ssm" {
name = "ssm"
role = aws_iam_role.lambda.id
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ssm:GetParameter"
],
"Resource": "*"
}
]
}
EOF
}
Golang
各環境変数を参照し、GitHub Actionsのworkflow dispatchを実行します。
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ssm"
)
type WorkflowDispatchPayload struct {
Ref string `json:"ref,omitempty"`
}
func main() {
lambda.Start(handler)
}
func handler(ctx context.Context) {
// AWS Systems Manager パラメータストアからGitHubトークンを取得
paramerStore := os.Getenv("PARAMETER_STORE")
githubToken, err := getGitHubTokenFromParameterStore(paramerStore)
if err != nil {
fmt.Printf("Failed to get GitHub token: %v", err)
return
}
// GitHubリポジトリとワークフローの情報
repoOwner := os.Getenv("REPO_OWNER")
repoName := os.Getenv("REPO_NAME")
workflowName := os.Getenv("WORKFLOW_NAME")
// Workflow DispatchのPayloadを構築
payload := WorkflowDispatchPayload{
Ref: os.Getenv("BRANCH"), // 実行するブランチ名
}
// Workflow Dispatchのリクエストを作成
payloadBytes, err := json.Marshal(payload)
if err != nil {
fmt.Printf("Failed to marshal payload: %v", err)
return
}
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/actions/workflows/%s/dispatches", repoOwner, repoName, workflowName)
req, err := http.NewRequest("POST", url, bytes.NewReader(payloadBytes))
if err != nil {
fmt.Printf("Failed to create request: %v", err)
return
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/vnd.github.v3+json")
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", githubToken))
// Workflow Dispatchのリクエストを送信
client := http.DefaultClient
resp, err := client.Do(req)
if err != nil {
fmt.Printf("Failed to send request: %v", err)
return
}
defer resp.Body.Close()
// レスポンスをチェック
if resp.StatusCode != http.StatusCreated {
fmt.Print(url, "\n")
fmt.Printf("Unexpected response status: %s\n", resp.Status)
b, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(b))
return
}
fmt.Println("Workflow Dispatch triggered successfully!")
}
func getGitHubTokenFromParameterStore(parameterPath string) (string, error) {
// AWSセッションの作成
sess := session.Must(session.NewSession())
// AWS Systems Manager サービスクライアント
ssmClient := ssm.New(sess)
// パラメータストアからGitHubトークンを取得
input := &ssm.GetParameterInput{
Name: aws.String(parameterPath),
WithDecryption: aws.Bool(true),
}
result, err := ssmClient.GetParameter(input)
if err != nil {
return "", err
}
return *result.Parameter.Value, nil
}
AWS Chatbot
Slackワークスペースとの連携設定と、AWSリソースの権限を付与をしています。(初回時のみSlackワークスペースとの連携設定が必要)
また、仕様として同一チャンネル名で複数の作成ができないので注意が必要です。
# Chatbot
resource "awscc_chatbot_slack_channel_configuration" "chatbot" {
configuration_name = local.configuration_name
slack_workspace_id = local.slack_workspace_id
slack_channel_id = local.slack_channel_id
# チャンネルロール(IAM Role)
iam_role_arn = aws_iam_role.chatbot.arn
# ガードレールポリシ (チャンネルロールよりも優先)
guardrail_policies = [
aws_iam_policy.chatbot_guardrail.arn # 必須
]
logging_level = "ERROR"
depends_on = [
aws_iam_role.chatbot,
aws_iam_policy.chatbot_guardrail
]
}
resource "aws_iam_role" "chatbot" {
name = local.chatbot_role_name
assume_role_policy = data.aws_iam_policy_document.chatbot_assume.json
}
data "aws_iam_policy_document" "chatbot_assume" {
statement {
effect = "Allow"
actions = [
"sts:AssumeRole",
]
principals {
type = "Service"
identifiers = [
"chatbot.amazonaws.com",
]
}
}
}
resource "aws_iam_policy" "chatbot_guardrail" {
name = "chatbot_guardrail_policy"
policy = data.aws_iam_policy_document.chatbot_guardrail.json
}
data "aws_iam_policy_document" "chatbot_guardrail" {
statement {
effect = "Allow"
actions = [
"lambda:invokeAsync",
"lambda:invokeFunction"
]
resources = [
"*",
]
}
statement {
effect = "Allow"
actions = [
"cloudwatch:Describe*",
"cloudwatch:Get*",
"cloudwatch:List*"
]
resources = [
"*",
]
}
}
# 以下、IAMロール ポリシー (ガードレールポリシと同じもの)
resource "aws_iam_role_policy_attachment" "chatbot_readonly" {
role = aws_iam_role.chatbot.id
policy_arn = "arn:aws:iam::aws:policy/AWSResourceExplorerReadOnlyAccess"
}
resource "aws_iam_role_policy" "chatbot_lambda" {
name = "lambda"
role = aws_iam_role.chatbot.id
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"lambda:invokeAsync",
"lambda:invokeFunction"
],
"Resource": [
"*"
]
}
]
}
EOF
}
resource "aws_iam_role_policy" "chatbot_noti" {
name = "AWS-Chatbot-NotificationsOnly-Policy"
role = aws_iam_role.chatbot.id
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"cloudwatch:Describe*",
"cloudwatch:Get*",
"cloudwatch:List*"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
EOF
}
詳しくはこちらにまとめています。
https://github.com/applibot-inc/lambda_chatbot
まとめ
GitHub Actionsを使った開発を行っているリポジトリの運用がChatOpsで可能となり、サポートや確認のみのやりとりの工数を大幅に削減できました。
Slackワークフローはエンジニア職以外の方との共通利用に適していて、他にも色々と応用が可能です。
例えば、エンジニアではないメンバでEC2の稼働・停止をしたいという要件や、単体でAWS Lambdaを実行することもできます。利便性は高まるかと思われますので、ぜひご活用ください。

この記事へのコメントはありません。