Skip to content

Commit 1e97855

Browse files
author
Glenn Snyder
authored
Merge pull request #61 from blackducksoftware/gsnyder/custom-fields
adding support for creating and deleting custom fields
2 parents ea11746 + f76449f commit 1e97855

File tree

4 files changed

+203
-1
lines changed

4 files changed

+203
-1
lines changed

blackduck/HubRestApi.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1457,6 +1457,126 @@ def update_component_by_url(self, component_url, update_json):
14571457
return self.execute_put(component_url, update_json)
14581458

14591459

1460+
##
1461+
#
1462+
# Custom fields
1463+
#
1464+
##
1465+
def _get_cf_url(self):
1466+
return self.get_apibase() + "/custom-fields/objects"
1467+
1468+
def supported_cf_object_types(self):
1469+
'''Get the types and cache them since they are static (on a per-release basis)'''
1470+
if not hasattr(self, "_cf_object_types"):
1471+
logging.debug("retrieving object types")
1472+
self._cf_object_types = [cfo['name'] for cfo in self.get_cf_objects().get('items', [])]
1473+
return self._cf_object_types
1474+
1475+
def get_cf_objects(self):
1476+
'''Get CF objects and cache them since these are static (on a per-release basis)'''
1477+
url = self._get_cf_url()
1478+
if not hasattr(self, "_cf_objects"):
1479+
logging.debug("retrieving objects")
1480+
custom_headers = {'Accept': 'application/vnd.blackducksoftware.admin-4+json'}
1481+
response = self.execute_get(url, custom_headers=custom_headers)
1482+
self._cf_objects = response.json()
1483+
return self._cf_objects
1484+
1485+
def _get_cf_object_url(self, object_name):
1486+
for cf_object in self.get_cf_objects().get('items', []):
1487+
if cf_object['name'].lower() == object_name.lower():
1488+
return cf_object['_meta']['href']
1489+
1490+
def get_cf_object(self, object_name):
1491+
assert object_name in self.supported_cf_object_types(), "Object name {} not one of the supported types ({})".format(object_name, self.supported_cf_object_types())
1492+
1493+
object_url = self._get_cf_object_url(object_name)
1494+
custom_headers = {'Accept': 'application/vnd.blackducksoftware.admin-4+json'}
1495+
response = self.execute_get(object_url, custom_headers=custom_headers)
1496+
return response.json()
1497+
1498+
def _get_cf_obj_rel_path(self, object_name):
1499+
return object_name.lower().replace(" ", "-")
1500+
1501+
def create_cf(self, object_name, field_type, description, label, position, active=True, initial_options=[]):
1502+
'''
1503+
Create a custom field for the given object type (e.g. "Project", "Project Version") using the field_type and other parameters.
1504+
1505+
Initial options are needed for field types like multi-select where the multiple values to choose from must also be provided.
1506+
1507+
initial_options = [{"label":"val1", "position":0}, {"label":"val2", "position":1}]
1508+
'''
1509+
assert isinstance(position, int) and position >= 0, "position must be an integer that is greater than or equal to 0"
1510+
assert field_type in ["BOOLEAN", "DATE", "DROPDOWN", "MULTISELECT", "RADIO", "TEXT", "TEXTAREA"]
1511+
1512+
types_using_initial_options = ["DROPDOWN", "MULTISELECT", "RADIO"]
1513+
1514+
post_url = self._get_cf_object_url(object_name) + "/fields"
1515+
cf_object = self._get_cf_obj_rel_path(object_name)
1516+
cf_request = {
1517+
"active": active,
1518+
"description": description,
1519+
"label": label,
1520+
"position": position,
1521+
"type": field_type,
1522+
}
1523+
if field_type in types_using_initial_options and initial_options:
1524+
cf_request.update({"initialOptions": initial_options})
1525+
custom_headers = {
1526+
'Content-Type': 'application/vnd.blackducksoftware.admin-4+json',
1527+
'Accept': 'application/json'
1528+
}
1529+
response = self.execute_post(post_url, data=cf_request, custom_headers=custom_headers)
1530+
return response
1531+
1532+
def delete_cf(self, object_name, field_id):
1533+
'''Delete a custom field from a given object type, e.g. Project, Project Version, Component, etc
1534+
1535+
WARNING: Deleting a custom field is irreversiable. Any data in the custom fields could be lost so use with caution.
1536+
'''
1537+
assert object_name in self.supported_cf_object_types(), "You must supply a supported object name that is in {}".format(self.supported_cf_object_types())
1538+
1539+
delete_url = self._get_cf_object_url(object_name) + "/fields/{}".format(field_id)
1540+
return self.execute_delete(delete_url)
1541+
1542+
def get_custom_fields(self, object_name):
1543+
'''Get the custom field (definition) for a given object type, e.g. Project, Project Version, Component, etc
1544+
'''
1545+
assert object_name in self.supported_cf_object_types(), "You must supply a supported object name that is in {}".format(self.supported_cf_object_types())
1546+
1547+
url = self._get_cf_object_url(object_name) + "/fields"
1548+
1549+
custom_headers = {'Accept': 'application/vnd.blackducksoftware.admin-4+json'}
1550+
response = self.execute_get(url, custom_headers=custom_headers)
1551+
return response.json()
1552+
1553+
def get_cf_values(self, obj):
1554+
'''Get all of the custom fields from an object such as a Project, Project Version, Component, etc
1555+
1556+
The obj is expected to be the JSON document for a project, project-version, component, etc
1557+
'''
1558+
url = self.get_link(obj, "custom-fields")
1559+
custom_headers = {'Accept': 'application/vnd.blackducksoftware.component-detail-5+json'}
1560+
response = self.execute_get(url, custom_headers=custom_headers)
1561+
return response.json()
1562+
1563+
def get_cf_value(self, obj, field_id):
1564+
'''Get a custom field value from an object such as a Project, Project Version, Component, etc
1565+
1566+
The obj is expected to be the JSON document for a project, project-version, component, etc
1567+
'''
1568+
url = self.get_link(obj, "custom-fields") + "/{}".format(field_id)
1569+
custom_headers = {'Accept': 'application/vnd.blackducksoftware.component-detail-5+json'}
1570+
response = self.execute_get(url, custom_headers=custom_headers)
1571+
return response.json()
1572+
1573+
def put_cf_value(self, cf_url, new_cf_obj):
1574+
'''new_cf_obj is expected to be a modified custom field value object with the values updated accordingly, e.g.
1575+
call get_cf_value, modify the object, and then call put_cf_value
1576+
'''
1577+
custom_headers = {'Accept': 'application/vnd.blackducksoftware.component-detail-5+json'}
1578+
return self.execute_put(cf_url, new_cf_obj, custom_headers=custom_headers)
1579+
14601580
##
14611581
#
14621582
# General stuff

blackduck/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
VERSION = (0, 0, 34)
1+
VERSION = (0, 0, 35)
22

33
__version__ = '.'.join(map(str, VERSION))

examples/create_custom_field.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
2+
#!/usr/bin/env python
3+
4+
import argparse
5+
import json
6+
import logging
7+
import sys
8+
9+
from blackduck.HubRestApi import HubInstance
10+
11+
parser = argparse.ArgumentParser("Create a custom field")
12+
parser.add_argument("object", choices=["BOM Component", "Component", "Component Version", "Project", "Project Version"], help="The object that the custom field should be attached to")
13+
parser.add_argument("field_type", choices=["BOOLEAN", "DATE", "DROPDOWN", "MULTISELECT", "RADIO", "TEXT", "TEXTAREA"])
14+
parser.add_argument("description")
15+
parser.add_argument("label")
16+
parser.add_argument("-i", "--initial_options", action='append', nargs=2, metavar=('label', 'position'), help="Set the initial options by repeatedly using the -i option, supply a label and position for each possible selection. Used for DROPDOWN, MULTISELECT, and RADIO field types.")
17+
parser.add_argument("-a", "--active", action='store_true', default=False, help="Use the --active option to make the created field active (dafault: Inactive")
18+
parser.add_argument("-p", "--position", default=0, type=int, help="Use the --position option to specify what numeric position the custom field should be displayed in")
19+
args = parser.parse_args()
20+
21+
22+
logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', stream=sys.stderr, level=logging.DEBUG)
23+
logging.getLogger("requests").setLevel(logging.DEBUG)
24+
logging.getLogger("urllib3").setLevel(logging.WARNING)
25+
26+
27+
hub = HubInstance()
28+
29+
logging.debug("Creating custom field using arguments: {}".format(args))
30+
31+
initial_options = [{"label": io[0], "position": io[1]} for io in args.initial_options]
32+
33+
import pdb; pdb.set_trace()
34+
35+
response = hub.create_cf(
36+
args.object,
37+
args.field_type,
38+
args.description,
39+
args.label,
40+
position=args.position,
41+
active=args.active,
42+
initial_options=initial_options)
43+
44+
logging.debug("status code: {}".format(response.status_code))
45+
46+
47+
48+

examples/delete_custom_field.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
2+
#!/usr/bin/env python
3+
4+
import argparse
5+
import json
6+
import logging
7+
import sys
8+
9+
from blackduck.HubRestApi import HubInstance
10+
11+
parser = argparse.ArgumentParser("Delete a custom field or all of them")
12+
parser.add_argument("object", choices=["BOM Component", "Component", "Component Version", "Project", "Project Version"], help="The object that the custom field should be attached to")
13+
parser.add_argument("field_id", help="Use a value of 'all' to delete all the custom fields for the given object")
14+
args = parser.parse_args()
15+
16+
17+
logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', stream=sys.stderr, level=logging.DEBUG)
18+
logging.getLogger("requests").setLevel(logging.DEBUG)
19+
logging.getLogger("urllib3").setLevel(logging.WARNING)
20+
21+
22+
hub = HubInstance()
23+
24+
if args.field_id == "all":
25+
# delete all custom fields for the specified object type
26+
custom_fields = hub.get_custom_fields(args.object).get('items', [])
27+
for custom_field in custom_fields:
28+
logging.debug("Deleting custom field")
29+
custom_field_url = custom_field['_meta']['href']
30+
response = hub.execute_delete(custom_field_url)
31+
logging.debug("status code for deleting {} is {}".format(custom_field_url, response.status_code))
32+
else:
33+
response = hub.delete_cf(args.object, args.field_id)
34+
logging.debug("status code: {}".format(response.status_code))

0 commit comments

Comments
 (0)