Infer content type in alloc fs stat endpoint

This commit is contained in:
Preetha Appan
2019-06-28 20:31:28 -05:00
parent 520cd901d7
commit f7f41c42e6
8 changed files with 68 additions and 19 deletions

View File

@@ -20,11 +20,12 @@ const (
// AllocFileInfo holds information about a file inside the AllocDir
type AllocFileInfo struct {
Name string
IsDir bool
Size int64
FileMode string
ModTime time.Time
Name string
IsDir bool
Size int64
FileMode string
ModTime time.Time
ContentType string
}
// StreamFrame is used to frame data of a file when streaming

View File

@@ -11,6 +11,9 @@ import (
"sync"
"time"
"net/http"
"strings"
hclog "github.com/hashicorp/go-hclog"
multierror "github.com/hashicorp/go-multierror"
cstructs "github.com/hashicorp/nomad/client/structs"
@@ -392,15 +395,41 @@ func (d *AllocDir) Stat(path string) (*cstructs.AllocFileInfo, error) {
return nil, err
}
contentType := detectContentType(info, p)
return &cstructs.AllocFileInfo{
Size: info.Size(),
Name: info.Name(),
IsDir: info.IsDir(),
FileMode: info.Mode().String(),
ModTime: info.ModTime(),
Size: info.Size(),
Name: info.Name(),
IsDir: info.IsDir(),
FileMode: info.Mode().String(),
ModTime: info.ModTime(),
ContentType: contentType,
}, nil
}
// detectContentType tries to infer the file type by reading the first
// 512 bytes of the file. Json file extensions are special cased.
func detectContentType(fileInfo os.FileInfo, path string) string {
contentType := "unknown"
if !fileInfo.IsDir() {
f, err := os.Open(path)
// Best effort content type detection
// We ignore errors because this is optional information
if err == nil {
fileBytes := make([]byte, 512)
_, err := f.Read(fileBytes)
if err == nil {
contentType = http.DetectContentType(fileBytes)
}
}
}
// Special case json files
if strings.HasSuffix(path, ".json") {
contentType = "application/json"
}
return contentType
}
// ReadAt returns a reader for a file at the path relative to the alloc dir
func (d *AllocDir) ReadAt(path string, offset int64) (io.ReadCloser, error) {
if escapes, err := structs.PathEscapesAllocDir("", path); err != nil {

View File

@@ -472,3 +472,18 @@ func TestPathFuncs(t *testing.T) {
t.Errorf("%q is not empty. empty=%v error=%v", dir, empty, err)
}
}
func TestAllocDir_DetectContentType(t *testing.T) {
require := require.New(t)
imgPath := "input/image.png"
fileInfo, err := os.Stat(imgPath)
require.Nil(err)
res := detectContentType(fileInfo, imgPath)
require.Equal("image/png", res)
jsonPath := "input/test.json"
fileInfo, err = os.Stat(jsonPath)
require.Nil(err)
res = detectContentType(fileInfo, jsonPath)
require.Equal("application/json", res)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

View File

@@ -0,0 +1,3 @@
{
"test":"test"
}

View File

@@ -98,7 +98,7 @@ func TestFS_Stat(t *testing.T) {
// Wait for alloc to be running
alloc := testutil.WaitForRunning(t, s.RPC, job)[0]
// Make the request with bad allocation id
// Make the request
req := &cstructs.FsStatRequest{
AllocID: alloc.ID,
Path: "/",

View File

@@ -36,11 +36,12 @@ type ClientStatsResponse struct {
// AllocFileInfo holds information about a file inside the AllocDir
type AllocFileInfo struct {
Name string
IsDir bool
Size int64
FileMode string
ModTime time.Time
Name string
IsDir bool
Size int64
FileMode string
ModTime time.Time
ContentType string `json:"contenttype,omitempty"`
}
// FsListRequest is used to list an allocation's directory.

View File

@@ -213,7 +213,7 @@ func (f *AllocFSCommand) Run(args []string) int {
if stat {
// Display the file information
out := make([]string, 2)
out[0] = "Mode|Size|Modified Time|Name"
out[0] = "Mode|Size|Modified Time|Content Type|Name"
if file != nil {
fn := file.Name
if file.IsDir {
@@ -225,8 +225,8 @@ func (f *AllocFSCommand) Run(args []string) int {
} else {
size = humanize.IBytes(uint64(file.Size))
}
out[1] = fmt.Sprintf("%s|%s|%s|%s", file.FileMode, size,
formatTime(file.ModTime), fn)
out[1] = fmt.Sprintf("%s|%s|%s|%s|%s", file.FileMode, size,
formatTime(file.ModTime), file.ContentType, fn)
}
f.Ui.Output(formatList(out))
return 0