-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrole_delete.py
executable file
·146 lines (106 loc) · 3.62 KB
/
role_delete.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#! /usr/bin/env python3
import os
import subprocess
import pandas as pd
from bs4 import BeautifulSoup
FILE_DIR = "_roleDelete"
ROLES_DIR = f"{FILE_DIR}/force-app/main/default/roles"
USER_UPSERT = f'sfdx force:data:bulk:upsert -s User -f users_update.csv -i Id -w 4'
SOQL_QUERY = (f'sfdx force:data:soql:query '
f'-q "SELECT Id, UserRoleId FROM User WHERE UserRoleId != null" '
f'-r csv > users.csv')
DESTRUCT_CMD = f'sfdx force:mdapi:deploy -d destructiveChanges -w 5 --verbose'
def cmd(command: str, sfdx: bool):
# run a given bash command
# if sfdx = true, run in sfdx project folder
if sfdx:
subprocess.run([f'cd {FILE_DIR} && {command}'], shell=True)
else:
subprocess.run([command], shell=True)
def unassign_roles():
# query users who have roles
cmd(SOQL_QUERY, True)
if os.stat("_roleDelete/users.csv").st_size == 1:
return
df = pd.read_csv('_roleDelete/users.csv')
df['UserRoleId'] = "#N/A"
df.to_csv('_roleDelete/users_update.csv', index=False)
cmd(USER_UPSERT, True)
def make_package(del_list: list):
# create folder structure
os.makedirs('_roleDelete/destructiveChanges', exist_ok=True)
# package.xml starter
package = """
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<version>54.0</version>
</Package>
"""
# with open(package) as f:
soup = BeautifulSoup(package, 'xml')
# create blank package.xml
with open("_roleDelete/destructiveChanges/package.xml", "w") as f:
f.write(str(soup))
# include metadata to delete
types_tag = soup.new_tag('types')
name_tag = soup.new_tag('name')
name_tag.string = 'Role'
for item in del_list:
member_tag = soup.new_tag('members')
member_tag.string = item
types_tag.append(member_tag)
types_tag.append(name_tag)
soup.Package.append(types_tag)
# save changes to destructiveChanges.xml
with open("_roleDelete/destructiveChanges/destructiveChanges.xml", "w") as f:
f.write(str(soup))
def parse_roles():
# build dict of roles and their parents
# get files in roles folder
role_files = os.listdir(ROLES_DIR)
roles = {}
for rf in role_files:
with open(f'{ROLES_DIR}/{rf}') as f:
soup = BeautifulSoup(f, 'xml')
parent_role = soup.find('parentRole')
role_name = rf.replace(".role-meta.xml", "")
if parent_role:
roles[role_name] = parent_role.string
else:
roles[role_name] = None
return roles
def delete_child_roles(roles: dict):
# find roles who are not parents
del_list = []
for rf in roles.keys():
if rf not in roles.values():
del_list.append(rf)
# dl = len(del_list)
# print(f'delete list ({dl}): ')
# print(del_list)
# print('\n')
make_package(del_list)
# delete roles from org
cmd(DESTRUCT_CMD, True)
# remove deleted from roles
for k in del_list:
roles.pop(k)
# rl = len(roles)
# print(f'next roles ({rl}): ')
# print(roles)
# print('\n')
# select org
ORG = input("Which org should this be run against?: ")
# create temporary sfdx project
cmd(f'sfdx force:project:create -n {FILE_DIR}', False)
cmd(f'sfdx config:set defaultusername={ORG}', True)
# unassign all users from roles
unassign_roles()
# pull role metadata
cmd('sfdx force:source:retrieve -m "Role"', True)
# map roles to their parents
roles = parse_roles()
# while roles exists, build a destructiveChanges.xml and delete one level of roles at a time
while roles:
delete_child_roles(roles)
# # clean up files
cmd(f'rm -rf {FILE_DIR}', False)