1
+ '''
2
+ Created: Nov 23, 2023
3
+ Author: mkumykov
4
+
5
+ Copyright (c) 2023 - Synopsys, Inc.
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
18
+
19
+ This script will remove project hierarchies from a Black Duck server.
20
+
21
+ usage: python3 recursive_delete_project.py [-h] -u BASE_URL -t TOKEN_FILE [-nv] [-p PROJECT_NAME] [-pv VERSION_NAME] [-pl PROJECT_LIST_FILE]
22
+
23
+ options:
24
+ -h, --help show this help message and exit
25
+ -u BASE_URL, --base-url BASE_URL
26
+ Hub server URL e.g. https://your.blackduck.url
27
+ -t TOKEN_FILE, --token-file TOKEN_FILE
28
+ File containing access token
29
+ -nv, --no-verify Disable TLS certificate verification
30
+ -p PROJECT_NAME, --project-name PROJECT_NAME
31
+ Project Name
32
+ -pv VERSION_NAME, --version-name VERSION_NAME
33
+ Version Name
34
+ -pl PROJECT_LIST_FILE, --project-list-file PROJECT_LIST_FILE
35
+ File containing project name list
36
+
37
+ Options -pl and -p can not be used at the same time
38
+
39
+ Examples:
40
+
41
+ remove project with all sub-projects
42
+
43
+ python3 recursive_delete_project.py -u BASE_URL -t TOKEN_FILE -nv -p PROJECT_NAME
44
+
45
+ remove project version with all sub-projects, keep sub-projects still in use intact
46
+
47
+ python3 recursive_delete_project.py -u BASE_URL -t TOKEN_FILE -nv -p PROJECT_NAME -pv VERSION_NAME
48
+
49
+ remove projects listed in a file
50
+
51
+ python3 recursive_delete_project.py -u BASE_URL -t TOKEN_FILE -nv -pl PROJECT_LIST_FILE
52
+
53
+ '''
54
+
55
+ import argparse
56
+ import json
57
+ import logging
58
+ import sys
59
+ import arrow
60
+
61
+ from blackduck import Client
62
+ from pprint import pprint ,pformat
63
+
64
+ logging .basicConfig (format = '%(asctime)s:%(levelname)s:%(message)s' , stream = sys .stderr , level = logging .DEBUG )
65
+ logging .getLogger ("requests" ).setLevel (logging .INFO )
66
+ logging .getLogger ("urllib3" ).setLevel (logging .INFO )
67
+ logging .getLogger ("blackduck" ).setLevel (logging .INFO )
68
+
69
+ strict = False
70
+
71
+ def remove_project_structure (project_name ):
72
+ project = find_project_by_name (project_name )
73
+ if not project :
74
+ logging .info (f"Project { project_name } does not exist." )
75
+ return
76
+ versions = bd .get_resource ('versions' , project )
77
+ for version in versions :
78
+ remove_project_version_structure (project_name , version ['versionName' ])
79
+
80
+ def remove_project_version_structure (project_name , version_name ):
81
+ project = find_project_by_name (project_name )
82
+ if not project :
83
+ logging .info (f"Project { project_name } does not exist." )
84
+ return
85
+ num_versions = bd .get_resource ('versions' , project , items = False )['totalCount' ]
86
+ version = find_project_version_by_name (project ,version_name )
87
+ if not version :
88
+ logging .info (f"Project { project_name } with version { version_name } does not exist." )
89
+ return
90
+ components = [
91
+ c for c in bd .get_resource ('components' ,version ) if c ['componentType' ] == "SUB_PROJECT"
92
+ ]
93
+ logging .info (f"Project { project_name } :{ version_name } has { len (components )} subprojects" )
94
+ for component in components :
95
+ component_name = component ['componentName' ]
96
+ component_version_name = component ['componentVersionName' ]
97
+ logging .info (f"Removing subproject { component_name } from { project_name } :{ version_name } " )
98
+ component_url = component ['_meta' ]['href' ]
99
+ response = bd .session .delete (component_url )
100
+ logging .info (f"Operation completed with { response } " )
101
+ remove_project_version_structure (component_name , component_version_name )
102
+ logging .info (f"Removing { project_name } :{ version_name } " )
103
+ if num_versions > 1 :
104
+ response = bd .session .delete (version ['_meta' ]['href' ])
105
+ else :
106
+ response = bd .session .delete (project ['_meta' ]['href' ])
107
+ logging .info (f"Operation completed with { response } " )
108
+
109
+ def remove_codelocations_recursively (version ):
110
+ components = bd .get_resource ('components' , version )
111
+ subprojects = [x for x in components if x ['componentType' ] == 'SUB_PROJECT' ]
112
+ logging .info (f"Found { len (subprojects )} subprojects" )
113
+ unmap_all_codelocations (version )
114
+ for subproject in subprojects :
115
+ subproject_name = subproject ['componentName' ]
116
+ subproject_version_name = subproject ['componentVersionName' ]
117
+ project = find_project_by_name (subproject_name )
118
+ if not project :
119
+ logging .info (f"Project { subproject_name } does not exist." )
120
+ return
121
+ subproject_version = find_project_version_by_name (project , subproject_version_name )
122
+ if not subproject_version :
123
+ logging .info (f"Project { subproject_name } with version { subversion_name } does not exist." )
124
+ return
125
+ remove_codelocations_recursively (subproject_version )
126
+
127
+ def unmap_all_codelocations (version ):
128
+ codelocations = bd .get_resource ('codelocations' ,version )
129
+ for codelocation in codelocations :
130
+ logging .info (f"Unmapping codelocation { codelocation ['name' ]} " )
131
+ codelocation ['mappedProjectVersion' ] = ""
132
+ response = bd .session .put (codelocation ['_meta' ]['href' ], json = codelocation )
133
+ pprint (response )
134
+
135
+ def find_project_by_name (project_name ):
136
+ params = {
137
+ 'q' : [f"name:{ project_name } " ]
138
+ }
139
+ projects = [p for p in bd .get_resource ('projects' , params = params ) if p ['name' ] == project_name ]
140
+ if len (projects ) == 1 :
141
+ return projects [0 ]
142
+ else :
143
+ return None
144
+
145
+ def find_project_version_by_name (project , version_name ):
146
+ params = {
147
+ 'q' : [f"versionName:{ version_name } " ]
148
+ }
149
+ versions = [v for v in bd .get_resource ('versions' , project , params = params ) if v ['versionName' ] == version_name ]
150
+ if len (versions ) == 1 :
151
+ return versions [0 ]
152
+ else :
153
+ return None
154
+
155
+
156
+
157
+ def parse_command_args ():
158
+
159
+ parser = argparse .ArgumentParser ("python3 recursive_delete_project.py" )
160
+ parser .add_argument ("-u" , "--base-url" , required = True , help = "Hub server URL e.g. https://your.blackduck.url" )
161
+ parser .add_argument ("-t" , "--token-file" , required = True , help = "File containing access token" )
162
+ parser .add_argument ("-nv" , "--no-verify" , action = 'store_false' , help = "Disable TLS certificate verification" )
163
+ group = parser .add_mutually_exclusive_group ()
164
+ group .add_argument ("-p" , "--project-name" , required = False , help = "Project Name" )
165
+ parser .add_argument ("-pv" , "--version-name" , required = False , help = "Version Name" )
166
+ group .add_argument ("-pl" , "--project-list-file" , required = False , help = "File containing project name list" )
167
+ return parser .parse_args ()
168
+
169
+ def main ():
170
+ args = parse_command_args ()
171
+ with open (args .token_file , 'r' ) as tf :
172
+ access_token = tf .readline ().strip ()
173
+ global bd
174
+ global scan_params
175
+ scan_params = []
176
+ bd = Client (base_url = args .base_url , token = access_token , verify = args .no_verify , timeout = 60.0 , retries = 4 )
177
+ logging .info (f"{ args } " )
178
+
179
+ if not (args .project_list_file or args .project_name ):
180
+ logging .error ("Project Name or a File containing Project Names should be specified" )
181
+ return
182
+
183
+ if args .project_list_file :
184
+ with open (args .project_list_file ) as file :
185
+ lines = [line .rstrip () for line in file ]
186
+ for line in lines :
187
+ remove_project_structure (line )
188
+ elif args .version_name :
189
+ remove_project_version_structure (args .project_name , args .version_name )
190
+ else :
191
+ remove_project_structure (args .project_name )
192
+
193
+ if __name__ == "__main__" :
194
+ sys .exit (main ())
0 commit comments