Skip to content

Commit 5db21d2

Browse files
committed
full scan
1 parent 480f86f commit 5db21d2

File tree

1 file changed

+258
-0
lines changed

1 file changed

+258
-0
lines changed
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,259 @@
1+
#!/usr/bin/env python3
2+
'''
13
4+
Copyright (C) 2023 Synopsys, Inc.
5+
http://www.blackducksoftware.com/
6+
7+
Licensed to the Apache Software Foundation (ASF) under one
8+
or more contributor license agreements. See the NOTICE file
9+
distributed with this work for additional information
10+
regarding copyright ownership. The ASF licenses this file
11+
to you under the Apache License, Version 2.0 (the
12+
"License"); you may not use this file except in compliance
13+
with the License. You may obtain a copy of the License at
14+
15+
http://www.apache.org/licenses/LICENSE-2.0
16+
17+
Unless required by applicable law or agreed to in writing,
18+
software distributed under the License is distributed on an
19+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20+
KIND, either express or implied. See the License for the
21+
specific language governing permissions and limitations
22+
under the License.
23+
24+
This script will scan a multi-container project into
25+
hierarchical structure.
26+
27+
Project Name
28+
Project Version
29+
Subproject Name
30+
Subproject Vesrsion
31+
Base Image
32+
Base Image Version
33+
Add on Image
34+
Add on Image Version
35+
36+
usage: python3 manage_project_structure.py [-h] -u BASE_URL -t TOKEN_FILE [-pg PROJECT_GROUP] -p PROJECT_NAME -pv VERSION_NAME
37+
[-sp SUBPROJECT_LIST] [-nv] [-rm] [--dry-run]
38+
39+
options:
40+
-h, --help show this help message and exit
41+
-u BASE_URL, --base-url BASE_URL
42+
Hub server URL e.g. https://your.blackduck.url
43+
-t TOKEN_FILE, --token-file TOKEN_FILE
44+
File containing access token
45+
-pg PROJECT_GROUP, --project_group PROJECT_GROUP
46+
Project Group to be used
47+
-p PROJECT_NAME, --project-name PROJECT_NAME
48+
Project Name
49+
-pv VERSION_NAME, --version-name VERSION_NAME
50+
Project Version Name
51+
-sp SUBPROJECT_LIST, --subproject-list SUBPROJECT_LIST
52+
List of subprojects to generate with subproject:container:tag
53+
-nv, --no-verify Disable TLS certificate verification
54+
-rm, --remove Remove project structure with all subprojects (DANGEROUS!)
55+
--dry-run Create structure only, do not execute scans
56+
57+
Subprojects ae specified as subproject:[container]:[tag]
58+
if container name omited it will be set to subproject
59+
if tag omited it would be set to 'latest'
60+
61+
Container image name scanned will be written into project version nickname field
62+
63+
64+
65+
'''
66+
67+
import argparse
68+
import json
69+
import logging
70+
import sys
71+
import arrow
72+
73+
from blackduck import Client
74+
from pprint import pprint,pformat
75+
76+
logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', stream=sys.stderr, level=logging.DEBUG)
77+
logging.getLogger("requests").setLevel(logging.INFO)
78+
logging.getLogger("urllib3").setLevel(logging.INFO)
79+
logging.getLogger("blackduck").setLevel(logging.INFO)
80+
81+
def remove_project_structure(project_name, version_name):
82+
project = find_project_by_name(project_name)
83+
if not project:
84+
logging.info(f"Project {project_name} does not exist.")
85+
return
86+
num_versions = bd.get_resource('versions', project, items=False)['totalCount']
87+
version = find_project_version_by_name(project,version_name)
88+
if not version:
89+
logging.info(f"Project {project_name} with version {version_name} does not exist.")
90+
return
91+
components = [
92+
c for c in bd.get_resource('components',version) if c['componentType'] == "SUB_PROJECT"
93+
]
94+
logging.info(f"Project {project_name}:{version_name} has {len(components)} subprojects")
95+
for component in components:
96+
component_name = component['componentName']
97+
component_version_name = component['componentVersionName']
98+
logging.info(f"Removing subproject {component_name} from {project_name}:{version_name}")
99+
component_url = component['_meta']['href']
100+
response = bd.session.delete(component_url)
101+
logging.info(f"Operation completed with {response}")
102+
remove_project_structure(component_name, component_version_name)
103+
logging.info(f"Removing {project_name}:{version_name}")
104+
if num_versions > 1:
105+
response = bd.session.delete(version['_meta']['href'])
106+
else:
107+
response = bd.session.delete(project['_meta']['href'])
108+
logging.info(f"Operation completed with {response}")
109+
110+
def find_or_create_project_group(group_name):
111+
url = '/api/project-groups'
112+
params = {
113+
'q': [f"name:{group_name}"]
114+
}
115+
groups = [p for p in bd.get_items(url, params=params) if p['name'] == group_name]
116+
if len(groups) == 0:
117+
headers = {
118+
'Accept': 'application/vnd.blackducksoftware.project-detail-5+json',
119+
'Content-Type': 'application/vnd.blackducksoftware.project-detail-5+json'
120+
}
121+
data = {
122+
'name': group_name
123+
}
124+
response = bd.session.post(url, headers=headers, json=data)
125+
return response.headers['Location']
126+
else:
127+
return groups[0]['_meta']['href']
128+
129+
def find_project_by_name(project_name):
130+
params = {
131+
'q': [f"name:{project_name}"]
132+
}
133+
projects = [p for p in bd.get_resource('projects', params=params) if p['name'] == project_name]
134+
if len(projects) == 1:
135+
return projects[0]
136+
else:
137+
return None
138+
139+
def find_project_version_by_name(project, version_name):
140+
params = {
141+
'q': [f"versionName:{version_name}"]
142+
}
143+
versions = [v for v in bd.get_resource('versions', project, params=params) if v['versionName'] == version_name]
144+
if len(versions) == 1:
145+
return versions[0]
146+
else:
147+
return None
148+
149+
def create_project_version(project_name,version_name,args, nickname = None):
150+
version_data = {"distribution": "EXTERNAL", "phase": "DEVELOPMENT", "versionName": version_name}
151+
if nickname:
152+
version_data['nickname'] = nickname
153+
url = '/api/projects'
154+
project = find_project_by_name(project_name)
155+
if project:
156+
data = version_data
157+
url = project['_meta']['href'] + '/versions'
158+
else:
159+
data = {"name": project_name,
160+
"projectGroup": find_or_create_project_group(args.project_group),
161+
"versionRequest": version_data}
162+
return bd.session.post(url, json=data)
163+
164+
def add_component_to_version_bom(child_version, version):
165+
url = version['_meta']['href'] + '/components'
166+
data = { 'component': child_version['_meta']['href']}
167+
return bd.session.post(url, json=data)
168+
169+
def create_and_add_child_projects(version, args):
170+
version_url = version['_meta']['href'] + '/components'
171+
for child_spec in [x.split(':') for x in args.subproject_list.split(",")]:
172+
i = iter(child_spec)
173+
child = next(i)
174+
repo = next(i, child)
175+
tag = next(i,'latest')
176+
container_spec = f"{repo}:{tag}"
177+
scan_param = {'image': container_spec, 'project': child, 'version': args.version_name}
178+
project = find_project_by_name(child)
179+
if project:
180+
version = find_project_version_by_name(project,args.version_name)
181+
if version:
182+
logging.error(f"Child project {project['name']} with version {args.version_name} exists.")
183+
return
184+
response = create_project_version(child,args.version_name, args, nickname=container_spec)
185+
logging.info(f"Creating project {child} : {args.version_name} completed with {response}")
186+
if response.ok:
187+
child_version = find_project_version_by_name(find_project_by_name(child),args.version_name)
188+
child_version_url = child_version['_meta']['href']
189+
response = bd.session.post(version_url,json={'component': child_version_url})
190+
logging.info(f"Adding {child} : {args.version_name} to parent project completed with {response}")
191+
scan_params.append(scan_param)
192+
193+
def create_project_structure(args):
194+
project = find_project_by_name(args.project_name)
195+
logging.info(f"Project {args.project_name} located")
196+
if project:
197+
version = find_project_version_by_name(project,args.version_name)
198+
if version:
199+
logging.error(f"Project {project['name']} with version {args.version_name} exists.")
200+
sys.exit(1)
201+
response = create_project_version(args.project_name,args.version_name,args)
202+
if response.ok:
203+
version = find_project_version_by_name(find_project_by_name(args.project_name),args.version_name)
204+
logging.info(f"Project {args.project_name} : {args.version_name} created")
205+
create_and_add_child_projects(version, args)
206+
207+
def scan_container_images(scan_params):
208+
from scan_docker_image_lite import scan_container_image
209+
for params in scan_params:
210+
scan_container_image(
211+
params['image'],
212+
None,
213+
None,
214+
None,
215+
params['project'],
216+
params['version'],
217+
(f"--detect.parent.project.name={params['project']} "
218+
f"--detect.parent.project.version.name={params['version']} "
219+
f"--detect.project.version.nickname={params['image']}")
220+
)
221+
222+
223+
def parse_command_args():
224+
225+
parser = argparse.ArgumentParser("python3 manage_project_structure.py")
226+
parser.add_argument("-u", "--base-url", required=True, help="Hub server URL e.g. https://your.blackduck.url")
227+
parser.add_argument("-t", "--token-file", required=True, help="File containing access token")
228+
parser.add_argument("-pg", "--project_group", required=False, default='Multi-Image', help="Project Group to be used")
229+
parser.add_argument("-p", "--project-name", required=True, help="Project Name")
230+
parser.add_argument("-pv", "--version-name", required=True, help="Project Version Name")
231+
parser.add_argument("-sp", "--subproject-list", required=False, help="List of subprojects to generate with subproject:container:tag")
232+
parser.add_argument("-nv", "--no-verify", action='store_false', help="Disable TLS certificate verification")
233+
parser.add_argument("-rm", "--remove", action='store_true', required=False, help="Remove project structure with all subprojects (DANGEROUS!)")
234+
parser.add_argument("--dry-run", action='store_true', required=False, help="Create structure only, do not execute scans")
235+
return parser.parse_args()
236+
237+
def main():
238+
args = parse_command_args()
239+
with open(args.token_file, 'r') as tf:
240+
access_token = tf.readline().strip()
241+
global bd
242+
global scan_params
243+
scan_params = []
244+
bd = Client(base_url=args.base_url, token=access_token, verify=args.no_verify, timeout=60.0, retries=4)
245+
logging.info(f"{args}")
246+
247+
if args.remove:
248+
remove_project_structure(args.project_name, args.version_name)
249+
else:
250+
create_project_structure(args)
251+
if args.dry_run:
252+
logging.info(f"{pformat(scan_params)}")
253+
else:
254+
logging.info("Now execution scans")
255+
scan_container_images(scan_params)
256+
257+
258+
if __name__ == "__main__":
259+
sys.exit(main())

0 commit comments

Comments
 (0)