Allow to exclude group of accounts from the auto-disable script

Introduce the ability to add a group of accounts in the exclusion list
of the track-and-disable-inactive-users utility.

This allows to automatically exclude the administrators of Gerrit from
being disabled accidentally and also other users that are for sure
active even though they have not connected recently.

Change-Id: I9b75d75d5be076125ef2669d4e14573a6c4735d3
diff --git a/admin/track-and-disable-inactive-users-1.3.groovy b/admin/track-and-disable-inactive-users-1.3.groovy
index a591aef..0cc0cb3 100644
--- a/admin/track-and-disable-inactive-users-1.3.groovy
+++ b/admin/track-and-disable-inactive-users-1.3.groovy
@@ -19,6 +19,7 @@
 import com.google.gerrit.extensions.annotations.*
 import com.google.gerrit.extensions.events.*
 import com.google.gerrit.extensions.registration.*
+import com.google.gerrit.server.query.group.*
 import com.google.gerrit.metrics.*
 import com.google.gerrit.lifecycle.*
 import com.google.gerrit.server.*
@@ -104,19 +105,41 @@
     return false
   }
 }
+
+@Singleton
 class AutoDisableInactiveUsersConfig {
+  static final FluentLogger logger = FluentLogger.forEnclosingClass()
+
   final Set<Account.Id> ignoreAccountIds
+  final Set<AccountGroup.UUID> ignoreGroupIds
 
   private final PluginConfig config
 
   @Inject
   AutoDisableInactiveUsersConfig(
       PluginConfigFactory configFactory,
+      GroupCache groupCache,
+      InternalGroupBackend internalGroupBackend,
+      IdentifiedUser.GenericFactory userFactory,
+      Accounts accounts,
       @PluginName String pluginName
   ) {
     config = configFactory.getFromGerritConfig(pluginName)
 
     ignoreAccountIds = ignoreAccountIdsFromConfig("ignoreAccountId")
+    ignoreGroupIds = ignoreGroupIdsFromConfig("ignoreGroup", groupCache)
+
+    logger.atInfo().log("Accounts ids ignored for inactivity: %s", ignoreAccountIds)
+    logger.atInfo().log("Group ids ignored for inactivity: %s", ignoreGroupIds)
+
+    def impliedAccountIds = accounts.all().findAll {
+      internalGroupBackend.membershipsOf(userFactory.create(it.account().id())).containsAnyOf(ignoreGroupIds)
+    }.collect { it.account.id() }
+
+    logger.atInfo().log("Implied accounts ids ignored for inactivity: %s", impliedAccountIds)
+    ignoreAccountIds += impliedAccountIds
+
+    logger.atInfo().log("Full list of accounts ids ignored for inactivity: %s", ignoreAccountIds)
   }
 
   private Set<Account.Id> ignoreAccountIdsFromConfig(String name) {
@@ -127,6 +150,22 @@
         .map { it.get() }
         .collect(Collectors.toSet())
   }
+
+  private Set<AccountGroup.UUID> ignoreGroupIdsFromConfig(String name, GroupCache groupCache) {
+    def groups = config.getStringList(name)
+    def groupNames = groups.collect { AccountGroup.nameKey(it)}
+    groupNames
+        .collect { groupName ->
+          def group = groupCache.get(groupName)
+          if (group.empty) {
+            logger.atWarning().log("Group %s not found", groupName)
+          }
+          group
+        }
+        .findAll { it.present }
+        .collect { it.get().groupUUID }
+        .toSet()
+  }
 }
 
 class AutoDisableInactiveUsersEvictionListener implements CacheRemovalListener<Integer, Long> {
@@ -201,6 +240,9 @@
   @Named(TrackActiveUsersCache.NAME)
   Cache<Integer, Long> trackActiveUsersCache
 
+  @Inject
+  AutoDisableInactiveUsersConfig autoDisableConfig
+
   @Override
   void start() {
     def currentMinutes = MILLISECONDS.toMinutes(System.currentTimeMillis())
diff --git a/admin/track-and-disable-inactive-users.md b/admin/track-and-disable-inactive-users.md
index e622c41..bcfdf66 100644
--- a/admin/track-and-disable-inactive-users.md
+++ b/admin/track-and-disable-inactive-users.md
@@ -30,6 +30,19 @@
    ignoreAccountId = 1000002
    ```
 
+```plugin.@PLUGIN@.ignoreGroup```
+:  Specify one group that includes directly or indirectly all the accounts that
+   should not be auto disabled.
+   May be specified more than once to specify multiple groups, for example:
+
+   ```
+   ignoreGroup = Active Developers
+   ignoreGroup = Administrators
+   ```
+
+   > **NOTE**: The `Service Users` group is always added to the list of groups of
+   > accounts to not disable.
+
 ```cache."@PLUGIN@.users_cache".maxAge```
 :  Maximum allowed inactivity time for user.
    Value should use common time unit suffixes to express their setting: