|
| 1 | +# Orphaned User Detection and Removal Feature |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +This feature automatically detects and optionally removes "orphaned users" from cost centers. Orphaned users are those who are assigned to a cost center but are no longer members of the corresponding GitHub team. |
| 6 | + |
| 7 | +## Why This Feature? |
| 8 | + |
| 9 | +When teams change over time (members leave, are removed, or switch teams), their cost center assignments can become stale. This feature keeps cost centers synchronized with actual team membership by: |
| 10 | + |
| 11 | +1. **Detecting** users in cost centers who are no longer in the corresponding team |
| 12 | +2. **Reporting** these orphaned users with warnings |
| 13 | +3. **Optionally removing** them based on configuration |
| 14 | + |
| 15 | +## How It Works |
| 16 | + |
| 17 | +### Detection Logic |
| 18 | + |
| 19 | +For each cost center being managed: |
| 20 | +1. Fetch the **expected members** from the GitHub team |
| 21 | +2. Fetch the **current members** from the cost center API |
| 22 | +3. Calculate orphaned users: `current_members - expected_members` |
| 23 | +4. Log warnings for any orphaned users found |
| 24 | + |
| 25 | +### Removal Logic (when enabled) |
| 26 | + |
| 27 | +If `teams.remove_orphaned_users: true`: |
| 28 | +- Automatically remove orphaned users from their cost centers |
| 29 | +- Log success/failure for each removal |
| 30 | +- Provide summary statistics |
| 31 | + |
| 32 | +## Configuration |
| 33 | + |
| 34 | +### Enable/Disable |
| 35 | + |
| 36 | +Add to `config/config.yaml`: |
| 37 | + |
| 38 | +```yaml |
| 39 | +teams: |
| 40 | + enabled: true |
| 41 | + scope: "enterprise" # or "organization" |
| 42 | + mode: "auto" |
| 43 | + |
| 44 | + # Orphaned user handling |
| 45 | + remove_orphaned_users: false # Set to true to enable automatic removal |
| 46 | +``` |
| 47 | +
|
| 48 | +### Default Behavior |
| 49 | +
|
| 50 | +- **Default**: `false` (disabled) |
| 51 | +- **When disabled**: Orphaned users are detected and logged but NOT removed |
| 52 | +- **When enabled**: Orphaned users are detected and automatically removed |
| 53 | + |
| 54 | +## Usage Examples |
| 55 | + |
| 56 | +### Plan Mode (Preview) |
| 57 | + |
| 58 | +```bash |
| 59 | +# See what would happen (with removal disabled) |
| 60 | +python main.py --teams-mode --assign-cost-centers --mode plan |
| 61 | +
|
| 62 | +# Output shows: |
| 63 | +# MODE=plan: Orphaned user detection is DISABLED |
| 64 | +# Users in cost centers but not in teams will remain assigned |
| 65 | +``` |
| 66 | + |
| 67 | +```bash |
| 68 | +# With removal enabled in config |
| 69 | +python main.py --teams-mode --assign-cost-centers --mode plan |
| 70 | +
|
| 71 | +# Output shows: |
| 72 | +# MODE=plan: Orphaned user detection is ENABLED |
| 73 | +# In apply mode, users in cost centers but not in teams will be removed |
| 74 | +``` |
| 75 | + |
| 76 | +### Apply Mode (Execution) |
| 77 | + |
| 78 | +```bash |
| 79 | +# Apply with removal disabled (default) |
| 80 | +python main.py --teams-mode --assign-cost-centers --mode apply --yes |
| 81 | +
|
| 82 | +# Adds users to cost centers but leaves orphaned users alone |
| 83 | +``` |
| 84 | + |
| 85 | +```bash |
| 86 | +# Apply with removal enabled |
| 87 | +python main.py --teams-mode --assign-cost-centers --mode apply --yes |
| 88 | +
|
| 89 | +# Output includes: |
| 90 | +# [INFO] Checking for orphaned users... |
| 91 | +# [WARNING] ⚠️ Found 3 orphaned users in cost center 'Team: Frontend' |
| 92 | +# [WARNING] ⚠️ alice is in cost center but not in team |
| 93 | +# [WARNING] ⚠️ bob is in cost center but not in team |
| 94 | +# [INFO] Removing 3 orphaned users from 'Team: Frontend'... |
| 95 | +# [INFO] ✅ Successfully removed 3 users from cost center |
| 96 | +# [INFO] 📊 Orphaned users summary: Found 3 orphaned users, successfully removed 3 |
| 97 | +``` |
| 98 | + |
| 99 | +## API Methods Added |
| 100 | + |
| 101 | +### 1. `get_cost_center_members(cost_center_id)` |
| 102 | + |
| 103 | +**Purpose**: Fetch current members of a cost center |
| 104 | + |
| 105 | +**Endpoint**: `GET /enterprises/{enterprise}/settings/billing/cost-centers/{cost_center_id}` |
| 106 | + |
| 107 | +**Returns**: List of usernames currently assigned to the cost center |
| 108 | + |
| 109 | +**Example**: |
| 110 | +```python |
| 111 | +members = github_manager.get_cost_center_members("abc-123-def") |
| 112 | +# Returns: ['alice', 'bob', 'charlie'] |
| 113 | +``` |
| 114 | + |
| 115 | +### 2. `remove_users_from_cost_center(cost_center_id, usernames)` |
| 116 | + |
| 117 | +**Purpose**: Remove multiple users from a cost center |
| 118 | + |
| 119 | +**Endpoint**: `DELETE /enterprises/{enterprise}/settings/billing/cost-centers/{cost_center_id}/resource` |
| 120 | + |
| 121 | +**Parameters**: |
| 122 | +- `cost_center_id`: ID of the cost center |
| 123 | +- `usernames`: List of usernames to remove |
| 124 | + |
| 125 | +**Returns**: Dict mapping username → success status (True/False) |
| 126 | + |
| 127 | +**Example**: |
| 128 | +```python |
| 129 | +results = github_manager.remove_users_from_cost_center( |
| 130 | + "abc-123-def", |
| 131 | + ["alice", "bob"] |
| 132 | +) |
| 133 | +# Returns: {'alice': True, 'bob': True} |
| 134 | +``` |
| 135 | + |
| 136 | +## Implementation Details |
| 137 | + |
| 138 | +### Files Modified |
| 139 | + |
| 140 | +1. **`src/github_api.py`** |
| 141 | + - Added `get_cost_center_members()` method |
| 142 | + - Added `remove_users_from_cost_center()` method |
| 143 | + |
| 144 | +2. **`src/config_manager.py`** |
| 145 | + - Added `teams_remove_orphaned_users` configuration property |
| 146 | + |
| 147 | +3. **`src/teams_cost_center_manager.py`** |
| 148 | + - Added `_remove_orphaned_users()` private method |
| 149 | + - Modified `sync_team_assignments()` to call orphaned user detection |
| 150 | + - Added orphaned user handling in apply mode |
| 151 | + |
| 152 | +4. **`config/config.yaml` & `config/config.example.yaml`** |
| 153 | + - Added `remove_orphaned_users` configuration option with documentation |
| 154 | + |
| 155 | +5. **`main.py`** |
| 156 | + - Display `remove_orphaned_users` status in configuration output |
| 157 | + |
| 158 | +### Logging |
| 159 | + |
| 160 | +The feature provides comprehensive logging: |
| 161 | + |
| 162 | +- **INFO**: General operation status |
| 163 | +- **WARNING**: Orphaned users detected |
| 164 | +- **ERROR**: API failures or removal failures |
| 165 | +- **DEBUG**: Detailed member counts |
| 166 | + |
| 167 | +### Error Handling |
| 168 | + |
| 169 | +- API failures are logged but don't stop execution |
| 170 | +- Individual user removal failures are tracked separately |
| 171 | +- Summary statistics show success/failure counts |
| 172 | + |
| 173 | +## Use Cases |
| 174 | + |
| 175 | +### Use Case 1: Team Restructuring |
| 176 | + |
| 177 | +**Scenario**: Your engineering team is split into "Frontend" and "Backend" teams. Some engineers move from one team to another. |
| 178 | + |
| 179 | +**Without this feature**: |
| 180 | +- Users remain in their old cost center |
| 181 | +- Cost reporting is inaccurate |
| 182 | +- Manual cleanup required |
| 183 | + |
| 184 | +**With this feature**: |
| 185 | +```yaml |
| 186 | +teams: |
| 187 | + remove_orphaned_users: true |
| 188 | +``` |
| 189 | +- Users automatically removed from old cost center |
| 190 | +- Added to new cost center |
| 191 | +- Cost reporting stays accurate |
| 192 | + |
| 193 | +### Use Case 2: Employee Departures |
| 194 | + |
| 195 | +**Scenario**: Team members leave the company and are removed from GitHub teams. |
| 196 | + |
| 197 | +**Without this feature**: |
| 198 | +- Departed users remain in cost centers |
| 199 | +- Inflated cost center counts |
| 200 | +- Security/audit concerns |
| 201 | + |
| 202 | +**With this feature**: |
| 203 | +- Departed users automatically removed from cost centers |
| 204 | +- Accurate headcount per cost center |
| 205 | +- Clean audit trail |
| 206 | + |
| 207 | +### Use Case 3: Temporary Team Assignments |
| 208 | + |
| 209 | +**Scenario**: Users temporarily join teams for projects, then return to their main team. |
| 210 | + |
| 211 | +**With this feature enabled**: |
| 212 | +- Users are automatically moved back when they leave the temporary team |
| 213 | +- No manual cleanup needed |
| 214 | + |
| 215 | +## Best Practices |
| 216 | + |
| 217 | +### 1. Test in Plan Mode First |
| 218 | + |
| 219 | +Always run with `--mode plan` to see what changes would be made: |
| 220 | + |
| 221 | +```bash |
| 222 | +python main.py --teams-mode --assign-cost-centers --mode plan |
| 223 | +``` |
| 224 | + |
| 225 | +### 2. Enable Gradually |
| 226 | + |
| 227 | +Start with `remove_orphaned_users: false` to see how many orphaned users exist: |
| 228 | + |
| 229 | +```bash |
| 230 | +# Step 1: Run once to see orphaned users (they'll be logged as warnings) |
| 231 | +python main.py --teams-mode --assign-cost-centers --mode apply --yes |
| 232 | +
|
| 233 | +# Step 2: Review logs for orphaned users |
| 234 | +# Step 3: If comfortable, enable removal |
| 235 | +# Step 4: Run again with removal enabled |
| 236 | +``` |
| 237 | + |
| 238 | +### 3. Regular Sync Schedule |
| 239 | + |
| 240 | +Run teams sync regularly to keep cost centers up-to-date: |
| 241 | + |
| 242 | +```bash |
| 243 | +# Daily cron job |
| 244 | +0 2 * * * cd /path/to/repo && python main.py --teams-mode --assign-cost-centers --mode apply --yes |
| 245 | +``` |
| 246 | + |
| 247 | +### 4. Monitor Logs |
| 248 | + |
| 249 | +Review execution logs for: |
| 250 | +- Number of orphaned users found |
| 251 | +- Removal success rates |
| 252 | +- Any API errors |
| 253 | + |
| 254 | +### 5. Consider Impact |
| 255 | + |
| 256 | +Before enabling `remove_orphaned_users: true`, consider: |
| 257 | +- Do you have users manually added to cost centers outside of team membership? |
| 258 | +- Are there legitimate reasons for users to be in cost centers but not teams? |
| 259 | +- Do you have adequate logging/monitoring? |
| 260 | + |
| 261 | +## Limitations |
| 262 | + |
| 263 | +1. **Plan Mode**: Cannot show specific orphaned users in plan mode because cost centers may not exist yet. Orphaned user detection only runs in apply mode after cost centers are created/resolved. |
| 264 | + |
| 265 | +2. **Manual Assignments**: If you manually add users to cost centers outside of this tool, they will be detected as orphaned and removed if they're not in the corresponding team. |
| 266 | + |
| 267 | +3. **API Rate Limits**: Checking cost center membership adds API calls. Large numbers of cost centers may hit rate limits. |
| 268 | + |
| 269 | +4. **Single Cost Center**: Remember, users can only belong to ONE cost center at a time (GitHub API constraint). |
| 270 | + |
| 271 | +## Troubleshooting |
| 272 | + |
| 273 | +### Orphaned users not being removed |
| 274 | + |
| 275 | +**Check**: |
| 276 | +1. Is `remove_orphaned_users: true` in config? |
| 277 | +2. Running in `--mode apply` (not plan)? |
| 278 | +3. Check logs for API errors |
| 279 | + |
| 280 | +### False positives (users incorrectly identified as orphaned) |
| 281 | + |
| 282 | +**Cause**: User is not in the team being synced |
| 283 | + |
| 284 | +**Solutions**: |
| 285 | +- Verify team membership in GitHub |
| 286 | +- Check that correct teams are configured |
| 287 | +- Review team mappings in manual mode |
| 288 | + |
| 289 | +### API 404 errors when checking cost center members |
| 290 | + |
| 291 | +**Cause**: Cost center doesn't exist yet (plan mode issue) |
| 292 | + |
| 293 | +**Solution**: This is expected in plan mode. Orphaned detection only runs in apply mode. |
| 294 | + |
| 295 | +## Future Enhancements |
| 296 | + |
| 297 | +Potential improvements: |
| 298 | +1. **Dry-run for orphaned users**: Show what would be removed without actually removing |
| 299 | +2. **Whitelist**: Configure specific users to never be removed |
| 300 | +3. **Notification**: Send alerts when orphaned users are found/removed |
| 301 | +4. **Audit log export**: Export orphaned user reports to CSV |
| 302 | + |
| 303 | +## Summary |
| 304 | + |
| 305 | +The orphaned user detection and removal feature helps maintain clean, accurate cost center assignments by: |
| 306 | + |
| 307 | +- ✅ **Detecting** users who shouldn't be in cost centers |
| 308 | +- ✅ **Reporting** discrepancies with clear warnings |
| 309 | +- ✅ **Removing** orphaned users automatically (when enabled) |
| 310 | +- ✅ **Working** with both organization and enterprise team scopes |
| 311 | +- ✅ **Configurable** - enable/disable as needed |
| 312 | +- ✅ **Safe** - disabled by default, clear logging, error handling |
| 313 | + |
| 314 | +This keeps your cost center data synchronized with actual team membership over time with minimal manual intervention. |
0 commit comments