RubyでDockerイメージをビルドしてECRにプッシュする

# 背景
あるシステムをkubernetesで動かすために、ローカル環境にあるDockerイメージをECRにプッシュする必要があって、`aws-cli`と`docker`コマンドでのやり方は理解していたが、他のメンバーに`aws-cli`入れてもらうのとかシェルのスクリプト書くのもなんだかなーと思っていた。

そこで、そのシステムがRailsで動いているのでRubyでローカルでDockerイメージをビルドして、ECRにプッシュできないかなと考えていた。

# 概要
[docker-api](https://github.com/swipely/docker-api)でDockerイメージのビルドはできそうだったので、あとは[aws-sdk-ecr](https://github.com/aws/aws-sdk-ruby/tree/master/gems/aws-sdk-ecr)の`Aws::ECR::Client`の`put_image`か[docker-api](https://github.com/swipely/docker-api)の`Docker::Image`の`push`でECRへのイメージのプッシュができそうだった。

[aws-sdk-ecr](https://github.com/aws/aws-sdk-ruby/tree/master/gems/aws-sdk-ecr)の`Aws::ECR::Client`の[put_image](https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/ECR/Client.html#put_image-instance_method)の必須オプションで`image_manifest`があったのだけど、今まで扱ったことないし、[docker-api](https://github.com/swipely/docker-api)でも取得できなそうだった。

Dockerの[ドキュメント](https://docs.docker.com/engine/reference/commandline/manifest/)見たら、`docker manifest`関連のコマンド自体が`experimental`(実験的)っぽいので、`Aws::ECR::Client`の`put_image`を使う方法は断念。

`Docker::Image`の`push`の方法は、`Docker.authenticate!`通せるかが肝になる。

`aws ecr get-login --region region --no-include-email`で`docker login -u AWS -p password https://aws_account_id.dkr.ecr.us-east-1.amazonaws.com`と`docker`で`ECR`にログインできる情報が取得できるので、`Aws::ECR::Client`でなんとかその情報取れれば良さそう。

`Aws::ECR::Client`の[get_authorization_token](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ecr.html#ECR.Client.get_authorization_token)のドキュメント見るとレスポンスの中の`authorizationToken`が

```
A base64-encoded string that contains authorization data for the specified Amazon ECR registry. When the string is decoded, it is presented in the format user:password for private registry authentication using docker login .
```

と`docker login`で使える`user:password`が`base64`でエンコードされたものと書いてある。
要は`base64`デコードすると`AWS:[docker loginで使えるパスワード]`が取得できる。

これで、`Docker.authenticate!`通せそう。

# コード
```push_image_to_ecr.rb
require 'docker'
require 'aws-sdk'

aws_access_key_id, aws_secret_access_key = [何らかの方法で取得(環境によると思うので省略)]
Aws.config.update(credentials: Aws::Credentials.new(aws_access_key_id, aws_secret_access_key))

tag = '0.0.1' # 実際はYAMLに書くかコマンド引数にすべきだかここでは一旦変数で持つ

ecr_client = Aws::ECR::Client.new

# 既にECRにプッシュされたものがないか確認
list_images_response = ecr_client.list_images(repository_name: 'hoge')
pushed_image = list_images_response.image_ids.detect { |image_id| image_id.image_tag == tag }

if pushed_image
  puts 'Pushed image is found. Nothing to do.'
  return
end

# Docker.authenticate!に必要な情報だけでなくendpointもget_authorization_tokenで取得できる
authorization_token_response = ecr_client.get_authorization_token
authorization_token = authorization_token_response.authorization_data[0].authorization_token
proxy_endpoint = authorization_token_response.authorization_data[0].proxy_endpoint

full_repository_name = proxy_endpoint.sub(Regexp.escape('https://'), '') + '/hoge'

# 既にローカルにビルドされたイメージがあれば取得
builded_image = Docker::Image.all.detect { |image| image.info['RepoTags'].first == "#{full_repository_name}:#{tag}" }

unless builded_image
  # ビルドしてタグ付け
  puts 'Building image'
  builded_image = Docker::Image.build_from_dir('.')
  builded_image.tag('repo' => full_repository_name, 'tag' => tag)
end

# docker loginで使えるユーザー名とパスワードの取得
username, password = Base64.decode64(authorization_token).split(':')
options = {'username' => username, 'password' => password, 'serveraddress' => proxy_endpoint, 'email' => 'none'}
Docker.authenticate!(options)

# ECRにプッシュ
builded_image.push
```

# 実行
あとは`Dockerfile`と同じディレクトリに`push_image_to_ecr.rb`を配置して、`bundle exec rails r push_image_to_ecr.rb`するだけ(`aws_access_key_id, aws_secret_access_key`の取得方法によっては`ruby  push_image_to_ecr.rb`でも)。