Skip to content

Commit 870c4f7

Browse files
authored
Merge pull request #203 from seqeralabs/dev
Release v0.5.3
2 parents bfe2c72 + bf814a9 commit 870c4f7

File tree

9 files changed

+214
-18
lines changed

9 files changed

+214
-18
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,7 @@ Values in the provided environment file will override any existing environment v
486486
We have provided template YAML files for each of the entities that can be created on Seqera Platform. These can be found in the [`templates/`](https://github.com/seqeralabs/blob/main/seqera-kit/templates) directory and should form a good starting point for you to add your own customization:
487487

488488
- [organizations.yml](./templates/organizations.yml)
489+
- [members.yml](./templates/members.yml)
489490
- [teams.yml](./templates/teams.yml)
490491
- [workspaces.yml](./templates/workspaces.yml)
491492
- [participants.yml](./templates/participants.yml)
@@ -497,6 +498,8 @@ We have provided template YAML files for each of the entities that can be create
497498
- [labels.yml](./templates/labels.yml)
498499
- [pipelines.yml](./templates/pipelines.yml)
499500
- [launch.yml](./templates/launch.yml)
501+
- [data-links.yml](./templates/data-links.yml)
502+
- [studios.yml](./templates/studios.yml)
500503

501504
## Real world example
502505

seqerakit/cli.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,8 @@ def main(args=None):
272272
"secrets",
273273
"actions",
274274
"datasets",
275+
"studios",
276+
"data-links",
275277
],
276278
)
277279

seqerakit/helper.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ def parse_all_yaml(file_paths, destroy=False, targets=None):
128128
"datasets",
129129
"pipelines",
130130
"launch",
131+
"data-links",
132+
"studios",
131133
]
132134

133135
# Reverse the order of resources to delete if destroy is True
@@ -175,7 +177,11 @@ def parse_block(block_name, item):
175177
def parse_generic_block(item):
176178
cmd_args = []
177179
for key, value in item.items():
178-
cmd_args.extend([f"--{key}", str(value)])
180+
if isinstance(value, bool):
181+
if value:
182+
cmd_args.append(f"--{key}")
183+
else:
184+
cmd_args.extend([f"--{key}", str(value)])
179185
return cmd_args
180186

181187

seqerakit/overwrite.py

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class Overwrite:
3434
"datasets",
3535
"actions",
3636
"pipelines",
37+
"studios",
3738
]
3839

3940
def __init__(self, sp):
@@ -62,7 +63,7 @@ def __init__(self, sp):
6263
},
6364
"labels": {
6465
"keys": ["name", "value", "workspace"],
65-
"method_args": self._get_label_args,
66+
"method_args": lambda args: self._get_id_resource_args("labels", args),
6667
"name_key": "name",
6768
},
6869
"members": {
@@ -85,6 +86,13 @@ def __init__(self, sp):
8586
"method_args": self._get_workspace_args,
8687
"name_key": "workspaceName",
8788
},
89+
"data-links": {
90+
"keys": ["name", "workspace"],
91+
"method_args": lambda args: self._get_id_resource_args(
92+
"data-links", args
93+
),
94+
"name_key": "name",
95+
},
8896
}
8997

9098
def handle_overwrite(self, block, args, overwrite=False, destroy=False):
@@ -197,14 +205,17 @@ def _get_workspace_args(self, args):
197205
workspace_id = self._find_workspace_id(args["organization"], args["name"])
198206
return ("delete", "--id", str(workspace_id))
199207

200-
def _get_label_args(self, args):
208+
def _get_id_resource_args(self, resource_type, args):
201209
"""
202210
Returns a list of arguments for the delete() method for labels. The
203-
label_id used to delete will be retrieved using the _find_label_id()
204-
method.
211+
label_id used to delete will be retrieved using the _find_id() method.
205212
"""
206-
label_id = self._find_label_id(args["name"], args["value"])
207-
return ("delete", "--id", str(label_id), "-w", args["workspace"])
213+
if resource_type == "labels":
214+
resource_id = self._find_id(resource_type, args["name"], args["value"])
215+
else: # data-links
216+
resource_id = self._find_id(resource_type, args["name"])
217+
218+
return ("delete", "--id", str(resource_id), "-w", args["workspace"])
208219

209220
def _get_generic_deletion_args(self, args):
210221
"""
@@ -252,6 +263,7 @@ def _get_json_data(self, block, args, keys_to_get):
252263
elif block in Overwrite.generic_deletion or block in {
253264
"participants",
254265
"labels",
266+
"data-links",
255267
}:
256268
sp_args = self._get_values_from_cmd_args(args, keys_to_get)
257269
with self.sp.suppress_output():
@@ -326,16 +338,41 @@ def _find_workspace_id(self, organization, workspace_name):
326338
return workspace_id
327339
return None
328340

329-
def _find_label_id(self, label_name, label_value):
341+
def _find_id(self, resource_type, name, value=None):
330342
"""
331-
Custom method to find a label ID in a nested dictionary with a given
332-
workspace name. This ID will be used to delete the label.
343+
Finds the unique identifier (ID) for a Seqera Platform resource by searching
344+
through the cached JSON data. This method is necessary because certain Platform
345+
resources must be deleted using their ID rather than their name.
346+
347+
Args:
348+
resource_type (str): Type of resource to search for. Currently supports:
349+
- 'labels': Platform labels that require both name and value matching
350+
- 'data-links': Data link resources that only require name matching
351+
name (str): Name of the resource to find
352+
value (str, optional): For labels only, the value field that must match
353+
along with the name. Defaults to None for non-label resources.
354+
355+
Returns:
356+
str: The unique identifier (ID) of the matching resource if found
357+
None: If no matching resource is found
358+
359+
Note:
360+
- For labels, both name and value must match to find the correct ID
361+
- For data-links, only the name needs to match
362+
- The method uses cached JSON data from previous API calls to avoid
363+
redundant requests to the Platform
333364
"""
334365
jsondata = json.loads(self.cached_jsondata)
335-
labels = jsondata["labels"]
336-
for label in labels:
337-
if label.get("name") == utils.resolve_env_var(label_name) and label.get(
338-
"value"
339-
) == utils.resolve_env_var(label_value):
340-
return label.get("id")
366+
json_key = "dataLinks" if resource_type == "data-links" else resource_type
367+
resources = jsondata[json_key]
368+
369+
for resource in resources:
370+
if resource_type == "labels":
371+
if resource.get("name") == utils.resolve_env_var(name) and resource.get(
372+
"value"
373+
) == utils.resolve_env_var(value):
374+
return resource.get("id")
375+
elif resource_type == "data-links":
376+
if resource.get("name") == utils.resolve_env_var(name):
377+
return resource.get("id")
341378
return None

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from setuptools import find_packages, setup
44

5-
VERSION = "0.5.2"
5+
VERSION = "0.5.3"
66

77
with open("README.md") as f:
88
readme = f.read()

templates/data-links.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
## To see the full list of options available run: "tw data-links add"
2+
3+
# Creating a private data link
4+
data-links:
5+
- name: "my-data-link" # required
6+
workspace: "my_organization/my_workspace" # required
7+
description: "Data link for my results" # optional
8+
provider: "aws" # required
9+
credentials: "my_aws_credentials" # optional
10+
uri: "s3://my-bucket/my-results/" # required
11+
overwrite: False # optional
12+
13+
# Creating a public data link
14+
- name: "1000-genomes" # required
15+
workspace: "my_organization/my_workspace" # required
16+
description: "Public data link to 1000 genomes public bucket" # optional
17+
provider: "aws" # required
18+
uri: "s3://1000genomes" # required
19+
overwrite: False # optional

templates/studios.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
## To see the full list of options available run: "tw studios add"
2+
## The options required to create studios can be specified:
3+
## 1. With a provided template using `template:` option
4+
## 2. With a custom template using `custom-template:` option
5+
## 3. A template image customized with conda packages using `conda-env-yml:` option
6+
## 4. Mounting data resources using `mount-data-resource-refs:` option
7+
## 5. Mounting data using data link names using `mount-data:` option
8+
9+
# Mounting with a template image
10+
studios:
11+
- name: 'rstudio_environment' # required
12+
description: 'An RStudio environment for testing' # optional
13+
workspace: 'my_organization/my_workspace' # required
14+
template: 'public.cr.seqera.io/platform/data-studio-rstudio:4.4.1-u1-0.7' # required
15+
compute-env: 'my_aws_compute_environment' # required
16+
gpu: 1 # optional
17+
cpu: 2 # optional
18+
memory: 4096 # optional
19+
autoStart: True # optional
20+
mount-data-resource-refs: 's3://my_bucket/my_data' # optional, comma separated list
21+
overwrite: False # optional
22+
23+
# Mounting with a custom template
24+
- name: 'rstudio_environment_custom_template' # required
25+
description: 'An RStudio environment built with a custom image for testing' # optional
26+
workspace: 'my_organization/my_workspace' # required
27+
custom-template: 'my-registry/my-template:latest' # required
28+
compute-env: 'my_aws_compute_environment' # required
29+
gpu: 1 # optional
30+
cpu: 2 # optional
31+
memory: 4096 # optional
32+
autoStart: True # optional
33+
mount-data-resource-refs: 's3://my_bucket/my_data' # optional, comma separated list
34+
overwrite: False # optional
35+
36+
# Mounting with a template image customized with conda packages
37+
- name: 'rstudio_environment_conda_packages' # required
38+
description: 'An RStudio environment built with conda packages for testing' # optional
39+
workspace: 'my_organization/my_workspace' # required
40+
template: 'public.cr.seqera.io/platform/data-studio-rstudio:4.4.1-u1-0.7' # required
41+
conda-env-yml: './templates/rstudio_environment.yml' # required
42+
compute-env: 'my_aws_compute_environment' # required
43+
gpu: 1 # optional
44+
cpu: 2 # optional
45+
memory: 4096 # optional
46+
autoStart: True # optional
47+
mount-data-resource-refs: 's3://my_bucket/my_data' # optional, comma separated list
48+
overwrite: False # optional

tests/unit/test_helper.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,88 @@ def test_create_mock_members_yaml(mock_yaml_file):
333333
assert result["members"] == expected_block_output
334334

335335

336+
def test_create_mock_studios_yaml(mock_yaml_file):
337+
test_data = {
338+
"studios": [
339+
{
340+
"name": "test_studio1",
341+
"workspace": "my_organization/my_workspace",
342+
"template": "public.seqera.io/public/data-studio-rstudio:4.4.1",
343+
"compute-env": "my_computeenv",
344+
"cpu": 2,
345+
"memory": 4096,
346+
"autoStart": False,
347+
"overwrite": True,
348+
"mount-data-ids": "v1-user-bf73f9d33997f93a20ee3e6911779951",
349+
}
350+
]
351+
}
352+
353+
expected_block_output = [
354+
{
355+
"cmd_args": [
356+
"--compute-env",
357+
"my_computeenv",
358+
"--cpu",
359+
"2",
360+
"--memory",
361+
"4096",
362+
"--mount-data-ids",
363+
"v1-user-bf73f9d33997f93a20ee3e6911779951",
364+
"--name",
365+
"test_studio1",
366+
"--template",
367+
"public.seqera.io/public/data-studio-rstudio:4.4.1",
368+
"--workspace",
369+
"my_organization/my_workspace",
370+
],
371+
"overwrite": True,
372+
}
373+
]
374+
375+
file_path = mock_yaml_file(test_data)
376+
result = helper.parse_all_yaml([file_path])
377+
print(f"debug - result: {result}")
378+
assert "studios" in result
379+
assert result["studios"] == expected_block_output
380+
381+
382+
def test_create_mock_data_links_yaml(mock_yaml_file):
383+
test_data = {
384+
"data-links": [
385+
{
386+
"name": "test_data_link1",
387+
"workspace": "my_organization/my_workspace",
388+
"provider": "aws",
389+
"credentials": "my_credentials",
390+
"uri": "s3://scidev-playground-eu-west-2/esha/nf-core-scrnaseq/",
391+
"overwrite": True,
392+
}
393+
]
394+
}
395+
expected_block_output = [
396+
{
397+
"cmd_args": [
398+
"--credentials",
399+
"my_credentials",
400+
"--name",
401+
"test_data_link1",
402+
"--provider",
403+
"aws",
404+
"--uri",
405+
"s3://scidev-playground-eu-west-2/esha/nf-core-scrnaseq/",
406+
"--workspace",
407+
"my_organization/my_workspace",
408+
],
409+
"overwrite": True,
410+
}
411+
]
412+
file_path = mock_yaml_file(test_data)
413+
result = helper.parse_all_yaml([file_path])
414+
assert "data-links" in result
415+
assert result["data-links"] == expected_block_output
416+
417+
336418
def test_empty_yaml_file(mock_yaml_file):
337419
test_data = {}
338420
file_path = mock_yaml_file(test_data)

tests/unit/test_overwrite.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ def test_participant_deletion(self):
142142

143143
@patch("seqerakit.utils.resolve_env_var")
144144
def test_organization_deletion_with_env_var(self, mock_resolve_env_var):
145-
146145
args = ["--name", "${ORG_NAME}"]
147146

148147
# Setup environment variable mock

0 commit comments

Comments
 (0)