Skip to content

Commit

Permalink
Add validations to check that host directory exists in mount option
Browse files Browse the repository at this point in the history
  • Loading branch information
apoorvam committed May 15, 2019
1 parent 2445611 commit b09ec14
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 24 deletions.
22 changes: 22 additions & 0 deletions internal/util/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package util

import (
"os"
"path"
"strings"
)

// HomeDir is the environment variable HOME
var HomeDir = os.Getenv("HOME")

// DirExists returns true if the given param is a valid existing directory
func DirExists(dir string) bool {
if dir[0] == '~' {
dir = path.Join(HomeDir, strings.Trim(dir, "~"))
}
src, err := os.Stat(dir)
if err != nil {
return false
}
return src.IsDir()
}
51 changes: 51 additions & 0 deletions internal/util/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package util

import (
"io/ioutil"
"os"
"testing"
)

func TestDirExistsSuccess(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "TestDir")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)

exists := DirExists(tmpdir)

if !exists {
t.Fatalf("Directory exists; but got false")
}
}

func TestDirExistsFail(t *testing.T) {
exists := DirExists("this path is invalid")

if exists {
t.Fatalf("Directory invalid; but got as exists")
}
}

func TestDirExistsFailForFile(t *testing.T) {
tmpfile, err := ioutil.TempFile("", "TestFileExists")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpfile.Name())

exists := DirExists(tmpfile.Name())

if exists {
t.Fatalf("Not a directory; but got as true")
}
}

func TestDirExistsIfNotAbsPath(t *testing.T) {
exists := DirExists("~/invalidpathfortesting")

if exists {
t.Fatalf("Not a directory; but got as true")
}
}
51 changes: 29 additions & 22 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
ut "github.com/go-playground/universal-translator"
"github.com/joho/godotenv"
"github.com/leopardslab/Dunner/internal/logger"
"github.com/leopardslab/Dunner/internal/util"
"github.com/leopardslab/Dunner/pkg/docker"
"github.com/spf13/viper"
"gopkg.in/go-playground/validator.v9"
Expand All @@ -25,19 +26,23 @@ import (
var log = logger.Log

var (
uni *ut.UniversalTranslator
govalidator *validator.Validate
trans ut.Translator
uni *ut.UniversalTranslator
govalidator *validator.Validate
trans ut.Translator
defaultPermissionMode = "r"
validDirPermissionModes = []string{defaultPermissionMode, "wr", "rw", "w"}
)

var customValidations = []struct {
type customValidation struct {
tag string
translation string
validationFn func(fl validator.FieldLevel) bool
}{
}

var customValidations = []customValidation{
{
tag: "mountdir",
translation: "mount directory '{0}' is invalid. Use '<src>:<dest>:<mode>'",
translation: "mount directory '{0}' is invalid. Check format is '<valid_src_dir>:<valid_dest_dir>:<mode>' and has right permission level",
validationFn: ValidateMountDir,
},
}
Expand Down Expand Up @@ -66,7 +71,7 @@ type Configs struct {

// Validate validates config and returns errors.
func (configs *Configs) Validate() []error {
err := initValidator()
err := initValidator(customValidations)
if err != nil {
return []error{err}
}
Expand Down Expand Up @@ -98,7 +103,7 @@ func formatErrors(valErrs error, taskName string) []error {
return errs
}

func initValidator() error {
func initValidator(customValidations []customValidation) error {
govalidator = validator.New()
govalidator.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("yaml"), ",", 2)[0]
Expand Down Expand Up @@ -132,15 +137,28 @@ func initValidator() error {
return nil
}

// ValidateMountDir verifies that mount values are in proper format
// ValidateMountDir verifies that mount values are in proper format <src>:<dest>:<mode>
// Format should match, <mode> is optional which is `readOnly` by default and `src` directory exists in host machine
func ValidateMountDir(fl validator.FieldLevel) bool {
value := fl.Field().String()
f := func(c rune) bool { return c == ':' }
mountValues := strings.FieldsFunc(value, f)
if len(mountValues) != 3 {
mountValues = append(mountValues, defaultPermissionMode)
}
if len(mountValues) != 3 {
return false
}
return true
validPerm := false
for _, perm := range validDirPermissionModes {
if mountValues[2] == perm {
validPerm = true
}
}
if !validPerm {
return false
}
return util.DirExists(mountValues[0])
}

// GetConfigs reads and parses tasks from the dunner file
Expand Down Expand Up @@ -228,21 +246,10 @@ func DecodeMount(mounts []string, step *docker.Step) error {
strings.Trim(strings.Trim(m, `'`), `"`),
":",
)
if len(arr) != 3 && len(arr) != 2 {
return fmt.Errorf(
`config: invalid format for mount %s`,
m,
)
}
var readOnly = true
if len(arr) == 3 {
if arr[2] == "wr" || arr[2] == "w" {
readOnly = false
} else if arr[2] != "r" {
return fmt.Errorf(
`config: invalid format of read-write mode for mount '%s'`,
m,
)
}
}
src, err := filepath.Abs(joinPathRelToHome(arr[0]))
Expand All @@ -263,7 +270,7 @@ func DecodeMount(mounts []string, step *docker.Step) error {

func joinPathRelToHome(p string) string {
if p[0] == '~' {
return path.Join(os.Getenv("HOME"), strings.Trim(p, "~"))
return path.Join(util.HomeDir, strings.Trim(p, "~"))
}
return p
}
Expand Down
68 changes: 66 additions & 2 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func TestConfigs_ValidateWithParseErrors(t *testing.T) {
}
}

func TestConfigs_ValidateWithInvalidMountDirectory(t *testing.T) {
func TestConfigs_ValidateWithInvalidMountFormat(t *testing.T) {
tasks := make(map[string][]Task, 0)
task := getSampleTask()
task.Mounts = []string{"invalid_dir"}
Expand All @@ -118,7 +118,7 @@ func TestConfigs_ValidateWithInvalidMountDirectory(t *testing.T) {
t.Fatalf("expected 1 error, got %d : %s", len(errs), errs)
}

expected := "task 'stats': mount directory 'invalid_dir' is invalid. Use '<src>:<dest>:<mode>'"
expected := "task 'stats': mount directory 'invalid_dir' is invalid. Check format is '<valid_src_dir>:<valid_dest_dir>:<mode>' and has right permission level"
if errs[0].Error() != expected {
t.Fatalf("expected: %s, got: %s", expected, errs[0].Error())
}
Expand All @@ -139,6 +139,70 @@ func TestConfigs_ValidateWithValidMountDirectory(t *testing.T) {
}
}

func TestConfigs_ValidateWithNoModeGiven(t *testing.T) {
tasks := make(map[string][]Task, 0)
task := getSampleTask()
wd, _ := os.Getwd()
task.Mounts = []string{fmt.Sprintf("%s:%s", wd, wd)}
tasks["stats"] = []Task{task}
configs := &Configs{Tasks: tasks}

errs := configs.Validate()

if errs != nil {
t.Fatalf("expected no errors, got %s", errs)
}
}

func TestConfigs_ValidateWithInvalidMode(t *testing.T) {
tasks := make(map[string][]Task, 0)
task := getSampleTask()
wd, _ := os.Getwd()
task.Mounts = []string{fmt.Sprintf("%s:%s:ab", wd, wd)}
tasks["stats"] = []Task{task}
configs := &Configs{Tasks: tasks}

errs := configs.Validate()

expected := fmt.Sprintf("task 'stats': mount directory '%s' is invalid. Check format is '<valid_src_dir>:<valid_dest_dir>:<mode>' and has right permission level", task.Mounts[0])
if errs[0].Error() != expected {
t.Fatalf("expected: %s, got: %s", expected, errs[0].Error())
}
}

func TestConfigs_ValidateWithInvalidMountDirectory(t *testing.T) {
tasks := make(map[string][]Task, 0)
task := getSampleTask()
task.Mounts = []string{"blah:foo:w"}
tasks["stats"] = []Task{task}
configs := &Configs{Tasks: tasks}

errs := configs.Validate()

if len(errs) != 1 {
t.Fatalf("expected 1 error, got %d : %s", len(errs), errs)
}

expected := "task 'stats': mount directory 'blah:foo:w' is invalid. Check format is '<valid_src_dir>:<valid_dest_dir>:<mode>' and has right permission level"
if errs[0].Error() != expected {
t.Fatalf("expected: %s, got: %s", expected, errs[0].Error())
}
}

func getSampleTask() Task {
return Task{Image: "image_name", Command: []string{"node", "--version"}}
}

func TestInitValidatorForNilTranslation(t *testing.T) {
vals := []customValidation{{tag: "foo", translation: "", validationFn: nil}}

err := initValidator(vals)

expected := "failed to register validation: Function cannot be empty"
if err == nil {
t.Fatalf("expected %s, got %s", expected, err)
}
if err.Error() != expected {
t.Fatalf("expected %s, got %s", expected, err.Error())
}
}

0 comments on commit b09ec14

Please sign in to comment.