1+ # --------------------------------------------------------------------------------------------
2+ # Copyright (c) Microsoft Corporation. All rights reserved.
3+ # Licensed under the MIT License. See License.txt in the project root for license information.
4+ # --------------------------------------------------------------------------------------------
5+
6+ import os
7+ import argparse
8+ import requests
9+ import json
10+ import logging
11+ from github import Github , Auth
12+ from ci_tools .functions import discover_targeted_packages
13+
14+ logging .getLogger ().setLevel (logging .INFO )
15+ root_dir = os .path .abspath (os .path .join (os .path .abspath (__file__ ), ".." , ".." , ".." , ".." ))
16+
17+ def get_build_info (service_directory : str , package_name : str ) -> str :
18+ """Get the build info from the build link."""
19+ build_id = os .getenv ("BUILD_BUILDID" )
20+ timeline_link = f"https://dev.azure.com/azure-sdk/internal/_apis/build/builds/{ build_id } /timeline?api-version=6.0"
21+
22+ token = os .environ ["SYSTEM_ACCESSTOKEN" ]
23+ AUTH_HEADERS = {"Authorization" : f"Bearer { token } " }
24+
25+ try :
26+ # Make the API request
27+ response = requests .get (timeline_link , headers = AUTH_HEADERS )
28+ response_json = json .loads (response .text )
29+
30+ for task in response_json ["records" ]:
31+ if "Run Pylint Next" in task ["name" ]:
32+ log_link = task ['log' ]['url' ] + "?api-version=6.0"
33+ # Get the log file from the build link
34+ log_output = requests .get (log_link , headers = AUTH_HEADERS )
35+ build_output = log_output .content .decode ("utf-8" )
36+ new_output = (build_output .split (f"next-pylint: commands[3]> python /mnt/vss/_work/1/s/eng/tox/run_pylint.py -t { service_directory } --next=True" )[1 ]).split (f"ERROR:root:{ package_name } exited with linting error" )[0 ]
37+ return new_output
38+ except Exception as e :
39+ logging .error (f"Exception occurred while getting build info: { e } " )
40+ return "Error getting build info"
41+
42+
43+
44+ def main (targeted_packages ):
45+ for package in targeted_packages :
46+ # iterate through the packages and find if there is an issue associated with the package
47+ # there may be multiple packages in the targeted_packages list
48+ logging .info (f"Processing package: { package } " )
49+
50+ # update open GitHub issues matching the package pattern
51+ auth = Auth .Token (os .environ ["GH_TOKEN" ])
52+ g = Github (auth = auth )
53+ repo = g .get_repo ("Azure/azure-sdk-for-python" )
54+
55+ package_name = package .split ("/" )[- 1 ]
56+ logging .info (f"Searching for issues in repo { repo .name } for package { package_name } " )
57+
58+ for issue in repo .get_issues (state = "open" , labels = ["pylint" ], creator = "azure-sdk" ):
59+ if f"{ package_name } needs linting updates for pylint version" in issue .title :
60+ logging .info (f"Found issue for package { package_name } : { issue .title } " )
61+ build_info = get_build_info (package , package_name )
62+
63+ template = (
64+ f"\n \n **Pylint Errors:**"
65+ f"\n \n { build_info } "
66+ f"\n "
67+ f"\n \n **How to fix:**"
68+ f"\n **Note:** If you are a bot. Fix the issue by fixing the above pylint errors."
69+ )
70+ if issue .body :
71+ first_section = issue .body .split ("**How to fix:**" )[0 ]
72+ new_body = first_section + template + "\n " + issue .body .split ("**How to fix:**" )[1 ]
73+ issue .edit (body = new_body )
74+ logging .info (f"Updated issue #{ issue .number } for package { package_name } " )
75+
76+ if __name__ == "__main__" :
77+ parser = argparse .ArgumentParser (
78+ description = """
79+ This script is the single point for all checks invoked by CI within this repo. It works in two phases.
80+ 1. Identify which packages in the repo are in scope for this script invocation, based on a glob string and a service directory.
81+ 2. Invoke one or multiple `tox` environments for each package identified as in scope.
82+
83+ In the case of an environment invoking `pytest`, results can be collected in a junit xml file, and test markers can be selected via --mark_arg.
84+ """
85+ )
86+
87+ parser .add_argument (
88+ "glob_string" ,
89+ nargs = "?" ,
90+ help = (
91+ "A comma separated list of glob strings that will target the top level directories that contain packages."
92+ 'Examples: All = "azure-*", Single = "azure-keyvault", Targeted Multiple = "azure-keyvault,azure-mgmt-resource"'
93+ ),
94+ )
95+
96+ parser .add_argument ("--disablecov" , help = ("Flag. Disables code coverage." ), action = "store_true" )
97+
98+ parser .add_argument (
99+ "--service" ,
100+ help = ("Name of service directory (under sdk/) to test. Example: --service applicationinsights" ),
101+ )
102+
103+ parser .add_argument (
104+ "-w" ,
105+ "--wheel_dir" ,
106+ dest = "wheel_dir" ,
107+ help = "Location for prebuilt artifacts (if any)" ,
108+ )
109+
110+ parser .add_argument (
111+ "-i" ,
112+ "--injected-packages" ,
113+ dest = "injected_packages" ,
114+ default = "" ,
115+ help = "Comma or space-separated list of packages that should be installed prior to dev_requirements. If local path, should be absolute." ,
116+ )
117+
118+ parser .add_argument (
119+ "--filter-type" ,
120+ dest = "filter_type" ,
121+ default = "Build" ,
122+ help = "Filter type to identify eligible packages. for e.g. packages filtered in Build can pass filter type as Build," ,
123+ choices = ["Build" , "Docs" , "Regression" , "Omit_management" , "None" ],
124+ )
125+
126+
127+ args = parser .parse_args ()
128+
129+ # We need to support both CI builds of everything and individual service
130+ # folders. This logic allows us to do both.
131+ if args .service and args .service != "auto" :
132+ service_dir = os .path .join ("sdk" , args .service )
133+ target_dir = os .path .join (root_dir , service_dir )
134+ else :
135+ target_dir = root_dir
136+
137+ logging .info (f"Beginning discovery for { args .service } and root dir { root_dir } . Resolving to { target_dir } ." )
138+
139+ if args .filter_type == "None" :
140+ args .filter_type = "Build"
141+ compatibility_filter = False
142+ else :
143+ compatibility_filter = True
144+
145+ targeted_packages = discover_targeted_packages (
146+ args .glob_string , target_dir , "" , args .filter_type , compatibility_filter
147+ )
148+
149+ if len (targeted_packages ) == 0 :
150+ logging .info (f"No packages collected for targeting string { args .glob_string } and root dir { root_dir } . Exit 0." )
151+ exit (0 )
152+
153+ main (targeted_packages )
0 commit comments