Overview: Provisioning Application

This application creates new OpenDevStack digital projects. It is the central entrypoint to get started with a new project / or provision new components based on quickstarters. It delegates the tasks to create / update resources to several services such as jira, confluence, bitbucket and rundeck.

Basic idea & usage

  1. An admin (user in a group defined in property idmanager.group.opendevstack-administrators) creates new ODS project. This in turn creates

    • a Jira Project (name based on project key & name)

    • a Confluence Space (name based on project’s key)

    • the required Openshift projects named key-dev, key-test and key-cd - in case openshiftproject == true. Internally this is done thru a rest call to rundeck triggering the create-projects rundeck job

    • a Bitbucket Project (name based on project key) - in case openshiftproject == true. Within this project two default repositories are created key-oc-config-artifacts for all yaml resources as well as key-design for any design artifacts (e.g. sketches)

  2. A normal user (user in a group defined in property idmanager.group.opendevstack-users) creates all resources required for a working component - this happens thru the user interface - in going to modify project / picking your project and then the wanted quickstarter. Internally this is done thru a rest call to rundeck - with the picked job as parameter - here

    • Bitbucket repository within the chosen project named key-boilerplate name

    • Openshift components based on the chosen boilerplate, coming from ods-quickstarters

  3. The involved people receive an email with the setup, URLs to components etc. - in case mail.enabled == true

Integration with Bitbucket (webhooks)

Next to the provision app creating the bitbucket repository for a chosen quickstarter - it also creates a webhook on that repo, which triggers on three events

    List<String> events = new ArrayList<String>();
        events.add("repo:refs_changed");
        events.add("pr:merged");
        events.add("pr:declined");
    webhook.setEvents(events);

This webhook calls the webhook proxy which in turn creates an openshift build config of type pipeline in the name-cd project and executes it.

Permissions

By default no special permissions are set on either confluence / jira / bitbucket or openshift, only system-wide settings are inherited.

However there is a special knob to tighten security (which can be passed with the project input createpermissionset : boolean) - based on three groups that need to be provided as part of the API call / from the userinterface.

  1. admin group: admin rights on the generated projects / spaces / repositories

  2. user group: read / write rights on the generated projects / spaces / repositories

  3. readonly group: read rights on the generated projects / spaces / repositories

The configuration for the permission sets are configured:

  1. JIRA Project is provisioned with its own permissionset defined in src/main/resources/permission-templates/jira.permission.all.txt

  2. Confluence Project is provisioned with special permission set defined in src/main/resources/permission-templates/confluence.permission.*

  3. Bitbucket Project is provisioned with tight read & write roles

  4. Openshift Project roles linked to the passed groups (READONLY - view, ADMINGROUP - admin, USERS - edit)

Project/Space types based on templates

The default jira / confluence project' types are defined in src/main/resources/application.properties - and correspondingly in the config maps

project.template.key.names=default

jira.project.template.key=com.pyxis.greenhopper.jira:gh-scrum-template
jira.project.template.type=software

confluence.blueprint.key=com.atlassian.confluence.plugins.confluence-software-project:sp-space-blueprint

To add a new template - copy, and add your config, based on a new <name>

jira.project.template.key.<name>=com.pyxis.greenhopper.jira:gh-scrum-template
jira.project.template.type.<name>=software

# optional, can stay as is
confluence.blueprint.key.<name>=com.atlassian.confluence.plugins.confluence-software-project:sp-space-blueprint

and add the new from above to the existing property project.template.key.names

# list of templates surfaced to the UI and API
project.template.key.names=default,<name>

Using the provision application via API / thru direct REST calls

PROVISION_API_HOST=<host name>

curl -D headers.txt -k -H "Content-Type: application/x-www-form-urlencoded" \
-X POST ${PROVISION_API_HOST}/j_security_check \
-d username=<username> -d password=<password>

# grab the login status, and exit if error
login_status=$(cat headers.txt | grep ${PROVISION_API_HOST}/login?error)

if [[ $login_status != "" ]]; then echo "Login Error"; exit 1; fi;

# grab the needed IDs and bake the cookies
JSESSION_ID=$(cat headers.txt | grep "Set-Cookie: JSESSION" | cut -d ';' -f1 | cut -d ":" -f2)";"
CROWD_COOKIE=$(cat headers.txt | grep "Set-Cookie: crowd" | cut -d ';' -f1 | cut -d ":" -f2)

COOKIES=${JSESSION_ID}${CROWD_COOKIE}

# sample provision file >> create.txt
{
  "name" : "<Mandatory name>",
  "key" : "<Mandatory key>",
  "createpermissionset" : true,
  "jiraconfluencespace" : true,
  "admin" : "<admin user>",
  "adminGroup" : "<admin group>",
  "userGroup" : "<user group>",
  "readonlyGroup" : "<readonly group>",
  "openshiftproject" : false
}

provisionfile=create.txt

# invoke the provision API to create a new project
curl -k -X POST --cookie "$COOKIES" -d @"$provisionfile" \
-H "Content-Type: application/json; charset=utf-8" -v ${PROVISION_API_HOST}/api/v2/project

What happens in error cases

Up to (and including) v1.1.x when provisioning failed, corrupt and inconsistent states where left in the bugtracker system, bitbucket etc. which had do be cleaned up manually based on logs. This is rectified and a the new default behavior is to see every post to the API as atomic unit of work, which in case of failure is tried to be cleaned up (alike functional rollback). This behavior can be turned off by specifying the new property provision.cleanup.incomplete.projects and setting it to false.