James Ridgway - Software Engineer

Re-ordering environments in Bamboo deployment projects

Using Selenium web driver in Python to more efficiently re-order environments in an Atlassian Bamboo deployment project

In Atlassian's Bamboo you can re-order the environments in a deployment project by clicking Edit, Actions and then Move Up or Move Down.

file

When you've got a lot of environments, trying to re-order them takes a long time and it's very monotonous.

From past attempts to automate other aspects of Bamboo I've found the Bamboo APIs to be limited in comparison to what you can do through the UI.

For this problem I put together a fairly simple Python script using Selenium webdriver to automate the process of re-ordering environments.

Prerequisites

  1. You need to be using Python 3+
  2. Install the selenium python package using pip
  3. Download ChromeDriver and place this on your PATH

The Script

This script will use Selenium to launch an instance of Chrome via the ChromeDriver. Using the driver the script will interact with the page performing the re-ordering task that you'd otherwise have to do manually.

I tested this script using Bamboo 6.3.2

bamboo_deployment_ordering.py
import os
import re
import urllib
from argparse import ArgumentParser
from time import sleep

from selenium import webdriver
from selenium.webdriver import ActionChains


class DeploymentProject:
    def __init__(self, driver, base_url, url):
        self.driver = driver
        self.base_url = base_url
        self.url = url
        self.driver.get(self.url)
        self.__load()

    def sort_environments(self):
        while not self.__sort_environments():
            print('Sorting')

    def __sort_environments(self):
        for deploy_env in self.environments:
            diff = deploy_env.sorted_position - deploy_env.current_position
            if diff == 0:
                print("%s - has the correct order" % deploy_env.name)
            elif diff > 0:
                deploy_env.move_down()
                self.__load()
                return False
            else:
                deploy_env.move_up()
                self.__load()
                return False
        return True

    def __load(self):
        self.environments = []
        current_position = 1
        for project_block in self.driver.find_elements_by_css_selector("li.configure-project-environment"):
            env_name = re.sub(r"^Environment: ", "", project_block.find_element_by_tag_name('h3').text)
            env_id = int(re.sub(r"configure-project-environment-", "", project_block.get_attribute("id")))
            self.__add_environment(DeployEnvironment(self.driver, env_name, env_id, current_position))
            current_position += 1
        self.environments = sorted(self.environments, key=lambda env: env.name)
        sorted_pos = 1
        for environment in self.environments:
            environment.sorted_position = sorted_pos
            sorted_pos += 1

    def __add_environment(self, deploy_environment):
        self.environments.append(deploy_environment)


class DeployEnvironment:
    def __init__(self, driver, name, environment_id, current_position):
        self.name = name
        self.driver = driver
        self.environment_id = environment_id
        self.current_position = current_position
        self.sorted_position = -1
        self.env_block = self.driver.find_element_by_id("configure-project-environment-%d" % self.environment_id)

    def move_down(self):
        print("Moving down - %s (%d)" % (self.name, self.environment_id))
        self.__edit_actions_menu()
        self.driver.find_element_by_partial_link_text('Move down').click()

    def move_up(self):
        print("Moving up - %s (%d)" % (self.name, self.environment_id))
        self.__edit_actions_menu()
        self.driver.find_element_by_partial_link_text('Move up').click()

    def __edit_actions_menu(self):
        actions = ActionChains(self.driver)
        actions.move_to_element(self.env_block).perform()
        self.env_block.find_element_by_css_selector('button.aui-button.aui-button-subtle.toggle').click()
        actions.move_to_element(self.env_block).perform()
        sleep(1)
        self.env_block.find_element_by_css_selector('a.aui-button.aui-dropdown2-trigger.aui-button-subtle').click()
        sleep(1)


class Bamboo:
    def __init__(self, base_url):
        self.base_url = base_url
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(5)
        self.driver.maximize_window()

    def login(self, username, password):
        self.driver.get(self.__url("/userlogin!doDefault.action"))
        username_textbox = self.driver.find_element_by_id("loginForm_os_username")
        password_textbox = self.driver.find_element_by_id("loginForm_os_password")

        username_textbox.send_keys(username)
        password_textbox.send_keys(password)
        password_textbox.submit()

    def logout(self):
        self.driver.get(self.__url("/userLogout.action"))

    def configure_deployment_project(self, deployment_id):
        return DeploymentProject(self.driver, self.base_url,
                                 self.__url("/deploy/config/configureDeploymentProject.action?id=%s" % deployment_id))

    def __url(self, suffix):
        return urllib.parse.urljoin(self.base_url, suffix)

    def close(self):
        self.driver.close()


def main():
    parser = ArgumentParser()
    parser.add_argument('deploy_id', type=str, help='Deployment Project ID')
    parser.add_argument('--server', type=str,
                        default=os.getenv('BAMBOO_URL'), help='Bamboo server url (e.g. https://bamboo.example.com')
    parser.add_argument('--username', type=str,
                        default=os.getenv('BAMBOO_USERNAME'), help='Bamboo username')
    parser.add_argument('--password', type=str,
                        default=os.getenv('BAMBOO_PASSWORD'), help='Bamboo password')

    args = parser.parse_args()

    bamboo = Bamboo(args.server)
    bamboo.login(args.username, args.password)

    deployment_project = bamboo.configure_deployment_project(args.deploy_id)
    deployment_project.sort_environments()

    bamboo.logout()
    bamboo.close()


if __name__ == '__main__':
    main()

Running the script

Run the script providing the address of your bamboo server, credentials and the deploy ID.

The deploy ID is take from the ID parameter of the deployment pages:

/deploy/viewDeploymentProjectEnvironments.action?id=16973826

With the above example the deploy ID is 16973826.

Re-ordering a long list of environments can now be done automatically by running the script:

python bamboo_deployment_ordering.py --server https://bamboo.example.com --username james --password ******** 16973826

Updated 23 Feb 2018 22:20: Updated to support XSRF protection.

Read More