Move domain removal into the plugins.
authorMichael Orlitzky <michael@orlitzky.com>
Sun, 6 Oct 2013 19:35:18 +0000 (15:35 -0400)
committerMichael Orlitzky <michael@orlitzky.com>
Sun, 6 Oct 2013 19:35:18 +0000 (15:35 -0400)
17 files changed:
doc/TODO
lib/common/agendav_plugin.rb
lib/common/davical_plugin.rb
lib/common/dovecot_mailstore_plugin.rb
lib/common/mailstore.rb [deleted file]
lib/common/plugin.rb
lib/common/postfixadmin_db_plugin.rb
lib/common/roundcube_db_plugin.rb
lib/common/runner.rb
lib/prune/plugins/dovecot_mailstore.rb
lib/rm/plugins/agendav.rb
lib/rm/plugins/davical.rb
lib/rm/plugins/dovecot_mailstore.rb
lib/rm/plugins/postfixadmin_db.rb
lib/rm/plugins/roundcube_db.rb
lib/rm/rm_plugin.rb
lib/rm/rm_runner.rb

index 8d773983a2cb247cdcb328638538a53fc9a23de5..6aa317b72c273136cab43aa7c7b39b9b2eeeb2cd 100644 (file)
--- a/doc/TODO
+++ b/doc/TODO
@@ -1,16 +1,3 @@
-* When we delete a domain, do we delete all of the accounts, too? Or
-  just the domain? We can do this in either the runner or the plugin.
-
-  At the moment, it's mixed. The RmRunner does it manually, but e.g.
-  the PostfixadminDbRm plugin also deletes the mailboxes when the
-  domain is deleted.
-
-  One doesn't seem any better than the other, but maybe if you
-  consider that some of the plugins have no-ops for
-  delete_domain(). Those could be made to actually do something
-  (i.e. delete all matching account names). Then we could get rid of
-  the get_domain_accounts() or-whatever-it's-called methods.
-
 * PostfixadminDb can be made a plugin -- prune just won't work without
   it.
 
index fad869f95235964884d58635a465d670cde7b455..cb445bed817d853c3b91c3e3c75c2735330614cc 100644 (file)
@@ -20,7 +20,7 @@ module AgendavPlugin
 
   def describe_domain(domain)
     # AgenDAV doesn't have a concept of domains.
-    return 'N/A'
+    return domain
   end
 
 
@@ -36,11 +36,11 @@ module AgendavPlugin
   protected;
 
   def user_exists(account)
-    ad_users = get_agendav_usernames()
+    ad_users = list_users()
     return ad_users.include?(account)
   end
 
-  def get_agendav_usernames()
+  def list_users()
     usernames = []
 
     # Just assume PostgreSQL for now.
index cdde3d1e068301fbd13a3eb05783fed7f878d8bc..1cc06ce0dd397d7e4e26443aa68871b82511e8df 100644 (file)
@@ -19,7 +19,7 @@ module DavicalPlugin
 
   def describe_domain(domain)
     # DAViCal doesn't have a concept of domains.
-    return 'N/A'
+    return domain
   end
 
 
@@ -70,7 +70,7 @@ module DavicalPlugin
   end
 
 
-  def get_davical_usernames()
+  def list_users()
     usernames = []
 
     begin
index d0f8dd11f7f76c10b8a53e7e83fa87989fe0b7b2..90aa02c8fde2b582c93afda07902a36ea17c5722 100644 (file)
@@ -1,4 +1,5 @@
 require 'common/plugin'
+require 'common/filesystem'
 
 module DovecotMailstorePlugin
   # Code that all DovecotMailstore plugins (Prune, Rm, Mv...) will
@@ -49,7 +50,8 @@ module DovecotMailstorePlugin
     # Return the filesystem path of this account's mailbox.
     # Only works if the account exists!
     if not account.include?('@')
-      raise InvalidAccountError.new("#{account}: Accounts must contain an '@' symbol.")
+      msg = "#{account}: Accounts must contain an '@' symbol."
+      raise InvalidAccountError.new(msg)
     end
 
     account_parts = account.split('@')
@@ -71,4 +73,38 @@ module DovecotMailstorePlugin
     end
   end
 
+
+  def list_domains()
+    return Filesystem.get_subdirs(@domain_root)
+  end
+
+  def list_domains_users(domains)
+    accounts = []
+
+    domains.each do |domain|
+      begin
+        # Throws a NonexistentDomainError if the domain's path
+        # doesn't exist on the filesystem. In this case, we want
+        # to report zero accounts.
+        domain_path = get_domain_path(domain)
+        usernames = Filesystem.get_subdirs(domain_path)
+
+        usernames.each do |username|
+          accounts << "#{username}@#{domain}"
+        end
+      rescue NonexistentDomainError
+        # Party hard.
+      end
+    end
+
+    return accounts
+  end
+
+
+  def list_users()
+    domains = list_domains()
+    users = list_domains_users(domains)
+    return users
+  end
+
 end
diff --git a/lib/common/mailstore.rb b/lib/common/mailstore.rb
deleted file mode 100644 (file)
index 19772d3..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-class Mailstore
-
-  attr_accessor :domain_root
-
-  def def get_domains_from_filesystem
-    raise NotImplementedError
-  end
-
-  def get_accounts_from_filesystem(domains)
-    raise NotImplementedError
-  end
-
-  def get_domain_path(domain)
-    raise NotImplementedError
-  end
-
-  def get_account_path(account)
-    raise NotImplementedError
-  end
-end
index 0687989fe03169e3aa2e4d9c256f30bf9cbe8b67..633c8fd26d0c2f4bd467ff3d33aa3a48cc1a93fc 100644 (file)
@@ -28,4 +28,30 @@ module Plugin
     raise NotImplementedError
   end
 
+  def list_users()
+    # Return a list of all users managed by this plugin.
+    raise NotImplementedError
+  end
+
+  def list_domains_users(domains)
+    # Get all usernames belonging to the given domains. If a username
+    # ends in @example.com, it belongs to the domain example.com
+    #
+    # This uses a naive loop, but relies only on the existence of a
+    # list_users() method which is guaranteed above. More efficient
+    # implementations can usually be made within the plugin.
+    domains_users = []
+
+    usernames = list_users();
+    domains.each do |d|
+      matches = usernames.select do |username|
+        username =~ /@#{d}$/
+      end
+
+      domains_users += matches
+    end
+
+    return domains_users
+  end
+
 end
index 936672e27a331d60f1c05bead0fb14a96efb72e1..da40879ed07c03214cab62d2984d5a5da86b7cf6 100644 (file)
@@ -31,7 +31,7 @@ module PostfixadminDbPlugin
   end
 
 
-  def get_domains_from_db()
+  def list_domains()
     domains = []
 
     # Just assume PostgreSQL for now.
@@ -59,7 +59,7 @@ module PostfixadminDbPlugin
   end
 
 
-  def get_accounts_from_db()
+  def list_users()
     accounts = []
 
     # Just assume PostgreSQL for now.
@@ -88,4 +88,32 @@ module PostfixadminDbPlugin
   end
 
 
+  def list_domains_users(domains)
+    usernames = []
+
+    # Just assume PostgreSQL for now.
+    begin
+      connection = PGconn.connect(@db_host,
+                                  @db_port,
+                                  @db_opts,
+                                  @db_tty,
+                                  @db_name,
+                                  @db_user,
+                                  @db_pass)
+
+      sql_query  = 'SELECT username FROM mailbox WHERE domain IN $1;'
+
+      connection.query(sql_query, [domains]) do |result|
+        usernames = result.field_values('username')
+      end
+
+      connection.close()
+    rescue PGError => e
+      # Pretend like we're database-agnostic in case we ever are.
+      raise DatabaseError.new(e)
+    end
+
+    return usernames
+  end
+
 end
index e4e9e7fb73471d4115cd6694527a43bb139cc293..df5413fdb0cc7a797dc2fd1f7070f3809b7f2abc 100644 (file)
@@ -19,7 +19,7 @@ module RoundcubeDbPlugin
 
   def describe_domain(domain)
     # Roundcube doesn't have a concept of domains.
-    return 'N/A'
+    return domain
   end
 
   def describe_account(account)
@@ -66,8 +66,8 @@ module RoundcubeDbPlugin
   end
 
 
-  # Uses in both prune/rm.
-  def get_roundcube_usernames()
+  # Used in both prune/rm.
+  def list_users()
     usernames = []
 
     # Just assume PostgreSQL for now.
index b9bb8c9323af87df7d097f2879f481f7ba7cd039..32f2b3ecf8bb0354408fb7fbe46596155e2baf3a 100644 (file)
@@ -1,4 +1,5 @@
 module Runner
+
   def run(plugin, targets)
     raise NotImplementedError
   end
index f504d9c5560159d55e5ccbb900fbdd33fc9d76a8..eddd822f0bfff4c04fbf2d9bcf2da51aba17d23f 100644 (file)
@@ -1,5 +1,3 @@
-require 'common/filesystem'
-require 'common/mailstore'
 require 'common/dovecot_mailstore_plugin'
 require 'prune/prune_plugin'
 require 'rm/plugins/dovecot_mailstore'
@@ -12,7 +10,7 @@ class DovecotMailstorePrune < DovecotMailstoreRm
 
   def get_leftover_domains(db_domains)
     # Get the list of domains according to the filesystem.
-    fs_domains = self.get_domains_from_filesystem()
+    fs_domains = self.list_domains()
 
     # Return the list of domains on the filesystem that aren't in the DB.
     return (fs_domains - db_domains)
@@ -20,8 +18,8 @@ class DovecotMailstorePrune < DovecotMailstoreRm
 
   def get_leftover_accounts(db_accounts)
     # Get the list of accounts according to the filesystem.
-    fs_domains = self.get_domains_from_filesystem()
-    fs_accounts = self.get_accounts_from_filesystem(fs_domains)
+    fs_domains = self.list_domains()
+    fs_accounts = self.list_domains_users(fs_domains)
 
     # And return the accounts on the filesystem that aren't in the DB.
     return (fs_accounts - db_accounts)
index 6aa6d9191216d59f635238836bfe7e05ad68d49e..1366bd6db69a15452a0b777dd3b131e65b90d54a 100644 (file)
@@ -9,10 +9,6 @@ class AgendavRm
   include RmPlugin
 
 
-  def delete_domain(domain)
-    # AgenDAV doesn't have a concept of domains.
-  end
-
   def delete_account(account)
     # Delete the given username and any records in other tables
     # belonging to it.
@@ -42,14 +38,4 @@ class AgendavRm
 
   end
 
-
-  def get_domain_usernames(domain)
-    usernames = get_agendav_usernames();
-    matches = usernames.select do |username|
-      username =~ /@#{domain}$/
-    end
-
-    return matches
-  end
-
 end
index ce5828a627b3cf72a29069815feab17660a07267..02e889466849fb7c67aad15e0812afce1e23e33e 100644 (file)
@@ -12,11 +12,6 @@ class DavicalRm
   include RmPlugin
 
 
-  def delete_domain(domain)
-    # DAViCal doesn't have a concept of domains.
-  end
-
-
   def delete_account(account)
     # Delete the given username. DAViCal uses foreign keys properly
     # and only supports postgres, so we let the ON DELETE CASCADE
@@ -46,14 +41,4 @@ class DavicalRm
   end
 
 
-
-  def get_domain_usernames(domain)
-    usernames = get_davical_usernames();
-    matches = usernames.select do |username|
-      username =~ /@#{domain}$/
-    end
-
-    return matches
-  end
-
 end
index 8ab507d674c3c77739d854ad9d95439b702531ee..72c80c83537559bef358868dc9fc57c0dc905890 100644 (file)
@@ -1,12 +1,10 @@
 # Needed for rm_r.
 require 'fileutils'
 
-require 'common/filesystem'
-require 'common/mailstore'
 require 'common/dovecot_mailstore_plugin'
 require 'rm/rm_plugin'
 
-class DovecotMailstoreRm < Mailstore
+class DovecotMailstoreRm
 
   include DovecotMailstorePlugin
   include RmPlugin
@@ -22,54 +20,4 @@ class DovecotMailstoreRm < Mailstore
     FileUtils.rm_r(account_path)
   end
 
-  def get_leftover_domains(db_domains)
-    # Get the list of domains according to the filesystem.
-    fs_domains = self.get_domains_from_filesystem()
-
-    # Return the list of domains on the filesystem that aren't in the DB.
-    return (fs_domains - db_domains)
-  end
-
-  def get_leftover_accounts(db_accounts)
-    # Get the list of accounts according to the filesystem.
-    fs_domains = self.get_domains_from_filesystem()
-    fs_accounts = self.get_accounts_from_filesystem(fs_domains)
-
-    # And return the accounts on the filesystem that aren't in the DB.
-    return (fs_accounts - db_accounts)
-  end
-
-
-  def get_domain_usernames(domain)
-    return get_accounts_from_filesystem([domain])
-  end
-
-  protected;
-
-  def get_domains_from_filesystem()
-    return Filesystem.get_subdirs(@domain_root)
-  end
-
-  def get_accounts_from_filesystem(domains)
-    accounts = []
-
-    domains.each do |domain|
-      begin
-        # Throws a NonexistentDomainError if the domain's path
-        # doesn't exist on the filesystem. In this case, we want
-        # to report zero accounts.
-        domain_path = get_domain_path(domain)
-        usernames = Filesystem.get_subdirs(domain_path)
-
-        usernames.each do |username|
-          accounts << "#{username}@#{domain}"
-        end
-      rescue NonexistentDomainError
-        # Party hard.
-      end
-    end
-
-    return accounts
-  end
-
 end
index cd59928007448a3bd77c400cecad60eb717f6365..1928202123e1b0316157f67552513ef796b325f4 100644 (file)
@@ -51,12 +51,15 @@ class PostfixadminDbRm
 
 
   def delete_domain(domain)
+    raise NonexistentDomainError.new(domain) if not domain_exists(domain)
+
     sql_queries = ['DELETE FROM domain_admins WHERE domain = $1;']
     sql_queries << 'DELETE FROM alias WHERE domain = $1;'
     sql_queries << 'DELETE FROM mailbox WHERE domain = $1;'
     sql_queries << 'DELETE FROM alias_domain WHERE alias_domain = $1;'
     sql_queries << 'DELETE FROM log WHERE domain = $1;'
     sql_queries << 'DELETE FROM vacation WHERE domain = $1;'
+    sql_queries << 'DELETE FROM domain WHERE domain = $1;'
 
     begin
       connection = PGconn.connect(@db_host,
@@ -81,9 +84,10 @@ class PostfixadminDbRm
   end
 
 
+  protected;
 
-  def get_domain_usernames(domain)
-    usernames = []
+  def domain_exists(domain)
+    count = 0
 
     # Just assume PostgreSQL for now.
     begin
@@ -95,19 +99,23 @@ class PostfixadminDbRm
                                   @db_user,
                                   @db_pass)
 
-      sql_query  = 'SELECT username FROM mailbox WHERE domain = $1;'
-
+      sql_query = 'SELECT COUNT(domain) as count FROM domain WHERE domain = $1;'
       connection.query(sql_query, [domain]) do |result|
-        usernames = result.field_values('username')
+        return false if result.ntuples() < 1
+        begin
+          count = result.getvalue(0,0).to_i()
+          return false if count.nil?
+        rescue StandardError
+          return false
+        end
       end
-
       connection.close()
     rescue PGError => e
-      # Pretend like we're database-agnostic in case we ever are.
+      # But pretend like we're database-agnostic in case we ever are.
       raise DatabaseError.new(e)
     end
 
-    return usernames
+    return (count > 0)
   end
 
 end
index 252ce32688cbb82faa81f42b5522ac8abf73cc7b..5687ce436ea76cca659f66cd00b6825499ae43b1 100644 (file)
@@ -8,10 +8,6 @@ class RoundcubeDbRm
   include RoundcubeDbPlugin
   include RmPlugin
 
-  def delete_domain(domain)
-    # Roundcube doesn't have a concept of domains.
-  end
-
   def delete_account(account)
     # Delete the given username and any records in other tables
     # belonging to it.
@@ -50,15 +46,4 @@ class RoundcubeDbRm
 
   end
 
-
-
-  def get_domain_usernames(domain)
-    usernames = get_roundcube_usernames();
-    matches = usernames.select do |username|
-      username =~ /@#{domain}$/
-    end
-
-    return matches
-  end
-
 end
index cad493d9f85c95ae8a3c79bee121e59c508a8efb..49c340f7988f1fc75d1243de6d437795be0ac365 100644 (file)
@@ -16,8 +16,16 @@ module RmPlugin
   end
 
   def delete_domain(domain)
-    # Delete the given domain.
-    raise NotImplementedError
+    # Delete the given domain. Some plugins don't have a concept of
+    # domains, so just delete all users with a username that looks
+    # like it's in the given domain.
+    usernames = list_domains_users([domain])
+
+    raise NonexistentDomainError.new(domain) if usernames.empty?
+
+    usernames.each do |u|
+      delete_account(u)
+    end
   end
 
   def delete_account(account)
@@ -25,11 +33,4 @@ module RmPlugin
     raise NotImplementedError
   end
 
-  def get_domain_usernames(domain)
-    # Get a list of usernames for a given domain,
-    # needed to delete accounts before removing
-    # a domain.
-    raise NotImplementedError
-  end
-
 end
index 0392d1192acbac86218c0813218e37b5b5c56929..114aabc355ad1c215f0b95e3cf1f20a2a626ea4d 100644 (file)
@@ -20,13 +20,6 @@ class RmRunner
         end
       else
         begin
-          # We must delete all accounts belonging to the domain first.
-          # This prevents us from leaving behind accounts. Not a
-          # problem with the mailstore, since we'll delete the domain
-          # directory anyway, but it is for the database plugins.
-          usernames = plugin.get_domain_usernames(target)
-          usernames.each { |u| run(plugin, u) }
-
           domain_description = plugin.describe_domain(target)
           plugin.delete_domain(target)
           report(plugin, "Removed domain: #{target} (#{domain_description})")