-
Notifications
You must be signed in to change notification settings - Fork 11
WIP - snakemake v8+ remote storage support #89
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
Closed
Closed
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
26ad6e9
WIP - snakemake v8+ remote storage support
jameshadfield cc86bd7
wip / fixup / drop. Fix typo
jameshadfield 70e16f8
WIP: add Google Cloud storage
joverlee521 9168fc6
squash - various improvements
jameshadfield 879ba99
Add GS USVI location
j23414 79ecf00
Remove GS storage
jameshadfield 5319043
remove HTTP support
jameshadfield File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
|
joverlee521 marked this conversation as resolved.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| """ | ||
| Helper functions to set-up storage plugins for remote inputs/outputs. | ||
| See the docstring of `path_or_url` for usage instructions. | ||
|
|
||
| The errors raised by storage plugins are often confusing. For instance, | ||
| a HTTP 404 error will result in a `MissingInputException` with little | ||
| hint as to the underlying issue. S3 credentials errors are similarly | ||
| confusing and we attempt to check these ourselves to improve UX here. | ||
| """ | ||
|
|
||
| from urllib.parse import urlparse | ||
|
|
||
| # Keep a list of known public buckets, which we'll allow uncredentialled (unsigned) access to | ||
| # We could make this config-definable in the future | ||
| PUBLIC_BUCKETS = set(['nextstrain-data']) | ||
|
|
||
| # Keep track of registered storage plugins to enable reuse | ||
| _storage_registry = {} | ||
|
|
||
| class RemoteFilesMissingCredentials(Exception): | ||
| pass | ||
|
|
||
| def _storage_s3(*, bucket, keep_local, retries) -> snakemake.storage.StorageProviderProxy: | ||
| """ | ||
| Registers and returns an instance of snakemake-storage-plugin-s3 | ||
| """ | ||
| # If the bucket is public then we may use an unsigned request which has the nice UX | ||
| # of not needing credentials to be present. If we've made other signed requests _or_ | ||
| # credentials are present then we just sign everything. This has implications for upload: | ||
| # if you attempt to upload to a public bucket without credentials then we allow that here | ||
| # and you'll get a subsequent `AccessDenied` error when the upload is attempted. | ||
| if bucket in PUBLIC_BUCKETS and \ | ||
| "s3_signed" not in _storage_registry and \ | ||
| ("s3_unsigned" in _storage_registry or not _aws_credentials_present()): | ||
|
|
||
| if provider:=_storage_registry.get('s3_unsigned', None): | ||
| return provider | ||
|
|
||
| from botocore import UNSIGNED # dependency of snakemake-storage-plugin-s3 | ||
| storage s3_unsigned: | ||
| provider="s3", | ||
| signature_version=UNSIGNED, | ||
| retries=retries, | ||
| keep_local=keep_local, | ||
|
|
||
| _storage_registry['s3_unsigned'] = storage.s3_unsigned | ||
| return _storage_registry['s3_unsigned'] | ||
|
|
||
| # Resource fetched/uploaded via a signed request, which will require AWS credentials | ||
| if provider:=_storage_registry.get('s3_signed', None): | ||
| return provider | ||
|
|
||
| # Enforce the presence of credentials to paper over <https://github.com/snakemake/snakemake/issues/3663> | ||
| if not _aws_credentials_present(): | ||
| raise RemoteFilesMissingCredentials() | ||
|
|
||
| # the tag appears in the local file path, so reference 'signed' to give a hint about credential errors | ||
| storage s3_signed: | ||
| provider="s3", | ||
| retries=retries, | ||
| keep_local=keep_local, | ||
|
|
||
| _storage_registry['s3_signed'] = storage.s3_signed | ||
| return _storage_registry['s3_signed'] | ||
|
|
||
| def _aws_credentials_present() -> bool: | ||
| import boto3 # dependency of snakemake-storage-plugin-s3 | ||
| session = boto3.Session() | ||
| creds = session.get_credentials() | ||
| return creds is not None | ||
|
|
||
| def _storage_http(*, keep_local, retries) -> snakemake.storage.StorageProviderProxy: | ||
| """ | ||
| Registers and returns an instance of snakemake-storage-plugin-http | ||
| """ | ||
| if provider:=_storage_registry.get('http', None): | ||
| return provider | ||
|
|
||
| storage: | ||
| provider="http", | ||
| allow_redirects=True, | ||
| supports_head=True, | ||
| keep_local=keep_local, | ||
| retries=retries, | ||
|
|
||
| _storage_registry['http'] = storage.http | ||
| return _storage_registry['http'] | ||
|
|
||
|
|
||
| def path_or_url(uri, *, keep_local=True, retries=2) -> str: | ||
| """ | ||
| Intended for use in Snakemake inputs / outputs to transparently use remote | ||
| resources. Returns the URI wrapped by an applicable storage plugin. Local | ||
| filepaths will be returned unchanged. | ||
|
|
||
| For example, the following rule will download inputs from HTTPs and upload | ||
| the output to S3: | ||
|
|
||
| rule filter: | ||
| input: | ||
| sequences = path_or_url("https://data.nextstrain.org/files/workflows/zika/sequences.fasta.zst"), | ||
| metadata = path_or_url("https://data.nextstrain.org/files/workflows/zika/metadata.tsv.zst"), | ||
| output: | ||
| sequences = path_or_url("s3://nextstrain-scratch/filtered.fasta") | ||
| shell: | ||
| r''' | ||
| augur filter \ | ||
| --sequences {input.sequences:q} \ | ||
| --metadata {input.metadata:q} \ | ||
| --metadata-id-columns accession \ | ||
| --output-sequences {output.sequences:q} | ||
| ''' | ||
|
|
||
| If `keep_local` is True (the default) then downloaded/uploaded files will | ||
| remain in `.snakemake/storage/`. The presence of a previously downloaded | ||
| file (via `keep_local=True`) does not guarantee that the file will not be | ||
| re-downloaded if the storage plugin decides the local file is out of date. | ||
|
|
||
| See <https://snakemake.readthedocs.io/en/stable/snakefiles/storage.html> for | ||
| more information on Snakemake storage plugins. Note: various snakemake | ||
| plugins will be required depending on the URIs provided. | ||
| """ | ||
| info = urlparse(uri) | ||
|
|
||
| if info.scheme=='': # local | ||
| return uri # no storage wrapper | ||
|
|
||
| if info.scheme=='s3': | ||
| try: | ||
| return _storage_s3(bucket=info.netloc, keep_local=keep_local, retries=retries)(uri) | ||
| except RemoteFilesMissingCredentials as e: | ||
| raise Exception(f"AWS credentials are required to access {uri!r}") from e | ||
|
|
||
| if info.scheme=='https': | ||
| return _storage_http(keep_local=keep_local, retries=retries)(uri) | ||
| elif info.scheme=='http': | ||
| raise Exception(f"HTTP remote file support is not implemented in nextstrain workflows (attempting to access {uri!r}).\n" | ||
| "Please use an HTTPS address instead.") | ||
|
|
||
| if info.scheme in ['gs', 'gcs']: | ||
| raise Exception(f"Google Storage is not yet implemented for nextstrain workflows (attempting to access {uri!r}).\n" | ||
| "Please get in touch if you require this functionality and we can add it to our workflows") | ||
|
|
||
| raise Exception(f"Input address {uri!r} (scheme={info.scheme!r}) is from a non-supported remote") |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.