DMM.comの、一番深くておもしろいトコロ。

Go言語初学者がConfluenceをMarkdownで書くためのCLIツールを開発した話

Go言語初学者がConfluenceをMarkdownで書くためのCLIツールを開発した話

はじめに

こんにちは。DMM.comラボ プラットフォーム開発部エンジニアの松下 (@_kentaro_m) です。DMMのサービス全体で利用される基盤システムの開発を担当しています。

最近、個人的に興味を持ったGo言語の学習の一環として、社内情報共有ツールになっているConfluenceを便利に使うためのCLIツール開発に挑戦したので、本記事ではその学習の流れを紹介したいと思います。

これからGoを学びたいと考えているエンジニアの参考になれば幸いです。

目次

Goの学習をはじめた動機

普段はNode.jsをメインで開発しており、Goはまったく触る機会がありませんでした。社内で開催されたGoの勉強会のレポートを見たり、ブラウザプッシュ通知の開発チームでプロダクトに採用された話を聞いたりして興味を持ち、個人で勉強するようになりました。

Goの学習の流れ

Goの学習は以下のように進めました。

  1. A Tour of Goを行う
  2. ローカル開発環境を構築する
  3. CLIツール開発に挑戦する

1. A Tour of Goを行う

A Tour of Goはブラウザ上でGoの文法が学べる公式のチュートリアルです。CLIツールを開発するにあたって、事前にひととおり行いました。

基本的にはスライドを見つつ、サンプルコードを動かして学んでいきます。各章の終わりには演習問題があり、それを解いて理解度を測れるようになっていました。私は演習問題で何度かハマったので、もう何周かして理解を深めたいです。

A Tour of Go

2. ローカル開発環境を構築する

ローカル開発環境の構築には次の3つを行いました。

  • homebrewでのGoのインストール
  • GOPATHを設定
  • エディタに開発補助パッケージをインストール

homebrewでのGoのインストール

Macをお使いであれば、homebrewでGoをインストールするのが一番早いと思います。

$ brew install go

GOPATHを設定

~/.bash_profileにGoのワークスペース(GOPATH)の指定と、GOPATH配下のbinディレクトリをPATHに追加します。

export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin

設定ファイルを読み込み直します。

$ source ~/.bash_profile

エディタに開発補助パッケージをインストール

お使いのエディタに合わせて、必要なものを入れてください。

3. CLIツール開発に挑戦する

Goの学習でCLIツール開発に挑戦した理由は以下の3つです。

  • Goではクロスコンパイルをサポートしており、各種プラットフォーム向けにCLIツールを配布しやすいこと
  • Go向けのCLIツール開発を支援するフレームワークが存在すること
  • GoでCLIツール開発に挑戦する人が多く、情報量が多いこと

CLIツールの開発を始める

何を開発するか

DMMでは社内情報共有のためにAtlassianのConfluenceが多くの部署で利用されています。ドキュメントをリッチテキストで書けるため、誰でも簡単に扱えるのが特徴です。

一方でドキュメントを書きながら、装飾もしなくてはいけないため、Markdownを好むエンジニアにとっては使いづらい一面1もありました。

f:id:matsushita-ken:20180506235618p:plain
Confluence Wikiマークアップのテキストを挿入するときの画面

そこでMarkdownをConfluenceの独自記法であるWikiマークアップに変換する仕組みを作り、Markdown文書をConfluenceへ容易に投稿できる環境を実現することにしました。

作ったもの

f:id:matsushita-ken:20180501153610g:plain
md2conflでMarkdownテキストをConfluece Wikiマークアップに変換している様子

MarkdownをConfluence Wikiマークアップに変換するmd2conflというCLIツールを作りました。

github.com

ファイルまたは標準入力から読み込んだMarkdownの文書をConfluenceのWikiマークアップに変換して、標準出力に出力します。

$ md2confl sample.md
h1. Section
Some _Markdown_ text.

* list1
* list2
* list3

*strong text*
-strikethrough text-
[Example Domain|http://www.example.com/]
!https://blog.golang.org/gopher/header.jpg!

$ echo "# Hello, world" | md2confl
h1. Hello, world

Macをお使いであれば、homebrewからインストールが可能です。その他のプラットフォーム向けにもGitHub Releaseにバイナリが公開されています。

$ brew tap kentaro-m/md2confl
$ brew install md2confl

CLIツールをリリースするまでの流れ

次にCLIツールをリリースするまでに行った3つの作業を紹介したいと思います。

  1. CLIツールのフレームワークで雛形を作成
  2. Markdownパーサーのカスタムレンダラの開発
  3. Circle CIでリリースの仕組みを準備

CLIツールのフレームワークで雛形を作成

GoではCLIツールを簡単に素早く開発するためのフレームワークがあります。今回はKubernetesやDockerでも採用されているspf13/cobraを使用しました。

github.com

数ステップでCLIツールの雛形を作成することができます。

cobraをインストールします($GOPATH/binにインストールされる)。

$ go get github.com/spf13/cobra/cobra

cobra init <アプリケーション名>で雛形が作成されます。

$ cobra init github.com/kentaro-m/sampleApp
$ cd $GOPATH/src/github.com/kentaro-m/sampleApp
$ go run main.go
Your Cobra application is ready at
~/go/src/github.com/kentaro-m/sampleApp

Give it a try by going there and running `go run main.go`.
Add commands to it by running `cobra add [cmdname]`.

cobra addコマンドでサブコマンドが追加されます。

$ cobra add show
show created at ~/go/src/github.com/kentaro-m/sampleApp/cmd/show.go

$ go run main.go
A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:

Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.

Usage:
  sampleApp [command]

Available Commands:
  help        Help about any command
  show        A brief description of your command

Flags:
      --config string   config file (default is $HOME/.sampleApp.yaml)
  -h, --help            help for sampleApp
  -t, --toggle          Help message for toggle

Use "sampleApp [command] --help" for more information about a command.

$ go run main.go show
show called

この時点でのディレクトリは以下のとおりです。

.
├── LICENSE
├── cmd
│   ├── root.go
│   └── show.go // 追加したサブコマンド
└── main.go

あとはcmd/show.goに必要な処理を追加すれば、オリジナルのCLIツールが完成します。 今回作成したCLIツールは、この雛形をベースに開発していきました。

package cmd

import (
    "fmt"

    "github.com/spf13/cobra"
)

// showCmd represents the show command
var showCmd = &cobra.Command{
    Use:   "show",
    Short: "A brief description of your command",
    Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:

Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
    Run: func(cmd *cobra.Command, args []string) {
    // ここに処理を書く
        fmt.Println("show called")
    },
}

func init() {
    RootCmd.AddCommand(showCmd)
}

Markdownパーサーのカスタムレンダラの開発

Go製のマークダウンパーサー github.com

Confluence Wikiマークアップを出力するBlackfridayカスタムレンダラ github.com

MarkdownのパースとConfluence Wikiマークアップへの変換はrussross/blackfridayを使用しました。このライブラリはデフォルトではMarkdownをHTMLに変換するようになっていますが、カスタムレンダラを使用することでLaTeXなどの他言語の出力が可能になります。

md2conflを開発するにあたり、Confluence Wikiマークアップを出力するレンダラを作成しました。正しい出力を得るためにマークダウンの各要素ごとにユニットテストを作成し、出力を確認しながら開発を進める非常に愚直な作業でした。

// マークダウンの各要素ごとにユニットテストを作成
func TestHeading(t *testing.T) {
  // マークダウン形式のテキスト (入力値)
    input := `
# Section
hello, world.
`
  // Confluece Wikiマークアップのテキスト (期待値)
    expected := `h1. Section
hello, world.
`

  // Confluece Wikiマークアップ変換後のテキスト (出力結果)
    output := string(bfconfluence.Run([]byte(input)))

  // 出力結果と期待値を比較
    if output != expected {
        t.Errorf("got:%v\nwant:%v", output, expected)
    }
}

Circle CIでリリースの仕組みを準備

クロスコンパイルを実施してバイナリを生成 github.com

GitHub Releaseのページ作成及びバイナリをアップロード github.com

バイナリを配布するためにCircle CIとgox、ghrでリリースの仕組みを準備しました。最終的に以下の構成のconfig.ymlができあがりました。

Gitでv.X.Y.Zのバージョンタグを付与して、リモートにプッシュしたタイミングでCircle CIがクロスコンパイルを行い、リリースページにバイナリをアップロードします。

これでCLIツールを配布できるようになりました。

version: 2
jobs:
  build:
    docker:
      - image: circleci/golang:1.10
    working_directory: /go/src/github.com/kentaro-m/md2confl
    steps:
      - checkout
      - run:
          name: Install dependencies
          command: go get -v -t -d ./...
      - run:
          name: Run linters
          command: |
            go get github.com/golang/lint/golint
            golint ./...
            go vet ./...
  deploy:
    docker:
      - image: circleci/golang:1.10
    working_directory: /go/src/github.com/kentaro-m/md2confl
    steps:
      - checkout
      - run:
          name: Deploy the project
          command: |
            go get -v -t -d ./...
            go get github.com/inconshreveable/mousetrap
            go get github.com/mitchellh/gox
            go get github.com/tcnksm/ghr
            # クロスコンパイル・バイナリ生成
            gox -output "pkg/{{.OS}}_{{.Arch}}_{{.Dir}}"
            # GitHub Releaseにバイナリ公開
            # repo権限を付与したトークンを環境変数に登録しておく
            ghr -t $GITHUB_TOKEN -u $CIRCLE_PROJECT_USERNAME -r $CIRCLE_PROJECT_REPONAME --replace `git describe --abbrev=0 --tags` pkg/
workflows:
  version: 2
  build:
    jobs:
      - build:
          filters:
            branches:
              only: /.*/
  deploy:
    jobs:
      - deploy:
          filters:
            branches:
              ignore: /.*/
            tags: # タグが付与されたときのみジョブが実行される
              only: /v[0-9]+\.[0-9]+\.[0-9]+/

まとめ

普段の業務で使うプログラミング言語やツールは、比較検討を重ねて選定していきます。Goの学習に関しては、個人的な勉強なので純粋に楽しそうという理由で触りました。結果的にCLIツールを開発してリリースするところまででき、今はさらに深くGoを学びたいという気持ちになっています。

概算になりますが、学習時間を簡単に紹介したいと思います。

f:id:matsushita-ken:20180517184410p:plain
Goの学習時間

平日帰宅後に1時間くらい時間をとってコツコツと進めて、Goの学習開始からリリースまでおよそ30時間でした。 最初に作るものを決めて、それを作るためには何を勉強すべきかという点を考えながらやったので、学習効率は良かったと思います。

今回はCLIツールを完成させてリリースすることに重きを置いていたため、エラーハンドリングやテストの書き方などGoの作法が分からないまま、おざなりに進めてしまった部分がありました。今後はGoらしいコードを書くために勉強を続けていきたいと思います。

皆さんも何か作るものを1つ決めて、Goの学習を始めてみてはいかがでしょうか。

私たちと一緒にDMMサービスを支えるプラットフォームを作りませんか

プラットフォーム開発部で一緒に働くエンジニアを募集しています。興味がある方は下記リンクより詳細をご確認ください。

dmm-corp.com

dmm-corp.com


  1. プラグインを使用することでMarkdownの文書を投稿することは可能