diff --git a/.github/actions/nf-test-action/action.yml b/.github/actions/nf-test-action/action.yml index 799e5db0..fecad350 100644 --- a/.github/actions/nf-test-action/action.yml +++ b/.github/actions/nf-test-action/action.yml @@ -66,11 +66,17 @@ runs: nextflow secrets set SENTIEON_AUTH_DATA $(python3 tests/modules/nf-core/sentieon/license_message.py encrypt --key "$SENTIEON_ENCRYPTION_KEY" --message "$SENTIEON_LICENSE_MESSAGE") - name: Set up nextflow ONCOKB secrets - if: env.ONCOKB_TOKEN != '' + if: env.ONCOKB_TOKEN != '' shell: bash run: | nextflow secrets set ONCOKB_TOKEN $ONCOKB_TOKEN + - name: Set up nextflow ACELA secrets + # acela/bampatientid and acela/bamsampleid only run stub tests in CI (the Acela API is internal-only) + shell: bash + run: | + nextflow secrets set ACELA_TOKEN stub-placeholder + # TODO Skip failing conda tests and document their failures # https://github.com/nf-core/modules/issues/7017 diff --git a/.github/skip_nf_test.json b/.github/skip_nf_test.json index 633dc3e7..928d1b6b 100644 --- a/.github/skip_nf_test.json +++ b/.github/skip_nf_test.json @@ -33,6 +33,8 @@ "modules/msk/pvmaf/concat", "modules/msk/pvmaf/tagtraceback", "modules/msk/oncokb/mafannotate", + "modules/msk/acela/bampatientid", + "modules/msk/acela/bamsampleid", "subworkflows/msk/genome_nexus", "subworkflows/msk/netmhcstabandpan", "subworkflows/msk/phylowgs", diff --git a/modules/msk/acela/bampatientid/environment.yml b/modules/msk/acela/bampatientid/environment.yml new file mode 100644 index 00000000..a6cb12f0 --- /dev/null +++ b/modules/msk/acela/bampatientid/environment.yml @@ -0,0 +1,10 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + # acela-cli is a private MSKCC package, not available on Bioconda/PyPI. + # Install it into the container image via: + # pip install git+ssh://git@github.com/mskcc/acela-cli.git + - "YOUR-TOOL=HERE" diff --git a/modules/msk/acela/bampatientid/main.nf b/modules/msk/acela/bampatientid/main.nf new file mode 100644 index 00000000..e630caa2 --- /dev/null +++ b/modules/msk/acela/bampatientid/main.nf @@ -0,0 +1,58 @@ +process ACELA_BAMPATIENTID { + tag "$meta.id" + label 'process_single' + + // acela-cli authenticates to the internal MSKCC Acela/Voyager API with a session token. + // Before running a pipeline that uses this module, obtain one (`acela login`) + secret 'ACELA_TOKEN' + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'ghcr.io/mskcc-omics-workflows/acela_cli:0.1.0-amd64': + 'ghcr.io/mskcc-omics-workflows/acela_cli:0.1.0-amd64' }" + + input: + tuple val(meta), val(patient_ids) + + output: + tuple val(meta), path("*.acela_bam.tsv"), emit: tsv + path "versions.yml", emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + if (!(meta.id_type in ['cmo', 'dmp'])) { + error "ACELA_BAMPATIENTID: meta.id_type must be 'cmo' or 'dmp', got '${meta.id_type}'" + } + def subcommand = meta.id_type == 'dmp' ? 'by-dmp-id' : 'by-cmo-id' + def ids = (patient_ids instanceof List ? patient_ids : [patient_ids]).join(' ') + def sep_arg = meta.sep ? "--sep '${meta.sep}'" : '' + + """ + acela bam ${subcommand} \\ + ${ids} \\ + --output ${prefix}.acela_bam.tsv \\ + ${sep_arg} \\ + $args + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + acela-cli: \$(echo \$(acela --version) | sed -e "s/acela-cli //g") + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + touch ${prefix}.acela_bam.tsv + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + acela-cli: \$(echo \$(acela --version) | sed -e "s/acela-cli //g") + END_VERSIONS + """ +} diff --git a/modules/msk/acela/bampatientid/meta.yml b/modules/msk/acela/bampatientid/meta.yml new file mode 100644 index 00000000..2c3e8c7d --- /dev/null +++ b/modules/msk/acela/bampatientid/meta.yml @@ -0,0 +1,52 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "acela_bampatientid" +description: Look up mirrored BAM file paths and sample metadata by CMO or DMP patient ID using the Acela CLI. +keywords: + - acela + - bam + - mirror + - mskcc +tools: + - "acela-cli": + description: "Command-line client for the Acela BAM mirror API. Looks up BAM files by patient/sample ID and returns tab-delimited or JSON results. Requires network access to the internal MSKCC Acela/Voyager endpoint and a valid session token. Before running a pipeline that uses this module, obtain a token (`acela login`, or set ACELA_USER/ACELA_PASSWORD once) and register it locally with `nextflow secrets set ACELA_TOKEN ` -- Nextflow injects it into the task environment at runtime." + homepage: "https://github.com/mskcc/acela-cli" + documentation: "https://github.com/mskcc/acela-cli" + licence: ["unspecified (private MSKCC repository)"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information, plus an `id_type` key selecting which Acela + lookup to perform, and an optional `sep` key selecting the output field delimiter + ("tsv" (default), "csv", "\s" for space, or any literal character, e.g. "|"). + e.g. `[ id:'patient1', id_type:'cmo' ]` or `[ id:'patient1', id_type:'dmp', sep:'csv' ]` + - patient_ids: + type: list + description: | + One or more patient identifiers to look up, matching `meta.id_type`: CMO patient IDs + (e.g. `C-ABCDEF`) when `id_type` is `cmo`, or DMP patient IDs (e.g. `P-0001234`) when + `id_type` is `dmp`. A single String is also accepted for a one-off lookup. +output: + tsv: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'patient1', id_type:'cmo' ]` + - "*.acela_bam.tsv": + type: file + description: Tab-delimited mirrored BAM paths and sample metadata returned by the Acela API. + pattern: "*.acela_bam.tsv" + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: "http://edamontology.org/format_3750" # YAML +authors: + - "@buehlere" +maintainers: + - "@buehlere" diff --git a/modules/msk/acela/bampatientid/tests/main.nf.test b/modules/msk/acela/bampatientid/tests/main.nf.test new file mode 100644 index 00000000..487e0766 --- /dev/null +++ b/modules/msk/acela/bampatientid/tests/main.nf.test @@ -0,0 +1,64 @@ +// nf-core modules test acela/bampatientid +nextflow_process { + + name "Test Process ACELA_BAMPATIENTID" + script "../main.nf" + process "ACELA_BAMPATIENTID" + + tag "modules" + tag "modules_msk" + tag "acela" + tag "acela/bampatientid" + + // acela-cli talks to a live, internal-only MSKCC endpoint and requires a real session + // token, so it cannot run against fixture data in CI. Stub-only test per the MSK + // contributing guide's allowance for modules that can't run on small test data. + test("cmo patient id - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'patient1', id_type:'cmo' ], // meta map + ['C-ABCDEF', 'C-GHIJKL'] + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("dmp patient id - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'patient2', id_type:'dmp' ], // meta map + 'P-0001234' + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/msk/acela/bampatientid/tests/main.nf.test.snap b/modules/msk/acela/bampatientid/tests/main.nf.test.snap new file mode 100644 index 00000000..1e2c23d9 --- /dev/null +++ b/modules/msk/acela/bampatientid/tests/main.nf.test.snap @@ -0,0 +1,72 @@ +{ + "dmp patient id - stub": { + "content": [ + { + "0": [ + [ + { + "id": "patient2", + "id_type": "dmp" + }, + "patient2.acela_bam.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,b92b4a337b21c1a5682425e69fcf0b20" + ], + "tsv": [ + [ + { + "id": "patient2", + "id_type": "dmp" + }, + "patient2.acela_bam.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,b92b4a337b21c1a5682425e69fcf0b20" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.3" + }, + "timestamp": "2026-08-14T10:34:19.513344" + }, + "cmo patient id - stub": { + "content": [ + { + "0": [ + [ + { + "id": "patient1", + "id_type": "cmo" + }, + "patient1.acela_bam.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,b92b4a337b21c1a5682425e69fcf0b20" + ], + "tsv": [ + [ + { + "id": "patient1", + "id_type": "cmo" + }, + "patient1.acela_bam.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,b92b4a337b21c1a5682425e69fcf0b20" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.3" + }, + "timestamp": "2026-08-14T10:34:15.706525" + } +} \ No newline at end of file diff --git a/modules/msk/acela/bamsampleid/environment.yml b/modules/msk/acela/bamsampleid/environment.yml new file mode 100644 index 00000000..a6cb12f0 --- /dev/null +++ b/modules/msk/acela/bamsampleid/environment.yml @@ -0,0 +1,10 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + # acela-cli is a private MSKCC package, not available on Bioconda/PyPI. + # Install it into the container image via: + # pip install git+ssh://git@github.com/mskcc/acela-cli.git + - "YOUR-TOOL=HERE" diff --git a/modules/msk/acela/bamsampleid/main.nf b/modules/msk/acela/bamsampleid/main.nf new file mode 100644 index 00000000..16df26f8 --- /dev/null +++ b/modules/msk/acela/bamsampleid/main.nf @@ -0,0 +1,53 @@ +process ACELA_BAMSAMPLEID { + tag "$meta.id" + label 'process_single' + + // acela-cli authenticates to the internal MSKCC Acela/Voyager API with a session token. + // Before running a pipeline that uses this module, obtain one (`acela login`) + secret 'ACELA_TOKEN' + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'ghcr.io/mskcc-omics-workflows/acela_cli:0.1.0-amd64': + 'ghcr.io/mskcc-omics-workflows/acela_cli:0.1.0-amd64' }" + + input: + tuple val(meta), val(dmp_sample_ids) + + output: + tuple val(meta), path("*.acela_bam.tsv"), emit: tsv + path "versions.yml", emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def ids = (dmp_sample_ids instanceof List ? dmp_sample_ids : [dmp_sample_ids]).join(' ') + def sep_arg = meta.sep ? "--sep '${meta.sep}'" : '' + + """ + acela bam by-sample-id \\ + ${ids} \\ + --output ${prefix}.acela_bam.tsv \\ + ${sep_arg} \\ + $args + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + acela-cli: \$(echo \$(acela --version) | sed -e "s/acela-cli //g") + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + touch ${prefix}.acela_bam.tsv + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + acela-cli: \$(echo \$(acela --version) | sed -e "s/acela-cli //g") + END_VERSIONS + """ +} diff --git a/modules/msk/acela/bamsampleid/meta.yml b/modules/msk/acela/bamsampleid/meta.yml new file mode 100644 index 00000000..6f56291d --- /dev/null +++ b/modules/msk/acela/bamsampleid/meta.yml @@ -0,0 +1,51 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "acela_bamsampleid" +description: Look up mirrored BAM file paths and sample metadata by DMP sample ID using the Acela CLI. +keywords: + - acela + - bam + - mirror + - mskcc +tools: + - "acela-cli": + description: "Command-line client for the Acela BAM mirror API. Looks up BAM files by patient/sample ID and returns tab-delimited or JSON results. Requires network access to the internal MSKCC Acela/Voyager endpoint and a valid session token. Before running a pipeline that uses this module, obtain a token (`acela login`, or set ACELA_USER/ACELA_PASSWORD once) and register it locally with `nextflow secrets set ACELA_TOKEN ` -- Nextflow injects it into the task environment at runtime." + homepage: "https://github.com/mskcc/acela-cli" + documentation: "https://github.com/mskcc/acela-cli" + licence: ["unspecified (private MSKCC repository)"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information, plus an optional `sep` key selecting the + output field delimiter ("tsv" (default), "csv", "\s" for space, or any literal + character, e.g. "|"). + e.g. `[ id:'sample1' ]` or `[ id:'sample1', sep:'csv' ]` + - dmp_sample_ids: + type: list + description: | + One or more DMP sample IDs to look up, e.g. `P-0001234-T01-IM6`. A single String is + also accepted for a one-off lookup. +output: + tsv: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*.acela_bam.tsv": + type: file + description: Tab-delimited mirrored BAM paths and sample metadata returned by the Acela API. + pattern: "*.acela_bam.tsv" + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: "http://edamontology.org/format_3750" # YAML +authors: + - "@buehlere" +maintainers: + - "@buehlere" diff --git a/modules/msk/acela/bamsampleid/tests/main.nf.test b/modules/msk/acela/bamsampleid/tests/main.nf.test new file mode 100644 index 00000000..0e078c7b --- /dev/null +++ b/modules/msk/acela/bamsampleid/tests/main.nf.test @@ -0,0 +1,40 @@ +// nf-core modules test acela/bamsampleid +nextflow_process { + + name "Test Process ACELA_BAMSAMPLEID" + script "../main.nf" + process "ACELA_BAMSAMPLEID" + + tag "modules" + tag "modules_msk" + tag "acela" + tag "acela/bamsampleid" + + // acela-cli talks to a live, internal-only MSKCC endpoint and requires a real session + // token, so it cannot run against fixture data in CI. Stub-only test per the MSK + // contributing guide's allowance for modules that can't run on small test data. + test("dmp sample id - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'sample1' ], // meta map + 'P-0001234-T01-IM6' + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/msk/acela/bamsampleid/tests/main.nf.test.snap b/modules/msk/acela/bamsampleid/tests/main.nf.test.snap new file mode 100644 index 00000000..870c94ec --- /dev/null +++ b/modules/msk/acela/bamsampleid/tests/main.nf.test.snap @@ -0,0 +1,35 @@ +{ + "dmp sample id - stub": { + "content": [ + { + "0": [ + [ + { + "id": "sample1" + }, + "sample1.acela_bam.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,802f9377284341242a8963ef2bf8c1c9" + ], + "tsv": [ + [ + { + "id": "sample1" + }, + "sample1.acela_bam.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,802f9377284341242a8963ef2bf8c1c9" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.3" + }, + "timestamp": "2026-08-14T10:34:23.817821" + } +} \ No newline at end of file