mlog

備忘録とかメモとか

【Redmine】Python で Redmine CLI ツールを作ってみる!

Python で Redmine CLI ツールを作ってみる!

今回は Python を使って、Redmine CLI (コマンドラインインターフェイス) ツールを作ってみたい と思います。

以下のリポジトリに設置したサンプルコードを使って、解説を進めていきたいと思います。

CLI のベースには Click パッケージを、Redmine API へのアクセスには Python-Redmine パッケージを使用します。

目次

必要な環境

本記事の内容をトレースする場合は、「 Docker 」 と 「 Docker Compose 」 が必要になります。

Redmine 環境や、Python 環境は サンプルコード中の docker-compose.yml を使ってセットアップするので、インストールする必要はありません。

簡単な流れ

本記事では、以下のような流れで解説を進めていきます。

  1. サンプルコードを取得
  2. サンプルコードの中身を簡単に解説
  3. 実際に、サンプル Redmine CLI ツールを使ってみる
  4. まとめ

それでは、早速、Python を使って、Redmine CLI ツールを作っていきたいと思います!

サンプルコードの取得

サンプルコードを以下のリポジトリに設置しています。

以下のコマンドで、サンプルコードをクローンしてください。

git clone https://github.com/mm0202/template_python-redmine-cli.git

※ サンプルコードはテンプレートリポジトリとして公開しています。テンプレートとしても使用可能です。自由に使ってください。

サンプルコードの解説

サンプルコードの主な構成ファイルは以下の5つです。

  • .env.template
  • Dockerfile
  • docker-compose.yml
  • app/setup.py
  • app/packages/redmine.py

.env.template

必要な環境変数をまとめてあります。

.env.template の中身は以下の通り

# For redmine-cli(python) container
API_ACCESS_KEY=[APIアクセスキー]
REDMINE_API=redmine:3000

# For redmine container
REDMINE_DB_MYSQL=db
REDMINE_DB_PASSWORD=example
REDMINE_SECRET_KEY_BASE=supersecretkey

# For db container
MYSQL_ROOT_PASSWORD=example
MYSQL_DATABASE=redmine

ファイル名を .env に変更するか、コピーして名前を変更してください。

[APIアクセスキー] の部分は、後ほど、Redmine からアクセスキーを取得して置き換えます。

Dockerfile

redmine-cli コンテナ用のビルドファイル です。

FROM python:3

WORKDIR /usr/src/app

# 使用するパッケージをインストール
RUN pip install click python-redmine

# ソースコードのインストール (作成コマンドを有効化)
COPY src /usr/src/app
RUN pip install --editable .

# 自動補完を有効化
RUN echo 'eval "$(_REDMINE_COMPLETE=source_bash redmine)"' >> ~/.bashrc

CMD [ "bash" ]

基本設定

python イメージをベースにしています。

WORKDIR には /usr/src/app を指定しています。

後述の docker-compose.yml で、この WORKDIR にソースコードをマウントします。

redmine-cli コンテナ内のプロジェクトルートは /usr/src/app になります。

パッケージのインストール

Click パッケージと Python-Redmine パッケージを使用するため、pip install click python-redmine でインストールしています。

ソースコードをインストール

以下の部分で、作成したソースコードをインストール しています。

COPY src /usr/src/app
RUN pip install --editable .

ソースコードのインストールにより、作成コマンドが使用可能となります。

--editable オプションを使用しているので、ソースを変更するとビルドなしで変更が適用されます。

コマンドの自動補完を有効化

echo 'eval "$(_REDMINE_COMPLETE=source_bash redmine)"' >> ~/.bashrc の部分で、コマンドに自動補完機能を追加 しています。

自動補完の設定についての詳細は、以下のページを参照してください。

Activation | Shell Completion | Click Documentation

docker-compose.yml

トライアル用に使用するコンテナの、起動設定です。

作成したコマンドを試すための redmine-cli コンテナと、redmine コンテナ、Redmine 用の db コンテナを起動 します。

version: "3"

services:
  redmine-cli:
    build: .
    env_file:
      - .env
    volumes:
      - ./app/packages:/usr/src/app/packages

  redmine:
    image: redmine
    restart: always
    ports:
      - 53000:3000
    env_file:
      - .env

  db:
    image: mariadb
    restart: always
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci
    env_file:
      - .env

構成は以下のような感じになります。

Python で Redmine CLI ツールを作ってみる!

redmine-cli コンテナは、先ほどの Dockerfile をビルドして使用 します。

すでに Dockerfile で、作成コードをインストールのために、イメージ内にソースコードをコピーしていますが、app/packages ディレクトリをコンテナ側の /usr/src/app/packages にマウント してイメージ内のコードを上書きしています。

これにより、コードの変更がリアルタイムに適用されます

※ 開発時の利便性用の設定なので、本番環境ではやめた方がいいと思います(>ω<;)

各コンテナの env_file で指定している .env は、.env.template を元に作成します。

コンテナを起動した後、Redmine コンテナには http://localhost:53000 からアクセスできるようになります。

※ ホストが localhost 以外の場合は、「 localhost 」の部分を変更してアクセスしてください。

app/setup.py

パッケージのセットアップ設定ファイルです。

setup.py の中身は以下の通り。

from setuptools import setup, find_packages

setup(
    name='redmine',
    version='0.0.1',
    packages=find_packages(),
    include_package_data=True,
    install_requires=[
        'Click',
    ],
    entry_points='''
        [console_scripts]
        redmine=packages.redmine:cli
    ''',
)

entry_pontsredmine=packages.redmine:cli の部分がコマンドの設定部分 です。

= の左側の redmine がコマンド名 になります。

= の右側の packages.redmine:cli がコマンドのパス指定 になります。

上記の設定の場合は、コマンド redmine を実行すると、setup.py のあるディレクトリからの相対パスで packages/redmine.pycli 関数が実行されます。

setup.py の設定のより詳細な内容は、以下のページを参考にしてください。

app/packages/redmine.py

コマンドの実装ファイルです。

redmine.py の中身は以下の通りです。

import click
import os
from redminelib import Redmine

# リクエスト設定
redmine = Redmine('http://' + os.environ.get('REDMINE_API'),
                  key=os.environ.get('API_ACCESS_KEY'))

# プロジェクトの作成コマンド
@click.command(help="create project")
@click.option('--indentifier', '-i', help=u'project identifier', required=True)
@click.option('--production',  '-p', is_flag=True, help=u'add production project')
@click.option('--prototype',  '-pt', is_flag=True, help=u'add prototype project')
@click.option('--sandbox',  '-sb', is_flag=True, help=u'add sandbox project')
def create(indentifier, production, prototype, sandbox):
    if production:
        redmine.project.create(name=indentifier, identifier=indentifier)
        print('production project created')

    if prototype:
        redmine.project.create(
            name=indentifier + '-prototype', identifier=indentifier + '-prototype')
        print('prototype project created')

    if sandbox:
        redmine.project.create(
            name=indentifier + '-sandbox', identifier=indentifier + '-sandbox')
        print('sandbox project created')

# プロジェクトのリスト表示コマンド
@click.command(help="list projects")
def list():
    projects = redmine.project.all()
    for project in projects:
        print(project.identifier)

# プロジェクトの削除コマンド
@click.command(help="delete project")
@click.option('--indentifier', '-i', help=u'project identifier', required=True)
@click.option('--production',  '-p', is_flag=True, help=u'delete production project')
@click.option('--prototype',  '-pt', is_flag=True, help=u'delete prototype project')
@click.option('--sandbox',  '-sb', is_flag=True, help=u'delete sandbox project')
def delete(indentifier, production, prototype, sandbox):
    if production:
        redmine.project.get(indentifier).delete()
        print('production project deleted')

    if prototype:
        redmine.project.get(indentifier + '-prototype').delete()
        print('prototype project deleted')

    if sandbox:
        redmine.project.get(indentifier + '-sandbox').delete()
        print('sandbox project deleted')


# コマンド構成
@click.group(help="command for project")
def project():
    pass


project.add_command(create)
project.add_command(list)
project.add_command(delete)


@click.group(help="command for redmine")
def cli():
    pass


cli.add_command(project)

if __name__ == "__main__":
    cli()

コードの内容は大きくわけて、以下の3つです。

  • API リクエストに必要な情報を設定 ( Redmine パス、認証 )
  • プロジェクト操作コマンド群を定義 ( create、list、delete、delete-all )
  • コマンドの構成

API リクエストに必要な情報を設定 ( Redmine パス、認証 )

以下の部分で、Redmine API の使用に必要なの API の URL とAPIアクセスキーを設定しています

redmine = Redmine('http://' + os.environ.get('REDMINE_API'),
                  key=os.environ.get('API_ACCESS_KEY'))

ここで使用している 環境変数 REDMINE_APIAPI_ACCESS_KEY には .env ファイルで指定した REDMINE_APIAPI_ACCESS_KEY の値を使用されます

プロジェクト操作コマンド群を定義 ( create、list、delete、delete-all )

プロジェクト作成コマンド : create

以下の部分で、コマンドのオプションを設定しています。

@click.command()
@click.option('--indentifier', '-i', help=u'project identifier', required=True)
@click.option('--production',  '-p', is_flag=True, help=u'add production project')
@click.option('--prototype',  '-pt', is_flag=True, help=u'add prototype project')
@click.option('--sandbox',  '-sb', is_flag=True, help=u'add sandbox project')

identifier オプションでは、作成するプロジェクトの「 識別子 」を指定します。

identifier オプションは、指定必須に設定しています。

productionprototypesandbox オプションでは、それぞれ追加するプロジェクトのタイプを指定しています。

例えば、オプションを -i apple -p -pt -sb のように指定してプロジェクト作成コマンドを実行した場合、以下の3つのプロジェクトが作成されます。

  • apple
  • apple-prototype
  • apple-sandbox

プロジェクト作成関数では、各プロジェクトのタイプごとにプロジェクトを作成しています。

例えば、prototype フラグが True の場合は、以下のコードが実行され、プロジェクト名と識別子が「 xxx-prototype 」のような文字列のプロジェクトが作成されます。

redmine.project.create(
    name=indentifier + '-prototype', identifier=indentifier + '-prototype')
プロジェクトのリスト表示コマンド : list

プロジェクトを全て表示するコマンドです。

プロジェクトの識別子がリスト表示されます。

他のコマンドの実行結果の確認に使用します。

プロジェクト削除コマンド : delete

プロジェクトの個別削除コマンドです。

オプションは create コマンドと同様で、削除対象は identifier とプロジェクトタイプごとのフラグで指定します。

例えば、オプションを -i apple -pt -sb のように指定して、プロジェクト削除コマンドを実行した場合、以下の2つのプロジェクトが削除されます。

  • apple-prototype
  • apple-sandbox

この例の場合は、「 production 」プロジェクトはフラグで指定していないので、production用プロジェクトの「 apple 」は削除されません。

コマンド構成

以下の部分で、コマンド全体を構成している部分です。

@click.group(help="command for project")
def project():
    pass


project.add_command(create)
project.add_command(list)
project.add_command(delete)


@click.group(help="command for redmine")
def cli():
    pass


cli.add_command(project)

projectコマンドグループにここまでに定義したコマンド群を追加し、最後に、コマンドルートcliprojectを追加しています。

前述のsetup.pyの設定により、ルートコマンドはredmineに置きかわります。

例えば、listコマンドの場合は、以下のようなコマンドになります。

redmine project list

使用しているパッケージについて

CLI の構成には、Click パッケージを使用 しています。

Click パッケージの詳細については、以下の公式ページを参照してください。

Click

また、Redmine API へのアクセスには Python-Redmine パッケージを使用 しています。

Python-Redmine パッケージの詳細については、以下の公式ページを参照してください。

Python-Redmine

実際に、サンプル Redmine CLI ツールを試してみる!

まずは、サンプルコードのルートディレクトリへ移動してください。

cd template_python-redmine-cli

環境変数ファイルの設置

以下のコマンドを実行して、.env ファイルを追加してください。

cp .env.template .env

コンテナの起動

次に、コンテナを起動します。

以下のコマンドでコンテナを起動してください。

docker-compose up -d

http://localhost:53000 ( Redmine ) にアクセスして、コンテナが起動しているか確認してください。

Redmine のログイン画面が表示されれば起動成功です。

※ Redmine コンテナは起動に少し時間がかかります。

APIアクセスキーを取得&設定

「APIアクセスキー」を起動した Redmine から取得して、.env ファイル内の [APIアクセスキー] の部分を取得したアクセスキーと置き換えます。

「APIアクセスキー」は、以下のリンク先を参考にして、取得してください。

Redmine API を試してみる【 curl 編 】 - API アクセスキーの確認

サンプル Redmine CLI ツール を使ってみる

コマンドの自動補完を、せっかく有効にしているので、活用するためにコンテナ内でコマンドを実行していきます。

ホスト環境からコマンドを実行する場合は、以降で解説するコマンドの前に docker-compose run redmine-cli を付け加えて実行してください。

※ ホスト環境で使用する場合は、コマンドの自動補完は使用できません。

以下のコマンドを実行して、コンテナ内へ入ってください。

docker-compose run redmine-cli

DockerfileCMDbash を指定しているので、サービス名を指定するだけで、bashが起動します。

ヘルプをチェックしてみる

まずは、ヘルプを表示してみます。

以下のコマンドを実行してください。

$ redmine --help
Usage: redmine [OPTIONS] COMMAND [ARGS]...

  command for redmine

Options:
  --help  Show this message and exit.

Commands:
  project  command for project

サブコマンド project が表示されていれば、設定どおりです。

ホスト環境から実行する場合は、以下のようにコマンドになります。

docker-compose run redmine-cli redmine --help

コマンドリストをチェック

サブコマンド project のヘルプで、追加したコマンドのリストを確認します。

以下のようにコマンドを実行してください。

$ redmine project --help
Usage: redmine project [OPTIONS] COMMAND [ARGS]...

  command for project

Options:
  --help  Show this message and exit.

Commands:
  create      create project
  delete      delete project
  delete-all  delete all project
  list        list projects

コマンドの入力途中で、Tab キーを押すとコマンドが自動補完されます。 候補が複数ある場合は、候補リストが表示されます。

追加した4つのコマンドが表示されていれば、設定の通りです。

続いて、ここで確認したコマンドを使用して、プロジェクトを操作していきたいと思います!

プロジェクトの作成

まずは、プロジェクトを作成します。

とりあえず、オプションなしで実行してみます。

$ redmine project create 
Usage: redmine project create [OPTIONS]
Try 'redmine project create --help' for help.

Error: Missing option '--indentifier' / '-i'.

必須に指定していた indentifier を指定していなかったため、エラーとなりました。

ヘルプでオプションを確認しておきます。

$ redmine project create --help
Usage: redmine project create [OPTIONS]

  create project

Options:
  -i, --indentifier TEXT  project identifier  [required]
  -p, --production        add production project
  -pt, --prototype        add prototype project
  -sb, --sandbox          add sandbox project
  --help                  Show this message and exit.

ヘルプを参考にして、まずは、production、prototype、sandboxタイプの「 apple 」プロジェクトを作成します

以下のようにコマンドを実行してください。

$ redmine project create -i apple -p -pt -sb 
production project created
prototype project created
sandbox project created

※ オプションについても自動補完が利用できます。

このあとの解説のために、「 orange 」プロジェクトも追加しておきます。

以下のコマンドを実行して、production、sandbox タイプの「 orange 」プロジェクトを追加 してください。

$ redmine project create -i orange -p -sb
production project created
sandbox project created

プロジェクトが追加されているか、確認しておきます。

http://localhost:53000/projects にアクセスして、プロジェクトが作成されているか確認してください。

以下のように、プロジェクトが5つ表示されていれば、プロジェクトの追加は成功です!

Python で Redmine CLI ツールを作ってみる!

念のためlist コマンドでも、プロジェクトが追加されているか確認しておきます。

以下のようにコマンドを実行してください。

$ redmine project list
apple
apple-prototype
apple-sandbox
orange
orange-sandbox

上記のように、追加した5つのプロジェクトが表示されればOKです。

指定プロジェクトの削除

続いて、削除コマンドを試してみたいと思います。

「 apple 」プロジェクトのprototype、sandboxプロジェクトの2つを削除 したいと思います。

まずは、ヘルプを表示してオプションを確認しておきます。

$ redmine project delete --help
Usage: redmine project delete [OPTIONS]

  delete project

Options:
  -i, --indentifier TEXT  project identifier  [required]
  -p, --production        delete production project
  -pt, --prototype        delete prototype project
  -sb, --sandbox          delete sandbox project
  --help                  Show this message and exit.

ヘルプの内容を参考に、「 apple-prototype 」と「 apple-sandbox 」プロジェクトを削除します。

以下のようにコマンドを実行してください。

$ redmine project delete -i apple -pt -sb 
prototype project deleted
sandbox project deleted

プロジェクトが削除されているか、確認しておきます。

http://localhost:53000/projects にアクセスして、プロジェクトが削除されているか確認してください。

以下のように、「 apple-prototype 」と「 apple-sandbox 」プロジェクトが消えていれば、削除成功です!

Python で Redmine CLI ツールを作ってみる!

list コマンドでもプロジェクトが削除されているか、確認しておきます。

以下のようにコマンドを実行してください。

$ redmine project list 
apple
orange
orange-sandbox

「 apple-prototype 」と「 apple-sandbox 」プロジェクトが消えていればOKです。

残りのプロジェクトも削除しておきます。

$ redmine project delete -i apple -p
production project deleted

$ redmine project delete -i orange -p -sb 
production project deleted
sandbox project deleted

http://localhost:53000/projectsredmine project list コマンドで、全てのプロジェクトが削除されているか、確認してください。

後始末

最後に、起動したコンテナを停止しておきます。

以下のコマンドで、起動したコンテナを停止してください。

docker-compose down

データの永続化設定はしていないので、再起動したときは、再度、APIアクセスキーの設定が必要になります。 注意してください。

注意書き

あくまで、お試しサンプルなので、例外処理などは記述していません。

重複するプロジェクトの追加や、存在しないプロジェクトを削除を行うとエラーになります。

まとめ

今回は、Python で Redmine CLI ツールを作成 してみました!

Python-Redmine が非常に便利でした!

Redmine API、Python-Redmine ともに、さらっと見た程度なので、はっきりとは言えませんが、API をそのままメソッドにしてあるだけでなく、よく使うような機能は API を組み合わせて実装されているような気がします。

公式ツールというわけではないようですが、Redmine 公式の Wiki で紹介されています。

Using the REST API with Python

別記事で、Node.js を使って、Redmine にカスタム API を実装してみましたが、カスタム API を実装する場合も、Python-Redmine を使って Python でやった方がいいような気がします(*´Д`)

Python はあまり使ったことがないので、サーバプログラム向けのおすすめのフレームワークなどがあれば、コメントなどから教えてもらえるとありがたいです(*´▽`*)

おすすめのテストツールとかもあれば、ぜひ!

最後に、今回の内容について、簡単にまとめてみたいと思います。

  • Python-Redmine が便利!
  • カスタム API も Python で良くね?
  • Python の便利なツールとか教えてほしい |д<)