Skip to content

Commit

Permalink
Merge pull request #5907 from hashicorp/f-infer-contenttype
Browse files Browse the repository at this point in the history
Infer content type in alloc fs stat endpoint
  • Loading branch information
preetapan committed Jul 1, 2019
2 parents 21f07c2 + de8ae8b commit 15df89b
Show file tree
Hide file tree
Showing 12 changed files with 91 additions and 19 deletions.
11 changes: 6 additions & 5 deletions api/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
39 changes: 34 additions & 5 deletions client/allocdir/alloc_dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 := "application/octet-stream"
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 {
Expand Down
28 changes: 28 additions & 0 deletions client/allocdir/alloc_dir_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,3 +472,31 @@ 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)
inputPath := "input/"
var testFiles []string
err := filepath.Walk(inputPath, func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
testFiles = append(testFiles, path)
}
return err
})
require.Nil(err)

expectedEncodings := map[string]string{
"input/happy.gif": "image/gif",
"input/image.png": "image/png",
"input/nomad.jpg": "image/jpeg",
"input/test.go": "application/octet-stream",
"input/test.json": "application/json",
"input/test.txt": "text/plain; charset=utf-8",
}
for _, file := range testFiles {
fileInfo, err := os.Stat(file)
require.Nil(err)
res := detectContentType(fileInfo, file)
require.Equal(expectedEncodings[file], res)
}
}
Binary file added client/allocdir/input/happy.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/allocdir/input/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/allocdir/input/nomad.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions client/allocdir/input/test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

import (
"fmt"
)

func main() {
fmt.Println("Hello, playground")
}
3 changes: 3 additions & 0 deletions client/allocdir/input/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"test":"test"
}
1 change: 1 addition & 0 deletions client/allocdir/input/test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello world
2 changes: 1 addition & 1 deletion client/fs_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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: "/",
Expand Down
11 changes: 6 additions & 5 deletions client/structs/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions command/alloc_fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down

0 comments on commit 15df89b

Please sign in to comment.