読者です 読者をやめる 読者になる 読者になる

Cfdef: CloudFrontをコード化するやつ

github.com

こんなん。

export AWS_ACCESS_KEY_ID='...'
export AWS_SECRET_ACCESS_KEY='...'
cfdef -e -o CFfile  # export CloudFront
vi CFfile
cfdef -a --dry-run
cfdef -a            # apply `CFfile`
require 'other/cffile'

distribution "EXAMPLEID" do
  aliases do
    quantity 0
  end
  origins do
    quantity 2
    items do |*|
      id "Custom-ehxample.cpm"
      domain_name "example.cpm"
      origin_path ""
      custom_headers do
        quantity 0
      end
      custom_origin_config do
        http_port 80
        https_port 443
        origin_protocol_policy "http-only"
        origin_ssl_protocols do
          quantity 3
          items ["TLSv1", "TLSv1.1", "TLSv1.2"]
        end
      end
    end
    items do |*|
      id "S3-example"
      domain_name "example.s3.amazonaws.com"
      origin_path ""
      custom_headers do
        quantity 0
      end
      s3_origin_config do
        origin_access_identity ""
      end
    end
  end
  default_cache_behavior do
    target_origin_id "S3-example"
    forwarded_values do
      query_string false
      cookies do
        forward "none"
      end
      headers do
        quantity 0
      end
    end
    trusted_signers do
      enabled false
      quantity 0
    end
    viewer_protocol_policy "allow-all"
    min_ttl 0
    allowed_methods do
      quantity 2
      items ["GET", "HEAD"]
      cached_methods do
        quantity 2
        items ["GET", "HEAD"]
      end
    end
    smooth_streaming false
    default_ttl 86400
    max_ttl 31536000
    compress false
  end
  cache_behaviors do
    quantity 0
  end
  custom_error_responses do
    quantity 0
  end
  comment ""
  price_class "PriceClass_All"
  enabled true
  viewer_certificate do
    cloud_front_default_certificate true
    minimum_protocol_version "SSLv3"
    certificate_source "cloudfront"
  end
  restrictions do
    geo_restriction do
      restriction_type "none"
      quantity 0
    end
  end
  web_acl_id ""
end

Repol: ECRのレポジトリをコード化するやつ

github.com

こんなん。

export AWS_ACCESS_KEY_ID='...'
export AWS_SECRET_ACCESS_KEY='...'
repol -e -o Repolfile  # export Repository Policy
vi Repolfile
repol -a --dry-run
repol -a               # apply `Repolfile`
Help
require 'other/repolfile'

repository "my_ecr_repo" do
  {"Version"=>"2008-10-17",
   "Statement"=>
    [{"Sid"=>"PullOnly",
      "Effect"=>"Allow",
      "Principal"=>{"AWS"=>"arn:aws:iam::123456789012:root"},
      "Action"=>
       ["ecr:BatchCheckLayerAvailability",
        "ecr:BatchGetImage",
        "ecr:GetDownloadUrlForLayer"]}]}
end

CloudWatch Logs用 cat / tailf

作った

github.com

$ cat hello | cwlogs-cat -g my-group -s my-stream

github.com

$ cwlogs-tailf -g my-group -s my-stream
2016-05-13T01:51:18+09:00 foo
2016-05-13T01:51:18+09:00 bar
2016-05-13T01:51:19+09:00 zoo
...
$ cwlogs-tailf -g my-group
stream1 2016-05-13T01:51:18+09:00 foo
stream2 2016-05-13T01:51:18+09:00 bar
stream1 2016-05-13T01:51:19+09:00 zoo
...

mappru: VPC Route Tableをgitで管理するやつ

勢いで実装できそうだったので、実装してみました。

github.com

mappru -e -o RouteTableVPC Route Tableをエクスポートして、mappru -aでRouteTableファイルを適用します。ファイルに書いてないVPC・Route Tableは管理対象外となります。

あと、重要な点ですがS3エンドポイントはAPIが異なるため今のところ管理できません。無視します。

DSLは以下のような感じです。

require 'other/tablefile'

vpc "vpc-12345678" do
  route_table "foo-rt" do
    subnets "subnet-12345678"
    route destination_cidr_block: "0.0.0.0/0", gateway_id: "igw-12345678"
    route destination_cidr_block: "192.168.100.101/32", network_interface_id: "eni-12345678"
    # 今のところ、network_interface_idで指定するようにしてます。instance_idは指定できません
  end

  route_table "bar-rt" do
    subnets "subnet-87654321"
    route destination_cidr_block: "192.168.100.102/32", network_interface_id: "eni-87654321"
  end

  # Undefined Route Table will be ignored
end

# Undefined VPC will be ignored

一応、基本的な動作確認はしていますし、運用にも乗せるつもりではありますが、影響範囲が大きいのでご利用する場合はご注意を…

bukelatta: S3のバケットポリシーをgitで管理するやつ

連休なのにこんなことをやっているのもアレなんですが「S3のバケットポリシーをコピペして目視確認」という作業を見てカッとなって作りました。terraformを使った方が手っ取り早かったかも…

https://github.com/winebarrel/bukelatta

bukelatta -e -o PolicyFileでS3のバケットポリシーをファイルに出力して、bukelatta -aで変更を適用します。ファイルに定義されていないバケットについては、管理対象外としてとくに何も行いません。

定義ファイルは以下のような感じ。テンプレートも使えます。

require 'other/policyfile'

bucket "foo-bucket" do
  {"Version"=>"2012-10-17",
   "Id"=>"AWSConsole-AccessLogs-Policy-XXX",
   "Statement"=>
    [{"Sid"=>"AWSConsoleStmt-XXX",
      "Effect"=>"Allow",
      "Principal"=>{"AWS"=>"arn:aws:iam::XXX:root"},
      "Action"=>"s3:PutObject",
      "Resource"=>
       "arn:aws:s3:::foo-bucket/AWSLogs/XXX/*"}]}
end

bucket "bar-bucket" do
  {"Version"=>"2012-10-17",
   "Statement"=>
    [{"Sid"=>"AddPerm",
      "Effect"=>"Allow",
      "Principal"=>"*",
      "Action"=>"s3:GetObject",
      "Resource"=>"arn:aws:s3:::bar-bucket/*"}]}
end

どうぞご利用ください。

Kumogata2

連休に入って酒量が増え体重も増え、昼夜わからない生活をし、ridgepoleのRails5対応はFが減らず、必要に駆られて使い始めたElasticBeanstalkの闇が見え始めた現実から目をそらすため、以前から懸案だったCloudFormation用ツールKumogataの次世代版を作ってみました。

なにが問題だったのか

  • aws-sdk-v1
    • v1もまだまだ現役で使えるとはいえ状況を見る感じだいぶv2が主流になってきて、このままv1を使い続けるのはリスキー…なんだけどライブラリを変更するのが地味に大変。大量のFでモチベーション低下
  • モノリシック
    • Kumogataは「フォーマットコンバーター+便利ツール」というもので、この2つは論理的に切り離すことができるはずなんだけど、初期の設計に建て増しを繰り返した結果、拡張がひっじょーにしづらい状況。おまけに対応フォーマットの追加に付随してgemもばかすか追加したからインストールが重い・めんどい。さすがにtherubyracerへの依存はやりすぎだった。申し訳ない
  • Ruby DSLの黒魔術
    • Ruby DSLJSONをはくためにdslhという連想配列Ruby DSLで書くライブラリを作ったんですが、裏で結構変なことをやっているので、テンプレート全体をRubyDSLとして評価するのはリスキーな感じです。できれば「template do ... end」というブロック内だけでDSLを評価して、外部に黒魔術の影響を漏れ出さないようにすべきと考えていたけれど、すでに込み入ったソースを回収するのはちょっと大変
  • いくつかの不要そうな機能
    • パラメータの暗号化とか初期に必要そうだったので追加した謎機能がいくつかあって、たぶんほとんどのユースケースで使われておらず、コードのスパゲッティ化に貢献

Kumogata2

ということで、連休と現実逃避とビール・チューハイ20本ぐらいの結果、Kumogata2が爆誕しました。

github.com

改善点

  • aws-sdk-v2!
  • プラガブルなアーキテクチャ
  • 不要機能の削除
    • 暗号化みたいな謎機能は削除。またsshの実行なんかも普通にRubyコードとして実行できるようになった(はず)なので内部のコードは削除しました
  • ChangeSet対応
    • 待望…だけど割とびみょーな機能のChangeSetにも対応。いろいろとインターフェースを考えた結果、dry-runコマンドを実行するとChangeSetつくって、差異を出力して、ChangeSetを削除する、という動作になりました(ChangeSetを作って実行…ってだれがあの機能考えたんだろう…)

Usage

Kumogata v1とだいたい同じです。出力もだいたい同じ。

Usage: kumogata2 <command> [args] [options]

Commands:
  create         PATH_OR_URL [STACK_NAME]   Create resources as specified in the template
  update         PATH_OR_URL STACK_NAME     Update a stack as specified in the template
  delete         STACK_NAME                 Delete a specified stack
  validate       PATH_OR_URL                Validate a specified template
  list           [STACK_NAME]               List summary information for stacks
  export         STACK_NAME                 Export a template from a specified stack
  convert        PATH_OR_URL                Convert a template format
  diff           PATH_OR_URL1 PATH_OR_URL2  Compare templates logically (file, http://..., stack://...)
  dry-run        PATH_OR_URL STACK_NAME     Create a change set and show it
  show-events    STACK_NAME                 Show events for a specified stack
  show-outputs   STACK_NAME                 Show outputs for a specified stack
  show-resources STACK_NAME                 Show resources for a specified stack

Support Format:
  json, js, template

Options:
    -k, --access-key ACCESS_KEY
    -s, --secret-key SECRET_KEY
    -r, --region REGION
        --profile PROFILE
        --credentials-path PATH
        --output-format FORMAT
    -p, --parameters KEY_VALUES
    -j, --json-parameters JSON
        --[no-]deletion-policy-retain
        --[no-]disable-rollback
        --timeout-in-minutes TIMEOUT_IN_MINUTES
        --notification-arns NOTIFICATION_ARNS
        --capabilities CAPABILITIES
        --resource-types RESOURCE_TYPES
        --on-failure ON_FAILURE
        --stack-policy-body STACK_POLICY_BODY
        --stack-policy-url STACK_POLICY_URL
        --[no-]use-previous-template
        --stack-policy-during-update-body STACK_POLICY_DURING_UPDATE_BODY
        --stack-policy-during-update-url STACK_POLICY_DURING_UPDATE_URL
        --result-log PATH
        --command-result-log PATH
        --[no-]detach
        --[no-]force
        --[no-]color
        --[no-]ignore-all-space
        --[no-]debug

Ruby DSL

github.com

ほぼ一緒ですが、

  • プラグインになって本体と分離された
  • DSLを「template do ... end」内に定義
  • post処理の実行が変わった
    • _post()メソッドがなくなって、template()と同階層post()メソッドでコマンドを実行するようになります。これにより任意のRubyコマンドがたたけるので、sshなりシェルコマンドの実行なりはそこでやってください、とう感じです。
template do
  AWSTemplateFormatVersion "2010-09-09"

  Description (<<-EOS).undent
    Kumogata Sample Template
    You can use Here document!
  EOS

  Parameters do
    InstanceType do
      Default "t2.micro"
      Description "Instance Type"
      Type "String"
    end
  end

  Resources do
    myEC2Instance do
      Type "AWS::EC2::Instance"
      Properties do
        ImageId "ami-XXXXXXXX"
        InstanceType { Ref "InstanceType" }
        KeyName "your_key_name"

        UserData do
          Fn__Base64 (<<-EOS).undent
            #!/bin/bash
            yum install -y httpd
            service httpd start
          EOS
        end
      end
    end
  end
end

post do |output|
  puts output
  #=> '{"WebsiteURL"=>"http://ec2-XX-XX-XX-XX.ap-northeast-1.compute.amazonaws.com"}'
end

Plugin

parse()とdump()メソッドを定義したクラスを作れば簡単に作れます。 たとえばyamlをパースするプラグインを作ろうと思ったらlib/kumogata2/plugin/yaml.rbを含むgemをkumogata2-plugin-yamlという名前で作って、yaml.rbを以下のようにすればOK。

require 'yaml'

class Kumogata2::Plugin::YAML
  Kumogata2::Plugin.register(:yaml, ['yml'], self)

  def initialize(options)
    @options = options
  end

  def parse(str)
    YAML.load(str)
  end

  def dump(hash)
    YAML.dump(hash).colorize_as(:yaml)
    # dumpはexportやconvertで使うメソッドなので必須、という訳ではないです
    # 最悪「raise "Not implemented"」としてもいい気がします
  end

  #def post(outputs)
  #  postが定義されれば処理完了後にコールバックしてもらえます
  #end
end

CodeRayが対応していれば、いい感じに色づけしてくれます。(もちろんttyをみて適時色なしにします)。

連想配列を扱えるならhclでもperlでもpythonでもjsでもjson5でもcoffeescriptでも、論理的には対応可能なはずなので、JSONに疲れて自分の好きな言語で書きたい…というかたは新しくプラグインを書いてみるとよいのではないでしょうか?

ということで、Kumogataを細々と使っている方がいましたら、人柱バグレポやプルリクお待ちしていますm(_ _)m

TinyMysql

TinyMysqlというRubyのライブラリ的何かを作ってみました。

tiny_mysql.rb · GitHub

gist.github.com

これはなに?

MySQLCLIを使ったRubyMySQLライブラリです。 普通のMySQLのライブラリと違ってCLIさえインストールされていればどこでも使えます。

なんでつくったの?

とにかく手軽にRubyMySQLを使いたい、ということがたまにあるんですよ。ないですか?

MySQL/Rubymysql2はCライブラリの依存関係がたまにめんどくさくなるのでCLIで適当にできるライブラリがあるとうれしかったので作りました。

qeuryメソッドの出力はHashなので「文字列をパースして…」とか考えなくてすむのがよいと思います。

使い方

ちょうかんたんな使い方は以下の通り。

#!/usr/bin/env ruby

# ▼▼▼ おまじない ▼▼▼
require 'open-uri'
require 'digest/sha1'

tm = open('https://goo.gl/6z8rE1', &:read)
Digest::SHA1.hexdigest(tm) == '4028a6764164a69086bb28e7a940380bfbf7cc71' or raise 'checksum error'
eval tm
# ▲▲▲ おまじない▲▲▲

require 'pp'
mysql = TinyMysql.new(username: 'scott', password: 'tiger', database: 'mysql')
pp mysql.query('show tables')
# => [{"Tables_in_mysql"=>"columns_priv"},
#     {"Tables_in_mysql"=>"db"},
#     {"Tables_in_mysql"=>"event"},
#     {"Tables_in_mysql"=>"func"},
#     {"Tables_in_mysql"=>"general_log"},
#     {"Tables_in_mysql"=>"help_category"},
#     {"Tables_in_mysql"=>"help_keyword"},
#     {"Tables_in_mysql"=>"help_relation"},
#     {"Tables_in_mysql"=>"help_topic"},
#     {"Tables_in_mysql"=>"innodb_index_stats"},
#     {"Tables_in_mysql"=>"innodb_table_stats"},
#     {"Tables_in_mysql"=>"ndb_binlog_index"},
#     {"Tables_in_mysql"=>"plugin"},
#     {"Tables_in_mysql"=>"proc"},
#     {"Tables_in_mysql"=>"procs_priv"},
#     {"Tables_in_mysql"=>"proxies_priv"},
#     {"Tables_in_mysql"=>"servers"},
#     {"Tables_in_mysql"=>"slave_master_info"},
#     {"Tables_in_mysql"=>"slave_relay_log_info"},
#     {"Tables_in_mysql"=>"slave_worker_info"},
#     {"Tables_in_mysql"=>"slow_log"},
#     {"Tables_in_mysql"=>"tables_priv"},
#     {"Tables_in_mysql"=>"time_zone"},
#     {"Tables_in_mysql"=>"time_zone_leap_second"},
#     {"Tables_in_mysql"=>"time_zone_name"},
#     {"Tables_in_mysql"=>"time_zone_transition"},
#     {"Tables_in_mysql"=>"time_zone_transition_type"},
#     {"Tables_in_mysql"=>"user"}]

ファイルをダウンロードしてローカルで読み込んでもよいと思います。

$ curl https://goo.gl/6z8rE1 -L -o tiny_mysql.rb
$ shasum tiny_mysql.rb
4028a6764164a69086bb28e7a940380bfbf7cc71  tiny_mysql.rb
$ RUBYLIB=. ruby -r tiny_mysql -e 'p TinyMysql.new(username: "scott", password: "tiger").query("select 1")'
[{"1"=>"1"}]