VB.NETでもナウいCIサーバでテストをギュイーンしたい

はじめに

この記事はVisual Basic Advent Calendar 2018の15日目の記事です。

どうも、闇落ちJenkinsおじさんです。

オンプレのCIサーバといえばJenkinsが筆頭に上がりますが、Jenkinsのビルド環境が秘伝のタレ化してスレーブを追加する際に死ぬほどコンパイラやらSDKやらをぶち込まないとならず、自動化の裏側でソウルジェムをマッハで濁らせながらメンテナンスしている管理者も多いのではないでしょうか。

こんな時、コンテナでテスト環境は生成・破棄出来たらどんなに幸せでしょうか。オンプレで

コンテナは投げ捨てるもの

JenkinsでもPipelineを使えばDockerコンテナ上でビルド・テストを実行出来ますが、いかんせんGroovyで記述しなくてはならないというところが個人的にはつらみポイントでした。*1

という感じで色々迷っていたところ、drone.ioというオンプレにも展開できるDockerコンテナを使用したCIサーバを見つけたので試してみました。*2

Dockerパーリナイ

というわけで以下の図のようにLinuxインスタンスを生やしまくります。

f:id:jyuch:20181121220915p:plain

今回の検証に使用したソフトウェアは以下な感じです。

  • dnsmasq
    • 弊社は3桁以上の数字は覚えられないのでDNSサーバを立てます。
  • Gitea
    • Giteaを使うと割と簡単に構築できるそうなので今回はGiteaを使用しました。
  • drone
    • 今回の主役です。今回はサーバとエージェントを分ける構成で構築してみました。

今回はdnsmasq以外はすべてDocker上で構築します。これは別にコンテナ至上主義とかそういうのではなく、単純にそうしたほうが楽だから程度の理由です。

Gitea

まずはソースコードをホストするためにGiteaを立てます。

Giteaはデータ格納用にPostgres・MySQLSQL Server(とSQLite)が選べますが、マイクロソフト信者の私たちはもちろん最強のデータベースであるSQL Serverを選びますよね?

version: '3'
services:
  mssql:
    image: 'microsoft/mssql-server-linux:2017-CU12'
    volumes:
      - ./data:/var/opt/mssql/data
    ports:
      - '1433:1433'
    environment:
      - ACCEPT_EULA=Y
      - SA_PASSWORD=mssqllinux@1
      - MSSQL_LCID=1041
      - MSSQL_COLLATION=Japanese_CI_AS
version: "2"

networks:
  gitea:
    external: false

services:
  server:
    image: gitea/gitea:1.6
    environment:
      - USER_UID=1000
      - USER_GID=1000
    restart: always
    networks:
      - gitea
    volumes:
      - ./gitea:/data
    ports:
      - "80:3000"
      - "222:22"

f:id:jyuch:20181121222947p:plain

drone

droneもDockerで立てていきます。というよりはDockerでしか立てられません。

version: "2"

services:
  server:
    image: drone/drone:1.0.0-rc.1
    environment:
      - DRONE_GIT_ALWAYS_AUTH=false
      - DRONE_GITEA_SERVER=http://gitea.opabinia.co.jp/
      - DRONE_RPC_SECRET=2039906835c92213b78cce47d38be1e9
      - DRONE_SERVER_HOST=drone.opabinia.co.jp
      - DRONE_SERVER_PROTO=http
      - DRONE_TLS_AUTOCERT=false
    restart: always
    volumes:
      - ./drone:/data 
    ports:
      - "80:80"
      - "443:443"

f:id:jyuch:20181121224256p:plain

drone agent

こちらに至ってはOSインストールしてDockerインストールして以下のコマンドをコピペでぶち込めばそれで終わりです。

docker run \
  --volume=/var/run/docker.sock:/var/run/docker.sock \
  --env=DRONE_RPC_SERVER=http://drone.opabinia.co.jp \
  --env=DRONE_RPC_SECRET=2039906835c92213b78cce47d38be1e9 \
  --env=DRONE_RUNNER_CAPACITY=4 \
  --env=DRONE_RUNNER_NAME=dad96u1 \
  --restart=always \
  --detach=true \
  --name=agent \
  drone/agent:1.0.0-rc.1

プロジェクトの作成

まぁ、Linuxとかが出てきてお察しの通り、今回は.NET Coreで検証しています。droneのエージェントにはWindowsも使えるのでもしかしたら.NET Frameworkの方も行けるかもしれませんが今回は検証していないです。

Public Class Class1

    Public Function Add(x As Integer, y As Integer) As Integer
        Return x - y
    End Function

End Class

みたいなクラスがあって

Imports System
Imports Xunit
Imports ClassLibrary1

Public Class Class1Test

    <Fact>
    Sub Add_return_valid_value()
        Dim x = 10 : Dim y = 20
        Dim expected = x + y

        Dim c = New Class1()

        Assert.Equal(expected, c.Add(x, y))
    End Sub

End Class

みたいなテストを用意します。そして、手元でテストを回してばっちり失敗することを確認してから以下の.drone.ymlを追加してからプッシュします。

kind: pipeline
name: default

steps:
- name: classlibrary1
  image: microsoft/dotnet:2.1-sdk
  commands:
  - dotnet test

ちゃんと失敗しています。

f:id:jyuch:20181121230617p:plain

なおすと

f:id:jyuch:20181121230955p:plain

成功します。

おわりに

多分ドキュメントの整備が追い付いていないのか、ちょっとドキュメント類が不足しているなというところが率直な感想です。 また、開発途中だから無いのかそれともエンタープライズ版との差別化なのかは分かりませんが、ブラウザから操作できることが非常に限られている感じです。ブラウザからは現在いくつのエージェントがぶら下がっているのかも分かりません。*3

今回はクッソ簡単な例しか試していませんが、これが大規模になった場合、ビルド実行用のDockerイメージを用意したり、用意したDockerコンテナをローカルでホストするためリポジトリ(Sonatype Nexus OSSとか)を用意したり、独自ライブラリをNuGet経由で配布するためにパッケージを作ったり、それをホストするためのNuGetリポジトリを立てたりと想像するだけで大変そうです。

逆にその辺を解決できれば自動化インフラ管理者にとっての救済となるかもしれません。

おわり

github.com

*1:似たような理由でGradleもいまだに苦手意識ががが

*2:飛ぶほうのドローンが流行っているせいでググラビリティは割と低め

*3:エージェントについては意識するなという事?

Linux版SQL ServerをDockerで動かしたときにサーバの照合順序を変更したい

はじめに

他のデータベースにもれず、SQL ServerもDockerで動かすことが出来ます。

ただ、単純にDocker Hubのページのように

> docker run \
    --name=mssql \
    --hostname=my-docker-mssql \
    -d \
    -e 'ACCEPT_EULA=Y' \
    -e 'SA_PASSWORD=mssqllinux@1' \
    -p 1433:1433 \
    microsoft/mssql-server-linux:latest

とやると言語やサーバの照合順序が英語(SQL_Latin1_General_CP1_CI_AS)になってしまいます。

f:id:jyuch:20180904203231p:plain

データベースの照合順序は作成時に指定できますしデータベース名に日本語を使わなければそうそう地雷を踏みぬくことも無いと思いますが、本番環境と異なっていると変なところで踏みぬいて嫌な感じになるので出来れば統一したいものです。

環境変数

さて、Docker Hubのページにはぱっと見

しか紹介されておらず、照合順序が変更できなさそうですがConfigure SQL Server settings with environment variables on Linuxを見るとかなりの設定が出来るようです。

というわけでこれが正解。

> docker run \
    --name=mssql \
    --hostname=my-docker-mssql \
    -d \
    -e 'ACCEPT_EULA=Y' \
    -e 'SA_PASSWORD=mssqllinux@1' \
    -e 'MSSQL_LCID=1041' \
    -e 'MSSQL_COLLATION=Japanese_CI_AS' \
    -p 1433:1433 \
    microsoft/mssql-server-linux:latest

f:id:jyuch:20180904205759p:plain

おわり

Scala.jsでもReactを使いたい

はじめに

Scalaが好きすぎてフロントエンドでも使いたいのでScala.jsに入門してみました。

必須環境

Scala.jsのチュートリアル曰くsbtとかnode.jsを入れなさいということなのでインストールしましょう。

また、npmパッケージのキャッシュとかバージョンの固定とかしてくれるYarnも一緒に入れておきます。直接使うことはありませんが、scalajs-bundler経由で利用します。

Hello World

scala-seed.g8からプロジェクトをシードして、ちょろっと書き換えてScala.js用のプロジェクトを作成します。

Hello world · jyuch/scalajs-react-tutorial@60aaf73 · GitHub

この状態で

> sbt run

とするとコンソールにHello worldと表示されますが、何となく味気ないです。

Hello World in browser

今度は適当なHTMLを書いて生成されたJavaScriptを読み込んでみましょう。

Hello World in browser · jyuch/scalajs-react-tutorial@2c18e9d · GitHub

> sbt fastOptJS

Scalaのコードをトランスパイルし、ブラウザでindex.htmlを開きます。

f:id:jyuch:20180807221154p:plain

ちゃんとScalaコードが(JavaScriptにトランスパイルされて)ブラウザ上で動いています。

Hello World on DOM

今度はReactを使ってDOMの差し替えを行ってみましょう。

scalajs-reactbuild.sbtの依存ライブラリに追加します。

また、Reactの依存ライブラリ諸々を1つのjsファイルに押し固めたいのでscalajs-bundlerプラグインを追加します。

Hello World on DOM · jyuch/scalajs-react-tutorial@6a064bb · GitHub

そしてこうやって

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>The Scala.js Tutorial</title>
</head>
<body>

<div id="playground"></div>

<script type="text/javascript" src="./target/scala-2.12/scalajs-bundler/main/scalajs-react-tutorial-fastopt-bundle.js"></script>
</body>
</html>

こうして

package react4s

import org.scalajs.dom.document
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._

object Hello {
  val playground = document.getElementById("playground")

  def main(args: Array[String]): Unit = {
    val NoArgs =
      ScalaComponent.builder[Unit]("No args")
        .renderStatic(<.div("Hello World"))
        .build

    NoArgs().renderIntoDOM(playground)
  }
}

トランスパイルして

> sbt fastOptJS::webpack

こうです。

f:id:jyuch:20180807224933p:plain

Todo list

せっかくReactを使っているので、SPAっぽいことをやってみましょう。というわけで(消せない)Todoリストです。

Todo list · jyuch/scalajs-react-tutorial@a224ac7 · GitHub

f:id:jyuch:20180807235605p:plain

package react4s

import org.scalajs.dom.document
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._

object Hello {
  val playground = document.getElementById("playground")

  def main(args: Array[String]): Unit = {
    val Example = ScalaComponent.builder[Unit]("Example")
      .initialState(State("", List("Hello World")))
      .renderBackend[Backend].build

    Example().renderIntoDOM(playground)
  }

  case class State(item: String, items: List[String])

  class Backend($: BackendScope[Unit, State]) {
    def render(s: State): VdomElement = {
      <.div(
        <.form(^.onSubmit ==> handleSubmit,
          <.input(^.onChange ==> onChange, ^.value := s.item),
          <.button("Add item")
        ),
        <.ul(
          s.items map { it => <.li(it) }: _*
        )
      )
    }

    def handleSubmit(e: ReactEventFromInput): Callback = {
      e.preventDefaultCB >> $.modState(s => State("", s.item :: s.items))
    }

    def onChange(e: ReactEventFromInput): Callback = {
      val v = e.target.value
      $.modState(_.copy(item = v))
    }
  }

}

<.div^.onSubmitのような面妖なDSLが出てくると『うわっ、こっわ』となりますが、しばらく書いているとこんなものかなーと思えてくるので慣れって怖いですね。

github.com