forked from gruntwork-io/fetch
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfile.go
144 lines (118 loc) · 4.89 KB
/
file.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package main
import (
"io/ioutil"
"os"
"fmt"
"net/http"
"path/filepath"
"bytes"
"archive/zip"
"strings"
)
// Download the zip file at the given URL to a temporary local directory.
// Returns the absolute path to the downloaded zip file.
// IMPORTANT: You must call "defer os.RemoveAll(dir)" in the calling function when done with the downloaded zip file!
func downloadGithubZipFile(gitHubCommit GitHubCommit, gitHubToken string) (string, *FetchError) {
var zipFilePath string
// Create a temp directory
// Note that ioutil.TempDir has a peculiar interface. We need not specify any meaningful values to achieve our
// goal of getting a temporary directory.
tempDir, err := ioutil.TempDir("", "")
if err != nil {
return zipFilePath, wrapError(err)
}
// Download the zip file, possibly using the GitHub oAuth Token
httpClient := &http.Client{}
req, err := MakeGitHubZipFileRequest(gitHubCommit, gitHubToken)
if err != nil {
return zipFilePath, wrapError(err)
}
resp, err := httpClient.Do(req)
if err != nil {
return zipFilePath, wrapError(err)
}
if resp.StatusCode != 200 {
return zipFilePath, newError(FAILED_TO_DOWNLOAD_FILE, fmt.Sprintf("Failed to download file at the url %s. Received HTTP Response %d.", req.URL.String(), resp.StatusCode))
}
if resp.Header.Get("Content-Type") != "application/zip" {
return zipFilePath, newError(FAILED_TO_DOWNLOAD_FILE, fmt.Sprintf("Failed to download file at the url %s. Expected HTTP Response's \"Content-Type\" header to be \"application/zip\", but was \"%s\"", req.URL.String(), resp.Header.Get("Content-Type")))
}
// Copy the contents of the downloaded file to our empty file
respBodyBuffer := new(bytes.Buffer)
respBodyBuffer.ReadFrom(resp.Body)
err = ioutil.WriteFile(filepath.Join(tempDir, "repo.zip"), respBodyBuffer.Bytes(), 0644)
if err != nil {
return zipFilePath, wrapError(err)
}
zipFilePath = filepath.Join(tempDir, "repo.zip")
return zipFilePath, nil
}
// Decompress the file at zipFileAbsPath and move only those files under filesToExtractFromZipPath to localPath
func extractFiles(zipFilePath, filesToExtractFromZipPath, localPath string) error {
// Open the zip file for reading.
r, err := zip.OpenReader(zipFilePath)
if err != nil {
return err
}
defer r.Close()
// pathPrefix represents the portion of the local file path we will ignore when copying the file to localPath
// E.g. full path = fetch-test-public-0.0.3/folder/file1.txt
// path prefix = fetch-test-public-0.0.3
// file that will eventually get written = <localPath>/folder/file1.txt
// By convention, the first file in the zip file is the top-level directory
pathPrefix := r.File[0].Name
// Add the path from which we will extract files to the path prefix so we can exclude the appropriate files
pathPrefix = filepath.Join(pathPrefix, filesToExtractFromZipPath)
// Iterate through the files in the archive,
// printing some of their contents.
for _, f := range r.File {
// If the given file is in the filesToExtractFromZipPath, proceed
if strings.Index(f.Name, pathPrefix) == 0 {
if f.FileInfo().IsDir() {
// Create a directory
os.MkdirAll(filepath.Join(localPath, strings.TrimPrefix(f.Name, pathPrefix)), 0777)
} else {
// Read the file into a byte array
readCloser, err := f.Open()
if err != nil {
return fmt.Errorf("Failed to open file %s: %s", f.Name, err)
}
byteArray, err := ioutil.ReadAll(readCloser)
if err != nil {
return fmt.Errorf("Failed to read file %s: %s", f.Name, err)
}
// Write the file
err = ioutil.WriteFile(filepath.Join(localPath, strings.TrimPrefix(f.Name, pathPrefix)), byteArray, 0644)
if err != nil {
return fmt.Errorf("Failed to write file: %s", err)
}
}
}
}
return nil
}
// Return an HTTP request that will fetch the given GitHub repo's zip file for the given tag, possibly with the gitHubOAuthToken in the header
// Respects the GitHubCommit hierachy as defined in the code comments for GitHubCommit (e.g. GitTag > CommitSha)
func MakeGitHubZipFileRequest(gitHubCommit GitHubCommit, gitHubToken string) (*http.Request, error) {
var request *http.Request
// This represents either a commit, branch, or git tag
var gitRef string
if gitHubCommit.CommitSha != "" {
gitRef = gitHubCommit.CommitSha
} else if gitHubCommit.BranchName != "" {
gitRef = gitHubCommit.BranchName
} else if gitHubCommit.GitTag != "" {
gitRef = gitHubCommit.GitTag
} else {
return request, fmt.Errorf("Neither a GitCommitSha nor a GitTag nor a BranchName were specified so impossible to identify a specific commit to download.")
}
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/zipball/%s", gitHubCommit.Repo.Owner, gitHubCommit.Repo.Name, gitRef)
request, err := http.NewRequest("GET", url, nil)
if err != nil {
return request, wrapError(err)
}
if gitHubToken != "" {
request.Header.Set("Authorization", fmt.Sprintf("token %s", gitHubToken))
}
return request, nil
}