text: import datetime
  import getpass
  import logging
  import os
  import sys
  import time
  
  import configparser
  from progress.bar import ChargingBar
  from py_jama_rest_client.client import JamaClient, APIException
  
  
  def init_jama_client():
      # do we have credentials in the config?
      credentials_dict = {}
      if 'CREDENTIALS' in config:
          credentials_dict = config['CREDENTIALS']
      try:
          instance_url = get_instance_url(credentials_dict)
          oauth = get_oauth(credentials_dict)
          username = get_username(credentials_dict)
          password = get_password(credentials_dict)
          jama_client = JamaClient(instance_url, credentials=(username, password), oauth=oauth)
          jama_client.get_available_endpoints()
          return jama_client
      except APIException:
          # we cant do things without the API so lets kick out of the execution.
          print('Error: invalid Jama credentials, check they are valid in the config.ini file.')
      except:
          print('Failed to authenticate to <' + get_instance_url(credentials_dict) + '>')
  
      response = input('\nWould you like to manually enter server credentials?\n')
      response = response.lower()
      if response == 'y' or response == 'yes' or response == 'true':
          config['CREDENTIALS'] = {}
          return init_jama_client()
      else:
          sys.exit()
  
  
  def get_instance_url(credentials_object):
      if 'instance url' in credentials_object:
          instance_url = str(credentials_object['instance url'])
          instance_url = instance_url.lower()
          # ends with a slash? lets remove this
          if instance_url.endswith('/'):
              instance_url = instance_url[:-1]
          # user forget to put the "https://" bit?
          if not instance_url.startswith('https://') or instance_url.startswith('http://'):
              # if forgotten then ASSuME that this is an https server.
              instance_url = 'https://' + instance_url
          # also allow for shorthand cloud instances
          if '.' not in instance_url:
              instance_url = instance_url + '.jamacloud.com'
          return instance_url
      # otherwise the user did not specify this in the config. prompt the user for it now
      else:
          instance_url = input('Enter the Jama Instance URL:\n')
          credentials_object['instance url'] = instance_url
          return get_instance_url(credentials_object)
  
  
  def get_username(credentials_object):
      if 'username' in credentials_object:
          username = str(credentials_object['username'])
          return username.strip()
      else:
          username = input('Enter the username (basic auth) or client ID (oAuth):\n')
          credentials_object['username'] = username
          return get_username(credentials_object)
  
  
  def get_password(credentials_object):
      if 'password' in credentials_object:
          password = str(credentials_object['password'])
          return password.strip()
      else:
          password = getpass.getpass(prompt='Enter your password (basic auth) or client secret (oAuth):\n')
          credentials_object['password'] = password
          return get_password(credentials_object)
  
  
  def get_oauth(credentials_object):
      if 'using oauth' in credentials_object:
          # this is user input here so lets be extra careful
          user_input = credentials_object['using oauth'].lower()
          user_input = user_input.strip()
          return user_input == 'true' or user_input == 'yes' or user_input == 'y'
      else:
          oauth = input('Using oAuth to authenticate?\n')
          credentials_object['using oauth'] = oauth
          return get_oauth(credentials_object)
  
  
  def get_target_project_id():
      try:
          return int(config['PARAMETERS']['target project id'])
      except:
          print("missing target project id... please provide a target project id in the config ini")
          sys.exit()
  
  
  def get_source_project_id():
      try:
          return int(config['PARAMETERS']['source project id'])
      except:
          print("missing source project id... please provide a source project id in the config ini")
          sys.exit()
  
  
  def get_global_pool_field_name():
      try:
          return str(config['PARAMETERS']['global pool field name'])
      except:
          print("missing global pool field name... please provide a global pool field name in the config ini")
          sys.exit()
  
  
  def get_test_case_item_type_id():
      try:
          return str(config['PARAMETERS']['test case item type id'])
      except:
          print("missing test case item type id... please provide a test case item type id in the config ini")
          sys.exit()
  
  
  def get_related_item_type_ids():
      try:
          item_type_id_string = str(config['PARAMETERS']['related item type ids'])
          id_list = []
          for item_type_id in item_type_id_string.split(','):
              id_list.append(item_type_id.strip())
  
          if len(id_list) == 0:
              print("missing related item type ids... please provide a related item type ids in the config ini")
              sys.exit()
  
          return id_list
      except:
          print("missing related item type ids... please provide a related item type ids in the config ini")
          sys.exit()
  
  
  def init_logger():
      # Setup logging
      try:
          os.makedirs('logs')
      except FileExistsError:
          pass
  
      current_date_time = datetime.datetime.now().strftime('%m-%d-%Y_%H-%M-%S')
      log_file = 'logs/' + str(current_date_time) + '.log'
  
      logging.basicConfig(filename=log_file, level=logging.INFO,
                          format='%(asctime)s - %(levelname)s - %(message)s',
                          datefmt='%H:%M:%S')
  
      logger = logging.getLogger()
      logger.addHandler(logging.StreamHandler(sys.stdout))
      return logger
  
  
  def get_test_case_item_type():
      try:
          # we will need to pull down all the item types to determine what the test case item type is
          return client.get_item_type(get_test_case_item_type_id())
      except APIException as exception:
          logger.error('Unable to retrieve required instance meta data. exiting...')
          sys.exit()
  
  
  def validate_global_pool_field(item_type, global_pool_field_name):
      # lets confirm that the specified item type id is defined in the test case item type.
      has_valid_field_name = False
  
      for field in item_type['fields']:
          # we need to handle both cases of using a standard field or a
          # custom field (appended with $itemTypeID)
          if field['name'] == global_pool_field_name:
              has_valid_field_name = True
              break
          elif field['name'] == global_pool_field_name + '$' + str(item_type['id']):
              global_pool_field_name = global_pool_field_name + '$' + str(item_type['id'])
              has_valid_field_name = True
              break
  
      if not has_valid_field_name:
          logger.error(
              'Unable to associate field name:[' + global_pool_field_name + '] with test case item type')
          sys.exit()
      # we reach this point? everything is good. return the updated string here
      return global_pool_field_name
  
  
  # this method must produce items. otherwise quit.
  def get_items(test_case_id, related_item_types, project_id):
      # grab all the items for the specififed project
      logger.info(
          'Retrieving test cases with ID:[' + str(test_case_id) + '] and related items with ID(s):' + str(
              related_item_type_ids) + ' from project ID:[' + str(project_id) + ']')
  
      all_types = related_item_types.append(str(test_case_id))
  
      try:
          # get all the test cases of the specified project
          project_items = client.get_abstract_items(project=project_id, item_type=all_types)
          logger.info('Successfully retrieved ' + str(len(project_items)) + ' items from project ID:[' + str(
              project_id) + ']')
          if len(project_items) == 0:
              logger.info('No items found. for project [' + str(project_id) + '] exiting...')
              sys.exit()
          return project_items
      except APIException:
          logger.error('Unable to retrieve items from projectID:[' + str(project_id) + '] exiting...')
          sys.exit()
  
  
  # this method must produce items. otherwise quit.
  def get_relationships(project_id):
      # grab all the relationships for the specified project
      logger.info('Retrieving relationships from project id:[' + str(project_id) + ']...')
      project_relationships = []
      try:
          # get all the test cases of the specified project
          project_relationships = client.get_relationships(project_id)
          logger.info(
              'Successfully retrieved ' + str(len(project_relationships)) + ' relationships from project ID:[' + str(
                  project_id) + ']')
          if len(project_relationships) == 0:
              logger.info('No relationships found. for project [' + str(project_id) + '] exiting...')
          return project_relationships
      except APIException:
          logger.error('Unable to retrieve relationships from projectID:[' + str(project_id) + '] exiting...')
          return project_relationships
  
  
  def generate_relationship_map(item_list, relationship_list, direction_key):
      relationship_map = {}
      for item in item_list:
          matched_relationships = []
          for source_relationship in relationship_list:
              if source_relationship.get(direction_key) == item.get('id'):
                  matched_relationships.append(source_relationship)
          relationship_map[item['id']] = matched_relationships
      return relationship_map
  
  
  def generate_source_to_target_map():
      source_to_target_map = {}
      # loop over all the source items and match on the globalId or the custom field
      for source_item in source_items:
          for target_item in target_items:
              # already synced?
              if source_item['globalId'] == target_item['globalId']:
                  source_to_target_map[source_item['id']] = target_item['id']
                  break
              # also check the custom field
              elif source_item['globalId'] == target_item['fields'].get(global_pool_field_name):
                  source_to_target_map[source_item['id']] = target_item['id']
                  break
          if source_item['id'] not in source_to_target_map:
              source_to_target_map[source_item['id']] = None
      return source_to_target_map
  
  
  def get_missing_synced_items_list():
      sync_list = []
      # loop over all the target items
      for target_item in target_items:
          # only process the items of type test case
          if target_item.get('itemType') == test_case_item_type.get('id'):
              if global_pool_field_name in target_item['fields']:
                  global_id = target_item['fields'][global_pool_field_name]
                  if global_id != target_item['globalId']:
                      sync_list.append(target_item)
      return sync_list
  
  
  def get_missing_upstream_relationships():
      source_upstream_relationships = []
      for source_item in source_items:
          if source_item.get('itemType') == test_case_item_type.get('id'):
              source_upstream_relationships += upstream_relationship_map[source_item['id']]
  
      target_upstream_relationships = []
      for target_item in target_items:
          if target_item.get('itemType') == test_case_item_type.get('id'):
              target_upstream_relationships += upstream_relationship_map[target_item['id']]
  
      synced_relationships = []
      for source_relationship in source_upstream_relationships:
  
          source_from_item_id = source_relationship['fromItem']
          source_to_item_id = source_relationship['toItem']
  
          target_from_id = source_to_target_map[source_from_item_id]
          target_to_id = source_to_target_map[source_to_item_id]
  
          if target_from_id is not None and target_to_id is not None:
              synced_relationships.append({
                  'fromItem': source_to_target_map[source_from_item_id],
                  'toItem': source_to_target_map[source_to_item_id]
              })
      missing_upstream_relationship_list = []
      for synced_relationship in synced_relationships:
          from_item = synced_relationship['fromItem']
          to_item = synced_relationship['toItem']
          found_in_target = False
  
          for target_relationship in target_upstream_relationships:
              if from_item == target_relationship['fromItem'] and to_item == target_relationship['toItem']:
                  found_in_target = True
                  break
  
          if not found_in_target:
              missing_upstream_relationship_list.append(synced_relationship)
      return missing_upstream_relationship_list
  
  
  # link fixer script, will identify broken links from old projects, and correct the links
  # a link to the past
  if __name__ == '__main__':
      # int some logging ish
      logger = init_logger()
      start_time = time.time()
  
      logger.info('***********************************************\n' +
                  '    Re-syncing Global IDs and relationships     \n' +
                  '***********************************************\n')
  
      config = configparser.ConfigParser()
      config.read('config.ini')
      logger.info('Reading in configuration file')
  
      client = init_jama_client()
      instance_url = get_instance_url(config['CREDENTIALS'])
      logger.info('Successfully connected to instance: <' + instance_url + '>\n')
  
      # grab the required script parameters from the config.ini file.
      target_project_id = get_target_project_id()
      source_project_id = get_source_project_id()
      global_pool_field_name = get_global_pool_field_name()
      related_item_type_ids = get_related_item_type_ids()
  
      # lets collect the required instance meta data here
      test_case_item_type = get_test_case_item_type()
      global_pool_field_name = validate_global_pool_field(test_case_item_type, global_pool_field_name)
  
      # grab all items.
      source_items = get_items(test_case_item_type.get('id'), related_item_type_ids, source_project_id)
      target_items = get_items(test_case_item_type.get('id'), related_item_type_ids, target_project_id)
      # gran all the relationships
      source_relationships = get_relationships(source_project_id)
      target_relationships = get_relationships(target_project_id)
  
      # lets process the the source items and make a GID to item API ID map.
      source_gid_map = {}
      for source_item in source_items:
          source_gid_map[source_item['globalId']] = source_item.get('id')
  
      # lets make maps of item ids -> list of relationships
      upstream_relationship_map = generate_relationship_map(source_items + target_items,
                                                            source_relationships + target_relationships, 'toItem')
      downstream_relationship_map = generate_relationship_map(source_items + target_items,
                                                              source_relationships + target_relationships, 'fromItem')
  
      # build out a map of source items ids that point to target item ids
      source_to_target_map = generate_source_to_target_map()
  
      post_sync_list = get_missing_synced_items_list()
      logger.info('\n - Identified ' + str(len(post_sync_list)) + ' test case(s) with required global ID update')
  
      post_relationship_list = get_missing_upstream_relationships()
      logger.info('\n - Identified ' + str(
          len(post_relationship_list)) + ' upstream relationships that are missing from target project\n')
  
      # let the user know how we are doing on our sync posting progress
      if len(post_sync_list) > 0:
          with ChargingBar('Updating Global ID values... ', max=len(post_sync_list),
                           suffix='%(percent).1f%% - %(eta)ds') as bar:
              # loop through all the test cases that need to be processed
              for test_case in post_sync_list:
                  new_global_id = test_case['fields'][global_pool_field_name]
                  global_pool_item_id = source_gid_map[new_global_id]
  
                  # do we have an individual pool item identified here? throw error and skip if not
                  if global_pool_item_id is None or global_pool_item_id == '':
                      logger.error('unable to identify global pool with ID:[' + str(new_global_id))
                      break
  
                  # lets post up this new synced item
                  try:
                      client.post_item_sync(test_case['id'], global_pool_item_id)
                      logger.info(
                          'Successfully added test case with ID:[' + str(test_case['id']) + '] to the global pool.')
                  except APIException as exception:
                      logger.error('Failed to add test case ID:[' + str(test_case['id']) + '] to global pool.')
                  bar.next()
              bar.finish()
  
      # use a progress bar to report back to teh user on the progression of posting up these relationships
      if len(post_relationship_list) > 0:
          with ChargingBar('Posting relationships... ', max=len(post_relationship_list),
                           suffix='%(percent).1f%% - %(eta)ds') as bar:
              # loop through all the test cases that need to be processed
              for relationship in post_relationship_list:
                  from_item = relationship['fromItem']
                  to_item = relationship['toItem']
                  # try and post this relationship
                  try:
                      client.post_relationship(from_item=from_item, to_item=to_item)
                      logger.info('Successfully created relationship: ' + str(from_item) + ' -> ' + str(to_item))
                  except APIException as exception:
                      logger.info('Failed to created relationship: ' + str(from_item) + ' -> ' + str(to_item))
  
                  bar.next()
              bar.finish()
  
      # were done here 🎉🎉🎉
      elapsed_time = '%.2f' % (time.time() - start_time)
      print('total execution time: ' + elapsed_time + ' seconds')
  