github.com/stevenmatthewt/agent@v3.5.4+incompatible/agent/form_uploader.go (about) 1 package agent 2 3 import ( 4 "bytes" 5 _ "crypto/sha512" // import sha512 to make sha512 ssl certs work 6 "fmt" 7 "io" 8 "mime/multipart" 9 "net/http" 10 "net/http/httputil" 11 "regexp" 12 // "net/http/httputil" 13 "errors" 14 "net/url" 15 "os" 16 17 "github.com/buildkite/agent/api" 18 "github.com/buildkite/agent/logger" 19 ) 20 21 var ArtifactPathVariableRegex = regexp.MustCompile("\\$\\{artifact\\:path\\}") 22 23 type FormUploader struct { 24 // Whether or not HTTP calls shoud be debugged 25 DebugHTTP bool 26 } 27 28 func (u *FormUploader) Setup(destination string, debugHTTP bool) error { 29 u.DebugHTTP = debugHTTP 30 31 return nil 32 } 33 34 // The FormUploader doens't specify a URL, as one is provided by Buildkite 35 // after uploading 36 func (u *FormUploader) URL(artifact *api.Artifact) string { 37 return "" 38 } 39 40 func (u *FormUploader) Upload(artifact *api.Artifact) error { 41 // Create a HTTP request for uploading the file 42 request, err := createUploadRequest(artifact) 43 if err != nil { 44 return err 45 } 46 47 // Create the client 48 client := &http.Client{} 49 50 // Perform the request 51 logger.Debug("%s %s", request.Method, request.URL) 52 response, err := client.Do(request) 53 54 // Check for errors 55 if err != nil { 56 return err 57 } else { 58 // Be sure to close the response body at the end of 59 // this function 60 defer response.Body.Close() 61 62 if u.DebugHTTP { 63 responseDump, err := httputil.DumpResponse(response, true) 64 logger.Debug("\nERR: %s\n%s", err, string(responseDump)) 65 } 66 67 if response.StatusCode/100 != 2 { 68 body := &bytes.Buffer{} 69 _, err := body.ReadFrom(response.Body) 70 if err != nil { 71 return err 72 } 73 74 // Return a custom error with the response body from the page 75 message := fmt.Sprintf("%s (%d)", body, response.StatusCode) 76 return errors.New(message) 77 } 78 } 79 80 return nil 81 } 82 83 // Creates a new file upload http request with optional extra params 84 func createUploadRequest(artifact *api.Artifact) (*http.Request, error) { 85 file, err := os.Open(artifact.AbsolutePath) 86 if err != nil { 87 return nil, err 88 } 89 defer file.Close() 90 91 body := &bytes.Buffer{} 92 writer := multipart.NewWriter(body) 93 94 // Set the post data for the request 95 for key, val := range artifact.UploadInstructions.Data { 96 // Replace the magical ${artifact:path} variable with the 97 // artifact's path 98 newVal := ArtifactPathVariableRegex.ReplaceAllLiteralString(val, artifact.Path) 99 100 // Write the new value to the form 101 err = writer.WriteField(key, newVal) 102 if err != nil { 103 return nil, err 104 } 105 } 106 107 // It's important that we add the form field last because when 108 // uploading to an S3 form, they are really nit-picky about the field 109 // order, and the file needs to be the last one other it doesn't work. 110 part, err := writer.CreateFormFile(artifact.UploadInstructions.Action.FileInput, artifact.Path) 111 if err != nil { 112 return nil, err 113 } 114 115 _, err = io.Copy(part, file) 116 if err != nil { 117 return nil, err 118 } 119 120 err = writer.Close() 121 if err != nil { 122 return nil, err 123 } 124 125 // Create the URL that we'll send data to 126 uri, err := url.Parse(artifact.UploadInstructions.Action.URL) 127 if err != nil { 128 return nil, err 129 } 130 131 uri.Path = artifact.UploadInstructions.Action.Path 132 133 // Create the request 134 req, err := http.NewRequest(artifact.UploadInstructions.Action.Method, uri.String(), body) 135 if err != nil { 136 return nil, err 137 } 138 139 // Finally add the multipart content type to the request 140 req.Header.Add("Content-Type", writer.FormDataContentType()) 141 142 return req, nil 143 }