Skip to content

Commit

Permalink
New pwpush-worker Docker container (#2601)
Browse files Browse the repository at this point in the history
* Add SolidQueue and background jobs

* Add worker to Procfiles

* Docker container build

* Worker container: updated entrypoint
  • Loading branch information
pglombardo authored Oct 9, 2024
1 parent b7376ed commit a886e69
Show file tree
Hide file tree
Showing 16 changed files with 564 additions and 3 deletions.
44 changes: 44 additions & 0 deletions .github/workflows/docker-containers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,47 @@ jobs:
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=registry,ref=${{ secrets.DOCKER_USERNAME }}/pwpush-public-gateway:buildcache
cache-to: type=registry,ref=${{ secrets.DOCKER_USERNAME }}/pwpush-public-gateway:buildcache,mode=max,ignore-error=${{env.DOCKER_PUSH == 'false'}}

worker-container:
needs: pwpush-container
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Populate Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ secrets.DOCKER_USERNAME }}/pwpush-worker
tags: |
type=match,pattern=stable
type=schedule,pattern=nightly
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
file: ./containers/docker/Dockerfile.worker
platforms: linux/amd64,linux/arm64
provenance: false
push: true
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=registry,ref=${{ secrets.DOCKER_USERNAME }}/pwpush-worker:buildcache
cache-to: type=registry,ref=${{ secrets.DOCKER_USERNAME }}/pwpush-worker:buildcache,mode=max,ignore-error=${{env.DOCKER_PUSH == 'false'}}

2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,5 @@ gem "version", git: "https://github.com/pglombardo/version.git", branch: "master
gem "administrate", "~> 0.20.1"
gem "rqrcode", "~> 2.2"
gem "turnout2024", require: "turnout"

gem "solid_queue", "~> 1.0"
14 changes: 14 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ GEM
rubocop (>= 1)
smart_properties
erubi (1.13.0)
et-orbi (1.2.11)
tzinfo
execjs (2.9.1)
faraday (1.10.4)
faraday-em_http (~> 1.0)
Expand Down Expand Up @@ -215,6 +217,9 @@ GEM
ffi (1.17.0)
foreman (0.88.1)
forwardable (1.3.3)
fugit (1.11.1)
et-orbi (~> 1, >= 1.2.11)
raabro (~> 1.4)
gettext (3.4.9)
erubi
locale (>= 2.0.5)
Expand Down Expand Up @@ -392,6 +397,7 @@ GEM
public_suffix (6.0.1)
puma (6.4.3)
nio4r (~> 2.0)
raabro (1.4.0)
racc (1.8.1)
rack (3.1.7)
rack-accept (0.4.5)
Expand Down Expand Up @@ -525,6 +531,13 @@ GEM
devise (>= 3.2, < 6)
singleton (0.2.0)
smart_properties (1.17.0)
solid_queue (1.0.0)
activejob (>= 7.1)
activerecord (>= 7.1)
concurrent-ruby (>= 1.3.1)
fugit (~> 1.11.0)
railties (>= 7.1)
thor (~> 1.3.1)
sprockets (4.2.1)
concurrent-ruby (~> 1.0)
rack (>= 2.2.4, < 4)
Expand Down Expand Up @@ -646,6 +659,7 @@ DEPENDENCIES
sass-rails (~> 6.0, >= 6.0.0)
selenium-webdriver
simple_token_authentication
solid_queue (~> 1.0)
sqlite3
standardrb (~> 1.0)
stimulus-rails
Expand Down
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
release: bundle exec rails db:migrate
web: bundle exec puma -C config/puma.rb
console: bundle exec rails console
worker: bundle exec rake solid_queue:start
1 change: 1 addition & 0 deletions Procfile.dev
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
web: bin/rails server -p 5100
js: yarn watch
worker: bundle exec rake solid_queue:start
75 changes: 75 additions & 0 deletions app/jobs/clean_up_pushes_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
class CleanUpPushesJob < ApplicationJob
queue_as :default

# Delete Anonymous Expired Pushes
#
# When a push expires, the payload is deleted but the metadata record still exists. This
# includes information such as creation date, audit logs, duration etc.. When the record
# was created by an anonymous user, this data is no longer needed and we delete it (we
# don't want it).
#
# If a user attempts to retrieve a secret link that doesn't exist anymore, we still show
# the standard "This secret link has expired" message. This strategy provides two benefits:
#
# 1. It hides the fact that if a secret ever exists or not (more secure)
# 2. It allows us to delete data that we don't want
#
# This task will run through all expired and anonymous records and delete them entirely.
#
# Because of the above, expired and anonymous secret URLs still will show the same
# expiration message
#
# Note: This applies to anonymous pushes. For logged-in user records, we don't do this
# to maintain user audit logs.
#
def perform(*args)
# Log task start
logger.info("--> #{self.class.name}: Starting...")

counter = 0

Password.includes(:views)
.where(expired: true)
.where(user_id: nil)
.find_each do |push|
counter += 1
push.destroy
end

if Settings.enable_file_pushes
FilePush.includes(:views)
.where(expired: true)
.where(user_id: nil)
.find_each do |push|
counter += 1
push.destroy
end
end

if Settings.enable_url_pushes
Url.includes(:views)
.where(expired: true)
.where(user_id: nil)
.find_each do |push|
counter += 1
push.destroy
end
end

logger.info(" -> #{counter} total anonymous expired pushes deleted.")

# Log completion
logger.info(" -> #{self.class.name}: Finished.")
end

private

def logger
@logger ||= if ENV.key?(PWP_WORKER)
# We are running inside the pwpush-worker container. Log to STDOUT
Logger.new($stdout)
else
Logger.new(Rails.root.join("log", "recurring.log"))
end
end
end
64 changes: 64 additions & 0 deletions app/jobs/expire_pushes_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
class ExpirePushesJob < ApplicationJob
queue_as :default

##
# This job is responsible for scanning all unexpired password pushes and
# conditionally expiring them. This is a preemptive measure to expire pushes
# periodically. It saves some CPU and DB calls on live requests.
#
def perform(*args)
# Log task start
logger.info("--> #{self.class.name}: Starting...")

counter = 0
expiration_count = 0

Password.where(expired: false).find_each do |push|
counter += 1
push.validate!
expiration_count += 1 if push.expired
end

logger.info(" -> Finished validating #{counter} unexpired password pushes. #{expiration_count} total pushes expired...")

if Settings.enable_file_pushes
counter = 0
expiration_count = 0
FilePush.where(expired: false).find_each do |push|
counter += 1
push.validate!
expiration_count += 1 if push.expired
end
logger.info(" -> Finished validating #{counter} unexpired File pushes. #{expiration_count} total pushes expired...")
end

if Settings.enable_url_pushes
counter = 0
expiration_count = 0
Url.where(expired: false).find_each do |push|
counter += 1
push.validate!
expiration_count += 1 if push.expired
end
logger.info(" -> Finished validating #{counter} unexpired URL pushes. #{expiration_count} total pushes expired...")
end

# Log results
logger.info(" -> #{self.class.name}: #{counter} anonymous and expired pushes have been deleted.")

# Log completion
logger.info(" -> #{self.class.name}: Finished.")
end

private

def logger
@logger ||= if ENV.key?(PWP_WORKER)
# We are running inside the pwpush-worker container. Log to STDOUT
# so that docker logs works to investigate problems.
Logger.new($stdout)
else
Logger.new(Rails.root.join("log", "recurring.log"))
end
end
end
6 changes: 6 additions & 0 deletions bin/jobs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env ruby

require_relative "../config/environment"
require "solid_queue/cli"

SolidQueue::Cli.start(ARGV)
5 changes: 3 additions & 2 deletions config/environments/production.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,9 @@
# config.cache_store = :mem_cache_store

# Use a real queuing backend for Active Job (and separate queues per environment).
# config.active_job.queue_adapter = :resque
# config.active_job.queue_name_prefix = "password_pusher_production"
config.active_job.queue_adapter = :solid_queue

# config.active_job.queue_name_prefix = "pwp_prod"

config.action_mailer.perform_caching = true

Expand Down
18 changes: 18 additions & 0 deletions config/queue.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
default: &default
dispatchers:
- polling_interval: 1
batch_size: 500
workers:
- queues: "*"
threads: 3
processes: <%= ENV.fetch("JOB_CONCURRENCY", 1) %>
polling_interval: 0.1

development:
<<: *default

test:
<<: *default

production:
<<: *default
42 changes: 42 additions & 0 deletions config/recurring.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# production:
# periodic_cleanup:
# class: CleanSoftDeletedRecordsJob
# queue: background
# args: [ 1000, { batch_size: 500 } ]
# schedule: every hour
# periodic_command:
# command: "SoftDeletedRecord.due.delete_all"
# priority: 2
# schedule: at 5am every day

production:
expire_pushes:
class: ExpirePushesJob
queue: background
schedule: every 2 hours

# expire_pulls:
# class: ExpirePullsJob
# queue: background
# schedule: every 3 hours

cleanup_pushes:
class: CleanUpPushesJob
queue: background
schedule: every day at 5am

development:
expire_pushes:
class: ExpirePushesJob
queue: background
schedule: every 4 hours

# expire_pulls:
# class: ExpirePullsJob
# queue: background
# schedule: every 5 hours

cleanup_pushes:
class: CleanUpPushesJob
queue: background
schedule: every 7 hours
1 change: 1 addition & 0 deletions config/routes/admin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@

root to: "users#index"
end
# mount MissionControl::Jobs::Engine, at: "/admin/jobs"
end
end
6 changes: 6 additions & 0 deletions containers/docker/Dockerfile.worker
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM pglombardo/pwpush:latest

ENV PWP_WORKER=true

USER pwpusher
ENTRYPOINT ["containers/docker/worker-entrypoint.sh"]
28 changes: 28 additions & 0 deletions containers/docker/worker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/bin/bash
set -e

export RAILS_ENV=production

echo ""
if [ -z "$DATABASE_URL" ]
then
echo "DATABASE_URL not specified. This worker container only works with a PostgreSQL, MySQL or MariaDB database."
echo "Please specify DATABASE_URL and use the same settings & environment variables as you do with other pwpush containers."
echo ""
echo "See https://docs.pwpush.com/docs/database_url/ for more information on DATABASE_URL."
exit 1
elif [[ "$DATABASE_URL" == sqlite3://* ]]; then
echo "Error: sqlite3 isn't supported for the pwpush-worker container."
exit 1
else
echo "According to DATABASE_URL database backend is set to $(echo $DATABASE_URL|cut -d ":" -f 1):..."
fi
echo ""

echo "Password Pusher: migrating database to latest..."
bundle exec rake db:migrate

echo "Password Pusher: starting background workers..."
bundle exec solid_queue:start

exec "$@"
Loading

0 comments on commit a886e69

Please sign in to comment.