From 3e14dca3588133e7c7364eaef5e80195a876007b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Mu=C3=B1oz=20Redondo?= <105782439+JaimeSeqLabs@users.noreply.github.com> Date: Tue, 5 Dec 2023 16:15:20 +0100 Subject: [PATCH] Add extra metadata to workflow runs exported files (#364) * extend workflow export format with virtual fields * runs dump cmd, extend workflow export fields * use tower's object mapper for virtual extension formats * clean json nodes handling in export formats * export workflow metadata to separate file * reflection files * comment for user info filtering * add workspace name to metadata * add workflow run URL to metadata * add workflow labels to metadata * fix reflection files * remove user email from metadata --- conf/reflect-config.json | 43 +++++++--- .../cli/commands/runs/AbstractRunsCmd.java | 11 +++ .../tower/cli/commands/runs/DumpCmd.java | 46 ++++++++++- .../cli/shared/ComputeEnvExportFormat.java | 22 +++-- .../tower/cli/shared/WorkflowMetadata.java | 81 +++++++++++++++++++ .../io/seqera/tower/cli/runs/RunsCmdTest.java | 62 ++++++++++++++ 6 files changed, 244 insertions(+), 21 deletions(-) create mode 100644 src/main/java/io/seqera/tower/cli/shared/WorkflowMetadata.java diff --git a/conf/reflect-config.json b/conf/reflect-config.json index f23b7d24..00716143 100644 --- a/conf/reflect-config.json +++ b/conf/reflect-config.json @@ -1969,6 +1969,13 @@ "name":"io.seqera.tower.cli.shared.ComputeEnvExportFormat$ComputeConfigMixin", "queryAllDeclaredMethods":true }, +{ + "name":"io.seqera.tower.cli.shared.WorkflowMetadata", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"getLabels","parameterTypes":[] }, {"name":"getPipelineId","parameterTypes":[] }, {"name":"getRunUrl","parameterTypes":[] }, {"name":"getUserId","parameterTypes":[] }, {"name":"getWorkspaceId","parameterTypes":[] }, {"name":"getWorkspaceName","parameterTypes":[] }] +}, { "name":"io.seqera.tower.cli.utils.VersionProvider", "allDeclaredFields":true, @@ -2150,13 +2157,13 @@ "allDeclaredFields":true, "allDeclaredMethods":true, "allDeclaredConstructors":true, - "methods":[{"name":"getConfig","parameterTypes":[] }, {"name":"getCredentialsId","parameterTypes":[] }, {"name":"getDateCreated","parameterTypes":[] }, {"name":"getDeleted","parameterTypes":[] }, {"name":"getDescription","parameterTypes":[] }, {"name":"getId","parameterTypes":[] }, {"name":"getLastUpdated","parameterTypes":[] }, {"name":"getLastUsed","parameterTypes":[] }, {"name":"getMessage","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"getOrgId","parameterTypes":[] }, {"name":"getPlatform","parameterTypes":[] }, {"name":"getPrimary","parameterTypes":[] }, {"name":"getStatus","parameterTypes":[] }, {"name":"getWorkspaceId","parameterTypes":[] }] + "methods":[{"name":"","parameterTypes":[] }, {"name":"getConfig","parameterTypes":[] }, {"name":"getCredentialsId","parameterTypes":[] }, {"name":"getDateCreated","parameterTypes":[] }, {"name":"getDeleted","parameterTypes":[] }, {"name":"getDescription","parameterTypes":[] }, {"name":"getId","parameterTypes":[] }, {"name":"getLastUpdated","parameterTypes":[] }, {"name":"getLastUsed","parameterTypes":[] }, {"name":"getMessage","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"getOrgId","parameterTypes":[] }, {"name":"getPlatform","parameterTypes":[] }, {"name":"getPrimary","parameterTypes":[] }, {"name":"getStatus","parameterTypes":[] }, {"name":"getWorkspaceId","parameterTypes":[] }, {"name":"setConfig","parameterTypes":["io.seqera.tower.model.ComputeConfig"] }, {"name":"setCredentialsId","parameterTypes":["java.lang.String"] }, {"name":"setDescription","parameterTypes":["java.lang.String"] }, {"name":"setMessage","parameterTypes":["java.lang.String"] }, {"name":"setName","parameterTypes":["java.lang.String"] }, {"name":"setPlatform","parameterTypes":["io.seqera.tower.model.ComputeEnv$PlatformEnum"] }, {"name":"setStatus","parameterTypes":["io.seqera.tower.model.ComputeEnvStatus"] }] }, { "name":"io.seqera.tower.model.ComputeEnv$PlatformEnum", "allDeclaredFields":true, "allDeclaredMethods":true, - "methods":[{"name":"getValue","parameterTypes":[] }] + "methods":[{"name":"fromValue","parameterTypes":["java.lang.String"] }, {"name":"getValue","parameterTypes":[] }] }, { "name":"io.seqera.tower.model.ComputeEnvDbDto", @@ -2181,13 +2188,14 @@ "name":"io.seqera.tower.model.ComputeEnvStatus", "allDeclaredFields":true, "allDeclaredMethods":true, - "methods":[{"name":"fromValue","parameterTypes":["java.lang.String"] }] + "methods":[{"name":"fromValue","parameterTypes":["java.lang.String"] }, {"name":"getValue","parameterTypes":[] }] }, { "name":"io.seqera.tower.model.ComputePlatformDto", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"setId","parameterTypes":["java.lang.String"] }, {"name":"setName","parameterTypes":["java.lang.String"] }] }, { "name":"io.seqera.tower.model.ConfigEnvVariable", @@ -2395,7 +2403,8 @@ "name":"io.seqera.tower.model.DescribeLaunchResponse", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"setLaunch","parameterTypes":["io.seqera.tower.model.Launch"] }] }, { "name":"io.seqera.tower.model.DescribeOrganizationResponse", @@ -2547,7 +2556,8 @@ "name":"io.seqera.tower.model.JobInfoDto", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"setExitCode","parameterTypes":["java.lang.Integer"] }, {"name":"setId","parameterTypes":["java.lang.Long"] }, {"name":"setMessage","parameterTypes":["java.lang.String"] }, {"name":"setOperationId","parameterTypes":["java.lang.String"] }, {"name":"setStatus","parameterTypes":["java.lang.String"] }] }, { "name":"io.seqera.tower.model.K8sComputeConfig", @@ -2578,7 +2588,8 @@ "name":"io.seqera.tower.model.Launch", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"getComputeEnv_JsonNullable","parameterTypes":[] }, {"name":"getConfigProfiles","parameterTypes":[] }, {"name":"getConfigText","parameterTypes":[] }, {"name":"getDateCreated","parameterTypes":[] }, {"name":"getEntryName","parameterTypes":[] }, {"name":"getHeadJobCpus","parameterTypes":[] }, {"name":"getHeadJobMemoryMb","parameterTypes":[] }, {"name":"getId","parameterTypes":[] }, {"name":"getLastUpdated","parameterTypes":[] }, {"name":"getMainScript","parameterTypes":[] }, {"name":"getOptimizationId","parameterTypes":[] }, {"name":"getOptimizationTargets","parameterTypes":[] }, {"name":"getParamsText","parameterTypes":[] }, {"name":"getPipeline","parameterTypes":[] }, {"name":"getPostRunScript","parameterTypes":[] }, {"name":"getPreRunScript","parameterTypes":[] }, {"name":"getPullLatest","parameterTypes":[] }, {"name":"getResume","parameterTypes":[] }, {"name":"getResumeLaunchId","parameterTypes":[] }, {"name":"getRevision","parameterTypes":[] }, {"name":"getRunName","parameterTypes":[] }, {"name":"getSchemaName","parameterTypes":[] }, {"name":"getSessionId","parameterTypes":[] }, {"name":"getStubRun","parameterTypes":[] }, {"name":"getTowerConfig","parameterTypes":[] }, {"name":"getUserSecrets","parameterTypes":[] }, {"name":"getWorkDir","parameterTypes":[] }, {"name":"getWorkspaceSecrets","parameterTypes":[] }, {"name":"setComputeEnv_JsonNullable","parameterTypes":["org.openapitools.jackson.nullable.JsonNullable"] }, {"name":"setConfigProfiles","parameterTypes":["java.util.List"] }, {"name":"setConfigText","parameterTypes":["java.lang.String"] }, {"name":"setDateCreated","parameterTypes":["java.time.OffsetDateTime"] }, {"name":"setEntryName","parameterTypes":["java.lang.String"] }, {"name":"setHeadJobCpus","parameterTypes":["java.lang.Integer"] }, {"name":"setHeadJobMemoryMb","parameterTypes":["java.lang.Integer"] }, {"name":"setId","parameterTypes":["java.lang.String"] }, {"name":"setLastUpdated","parameterTypes":["java.time.OffsetDateTime"] }, {"name":"setMainScript","parameterTypes":["java.lang.String"] }, {"name":"setOptimizationId","parameterTypes":["java.lang.String"] }, {"name":"setOptimizationTargets","parameterTypes":["java.lang.String"] }, {"name":"setParamsText","parameterTypes":["java.lang.String"] }, {"name":"setPipeline","parameterTypes":["java.lang.String"] }, {"name":"setPostRunScript","parameterTypes":["java.lang.String"] }, {"name":"setPreRunScript","parameterTypes":["java.lang.String"] }, {"name":"setPullLatest","parameterTypes":["java.lang.Boolean"] }, {"name":"setResume","parameterTypes":["java.lang.Boolean"] }, {"name":"setResumeLaunchId","parameterTypes":["java.lang.String"] }, {"name":"setRevision","parameterTypes":["java.lang.String"] }, {"name":"setRunName","parameterTypes":["java.lang.String"] }, {"name":"setSchemaName","parameterTypes":["java.lang.String"] }, {"name":"setSessionId","parameterTypes":["java.lang.String"] }, {"name":"setStubRun","parameterTypes":["java.lang.Boolean"] }, {"name":"setTowerConfig","parameterTypes":["java.lang.String"] }, {"name":"setUserSecrets","parameterTypes":["java.util.List"] }, {"name":"setWorkDir","parameterTypes":["java.lang.String"] }, {"name":"setWorkspaceSecrets","parameterTypes":["java.util.List"] }] }, { "name":"io.seqera.tower.model.LaunchActionRequest", @@ -2656,7 +2667,8 @@ "name":"io.seqera.tower.model.ListParticipantsResponse", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"setParticipants","parameterTypes":["java.util.List"] }, {"name":"setTotalSize","parameterTypes":["java.lang.Long"] }] }, { "name":"io.seqera.tower.model.ListPipelineSecretsResponse", @@ -2747,7 +2759,8 @@ { "name":"io.seqera.tower.model.OrgRole", "allDeclaredFields":true, - "allDeclaredMethods":true + "allDeclaredMethods":true, + "methods":[{"name":"fromValue","parameterTypes":["java.lang.String"] }] }, { "name":"io.seqera.tower.model.Organization", @@ -2765,7 +2778,8 @@ "name":"io.seqera.tower.model.ParticipantDbDto", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"setEmail","parameterTypes":["java.lang.String"] }, {"name":"setFirstName","parameterTypes":["java.lang.String"] }, {"name":"setLastName","parameterTypes":["java.lang.String"] }, {"name":"setMemberId","parameterTypes":["java.lang.Long"] }, {"name":"setOrgRole","parameterTypes":["io.seqera.tower.model.OrgRole"] }, {"name":"setParticipantId","parameterTypes":["java.lang.Long"] }, {"name":"setTeamAvatarUrl","parameterTypes":["java.lang.String"] }, {"name":"setTeamId","parameterTypes":["java.lang.Long"] }, {"name":"setTeamName","parameterTypes":["java.lang.String"] }, {"name":"setType","parameterTypes":["io.seqera.tower.model.ParticipantType"] }, {"name":"setUserAvatarUrl","parameterTypes":["java.lang.String"] }, {"name":"setUserName","parameterTypes":["java.lang.String"] }, {"name":"setWspRole","parameterTypes":["io.seqera.tower.model.WspRole"] }] }, { "name":"io.seqera.tower.model.ParticipantType", @@ -3042,7 +3056,7 @@ "allDeclaredFields":true, "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":[] }, {"name":"setComputeEnv","parameterTypes":["io.seqera.tower.model.ComputeEnv"] }, {"name":"setConfigProfiles","parameterTypes":["java.util.List"] }, {"name":"setConfigText","parameterTypes":["java.lang.String"] }, {"name":"setDateCreated","parameterTypes":["java.time.OffsetDateTime"] }, {"name":"setEntryName","parameterTypes":["java.lang.String"] }, {"name":"setId","parameterTypes":["java.lang.String"] }, {"name":"setMainScript","parameterTypes":["java.lang.String"] }, {"name":"setParamsText","parameterTypes":["java.lang.String"] }, {"name":"setPipeline","parameterTypes":["java.lang.String"] }, {"name":"setPostRunScript","parameterTypes":["java.lang.String"] }, {"name":"setPreRunScript","parameterTypes":["java.lang.String"] }, {"name":"setPullLatest","parameterTypes":["java.lang.Boolean"] }, {"name":"setResume","parameterTypes":["java.lang.Boolean"] }, {"name":"setResumeCommitId","parameterTypes":["java.lang.String"] }, {"name":"setResumeDir","parameterTypes":["java.lang.String"] }, {"name":"setRevision","parameterTypes":["java.lang.String"] }, {"name":"setSchemaName","parameterTypes":["java.lang.String"] }, {"name":"setSessionId","parameterTypes":["java.lang.String"] }, {"name":"setStubRun","parameterTypes":["java.lang.Boolean"] }, {"name":"setTowerConfig","parameterTypes":["java.lang.String"] }, {"name":"setUserSecrets","parameterTypes":["java.util.List"] }, {"name":"setWorkDir","parameterTypes":["java.lang.String"] }, {"name":"setWorkspaceSecrets","parameterTypes":["java.util.List"] }] + "methods":[{"name":"","parameterTypes":[] }, {"name":"setComputeEnv","parameterTypes":["io.seqera.tower.model.ComputeEnv"] }, {"name":"setConfigProfiles","parameterTypes":["java.util.List"] }, {"name":"setConfigText","parameterTypes":["java.lang.String"] }, {"name":"setDateCreated","parameterTypes":["java.time.OffsetDateTime"] }, {"name":"setEntryName","parameterTypes":["java.lang.String"] }, {"name":"setHeadJobCpus","parameterTypes":["java.lang.Integer"] }, {"name":"setHeadJobMemoryMb","parameterTypes":["java.lang.Integer"] }, {"name":"setId","parameterTypes":["java.lang.String"] }, {"name":"setMainScript","parameterTypes":["java.lang.String"] }, {"name":"setOptimizationId","parameterTypes":["java.lang.String"] }, {"name":"setOptimizationTargets","parameterTypes":["java.lang.String"] }, {"name":"setParamsText","parameterTypes":["java.lang.String"] }, {"name":"setPipeline","parameterTypes":["java.lang.String"] }, {"name":"setPipelineId","parameterTypes":["java.lang.Long"] }, {"name":"setPostRunScript","parameterTypes":["java.lang.String"] }, {"name":"setPreRunScript","parameterTypes":["java.lang.String"] }, {"name":"setPullLatest","parameterTypes":["java.lang.Boolean"] }, {"name":"setResume","parameterTypes":["java.lang.Boolean"] }, {"name":"setResumeCommitId","parameterTypes":["java.lang.String"] }, {"name":"setResumeDir","parameterTypes":["java.lang.String"] }, {"name":"setRevision","parameterTypes":["java.lang.String"] }, {"name":"setSchemaName","parameterTypes":["java.lang.String"] }, {"name":"setSessionId","parameterTypes":["java.lang.String"] }, {"name":"setStubRun","parameterTypes":["java.lang.Boolean"] }, {"name":"setTowerConfig","parameterTypes":["java.lang.String"] }, {"name":"setUserSecrets","parameterTypes":["java.util.List"] }, {"name":"setWorkDir","parameterTypes":["java.lang.String"] }, {"name":"setWorkspaceSecrets","parameterTypes":["java.util.List"] }] }, { "name":"io.seqera.tower.model.WorkflowLoad", @@ -3073,7 +3087,8 @@ { "name":"io.seqera.tower.model.WspRole", "allDeclaredFields":true, - "allDeclaredMethods":true + "allDeclaredMethods":true, + "methods":[{"name":"fromValue","parameterTypes":["java.lang.String"] }] }, { "name":"java.io.Console", @@ -3085,6 +3100,10 @@ "allDeclaredMethods":true, "allDeclaredConstructors":true }, +{ + "name":"java.io.FileNotFoundException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, { "name":"java.io.IOException", "methods":[{"name":"","parameterTypes":["java.lang.String"] }] diff --git a/src/main/java/io/seqera/tower/cli/commands/runs/AbstractRunsCmd.java b/src/main/java/io/seqera/tower/cli/commands/runs/AbstractRunsCmd.java index 32c5dfb7..3c18ce0e 100644 --- a/src/main/java/io/seqera/tower/cli/commands/runs/AbstractRunsCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/runs/AbstractRunsCmd.java @@ -23,6 +23,7 @@ import io.seqera.tower.cli.exceptions.RunNotFoundException; import io.seqera.tower.cli.exceptions.WorkflowProgressNotFoundException; import io.seqera.tower.model.DescribeLaunchResponse; +import io.seqera.tower.model.DescribeWorkflowLaunchResponse; import io.seqera.tower.model.DescribeWorkflowResponse; import io.seqera.tower.model.GetProgressResponse; import io.seqera.tower.model.Launch; @@ -45,6 +46,16 @@ protected DescribeWorkflowResponse workflowById(Long workspaceId, String id) thr return workflowResponse; } + protected DescribeWorkflowLaunchResponse workflowLaunchById(Long workspaceId, String workflowId) throws ApiException { + DescribeWorkflowLaunchResponse wfLaunchResponse = api().describeWorkflowLaunch(workflowId, workspaceId); + + if (wfLaunchResponse == null) { + throw new ApiException(String.format("Workflow '%s' launch not found at %d workspace", workflowId, workspaceId)); + } + + return wfLaunchResponse; + } + protected Launch launchById(Long workspaceId, String id) throws ApiException { DescribeLaunchResponse launchResponse = api().describeLaunch(id, workspaceId); diff --git a/src/main/java/io/seqera/tower/cli/commands/runs/DumpCmd.java b/src/main/java/io/seqera/tower/cli/commands/runs/DumpCmd.java index 2f58a9f7..2037c9f2 100644 --- a/src/main/java/io/seqera/tower/cli/commands/runs/DumpCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/runs/DumpCmd.java @@ -24,8 +24,10 @@ import io.seqera.tower.cli.exceptions.TowerException; import io.seqera.tower.cli.responses.Response; import io.seqera.tower.cli.responses.runs.RunDump; +import io.seqera.tower.cli.shared.WorkflowMetadata; import io.seqera.tower.cli.utils.SilentPrintWriter; import io.seqera.tower.model.DescribeTaskResponse; +import io.seqera.tower.model.DescribeWorkflowLaunchResponse; import io.seqera.tower.model.DescribeWorkflowResponse; import io.seqera.tower.model.Launch; import io.seqera.tower.model.ListTasksResponse; @@ -167,15 +169,41 @@ private void dumpTasks(PrintWriter progress, TarArchiveOutputStream out, Long ws private void dumpWorkflowDetails(PrintWriter progress, TarArchiveOutputStream out, Long wspId) throws ApiException, IOException { progress.println(ansi("- Workflow details")); + // General workflow info DescribeWorkflowResponse workflowResponse = workflowById(wspId, id); Workflow workflow = workflowResponse.getWorkflow(); if (workflow == null) { throw new TowerException("Unknown workflow"); } + + // Launch info + Launch launch = null; + Long pipelineId = null; + if (workflow.getLaunchId() != null) { + + launch = launchById(wspId, workflow.getLaunchId()); + + DescribeWorkflowLaunchResponse wfLaunchResponse = workflowLaunchById(wspId, workflow.getId()); + if (wfLaunchResponse != null && wfLaunchResponse.getLaunch() != null) { + pipelineId = wfLaunchResponse.getLaunch().getPipelineId(); + } + } + + // Load and metrics info WorkflowLoad workflowLoad = workflowLoadByWorkflowId(wspId, id); - Launch launch = workflow.getLaunchId() != null ? launchById(wspId, workflow.getLaunchId()) : null; List metrics = api().describeWorkflowMetrics(workflow.getId(), wspId).getMetrics(); + + WorkflowMetadata wfMetadata = new WorkflowMetadata( + pipelineId, + wspId, + workflowResponse.getWorkspaceName(), + workflow.getOwnerId(), + generateUrl(wspId, workflow.getUserName(), workflow.getId()), + workflowResponse.getLabels() + ); + addEntry(out, "workflow.json", Workflow.class, workflow); + addEntry(out, "workflow-metadata.json", WorkflowMetadata.class, wfMetadata); addEntry(out, "workflow-load.json", WorkflowLoad.class, workflowLoad); addEntry(out, "workflow-launch.json", Launch.class, launch); addEntry(out, "workflow-metrics.json", List.class, metrics); @@ -285,8 +313,14 @@ private void addEntry(TarArchiveOutputStream out, String fileName, Class if (value == null) { return; } + addEntry(out, fileName, toJSON(type, value)); + } + + private void addEntry(TarArchiveOutputStream out, String fileName, byte[] data) throws IOException { + if (data == null) { + return; + } TarArchiveEntry entry = new TarArchiveEntry(fileName); - byte[] data = toJSON(type, value); entry.setSize(data.length); out.putArchiveEntry(entry); out.write(data); @@ -306,5 +340,13 @@ private byte[] toJSON(Class type, T value) throws JsonProcessingException .writerWithDefaultPrettyPrinter() .writeValueAsBytes(value); } + + private String generateUrl(Long wspId, String userName, String wfId) throws ApiException { + if (wspId == null) { + return String.format("%s/user/%s/watch/%s", serverUrl(), userName, wfId); + } + return String.format("%s/orgs/%s/workspaces/%s/watch/%s", serverUrl(), orgName(wspId), workspaceName(wspId), wfId); + } + } diff --git a/src/main/java/io/seqera/tower/cli/shared/ComputeEnvExportFormat.java b/src/main/java/io/seqera/tower/cli/shared/ComputeEnvExportFormat.java index 4ecab22b..3016e119 100644 --- a/src/main/java/io/seqera/tower/cli/shared/ComputeEnvExportFormat.java +++ b/src/main/java/io/seqera/tower/cli/shared/ComputeEnvExportFormat.java @@ -30,6 +30,7 @@ import com.fasterxml.jackson.databind.annotation.JsonAppend; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import com.fasterxml.jackson.databind.module.SimpleModule; +import io.seqera.tower.JSON; import io.seqera.tower.model.ComputeConfig; import io.seqera.tower.model.LabelDbDto; @@ -78,7 +79,7 @@ public static String serialize(final ComputeEnvExportFormat ceExport) throws Jso } private static ObjectMapper buildMapper() { - ObjectMapper mapper = new ObjectMapper() + ObjectMapper mapper = new JSON().getContext(ComputeConfig.class) .setSerializationInclusion(JsonInclude.Include.NON_NULL) .addMixIn(ComputeConfig.class, ComputeConfigMixin.class); mapper.configOverride(List.class).setSetterInfo(JsonSetter.Value.forContentNulls(Nulls.AS_EMPTY)); @@ -115,18 +116,25 @@ public ComputeEnvExportFormat deserialize(JsonParser parser, DeserializationCont JsonNode root = parser.getCodec().readTree(parser); - ObjectMapper mapper = new ObjectMapper() + ObjectMapper mapper = buildMapper() .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // ignore 'labels' field for now ComputeConfig cfg = mapper.readValue(root.toString(), ComputeConfig.class); - JsonNode labels = root.get("labels"); - if (labels != null) { - List labelDbDtos = mapper.readValue(labels.toString(), new TypeReference>() {}); - return new ComputeEnvExportFormat(cfg, labelDbDtos); + List labelDbDtos = extractJsonNodeValue(root, "labels", mapper); + if (labelDbDtos == null) { + labelDbDtos = Collections.emptyList(); } - return new ComputeEnvExportFormat(cfg, Collections.emptyList()); + return new ComputeEnvExportFormat(cfg, labelDbDtos); + } + + private static T extractJsonNodeValue(JsonNode root, String nodeName, ObjectMapper mapper) throws JsonProcessingException { + JsonNode node = root.get(nodeName); + if (node != null) { + return mapper.readValue(node.toString(), new TypeReference<>() {}); + } + return null; } } } diff --git a/src/main/java/io/seqera/tower/cli/shared/WorkflowMetadata.java b/src/main/java/io/seqera/tower/cli/shared/WorkflowMetadata.java new file mode 100644 index 00000000..5fc7a19b --- /dev/null +++ b/src/main/java/io/seqera/tower/cli/shared/WorkflowMetadata.java @@ -0,0 +1,81 @@ +/* + * Copyright 2021-2023, Seqera. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.seqera.tower.cli.shared; + +import io.seqera.tower.cli.commands.runs.DumpCmd; +import io.seqera.tower.model.LabelDbDto; + +import java.util.List; + +/** + * This is the class used by {@link DumpCmd} to structure Workflow metadata as JSON. + */ +public final class WorkflowMetadata { + + private final Long pipelineId; + + private final Long workspaceId; + + private final String workspaceName; + + private final Long userId; + + private final String runUrl; + + private final List labels; + + public WorkflowMetadata( + final Long pipelineId, + final Long workspaceId, + final String workspaceName, + final Long userId, + final String runUrl, + final List labels + ) { + this.pipelineId = pipelineId; + this.workspaceId = workspaceId; + this.workspaceName = workspaceName; + this.userId = userId; + this.runUrl = runUrl; + this.labels = labels; + } + + public Long getPipelineId() { + return pipelineId; + } + + public Long getWorkspaceId() { + return workspaceId; + } + + public String getWorkspaceName() { + return workspaceName; + } + + public Long getUserId() { + return userId; + } + + public String getRunUrl() { + return runUrl; + } + + public List getLabels() { + return labels; + } +} diff --git a/src/test/java/io/seqera/tower/cli/runs/RunsCmdTest.java b/src/test/java/io/seqera/tower/cli/runs/RunsCmdTest.java index ab6f0061..9a3ad9ad 100644 --- a/src/test/java/io/seqera/tower/cli/runs/RunsCmdTest.java +++ b/src/test/java/io/seqera/tower/cli/runs/RunsCmdTest.java @@ -27,6 +27,7 @@ import io.seqera.tower.cli.exceptions.TowerException; import io.seqera.tower.cli.responses.runs.RunCanceled; import io.seqera.tower.cli.responses.runs.RunDeleted; +import io.seqera.tower.cli.responses.runs.RunDump; import io.seqera.tower.cli.responses.runs.RunFileDownloaded; import io.seqera.tower.cli.responses.runs.RunList; import io.seqera.tower.cli.responses.runs.RunSubmited; @@ -553,4 +554,65 @@ void testDownloadTaskLog(MockServerClient mock) throws IOException { assertEquals(new RunFileDownloaded(file, RunDownloadFileType.stdout).toString(), out.stdOut); assertEquals(0, out.exitCode); } + + @Test + void testDumpRuns(MockServerClient mock) throws IOException { + + mock.when( + request().withMethod("GET").withPath("/user-info"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("user")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/service-info"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("info/service-info")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/workflow/5mDfiUtqyptDib"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("workflow_view")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/workflow/5mDfiUtqyptDib/progress"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("workflow_progress")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/workflow/5mDfiUtqyptDib/metrics"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("runs/runs_metrics")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/workflow/5mDfiUtqyptDib/tasks"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("runs/tasks_list_response")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/workflow/5mDfiUtqyptDib/launch"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("runs/workflow_launch")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/launch/5SCyEXKrCqFoGzOXGpesr5"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("launch_view")).withContentType(MediaType.APPLICATION_JSON) + ); + + File file = new File(tempFile("", "test-dump-runs", ".tar.gz")); + String workflowRunId = "5mDfiUtqyptDib"; + + ExecOut out = exec(mock, "runs", "dump", "-i", workflowRunId, "-o", file.getAbsolutePath(), "--silent"); + assertEquals("", out.stdErr); + assertEquals(new RunDump(workflowRunId, "user", file.toPath()).toString(), out.stdOut); + assertEquals(0, out.exitCode); + + } } \ No newline at end of file