@@ -15,12 +15,15 @@
 package daisy
 
 import (
+	"bytes"
 	"context"
 	"io"
+	"io/ioutil"
 	"net/http"
 	"os"
 	"path"
 	"path/filepath"
+	"regexp"
 	"strings"
 
 	"cloud.google.com/go/storage"
//  Copyright 2017 Google Inc. All Rights Reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.

package daisy

import (
	"context"
	"io"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"

	"cloud.google.com/go/storage"
	"google.golang.org/api/googleapi"
	"google.golang.org/api/iterator"
)

func (w *Workflow) recursiveGCS(ctx context.Context, bkt, prefix, dst string) dErr {
	it := w.StorageClient.Bucket(bkt).Objects(ctx, &storage.Query{Prefix: prefix})
	for objAttr, err := it.Next(); err != iterator.Done; objAttr, err = it.Next() {
		if err != nil {
			return typedErr(apiError, err)
		}
		if objAttr.Size == 0 {
			continue
		}
		srcPath := w.StorageClient.Bucket(bkt).Object(objAttr.Name)
		o := path.Join(w.sourcesPath, dst, strings.TrimPrefix(objAttr.Name, prefix))
		dstPath := w.StorageClient.Bucket(w.bucket).Object(o)
		if _, err := dstPath.CopierFrom(srcPath).Run(ctx); err != nil {
			return typedErr(apiError, err)
		}
	}
	return nil
}

func (w *Workflow) sourceExists(s string) bool {
	_, ok := w.Sources[s]
	return ok
}

func (w *Workflow) uploadFile(ctx context.Context, src, obj string) dErr {
	obj = filepath.ToSlash(obj)
	dstPath := w.StorageClient.Bucket(w.bucket).Object(path.Join(w.sourcesPath, obj))
	gcs := dstPath.NewWriter(ctx)
	f, err := os.Open(src)
	if err != nil {
		return newErr(err)
	}
	if _, err := io.Copy(gcs, f); err != nil {
		return newErr(err)
	}
	return newErr(gcs.Close())
}

func (w *Workflow) uploadSources(ctx context.Context) dErr {
	for dst, origPath := range w.Sources {
		if origPath == "" {
			continue
		}
		// GCS to GCS.
		if bkt, objPath, err := splitGCSPath(origPath); err == nil {
			if objPath == "" || strings.HasSuffix(objPath, "/") {
				if err := w.recursiveGCS(ctx, bkt, objPath, dst); err != nil {
					return errf("error copying from bucket %s: %v", origPath, err)
				}
				continue
			}
			src := w.StorageClient.Bucket(bkt).Object(objPath)
			dstPath := w.StorageClient.Bucket(w.bucket).Object(path.Join(w.sourcesPath, dst))
			if _, err := dstPath.CopierFrom(src).Run(ctx); err != nil {
				if gErr, ok := err.(*googleapi.Error); ok && gErr.Code == http.StatusNotFound {
					return typedErrf(resourceDNEError, "error copying from file %s: %v", origPath, err)
				}
				return errf("error copying from file %s: %v", origPath, err)
			}

			continue
		}

		// Local to GCS.
		if !filepath.IsAbs(origPath) {
			origPath = filepath.Join(w.workflowDir, origPath)
		}
		fi, err := os.Stat(origPath)
		if err != nil {
			return typedErr(fileIOError, err)
		}
		if fi.IsDir() {
			var files []string
			if err := filepath.Walk(origPath, func(path string, info os.FileInfo, err error) error {
				if err != nil {
					return err
				}
				if info.IsDir() {
					return nil
				}
				files = append(files, path)
				return nil
			}); err != nil {
				return typedErr(fileIOError, err)
			}
			for _, file := range files {
				obj := path.Join(dst, strings.TrimPrefix(file, filepath.Clean(origPath)))
				if err := w.uploadFile(ctx, file, obj); err != nil {
					return err
				}
			}
			continue
		}
		if err := w.uploadFile(ctx, origPath, dst); err != nil {
			return err
		}
	}
	for _, step := range w.Steps {
		if step.SubWorkflow != nil {
			if err := step.SubWorkflow.Workflow.uploadSources(ctx); err != nil {
				return err
			}
		}
	}
	return nil
}

Just an idea, I don't know if it is any cleaner. You could create a Source struct with a read method: type Source struct { Path string } func (s *Source) read() string {}