Merge branch '15814-wb2-secrets' refs #15814
[arvados.git] / services / workbench2 / src / models / workflow.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import { Resource, ResourceKind } from "./resource";
6 import { safeLoad } from 'js-yaml';
7 import { CommandOutputParameter } from "cwlts/mappings/v1.0/CommandOutputParameter";
8
9 export interface WorkflowResource extends Resource {
10     kind: ResourceKind.WORKFLOW;
11     name: string;
12     description: string;
13     definition: string;
14 }
15 export interface WorkflowResourceDefinition {
16     cwlVersion: string;
17     $graph?: Array<Workflow | CommandLineTool>;
18 }
19 export interface Workflow {
20     class: 'Workflow';
21     doc?: string;
22     id?: string;
23     inputs: CommandInputParameter[];
24     outputs: any[];
25     steps: any[];
26     hints?: ProcessRequirement[];
27 }
28
29 export interface CommandLineTool {
30     class: 'CommandLineTool';
31     id: string;
32     inputs: CommandInputParameter[];
33     outputs: any[];
34     hints?: ProcessRequirement[];
35 }
36
37 export type ProcessRequirement = GenericProcessRequirement | WorkflowRunnerResources;
38
39 export interface GenericProcessRequirement {
40     class: string;
41 }
42
43 export interface WorkflowRunnerResources {
44     class: 'http://arvados.org/cwl#WorkflowRunnerResources';
45     ramMin?: number;
46     coresMin?: number;
47     keep_cache?: number;
48     acrContainerImage?: string;
49 }
50
51 export type CommandInputParameter =
52     BooleanCommandInputParameter |
53     IntCommandInputParameter |
54     LongCommandInputParameter |
55     FloatCommandInputParameter |
56     DoubleCommandInputParameter |
57     StringCommandInputParameter |
58     FileCommandInputParameter |
59     DirectoryCommandInputParameter |
60     StringArrayCommandInputParameter |
61     IntArrayCommandInputParameter |
62     FloatArrayCommandInputParameter |
63     FileArrayCommandInputParameter |
64     DirectoryArrayCommandInputParameter |
65     EnumCommandInputParameter;
66
67 export enum CWLType {
68     NULL = 'null',
69     BOOLEAN = 'boolean',
70     INT = 'int',
71     LONG = 'long',
72     FLOAT = 'float',
73     DOUBLE = 'double',
74     STRING = 'string',
75     FILE = 'File',
76     DIRECTORY = 'Directory',
77 }
78
79 export interface CommandInputEnumSchema {
80     symbols: string[];
81     type: 'enum';
82     label?: string;
83     name?: string;
84 }
85
86 export interface CommandInputArraySchema<ItemType> {
87     items: ItemType;
88     type: 'array';
89     label?: string;
90 }
91
92 export interface File {
93     class: CWLType.FILE;
94     location?: string;
95     path?: string;
96     basename?: string;
97 }
98
99 export interface Directory {
100     class: CWLType.DIRECTORY;
101     location?: string;
102     path?: string;
103     basename?: string;
104 }
105
106 export interface SecretInclude {
107     $include: string;
108 }
109
110 export interface GenericCommandInputParameter<Type, Value> {
111     id: string;
112     label?: string;
113     doc?: string | string[];
114     default?: Value;
115     type?: Type | Array<Type | CWLType.NULL>;
116     value?: Value;
117     disabled?: boolean;
118     secret?: boolean;
119 }
120 export type GenericArrayCommandInputParameter<Type, Value> = GenericCommandInputParameter<CommandInputArraySchema<Type>, Value[]>;
121
122 export type BooleanCommandInputParameter = GenericCommandInputParameter<CWLType.BOOLEAN, boolean>;
123 export type IntCommandInputParameter = GenericCommandInputParameter<CWLType.INT, number>;
124 export type LongCommandInputParameter = GenericCommandInputParameter<CWLType.LONG, number>;
125 export type FloatCommandInputParameter = GenericCommandInputParameter<CWLType.FLOAT, number>;
126 export type DoubleCommandInputParameter = GenericCommandInputParameter<CWLType.DOUBLE, number>;
127 export type StringCommandInputParameter = GenericCommandInputParameter<CWLType.STRING, string>;
128 export type FileCommandInputParameter = GenericCommandInputParameter<CWLType.FILE, File>;
129 export type DirectoryCommandInputParameter = GenericCommandInputParameter<CWLType.DIRECTORY, Directory>;
130 export type EnumCommandInputParameter = GenericCommandInputParameter<CommandInputEnumSchema, string>;
131
132 export type StringArrayCommandInputParameter = GenericArrayCommandInputParameter<CWLType.STRING, string>;
133 export type IntArrayCommandInputParameter = GenericArrayCommandInputParameter<CWLType.INT, string>;
134 export type FloatArrayCommandInputParameter = GenericArrayCommandInputParameter<CWLType.FLOAT, string>;
135 export type FileArrayCommandInputParameter = GenericArrayCommandInputParameter<CWLType.FILE, File>;
136 export type DirectoryArrayCommandInputParameter = GenericArrayCommandInputParameter<CWLType.DIRECTORY, Directory>;
137 export type SecretCommandInputParameter = GenericArrayCommandInputParameter<CWLType.STRING, SecretInclude>;
138
139
140 export type WorkflowInputsData = {
141     [key: string]: boolean | number | string | File | Directory | SecretInclude;
142 };
143 export const parseWorkflowDefinition = (workflow: WorkflowResource): WorkflowResourceDefinition => {
144     const definition = safeLoad(workflow.definition);
145     return definition;
146 };
147
148 export const getWorkflow = (workflowDefinition: WorkflowResourceDefinition) => {
149     if (!workflowDefinition.$graph) { return undefined; }
150     const mainWorkflow = workflowDefinition.$graph.find(item => item.id === '#main');
151     return mainWorkflow
152         ? mainWorkflow
153         : undefined;
154 };
155
156 export interface CwlSecrets {
157     class: 'http://commonwl.org/cwltool#Secrets';
158     secrets: string[];
159 }
160
161 export const getWorkflowInputs = (workflowDefinition: WorkflowResourceDefinition) => {
162     if (!workflowDefinition) { return undefined; }
163     const wf = getWorkflow(workflowDefinition);
164     if (!wf) { return undefined; }
165     const inputs = wf.inputs;
166     if (wf.hints) {
167         const secrets = wf.hints.find(item => item.class === 'http://commonwl.org/cwltool#Secrets') as CwlSecrets | undefined;
168         if (secrets?.secrets) {
169             inputs.forEach((param) => {
170                 param.secret = secrets.secrets.includes(param.id);
171             });
172         }
173     }
174
175     return inputs;
176 };
177
178
179 export const getWorkflowOutputs = (workflowDefinition: WorkflowResourceDefinition) => {
180     if (!workflowDefinition) { return undefined; }
181     return getWorkflow(workflowDefinition)
182         ? getWorkflow(workflowDefinition)!.outputs
183         : undefined;
184 };
185
186 export const getInputLabel = (input: CommandInputParameter) => {
187     return `${input.label || input.id.split('/').pop()}`;
188 };
189
190 export const getIOParamId = (input: CommandInputParameter | CommandOutputParameter) => {
191     return `${input.id.split('/').pop()}`;
192 };
193
194 export const isRequiredInput = ({ type }: CommandInputParameter) => {
195     if (type instanceof Array) {
196         for (const t of type) {
197             if (t === CWLType.NULL) {
198                 return false;
199             }
200         }
201     }
202     return true;
203 };
204
205 export const isPrimitiveOfType = (input: GenericCommandInputParameter<any, any>, type: CWLType) =>
206     input.type instanceof Array
207         ? input.type.indexOf(type) > -1
208         : input.type === type;
209
210 export const isArrayOfType = (input: GenericCommandInputParameter<any, any>, type: CWLType) =>
211     input.type instanceof Array
212         ? (input.type.filter(t => typeof t === 'object' &&
213             t.type === 'array' &&
214             t.items === type).length > 0)
215         : (typeof input.type === 'object' &&
216             input.type.type === 'array' &&
217             input.type.items === type);
218
219 export const getEnumType = (input: GenericCommandInputParameter<any, any>) => {
220     if (input.type instanceof Array) {
221         const f = input.type.filter(t => typeof t === 'object' &&
222             !(t instanceof Array) &&
223             t.type === 'enum');
224         if (f.length > 0) {
225             return f[0];
226         }
227     } else {
228         if ((typeof input.type === 'object' &&
229             !(input.type instanceof Array) &&
230             input.type.type === 'enum')) {
231             return input.type;
232         }
233     }
234     return null;
235 };
236
237 export const isSecret = (input: GenericCommandInputParameter<any, any>) =>
238     (typeof input.value === 'object') && input.value.$include?.startsWith("/secrets/");
239
240 export const stringifyInputType = ({ type }: CommandInputParameter) => {
241         if (typeof type === 'string') {
242         return type;
243     } else if (type instanceof Array) {
244         return type.join(' | ');
245     } else if (typeof type === 'object') {
246         if (type.type === 'enum') {
247             return 'enum';
248         } else if (type.type === 'array') {
249             return `${type.items}[]`;
250         } else {
251             return 'unknown';
252         }
253     } else {
254         return 'unknown';
255     }
256 };