diff --git a/d42_sd_sync.py b/d42_sd_sync.py index 28f840f..129565f 100755 --- a/d42_sd_sync.py +++ b/d42_sd_sync.py @@ -90,17 +90,23 @@ def get_map_value_from_device42(source, map_info, b_add=False, asset_type_id=Non if "@target-foregin-key" in map_info: value = freshservice.get_id_by_name(map_info["@target-foregin"], d42_value) if b_add and value is None and "@not-null" in map_info and map_info[ - "@not-null"] and "@required" in map_info and map_info["@required"]: - name = d42_value - id = freshservice.insert_and_get_id_by_name(map_info["@target-foregin"], name, asset_type_id) - d42_value = id + "@not-null"]: # and "@required" in map_info and map_info["@required"] + if d42_value is not None: + name = d42_value + if map_info["@target-foregin"] == "vendors": + new_id = freshservice.insert_and_get_id_by_name(map_info["@target-foregin"], name, None) + else: + new_id = freshservice.insert_and_get_id_by_name(map_info["@target-foregin"], name, asset_type_id) + d42_value = new_id + else: + d42_value = None else: d42_value = value return d42_value -def update_objects_from_server(sources, _target, mapping, doql=False): +def update_objects_from_server(sources, _target, mapping): global freshservice logger.info("Getting all existing devices in FS.") @@ -142,9 +148,12 @@ def update_objects_from_server(sources, _target, mapping, doql=False): is_valid = True if value is not None and "@min-length" in map_info and len(value) < map_info["@min-length"]: is_valid = False + if value == "" and "@set-space" in map_info and map_info["@set-space"]: + is_valid = True + value = " " * map_info["@min-length"] if value is None and "@not-null" in map_info and map_info["@not-null"]: is_valid = False - if not is_valid and "@required" in map_info and map_info["@required"]: + if not is_valid and "@target-foregin-key" in map_info: value = get_map_value_from_device42(source, map_info, True, data["asset_type_id"]) if value is not None: is_valid = True @@ -203,6 +212,149 @@ def delete_objects_from_server(sources, _target, mapping): logger.exception("Error (%s) deleting device %s" % (type(e), existing_object["name"])) +def create_relationships_from_affinity_group(sources, _target, mapping): + global freshservice + + logger.info("Getting all existing devices in FS.") + existing_objects = freshservice.request("api/v2/assets" + "?include=type_fields", "GET", _target["@model"]) + + logger.info("finished getting all existing devices in FS.") + + logger.info("Getting relationship type in FS.") + relationship_type = freshservice.get_relationship_type_by_content(mapping["@forward-relationship"], + mapping["@backward-relationship"]) + logger.info("finished getting relationship type in FS.") + if relationship_type is None: + logger.info("There is not relationship type in FS. (%s - %s)" % ( + mapping["@forward-relationship"], mapping["@backward-relationship"])) + return + + for source in sources: + try: + logger.info("Processing %s - %s." % (source[mapping["@key"]], source[mapping["@target-key"]])) + primary_asset = find_object_by_name(existing_objects, source[mapping["@key"]]) + secondary_asset = find_object_by_name(existing_objects, source[mapping["@target-key"]]) + + if primary_asset is None: + logger.info("There is no dependent asset(%s) in FS." % source[mapping["@key"]]) + continue + + if secondary_asset is None: + logger.info("There is no dependency asset(%s) in FS." % source[mapping["@target-key"]]) + continue + + relationships = freshservice.get_relationships_by_id(primary_asset["display_id"]) + exist = False + for relationship in relationships: + if relationship["relationship_type_id"] == relationship_type["id"]: + if relationship["config_item"]["display_id"] == secondary_asset["display_id"]: + exist = True + break + if exist: + logger.info("There is already relationship in FS.") + continue + + data = dict() + data["type"] = "config_items" + data["type_id"] = [secondary_asset["display_id"]] + data["relationship_type_id"] = relationship_type["id"] + data["relationship_type"] = "forward_relationship" + logger.info("adding relationship %s" % source[mapping["@key"]]) + new_relationship_id = freshservice.insert_relationship(primary_asset["display_id"], data) + logger.info("added new relationship %d" % new_relationship_id) + except Exception as e: + logger.exception("Error (%s) creating relationship %s" % (type(e), source[mapping["@key"]])) + + +def delete_relationships_from_affinity_group(sources, _target, mapping): + global freshservice + logger.info("Getting all existing devices in FS.") + existing_objects = freshservice.request("api/v2/assets" + "?include=type_fields", "GET", _target["@model"]) + + logger.info("finished getting all existing devices in FS.") + + logger.info("Getting relationship type in FS.") + relationship_type = freshservice.get_relationship_type_by_content(mapping["@forward-relationship"], + mapping["@backward-relationship"]) + logger.info("finished getting relationship type in FS.") + if relationship_type is None: + logger.info("There is not relationship type in FS. (%s - %s)" % ( + mapping["@forward-relationship"], mapping["@backward-relationship"])) + return + + for source in sources: + try: + logger.info("Processing %s - %s." % (source[mapping["@key"]], source[mapping["@target-key"]])) + primary_asset = find_object_by_name(existing_objects, source[mapping["@key"]]) + secondary_asset = find_object_by_name(existing_objects, source[mapping["@target-key"]]) + + if primary_asset is None: + logger.info("There is no dependent asset(%s) in FS." % source[mapping["@key"]]) + continue + + if secondary_asset is None: + logger.info("There is no dependency asset(%s) in FS." % source[mapping["@target-key"]]) + continue + + relationships = freshservice.get_relationships_by_id(primary_asset["display_id"]) + remove_relationship = None + for relationship in relationships: + if relationship["relationship_type_id"] == relationship_type["id"]: + if relationship["config_item"]["display_id"] == secondary_asset["display_id"]: + remove_relationship = relationship + break + if remove_relationship is None: + logger.info("There is not relationship in FS.") + continue + + freshservice.detach_relationship(primary_asset["display_id"], remove_relationship["id"]) + logger.info("detached relationship %d" % remove_relationship["id"]) + except Exception as e: + logger.exception("Error (%s) creating relationship %s" % (type(e), source[mapping["@key"]])) + + +def create_relationships_from_business_app(sources, _target, mapping): + create_relationships_from_affinity_group(sources, _target, mapping) + + +def delete_relationships_from_business_app(sources, _target, mapping): + global freshservice + logger.info("Getting all existing devices in FS.") + existing_objects = freshservice.request("api/v2/assets" + "?include=type_fields", "GET", _target["@model"]) + + logger.info("finished getting all existing devices in FS.") + + logger.info("Getting relationship type in FS.") + relationship_type = freshservice.get_relationship_type_by_content(mapping["@forward-relationship"], + mapping["@backward-relationship"]) + logger.info("finished getting relationship type in FS.") + if relationship_type is None: + logger.info("There is not relationship type in FS. (%s - %s)" % ( + mapping["@forward-relationship"], mapping["@backward-relationship"])) + return + + for existing_object in existing_objects: + logger.info("Checking relationship of asset(%s)." % existing_object["name"]) + relationships = freshservice.get_relationships_by_id(existing_object["display_id"]) + for relationship in relationships: + if relationship["relationship_type_id"] == relationship_type["id"] and \ + relationship["relationship_type"] == "forward_relationship": + remove_relationship = relationship + target_display_id = relationship["config_item"]["display_id"] + for source in sources: + if source[mapping["@key"]] == existing_object["name"]: + secondary_asset = find_object_by_name(existing_objects, source[mapping["@target-key"]]) + if target_display_id == secondary_asset["display_id"]: + remove_relationship = None + break + + if remove_relationship is None: + continue + + freshservice.detach_relationship(existing_object["display_id"], remove_relationship["id"]) + logger.info("detached relationship %d" % remove_relationship["id"]) + + def parse_config(url): config = eTree.parse(url) meta = config.getroot() @@ -239,11 +391,22 @@ def task_execute(task, device42): else: sources = device42.request(source_url, method, _resource["@model"]) - if "@delete" in _target and _target["@delete"]: - delete_objects_from_server(sources, _target, mapping) - return + if _type == "affinity_group": + if "@delete" in _target and _target["@delete"]: + delete_relationships_from_affinity_group(sources, _target, mapping) + else: + create_relationships_from_affinity_group(sources, _target, mapping) + elif _type == "business_app": + if "@delete" in _target and _target["@delete"]: + delete_relationships_from_business_app(sources, _target, mapping) + else: + create_relationships_from_business_app(sources, _target, mapping) + else: + if "@delete" in _target and _target["@delete"]: + delete_objects_from_server(sources, _target, mapping) + return - update_objects_from_server(sources, _target, mapping, doql=False) + update_objects_from_server(sources, _target, mapping) def main(): diff --git a/freshservice.py b/freshservice.py index e0ec786..2eba3ce 100755 --- a/freshservice.py +++ b/freshservice.py @@ -51,7 +51,7 @@ def _send(self, method, path, data=None): is_getting_exist = True if not is_getting_exist and self.last_time_call_api is not None and ( - now - self.last_time_call_api).total_seconds() < self.period_call_api: + now - self.last_time_call_api).total_seconds() < self.period_call_api: time.sleep(self.period_call_api - (now - self.last_time_call_api).total_seconds()) url = "%s/%s" % (self.base_url, path) @@ -105,8 +105,8 @@ def _put(self, path, data): path += '/' return self._send("PUT", path, data=data) - def _delete(self, path): - return self._send("DELETE", path) + def _delete(self, path, data=None): + return self._send("DELETE", path, data) def _log(self, message, level="DEBUG"): if self.logger: @@ -201,15 +201,18 @@ def get_id_by_name(self, model, name): path = "/api/v2/%s" % model models = self.request(path, "GET", model) for model in models: - if "name" in model and model["name"] is not None and name is not None and model[ - "name"].lower() == name.lower(): + if "name" in model and model["name"] is not None and name is not None and \ + model["name"].lower() == name.lower(): return model["id"] return None def insert_and_get_id_by_name(self, model, name, asset_type_id): path = "/api/v2/%s" % model - data = {"name": name, "asset_type_id": asset_type_id} + if asset_type_id is not None: + data = {"name": name, "asset_type_id": asset_type_id} + else: + data = {"name": name} models = self._post(path, data) for key in models: if model in self.server_data: @@ -239,3 +242,39 @@ def request(self, source_url, method, model): self.server_data[model] = models return models return [] + + def get_relationship_type_by_content(self, forward, backward): + path = "/cmdb/relationship_types/list.json" + relationships = None + if "relationships" in self.server_data: + relationships = self.server_data["relationships"] + + if relationships is None: + relationships = self._get(path) + self.server_data["relationships"] = relationships + + for relationship in relationships: + if relationship["forward_relationship"] == forward and relationship["backward_relationship"] == backward: + return relationship + + return None + + def get_relationships_by_id(self, asset_id): + path = "/cmdb/items/%d/relationships.json" % asset_id + result = self._get(path) + if "relationships" in result: + return result["relationships"] + return [] + + def insert_relationship(self, asset_id, data): + path = "/cmdb/items/%d/associate.json" % asset_id + relationships = self._post(path, data) + if len(relationships) > 0: + return relationships[0]["id"] + + return -1 + + def detach_relationship(self, asset_id, relationship_id): + path = "/cmdb/items/%d/detach_relationship.json" % asset_id + + return self._delete(path, {"relationship_id": relationship_id}) diff --git a/mapping.xml.sample b/mapping.xml.sample index e549640..5434db6 100644 --- a/mapping.xml.sample +++ b/mapping.xml.sample @@ -1,12 +1,12 @@ + url="{url}.freshservice.com" + api_key="{api_key}"/> + url="https://{url}" + user="{user}" + pass="{pass}"/> @@ -16,7 +16,7 @@ asset-type="Server"/> @@ -43,6 +43,11 @@ + + - - @@ -82,8 +87,56 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + \ No newline at end of file