20190827のGoに関する記事は3件です。

GithubにGoで書いたDockerをTravisでCIする

あまり他人のソフト使うのが上手くないので、色々とハマりポイントがあったもので
Git履歴よりハマりポイントのおさらい

目指すもの

GithubにTravisCIを連携させることで、PushやPullRequestした時などに
自動的にTestやLintを行い、コードカバレッジを行う事ができる
大手の開発では一般的なスタイルではなかろうか?

個人的に勉強ついでに作っていた SlackBotのプログラムを使って勉強してみた

https://github.com/YukiMiyatake/GOSICK

SlackBotである。プラグインを入れる事で機能拡張を行う事ができる
とりあえず勉強用テストプロジェクトである
これからリファクタリングしたり interface化して、LintなりTestなり色々な改造を行う予定
(現在 gomodule導入テストのためプラグインもecho以外動かないはず)

dockerファイルを修正

今までDockerfileにて go buildして実行ファイルを起動させていたが
TravisやローカルでTestをまわすために
また、コード修正のたびに docker-compose buildしたくないので
Dockerでは何もせず、シェルスクリプトでビルド実行やテストを行うように変更した

Dockerファイル

dockerfile
WORKDIR /build
COPY src .
CMD ./build.sh
CMD ls

CMD ./GOSICK

この辺りを削除した。ぶっちゃけ Goのイメージそのまま使うのでDockerfileは不要で
docker-composeにimage指定するだけでいいけど
今後なにかインストールする可能性もあるので Dockerファイルはのこした

build.sh

今回はプラグインのビルドなども入るので、ワンライナーで書かず
ビルドをシェルスクリプトでわけたかった
(最終的にはMakefile等にしたいなあ)

build.sh
if [ $TEST = "test" ]; then
    cd plugins/echo
    go test -v ./... --buildmode=plugin
    cd ../..

    go test -v ./...
else
    cd plugins/echo
    go build --buildmode=plugin
    cd ../..

    go build
fi

テストと通常ビルドを分けるため $TEST環境変数で分けることにした

Script

docker_app_build
docker-compose up -d
docker-compose exec gosick_app bash -C '/app/build.sh'
docker_app_test
docker-compose up -d
docker-compose exec -e TEST=test gosick_app bash -C '/app/build.sh'

テストと実行でファイルを分けたが、環境変数以外はかわらないので、まとめてもいいなあ
docker-compose up も別にすべきかなあ

等色々考えているが、しばらく使って考える予定

GithubにTravisを連携させバッヂをつける

https://qiita.com/hoshimado/items/4090d8e64beb8a7f95e1

例えばこの辺を参考に、バッヂまでつける

Travisの設定

とっても苦労したTravisの設定
とりあえずはどのリポジトリをPushしても通知が入るようにしている
プロジェクトのルートに .travis.yml ファイルを作成してPush

最初の設定

.travis.yaml
sudo: required

services:
  - docker

before_install:
#  - docker-compose up -d

install:

before_script:

script:
  - scripts/docker_app_run
 # - docker-compose exec golang go test -v ./...

after_script:

notifications:

いろんなサイトをみて上記の設定ファイルを書いたが、色々なエラーが出てダメ
なので色々と

language設定

TravisのページをみるとRubyと判断されていたのでlanguageを設定

.travis.yaml
language: go

ただ、Goのバージョンが古かったのでちゃんとバージョン指定してみた

.travis.yaml
language: go
go: 
  - 1.12.x

GO111MODULE

ログをみていると GO111MODULE=autoになっていた
今回はgo module使いたいので onにしたいので下記のようにかいてみた

.travis.yaml
script:
  - env GO111MODULE=on scripts/docker_app_test

ところがログをみても GO111MODULE=autoのままだった

どうやらenvセクションに書く必要があった

.travis.yaml
env:
  - GO111MODULE=on

go_import_path

githubのアドレスを加えた

.travis.yaml
go_import_path: github.com/YukiMiyatake/GOSICK

スクリプトパーミッション

Windowsで開発を行っていたため、ファイルのパーミッションがおかしくなっていた
Travis内でパーミッションを設定してもいいが、根本的な解決としてスクリプトファイルにパーミッションをあたえPushした

https://qiita.com/maosanhioro/items/aaade68cdca591232159

この辺をみて、パーミッションを正しく設定したら動いた

DockerComposeのバージョンが古い

docker-composeのバージョンが古かったので念のためバージョンアップを行った

まず、TravisのDockerやDockerComposeバージョンは例えば下記で調べられる

.travis.yml
before_install:
  - docker-compose -v
  - docker -v

これによると

$ docker-compose -v
docker-compose version 1.17.1, build 6d101fb
before_install.3
$ docker -v
Docker version 17.09.0-ce, build afdb6d4

Dockerはとりあえず今回はバージョンはいいとします
DockerComposeだけ 1.24.1にバージョン上げます

.travis.yml
env:
  - GO111MODULE=on
  - DOCKER_COMPOSE_VERSION=1.24.1

before_install:
  - sudo rm /usr/local/bin/docker-compose
  - curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
  - chmod +x docker-compose
  - sudo mv docker-compose /usr/local/bin

Travis公式のドキュメント通りです。

ところが色々不可解なエラーが出ます

Yamlの罠

yamlじたいほとんど書かないのでよくわかってませんが、気になるポイントをみつけた

.travis.yml
env:
  - GO111MODULE=on
  - DOCKER_COMPOSE_VERSION=1.24.1

結論からいうと

GO111MODULE:on
だとGO111MODULEは定義されずautoになる

GO111MODULE: on
だとGO111MODULEはtrueが定義されてしまう

GO111MODULE= on
だとGO111MODULEは空文字が定義される

よって
GO111MODULE=on

以外はダメ

DOCKER_COMPOSE_VERSION = 1.24.1
DOCKER_COMPOSE_VERSION:1.24.1
DOCKER_COMPOSE_VERSION :1.24.1
は共に定義されない

DOCKER_COMPOSE_VERSION: 1.24.1
DOCKER_COMPOSE_VERSION=1.24.1
はOK

:の場合は前にスペースNG、後ろにスペース必須
=の場合は 前も後ろもスペースNG
GO111MODULEには=しか使えない
DOCKER_COMPOSE_VERSIONは上記ルールを守っているなら:でも=でもOK

細かい事はおいといて

.travis.yml
  - GO111MODULE=on
  - DOCKER_COMPOSE_VERSION=1.24.1

とするとちゃんと定義されるようになった

並列実行問題

.travis.yml
env:
  - GO111MODULE=on
  - DOCKER_COMPOSE_VERSION=1.24.1

で両方の環境変数は定義されたが、なぜか2つ実行されているっぽい
その2つの環境変数は

GO111MODULE=on, DOCKER_COMPOSE_VERSION定義なし
GO111MODULE定義なし, DOCKER_COMPOSE_VERSION=1.24.1

の2つ
どう考えても並列実行されている
ドキュメント見た感じだと globalに設定すればよさそう

.travis.yml
env:
  global:
    - GO111MODULE=on
    - DOCKER_COMPOSE_VERSION=1.24.1

これで、すべての実行環境において上記のフラグが立つようになった
今回は使用していないが metrix: に環境変数配列を列挙すると並列で実行できるので
テストの高速化を行う時は使ってみよう
(ただし無料版だとたぶん2並列までしかできない)

DBのbind問題

下記の謎エラーが出て結構悩まされた

ERROR: for gosick_mysql  Cannot start service gosick_mysql: driver failed programming external connectivity on endpoint gosick_mysql (cb76f17f3761214c04ca3c4ca03fe4b993e65b0ca14b76b711606651d40c8a8c): Error starting userland proxy: listen tcp 0.0.0.0:3306: bind: address already in use
ERROR: for gosick_mysql  Cannot start service gosick_mysql: driver failed programming external connectivity on endpoint gosick_mysql (cb76f17f3761214c04ca3c4ca03fe4b993e65b0ca14b76b711606651d40c8a8c): Error starting userland proxy: listen tcp 0.0.0.0:3306: bind: address already in use

BindAddressが使われている。3306なのでMySQLが既に立ち上がっているようだが、いくら見渡してもそれらしいものがない・・
ので困った
MySQLのDockerは今は使ってないので消せば解決はするが、ちゃんと解決させたいので調べたら、まさかの! TravisでMySQLが立ち上がっていた・・・

ので、実行前にTravisのMySQLを落とす

.travis.yml
before_install:
  - sudo /etc/init.d/mysql stop

やった!できた!

まとめ

色々な罠があったが、とりあえずTravisCIを動かすことができた
一通り自分の陥った罠をリストアップしたので、誰かの役にたてば幸いです

なぜかすべてのテストが通ってないので問題は残ってるけど、TravisCIの問題は解決したということで。

最終的な設定ファイル(不要な設定もあるかもしれないけど)

.travis.yml
language: go
go:
  - 1.12.x

go_import_path: github.com/YukiMiyatake/GOSICK

env:
  global:
    - GO111MODULE=on
    - DOCKER_COMPOSE_VERSION=1.24.1

services:
  - docker

before_install:
  - sudo /etc/init.d/mysql stop
  - docker-compose -v
  - docker -v
  - sudo rm /usr/local/bin/docker-compose
  - curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
  - chmod +x docker-compose
  - sudo mv docker-compose /usr/local/bin
  - docker-compose build

install:

before_script:

script:
  - scripts/docker_app_test

after_script:

notifications:
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

goで大食い大会

はじめに

ゴルーチンとチャネルの勉強として大食い大会を書いてみた。

1)コックはひたすらホットドッグを作る
2)ウェイターはひたすらホットドッグをフードファイターへ運ぶ
3)フードファイターはひたすらホットドッグを食べて、自分が食べたホットドッグをカウントする
4)制限時間になったら大会終了。その時点でのランキングを出す。

この 1 => 2 => 3 の流れをチャネルを使い、4の制限時間の制御にcontextを使った。

コード

package main

import (
    "context"
    "fmt"
    "time"

    . "github.com/ahmetb/go-linq"
)

func cook(
    done <-chan interface{},
    fn func() interface{},
) <-chan interface{} {
    foods := make(chan interface{})
    go func() {
        defer close(foods)
        for {
            select {
            case <-done:
                return
            case foods <- fn():
            }
        }
    }()
    return foods
}

func hotDog() interface{} {
    fmt.Printf("コックはホットドッグを作ります\n")

    return "ホットドッグ"
}

func waiter(foods <-chan interface{}) interface{} {
    food := <-foods
    fmt.Printf("ウェイターは%vをフードファイターへ持っていきます。\n", food)
    return food
}

func waitperson(
    done <-chan interface{},
    foods <-chan interface{},
) <-chan interface{} {
    toFoodFighter := make(chan interface{})
    go func() {
        defer close(toFoodFighter)
        for {
            select {
            case <-done:
                return
            case toFoodFighter <- waiter(foods):
            }
        }
    }()
    return toFoodFighter
}

type FoodFighter struct {
    name    string
    current int
}

type FoodFighters []*FoodFighter

func (f *FoodFighter) eat(foods <-chan interface{}) *FoodFighter {
    food := <-foods
    f.current++
    fmt.Printf("%vは%vを%v個食べた\n", f.name, food, f.current)
    return f
}

func (f *FoodFighter) startEat(
    done <-chan interface{},
    foods <-chan interface{},
    limit int,
) <-chan *FoodFighter {
    results := make(chan *FoodFighter)
    go func() {
        defer close(results)
        for i := 0; i <= limit; i++ {
            select {
            case <-done:
                return
            case results <- f.eat(foods):
            }
        }
    }()
    return results
}

func start(done <-chan interface{}, f *FoodFighter) <-chan *FoodFighter {
    return f.startEat(done, waitperson(done, cook(done, hotDog)), 99)
}

func competition(ctx context.Context, cancel context.CancelFunc, done <-chan interface{}) FoodFighters {
    defer cancel()
    var foodFighters FoodFighters
    var f1Result *FoodFighter
    var f2Result *FoodFighter
    var f3Result *FoodFighter
    f1 := &FoodFighter{name: "ボブ"}
    f2 := &FoodFighter{name: "ビル"}
    f3 := &FoodFighter{name: "マイケル"}

    for {
        select {
        case f1Result = <-start(done, f1):
        case f2Result = <-start(done, f2):
        case f3Result = <-start(done, f3):
        case <-ctx.Done():
            fmt.Println("大会終了です\n")
            foodFighters = append(foodFighters, f1Result)
            foodFighters = append(foodFighters, f2Result)
            foodFighters = append(foodFighters, f3Result)

            return foodFighters
        }
    }
}

func displayResult(results FoodFighters) {
    time.Sleep(1 * time.Second)
    fmt.Println("大会結果発表\n")

    sortedResults := From(results).Sort(func(f1, f2 interface{}) bool {
        return f1.(*FoodFighter).current > f2.(*FoodFighter).current
    }).Results()

    for i, fighter := range sortedResults {
        fmt.Printf("%v位: %v %v個\n", i+1, fighter.(*FoodFighter).name, fighter.(*FoodFighter).current)
    }
}

func main() {
    done := make(chan interface{})
    defer close(done)

    limitTime := 2 * time.Second
    ctx, cancel := context.WithTimeout(context.Background(), limitTime)
    results := competition(ctx, cancel, done)

    displayResult(results)
}

実行結果

:
:
マイケルはホットドッグを417個食べた
ウェイターはホットドッグをフードファイターへ持っていきます。
ウェイターはホットドッグをフードファイターへ持っていきます。
コックはホットドッグを作ります
コックはホットドッグを作ります
ボブはホットドッグを447個食べた
コックはホットドッグを作ります
ビルはホットドッグを442個食べた
ウェイターはホットドッグをフードファイターへ持っていきます。
大会結果発表

1位: ボブ 447個
2位: ビル 442個
3位: マイケル 418個

goではselectのcase文全体に対して 擬似乱数による一様選択1 をしているらしい。
それぞれのcaseは等しく選択されていく。なので、結果は毎回、拮抗した感じになる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

goroutineで大食い大会

はじめに

ゴルーチンとチャネルの勉強として大食い大会を書いてみた。

1)コックはひたすらホットドッグを作る
2)ウェイターはひたすらホットドッグをフードファイターへ運ぶ
3)フードファイターはひたすらホットドッグを食べて、自分が食べたホットドッグをカウントする
4)制限時間になったら大会終了。その時点でのランキングを出す。

この 1 => 2 => 3 の流れをチャネルを使い、4の制限時間の制御にcontextを使った。

コード

package main

import (
    "context"
    "fmt"
    "time"

    . "github.com/ahmetb/go-linq"
)

func cook(
    done <-chan interface{},
    fn func() interface{},
) <-chan interface{} {
    foods := make(chan interface{})
    go func() {
        defer close(foods)
        for {
            select {
            case <-done:
                return
            case foods <- fn():
            }
        }
    }()
    return foods
}

func hotDog() interface{} {
    fmt.Printf("コックはホットドッグを作ります\n")

    return "ホットドッグ"
}

func waiter(foods <-chan interface{}) interface{} {
    food := <-foods
    fmt.Printf("ウェイターは%vをフードファイターへ持っていきます。\n", food)
    return food
}

func waitperson(
    done <-chan interface{},
    foods <-chan interface{},
) <-chan interface{} {
    toFoodFighter := make(chan interface{})
    go func() {
        defer close(toFoodFighter)
        for {
            select {
            case <-done:
                return
            case toFoodFighter <- waiter(foods):
            }
        }
    }()
    return toFoodFighter
}

type FoodFighter struct {
    name    string
    current int
}

type FoodFighters []*FoodFighter

func (f *FoodFighter) eat(foods <-chan interface{}) *FoodFighter {
    food := <-foods
    f.current++
    fmt.Printf("%vは%vを%v個食べた\n", f.name, food, f.current)
    return f
}

func (f *FoodFighter) startEat(
    done <-chan interface{},
    foods <-chan interface{},
) <-chan *FoodFighter {
    results := make(chan *FoodFighter)
    go func() {
        defer close(results)
        for {
            select {
            case <-done:
                return
            case results <- f.eat(foods):
            }
        }
    }()
    return results
}

func start(done <-chan interface{}, f *FoodFighter) <-chan *FoodFighter {
    return f.startEat(done, waitperson(done, cook(done, hotDog)))
}

func competition(ctx context.Context, cancel context.CancelFunc, done <-chan interface{}) FoodFighters {
    defer cancel()
    var foodFighters FoodFighters
    var f1Result *FoodFighter
    var f2Result *FoodFighter
    var f3Result *FoodFighter
    f1 := &FoodFighter{name: "ボブ"}
    f2 := &FoodFighter{name: "ビル"}
    f3 := &FoodFighter{name: "マイケル"}

    for {
        select {
        case f1Result = <-start(done, f1):
        case f2Result = <-start(done, f2):
        case f3Result = <-start(done, f3):
        case <-ctx.Done():
            fmt.Println("大会終了です\n")
            foodFighters = append(foodFighters, f1Result)
            foodFighters = append(foodFighters, f2Result)
            foodFighters = append(foodFighters, f3Result)

            return foodFighters
        }
    }
}

func displayResult(results FoodFighters) {
    time.Sleep(1 * time.Second)
    fmt.Println("大会結果発表\n")

    sortedResults := From(results).Sort(func(f1, f2 interface{}) bool {
        return f1.(*FoodFighter).current > f2.(*FoodFighter).current
    }).Results()

    for i, fighter := range sortedResults {
        fmt.Printf("%v位: %v %v個\n", i+1, fighter.(*FoodFighter).name, fighter.(*FoodFighter).current)
    }
}

func foodFight() {
    done := make(chan interface{})
    defer close(done)

    limitTime := 2 * time.Second
    ctx, cancel := context.WithTimeout(context.Background(), limitTime)
    results := competition(ctx, cancel, done)

    displayResult(results)
}

func main() {
    foodFight()
}

実行結果

:
:
マイケルはホットドッグを417個食べた
ウェイターはホットドッグをフードファイターへ持っていきます。
ウェイターはホットドッグをフードファイターへ持っていきます。
コックはホットドッグを作ります
コックはホットドッグを作ります
ボブはホットドッグを447個食べた
コックはホットドッグを作ります
ビルはホットドッグを442個食べた
ウェイターはホットドッグをフードファイターへ持っていきます。
大会結果発表

1位: ボブ 447個
2位: ビル 442個
3位: マイケル 418個

goではselectのcase文全体に対して 擬似乱数による一様選択1 をしているらしい。
それぞれのcaseは等しく選択されていく。なので、結果は毎回、拮抗した感じになる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む