Skip to content

Commit e7e57b9

Browse files
Fix workflow parsing for log artifact
Signed-off-by: droctothorpe <mythicalsunlight@gmail.com> Co-authored-by: quinnovator <jack@jq.codes>
1 parent 03426c5 commit e7e57b9

File tree

3 files changed

+52
-31
lines changed

3 files changed

+52
-31
lines changed

frontend/server/workflow-helper.ts

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,27 @@
1111
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
14-
import path from 'path';
1514
import { PassThrough, Stream } from 'stream';
1615
import { ClientOptions as MinioClientOptions } from 'minio';
1716
import { getK8sSecret, getArgoWorkflow, getPodLogs } from './k8s-helper';
1817
import { createMinioClient, MinioRequestConfig, getObjectStream } from './minio-helper';
1918

2019
export interface PartialArgoWorkflow {
2120
status: {
21+
artifactRepositoryRef?: ArtifactRepositoryRef;
2222
nodes?: ArgoWorkflowStatusNode;
2323
};
2424
}
2525

26+
export interface ArtifactRepositoryRef {
27+
artifactRepository?: ArtifactRepository;
28+
}
29+
30+
export interface ArtifactRepository {
31+
archiveLogs?: boolean;
32+
s3?: S3Artifact;
33+
}
34+
2635
export interface ArgoWorkflowStatusNode {
2736
[key: string]: ArgoWorkflowStatusNodeInfo;
2837
}
@@ -34,9 +43,12 @@ export interface ArgoWorkflowStatusNodeInfo {
3443
}
3544

3645
export interface ArtifactRecord {
37-
archiveLogs?: boolean;
38-
name: string;
39-
s3?: S3Artifact;
46+
name?: string;
47+
s3: S3Key;
48+
}
49+
50+
export interface S3Key {
51+
key: string;
4052
}
4153

4254
export interface S3Artifact {
@@ -80,6 +92,7 @@ export function composePodLogsStreamHandler<T = Stream>(
8092
/**
8193
* Returns a stream containing the pod logs using kubernetes api.
8294
* @param podName name of the pod.
95+
* @param createdAt YYYY-MM-DD run was created. Not used.
8396
* @param namespace namespace of the pod (uses the same namespace as the server if not provided).
8497
* @param containerName container's name of the pod, the default value is 'main'.
8598
*/
@@ -91,14 +104,17 @@ export async function getPodLogsStreamFromK8s(
91104
) {
92105
const stream = new PassThrough();
93106
stream.end(await getPodLogs(podName, namespace, containerName));
94-
console.log(`Getting logs for pod:${podName} in namespace ${namespace}.`);
107+
console.log(
108+
`Getting logs for pod, ${podName}, in namespace, ${namespace}, by calling the Kubernetes API.`,
109+
);
95110
return stream;
96111
}
97112

98113
/**
99114
* Returns a stream containing the pod logs using the information provided in the
100115
* workflow status (uses k8s api to retrieve the workflow and secrets).
101116
* @param podName name of the pod.
117+
* @param createdAt YYYY-MM-DD run was created. Not used.
102118
* @param namespace namespace of the pod (uses the same namespace as the server if not provided).
103119
*/
104120
export const getPodLogsStreamFromWorkflow = toGetPodLogsStream(
@@ -121,7 +137,7 @@ export function toGetPodLogsStream(
121137
) {
122138
return async (podName: string, createdAt: string, namespace?: string) => {
123139
const request = await getMinioRequestConfig(podName, createdAt, namespace);
124-
console.log(`Getting logs for pod:${podName} from ${request.bucket}/${request.key}.`);
140+
console.log(`Getting logs for pod, ${podName}, from ${request.bucket}/${request.key}.`);
125141
return await getObjectStream(request);
126142
};
127143
}
@@ -193,33 +209,42 @@ export async function getPodLogsMinioRequestConfigfromWorkflow(
193209
podName: string,
194210
): Promise<MinioRequestConfig> {
195211
let workflow: PartialArgoWorkflow;
212+
// We should probably parameterize this replace statement. It's brittle to
213+
// changes in implementation. But brittle is better than completely broken.
214+
let workflowName = podName.replace(/-system-container-impl-.*/, '');
196215
try {
197-
workflow = await getArgoWorkflow(workflowNameFromPodName(podName));
216+
workflow = await getArgoWorkflow(workflowName);
198217
} catch (err) {
199218
throw new Error(`Unable to retrieve workflow status: ${err}.`);
200219
}
201220

221+
// archiveLogs can be set globally for the workflow as a whole and / or for
222+
// each individual task. The compiler sets it globally so we look for it in
223+
// the global field, which is documented here:
224+
// https://argo-workflows.readthedocs.io/en/release-3.4/fields/#workflow
225+
if (!workflow.status.artifactRepositoryRef?.artifactRepository?.archiveLogs) {
226+
throw new Error('Unable to retrieve logs from artifact store; archiveLogs is disabled.');
227+
}
228+
202229
let artifacts: ArtifactRecord[] | undefined;
203-
// check if required fields are available
204230
if (workflow.status && workflow.status.nodes) {
205-
const node = workflow.status.nodes[podName];
206-
if (node && node.outputs && node.outputs.artifacts) {
207-
artifacts = node.outputs.artifacts;
208-
}
231+
const nodeName = podName.replace('-system-container-impl', '');
232+
const node = workflow.status.nodes[nodeName];
233+
artifacts = node?.outputs?.artifacts || undefined;
209234
}
210235
if (!artifacts) {
211-
throw new Error('Unable to find pod info in workflow status to retrieve logs.');
236+
throw new Error('Unable to find corresponding log artifact in node.');
212237
}
213238

214-
const archiveLogs: ArtifactRecord[] = artifacts.filter((artifact: any) => artifact.archiveLogs);
215-
216-
if (archiveLogs.length === 0) {
217-
throw new Error('Unable to find pod log archive information from workflow status.');
239+
const logKey =
240+
artifacts.find((artifact: ArtifactRecord) => artifact.name === 'main-logs')?.s3.key || false;
241+
if (!logKey) {
242+
throw new Error('No artifact named "main-logs" for node.');
218243
}
219244

220-
const s3Artifact = archiveLogs[0].s3;
245+
const s3Artifact = workflow.status.artifactRepositoryRef.artifactRepository.s3 || false;
221246
if (!s3Artifact) {
222-
throw new Error('Unable to find s3 artifact info from workflow status.');
247+
throw new Error('Unable to find artifact repository information from workflow status.');
223248
}
224249

225250
const { host, port } = urlSplit(s3Artifact.endpoint, s3Artifact.insecure);
@@ -228,6 +253,10 @@ export async function getPodLogsMinioRequestConfigfromWorkflow(
228253
const client = await createMinioClient(
229254
{
230255
accessKey,
256+
// TODO: endPoint needs to be set to 'localhost' for local development.
257+
// start-proxy-and-server.sh sets MINIO_HOST=localhost, but it doesn't
258+
// seem to be respected when running the server in development mode.
259+
// Investigate and fix this.
231260
endPoint: host,
232261
port,
233262
secretKey,
@@ -238,7 +267,7 @@ export async function getPodLogsMinioRequestConfigfromWorkflow(
238267
return {
239268
bucket: s3Artifact.bucket,
240269
client,
241-
key: s3Artifact.key,
270+
key: logKey,
242271
};
243272
}
244273

@@ -268,13 +297,3 @@ function urlSplit(uri: string, insecure: boolean) {
268297
}
269298
return { host: chunks[0], port: parseInt(chunks[1], 10) };
270299
}
271-
272-
/**
273-
* Infers workflow name from pod name.
274-
* @param podName name of the pod.
275-
*/
276-
function workflowNameFromPodName(podName: string) {
277-
const chunks = podName.split('-');
278-
chunks.pop();
279-
return chunks.join('-');
280-
}

frontend/src/pages/RunDetails.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,6 +1165,7 @@ describe('RunDetails', () => {
11651165
'test-run-id',
11661166
'workflow1-template1-node1',
11671167
'ns',
1168+
'',
11681169
);
11691170
expect(tree).toMatchSnapshot();
11701171
});
@@ -1255,6 +1256,7 @@ describe('RunDetails', () => {
12551256
'test-run-id',
12561257
'workflow1-template1-node1',
12571258
'username',
1259+
'',
12581260
);
12591261
});
12601262

frontend/src/pages/RunDetails.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1062,7 +1062,7 @@ class RunDetails extends Page<RunDetailsInternalProps, RunDetailsState> {
10621062

10631063
try {
10641064
const nodeName = getNodeNameFromNodeId(this.state.workflow!, selectedNodeDetails.id);
1065-
selectedNodeDetails.logs = await Apis.getPodLogs(runId, nodeName, namespace);
1065+
selectedNodeDetails.logs = await Apis.getPodLogs(runId, nodeName, namespace, '');
10661066
} catch (err) {
10671067
let errMsg = await errorToMessage(err);
10681068
logsBannerMessage = 'Failed to retrieve pod logs.';

0 commit comments

Comments
 (0)