package common import ( "bytes" "context" "crypto/sha1" "encoding/hex" "io/ioutil" "log" "net/http" "net/http/httptest" "os" "path/filepath" "reflect" "runtime" "testing" "github.com/google/go-cmp/cmp" urlhelper "github.com/hashicorp/go-getter/v2/helper/url" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer/tmp" ) var _ multistep.Step = new(StepDownload) func toSha1(in string) string { b := sha1.Sum([]byte(in)) return hex.EncodeToString(b[:]) } func abs(t *testing.T, path string) string { path, err := filepath.Abs(path) if err != nil { t.Fatal(err) } u, err := urlhelper.Parse(path) if err != nil { t.Fatal(err) } return u.String() } func TestStepDownload_Run(t *testing.T) { srvr := httptest.NewServer(http.FileServer(http.Dir("test-fixtures"))) defer srvr.Close() cs := map[string]string{ "/root/basic.txt": "f572d396fae9206628714fb2ce00f72e94f2258f", "/root/another.txt": "7c6e5dd1bacb3b48fdffba2ed096097eb172497d", } type fields struct { Checksum string Description string ResultKey string TargetPath string Url []string Extension string } tests := []struct { name string fields fields want multistep.StepAction wantFiles []string }{ {"Empty URL field passes", fields{Url: []string{}}, multistep.ActionContinue, nil, }, {"not passing a checksum passes", fields{Url: []string{abs(t, "./test-fixtures/root/another.txt")}}, multistep.ActionContinue, []string{ toSha1(abs(t, "./test-fixtures/root/another.txt")) + ".lock", }, }, {"double slashes on a local filesystem passes", fields{Url: []string{abs(t, "./test-fixtures/root//another.txt")}}, multistep.ActionContinue, []string{ toSha1(abs(t, "./test-fixtures/root//another.txt")) + ".lock", }, }, {"none checksum works", fields{Url: []string{abs(t, "./test-fixtures/root/another.txt")}, Checksum: "none"}, multistep.ActionContinue, []string{ toSha1(abs(t, "./test-fixtures/root/another.txt")) + ".lock", }, }, {"bad checksum removes file - checksum from string - no Checksum Type", fields{Extension: "txt", Url: []string{abs(t, "./test-fixtures/root/another.txt")}, Checksum: cs["/root/basic.txt"]}, multistep.ActionHalt, []string{ toSha1(cs["/root/basic.txt"]) + ".txt.lock", // a lock file is created & deleted on mac for each download }, }, {"bad checksum removes file - checksum from string - Checksum Type", fields{Extension: "txt", Url: []string{abs(t, "./test-fixtures/root/another.txt")}, Checksum: "sha1:" + cs["/root/basic.txt"]}, multistep.ActionHalt, []string{ toSha1("sha1:"+cs["/root/basic.txt"]) + ".txt.lock", }, }, {"bad checksum removes file - checksum from url - Checksum Type", fields{Extension: "txt", Url: []string{abs(t, "./test-fixtures/root/basic.txt")}, Checksum: "file:" + srvr.URL + "/root/another.txt.sha1sum"}, multistep.ActionHalt, []string{ toSha1("file:"+srvr.URL+"/root/another.txt.sha1sum") + ".txt.lock", }, }, {"successfull http dl - checksum from http file - parameter", fields{Extension: "txt", Url: []string{srvr.URL + "/root/another.txt"}, Checksum: "file:" + srvr.URL + "/root/another.txt.sha1sum"}, multistep.ActionContinue, []string{ toSha1("file:"+srvr.URL+"/root/another.txt.sha1sum") + ".txt", toSha1("file:"+srvr.URL+"/root/another.txt.sha1sum") + ".txt.lock", }, }, {"successfull http dl - checksum from http file - url", fields{Extension: "txt", Url: []string{srvr.URL + "/root/another.txt?checksum=file:" + srvr.URL + "/root/another.txt.sha1sum"}}, multistep.ActionContinue, []string{ toSha1("file:"+srvr.URL+"/root/another.txt.sha1sum") + ".txt", toSha1("file:"+srvr.URL+"/root/another.txt.sha1sum") + ".txt.lock", }, }, {"successfull http dl - checksum from url", fields{Extension: "txt", Url: []string{srvr.URL + "/root/another.txt?checksum=" + cs["/root/another.txt"]}}, multistep.ActionContinue, []string{ toSha1(cs["/root/another.txt"]) + ".txt", toSha1(cs["/root/another.txt"]) + ".txt.lock", }, }, {"successfull http dl - checksum from parameter - no checksum type", fields{Extension: "txt", Url: []string{srvr.URL + "/root/another.txt?"}, Checksum: cs["/root/another.txt"]}, multistep.ActionContinue, []string{ toSha1(cs["/root/another.txt"]) + ".txt", toSha1(cs["/root/another.txt"]) + ".txt.lock", }, }, {"successfull http dl - checksum from parameter - checksum type", fields{Extension: "txt", Url: []string{srvr.URL + "/root/another.txt?"}, Checksum: "sha1:" + cs["/root/another.txt"]}, multistep.ActionContinue, []string{ toSha1("sha1:"+cs["/root/another.txt"]) + ".txt", toSha1("sha1:"+cs["/root/another.txt"]) + ".txt.lock", }, }, {"successfull relative symlink - checksum from url", fields{Extension: "txt", Url: []string{"./test-fixtures/root/another.txt?checksum=" + cs["/root/another.txt"]}}, multistep.ActionContinue, []string{ toSha1(cs["/root/another.txt"]) + ".txt.lock", }, }, {"successfull relative symlink - checksum from parameter - no checksum type", fields{Extension: "txt", Url: []string{"./test-fixtures/root/another.txt?"}, Checksum: cs["/root/another.txt"]}, multistep.ActionContinue, []string{ toSha1(cs["/root/another.txt"]) + ".txt.lock", }, }, {"successfull relative symlink - checksum from parameter - checksum type", fields{Extension: "txt", Url: []string{"./test-fixtures/root/another.txt?"}, Checksum: "sha1:" + cs["/root/another.txt"]}, multistep.ActionContinue, []string{ toSha1("sha1:"+cs["/root/another.txt"]) + ".txt.lock", }, }, {"successfull absolute symlink - checksum from url", fields{Extension: "txt", Url: []string{abs(t, "./test-fixtures/root/another.txt") + "?checksum=" + cs["/root/another.txt"]}}, multistep.ActionContinue, []string{ toSha1(cs["/root/another.txt"]) + ".txt.lock", }, }, {"successfull absolute symlink - checksum from parameter - no checksum type", fields{Extension: "txt", Url: []string{abs(t, "./test-fixtures/root/another.txt") + "?"}, Checksum: cs["/root/another.txt"]}, multistep.ActionContinue, []string{ toSha1(cs["/root/another.txt"]) + ".txt.lock", }, }, {"successfull absolute symlink - checksum from parameter - checksum type", fields{Extension: "txt", Url: []string{abs(t, "./test-fixtures/root/another.txt") + "?"}, Checksum: "sha1:" + cs["/root/another.txt"]}, multistep.ActionContinue, []string{ toSha1("sha1:"+cs["/root/another.txt"]) + ".txt.lock", }, }, {"wrong first 2 urls - absolute urls - checksum from parameter - no checksum type", fields{ Url: []string{ abs(t, "./test-fixtures/root/another.txt"), abs(t, "./test-fixtures/root/not_found"), abs(t, "./test-fixtures/root/basic.txt"), }, Checksum: cs["/root/basic.txt"], }, multistep.ActionContinue, []string{ toSha1(cs["/root/basic.txt"]) + ".lock", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dir := createTempDir(t) defer os.RemoveAll(dir) s := &StepDownload{ TargetPath: tt.fields.TargetPath, Checksum: tt.fields.Checksum, ResultKey: tt.fields.ResultKey, Url: tt.fields.Url, Extension: tt.fields.Extension, Description: tt.name, } defer os.Setenv("PACKER_CACHE_DIR", os.Getenv("PACKER_CACHE_DIR")) os.Setenv("PACKER_CACHE_DIR", dir) if got := s.Run(context.Background(), testState(t)); !reflect.DeepEqual(got, tt.want) { t.Fatalf("StepDownload.Run() = %v, want %v", got, tt.want) } files := listFiles(t, dir) if diff := cmp.Diff(tt.wantFiles, files); diff != "" { t.Fatalf("file list differs in %s: %s", dir, diff) } }) } } func TestStepDownload_download(t *testing.T) { step := &StepDownload{ Checksum: "sha1:f572d396fae9206628714fb2ce00f72e94f2258f", Description: "ISO", ResultKey: "iso_path", Url: nil, } ui := &packer.BasicUi{ Reader: new(bytes.Buffer), Writer: new(bytes.Buffer), PB: &packer.NoopProgressTracker{}, } dir := createTempDir(t) defer os.RemoveAll(dir) defer os.Setenv("PACKER_CACHE_DIR", os.Getenv("PACKER_CACHE_DIR")) os.Setenv("PACKER_CACHE_DIR", dir) // Abs path with extension provided step.TargetPath = "./packer" step.Extension = "ova" _, err := step.download(context.TODO(), ui, "./test-fixtures/root/basic.txt") if err != nil { t.Fatalf("Bad: non expected error %s", err.Error()) } os.RemoveAll(step.TargetPath) // Abs path with no extension provided step.TargetPath = "./packer" step.Extension = "" _, err = step.download(context.TODO(), ui, "./test-fixtures/root/basic.txt") if err != nil { t.Fatalf("Bad: non expected error %s", err.Error()) } os.RemoveAll(step.TargetPath) // Path with file step.TargetPath = "./packer/file.iso" _, err = step.download(context.TODO(), ui, "./test-fixtures/root/basic.txt") if err != nil { t.Fatalf("Bad: non expected error %s", err.Error()) } os.RemoveAll(step.TargetPath) } func TestStepDownload_WindowsParseSourceURL(t *testing.T) { if runtime.GOOS != "windows" { t.Skip("skip windows specific tests") } source := "\\\\hostname\\dir\\filename.txt" url, err := parseSourceURL(source) if err != nil { t.Fatalf("bad: parsing source url failed: %s", err.Error()) } if url.Scheme != "smb" { t.Fatalf("bad: url should contain smb scheme but contains: %s", url.Scheme) } } func TestStepDownload_ParseSourceSmbURL(t *testing.T) { source := "smb://hostname/dir/filename.txt" url, err := parseSourceURL(source) if err != nil { t.Fatalf("bad: parsing source url failed: %s", err.Error()) } if url.Scheme != "smb" { t.Fatalf("bad: url should contain smb scheme but contains: %s", url.Scheme) } if url.String() != "smb://hostname/dir/filename.txt" { t.Fatalf("bad: url should contain smb scheme but contains: %s", url.String()) } } func createTempDir(t *testing.T) string { dir, err := tmp.Dir("pkr") if err != nil { t.Fatalf("err: %s", err) } return dir } func listFiles(t *testing.T, dir string) []string { fs, err := ioutil.ReadDir(dir) if err != nil { log.Fatal(err) } var files []string for _, file := range fs { if file.Name() == "." { continue } files = append(files, file.Name()) } return files }