Repository: argoproj/argo (go)

Issue:
Disabling compression for binary output artifact works but gives <artifact-name>.gz when downloaded from the UI
## Summary 

I am using the `script` template to execute a machine learning training python script which generates the model as a binary file having __.sav__ extension. Then I am exposing the model binary as an artifact, with compression disabled.

In minio, I can see that model file is present uncompressed, but on the UI the same uncompressed file shows up with a ".tgz" extension.

I am using the `k8sapi` executor to run the workflows

Attachments/Screenshots:
#### 1. File in minio - uncompressed binary as expected
![image](https://user-images.githubusercontent.com/52691361/95597433-3eb92980-0a6c-11eb-9204-9f89bde5a6fd.png)

#### 2. UI Screenshot
![image](https://user-images.githubusercontent.com/52691361/95597634-82ac2e80-0a6c-11eb-8e46-9d1423c3da93.png)

#### 3. Artifact downloaded from UI 
`model.tgz` which is the same uncompressed binary `IrisClassifier.sav` is incorrectly named as __model.tgz__ instead of __model.sav__ 
![image](https://user-images.githubusercontent.com/52691361/95597461-4a0c5500-0a6c-11eb-874e-34c2039cd17d.png)

#### 4. Executable workflow

[sklearn-iris.txt](https://github.com/argoproj/argo/files/5355761/sklearn-iris.txt)

Rename above attachment to sklearn-iris.__yaml__ and then 

```bash
argo submit ${workflowFile} --serviceaccount ${serviceaccount} --namespace ${namespace} 
```


## Diagnostics

### What Kubernetes provider are you using? 
Azure Aks

### What version of Argo Workflows are you running? 
v2.11.2

### Paste a workflow that reproduces the bug, including status:
kubectl get wf -o yaml ${workflow} 

```yaml
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  creationTimestamp: "2020-10-09T14:20:17Z"
  generateName: sklearn-iris-training-
  generation: 6
  labels:
    workflows.argoproj.io/completed: "true"
    workflows.argoproj.io/phase: Succeeded
  name: sklearn-iris-training-96j9n
  namespace: testing
  resourceVersion: "5619238"
  selfLink: /apis/argoproj.io/v1alpha1/namespaces/testing/workflows/sklearn-iris-training-96j9n
  uid: 28e3f15c-35f9-4caa-a671-8f96840f144c
spec:
  arguments: {}
  entrypoint: iris-model-training
  serviceAccountName: argo-workflow-service-account
  templates:
  - arguments: {}
    inputs: {}
    metadata: {}
    name: iris-model-training
    outputs: {}
    steps:
    - - arguments: {}
        name: generate-iris-model
        template: model-training
  - arguments: {}
    inputs: {}
    metadata: {}
    name: model-training
    outputs:
      artifacts:
      - archive:
          none: {}
        name: model
        path: /workspace/IrisClassifier.sav
    script:
      command:
      - bash
      image: python:3.7
      name: ""
      resources: {}
      source: "cd /workspace\nwget https://raw.githubusercontent.com/SeldonIO/seldon-core/master/examples/models/sklearn_iris/train_iris.py\npip
        install scikit-learn==0.20.3 joblib==0.16.0\npython train_iris.py \nls -lrth\n"
      volumeMounts:
      - mountPath: /workspace
        name: workspace
  volumes:
  - emptyDir: {}
    name: workspace
status:
  conditions:
  - status: "True"
    type: Completed
  finishedAt: "2020-10-09T14:20:37Z"
  nodes:
    sklearn-iris-training-96j9n:
      children:
      - sklearn-iris-training-96j9n-3701877419
      displayName: sklearn-iris-training-96j9n
      finishedAt: "2020-10-09T14:20:37Z"
      id: sklearn-iris-training-96j9n
      name: sklearn-iris-training-96j9n
      outboundNodes:
      - sklearn-iris-training-96j9n-739202604
      phase: Succeeded
      startedAt: "2020-10-09T14:20:17Z"
      templateName: iris-model-training
      templateScope: local/sklearn-iris-training-96j9n
      type: Steps
    sklearn-iris-training-96j9n-739202604:
      boundaryID: sklearn-iris-training-96j9n
      displayName: generate-iris-model
      finishedAt: "2020-10-09T14:20:35Z"
      hostNodeName: *redacted*
      id: sklearn-iris-training-96j9n-739202604
      name: sklearn-iris-training-96j9n[0].generate-iris-model
      outputs:
        artifacts:
        - archive:
            none: {}
          name: model
          path: /workspace/IrisClassifier.sav
          s3:
            accessKeySecret:
              key: accesskey
              name: argo-workflow-minio
            bucket: argo-artifacts
            endpoint: argo-workflow-minio:9000
            insecure: true
            key: sklearn-iris-training-96j9n/sklearn-iris-training-96j9n-739202604/IrisClassifier.sav
            secretKeySecret:
              key: secretkey
              name: argo-workflow-minio
        exitCode: "0"
      phase: Succeeded
      resourcesDuration:
        cpu: 27
        memory: 27
      startedAt: "2020-10-09T14:20:17Z"
      templateName: model-training
      templateScope: local/sklearn-iris-training-96j9n
      type: Pod
    sklearn-iris-training-96j9n-3701877419:
      boundaryID: sklearn-iris-training-96j9n
      children:
      - sklearn-iris-training-96j9n-739202604
      displayName: '[0]'
      finishedAt: "2020-10-09T14:20:37Z"
      id: sklearn-iris-training-96j9n-3701877419
      name: sklearn-iris-training-96j9n[0]
      phase: Succeeded
      startedAt: "2020-10-09T14:20:17Z"
      templateName: iris-model-training
      templateScope: local/sklearn-iris-training-96j9n
      type: StepGroup
  phase: Succeeded
  resourcesDuration:
    cpu: 27
    memory: 27
  startedAt: "2020-10-09T14:20:17Z"
```
### Paste the logs from the workflow controller:
```text
time="2020-10-09T14:20:17Z" level=info msg="Processing workflow" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:17Z" level=info msg="Updated phase  -> Running" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:17Z" level=info msg="Steps node sklearn-iris-training-96j9n initialized Running" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:17Z" level=info msg="StepGroup node sklearn-iris-training-96j9n-3701877419 initialized Running" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:17Z" level=info msg="Pod node sklearn-iris-training-96j9n-739202604 initialized Pending" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:17Z" level=info msg="Created pod: sklearn-iris-training-96j9n[0].generate-iris-model (sklearn-iris-training-96j9n-739202604)" namespace=testing workflow=sklearn-iris-training-96j9
time="2020-10-09T14:20:17Z" level=info msg="Workflow step group node sklearn-iris-training-96j9n-3701877419 not yet completed" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:17Z" level=info msg="Workflow update successful" namespace=testing phase=Running resourceVersion=5619181 workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:18Z" level=info msg="Processing workflow" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:18Z" level=info msg="Updating node sklearn-iris-training-96j9n-739202604 message: PodInitializing"
time="2020-10-09T14:20:18Z" level=info msg="Skipped pod sklearn-iris-training-96j9n[0].generate-iris-model (sklearn-iris-training-96j9n-739202604) creation: already exists" namespace=testing podPhase=Pending workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:18Z" level=info msg="Workflow step group node sklearn-iris-training-96j9n-3701877419 not yet completed" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:18Z" level=info msg="Workflow update successful" namespace=testing phase=Running resourceVersion=5619185 workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:19Z" level=info msg="Processing workflow" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:19Z" level=info msg="Skipped pod sklearn-iris-training-96j9n[0].generate-iris-model (sklearn-iris-training-96j9n-739202604) creation: already exists" namespace=testing podPhase=Pending workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:19Z" level=info msg="Workflow step group node sklearn-iris-training-96j9n-3701877419 not yet completed" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:20Z" level=info msg="Processing workflow" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:20Z" level=info msg="Skipped pod sklearn-iris-training-96j9n[0].generate-iris-model (sklearn-iris-training-96j9n-739202604) creation: already exists" namespace=testing podPhase=Pending workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:20Z" level=info msg="Workflow step group node sklearn-iris-training-96j9n-3701877419 not yet completed" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:21Z" level=info msg="Processing workflow" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:21Z" level=info msg="Updating node sklearn-iris-training-96j9n-739202604 status Pending -> Running"
time="2020-10-09T14:20:21Z" level=info msg="Workflow step group node sklearn-iris-training-96j9n-3701877419 not yet completed" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:21Z" level=info msg="Workflow update successful" namespace=testing phase=Running resourceVersion=5619204 workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:22Z" level=info msg="Processing workflow" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:22Z" level=info msg="Workflow step group node sklearn-iris-training-96j9n-3701877419 not yet completed" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:36Z" level=info msg="Processing workflow" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:36Z" level=info msg="Setting node sklearn-iris-training-96j9n-739202604 outputs"
time="2020-10-09T14:20:36Z" level=info msg="Workflow step group node sklearn-iris-training-96j9n-3701877419 not yet completed" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:36Z" level=info msg="Workflow update successful" namespace=testing phase=Running resourceVersion=5619235 workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:37Z" level=info msg="Processing workflow" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:37Z" level=info msg="Updating node sklearn-iris-training-96j9n-739202604 status Running -> Succeeded"
time="2020-10-09T14:20:37Z" level=info msg="Step group node &NodeStatus{ID:sklearn-iris-training-96j9n-3701877419,Name:sklearn-iris-training-96j9n[0],DisplayName:[0],Type:StepGroup,TemplateName:iris-model-training,TemplateRef:nil,Phase:Running,BoundaryID:sklearn-iris-training-96j9n,Message:,StartedAt:2020-10-09 14:20:17 +0000 UTC,FinishedAt:0001-01-01 00:00:00 +0000 UTC,PodIP:,Daemoned:nil,Inputs:nil,Outputs:nil,Children:[sklearn-iris-training-96j9n-739202604],OutboundNodes:[],StoredTemplateID:,WorkflowTemplateName:,TemplateScope:local/sklearn-iris-training-96j9n,ResourcesDuration:ResourcesDuration{},HostNodeName:,MemoizationStatus:nil,SynchronizationStatus:nil,} successful" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:37Z" level=info msg="node sklearn-iris-training-96j9n-3701877419 phase Running -> Succeeded" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:37Z" level=info msg="node sklearn-iris-training-96j9n-3701877419 finished: 2020-10-09 14:20:37.08788491 +0000 UTC" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:37Z" level=info msg="Outbound nodes of sklearn-iris-training-96j9n-739202604 is [sklearn-iris-training-96j9n-739202604]" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:37Z" level=info msg="Outbound nodes of sklearn-iris-training-96j9n is [sklearn-iris-training-96j9n-739202604]" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:37Z" level=info msg="node sklearn-iris-training-96j9n phase Running -> Succeeded" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:37Z" level=info msg="node sklearn-iris-training-96j9n finished: 2020-10-09 14:20:37.087981011 +0000 UTC" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:37Z" level=info msg="Checking daemoned children of sklearn-iris-training-96j9n" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:37Z" level=info msg="Updated phase Running -> Succeeded" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:37Z" level=info msg="Marking workflow completed" namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:37Z" level=info msg="Checking daemoned children of " namespace=testing workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:37Z" level=info msg="Workflow update successful" namespace=testing phase=Succeeded resourceVersion=5619238 workflow=sklearn-iris-training-96j9n
time="2020-10-09T14:20:38Z" level=info msg="Labeled pod completed" namespace=testing pod=sklearn-iris-training-96j9n-739202604```

```

<!-- Issue Author: Don't delete this message to encourage other users to support your issue! -->
**Message from the maintainers**:

Impacted by this bug? Give it a 👍. We prioritise the issues with the most 👍.


Pull request:
fix(server): use the correct name when downloading artifacts
When artifacts are uploaded to whatever repository is in use, they're stored
under a key that includes both the intended file name as well as the extension
to use based on whether or not compression was used when it was created.

Use that key as the file name when returning artifacts for download rather than
just "filename.tgz", that way we don't incorrectly identify non-compressed artifacts
as compressed tarballs.

Fixes #4252


Patch:
diff --git a/pkg/apis/workflow/v1alpha1/workflow_types.go b/pkg/apis/workflow/v1alpha1/workflow_types.go
index 340afb039..f0d49e478 100644
--- a/pkg/apis/workflow/v1alpha1/workflow_types.go
+++ b/pkg/apis/workflow/v1alpha1/workflow_types.go
@@ -924,6 +924,22 @@ func (a *ArtifactLocation) GetType() ArtifactLocationType {
 
 }
 
+func (a *ArtifactLocation) GetKey() string {
+	if a.S3 != nil {
+		return a.S3.Key
+	}
+
+	if a.OSS != nil {
+		return a.OSS.Key
+	}
+
+	if a.GCS != nil {
+		return a.GCS.Key
+	}
+
+	return ""
+}
+
 type ArtifactRepositoryRef struct {
 	ConfigMap string `json:"configMap,omitempty" protobuf:"bytes,1,opt,name=configMap"`
 	Key       string `json:"key,omitempty" protobuf:"bytes,2,opt,name=key"`
diff --git a/server/artifacts/artifact_server.go b/server/artifacts/artifact_server.go
index 98458dacb..38d2359db 100644
--- a/server/artifacts/artifact_server.go
+++ b/server/artifacts/artifact_server.go
@@ -6,6 +6,7 @@ import (
 	"io/ioutil"
 	"net/http"
 	"os"
+	"path"
 	"strings"
 
 	log "github.com/sirupsen/logrus"
@@ -27,10 +28,15 @@ type ArtifactServer struct {
 	hydrator          hydrator.Interface
 	wfArchive         sqldb.WorkflowArchive
 	instanceIDService instanceid.Service
+	artDriverFactory  artifact.NewDriverFunc
 }
 
 func NewArtifactServer(authN auth.Gatekeeper, hydrator hydrator.Interface, wfArchive sqldb.WorkflowArchive, instanceIDService instanceid.Service) *ArtifactServer {
-	return &ArtifactServer{authN, hydrator, wfArchive, instanceIDService}
+	return newArtifactServer(authN, hydrator, wfArchive, instanceIDService, artifact.NewDriver)
+}
+
+func newArtifactServer(authN auth.Gatekeeper, hydrator hydrator.Interface, wfArchive sqldb.WorkflowArchive, instanceIDService instanceid.Service, artDriverFactory artifact.NewDriverFunc) *ArtifactServer {
+	return &ArtifactServer{authN, hydrator, wfArchive, instanceIDService, artDriverFactory}
 }
 
 func (a *ArtifactServer) GetArtifact(w http.ResponseWriter, r *http.Request) {
@@ -55,12 +61,15 @@ func (a *ArtifactServer) GetArtifact(w http.ResponseWriter, r *http.Request) {
 		a.serverInternalError(err, w)
 		return
 	}
-	data, err := a.getArtifact(ctx, wf, nodeId, artifactName)
+
+	data, filename, err := a.getArtifact(ctx, wf, nodeId, artifactName)
+
 	if err != nil {
 		a.serverInternalError(err, w)
 		return
 	}
-	w.Header().Add("Content-Disposition", fmt.Sprintf(`filename="%s.tgz"`, artifactName))
+
+	w.Header().Add("Content-Disposition", fmt.Sprintf(`filename="%s"`, filename))
 	a.ok(w, data)
 }
 
@@ -87,12 +96,14 @@ func (a *ArtifactServer) GetArtifactByUID(w http.ResponseWriter, r *http.Request
 		return
 	}
 
-	data, err := a.getArtifact(ctx, wf, nodeId, artifactName)
+	data, filename, err := a.getArtifact(ctx, wf, nodeId, artifactName)
+
 	if err != nil {
 		a.serverInternalError(err, w)
 		return
 	}
-	w.Header().Add("Content-Disposition", fmt.Sprintf(`filename="%s.tgz"`, artifactName))
+
+	w.Header().Add("Content-Disposition", fmt.Sprintf(`filename="%s"`, filename))
 	a.ok(w, data)
 }
 
@@ -125,37 +136,37 @@ func (a *ArtifactServer) serverInternalError(err error, w http.ResponseWriter) {
 	_, _ = w.Write([]byte(err.Error()))
 }
 
-func (a *ArtifactServer) getArtifact(ctx context.Context, wf *wfv1.Workflow, nodeId, artifactName string) ([]byte, error) {
+func (a *ArtifactServer) getArtifact(ctx context.Context, wf *wfv1.Workflow, nodeId, artifactName string) ([]byte, string, error) {
 	kubeClient := auth.GetKubeClient(ctx)
 
 	art := wf.Status.Nodes[nodeId].Outputs.GetArtifactByName(artifactName)
 	if art == nil {
-		return nil, fmt.Errorf("artifact not found")
+		return nil, "", fmt.Errorf("artifact not found")
 	}
 
-	driver, err := artifact.NewDriver(art, resources{kubeClient, wf.Namespace})
+	driver, err := a.artDriverFactory(art, resources{kubeClient, wf.Namespace})
 	if err != nil {
-		return nil, err
+		return nil, "", err
 	}
 	tmp, err := ioutil.TempFile("/tmp", "artifact")
 	if err != nil {
-		return nil, err
+		return nil, "", err
 	}
-	path := tmp.Name()
-	defer func() { _ = os.Remove(path) }()
+	tmpPath := tmp.Name()
+	defer func() { _ = os.Remove(tmpPath) }()
 
-	err = driver.Load(art, path)
+	err = driver.Load(art, tmpPath)
 	if err != nil {
-		return nil, err
+		return nil, "", err
 	}
 
-	file, err := ioutil.ReadFile(path)
+	file, err := ioutil.ReadFile(tmpPath)
 	if err != nil {
-		return nil, err
+		return nil, "", err
 	}
 	log.WithFields(log.Fields{"size": len(file)}).Debug("Artifact file size")
 
-	return file, nil
+	return file, path.Base(art.GetKey()), nil
 }
 
 func (a *ArtifactServer) getWorkflowAndValidate(ctx context.Context, namespace string, workflowName string) (*wfv1.Workflow, error) {
diff --git a/workflow/artifacts/artifacts.go b/workflow/artifacts/artifacts.go
index d69ec1278..21730808f 100644
--- a/workflow/artifacts/artifacts.go
+++ b/workflow/artifacts/artifacts.go
@@ -27,6 +27,8 @@ type ArtifactDriver interface {
 
 var ErrUnsupportedDriver = fmt.Errorf("unsupported artifact driver")
 
+type NewDriverFunc func(art *wfv1.Artifact, ri resource.Interface) (ArtifactDriver, error)
+
 // NewDriver initializes an instance of an artifact driver
 func NewDriver(art *wfv1.Artifact, ri resource.Interface) (ArtifactDriver, error) {
 	if art.S3 != nil {


Test patch:
diff --git a/server/artifacts/artifact_server_test.go b/server/artifacts/artifact_server_test.go
index 3abf82651..3abcbc58e 100644
--- a/server/artifacts/artifact_server_test.go
+++ b/server/artifacts/artifact_server_test.go
@@ -2,10 +2,15 @@ package artifacts
 
 import (
 	"context"
+	"fmt"
+	"io/ioutil"
 	"net/http"
 	"net/url"
 	"testing"
 
+	artifact "github.com/argoproj/argo/workflow/artifacts"
+	"github.com/argoproj/argo/workflow/artifacts/resource"
+
 	"github.com/stretchr/testify/assert"
 	testhttp "github.com/stretchr/testify/http"
 	"github.com/stretchr/testify/mock"
@@ -30,6 +35,19 @@ func mustParse(text string) *url.URL {
 	return u
 }
 
+type fakeArtifactDriver struct {
+	artifact.ArtifactDriver
+	data []byte
+}
+
+func (a *fakeArtifactDriver) Load(_ *wfv1.Artifact, path string) error {
+	return ioutil.WriteFile(path, a.data, 0666)
+}
+
+func (a *fakeArtifactDriver) Save(_ string, _ *wfv1.Artifact) error {
+	return fmt.Errorf("not implemented")
+}
+
 func newServer() *ArtifactServer {
 	gatekeeper := &authmocks.Gatekeeper{}
 	kube := kubefake.NewSimpleClientset()
@@ -44,10 +62,26 @@ func newServer() *ArtifactServer {
 					Outputs: &wfv1.Outputs{
 						Artifacts: wfv1.Artifacts{
 							{
-								Name: "my-artifact",
+								Name: "my-s3-artifact",
 								ArtifactLocation: wfv1.ArtifactLocation{
-									Raw: &wfv1.RawArtifact{
-										Data: "my-data",
+									S3: &wfv1.S3Artifact{
+										Key: "my-wf/my-node/my-s3-artifact.tgz",
+									},
+								},
+							},
+							{
+								Name: "my-gcs-artifact",
+								ArtifactLocation: wfv1.ArtifactLocation{
+									GCS: &wfv1.GCSArtifact{
+										Key: "my-wf/my-node/my-gcs-artifact",
+									},
+								},
+							},
+							{
+								Name: "my-oss-artifact",
+								ArtifactLocation: wfv1.ArtifactLocation{
+									GCS: &wfv1.GCSArtifact{
+										Key: "my-wf/my-node/my-oss-artifact.zip",
 									},
 								},
 							},
@@ -62,18 +96,46 @@ func newServer() *ArtifactServer {
 	gatekeeper.On("Context", mock.Anything).Return(ctx, nil)
 	a := &mocks.WorkflowArchive{}
 	a.On("GetWorkflow", "my-uuid").Return(wf, nil)
-	return NewArtifactServer(gatekeeper, hydratorfake.Noop, a, instanceid.NewService(instanceId))
+
+	fakeArtifactDriverFactory := func(_ *wfv1.Artifact, _ resource.Interface) (artifact.ArtifactDriver, error) {
+		return &fakeArtifactDriver{data: []byte("my-data")}, nil
+	}
+
+	return newArtifactServer(gatekeeper, hydratorfake.Noop, a, instanceid.NewService(instanceId), fakeArtifactDriverFactory)
 }
 
 func TestArtifactServer_GetArtifact(t *testing.T) {
 	s := newServer()
-	r := &http.Request{}
-	r.URL = mustParse("/artifacts/my-ns/my-wf/my-node/my-artifact")
-	w := &testhttp.TestResponseWriter{}
-	s.GetArtifact(w, r)
-	assert.Equal(t, 200, w.StatusCode)
-	assert.Equal(t, "filename=\"my-artifact.tgz\"", w.Header().Get("Content-Disposition"))
-	assert.Equal(t, "my-data", w.Output)
+
+	tests := []struct {
+		fileName     string
+		artifactName string
+	}{
+		{
+			fileName:     "my-s3-artifact.tgz",
+			artifactName: "my-s3-artifact",
+		},
+		{
+			fileName:     "my-gcs-artifact",
+			artifactName: "my-gcs-artifact",
+		},
+		{
+			fileName:     "my-oss-artifact.zip",
+			artifactName: "my-oss-artifact",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.artifactName, func(t *testing.T) {
+			r := &http.Request{}
+			r.URL = mustParse(fmt.Sprintf("/artifacts/my-ns/my-wf/my-node/%s", tt.artifactName))
+			w := &testhttp.TestResponseWriter{}
+			s.GetArtifact(w, r)
+			assert.Equal(t, 200, w.StatusCode)
+			assert.Equal(t, fmt.Sprintf(`filename="%s"`, tt.fileName), w.Header().Get("Content-Disposition"))
+			assert.Equal(t, "my-data", w.Output)
+		})
+	}
 }
 
 func TestArtifactServer_GetArtifactWithoutInstanceID(t *testing.T) {