2017年10月31日火曜日

SpriteKit で iPhoneX 対応する方法

概要

過去に iPad 対応しました
今度発売される iPhoneX も対応しなければいけなかったのでその対応方法を紹介します

環境

  • macOS X 10.12.6
  • Xcode 9.0.1 (9A1004)

対応コード

Xcode で Game プロジェクト作成した場合を例に紹介します
デフォルトだと UIViewController を継承した GameViewController があります
そこの viewDidLoad を以下のように変更します

override func viewDidLoad() {
    super.viewDidLoad()
    if let view = self.view as! SKView? {
        // Load the SKScene from 'GameScene.sks'
        if let scene = SKScene(fileNamed: "GameScene") {
            // Set the scale mode to scale to fit the window
            if (UIDevice.current.model.range(of: "iPad") != nil) {
                // iPad の場合
                print("iPad")
                scene.scaleMode = .fill
            } else {
                if (UIScreen.main.nativeBounds.height == 2436.0) {
                    // iPhoneX の場合
                    print("iPhoneX")
                    scene.scaleMode = .aspectFit
                } else {
                    // iPhone の場合
                    print("iPhone")
                    scene.scaleMode = .aspectFill
                }
            }
            view.presentScene(scene)
        }
        view.ignoresSiblingOrder = true
        view.showsFPS = true
        view.showsNodeCount = true
    }
}

ポイントは UIDevice.current.model.rangeUIScreen.main.nativeBounds.height を使う点です

まず UIDevice.current.model.range を使って iPad かどうかを判断します
というのも iPhoneX の場合「iPhone」として認識されるのでまずは iPad かどうか判断する必要があります

次に UIScreen.main.nativeBounds.height で iPhoneX かそうでないかを判断します
高さが 2436.0 が iPhoneX なのでその高さの場合に aspectFit を設定することで画面からはみ出ることなく表示されます

そして最後に iPhoneX 以外のデバイスの場合に aspectFill とします

最後に

SpriteKit で iPhoneX に対応する方法を紹介しました
デバイスのタイプでは判断できないのでサイズを取得して判断する必要があります

UIKit の場合 constraints を使っていれば特に何もしなくても iPhoneX に対応できると思います
SpriteKit には constraints がないのでこのような対応をしなければなりません

執筆時点で iTunesConnect にはすでに iPhoneX 対応版のバイナリをアップロードできるので対応したらすぐにアップロードすると良いと思います
スクリーンショットや AppPreviews も iPhoneX 版をアップロードできます

参考サイト

2017年10月29日日曜日

jenkins_api_client で Pipeline Job を作成するときのコツ

概要

前回 Ruby + jenkins_api_client を使って Jenkins を操作してみました
API からジョブを作成することはできますがジョブの作成には XML が必要です
今回はパイプラインジョブでその XML の情報を生成するコツを紹介します

環境

  • macOS X 10.12.6
  • Ruby 2.4.1p111
  • jenkins_api_client 1.5.3

とりあえずパイプラインジョブを作成するコード

こんな感じでパイプラインジョブを作成することができます

require 'jenkins_api_client'

client = JenkinsApi::Client.new(
  :server_ip => 'localhost',
  :username => 'root',
  :password => 'pass'
)

def create_job_xml
  builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') { |xml|
    xml.send("flow-definition", :plugin => "workflow-job@2.12.2") {
      xml.description
      xml.keepDependencies "false"
      xml.properties {
        xml.send("org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty") {
          xml.triggers
        }
      }
      xml.definition(:class => "org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition", :plugin => "workflow-cps@2.41") {
        xml.script "node {\n   echo 'Hello World'\n}"
        xml.sandbox "true"
      }
      xml.triggers
      xml.disabled "false"
    }
  }
  builder.to_xml
end

# puts create_job_xml
puts client.job.create('job_name', create_job_xml)

このコード内で生成している XML の情報は以下の通りです
puts のコメントアウトすれば XML の情報を確認することができます


<?xml version="1.0" encoding="UTF-8"?>
<flow-definition plugin="workflow-job@2.12.2">
   <description />
   <keepDependencies>false</keepDependencies>
   <properties>
      <org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty>
         <triggers />
      </org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty>
   </properties>
   <definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition" plugin="workflow-cps@2.41">
      <script>node {
   echo &amp;apos;Hello World&amp;apos;
}</script>
      <sandbox>true</sandbox>
   </definition>
   <triggers />
   <disabled>false</disabled>
</flow-definition>

XML をコードに落とすときのコツ

自分のおすすめとしては

  1. まず手動でパイプラインジョブを作成する
  2. jenkins_home にある jobs/job_name/config.xml で XML の内容を確認する
  3. XML をコードに落とし込む

という流れが一番簡単で良いと思います

サンプルコードの場合 XML のマーシャライザは nokogiri を使っています
なので少し nokogiri の使い方も知っておく必要があります
まずタグ情報の定義は xml.hoge という感じで定義します
XML 内に値がある場合はスペースを 1 つ開けて文字列で値を定義します

ex)

xml.disabled "false"

XML のタグ情報にハイフンやドットが含まれる場合は xml.send メソッドを使います
第一引数で要素名を指定して第二引数で要素の属性情報を定義します

xml.send("org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty")

また XML が入れ子になっている場合はブロックにすれば OK です

xml.definition(:class => "org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition", :plugin => "workflow-cps@2.41") {
  xml.script "node {\n   echo 'Hello World'\n}"
  xml.sandbox "true"
}

改行は \n で置換してあげましょう
それ以外もメタ文字に関してはちゃんと置換してあげてください

たぶん気をつけるべきはそれくらいだと思います
実際にリクエストを送信する前に XML の情報を出力してみて、手動で作成したジョブの XML と合っているか確認すると良いと思います

最後に

jenkins_api_client でジョブを作成するときのコツを紹介しました
基本は素の XML を頑張って Ruby のコードに落とし込む作業をやる感じになります
なれれば簡単にコードに落とし込むことができます
今回はジョブの設定がほとんどないジョブをコード化したので短いですがジョブの設定が多くなればそれだけコード側も多くなります

jenkins_api_client を使う場合はおそらくこれが一番簡単だと思いますが、結局 XML の文字列情報を client.job.create に渡せれば良いだけなのでファイルから文字列情報を生成しても良いかと思います

2017年10月28日土曜日

docker-compose でデータコンテナを作成して別コンテナでマウントする方法

概要

docker-compose を使ってデータコンテナを作成し同時に別のコンテナからデータコンテナの領域をマウントする yml ファイルを作成してみました

環境

  • macOS X 10.12.6
  • docker 17.09.0-ce
  • docker-compose 1.16.1

docker-compose.yml

全体は以下の通りです

version: '2'
services:
  main:
    image: nginx
    ports:
      - 80:80
    volumes_from:
      - vol
    links:
      - vol
    container_name: main
  vol:
    build: .
    image: vol_image
    volumes:
      - /share
    container_name: vol

コンテナを永続化させるために nginx のイメージを使っています
動作確認のために使ってるだけなので他のイメージからでも OK です

vol がデータコンテナ側の定義になります
確認のためファイルを 1 つコンテナに追加してそれが共有されているか確認するためイメージはビルドします
ビルド後のイメージ名は vol_image とします
データコンテナが共有する領域を定義するのは volumes で行います
今回の場合 /share というパスが共有されるので、別のコンテナがデータコンテナをマウントすると /share が共有されます

main 側がマウントする側のコンテナになります
ポイントは volumes_from になります
ここにはデータコンテナのコンテナ名を指定します
今回であれば vol になるので、それを指定します
links はなくても OK です

Dockerfile

ファイルを 1 つ追加しているだけです
nginx コンテナ内にある既存のファイルを使っても問題ないですがわかりやすいように独自でファイルを作成して追加しています

FROM nginx:latest

RUN mkdir -p /share
ADD ./hoge /share

動作確認

  • docker-compose up -d

でコンテナのビルド、起動をしましょう

Creating network "docker_default" with the default driver
Building vol
Step 1/3 : FROM nginx:latest
latest: Pulling from library/nginx
bc95e04b23c0: Pull complete
110767c6efff: Pull complete
f081e0c4df75: Pull complete
Digest: sha256:004ac1d5e791e705f12a17c80d7bb1e8f7f01aa7dca7deee6e65a03465392072
Status: Downloaded newer image for nginx:latest
 ---> 1e5ab59102ce
Step 2/3 : RUN mkdir -p /share
 ---> Running in e6d30579c4f9
 ---> 3db5418c7f21
Removing intermediate container e6d30579c4f9
Step 3/3 : ADD ./hoge /share
 ---> 2dd5e9403fa6
Successfully built 2dd5e9403fa6
Successfully tagged vol_image:latest
WARNING: Image for service vol was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating vol ... 
Creating vol ... done
Creating main ... 
Creating main ... done

コンテナが起動したら領域がちゃんと main 側にマウントされているか確認してみます

  • docker exec -it main ls -ltr /share
total 4
-rw-r--r-- 1 root root 5 Oct 25 07:59 hoge

こんな感じでマウントされてファイルが存在していれば OK です

最後に

docker-compose でデータコンテナを作成して別コンテナでその領域をマウントする方法を紹介しました
docker コマンドの -v--volumes-from を使っているだけです

ちなみにデータコンテナ側が停止すると共有されるのは停止するまでの状態になります
またデータコンテナ側には新規でファイルが追加されなくなります (当然かもしれませんが)

2017年10月27日金曜日

Sinatra で haml を使ってみた (partial も試してみる)

概要

Sinatra のテンプレートエンジンはデフォルトで erb が使えます
haml を使うこともできるので使ってみました
また、helper メソッドを定義することで別のテンプレートを参照する方法も紹介します

環境

  • CentOS 7.3
  • Ruby 2.3.3p222
  • sinatra 2.0.0
  • haml 5.0.4

各種ライブラリのインストール

  • bundle init
  • vim Gemfile
gem "sinatra"
gem "haml"
  • bundle install --path vendor

app.rb の作成

まずはアプリ本体を作成します

  • vim app.rb
require 'sinatra'
require 'haml'

set :bind, '0.0.0.0'

get '/' do
  @msg = "hello"
  haml :index
end

erb 同様インスタンス変数を定義することで haml テンプレート内でその変数を参照することができます

haml テンプレートの作成

とりあえず簡単な HTML を出力する haml テンプレートを作成します

  • vim views/index.haml
!!!
%html
%head
  %title haml test
%body
%h1 #{@msg}
%p ほげ

haml ではタグ情報を % を付与するだけで表現できます
見やすいように好きなようにインデントして OK です
文字列も特にダブルクォートで囲まなくて OK です、逆に囲んでしまうと表示されてしまうので注意です
先頭のビックリマーク 3 は「haml front matter」と呼ばれる特殊な命令でメタデータなどを設定することができます
#{@msg} とすることで app.rb 内で定義した変数を参照することができます

とりあえず動作確認

これでまずは動作確認してみましょう

  • bundle exec ruby app.rb

localhost:4567 にアクセスすると簡単な HTML が表示されると思います

css を追加してみる

class 属性を追加して css で装飾してみましょう

  • vim view/index.haml
!!!
%html
%head
  %title haml test
  %link{href: "style.css", rel: "stylesheet", type: "text/css"}
%body
%h1 #{@msg}
%p{class: "p1"} ほげ

%p に class 属性を追加しました
他にも追加するための記述はありますが個人的にはこれが好きです
そして %head 内にスタイルシートの参照を追記しています

スタイルシートを追加する

これは既存のスタイルを追加するだけです
haml 内でスタイルシートを定義することもできますが今回は別ファイルにしました

  • vim public/style.css
.p1 {
  font-size: 48px;
}

これで動作確認してみると文字が大きくなりスタイルが適用されているのがわかると思います
余談ですが Sinatra の場合画像やスタイル、JavaScript などの公開ファイルは public 配下に配置します

別の haml ファイルを作成して参照してみる

テンプレートファイルは共通する部分を切り出して部分的に管理したくなります
rails だと render という機能がありデフォルトで使用できるのですが、Sinatra ではそのような機能はありません
なので、今回は helper として partial というメソッドを定義して haml テンプレート内から別の haml テンプレートを参照してみたいと思います

ヘルパーモジュールの追加

  • vim helpers/sinatra_partials.rb
module Sinatra::Partials
  def partial(template, *args)
    template_array = template.to_s.split('/')
    template = template_array[0..-2].join('/') + "/_#{template_array[-1]}"
    options = args.last.is_a?(Hash) ? args.pop : {}
    options.merge!(:layout => false)
    locals = options[:locals] || {}
    if collection = options.delete(:collection) then
      collection.inject([]) do |buffer, member|
        buffer << haml(:"#{template}", options.merge(:layout =>
        false, :locals => {template_array[-1].to_sym => member}.merge(locals)))
      end.join("\n")
    else
      haml(:"#{template}", options)
    end
  end
end

何をしているかというと引数で与えられた template ファイルにアンダースコアを追加して haml 関数をコールしています
アンダースコアを追加しているのは非参照のファイルであることを明示的にするためです
またテンプレート内に与える引数をオプションで指定することができます

アプリでヘルパーモジュールを参照する

追加したヘルパーモジュールを app.rb から呼び出します

  • vim app.rb
require 'sinatra'
require 'haml'
require './helpers/sinatra_partials.rb'

set :bind, '0.0.0.0'
helpers Sinatra::Partials

get '/' do
  @msg = "hello"
  haml :index
end

これで haml テンプレート内で partial メソッドが使えるようになります

haml テンプレートの変更

別の haml テンプレートを呼び出してみます

  • vim views/index.haml
!!!
%html
%head
  %title haml test
  %link{href: "style.css", rel: "stylesheet", type: "text/css"}
%body
%h1 #{@msg}
%p{class: "p1"} ほげ
= partial "footer", :locals => {:key => "footer!"}

最後の 1 行を追加してます
haml 内で関数をコールする場合は = を使います
そしてヘルパーモジュール内で定義した partial メソッドをコールします
今回は _footer.haml というテンプレートを呼び出します
またオプションで :locals を追加することで _footer.haml 内で変数の値を参照できるようにします

_footer.haml の作成

参照される側の haml ファイルを作成します

  • vim views/_footer.haml
%h2 #{key}

先程も少し紹介しましたが非参照がわかるようにアンダースコアを付与するのを忘れないようにしてください
内容は渡された変数の内容を表示しているだけです

動作確認

これで動作確認すると別の haml ファイルの内容も HTML に反映されていると思います
またローカル変数の値も参照できていると思います

最後に

Sinatra + haml を使って HTML を表示してみました
erb よりも haml を使ったほうが記述量が減らせるのが嬉しい点かなと思います
また今回紹介した partial は erb でも使うことができます
haml とコールしてる部分を erb に変更するだけです

参考サイト

2017年10月26日木曜日

terraform に入門してみた (docker 編)

概要

terraform を試してみました
terraform はインフラ自動構築するためのツールでコード化することができます
terraform にはたくさんのプロバイダがあり今回は docker プロバイダを使って試してみました

環境

  • CentOS 7.3
  • terraform 0.10.7

terraform のインストール

まずはインストールです
hashicorp 製品はバイナリを一つ置いて終了です

  • wget 'https://releases.hashicorp.com/terraform/0.10.7/terraform_0.10.7_linux_amd64.zip?_ga=2.150895542.1898133342.1508819233-1686377386.1508819233' -O terraform_0.10.7_linux_amd64.zip
  • unzip terraform_0.10.7_linux_amd64.zip
  • mv terraform /usr/local/bin

とりあえず試してみる

公式の Getting Started にあるサンプルをとりあえず実行してみましょう

  • vim main.tf
provider "docker" {
  host = "tcp://127.0.0.1:2376/"
}

# Create a container
resource "docker_container" "foo" {
  image = "${docker_image.ubuntu.latest}"
  name  = "foo"
}

resource "docker_image" "ubuntu" {
  name = "ubuntu:latest"
}

dockerd の tcp 接続を有効にしている場合は上記で OK です
socket 接続の場合はコメントアウトすれば OK です
dockerd の tcp 接続の有効化に関してはこちら を参考にしてください

.tf ファイルが記載できたら初期化します

  • terraform init

これで docker プロバイダのインストールが始まります
完了すると .terraform というディレクトリが作成されています
terraform は実行する前に実行計画を確認することができます

  • terraform plan

実行すると作成されるリソースの一覧とその情報が表示されます
今回であれば pull されるイメージの情報や作成されるコンテナの情報が表示されます

では実行してみましょう

  • terraform apply

apply コマンドで実行できます
実際に .tf ファイルに記載した内容のコンテナとイメージが作成されます
確認は docker コマンドでもできますが terraform で作成したリソースだけを表示することもできます

  • terraform show

show コマンドを実行することで terraform 経由で pull したイメージやコンテナの情報だけを表示することができます

リソースを削除する

terraform 経由で作成されたリソースのみ削除したい場合は destroy コマンドを利用します

  • terraform destroy

基本的にはこれで削除したほうが良いと思います
もちろん docker rm などを使えばそれでも削除できますが面倒なので一気に削除したい場合は destroy を使うと良いと思います
デフォルトだとリソースの削除時に確認があるので確認が不要な場合は -force を付与してください
また、plan コマンドで-destroy オプションを付与すると削除時の計画を表示することも可能です

インフラ構成を変更する

terraform では一度構築した環境を変更することもできます
以下のように .tf ファイルを書き換えてみましょう

  • vim main.tf
provider "docker" {
  host = "tcp://127.0.0.1:2376/"
}

# Create a container
resource "docker_container" "foo" {
  image = "${docker_image.ubuntu.latest}"
  name  = "foo2"
}

resource "docker_image" "ubuntu" {
  name = "ubuntu:latest"
}

コンテナの名前を変更しただけです
これで再度 apply するとコンテナの名前が変わっていると思います
docker プロバイダの場合コンテナの名前が変更されたわけではなく実際は前のコンテナが削除されて別のコンテナが作成されています

プロビジョニングする

terraform ではインスタンスなどを構築時にスクリプトを実行してプロビジョニングすることも可能です
ただ、docker プロバイダではプロビジョンの機能は提供されていません (当然ですが)
AWS のリソースなどを扱える aws_instance などでは使うことができます

変数を利用する

場合によっては apply 時に毎回情報を書き換えたい場合があると思います
そんなときは変数を使うことができます
.tf ファイルを以下のように書き換えましょう

  • vim main.tf
variable "container_name" {
  default = "hoge"
}

provider "docker" {
  host = "tcp://127.0.0.1:2376/"
}

# Create a container
resource "docker_container" "foo" {
  image = "${docker_image.ubuntu.latest}"
  name  = "${var.container_name}"
}

resource "docker_image" "ubuntu" {
  name = "ubuntu:latest"
}

variable を使って変数を使用することができます
default を使えばデフォルト値を設定することもできます
参照するときは ${var.container_name} として参照することができます (なぜか var. なので注意)

  • terraform apply

で実行すると default の値が使用されます

  • terraform plan -var container_name="fuga"
  • terraform apply -var container_name="fuga"

とすると fuga というコンテナを作成することができます

また key = value 形式のファイルを作成して -var-file でファイルを参照することもできます
変数には list や map 形式で定義することもできます

特定の結果を表示する

apply で実行した結果を確認したいケースはあると思います
そんなときは output リソースを使用することで指定した結果を取得することができます

  • vim main.tf
variable "container_name" {
  default = "hoge"
}

provider "docker" {
  host = "tcp://127.0.0.1:2376/"
}

# Create a container
resource "docker_container" "foo" {
  image = "${docker_image.ubuntu.latest}"
  name  = "${var.container_name}"
}

resource "docker_image" "ubuntu" {
  name = "ubuntu:latest"
}

output "id" {
  value = "${docker_container.foo.id}"
}

最後の output リソースがそれに当たります
上記の場合は作成されたコンテナの id を取得します

  • terraform apply

で実行したあとに

  • terraform output id

とすることで id の情報を取得することができます

モジュール化する

terraform では .tf ファイルをモジュールにすることができます
モジュール化した .tf ファイルを参照することでモジュール内のリソースを使用することができるようになります

.tf ファイル内で modules の定義をして terraform get を実行するとモジュールの .tf ファイルを取得できます

今回は試さないので詳しくはこちらを試してみてください
サンプルは consul 用の module を使用しています

Remote Backends

それ以外には Remote Backends という機能があり terraform で実行した履歴や状態を保存しておくことができます
自分一人で使っている場合には不要なのですが、例えばプロダクションなどの環境でいろんなホストから実行される場合などにどこまで apply しているかを確認することができようになります

こちらも今回は試していないので興味があれば公式を見ながら試してみてださい

最後に

terraform を試してみました
公式の Getting Started は AWS 環境を使っていますが準備が大変だったのでさらっとできる docker プロバイダで試してみました

正直 docker-compose で足りるような気もしますが docker コマンドのオペレーションもこれを使えばコード化できるのが嬉しい点かなと思います
ただ docker build はできないっぽいので、それは terraform 以外の世界で事前に作成する必要があります

参考サイト

2017年10月25日水曜日

spin.js を使ってみた

概要

spin.js はローティング画面を簡単に実装できる JavaScript のライブラリです
今回は HTML と JavaScript だけを使って spin.js の動きを確認してみました

環境

  • macOS X 10.12.6
  • nginx 1.10.1
  • spin.js 2.3.2

HTML 作成

  • vim index.html
<html>
<head>
<style type="text/css">
<!--
#layer {
    width  : 100%;
    height : 100%;
    background-color : blue;
    position : absolute;
    opacity: 0.1;
}
-->
</style>
<script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
<script type="text/javascript" src="http://spin.js.org/spin.min.js"></script>
</head>
<body>
<div id="layer"></div>
<div id="test_spin">spin_area</div>
<div id="stop">stop</div>
<script type="text/javascript" src="/spin/my_spin.js"></script>
</body>
</html>

dom の参照を jQuery で行いたかったので、jQuery を追加しています
あとは spin.js と独自の JavaScript ファイルを body の直前で読み込んでいます

spin.js を使った JavaScript ファイルの作成

  • vim my_spin.js
var opts = {
  lines: 13 // The number of lines to draw
, length: 28 // The length of each line
, width: 14 // The line thickness
, radius: 42 // The radius of the inner circle
, scale: 1 // Scales overall size of the spinner
, corners: 1 // Corner roundness (0..1)
, color: '#000' // #rgb or #rrggbb or array of colors
, opacity: 0.25 // Opacity of the lines
, rotate: 0 // The rotation offset
, direction: 1 // 1: clockwise, -1: counterclockwise
, speed: 1 // Rounds per second
, trail: 60 // Afterglow percentage
, fps: 20 // Frames per second when using setTimeout() as a fallback for CSS
, zIndex: 2e9 // The z-index (defaults to 2000000000)
, className: 'spinner' // The CSS class to assign to the spinner
, top: '50%' // Top position relative to parent
, left: '50%' // Left position relative to parent
, shadow: false // Whether to render a shadow
, hwaccel: false // Whether to use hardware acceleration
, position: 'absolute' // Element positioning
}
console.log("start");
var target = $("#test_spin").get(0);
var spinner = new Spinner(opts).spin(target);
setTimeout(function() {
    spinner.stop();
    $("#layer").hide();
    console.log("stop");
}, 5000);

$('#stop').click(function () {
  console.log("stop");
  spinner.stop();
});

JavaScript ファイルが読み込まれた瞬間にローティングが始まります
おまけで stop 要素をタップした場合の処理も実装しています
これはなくても動作するので参考まで

動作確認

index.html にアクセスしてみましょう
すると 5 秒後にローティング画面が非表示になりかつ薄青のレイヤー用の div も非表示になります
spin_js.gif

非表示になったあとに stop をタップすることができるようになります
レイヤ用の div を初めから削除した状態で index.html を開くといきなり stop をタップすることができます
stop をタップするとタップした瞬間ローティング画面が非表示になります

spin.js の挙動としてはローティングを表示する div タグは別にどこにあっても良くその上に強制的にローティング画面を表示します
そして、今回の設定であれば画面中央に表示されます
表示される場所やそれ以外の設定はすべて opts 変数で定義されているので適当に変更しながら挙動を確認すると良いと思います

最後に

spin.js を HTML と JavaScript だけで簡単に試してみました
spin.js 自体はシンプルにローティング画面を表示/非表示するだけなので、ローティング中に他のボタンを表示させないようにレイヤなどを自分で用意する必要はありそうです
それでも簡単にローティング画面を実装できる可能です

2017年10月24日火曜日

webpack に入門してみた

概要

webpack は複数の js ファイルや css をファイルを一つの js ファイルにまとめてくれるツールです
node で動作します
今回は webpack を使って簡単な HTML を出力するところまでやってみました

環境

  • macOS X 10.12.6
  • node 8.7.0
  • webpack 3.8.1

作成するファイル一覧

今回作成するファイルの構成は以下の通りです

.
├── package-lock.json
├── package.json
├── public
│   ├── index.html
│   └── js
│       └── bundle.js
├── src
│   └── js
│       ├── app.js
│       └── modules
│           ├── footer.js
│           └── header.js
└── webpack.config.js

順を追って全部作成していきます

webpack のインストール

まずはこれがないと始まりません

  • npm install webpack -g

webpack コマンドが使えるようになります

packer.json の作成

今回は外部のモジュールも使用するため dependencies に 2 つほど追加しています

  • npm init
  • vim packer.json
{
  "name": "webpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "jquery": ">=3.2.1",
    "date-utils": ">=1.2.21"
  }
}
  • npm install

jquery と date-utils をインストールするために作成します
やることによっては必須ではないです
今回は使うためインストールしています

webpack.config.js の作成

いわゆる設定ファイルです

  • vim webpack.config.js
const path = require('path');

module.exports = {
    entry: './src/js/app.js',
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'public/js')
    }
};

起点となる js ファイルと最終的に出力される bundle.js の出力パスを指定しています

起点となる app.js の作成

あとで作成するモジュールを読み込んでメインとなる処理を実施します

  • vim src/js/app.js
import $ from 'jquery';
import headerText from './modules/header';
import footerText from './modules/footer';

var htext = headerText();
var ftext = footerText();

$('header').html(htext);
$('footer').html(ftext);

今回はヘッダ情報とフッタ情報を生成するモジュールを作成しました
そしてそれらモジュールと jquery を使って HTML 情報を作成しています

モジュールファイルの作成

各機能ごとに作成します
今回は簡単な文字列情報を返却するモジュールファイルを 2 つ作成します

  • vim src/js/modules/header.js
export default function headerText() {
    return "Header";
}
  • vim src/js/modules/footer.js
export default function footerText() {
    require('date-utils');
    var date = new Date();
    var now = date.toFormat("YYYY/MM/DD HH24:MI:SS");
    return `DateTime: ${now}`;
}

せっかくなので外部のモジュールを読み込んで使っています
アクセスごとに時刻が変わるようになります

HTML ファイルの作成

  • vim public/index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>webpack tutorial</title>
</head>
<body>
  <header></header>
  <footer></footer>
  <script src="js/bundle.js"></script>
</body>
</html>

ビルドして作成される bundle.js を読み込んでいます

ビルド

webpack.config.js のあるディレクトリで実行しましょう

  • webpack

すると以下のようにビルド結果が表示されます

Hash: 44c1686f210ca4db43de
Version: webpack 3.8.1
Time: 362ms
    Asset    Size  Chunks                    Chunk Names
bundle.js  325 kB       0  [emitted]  [big]  main
   [0] ./src/js/app.js 214 bytes {0} [built]
   [2] ./src/js/modules/header.js 62 bytes {0} [built]
   [3] ./src/js/modules/footer.js 180 bytes {0} [built]
    + 2 hidden modules

これで public/js/bundle.js にすべてのファイルがまとめられたバンドルファイルが作成されます

動作確認

public/index.html をブラウザで確認してみましょう
すると以下のような感じで表示されると思います
webpack1.png

最後に

webpack を使ってみました
モジュールをわけて開発することができるのが一番の魅力かなと思います
最終的には 1 つのファイルができあげるだけなので利用する側も簡単になります
今回は Javascript のバンドルだけを行いましたが、CSS のバンドルもできるようです
できあがったサンプルが寂しいので次回は webpack を使って CSS でも当ててみたいと思います

参考サイト

2017年10月23日月曜日

Jenkins の API を Ruby から操作してみた

概要

前回 Jenkins の API を curl で試してみました
今回は Ruby から実行してみました
専用の SDK があるようなのでそれを使ってみます

環境

  • macOS X 10.12.6
  • Jenkins 2.60.3
  • Ruby 2.4.1p111

ライブラリインストール

  • bundle init
  • vim Gemfile
gem "jenkins_api_client"
  • bundle install

サンプルコード集

いろいろと試してみます

ジョブの一覧の取得

require 'jenkins_api_client'

client = JenkinsApi::Client.new(
  :server_ip => 'localhost',
  :username => 'root',
  :password => 'ab4eda0916f1a599e8132bc2bf8a3cec'
)
jobs = client.job.list_all
puts jobs.class # -> Array
jobs.each { |job|
  puts job
}

jobs = client.job.list("^hoge")
jobs.each { |job|
  puts job
}

list メソッドを使うことで正規表現でジョブを絞込することができます

ジョブをビルド

require 'jenkins_api_client'

client = JenkinsApi::Client.new(
  :server_ip => 'localhost',
  :username => 'root',
  :password => 'ab4eda0916f1a599e8132bc2bf8a3cec'
)
jobs = client.job.list_all
jobs.each { |job|
  code = client.job.build(job)
  # build_id = client.job.build(job, params = {}, opts = {"build_start_timeout" => 100})
}

client.job.build でジョブ名を文字列で指定するだけです
上記サンプルは全ジョブを実行しているので必要なジョブだけ絞り込んで実行したほうが良いでしょう

また build_start_timeout を指定してビルドを実行するとレスコンスコードではなくビルドの番号が返ってきます
なのでビルド番号がほしい場合は下の方法で呼ぶ必要があります

コンソール情報を取得する

ビルド番号からコンソールの情報を取得します
text or html が選択できます

require 'jenkins_api_client'

client = JenkinsApi::Client.new(
  :server_ip => 'localhost',
  :username => 'root',
  :password => 'ab4eda0916f1a599e8132bc2bf8a3cec'
)

job = 'hoge'
build_id = client.job.build(job, params = {}, opts = {"build_start_timeout" => 100})
ret = client.job.get_console_output(job, build_num = build_id, start = 0, mode = 'text')
puts ret['output']
puts ret['size']
puts ret['more']

client.job.get_console_output を使います
ビルド番号が必須なので、ジョブを実行した際にビルド番号を取得するようにします
(ビルド番号がない場合は最新のビルドから取得しても良いような気がしますが)

ジョブを連結する

いわゆる「他のプロジェクトのビルド」を設定することができます

require 'jenkins_api_client'

client = JenkinsApi::Client.new(
  :server_ip => 'localhost',
  :username => 'root',
  :password => 'ab4eda0916f1a599e8132bc2bf8a3cec'
)
jobs = client.job.list_all
target_jobs = client.job.chain(jobs, 'success', ["all"]) # 安定している場合のみ起動にチェックが入る
# target_jobs = client.job.chain(jobs, 'failure', ["all"]) # 失敗した場合でも起動にチェックが入る
# target_jobs = client.job.chain(jobs, 'unstable', ["all"]) # 不安定でも起動にチェックが入る

client.job.build(target_jobs[0])

第二引数を変更することで次のビルドに進む条件を変更できます
第三引数は chain する対象のジョブを絞り込むことができます
例えば failure を指定すると失敗したジョブだけを chain することができます
これを使えば失敗したジョブだけをつないでビルドすることができるようになります
ちなみに chain コール後はジョブが連結されているので each で回してビルドするのではなく先頭だけ実行すればすべてのジョブが実行されます

ジョブを作成する

XMl の生成が必要です
Helper クラスが合ったのそれを使います

require 'jenkins_api_client'

client = JenkinsApi::Client.new(
  :server_ip => 'localhost',
  :username => 'root',
  :password => 'ab4eda0916f1a599e8132bc2bf8a3cec'
)

def create_job_xml
  builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') { |xml|
    xml.project {
      xml.actions
      xml.description
      xml.keepDependencies "false"
      xml.properties
      xml.scm(:class => "hudson.scm.NullSCM")
      xml.canRoam "true"
      xml.disabled "false"
      xml.blockBuildWhenDownstreamBuilding "false"
      xml.blockBuildWhenUpstreamBuilding "false"
      xml.triggers.vector
      xml.concurrentBuild "false"
      xml.builders {
        xml.send("hudson.tasks.Shell") {
          xml.command "\necho 'going to take a nice nap'\nsleep 15\necho 'took a nice nap'"
        }
      }
      xml.publishers
      xml.buildWrappers
    }
  }
  builder.to_xml
end

puts client.job.create('job_name', create_job_xml)

create_job_xml の内容を変更すればジョブの内容を変更することができます
curl でファイルを操作してジョブを作成するよりかはプログラム内で XML を扱ったほうが若干ジョブの作成しやすさは上かなと思います

最後に

Ruby から Jenkins の API をコールしてみました

ビルドの実行からジョブの作成まで基本的なことはすべてできると思います
また、今回は触れませんでしたがプラグイン関連の処理もできるので Jenkins 自体の設定もコードから操作することができるかもしれません (未確認)

あとは Sinatra や Rails などの Web フレームワークと組み合わせれば独自 UI なども簡単にできると思います

参考サイト

2017年10月22日日曜日

packer で Powershell を使って Windows サーバに DNS を設定する方法

概要

packer で WindowsServer2016 を構築する際にネットワークアダプタに DNS を設定する方法を紹介します
もし DHCP で IP を取得する場合だと大抵は DHCP サーバが DNS の情報も教えてくれますが、そうじゃない環境の場合には手動で DNS の向き先を変更する必要があります
そういった場合には使える手法だと思います

環境

  • ESXi 6.0.0 (Build 3620759)
  • Ubuntu 16.0.4
  • packer 1.0.4

Powershell スクリプト

DNS を Ethernet0 アダプタに設定する方法を紹介します

  • vim scripts/dns.ps1
Set-DnsClientServerAddress -InterfaceAlias "Ethernet0" -ServerAddresses 8.8.8.8

これを packer を使って WindowsServer 上で実行させます

テンプレートファイル修正

provisoners セクションに powershell を実行する設定を追加します

  • win2016.json
"provisioners": [
    {
        "type": "powershell",
        "execute_command": "{{ .Vars }} cmd /c C:/Windows/Temp/script.bat",
        "scripts": [
            "scripts/dns.ps1",
        ]
    }
]

type は powershell を指定します
windows-shell というタイプもありますが、それは .bat 用なので違います

execute_command はそのまま使いましょう
{{ .Vars }} は environment_vars という設定項目でセットした環境変数を参照するためのテンプレート変数です
今回は特に設定していないので何も入らないですが入れるのが基本らしいので入れておきます
そのあとの C:/Windows/Temp/script.bat ですが、これは scripts で指定した Powershell スクリプトが WindowsServer 側にアップロードされた際のパスを指定しています
remote_path という設定項目でアップロード先を指定できるのですが、今回は指定していません
指定を省略した場合はデフォルトの C:/Windows/Temp/script.bat が使われるため execute_command でもそのパスを指定する感じです

これで Powershell を呼び出すことができます

動作確認

実行して成功するか確認してみましょう
packer のログ的には以下のように表示されれば成功です

==> vmware-iso: Provisioning with Powershell...
==> vmware-iso: Provisioning with shell script: scripts/dns.ps1
    vmware-iso: #< CLIXML
    vmware-iso: <Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TNRefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS>
</Obj></Objs>

もし失敗するとしたら Powershell 内で指定している -InterfaceAlias "Ethernet0" の部分かと思います
ここで指定したネットワークアダプタ名がないとエラーになるので、自分の環境に合わせて変更してください

最後に

packer + Powershell で Windows サーバの DNS の設定を行ってみました
1 行で行えるので簡単でした

packer ビルド内でインターネットにアクセスするような処理がある場合に DNS で名前解決できない場合などで使えると思います
もしくは内部向けの DNS がないとイントラのホスト名を解決できない場合かなと思います

2017年10月21日土曜日

packer で WindowsServer2016 に vmware-tools をインストールしてみた

概要

packer はデフォルトだと vmware-tools のインストーラが入った圧縮ファイル windows.iso を Windows サーバ側にアップロードするところまでしかやってくれません
自動化する場合にはアップロードされた iso ファイルを解凍して setup.exe を実行するスクリプトを自分で作成する必要があります
今回はその方法を紹介したいと思います

環境

  • ESXi 6.0.0 (Build 3620759)
  • Ubuntu 16.0.4
  • packer 1.0.4

vmware-tools をインストールするバッチスクリプトの作成

ISO を展開して setup.exe を叩くバッチスクリプトです
下部で説明をしています

  • vim scripts/install_tools.bat
if not exist "C:\Windows\Temp\7z920-x64.msi" (
    powershell -Command "(New-Object System.Net.WebClient).DownloadFile('http://www.7-zip.org/a/7z920-x64.msi', 'C:\Windows\Temp\7z920-x64.msi')" <NUL
)
msiexec /qb /i C:\Windows\Temp\7z920-x64.msi

if not exist "C:\Windows\Temp\windows.iso" (
    powershell -Command "(New-Object System.Net.WebClient).DownloadFile('http://softwareupdate.vmware.com/cds/vmw-desktop/ws/12.0.0/2985596/windows/packages/tools-windows.tar', 'C:\Windows\Temp\vmware-tools.tar')" <NUL
    cmd /c ""C:\Program Files\7-Zip\7z.exe" x C:\Windows\Temp\vmware-tools.tar -oC:\Windows\Temp"
    FOR /r "C:\Windows\Temp" %%a in (VMware-tools-windows-*.iso) DO REN "%%~a" "windows.iso"
    rd /S /Q "C:\Program Files (x86)\VMWare"
)

cmd /c ""C:\Program Files\7-Zip\7z.exe" x "C:\Windows\Temp\windows.iso" -oC:\Windows\Temp\VMWare"
cmd /c C:\Windows\Temp\VMWare\setup.exe /S /v"/qn REBOOT=R\"

ちょっと長いですがやってることは単純です
まず 7z をインストールします
サイトからインストーラをダウンロードして msiexec コマンドでインストーラを実行してインストールします
7z をインストール理由は iso ファイルを展開するためです

そして次に vmware-tools が入っている iso をダウンロードし解凍します
C:\Windows\Temp\windows.iso に iso ファイルがあるかないか確認し、ない場合は VMware さんが公開している tools-windows.tar をダウンロードし解凍します
これで iso が取得できます

あとは iso を 7z で展開して vmware-tools のインストーラである setup.exe を実行しています
インストール処理はだいたい 30 分程度で終了します

テンプレートファイル修正

provisioners で上記のバッチスクリプトを実行します

  • vim win2016.json
"provisioners": [
    {
        "type": "windows-shell",
        "execute_command": "{{ .Vars }} cmd /c C:/Windows/Temp/script.bat",
        "scripts": [
            "scripts/install_tools.bat"
        ]
    }
]

まず type は windows-shell を指定します
他に shell というタイプもありますが、これは Linux 用のタイプなので違います
execute_command はこのまま使います
scripts で指定したバッチスクリプトは Windows サーバ側にアップロードされます
その際のアップロード先が C:/Windows/Temp/script.bat になります
これは remote_path でも設定できますが指定しない場合は上記のパスになります
あとは cmd で実行していくだけです

で実は以下の設定を builders に設定して実行していました

"tools_upload_flavor": "windows",
"tools_upload_path": "c:/Windows/Temp/windows.iso",

packer のドキュメントを見るとこれを指定すると ESXi 上にある windows.iso という vmware-tools が入った ISO を Windows サーバ側にアップロードしてくれるのですがどうもアップロードしてくれません
いろいろと調べるとこれ絡みの issue が見つかるのでバグの可能性もあるかもしれません (使い方が悪いだけかもしれません)

で本来はこれで Windows サーバ側に ISO がアップロードされるのでわざわざ VMware さんのサイトから tools-windows.tar をダウンロードする必要がなかったのですが、どうしてもアップロードできず泣く泣くダウンロード方式にしました
なので、本当はダウンロードする部分の処理はまるごと削除できます

動作確認

実行してみましょう
packer のログが長いので省略しますが iso が展開され setup.exe が実行されエラーがでなければ OK です

作成された vmx ファイルをデプロイして VM を起動すると vmware-tools がインストールされている状態で起動するのが確認できると思います

最後に

packer + WindowsServer2016 のビルドで vmware-tools の自動インストールを行ってみました
今回は vmware-toosl のインストーラを Web から取得する方式で行いました
またバッチスクリプトで実装していますが、まるっとそのまま Powershell に置き換えることもできると思います
provisioners 内にバッチやら Powershell やらいろいろと混在するのが嫌な方は書き直しても良いかと思います

ちなみに今回のバッチスクリプトは参考サイトにあるものを流用しているので、参考サイトにあるバッチスクリプトをそのまま使っても OK です

1 つアドバイスですが packer でプロビジョニング系のスクリプトを書く場合は一旦普通に動作している VM を作成するのをおすすめします
その VM でバッチやら Powershell やらを書いてそこで動作確認してから packer に組み込んだほうが効率が良いです
スクリプトを書き換えて毎回 packer build していると正直ビルド時間が非常にかかるので効率が悪いかなと思います

参考サイト

2017年10月20日金曜日

Ubuntu で docker のアップグレードを行おうとしたら Linux カーネルのインストールでエラーになった話

概要

Ubuntu 16.04 で docker-ce のアップグレードを行ってみました
アップグレード自体は apt upgrade コマンドを叩けば終了なのですが、全然別件で Linux カーネルのインストールエラーに直面したので対応してみました

環境

  • Ubuntu 16.04
  • docker-engine 17.03 -> 17.05
  • linux-image-generic 4.4.0.96 -> 4.4.0.97

遭遇したエラー

アップグレードしようとしたときに遭遇したエラーは以下の通りです

[root@localhost /boot Wed Oct 18 10:40:08]# apt upgrade docker-engine
Reading package lists... Done
Building dependency tree
Reading state information... Done
You might want to run 'apt-get -f install' to correct these.
The following packages have unmet dependencies:
 linux-image-extra-4.4.0-97-generic : Depends: linux-image-4.4.0-97-generic but it is not installed
 linux-image-generic : Depends: linux-image-4.4.0-97-generic but it is not installed
                       Recommends: thermald but it is not installed
E: Unmet dependencies. Try using -f.

こんな感じです
実は /boot の領域が足りなくて上記のエラーが発生しています

対応する

単純に /boot の領域を空ければ OK
古いカーネルのファイルが残っているので削除してあげます

  • cd /boot

例えば config 系のファイルでは過去のバージョンがずらっと保存されているのが確認できると思います

[root@localhost /boot Wed Oct 18 10:45:39]# ls -l config-4.4.0-*
-rw-r--r-- 1 root root 189412 Apr 19  2016 config-4.4.0-21-generic
-rw-r--r-- 1 root root 190236 Mar 25  2017 config-4.4.0-71-generic
-rw-r--r-- 1 root root 190356 Jun 14 21:24 config-4.4.0-81-generic
-rw-r--r-- 1 root root 190356 Jun 27 04:45 config-4.4.0-83-generic
-rw-r--r-- 1 root root 190356 Jul 19 00:00 config-4.4.0-87-generic
-rw-r--r-- 1 root root 190356 Aug  1 07:25 config-4.4.0-89-generic
-rw-r--r-- 1 root root 190356 Aug  8 22:58 config-4.4.0-91-generic
-rw-r--r-- 1 root root 190356 Aug 10 20:02 config-4.4.0-92-generic
-rw-r--r-- 1 root root 190356 Aug 12 08:40 config-4.4.0-93-generic

この中の最新版以外 (今回の場合は 4.4.0-93 以外) の古いファイルをすべて削除してあげます
config の他に abi, initrd System.map, vmlinuz があるのでこれも削除するコマンドを実行します

  • rm *4.4.0-21-*
  • rm *4.4.0-71-*
  • rm *4.4.0-81-*
  • rm *4.4.0-83-*
  • rm *4.4.0-87-*
  • rm *4.4.0-89-*
  • rm *4.4.0-91-*
  • rm *4.4.0-92-*

これでだいぶ /boot が空くと思います
空いたら最新のカーネルをインストールしてあげましょう

  • apt -f install

で該当のエラーが出なくなると思います
無事インストールできたら docker-engine のアップグレードを実施しましょう

  • apt upgrade docker-engine
  • docker -v
Docker version 17.05.0-ce, build 89658be

こんな感じです

最後に

docker-engine を最新版にアップグレードした際に遭遇したカーネルエラーに対応してみました
/boot に空きがあれば遭遇することはないエラーだと思います

もし同じようなエラーに遭遇した場合はやってみてください

参考サイト

2017年10月19日木曜日

Jenkins の API を curl でサラッと試してみた

概要

久しぶりに Jenkins のリモート API に触れたので使い方などをまとめておきます

環境

  • macOS X 10.12.6
  • Jenkins 2.60.3

事前作業

今回は docker 上で動かしました
Jenkins を動かすのはどこでも問題ないです
一番初めのテンポラリーパスワードによる認証とプラグインのインストール、ユーザの作成までは済ませておいてください

今回は root という名前のユーザを作成した体で勧めます

CSRF の無効化

Jenkins -> Jenkins の管理 -> グローバルセキュリティ設定 -> CSRF対策

のチェックをオフにして Apply

API トークンの取得

root -> 設定 -> API トークン -> API トークンの表示

ジョブの作成

適当に作成してください
API でも良いですが面倒なので手動で作成しました
ちなみに API で作成した場合は謎の XML ファイルを作成して FORM として POST すれば作成できます -> 参考

いろいろコールしてみる

つらつらと curl でコールしてみました
ちなみに全部 POST で送っていますが GET でも OK です

ジョブの一覧の取得

curl -X POST --user 'root:bcc000720152bfd435e5c5128705908a' \
'http://localhost:8080/api/json?tree=jobs'

ちなみに件数を指定して取得することもできます

curl --user 'root:bcc000720152bfd435e5c5128705908a' \
'http://localhost:8080/api/json?tree=jobs%5Bname%5D%7B0%2C1%7D'

URL にするとこんなフォーマットです
curl でコールするときは URL エンコードする必要があるので上記のようになっています

http://localhost:8080/api/json?tree=jobs[name]{0,1}

ジョブのビルド

curl -v -X POST --user 'root:bcc000720152bfd435e5c5128705908a' \
'http://localhost:8080/job/test/build'

ここでポイントですがレスポンスの Location ヘッダにキューの情報が付与されています
ここに番号が振られておりそれがビルドの番号にもなるので、実行したビルドの結果を追いたい場合には Location ヘッダの情報を使用してください

Location: http://localhost:8080/queue/item/3/

最後に成功したビルドの結果を取得

curl -X POST --user 'root:bcc000720152bfd435e5c5128705908a' \
'http://localhost:8080/job/test/lastSuccessfulBuild/api/json?pretty=true'

最後に成功したビルドのコンソール結果を取得

curl -X POST --user 'root:bcc000720152bfd435e5c5128705908a' \
'http://localhost:8080/job/test/lastSuccessfulBuild/logText/progressiveText?start=0'

ビルド番号を指定してビルドの結果を取得

curl -X POST --user 'root:bcc000720152bfd435e5c5128705908a' \
'http://localhost:8080/job/test/3/api/json?pretty=true'

ビルド番号を指定してビルドのコンソール結果を取得

curl -X POST --user 'root:bcc000720152bfd435e5c5128705908a' \
'http://localhost:8080/job/test/3/logText/progressiveText?start=0'

ビルド番号を指定してビルドのコンソール結果を HTML で取得

curl -X POST --user 'root:bcc000720152bfd435e5c5128705908a' \
'http://localhost:8080/job/test/3/logText/progressiveHtml?start=0'

Tips

トークンを使わないでも API をコールすることができます
トークンの箇所に作成したユーザのパスワードを入力しても API をコールすることができます

また、良くないですがそもそも認証情報付与するの面倒くさいという場合はグローバルセキュリティ設定から「セキュリティを有効化」のチェックをオフにすればトークンもパスワードもなしで API をコールできます

最後に

Jenkins のリモート API を試してみました
基本は UI で操作していて、その操作を API として使いたいときに URL の最後に /api を付け加えてやるとその操作の API リファレンスがいろいろと表示されるのでそこでやり方を確認するといいと思います

コンソールの結果が平文 or HTML じゃなくてそれっぽい JSON とかに置き換えできれば Jenkins を使って簡単な API サーバが作れそうな気がしました
プラグインとか探せばあるのかな、、、

2017年10月18日水曜日

packer + WindowsServer2016 で vmxnet3 を使う

概要

前回 packer + esxi で WindowsServer2016 を自動構築してみました
しかし Windows2016 はデフォルトでネットワークドライバに vmxnet3 が使えません
packer ビルド時にドライバをインストールすれば使用することができたのでその方法を紹介します

環境

  • ESXi 6.0.0 (Build 3620759)
  • Ubuntu 16.0.4
  • packer 1.0.4

ドライバファイル

こちらです (GoogleDrive なので wget などはできません、手動でダウンロードしてサーバにアップロードしてください)
この中に inf ファイルが含まれています
WindowsServer2016 はこのファイルを自動実行してくれます
そうすることでビルド時にドライバを自動インストールします

テンプレートの修正

ほぼ同じなので一部だけ紹介します
ダウンロードした zip ファイルを展開して drivers 配下に配置します
そしてテンプレートファイル内の floppy_files に含めるようにします

zip ファイルのまま drivers 配下においても inf ファイルが見つからず自動で実行されないので、ちゃんと解凍したファイルたち (.dll や .inf ファイルなど) を drivers 配下に配置してください

"floppy_files": [
    "answer_files/Autounattend.xml",
    "drivers/",
    "scripts/winrm.ps1"
]

動作確認

これでビルドしてみましょう
もちろん Autounattend.xml や winrm.ps1 などもちゃんと配置して実行してください
うまくいくと vmxnet3 のドライバで動作する WindowsServer2016 が作成されると思います

最後に

packer + esxi + WindowsServer2016 で vmxnet3 ドライバを使用する方法を紹介しました
ドライバは以下の参考サイトで紹介されているものをそのまま使用しています
なくなっても問題ないようにキャッシュとして残しているだけなので、Github から持ってきても OK です
おそらく同様にストレージドライバなどをインストールすれば PVSCSI などを利用することもできると思います

参考サイト

2017年10月17日火曜日

packer を使って ESXi 上で WindowsServer2016 を自動構築してみた

概要

前回 Virtualbox 上で Windows Server 2016 の自動構築を行いました
今回は VMware ESXi 上でビルドして vmx + vmdk ファイルを作成してみたいと思います

環境

  • ESXi 6.0.0 (Build 3620759)
  • Ubuntu 16.0.4
  • packer 1.0.4

事前準備

Windows Server 2016 の ISO は事前にダウンロードしておいてください
過去にダウンロードの仕方を紹介しているので参考にしてください
packer を実行するマシン (今回であれば Ubuntu を使っています) 上に配置してください

前回同様 Windows の自動構築の仕組みは Autounattend.xml を使います
これは前回使用したものをそのまま流用できます

Autounattend.xml は Windows の OS 自動インストールを行うための仕組みなので Virtualbox であろうが ESXi であろうが同じものが使えて当然です
そして、内部で使用している WinRM を有効にする Powershell スクリプトも前回と同じものを流用します

テンプレートファイルの準備

ESXi 上で実行するテンプレートファイルを新規で作成します

  • win2016.json
{
    "builders": [{
        "type": "vmware-iso",
        "vm_name": "win2016",
        "guest_os_type": "windows8srv-64",
        "vmx_data": {
            "gui.fitguestusingnativedisplayresolution": "FALSE",
            "memsize": "2048",
            "numvcpus": "2",
            "virtualHW.version": "10",
            "scsi0.virtualDev": "lsisas1068",
            "ethernet0.networkName": "VM Network",
            "ethernet0.virtualDev": "e1000",
            "ethernet0.present": "TRUE",
            "ethernet0.connectionType": "custom",
            "ethernet0.vnet": "vmnet8",
            "ethernet0.startConnected": "TRUE"
        },
        "disk_size": 81920,
        "disk_type_id": "thin",
        "remote_type": "esx5",
        "remote_host": "192.168.100.101",
        "remote_datastore": "datastore12",
        "remote_username": "esxi_user",
        "remote_password": "esxi_password",
        "headless": "false",
        "iso_url": "/vol/win2016.ISO",
        "iso_checksum": "18a4f00a675b0338f3c7c93c4f131beb",
        "iso_checksum_type": "none",
        "communicator": "winrm",
        "winrm_username": "winuser1",
        "winrm_password": "winpass123",
        "winrm_timeout": "12h",
        "floppy_files": [
            "answer_files/Autounattend.xml",
            "scripts/winrm.ps1"
        ]
    }]
}

VirtualBox 時のテンプレートファイルと比較してポイントをいくつか説明します

まず type は vmware-iso を指定します
こうすることで VMware 環境 (今回は ESXi) 上でビルドを行うことができます
ESXi の情報は remote_type, remote_host, remote_datastore, remote_username, remote_password で設定しています

guest_os_type は Windodws8 のものを使用しています
とりあえずこれでも動作します

vmx_data で VM の構成を設定します
ここが一番のポイントです
まず scsi0.virtualDevlsisas1068 を指定してください
これは LSI Logic SAS というドライバなのですが、これでないとハードディスクをうまく認識することができません
これ以外のドライバを使いたい場合は別途ドライバをインストールする必要があります
次に ethernet0.virtualDeve1000 を指定してください
これも e1000 にしないとネットワークアダプタがうまく認識されず IP アドレスが取得できません
IP アドレスが取得できないと WinRM で接続もできないためビルドが失敗します

あとは "iso_checksum_type": "none" ですが、こうすることで ISO のアップロードを毎回行わないようにします
VirtualBox 時は md5 で指定していたのですが、md5 にするとなぜか ESXi に ISO を毎回アップロードしてしまいます
本来であればチェックサムの値が同じであれば既にアップロードしてある ESXi のデータストアにキャッシュされた ISO を使うのですが、なぜか使ってくれません
なので、ワークアラウンド的に none を指定しています

それ以外の項目に関しては前回の VirtualBox で使用したテンプレートとほぼ同じです

実行する

  • packer build win2016.json

で実行しましょう
問題なくビルドが完了すると指定したデータストア上に「output-vmware-iso」というディレクトリが作成されその配下に vmx ファイルと vmdk ファイルが保存されます
packer_vmware_win2016.jpg

今回のテンプレートの設定だとビルドが完了するとサーバは削除されてしまいます

最後に

packer を使って ESXi 上で Windows2016 をビルドしてみました
ハマったのは使用するドライバをテンプレートで指定する箇所で vmxnet3 や VMware Paravirtual (PVSCSI) を使用するとうまくデバイスが認識されませんでした

少し上記で述べましたが vmxnet3 にしていると Windows がネットワークアダプタを認識できず WinRM を使って packer から接続できずにビルドエラーとなります

ちなみに PVSCSI にしていると OS インストール時に Windows could not apply the unattend answer file's <DiskConfiguration> setting というエラーが発生します
うまくハードディスクが認識されないためパーティションの作成と設定に失敗しています
packer_vmware_win2016_2.jpg

この問題も頑張れば解決することができるらしいので解決できたらその方法を紹介したいと思います

2017年10月13日金曜日

dep で SSH な git リポジトリを使う方法

概要

dep init するときにリポジトリにアクセスするのですが、デフォルトだと https でアクセスしに行きます
もし https でアクセスできないと以下のエラーとなり init に失敗します

emote repository at https://your-domain-git-repo.local/project/repository.git does not exist, or is inaccessible: : exit status 128

こんな場合に SSH + git で対象にリポジトリにアクセスする方法を紹介します

環境

  • Ubuntu 16.04.2
  • git 2.7.4
  • golang 1.8.4
  • dep devel

対応方法

以下のコマンドを実行します

  • git config --global url.git://your-domain-git-repo.local/.insteadOf https://your-domain-git-repo.local/

これで「your-domain-git-repo.local」には https ではなく SSH で接続するようになります
うまくいかない場合は

  • git config --global url.git@your-domain-git-repo.local/.insteadOf https://your-domain-git-repo.local/

も試してみてください

ちなみに global な .gitconfig を直接編集したい場合は ~/.gitconfig にあります

参考サイト

2017年10月12日木曜日

golang 純正のパッケージ管理ツール「dep」を使ってみた

概要

golang 純正のパッケージ管理ツールである dep を使ってみました
ruby で言うところの gem、python で言うところの pip という感じでしょうか

環境

  • macOS X 10.12.6
  • golang 1.9.1
  • dep devel

dep のインストール

  • brew install dep

で今回はインストールしました
homebrew が使えない場合は go get でもインストール可能です

  • go get -u github.com/golang/dep/cmd/dep

GOPATH 配下に作業ディレクトリを作成する

今回 GOPATH は /Users/hawksnowlog/go/ とします
この配下に作業ディレクトリを作成します

  • mkdir -p /Users/hawksnowlog/go/src/github.com/hawksnowlog/dep-test
  • cd /Users/hawksnowlog/go/src/github.com/hawksnowlog/dep-test

初期化

  • dep init

とすると dep 関連のファイルが自動で生成されます

  • Gopkg.lock
  • Gopkg.toml
  • vendor/

Gopkg.toml ファイルに依存パッケージを記載していきます

テスト用のコード

以下を使います
logrus というロギング用のライブラリを使っています

  • vim main.go
package main

import (
        log "github.com/Sirupsen/logrus"
)

func main() {
        log.WithFields(log.Fields{
                "key": "value",
        }).Info("logrus test")
}

依存パッケージを追加する

今回はとりあえず 1 つだけ追加してみます

  • dep ensure -add github.com/Sirupsen/logrus

Gopkg.toml に以下のように定義が追加されていると思います

  • cat Gopkg.toml
[[constraint]]
  name = "github.com/Sirupsen/logrus"
  version = "1.0.3"

パッケージをインストールする

  • dep ensure

と実行すると Gopkg.toml に記載されたパッケージが vendor/ 配下にインストールされます
確認は dep status で確認できます

  • dep status
PROJECT                     CONSTRAINT  VERSION        REVISION  LATEST   PKGS USED
github.com/Sirupsen/logrus  ^1.0.3      v1.0.3         f006c2a   f006c2a  1  
golang.org/x/crypto         *           branch master  9419663   9419663  1  
golang.org/x/sys            *           branch master  ebfc5b4   ebfc5b4  2  

簡単です

動作確認

  • go build
  • go install
  • dep-test

と実行すると以下のようにログが表示されると思います

INFO[0000] logrus test                                   key=value

追加してもコード内で使用されていないと vendor/ 配下には配置されない

試しに使っていないパッケージを追加してみます

  • dep ensure -add github.com/astaxie/beego

まず使っていないパッケージを追加しようとすると警告が出ます

"github.com/astaxie/beego" is not imported by your project, and has been temporarily added to Gopkg.lock and vendor/.
If you run "dep ensure" again before actually importing it, it will disappear from Gopkg.lock and vendor/.

ただ、Gopkg.toml には記載されています

[[constraint]]
  name = "github.com/astaxie/beego"
  version = "1.9.0"

この状態でインストールして確認してみましょう

  • dep ensure
  • dep status

しても先ほどと結果は変わらないです
このように dep はコード内で使用しているパッケージだけを vendor` 配下にインストールしてくれるようです

ちなみに dep で追加したけど使用されていないパッケージは

  • /Users/hawksnowlog/go/pkg/dep/sources/https---github.com-astaxie-beego/

配下にあるようです

最後に

go 純正のパッケージ管理ツール dep を使ってみました
昔は toml ファイルではなく Json ファイルで管理していたようです
dep を使用するには golang のバージョンも 1.8 以上が必要になります

dep は import で参照しているライブラリを管理しているリポジトリにアクセスできる必要があります
今回は Github 上で公開されているライブラリだったので特に問題ないですが例えばプライベートなリポジトリや IP などでアクセス制限をしているライブラリに関しては dep を実行するサーバからもアクセスできる必要があるのでご注いください

参考サイト

2017年10月11日水曜日

VMware VIC の 1.2.1 を使ってみた

概要

VMware VIC の 1.2.1 GA がリリースされていたので試してみました
そのうち正式版がリリースされると思うのでそれを待っても良かったのですが触ってみました

環境

  • vic-machine v.1.2.1-13858-c3db65f
  • docker 17.03.0-ce
  • Ubuntu 16.04
  • vCenter Server 5.5.0

vic-machine コマンドのインストール

  • wget https://storage.googleapis.com/vic-engine-releases/vic_1.2.1.tar.gz

各種コマンド検証

create

./vic-machine-linux create \
--target 192.168.100.101/dc \
--user "vc-user" \
--password "vc-pass" \
--compute-resource cluster \
--image-store datastore2 \
--bridge-network "dvs-for-vch" \
--public-network "VM Network" \
--no-tlsverify --force

特に追加になってそうなオプションはなさそう
--name を指定しないと「virtual-container-host」という名前で vApp が作成され、その配下に VM が作成されます

delete

./vic-machine-linux delete \
--target 192.168.100.101/dc \
--user "vc-user" \
--password "vc-pass" \
--compute-resource cluster \
--name virtual-container-host \
--thumbprint "37:1D:..."

こちらも特になし
--name オプションを指定しないと「virtual-container-host」を削除しにいきます

ls

./vic-machine-linux ls \
--target "192.168.100.101/dc" \
--user "vc-user" \
--password "vc-pass" \
--compute-resource "cluster" \
--thumbprint "37:1D:..."

こちらも特になし

inspect

./vic-machine-linux inspect -\
-target "192.168.100.101/dc" \
--user "vc-user" \
--password "vc-pass" \
--compute-resource "cluster" \
--thumbprint "37:1D:..."

--name の指定が必須ではなくなったようです
指定しない場合は「virtual-container-host」に対して実行します

また今回からか不明なのですが ./vic-machine-linux inspect config というコマンドが追加されており、これを使うと create 時のオプションを確認することができます

debug

./vic-machine-linux debug \
--target "192.168.100.101/dc" \
--user "vc-user" \
--password "vc-pass" \
--compute-resource "cluster" \
--enable-ssh --rootpw password \
--thumbprint "37:1D:..."

こちらも特になし
--name がない場合は「virtual-container-host」に対して実行します

update

./vic-machine-linux update firewall \
--target "192.168.100.101/dc" \
--user "vc-user" \
--password "vc-pass" \
--compute-resource "cluster" \
--deny \
--thumbprint "37:1D:..."

こちらも特になし
firewall しか操作できないのとこのコマンドは ESXi に対して実行するコマンドなのでいろいろと変更したほうが良いと思っているんですが変わらないですね、、、、
--deny--allow にすることもできます

upgrade

./vic-machine-linux upgrade \
--target "192.168.100.101/dc" \
--user "vc-user" \
--password "vc-pass" \
--compute-resource "cluster" \
--name virtual-container-host \
--appliance-iso ./appliance.iso \
--bootstrap-iso ./bootstrap.iso \
--thumbprint "37:1D:..."

今回は 1.1.1 -> 1.2.1 でやってみましたが問題なく行えました
ping を投げ続けてみましたが切断っぽい挙動が 2 回ほどありました

64 bytes from 192.168.200.100: icmp_seq=37 ttl=64 time=0.833 ms
64 bytes from 192.168.200.100: icmp_seq=38 ttl=64 time=17.0 ms
64 bytes from 192.168.200.100: icmp_seq=39 ttl=64 time=0.223 ms
64 bytes from 192.168.200.100: icmp_seq=40 ttl=64 time=0.301 ms
64 bytes from 192.168.200.100: icmp_seq=41 ttl=64 time=0.296 ms
64 bytes from 192.168.200.100: icmp_seq=42 ttl=64 time=0.318 ms
64 bytes from 192.168.200.100: icmp_seq=43 ttl=64 time=0.331 ms
64 bytes from 192.168.200.100: icmp_seq=44 ttl=64 time=8.29 ms
64 bytes from 192.168.200.100: icmp_seq=45 ttl=64 time=0.251 ms

今回は検証していませんが 0.8 や 0.9 から 1.2.1 に upgrade できるのかも気になりました

configure

おそらく新規で追加になったコマンドです
これで作成後の VCH に対していろいろと変更を入れられるようになりました

./vic-machine-linux configure \
--target "192.168.100.101/dc" \
--user "vc-user" \
--password "vc-pass" \
--compute-resource "cluster" \
--ops-user administrator@vsphere.local \
--ops-password adminpass \
--thumbprint "37:1D:..."

ops-user と ops-password を変更します
このユーザとパスワードを使って VCH から vCenter に対して API を発行してコンテナ VM を作成します

./vic-machine-linux configure \
--target "192.168.100.101/dc" \
--user "vc-user" \
--password "vc-pass" \
--compute-resource "cluster" \
--volume-store datastore2/directory:label \
--thumbprint "37:1D:..."

volume-store を追加します
ここで指定したvolume-store 上に docker volume で作成したデータが作成されます

./vic-machine-linux configure \
--target "192.168.100.101/dc" \
--user "vc-user" \
--password "vc-pass" \
--compute-resource "cluster" \
--dns-server 8.8.8.8 \
--thumbprint "37:1D:..."

VCH 上に DNS サーバを変更します
デフォルトだと public-network 上にある DHCP から教えてもらえる DNS が設定されています
それ以外にしたい場合は dns-server オプションで変更できます

他にも

  • 証明書の更新
  • レジストリの設定を変更
  • コンテナ専用ネットワークの変更
  • VCH から外に出るときのプロキシの設定
  • ログレベルの変更
  • メモリとCPU のアロケートサイズの変更

などができたりします
詳しくは configure --help してみると良いと思います

その他

  • version
  • help

docker コマンド検証

一番の特徴は exec が使えるようになっている点です

  • docker -H 192.168.200.100:2375 exec -it 7f4398f4c2d9 /bin/bash

それ以外のコマンドに関してはこれまで通り使えます
docker-compose も対応しています

  • docker-compose -H 192.168.200.100:2375 up -d

当然ですが、イメージは Docker hub で公開していなければありません (registry の設定を VCH にしていれば別)

ただ、swarm コマンドに現在もサポートされていません
おそらく今後のバージョンでも swarm はサポートされないと思います

最後に

VIC の 1.2.1 を試してみました
基本的なことしか試していないのですべてのエンハンス内容を網羅しているわけではありませんのでご注意を

それでも exec が使えるようになっていたり作成後の VCH に対して変更を加えられている点は嬉しいエンハンスかなと思います

個人的には vic-machine-linux コマンドの方法で VCH の操作ができるようになると嬉しいなと思いました (API)

参考サイト