dev: move script from /bin to /script
This commit is contained in:
Executable
+289
@@ -0,0 +1,289 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require_relative "../config/environment"
|
||||
|
||||
class EnactTenanting
|
||||
CURRENT_DOMAIN = "https://fizzy.37signals.com"
|
||||
TENANTED_DOMAIN = Rails.env.local? ? "http://%{tenant}.fizzy.localhost:3006" : "https://%{tenant}.fizzy.37signals.com"
|
||||
|
||||
PROJECT_MAPPING = {
|
||||
693169841 => "37s", # Writebook improvements
|
||||
693169850 => "37s", # Fizzy: Issues
|
||||
693169853 => "37s", # Fizzy: 💡 IDEAS
|
||||
693169858 => "37s", # Know It All: 💡 IDEAS
|
||||
693169859 => "37s", # Cycle 1: BC4 Client access for templates
|
||||
693169860 => "37s", # Know It All: Issues
|
||||
693169862 => "37s", # Active Record Tenanting
|
||||
693169863 => "37s", # Cycle 1: BC4 Hilltop View
|
||||
693169865 => "37s", # [BC iOS] Search refactor
|
||||
693169867 => "37s", # File icons refactor
|
||||
693169870 => "37s", # HEY Preload Replies
|
||||
693169872 => "37s", # [BC4 Android] Search Refactor
|
||||
693169873 => "37s", # BC4
|
||||
693169874 => "37s", # [HEY] ContentFilter pipeline rewrite
|
||||
693169875 => "37s", # [Hey Calendar Android] Search
|
||||
693169876 => "37s", # [HEY Calendar iOS] Search
|
||||
693169877 => "37s", # [BC4] Turbo for comment and answer forms
|
||||
|
||||
693169842 => "dev", # Foobar
|
||||
693169843 => "dev", # Test
|
||||
693169851 => "dev", # Kevin
|
||||
693169861 => "dev", # Testing space
|
||||
693169869 => "dev", # Flower ideas
|
||||
693169871 => "dev", # Mike's TODOs
|
||||
|
||||
693169856 => "qa", # QA Exploration
|
||||
}
|
||||
|
||||
TENANTS = PROJECT_MAPPING.values.uniq
|
||||
|
||||
attr_reader :creation_counter, :update_counter
|
||||
|
||||
def initialize
|
||||
@creation_counter = {}
|
||||
@update_counter = {}
|
||||
end
|
||||
|
||||
def up
|
||||
setup
|
||||
destroy_tenants
|
||||
|
||||
safety_check
|
||||
|
||||
copy_accounts_et_al
|
||||
copy_buckets_et_al
|
||||
copy_bubbles_et_al
|
||||
copy_filters
|
||||
copy_active_storage
|
||||
|
||||
cross_check
|
||||
|
||||
update_action_text_urls
|
||||
|
||||
pp ["updated:", update_counter]
|
||||
|
||||
hardlink_active_storage
|
||||
end
|
||||
|
||||
def setup
|
||||
ApplicationRecord.connects_to shards: { untenanted: { reading: :primary_original } }
|
||||
end
|
||||
|
||||
def with_original_db(&block)
|
||||
ApplicationRecord.connected_to(role: :reading, shard: :untenanted, &block)
|
||||
end
|
||||
|
||||
def safety_check
|
||||
buckets = with_original_db { Bucket.all.to_a }
|
||||
|
||||
unless buckets.map(&:id).sort == PROJECT_MAPPING.keys.sort
|
||||
unknown_buckets = buckets.map(&:id) - PROJECT_MAPPING.keys
|
||||
missing_buckets = PROJECT_MAPPING.keys - buckets.map(&:id)
|
||||
|
||||
raise "Surprising buckets. unknown #{unknown_buckets.inspect}, missing #{missing_buckets.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def copy_accounts_et_al
|
||||
account = with_original_db { Account.first }
|
||||
users = with_original_db { User.all.to_a }
|
||||
workflows = with_original_db { account.workflows.to_a }
|
||||
workflow_stages = with_original_db { workflows.flat_map(&:stages) }
|
||||
tags = with_original_db { account.tags.to_a }
|
||||
|
||||
TENANTS.each do |tenant|
|
||||
ApplicationRecord.with_tenant(tenant) do
|
||||
Account.create! name: tenant, id: account.id
|
||||
|
||||
upsert_all users
|
||||
upsert_all workflows
|
||||
upsert_all workflow_stages
|
||||
upsert_all tags
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def copy_buckets_et_al
|
||||
PROJECT_MAPPING.each do |bucket_id, tenant|
|
||||
bucket = with_original_db { Bucket.where(id: bucket_id).first }
|
||||
accesses = with_original_db { bucket.accesses.to_a }
|
||||
subscriptions = with_original_db { bucket.subscriptions.to_a }
|
||||
|
||||
ApplicationRecord.with_tenant(tenant) do
|
||||
upsert_all bucket
|
||||
upsert_all accesses
|
||||
upsert_all subscriptions
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def copy_bubbles_et_al
|
||||
PROJECT_MAPPING.each do |bucket_id, tenant|
|
||||
bubbles = with_original_db { Bucket.find(bucket_id).bubbles.to_a }
|
||||
assignments = with_original_db { bubbles.flat_map(&:assignments) }
|
||||
pops = with_original_db { bubbles.filter_map(&:pop) }
|
||||
notifications = with_original_db { bubbles.flat_map(&:notifications) }
|
||||
events = with_original_db { bubbles.flat_map(&:events) }
|
||||
taggings = with_original_db { bubbles.flat_map(&:taggings) }
|
||||
watches = with_original_db { bubbles.flat_map(&:watches) }
|
||||
messages = with_original_db { bubbles.flat_map(&:messages) }
|
||||
messageables = with_original_db { messages.filter_map(&:messageable) }
|
||||
comments = messageables.select { _1.is_a?(Comment) }
|
||||
markdowns = with_original_db { comments.map(&:markdown_body) }
|
||||
reactions = with_original_db { comments.flat_map(&:reactions) }
|
||||
|
||||
ApplicationRecord.with_tenant(tenant) do
|
||||
upsert_all bubbles
|
||||
upsert_all assignments
|
||||
upsert_all pops
|
||||
upsert_all taggings
|
||||
upsert_all watches
|
||||
messageables.group_by(&:class).each do |klass, klass_messageables|
|
||||
upsert_all klass_messageables
|
||||
end
|
||||
upsert_all markdowns
|
||||
upsert_all reactions
|
||||
upsert_all messages
|
||||
upsert_all events
|
||||
upsert_all notifications
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def copy_filters
|
||||
creation_counter["Filter"] = 0
|
||||
TENANTS.each do |tenant|
|
||||
buckets = ApplicationRecord.with_tenant(tenant) { Bucket.all.to_a }
|
||||
|
||||
filters = with_original_db do
|
||||
Filter.all.filter_map do |filter| # hah no pun intended
|
||||
next unless filter.buckets.empty? || buckets.any? { |b| filter.buckets.include?(b) }
|
||||
|
||||
filter.attributes.merge(filter.as_params)
|
||||
end
|
||||
end
|
||||
|
||||
ApplicationRecord.with_tenant(tenant) do
|
||||
filters.each do |attr|
|
||||
Filter.create! attr
|
||||
end
|
||||
end
|
||||
creation_counter["Filter"] += filters.length
|
||||
end
|
||||
end
|
||||
|
||||
def copy_active_storage
|
||||
attachments = with_original_db { ActiveStorage::Attachment.all.to_a }
|
||||
blobs = with_original_db { ActiveStorage::Blob.all.to_a }
|
||||
|
||||
TENANTS.each do |tenant|
|
||||
ApplicationRecord.with_tenant(tenant) do
|
||||
upsert_all blobs
|
||||
upsert_all attachments
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_tenants
|
||||
TENANTS.each do |tenant|
|
||||
ApplicationRecord.destroy_tenant(tenant)
|
||||
end
|
||||
FileUtils.rm_rf "storage/tenants"
|
||||
end
|
||||
|
||||
def cross_check
|
||||
pp ["created:", creation_counter]
|
||||
|
||||
with_original_db do
|
||||
assert_count User, User.count * TENANTS.length
|
||||
assert_count Workflow, Workflow.count * TENANTS.length
|
||||
assert_count Workflow::Stage, Workflow::Stage.count * TENANTS.length
|
||||
assert_count Tag, Tag.count * TENANTS.length
|
||||
|
||||
assert_count Bucket, Bucket.count
|
||||
assert_count Access, Access.count
|
||||
assert_count Subscription, Subscription.count
|
||||
|
||||
assert_count Bubble, Bubble.count
|
||||
assert_count Assignment, Assignment.count
|
||||
assert_count Pop, Pop.count
|
||||
assert_count EventSummary, EventSummary.count
|
||||
assert_count Comment, Comment.count
|
||||
assert_count Reaction, Reaction.count
|
||||
assert_count Message, Message.count
|
||||
assert_count Tagging, Tagging.count
|
||||
assert_count Watch, Watch.count
|
||||
|
||||
# we're only copying the markdown records that are still accessible.
|
||||
assert_count ActionText::Markdown, Comment.count
|
||||
|
||||
assert_count Event, Event.count
|
||||
assert_count Notification, Notification.count
|
||||
|
||||
assert_count ActiveStorage::Attachment, ActiveStorage::Attachment.count * TENANTS.length
|
||||
assert_count ActiveStorage::Blob, ActiveStorage::Blob.count * TENANTS.length
|
||||
|
||||
raise "Filter count is off" unless creation_counter["Filter"] >= Filter.count
|
||||
end
|
||||
end
|
||||
|
||||
def update_action_text_urls
|
||||
update_counter["ActionText::Markdown"] = 0
|
||||
TENANTS.each do |tenant|
|
||||
tenanted_domain = sprintf(TENANTED_DOMAIN, tenant: tenant)
|
||||
|
||||
ApplicationRecord.with_tenant(tenant) do
|
||||
ActionText::Markdown.all.each do |markdown|
|
||||
content = markdown.content
|
||||
next unless content =~ %r(#{CURRENT_DOMAIN}/u/)
|
||||
|
||||
content.gsub!(%r(#{CURRENT_DOMAIN}/u/), "#{tenanted_domain}/u/")
|
||||
|
||||
markdown.update_column :content, content
|
||||
update_counter["ActionText::Markdown"] += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def hardlink_active_storage
|
||||
files = Dir.glob("storage/files/*/*/*").map { _1.split("/")[2..].join("/") }
|
||||
|
||||
TENANTS.each do |tenant|
|
||||
ApplicationRecord.with_tenant(tenant) do
|
||||
destdir = ActiveStorage::Blob.service.root
|
||||
|
||||
files.each do |file|
|
||||
FileUtils.mkdir_p File.join(destdir, File.dirname(file))
|
||||
FileUtils.ln File.join("storage/files", file), File.join(destdir, file)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def upsert_all(originals)
|
||||
originals = Array(originals)
|
||||
return if originals.empty?
|
||||
|
||||
klass = originals.first.class
|
||||
|
||||
result = klass.upsert_all(originals.collect(&:attributes))
|
||||
raise "Error upserting" unless result.rows.length == originals.length
|
||||
|
||||
creation_counter[klass.name] ||= 0
|
||||
creation_counter[klass.name] += originals.length
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def assert_count(klass, expected)
|
||||
actual = creation_counter[klass.name]
|
||||
unless actual == expected
|
||||
raise "#{klass} count is off: expected #{expected}, got #{actual}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
EnactTenanting.new.up
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user