2017年6月30日金曜日

Firebase で Database を使う方法

概要

前回 アノニマス認証の方法を紹介しました
今回は認証後に Firebase のリアルタイム Database にアクセスしてみたいと思います
データベースのルールを変更することで認証しているかどうかでアクセスできるかできないかも検証してみます

環境

  • macOS X 10.12.5
  • Xcode 8.3.3 (8E3004b)
  • Firebase/Core/Auth/Database 4.0.2

データベースへのアクセスルール

とりあえず以下のように設定します

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

このルールは「どんなアカウントでもいいので認証済み」であれば read も write もできるルールになります

データベースに保存する

  • ViewController.swift
import UIKit
import Firebase
import FirebaseAuth
import FirebaseDatabase

class ViewController: UIViewController {

    var ref: DatabaseReference!

    override func viewDidLoad() {
        super.viewDidLoad()
        Auth.auth().signInAnonymously(completion: { (user, error) in
            if let e = error {
                print(e)
                print("login error")
                return
            }
            print(user?.displayName ?? "no displayName")
            print(user?.isAnonymous ?? false)
            self.ref = Database.database().reference()
            // データの保存
            self.ref.child("users").setValue(["username": user?.uid])
        })
    }
}

データベースを参照するための変数「ref」を定義しておきログイン後に接続します (Database.database().reference())
そして ref.child.setValue を使ってデータを保存します
認証に成功するとデータベースに write する権限があるため以下のようにデータが投入されます
firebase_anonymous3.png

権限を変更して挙動を確認する

次に write の権限を変更してみます

{
  "rules": {
    ".read": "auth != null",
    ".write": false
  }
}

false に変更しました
こうすることでデータベースに対してどんなユーザでもアクセスできなくなります
実際にアプリをビルドし直して試してみるとコンソールに

<Warning> [Firebase/Database][I-RDB038012] setValue: or removeValue: at /users failed: permission_denied

というデバッグ文が出力されているのがわかると思います

データを取得する

ついでに取得もやってみましょう
* ViewController.swift

// データの取得
self.ref.child("users").observeSingleEvent(of: .value, with: { (snapshot) in
    print(snapshot.value ?? "no value")
})

を保存の下に追記すれば OK です
今回は observeSingleEvent を使いました
リアルタイム Database にはイベントドリブンでデータを取得する方法もありデータが追加されたらイベントが発行され、その都度何か処理する方法もあります

その辺りの方法は参考サイトの URL を参考に試してみてください
やり方はほとんど同じようにできると思います

最後に

Firebase のリアルタイム Database を使ってみました
また今回は使いませんでしたが、イベントドリブンな取得もできるのでチャットアプリなどの開発が捗る機能かなと思います

参考サイト

2017年6月29日木曜日

Firebase で Anonymous 認証を使う方法

概要

Firebase のアノニマス認証を使ってみました
簡単な利用方法を紹介します

環境

  • macOS X 10.12.5
  • Xcode 8.3.3 (8E3004b)
  • Firebase/Core/Auth 4.0.2

Firebase 側の設定

デフォルトではアノニマス認証はオフになっていて使えません
Firebase のコンソールにログインして有効にする必要があります

Authentication -> ログイン方法 (タブ) -> 匿名

で有効にします
firebase_anonymous1.png

また、アプリの GoogleService-Info.plist をダウンロードしてプロジェクトにコピーしてください
firebase_anonymous2.png

ライブラリインストール

  • pod init
  • vim Podfile
target 'firebase-test' do
  use_frameworks!

  # Pods for firebase-test
  pod 'Firebase/Core'
  pod 'Firebase/Auth'
end
  • pod install

初期化

  • AppDelegate.swift
import UIKit
import CoreData
import Firebase

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        FirebaseApp.configure()
        return true
    }
    // 途中省略...
}

import して FirebaseApp.configure() を実行します

ログイン認証

本題です

  • ViewController.swift
import UIKit
import Firebase
import FirebaseAuth

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        Auth.auth().signInAnonymously(completion: { (user, error) in
            if let e = error {
                print(e)
                print("login error")
                return
            }
            print(user?.displayName ?? "no displayName")
            print(user?.isAnonymous ?? false)
        })
    }
}

signInAnonymously を呼ぶ感じです
completion のコールバックメソッドにログイン後のユーザ情報 or エラー情報が返ってきます

最後に

Firebase のアノニマス認証の方法を紹介しました
認証のあとにデータベースなどにアクセスするロジックを実装する感じです

Swift の Firebase のライブラリは現在バージョン 4 ですが、3 から上がった時に非互換な部分が多かったようでググって出たページが古いとクラスがなかったり関数がなかったりと動作しないケースが多かったです
公式のドキュメントですら古いケースがあったので Xcode の補完を使ったりコードを直接見ると良いかもしれません

参考サイト

2017年6月28日水曜日

StackEdit で Google ドライブに保存使用したら「Unable to authenticate user xxxxxxxxxxx, please sign in with Google.」が出た

概要

作成した Markdown を Google ドライブに保存しようとしたらタイトルのエラーが発生しました
対応方法を紹介します

環境

  • Windows7
  • Firefox 53.0.3

対応方法

この作業は StackEdit に関するローカルストレージの情報をすべて削除してるので Markdown の記事の情報も削除されます
なので、事前に Markdown の情報はどこかに退避しておいてください

  1. https://stackedit.io/recovery.html にアクセスします
  2. 「StackEdit local storage.json」をダウンロードします
  3. Menu -> Settings -> Utils -> Import docs and settings で先程ダウンロードした json を開きます
  4. 再度 https://stackedit.io/recovery.html の画面に戻り「To remove one document from the local storage click here.」の click here をクリックします
  5. 同様に「To fully clear the local storage click here.」の click here をクリックします
  6. 「To reopen StackEdit click here.」のクリックして再度 StackEdit を開きます

これで再度 Google ドライブに保存してみてください
成功するはずです

最後に

原因おそらくは複数のユーザを使い分けようとしてるのが原因かなと思います
例えば昔 Firefox で StackEdit を使っていて、そのとき利用してた Google アカウントではないアカウントで今回利用とすると該当のエラーが発生すると思います
認証情報が過去のユーザのものになっているのでエラーが発生していた感じだと思います

2017年6月27日火曜日

VMware Harbor の REST API を有効にしてプロジェクトやユーザに API でアクセスしてみる

概要

VMware Harbor の管理画面で実行されているライブラリやユーザ作成はバックエンドでは API 経由で実行されています
実はこの API は利用者側からも使用することができます
デフォルトではオフになっているので有効にすることで使用できるようになります

環境

  • Ubuntu 16.04
  • Harbor 1.1.1
  • docker-compose 1.13.0
  • docker 17.03

事前作業

Harbor のインストールはオフラインインストールを使って Ubuntu 上にインストールしています
手順はこちらの記事を参考にしてください

REST API の有効化

Harbor が動作しているサーバにログインしてサーバ上で作業する必要があります
harbor は /root/harbor にインストールしているものとします

  • cd /root/harbor
  • wget https://raw.githubusercontent.com/vmware/harbor/master/docs/prepare-swagger.sh https://raw.githubusercontent.com/vmware/harbor/master/docs/swagger.yaml
  • vi prepare-swagger.sh
SCHEME=https
SERVER_IP=reg.your.domain

ここのスキーマとサーバの設定の部分は http で IP ベースで動作している場合は、それを入力してください
ドメインを貼って証明書まで設定してる場合は、https で Harbor に設定したドメインを入力してください

  • chmod +x prepare-swagger.sh
  • ./prepare-swagger.sh

シェルを実行するとカレントに swagger.tar.gz が作成されているはずです

  • cp docker-compose.yml{,.back}
  • vi docker-compose.yml
- ../src/ui/static/vendors/swagger-ui-2.1.4/dist:/harbor/static/vendors/swagger
- ../src/ui/static/resources/yaml/swagger.yaml:/harbor/static/resources/yaml/swagger.yaml

上記 2 行を「ui:」の「volumes:」の部分に追記します
diff だと以下の通りになります

  • diff docker-compose.yml.back docker-compose.yml
79a80,81
>       - ../src/ui/static/vendors/swagger-ui-2.1.4/dist:/harbor/static/vendors/swagger
>       - ../src/ui/static/resources/yaml/swagger.yaml:/harbor/static/resources/yaml/swagger.yaml

あとはコンテナを再起動すれば OK です

  • docker-compose down
  • docker-compose up -d

動作確認

まず https://reg.your.domain/static/vendors/swagger/index.html にアクセスして Swagger UI が表示されることを確認してください
ここで UI が表示されないと API が有効になっていません

表示されたら実際に API をコールしてみましょう
例えばプロジェクトやリポジトリを検索する API /search は以下のようにコールできます

  • curl -X GET --header 'Accept: application/json' 'https://reg.your.domain/api/search?q=library'

結果以下のような json が返却されれば OK です

{
  "project": [
    {
      "project_id": 1,
      "owner_id": 1,
      "name": "library",
      "creation_time": "2017-06-26T08:03:45Z",
      "creation_time_str": "",
      "deleted": 0,
      "owner_name": "",
      "public": 1,
      "Togglable": false,
      "update_time": "2017-06-26T08:03:45Z",
      "current_user_role_id": 0,
      "repo_count": 0
    }
  ],
  "repository": []
}

SDK や詳しいドキュメントはほぼないので、Swagger UI を見ながら API をコールするしかないと思います
swagger.yaml が公開されているので go-swagger を使って swagger generate client すればクライアントツールは簡単に作れるかもしれません

最後に

Harbor の REST API を有効にしてプロジェクトやレジストリの情報にアクセスできるようにしてみました
認証等は特になかったので公開するのは少し危険ですが、プライベートで独自の UI を作ったりするには便利かなと思います

参考サイト

2017年6月23日金曜日

Arduino でバイト配列を文字列に変換する方法

概要

タイトルの通り
MQTT の PubSubClient.h を使う時にペイロードが byte* だったので文字列に変更したかった感じです

環境

  • macOS 10.12.5
  • Arduino IDE 1.6.12

コード

void callback(char* topic, byte* payload, unsigned int length) {
  payload[length] = '\0';
  String msg = String((char*) payload);
  Serial.println(msg);
  if (msg == "start") {
    // something ...
  } else if (msg == "stop") {
    // something ...
  }
}

ポイントはバイト配列の最後に終端文字を入れてること
こうしないとうまく文字列に変換できません

最後に

*byte を String に変換する方法を紹介しました
久しぶりに Arduino に触るとよくわかんないですね

参考サイト

2017年6月22日木曜日

firebase-ruby の get, push, set, update, delete の挙動を確認してみた

概要

前回 は firebase-ruby を使った GettingStarted 的な記事を紹介しました
今回はリアルタイムデータベースを操作できるその他のメソッドについて挙動を確認してみました

環境

  • CentOS 7.3.1611
  • ruby 2.3.3p222
  • firebase-ruby 0.2.6

使用するコード

  • bundle init
  • vim Gemfile
gem "firebase"
  • bundle install
  • vim test.rb
require 'firebase'

class FB
  BASE_URI = 'https://project-id.firebaseio.com/'
  SECRET_KEY = 'your-database-secret-key'

  def initialize
    @client = Firebase::Client.new(BASE_URI, SECRET_KEY)
  end

  def get(path, query)
    @client.get(path, query)
  end

  def push(path, hash)
    @client.push(path, hash)
  end

  def set(path, hash)
    @client.set(path, hash)
  end

  def update(path, hash)
    @client.update(path, hash)
  end

  def delete(path, query)
    @client.delete(path, query)
  end
end

f = FB.new

res = f.push('test', { :name => "hoge" })
id = res.body['name']
res = f.get("test/#{id}", {})
p res.body['name'] # => "hoge"

f.set('test1', { :name => "fuga" })
res = f.get('test1', {})
p res.body['name'] # => "fuga"

f.update("test/#{id}", { :name => "hogehoge" })
res = f.get("test/#{id}", {})
p res.body['name'] # => "hogehoge"
f.update('test1', { :name => "fugafuga" })
res = f.get('test1', {})
p res.body['name'] # => "fuga"

f.delete("test/#{id}", {})
f.delete('test1', {})
  • bundle exec ruby test.rb

BASE_URI と SECRET_KEY は自分のプロジェクトの値を設定してください

挙動説明

firebase-ruby にはデータを登録するためのメソッドが 2 つ用意されています
push と set があります

push の場合、基本的に毎回新規オブジェクトの追加となります
追加したオブジェクトに自動で ID を振ってくれるため、その ID でオブジェクトを識別します
ID は push したレスポンス情報に含まれています

方や set は指定したパスにそのままデータを登録します
なので 2 回 set しても内容は変わりません、オブジェクトも増えません
get でアクセスする場合のパスにも ID が含まれることはありません

get は path を指定してデータを取得します
path 配下にあるオブジェクトに対して絞り込みを行いたい場合はクエリを使います
https://firebase.google.com/docs/reference/android/com/google/firebase/database/Query
例えば上位 10 件のみを取得したい場合は limit を使います

あとは update と delete ですが、これも path で取得したオブジェクト配下の情報を削除したり更新したりします
update に関しては上書き更新なのでデータが新規で登録されるということはありません
delete に関しては物理削除なので、一度削除したオブジェクトは再度戻すことはできません

最後に

firebase-ruby が用意している関数の挙動を確認してみました
個人的には push ではなく set を使ってデータ登録したほうが毎回 ID を走査しなくて済むので簡単かなと思います

あとは get, update, delete を使って登録したデータを path を指定して操作する感じかなと思います

Firebase のデータベース機能はリアルタイムデータベースなので push や set で登録したときのアプリ側の挙動も確認したいと思っています
また Firebase には他にもストレージやホスティング機能、スクリプト機能があるのですが、firebase-ruby ではデータベース機能に対する制御しかできません
ストレージなどを使いたい場合は直接 Google Storege を操作する感じになるみたいです

参考サイト

2017年6月21日水曜日

作成した gem ファイルを rubygems.org で公開する手順

概要

前回 独自の gem ファイルを作成する方法を紹介しました
今回は作成した gem ファイルを rubygems.org で公開する手順を紹介します

環境

  • CentOS 7.3.1611
  • ruby 2.3.3p222
  • gem 2.6.11
  • rake 10.4.2

サインアップ

https://rubygems.org/sign_up からアカウントを作成しましょう
メールアドレスがあれば簡単に作成できます

登録したメールアドレスに確認用の URL が送信されるのでクリックしてアカウントを承認します
問題なくログインできるか確認してください

gem の作成

とりあえず今回は何でも OK です
本記事では 前回 の記事を参考に gem を作成しています

とりあえずリリースしてみる

まず Github にソースをアップロードします
Github じゃなくても良いですが remote のリポジトリがないと怒られます

でコードを push しましょう
そして

  • bundle exec gem push

で rubygems.org に認証します
登録したメールアドレスとパスワードで認証しましょう
cat ~/.gem/credentials ができれば OK です
そして

  • bundle exec rake release

で gem ファイルアップロードできます

fstlib 0.1.0 built to pkg/fstlib-0.1.0.gem.
Tag v0.1.0 has already been created.
Pushed fstlib 0.1.0 to rubygems.org.

こんな感じになれば成功です
rubygems の自分のアカウントのページにアクセスするとアップロードした gem があると思います

バージョンアップしてみる

適当にコードを改修してバージョンアップしてみましょう

  • vim lib/fstlib.rb
require "fstlib/version"

module Fstlib
  def self.hello
    "My second rubygems"
  end
end
  • vim lib/fstlib/version.rb
module Fstlib
  VERSION = "0.1.1"
end

でコードを改修したら

  • git add .
  • git commit -m “Second commit”
  • git push -u origin master

でコミットして

  • bundle exec rake release

で再度アップロードします
アップロード完了後再度 gem のページを見てみるとバージョンが 0.1.1 に上がっていることが確認できると思います

使ってみる

新しいスクリプトを作成してアップロードした gem が使えるか確認してみましょう

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

で rubygems.org からアップロードした gem がインストールできます

  • vim test.rb
require 'fstlib'

p Fstlib.hello
  • bundle exec ruby test.rb

実行すると "My second rubygems" が表示されると思います

最後に

作成した gem を rubygems.org で公開する手順を紹介しました
今回紹介した rake release の流れは「ビルド -> コミットがあるかチェック -> リモートに push -> rubygems.org にプッシュ」という流れをやってくれています
https://github.com/bundler/bundler/blob/master/lib/bundler/gem_helper.rb#L55

なので bundle exec gem push を実施しないでいきなり release しても初回であれば rubygems.org に対する認証を聞かれると思います
うまくログインができない場合は個別で gem push を実行すると良いと思います

また削除する場合はコマンドで行います

  • gem install gemcutter
  • gem yank fstlib -v 0.1.1
  • gem yank fstlib -v 0.1.0

という感じで全バージョン削除すると rubygems.org から消えます

参考サイト

2017年6月20日火曜日

RSpec で undefined method 'get' for が出たときの対処法

概要

エラーの詳細は以下の通り

undefined method `get' for #<RSpec::ExampleGroups::MyApp:0x00000002fc48e0>
Did you mean?  gets
               gem

Sinatra など get リクエストを送信するテストが実行できない状況です

環境

  • CentOS 7.3.1611
  • ruby 2.3.3p222
  • rspec 3.6.0

対処方法

  • rspec --init

で .rspec ファイルを作成します
中身は

--require spec_helper

となっているので .rspec ファイルを作成しないで spec_helper.rb で require しても大丈夫だと思います

2017年6月19日月曜日

Sinatra で設定ファイルを使う方法

概要

sinatra-contrib というパッケージを使うと簡単に設定ファイルを読み込むことができます
実際に設定ファイルを使ったサンプルの sinatra アプリケーションを紹介します

環境

  • CentOS 7.3.1611
  • ruby 2.3.3p222
  • sinatra 2.0.0
  • sinatra-contrib 2.0.0

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

  • bundle init
  • vim Gemfile
gem "sinatra"
gem "sinatra-contrib"
  • bundle install

また今回のディレクトリ構成は以下の通りです

.
├── app.rb
├── config.ru
├── config.yml
├── Gemfile
├── Gemfile.lock
└── views
    └── hello.erb

アプリの作成

  • vim app.rb
require "sinatra/base"
require "sinatra/config_file"

class MyApp < Sinatra::Base
  register Sinatra::ConfigFile
  config_file './config.yml'

  get '/' do
    @message = settings.message
    @friends = settings.friends
    erb :hello
  end
end

Sinatra::Base を継承して独自のコントローラクラスを定義します
そして、設定ファイルを読み込むために Sinatra::ConfigFile を登録します
読み込みに成功した後は settings というオブジェクトを参照することで値を取得することができます
今回は取得した値をテンプレートに渡してテンプレート側で値を確認します

設定ファイルの作成

  • vim config.yml
message: "My friends"
friends:
  - name: 'bob'
    age: 20
    favorites:
      - 'baseball'
      - 'soccer'
  - name: 'tom'
    age: 19
    favorites:
      - 'soccer'

app.rb と同じディレクトリに配置します
YAML 形式で記述します
今回は配列も使っています

テンプレートファイルの作成

  • mkdir views
  • vim views/hello.erb
<h2>"<%= @message %>"</h2>
<% @friends.each do |f| %>
<div>
  <div>name: <%= f['name'] %></div>
  <div>age: <%= f['age'] %></div>
  <div>favorites:</div>
  <% f['favorites'].each do |fa| %>
    <div>- <%= fa %></div>
  <% end %>
</div>
<% end %>

app.rb から呼び出されるテンプレートファイルです
設定ファイルの内容を出力しています
ハッシュとして渡ってくるので ruby のハッシュを参照する方法で値を取り出すことができます

アプリ起動用の rackup ファイル作成

  • vim config.ru
require 'bundler'
Bundler.require

require './app'
run MyApp

rackup コマンドでアプリを起動するためのスクリプトです

動作確認

  • bundle exec rackup
  • curl localhost:9292

で config.yml に書かれた情報が HTML で出力されると思います

最後に

sinatra で YAML で書かれた設定ファイルを読む込む方法を紹介しました
値の参照は settings オブジェクトを使って参照しますがスコープは sinatra アプリケーション内のみとなります
なので、自分で定義したライブラリの関数などに値を渡したい場合は引数などで渡す必要があります (ライブラリ側で settings を参照することはできません)

参考サイト

2017年6月17日土曜日

独自のコントローラクラスを作成している sinatra を rspec でテストする

概要

Sinatra::Base を継承して独自のコントローラクラスを作成している場合に rspec を使ってテストする方法を紹介します
少し工夫が必要でした

環境

  • CentOS 7.3.1611
  • ruby 2.3.3p222
  • rspec 3.6.0
  • sinatra 1.4.8

インストール

  • bundle init
  • vim Gemfile
gem "sinatra"
gem "rspec"
gem "rack-test"
  • bundle install

独自のコントローラクラスの作成

  • vim app.rb
require 'sinatra/base'

class MyApp < Sinatra::Base
  get '/' do
    'myapp'
  end
end

このクラス内でアプリを起動 (run) することはしません

rackup で起動させる

  • vim config.ru
require 'bundler'
Bundler.require

require './app'
run MyApp

これで rackup コマンドで MyApp アプリケーションが起動します
こうすることで独自のコントローラクラスを切り離しテストし易いようにします
もし MyApp 内で run! をしてしまうと rspec でテストを実行したときにアプリケーションが起動してしまいテストが進まなくなってしまいます

今回は直接アプリを起動しませんがもし起動させたい場合は

  • bundle exec rackup

で 127.0.0.1:9292 で起動します

テストの作成

  • rspec --init
  • vim spec/app_spec.rb
# -*- coding: utf-8 -*-
ENV['RACK_ENV'] = 'test'

require './app.rb'
require 'rspec'
require 'rack/test'

describe "MyApp のテスト" do
  include Rack::Test::Methods

  def app
    MyApp
  end

  it "root にアクセスしたときに myapp を返却する" do
    get '/'
    expect(last_response.status).to eq 200
    expect(last_response.body).to eq 'myapp'
  end
end
  • bundle exec rspec -f d spec/
MyApp のテスト
  root にアクセスしたときに myapp を返却する

Finished in 0.02609 seconds (files took 0.18599 seconds to load)
1 example, 0 failures

こんな感じになれば成功です

おまけ spec_helper.rb を使って記述を楽にする

今のままだと describe するたびに include して def しなければなりません
そんなときに spec_helper.rb を使って設定しておくと省略することができます

  • vim spec/spec_helper.rb
ENV['RACK_ENV'] = 'test'

require File.expand_path '../../app.rb', __FILE__
require 'rack/test'
require 'rspec'

module RSpecMixin
  include Rack::Test::Methods

  def app
    MyApp
  end
end

RSpec.configure do |config|
  config.include RSpecMixin
  config.expect_with :rspec do |expectations|
    expectations.include_chain_clauses_in_custom_matcher_descriptions = true
  end

  config.mock_with :rspec do |mocks|
    mocks.verify_partial_doubles = true
  end

  config.shared_context_metadata_behavior = :apply_to_host_groups
end

デフォルトのコメントはすべて削除しました
要するにテスト内でやっていた処理を module 化して、それを Rspec.configure にあらかじめ設定してあげる感じです

テスト側は以下のように変更します

  • vim spec/app_spec.rb
# -*- coding: utf-8 -*-
require File.expand_path '../spec_helper.rb', __FILE__

describe "MyApp のテスト" do
  it "root にアクセスしたときに myapp を返却する" do
    get '/'
    expect(last_response.status).to eq 200
    expect(last_response.body).to eq 'myapp'
  end
end

かなりすっきりしました
冒頭で spec_helper.rb を require するだけになります

あとは同じようにテストを実行すれば OK です

  • bundle exec rspec -f d spec/

最後に

Sinatra で独自のコントローラクラスを定義している場合に rspec でテストする方法を紹介しました
ポイントはアプリケーションの起動を rackup を使うということでした

参考サイト

2017年6月16日金曜日

rspec のインストールとサンプルテストの実行

概要

rspec に入門しました
Getting Started の手順を紹介します

環境

  • CentOS 7.3.1611
  • ruby 2.3.3p222
  • rspec 3.6.0

インストール

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

テスト対象のクラス作成

  • vim car.rb
class Car
  def initialize(gas, kilo)
    @gas = gas
    @kilo = kilo
  end

  def available
    @gas / @kilo
  end
end

テスト作成

  • vim car_spec.rb
require './car.rb'

RSpec.describe '車のスペック' do
  it 'ガソリン 100 リットルなら 10 km 走れます' do
    c = Car.new(100, 10)
    expect(c.available).to eq 10
  end
end

テスト実行

  • rspec -f d car_spec.rb

もしくは

  • bundle exec rspec -f d car_spec.rb
車のスペック
  ガソリン 100 リットルなら 10 km 走れます

Finished in 0.00202 seconds (files took 0.08629 seconds to load)
1 example, 0 failures

おまけ spec_helper を使う

  • bundle exec rspec --init
  • mv car_spec.rb spec/
  • rspec -f d spec/
  • vim spec/spec_helper.rb

=begin から =end の部分を削除します

  • bundle exec rspec -f d spec/

そして再度テストを実行してみると spec_helper.rb の設定が反映され結果の表示が変わると思います

最後に

ゼロから rspec をとりあえず実行できるところまでやってみました
mock 系や他の Tips もあれば別記事で紹介したいと思います

参考サイト

2017年6月15日木曜日

Swift3 で画像を良い感じにキャッシュしてくれる Kingfisher を使ってみた

概要

UITableView の UITableViewCell 内に ImageView がありテーブルをスクロールするたびに画像をダウンロードするアプリがありました
Android と同様でセルが表示されるたびに画像をダウンロードしてしまう仕様でかなりいけていませんでした
そんなとき Kingfisher という素晴らしいライブラリに出会いこれがやりたいことを完璧にやってくれたので使い方を紹介します

環境

  • macOS 10.12.5
  • Xcode 8.3.3 (8E3004b)
  • Kingfisher 3.3.3

インストール

Podfile に以下を追記して install するだけです

pod 'Kingfisher'

使いたい .swift ファイルの先頭で import しましょう

import Kingfisher

画像を読み込む

今まで以下のようなコードで UITableViewCell 内の ImageView に画像をダウンロードして設定していました

let imageURL: NSURL? = NSURL(string: img["src"]!)
let req = NSURLRequest(url: imageURL! as URL)
NSURLConnection.sendAsynchronousRequest(req as URLRequest, queue: OperationQueue.main) { (res, data, err) in
    let image = UIImage(data: data!)
    imageView.image = image
}

これが cellForRowAt メソッドで実装されているためセルが表示されるたびに sendAsynchronousRequest が使われるため無駄に通信を行っていました

これを Kingfisher を使って以下のように書き換えます

imageView.kf.indicatorType = .activity
imageView.kf.setImage(with: URL(string: img["src"]!),
                      // キャッシュがある場合はキャッシュを使用する
                      completionHandler: {
                        (image, error, cacheType, imageURL) in
                        imageView.image = image
})

まだ画像がダウンロードできていない場合には URL から画像をダウンロードします
そして、ダウンロード中はインジケータを表示します
画像がダウンロードできているセルにスクロールした場合には completionHandler 側がコールされすでにキャッシュされている image を imageView に設定するだけでキャッシュが利用できます

動作確認

アプリをビルドして確認してみましょう
改修前はスクロールするたびに画像をダウンロードするため、回線状況の悪い場合だとセルに正しい画像が表示されなかたり、画像の表示が追いつかなかったりします

Kingfisher を使った改修後はセルをスクロールしまくってもダウンロード中にインジケータが表示されるし、ダウンロードが完了した画像はキャッシュから表示してくれるため、画像の表示が遅れたりすることが一切ありません

比較のスクリーンショットがないのが残念ですがかなり快適な感じになると思います

またアプリを kill して再度立ち上げてもキャッシュが残っている状態だったのですでにダウンロード済みの画像に関してはすぐに表示されました

最後に

Swift で Kingfisher を使ってみました
かなり良い感じのライブラリでビックリしました
Swift には他にも同じようなライブラリがあるようで Moa や Vincent, MapleBacon などあるようです
今回 Kingfisher を使ったのは単純に一番人気そうだったので使いましたが他のやつも代用可能だと思います

画像ダウンロード系はもう全部 Kingfisher を使っていこうと思います

参考サイト

2017年6月14日水曜日

ruby の標準 logger でクラスの変数情報を出力する方法

概要

答え -> inspect を使います

環境

  • CentOS 7.3.1611
  • ruby 2.3.3p222

サンプルコード

require 'logger'

class Sub
  def initialize(hoge)
    @hoge = hoge
  end
end


logger = Logger.new(STDOUT)
s = Sub.new 'hoge'
logger.info "#{s.inspect}"

出力

I, [2017-06-14T15:56:06.326706 #26690]  INFO -- : #<Sub:0x00000002454b90 @hoge="hoge">

2017年6月13日火曜日

個人的な動く LINE スタンプの作り方

概要

個人的に動く LINE スタンプを作るときの方法を紹介します
一応全部無料でできます
各ツールの詳細な使い方は説明しないので、公式サイト等を見て勉強してください
今回は Windows 環境のスクリーンショットで説明しています

環境

  • macOS 10.12.5 or Windows 10
  • FireAlpaca 1.7.4
  • アニメ画像に変換する君 1.2.0
  • LINE シミュレータ

FireAlpaca で絵を書く

全てはここから、絵を書かないことにはスタンプはできません
FireAlpaca を開いたら

ファイル -> 新規作成 -> 幅320pixel, 高さ270pixel -> OK

でレイヤを 1 つ作成します
このレイヤに適当に絵を書きます
ペンを選択してマウスの左クリックを押しながら移動すれば線が書けます

そして 1 つのレイヤを書き終えたら更にレイヤを追加します

レイヤ -> 追加

追加したレイヤに次のアニメーションの絵を書きます
こんな感じで画像をたくさん作りコマ送りすることでアニメーションを実現しています

アニメーションスタンプのガイドライン を見ると「イラストは最低5 - 最大20フレーム」とあるのでレイヤも最低限 5 つ以上、最高で 20 個以下にしましょう

とりあえず 5 レイヤ分で作成しました
line_animetaion_stamp1.png

各レイヤに丸を横に並べていって最後に四角が登場する謎のアニメーションです

FireAlpaca で png を連番で書き出す

絵が書けたらこのレイヤたちを連番 png で書き出します

表示 -> オニオンスキンモード

にします
オニオンスキンモードはレイヤの前後が薄緑と薄ピンクで表示される機能です
この機能を使うと前後のアニメーションでどういう動きをするか確認できるのでアニメーションが作成しやすいです

調整できたら、連番で書き出します

ファイル -> レイヤーを連番出力 (オニオンスキンモード)

で連番ファイルを保存するディレクトリを選択するダイアログが表示されるので選択して保存します
その後動画を見るかどうか聞かれますが No で OK です

これで連番ファイルが png で出力されました
ちなみに出力される連番は 000.png から出力されます
レイヤ的には番号の大きい方から 000 になるので注意してください
(図で言うと レイヤ5 の四角が 000.png になり、レイヤ 1 の左の丸が 0005.png になります)

アニメ画像に変換する君で apng を作成する

作成された連番の png を apng という動く LINE スタンプのファイル形式にします
ダウンロードしたアニメ画像に変換する君を開きます

そして「ファイルを選択」もしくはドラッグアンドドロップで作成された連番ファイルを開きます
左メニューの「フレームレート」と「ループ回数」を調整します

今回だとフレームレートを「5」ループ回数を「2」にするといい感じになります
いい感じというのは再生時間の調整の部分でこれもガイドラインに書いているのですが再生時間は「1, 2, 3, 4」秒のどれかでないとダメみたいです
再生する機能もあるので再生しながらどんな感じのアニメーションになるか確認すると良いと思います
line_animetaion_stamp2.png

フレームレートとループ回数を調整したら「アニメ画像を保存する」を選択して apng ファイルを作成します
名前と保存先を決定して出力しましょう

できあがった apng ファイルは拡張子は .png で普通に開いてもただの静止画が表示されるだけだと思います

LINE シミュレータで動作確認する

ただの静止画をシミュレータ上で表示してみましょう

Firefox で https://creator.line.me/ja/simulator/ からマイページにログインし左メニューの「作ったスタンプをチェックしよう!」を選択します
「スタンプシミュレータを使ってみる」を選択すると別ウィンドウでシミュレータが起動します
ここに先程作成した apng ファイルをドラッグアンドドロップで配置してみましょう
すると動く LINE スタンプのようにアニメーションすると思います
line_animetaion_stamp3.png

写真はアニメーション後の状況です

最後に

個人的に動く LINE スタンプを作成する方法を紹介しました
メリットはすべて無料で使えるという点でしょうか
フォトショップなどが使えるのであればそれだけで apng まで作成できると思います
また、絵自体もクオリティの高いものができあがると思います

また、絵を書くのにマウスだと正直辛いので本格的に書きたいのであればペンタブを購入するのをおすすめします
自分はまだそこまで本格的にやっていないのでペンタブを購入していませんが、もし購入してスタンプを作成することになったそのときにレビューや FireAlpaca との連携方法なども紹介したいと思います

参考サイト

2017年6月12日月曜日

Firebase で iOS にプッシュ通知を送信してみた

概要

プッシュ通知を Firebase に移行してみたので設定や流れをメモしておきます
ある程度端折っいる流れもあるのでその点はあしからず
基本は公式ドキュメントを見ることをおすすめします

環境

  • macOS 10.12.5
  • Xcode 8.3.2 (8E2002)
  • Firebase (2017/6/9 時点)
  • プッシュ対象端末 iPhone5 iOS 10.3.2

流れ

AppID にプッシュ通知用の証明書を登録する

作成するときは AppID の一覧から作成することをおすすめします
iOS Certificates にログインして

Identifiers -> App IDs -> 対象の AppID を選択 -> Edit

で編集画面に移動し「Push Notifications」の欄をチェックオンにします
そしてそこから「Create Certificate」でプッシュ通知用の証明書を作成します
作成には CSR が必要になるので事前に作成します
Development, Production 用の証明書の 2 つが作成できれば OK です
firebase_push1.png

作成したプッシュ通知用の証明書から p12 ファイルを書き出す

先ほどの画面から証明書を「Download」できるので Mac 上にダウンロードします
そして、ダブルクリックしてキーチェーンアクセスで開いて p12 ファイルを書き出しましょう
firebase_push3.png

パスワードはなしで書き出しました
後から気づいたのですが、Firebase は p12 ファイルのパスワードも設定できるのでパスワードはありでも大丈夫だと思います

p12 ファイルを Firebase に登録する

Firebase のコンソールにログインしましょう

プロジェクトの設定 -> クラウドメッセージング -> 対象のアプリを選択 -> APNs 証明書

を選択します
同じような項目で「APNs 認証キー」という項目がありますが、そっちじゃないので注意してください
firebase_push4.png

「開発用 APNs 証明書」「本番環境の APNs 証明書」という 2 つの欄があるので書き出した p12 ファイルをそれぞれアップロードしてください
ちゃんと Development 用の証明書から書き出した p12 ファイルは「開発用の APNs 証明書」Production 用の証明書から書き出し p12 ファイルは「本番環境の APNs 証明書」に設定しましょう
当然ですが、間違えるとプッシュ通知が送信されません

アプリ側修正

これで Firebase 側の設定は完了です
アプリ側の修正をしていきましょう

ライブラリのインストールは Cocoapods を使います

pod 'Firebase/Messaging'

プロジェクトのプッシュ通知を有効にします
プロジェクトを選択した状態で Capabilities を選択し「Push Notifications」を ON にします
firebase_push2.png

あとは AppDelete.swift を編集します
今回は Swift3 の文法を使っています
Swift2 だと若干コールするメソッドが違うので気をつけてください
全部紹介すると長いのでポイントを紹介します

import UserNotifications

を import しましょう

// Firebase の初期化
FIRApp.configure()
// プッシュ通知の許可を表示する
if #available(iOS 10.0, *) {
    let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
    UNUserNotificationCenter.current().requestAuthorization(
        options: authOptions,
        completionHandler: {_, _ in })
    // For iOS 10 display notification (sent via APNS)
    UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
    // For iOS 10 data message (sent via FCM)
    FIRMessaging.messaging().remoteMessageDelegate = self as? FIRMessagingDelegate
} else {
    let settings: UIUserNotificationSettings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
    application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()

didFinishLaunchingWithOptions に追加します
これでアプリの初回起動時にプッシュ通知を許可するかどうかのダイアログが出るようになり、許可を押した人にプッシュ通知できるようになります

// メッセージ受信時の処理
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
    print(userInfo)
}

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                 fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    print(userInfo)
    completionHandler(UIBackgroundFetchResult.newData)
}

メッセージを受信したときの処理です
メッセージをカスタマイズしていて何か特別な処理をしたいときはここにロジックを記載します

動作確認

これで OK なはずです
あとは Firebase の Notifications からメッセージを作成して送信すればメッセージが届くはずです

当然ですが実機でないと届きません
なので、Mac に iPhone を接続して Firebase 対応したアプリをビルドしてインストールしてください
アプリ起動時にプッシュ通知の許可拒否のダイアログが表示されるのでそこでも必ず許可を選択してください

最後に

Firebase を使って iOS にプッシュ通知を送信してみました
自分が一番ハマったのは証明書の部分で始め Production 用の証明書と p12 ファイルしか登録しないでやっていたら全然プッシュ通知が届かなく、Development 用の証明書も作成したら届くようになりました

あとはコードの部分ですが、今回のコードは公式のドキュメントに記載されていることとほぼ同様です
でも「firebase ios push」あたりでググると公式ドキュメントが一番最初に出てきません
引っかかった記事のサンプルコードを使っていたのですが、単純にプッシュ通知したいだけなのに setAPNSToken を使っていたりしてうまく動作しませんでした
単純にプッシュ通知を受け取りたいだけなら上記のコードだけで十分だと思います
それ以外にもアプリがアクティブになった場合のプッシュ通知の処理や、通知の開封状況などを解析するためのコードがありますが単純にプッシュ通知したい場合であれば不要です

あともう一点良くわからなかったのは Firebase ではプッシュ通知を送信するときに単一の端末にだけ送信する機能があります
そのときに「FCM 登録トークン」というものを使うのですが、これが Firebase 上のどこで確認できるかさっぱりわかりませんでした
コード上でデバッグする方法はドキュメントに載っていたのですが、コンソール上で確認する方法はわかりませんでした
たぶん、個別配信をするときは FCM 登録トークンを使わないで、トピックを使ってやれということだとは思います
FCM 登録トークンは開発者が知りたい時に直接コードにデバッグコードを埋め込んで取得しましょうというスタンスなんだと思います
それともどっかで確認できるのかもしれませんが、、

参考サイト

2017年6月11日日曜日

Getting Started dozens rubygem

概要

dozens には API があります
Ruby の SDK が公開されているので使ってみました

環境

  • CentOS 7.3
  • Ruby 2.3.3p222
  • dozens 0.0.2

Getting Started

dozens のユーザ ID と API Key を事前に取得しておいてください

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

  • gem install dozens

認証

require 'dozens'

api = Dozens::API.new("your_dozens_id" , "your_dozens_api_key")
api.authenticate

ゾーンの一覧表示

p api.zones

ゾーン内のレコード一覧

rs = api.records "your.domain"
rs['record'].each {|r|
  p "id => #{r['id']}, name => #{r['name']}, type => #{r['type']}, prio => #{r['prio']}, content => #{r['content']}, ttl => #{r['ttl']}"
}

ゾーン内のレコードの更新

rs = api.records "your.domain"
rs['record'].each {|r|
  if r['name'] == "site.your.domain"
    target = r
    target['content'] = "1.1.1.1"
    p api.update_record(target['id'], target)
  end
}

とかとか

最後に

dozens の rubygem を試してみました
ドキュメントがほぼないので、ソースをみるか rubydoc を見るしかないですが簡単に使えました

dozens の API 自体に非常にシンプルなので自分で SDK を作ってもいいかもしれません

参考サイト

2017年6月10日土曜日

harbor に SSL 証明書を設定して https でレジストリを使うための手順

概要

harbor を https で使うための手順を紹介します
harbor はデフォルトで http なので、わざわざ docker クライアント側の insecure-registry に登録する処理が必要になります
ドメインと証明書が使える環境であればレジストリ自体 https を使うことが推奨されています
今回はドメインと証明書を用意して harbor を https で使えるようにしてみました

環境

  • Ubuntu 16.04
  • harbor 1.1.1
  • docker 17.05

事前準備

ドメインを取得して A レコードを登録して証明書を取得します
この流れは過去に紹介もしているので過去の手順でも OK です

ドメインの取得

何でも OK です
とりあえずドメインを取得してください

A レコードの追加

取得したドメインのサブドメインを A レコードとして DNS サーバに登録します
今回登録したレジストリのサブドメインは「reg01.harbor.gq」とします

証明書の作成

とりあえず A レコードで登録したサブドメインの証明書を 1 つ作成しましょう
同一ドメインでレジストリを何台も構築する場合はワイルドカード SSL 証明書で作成することをオススメします
そして作成した証明書は harbor をインストールするサーバに転送しておきましょう

  • scp -r /etc/letsencrypt/archive/reg01.harbor.gq 192.168.100.201:~

harbor の設定

事前準備

今回は Ubuntu 上に harbor を構築します
事前に docker と docker-compose が必要になるので準備しておきましょう (参考)

設定ファイルの編集

インストーラをダウンロードして展開し、設定ファイルの証明書を設定する部分を編集します

hostname = reg01.harbor.gq
ui_url_protocol = https
ssl_cert = /root/reg01.harbor.gq/fullchain1.pem
ssl_cert_key = /root/reg01.harbor.gq/privkey1.pem
  • ./prepare

customize_crt = on という項目がありますがこれは on のままにしておかないてください
証明書を転送したときの転送先は /root/reg01.harbor.gq/ に転送しているのでそのパスを指定しています

インストールと起動

設定が変更できたらインストールしましょう

  • ./install

ダラダラとログが流れて最終的にコンテナが上がってきます
同一ディレクトリ内に docker-compose.yml があるので docker-compose ps などでコンテナを確認することができます

これで証明書が設定されて https でアクセスできるようになっているはずです

動作確認

docker クライアントから https でアクセスできるか確認してみましょう

  • docker login reg01.harbor.gq

admin/Harbor12345 でログインしましょう
ちゃんと証明書が設定されていれば怒られないはずです

  • docker pull alpine
  • docker tag alpine reg01.harbor.gq/library/alpine
  • docker push reg01.harbor.gq/library/alpine

https://reg01.harbor.gq/harbor/tags/1/library%2Falpine にアクセスすると alpine の latest が存在することがわかります
pull もできるか確認してみましょう

  • docker rmi reg01.harbor.gq/library/alpine
  • docker pull reg01.harbor.gq/library/alpine:latest
  • docker logout reg01.harbor.gq

ログアウトしてから pull するとできないのが確認できると思います

最後に

harbor に https でアクセスできるようにしてみました
ドメインと証明書が取れれば簡単に設定することができます

以下の証明書のエラーが出た場合は再度コンテナを生成し直してみてください

Error response from daemon: Get https://reg01.harbor.gq/v1/users/: dial tcp 192.168.100.201:443: getsockopt: no route to host
  • docker-compose down
  • docker rmi $(docker images -q)
  • ./prepare
  • docker-compose up -d

参考サイト

2017年6月9日金曜日

govmomi で指定の VApp をフォルダ配下に移動させる

概要

VApp をフォルダ配下に移動する方法を紹介します
1 つの VApp をあるフォルダに移動します

環境

  • Ubuntu 16.04
  • golang 1.8
  • govmomi 0.14.0

ソースコード

  • vim main.go
package main

import (
        "context"
        "flag"
        "fmt"
        "net/url"
        "os"

        "github.com/vmware/govmomi"
        "github.com/vmware/govmomi/find"
        "github.com/vmware/govmomi/property"
        "github.com/vmware/govmomi/vim25/mo"
        "github.com/vmware/govmomi/vim25/types"
)

var envURL = "https://192.168.100.101/sdk"
var urlDescription = fmt.Sprintf("ESX or vCenter URL [%s]", envURL)
var urlFlag = flag.String("url", envURL, urlDescription)

var envInsecure = true
var insecureDescription = fmt.Sprintf("Don't verify the server's certificate chain [%s]", envInsecure)
var insecureFlag = flag.Bool("insecure", envInsecure, insecureDescription)

func main() {
        // vCenter への接続
        ctx, cancel := context.WithCancel(context.Background())
        defer cancel()
        flag.Parse()
        u, err := url.Parse(*urlFlag)
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        u.User = url.UserPassword("vcenter_user", "vcenter_pass")
        c, err := govmomi.NewClient(ctx, u, *insecureFlag)
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        fmt.Println(c.Client.Client.Version)

        // データセンターの取得
        f := find.NewFinder(c.Client, true)
        dc, err := f.DefaultDatacenter(ctx)
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        f.SetDatacenter(dc)
        fmt.Println(dc)

        // フォルダの取得
        folder, err := f.Folder(ctx, "/dc/vm/folder/")
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        fmt.Println(folder.Common.InventoryPath)
        // VirtualApp の取得
        varefs := []types.ManagedObjectReference{}
        va, err := f.VirtualApp(ctx, "/dc/vm/vapp")
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        fmt.Println(va.Common.InventoryPath)
        varefs = append(varefs, va.Reference())
        // フォルダへ移動
        t, err := folder.MoveInto(ctx, varefs)
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        info, err := t.WaitForResult(context.Background(), nil)
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        fmt.Println(info.State)
}

ポイントはフォルダのオブジェクトに対して実行している MoveInto メソッドになります
この引数に moref を指定することで指定した moref をフォルダに移動するという処理を行っています
対象の VApp に対して移動せよという命令をするわけではなく、フォルダに対してこのフォルダに入れるという処理をしているのがポイントです

実行後に WaitForResult を使って処理が完了するのを待っています
完了後 info.State で結果を表示して終了です

今回のコードの場合、指定するフォルダと VApp のパスが存在しない場合はエラーになります
なので、一度実行したあと再度実行したい場合は対象の VApp を元のパスに戻す必要があります

最後に

govmomi を使って VApp を特定のフォルダに移動してみました
参照系よりか簡潔に書くことができました

参考サイト

2017年6月8日木曜日

govmomi で VApp の情報を取得する方法

概要

vCenter 配下で管理されている VApp の情報を govmomi を使って取得してみます

環境

  • Ubuntu 16.04
  • golang 1.8
  • govmomi 0.14.0

ソースコード

  • vim main.go
package main

import (
        "context"
        "flag"
        "fmt"
        "net/url"
        "os"

        "github.com/vmware/govmomi"
        "github.com/vmware/govmomi/find"
        "github.com/vmware/govmomi/property"
        "github.com/vmware/govmomi/vim25/mo"
        "github.com/vmware/govmomi/vim25/types"
)

var envURL = "https://192.168.100.101/sdk"
var urlDescription = fmt.Sprintf("ESX or vCenter URL [%s]", envURL)
var urlFlag = flag.String("url", envURL, urlDescription)

var envInsecure = true
var insecureDescription = fmt.Sprintf("Don't verify the server's certificate chain [%s]", envInsecure)
var insecureFlag = flag.Bool("insecure", envInsecure, insecureDescription)

func main() {
        // vCenter への接続
        ctx, cancel := context.WithCancel(context.Background())
        defer cancel()
        flag.Parse()
        u, err := url.Parse(*urlFlag)
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        u.User = url.UserPassword("vcenter_user", "vcenter_pass")
        c, err := govmomi.NewClient(ctx, u, *insecureFlag)
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        fmt.Println(c.Client.Client.Version)

        // データセンターの取得
        f := find.NewFinder(c.Client, true)
        dc, err := f.DefaultDatacenter(ctx)
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        f.SetDatacenter(dc)
        fmt.Println(dc)

        // 全 VirtualApp の取得
        pc := property.DefaultCollector(c.Client)
        varefs := []types.ManagedObjectReference{}
        vas, err := f.VirtualAppList(ctx, "*")
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        for _, va := range vas {
                fmt.Println(va.Common.InventoryPath)
                varefs = append(varefs, va.Reference())
        }
        var vadst []mo.VirtualApp
        err = pc.Retrieve(ctx, varefs, nil, &vadst)
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        for _, va := range vadst {
                fmt.Println(va.VAppConfig.Annotation)
                fmt.Println(va.VAppConfig.EntityConfig) // VApp 内の VM の情報
                fmt.Println(va.Name)
        }
}

まず vCenter に接続しデータセンターの情報を取得します
その後 f.VirtualAppList を使ってすべての VApp の情報を取得します
取得後は types.ManagedObjectReference に突っ込んで pc.Retrieve することで VApp の情報を取得しています
あとは types.ManagedObjectReference に設定された値をループで回して参照しているだけです

VApp の場合 VApp 内に VM が存在することがあると思います
VApp 内の子の要素を参照するには EntityConfig を使えば参照できそうだったので、そこから VM の情報を取得できると思います

VApp の別の情報を参照したい場合は以下を参照してください
https://godoc.org/github.com/vmware/govmomi/vim25/mo#VirtualApp

最後に

govmomi を使って VApp の情報を参照していました
これまで紹介したフォルダや VM を取得する方法とほぼ同じなので特に躓くところはありませんでした

参考サイト

2017年6月7日水曜日

govmomi でフォルダの情報を取得する方法

概要

vCenter 配下で管理されているフォルダ情報を govmomi を使って取得してみます
すべてのフォルダを取得する方法と単体で取得する方法を紹介します

環境

  • Ubuntu 16.04
  • golang 1.8
  • govmomi 0.14.0

ソースコード

  • vim main.go
package main

import (
        "context"
        "flag"
        "fmt"
        "net/url"
        "os"

        "github.com/vmware/govmomi"
        "github.com/vmware/govmomi/find"
        "github.com/vmware/govmomi/property"
        "github.com/vmware/govmomi/vim25/mo"
        "github.com/vmware/govmomi/vim25/types"
)

var envURL = "https://192.168.100.101/sdk"
var urlDescription = fmt.Sprintf("ESX or vCenter URL [%s]", envURL)
var urlFlag = flag.String("url", envURL, urlDescription)

var envInsecure = true
var insecureDescription = fmt.Sprintf("Don't verify the server's certificate chain [%s]", envInsecure)
var insecureFlag = flag.Bool("insecure", envInsecure, insecureDescription)

func main() {
        // vCenter への接続
        ctx, cancel := context.WithCancel(context.Background())
        defer cancel()
        flag.Parse()
        u, err := url.Parse(*urlFlag)
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        u.User = url.UserPassword("vcenter_user", "vcenter_pass")
        c, err := govmomi.NewClient(ctx, u, *insecureFlag)
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        fmt.Println(c.Client.Client.Version)

        // データセンターの取得
        f := find.NewFinder(c.Client, true)
        dc, err := f.DefaultDatacenter(ctx)
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        f.SetDatacenter(dc)
        fmt.Println(dc)

        // フォルダの取得
        pc := property.DefaultCollector(c.Client)
        frefs := []types.ManagedObjectReference{}
        folder, err := f.Folder(ctx, "/dc/vm/folder_name/")
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        fmt.Println(folder.Common.InventoryPath)
        frefs = append(frefs, folder.Reference())
        var fdst []mo.Folder
        err = pc.Retrieve(ctx, frefs, nil, &fdst)
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        for _, f := range fdst {
                fmt.Println(f.Name)
                fmt.Println(f.Tag)
        }
        // 全フォルダの取得
        fsrefs := []types.ManagedObjectReference{}
        folders, err := f.FolderList(ctx, "*")
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        for _, folder := range folders {
                fmt.Println(folder.Common.InventoryPath)
                fsrefs = append(fsrefs, folder.Reference())
        }
        var fsdst []mo.Folder
        err = pc.Retrieve(ctx, fsrefs, nil, &fsdst)
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
        for _, f := range fsdst {
                fmt.Println(f.Name)
                fmt.Println(f.Tag)
        }
}

まず vCenter に接続しデータセンターの情報を取得します
その後 f.Folder にフォルダのパスを指定することでフォルダ単体を取得しています
取得後は types.ManagedObjectReference に突っ込んで pc.Retrieve することでフォルダの情報を取得しています
あとは types.ManagedObjectReference に設定された値をループで回して参照しているだけです

全件取得の場合には f.FolderList(ctx, "*") とパスの部分をアスタリスクにすることで実現できます
あとは同じ流れで moref に突っ込んで pc.Retrieve で設定し、moref をループで回して参照するだけです

今回は Name と Tag を参照していますが、フォルダの他の情報を参照したい場合は godoc を見てください
https://godoc.org/github.com/vmware/govmomi/vim25/mo#Folder

最後に

govmomi を使ってフォルダを参照する方法を紹介しました
フォルダのオブジェクトが取得できるようになればフォルダに対する操作もできるようになります
例えば削除や名前の変更 VM をフォルダに移動することなどができるようになります

その辺の操作周りに関しても別記事で紹介できればなと思います

参考サイト