ページ

2018年7月25日水曜日

CircleCI2.0上でDockerHubへPushする

概要

  • Docker Buildを行う
  • Serverspecでテストを行う
  • Docker HubへPushを行う

環境

↓環境毎のディレクトリにDockerfileとspec用のディレクトリがある
.
├── android
│   └── spec
├── gcc_cmake
│   └── spec
├── rails
│   └── spec
...
├── specfiles #spec用の共通して使用するファイル
│   └── spec
テストを行う場合、各環境のDocker Imageをビルドし、それぞれのテストをServerspecで行う。

Dockerfile

version: 2
jobs:
  build:
    working_directory: ~/workspace
    branches:
      only:
        - develop
    docker:
      - image: circleci/ruby:2.4
    steps:
      - checkout
      - setup_remote_docker

      # Download and cache dependencies
      - restore_cache:
          keys:
          - v1-dependencies-{{ checksum "Gemfile.lock" }}
          - v1-dependencies-

      # Install dependencies
      - run:
          name: bundle install
          command: bundle install --path vendor/bundle

      - save_cache:
          paths:
            - vendor/bundle
          key: v1-dependencies-{{ checksum "Gemfile.lock" }}

      # Test
      - run:
          name: run test
          command: ./run-tests.sh

      # Deploy
      - run:
          name: deploy docker images
          command: ./deploy.sh
  • ポイント
    • setup_remote_docker を設定
      • dockerやdocker-composeコマンドが使えるようになる
      • 詳しくはこちら
    • gemに関してはキャッシュしてます

テスト

run-tests.sh
#!/usr/bin/env bash

WORKDIR=$PWD

# Find target directory.
for entity in `find . -type d -mindepth 1 -maxdepth 1 -not -name ".git"`; do

  if [ $entity = "./specfiles" ]; then
    # Ignore directory
    continue
  fi

  # Only exist spec directory.
  if [ -e $entity/spec ]; then
    # Copy specfiles.
    cp $WORKDIR/specfiles/.rspec $entity/.
    cp -r $WORKDIR/specfiles/spec $entity/.

    # Build docker image.
    cd $entity
    ./docker-build.sh
    cd $WORKDIR

    # Exec rspec
    bundle exec rspec --default-path $entity/spec
  fi
done
対象となるディレクトリ配下に spec ディレクトリがある環境だけ
Docker Imageをビルドし、テストを行なっています。
sh
   cp $WORKDIR/specfiles/.rspec $entity/.
   cp -r $WORKDIR/specfiles/spec $entity/.
↑は共通して使用するテスト用のファイルを各環境へコピーしてます。

Docker Hub へ Push

deploy.sh
#!/usr/bin/env bash

docker login -u $DOCKER_USER -p $DOCKER_PASS

# Android
docker push slowhand/android:1.0

# Rails
docker push slowhand/rails:1.0

# gcc + cmake
docker push slowhand/gcc_cmake:1.0
こちらは単純にloginしてpushしているだけです。
$DOCKER_USER などの環境変数はCircleCI上で定義してます。

dockerfiles

今回書いた分は
https://github.com/Slowhand0309/dockerfiles
こちらのリポジトリで公開してます。何かツッコミ等あれば指摘頂けると助かります。

2018年7月5日木曜日

Firebaseのfuncsionsを色々試す

Cloud Funcsions

:computer:環境構築


事前にfirebase-toolsをインストール
sh
$ npm install -g firebase-tools
firebaseにログイン
sh
$ firebase login
※ 複数のアカウントを使用している場合
~/.config/configstore/ここに
  • firebase-tools.json
  • update-notifier-firebase-tools.json
がアカウント毎に作成されるので、アカウント切り替える場合は
このファイルを一旦削除なりして firebase login すると新しいのが作成される。
functionsプロジェクトの作成
sh
$ firebase init functions

                      
                                               
                      
                                               
                               

You're about to initialize a Firebase project in this directory:

  /xxxxxxx/xxxxxx/xxxxx


=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add, 
but for now we'll just set up a default project.

? Select a default Firebase project for this directory: xxxxxx (xxxxxxxx)

=== Functions Setup

A functions directory will be created in your project with a Node.js
package pre-configured. Functions can be deployed with firebase deploy.

✔  Wrote functions/package.json
✔  Wrote functions/index.js
? Do you want to install dependencies with npm now? Yes
複数プロジェクトがあった場合プロジェクトの選択と
依存パッケージのインストールをついでにやってくれるので
インストールしたい場合はここでインストールする。

:pencil: 実装


簡単なサンプル

とりあえずHello worldやってみる。
firebase init functionsで作成されたfunctions/index.js に実装されて
コメントされているのでコメントアウトする。
javascript
exports.helloWorld = functions.https.onRequest((request, response) => {
 response.send("Hello from Firebase!");
});
デプロイ
sh
$ firebase deploy --only functions
firebase consoleからFunctionsを選択すると先ほどデプロイされたHello worldがデプロイされていればOK!
スクリーンショット 2017-07-09 17.24.19.png (74.9 kB)
URLを叩けばHello from Firebase!が表示されるはず。

ローカルでの動作確認方法

firebase serve を使う

例)
js
exports.hello = functions.https.onRequest((request, response) => {
  response.status(200).send('hogehoge!');
});
functionsディレクトリ直下で firebase serve --only functions コマンドを実行したいけど、
プロジェクト直下で実行しないと設定ファイルとかが読み込めなくてエラーになるorz
なので、プロジェクト直下で実行する場合は
$ npm --prefix functions run serve 
とすると functions ディレクトリの package.json の scripts の serve を実行してくれる
functions/package.json
  "main": "lib/index.js",
  "scripts": {
    "serve": "firebase serve --only functions",
    "shell": "firebase experimental:functions:shell",
    "build": "./node_modules/.bin/tsc"
  },
↑ Typescript などを使っている場合は "main": "lib/index.js", とする事で
トランスパイラ先のjsを指定できる
そしていざ実行すると
i  functions: Preparing to emulate functions.
✔  functions: sampleHandler: http://localhost:5000/xxxxxxx/us-central1/sampleHandler
こんな感じでurlが表示されるので叩いてやればOK

:bomb: バッドノウハウ


  • firebase serve --only functions コマンドを実行するとエラー1
Error: No project active, but project aliases are available.

Run firebase use <alias> with one of these options:

  xxxxx (xxxxxx)
  xxxxx (xxxxxx)
active projectを設定してやる
sh
$ firebase use <project>
↑のコマンドを実行すると、~/.config/configstore/firebase-tools.json の activeProjects にパスが設定される!
  • firebase serve --only functions コマンドを実行するとエラー2
⚠  functions: Cannot start emulator. Error: Cannot find module '@google-cloud/functions-emulator/src/config'
(node:49529) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): TypeError: Cannot read property 'exit' of undefined
同じようなissueが上がっていた:eyes:
https://github.com/firebase/firebase-tools/issues/552
自分の場合はnodeのversionが古かった見たく、6.11.5以上にしたらOKだった:+1:

2018年7月1日日曜日

Retrofit + Rxを試す(Android)

Retrofit+Rx

RetrofitのRx化に関しては Jake神作のAdapterを使っていたらしい。。が
現在本家のretrofitにAdapterが出来てるみたいで Jake神作のはDEPRECATEDになっている。
こちらを使う:eyes:

:computer: 環境構築


app/build.gradleに以下を追加
    implementation 'io.reactivex.rxjava2:rxjava:2.1.6'
    implementation 'io.reactivex.rxjava2:rxkotlin:2.1.0'
    implementation 'com.squareup.moshi:moshi:1.5.0'
    implementation 'com.squareup.retrofit2:retrofit:2.4.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
    implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'  

:pencil: 実装


簡単なサンプル

サンプルとしても多い、GithubのAPIを使ってユーザー情報を取得して見ます。
  • Userクラス (ユーザー情報を格納するデータクラス)

    import com.squareup.moshi.Json
    
    data class User(
        @Json(name = "name")
        var name: String,
        @Json(name = "login")
        var login: String,
        @Json(name = "blog")
        var blog: String,
        @Json(name = "type")
        var type: String
    )
    
  • GithubApiクラス (interfaceを定義)

    import io.reactivex.Observable
    import retrofit2.http.GET
    import retrofit2.http.Path
    
    interface GithubApi {
    
        companion object {
            const val BASE_URL = "https://api.github.com"
        }
    
        @GET("users/{username}")
        fun getUser(@Path("username") user: String): Observable<User>
    }
    
  • MainActivityクラス

    class MainActivity : AppCompatActivity() {
    
        companion object {
            val TAG = MainActivity::class.java.simpleName
        }
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            val retrofit = Retrofit.Builder()
                    .baseUrl(GithubApi.BASE_URL)
                    .addConverterFactory(MoshiConverterFactory.create(Moshi.Builder().build()))
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
                    .build()
    
            val service = retrofit.create(GithubApi::class.java)
            service.getUser("Slowhand0309").subscribe({ ret ->
                Log.d(TAG, "ret $ret")
            }, { error ->
                Log.e(TAG, error.message)
            })
        }
    }
    
AndroidManifestに
<uses-permission android:name="android.permission.INTERNET"/> を追加するのを忘れずに

戻り値に関して

RetrofitのRxJava2 Adapterは戻り値として
Observable, Single, Maybe, Completable
を提供しているが、それぞれの違いがわからないので調べてみる。
  • Observable: いつもの
  • Single
    • onNext+onComplete = onSuccess でonSuccessは一回しか呼べない
  • Maybe
    • onSuccessかonErrorかonCompleteのどれかが呼ばれる、または全く呼ばれない
  • Completable
    • onErrorかonCompleteのどれかが呼ばれる、または全く呼ばれない

エラー処理に関して

200番台以外のレスポンスの場合 HttpException として onError が呼ばれる
実際のソースコード
エラー処理に関しては↓のように捌けば良さそう。
        disposable += ApiClient.createUser()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe({ res ->
                    Log.d(TAG, "create user success ${res.id}")
                }, { error ->
                    (error as? HttpException)?.let {
                        Log.e(TAG, "code: ${it.code()}")
                        Log.e(TAG, "message: ${it.message()}")
                        Log.e(TAG, "response: ${it.response()}")
                    }
                })
401が返ってきた場合の実際の出力
E/MainActivity: code: 401
    message: 
E/MainActivity: response: Response{protocol=h2, code=401, message=, url=https://....} 

:bomb: バッドノウハウ


単純にmoshiを使うだけだと、dataクラスのnon-nullのプロパティにnullが入ってしまう!
参考URL
そこでmoshiのkotlin extensionを導入して、non-nullにnullを入れうようとしたら
例外を投げるように修正
app/build.gradleに以下を追加
implementation 'com.squareup.moshi:moshi-kotlin:1.5.0'
MainActivityのmoshiを生成している箇所をいかに変更
        val moshi = Moshi.Builder()
                    .add(KotlinJsonAdapterFactory())
                    .build()

        val retrofit = Retrofit.Builder()
                .baseUrl(GithubApi.BASE_URL)
                .addConverterFactory(MoshiConverterFactory.create(moshi))
                .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
                .build()
これでnon-null / null をきっちり管理してくれます:+1:

:link: 関連リンク