diff --git a/docs/workflow_users/workflow_deployment.rst b/docs/workflow_users/workflow_deployment.rst index 18b8f27..18cd778 100644 --- a/docs/workflow_users/workflow_deployment.rst +++ b/docs/workflow_users/workflow_deployment.rst @@ -41,6 +41,24 @@ Further, the workflow definition Snakefile can be arbitrarily extended and modif It is highly advisable to put the deployed workflow into a new (perhaps private) git repository (e.g., see `here `_ for instructions how to do that with Github). +If you want to pin the deployment to an exact commit (e.g. for full reproducibility independent of any future changes to a branch or tag), use ``--commit`` instead of ``--tag``/``--branch``: + +.. code-block:: console + + $ snakedeploy deploy-workflow https://github.com/snakemake-workflows/dna-seq-varlociraptor /tmp/dest --commit 87709354b54391aee5dbb01a64cacfc20aed5ec3 + +This will generate a module declaration pinned to that exact commit: + +.. code-block:: python + + module dna_seq_varlociraptor: + snakefile: + github("snakemake-workflows/dna-seq-varlociraptor", path="workflow/Snakefile", commit="87709354b54391aee5dbb01a64cacfc20aed5ec3") + config: + config + +Note that ``--commit``, ``--tag``, and ``--branch`` are mutually exclusive: exactly one of them has to be specified. + For more options and details, run .. code-block:: console diff --git a/snakedeploy/client.py b/snakedeploy/client.py index 7700697..91a0f42 100644 --- a/snakedeploy/client.py +++ b/snakedeploy/client.py @@ -78,16 +78,23 @@ def get_parser(): help="Path to create the deploying workflow in.", ) - deploy_workflow_parser.add_argument( + ref_group = deploy_workflow_parser.add_mutually_exclusive_group(required=True) + + ref_group.add_argument( "--tag", help="Git tag to deploy from (e.g. a certain release).", ) - deploy_workflow_parser.add_argument( + ref_group.add_argument( "--branch", help="Git branch to deploy from.", ) + ref_group.add_argument( + "--commit", + help="Git commit (SHA) to deploy from and pin the resulting module to.", + ) + deploy_group.add_argument( "--name", help="The name for the module in the resulting Snakefile (default: repository name).", @@ -290,13 +297,12 @@ def help(return_code=0): try: if args.subcommand == "deploy-workflow": - if not (args.tag or args.branch): - raise UserError("Please specify either --tag or --branch") deploy( args.repo, name=args.name, tag=args.tag, branch=args.branch, + commit=args.commit, dest_path=Path(args.dest), force=args.force, ) diff --git a/snakedeploy/deploy.py b/snakedeploy/deploy.py index cd82b15..5b3655d 100644 --- a/snakedeploy/deploy.py +++ b/snakedeploy/deploy.py @@ -19,6 +19,7 @@ def __init__( dest: Path, tag: str | None = None, branch: str | None = None, + commit: str | None = None, force=False, ): self.provider = get_provider(source) @@ -28,6 +29,7 @@ def __init__( self._cloned = None self.tag = tag self.branch = branch + self.commit = commit def __enter__(self): return self @@ -137,7 +139,9 @@ def repo_clone(self): logger.info("Obtaining source repository...") self._cloned = tempfile.TemporaryDirectory() self.provider.clone(self._cloned.name) - if self.tag is not None: + if self.commit is not None: + self.provider.checkout(self._cloned.name, self.commit) + elif self.tag is not None: self.provider.checkout(self._cloned.name, self.tag) elif self.branch is not None: self.provider.checkout(self._cloned.name, self.branch) @@ -225,7 +229,7 @@ def deploy_snakefile(self, tmpdir: str, name: str): module_deployment = template.render( name=name, snakefile=self.provider.get_source_file_declaration( - snakefile, self.tag, self.branch + snakefile, self.tag, self.branch, self.commit ), repo=self.provider.source_url, config=config, @@ -250,6 +254,7 @@ def deploy( tag: str | None, branch: str | None, dest_path: Path, + commit: str | None = None, force=False, ): """ @@ -271,8 +276,27 @@ def deploy( force=True ) + Instead of a tag or branch, a specific commit can be pinned (the three + are mutually exclusive): + + .. code-block:: python + + from snakedeploy.deploy import deploy + deploy( + "https://github.com/snakemake-workflows/dna-seq-varlociraptor", + dest_path="/tmp/dest", + name="dna_seq", + commit="a1b2c3d4e5f6...", + force=True + ) + """ with WorkflowDeployer( - source=source_url, dest=dest_path, tag=tag, branch=branch, force=force + source=source_url, + dest=dest_path, + tag=tag, + branch=branch, + commit=commit, + force=force, ) as sd: sd.deploy(name=name) diff --git a/snakedeploy/providers.py b/snakedeploy/providers.py index 96a4963..22c3d7f 100644 --- a/snakedeploy/providers.py +++ b/snakedeploy/providers.py @@ -71,7 +71,9 @@ def get_raw_file(self, path: str, tag: str): ) return f"{self.source_url}/{path}" - def get_source_file_declaration(self, path: str, tag: str, branch: str): + def get_source_file_declaration( + self, path: str, tag: str, branch: str, commit: str | None = None + ): relative_path = path.replace(self.source_url, "").strip(os.sep) return f'"{relative_path}"' @@ -103,11 +105,20 @@ def checkout(self, path: str, ref: str): def get_raw_file(self, path: str, tag: str): return f"{self.source_url}/raw/{tag}/{path}" - def get_source_file_declaration(self, path: str, tag: str, branch: str): + def get_source_file_declaration( + self, path: str, tag: str, branch: str, commit: str | None = None + ): owner_repo = "/".join(self.source_url.split("/")[-2:]) - if not (tag or branch): - raise UserError("Either tag or branch has to be specified for deployment.") - ref_arg = f'tag="{tag}"' if tag is not None else f'branch="{branch}"' + if not (tag or branch or commit): + raise UserError( + "Either tag, branch, or commit has to be specified for deployment." + ) + if commit is not None: + ref_arg = f'commit="{commit}"' + elif tag is not None: + ref_arg = f'tag="{tag}"' + else: + ref_arg = f'branch="{branch}"' return f'{self.name}("{owner_repo}", path="{path}", {ref_arg})' diff --git a/tests/test_client.sh b/tests/test_client.sh index 313ce9e..44c6a7c 100755 --- a/tests/test_client.sh +++ b/tests/test_client.sh @@ -46,6 +46,15 @@ dest=$tmpdir/gitlab-testing repo="https://gitlab.com/nate-d-olson/snaketestworkflow" runTest 0 $output snakedeploy deploy-workflow "${repo}" "${dest}" --name snake-test --branch master +echo +echo "#### Testing snakedeploy deployment pinned to a specific commit" +dest=$tmpdir/commit-testing +repo="https://github.com/snakemake-workflows/dna-seq-varlociraptor" +commit_sha="87709354b54391aee5dbb01a64cacfc20aed5ec3" +runTest 0 $output snakedeploy deploy-workflow "${repo}" "${dest}" --commit ${commit_sha} --name dna-seq-commit +runTest 0 $output grep "commit=\"${commit_sha}\"" ${dest}/workflow/Snakefile +runTest 2 $output snakedeploy deploy-workflow "${repo}" "${dest}" --branch master --commit ${commit_sha} + echo echo "#### Testing snakedeploy local deployment" local=$tmpdir/rna-seq-star-deseq2