Adding TLS authentication
[icn.git] / cmd / bpa-restapi-agent / api / imagehandler.go
1 package api
2
3 import (
4         "bytes"
5         "encoding/json"
6         "fmt"
7         "io"
8         "io/ioutil"
9         "net/http"
10         "os"
11         "log"
12         "strconv"
13
14         image "bpa-restapi-agent/internal/app"
15         minioc "bpa-restapi-agent/internal/storage"
16
17         "github.com/gorilla/mux"
18 )
19
20 // imageHandler is used to store backend implementations objects
21 // Also simplifies mocking for unit testing purposes
22 type imageHandler struct {
23         // Interface that implements Image operations
24         // We will set this variable with a mock interface for testing
25         client image.ImageManager
26         dirPath string
27         minioI minioc.MinIOInfo
28         storeName string  // as minio client bucketname
29 }
30
31 // CreateHandler handles creation of the image entry in the database
32
33 func (h imageHandler) createHandler(w http.ResponseWriter, r *http.Request) {
34         var v image.Image
35
36         // Implemenation using multipart form
37         // Review and enable/remove at a later date
38         // Set Max size to 16mb here
39         err := r.ParseMultipartForm(16777216)
40         if err != nil {
41                 http.Error(w, err.Error(), http.StatusUnprocessableEntity)
42                 return
43         }
44
45         jsn := bytes.NewBuffer([]byte(r.FormValue("metadata")))
46         err = json.NewDecoder(jsn).Decode(&v)
47         switch {
48         case err == io.EOF:
49                 http.Error(w, "Empty body", http.StatusBadRequest)
50                 return
51         case err != nil:
52                 http.Error(w, err.Error(), http.StatusUnprocessableEntity)
53                 return
54         }
55
56         // Name is required.
57         if v.ImageName == "" {
58                 http.Error(w, "Missing name in POST request", http.StatusBadRequest)
59                 return
60         }
61
62         // Owner is required.
63         if v.Owner == "" {
64                 http.Error(w, "Missing Owner in POST request", http.StatusBadRequest)
65                 return
66         }
67
68         if v.ImageLength == 0 {
69                 e := "Improper upload length"
70                 w.WriteHeader(http.StatusBadRequest)
71                 w.Write([]byte(e))
72                 return
73         }
74
75         //Read the file section and ignore the header
76         file, _, err := r.FormFile("file")
77         if err != nil {
78                 http.Error(w, "Unable to process file", http.StatusUnprocessableEntity)
79                 return
80         }
81
82         defer file.Close()
83
84
85         ret, err := h.client.Create(v)
86         if err != nil {
87                 http.Error(w, err.Error(), http.StatusInternalServerError)
88                 return
89         }
90
91         w.Header().Set("Content-Type", "application/json")
92         w.WriteHeader(http.StatusCreated)
93         err = json.NewEncoder(w).Encode(ret)
94         if err != nil {
95                 http.Error(w, err.Error(), http.StatusInternalServerError)
96                 return
97         }
98 }
99
100 // getHandler handles GET operations on a particular name
101 // Returns an Image
102 func (h imageHandler) getHandler(w http.ResponseWriter, r *http.Request) {
103         vars := mux.Vars(r)
104         imageName := vars["imgname"]
105
106         ret, err := h.client.Get(imageName)
107         if err != nil {
108                 http.Error(w, err.Error(), http.StatusInternalServerError)
109                 return
110         }
111
112         w.Header().Set("Content-Type", "application/json")
113         w.WriteHeader(http.StatusOK)
114         err = json.NewEncoder(w).Encode(ret)
115         if err != nil {
116                 http.Error(w, err.Error(), http.StatusInternalServerError)
117                 return
118         }
119 }
120
121 // deleteHandler handles DELETE operations on a particular record
122 func (h imageHandler) deleteHandler(w http.ResponseWriter, r *http.Request) {
123         vars := mux.Vars(r)
124         imageName := vars["imgname"]
125
126         err := h.client.Delete(imageName)
127         if err != nil {
128                 http.Error(w, err.Error(), http.StatusInternalServerError)
129                 return
130         }
131
132         h.minioI.DeleteImage(h.storeName, imageName)
133
134         w.WriteHeader(http.StatusNoContent)
135 }
136
137 // UpdateHandler handles Update operations on a particular image
138 func (h imageHandler) updateHandler(w http.ResponseWriter, r *http.Request) {
139         var v image.Image
140         vars := mux.Vars(r)
141         imageName := vars["imgname"]
142
143         err := r.ParseMultipartForm(16777216)
144         if err != nil {
145                 http.Error(w, err.Error(), http.StatusUnprocessableEntity)
146                 return
147         }
148
149         jsn := bytes.NewBuffer([]byte(r.FormValue("metadata")))
150         err = json.NewDecoder(jsn).Decode(&v)
151         switch {
152         case err == io.EOF:
153                 http.Error(w, "Empty body", http.StatusBadRequest)
154                 return
155         case err != nil:
156                 http.Error(w, err.Error(), http.StatusUnprocessableEntity)
157                 return
158         }
159
160         // Name is required.
161         if v.ImageName == "" {
162                 http.Error(w, "Missing name in PUT request", http.StatusBadRequest)
163                 return
164         }
165
166         // Owner is required.
167         if v.Owner == "" {
168                 http.Error(w, "Missing Owner in PUT request", http.StatusBadRequest)
169                 return
170         }
171
172         //Read the file section and ignore the header
173         file, _, err := r.FormFile("file")
174         if err != nil {
175                 http.Error(w, "Unable to process file", http.StatusUnprocessableEntity)
176                 return
177         }
178
179         defer file.Close()
180
181         ret, err := h.client.Update(imageName, v)
182         if err != nil {
183                 http.Error(w, err.Error(), http.StatusInternalServerError)
184                 return
185         }
186
187         w.Header().Set("Content-Type", "application/json")
188         w.WriteHeader(http.StatusCreated)
189         err = json.NewEncoder(w).Encode(ret)
190         if err != nil {
191                 http.Error(w, err.Error(), http.StatusInternalServerError)
192                 return
193         }
194 }
195
196 // File upload is handled by the patchHandler
197
198 func (h imageHandler) patchHandler(w http.ResponseWriter, r *http.Request) {
199         log.Println("going to patch file")
200         vars := mux.Vars(r)
201         imageName := vars["imgname"]
202         file, err := h.client.Get(imageName)
203         if err != nil {
204                 http.Error(w, err.Error(), http.StatusInternalServerError)
205                 return
206         }
207         if *file.UploadComplete == true {
208                 e := "Upload already completed"
209                 w.WriteHeader(http.StatusUnprocessableEntity)
210                 w.Write([]byte(e))
211                 log.Println("Upload already completed")
212                 return
213         }
214         off, err := strconv.Atoi(r.Header.Get("Upload-Offset"))
215         if err != nil {
216                 log.Println("Improper upload offset", err)
217                 w.WriteHeader(http.StatusBadRequest)
218                 return
219         }
220         log.Printf("Upload offset %d\n", off)
221         if *file.ImageOffset != off {
222                 e := fmt.Sprintf("Expected Offset %d, actual offset %d", *file.ImageOffset, off)
223                 w.WriteHeader(http.StatusConflict)
224                 w.Write([]byte(e))
225                 log.Printf("Expected Offset:%d doesn't match got offset:%d\n", *file.ImageOffset, off)
226                 return
227         }
228
229         log.Println("Content length is", r.Header.Get("Content-Length"))
230         clh := r.Header.Get("Content-Length")
231         cl, err := strconv.Atoi(clh)
232         if err != nil {
233                 log.Println("unknown content length")
234                 w.WriteHeader(http.StatusInternalServerError)
235                 return
236         }
237
238         if cl != (file.ImageLength - *file.ImageOffset) {
239                 e := fmt.Sprintf("Content length doesn't match upload length. Expected content length %d got %d", file.ImageLength-*file.ImageOffset, cl)
240                 log.Println(e)
241                 w.WriteHeader(http.StatusBadRequest)
242                 w.Write([]byte(e))
243                 return
244         }
245
246         body, err := ioutil.ReadAll(r.Body)
247         if err != nil {
248                 log.Printf("Received file partially %s\n", err)
249                 log.Println("Size of received file ", len(body))
250         }
251
252         fp, _, err := h.client.GetDirPath(imageName)
253         if err != nil {
254                 log.Printf("unable to get file path %s\n", err)
255                 w.WriteHeader(http.StatusInternalServerError)
256                 return
257         }
258         f, err := os.OpenFile(fp, os.O_APPEND|os.O_WRONLY, 0644)
259         if err != nil {
260                 log.Printf("unable to open file %s\n", err)
261                 w.WriteHeader(http.StatusInternalServerError)
262                 return
263         }
264         defer f.Close()
265
266         n, err := f.WriteAt(body, int64(off))
267         if err != nil {
268                 log.Printf("unable to write %s", err)
269                 w.WriteHeader(http.StatusInternalServerError)
270                 return
271         }
272
273         log.Printf("Start to Patch image, bucket: %s, image: %s, dirpath: %s, offset: %d, n: %d\n",
274                 h.storeName, imageName, fp, *file.ImageOffset, n)
275         uploadbytes, err := h.minioI.PatchImage(h.storeName, imageName, fp, int64(*file.ImageOffset), int64(n))
276         if err != nil || uploadbytes == 0  {
277                 log.Printf("MinIO upload with offset %d failed: %s", *file.ImageOffset, err)
278                 w.WriteHeader(http.StatusInternalServerError)
279                 return
280     }
281
282         log.Println("number of bytes written ", n)
283         no := *file.ImageOffset + n
284         file.ImageOffset = &no
285
286         uo := strconv.Itoa(*file.ImageOffset)
287         w.Header().Set("Upload-Offset", uo)
288         if *file.ImageOffset == file.ImageLength {
289                 log.Println("upload completed successfully")
290                 *file.UploadComplete = true
291         }
292
293         _, err = h.client.Update(imageName, file)
294         if err != nil {
295                 log.Println("Error while updating file", err)
296                 w.WriteHeader(http.StatusInternalServerError)
297                 return
298         }
299         w.WriteHeader(http.StatusNoContent)
300
301         return
302
303 }