339 lines
8.2 KiB
Go
339 lines
8.2 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"math"
|
|
"net/http"
|
|
"os"
|
|
"os/user"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
|
|
_ "github.com/paypal/katbox/stream/docs"
|
|
httpSwagger "github.com/swaggo/http-swagger"
|
|
)
|
|
|
|
type StreamData struct {
|
|
Data string `json:"data"`
|
|
Offset int64 `json:"offset"`
|
|
}
|
|
|
|
type FileInformation struct {
|
|
Mode string `json:"mode"`
|
|
Nlink string `json:"nlink"`
|
|
UID string `json:"uid"`
|
|
GID string `json:"gid"`
|
|
Size string `json:"size"`
|
|
Mtime int64 `json:"mtime"`
|
|
Path string `json:"path"`
|
|
}
|
|
|
|
type Result struct {
|
|
Data []FileInformation `json:"data`
|
|
}
|
|
|
|
type Error struct {
|
|
Error string `json:"error"`
|
|
}
|
|
|
|
// Download a file from sandbox filesystem with given offset and length godoc
|
|
// @Summary Download a file from Filesystem
|
|
// @Description Download any file from sandbox logs filesystem
|
|
// @Accept json
|
|
// @Produce octet-stream
|
|
// @Success 200 {object} main.StreamData
|
|
// @Router /files/download [get]
|
|
// @Param path query string true "Path"
|
|
func downloadhandler(w http.ResponseWriter, r *http.Request) {
|
|
//Get query params
|
|
params := r.URL.Query()
|
|
path := params["path"]
|
|
|
|
if len(path) == 0 {
|
|
log.Println("path not found in URL")
|
|
error := &Error{
|
|
Error: "path not found in URL"}
|
|
retObj, err := json.Marshal(error)
|
|
if err != nil {
|
|
log.Print(err)
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Write(retObj)
|
|
return
|
|
}
|
|
|
|
file, err := os.Open(path[0])
|
|
if err != nil {
|
|
log.Print(err)
|
|
}
|
|
filename := filepath.Base(path[0])
|
|
|
|
w.Header().Set("Content-Type", "application/octet-stream")
|
|
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename))
|
|
io.Copy(w, file)
|
|
file.Close()
|
|
|
|
}
|
|
|
|
// read a file from sandbox filesystem with given offset and length godoc
|
|
// @Summary Read a file from Filesystem
|
|
// @Description Reads any file from sandbox logs filesystem and serves as a json object
|
|
// @Accept json
|
|
// @Success 200 {object} main.StreamData
|
|
// @Router /files/read [get]
|
|
// @Param path query string true "Path"
|
|
// @Param offset query int true "Offset"
|
|
// @Param length query int true "Length"
|
|
// @Param jsonp query string true "jsonp"
|
|
func readHandler(w http.ResponseWriter, r *http.Request) {
|
|
// Get query path params
|
|
params := r.URL.Query()
|
|
path := params["path"]
|
|
|
|
//check if path exists in the query params if not send error response
|
|
if len(path) == 0 {
|
|
log.Println("path not found in URL")
|
|
error := &Error{
|
|
Error: "path not found in URL"}
|
|
retObj, err := json.Marshal(error)
|
|
if err != nil {
|
|
log.Print(err)
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Write(retObj)
|
|
return
|
|
}
|
|
|
|
var err error
|
|
//check if offset exists in the query params if not send error response
|
|
if len(params["offset"]) == 0 {
|
|
error := &Error{
|
|
Error: "offset not found in URL"}
|
|
retObj, err := json.Marshal(error)
|
|
if err != nil {
|
|
log.Print(err)
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Write(retObj)
|
|
return
|
|
}
|
|
offset, err := strconv.ParseInt(params["offset"][0], 10, 64)
|
|
if err != nil {
|
|
log.Print(err)
|
|
}
|
|
|
|
//check if length exists in the query params if not send error response
|
|
if len(params["length"]) == 0 {
|
|
error := &Error{
|
|
Error: "length not found in URL"}
|
|
retObj, err := json.Marshal(error)
|
|
if err != nil {
|
|
log.Print(err)
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Write(retObj)
|
|
return
|
|
}
|
|
length, err := strconv.ParseInt(params["length"][0], 10, 64)
|
|
if err != nil {
|
|
log.Print(err)
|
|
}
|
|
|
|
size := getSize(path[0])
|
|
|
|
// if offset is invalid set it to size of the file
|
|
if offset == -1 {
|
|
offset = size
|
|
}
|
|
|
|
//set length if it is invalid
|
|
if length == -1 {
|
|
length = size - offset
|
|
}
|
|
|
|
// Cap the read length at 16 pages.
|
|
length = int64(math.Min(float64(length), float64(os.Getpagesize()*16)))
|
|
|
|
// return empty data when offset is greater than size of the file
|
|
if offset >= size {
|
|
streamData := &StreamData{
|
|
Data: "",
|
|
Offset: size}
|
|
|
|
respObj, err := json.Marshal(streamData)
|
|
if err != nil {
|
|
fmt.Fprintf(w, "json encode error")
|
|
return
|
|
}
|
|
|
|
callbackName := r.URL.Query().Get("jsonp")
|
|
if callbackName == "" {
|
|
fmt.Fprintf(w, "Please give callback name in query string")
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/javascript")
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
fmt.Fprintf(w, "%s(%s);", callbackName, respObj)
|
|
|
|
return
|
|
}
|
|
|
|
file, err := os.Open(path[0])
|
|
if err != nil {
|
|
log.Print(err)
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
s := io.NewSectionReader(file, 0, size)
|
|
|
|
buf2 := make([]byte, length)
|
|
_, err = s.ReadAt(buf2, offset)
|
|
if err != nil {
|
|
log.Print(err)
|
|
}
|
|
|
|
staremData := &StreamData{
|
|
Data: string(buf2),
|
|
Offset: offset}
|
|
|
|
respObj, err := json.Marshal(staremData)
|
|
if err != nil {
|
|
fmt.Fprintf(w, "json encode error")
|
|
return
|
|
}
|
|
|
|
callbackName := r.URL.Query().Get("jsonp")
|
|
if callbackName == "" {
|
|
fmt.Fprintf(w, "Please give callback name in query string")
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/javascript")
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
fmt.Fprintf(w, "%s(%s);", callbackName, respObj)
|
|
|
|
}
|
|
|
|
// browse sandbox filesystem godoc
|
|
// @Summary Browse Filesystem
|
|
// @Description serves sandbox logs filesystem as a json object
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Success 200 {object} main.Result
|
|
// @Router /files/browse [get]
|
|
// @Param path query string true "Path"
|
|
func browseHandler(w http.ResponseWriter, r *http.Request) {
|
|
//Get query Params
|
|
params := r.URL.Query()
|
|
path := params["path"]
|
|
|
|
//check if path exists in the query params if not send error response
|
|
if len(path) == 0 {
|
|
log.Println("path not found in URL")
|
|
error := &Error{
|
|
Error: "path not found in URL"}
|
|
retObj, err := json.Marshal(error)
|
|
if err != nil {
|
|
log.Print(err)
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Write(retObj)
|
|
return
|
|
}
|
|
|
|
// walk through the directory structure and build fileInformation struct and append to result array
|
|
result := []FileInformation{}
|
|
//test := strings.Split(path[0], "/")[len(strings.Split(path[0], "/"))-1]
|
|
//fmt.Print(test, " ")
|
|
//fmt.Print(test, " ")
|
|
err := filepath.WalkDir(path[0],
|
|
func(filePath string, dirEntry os.DirEntry, err error) error {
|
|
if len(strings.Split(filePath, "/")) < len(strings.Split(path[0], "/"))+2 {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fileInfo, err := dirEntry.Info()
|
|
|
|
fileStat, err := os.Stat(filePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// retrieve file ownership information and number of hardlinks to the file
|
|
var nlink, uid, gid uint64
|
|
if sys := fileStat.Sys(); sys != nil {
|
|
if stat, ok := sys.(*syscall.Stat_t); ok {
|
|
nlink = uint64(stat.Nlink)
|
|
uid = uint64(stat.Uid)
|
|
gid = uint64(stat.Gid)
|
|
}
|
|
}
|
|
usr, err := user.LookupId(strconv.FormatUint(uid, 10))
|
|
group, err := user.LookupGroupId(strconv.FormatUint(gid, 10))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
jsonData := FileInformation{
|
|
Mode: fileInfo.Mode().String(),
|
|
Nlink: string(strconv.FormatUint(nlink, 10)),
|
|
UID: usr.Username,
|
|
GID: group.Name,
|
|
Size: strconv.Itoa(int(fileInfo.Size())),
|
|
Mtime: fileInfo.ModTime().Unix(),
|
|
Path: filePath,
|
|
}
|
|
if err == nil {
|
|
result = append(result, jsonData)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
log.Println(err)
|
|
}
|
|
|
|
resultdata := Result{result}
|
|
c, err := json.Marshal(resultdata)
|
|
|
|
if err == nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Write(c)
|
|
}
|
|
|
|
}
|
|
|
|
//Get the size of a file
|
|
func getSize(p string) int64 {
|
|
if stat, err := os.Stat(p); err == nil {
|
|
return stat.Size()
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// @title k8s Sandbox Go Restful API with Swagger
|
|
// @version 1.0
|
|
// @description Rest API doc for sandbox API's
|
|
// @contact.name Revanth Chandra
|
|
// @host localhost:8080
|
|
// @BasePath /
|
|
func main() {
|
|
http.HandleFunc("/files/read", readHandler)
|
|
http.HandleFunc("/files/browse", browseHandler)
|
|
http.HandleFunc("/files/download", downloadhandler)
|
|
http.HandleFunc("/swagger/", httpSwagger.WrapHandler)
|
|
http.ListenAndServe(":5051", nil)
|
|
}
|