Gemfileに記載してあるgemの説明を一覧で表示する #fjordbootcamp

タイトルに #fjordbootcamp がついていますが、これは #fjordbootcamp の課題とかではありません。

フィヨルドブートキャンプとは

bootcamp.fjord.jp

きっかけ

フィヨルドブートキャンプのGitHubリポジトリに以下のIssueが登録されていた。*1

github.com

これを読んでいて、ふと「gemspecから説明を抜き出して、一覧で表示できると便利そう」と思いついたので、試しに実装してみた。

コード

#!/usr/bin/env ruby
# frozen_string_literal: true

require 'bundler'

lockfile = Bundler.default_lockfile.read
parser = Bundler::LockfileParser.new(lockfile)

spec_per_name = parser.specs.group_by(&:name).transform_values(&:first)

parser.dependencies.keys.each do |name|
  spec = spec_per_name[name]

  if spec
    path = "#{Bundler.specs_path}/#{name}-#{spec.version}.gemspec"
    gemspec = Gem::Specification.load(path)
  end

  puts "#{name}: #{gemspec&.summary}"
end

実行結果

$ ./bundler-summaries
bootsnap: Boot large ruby/rails apps faster
byebug: Ruby fast debugger - base + CLI
capybara: Capybara aims to simplify the process of integration testing Rack applications, such as Rails, Sinatra or Merb
jbuilder: Create JSON structures via a Builder-style DSL
listen: Listen to file modifications
puma: Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications
rack-mini-profiler: Profiles loading speed for rack applications.
rails: Full-stack web application framework.
sass-rails: Sass adapter for the Rails asset pipeline.
selenium-webdriver: The next generation developer focused tool for automated testing of webapps
spring: Rails application preloader
sqlite3: This module allows Ruby programs to interface with the SQLite3 database engine (http://www.sqlite.org)
turbolinks: Turbolinks makes navigating your web application faster
tzinfo-data:
web-console: A debugging tool for your Ruby on Rails applications.
webdrivers: Easy download and use of browser drivers.
webpacker: Use webpack to manage app-like JavaScript modules in Rails

*1:フィヨルドブートキャンプでは学習で使うサービス自体がGitHubオープンソースになっている

SlackのGitHub Appをアップグレードするとdeployコマンドと通知が壊れる

先日、SlackのGitHub Appがアップグレードされました。

github.com

しかし、アップグレードすると /github deploy コマンドとデプロイ通知が 壊れる ため、業務で使っている場合はもう少し様子を見てからアップグレードした方が良さそうです。

READMEの記述

以下はREADMEの引用ですが、意図的に機能を消しているので復活しない可能性もある。

Removed deploy command and notification support: Today, the functionality provided by deploy command is very limited and doesn't address all the scenarios. We are removing deploy command and notifications support as part of this version. We want to relook at the scenarios and build a more holistic experience that customers need.

関連するIssue

github.com

github.com

GitHub(Legacy)を復活させる

https://<workspace>.slack.com/apps/A8GBNUWU8-github-legacy から再インストールすると復活できた。
workspace の部分は各ワークスペースの名前に直してください。

Rubyをインストールせず Docker だけを使ってrails newを実行する

rails new するときにDockerfileを使う必要は特にない。

むしろ開発時に使うDockerfileとは別物になるので、Dockerfileを作らない方が良いです。

コマンド

$ mkdir example_app
$ cd example_app
$ docker run --rm -v $(pwd):/app -w /app ruby:3.0.0 bash -c '\
    curl -fsSL https://deb.nodesource.com/setup_14.x | bash - \
    && apt-get update && apt-get install -y git nodejs \
    && npm install -g yarn \
    && gem i --no-document rails \
    && rails new .'

解説

--rm

一時的な実行でコンテナを残しておく必要がないのでつけてる。

-v $(pwd):/app -w /app

ホストPCのディレクトリをコンテナ内の /app にマウントし、そのディレクトリを作業ディレクトリに指定する。

ruby:3.0.0 bash -c '...'

rubyのイメージのbashを使ってコマンドを実行する。

curl -fsSL https://deb.nodesource.com/setup_14.x | bash - \
&& apt-get update && apt-get install -y git nodejs \
&& npm install -g yarn \

rails newすると自動的にbundle installyarn installが実行されるため、必要になるパッケージの類いを追加する。

gem i --no-document rails \
&& rails new .

railsのgemを追加した後、rails new . で作業ディレクトリ(/app)にファイルを作成する。

開発環境で使うDockerfileについて

開発時に使うDockerfileとdocker-compose.ymlは前回に記事を書いてある。

sinsoku.hatenablog.com

最後に

たぶんRubyを入れて、普通に rails new した方が楽だと思う。

Railsアプリの開発環境向けDockerfile + docker-compose.yml

人に説明するときに記事あると便利なので、開発環境向けのDockerfileとdocker-compose.ymlを書いておく。

Dockerfile

FROM ruby:3.0.0

WORKDIR /app

# Using Node.js v14.x(LTS)
RUN curl -fsSL https://deb.nodesource.com/setup_14.x | bash -

# Add packages
RUN apt-get update && apt-get install -y \
      git \
      nodejs \
      vim

# Add yarnpkg for assets:precompile
RUN npm install -g yarn

# Add Chrome
RUN curl -sO https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \
    && apt install -y ./google-chrome-stable_current_amd64.deb \
    && rm google-chrome-stable_current_amd64.deb

# Add chromedriver
RUN CHROME_DRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE` \
    && curl -sO https://chromedriver.storage.googleapis.com/$CHROME_DRIVER_VERSION/chromedriver_linux64.zip \
    && unzip chromedriver_linux64.zip \
    && mv chromedriver /usr/bin/chromedriver
  • git: GitHubを参照してgemを入れるときに必要
  • nodejs + yarn: bin/rails assets:precompile で必要
  • vim: bin/rails credentials:edit で使う
  • Chrome + chromedriver: System Test で必要

docker-compose.yml

version: "3.8"
services:
  db:
    image: postgres:12-alpine
    volumes:
      - postgres:/var/lib/postgresql/data
    environment:
      POSTGRES_HOST_AUTH_METHOD: trust

  redis:
    image: redis:5-alpine
    volumes:
      - redis:/data

  web: &web
    build: .
    image: app:1.0.0
    stdin_open: true
    tty: true
    volumes:
      - .:/app:cached
      - bundle:/app/vendor/bundle
      - node_modules:/app/node_modules
      - rails_cache:/app/tmp/cache
      - packs:/app/public/packs
      - packs_test:/app/public/packs-test
    tmpfs:
      - /tmp
    environment:
      BUNDLE_PATH: "/app/vendor/bundle"
      BOOTSNAP_CACHE_DIR: "/app/vendor/bundle"
      WD_INSTALL_DIR: "/usr/local/bin"
      HISTFILE: "/app/log/.bash_history"
      EDITOR: "vi"
      DATABASE_URL: "postgres://postgres:postgres@db:5432"
      REDIS_URL: "redis://redis:6379/"
      RAILS_MASTER_KEY:
    depends_on:
      - db
      - redis
    command: ["bin/rails", "server", "-b", "0.0.0.0"]
    expose: ["3000"]
    ports: ["3000:3000"]
    user: root
    working_dir: /app

  worker:
    <<: *web
    command: ["bundle", "exec", "sidekiq"]
    expose: []
    ports: []

volumes:
  postgres:
  redis:
  bundle:
  node_modules:
  rails_cache:
  packs:
  packs_test:
image: app:1.0.0

imageに名前とバージョンをつけておくことで、バージョンをインクリメントすると docker-compose run のときに自動的にビルドが走る。

Dockerfileを変えたときにバージョンをインクリメントすることで、他の開発者が古いイメージを使い続けてしまう事を防ぐことができる。

stdin_open: true
tty: true

byebug などでデバッグできるようにするため。

volumes:
  - .:/app:cached
  - bundle:/app/vendor/bundle
  - node_modules:/app/node_modules
  - rails_cache:/app/tmp/cache
  - packs:/app/public/packs
  - packs_test:/app/public/packs-test

Docker for Mac遅い問題の対処。

ホストとの同期を減らすことで若干マシになる。遅いけど。

worker:
  <<: *web
  command: ["bundle", "exec", "sidekiq"]
  expose: []
  ports: []

sidekiqを使う場合、基本的な設定はwebと同じなのでエイリアスを使う。

expose, portsはworkerで不要なので上書きする。

使い方

初回の環境構築

Dockerイメージを作成して、 bin/setup を実行する。

$ docker-compose build
$ docker-compose run --rm web bin/setup

サーバの起動

$ docker-compose run --rm --service-ports web

実行すると http://localhost:3000 でアクセスできる。

Railsコマンド類の実行

$ docker-compose run --rm web bin/rails -T

テストの実行

$ docker-compose run --rm -e RAILS_ENV=test web bin/rails db:test:prepare
$ docker-compose run --rm -e RAILS_ENV=test web bin/rails test

コンテナ内で作業する

毎回コンテナの起動をすると遅いので、基本的にコンテナ内で作業した方が楽。

$ docker-compose run --rm web bash
root@b419978e89ec:/app# bin/rails --version
Rails 6.1.3

全てのvolumeを削除する

開発環境を一新したいときに使う。

$ docker-compose down --volumes

追加の説明

Dockerfileでbundle installは不要なのか?

Dockerイメージのビルドでは不要です。

bin/setup の実行で、マウントしてるvolumeにインストールされます。

なぜdocker-compose up webで起動しないのか?

docker-compose up で起動すると標準入力が使えなくなって、byebugでデバッグできなくなるため。

あと、コンテナを止めたときに稀にtmp/pids/server.pidが残ることがある。*1

コマンドが長い

bibendi/dipを使うか、bashエイリアスを使うと楽です。

開発用のイメージが大きい

alpineやmulti stage buildを使えば軽量化できるかもしれないですが、面倒だったので調べてないです。

開発環境用のDockerイメージが少し重くても、大して困らないため。

*1:原因の詳細は調べられていないですが、 docker-compose run では今まで起きていない。

assets:precompileの結果を1世代だけ残す

Sprocketsは assets:clean[0] しても1時間以内に作成したassetsは消してくれません。

参照: https://github.com/rails/sprockets/blob/v4.0.2/lib/sprockets/manifest.rb#L245

さらに assets:clean の後に webpacker:clean を実行してくれるけど、引数は伝搬してくれない。

参考: https://github.com/rails/webpacker/blob/v6.0.0.pre.2/lib/tasks/webpacker/clean.rake#L19-L21

webpacker:clean に引数を伝搬させる

webpackerが登録してるProcを削除し、 enhance でタスクを登録し直す。

# Rakefile

assets_clean = Rake::Task['assets:clean']
assets_clean.actions.reject! { |act| act.source_location[0].include?('webpacker/clean.rake') }
assets_clean.enhance(['webpacker:clean'])

assets:clean[keep,age] に対応させる

with_loggerやmanifestを使えるように binding を取得して頑張る。

# Rakefile

assets_clean = Rake::Task['assets:clean']
assets_clean.arg_names << :age
pos = assets_clean.actions.index { |act| act.source_location[0].include?('sprockets/rails/task.rb') }
act = assets_clean.actions[pos]
assets_clean.actions[pos] = lambda do |_t, args, **opts|
  keep = Integer(args.keep || act.binding.eval('self.keep'))
  age = Integer(args.age || 3600)

  act.binding.eval(<<~RUBY)
    with_logger do
      manifest.clean(#{keep}, #{age})
    end
  RUBY
end

まとめ

これにより bin/rails assets:precompile assets:clean[0] を実行すると、1世代だけassetsを残せる。

Herokuでカスタムドメイン(ルートドメイン)を使うためにCloudFrontを設定する

Route53で取得したカスタムドメインをHerokuで使うためにCloudFrontの設定をしたのでブログに書いておく。

コード例は foo.herokuapp.com で動くアプリケーションを foo-example.com でアクセスできるようにする設定です。

AWS側の設定

  • CloudFrontで使うacmはvirginiaで作成
  • ttlは0にしてキャッシュしない
    • /assets/packs はキャッシュした方が良いけど、まだ未対応
locals {
  foo_domain    = "foo-example.com"
  foo_origin_id = "foo-heroku"
}

module "acm" {
  source  = "terraform-aws-modules/acm/aws"
  version = "2.12.0"

  providers = {
    aws = aws.virginia
  }

  domain_name = local.foo_domain
  zone_id     = aws_route53_zone.main.zone_id
}

resource "aws_cloudfront_distribution" "foo" {
  enabled         = true
  is_ipv6_enabled = true
  aliases         = [local.foo_domain]

  origin {
    domain_name = "foo.herokuapp.com"
    origin_id   = local.foo_origin_id

    custom_origin_config {
      http_port              = 80
      https_port             = 443
      origin_protocol_policy = "https-only"
      origin_ssl_protocols   = ["TLSv1", "TLSv1.1", "TLSv1.2"]
    }
  }

  default_cache_behavior {
    target_origin_id       = local.foo_origin_id
    allowed_methods        = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
    cached_methods         = ["GET", "HEAD"]
    compress               = true
    default_ttl            = 0
    max_ttl                = 0
    viewer_protocol_policy = "redirect-to-https"

    forwarded_values {
      headers      = ["*"]
      query_string = true

      cookies {
        forward = "all"
      }
    }
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }

  viewer_certificate {
    acm_certificate_arn      = module.acm.this_acm_certificate_arn
    minimum_protocol_version = "TLSv1.2_2019"
    ssl_support_method       = "sni-only"
  }
}

Heroku側の設定

カスタムドメインを登録しておく。

$ heroku domains:add foo-example.com

git-notesでコミットにメモをつける

2020年に「コミットログは良くならない」というのを悟ったので、現実的な解決案である「git-notesでメモを残す」について記事を書いておきます。

前回の記事

sinsoku.hatenablog.com

git-notes

詳細は git notes --help を読んでください。

概要は以下の通りです。

  • コミットログとは別にメモを残せる
    • コミットはそのままなのでshaは変わらない
    • shaが変わらないのでCIの再実行が起きない
  • 他人のコミットにメモをつけられる
    • 他人に作業を依頼する必要がない
  • メモもリモートにプッシュできる
  • 過去のコミットにメモを残せる

使い方

メモを書く

git notes edit <sha> でメモを書くと、git log のときに一緒に表示される。

$ git notes edit d2cdf0b
$ git log -1 d2cdf0b
commit d2cdf0be675b44771f950697fc0b19ef0ea453f9
Merge: 25d156673e 0adcec4954
Author: Ryuta Kamizono <kamipo@gmail.com>
Date:   Wed Jun 17 20:29:47 2020 +0900

    Merge pull request #39612 from kamipo/faster_attributes

    PERF: 45% faster attributes for readonly usage

Notes:
    ActiveRrecordが速くなった。 #kamipoさんはすごい人

コミット権がないとプッシュできないですが、ローカルなら自由にメモを書ける。

メモを削除する

git notes remove <sha> で削除できます。

メモをリモートにプッシュ(フェッチ)する

$ git push origin refs/notes/commits
$ git fetch origin refs/notes/commits:refs/notes/commits

簡単にプッシュ(フェッチ)できるようにする

.git/config に以下の設定を追加する。

 [remote "origin"]
   url = git@github.com:sinsoku/dotfiles.git
   fetch = +refs/heads/*:refs/remotes/origin/*
+  fetch = +refs/notes/*:refs/notes/*
+  push = +refs/notes/*:refs/notes/*

これで普通に git fetchgit push できるようになります。

注意

.git/config に設定を追加すると git push origin の挙動が変わります。

  • 変更前: 現在のブランチをプッシュ
  • 変更後: notes をプッシュ

挙動が変わって困る人はエイリアスを使った方が良いです。

$ git config alias.push-notes 'push origin refs/notes/*'

参考ページ