ksaitoの日記

日々試したことの覚え書き

bashのretry

下記のコードを$HOME/.bashrcに追加するとコマンドが失敗した場合に、一定時間待って、指定した回数リトライするretry関数を追加できます。

オリジナルは、ここのソースを参考に改良しました。(コメントしようと思いましたが、50以上の評価がないとコメントできませんでした。)

aws cliでリソースを作成した場合、awsコマンドは、リソース作成の終了を待たずに終わってしまいます。リソース作成が終わってから後続の処理をしたい場合に、適切なタイムアウトを設けてリトライするのに便利です。

コード

function retry() {
    if [ $# -lt 3 ]; then
        echo 'usage: retry <num retries> <wait retry secs> "<command>"'
        return 1
    fi

    retries=$1
    wait_retry=$2
    shift 2
    command=$@

    for i in `seq 1 $retries`; do
        echo "$command"
        $command
        ret_value=$?
        [ $ret_value -eq 0 ] && break
        echo "> failed with $ret_value, $wait_retry sec waiting to retry..."
        sleep $wait_retry
    done

    return $ret_value
}

使い方

成功する場合

$ retry 5 1 test 0 -eq 0
test 0 -eq 0
$ echo $?
0
$ 

失敗する場合

$ retry 5 1 test 0 -eq 1
test 0 -eq 1
> failed with 1, 1 sec waiting to retry...
test 0 -eq 1
> failed with 1, 1 sec waiting to retry...
test 0 -eq 1
> failed with 1, 1 sec waiting to retry...
test 0 -eq 1
> failed with 1, 1 sec waiting to retry...
test 0 -eq 1
> failed with 1, 1 sec waiting to retry...
$ echo $?
1
$ 

concourseを使ったビルド

concourse.ioは、日本語情報とても少ないです。 とてもクールなCIツールです。

パイプライン、ジョブ、リソース、タスクといった考え方があり、CI/CDのパイプラインを構築できますが、実装しようと思うと意外と手が止まります。 パイプラインの起点は、S3やgitなどで、Jenkinsのようにとりあえず実行といったことができないからです。

とりあえず実行をするには、タスクを作るのがお勧めです。文書だけでは、うまく伝わらないと思いますが、日常の開発でも地味に便利です。

ユースケース

Javaの開発をしていたとします。対応する必要のあるJDKのバージョンはたくさんあるとします。

従来のビルド環境であれば、複数のバージョンをインストールして環境変数で切り替えるといったことが考えられますが、以外と面倒なことになります。

タスクの作成

concourseは、タスクはdockerコンテナで実行されます。 下記のようなタスクを準備してJDK1.6の環境を準備できます。

DockerHubのjavaオフシャルコンテナを使っています。tagの指定を変えれば、好みのバージョンを使うことができます。

$ cat sample.yml
---
platform: linux

image_resource:
  type: docker-image
  source: {repository: java, tag: 6}

run:
  path: sh
  args:
  - -exc
  - |
    java -version
$ fly -t test execute -c sample.yml
executing build 41 at http://172.17.0.1:8080/builds/41 
initializing
running sh -exc java -version

+ java -version
openjdk version "1.8.0_111"
OpenJDK Runtime Environment (build 1.8.0_111-8u111-b14-2~bpo8+1-b14)
OpenJDK 64-Bit Server VM (build 25.111-b14, mixed mode)
succeeded
$

パラメータを指定する

任意のパラメータを環境変数で渡すことができます。コンパイルオプションの指定に便利です。

$ cat sample.yml
---
platform: linux

image_resource:
  type: docker-image
  source: {repository: java, tag: 6}

params:
  JAVA_OPTS: -version

run:
  path: sh
  args:
  - -exc
  - |
    java $JAVA_OPTS
$ JAVA_OPTS=-fullversion fly -t test execute -c sample.yml
executing build 43 at http://172.17.0.1:8080/builds/43 
initializing
running sh -exc java $JAVA_OPTS

+ java -fullversion
java full version "1.6.0_38-b38"
succeeded
$

ファイルの受け渡し

下記のようなプログラムをコンパイルする場合、ソースコードを渡して、コンパイル結果を受け取る必要があります。

$ cat Hello.java 
public class Hello {
    public static void main(String[] args)
    {
        System.out.println("Hello, world.");
    }
}
$ 

タスクにファイルを渡すためのinputsとファイルを受け取るためのoutputsを定義します。

$ cat sample.yml
---
platform: linux

image_resource:
  type: docker-image
  source: {repository: java, tag: 6}

params:
  JAVA_OPTS: -version
inputs:
  - name: src
outputs:
  - name: obj

run:
  path: sh
  args:
  - -exc
  - |
    java $JAVA_OPTS
    javac src/Hello.java
    java -cp src Hello
    cp -p src/Hello.class obj

下記のように実行するとカレントディレクトリのファイルがsrcディレクトリにコピーされてタスク内で使用できるようになります。また、タスク内でobjディレクトリにコピーしたファイルは、/tmp/objで受け取ることができます。

$ fly -t test execute -c sample.yml -i src=. -o obj=/tmp/obj
executing build 44 at http://172.17.0.1:8080/builds/44 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1337    0  1337    0     0   1337      0 --:--:-- --:--:-- --:--:--  435k
initializing
running sh -exc java $JAVA_OPTS
javac src/Hello.java
java -cp src Hello
cp -p src/Hello.class obj

+ java -version
java version "1.6.0_38"
OpenJDK Runtime Environment (IcedTea6 1.13.10) (6b38-1.13.10-1~deb7u1)
OpenJDK 64-Bit Server VM (build 23.25-b01, mixed mode)
+ javac src/Hello.java
+ java -cp src Hello
Hello, world.
+ cp -p src/Hello.class obj
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   455    0     0    0   455      0    455 --:--:-- --:--:-- --:--:-- 45500
succeeded
$ ls /tmp/obj
Hello.class
$ 

パイプラインは、タスクに定義されたinputsとoutputsをつなぎ合わせることで成果物の受け渡しを行います。非常によく考えられていて便利です。

macOS High Sierraのアップグレードに失敗

MacBook PromacOS High Sierraのアップグレード通知がきたのでアップグレードしたところ、見事に失敗しました。 1時間ほどアップグレード処理が続いた後にWi-Fiを選択する画面が表示されました。Wi-Fiを選択したところ、しばらくして画面がブラックアウトしたまま、1時間ほど経過...

強制的に再起動すると、ログイン画面が表示されます。 ログインするとプログレスバーが表示されて、また、Wi-Fiの選択画面...

有線LANのアダプタを買ってきてリトライするしかないかな。 クリーンインストールになるかも...

有線LANで無事インストールできました。

優先LANのアダプターを購入してリトライしたら、15分くらいであっさり終わりました。 有線LAN接続して、起動して、ログインするとプログレスバーが進みます。 その後、iCloudのパスワード入力を求められ、Apple Payを設定(スキップ)したら無事立ち上がりました。

iPhoneのアップグレードもそうですが、Wi-FiiCloudを設定するする処理に不具合があるのかもしれません。

Jenkins-cli

Jenkinsをコマンドラインから操作することでコード化することができます。 Jenkinsをコマンドで操作するには、Jenkins-cliに付属しているjenkins-cli.jarを使います。

Jenkins-cliの設定

最初にjenkins-cli.jarファイルのダウンロードとAPIトークンを作成する必要があります。

jenkins-cli.jarのダウンロード

Jenkinsの管理-Jenkins CLIメニューを選択します。 jenkins-cli.jarのリンクからjenkins-cli.jarファイルがダウンロードできます。

[f:id:ksaito11:20171103194736p:plain

APIトーク

Jenkinsのアカウントから設定メニューを開きます。 APIトークンが作成されていなければ作成します。

f:id:ksaito11:20171103195002p:plain

APIの使い方

下記のようにジョブ一覧を取得できます。 Jenkinsの設定もコードで自動化できます。

e03b5811fd88:/$ export JENKINS_URL=http://localhost:8080
e03b5811fd88:/$ cd
e03b5811fd88:~$ java -jar `find . -name jenkins-cli.jar` -http -auth admin:APIトークン list-jobs
debug
test2
test3
e03b5811fd88:~$ 

JenkinsfileとMakefileの統合

Jenkinsfileをメンテナンスし続ける自信がないので、Jenkinsfileをパイプラインの定義のみで使うようにしました。 定義したパイプラインの各ステップは、Makefileのターゲットに対応させるとメンテナンスし易くなります。

パイプラインの定義

パイプラインは、下記のように定義します。

ステージと同じ名前のターゲットをMakefileに定義すると、開発するときにmakeコマンドで実行した処理がJenkinsのビルドでも同じように利用されます。

makeコマンドをantやmavenなどに入れ替えれば、他かの開発でも応用が利きます。

ステージ 処理
setup ビルドに必要な環境をセットアップ
build ビルド
test テストは、単体テスト結合テストを並列で実行
deploy デプロイ

Jenkinsのパイプラインジョブで実行するとこんな感じになります。

f:id:ksaito11:20171103175600p:plain

パイプライン

Jenkinsfileの定義

pipeline {                              
    agent any                           
    environment {                       
        branch = 'master'               
        scmUrl = 'git@bitbucket.org:***/***.git'                       
    }                                   
    stages {                            
        stage('setup') {                
            steps {                     
                sh: 'make setup'
            }                           
        }                               
        stage('checkout git') {         
            steps {                     
                print "checkout"        
                git credentialsId: credentialId, url: scmUrl                    

            }                           
        }
        stage('build') {
            steps {
                sh 'make'
            }
        }
        stage ('test') {
            steps {
                parallel (
                    "unit tests": { sh 'make test' },
                    "integration tests": { sh 'make integration-test' }
                )
            }
        }
        stage('deploy'){
            steps {
                sh 'make deploy'
            }
        }
    }
}

Makefileの定義

all: build                              

build:                                  
        echo build                      

test:                                   
        echo test                       

integration-test:                       
        echo integration-test           

deploy:                                 
        echo deploy                     

setup:                                  
        echo setup 

Docker for macでコンテナの中からMacのIPを知る方法

I WANT TO CONNECT FROM A CONTAINER TO A SERVICE ON THE HOSTに書かれているdocker.for.mac.localhostで、下記のようにコンテナの中からMacIPアドレスを引くことができます。

$ nmap -Pn -p 8080 docker.for.mac.localhost

Starting Nmap 7.40 ( https://nmap.org ) at 2017-10-15 00:37 JST
Nmap scan report for docker.for.mac.localhost (192.168.5.1)
Host is up (0.00038s latency).
Other addresses for docker.for.mac.localhost (not scanned):
PORT     STATE SERVICE
8080/tcp open  http-proxy

Nmap done: 1 IP address (1 host up) scanned in 0.03 seconds
$

githubの二要素認証を設定するとpush時に認証エラーになる

githubで二要素認証を設定するとpush時に認証エラーになります。

$ git push origin develop
Username for 'https://github.com': ***
Password for 'https://***@github.com':
remote: Invalid username or password.   
fatal: Authentication failed for 'https://github.com/***/***.git/'

下記のリンクにある通り、githubのアカウント-Settingsでpersonal access tokens pageを選択してアクセストークンを作成し、パスワードは、このトークンを使う必要があるようです。 githubのパスワードを何度入力してもダメで焦りました。

Two-factor Authentication · GitHub