diff --git a/server/artifacts/artifact_server.go b/server/artifacts/artifact_server.go index 5804adadd057..3217ecbedc17 100644 --- a/server/artifacts/artifact_server.go +++ b/server/artifacts/artifact_server.go @@ -544,7 +544,14 @@ func (a *ArtifactServer) returnArtifact(ctx context.Context, w http.ResponseWrit key, _ := art.GetKey() w.Header().Add("Content-Disposition", fmt.Sprintf(`filename="%s"`, path.Base(key))) - w.Header().Add("Content-Type", mime.TypeByExtension(path.Ext(key))) + // mime.TypeByExtension relies on the system MIME database, which may not exist in + // minimal container images such as distroless. Fall back to "text/plain; charset=utf-8" so that + // unrecognized extensions (e.g. ".log") are still served with a valid Content-Type. + contentType := mime.TypeByExtension(path.Ext(key)) + if contentType == "" { + contentType = "text/plain; charset=utf-8" + } + w.Header().Add("Content-Type", contentType) a.setSecurityHeaders(w) _, err = io.Copy(w, stream) diff --git a/server/artifacts/artifact_server_test.go b/server/artifacts/artifact_server_test.go index cb36ff906d52..5b757d8673b7 100644 --- a/server/artifacts/artifact_server_test.go +++ b/server/artifacts/artifact_server_test.go @@ -65,6 +65,7 @@ var bucketsOfKeys = map[string][]string{ "my-wf/my-node-1/my-gcs-artifact.tgz", "my-wf/my-node-1/my-oss-artifact.zip", "my-wf/my-node-1/my-s3-artifact.tgz", + "my-wf/my-node-1/main.log", "my-wf/my-node-inline/main.log", }, "my-bucket-2": { @@ -264,6 +265,18 @@ func newServer(t *testing.T) *ArtifactServer { }, }, }, + { + // Log artifact created when archiveLogs is enabled + // (see workflow/executor/executor.go:saveContainerLogs). + // The key ends with ".log", which is not in the MIME database + // on minimal container images (e.g. distroless). + Name: "main-logs", + ArtifactLocation: wfv1.ArtifactLocation{ + S3: &wfv1.S3Artifact{ + Key: "my-wf/my-node-1/main.log", + }, + }, + }, }, }, }, @@ -474,6 +487,16 @@ func TestArtifactServer_GetArtifactFile(t *testing.T) { statusCode: 200, isDirectory: false, }, + { + path: "/artifact-files/my-ns/workflows/my-wf/my-node-1/outputs/main-logs", + statusCode: 200, + isDirectory: false, + // Verify that .log artifacts are served with a non-empty Content-Type. + // The ".log" extension may not be in the system MIME database on minimal + // container images (e.g. distroless), causing mime.TypeByExtension to + // return "". The fallback in returnArtifact ensures "text/plain; charset=utf-8" + // is used instead. + }, } for _, tt := range tests { @@ -502,6 +525,7 @@ func TestArtifactServer_GetArtifactFile(t *testing.T) { } } else { assert.Equal(t, "my-data", string(all)) + assert.NotEmpty(t, recorder.Header().Get("Content-Type"), "Content-Type header must not be empty") } } })