├── .gitignore ├── README.md ├── acl-2018-screenshot.png ├── atlassian_command_line.py ├── config ├── wiki_global_custom_colour_scheme.default └── wiki_global_custom_colour_scheme.dev ├── downloads └── .ignore └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.log 3 | 4 | Atlassian_Command_Line.egg-info/dependency_links.txt 5 | 6 | Atlassian_Command_Line.egg-info/entry_points.txt 7 | 8 | Atlassian_Command_Line.egg-info/PKG-INFO 9 | 10 | Atlassian_Command_Line.egg-info/requires.txt 11 | 12 | Atlassian_Command_Line.egg-info/SOURCES.txt 13 | 14 | Atlassian_Command_Line.egg-info/top_level.txt 15 | 16 | Test.py 17 | 18 | downloads/ 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Automate Atlassian from Command Line (ACL) 2 | Entry to _Atlassian Codegeist Hackathon 2015_: http://devpost.com/software/atlassian-command-line 3 | 4 | screen shot 2015-10-24 at 10 59 58 pm 5 | 6 | ## What it does 7 | We are going to use Python, Selenium along with Chrome Browser to just automate anything that you like. 8 | > * Automate On-premise JIRA / Confluence instances. 9 | > * Use headless browser such as Chrome to achieve automation at regular intervals 10 | > * Automate administration / user tasks across all Atlassian applications (current focus limited to JIRA and Confluence though) 11 | > * Automate multiple actions from single command _(just add --action = x, --action = y)_ 12 | 13 | #### JIRA Tasks Automation 14 | _(--app-name = Jira)_ 15 | * Disable notification schemes for all projects **--action = disable_project_notification_schemes** 16 | * Mail Queue health check **--action = check_jira_mail_queue_status** 17 | * LDAP sync status **--action = check_ldap_sync_status** 18 | * Download attachments for all issues as per JQL **--action = get_jira_attachments** 19 | * Add / update list of options associated to Selectfield (Will not be available, instead use Customfield Editor plugin for Jira) 20 | 21 | #### Confluence Automation 22 | _(--app-name = Confluence)_ 23 | * Update Global color scheme **--action = update_global_color_scheme** 24 | * Update color scheme for all Wiki spaces **--action = update_wiki_spaces_color_scheme** 25 | * Update general configuration **--action = update_general_configuration** 26 | 27 | # Usage instructions 28 | ## How to use this Add On 29 | * Clone source from [Atlassian Command Line](https://github.com/rkadam/atlassian_command_line) git repository 30 | * Install WebDriver for Chrome [ChromeDriver](http://chromedriver.chromium.org/downloads) 31 | * Install [Virtual Environment](http://docs.python-guide.org/en/latest/dev/virtualenvs/) 32 | * create virtual environment for python 2.7+ -> _virtualenv -p /usr/bin/python2.7 venv_ 33 | * Install "Atlassian Command Line" as a python module -> _pip install --editable ._ 34 | * _Run_ **Atlassian Command Line** from command line as follows: 35 | 36 | ``` 37 | > acl --help 38 | This will provide detail information on all parameters available and how to use ACL 39 | ``` 40 | **Examples** 41 | 42 | ``` 43 | > acl 44 | 45 | * In above case, ACL will use all default values as mentioned below: 46 | --app-type=on-premise, --app-name=Confluence --browser-name=Chrome --mail-threshold-limi=100 --ldap-sync-threshold-limit=4 --download-dir=./downloads --wiki-global-color-scheme-file=wiki_global_custom_colour_scheme.default 47 | ``` 48 | 49 | ``` 50 | -- Automate on-premise Confluence Wiki ( Update Global Color Scheme ) 51 | > acl --base-url https://example.com/wiki --userid admin --password admin --action 'update_global_color_scheme' 52 | ``` 53 | ``` 54 | -- Automate Global color scheme updates, all wiki spaces color scheme updates, update general configuration (just "title" change right now) of Confluence application 55 | > acl --base-url https://example.com/wiki --action 'update_global_color_scheme' --action 'update_wiki_spaces_color_scheme' --action 'update_general_configuration' --userid --password 56 | ``` 57 | 58 | ``` 59 | - Automate the act of disabling notification schemes for all JIRA projects 60 | > acl --base-url https://example.com/jira --userid admin --action 'disable_project_notification_schemes' --app-name JIRA --password 61 | ``` 62 | 63 | ``` 64 | - Automate the act of downloading all attachments as per JQL 65 | - default JQL = created = now() 66 | > acl --app-name Jira --action --base-url https://jira.example.com get_jira_attachments --userid admin --password pongbot --jql "key=TEST-1" 67 | 68 | -- Setting up new location for jira attachments download 69 | > acl --app-name Jira --action --base-url https://jira.example.com get_jira_attachments --userid admin --password pongbot --jql "key=TEST-1" --download-dir "/tmp" 70 | ``` 71 | 72 | ``` 73 | - Automate JIRA Outgoing Mail Queue check 74 | > acl --base-url https://jira.example.com --userid admin --app-name JIRA --password 'password' --action 'check_jira_mail_queue_status' --mail_threshold_limit 50 75 | ``` 76 | 77 | ``` 78 | - Automate JIRA LDAP Sync Status check and Outgoing mail queue check 79 | - ACL will warn user if LDAP sync status time is greater than default LDAP Threshold limit (4 hours) and Mails in Mail queue are greater than default Mail threshold limit (100 emails) 80 | > acl --base-url https://jira.example.com --userid --app-name JIRA --password 'password' --action 'check_jira_mail_queue_status' --action 'check_ldap_sync_status' 81 | ``` 82 | 83 | ``` 84 | - Automate JIRA LDAP Sync Status check and Outgoing mail queue check against given threshold values 85 | - ACL will warn user if LDAP sync status time is greater than given LDAP Threshold limit (1 hour) and Mails in Mail queue are greater than given Mail threshold limit (50 emails) 86 | >acl --base-url https://jira.example.com --userid --app-name JIRA --password --action 'check_jira_mail_queue_status' --mail-threshold-limit 50 --action 'check_ldap_sync_status' --ldap-sync-threshold-limit 1 87 | ``` 88 | **Notes**: 89 | >* Uses _config/wiki_global_custom_colour_scheme.default_ as color scheme file input. Update colors as required. 90 | > * If mandatory input parameters are not provided (such as userid and password) from command line, application will prompt for these values before start of each run. 91 | -------------------------------------------------------------------------------- /acl-2018-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkadam/automate_atlassian_command_line/f93a927156813d371155d156ebc1500cc9f4ef0a/acl-2018-screenshot.png -------------------------------------------------------------------------------- /atlassian_command_line.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Raju Kadam' 2 | 3 | from selenium import webdriver 4 | from selenium.webdriver import * 5 | from selenium.common.exceptions import NoSuchElementException 6 | from selenium.webdriver.common.by import By 7 | from selenium.webdriver.support.ui import WebDriverWait 8 | from selenium.webdriver.support import expected_conditions as EC 9 | from selenium.webdriver.common.keys import Keys 10 | from selenium.webdriver.support.ui import Select 11 | from selenium.webdriver.chrome.options import Options 12 | 13 | import click 14 | import random 15 | import os 16 | 17 | import time 18 | import datetime 19 | import sys 20 | import traceback 21 | 22 | import requests 23 | import shutil 24 | 25 | # Disable warnings about not verifying SSL access. 26 | requests.packages.urllib3.disable_warnings() 27 | 28 | header_params = {"content-type": "application/json"} 29 | 30 | # TODO: 31 | # Current priority is to have working entry available for Codegeist participation. 32 | # Later we will use Composition to share the common methods and let individual classes do their distinct work. 33 | # Using Composition, we will keep all common functions such as connect(), get_login_elements(), login(), 34 | # verify_admin_access(), check_ldap_sync_status() in *AtlassianBrowser* (it will be a new class). 35 | # And application specific methods such as 36 | # disable_project_notification_schemes(), check_jira_mail_queue_status() will remain in *JIRABrowser* 37 | # update_global_color_scheme(), update_general_configuration() and update_wiki_spaces_color_scheme() remain in *WikiBrowser* 38 | # Till that happens you will see little bit overlapping between all these classes. 39 | # 40 | # https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment 41 | 42 | class JIRABrowser: 43 | def __init__(self, driver): 44 | self.browser = driver 45 | 46 | def get_login_elements(self, login_to, base_url): 47 | 48 | return { 49 | 'param_user': 'login-form-username', 50 | 'param_password': 'login-form-password', 51 | 'param_submit': 'login-form-submit', 52 | 'param_login_url': base_url + '/login.jsp', 53 | 'param_new_base_url': base_url 54 | } 55 | 56 | # noinspection PyBroadException 57 | def login(self, login_type, base_url, userid, password): 58 | browser = self.browser 59 | login_elem_dict = self.get_login_elements(login_type, base_url) 60 | #click.echo(login_elem_dict) 61 | new_base_url = None 62 | 63 | if not self.verify_admin_access(): 64 | try: 65 | browser.get(login_elem_dict['param_login_url']) 66 | browser.implicitly_wait(1) 67 | os_name = browser.find_element_by_id(login_elem_dict['param_user']) 68 | os_name.clear() 69 | os_name.send_keys(userid) 70 | 71 | os_password = browser.find_element_by_id(login_elem_dict['param_password']) 72 | os_password.clear() 73 | os_password.send_keys(password) 74 | 75 | browser.find_element_by_id(login_elem_dict['param_submit']).click() 76 | time.sleep(1) 77 | 78 | #click.echo('Login as Admin user') 79 | 80 | # On Premise Atlassian application usually asks Authentication for one more time. 81 | new_base_url = login_elem_dict['param_new_base_url'] 82 | browser.get(new_base_url + "/secure/admin/ViewApplicationProperties.jspa") 83 | if login_type == 'on-premise': 84 | browser.find_element_by_id('login-form-authenticatePassword').send_keys(password) 85 | browser.find_element_by_id('login-form-submit').click() 86 | 87 | # Verify that we are on Administration Console. 88 | # This will confirm, we are logged in as a Global Administrator. 89 | assert browser.find_element_by_id('maximumAuthenticationAttemptsAllowed').text.startswith('Maximum Authentication Attempts Allowed') 90 | 91 | except NoSuchElementException: 92 | click.echo("Unable to login to Jira Application, exiting.") 93 | traceback.print_exc(file=sys.stdout) 94 | browser.close() 95 | browser.quit() 96 | sys.exit(0) 97 | 98 | return browser, new_base_url 99 | 100 | def verify_admin_access(self): 101 | browser = self.browser 102 | try: 103 | browser.implicitly_wait(1) 104 | browser.find_element_by_id("system-admin-menu") 105 | return True 106 | except NoSuchElementException: 107 | return False 108 | 109 | def get_jira_project_list(self, base_url, userid, password): 110 | jira_project_list_rest_url = base_url + "/rest/api/2/project" 111 | result = requests.get(jira_project_list_rest_url, headers=header_params, auth=(userid, password), verify=False) 112 | result.raise_for_status() 113 | 114 | result_len = len(result.json()) 115 | 116 | project_id_dict = {} 117 | for i in range(0, result_len): 118 | project_id_dict[result.json()[i]['key']] = result.json()[i]['id'] 119 | #click.echo(result.json()[i]['key'] + ":" + result.json()[i]['id']) 120 | 121 | return project_id_dict 122 | 123 | def disable_project_notification_schemes(self, browser, base_url, userid, password): 124 | project_notification_url = base_url + '/secure/project/SelectProjectScheme!default.jspa?projectId=%s' 125 | 126 | project_dict = self.get_jira_project_list(base_url, userid, password) 127 | for project_key, project_id in project_dict.iteritems(): 128 | browser.get(project_notification_url % project_id) 129 | scheme_dropdown_element = Select(browser.find_element_by_id('schemeIds_select')) 130 | current_selected_option = scheme_dropdown_element.first_selected_option 131 | current_notification_scheme_name = current_selected_option.text.strip() 132 | if current_notification_scheme_name != 'None': 133 | scheme_dropdown_element.select_by_visible_text('None') 134 | browser.find_element_by_id('associate_submit').click() 135 | click.echo('For Project "%s", Notification Scheme changed from "%s" to None' % (project_key, current_notification_scheme_name)) 136 | 137 | def check_jira_mail_queue_status (self, browser, base_url, mail_threshold_limit): 138 | click.echo(" Override default mail-threshold-limit (100 emails in queue) if necessary.") 139 | click.echo("---") 140 | mail_queue_url = base_url + '/secure/admin/MailQueueAdmin!default.jspa' 141 | 142 | # Visit Mail Queue page 143 | browser.get(mail_queue_url) 144 | current_queue_status_text = browser.find_element_by_class_name('jiraformbody').text 145 | current_email_in_queue_count = current_queue_status_text.strip().split()[4] 146 | if int(current_email_in_queue_count) > mail_threshold_limit: 147 | # TODO: Send Email to Admins 148 | click.echo('Emails Queued in Jira : %s' % current_email_in_queue_count) 149 | click.echo('Emails are piling in Jira Mail queue. Please have a look at earliest') 150 | else: 151 | click.echo('All is well at Mail Queue!') 152 | 153 | def get_jira_attachments(self, browser, base_url, userid, password, jql, download_dir): 154 | click.echo(" Override default values to jql (created=now()) and download-dir (./downloads) if necessary.") 155 | click.echo("---") 156 | 157 | auth = (userid, password) 158 | 159 | #jira_search_rest_url = base_url + "/rest/api/2/search?" + urllib.urlencode(jql) +"&fields=attachment" 160 | jira_search_rest_url = base_url + "/rest/api/2/search?jql=" + requests.utils.quote(jql) +"&fields=attachment" 161 | 162 | #click.echo(jira_search_rest_url) 163 | 164 | issue_starting_index = 0 165 | total_issue_entries_available = 100 166 | issue_limit_per_fetch = 50 167 | 168 | while issue_starting_index < total_issue_entries_available: 169 | updated_jira_search_rest_url = jira_search_rest_url + "&startAt=" + str(issue_starting_index) + "&maxResults=" + str(issue_limit_per_fetch) 170 | #click.echo(updated_jira_search_rest_url) 171 | 172 | search_result = requests.get(updated_jira_search_rest_url, headers=header_params, auth=auth, verify=False) 173 | search_result.raise_for_status() 174 | 175 | result_issue_entries = search_result.json()["issues"] 176 | #click.echo(result_issue_entries) 177 | result_issue_count_fetch_in_this_iteration = len(result_issue_entries) 178 | total_issue_entries_available = search_result.json()["total"] 179 | 180 | click.echo("Starting Index - " + str(issue_starting_index) + ", Issues fetched in this iteration - " + str(result_issue_count_fetch_in_this_iteration) 181 | + ", Total Issues to be fetched - " + str(total_issue_entries_available)) 182 | for i in range(0, result_issue_count_fetch_in_this_iteration): 183 | # Get Attachment info. 184 | if 'fields' in result_issue_entries[i] and 'attachment' in result_issue_entries[i]['fields']: 185 | attachment_info = result_issue_entries[i]['fields']['attachment'] 186 | if attachment_info != None: 187 | total_attachments = len(attachment_info) 188 | for attach_index in range(0, total_attachments): 189 | click.echo("Downloading attachment - " + attachment_info[attach_index]['content'] + " for Issue: " + result_issue_entries[i]['key']) 190 | 191 | attachment_response = requests.get(attachment_info[attach_index]['content'], auth=auth, stream=True) 192 | attachment_response.raise_for_status() 193 | with open(download_dir + "/" + attachment_info[attach_index]['filename'], 'wb') as f: 194 | attachment_response.raw.decode_content = True 195 | shutil.copyfileobj(attachment_response.raw, f) 196 | 197 | issue_starting_index = search_result.json()['startAt'] + issue_limit_per_fetch 198 | 199 | def check_ldap_sync_status(self, browser, base_url, ldap_sync_threshold_limit): 200 | click.echo(" Override default ldap-sync-threshold-limit (4) hours if necessary.") 201 | click.echo("---") 202 | 203 | # If last LDAP sync happened more than ldap_sync_threshold_limit hours ago, warn Jira Admin 204 | ldap_sync_status_url = base_url + '/plugins/servlet/embedded-crowd/directories/list' 205 | browser.get(ldap_sync_status_url) 206 | 207 | # Get last successful SYNC time information. Example: Last synchronised at 7/16/15 9:52 AM (took 25s) 208 | try: 209 | ldap_sync_info_element = browser.find_element_by_class_name('sync-info') 210 | 211 | ldap_sync_status_string_aray = browser.find_element_by_class_name('sync-info').text.strip().split() 212 | last_successful_sync_status_time = '%s %s %s' % (ldap_sync_status_string_aray[3], ldap_sync_status_string_aray[4], ldap_sync_status_string_aray[5]) 213 | click.echo('Last Successful Sync Status Time: %s' % last_successful_sync_status_time) 214 | 215 | # Do arithmetic to find out how many hours before this sync happened. 216 | last_sync_datetime = datetime.datetime.strptime(last_successful_sync_status_time, "%m/%d/%y %I:%M %p") 217 | current_daytime = datetime.datetime.now() 218 | time_delta = current_daytime - last_sync_datetime 219 | hours, minutes, seconds = self.convert_timedelta(time_delta) 220 | sync_status_message = 'Time elapsed since last LDAP sync: {} hour(s), {} minute(s)'.format(hours, minutes) 221 | click.echo(sync_status_message) 222 | 223 | if hours > ldap_sync_threshold_limit: 224 | click.echo("Something is wrong with LDAP sync process. Please verify at your earliest your convenience.") 225 | 226 | except NoSuchElementException, e: 227 | click.echo('Looks like you are not using LDAP or Active Directory! Nothing much to do here...') 228 | except Exception,e: 229 | click.echo(e) 230 | 231 | def convert_timedelta(self, duration): 232 | days, seconds = duration.days, duration.seconds 233 | hours = days * 24 + seconds // 3600 234 | minutes = (seconds % 3600) // 60 235 | seconds = (seconds % 60) 236 | return hours, minutes, seconds 237 | 238 | command_dictionary = { 239 | 'disable_project_notification_schemes': disable_project_notification_schemes, 240 | 'check_jira_mail_queue_status': check_jira_mail_queue_status, 241 | 'check_ldap_sync_status': check_ldap_sync_status, 242 | 'get_jira_attachments': get_jira_attachments 243 | } 244 | 245 | 246 | class WikiBrowser: 247 | def __init__(self, driver): 248 | self.browser = driver 249 | 250 | # noinspection PyBroadException 251 | def login(self, login_type, base_url, userid, password): 252 | browser = self.browser 253 | 254 | login_elem_dict = self.get_login_elements(login_type, base_url) 255 | #click.echo(login_elem_dict) 256 | 257 | new_base_url = None 258 | 259 | if not self.verify_admin_access(): 260 | try: 261 | browser.get(login_elem_dict['param_login_url']) 262 | browser.implicitly_wait(1) 263 | os_name = browser.find_element_by_id(login_elem_dict['param_user']) 264 | os_name.clear() 265 | os_name.send_keys(userid) 266 | 267 | if login_type == 'atlassian.net': 268 | submit = browser.find_element_by_id('login-submit') 269 | submit.click() 270 | time.sleep(2) 271 | 272 | os_password = browser.find_element_by_id(login_elem_dict['param_password']) 273 | os_password.clear() 274 | os_password.send_keys(password) 275 | 276 | browser.find_element_by_id(login_elem_dict['param_submit']).click() 277 | time.sleep(1) 278 | 279 | # On Premise Atlassian application usually asks for Authentication for one more time. 280 | new_base_url = login_elem_dict['param_new_base_url'] 281 | browser.get(new_base_url + "/admin/viewgeneralconfig.action") 282 | if login_type == 'on-premise': 283 | browser.find_element_by_id('password').send_keys(password) 284 | browser.find_element_by_id('authenticateButton').click() 285 | 286 | # Verify that we are on Administration Console. 287 | # This will confirm, we are logged in as a Global Administrator. 288 | assert browser.find_element_by_class_name('admin-heading').text == 'General Configuration' 289 | assert browser.find_element_by_id('editbaseurl-label').text == 'Server Base URL' 290 | 291 | except NoSuchElementException: 292 | click.echo("Unable to login to Wiki Application, exiting.") 293 | traceback.print_exc(file=sys.stdout) 294 | browser.close() 295 | sys.exit(0) 296 | 297 | return browser, new_base_url 298 | 299 | def get_login_elements(self, login_to, base_url): 300 | 301 | return { 302 | 'param_user': 'os_username', 303 | 'param_password': 'os_password', 304 | 'param_submit': 'loginButton', 305 | 'param_login_url': base_url + '/login.action', 306 | 'param_new_base_url': base_url 307 | } 308 | 309 | def update_general_configuration(self, browser, new_base_url): 310 | click.echo("Right now it just changes siteTitle value to 'Pongbot\'s confluence ") 311 | click.echo("Future we will provide configuration file to update all general configuration values.") 312 | click.echo("Future is Bright, just stay tight!") 313 | click.echo("---") 314 | 315 | general_config_url = new_base_url + "/admin/editgeneralconfig.action" 316 | browser.get(general_config_url) 317 | site_title = browser.find_element_by_id('siteTitle') 318 | click.echo('Current title: %s' % site_title.get_attribute("value")) 319 | site_title.clear() 320 | site_title.send_keys('Pongbot\'s confluence %s' % str(random.randint(1,10))) 321 | click.echo('New title: %s' % site_title.get_attribute("value")) 322 | browser.find_element_by_id('confirm').click() 323 | 324 | def update_global_color_scheme(self, browser, new_base_url, new_color_scheme_file): 325 | # Let's get to "View Colour Scheme Settings" screen (lookandfeel.action) 326 | click.echo("Update default color values from file config/wiki_global_custom_colour_scheme.default if necessary") 327 | click.echo("---") 328 | custom_colour_scheme_url = new_base_url + "/admin/lookandfeel.action" 329 | browser.get(custom_colour_scheme_url) 330 | browser.find_element_by_id("edit-scheme-link").click() 331 | time.sleep(2) 332 | WebDriverWait(browser, 10).until(EC.presence_of_element_located((By.NAME, "cancel"))) 333 | 334 | # Clicking "Edit" link above makes all hidden elements in div "edit-scheme" visible for FireFox, Chrome. 335 | # Let's make sure these hidden elements are visible for PhantomJS browser too. 336 | browser.execute_script("document.getElementById('edit-scheme').style.display='block'") 337 | 338 | global_custom_color_scheme_dict = {} 339 | with open(new_color_scheme_file) as colour_scheme_file: 340 | for line in colour_scheme_file: 341 | colour_name, colour_value = line.partition("=")[::2] 342 | global_custom_color_scheme_dict[colour_name] = colour_value.strip() 343 | 344 | colour_element = None 345 | for colour_name, colour_value in global_custom_color_scheme_dict.iteritems(): 346 | click.echo ('%s , %s' % (colour_name, colour_value)) 347 | colour_element = browser.find_element_by_id(colour_name) 348 | colour_element.clear() 349 | time.sleep(1) 350 | colour_element.send_keys(colour_value) 351 | 352 | browser.find_element_by_name("confirm").click() 353 | 354 | click.echo() 355 | click.echo("Successfully updated global color scheme.") 356 | 357 | def get_wiki_space_list(self, space_type, base_url, userid, password): 358 | spaces = [] 359 | 360 | space_list_rest_url = base_url + ("/rest/api/space?max-results=10000&type=%s" % space_type) 361 | #click.echo(space_list_rest_url) 362 | 363 | result = requests.get(space_list_rest_url, headers=header_params, auth=(userid, password), verify=False) 364 | result.raise_for_status() 365 | 366 | space_list = result.json()['results'] 367 | space_keys = [space['key'] for space in space_list] 368 | return space_keys 369 | 370 | def update_wiki_spaces_color_scheme(self, browser, base_url, userid, password): 371 | # This function will update color scheme for all wiki spaces to global color scheme. 372 | # "global" will return only team wiki spaces 373 | # "personal" will return only personal wiki spaces 374 | # "all" will return all wiki spaces available. 375 | space_keys = self.get_wiki_space_list("global", base_url, userid, password) 376 | #click.echo(space_keys) 377 | 378 | # if needed, Update color scheme for each wiki space 379 | for key in space_keys: 380 | browser.get(base_url + "/spaces/lookandfeel.action?key=" + key) 381 | edit_button = browser.find_element_by_id('edit') 382 | if edit_button.get_attribute('name') == 'global': 383 | click.echo("Global color scheme is now activated for Wiki Space %s" % key) 384 | edit_button.click() 385 | #time.sleep(1) 386 | else: 387 | click.echo("Global color scheme is already selected for space: %s" % key) 388 | 389 | def verify_admin_access(self): 390 | browser = self.browser 391 | try: 392 | browser.implicitly_wait(1) 393 | browser.find_element_by_id("system-admin-menu") 394 | return True 395 | except NoSuchElementException: 396 | return False 397 | 398 | command_dictionary = { 399 | 'update_global_color_scheme': update_global_color_scheme, 400 | 'update_general_configuration': update_general_configuration, 401 | 'update_wiki_spaces_color_scheme': update_wiki_spaces_color_scheme 402 | } 403 | 404 | 405 | @click.command() 406 | # General Parameters needed for Atlassian Command Line use. 407 | @click.option('--app-type', type=click.Choice(['on-premise']), 408 | default='on-premise', help='->Default: on-premise<-') 409 | @click.option('--app-name', type=click.Choice(['Confluence', 'Jira', 'Bitbucket Server']), 410 | default='Confluence', help='->Default: Confluence<-') 411 | #"Chrome" is only supported browser as of now. To use ACL in cronjobs, you need to use Chrome with headless settings. 412 | @click.option('--browser-name', type=click.Choice(['Chrome']), default='Chrome', help='Default: ->Chrome<-') 413 | @click.option('--base-url', prompt='Enter Base URL for Atlassian application' ) 414 | @click.option('--userid', prompt='Enter Administrator Userid') 415 | @click.option('--password', prompt='Enter your credentials', hide_input=True, confirmation_prompt=True) 416 | @click.option('--action', '-a', multiple=True, 417 | help="Available actions for Confluence ->\n 'update_global_color_scheme', 'update_general_configuration', 'update_wiki_spaces_color_scheme' \n" 418 | "---------\n" 419 | "Available actions for Jira ->\n 'check_mail_queue_status', 'disable_all_project_notifications', 'check_ldap_sync_status', 'get_jira_attachments'\n -") 420 | # Parameters for Mail Queue Check 421 | @click.option('--mail-threshold-limit', default=100, help="If emails in queue are greater than this limit, then ACL will alert user. ->Default:100<- , Used in Function: check_mail_queue_status()") 422 | # Parameters for LDAP Sync Status check 423 | @click.option('--ldap-sync-threshold-limit', default=4, help="If last LDAP sync happened more than given 'ldap_sync_threshold_limit' hours, then ACL will alert user. ->Default: 4 (hours)<-, Used in Function: check_ldap_sync_status") 424 | # Parameters for Attachment Download 425 | @click.option('--jql', default='created=now()', help='Enter JQL to get attachments for all Jira tickets. ->Default: created = now()<-, Used in Function: get_jira_attachments') 426 | @click.option('--download-dir', default='./downloads', help='Enter complete path for a directory where you want attachments to be downloaded. ->Default Download Directory=./downloads<-, Used in Function: get_jira_attachments') 427 | @click.option('--chrome-driver-location', prompt='Enter complete path for Chrome Driver', help="Make sure you have downloaded Chrome Driver from http://chromedriver.chromium.org/downloads") 428 | @click.option('--wiki-global-color-scheme-file', default='wiki_global_custom_colour_scheme.default', help='Provide name of global color scheme config file for Wiki ->Default config file = wiki_global_custom_colour_scheme.default<-') 429 | def start(app_type, app_name, browser_name, base_url, userid, 430 | password, action, mail_threshold_limit, ldap_sync_threshold_limit, 431 | jql, download_dir, chrome_driver_location, wiki_global_color_scheme_file): 432 | """ 433 | 'Atlassian Command Line' aka ACL - Automate the tasks which you can not! 434 | """ 435 | """ 436 | :param string: 437 | :return: 438 | """ 439 | click.echo() 440 | # Remove forward slash from user if user entered in base_url 441 | base_url = base_url.rstrip('/') 442 | click.echo('Automating application located at %s' % base_url) 443 | click.echo() 444 | 445 | chrome_options = Options() 446 | chrome_options.add_argument("--headless") 447 | chrome_options.add_argument("--window-size=1366x768") 448 | chrome_driver = chrome_driver_location 449 | web_driver = webdriver.Chrome(chrome_options=chrome_options, executable_path=chrome_driver) 450 | 451 | if app_name == 'Confluence': 452 | 453 | wiki_browser = WikiBrowser(web_driver) 454 | 455 | (browser, new_base_url) = wiki_browser.login(app_type, base_url, userid, password) 456 | 457 | for act in action: 458 | click.echo('Executing Confluence command: %s' % act) 459 | if act == 'update_global_color_scheme': 460 | wiki_browser.command_dictionary[act](wiki_browser, browser, new_base_url, "./config/" + wiki_global_color_scheme_file) 461 | 462 | if act == 'update_general_configuration': 463 | wiki_browser.command_dictionary[act](wiki_browser, browser, new_base_url) 464 | 465 | if act == 'update_wiki_spaces_color_scheme': 466 | wiki_browser.command_dictionary[act](wiki_browser, browser, new_base_url, userid, password) 467 | 468 | click.echo() 469 | 470 | browser.close() 471 | browser.quit() 472 | 473 | if app_name == 'Jira': 474 | jira_browser = JIRABrowser(web_driver) 475 | (browser, new_base_url) = jira_browser.login(app_type, base_url, userid, password) 476 | 477 | for act in action: 478 | click.echo('Executing Jira command: %s' % act) 479 | if act == 'disable_project_notification_schemes': 480 | jira_browser.command_dictionary[act](jira_browser, browser, new_base_url, userid, password) 481 | 482 | if act == 'check_jira_mail_queue_status': 483 | jira_browser.command_dictionary[act](jira_browser, browser, new_base_url, mail_threshold_limit) 484 | 485 | if act == 'check_ldap_sync_status': 486 | jira_browser.command_dictionary[act](jira_browser, browser, new_base_url, ldap_sync_threshold_limit) 487 | 488 | if act == 'get_jira_attachments': 489 | jira_browser.command_dictionary[act](jira_browser, browser, new_base_url, userid, password, jql, download_dir) 490 | 491 | click.echo() 492 | 493 | browser.close() 494 | browser.quit() -------------------------------------------------------------------------------- /config/wiki_global_custom_colour_scheme.default: -------------------------------------------------------------------------------- 1 | property.style.topbarcolour=#cc3951 2 | property.style.breadcrumbstextcolour=#ffffff 3 | property.style.headerbuttonbasebgcolour=#e657b9 4 | property.style.headerbuttontextcolour=#ffffff 5 | property.style.topbarmenuselectedbgcolour=#e657b9 6 | property.style.topbarmenuselectedtextcolour=#ffffff 7 | property.style.topbarmenuitemtextcolour=#333333 8 | property.style.menuitemselectedbgcolour=#e657b9 9 | property.style.menuitemselectedtextcolour=#ffffff 10 | property.style.menuselectedbgcolour=#e657b9 11 | property.style.menuitemtextcolour=#333333 12 | property.style.searchfieldbgcolour=#cc3951 13 | property.style.searchfieldtextcolour=#cEEB22 14 | property.style.headingtextcolour=#333333 15 | property.style.linkcolour=#e657b9 16 | property.style.bordercolour=#cccccc -------------------------------------------------------------------------------- /config/wiki_global_custom_colour_scheme.dev: -------------------------------------------------------------------------------- 1 | property.style.topbarcolour=#ff3951 2 | property.style.breadcrumbstextcolour=#ffffff 3 | property.style.headerbuttonbasebgcolour=#f657b9 4 | property.style.headerbuttontextcolour=#ffffff 5 | property.style.topbarmenuselectedbgcolour=#f657b9 6 | property.style.topbarmenuselectedtextcolour=#ffffff 7 | property.style.topbarmenuitemtextcolour=#f33333 8 | property.style.menuitemselectedbgcolour=#f657b9 9 | property.style.menuitemselectedtextcolour=#ffffff 10 | property.style.menuselectedbgcolour=#f657b9 11 | property.style.menuitemtextcolour=#333333 12 | property.style.searchfieldbgcolour=#ff3951 13 | property.style.searchfieldtextcolour=#FEEB22 14 | property.style.headingtextcolour=#333333 15 | property.style.linkcolour=#f657b9 16 | property.style.bordercolour=#cccccc -------------------------------------------------------------------------------- /downloads/.ignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkadam/automate_atlassian_command_line/f93a927156813d371155d156ebc1500cc9f4ef0a/downloads/.ignore -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Raju Kadam' 2 | 3 | from setuptools import setup 4 | 5 | setup( 6 | name="Atlassian Command Line", 7 | version="0.1", 8 | py_modules=['atlassian_command_line'], 9 | install_requires=['Click', 'requests', 'selenium'], 10 | entry_points=''' 11 | [console_scripts] 12 | acl=atlassian_command_line:start 13 | ''' 14 | ) --------------------------------------------------------------------------------