Edited at

Conformance test #1 (Docker & 出力パラメータ編)


全体の構成



  • 前振り編


    • #1 でテストされる入力ファイルおよび期待される出力の説明




  • 実行コマンド生成編


    • #1 で実行されるコマンドを CWL ファイルから生成する方法の解説



  • Docker & 出力パラメータ編 (いまここ)


    • 実行コマンド生成編では取り扱わなかった、Docker 上でコマンドを動かす場合の解説および、出力パラメータの補足方法の解説



また、ダイジェスト版の説明スライド(英語)もあります。


注意!

この記事は CWL に対応したワークフローエンジンを実装する人向けの記事です!

CWL を書いて利用する一般の方は「中の人は大変そうだなぁ」と思いつつ、お茶でも飲みながら軽い気持ちで読んでいただければ幸いです。

実装する人は心を強く持ちましょう。


つづき

昨日の記事では conformance test #1 で記述されたツールの実行コマンドの生成について解説しました。

本日は、昨日説明していなかった DockerRequirement と出力オブジェクトの捕捉ルールについて解説します。


昨日のおさらい

これと


v1.0/bwa-mem-tool.cwl

#!/usr/bin/env cwl-runner

cwlVersion: v1.0

class: CommandLineTool

hints:
- class: ResourceRequirement
coresMin: 2
- class: DockerRequirement
dockerPull: python:2-slim

inputs:
- id: reference
type: File
inputBinding: { position: 2 }

- id: reads
type:
type: array
items: File
inputBinding: { position: 3 }

- id: minimum_seed_length
type: int
inputBinding: { position: 1, prefix: -m }

- id: min_std_max_min
type: { type: array, items: int }
inputBinding:
position: 1
prefix: -I
itemSeparator: ","

- id: args.py
type: File
default:
class: File
location: args.py
inputBinding:
position: -1

outputs:
- id: sam
type: ["null", File]
outputBinding: { glob: output.sam }
- id: args
type:
type: array
items: string

baseCommand: python

arguments:
- bwa
- mem
- valueFrom: $(runtime.cores)
position: 1
prefix: -t

stdout: output.sam


これが


v1.0/bwa-mem-job.json

{

"reference": {
"class": "File",
"location": "chr20.fa",
"size": 123,
"checksum": "sha1$hash"
},
"reads": [
{
"class": "File",
"location": "example_human_Illumina.pe_1.fastq"
},
{
"class": "File",
"location": "example_human_Illumina.pe_2.fastq"
}
],
"min_std_max_min": [
1,
2,
3,
4
],
"minimum_seed_length": 3
}

こうなった。

$ python args.py bwa mem -t 2 -I 1,2,3,4 -m 3 chr20.fa example_human_Illumina.pe_1.fastq example_human_Illumina.pe_2.fastq > $PWD/output.sam

$ git status
...
Untracked files:
...
cwl.output.json
...
$ cat cwl.output.json
{"args": ["bwa", "mem", "-t", "2", "-I", "1,2,3,4", "-m", "3", "chr20.fa", "example_human_Illumina.pe_1.fastq", "example_human_Illumina.pe_2.fastq"]}

その結果、以下が余った。


v1.0/bwa-mem-tool.cwl

hints:

...
- class: DockerRequirement
dockerPull: python:2-slim
...
outputs:
- id: sam
type: ["null", File]
outputBinding: { glob: output.sam }
- id: args
type:
type: array
items: string
...


DockerRequirement

3.3 節に記載があるように、hints にあるものは無視しても仕様上は問題無いため、実行コマンドは前回の最終版でも問題ありません。今回は DockerRequirement をちゃんと処理する場合を考えます。


v1.0/bwa-mem-tool.cwl

hints:

...
- class: DockerRequirement
dockerPull: python:2-slim

仕様の5.5 節に以下の記述があります。


The platform must execute the tool in the container using docker run with the appropriate Docker image and tool command line.

The workflow platform may provide input files and the designated output directory through the use of volume bind mounts.


Docker 上でコマンドを実行するために、以下の処理が必要です。


  • 各引数の File オブジェクトを volume mount します。



    • File オブジェクトから生成された引数は、args.pychr20.faexample_human_Illumina.pe_1.fastqexample_human_Illumina.pe_2.fastq の4種類です。それぞれ -v で、docker コンテナ内からアクセスできるようにします。



$ docker run -it --rm -v $PWD/v1.0/args.py:/staged/args.py:ro -v $PWD/v1.0/chr20.fa:/staged/chr20.fa:ro -v $PWD/v1.0/example_human_Illumina.pe_1.fastq:/staged/example_human_Illumina.pe_1.fastq:ro -v $PWD/v1.0/example_human_Illumina.pe_2.fastq:/staged/example_human_Illumina.pe_2.fastq:ro python:2-slim ...


  • 最初に示したように、args.py はカレントディレクトリに cwl.output.json を出力します。docker run 内で出力したファイルは、何もしなければ虚空の彼方に消えてしまうので、出力ディレクトリを volume mount する必要があります。4.2 節を見ると、出力ディレクトリは HOME として参照できないといけないため、-e オプションを用いて HOME を特定のディレクトリ(docker 内の場合は適当なディレクトリで問題ありません)を指すようにします。また、HOME で指定したディレクトリを volume mount で docker 外から参照できるようにします。最後に、--workdir でワーキングディレクトリを指定します。今回は -e HOME=/output -v $PWD:/output:rw --workdir=/output を追加して、これらを行えるようにします。

$ pwd

/Users/tom-tan/common-workflow-language/v1.0
$ docker run -it --rm -e HOME=/output -v $PWD:/output:rw --workdir=/output -v $PWD/v1.0/args.py:/staged/args.py:ro -v $PWD/v1.0/chr20.fa:/staged/chr20.fa:ro -v $PWD/v1.0/example_human_Illumina.pe_1.fastq:/staged/example_human_Illumina.pe_1.fastq:ro -v $PWD/v1.0/example_human_Illumina.pe_2.fastq:/staged/example_human_Illumina.pe_2.fastq:ro python:2-slim python /staged/args.py bwa mem -t 2 -I 1,2,3,4 -m 3 /staged/chr20.fa /staged/example_human_Illumina.pe_1.fastq /staged/example_human_Illumina.pe_2.fastq > $PWD/output.sam


出力オブジェクトの補足


v1.0/bwa-mem-tool.cwl

outputs:

- id: sam
type: ["null", File]
outputBinding: { glob: output.sam }
- id: args
type:
type: array
items: string

上を見ると、samargs の二種類の出力オブジェクトがあることがわかります。

sam の型の ["null", File] については、Issue 605 を確認することで、「"null" 型か File 型のどちらかを取る型」(Issue 中では union type)であることがわかります。

5.2.3 節を参照すると、outputBinding.glob で指定したファイル名 output.sam がある場合には、その output.sam を表す File オブジェクトが返ることがわかります1。今回はコマンドの標準出力が output.sam にリダイレクトされるので、このファイルを表す File オブジェクトが(その中身の有無にかかわらず) sam として返る…

と思うけどそうはならないのが conformance test #1!


出力オブジェクトの説明をガン無視する cwl.output.json に関する仕様

仕様をよく読むと、4.4 節cwl.output.json に関する記述があります。曰く:


If the output directory contains a file named "cwl.output.json", that file must be loaded and used as the output object.


つまり、

$ cat cwl.output.json

{"args": ["bwa", "mem", "-t", "2", "-I", "1,2,3,4", "-m", "3", "chr20.fa", "example_human_Illumina.pe_1.fastq", "example_human_Illumina.pe_2.fastq"]}

この内容がこのまま出力オブジェクトになります。glob をどう解釈するとかこのテストには全く関係ありません。

最終的に出力オブジェクトは、cwl.output.json の中身である

{

"args": [
"bwa",
"mem",
"-t",
"2",
"-I",
"1,2,3,4",
"-m",
"3",
"chr20.fa",
"example_human_Illumina.pe_1.fastq",
"example_human_Illumina.pe_2.fastq"
]
}

となります。値が null になっているフィールドは省略できる2ため、sam フィールドは出力オブジェクトには現れません。


まとめ

大変でしたね!

CWL でツール定義を書く人や、他の人から CWL ファイルを受け取って実行する人は、上記を気にしなくてもツールやワークフローの実行が可能です。全て処理系が善きにはからってくれます。

処理系を作る人は頑張りましょう!

…個人的には、最初は単純な ExpressionTool (#14 や #15) から始めるのがとっつきやすいと思います。





  1. conformance test #1 の意図としては、output.sam がない場合には "null" 型の唯一の値である null が返る気がしますが、その点についてはどこにも記載がありません… 



  2. これも仕様に無い(報告済み