diff --git a/main.go b/main.go index 6f35187..e821974 100644 --- a/main.go +++ b/main.go @@ -12,11 +12,13 @@ import ( "log" "log/slog" "os" + "path/filepath" "github.com/drone/go-task/task" "github.com/drone/go-task/task/cloner" download "github.com/drone/go-task/task/downloader" "github.com/drone/go-task/task/drivers/cgi" + "github.com/drone/go-task/task/packaged" ) var ( @@ -98,8 +100,9 @@ func main() { cloner.Default(), // top-level directory where the downloading should happen - cache, + filepath.Join(cache, "download"), ) + packageLoader := packaged.New(filepath.Join(cache, "default")) // create the task router router := task.NewRouter() @@ -112,6 +115,7 @@ func main() { // use the default downloader which // caches tasks at ~/.cache/harness/task downloader, + packageLoader, ), ) diff --git a/task/downloader/downloader.go b/task/downloader/downloader.go index cc84c8d..c41fb3b 100644 --- a/task/downloader/downloader.go +++ b/task/downloader/downloader.go @@ -6,7 +6,6 @@ package downloader import ( "context" - "path/filepath" "github.com/drone/go-task/task" "github.com/drone/go-task/task/cloner" @@ -22,15 +21,9 @@ type Downloader struct { } func New(cloner cloner.Cloner, dir string) Downloader { - baseDir := getBaseDownloadDir(dir) repoDownloader := newRepoDownloader(cloner) executableDownloader := newExecutableDownloader() - return Downloader{dir: baseDir, repoDownloader: repoDownloader, executableDownloader: executableDownloader} -} - -// getBaseDownloadDir returns the top-level directory where all files should be downloaded -func getBaseDownloadDir(dir string) string { - return filepath.Join(dir, ".harness", "cache") + return Downloader{dir: dir, repoDownloader: repoDownloader, executableDownloader: executableDownloader} } func (d *Downloader) DownloadRepo(ctx context.Context, repo *task.Repository) (string, error) { diff --git a/task/downloader/executable_downloader.go b/task/downloader/executable_downloader.go index d7eee70..9d602e3 100644 --- a/task/downloader/executable_downloader.go +++ b/task/downloader/executable_downloader.go @@ -43,17 +43,19 @@ func (e *executableDownloader) download(ctx context.Context, dir string, taskTyp return "", fmt.Errorf("os [%s] and architecture [%s] are not specified in executable configuration", operatingSystem, architecture) } - destDir := filepath.Join(dir, taskType, getHash(url)) - dest := getDownloadPath(url, destDir) + destDir := filepath.Join(dir, taskType, exec.Name) + // {baseDir}/taskType/{name}/{name}-{version}-{os}-{arch} + // download to a file named by this runner to make sure upstream changes doesn't affect the cache hit lookup + dest := filepath.Join(destDir, exec.Name+"-"+exec.Version+"-"+operatingSystem+"-"+architecture) - if cacheHit := isCacheHitFn(ctx, destDir); cacheHit { + if cacheHit := isCacheHitFn(ctx, dest); cacheHit { // exit if the artifact destination already exists return dest, nil } // if no cache hit, remove all downloaded executables for this task's type // so that we don't keep multiple executables of the same type - err := removeAllFn(filepath.Join(dir, taskType)) + err := removeAllFn(destDir) if err != nil { return "", err } diff --git a/task/drivers/cgi/driver.go b/task/drivers/cgi/driver.go index ce320c7..6b76e99 100644 --- a/task/drivers/cgi/driver.go +++ b/task/drivers/cgi/driver.go @@ -10,6 +10,7 @@ import ( "path/filepath" "github.com/drone/go-task/task/logger" + "github.com/drone/go-task/task/packaged" "github.com/drone/go-task/task" "github.com/drone/go-task/task/builder" @@ -31,12 +32,13 @@ type Config struct { } // New returns the task execution driver. -func New(d downloader.Downloader) task.Handler { - return &driver{downloader: d} +func New(d downloader.Downloader, pl packaged.PackageLoader) task.Handler { + return &driver{downloader: d, packageLoader: pl} } type driver struct { - downloader downloader.Downloader + downloader downloader.Downloader + packageLoader packaged.PackageLoader } // Handle handles the task execution request. @@ -50,9 +52,9 @@ func (d *driver) Handle(ctx context.Context, req *task.Request) task.Response { return task.Error(err) } - path, err := d.downloadArtifact(ctx, req.Task.Type, conf) + path, err := d.prepareArtifact(ctx, req.Task.Type, conf) if err != nil { - log.WithError(err).Error("artifact download failed") + log.WithError(err).Error("Prepare artifact failed") return task.Error(err) } @@ -74,13 +76,22 @@ func (d *driver) Handle(ctx context.Context, req *task.Request) task.Response { return task.Respond(resp) } -func (d *driver) downloadArtifact(ctx context.Context, taskType string, conf *Config) (string, error) { +func (d *driver) prepareArtifact(ctx context.Context, taskType string, conf *Config) (string, error) { + // use binary artifact if conf.ExecutableConfig != nil { - return d.downloader.DownloadExecutable(ctx, taskType, conf.ExecutableConfig) + if shouldUsePrepackagedBinary(conf) { + return d.packageLoader.GetPackagePath(ctx, taskType, conf.ExecutableConfig) + } else { + return d.downloader.DownloadExecutable(ctx, taskType, conf.ExecutableConfig) + } } return d.downloader.DownloadRepo(ctx, conf.Repository) } +func shouldUsePrepackagedBinary(conf *Config) bool { + return len(conf.ExecutableConfig.Executables) == 0 +} + func setDefaultConfigValues(conf *Config) { if conf.Method == "" { conf.Method = "POST" diff --git a/task/packaged/packaged.go b/task/packaged/packaged.go new file mode 100644 index 0000000..aa47163 --- /dev/null +++ b/task/packaged/packaged.go @@ -0,0 +1,51 @@ +package packaged + +/** + * @title PackageLoader + + * @desc PackageLoader is a struct that provides a method to get the path of a pre-package artifact + * based on the task type and the executable name. This is used when artifacts are packaged with Runner + * in a container. + * It is assumed there is only one artifact per task type and executable name, because the OS and architecture + * are pre-determined. + */ + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "github.com/drone/go-task/task" +) + +type PackageLoader struct { + dir string +} + +func New(dir string) PackageLoader { + return PackageLoader{dir: dir} +} + +func (p *PackageLoader) GetPackagePath(ctx context.Context, taskType string, exec *task.ExecutableConfig) (string, error) { + dir := filepath.Join(p.dir, taskType, exec.Name) + return getFirstFile(dir) +} + +func getFirstFile(directory string) (string, error) { + // Open the directory + files, err := os.ReadDir(directory) + if err != nil { + return "", err + } + + // Iterate through files to find the first file (not a directory) + for _, file := range files { + if !file.IsDir() { + return filepath.Join(directory, file.Name()), nil // Return the first file found + } + } + + // If no files are found, return an error + return "", fmt.Errorf("no files found in directory: %s", directory) +} diff --git a/task/types.go b/task/types.go index 0c7fab7..bea6867 100644 --- a/task/types.go +++ b/task/types.go @@ -71,6 +71,8 @@ type Repository struct { // supported operating systems and architectures type ExecutableConfig struct { Executables []Executable `json:"executables"` + Name string `json:"name"` + Version string `json:"version"` } // Executable provides the url to download