11
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
// See the License for the specific language governing permissions and
13
13
// limitations under the License.
14
- import path from 'path' ;
15
14
import { PassThrough , Stream } from 'stream' ;
16
15
import { ClientOptions as MinioClientOptions } from 'minio' ;
17
16
import { getK8sSecret , getArgoWorkflow , getPodLogs } from './k8s-helper' ;
18
17
import { createMinioClient , MinioRequestConfig , getObjectStream } from './minio-helper' ;
19
18
20
19
export interface PartialArgoWorkflow {
21
20
status : {
21
+ artifactRepositoryRef ?: ArtifactRepositoryRef ;
22
22
nodes ?: ArgoWorkflowStatusNode ;
23
23
} ;
24
24
}
25
25
26
+ export interface ArtifactRepositoryRef {
27
+ artifactRepository ?: ArtifactRepository ;
28
+ }
29
+
30
+ export interface ArtifactRepository {
31
+ archiveLogs ?: boolean ;
32
+ s3 ?: S3Artifact ;
33
+ }
34
+
26
35
export interface ArgoWorkflowStatusNode {
27
36
[ key : string ] : ArgoWorkflowStatusNodeInfo ;
28
37
}
@@ -34,9 +43,12 @@ export interface ArgoWorkflowStatusNodeInfo {
34
43
}
35
44
36
45
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 ;
40
52
}
41
53
42
54
export interface S3Artifact {
@@ -80,6 +92,7 @@ export function composePodLogsStreamHandler<T = Stream>(
80
92
/**
81
93
* Returns a stream containing the pod logs using kubernetes api.
82
94
* @param podName name of the pod.
95
+ * @param createdAt YYYY-MM-DD run was created. Not used.
83
96
* @param namespace namespace of the pod (uses the same namespace as the server if not provided).
84
97
* @param containerName container's name of the pod, the default value is 'main'.
85
98
*/
@@ -91,14 +104,17 @@ export async function getPodLogsStreamFromK8s(
91
104
) {
92
105
const stream = new PassThrough ( ) ;
93
106
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
+ ) ;
95
110
return stream ;
96
111
}
97
112
98
113
/**
99
114
* Returns a stream containing the pod logs using the information provided in the
100
115
* workflow status (uses k8s api to retrieve the workflow and secrets).
101
116
* @param podName name of the pod.
117
+ * @param createdAt YYYY-MM-DD run was created. Not used.
102
118
* @param namespace namespace of the pod (uses the same namespace as the server if not provided).
103
119
*/
104
120
export const getPodLogsStreamFromWorkflow = toGetPodLogsStream (
@@ -121,7 +137,7 @@ export function toGetPodLogsStream(
121
137
) {
122
138
return async ( podName : string , createdAt : string , namespace ?: string ) => {
123
139
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 } .` ) ;
125
141
return await getObjectStream ( request ) ;
126
142
} ;
127
143
}
@@ -193,33 +209,42 @@ export async function getPodLogsMinioRequestConfigfromWorkflow(
193
209
podName : string ,
194
210
) : Promise < MinioRequestConfig > {
195
211
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 ( / - s y s t e m - c o n t a i n e r - i m p l - .* / , '' ) ;
196
215
try {
197
- workflow = await getArgoWorkflow ( workflowNameFromPodName ( podName ) ) ;
216
+ workflow = await getArgoWorkflow ( workflowName ) ;
198
217
} catch ( err ) {
199
218
throw new Error ( `Unable to retrieve workflow status: ${ err } .` ) ;
200
219
}
201
220
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
+
202
229
let artifacts : ArtifactRecord [ ] | undefined ;
203
- // check if required fields are available
204
230
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 ;
209
234
}
210
235
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 .' ) ;
212
237
}
213
238
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 .' ) ;
218
243
}
219
244
220
- const s3Artifact = archiveLogs [ 0 ] . s3 ;
245
+ const s3Artifact = workflow . status . artifactRepositoryRef . artifactRepository . s3 || false ;
221
246
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.' ) ;
223
248
}
224
249
225
250
const { host, port } = urlSplit ( s3Artifact . endpoint , s3Artifact . insecure ) ;
@@ -228,6 +253,10 @@ export async function getPodLogsMinioRequestConfigfromWorkflow(
228
253
const client = await createMinioClient (
229
254
{
230
255
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.
231
260
endPoint : host ,
232
261
port,
233
262
secretKey,
@@ -238,7 +267,7 @@ export async function getPodLogsMinioRequestConfigfromWorkflow(
238
267
return {
239
268
bucket : s3Artifact . bucket ,
240
269
client,
241
- key : s3Artifact . key ,
270
+ key : logKey ,
242
271
} ;
243
272
}
244
273
@@ -268,13 +297,3 @@ function urlSplit(uri: string, insecure: boolean) {
268
297
}
269
298
return { host : chunks [ 0 ] , port : parseInt ( chunks [ 1 ] , 10 ) } ;
270
299
}
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
- }
0 commit comments