├── .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 |
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 | )
--------------------------------------------------------------------------------