Skip to content

Commit

Permalink
Adds dlss-capistrano-docker.
Browse files Browse the repository at this point in the history
  • Loading branch information
justinlittman committed Sep 27, 2022
1 parent ede4baf commit ed32058
Show file tree
Hide file tree
Showing 8 changed files with 59 additions and 237 deletions.
9 changes: 1 addition & 8 deletions Capfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,9 @@ require 'capistrano/deploy'
require 'capistrano/scm/git'
install_plugin Capistrano::SCM::Git

# require 'capistrano/bundler'
# require 'capistrano/rails'
# require 'capistrano/honeybadger'
# require 'capistrano/passenger'
require 'capistrano/maintenance'
require 'whenever/capistrano'

# require 'dlss/capistrano'
require 'capistrano/one_time_key'
require 'capistrano/shared_configs'
require 'dlss/docker/capistrano'

# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
4 changes: 1 addition & 3 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ end
group :deployment do
gem 'capistrano-maintenance', '~> 1.2', require: false
gem 'capistrano-rails', require: false
# gem 'dlss-capistrano', github: 'sul-dlss/dlss-capistrano', branch: 'docker', require: false
gem 'capistrano-one_time_key'
gem 'capistrano-shared_configs'
gem 'dlss-capistrano-docker', github: 'sul-dlss/dlss-capistrano-docker', branch: 'initial', require: false
end

gem 'action_policy', '~> 0.5.3'
Expand Down
13 changes: 11 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
GIT
remote: https://github.com/sul-dlss/dlss-capistrano-docker.git
revision: f180a5fc7329c506b0ad89bd0f8f821972a04fa6
branch: initial
specs:
dlss-capistrano-docker (0.0.1)
capistrano (~> 3.0)
capistrano-one_time_key
capistrano-shared_configs

GEM
remote: https://rubygems.org/
specs:
Expand Down Expand Up @@ -535,9 +545,7 @@ DEPENDENCIES
bunny (~> 2.17)
byebug
capistrano-maintenance (~> 1.2)
capistrano-one_time_key
capistrano-rails
capistrano-shared_configs
capybara (~> 3.34)
capybara-screenshot
config (~> 2.2)
Expand All @@ -547,6 +555,7 @@ DEPENDENCIES
cypress-rails
devise (~> 4.7)
devise-remote-user (~> 1.0)
dlss-capistrano-docker!
druid-tools
dry-types
edtf
Expand Down
9 changes: 5 additions & 4 deletions config/deploy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

set :linked_dirs, %w[log config/settings]

set :dereference_dirs, %w[config/settings]

# Default value for default_env is {}
# set :default_env, { path: "/opt/ruby/bin:$PATH" }

Expand All @@ -44,7 +46,6 @@
# Set Rails env to production in all Cap environments
set :rails_env, 'production'

set :whenever_environment, fetch(:rails_env)
set :whenever_roles, [:cron]

# TODO: WHENEVER
set :docker_compose_file, 'docker-compose.prod.yml'
set :docker_compose_seed_use_hooks, true
set :docker_compose_rabbitmq_use_hooks, true
216 changes: 2 additions & 214 deletions config/deploy/qa.rb
Original file line number Diff line number Diff line change
@@ -1,218 +1,6 @@
# frozen_string_literal: true

server 'h2-docker-qa.stanford.edu', user: 'h2', roles: %w[web app db]
# Roles are passed to docker-compose as profiles.
server 'h2-docker-qa.stanford.edu', user: 'h2', roles: %w[web app db cron worker]

Capistrano::OneTimeKey.generate_one_time_key!

# Most of below can be moved to tasks in dlss/capistrano
set(:docker_compose_file, 'docker-compose.prod.yml')
set :dereference_files, fetch(:linked_files)
set :dereference_dirs, %w[config/settings]
after 'deploy:starting', 'docker:login'
# Cleaning up docker on start so that images/logs are available for troubleshooting.
after 'deploy:starting', 'docker:prune'
after 'deploy:updating', 'shared_configs:update'
after 'deploy:updating', 'docker_compose:build'
after 'deploy:publishing', 'docker_compose:migrate'
after 'deploy:publishing', 'docker_compose:copy_assets'
after 'deploy:publishing', 'docker_compose:seed'
after 'deploy:publishing', 'docker_compose:setup_rabbitmq'
after 'deploy:publishing', 'docker_compose:restart'
after 'deploy:finishing', 'docker:logout'
after 'deploy:finishing', 'honeybadger:notify'

# rubocop:disable Metrics/BlockLength
namespace :docker_compose do
desc 'Build images'
task :build do
on roles(:app) do
# Docker build does not dereference symlinks.
invoke 'docker_compose:dereference_linked_files'
invoke 'docker_compose:dereference_linked_dirs'
within current_path do
execute(:docker, 'compose', '-f', fetch(:docker_compose_file, 'docker-compose.yml'), 'build')
end
info 'Docker images built'
end
end

desc 'Migrate database'
task :migrate do
on roles(:db) do
within current_path do
execute(:docker, 'compose', '-f', fetch(:docker_compose_file, 'docker-compose.yml'), 'run', 'app', 'bin/rails',
'db:migrate')
end
info 'Db migrated'
end
end

desc 'Seed database'
task :seed do
on roles(:db) do
within current_path do
execute(:docker, 'compose', '-f', fetch(:docker_compose_file, 'docker-compose.yml'), 'run', 'app', 'bin/rails',
'db:seed')
end
info 'Db migrated'
end
end

desc 'Copy assets'
task :copy_assets do
on roles(:web) do
within current_path do
# Can't do a direct copy to public/assets because it is a symlink.
execute(:docker, 'compose', '-f', fetch(:docker_compose_file, 'docker-compose.yml'), 'cp',
'app:/app/public/assets', '.')
execute(:rm, '-fr', 'public/assets/*')
execute(:mkdir, 'public/assets')
execute(:cp, 'assets/*', 'public/assets/')
end
info 'Assets copied'
end
end

desc 'Setup RabbitMQ'
task :setup_rabbitmq do
on roles(:web) do
within current_path do
execute(:docker, 'compose', '-f', fetch(:docker_compose_file, 'docker-compose.yml'), 'run', 'app', 'bin/rake',
'rabbitmq:setup')
end
info 'RabbitMQ setup'
end
end

desc 'Restart containers (down then up)'
task :restart do
on roles(:app) do
within current_path do
info 'Restarting containers'
invoke 'docker_compose:down'
invoke 'docker_compose:up'
end
end
end

desc 'Start containers'
task :up do
on roles(:app) do
within current_path do
execute(:docker, 'compose', '-f', fetch(:docker_compose_file, 'docker-compose.yml'), 'up', '-d')
info 'Containers started'
end
end
end

desc 'Tear down containers'
task :down do
on roles(:app) do
within current_path do
execute(:docker, 'compose', '-f', fetch(:docker_compose_file, 'docker-compose.yml'), 'down')
info 'Containers down'
end
end
end

desc 'Dereference linked files'
task :dereference_linked_files do
next unless any? :dereference_files

on roles(:app) do
fetch(:dereference_files).each do |file|
target = release_path.join(file)
source = shared_path.join(file)
execute :rm, target if test "[ -L #{target} ]"
execute :cp, source, target
end
end
end

desc 'Dereference linked directories'
task :dereference_linked_dirs do
next unless any? :dereference_dirs

on roles(:app) do
fetch(:dereference_dirs).each do |dir|
target = release_path.join(dir)
source = shared_path.join(dir)
next unless test "[ -L #{target} ]"

execute :rm, target
execute :cp, '-r', source, target
end
end
end
end
# rubocop:enable Metrics/BlockLength

namespace :docker do
desc 'Log in to Docker Hub'
task :login do
on roles(:app) do
within current_path do
execute(:docker, 'login', '-u', '$DOCKER_USERNAME', '-p', '$DOCKER_PASSWORD')
end
end
end

desc 'Log out of Docker Hub'
task :logout do
on roles(:app) do
within current_path do
execute(:docker, 'logout')
end
end
end

desc 'Prune unused images/containers'
task :prune do
on roles(:app) do
within current_path do
execute(:docker, 'image', 'prune', '-f')
execute(:docker, 'container', 'prune', '-f')
execute(:docker, 'builder', 'prune', '-f')
end
end
end
end

namespace :honeybadger do
# Replaces honeybadger:deploy to use curl instead of invoking ruby.
# Adapted from https://github.com/honeybadger-io/honeybadger-ruby/blob/master/vendor/capistrano-honeybadger/lib/capistrano/tasks/deploy.cap
desc 'Notify Honeybadger of a deploy (using the API via curl)'
task notify: %i[deploy:set_current_revision] do
fetch(:honeybadger_server) do
if (s = primary(:app))
set(:honeybadger_server, s.select?({ exclude: :no_release }) ? s : nil)
end
end

if (server = fetch(:honeybadger_server))
revision = fetch(:current_revision)

on server do |_host|
info 'Notifying Honeybadger of deploy.'

honeybadger_config = nil
within release_path do
honeybadger_config = capture(:cat, 'config/honeybadger.yml')
end
remote_api_key = YAML.safe_load(honeybadger_config)['api_key']
remote_api_key = capture(:echo, '$HONEYBADGER_API_KEY') if remote_api_key.nil?

options = {
'deploy[environment]' => fetch(:honeybadger_env, fetch(:rails_env, 'production')),
'deploy[local_username]' => fetch(:honeybadger_user, ENV['USER'] || ENV.fetch('USERNAME', nil)),
'deploy[revision]' => revision,
'deploy[repository]' => fetch(:repo_url),
'api_key' => fetch(:honeybadger_api_key, ENV.fetch('HONEYBADGER_API_KEY', nil)) || remote_api_key
}
data = options.to_a.map { |pair| pair.join('=') }.join('&')
execute(:curl, '--no-progress-meter', '--data', "\"#{data}\"", 'https://api.honeybadger.io/v1/deploys')
info 'Honeybadger notification complete.'
end
end
end
end
3 changes: 3 additions & 0 deletions config/schedule.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
# Use this file to easily define all of your cron jobs.
# Learn more: http://github.com/javan/whenever

# Don't execute with bash.
set :job_template, nil

every :day, at: '1:00am' do
runner 'WorkReminderGenerator.send_draft_reminders'
end
Expand Down
18 changes: 16 additions & 2 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,17 @@ services:
ports:
- 3000:3000
extra_hosts: &extra-hosts
- host.docker.internal:host-gateway
- host.docker.internal:host-gateway
profiles:
- web
# restart: on-failure
sneakers:
build: *build
environment: *environment
volumes: *volumes
extra_hosts: *extra-hosts
profiles:
- web
command: bin/bundle exec rake sneakers:run 2>&1 | tee -a log/sneakers.log
# restart: on-failure
sidekiq:
Expand All @@ -46,7 +50,17 @@ services:
volumes: *volumes
extra_hosts: *extra-hosts
command: bin/bundle exec sidekiq 2>&1 | tee -a log/sidekiq.log
profiles:
- worker
deploy:
replicas: ${SIDEKIQ_COUNT-1}
# restart: on-failure

cron:
build: *build
environment: *environment
volumes: *volumes
extra_hosts: *extra-hosts
profiles:
- cron
command: supercronic /app/config/crontab 2>&1 | tee -a log/cron.log
# restart: on-failure
24 changes: 20 additions & 4 deletions docker/app-prod/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
FROM ruby:3.1-alpine

# postgresql-client is required for invoke.sh
# busybox-suid is for editing cron
# curl is to install supercronic
RUN apk add --update --no-cache \
build-base \
postgresql-dev \
postgresql-client \
tzdata \
yarn \
git
git \
# busybox-suid \
curl

ENV SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/v0.2.1/supercronic-linux-amd64
ENV SUPERCRONIC=supercronic-linux-amd64
ENV SUPERCRONIC_SHA1SUM=d7f4c0886eb85249ad05ed592902fa6865bb9d70

# Cron replacement, since cron must be run as root
RUN curl -fsSLO "$SUPERCRONIC_URL" \
&& echo "${SUPERCRONIC_SHA1SUM} ${SUPERCRONIC}" | sha1sum -c - \
&& chmod +x "$SUPERCRONIC" \
&& mv "$SUPERCRONIC" "/usr/local/bin/${SUPERCRONIC}" \
&& ln -s "/usr/local/bin/${SUPERCRONIC}" /usr/local/bin/supercronic

# Get bundler 2.0
RUN gem install bundler
Expand All @@ -26,7 +39,8 @@ COPY Gemfile Gemfile.lock package.json yarn.lock ./

ENV RAILS_ENV=production
ENV RAILS_LOG_TO_STDOUT=true
ENV BUNDLE_WITHOUT="test:development"
ENV BUNDLE_WITHOUT="test:development:deployment"
ENV NODE_ENV=production

RUN bundle install
RUN yarn install
Expand All @@ -35,5 +49,7 @@ COPY --chown=h2:h2 . .
USER h2:h2

RUN bin/rails assets:precompile
# Write out the crontab file
RUN sh -c 'bundle exec whenever . | tee -a config/crontab'

CMD bin/puma -C config/puma.rb config.ru 2>&1 | tee -a log/production.log

0 comments on commit ed32058

Please sign in to comment.