1. JIRA

1.1. Wstęp

1.1.1. Wymagania przed szkoleniem

  1. System operacyjny wspierany przez Atlassian (zalecany Linux)
  2. Ściągnięta odpowiednia binarka dla wybranego systemu operacyjnego https://www.atlassian.com/software/jira/download

1.1.2. Licencje

  • Cloud vs. Server
  • Ilość użytkowników
  • Długość trwania licencji
  • Jira Core vs. Software vs. Service Desk
  • Evaluation license

1.1.3. Instalacja

1.1.5. Konfigurowanie profilu

  • Język
  • Avatar (gravatar)
  • Powiadamianie mailami
  • Dobre praktyki filtrów na maila

1.1.6. Skróty klawiszowe

  • gg oraz .
  • c
  • e

1.1.7. Assignments

1.1.7.1. Instalacja Jiry

  1. Zainstaluj Jirę z licencją evaluation (wykorzystaj 10 minute email - drugi wynik w Google)
  2. Utwórz projekt Moon Village (klucz: MOON) z przykładowymi danymi

1.2. Project

  • Project Lead

  • Categories

    • Department
    • Team
    • Project / Product
  • Project vs. Boards

  • Issue Collector

1.2.1. Issue key

  • krótki i zwięzły
  • łatwy do zapamiętania
  • 2-10 liter

1.2.2. Project Configuration

  • Versions
  • Components
  • Roles and Permissions
  • Application Links

1.2.3. Assignments

1.2.3.1. Projekt

  • Stwórz projekt
  • Dodaj użytkownika jira-administrator do roli Developers
  • Dodaj użytkownika jira-administrator do roli Administrators

1.3. Issues

1.3.1. Issue Types

  • Bug
  • Task
  • User Story
  • Epic
  • Sub-task

1.3.2. Issue Fields

  • Components

    • Component Leaders
  • Lables

  • Links

  • Assignee

  • Reporter

1.3.3. Custom Fields

  • kilka - kilkanaście
  • Team Assigned
  • Start Date (and Due Date)

1.3.4. Versions

  • Roadmap
  • Releases (with Bamboo)
  • Konwencja nazewnicza YYYY-MM (2017-01, 2017-02, 2017-03)
  • Time Tracking Report by Version
  • affectsVersion vs. fixVersion

1.3.5. Priorities

  • Standard

    • Lowest
    • Low
    • Medium
    • High
    • Highest
  • MoSCoW

    • Must
    • Schould
    • Could

1.3.6. Statusy

  • To Do
  • In Progress
  • Done
  • In Review
  • Waiting / Blocked
  • In Test

1.3.7. Resolutions

  • Fixed
  • Won’t Fix
  • Duplicate
  • Cannot Reproduce
  • Incomplete
  • [Jira Agile] -> Done

1.3.8. Issue Actions

  • Workflow Actions (Open, In Progress, Done)

  • Voting

  • Watching

  • Add Atachments

  • Clone

  • Move

  • Create subtask

  • Delete (kiedy?)

  • Log Work

  • Keyboard Shortcuts

  • Comment

    • Mentions
    • Rich Text Editing
    • Tworzenie tabelek
    • Używanie formatowania

1.3.9. Time Reporting

  • Original Time Estimate
  • Remaining Time
  • Log Work
  • Reports

1.3.10. Assignments

1.3.10.1. Tworzenie issues

  • Ustaw ekran tworzenia zadania

    • Issue Type
    • Summary
    • Description
    • Priority
    • Attachment
    • Linked Issue
    • Assignee
    • Time Tracking
  • Do jednego z zadań dodaj załącznik

    • obrazek PNG lub JEPG
    • archiwum .zip z przynajmniej dwoma plikami tekstowymi
  • Zadania powinny mieć różne priorytety

  • Zadania miały różne Issue Type

  • Powiąż dwa zadania linkami jako “is blocked by”/”blocks”

  • Sklonuj przynajmniej jedno zadanie

  • Niech jedno zadanie ma trzy sub-taski

    • status pierwszego: To Do
    • status drugiego: In Progress
    • status trzeciego: Done
  • Przenieś zadanie z projektu do innego projektu

1.4. JQL - JIRA Query Language

  • List View, Detail View
  • Konfiguracja Kolumn wyszukiwania
  • Searching Issues
  • Konfiguracja Boardów
  • Bulk edit
  • Bulk change limit
  • Limit wyświetlania wyników dla JQL
  • Import / Export CSV
  • jira.issue.editable = true dla statusu Done (Workflow)
project = DEMO
project = DEMO
    AND status = "To Do"
status = "To Do" OR status = "In Progress"

status IN ("To Do", "In Progress")

status NOT IN ("To Do", "In Progress")
project = DEMO
    AND resolution NOT IN (Fixed, "Won't Fix")
statusCategory = "To Do"
statusCategory NOT IN ("To Do", "In Progress")
statusCategory != "Done"
statusCategory NOT IN (Done, "In Progress")
    AND assignee = currentUser()
statusCategory NOT IN (Done, "In Progress")
    AND assignee IN membersOf("jira-administrators")
statusCategory NOT IN (Done, "In Progress")
    AND assignee = currentUser()
    ORDER BY priority DESC, key ASC
project = DEMO
    AND status WAS Done
    AND status != Done
project = DEMO
    AND status WAS Done
    AND status != Done
    AND updated > -1d
Sprint IN closedSprints()
Sprint IN openSprints()
Sprint IN futureSprints()
project = DEMO
    AND sprint in openSprints()
    AND status != Done
    AND updated > -1d
Flagged IS NOT EMPTY
project = DEMO
    AND sprint IN openSprints()
    AND (statusCategory = "In Progress" OR Flagged is not EMPTY)

    -- opcjonalnie, ze względu na omawianie Waiting i in test itp.
    AND updated >= -1d
project = DEMO
    AND sprint IN openSprints()
    AND assignee = currentUser()
reporter = currentUser()
    AND statusCategory != Done
    AND assignee != currentUser()
project = DEMO
    AND updated >= -7d
    AND assignee IN membersOf("jira-administrators")
due >= 2017-03-01 AND due <= 2017-03-31

due >= startOfMonth() AND due <= endOfMonth()
updated >= startOfWeek(-7d) AND updated <= endOfWeek(-7d)
due <= now()
    AND statusCategory != Done
status WAS NOT "In Progress" BEFORE "2011/02/02"
status WAS NOT IN ("Resolved","In Progress") BEFORE "2011/02/02"
status WAS IN ("Resolved","In Progress")
status WAS "Resolved" BY jsmith DURING ("2010/01/01","2011/01/01")
status WAS "Resolved" BY jsmith BEFORE "2011/02/02"
status changed by currentUser()
AFTER "date"
BEFORE "date"
BY "username"
DURING ("date1","date2")
ON "date"
FROM "oldvalue"
TO "newvalue"
assignee CHANGED

priority CHANGED BY freddo BEFORE endOfWeek() AFTER startOfWeek()

status CHANGED FROM "In Progress" TO "Open"
currentLogin()
lastLogin()
now()
startOfDay()
startOfWeek()
startOfMonth()
startOfYear()
endOfDay()
endOfWeek()
endOfMonth()
endOfYear()

More info: https://confluence.atlassian.com/jira064/advanced-searching-720416661.html

1.4.1. Assignments

1.4.1.1. JQL i Wyszukiwanie zadań

  1. wyszukaj wszystkie zadania, które są w statusie “In Progress”
  2. wyszukaj zadania, które zostały zaktualizowane od wczoraj
  3. wyszukaj zadania, które należą do obecnie otwartego sprintu
  4. wyszukaj zadania oflagowane
  5. wyszukaj zadania, które należą do osób z grupy jira-administrators
  6. wyszukaj zadania, które były przypisane do Ciebie, ale już nie są
  7. Wyszukaj wszystkie zadania zaktualizowane przez Ciebie w okresie ostatniego tygodnia
  • Pokaż kolumny: Priority, Key, Summary, Original Time Estimate, fixVersion, Epic Name, Status

1.5. Filtry

  • Tworzenie

  • Subskrybcja

  • Uprawnienia

    • Przydział do ról
    • Przydział do grup
    • Publiczny
  • Współdzielenie

1.5.1. Assignments

  • Stwórz filtr “Daily”
  • Stwórz filtr “Przekroczony Deadline”, ustaw uprawnienia by był widoczny dla administratorów w projekcie
  • Stwórz filtr “Praca mojego zespołu z ostatniego tygodnia”, ustaw by przychodził mail z zadaniami w poniedziałki o 6 rano

1.6. Dashboard

  • Tworzenie

  • Publikacja

  • Dodawanie gadżetów

    • Filter Results
    • Issue Statistics
    • Average Age Chart
    • Resolution Time
  • Wallboard plugin

    • Tables
    • Graphs
    • Piecharts
  • Jira Agile Reports

    • Sprint Health Report
    • Burndown
    • Days Remaining

1.6.1. Assignments

  1. Stwórz dashboard z trzema kolumnami

  2. Pierwsza kolumna:

    • Created vs. Resolved
    • Average Age Chart
  3. Druga kolumna:

    • Version Report
    • Issue statistics (Priority)
  4. Trzecia kolumna:

    • Sprint Health Gadget
    • Days remaining in Sprint Gadget
    • Sprint Burndown

1.7. Workflow

  • Tworznie

    • Directed graph
    • Complete graph
    • Few vertices
    • Lots of Edges
    • Try simple and add statuses
    • Keep transitions from all statues
    • Simplified Workflow
  • Dobre praktyki

  • Triggery

  • Post Functions

  • Validators

  • Closed vs Resolved vs Done

1.7.1. Assignments

  1. Dodaj do workflow status In Review, Blocked, In Test
  2. Przy przenoszeniu do statusu Done ma wyświetlać się okienko z logownaiem czasu
  3. Przy przenoszeniu do statusu Blocked ma wyświetlać się okienko z komentarzem (przyczyna zablokowania)

1.8. Jira Software (Agile)

1.8.1. Project Management

  • Prowadzenie projektów
  • Kanban
  • Scrum
  • Portfolio
  • Scrum + Kanban

1.8.2. Artifacts

  • Backlog

  • Sprintlog

  • Task board

  • Units:

    • Story Points
    • Business Value

1.8.3. Epic

  • Brak worków (np. Poprawki błędów)

  • Doważalne (określone w czasie, mają datę początku i końca)

  • Dobre praktyki:

    • Due Date
    • Start Date
    • Assignee
  • Doważalne

  • optymalna długość

  • kategoryzowanie

  • timeline i roadmapa

  • planowanie kwartalne

  • przypisywanie epikow do wersji

  • board epików

  • Business Value epików

1.8.4. Estimation

1.8.5. Metrics

  • Velocity
  • Capacity
  • Maturity

1.8.6. Planning and Refinement

  • Estimation
  • How big your tasks should be?
  • Estimation support systems
  • Sprint goal
  • Acceptance Criteria
  • Definition of Done
  • Time Tracking

1.8.7. Dobre praktyki

  • Kryteria akceptacyjne
  • INFO
  • BEFORE
  • TODO
  • AFTER
  • używanie (/) i (x)

1.8.8. Board

  • Scrum vs. Kanban

    • Scrum -> Rozwój (Story)
    • Kanban -> Utrzymanie (Task)
    • Praca w Scrum i Kanban jednocześnie
    • Konstytucja zespołu i dobre praktyki
  • Board vs. Project

    • Board z wielu projektów
    • Board z części jednego projektu
    • Board dla Projektu
    • Wiele boardów do jednego projektu (różne estymaty)
    • Wiele projektów czy wiele boardów (np. po komponentach)?
  • Sprinty:

    • Wielkość (ilość zadań, capacity chart)
    • Długość (tydzień)
    • Konwencja nazewnicza (YYYY-MM week W) (2017-03 week 2, 2017-03 week 3)
  • Uprawnienia

  • Konfiguracja

  • Kolumny

    • Column Constraint (max, min)
    • Dodawanie i usuwanie kolumn
    • Wiele statusów w jednej kolumnie
    • Statusy ciągnące pracę
  • Swimlines

    • wg. priorytetów
    • wg. wersji
  • Quick Filters

  • Card Colors

  • Card Layout

    • Backlog
    • Active Sprint
    • Days in Column
  • Estimation

    • Original Estimate + Remaining Estimate and Time Spent
    • Story Points
    • Business Value
    • Issue Count
  • Working Days

  • Issue Detail View

  • Portfolio na bazie Kanbana

  • Scope Changes

  • Otwieranie i zamykanie sprintów

  • Auto assign

  • Flagowanie zadań

  • Quick Filters dla Daily

1.8.9. Charts

  • Burn-down Chart
  • Burn-up Chart
  • Control Chart
  • Cumulative Flow Diagram
  • Epic Burndown
  • Epic Report
  • Release Burndown
  • Sprint Report
  • Velocity Chart
  • Version Report
  • Version Burndown
  • Refine Reports

1.8.10. Kanban

  • What’s Kanban?

  • Pull system

  • JIT

  • Context switching

  • Kanban Board

  • Improvement:

    • Muda
    • Jidoka
    • Kaizen
    • Bottlenecks
    • Metrics
    • Lean
  • Workflow:

    • Columns
    • Swimlanes
    • Expedite
    • Priority
    • SLA

1.8.11. Assignments

1.8.11.1. Board

  1. Stwórz Board dla zadań rozwojowych (Story, Bug):

    • Dodaj kolumnę In Test oraz In Review wraz z odpowiadającymi im statusami

    • Dodaj status Won't Do, który będzie w kolumnie Done jednocześnie ze statusem Done

    • Stwórz Quick Filter Daily:

      • zadania są w trakcie wykonywania
      • zaktualizowane w ciągu ostatniego dnia
      • lub mają flagę
    • Stwórz wersję board z Estymacją Time Estimate

    • Stwórz wersję board z Estymacją w Story Points

  2. Stwórz Board dla zadań utrzymaniowych (Task)

    • Kolumny: To Do, In Progress Blocked, Done
    • Dodaj status Won't Do, który będzie w kolumnie Done jednocześnie ze statusem Done
  3. Stwórz board Kanban z Epikami:

    • Stwórz swimline dla kwartałów
    • Określ aby w kolumnie “In Progress” mogły być maksymalnie 3 Epiku
  4. Stwórz board zadań przypisanych do Ciebie:

    • zadania mogą być w dowolnym projekcie
    • board ma być publiczny

1.8.11.2. Backlog i Estymacja

  • Stwórz epiki

    • Logowanie
    • Panel administracyjny
  • oszacuj zadania używając Story Points i skali S,M,L (Small: 1, Medium: 2, Large: 3)

  • Zadanie wyestymuj na 4h

  • Zaloguj 1h 30m do zadania i ustaw remaining na 3h

1.8.11.3. Wersje

  • Stwórz wersje

    • 2019-01 (rozpoczęcie: 1 styczeń 2019; zakończenie: 31 styczeń 2019)
    • 2019-02 (rozpoczęcie: 1 luty 2019; zakończenie: 28 luty 2019)
    • 2019-03 (rozpoczęcie: 1 marzec 2019; zakończenie: 31 marzec 2019)
  • Zadania przydziel do wersji

1.8.11.4. Sprinty

  • Stwórz Sprinty

    • 2019-01 week 1 (ma 4 Story Points)
    • 2019-01 week 2 (ma 10 Story Points)
    • 2019-01 week 3 (ma 8 Story Points)
    • 2019-01 week 4 (ma 10 Story Points)
    • 2019-02 week 5 (ma 8 Story Points)
  • Wystartuj sprint 2019-01 week 1

    • Data rozpoczęcia 1 styczeń 2019, 9:00
    • Data zakończenia 7 styczeń 2017, 9:00
  • Przenieś dwa zadania do “In progress”

  • Przenieś jedno zadanie do “Done”

  • Zamknij sprint

  • Zadania które nie zostały zakończone w sprincie niech spadną do następnego tygodnia

    • Co się dzieje z otwartymi zadaniami?
    • Co się dzieje z zamkniętymi zadaniami?
    • Co się dzieje z zamkniętymi subtaskami, ale otwartym zadaniem?
    • Co się dzieje z otwartymi subtaskami ale zamkniętym zadaniem?
  • Zobacz raporty

1.9. Konfiguracja

  • Skrót klawiszowy gg

1.9.1. Scheme

  • Issue Type Schemes
  • Workflow Scheme
  • Screen Scheme
  • Field Configuration Scheme
  • Permission Scheme
  • Notification Scheme
  • Priority Scheme

1.9.2. Konfiguracja Jiry

  • Time Tracking
  • Priorytetyzacja i dobre praktyki
  • Estymacja różnych issuetype (nie tylko Story)
  • Re-index
  • Application Links
  • Zaawansowane opcje konfiguracyjne
  • Zmiana formatu daty
issue-tracker/img/jira-date-format.png

Fig. 1.1. Zmiana formatu daty w zaawansowanych opcjach konfiguracyjnych

1.9.3. Jira Administration

  • Zarządzanie licencjami
  • Backup systemu
  • Tworzenie instancji testowych
  • Instalacja i upgrade + dobre praktyki
  • Tunning JVM pod Jirę
  • Dobre praktyki z Custom

1.9.4. Tworzenie Custom Field

  • Dobre praktyki
  • Ile?
  • Konsekwencje
  • CF w bazie dancyh
  • Javascript w opisie (nie używać)

1.9.5. Dirty hacks

  • Manipulacje na bazie
  • Django Inspect DB + Jira = Django ORM
  • Skryptowanie
  • Time tracking

1.9.6. Pluginy

  • Kiedy instalować

  • Licencje pluginów

  • Różnice między pliginami w Cloud a Server

    • Atlassian Connect vs p2
  • Stategia update’ów

    • pluginy darmowe
    • pluginy komercyjne
  • Instalacja dodatkowych języków

  • Pluginy a wykorzystywane zasoby:

    • Pamięć RAM
    • Baza danych
    • System operacyjny
    • Zasoby sieciowe
  • Jira Agile Cards

  • Dane pluginów w bazie danych Jiry

1.9.7. Assignments

1.9.7.1. Prosta Administracja

  1. Wyłącz obsługę gravatar
  2. Włącz obsługę Attachmentów
  3. Ustaw maksymalny rozmiar attachmentów na 100 MB

1.9.7.2. Priorytety

  1. Zmień priorytety na MoSCoW, zmień ikony i kolory (czerwony, zielony, szary)

1.9.7.3. Role

  1. Dodaj rolę “Scrum Master”
  2. Dodaj do roli w projekcie użytkownika z JIRY
  3. Zmień w Permission scheme, aby tylko Scrum Master mógł otwierać i zamykać sprinty

1.9.7.4. Custom Field

  1. Stwórz Custom Field “People Assigned”:

    • W polu mamy mieć możliwość przypisywania wielu użytkowników do zadania
    • Pole dodaj ekranu dla zadań w projekcie
    • Stwórz filtr który wyszuka zadania w których jesteś wymieniony w tym Custom Field
    • Na podstawie filtru stwórz tablicę Kanban, z zadaniami które są do Ciebie przypisane w tym Custom Fieldzie
    • Pole ma wyświetlać się w widoku Backlog w kolumnie po prawej stronie
    • Podpowiedź: typ User Picker (Multiple Users)
  2. Stwórz Custom Field “Team Assigned”:

    • Dodaj 4 zespoły: Team A, Team B, Team C, Team D
    • Można wybrać więcej niż jeden zespół
    • Pole dodaj ekranu dla zadań w projekcie
    • Pole ma być wymagane przy tworzeniu nowego zadania
    • Podpowiedź: typ Checkbox
  3. Stwórz Custom Field “Manhours”:

    • Pole dodaj ekranu dla zadań w projekcie
    • Stwórz nowy board do projektu z estymacją w Manhours
    • Stwórz filtr, który wyciągnie wszystkie zadania z projektu
    • Na filtrze mają być kolumny: Key, Summary, Original Time Estimate, Manhours, Status
    • Podpowiedź: typ Number

1.10. JIRA API

1.10.3. Atlassian Python API

1.10.4. Assignments

1.10.4.1. Atlassian Python API - Instalacja

  1. Zainstaluj bibliotekę Atlassian Python API atlassian-python-api

Note

Kod biblioteki dostępny jest na GitHub https://github.com/AstroMatt/atlassian-python-api

Warning

Wymagany Python 3.4 lub nowszy

1.10.4.2. Atlassian Python API - Reindeksacja

  1. Stwórz skrypt jira-reindex.py
  2. Skrypt wykorzystując bibliotekę atlassian-python-api ma reindeksować JIRĘ
  3. Skrypt jira-reindex.py dodaj Crontab by był uruchamiany o 4 w nocy
  4. Pamiętaj, że cron ma inne zmienne środowiskowe

1.10.4.3. Atlassian Python API - Project Administrators

  1. Stwórz skrypt jira-administrators.py

  2. Skrypt ma wyliistować wszystkich administratorów projektów w JIRA w tabelce, wraz z ich emailem jako link “mailto”

    • Wynik zapisz w Confluence i dodaj się do watchers strony, by być powiadamianym o zmianach
    • Jeżeli nie masz zainstalowanego Confluence to zrzuć do pliku /var/www/jira-admins.html i skonfiguruj nginx aby wyświetlał tą stronę
Podpowiedź:

Aby uruchomić Confluence możesz wykorzystać Docker

$ apt-get update
$ apt-get install docker.io
$ docker run -v /var/atlassian/application-data/confluence:/var/atlassian/application-data/confluence -d -p 8090:8090 atlassian/confluence-server

1.10.4.4. Atlassian Python API - Changelog

  1. Napisz skrypt jira-changelog.py

  2. Wygeneruj Changelog, tj. listę zadań które zmieniły się pomiędzy dwoma wersjami (wykorzystaj JQL)

    • Wynik zapisz w Confluence na osobnej stronie dla każdej wersji
    • Jeżeli nie masz zainstalowanego Confluence to zrzuć do pliku /var/www/changelog-XXX.html i skonfiguruj nginx aby wyświetlał tą stronę, XXX to nazwa wersji

1.11. Jira System Administration

1.11.1. Dobre praktyki

1.11.2. Installing Jira

1.11.2.1. Database

# CentOS
$ yum install posgresql-server
$ postgresql-setup initdb
$ systemctl start posgresql
# Ubuntu / Debian
$ apt-get install postgresql

Konfiguracja bazy danych:

# Musimy być na roocie
su posgres
psql
CREATE USER jira WITH PASSWORD 'jira';
CREATE DATABASE jira;
GRANT ALL PRIVILEGES ON DATABASE jira TO jira;

W ubunutu ścieżka do pg_hba to: /etc/postgresql/9.5/main/pg_hba.conf. Dla CentOS trzeba zmienić plik /var/lib/pgsql/data/pg_hba.conf, tak aby można było łączyć się z localhost (po IPv4 i IPv6) za pomocą hasła (md5):

# "local" is for Unix domain socket connections only
local   all             all                                     peer
# IPv4 local connections:
host    all             all             127.0.0.1/32            md5
# IPv6 local connections:
host    all             all             ::1/128                 md5
$ systemctl restart postgresql

1.11.2.2. Jira install

Code Listing 1.1. Jira install
wget https://www.atlassian.com/software/jira/downloads/binary/atlassian-jira-software-7.6.1-x64.bin
chmod +x atlassian-jira-software-7.6.1-x64.bin
./atlassian-jira-software-7.6.1-x64.bin
rm -fr atlassian-jira-software-7.6.1-x64.bin

1.11.2.3. Firewall

# CentOS
$ firewall-cmd --zone=public --add-port=8080/tcp --permanet
$ firewall-cmd --zone=public --add-port=5432/tcp --permanet
$ firewall-cmd --reload

# Other Linux
$ iptables -I INPUT 1 -i eth0 -p tcp --dport 8080 -j ACCEPT
$ iptables -I INPUT 1 -i eth0 -p tcp --dport 5432 -j ACCEPT

1.11.2.4. Websudo

  • automatic admin logout
  • admin rights notification
service jira stop
echo "jira.websudo.is.disabled = true" >> /var/atlassian/application-data/jira/jira-config.properties
service jira start

1.11.2.5. User Management (JIRA User Server)

  1. Go to Jira User Server (g+g and type JIRA User Server)
  2. Add application
  3. Set application name, password and IP Addresses (paste adresses from instances which you want connect with Jira User Server)
  • Always use LDAP (OpenLDAP or Active Directory)
  • name groups as jira-users or jira-administrators
  • local administrator jira-administrator only for fixing bugs with LDAP
  • use jira@example.com (for easy email fiterling)
  • use jira.example.com as domain name with Firewall blocking external access
  • /etc/resolv.conf search example.com -> ustawianie przez DHCP
  • Internal and external users in one LDAP server
  • Read only access via LDAPs
  • avoid nested groups
  • all tools in OU=ecosystem
  • use LDAP groups for project roles from OU=projects
  • do not use user accounts in project roles (only LDAP groups)
  • Confluence page with all *-administrators + mailto: links
  • Confluence page with JIRA project administrators
  • Do not use technical accounts (use SSH keys)
  • Use SSH keys with proper comment

1.11.3. Upgrading JIRA

1.11.3.1. Instalacja nowej wersji

  1. Wejdź na stronę https://www.atlassian.com/software/jira/download

  2. Kliknij prawym na przycisk Download obok wydania Jira TAR.GZ i “Copy Link Location”

  3. Uruchom polecenia poniżej:

    # Tu wklej zawartość linku
    $ URL=""
    
    $ ssh [email protected]
    $ cd /opt/jira
    $ wget "$URL" -O jira.tgz
    $ tar zxf jira.tgz
    $ rm -fr jira.tgz
    

1.11.3.2. Ustawienia środowiskowe

  1. Poniższych edycji dokonujemy w pliku atlassian-jira-XXX/bin/setenv.sh gdzie XXX to numer wersji (nowej)
JIRA_HOME="/opt/jira/home"
JVM_SUPPORT_RECOMMENDED_ARGS="-server -XX:MaxPermSize=512m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+OptimizeStringConcat -XX:+PrintGCDetails -XX:+DisableExplicitGC -Xloggc:/opt/jira/logs/gc-jira-$(hostname)-$(date +%Y.%m.%d).log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=10M"
JVM_MINIMUM_MEMORY="512m"
JVM_MAXIMUM_MEMORY="2048m"

1.11.3.3. Zmiana portu działania Jiry

  1. Edytuj linijkę /opt/jira/install/conf/server.xml i znajdź

    Connector port="8080"
    
  2. Zamień na:

    Connector port="8000"
    

1.11.3.4. Sprawdzenie wersji Javy dla Jiry

  1. Odpal poniższe polecenie

    /opt/java/default/bin/java -version
    
  2. Zobacz aktualną wersję na http://www.oracle.com/technetwork/java/javase/downloads/1880261

  3. Jeżeli wersja się różni ściągnij za pomocą wget nową Javę do /opt/java/

  4. rozpakuj i usuń symlink /opt/java/default

  5. stwórz symlink /opt/java/default wskazujący na nową Javę

1.11.3.5. Backup bazy oraz home’a

  1. Odpal skrypt /opt/jira/backup-jira.sh

1.11.3.6. Upgrade Jiry

$ service jira stop
$ rm -fr /opt/jira/install
$ ln -s /opt/jira/atlassian-jira-XXX
$ /opt/jira/install
$ service jira start

1.11.3.7. Sprzątanie

  1. Możesz usunąć stary katalog instalacyjny Jiry.
  2. Proponuję jednak zostawić jedną, poprzednią wersję tak na wszelki wypadek, gdyby jakieś zmiany nie zostały przeniesione.

1.11.4. Utils

1.11.4.1. Reindex

Code Listing 1.2. Jira reindex
from pprint import pprint
from atlassian import Jira


jira = Jira(
    url="http://localhost:8000/",
    username="admin",
    password="admin")

status = jira.reindex().json()
pprint(status)

1.11.4.2. Project Administrators

Code Listing 1.3. Jira Project Administrators
import logging
from atlassian import Confluence
from atlassian import Jira


logging.basicConfig(level=logging.DEBUG, format="[%(asctime).19s] [%(levelname)s] %(message)s")
logging.getLogger("requests").setLevel(logging.WARNING)
log = logging.getLogger("jira-projects-administrators")


jira = Jira(
    url="http://localhost:8000/",
    username="admin",
    password="admin")

html = "<table><tr><th>Project Key</th><th>Project Name</th><th>Leader</th><th>Email</th></tr>"

for data in jira.project_leaders():
    log.info("{project_key} leader is {lead_name} <{lead_email}>".format(**data))
    html += "<tr><td>{project_key}</td><td>{project_name}<td></td>{lead_name}<td></td><a href='mailto:{lead_email}'>{lead_email}</a></td></tr>".format(**data)

html += "</table><p></p><p></p>"
html += "<p>Autogenerated with <a href='http://localhost:7999/projects/AGILE/repos/devops-utils-jira/browse/bin/jira-projects-administrators.py'>this script</a></p>"

confluence = Confluence(
    url="http://localhost:8090/",
    username="admin",
    password="admin")

confluence.update_page(
    page_id=13207798,
    parent_id=7471197,
    title="Administratorzy JIRA",
    body=html)

log.info("Confluence Page Created with JRIA Administrators at: http://localhost:8095/pages/viewpage.action?pageId=13207798")

1.11.5. Migracja danych

Code Listing 1.4. Jira Migrate
#!/usr/bin/env python2

import httplib
import ldap
import urllib
import logging
import re


"""
Basic configuration
"""

class Config(object):
    host = 'localhost:8000'
    token = '...'
    jsessionid = '...'
    entities_file = '/Users/matt/Projects/clitools/entities.xml'
    entities_attrs = ['author', 'caller', 'lead', 'name', 'newstring', 'reporter', 'roletypeparameter', 'updateauthor', 'user', 'username', 'userName', 'lowerUserName', 'childName', 'lowerChildName']
    ldap_addr = 'ldap://localhost:3268'
    ldap_user = '...'
    ldap_pass = '...'
    ldap_dc = '...'
    fallback_domains = []


class DoNotDelete(object):
    groups = ["jira-administrators", "jira-users"]
    custom_fields = [11152, 10507, 11210, 10068, 11240, 10773, 11190, 10086, 10105, 10797, 11063, 11064, 10356]
    projects = [10605, 11600, 10607, 10800, 10600]
    permission_schemes = [0]
    workflow_schemes = []
    workflows = []
    issue_types = [1, 2, 3, 4, 5, 9, 107, 146, 126, 127, 128, 157, 237]
    issue_type_schemes = []
    statuses = []
    notification_schemes = []
    project_roles = [10002, 10001, 10000]

"""
You shouldn't change anything below this point,
unless you know what are you doing.
"""

logging.basicConfig(
    level=logging.INFO,
    format='[%(asctime).19s] %(levelname)s: %(message)s'
)

class Http(object):
    """ Http request class """

    @staticmethod
    def GET(url, params={}):
        params["atl_token"] = Config.token
        params = urllib.urlencode(params)
        return Http._request("GET", "%s?%s" %(url, params))

    @staticmethod
    def POST(url, params={}):
        params["atl_token"] = Config.token
        params["Delete"] = "Delete"
        params["confirmedDelete"] = "true"
        params["workflowMode"] = "live"
        params["confirm"] = "true"
        params["confirmed"] = "true"
        return Http._request("POST", url, params)

    @staticmethod
    def _request(method, url, params={}):
        params = urllib.urlencode(params)
        headers = {
            "Cookie": "atlassian.xsrf.token=%s; JSESSIONID=%s" % (Config.token, Config.jsessionid),
            "Content-Type": "application/x-www-form-urlencoded",
        }
        conn = httplib.HTTPConnection(Config.host)
        logging.debug("curl -X %(method)s -d '%(params)s' --cookie '%(cookie)s' http://%(host)s%(path)s" % {
            'method': method,
            'params': params,
            'cookie': headers['Cookie'],
            'host': Config.host,
            'path': url,
        })
        conn.request(method, url, params, headers)
        response = conn.getresponse()
        logging.debug("%s %s" % (response.status, response.reason))
        ret = response.read()
        response.close()
        return ret


class Delete(object):
    """ Delete Abstract Class """

    pretty_name = None
    list_url = None
    list_re = None
    safe_data = []
    delete_url = None
    delete_param = "id"

    def __init__(self):
        if not self.pretty_name:
            self.pretty_name = self.__class__.__name__

        if self.__class__.__name__ == "Delete":
            raise NotImplementedError

        logging.warning("Deleting %ss" % self.pretty_name)

    def get_delete_data(self):
        html = Http.GET(self.list_url, {"start": 0, "max": 10000})
        matches = re.findall(self.list_re, html)

        try:
            if isinstance(matches[0], tuple):
                matches = [id for string, id in matches]
        except IndexError:
            print("Not authorized or no entries.")

        def clean(matches):
            matches = [urllib.unquote(name).decode('utf8') for name in matches]
            matches = [name.replace('+', ' ') for name in matches]
            return matches

        return clean(matches)

    def run(self):
        for id in self.get_delete_data():
            if str(id) not in [str(x) for x in self.safe_data]:
                logging.info("Deleting %s: %s" % (self.pretty_name, id))
                Http.POST(self.delete_url, {self.delete_param: id})


"""
Migration procedures
"""

class DeleteGroups(Delete):
    pretty_name = "Group"
    list_url = "/GroupBrowser.jspa"
    list_re = r'DeleteGroup!default.jspa(.*);name=(.*)"'
    safe_data = DoNotDelete.groups
    delete_url = "/DeleteGroup.jspa"
    delete_param = "name"

class DeleteCustomFields(Delete):
    pretty_name = "Custom Field"
    list_url = "/ViewCustomFields.jspa"
    list_re = r'DeleteCustomField!default.jspa\?(.*)id=([0-9]*)"'
    safe_data = DoNotDelete.custom_fields
    delete_url = "/DeleteCustomField.jspa"

class DeletePermissionSchemes(Delete):
    pretty_name = "Permission Scheme"
    list_url = "/ViewPermissionSchemes.jspa"
    list_re = r'DeletePermissionScheme!default.jspa\?schemeId=([0-9]*)"'
    safe_data = DoNotDelete.permission_schemes
    delete_url = "/DeletePermissionScheme.jspa"
    delete_param = "schemeId"

class DeleteNotificationSchemes(Delete):
    pretty_name = "Notification Scheme"
    list_url = "/ViewNotificationSchemes.jspa"
    list_re = r'DeleteNotificationScheme!default.jspa\?(.*)schemeId=([0-9]*)'
    safe_data = DoNotDelete.notification_schemes
    delete_url = "/DeleteNotificationScheme.jspa"
    delete_param = "schemeId"

class DeleteOutgoingMailServers(Delete):
    pretty_name = "Outgoing Mail Sever"
    list_url = "/OutgoingMailServers.jspa"
    list_re = r'DeleteMailServer!default.jspa\?id=([0-9]*)'
    delete_url = "/DeleteMailServer.jspa"

class DeleteProjectRoles(Delete):
    pretty_name = "Project Role"
    list_url = "/ViewProjectRoles.jspa"
    list_re = r'DeleteProjectRole!default.jspa\?id=([0-9]*)'
    safe_data = DoNotDelete.project_roles
    delete_url = "/DeleteProjectRole.jspa"

class DeleteProjects(Delete):
    pretty_name = "Projects"
    list_url = "/ViewProjects.jspa"
    list_re = r'DeleteProject!default.jspa\?pid=([0-9]*)'
    safe_data = DoNotDelete.projects
    delete_url = "/DeleteProject.jspa"
    delete_param = "pid"

class DeleteIssueTypes(Delete):
    pretty_name = "Issue Type"
    list_url = "/ViewIssueTypes.jspa"
    list_re = r'DeleteIssueType!default.jspa\?id=([0-9]*)'
    safe_data = DoNotDelete.issue_types
    delete_url = "/DeleteIssueType.jspa"

class DeleteIssueTypeSchemes(Delete):
    pretty_name = "Issue Type Scheme"
    list_url = "/ManageIssueTypeSchemes.jspa"
    list_re = r'DeleteOptionScheme!default.jspa\?(.*)schemeId=([0-9]*)'
    safe_data = DoNotDelete.issue_type_schemes
    delete_url = "/DeleteOptionScheme.jspa"
    delete_param = "schemeId"

class DeleteWorkflows(Delete):
    pretty_name = "Workflow"
    list_url = "/ListWorkflows.jspa"
    list_re = r'DeleteWorkflow.jspa\?(.*)workflowName=(.*)"'
    safe_data = DoNotDelete.workflows
    delete_url = "/DeleteWorkflow.jspa"
    delete_param = "workflowName"

class DeleteWorkflowSchemes(Delete):
    pretty_name = "Workflow Scheme"
    list_url = "/ViewWorkflowSchemes.jspa"
    list_re = r'DeleteWorkflowScheme!default.jspa\?(.*)schemeId=([0-9]*)'
    safe_data = DoNotDelete.workflow_schemes
    delete_url = "/DeleteWorkflowScheme.jspa"
    delete_param = "schemeId"

class DeleteStatuses(Delete):
    pretty_name = "Status"
    list_url = "/ViewStatuses.jspa"
    list_re = r'DeleteStatus!default.jspa\?id=([0-9]*)'
    safe_data = DoNotDelete.statuses
    delete_url = "/DeleteStatus.jspa"


def rename_users():
    logging.warning("Renaming Users")
    conn = ldap.initialize(Config.ldap_addr)
    conn.simple_bind_s(Config.ldap_user, Config.ldap_pass)

    with open(Config.entities_file) as f:
        logging.info("Looking up for usernames in %s" % Config.entities_file)
        content = f.read()
        userdata = re.compile(r'\n\s{4}<User\s.+?userName="([a-zAZ0-9]+?)".*?emailAddress="([[email protected]]+?)"')
        unames = userdata.findall(content)
        unames = sorted(unames)

    def get_ldap_username(email, oldname=None):
        oldname = oldname.strip(' ')
        email = email.strip(' ')
        result = conn.search_s(Config.ldap_dc, ldap.SCOPE_SUBTREE, '(proxyAddresses=*%s*)' % email, ['sAMAccountName'])

        try:
            newname = result[0][1]['sAMAccountName'][0]
            logging.debug("User %s matched as %s" % (email, newname))
            return newname
        except Exception:
            logging.debug("User %s not found in LDAP directory" % email)
            try:
                loginname = re.match("(.*?)@(%s)" % "|".join(Config.fallback_domains), email).group(1)
                logging.debug("Will use loginname %s from email addres %s" % (loginname, email))
                return loginname.lower()
            except Exception:
                logging.debug("Domain is not whitelisted, will use old username: %s" % oldname)
                return oldname.lower()

    for oldname, email in unames:
        newname = get_ldap_username(email, oldname)
        logging.info("Substituting username %s with %s" % (oldname, newname))
        replace_from = re.compile(r'="%s"' % oldname)
        content = replace_from.sub(r'="%s"' % newname, content)

    with open(Config.entities_file, "w") as f:
        logging.info("Writing new conent to file %s" % Config.entities_file)
        f.write(content)
        logging.info("Content of %s updated." % Config.entities_file)


if __name__ == "__main__":
    #DeleteOutgoingMailServers().run()
    #DeleteGroups().run()
    #Do not run now new cf added#DeleteCustomFields().run()
    #DeleteProjectRoles().run()
    #DeleteProjects().run()
    #DeleteIssueTypes().run()
    #DeleteIssueTypeSchemes().run()
    #DeleteWorkflowSchemes().run()
    #DeleteWorkflows().run()
    #DeleteStatuses().run()
    #DeletePermissionSchemes().run()
    #DeleteNotificationSchemes().run()

    rename_users()

1.11.6. Backup

  • XML (http://localhost:8080/secure/admin/XmlBackup!default.jspa)

  • rsync:

    • JIRA_HOME="/var/atlassian/application-data/jira"
    • JIRA_INSTALL="/opt/atlassian/jira/"
    • database replication
    • pg_dump i pg_restore
  • database replication consistency and rsync while upgrading

  • /var/atlassian/application-data/jira/.jira-home.lock

  • Cold standby in alternative datacenter

  • database replication between datacenter

  • cold standby and licensing (same SEN number)

Code Listing 1.5. Jira backup
#!/bin/bash

echo "[$(date)] Reading database configuration..."
USER=$(cat /opt/jira/home/dbconfig.xml |xmllint --xpath '//username/text()' -)
export PGPASSWORD=$(cat /opt/jira/home/dbconfig.xml |xmllint --xpath '//password/text()' -)
HOST=$(cat /opt/jira/home/dbconfig.xml |xmllint --xpath '//url/text()' - |awk -F'/' '{print $3}' |awk -F':' '{print $1}')
PORT=$(cat /opt/jira/home/dbconfig.xml |xmllint --xpath '//url/text()' - |awk -F'/' '{print $3}' |awk -F':' '{print $2}')
DATABASE=$(cat /opt/jira/home/dbconfig.xml |xmllint --xpath '//url/text()' - |awk -F'/' '{print $4}')

echo "[$(date)] Database connection settings..."
echo "User: $USER"
echo "Pass: **********" #$PGPASSWORD"
echo "Host: $HOST"
echo "Port: $PORT"
echo "Database: $DATABASE"

echo "[$(date)] Cleanup previous backups..."
rm -fr /opt/jira/backup/*

echo "[$(date)] Backup database..."
pg_dump -h $HOST -p $PORT -U $USER $DATABASE |gzip > /opt/jira/backup/jira-database_$(date +%F).tar.gz

echo "[$(date)] Backup home directory..."
tar -jcf /opt/jira/backup/jira-home_$(date +%F).tar.bz2 --exclude-caches-all /opt/jira/home

echo "[$(date)] All done."

1.11.7. Test Environment

Code Listing 1.6. Jira test environment
#!/usr/bin/env python2

from fabric.api import *
from fabric.colors import *
from fabric.contrib.console import confirm
from fabric.tasks import Task
from datetime import datetime


class CloneTask(Task):
    name = "clone"
    origin_user = None
    origin_host = None
    clone_user = None
    clone_host = None

    def run(self):
        timestamp_start = datetime.now().strftime("%Y-%m-%d %H:%M")
        print(yellow("[%s] Starting executing jobs..." % timestamp_start))
        execute(self.ask_user)
        execute(self.env_create)
        execute(self.env_rsync)
        execute(self.env_install)
        execute(self.deb_install)
        execute(self.jdk_rsync)
        execute(self.jdk_install)
        execute(self.clitools_rsync)
        execute(self.clitools_install)
        execute(self.preinstall)
        execute(self.install)
        execute(self.postinstall)
        timestamp_end = datetime.now().strftime("%Y-%m-%d %H:%M")
        print(yellow("[%s] Everything done." % timestamp_end))

    def ask_user(self):
        self.origin_user = prompt('What is the origin user?', default=self.origin_user, validate=r'^(\w+)$')
        self.origin_host = prompt('What is the origin host address?', default=self.origin_host)
        self.clone_user = prompt('What is the clone user?', default=self.clone_user, validate=r'^(\w+)$')
        self.clone_host = prompt('What is the clone host address?', default=self.clone_host)
        env.roledefs['origin'] = ["%s@%s" % (self.origin_user, self.origin_host)]
        env.roledefs['clone'] = ["%s@%s" % (self.clone_user, self.clone_host)]
        print("Origin: %s\nClone: %s" % (env.roledefs['origin'], env.roledefs['clone']))
        if not confirm("Continue with this settings?", default=False):
            abort("Aborting at user request.")

    @roles('origin')
    def get_ssh_pubkey(slef):
        print(green("Getting ssh pubkey from origin host..."))
        return sudo('cat /root/.ssh/id_rsa.pub')

    @roles('clone')
    def env_create(self):
        ssh_pubkey = execute(self.get_ssh_pubkey).popitem()[1]
        print(green("Creating Environment..."))
        sudo('mkdir -p /home/%s/.ssh' % self.clone_user)
        sudo('mkdir -p /opt/java')
        sudo('mkdir -p /opt/clitools')
        sudo('echo "%s" >> /home/%s/.ssh/authorized_keys' % (ssh_pubkey, self.clone_user))
        sudo('echo \'export PS1="[\[email protected]\[$(tput bold)\]\[$(tput setaf 1)\]\h\[$(tput sgr0)\]:\w\$] "\' >> /root/.bashrc')
        sudo('chown -R %s /home/%s' % (self.clone_user, self.clone_user))
        sudo('chown -R %s /opt/java' % self.clone_user)
        sudo('chown -R %s /opt/clitools' % self.clone_user)
        sudo('echo "nameserver 8.8.8.8" >> /etc/resolvconf/resolv.conf.d/head')
        sudo('echo "nameserver 8.8.6.6" >> /etc/resolvconf/resolv.conf.d/head')
        sudo('resolvconf -u')

    @roles('origin')
    def env_rsync(self):
        sudo('rsync -raz --delete /etc/environment %s:/tmp/environment' % env.roledefs['clone'][0])

    @roles('clone')
    def env_install(self):
        sudo('mv /tmp/environment /etc/environment')
        sudo('chown root:root /etc/environment')

    @roles('clone')
    def deb_install(self):
        print(green("Installing deb..."))
        sudo('curl http://repo.varnish-cache.org/debian/GPG-key.txt | sudo apt-key add -')
        sudo('echo "deb http://repo.varnish-cache.org/ubuntu/ precise varnish-3.0" | sudo tee -a /etc/apt/sources.list')
        sudo('apt-get --quiet update')
        sudo('DEBIAN_FRONTEND=noninteractive apt-get --quiet --yes install mysql-server')
	sudo('''sed -i -r -b "N;s/\[mysqld\]\\n#/\[mysqld\]\\ninnodb_file_per_table\\nmax_allowed_packet=1024M/g" /etc/mysql/my.cnf''')
        sudo('service mysql restart')
        sudo('apt-get install --quiet --yes varnish')
        sudo('apt-get install --quiet --yes htop')
        sudo('apt-get install --quiet --yes memcached')
        sudo('apt-get install --quiet --yes libmemcached-dev')
        sudo('apt-get install --quiet --yes wget')
        sudo('apt-get install --quiet --yes libxml2-utils')
        sudo('apt-get install --quiet --yes curl')
        sudo('apt-get install --quiet --yes git')
        sudo('apt-get install --quiet --yes nmap')
        sudo('apt-get install --quiet --yes gcc')
        sudo('apt-get install --quiet --yes python-pip')
        sudo('apt-get install --quiet --yes python-virtualenv')
        sudo('apt-get install --quiet --yes libsasl2-dev')
        sudo('apt-get install --quiet --yes python-dev')
        sudo('apt-get install --quiet --yes libldap2-dev')
        sudo('apt-get install --quiet --yes libmysqld-dev')
        sudo('apt-get install --quiet --yes mc')

    @roles('origin')
    def jdk_rsync(self):
        print(green("Rsyncing jdk..."))
        sudo('rsync -raz --delete /opt/java/ %s:/opt/java' % env.roledefs['clone'][0])

    @roles('clone')
    def jdk_install(self):
        print(green("Installing jdk..."))
        sudo('update-alternatives --install /usr/bin/java java /opt/java/default/bin/java 1')
        sudo('update-alternatives --set java /opt/java/default/bin/java')
        sudo('chown -R root:root /opt/java')

    @roles('origin')
    def clitools_rsync(self):
        print(green("Installing clitools..."))
        sudo('rsync -raz --delete /opt/clitools/ %s:/opt/clitools' % env.roledefs['clone'][0])

    @roles('clone')
    def clitools_install(self):
        sudo('chown -R root:root /opt/clitools')

    def preinstall(self):
        raise NotImplementedError

    def install(self):
        raise NotImplementedError

    def postinstall(self):
        raise NotImplementedError


class UpdateTask(Task):
    name = "update"
    origin_user = None
    origin_host = None
    clone_user = None
    clone_host = None

    def run(self):
        timestamp_start = datetime.now().strftime("%Y-%m-%d %H:%M")
        print(yellow("[%s] Starting executing jobs..." % timestamp_start))
        execute(self.ask_user)
        execute(self.jdk_rsync)
        execute(self.preinstall)
        execute(self.install)
        execute(self.postinstall)
        timestamp_end = datetime.now().strftime("%Y-%m-%d %H:%M")
        print(yellow("[%s] Everything done." % timestamp_end))

    def ask_user(self):
        self.origin_user = prompt('What is the origin user?', default=self.origin_user, validate=r'^(\w+)$')
        self.origin_host = prompt('What is the origin host address?', default=self.origin_host)
        self.clone_user = prompt('What is the clone user?', default=self.clone_user, validate=r'^(\w+)$')
        self.clone_host = prompt('What is the clone host address?', default=self.clone_host)
        env.roledefs['origin'] = ["%s@%s" % (self.origin_user, self.origin_host)]
        env.roledefs['clone'] = ["%s@%s" % (self.clone_user, self.clone_host)]
        print("Origin: %s\nClone: %s" % (env.roledefs['origin'], env.roledefs['clone']))
        if not confirm("Continue with this settings?", default=False):
            abort("Aborting at user request.")

    @roles('origin')
    def jdk_rsync(self):
        print(green("Rsyncing jdk..."))
        sudo('rsync -raz --delete /opt/java/ %s:/opt/java' % env.roledefs['clone'][0])

    def preinstall(self):
        raise NotImplementedError

    def install(self):
        raise NotImplementedError

    def postinstall(self):
        raise NotImplementedError


"""
.. todo:
   * Add consolidate DeleteProjects.* to this module
   * Add dry-run option
"""


class Clone(CloneTask):
    name = "clone"
    origin_user = None
    origin_host = "localhost"
    clone_user = "ubuntu"
    clone_host = None

    @roles('clone')
    def preinstall(self):
        print(green("Configuring preinstall actions..."))
        with settings(warn_only=True):
            sudo('useradd --system jira')
        sudo('mkdir -p /opt/jira')
        sudo('chown -R %s /opt/jira' % self.clone_user)

    @roles('origin')
    def install(self):
        print(green("Rsyncing..."))
        clone = env.roledefs['clone'][0]
        exclude = [
            "home/caches/*",
            "home/data/attachments/*",
            "home/export/*",
            "home/import/*",
            "home/log/*",
            "home/tmp/*",
            "*/logs/*",
            "*/jre/*",
	    "home/plugins/.bundled-plugins/*",
	    "*/temp/*",
	    "home/plugins/.osgi-plugins"]
        sudo('rsync -raz --delete --exclude=%(exclude)s /opt/jira/ %(clone)s:/opt/jira' % {
            "clone": clone,
            "exclude": " --exclude=".join(exclude)})
        sudo('rsync -raz --delete /etc/init.d/jira %s:/opt/jira/initd.sh' % clone)

    @roles('clone')
    def postinstall(self):
        print(green("Configuring postinstall actions..."))
        sudo('sed -i "s/localhost/127.0.0.1/g" /opt/jira/home/dbconfig.xml')
        sudo('mysql -e "drop database if exists jira;"')
        sudo('mysql -e "create database jira /*!40100 DEFAULT CHARACTER SET utf8 COLLATE utf8_bin */;"')
        sudo('mysql -e "create user [email protected] identified by \'jira\';"')
        sudo('mysql -e "grant all privileges on jira.* to [email protected] identified by \'jira\';"')
        sudo('mysqldump -hlocalhost -ujira -p"jira" --lock-all-tables jira |mysql jira')
        sudo('mysql -e "delete from jira.filtersubscription";')
        sudo('mysql -e "delete from jira.mailserver";')
        sudo('mysql -e "update jira.propertystring set propertyvalue=\'http://%s:8080\' where id in (select id from jira.propertyentry where property_key like \'%%baseurl%%\');"' % env.roledefs['clone'][0].split('@')[1])
        sudo('mysql -e "update jira.cwd_user set credential=\'x61Ey612Kl2gpFL56FT9weDnpSo4AV8j8+qx2AuTHdRyY036xxzTTrw10Wq3+4qQyB+XURPWx1ONxp3Y3pB37A==\' where user_name=\'admin\';"')
	sudo('mysql -e "update jira.propertytext set propertyvalue=\'<h3>This is a JIRA test instance</h3>\' where ID=\'11216\';"')
        sudo('date > /opt/jira/database_lastupdate')
        sudo('''sed -i 's/JVM_MINIMUM_MEMORY=".*"/JVM_MINIMUM_MEMORY="256M"/g' /opt/jira/install/bin/setenv.sh''')
        sudo('''sed -i 's/JVM_MAXIMUM_MEMORY=".*"/JVM_MAXIMUM_MEMORY="768M"/g' /opt/jira/install/bin/setenv.sh''')
        sudo('''sed -i 's/scheme="https"//g' /opt/jira/install/conf/server.xml''')
        sudo('''sed -i 's/proxyName="localhost"//g' /opt/jira/install/conf/server.xml''')
        sudo('''sed -i 's/proxyPort="443"//g' /opt/jira/install/conf/server.xml''')
        sudo('echo "jira.autoexport=false" >> /opt/jira/home/jira-config.properties')
        sudo('mv /opt/jira/initd.sh /etc/init.d/jira')
        sudo('chown root:root /etc/init.d/jira')
        sudo('chmod +x /etc/init.d/jira')
        sudo('chown -R jira:jira /opt/jira')
        print(red('/etc/init.d/jira start'))
        print(red('sleep 240 && /opt/jira/home/reindex.sh'))


class Update(UpdateTask):
    name = "update"

    @roles('clone')
    def preinstall(self):
        print(green("Configuring preinstall actions..."))
        sudo('/etc/init.d/jira stop')
        sudo('chown -R %s /opt/jira' % self.clone_user)

    @roles('origin')
    def install(self):
        print(green("Rsyncing..."))
        clone = env.roledefs['clone'][0]
        exclude = [
            "home/caches/*",
            "home/data/attachments/*",
            "home/export/*",
            "home/import/*",
            "home/log/*",
            "home/tmp/*",
            "*/logs/*",
            "*/jre/*",
	    "home/plugins/.bundled-plugins/*",
            "*/temp/*",
            "home/plugins/.osgi-plugins"]
        sudo('rsync -raz --delete --exclude=%(exclude)s /opt/jira/ %(clone)s:/opt/jira' % {
            "clone": clone,
            "exclude": " --exclude=".join(exclude)})
        sudo('rsync -raz --delete /etc/init.d/jira %s:/opt/jira/initd.sh' % clone)

    @roles('clone')
    def postinstall(self):
        print(green("Configuring postinstall actions..."))
        sudo('sed -i "s/localhost/127.0.0.1/g" /opt/jira/home/dbconfig.xml')
        print(red('mysql -e "drop database if exists jira;"'))
        print(red('mysql -e "create database jira /*!40100 DEFAULT CHARACTER SET utf8 COLLATE utf8_bin */;"'))
        print(red('mysql -e "grant all privileges on jira.* to [email protected] identified by \'localhost\';"'))
        print(red('mysqldump -hlocalhost -ujira -p"jira" --lock-all-tables jira |mysql jira'))
        print(red('mysql -e "delete from jira.filtersubscription";'))
        print(red('mysql -e "delete from jira.mailserver";'))
        print(red('mysql -e "update jira.propertystring set propertyvalue=\'http://%s:8080\' where id in (select id from jira.propertyentry where property_key like \'%%baseurl%%\');"' % env.roledefs['clone'][0].split('@')[1]))
        print(red('mysql -e "update jira.cwd_user set credential=\'x61Ey612Kl2gpFL56FT9weDnpSo4AV8j8+qx2AuTHdRyY036xxzTTrw10Wq3+4qQyB+XURPWx1ONxp3Y3pB37A==\' where user_name=\'admin\';"'))
	print(red('mysql -e "update jira.propertytext set propertyvalue=\'<h3>This is a JIRA test instance</h3>\' where ID=\'11216\';"'))
        sudo('date > /opt/jira/database_lastupdate')
        sudo('''sed -i 's/JVM_MINIMUM_MEMORY=".*"/JVM_MINIMUM_MEMORY="256M"/g' /opt/jira/install/bin/setenv.sh''')
        sudo('''sed -i 's/JVM_MAXIMUM_MEMORY=".*"/JVM_MAXIMUM_MEMORY="768M"/g' /opt/jira/install/bin/setenv.sh''')
        sudo('''sed -i 's/scheme="https"//g' /opt/jira/install/conf/server.xml''')
        sudo('''sed -i 's/proxyName="localhost"//g' /opt/jira/install/conf/server.xml''')
        sudo('''sed -i 's/proxyPort="443"//g' /opt/jira/install/conf/server.xml''')
        sudo('mv /opt/jira/initd.sh /etc/init.d/jira')
        sudo('chown root:root /etc/init.d/jira')
        sudo('chown -R jira:jira /opt/jira')
        sudo('chmod +x /etc/init.d/jira')
        print(red('/etc/init.d/jira start'))
        print(red('sleep 240'))
        print(red('/opt/jira/home/reindex.sh'))

clone = Clone()
update = Update()
Code Listing 1.7. Jira delete projects
import httplib
import urllib
import logging
import re


"""
.. todo::
   * Add autodiscovery of token and jsessionid
   * Do not delete projects by porojectkey not id
   * Simplify
   * Remove not used headers and params
"""


class Config(object):
    host = 'localhost:8080'
    do_not_delete_project = [10300] #EKO

    # You can get this from inspecting HTTP request with WebInspector in your Browser
    token = '...'
    jsessionid = '...'


"""
You shouldn't change anything below this point,
unless you know what are you doing.
"""
logging.basicConfig(
    level=logging.INFO,
    format='[%(asctime).19s] %(levelname)s: %(message)s'
)


class Http(object):

    @staticmethod
    def GET(url, params={}):
        params["atl_token"] = Config.token
        params = urllib.urlencode(params)
        return Http._request("GET", "%s?%s" %(url, params))

    @staticmethod
    def POST(url, params={}):
        params["atl_token"] = Config.token
        params["Delete"] = "Delete"
        params["confirmedDelete"] = "true"
        params["workflowMode"] = "live"
        params["confirm"] = "true"
        params["confirmed"] = "true"
        return Http._request("POST", url, params)

    @staticmethod
    def _request(method, url, params={}):
        params = urllib.urlencode(params)
        headers = {
            "Cookie": "atlassian.xsrf.token=%s; JSESSIONID=%s" % (Config.token, Config.jsessionid),
            "Content-Type": "application/x-www-form-urlencoded",
        }
        conn = httplib.HTTPConnection(Config.host)
        logging.debug("curl -X %(method)s -d '%(params)s' --cookie '%(cookie)s' http://%(host)s%(path)s" % {
            'method': method,
            'params': params,
            'cookie': headers['Cookie'],
            'host': Config.host,
            'path': url,
        })
        conn.request(method, url, params, headers)
        response = conn.getresponse()
        logging.debug("%s %s" % (response.status, response.reason))
        ret = response.read()
        response.close()
        return ret


class DeleteAbstract(object):
    pretty_name = None
    list_url = None
    list_re = None
    safe_data = []
    delete_url = None
    delete_param = "id"

    def __init__(self):
        if not self.pretty_name:
            self.pretty_name = self.__class__.__name__
        if self.__class__.__name__ == "DeleteAbstract":
            raise NotImplementedError
        logging.warning("%s" % self.pretty_name)

    def get_delete_data(self):
        html = Http.GET(self.list_url, {"start":0, "max":10000})
        matches = re.findall(self.list_re, html)
        try:
            if isinstance(matches[0], tuple):
                matches = [id for string, id in matches]
        except IndexError:
            print("Not authorized or no entries.")

        def clean(matches):
            matches = [urllib.unquote(name).decode('utf8') for name in matches]
            matches = [name.replace('+', ' ') for name in matches]
            return matches
        return clean(matches)

    def run(self):
        for id in self.get_delete_data():
            if str(id) not in [str(x) for x in self.safe_data]:
                logging.info("Deleting %s: %s" % (self.pretty_name, id))
                Http.POST(self.delete_url, {self.delete_param: id})


class DeleteProjects(DeleteAbstract):
    pretty_name = "Deleting Projects"
    list_url = "/ViewProjects.jspa"
    list_re = r'DeleteProject!default.jspa\?pid=([0-9]*)'
    safe_data = Config.do_not_delete_project
    delete_url = "/DeleteProject.jspa"
    delete_param = "pid"


if __name__ == "__main__":
    DeleteProjects().run()

1.11.8. Jira Performance

  • JProfiler
  • MAT (Memory Analyzer Tool) [heapdump and MAT from Eclipse]
  • Performance SQL
  • own database indexes
  • pgpool and database cache
  • nginx as a SSL terminator
  • Varnish caching REST responses (JSON) and static files
  • Java Melody
  • New Relic

1.11.8.1. Optymalizacje

  • Wyłączyć Activity Stream
  • Update gadżetów na Dashboardzie (update na bazie dla wszystkich gadgetów)
  • Edukacja użytkowników aby nie mieli odpalonych miliona zakładek z JIRĄ
  • Czy wszystkie monitory z Wallboardami są potrzebne?

1.11.8.2. Database

  • /var/atlassian/application-data/jira/dbconfig.xml
<pool-min-size>20</pool-min-size>
<pool-max-size>20</pool-max-size>
<pool-max-wait>30000</pool-max-wait>
<validation-query>select 1</validation-query>
<min-evictable-idle-time-millis>60000</min-evictable-idle-time-millis>
<time-between-eviction-runs-millis>300000</time-between-eviction-runs-millis>
<pool-max-idle>20</pool-max-idle>
<pool-remove-abandoned>true</pool-remove-abandoned>
<pool-remove-abandoned-timeout>300</pool-remove-abandoned-timeout>
<pool-test-on-borrow>false</pool-test-on-borrow>
<pool-test-while-idle>true</pool-test-while-idle>

1.11.8.3. Garbage Collector

Code Listing 1.8. Jira Garbage Collector
JIRA_HOME="/opt/jira/home"
JVM_SUPPORT_RECOMMENDED_ARGS="-server -XX:MaxPermSize=512m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+OptimizeStringConcat -XX:+PrintGCDetails -XX:+DisableExplicitGC -Xloggc:/opt/jira/logs/gc-jira-$(hostname)-$(date +%Y.%m.%d).log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=10M"
JVM_MINIMUM_MEMORY="512m"
JVM_MAXIMUM_MEMORY="2048m"

# -server
# -XX:MaxPermSize=512m
# -XX:+UseG1GC
# -XX:+PrintGC
# -XX:MaxGCPauseMillis=200
# -XX:+PrintGCDateStamps
# -XX:+PrintGCDetails
# -XX:+UseGCLogFileRotation
# -XX:GCLogFileSize=10M
# -Xloggc:/opt/jira/logs/gc-jira-$(hostname)-$(date +%F).log
# -XX:NumberOfGCLogFiles=10
# -XX:+OptimizeStringConcat
# -XX:+DisableExplicitGC



# -Xms --> Minimum Memory
# -Xmx --> Maximum Memory
# -Xmn --> Heap of Younger Generation
# -Xss --> Thread Stack Size
# -XX:MaxMetaspaceSize --> Maximum Memory for Non-Heap Metaspace.
# -XX:NewRatio --> Ratio between Younger and Older Generation Memory sizes.
# -XX:ParallelGCThreads --> No of Parallel GC threads. By default, the GC threads will be equal to the number of CPUs of the Node / VM. Used when Parallel Garbage collectors are configured.






GC_JVM_PARAMETERS=""
GC_JVM_PARAMETERS="-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintGCCause ${GC_JVM_PARAMETERS}"
GC_JVM_PARAMETERS="-Xloggc:$LOGBASEABS/logs/atlassian-jira-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M ${GC_JVM_PARAMETERS}"




## Defaultowe ustawienia Jiry po instalacji:
/opt/atlassian/jira/jre//bin/java
	-Djava.util.logging.config.file=/opt/atlassian/jira/conf/logging.properties
	-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
	-Xms384m
	-Xmx768m
	-Djava.awt.headless=true
	-Datlassian.standalone=JIRA
	-Dorg.apache.jasper.runtime.BodyContentImpl.LIMIT_BUFFER=true
	-Dmail.mime.decodeparameters=true
	-Dorg.dom4j.factory=com.atlassian.core.xml.InterningDocumentFactory
	-XX:-OmitStackTraceInFastThrow
	-Datlassian.plugins.startup.options=
	-Djdk.tls.ephemeralDHKeySize=2048
	-Djava.protocol.handler.pkgs=org.apache.catalina.webresources
	-Xloggc:/opt/atlassian/jira/logs/atlassian-jira-gc-%t.log
	-XX:+UseGCLogFileRotation
	-XX:NumberOfGCLogFiles=5
	-XX:GCLogFileSize=20M
	-XX:+PrintGCDetails
	-XX:+PrintGCDateStamps
	-XX:+PrintGCTimeStamps
	-XX:+PrintGCCause
	-classpath /opt/atlassian/jira/bin/bootstrap.jar:/opt/atlassian/jira/bin/tomcat-juli.jar
	-Dcatalina.base=/opt/atlassian/jira
	-Dcatalina.home=/opt/atlassian/jira
	-Djava.io.tmpdir=/opt/atlassian/jira/temp
	org.apache.catalina.startup.Bootstrap start

1.11.8.4. Monitorowanie

1.11.8.5. Rozwiązywanie problemów

grep '/rest' /opt/atlassian/jira/logs/access_log.* |awk '{print $7}' |sort |uniq -c |sort -n

1.11.9. Baza danych

  • AO = Add-On (plugins)
  • cwd_user i cwd_directories
  • jiraissue
  • mailserver
  • filtersubscription
  • worklog
  • customfieldvalue i customfield
  • project i project_key
  • fileattachment
ssh -L 5432:localhost:5432 [email protected]

1.11.9.1. Backup data with pg_dump

$ service jira stop
$ pg_dump -i -h localhost -p 5432 -U jira -F c -b -v -f "/tmp/$(date +%F)_jira.pgdump" jira
$ pg_dump -?
-p, –port=PORT database server port number
-i, –ignore-version proceed even when server version mismatches
-h, –host=HOSTNAME database server host or socket directory
-U, –username=NAME connect as specified database user
-W, –password force password prompt (should happen automatically)
-d, –dbname=NAME connect to database name
-v, –verbose verbose mode
-F, –format=c|t|p output file format (custom, tar, plain text)
-c, –clean clean (drop) schema prior to create
-b, –blobs include large objects in dump
-v, –verbose verbose mode
-f, –file=FILENAME output file name

1.11.9.2. Restore data with pg_restore

DROP DATABSE jira;
CREATE DATABASE jira_new;
GRANT ALL PRIVILEGES ON DATABASE jira_new TO jira;
$ pg_restore -i -h localhost -p 5432 -U jira -v "/tmp/$(date +%F)_jira.pgdump" -d jira_new
$ pg_restore -?
-p, –port=PORT database server port number
-i, –ignore-version proceed even when server version mismatches
-h, –host=HOSTNAME database server host or socket directory
-U, –username=NAME connect as specified database user
-W, –password force password prompt (should happen automatically)
-d, –dbname=NAME connect to database name
-v, –verbose verbose mode

1.11.9.3. Restore data with psql from plaintext SQL

$ psql -h localhost -p 5432 -U jira -d jira < "/tmp/$(date +%F)_jira.pgdump"

1.11.9.4. Change JIRA DB config

  • Change /var/atlassian/application-data/jira/dbconfig.xml
$ service jira start

1.11.10. Assignments

1.11.10.1. Administracja - bazą danych

  1. Zrób backup bazy danych (musi być data w nazwie pliku)
  2. Zrób drop bazy
  3. Zmień DB Pool connection
  4. Przywróć backup do bazy jira_new
  5. Dodaj polecenie backupu bazy danych do crontab z @midnight

1.11.10.2. Administracja - backup

  1. Zrób backup $JIRA_HOME i $JIRA_INSTALL wykorzystując tar.gz (musi być data w nazwie pliku)

  2. Wylistuj pliki w archiwum (możesz przeglądnąć za pomocą midnight commander)

  3. Usuń katalogi $JIRA_HOME i $JIRA_INSTALL

  4. Przywróć oba katalogi do:

    • /opt/jira/home
    • /opt/jira/install
  5. Podmienić skrypty startowe

  6. Uruchom Jirę z nowej lokalizacji

  7. Dodaj polecenie backupu $JIRA_HOME i $JIRA_INSTALL do crontab z @midnight

1.11.10.3. Administracja - Garbage Collector

  1. Zmień Garbage Collector na G1
  2. Zmień Xmx na 1GB
  3. Wepnij Java Melody do monitorowania

1.11.10.4. Administracja - Zmiana Javy

  1. Zainstaluj nową Javę na serwerze w katalogu /opt/java/$VERSION
  2. Utwórz symlink /opt/java/default/ wskazujący na /opt/java/$VERSION (dlaczego to dobra praktyka?)
  3. Zrestartuj Jirę by wykorzystywała nową Javę