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インスタンスを生やしまくります。
今回の検証に使用したソフトウェアは以下な感じです。
- dnsmasq
- 弊社は3桁以上の数字は覚えられないのでDNSサーバを立てます。
- Gitea
- Giteaを使うと割と簡単に構築できるそうなので今回はGiteaを使用しました。
- drone
- 今回の主役です。今回はサーバとエージェントを分ける構成で構築してみました。
今回はdnsmasq以外はすべてDocker上で構築します。これは別にコンテナ至上主義とかそういうのではなく、単純にそうしたほうが楽だから程度の理由です。
Gitea
まずはソースコードをホストするためにGiteaを立てます。
Giteaはデータ格納用にPostgres・MySQL・SQL 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"
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"
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
ちゃんと失敗しています。
なおすと
成功します。
おわりに
多分ドキュメントの整備が追い付いていないのか、ちょっとドキュメント類が不足しているなというところが率直な感想です。 また、開発途中だから無いのかそれともエンタープライズ版との差別化なのかは分かりませんが、ブラウザから操作できることが非常に限られている感じです。ブラウザからは現在いくつのエージェントがぶら下がっているのかも分かりません。*3
今回はクッソ簡単な例しか試していませんが、これが大規模になった場合、ビルド実行用のDockerイメージを用意したり、用意したDockerコンテナをローカルでホストするためリポジトリ(Sonatype Nexus OSSとか)を用意したり、独自ライブラリをNuGet経由で配布するためにパッケージを作ったり、それをホストするためのNuGetリポジトリを立てたりと想像するだけで大変そうです。
逆にその辺を解決できれば自動化インフラ管理者にとっての救済となるかもしれません。
おわり
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
)になってしまいます。
データベースの照合順序は作成時に指定できますしデータベース名に日本語を使わなければそうそう地雷を踏みぬくことも無いと思いますが、本番環境と異なっていると変なところで踏みぬいて嫌な感じになるので出来れば統一したいものです。
環境変数
さて、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
おわり
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
を開きます。
ちゃんとScalaコードが(JavaScriptにトランスパイルされて)ブラウザ上で動いています。
Hello World on DOM
今度はReactを使ってDOMの差し替えを行ってみましょう。
scalajs-reactをbuild.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
こうです。
Todo list
せっかくReactを使っているので、SPAっぽいことをやってみましょう。というわけで(消せない)Todoリストです。
Todo list · jyuch/scalajs-react-tutorial@a224ac7 · GitHub
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が出てくると『うわっ、こっわ』となりますが、しばらく書いているとこんなものかなーと思えてくるので慣れって怖いですね。