Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sentieon-bwamem #994

Merged
merged 12 commits into from
Apr 13, 2023
17 changes: 16 additions & 1 deletion .github/workflows/pytest-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ jobs:
tags: vep
- profile: "conda"
tags: concatenate_vcfs
- profile: "conda"
tags: sentieon/bwamem
- profile: "singularity"
tags: merge
- profile: "singularity"
Expand All @@ -62,6 +64,7 @@ jobs:
env:
NXF_ANSI_LOG: false
TEST_DATA_BASE: "${{ github.workspace }}/test-datasets"
SENTIEON_LICENSE_BASE64: ${{ secrets.SENTIEON_LICENSE_BASE64 }}
steps:
- name: Check out pipeline code
uses: actions/checkout@v3
Expand Down Expand Up @@ -108,7 +111,7 @@ jobs:
${{ runner.os }}-pip-

- name: Install Python dependencies
run: python -m pip install --upgrade pip pytest-workflow
run: python -m pip install --upgrade pip pytest-workflow cryptography

- uses: actions/cache@v3
with:
Expand Down Expand Up @@ -136,6 +139,18 @@ jobs:
channels: conda-forge,bioconda,defaults
python-version: ${{ matrix.python-version }}

# Set up secrets
- name: Set up nextflow secrets
if: env.SENTIEON_LICENSE_BASE64 != null
run: |
nextflow secrets set SENTIEON_LICENSE_BASE64 ${{ secrets.SENTIEON_LICENSE_BASE64 }}
nextflow secrets set SENTIEON_AUTH_MECH_BASE64 ${{ secrets.SENTIEON_AUTH_MECH_BASE64 }}
SENTIEON_ENCRYPTION_KEY=$(echo -n "${{ secrets.ENCRYPTION_KEY_BASE64 }}" | base64 -d)
SENTIEON_LICENSE_MESSAGE=$(echo -n "${{ secrets.LICENSE_MESSAGE_BASE64 }}" | base64 -d)
SENTIEON_AUTH_DATA=$(python bin/license_message.py encrypt --key "$SENTIEON_ENCRYPTION_KEY" --message "$SENTIEON_LICENSE_MESSAGE")
SENTIEON_AUTH_DATA_BASE64=$(echo -n "$SENTIEON_AUTH_DATA" | base64 -w 0)
nextflow secrets set SENTIEON_AUTH_DATA_BASE64 $SENTIEON_AUTH_DATA_BASE64

- name: Conda clean
if: matrix.profile == 'conda'
run: conda clean -a
Expand Down
104 changes: 104 additions & 0 deletions bin/license_message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env python3

"""
Functions for generating and sending license messages
"""

# Modified from - https://stackoverflow.com/a/59835994

import argparse
import base64
import calendar
import re
import secrets
import sys

from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from datetime import datetime as dt

MESSAGE_TIMEOUT = 60 * 60 * 24 # Messages are valid for 1 day
NONCE_BYTES = 12


class DecryptionTimeout(Exception):
# Decrypting a message that is too old
pass


def generate_key():
key = secrets.token_bytes(32)
return key


def handle_generate_key(args):
key = generate_key()
key_b64 = base64.b64encode(key)
print(key_b64.decode("utf-8"), file=args.outfile)


def encrypt_message(key, message):
nonce = secrets.token_bytes(NONCE_BYTES)
timestamp = calendar.timegm(dt.now().utctimetuple())
data = timestamp.to_bytes(10, byteorder="big") + b"__" + message
ciphertext = nonce + AESGCM(key).encrypt(nonce, data, b"")
return ciphertext


def handle_encrypt_message(args):
key = base64.b64decode(args.key.encode("utf-8"))
message = args.message.encode("utf-8")
ciphertext = encrypt_message(key, message)
ciphertext_b64 = base64.b64encode(ciphertext)
print(ciphertext_b64.decode("utf-8"), file=args.outfile)


def decrypt_message(key, ciphertext, timeout=MESSAGE_TIMEOUT):
nonce, ciphertext = ciphertext[:NONCE_BYTES], ciphertext[NONCE_BYTES:]
message = AESGCM(key).decrypt(nonce, ciphertext, b"")

msg_timestamp, message = re.split(b"__", message, maxsplit=1)
msg_timestamp = int.from_bytes(msg_timestamp, byteorder="big")
timestamp = calendar.timegm(dt.now().utctimetuple())
if (timestamp - msg_timestamp) > timeout:
raise DecryptionTimeout("The message has an expired timeout")
return message.decode("utf-8")


def handle_decrypt_message(args):
key = base64.b64decode(args.key.encode("utf-8"))
ciphertext = base64.b64decode(args.message.encode("utf-8"))
message = decrypt_message(key, ciphertext, timeout=args.timeout)
print(str(message), file=args.outfile)


def parse_args(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--outfile", default=sys.stdout, type=argparse.FileType("w"), help="The output file")

subparsers = parser.add_subparsers(help="Available sub-commands")

gen_parser = subparsers.add_parser("generate_key", help="Generate a random key string")
gen_parser.set_defaults(func=handle_generate_key)

encrypt_parser = subparsers.add_parser("encrypt", help="Encrypt a message")
encrypt_parser.add_argument("--key", required=True, help="The encryption key")
encrypt_parser.add_argument("--message", required=True, help="Message to encrypt")
encrypt_parser.set_defaults(func=handle_encrypt_message)

decrypt_parser = subparsers.add_parser("decrypt", help="Decyrpt a message")
decrypt_parser.add_argument("--key", required=True, help="The encryption key")
decrypt_parser.add_argument("--message", required=True, help="Message to decrypt")
decrypt_parser.add_argument(
"--timeout",
default=MESSAGE_TIMEOUT,
type=int,
help="A message timeout. Decryption will fail for older messages",
)
decrypt_parser.set_defaults(func=handle_decrypt_message)

return parser.parse_args(argv)


if __name__ == "__main__":
args = parse_args()
args.func(args)
5 changes: 5 additions & 0 deletions conf/modules/aligner.config
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ process {
ext.args = { "--RGSM ${meta.patient}_${meta.sample} --RGID ${meta.read_group}" }
}

withName: "SENTIEON_BWAMEM" {
ext.when = { params.aligner == "sentieon-bwamem" }
ext.args = { "-R ${meta.read_group}" }
}

withName: "(BWAMEM.*_MEM|DRAGMAP_ALIGN)" {
// Markduplicates Spark NEEDS name-sorted reads or runtime goes through the roof
// However if it's skipped, reads need to be coordinate-sorted
Expand Down
2 changes: 1 addition & 1 deletion conf/modules/prepare_genome.config
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
process {

withName: 'BWAMEM1_INDEX' {
ext.when = { !params.bwa && params.step == "mapping" && params.aligner == "bwa-mem" }
ext.when = { !params.bwa && params.step == "mapping" && (params.aligner == "bwa-mem" || params.aligner == "sentieon-bwamem")}
publishDir = [
enabled: (params.save_reference || params.build_only_index),
mode: params.publish_dir_mode,
Expand Down
5 changes: 5 additions & 0 deletions conf/test/test.config
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ params {
}

process {
withLabel: 'sentieon' {
ext.sentieon_auth_mech_base64 = secrets.SENTIEON_AUTH_MECH_BASE64
ext.sentieon_auth_data_base64 = secrets.SENTIEON_AUTH_DATA_BASE64
}

withName:'.*:FREEC_SOMATIC'{
ext.args = {
[
Expand Down
5 changes: 5 additions & 0 deletions modules.json
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,11 @@
"git_sha": "c8e35eb2055c099720a75538d1b8adb3fb5a464c",
"installed_by": ["modules"]
},
"sentieon/bwamem": {
"branch": "master",
"git_sha": "8c159ebb28cfa08890042a133bfab78de932eff7",
"installed_by": ["modules"]
},
"snpeff/download": {
"branch": "master",
"git_sha": "aeefdd170be4583012816212f1345fbebb61ccd6",
Expand Down
72 changes: 72 additions & 0 deletions modules/nf-core/sentieon/bwamem/main.nf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 62 additions & 0 deletions modules/nf-core/sentieon/bwamem/meta.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion nextflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@
"type": "string",
"default": "bwa-mem",
"fa_icon": "fas fa-puzzle-piece",
"enum": ["bwa-mem", "bwa-mem2", "dragmap"],
"enum": ["bwa-mem", "bwa-mem2", "dragmap", "sentieon-bwamem"],
"description": "Specify aligner to be used to map reads to reference genome.",
"help_text": "`Sarek` will build missing indices automatically if not provided. Set `--bwa false` if indices should be (re-)built.\nIf `DragMap` is selected as aligner, it is recommended to skip baserecalibration with `--skip_tools baserecalibrator`. See [here](https://gatk.broadinstitute.org/hc/en-us/articles/4407897446939--How-to-Run-germline-single-sample-short-variant-discovery-in-DRAGEN-mode) for more info.\n",
"hidden": true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// MAPPING
//
// For all modules here:
// A when clause condition is defined in the conf/modules.config to determine if the module should be run

include { BWAMEM2_MEM } from '../../../modules/nf-core/bwamem2/mem/main'
include { BWA_MEM as BWAMEM1_MEM } from '../../../modules/nf-core/bwa/mem/main'
include { DRAGMAP_ALIGN } from '../../../modules/nf-core/dragmap/align/main'
include { SENTIEON_BWAMEM } from '../../../modules/nf-core/sentieon/bwamem/main'

workflow FASTQ_ALIGN_BWAMEM_MEM2_DRAGMAP_SENTIEON {
take:
reads // channel: [mandatory] meta, reads
index // channel: [mandatory] index
sort // boolean: [mandatory] true -> sort, false -> don't sort
fasta
fasta_fai

main:

versions = Channel.empty()
reports = Channel.empty()

// Only one of the following should be run
BWAMEM1_MEM(reads, index.map{ it -> [ [ id:'index' ], it ] }, sort) // If aligner is bwa-mem
BWAMEM2_MEM(reads, index.map{ it -> [ [ id:'index' ], it ] }, sort) // If aligner is bwa-mem2
DRAGMAP_ALIGN(reads, index.map{ it -> [ [ id:'index' ], it ] }, sort) // If aligner is dragmap
SENTIEON_BWAMEM(reads, index.map{ it -> [ [ id:'index' ], it ] }, fasta, fasta_fai) // The sentieon-bwamem-module does sorting as part of the conversion from sam to bam.

// Get the bam files from the aligner
// Only one aligner is run
bam = Channel.empty()
bam = bam.mix(BWAMEM1_MEM.out.bam)
bam = bam.mix(BWAMEM2_MEM.out.bam)
bam = bam.mix(DRAGMAP_ALIGN.out.bam)
bam = bam.mix(SENTIEON_BWAMEM.out.bam_and_bai.map{ meta, bam, bai -> [ meta, bam ] })

// Gather reports of all tools used
reports = reports.mix(DRAGMAP_ALIGN.out.log)

// Gather versions of all tools used
versions = versions.mix(BWAMEM1_MEM.out.versions)
versions = versions.mix(BWAMEM2_MEM.out.versions)
versions = versions.mix(DRAGMAP_ALIGN.out.versions)
versions = versions.mix(SENTIEON_BWAMEM.out.versions)

emit:
bam // channel: [ [meta], bam ]
reports
versions // channel: [ versions.yml ]
}
Loading