この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは。齋藤です。 今日はgolangを使ってgitの操作をやります。
はじめに
golangには pure go implementation な git のライブラリがあります。
今回の記事では、このライブラリを使って git の操作を行います。
今回動かしたいシナリオは以下のようなものです。
- あるリポジトリAを リリースする
- リポジトリAのイベントをhook起点に別のリポジトリBに コミットして PRを送る
今回はリポジトリBに commit して PRを送るに着目して この記事では リポジトリBを clone した後にファイルを変更・commit して push までをやってみます。
なお後々、Webhook などで動かしたいので AWS Lambda の上で動かしてみます。 gitの操作は全てインメモリで行います。
準備
今回はAWS Lambdaで実行することを考えているので アクセストークンを利用してリポジトリを clone します。
そのため、GitHubからなんらかの方法でrepo scopeのトークンを取得しておいてください。 なお、go-gitの場合、実行環境でGitHub に設定しているssh鍵がある場合は不要です。
lambdaを起動するための cloudformation
本題ではないので、ここは以下のようにさっくり設定しておきます。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: golang-lambda-test
Parameters:
GitHubToken:
Type : String
Description : Enter github repo scope token.
Resources:
Function:
Type: AWS::Serverless::Function
Properties:
Handler: handler
Runtime: go1.x
CodeUri: handler.zip
Handler: handler
Role: !GetAtt GoOnLambdaIamRole.Arn
Timeout: 60
Environment:
Variables:
GITHUB_ACCESS_TOKEN: !Ref GitHubToken
Events:
ProxyApiRoot:
Type: Api
Properties:
Path: /
Method: ANY
GoOnLambdaIamRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: "sts:AssumeRole"
Path: /
Policies:
- PolicyName: root
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup # ログ出力のためのポリシー
- logs:CreateLogStream
- logs:PutLogEvents
Resource: '*'
サンプルコードとビルド
サンプルコードです。 cloneします。
インメモリでcloneしています。
f := memfs.New()
repo, err := git.Clone(memory.NewStorage(), f, &git.CloneOptions{
URL: "https://" + userID + ":" + token + "@github.com/<your-id>/<your-repository>.git",
ReferenceName: plumbing.ReferenceName("refs/heads/develop"),
})
新しいブランチをcheckoutします。
w, err := repo.Worktree()
err = w.Checkout(&git.CheckoutOptions{
Create: true,
Branch: "feature/test-update",
})
リポジトリにあるファイルを書き換えます。
// ファイルを書き換える
// f := memfs.New()
err = rewriteVersion(f, ur.Version)
func rewriteVersion(fs billy.Filesystem, version string) (err error) {
file, err := fs.Open(".version")
if err != nil {
return
}
b, err := ioutil.ReadAll(file)
if err != nil {
return
}
err = file.Close()
if err != nil {
return
}
var obj interface{}
json.Unmarshal(b, &obj)
jsonpointer.Set(obj, "/version", version)
rb, err := json.MarshalIndent(obj, "", " ")
if err != nil {
return
}
err = fs.Remove(".version")
if err != nil {
return
}
file, err = fs.OpenFile(".version", os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
return
}
_, err = file.Write(rb)
if err != nil {
return
}
_, err = file.Write([]byte("\n"))
return
}
今度は書き換えたファイルをcommitします。
// Commitする
_, err = w.Add(".version")
ref := plumbing.ReferenceName("feature/test-update")
if err == nil {
hash, _ := w.Commit("update version", &git.CommitOptions{
Author: &object.Signature{
Name: "<your-id>",
Email: "<your-mail-address>",
When: time.Now(),
},
})
// ここが何故か必要だったのだけれど調べてない
repo.Storer.SetReference(plumbing.NewReferenceFromStrings("feature/test-update", hash.String()))
}
コミットしたブランチをpushします。
// Pushする
remote, err := repo.Remote("origin")
if err == nil {
err = remote.Push(&git.PushOptions{
Progress: os.Stdout,
RefSpecs: []config.RefSpec{
config.RefSpec(ref + ":" + plumbing.ReferenceName("refs/heads/feature/test-update")),
},
})
}
サンプルコード全体はこちらから見れます。
ビルドは以下のコマンドで AWS Lambda向けにビルドしておきます。 今回は簡略化のためにビルドのオプションは渡していません。 必要に応じて適切にビルドのオプションを設定してください。
# zip作っておく。
GOOS=linux GOARCH=amd64 go build -o handler handler.go && zip handler.zip handler
アプリケーションのデプロイ
先ほどビルドしたバイナリがあることを前提にしています。
# s3にcodeをアップロードしつつ templateを出力
aws cloudformation package --template-file template.yml --output-template-file packed.yml --s3-bucket <your-bucket>
# 出力されたテンプレートを使って、GitHubのTokenを渡しつつデプロイ
aws cloudformation deploy --template-file packed.yml --stack-name lambda-go-test-stack --parameter-overrides "GitHubToken=<token>" --capabilities CAPABILITY_IAM
デプロイしてlambdaを叩いてみます
今回はデプロイはマネージメントコンソールから行いました。 今回のサンプルコードでは、以下のような形で呼び出すことができます。
curl -XPOST https://$REST_API_ID.execute-api.$REGION.amazonaws.com/$STAGE -d '{"version": "from curl"}'
# updated version
まとめ
今回は golangとgitのライブラリである、go-gitを使って、基本的なgit操作を行いました。 今回のサンプルコードでは、GitHubにあるprivateリポジトリにあるコードを インメモリで編集してPushする所まで行いました。
ここからPRを作る所まで自動化できると楽しそうですね。
また、最近isomorphic-git という node/browserで動くgitのライブラリもあるので 気になっています。git操作に関するエコシステムが育っていくと楽しそうですね。
今度はここから発展した記事を書く予定でいます。 ではまた次の記事でお会いしましょう。
おまけ: IP制限
# StackにRestApiが一つしかないのでこれで大丈夫
REST_API_ID=$(aws cloudformation describe-stack-resources --stack-name lambda-go-test-stack | jq '.StackResources[] | select(.ResourceType == "AWS::ApiGateway::RestApi") | .PhysicalResourceId' -r)
# ポリシードキュメントを `jq ". | tostring" -c` でエスケープ。
JSON=$(cat << EOF | jq ". | tostring" -c
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": [
"arn:aws:execute-api:<>:<your-account-id>:$REST_API_ID/*"
],
"Condition" : {
"IpAddress": {
"aws:SourceIp": [ "<your-ip-address>" ]
}
}
}
]
}
EOF
)
# 設定を更新
aws apigateway update-rest-api --rest-api-id $REST_API_ID --patch-operations op=replace,path=/policy,value=$JSON
# 設定を更新後、再デプロイする必要がある。
aws apigateway update-stage --rest-api-id $REST_API_ID --stage-name <your-stage>
curl -XPOST https://$REST_API_ID.execute-api.$REGION.amazonaws.com/prod -d '{"version": "from curl"}'
# updated version