diff --git a/api/src/main/openapi/components/schemas/list-workflow-runs-response.yaml b/api/src/main/openapi/components/schemas/list-workflow-runs-response.yaml index 0e0f06db03..c5f63cd9e1 100644 --- a/api/src/main/openapi/components/schemas/list-workflow-runs-response.yaml +++ b/api/src/main/openapi/components/schemas/list-workflow-runs-response.yaml @@ -21,7 +21,7 @@ properties: workflow_runs: type: array items: - $ref: "./list-workflow-runs-response-item.yaml" + $ref: "./workflow-run-metadata.yaml" required: - _pagination - workflow_runs \ No newline at end of file diff --git a/api/src/main/openapi/components/schemas/list-workflow-runs-response-item.yaml b/api/src/main/openapi/components/schemas/workflow-run-metadata.yaml similarity index 96% rename from api/src/main/openapi/components/schemas/list-workflow-runs-response-item.yaml rename to api/src/main/openapi/components/schemas/workflow-run-metadata.yaml index 23591e5f0a..e998096618 100644 --- a/api/src/main/openapi/components/schemas/list-workflow-runs-response-item.yaml +++ b/api/src/main/openapi/components/schemas/workflow-run-metadata.yaml @@ -28,6 +28,8 @@ properties: maximum: 100 workflow_instance_id: type: string + task_queue_name: + type: string status: $ref: "./workflow-run-status.yaml" priority: @@ -53,6 +55,7 @@ required: - id - workflow_name - workflow_version +- task_queue_name - status - priority - created_at \ No newline at end of file diff --git a/api/src/main/openapi/openapi.yaml b/api/src/main/openapi/openapi.yaml index 1c83d3a486..dc44ffc671 100644 --- a/api/src/main/openapi/openapi.yaml +++ b/api/src/main/openapi/openapi.yaml @@ -152,6 +152,8 @@ paths: $ref: "./paths/teams__name_.yaml" /team-memberships: $ref: "./paths/team-memberships.yaml" + /workflow-instances/{id}: + $ref: "./paths/workflow-instances__id_.yaml" /workflow-runs: $ref: "./paths/workflow-runs.yaml" diff --git a/api/src/main/openapi/paths/workflow-instances__id_.yaml b/api/src/main/openapi/paths/workflow-instances__id_.yaml new file mode 100644 index 0000000000..3ecdcc251f --- /dev/null +++ b/api/src/main/openapi/paths/workflow-instances__id_.yaml @@ -0,0 +1,58 @@ +# This file is part of Dependency-Track. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. +get: + operationId: getWorkflowInstance + summary: Get a workflow instance + description: |- + Returns run metadata of a given workflow instance. + + Multiple runs can share an instance ID, but only a single run can exist + in non-terminal state at any given time. Thus, this operation only + returns metadata if a run in non-terminal state exists. + + To list all runs for an instance ID, including terminated ones, + use the `/workflow-runs` endpoint and filter by `workflow_instance_id`. + + **Note:** This is an internal API endpoint and may change without notice. + + Requires the `SYSTEM_CONFIGURATION` or `SYSTEM_CONFIGURATION_READ` permission. + tags: + - Workflows + parameters: + - name: id + description: ID of the workflow instance + in: path + schema: + type: string + required: true + responses: + "200": + description: Workflow run metadata + content: + application/json: + schema: + $ref: "../components/schemas/workflow-run-metadata.yaml" + "400": + $ref: "../components/responses/invalid-request-error.yaml" + "401": + $ref: "../components/responses/generic-unauthorized-error.yaml" + "403": + $ref: "../components/responses/generic-forbidden-error.yaml" + "404": + $ref: "../components/responses/generic-not-found-error.yaml" + default: + $ref: "../components/responses/generic-error.yaml" \ No newline at end of file diff --git a/apiserver/src/main/java/org/dependencytrack/resources/v2/WorkflowsResource.java b/apiserver/src/main/java/org/dependencytrack/resources/v2/WorkflowsResource.java index b45d5c1e2a..8a845fe5e6 100644 --- a/apiserver/src/main/java/org/dependencytrack/resources/v2/WorkflowsResource.java +++ b/apiserver/src/main/java/org/dependencytrack/resources/v2/WorkflowsResource.java @@ -20,11 +20,11 @@ import alpine.server.auth.PermissionRequired; import jakarta.inject.Inject; +import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.Path; import jakarta.ws.rs.core.Response; import org.dependencytrack.api.v2.WorkflowsApi; import org.dependencytrack.api.v2.model.ListWorkflowRunsResponse; -import org.dependencytrack.api.v2.model.ListWorkflowRunsResponseItem; import org.dependencytrack.api.v2.model.SortDirection; import org.dependencytrack.api.v2.model.WorkflowRunStatus; import org.dependencytrack.auth.Permissions; @@ -49,6 +49,20 @@ public class WorkflowsResource extends AbstractApiResource implements WorkflowsA this.dexEngine = dexEngine; } + @Override + @PermissionRequired({ + Permissions.Constants.SYSTEM_CONFIGURATION, + Permissions.Constants.SYSTEM_CONFIGURATION_READ + }) + public Response getWorkflowInstance(String id) { + final WorkflowRunMetadata runMetadata = dexEngine.getRunMetadataByInstanceId(id); + if (runMetadata == null) { + throw new NotFoundException(); + } + + return Response.ok(convert(runMetadata)).build(); + } + @Override @PermissionRequired({ Permissions.Constants.SYSTEM_CONFIGURATION, @@ -101,27 +115,7 @@ public Response listWorkflowRuns( final var response = ListWorkflowRunsResponse.builder() .workflowRuns(runsPage.items().stream() - .map( - runMetadata -> ListWorkflowRunsResponseItem.builder() - .id(runMetadata.id()) - .workflowName(runMetadata.workflowName()) - .workflowVersion(runMetadata.workflowVersion()) - .workflowInstanceId(runMetadata.workflowInstanceId()) - .status(convert(runMetadata.status())) - .priority(runMetadata.priority()) - .concurrencyKey(runMetadata.concurrencyKey()) - .labels(runMetadata.labels()) - .createdAt(runMetadata.createdAt().toEpochMilli()) - .updatedAt(runMetadata.updatedAt() != null - ? runMetadata.updatedAt().toEpochMilli() - : null) - .startedAt(runMetadata.startedAt() != null - ? runMetadata.startedAt().toEpochMilli() - : null) - .completedAt(runMetadata.completedAt() != null - ? runMetadata.completedAt().toEpochMilli() - : null) - .build()) + .map(WorkflowsResource::convert) .toList()) .pagination(createPaginationMetadata(getUriInfo(), runsPage)) .build(); @@ -152,4 +146,28 @@ private static WorkflowRunStatus convert(org.dependencytrack.dex.engine.api.Work }; } + private static org.dependencytrack.api.v2.model.WorkflowRunMetadata convert(WorkflowRunMetadata runMetadata) { + return org.dependencytrack.api.v2.model.WorkflowRunMetadata.builder() + .id(runMetadata.id()) + .workflowName(runMetadata.workflowName()) + .workflowVersion(runMetadata.workflowVersion()) + .workflowInstanceId(runMetadata.workflowInstanceId()) + .taskQueueName(runMetadata.taskQueueName()) + .status(convert(runMetadata.status())) + .priority(runMetadata.priority()) + .concurrencyKey(runMetadata.concurrencyKey()) + .labels(runMetadata.labels()) + .createdAt(runMetadata.createdAt().toEpochMilli()) + .updatedAt(runMetadata.updatedAt() != null + ? runMetadata.updatedAt().toEpochMilli() + : null) + .startedAt(runMetadata.startedAt() != null + ? runMetadata.startedAt().toEpochMilli() + : null) + .completedAt(runMetadata.completedAt() != null + ? runMetadata.completedAt().toEpochMilli() + : null) + .build(); + } + } diff --git a/apiserver/src/test/java/org/dependencytrack/resources/v2/WorkflowsResourceTest.java b/apiserver/src/test/java/org/dependencytrack/resources/v2/WorkflowsResourceTest.java index 5fb5e2b2cb..2c92b68d9f 100644 --- a/apiserver/src/test/java/org/dependencytrack/resources/v2/WorkflowsResourceTest.java +++ b/apiserver/src/test/java/org/dependencytrack/resources/v2/WorkflowsResourceTest.java @@ -44,6 +44,7 @@ import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -67,6 +68,78 @@ void afterEach() { Mockito.reset(DEX_ENGINE_MOCK); } + @Test + void getWorkflowInstanceShouldReturnMetadataOfWorkflowRun() { + initializeWithPermissions(Permissions.SYSTEM_CONFIGURATION_READ); + + final var workflowRunMetadata = new WorkflowRunMetadata( + UUID.fromString("724c0700-4eeb-45f0-8ff4-8bba369c0174"), + "workflowName", + 66, + "workflowInstanceId", + "taskQueueName", + WorkflowRunStatus.RUNNING, + "customStatus", + 12, + "concurrencyKey", + Map.of("foo", "bar"), + Instant.ofEpochMilli(666666), + Instant.ofEpochMilli(777777), + Instant.ofEpochMilli(888888), + null); + + doReturn(workflowRunMetadata) + .when(DEX_ENGINE_MOCK).getRunMetadataByInstanceId(eq("foo")); + + final Response response = jersey + .target("/workflow-instances/foo") + .request() + .header(X_API_KEY, apiKey) + .get(); + assertThat(response.getStatus()).isEqualTo(200); + assertThatJson(getPlainTextBody(response)).isEqualTo(/* language=JSON */ """ + { + "id": "724c0700-4eeb-45f0-8ff4-8bba369c0174", + "workflow_name": "workflowName", + "workflow_version": 66, + "workflow_instance_id": "workflowInstanceId", + "task_queue_name": "taskQueueName", + "status": "RUNNING", + "created_at": 666666, + "priority": 12, + "concurrency_key": "concurrencyKey", + "labels": { + "foo": "bar" + }, + "updated_at": 777777, + "started_at": 888888 + } + """); + } + + @Test + void getWorkflowInstanceShouldReturnNotFoundWhenNoMatchingRunExists() { + initializeWithPermissions(Permissions.SYSTEM_CONFIGURATION_READ); + + doReturn(null) + .when(DEX_ENGINE_MOCK).getRunMetadataByInstanceId(eq("foo")); + + final Response response = jersey + .target("/workflow-instances/foo") + .request() + .header(X_API_KEY, apiKey) + .get(); + assertThat(response.getStatus()).isEqualTo(404); + assertThatJson(getPlainTextBody(response)).isEqualTo(/* language=JSON */ """ + { + "type":"about:blank", + "status": 404, + "title": "Not Found", + "detail": "The requested resource could not be found." + } + """); + } + @Test public void listWorkflowRunsShouldReturnWorkflowRunMetadata() { initializeWithPermissions(Permissions.SYSTEM_CONFIGURATION_READ); @@ -76,6 +149,7 @@ public void listWorkflowRunsShouldReturnWorkflowRunMetadata() { "workflowName", 66, "workflowInstanceId", + "taskQueueName", WorkflowRunStatus.RUNNING, "customStatus", 12, @@ -102,6 +176,7 @@ public void listWorkflowRunsShouldReturnWorkflowRunMetadata() { "workflow_name": "workflowName", "workflow_version": 66, "workflow_instance_id": "workflowInstanceId", + "task_queue_name": "taskQueueName", "status": "RUNNING", "created_at": 666666, "priority": 12, diff --git a/dex/engine-api/src/main/java/org/dependencytrack/dex/engine/api/DexEngine.java b/dex/engine-api/src/main/java/org/dependencytrack/dex/engine/api/DexEngine.java index bf2ade659a..1bb28022a6 100644 --- a/dex/engine-api/src/main/java/org/dependencytrack/dex/engine/api/DexEngine.java +++ b/dex/engine-api/src/main/java/org/dependencytrack/dex/engine/api/DexEngine.java @@ -148,22 +148,32 @@ void registerActivity( * Retrieve all data about a workflow run, including its full event history. *

* If only high-level information about the run is required, prefer to use - * {@link #getRunMetadata(UUID)} as it is significantly more efficient. + * {@link #getRunMetadataById(UUID)} as it is significantly more efficient. * * @param id ID of the workflow run. * @return The run data, or {@code null} if no run with the given ID exists. */ @Nullable - WorkflowRun getRun(UUID id); + WorkflowRun getRunById(UUID id); /** - * Retrieve metadata about a workflow run. + * Retrieve metadata about a workflow run by ID. * * @param id ID of the workflow run. * @return The run metadata, or {@code null} if no run with the given ID exists. */ @Nullable - WorkflowRunMetadata getRunMetadata(UUID id); + WorkflowRunMetadata getRunMetadataById(UUID id); + + /** + * Retrieve metadata about a workflow run by workflow instance ID. + * + * @param instanceId Workflow instance ID of the workflow run. + * @return Metadata of the matching run, if and only if + * the run is in non-terminal state. Metadata of terminal runs is not returned. + */ + @Nullable + WorkflowRunMetadata getRunMetadataByInstanceId(String instanceId); Page listRuns(ListWorkflowRunsRequest request); diff --git a/dex/engine-api/src/main/java/org/dependencytrack/dex/engine/api/WorkflowRunMetadata.java b/dex/engine-api/src/main/java/org/dependencytrack/dex/engine/api/WorkflowRunMetadata.java index 7a20aa55d5..dcf439f87f 100644 --- a/dex/engine-api/src/main/java/org/dependencytrack/dex/engine/api/WorkflowRunMetadata.java +++ b/dex/engine-api/src/main/java/org/dependencytrack/dex/engine/api/WorkflowRunMetadata.java @@ -29,6 +29,7 @@ public record WorkflowRunMetadata( String workflowName, int workflowVersion, @Nullable String workflowInstanceId, + String taskQueueName, WorkflowRunStatus status, @Nullable String customStatus, int priority, diff --git a/dex/engine/src/main/java/org/dependencytrack/dex/engine/DexEngineImpl.java b/dex/engine/src/main/java/org/dependencytrack/dex/engine/DexEngineImpl.java index d1680eec1d..3652911a0c 100644 --- a/dex/engine/src/main/java/org/dependencytrack/dex/engine/DexEngineImpl.java +++ b/dex/engine/src/main/java/org/dependencytrack/dex/engine/DexEngineImpl.java @@ -74,7 +74,6 @@ import org.dependencytrack.dex.engine.persistence.jdbi.JdbiFactory; import org.dependencytrack.dex.engine.persistence.model.PolledWorkflowEvents; import org.dependencytrack.dex.engine.persistence.model.PolledWorkflowTask; -import org.dependencytrack.dex.engine.persistence.model.WorkflowRunMetadataRow; import org.dependencytrack.dex.engine.persistence.request.GetWorkflowRunHistoryRequest; import org.dependencytrack.dex.engine.support.Buffer; import org.dependencytrack.dex.proto.event.v1.ActivityTaskCompleted; @@ -607,7 +606,7 @@ public List createRuns(Collection eventHistory = jdbi.withHandle(handle -> { final var dao = new WorkflowRunDao(handle); @@ -643,27 +642,14 @@ public List createRuns(Collection new WorkflowDao(handle).getRunMetadataById(runId)); - if (metadataRow == null) { - return null; - } + @Override + public @Nullable WorkflowRunMetadata getRunMetadataById(UUID runId) { + return jdbi.withHandle(handle -> new WorkflowDao(handle).getRunMetadataById(runId)); + } - return new WorkflowRunMetadata( - metadataRow.id(), - metadataRow.workflowName(), - metadataRow.workflowVersion(), - metadataRow.workflowInstanceId(), - metadataRow.status(), - metadataRow.customStatus(), - metadataRow.priority(), - metadataRow.concurrencyKey(), - metadataRow.labels(), - metadataRow.createdAt(), - metadataRow.updatedAt(), - metadataRow.startedAt(), - metadataRow.completedAt()); + @Override + public @Nullable WorkflowRunMetadata getRunMetadataByInstanceId(String instanceId) { + return jdbi.withHandle(handle -> new WorkflowDao(handle).getRunMetadataByInstanceId(instanceId)); } @Override @@ -684,7 +670,7 @@ public void requestRunCancellation(UUID runId, String reason) { jdbi.useTransaction(handle -> { final var dao = new WorkflowDao(handle); - final WorkflowRunMetadataRow runMetadata = dao.getRunMetadataById(runId); + final WorkflowRunMetadata runMetadata = dao.getRunMetadataById(runId); if (runMetadata == null) { throw new NoSuchElementException("A workflow run with ID %s does not exist".formatted(runId)); } else if (runMetadata.status().isTerminal()) { @@ -714,7 +700,7 @@ public void requestRunSuspension(UUID runId) { jdbi.useTransaction(handle -> { final var dao = new WorkflowDao(handle); - final WorkflowRunMetadataRow runMetadata = dao.getRunMetadataById(runId); + final WorkflowRunMetadata runMetadata = dao.getRunMetadataById(runId); if (runMetadata == null) { throw new NoSuchElementException("A workflow run with ID %s does not exist".formatted(runId)); } else if (runMetadata.status().isTerminal()) { @@ -746,7 +732,7 @@ public void requestRunResumption(UUID runId) { jdbi.useTransaction(handle -> { final var dao = new WorkflowDao(handle); - final WorkflowRunMetadataRow runMetadata = dao.getRunMetadataById(runId); + final WorkflowRunMetadata runMetadata = dao.getRunMetadataById(runId); if (runMetadata == null) { throw new NoSuchElementException("A workflow run with ID %s does not exist".formatted(runId)); } else if (runMetadata.status().isTerminal()) { @@ -995,6 +981,7 @@ private void completeWorkflowTasksInternal( run.workflowName(), run.workflowVersion(), run.workflowInstanceId(), + run.taskQueueName(), run.status(), run.customStatus(), run.priority(), diff --git a/dex/engine/src/main/java/org/dependencytrack/dex/engine/persistence/WorkflowDao.java b/dex/engine/src/main/java/org/dependencytrack/dex/engine/persistence/WorkflowDao.java index f4579ee8c8..5da466e473 100644 --- a/dex/engine/src/main/java/org/dependencytrack/dex/engine/persistence/WorkflowDao.java +++ b/dex/engine/src/main/java/org/dependencytrack/dex/engine/persistence/WorkflowDao.java @@ -23,6 +23,7 @@ import org.dependencytrack.dex.engine.WorkflowMessage; import org.dependencytrack.dex.engine.WorkflowTask; import org.dependencytrack.dex.engine.api.TaskQueue; +import org.dependencytrack.dex.engine.api.WorkflowRunMetadata; import org.dependencytrack.dex.engine.api.WorkflowRunStatus; import org.dependencytrack.dex.engine.api.request.CreateTaskQueueRequest; import org.dependencytrack.dex.engine.api.request.ListTaskQueuesRequest; @@ -35,7 +36,6 @@ import org.dependencytrack.dex.engine.persistence.model.PolledWorkflowEvent; import org.dependencytrack.dex.engine.persistence.model.PolledWorkflowEvents; import org.dependencytrack.dex.engine.persistence.model.PolledWorkflowTask; -import org.dependencytrack.dex.engine.persistence.model.WorkflowRunMetadataRow; import org.dependencytrack.dex.engine.persistence.request.GetWorkflowRunHistoryRequest; import org.dependencytrack.dex.proto.event.v1.WorkflowEvent; import org.jdbi.v3.core.Handle; @@ -385,7 +385,7 @@ cte_deleted_task as ( .list(); } - public @Nullable WorkflowRunMetadataRow getRunMetadataById(UUID id) { + public @Nullable WorkflowRunMetadata getRunMetadataById(UUID id) { final Query query = jdbiHandle.createQuery(""" select * from dex_workflow_run @@ -394,7 +394,22 @@ cte_deleted_task as ( return query .bind("id", id) - .mapTo(WorkflowRunMetadataRow.class) + .mapTo(WorkflowRunMetadata.class) + .findOne() + .orElse(null); + } + + public @Nullable WorkflowRunMetadata getRunMetadataByInstanceId(String instanceId) { + final Query query = jdbiHandle.createQuery(""" + select * + from dex_workflow_run + where workflow_instance_id = :instanceId + and status in ('CREATED', 'RUNNING', 'SUSPENDED') + """); + + return query + .bind("instanceId", instanceId) + .mapTo(WorkflowRunMetadata.class) .findOne() .orElse(null); } diff --git a/dex/engine/src/main/java/org/dependencytrack/dex/engine/persistence/WorkflowRunDao.java b/dex/engine/src/main/java/org/dependencytrack/dex/engine/persistence/WorkflowRunDao.java index 493e32a0e1..3c1ca08942 100644 --- a/dex/engine/src/main/java/org/dependencytrack/dex/engine/persistence/WorkflowRunDao.java +++ b/dex/engine/src/main/java/org/dependencytrack/dex/engine/persistence/WorkflowRunDao.java @@ -26,7 +26,6 @@ import org.dependencytrack.dex.engine.api.request.ListWorkflowRunEventsRequest; import org.dependencytrack.dex.engine.api.request.ListWorkflowRunsRequest; import org.dependencytrack.dex.engine.persistence.model.WorkflowRunHistoryEntry; -import org.dependencytrack.dex.engine.persistence.model.WorkflowRunMetadataRow; import org.dependencytrack.dex.proto.event.v1.WorkflowEvent; import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.generic.GenericType; @@ -225,21 +224,7 @@ select count(*) .define("sortBy", sortBy) .define("sortDirection", sortDirection) .defineNamedBindings() - .mapTo(WorkflowRunMetadataRow.class) - .map(row -> new WorkflowRunMetadata( - row.id(), - row.workflowName(), - row.workflowVersion(), - row.workflowInstanceId(), - row.status(), - row.customStatus(), - row.priority(), - row.concurrencyKey(), - row.labels(), - row.createdAt(), - row.updatedAt(), - row.startedAt(), - row.completedAt())) + .mapTo(WorkflowRunMetadata.class) .list(); final List resultItems = rows.size() > 1 diff --git a/dex/engine/src/main/java/org/dependencytrack/dex/engine/persistence/jdbi/JdbiFactory.java b/dex/engine/src/main/java/org/dependencytrack/dex/engine/persistence/jdbi/JdbiFactory.java index d4b2358c2a..149c73afaf 100644 --- a/dex/engine/src/main/java/org/dependencytrack/dex/engine/persistence/jdbi/JdbiFactory.java +++ b/dex/engine/src/main/java/org/dependencytrack/dex/engine/persistence/jdbi/JdbiFactory.java @@ -21,11 +21,11 @@ import org.dependencytrack.common.pagination.PageTokenEncoder; import org.dependencytrack.dex.engine.ActivityTaskId; import org.dependencytrack.dex.engine.api.TaskQueue; +import org.dependencytrack.dex.engine.api.WorkflowRunMetadata; import org.dependencytrack.dex.engine.persistence.model.PolledActivityTask; import org.dependencytrack.dex.engine.persistence.model.PolledWorkflowEvent; import org.dependencytrack.dex.engine.persistence.model.PolledWorkflowTask; import org.dependencytrack.dex.engine.persistence.model.WorkflowRunHistoryEntry; -import org.dependencytrack.dex.engine.persistence.model.WorkflowRunMetadataRow; import org.dependencytrack.dex.proto.common.v1.RetryPolicy; import org.dependencytrack.dex.proto.event.v1.WorkflowEvent; import org.dependencytrack.dex.proto.payload.v1.Payload; @@ -93,7 +93,7 @@ public static Jdbi create( WorkflowRunHistoryEntry.class, new WorkflowRunHistoryEntryRowMapper()) .registerRowMapper( - WorkflowRunMetadataRow.class, + WorkflowRunMetadata.class, new WorkflowRunMetadataRowMapper()); } diff --git a/dex/engine/src/main/java/org/dependencytrack/dex/engine/persistence/jdbi/WorkflowRunMetadataRowMapper.java b/dex/engine/src/main/java/org/dependencytrack/dex/engine/persistence/jdbi/WorkflowRunMetadataRowMapper.java index fce9b89334..44c28fbef2 100644 --- a/dex/engine/src/main/java/org/dependencytrack/dex/engine/persistence/jdbi/WorkflowRunMetadataRowMapper.java +++ b/dex/engine/src/main/java/org/dependencytrack/dex/engine/persistence/jdbi/WorkflowRunMetadataRowMapper.java @@ -18,8 +18,8 @@ */ package org.dependencytrack.dex.engine.persistence.jdbi; +import org.dependencytrack.dex.engine.api.WorkflowRunMetadata; import org.dependencytrack.dex.engine.api.WorkflowRunStatus; -import org.dependencytrack.dex.engine.persistence.model.WorkflowRunMetadataRow; import org.jdbi.v3.core.config.ConfigRegistry; import org.jdbi.v3.core.mapper.ColumnMapper; import org.jdbi.v3.core.mapper.ColumnMappers; @@ -39,7 +39,7 @@ import static java.util.Objects.requireNonNull; import static org.jdbi.v3.core.generic.GenericTypes.parameterizeClass; -final class WorkflowRunMetadataRowMapper implements RowMapper { +final class WorkflowRunMetadataRowMapper implements RowMapper { private static final Type LABELS_TYPE = parameterizeClass(Map.class, String.class, String.class); @@ -53,10 +53,10 @@ public void init(final ConfigRegistry registry) { } @Override - public WorkflowRunMetadataRow map(final ResultSet rs, final StatementContext ctx) throws SQLException { + public WorkflowRunMetadata map(final ResultSet rs, final StatementContext ctx) throws SQLException { requireNonNull(instantColumnMapper); - return new WorkflowRunMetadataRow( + return new WorkflowRunMetadata( rs.getObject("id", UUID.class), rs.getString("workflow_name"), rs.getInt("workflow_version"), @@ -64,8 +64,8 @@ public WorkflowRunMetadataRow map(final ResultSet rs, final StatementContext ctx rs.getString("task_queue_name"), WorkflowRunStatus.valueOf(rs.getString("status")), rs.getString("custom_status"), - rs.getString("concurrency_key"), rs.getInt("priority"), + rs.getString("concurrency_key"), getLabels(rs, ctx), instantColumnMapper.map(rs, "created_at", ctx), instantColumnMapper.map(rs, "updated_at", ctx), diff --git a/dex/engine/src/main/java/org/dependencytrack/dex/engine/persistence/model/WorkflowRunMetadataRow.java b/dex/engine/src/main/java/org/dependencytrack/dex/engine/persistence/model/WorkflowRunMetadataRow.java deleted file mode 100644 index c34d1a1678..0000000000 --- a/dex/engine/src/main/java/org/dependencytrack/dex/engine/persistence/model/WorkflowRunMetadataRow.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * This file is part of Dependency-Track. - * - * 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. - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright (c) OWASP Foundation. All Rights Reserved. - */ -package org.dependencytrack.dex.engine.persistence.model; - -import org.dependencytrack.dex.engine.api.WorkflowRunStatus; -import org.jspecify.annotations.Nullable; - -import java.time.Instant; -import java.util.Map; -import java.util.UUID; - -public record WorkflowRunMetadataRow( - UUID id, - String workflowName, - int workflowVersion, - String workflowInstanceId, - String taskQueueName, - WorkflowRunStatus status, - @Nullable String customStatus, - @Nullable String concurrencyKey, - int priority, - @Nullable Map labels, - Instant createdAt, - @Nullable Instant updatedAt, - @Nullable Instant startedAt, - @Nullable Instant completedAt) { -} diff --git a/dex/engine/src/test/java/org/dependencytrack/dex/engine/DexEngineImplTest.java b/dex/engine/src/test/java/org/dependencytrack/dex/engine/DexEngineImplTest.java index cd07b5292e..e4fd947367 100644 --- a/dex/engine/src/test/java/org/dependencytrack/dex/engine/DexEngineImplTest.java +++ b/dex/engine/src/test/java/org/dependencytrack/dex/engine/DexEngineImplTest.java @@ -723,7 +723,7 @@ void shouldWaitForExternalEvent() throws Exception { await("Update") .atMost(Duration.ofSeconds(5)) .untilAsserted(() -> { - final WorkflowRunMetadata run = engine.getRunMetadata(runId); + final WorkflowRunMetadata run = engine.getRunMetadataById(runId); assertThat(run.updatedAt()).isNotNull(); }); @@ -1292,6 +1292,50 @@ void shouldListRunEvents() { assertThat(historyPage.nextPageToken()).isNotNull(); } + @Nested + class GetRunMetadataByInstanceIdTest { + + @Test + void shouldReturnMetadataWhenRunExistsWithNonTerminalState() { + registerWorkflow("foo", (ctx, arg) -> null); + + final UUID runId = engine.createRun( + new CreateWorkflowRunRequest<>("foo", 1) + .withWorkflowInstanceId("foo-instance")); + assertThat(runId).isNotNull(); + + final WorkflowRunMetadata runMetadata = + engine.getRunMetadataByInstanceId("foo-instance"); + assertThat(runMetadata).isNotNull(); + assertThat(runMetadata.id()).isEqualTo(runId); + } + + @Test + void shouldReturnNullWhenRunDoesNotExist() { + final WorkflowRunMetadata runMetadata = + engine.getRunMetadataByInstanceId("doesNotExist"); + assertThat(runMetadata).isNull(); + } + + @Test + void shouldReturnNullWhenRunExistsWithTerminalState() { + registerWorkflow("foo", (ctx, arg) -> null); + registerWorkflowWorker("workflow-worker", 1); + engine.start(); + + final UUID runId = engine.createRun( + new CreateWorkflowRunRequest<>("foo", 1) + .withWorkflowInstanceId("foo-instance")); + assertThat(runId).isNotNull(); + awaitRunStatus(runId, WorkflowRunStatus.COMPLETED); + + final WorkflowRunMetadata runMetadata = + engine.getRunMetadataByInstanceId("foo-instance"); + assertThat(runMetadata).isNull(); + } + + } + @Nested class WorkflowTaskQueueTest { @@ -1555,7 +1599,7 @@ private WorkflowRunMetadata awaitRunStatus( return await("Workflow Run Status to become " + expectedStatus) .atMost(timeout) .failFast(() -> { - final WorkflowRunStatus currentStatus = engine.getRunMetadata(runId).status(); + final WorkflowRunStatus currentStatus = engine.getRunMetadataById(runId).status(); if (currentStatus.isTerminal() && !expectedStatus.isTerminal()) { return true; } @@ -1564,7 +1608,7 @@ private WorkflowRunMetadata awaitRunStatus( && expectedStatus.isTerminal() && currentStatus != expectedStatus; }) - .until(() -> engine.getRunMetadata(runId), run -> run.status() == expectedStatus); + .until(() -> engine.getRunMetadataById(runId), run -> run.status() == expectedStatus); } private WorkflowRunMetadata awaitRunStatus(final UUID runId, final WorkflowRunStatus expectedStatus) { diff --git a/dex/testing/src/main/java/org/dependencytrack/dex/testing/WorkflowTestExtension.java b/dex/testing/src/main/java/org/dependencytrack/dex/testing/WorkflowTestExtension.java index 25bd1f6581..5e9c8ba450 100644 --- a/dex/testing/src/main/java/org/dependencytrack/dex/testing/WorkflowTestExtension.java +++ b/dex/testing/src/main/java/org/dependencytrack/dex/testing/WorkflowTestExtension.java @@ -117,7 +117,7 @@ public WorkflowTestExtension withConfigCustomizer(final @Nullable Consumer { - final WorkflowRun run = getEngine().getRun(runId); + final WorkflowRun run = getEngine().getRunById(runId); if (run == null) { return; } @@ -141,7 +141,7 @@ public WorkflowTestExtension withConfigCustomizer(final @Nullable Consumer getEngine().getRun(runId), run -> run != null && run.status() == expectedStatus); + .until(() -> getEngine().getRunById(runId), run -> run != null && run.status() == expectedStatus); } public @Nullable WorkflowRun awaitRunStatus(final UUID runId, final WorkflowRunStatus expectedStatus) {