AgileOps: Jira-центричный подход
Intro
Благородя своей гибкости, Jira в большинстве крупных компаний, используется в качестве единой системы принятия решений и отслеживания изменений.
Вопреки расхожему мнению, далеко не все компании занимаются разработкой софта.
Во многих крупных организациях Dev и Ops — это всего лишь одно структурное подразделение, так называемый Cost Center (то есть напрямую компании доходы не приносит). При этом одной из главных задач в Agile является обеспечение прозрачности процессов для всех заинтересованных сторон (stakeholders).
Для многих компаний парадигма JiraOps или скорее IssueTrackingOps подходит гораздо лучше, чем GitOps.
Для того, чтобы убрать препоны между бизнесом, DEV, OPS, SEC, QA и обеспечить прозрачность процессов можно разрабатывать свою автоматизацию для Jira, либо использовать инструменты от сторонних разработчиков.
Jira Server
Для работы нам понадобится Jira Data Center — тестовый стенд с триальной версией Jira.
Для этих целей будем использовать docker-compose.yml:
version: '3.7'
services:
node:
image: atlassian/jira-software:latest
ports:
- 9099:8080
volumes:
- ./jira-home-node1:/var/atlassian/application-data/jira:Z
ulimits:
nproc: 65535
nofile:
soft: 65535
hard: 65535
database:
image: postgres:10.5-alpine
volumes:
- ./postgresql-data:/var/lib/postgresql/data:Z
- ./postgre-db-init.sql:/docker-entrypoint-initdb.d/postgre-db-init.sql:Z
ports:
- 5432:5432
environment:
POSTGRES_PASSWORD: 1234
Запускаем Jira Server:
docker-compose up -d
Jira UI будет доступен по адресу http://
Нужно будет проклинать установочный визард, сгенерировать триальный ключ и создать пользователя.
jira_setup
CLI
У Jira есть отличный API и клиентские библиотеки для Python и Jira.
Для автоматизации CI/CD и мониторинга мы будем использовать go-jira (https://github.com/go-jira/jira), а для BI и ad-hoc аналитики Python и SQL.
Go-jira это и библиотека для работы с API Jira и инструмент командной строки, который идеально подходит для встраивания в CI/CD процессы.
Пример использования Go-клиента — получаем jira issues для проекта OpenJDK:
main.go:
package main
import (
"fmt"
"github.com/andygrunwald/go-jira"
)
func main() {
jiraClient, _ := jira.NewClient(nil, "https://bugs.openjdk.org")
jql := "project = JDK and status = Open ORDER BY updated ASC"
issues, resp, err := jiraClient.Issue.Search(jql, nil)
if err != nil {
fmt.Println(err)
panic(resp.StatusCode)
}
for _, issue := range issues {
fmt.Printf("%s: %+v\n", issue.Key, issue.Fields.Summary)
}
}
Получаем issues для проекта OpenJDK:
go run main.go | head -10
JDK-4158929: 8 bit Dithering problem in SwingSet HTML Text tab.
JDK-4184200: Color profiles for screen device should be used when available.
JDK-4199882: splotchy highlight at 24 bit True Color
JDK-4345973: Add set/get methods for boolean datatype in ParameterBlock
JDK-4148927: Input method highlighting needs to handle highlight segments
JDK-4102920: 12x: Multi-font caching scheme inadequate for non-Latin text
JDK-4387892: swing components show all black @ 16-bit depth (VNC on Linux)
JDK-4398379: java.awt.image.DataBufferShort constructor should verify size of array
JDK-4398381: java.awt.image.DataBufferShort should check for negative size
JDK-4396157: Merlin: Consonant RA Rule R2 doesn't hold true with Lucida font
Благородя CLI с Jira можно работать так же как и Git:
jira mine
+--------+------------+-------+----------+--------+----------+--------------+--------------+
| Issue | Summary | Type | Priority | Status | Age | Reporter | Assignee |
+--------+------------+-------+----------+--------+----------+--------------+--------------+
| JIRA-1 | Jira-1 | Story | Medium | To Do | an hour | Anton Krylov | Anton Krylov |
| JIRA-2 | Demo Issue | Bug | Medium | To Do | a minute | Anton Krylov | Anton Krylov |
+--------+------------+-------+----------+--------+----------+--------------+--------------+
Только сначала нужно будет создать конфиг для Jira:
mkdir ~/.jira.d
cat <~/.jira.d/config.yml
endpoint: https://jira.mycompany.com
EOM
config.yml
config:
stop: true
password-source: stdin
endpoint: http://:9099
user:
login:
project: JIRA
queries:
todo: >-
resolution = unresolved {{if .project}}AND project = '{{.project}}'{{end}} AND status = 'To Do'
custom-commands:
- name: env
help: print the JIRA environment variables available to custom commands
script: |-
env | sort | grep JIRA
- name: print-project
help: print the name of the configured project
script: "echo $JIRA_PROJECT"
- name: jira-path
help: print the path the jira command that is running this alias
script: |-
echo {{jira}}
- name: mine
help: display issues assigned to me
script: |-
{{jira}} list --template table --query "resolution = unresolved AND project=$JIRA_PROJECT AND assignee=currentuser() ORDER BY status, issue"
- name: mine-created
help: display issues created by me
script: |-
{{jira}} list --template table --query "resolution = unresolved AND project=$JIRA_PROJECT AND creator=currentuser() ORDER BY created"
- name: watching
help: display issues watched by me
script: |-
{{jira}} list --template table --query "resolution = unresolved AND project=$JIRA_PROJECT AND watcher=currentuser() ORDER BY created"
script: |-
if [ -n "$JIRA_PROJECT" ]; then
# if `project: ...` configured just list the issues for current project
{{jira}} list --template table --query "resolution = unresolved and assignee=currentuser() and project = $JIRA_PROJECT ORDER BY priority asc, created"
else
# otherwise list issues for all project
{{jira}} list --template table --query "resolution = unresolved and assignee=currentuser() ORDER BY priority asc, created"
fi
- name: sprint
help: display issues for active sprint
script: |-
if [ -n "$JIRA_PROJECT" ]; then
# if `project: ...` configured just list the issues for current project
{{jira}} list --template table --query "sprint in openSprints() and type != epic and resolution = unresolved and project=$JIRA_PROJECT ORDER BY rank asc, created"
else
# otherwise list issues for all project
{{jira}} list --template table --query "sprint in openSprints() and type != epic and resolution = unresolved ORDER BY rank asc, created"
fi
Если в кофиге password-source: stdin
то пароль для jira нужно передавать следующим образом:
export JIRA_PASSWORD='\n'
echo $JIRA_PASSWORD| jira ls
Если будет password-source: pass
то jira «попросит» его в интерактивном режиме.
jira mine это custom-command
который выводит все jira issues для текущего пользователя.
Таким образом можно создавать различные команды использующие api и jql для работы с jira.
- name: mine
help: display issues assigned to me
script: |-
{{jira}} list --template table --query "resolution = unresolved AND project=$JIRA_PROJECT AND assignee=currentuser() ORDER BY status, issue"
Можно быстро прикреплять файлы к issues из Bash:
jira attach create JIRA-1 exec.zip
OK 10000 http://109.207.172.67:9099/secure/attachment/10000/exec.zip
А если их несколько то можно написать Bash-скрипт:
for i in exec1.zip exec2.zip exec3.zip exec4.zip; do jira attach create JIRA-1 $i; done
OK 10003 http://109.207.172.67:9099/secure/attachment/10003/exec1.zip
OK 10004 http://109.207.172.67:9099/secure/attachment/10004/exec2.zip
OK 10005 http://109.207.172.67:9099/secure/attachment/10005/exec3.zip
OK 10006 http://109.207.172.67:9099/secure/attachment/10006/exec4.zip
Вложения можно быстро скачивать из командной строки:
jira attach list JIRA-1
+-------+-----------+---------+--------------+-----------+
| id | filename | bytes | user | created |
+-------+-----------+---------+--------------+-----------+
| 10000 | exec.zip | 4209783 | Anton Krylov | 5 minutes |
| 10001 | exec.zip | 4209783 | Anton Krylov | 3 minutes |
| 10002 | exec.zip | 4209783 | Anton Krylov | 3 minutes |
| 10003 | exec1.zip | 4209783 | Anton Krylov | a minute |
| 10004 | exec2.zip | 4209783 | Anton Krylov | a minute |
| 10005 | exec3.zip | 4209783 | Anton Krylov | a minute |
| 10006 | exec4.zip | 4209783 | Anton Krylov | a minute |
+---
Представьте себе, что вам нужно выполнять какие-то однокипные действия — например генерировать сертификаты для нового кластера Kubernetes.
Вы написали скрипт, который делает это автоматически:
make -f mkcerts.mk root-ca
generating root-key.pem
generating root-cert.csr
generating root-cert.pem
Certificate request self-signature ok
subject=O=Istio, CN=Root CA
А теперь эту задачу должен проконтролировать ИБ:
jira attach create JIRA-1 kube_certs.tar
OK 10007 http://109.207.172.67:9099/secure/attachment/10007/kube_certs.tar
kube-certs
Представим себе, что нужно создать SSH-ключ для нового сотрудника. И конечно эта задача должны пройти несколько стадий проверки,
а в случаи если сотрудник покидает компанию, вся информацию об этой задачи и ключах будет хранится в Jira:
ssh-key
Эту задачу можно превратить в YAML-шаблон, параметризовать и хранить в Git:
ssh-key-template
Задача может быть и посложней — например генерировать сертификаты для кластера Kafka:
bash genkeys.sh
Эти файлы нужно заархивировать:
tree
.
├── broker-ca-signed.crt
├── broker.csr
├── broker_keystore_creds
├── broker_sslkey_creds
├── broker_truststore_creds
├── consumer-ca-signed.crt
├── consumer.csr
├── consumer_keystore_creds
├── consumer_sslkey_creds
├── consumer_truststore_creds
├── genkeys.sh
├── jcerts-kafka-ca-key.pem
├── jcerts-kafka-ca.pem
├── jcerts-kafka-ca.srl
├── kafka.broker.keystore.cer.pem
├── kafka.broker.keystore.jks
├── kafka.broker.keystore.key.pem
├── kafka.broker.keystore.p12
├── kafka.broker.truststore.jks
├── kafka.consumer.keystore.cer.pem
├── kafka.consumer.keystore.jks
├── kafka.consumer.keystore.key.pem
├── kafka.consumer.keystore.p12
├── kafka.consumer.truststore.jks
├── kafka.producer.keystore.cer.pem
├── kafka.producer.keystore.jks
├── kafka.producer.keystore.key.pem
├── kafka.producer.keystore.p12
├── kafka.producer.truststore.jks
├── producer-ca-signed.crt
├── producer.csr
├── producer_keystore_creds
├── producer_sslkey_creds
└── producer_truststore_creds
И прикрепить к задаче:
find . -print0 | tar -cvf kafka_certs.tar --null -T -
jira attach create JIRA-2 kafka_certs.tar
OK 10100 http://109.207.172.67:9099/secure/attachment/10100/kafka_certs.tar
Автоматизация может быть и сложнее, поэтому неплохо было-бы это все «завернуть» в Docker, положить в Git и добавить в CI:
Dockerfile:
ARG JIRA_PROJECT
ENV JIRA_PASSWORD=JIRA_PROJECT
RUN apt-get update && apt-get -y install golang
RUN go install github.com/go-jira/jira/cmd/jira@latest
RUN ln -s ~/go/bin/jira /usr/local/bin/jira
COPY config.yml ~/.jira.d/config.yml
COPY gencerts.sh gencerts.sh
RUN bash gencerts.sh
RUN jira attach create INFRA kafka_certs.tar
положить в Git и сделать частью CI:
docker build . --build-arg='JIRA_PROJECT=INFRA'
GUI
Существует несколько GUI клиентов для Jira.
Один из них давно перешел в статус «Deprecated», но без проблем работает с последними версиями Jira:
GUI1
GUI2
Для чего это нужно?
Бывает множество нетехнических пользователей, которым сложно привыкнуть к Web-UI Jira.
Иногда в закрытом контуре, в принципе нет доступа к браузеру, на рабочем месте.
End
PS. Если наберется 50 лайков, то я напишу вторую, часть, где мы разберем как прикрутить продвинутый BI для Jira, без лишних хлопот и скриптов на Groovy.