```
go run main.go
```
-Integrated Cloud Native (ICN) RESTful API
-
-This is a Golang application providing a RESTful API to interact with and upload image objects.
-
-The API application source files are in the icn/cmd/bpa-restapi-agent directory.
-
-While the database back-end is extensible, this initial release requires mongodb.
-
-Install
-
-Install and start mongodb. For instructions: https://docs.mongodb.com/manual/installation/
-
-git clone "https://gerrit.akraino.org/r/icn"
-cd icn/cmd/bpa-restapi-agent
-
-Run the application
-go run main.go
-
-Output without a config file:
-
-2019/08/22 14:08:41 Error loading config file. Using defaults
-2019/08/22 14:08:41 Starting Integrated Cloud Native API
-
-RESTful API usage examples
-
-Sample Post Request
-
-curl -i -F "metadata=<jsonfile;type=application/json" -F file=@/home/<username>/<dir>/jsonfile -X POST http://NODE_IP:9015//baremetalcluster/{owner}/{clustername}/<image_type>
-
-#image type can be binary_image, container_image, or os_image
-
-Example requests and responses:
-
-Create image - POST
-
-#Using a json file called sample.json
-#image_length in sample.json can be determined with the command
-ls -al <image_file>
-
-Request
-
-curl -i -F "metadata=<sample.json;type=application/json" -F file=@/home/enyi/workspace/icn/cmd/bpa-restapi-agent/sample.json -X POST http://localhost:9015/v1/baremetalcluster/alpha/beta/container_images
-
-Response
-
-HTTP/1.1 100 Continue
-
-HTTP/1.1 201 Created
-Content-Type: application/json
-Date: Thu, 22 Aug 2019 22:56:16 GMT
-Content-Length: 239
-
-{"owner":"alpha","cluster_name":"beta","type":"container","image_name":"asdf246","image_offset":0,"image_length":29718177,"upload_complete":false,"description":{"image_records":[{"image_record_name":"iuysdi1234","repo":"icn","tag":"1"}]}}
-
-#this creates a database entry for the image, and an empty file in the file system
-
-List image - GET
-
-curl -i -X GET http://localhost:9015/v1/baremetalcluster/{owner}/{clustername}/<image_type>/{imgname}
-
-
-example:
-#continuing with our container image from above
-
-Request
-
-curl -i -X GET http://localhost:9015/v1/baremetalcluster/alpha/beta/container_images/asdf246
-
-Response
-
-HTTP/1.1 200 OK
-Content-Type: application/json
-Date: Thu, 22 Aug 2019 22:57:10 GMT
-Content-Length: 239
-
-{"owner":"alpha","cluster_name":"beta","type":"container","image_name":"asdf246","image_offset":0,"image_length":29718177,"upload_complete":false,"description":{"image_records":[{"image_record_name":"iuysdi1234","repo":"icn","tag":"1"}]}}
-
-Upload container image - PATCH
-Request
-
-curl --request PATCH --data-binary "@/home/enyi/workspace/icn/cmd/bpa-restapi-agent/sample_image" http://localhost:9015/v1/baremetalcluster/alpha/beta/container_images/asdf246 --header "Upload-Offset: 0" --header "Expect:" -i
-
-
-Response
-
-HTTP/1.1 204 No Content
-Upload-Offset: 29718177
-Date: Thu, 22 Aug 2019 23:19:44 GMT
-
-Check uploaded image - GET
-
-Request
-
-curl -i -X GET http://localhost:9015/v1/baremetalcluster/alpha/beta/container_images/asdf246
-
-Response
-
-HTTP/1.1 200 OK
-Content-Type: application/json
-Date: Fri, 23 Aug 2019 17:12:07 GMT
-Content-Length: 245
-
-{"owner":"alpha","cluster_name":"beta","type":"container","image_name":"asdf246","image_offset":29718177,"image_length":29718177,"upload_complete":true,"description":{"image_records":[{"image_record_name":"iuysdi1234","repo":"icn","tag":"1"}]}}
-
-#after the upload, the image_offset is now the same as image_length and upload_complete changed to true
-#if upload was incomplete
-
-Resumable upload instructions
-
-Resumable upload -PATCH
-
-#this is the current resumable upload mechanism
-
-Request
-
-curl --request PATCH --data-binary "@/home/enyi/workspace/icn/cmd/bpa-restapi-agent/sample_image" http://localhost:9015/v1/baremetalcluster/alpha/beta/container_images/asdf246 --header "Upload-Offset: 0" --header "Expect:" -i --limit-rate 200K
-
-#the above request limits transfer for testing purposes
-#'ctl c' out after a few seconds, to stop file transfer
-#check image_offset with a GET
-
-Check upload - GET
-
-Request
-
-curl -i -X GET http://localhost:9015/v1/baremetalcluster/alpha/beta/container_images/asdf246
-
-Response
-
-HTTP/1.1 200 OK
-Content-Type: application/json
-Date: Sat, 24 Aug 2019 00:30:00 GMT
-Content-Length: 245
-
-{"owner":"alpha","cluster_name":"beta","type":"container","image_name":"asdf246","image_offset":4079616,"image_length":29718177,"upload_complete":false,"description":{"image_records":[{"image_record_name":"iuysdi1234","repo":"icn","tag":"2"}]}}
-
-#from our response you can see that image_offset is still less than image_length and #upload_complete is still false
-#next we use the dd command (no limiting this time)
-
-Request
-
-dd if=/home/enyi/workspace/icn/cmd/bpa-restapi-agent/sample_image skip=4079616 bs=1 | curl --request PATCH --data-binary @- http://localhost:9015/v1/baremetalcluster/alpha/beta/container_images/asdf246 --header "Upload-Offset: 4079616" --header "Expect:" -i
-
-#the request skips already uploaded 4079616 bytes of data
-
-Response
-
-25638561+0 records in
-25638561+0 records out
-25638561 bytes (26 MB, 24 MiB) copied, 207.954 s, 123 kB/s
-HTTP/1.1 204 No Content
-Upload-Offset: 29718177
-Date: Sat, 24 Aug 2019 00:43:18 GMT
-
-Update image description - PUT
-
-# let's change the tag in description from 1 to latest
-# once the change is made in sample.json (or your json file)
-
-Request
-
-curl -i -F "metadata=<sample.json;type=application/json" -F file=@/home/enyi/workspace/icn/cmd/bpa-restapi-agent/sample.json -X PUT http://localhost:9015/v1/baremetalcluster/alpha/beta/container_images/asdf246
-
-Response
-
-HTTP/1.1 100 Continue
-
-HTTP/1.1 201 Created
-Content-Type: application/json
-Date: Fri, 23 Aug 2019 17:21:01 GMT
-Content-Length: 239
-
-{"owner":"alpha","cluster_name":"beta","type":"container","image_name":"asdf246","image_offset":0,"image_length":29718177,"upload_complete":false,"description":{"image_records":[{"image_record_name":"iuysdi1234","repo":"icn","tag":"2"}]}}
-
-Delete an image - DELETE
-
-Request
-
-curl -i -X DELETE http://localhost:9015/v1/baremetalcluster/alpha/beta/container_images/asdf246
-
-Response
import (
"bytes"
- //"encoding/base64"
+ "encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
+ "os/user"
"log"
+ "path"
"strconv"
image "bpa-restapi-agent/internal/app"
return
}
+ //Create file directory
+ dir, err := createFileDir(v.Type)
+ if err != nil {
+ log.Fatal("Error creating file server directory", err)
+ }
+
//Read the file section and ignore the header
file, _, err := r.FormFile("file")
if err != nil {
defer file.Close()
+ //Convert the file content to base64 for storage
+ content, err := ioutil.ReadAll(file)
+ if err != nil {
+ http.Error(w, "Unable to read file", http.StatusUnprocessableEntity)
+ return
+ }
+
+ v.Config = base64.StdEncoding.EncodeToString(content)
ret, err := h.client.Create(v)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
+ h.dirPath = dir
+ filePath := path.Join(h.dirPath, v.ImageName)
+ file1, err := os.Create(filePath)
+ if err != nil {
+ e := "Error creating file in filesystem"
+ log.Printf("%s %s\n", e, err)
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ defer file1.Close()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
}
}
+// Create file
+
+func createFileDir(dirName string) (string, error) {
+ u, err := user.Current()
+ if err != nil {
+ log.Println("Error while fetching user home directory", err)
+ return "", err
+ }
+ home := u.HomeDir
+ dirPath := path.Join(home, "images", dirName)
+ err = os.MkdirAll(dirPath, 0744)
+ if err != nil {
+ log.Println("Error while creating file server directory", err)
+ return "", err
+ }
+ return dirPath, nil
+}
// getHandler handles GET operations on a particular name
// Returns an Image
func (h imageHandler) getHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
+ // ownerName := vars["owner"]
+ // clusterName := vars["clustername"]
imageName := vars["imgname"]
ret, err := h.client.Get(imageName)
// deleteHandler handles DELETE operations on a particular record
func (h imageHandler) deleteHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
+ // ownerName := vars["owner"]
+ // clusterName := vars["clustername"]
imageName := vars["imgname"]
err := h.client.Delete(imageName)
defer file.Close()
+ //Convert the file content to base64 for storage
+ content, err := ioutil.ReadAll(file)
+ if err != nil {
+ http.Error(w, "Unable to read file", http.StatusUnprocessableEntity)
+ return
+ }
+
+ v.Config = base64.StdEncoding.EncodeToString(content)
+
ret, err := h.client.Update(imageName, v)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Println("Size of received file ", len(body))
}
- fp, _, err := h.client.GetDirPath(imageName)
+ u, err := user.Current()
if err != nil {
- log.Printf("unable to get file path %s\n", err)
- w.WriteHeader(http.StatusInternalServerError)
- return
+ log.Println("Error while fetching user home directory", err)
+ return
}
+ home := u.HomeDir
+ dir := path.Join(home, "images", file.Type)
+ h.dirPath = dir
+ fp := fmt.Sprintf("%s/%s", h.dirPath, imageName)
f, err := os.OpenFile(fp, os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
log.Printf("unable to open file %s\n", err)
*file.UploadComplete = true
}
- _, err = h.client.Update(imageName, file)
- if err != nil {
- log.Println("Error while updating file", err)
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
+ // err = h.updateFile(file)
+ // if err != nil {
+ // log.Println("Error while updating file", err)
+ // w.WriteHeader(http.StatusInternalServerError)
+ // return
+ // }
w.WriteHeader(http.StatusNoContent)
return
import (
//"encoding/base64"
"encoding/json"
- "os"
- "os/user"
- "path"
//"io/ioutil"
"bpa-restapi-agent/internal/db"
ClusterName string `json:"cluster_name"`
Type string `json:"type"`
ImageName string `json:"image_name"`
+ Config string `json:"config"`
ImageOffset *int `json:"image_offset"`
ImageLength int `json:"image_length"`
UploadComplete *bool `json:"upload_complete"`
// ImageKey is the key structure that is used in the database
type ImageKey struct {
+ // Owner string `json:"owner"`
+ // ClusterName string `json:"cluster_name"`
ImageName string `json:"image_name"`
}
Delete(imageName string) error
Update(imageName string, c Image) (Image, error)
GetImageRecordByName(imgname, imageName string) (map[string]string, error)
- GetDirPath(imageName string) (string, string, error)
}
// ImageClient implements the ImageManager
// which implements the ImageManager
func NewBinaryImageClient() *ImageClient {
return &ImageClient{
- storeName: "binary_images",
+ storeName: "binary_image",
tagMeta: "metadata",
}
}
func NewContainerImageClient() *ImageClient {
return &ImageClient{
- storeName: "container_images",
+ storeName: "container_image",
tagMeta: "metadata",
}
}
func NewOSImageClient() *ImageClient {
return &ImageClient{
- storeName: "os_images",
+ storeName: "os_image",
tagMeta: "metadata",
}
}
//Construct composite key consisting of name
key := ImageKey{
+ // Owner: c.Owner,
+ // ClusterName: c.ClusterName,
ImageName: c.ImageName,
}
return Image{}, pkgerrors.Wrap(err, "Creating DB Entry")
}
- //Create file
- err = v.CreateFile(v.storeName, c)
- if err != nil {
- return Image{}, pkgerrors.Wrap(err, "Creating File in FS")
- }
-
return c, nil
}
-
-// Create file
-
-func (v *ImageClient) CreateFile(dirName string, c Image) error {
-
- filePath, dirPath, err := v.GetDirPath(c.ImageName)
- if err != nil {
- return pkgerrors.Wrap(err, "Get file path")
- }
- err = os.MkdirAll(dirPath, 0744)
- if err != nil {
- return pkgerrors.Wrap(err, "Make image directory")
- }
- file1, err := os.Create(filePath)
- if err != nil {
- return pkgerrors.Wrap(err, "Create image file")
- }
-
- defer file1.Close()
-
-
- return nil
-}
-
// Get returns Image for corresponding to name
func (v *ImageClient) Get(imageName string) (Image, error) {
return nil, pkgerrors.New("Image record " + imageRecordName + " not found")
}
-func (v *ImageClient) GetDirPath(imageName string) (string, string, error) {
- u, err := user.Current()
- if err != nil {
- return "", "", pkgerrors.Wrap(err, "Current user")
- }
- home := u.HomeDir
- dirPath := path.Join(home, "images", v.storeName)
- filePath := path.Join(dirPath, imageName)
-
- return filePath, dirPath, err
-}
-
// Delete the Image from database
func (v *ImageClient) Delete(imageName string) error {
//Construct the composite key to select the entry
key := ImageKey{
+ // Owner: ownerName,
+ // ClusterName: clusterName,
ImageName: imageName,
}
err := db.DBconn.Delete(v.storeName, key, v.tagMeta)
if err != nil {
return pkgerrors.Wrap(err, "Delete Image")
}
- //Delete image from FS
- filePath, _, err := v.GetDirPath(imageName)
- if err != nil {
- return pkgerrors.Wrap(err, "Get file path")
- }
- err = os.Remove(filePath)
- if err != nil {
- return pkgerrors.Wrap(err, "Delete image file")
- }
-
return nil
}
"cluster_name": "beta",
"type": "container",
"image_name": "asdf246",
- "image_length": 29718177,
+ "image_length": 21579557,
"image_offset": 0,
"upload_complete": false,
"description": {
"image_records": [
{
"image_record_name": "iuysdi1234",
- "repo": "icn",
- "tag": "2"
+ "repo": "java",
+ "tag": "8"
}
]
}