From 097fd27ed11e632de1a1d501b17a08dbc07fb4cf Mon Sep 17 00:00:00 2001 From: Benjamin Ritter Date: Tue, 13 Feb 2024 17:03:06 +0000 Subject: [PATCH] Initial Commit --- .gitignore | 4 + README.md | 50 +++++++++ compose/.env | 3 + compose/db-init-script.sh | 10 ++ compose/docker-compose.yaml | 53 ++++++++++ createJCAppLink | 196 ++++++++++++++++++++++++++++++++++++ machine-setup.sh | 12 +++ setupAcademyInstancev2.pl | 96 ++++++++++++++++++ startAcademyEnvironment | 56 +++++++++++ updateConfluenceBaseURL | 48 +++++++++ updateJiraBaseURL | 46 +++++++++ 11 files changed, 574 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 compose/.env create mode 100644 compose/db-init-script.sh create mode 100644 compose/docker-compose.yaml create mode 100755 createJCAppLink create mode 100644 machine-setup.sh create mode 100755 setupAcademyInstancev2.pl create mode 100755 startAcademyEnvironment create mode 100755 updateConfluenceBaseURL create mode 100755 updateJiraBaseURL diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..207537b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +compose/confluence-home +compose/jira-home +compose/pgdata +instance* \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..79808c5 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# Setup + +``` +# As root on Rocky Linux +./machine-setup.sh + +mkdir -p compose +cd compose +docker-compose up -d +# JIRA_PORT=8080 +# CONFLUENCE_PORT=8090 +# Set up your instance with license and +``` + +## Template Instance Setup + +The template instances for Jira and Confluence are located in [./compose](./compose) + +``` +cd compose +# Start Instance +docker-compose up -d +# Stop Instance +``` + +On Confluence you need to install the BaseURL Plugin from here: +[OBR](https://marketplace.atlassian.com/download/apps/1220470/version/1000010) +[Marketplace](https://marketplace.atlassian.com/archive/1220470) + +The Plugin is required to change the Base URL on Confluence via REST + +Those instances are cloned. + +### Update Jira/Confluence Version + +To update your Academy Instances update the Image Version in the Compose File + + +## Creating an Academy environment + +!!! Shut Down the Template Instance before creating Academy Instances !!! +!!! Otherwise weird things™ might happen !!! + +``` +# Initialize and start instances - Will take a while (up to 30 minutes) +# 20 Instances need about 128 GB RAM, so choose your instance size appropriately +./startAcademyEnvironment --instances 20 --admin 0 +# When you are done +./startAcademyEnvironment --instances 20 --admin 0 --destroy +``` \ No newline at end of file diff --git a/compose/.env b/compose/.env new file mode 100644 index 0000000..7f20405 --- /dev/null +++ b/compose/.env @@ -0,0 +1,3 @@ +JIRA_PORT=8080 +CONFLUENCE_PORT=8090 +CONFLUENCE_SYNCHRONY_PORT=8091 \ No newline at end of file diff --git a/compose/db-init-script.sh b/compose/db-init-script.sh new file mode 100644 index 0000000..a2a55bb --- /dev/null +++ b/compose/db-init-script.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL + CREATE ROLE confluence WITH LOGIN PASSWORD 'test123'; + CREATE ROLE jira WITH LOGIN PASSWORD 'test123'; +EOSQL + +createdb -O confluence -E utf8 --lc-collate=en_US.utf8 --lc-ctype=en_US.utf8 confluence +createdb -O jira -E UNICODE -l C -T template0 jira diff --git a/compose/docker-compose.yaml b/compose/docker-compose.yaml new file mode 100644 index 0000000..7e6e063 --- /dev/null +++ b/compose/docker-compose.yaml @@ -0,0 +1,53 @@ +version: '3' +services: + db: + image: postgres + restart: unless-stopped + volumes: + - ./db-init-script.sh:/docker-entrypoint-initdb.d/init.sh + - ./pgdata:/var/lib/postgresql/data + environment: + - POSTGRES_PASSWORD=test123 + jira: + restart: unless-stopped + image: atlassian/jira-software:latest + environment: + - ATL_JDBC_URL=jdbc:postgresql://db/jira + - ATL_JDBC_USER=jira + - ATL_JDBC_PASSWORD=test123 + - ATL_DB_TYPE=postgres72 + - ATL_DB_DRIVER=org.postgresql.Driver + - JVM_MAXIMUM_MEMORY=1024m + volumes: + - ./jira-home:/var/atlassian/application-data/jira + ports: + - ${JIRA_PORT}:8080 + depends_on: + - db + ulimits: + nproc: 65535 + nofile: + soft: 26677 + hard: 46677 + + confluence: + restart: unless-stopped + image: atlassian/confluence:latest + environment: + - ATL_JDBC_URL=jdbc:postgresql://db/confluence + - ATL_JDBC_USER=confluence + - ATL_JDBC_PASSWORD=test123 + - ATL_DB_TYPE=postgresql + - JVM_MAXIMUM_MEMORY=1024m + ports: + - ${CONFLUENCE_PORT}:8090 + - ${CONFLUENCE_SYNCHRONY_PORT}:8091 + volumes: + - ./confluence-home:/var/atlassian/application-data/confluence + depends_on: + - db + ulimits: + nproc: 65535 + nofile: + soft: 26677 + hard: 46677 diff --git a/createJCAppLink b/createJCAppLink new file mode 100755 index 0000000..56015b2 --- /dev/null +++ b/createJCAppLink @@ -0,0 +1,196 @@ +#!/bin/bash + +############################################################################# +### +### this script creates a bi-directional application link between a Jira +### and a Confluence instance. +### +### Simply provide base URLs for both applications as well as a user account +### with admin privileges on both systems. +### +### (c) 2018 anaron GmbH +### Author(s): Korbinian Weidinger +### Heinz Jürgen Letsch +### +############################################################################# + + +########## DEFAULTS/PARAMETERS ######### +JIRAAPPLABEL="Jira Schulungsumgebung" +CONFAPPLABEL="Confluence Schulungsumgebung" +CHEADER=/tmp/cheader +CURLVERBOSITY="-s -S -w %{http_code}" +CURLVERBOSITY="-s -S " + +for i in "$@" +do + case $i in + -j=*|--JIRA=*) + JIRA="${i#*=}" + ;; + -l=*|--JIRAlabel=*) + JIRAAPPLABEL="${i#*=}" + ;; + -c=*|--CONFLUENCE=*) + CONFLUENCE="${i#*=}" + ;; + -k=*|--CONFLUENCElabel=*) + CONFAPPLABEL="${i#*=}" + ;; + -u=*|--username=*) + USERNAME="${i#*=}" + ;; + -p=*|--password=*) + PASSWORD="${i#*=}" + ;; + -d|--debug) + debug=true + CURLVERBOSITY="-D /tmp/header" + ;; + *) + # unknown option + ;; + esac +done + +if [[ "$debug" == "true" ]] +then + echo "jira: $JIRA" + echo "confluence: $CONFLUENCE" + echo "username: $USERNAME" + echo "password: $PASSWORD" + echo "jiraLabel: $JIRAAPPLABEL" + echo "confLabel: $CONFAPPLABEL" + echo + curlverbosity="-D ${CHEADER} -w %{http_code}" # save response header in debug mode + curlverbosity="-D ${CHEADER} " # save response header in debug mode +fi + + + +#################### FUNCTIONS ################### + +### +# show brief parameter summary +### +usage() { + echo -e "\nUsage:\n$0 \n\ + -u|--username= \n\ + -p|--password= \n\ + -j|--jira= \n\ + -c|--confluence= \n\ + [-l|--jiralabel=] \n\ + [-k|--confluencelabel=]\n" +} + + +### +# perform oauth authentication for given token/id +# params: baseUrl id +### +doOauth() +{ + response=$(curl ${CURLVERBOSITY} -u ${USERNAME}:${PASSWORD} -X PUT $1/rest/applinks/3.0/status/$2/oauth \ + -H 'content-type: application/json' \ + -H "origin: $1" \ + -d '{"incoming":{"enabled":true,"twoLoEnabled":true,"twoLoImpersonationEnabled":true},"outgoing":{"enabled":true,"twoLoEnabled":true,"twoLoImpersonationEnabled":true}}') + if [[ "${debug}" == "true" ]]; then + cat ${CHEADER} + echo ${response} + fi +} + + +### +# create an application link entity +# params: baseUrl schema +### +createAppLink() +{ + response=$(curl ${CURLVERBOSITY} \ + -u ${USERNAME}:${PASSWORD} \ + -H "X-Atlassian-Token: no-check" \ + -H "Origin: $1" \ + -H "Content-Type: application/json" \ + -X POST $1/rest/applinks/3.0/applicationlinkForm/createAppLink \ + -d "$2" + ) + if [[ "${debug}" == "true" ]]; then + echo ${CHEADER} + echo ${response} + fi + + id=$(echo ${response} | grep -oP '(?<=).*?(?=)') + if [[ -z ${id} ]]; then + + $(echo {$response} | grep -oPq '502 Bad Gateway') + badGW=$?; + $(echo {$response} | grep -oPq 'error status=\"400\"') + exists=$?; + if [ "$badGW" == "0" ]; then + echo " error: instance not running!" + else + if [ "$exists" == "0" ]; then + echo " link already exists" + else + echo "ERROR: could not create app link on $1: ${response}" + fi + fi + exit 1; + fi + #echo "got applink ID: ${id}" + doOauth $1 ${id} +} + + +### +# prepare required data for applink creation +# params: application appLabel baseUrlLink +# application: jira|confluence +# appLabel: label of applink shown in admin GUI +# baseUrlLink: baseURL of system to be linked to +### +getApplinkSchema(){ + echo "{ + \"applicationLink\": + { + \"typeId\":\"$1\", + \"name\":\"$2\", + \"displayUrl\":\"$3\", + \"rpcUrl\":\"$3\", + \"isPrimary\":true, + \"isSystem\":false + }, + \"username\":\"$username\", + \"password\":\"$password\", + \"customRpcURL\":false, + \"rpcUrl\":\"null\", + \"createTwoWayLink\":false, + \"orphanedTrust\":null, + \"configFormValues\": + { + \"trustEachOther\":true, + \"shareUserbase\":true + } + }" +} + + + +#################### BODY ################### + +### +# check if required parameters are set +### +if [[ -z "${JIRA}" ]] || [[ -z "${CONFLUENCE}" ]] || [[ -z "${USERNAME}" ]] || [[ -z "${PASSWORD}" ]] +then + usage + exit 1 +fi + + +### +# create link on both systems +### +createAppLink ${JIRA} "$(getApplinkSchema "confluence" "${CONFAPPLABEL}" "${CONFLUENCE}")" +createAppLink ${CONFLUENCE} "$(getApplinkSchema "jira" "${JIRAAPPLABEL}" "${JIRA}")" diff --git a/machine-setup.sh b/machine-setup.sh new file mode 100644 index 0000000..b1a12f6 --- /dev/null +++ b/machine-setup.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +yum install -y docker perl +systemctl enable --now docker +VERSION=v4.40.5 +BINARY=yq_linux_amd64 +wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY}.tar.gz -O - |\ + tar xz && mv ${BINARY} /usr/bin/yq + + diff --git a/setupAcademyInstancev2.pl b/setupAcademyInstancev2.pl new file mode 100755 index 0000000..9a600fb --- /dev/null +++ b/setupAcademyInstancev2.pl @@ -0,0 +1,96 @@ +#!/usr/bin/perl +use strict; +use warnings; +use 5.010; +use Getopt::Long qw(GetOptions); +use FileHandle; + +my $instance; +my $destroy = ''; +my $brokenAppLink = ''; +my $brokenBaseURL = ''; + +GetOptions('instance=s' => \$instance, + 'destroy' => \$destroy, + 'brokenAppLink' => \$brokenAppLink, + 'brokenBaseURL' => \$brokenBaseURL) or usage(); + +# check parameters +if (!(defined $instance && $instance >= 0)) { usage(); } + +# set some defaults +my $maxWait = 600; # seconds to wait for instance to come up +my $waitCycle = 10; +my $URLStem = "http://academy.scandio.net"; +my $ComposeFolder = "instance$instance"; +# Port stem for instance e.g. 8000 +# 8080 -> Jira +# 8090 -> Confluence +my $PortStem = 8000+($instance*100); +my $JiraPort = $PortStem+80; +my $ConfluencePort = $PortStem+90; +my $ConfluenceSynchronyPort = $PortStem+91; + +if ($destroy) { + print "Destroying Instance $instance\n"; + system("cd $ComposeFolder && docker-compose down"); + system("rm -rf $ComposeFolder"); + exit(0); +} +# Copy Template folder to $ComposeFolder +`rsync -a compose/ $ComposeFolder`; + + +# Write new .env file with new ports +my $FHenv = new FileHandle("${ComposeFolder}/.env", "w"); +unless ($FHenv) { die( "cannot create file ${ComposeFolder}/.env" );} +$FHenv->print("JIRA_PORT=$JiraPort\n"); +$FHenv->print("CONFLUENCE_PORT=$ConfluencePort\n"); +$FHenv->print("CONFLUENCE_SYNCHRONY_PORT=$ConfluenceSynchronyPort\n"); +$FHenv->close; + +# Start the Compose Stack +print "Starting instance $instance...\n"; +system("cd $ComposeFolder && docker-compose up -d"); + + +# wait for instance to come up, set new BaseURL +my $result; +my $resultConfluence; +my $resultJira; +my $waited = 0; +if (!$brokenAppLink) { + do { + print " waiting for instance to become available (" . $waited . "s)...\n"; + $waited += $waitCycle; + sleep $waitCycle; + $resultJira = system("./updateJiraBaseURL --baseurl=${URLStem}:${JiraPort} -u=admin -p=admin"); + $resultConfluence = system("./updateConfluenceBaseURL --baseurl=${URLStem}:${ConfluencePort} -u=admin -p=admin > /dev/null 2>&1"); + } while (($resultJira ne "0" || $resultConfluence ne "0") && $waited < $maxWait); +} + +if (!$brokenBaseURL) { + system ("./createJCAppLink -j=${URLStem}:${JiraPort} -c=${URLStem}:${ConfluencePort} -u=admin -p=admin"); +} + +# check if update was successful +if ($waited >= $maxWait) +{ + print "Instance not available after $waited seconds"; + print "baseURL not updated - aborting\n"; + exit 0; +} + +# restart container to activate context changes +print " restarting container to activate new configuration\n"; +system("cd $ComposeFolder && docker-compose up --force-recreate -d"); + +exit 0; + + + +sub usage +{ + print "Usage: $0 --instance INSTANCENO [--destroy] [--brokenAppLink] [--brokenBaseURL\n"; + exit 1; +} \ No newline at end of file diff --git a/startAcademyEnvironment b/startAcademyEnvironment new file mode 100755 index 0000000..fa944fe --- /dev/null +++ b/startAcademyEnvironment @@ -0,0 +1,56 @@ +#!/usr/bin/perl +use strict; +use warnings; +use threads; +use 5.010; +use Getopt::Long qw(GetOptions); + +my $instanceCount; +my $adminEnv; +my $offset; +my $destroy = ''; + + + +GetOptions('instances=s' => \$instanceCount, + 'admin=s' => \$adminEnv, + 'offset=i' => \$offset, + 'destroy' => \$destroy); + + +# check parameters +if (!(defined $instanceCount && length $instanceCount > 0)) { usage(); } +if (!(defined $offset)) { + print "No Offset specified; Setting Offset to 0\n"; + $offset = 0; +} +if (!(defined $adminEnv && length $adminEnv > 0)) { usage(); } +if ($adminEnv != "1") { $adminEnv = 0 } + +my $startArgs = ""; +if ($adminEnv) { + $startArgs = "--brokenAppLink --brokenBaseURL"; +} +if ($destroy) { + $startArgs = "$startArgs --destroy"; +} + + +my @threads; +# start as many instances as specified +for (my $i=$offset; $i<$instanceCount+$offset; $i++) +{ + push @threads, async {system ("./setupAcademyInstancev2.pl -instance $i $startArgs")}; +} + +for (@threads) { + $_->join(); +} + +exit 0; + +sub usage +{ + print "Usage: $0 --instances INSTANCECOUNT --admin [0|1]\n"; + exit 1; +} diff --git a/updateConfluenceBaseURL b/updateConfluenceBaseURL new file mode 100755 index 0000000..bec6738 --- /dev/null +++ b/updateConfluenceBaseURL @@ -0,0 +1,48 @@ +#!/usr/bin/perl +use strict; +use warnings; +use 5.010; +use Getopt::Long qw(GetOptions); + + +my $username; +my $password; +my $serverurl; +my $baseurl; +my $updateURL; +my $updateArgs; +my $response; +my $cmd; +my $isJira; + +GetOptions('username=s' => \$username, + 'password=s' => \$password, + 'baseurl=s' => \$baseurl) or usage(); + +if (!defined $username) { $username = "admin" } +if (!defined $password) { $password = "admin" } +if (!defined $baseurl) { usage(); } + +$updateURL = "$baseurl/rest/base-url/1.0/base-url?baseUrl=$baseurl"; +$updateArgs = "-X PUT -H \"X-Atlassian-Token: no-check\" -u \"$username:$password\""; + +# use curl to update the baseURL +$cmd = "curl -s -S -o /dev/null -w %{http_code} $updateArgs $updateURL"; +print "updating BaseURL with: $cmd\n"; +$response = `$cmd`; +print "http response: $response\n"; + + +if ($response == "200") { print "Confluence baseURL updated successfully\n"; } +else { +if ($response == "404") { print "FAILED: this Confluence instance does not support BaseURL updates. Install AddOn to add support!\n"; } +else { print "FAILED: http status code on update: $response\n"; exit 1;} +} +exit 0; + +sub usage +{ + print "Usage: $0 --baseurl newBaseURL [--username adminUsername] [--password adminPassword]\n"; + exit 1; +} + diff --git a/updateJiraBaseURL b/updateJiraBaseURL new file mode 100755 index 0000000..7fbf24d --- /dev/null +++ b/updateJiraBaseURL @@ -0,0 +1,46 @@ +#!/usr/bin/perl +use strict; +use warnings; +use 5.010; +use Getopt::Long qw(GetOptions); + + +my $username; +my $password; +my $serverurl; +my $baseurl; +my $updateURL; +my $updateArgs; +my $response; +my $cmd; +my $isJira; + +GetOptions('username=s' => \$username, + 'password=s' => \$password, + 'baseurl=s' => \$baseurl) or usage(); + +if (!defined $username) { $username = "admin" } +if (!defined $password) { $password = "admin" } +if (!defined $baseurl) { usage(); } + +$updateURL = "$baseurl/rest/api/2/settings/baseUrl"; +$updateArgs = "-X PUT -H \"X-Atlassian-Token: no-check\" -u \"$username:$password\" -H \"Content-Type:application/json\" -d \"$baseurl\""; + + +# use curl to update the baseURL +$cmd = "curl -s -S -o /dev/null -w %{http_code} $updateArgs $updateURL"; +print "updating BaseURL with: $cmd\n"; +$response = `$cmd`; +print "http response: $response\n"; + + +if ($response == "204") { print "Jira baseURL updated successfully\n"; } +else { print "FAILED: http status code on update: $response\n"; exit 1; } +exit 0; + +sub usage +{ + print "Usage: $0 --baseurl newBaseURL [--username adminUsername] [--password adminPassword]\n"; + exit 1; +} +