Advertisement
Guest User

Untitled

a guest
Nov 11th, 2019
239
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 20.79 KB | None | 0 0
  1. #!/usr/bin/python3
  2.  
  3. from __future__ import print_function
  4. import optparse, os, sys, dohq_teamcity, yaml, requests, json
  5. import getpass
  6.  
  7. TEAMCITY_URL = "https://teamcity.scm.tripwirelab.com"
  8. TEAMCITY_DRY_RUN = False
  9. TEAMCITY_FORCE_RUN = False
  10. TEAMCITY_INTERACTIVE = False
  11. TEAMCITY_DEBUG = False
  12.  
  13. tc_credentials = ('unknown', 'unknown')
  14.  
  15. def teamcity_connection():
  16.     return dohq_teamcity.TeamCity(TEAMCITY_URL, auth=tc_credentials)
  17.  
  18. def set_credentials():
  19.     global tc_credentials
  20.     if 'TEAMCITY_USER' in os.environ:
  21.         tc_user = os.environ['TEAMCITY_USER']
  22.     else:
  23.         print("No TEAMCITY_USER env variable defined. Exiting")
  24.         exit(11)
  25.     if 'TEAMCITY_PASS' in os.environ:
  26.         tc_pass = os.environ['TEAMCITY_PASS']
  27.     else:
  28.         tc_pass = getpass.getpass()
  29.  
  30.     tc_credentials = (tc_user, tc_pass)
  31.     print("User set to:  ", tc_credentials[0])
  32.  
  33. def get_projects():
  34.     tc = teamcity_connection()
  35.     return tc.projects.get_projects()
  36.  
  37. def list_projects():
  38.     projects = get_projects()
  39.     for project in projects:
  40.         print(project.web_url)
  41.  
  42. def list_project(projectID):
  43.     projects = get_projects()
  44.     for project in projects:
  45.         if project.web_url == projectID or \
  46.            project.id      == projectID:
  47.             print(project)
  48.  
  49. def get_project(projectID):
  50.     projects = get_projects()
  51.     for project in projects:
  52.         if project.web_url == projectID or \
  53.            project.id      == projectID:
  54.             tc = teamcity_connection()
  55.             return tc.projects.get(project)
  56.  
  57. def get_build(buildID):
  58.      tc = teamcity_connection()
  59.      bt = tc.build_types.get("id:"+buildID)
  60.      return bt
  61.  
  62. def list_vcs_root(vcsRootID):
  63.     url = "{}/httpAuth/app/rest/vcs-roots/id:{}".format(TEAMCITY_URL, vcsRootID)
  64.     headers = {'Accept': 'application/json'}
  65.     response = requests.get(url, headers=headers, auth=tc_credentials)
  66.     if response.status_code != 200:
  67.         data = list(get_vcs_from_root(vcsRootID))
  68.     else:
  69.         data = response.json()
  70.     return data
  71.  
  72. def get_branch_spec(vcsID):
  73.     tc = teamcity_connection()
  74.     vcs_id = tc.build_type_api.get_current_vcs_instances(vcsID).vcs_root_instance[0].vcs_root_id
  75.     branch_spec = list_vcs_root(vcs_id)
  76.     for branch in branch_spec["properties"]["property"]:
  77.         if 'teamcity:branchSpec' in branch["name"]:
  78.             return branch["value"]
  79.  
  80. def get_vcs_root(vcsRootID):
  81.     url = "{}/httpAuth/app/rest/vcs-roots/id:{}".format(TEAMCITY_URL, vcsRootID)
  82.     headers = {'Accept': 'application/json'}
  83.     response = requests.get(url, headers=headers, auth=tc_credentials)
  84.     return response.json()
  85.  
  86. def get_vcs_root_id_by_project(projectID):
  87.     url = "{}/httpAuth/app/rest/vcs-roots?locator=project:(id:{})".format(TEAMCITY_URL, projectID)
  88.     headers = {'Accept': 'application/json'}
  89.     response = requests.get(url, headers=headers, auth=tc_credentials)
  90.     dprint(response)
  91.     dprint(response.text)
  92.     data = response.json()
  93.     vcs_root = data["vcs-root"][0]
  94.     return vcs_root["id"]
  95.  
  96. def get_vcs_root_from_build(buildType):
  97.     url = "{}/httpAuth/app/rest/buildTypes/id:{}".format(TEAMCITY_URL, buildType)
  98.     headers = {'Accept': 'application/json'}
  99.     response = requests.get(url, headers=headers, auth=tc_credentials)
  100.     data = response.json()
  101.     data = data["vcs-root-entries"]["vcs-root-entry"][0]
  102.     return data
  103.  
  104. def get_vcs_from_root(rootID):
  105.     url = "{}/httpAuth/app/rest/projects/{}/buildTypes/".format(TEAMCITY_URL, rootID)
  106.     headers = {'Accept': 'application/json'}
  107.     response = requests.get(url, headers=headers, auth=tc_credentials)
  108.     data = response.json()
  109.     vcs_list = []
  110.     elements = []
  111.     for build in data["buildType"]:
  112.         url = "{}/httpAuth/app/rest/buildTypes/id:{}".format(TEAMCITY_URL, build["id"])
  113.         response = requests.get(url, headers=headers, auth=tc_credentials)
  114.         vcs = response.json()
  115.         vcs = vcs["vcs-root-entries"]["vcs-root-entry"]
  116.         for element in vcs:
  117.             vcs_list.append(element)
  118.     for element in vcs_list:
  119.         elements.append(element["vcs-root"]["id"])
  120.     return set(elements)
  121.  
  122. def get_vcs_name_root(vcsRootID, yaml_file, isIDL):
  123.     with open(yaml_file, 'r') as ymlfile:
  124.         properties_config = yaml.safe_load(ymlfile)
  125.     if "name" in properties_config:
  126.         name_vcs_root = get_vcs_root(vcsRootID)["name"] + "#" + properties_config["name"]
  127.         id_vcs_root = get_vcs_root(vcsRootID)["id"] + properties_config["name"]
  128.     else:
  129.         name_vcs_root = get_vcs_root(vcsRootID)["name"]
  130.         id_vcs_root = get_vcs_root(vcsRootID)["id"]
  131.    
  132.     if isIDL and "idl" not in str(name_vcs_root).lower():
  133.         return name_vcs_root, id_vcs_root, properties_config
  134.     elif not isIDL:
  135.         return name_vcs_root, id_vcs_root, properties_config
  136.  
  137. def set_unique_vcs_root(vcsRootID, yaml_file, isIDL=False):
  138.     name_vcs_root, id_vcs_root, properties_config = get_vcs_name_root(vcsRootID, yaml_file, isIDL) or (None, None, None)
  139.     if name_vcs_root is not None and id_vcs_root is not None:
  140.         try:
  141.             url_name = "{}/httpAuth/app/rest/vcs-roots/{}/name".format(TEAMCITY_URL, vcsRootID)
  142.             url_id = "{}/httpAuth/app/rest/vcs-roots/{}/id".format(TEAMCITY_URL, vcsRootID)
  143.             requests.put(url_name, data=name_vcs_root, auth=tc_credentials)
  144.             requests.put(url_id, data=id_vcs_root, auth=tc_credentials)
  145.             for key, value in properties_config.items():
  146.                 url = "{}/httpAuth/app/rest/vcs-roots/{}/properties/{}".format(TEAMCITY_URL, vcsRootID, key)
  147.                 requests.put(url, data=value, auth=tc_credentials)
  148.         except Exception as ex:
  149.             print(ex)
  150.  
  151. def set_multiple_vcs_root(vcsRootID, yaml_file):
  152.     list_vcs = list_vcs_root(vcsRootID)
  153.     for vcs_id in list_vcs:
  154.         if "idl" not in vcs_id.lower():
  155.             set_unique_vcs_root(vcs_id, yaml_file, True)
  156.  
  157. def list_vcs_roots(build_types, outfile):
  158.     child_projects_id = [x.id for x in build_types]
  159.     if TEAMCITY_DEBUG: print("child project ids: ", child_projects_id)
  160.     child_projects_name = [x.name for x in build_types]
  161.     for indx, (name,child) in enumerate(zip(child_projects_name,child_projects_id)):
  162.         outfile.write("\t\t-" + str(name) + "\n")
  163.         builds = get_branch_spec(child)
  164.         if builds:
  165.             if TEAMCITY_DEBUG: print("builds found: '" + builds + "'\n")
  166.             for thisbranch in builds.split("\n"):
  167.                 outfile.write("\t\t\t"+ thisbranch + "\n")
  168.         else:
  169.             outfile.write("\t\t\t(No build branches)" + "\n")
  170.  
  171. def export_list(projectID):
  172.     #create a file withe project id name, getting all the busprojects and vcsroot from each build. Parent-child structure
  173.     f = open(projectID + "-file.txt", "w")
  174.     f.write(" " + str(projectID) + "\n")
  175.     builds_id = []
  176.     parent_projects_list = get_project(projectID)
  177.     if TEAMCITY_DEBUG: print("List of projects:  ", parent_projects_list.projects)
  178.     if TEAMCITY_DEBUG: print("List of project build types:  ", parent_projects_list.build_types)
  179.  
  180.     is_subproject = True if parent_projects_list.build_types else False
  181.     if TEAMCITY_DEBUG: print("Is subproject?  ", is_subproject)
  182.     if not is_subproject:
  183.         for project in [parent_projects_list.projects]:
  184.             child_project_ids = [x.id for x in project.project]
  185.         if TEAMCITY_DEBUG: print("child projects", child_project_ids)
  186.         for subproject in child_project_ids:
  187.             if TEAMCITY_DEBUG: print("processing", subproject)
  188.             f.write(" \t" + subproject + "\n")
  189.             project_info = get_project(subproject)
  190.             if TEAMCITY_DEBUG: print("project info", project_info)
  191.             list_vcs_roots(project_info.build_types, f)
  192.     else:
  193.         list_vcs_roots(parent_projects_list.build_types, f)
  194.    
  195.     f.close()
  196.  
  197. def exit_with_error(parser, status):
  198.     parser.print_help()
  199.     exit(status)
  200.  
  201. def exit_with_message(message, status):
  202.     eprint(message)
  203.     exit(status)
  204.  
  205. def eprint(*args, **kwargs):
  206.     print(*args, file=sys.stderr, **kwargs)
  207.  
  208. def dprint(message):
  209.     if TEAMCITY_DEBUG: eprint(message)
  210.  
  211. def dpprint(message):
  212.     if TEAMCITY_DEBUG:
  213.         from pprint import pprint
  214.         pprint(message)
  215.  
  216. def enable_debug():
  217.     eprint("Enabling debug urllib3/dohq_teamcity output")
  218.     from pprint import pprint
  219.     import logging
  220.     logging.getLogger("urllib3").setLevel(logging.DEBUG)
  221.     logging.getLogger("dohq_teamcity").setLevel(logging.DEBUG)
  222.     global TEAMCITY_DEBUG
  223.     TEAMCITY_DEBUG = True
  224.  
  225. def set_vcs_root_property(vcsRootID, property, value):
  226.     dprint("About to set vcsRootId {} property {} => {}".format(vcsRootID, property, value))
  227.     url = "{}/httpAuth/app/rest/vcs-roots/{}/properties/{}".format(
  228.         TEAMCITY_URL, vcsRootID, property)
  229.  
  230.     response = requests.put(url, data=value, auth=tc_credentials)
  231.     dprint(response)
  232.     dprint(response.headers)
  233.     dprint(response.text)
  234.  
  235. def set_vcs_root_properties(build_type, feature_definition_file):
  236.     with open(feature_definition_file, 'r') as ymlfile:
  237.         feature_config = yaml.safe_load(ymlfile)
  238.  
  239.     vcsRootID = get_vcs_root_from_build(build_type)['id']
  240.  
  241.     try:
  242.         property = 'branch'
  243.         value = feature_config[property]
  244.         set_vcs_root_property(vcsRootID, property, value)
  245.     except KeyError as e:
  246.         dprint(e)
  247.  
  248.     try:
  249.         property = 'teamcity:branchSpec'
  250.         value = feature_config[property]
  251.         set_vcs_root_property(vcsRootID, property, value)
  252.     except KeyError as e:
  253.         dprint(e)
  254.  
  255. def read_commit_status_publisher_properties(feature_config, projectID):
  256.     property0 = dohq_teamcity.ModelProperty(name="github_authentication_type",
  257.                     value=feature_config['github_authentication_type'])
  258.     property1 = dohq_teamcity.ModelProperty(name="github_host",
  259.                     value=feature_config['github_host'])
  260.     property2 = dohq_teamcity.ModelProperty(name="publisherId",
  261.                     value=feature_config['publisherId'])
  262.     property3 = dohq_teamcity.ModelProperty(name="secure:github_access_token",
  263.                     value=feature_config['secure:github_access_token'])
  264.  
  265.     try:
  266.         vcsRootID = feature_config['vcsRootId']
  267.     except KeyError as e:
  268.         vcsRootID = get_vcs_root_id_by_project(projectID)
  269.  
  270.     property4 = dohq_teamcity.ModelProperty(name="vcsRootId", value=vcsRootID)
  271.  
  272.     properties_list = [property0, property1, property2, property3, property4]
  273.     return properties_list
  274.  
  275. def read_xml_report_plugin_properties(feature_config):
  276.     property0 = dohq_teamcity.ModelProperty(name="xmlReportParsing.reportType",
  277.                     value=feature_config['xmlReportParsing.reportType'])
  278.     property1 = dohq_teamcity.ModelProperty(name="xmlReportParsing.reportDirs",
  279.                     value=feature_config['xmlReportParsing.reportDirs'])
  280.  
  281.     properties_list = [property0, property1]
  282.     return properties_list
  283.  
  284. def read_feature_from_file(feature_definition_file, projectID):
  285.     with open(feature_definition_file, 'r') as ymlfile:
  286.         feature_config = yaml.safe_load(ymlfile)
  287.  
  288.     feature = dohq_teamcity.Feature()
  289.     feature.type = feature_config['feature_type']
  290.  
  291.     if   feature.type == "commit-status-publisher":
  292.         properties_list = read_commit_status_publisher_properties(feature_config, projectID)
  293.     elif feature.type == "xml-report-plugin":
  294.         properties_list = read_xml_report_plugin_properties(feature_config)
  295.     else:
  296.         exit_with_message("Unsupported feature type: {}".format(feature.type), 2)
  297.  
  298.     properties = dohq_teamcity.Properties(count=len(properties_list), _property=properties_list)
  299.     feature.properties = properties
  300.  
  301.     dpprint(feature)
  302.     return feature
  303.  
  304. def test_github_connection(repo, token):
  305.     permission_to_push = False
  306.  
  307.     try:
  308.         response = requests.get(repo, headers={'Authorization': 'token {}'.format(token)})
  309.         dprint(response.text)
  310.         response_json = response.json()
  311.         permission_to_push = response_json['permissions']['push']
  312.     except KeyError as e:
  313.         print("Invalid token: '{}', for '{}'".format(token, repo))
  314.         print("please review you feature commit-status-publisher.yaml file or use the -f/--force flags to bypass this step")
  315.         return False
  316.  
  317.     if permission_to_push:
  318.         return permission_to_push
  319.     else:
  320.         eprint("Invalid token: {}, for '{}'".format(token, repo))
  321.         return False
  322.  
  323. # https://stackoverflow.com/questions/3663450/python-remove-substring-only-at-the-end-of-string
  324. def rchop(thestring, ending):
  325.   if thestring.endswith(ending):
  326.     return thestring[:-len(ending)]
  327.   return thestring
  328.  
  329. def extract_github_repo(vcs_root):
  330.     for property in vcs_root["properties"]["property"]:
  331.         if property["name"] == "url":
  332.             # git@github.scm.tripwire.com:tripwire/cap-reference_plugin.git
  333.             github_repo =  property["value"]
  334.             github_repo = github_repo.split(":")[1]
  335.             github_repo = rchop(github_repo, ".git")
  336.             return github_repo
  337.  
  338. def validate_feature(feature):
  339.     if feature.type == "commit-status-publisher":
  340.         for property in feature.properties._property:
  341.             if property.name == "vcsRootId":
  342.                 vcs_root = get_vcs_root(property.value)
  343.             if property.name == "github_host":
  344.                 github_host = property.value
  345.             if property.name == "secure:github_access_token":
  346.                 github_token = property.value
  347.  
  348.         github_repo = extract_github_repo(vcs_root)
  349.         github_url  = github_host + "/repos/" + github_repo
  350.         return test_github_connection(github_url, github_token)
  351.  
  352.     elif feature.type == "xml-report-plugin":
  353.         return True
  354.     return False
  355.  
  356. def user_confirms(message):
  357.     answer = str(input(message)).lower().strip()
  358.     try:
  359.         if answer[0]   == 'y':
  360.             return True
  361.         elif answer[0] == 'n':
  362.             return False
  363.         elif answer[0] == 'q':
  364.             exit_with_message("Aborting execution ...", 2)
  365.         else:
  366.             print('Invalid Input')
  367.             return user_confirms(message)
  368.     except Exception as error:
  369.         print("Please enter a valid answer")
  370.         print(error)
  371.         return user_confirms(message)
  372.  
  373. def add_feature_to_project_or_build(projectOrBuildID, feature_definition_file, *args, **kwargs):
  374.     build_type_regex = kwargs.get('build_type_regex', None)
  375.  
  376.     if not os.path.exists(feature_definition_file):
  377.         exit_with_message("Feature definition file '{}' couldn't be found".format(feature_definition_file), 2)
  378.  
  379.     project = get_project(projectOrBuildID)
  380.     if project:
  381.         feature = read_feature_from_file(feature_definition_file, projectOrBuildID)
  382.         for build_type in project.build_types:
  383.             if build_type_regex:
  384.                 if not (build_type_regex in build_type.id or build_type_regex in build_type.name): continue
  385.             tc = teamcity_connection() #required due to short default urllib3 timeouts
  386.             build_type_details = tc.build_types.get(build_type)
  387.             build_feature(build_type_details, feature)
  388.  
  389.     if not project :
  390.         build_type_details = get_build(projectOrBuildID)
  391.         if build_type_details:
  392.             feature = read_feature_from_file(feature_definition_file, projectOrBuildID)
  393.             build_feature(build_type_details, feature)
  394.         if not build_type_details:
  395.             exit_with_message("Project '{}' couldn't be found at {}".format(projectOrBuildID, TEAMCITY_URL), 2)
  396.    
  397. def build_feature(build_type, feature):
  398.     commit_publisher_is_configured = False
  399.     dprint("About to process {} / {}".format(build_type.name, build_type.id))
  400.    
  401.     for build_type_feature in build_type.features:
  402.         if build_type_feature.type == feature.type:
  403.             for feature_property in build_type_feature.properties._property:
  404.                 if feature.type == "commit-status-publisher" and \
  405.                    feature_property.name  == "github_host"   and \
  406.                    feature_property.value == "https://github.scm.tripwire.com/api/v3":
  407.  
  408.                     commit_publisher_is_configured = True
  409.                     dpprint(build_type_feature)
  410.  
  411.                 if feature.type == "xml-report-plugin"                     and \
  412.                    feature_property.name  == "xmlReportParsing.reportType" and \
  413.                    feature_property.value == "junit":
  414.  
  415.                     commit_publisher_is_configured = True
  416.                     dpprint(build_type_feature)
  417.  
  418.     if commit_publisher_is_configured:
  419.         print("Job {} has been already configured, skipping".format(build_type.id))
  420.     else:
  421.         print("Job {} has NOT been configured, attempting to do it".format(build_type.id))
  422.  
  423.         if not TEAMCITY_FORCE_RUN and not validate_feature(feature):
  424.             return
  425.  
  426.         if TEAMCITY_DRY_RUN:
  427.             print("Running in dry mode, skipping")
  428.         else:
  429.             if TEAMCITY_INTERACTIVE and user_confirms("Want to apply the change? [Y/n/q]: "):
  430.                 set_vcs_root_properties(build_type.id, feature_definition_file)
  431.                 tc = teamcity_connection()
  432.                 response = tc.build_type_api.add_feature(build_type.id, body=feature)
  433.                 from pprint import pprint
  434.                 pprint(response)
  435.  
  436. def add_feature_to_project_with_build_type_regex(projectID, build_type_regex, feature_definition_file):
  437.     add_feature_to_project_or_build(projectID, feature_definition_file, build_type_regex=build_type_regex)
  438.  
  439. def main():
  440.     usage  = "Usage: %prog [options] [subcommand] [options] ...\n"
  441.     usage += "Automate Nahuales TeamCity tasks"
  442.     usage += "\n\n"
  443.     usage += "Subcommands\n"
  444.     usage += "  list_projects\n"
  445.     usage += "  list_project    PROJECT_ID\n"
  446.     usage += "  list_vcs_root   VCS_ROOT_ID\n"
  447.     usage += "  add_feature     PROJECT_ID FEATURE_FILE_DEFINITION.yaml\n"
  448.     usage += "  add_feature     PROJECT_ID JOB_REGEX FEATURE_FILE_DEFINITION.yaml\n"
  449.     usage += "  edit_vcs_root   VCS_ROOT_ID VCS_ROOT_DEFINITION.yaml\n"
  450.     usage += "  edit_multiple_vcs_root PROJECT_ID VCS_ROOT_DEFINITION.yaml\n"
  451.     usage += "  export_list PROJECT_ID"
  452.  
  453.     parser = optparse.OptionParser(usage=usage)
  454.     parser.add_option('-n', '--dry-run', dest='dry_run',
  455.                       default=False, action='store_true',
  456.                       help='print possible changes without actually applying them')
  457.     parser.add_option('-f', '--force', dest='force_run',
  458.                       default=False, action='store_true',
  459.                       help='apply changes forcefully')
  460.     parser.add_option('-i', '--interactive', dest='interactive',
  461.                       default=False, action='store_true',
  462.                       help='ask before applying changes')
  463.     parser.add_option('-d', '--debug', dest='enable_debug',
  464.                       default=False, action='store_true',
  465.                       help='enable debugging output')
  466.  
  467.     opts, args = parser.parse_args()
  468.     if len(args) == 0:
  469.         exit_with_error(parser, 0)
  470.  
  471.     if opts.dry_run:
  472.         global TEAMCITY_DRY_RUN
  473.         TEAMCITY_DRY_RUN = True
  474.     if opts.force_run:
  475.         global TEAMCITY_FORCE_RUN
  476.         TEAMCITY_FORCE_RUN = True
  477.     if opts.interactive:
  478.         global TEAMCITY_INTERACTIVE
  479.         TEAMCITY_INTERACTIVE = True
  480.     if opts.enable_debug:
  481.         enable_debug()
  482.  
  483.     set_credentials()
  484.  
  485.     subcommand = args[0]
  486.  
  487.     if   subcommand == "list_projects":
  488.         list_projects()
  489.     elif subcommand == "list_project":
  490.         try:
  491.             list_project(args[1])
  492.         except IndexError:
  493.             exit_with_error(parser, 1)
  494.     elif subcommand == "list_vcs_root":
  495.         try:
  496.             list_vcs_root(args[1])
  497.         except IndexError:
  498.             exit_with_error(parser, 1)
  499.     elif subcommand == "add_feature":
  500.         try:
  501.             add_feature_to_project_with_build_type_regex(args[1], args[2], args[3])
  502.         except IndexError:
  503.             try:
  504.                 add_feature_to_project_or_build(args[1], args[2])
  505.             except IndexError:
  506.                 exit_with_error(parser, 1)
  507.     elif subcommand == "del_feature":
  508.         print("TODO")
  509.     elif subcommand == "edit_feature":
  510.         print("TODO")
  511.     elif subcommand == "edit_vcs_root":
  512.         try:
  513.             set_unique_vcs_root(args[1], args[2])
  514.         except IndexError:
  515.             exit_with_error(parser, 1)
  516.     elif subcommand == "edit_multiple_vcs_root":
  517.         try:
  518.             set_multiple_vcs_root(args[1], args[2])
  519.         except IndexError:
  520.             exit_with_error(parser, 1)
  521.     elif subcommand == "export_list":
  522.         try:
  523.             export_list(args[1])
  524.         except IndexError:
  525.             exit_with_error(parser, 1)
  526.     else:
  527.         exit_with_error(parser, 1)
  528.  
  529. if __name__ == "__main__":
  530.    main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement