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://:9099

Нужно будет проклинать установочный визард, сгенерировать триальный ключ и создать пользователя.

jira_setup

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

kube-certs

Представим себе, что нужно создать SSH-ключ для нового сотрудника. И конечно эта задача должны пройти несколько стадий проверки,
а в случаи если сотрудник покидает компанию, вся информацию об этой задачи и ключах будет хранится в Jira:

ssh-key

ssh-key

Эту задачу можно превратить в YAML-шаблон, параметризовать и хранить в Git:

ssh-key-template

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

GUI1

GUI2

GUI2

Для чего это нужно?

  • Бывает множество нетехнических пользователей, которым сложно привыкнуть к Web-UI Jira.

  • Иногда в закрытом контуре, в принципе нет доступа к браузеру, на рабочем месте.

End

PS. Если наберется 50 лайков, то я напишу вторую, часть, где мы разберем как прикрутить продвинутый BI для Jira, без лишних хлопот и скриптов на Groovy.

© Habrahabr.ru