stepCIのOpenAPI定義インポートを試してみた
概要
OpenAPI定義書ドリブンで開発を進めるケースもあり、stepCIワークフローとgoのechoのコードを自動生成して試したメモ。
やってみた
OpenAPI定義の作成
簡単なGETとPOSTの定義を作成します。
公式の下記記載を参考に、バージョンは3.0.3で作成します。
WARNING
Only OpenAPI 3.0 and higher is supported. Prepare your OpenAPI spec before > running the command. Add request and response examples, default values and > specify required params
https://docs.stepci.com/import/openapi.html
version: "1.0"
name: step-ci-testing
config:
http:
baseURL: http://localhost:8888
tests:
morphs:
steps:
- id: getMorphs
http:
url: /morphs
method: GET
check:
status: 200
schema:
$ref: "#/components/schemas/Morphs"
- id: postMorph
http:
url: /morphs
method: POST
headers:
Content-Type: application/json
accept: application/json
json:
name: sed magna in
check:
status: 200
schema:
$ref: "#/components/schemas/Morph"
- id: getMorphById
http:
url: /morphs/1
method: GET
check:
status: 200
schema:
$ref: "#/components/schemas/Morph"
components:
schemas:
Morph:
allOf:
- type: object
required:
- id
properties:
id:
type: integer
example: 1
- $ref: "#/components/schemas/NewMorph"
NewMorph:
type: object
required:
- name
properties:
name:
type: string
example: Normal
Morphs:
type: array
items:
$ref: "#/components/schemas/Morph"
Id:
type: integer
example: 1
API実装
oapi-codegenを利用します。
$ go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@latest
oapi-codegenを実行するgo generate用のファイルgen/gen.go
を作成します。
//go:generate oapi-codegen -package oapi -include-tags "morphs" -o ../oapi/morphs.gen.go ../openapi.yml
package main
func main() {}
実行してmorphs.gen.goを生成します。
$ go generate ./gen
生成コードを参照しmain.goを実装します。
JSONファイルをDBとして読み書きするAPIとして簡易に実装します。
package main
import (
"encoding/json"
"io/ioutil"
"net/http"
"os"
"github.com/labstack/echo/v4"
"step-ci-testing/oapi"
)
// ServerInterfaceを満たすよう実装
type controller struct {}
func (c controller) GetMorphs(ctx echo.Context) error {
morphs := readData("data.json")
return ctx.JSON(http.StatusOK, &morphs)
}
func (c controller) PostMorph(ctx echo.Context) error {
newMorph := new(oapi.NewMorph)
ctx.Bind(newMorph)
if len(newMorph.Name) > 64 {
resp := &oapi.BadRequest{"Invalid value"}
return ctx.JSON(http.StatusBadRequest, resp)
}
morph := writeData("data.json", newMorph)
return ctx.JSON(http.StatusOK, &morph)
}
func (c controller) GetMorphById(ctx echo.Context, id oapi.Id) error {
morphs := readData("data.json")
for _, m := range morphs {
if m.Id == id {
return ctx.JSON(http.StatusOK, &m)
}
}
resp := &oapi.NotFound{"Not found"}
return ctx.JSON(http.StatusNotFound, &resp)
}
// dataaccess
func readData(filepath string) oapi.Morphs {
data, _ := ioutil.ReadFile(filepath)
var morphs oapi.Morphs
json.Unmarshal(data, &morphs)
return morphs
}
func writeData(filepath string, newMorph *oapi.NewMorph) oapi.Morph {
morphs := readData(filepath)
id := len(morphs) + 1
morph := oapi.Morph{
Id: id,
Name: newMorph.Name,
}
morphs = append(morphs, morph)
f, err := os.OpenFile(filepath, os.O_WRONLY, 0666)
if err != nil {
panic(err)
}
defer f.Close()
if err = json.NewEncoder(f).Encode(morphs); err != nil {
panic(err)
}
return morph
}
func main() {
e := echo.New()
c := controller{}
oapi.RegisterHandlers(e, c)
e.Logger.Fatal(e.Start(":8888"))
}
[]
stepciインストールとファイル生成
Homebrewでインストールします。
$ brew install stepci
ワークフローファイル生成コマンドのstepci generateのヘルプを確認します。
対象のopenapi定義ファイルと生成先ファイルを引数で順に渡せばいいようです。
$ stepci generate --help
ⓘ Anonymous usage data collected. Learn more on https://step.ci/privacy
stepci generate [spec] [path]
generate workflow from OpenAPI spec
位置:
spec openapi file url [文字列] [デフォルト: "openapi.json"]
path output file path
...
生成コマンドをgen/gen.goに追記し、実行して生成します。(2行目が追記箇所)
//go:generate oapi-codegen -package oapi -include-tags "morphs" -o ../oapi/morphs.gen.go ../openapi.yml
//go:generate stepci generate ../openapi.yml ../workflow.yml
package main
func main() {}
$ go generate ./gen
ⓘ Anonymous usage data collected. Learn more on https://step.ci/privacy
Success! The workflow file can be found at ./workflow.yml
Give us your feedback on https://step.ci/feedback
workflow.ymlが以下の通り生成されました。
各リクエストでopenapiで定義したステータスとレスポンスボディが得られるかテストしてくれそうです。
stepsのgetMorphByIdのパスパラメータについては、openapi定義の該当パラメータのexample値が利用されます。未指定の場合はランダム生成の値になるため注意が必要そうです。
version: "1.0"
name: step-ci-testing
config:
http:
baseURL: http://localhost:8888
tests:
morphs:
steps:
- id: getMorphs
http:
url: /morphs
method: GET
check:
status: 200
schema:
$ref: "#/components/schemas/Morphs"
- id: postMorph
http:
url: /morphs
method: POST
headers:
Content-Type: application/json
accept: application/json
json:
name: sed magna in
check:
status: 200
schema:
$ref: "#/components/schemas/Morph"
- id: getMorphById
http:
url: /morphs/1
method: GET
check:
status: 200
schema:
$ref: "#/components/schemas/Morph"
components:
schemas:
Morph:
allOf:
- type: object
required:
- id
properties:
id:
type: integer
example: 1
- $ref: "#/components/schemas/NewMorph"
NewMorph:
type: object
required:
- name
properties:
name:
type: string
example: Normal
Morphs:
type: array
items:
$ref: "#/components/schemas/Morph"
Id:
type: integer
example: 1
テスト
サーバを起動します。
$ go run ./main.go
____ __
/ __/___/ / ___
/ _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.9.0
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
O\
⇨ http server started on [::]:8888
stepciテストを実行します。
無事テストが通りました!
$ stepci run ./workflow.yml
ⓘ Anonymous usage data collected. Learn more on https://step.ci/privacy
PASS morphs
Tests: 0 failed, 1 passed, 1 total
Steps: 0 failed, 0 skipped, 3 passed, 3 total
Time: 0.112s, estimated 0s
CO2: 0.00007g
Workflow passed after 0.112s
Give us your feedback on https://step.ci/feedback
所感
最低限のテストとして簡単に生成できるのはとても良いなと思う反面、異常系については結局自分でworkflowを書く必要があるので、規模が大きくなるにつれ自動生成は使わなくなるかもと思いました。
複数のworkflowをincludeして実行といったこともできるようなので、自動生成した正常系と手動作成の異常系の2ファイルで運用もありですかね。
-> workflow.ymlをincludeするworkflow_main.ymlを作成してみたが、エラーで実行できず。詳細はちょっと調べましたがわかっておらずです。
version: "1.0"
name: step-ci-testing
include:
- workflow.yml
エラー
$ stepci run ./workflow_main.yml
ⓘ Anonymous usage data collected. Learn more on https://step.ci/privacy
FAIL morphs
Summary
✕ getMorphs failed after 0.065s
⚠︎ postMorph skipped after 0s
⚠︎ getMorphById skipped after 0s
⚠︎ morphs › undefined
can't resolve reference #/components/schemas/Morphs from id #
⚠︎ morphs › undefined
Step was skipped because previous one failed
⚠︎ morphs › undefined
Step was skipped because previous one failed
Tests: 1 failed, 0 passed, 1 total
Steps: 3 failed, 2 skipped, 0 passed, 3 total
Time: 0.095s, estimated 0s
CO2: 0.00007g
Workflow failed after 0.095s
Give us your feedback on https://step.ci/feedback
Discussion