Skip to content

Commit

Permalink
break out validation code
Browse files Browse the repository at this point in the history
  • Loading branch information
stevendborrelli committed Oct 5, 2016
1 parent 567c21b commit 10e4536
Show file tree
Hide file tree
Showing 2 changed files with 272 additions and 0 deletions.
143 changes: 143 additions & 0 deletions resource/file/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Copyright © 2016 Asteris, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package file

import (
"fmt"
"os/user"
"strings"

"github.com/pkg/errors"
)

// Validate runs checks against a File resource
func (f *File) Validate() error {
var err error
if f.Destination == "" {
return errors.New("file requires a destination parameter")
}

err = f.validateState()
if err != nil {
return err
}

err = f.validateType()
if err != nil {
return err
}

// links should have a target
err = f.validateTarget()
if err != nil {
return err
}

err = f.validateUser()
if err != nil {
return err
}

err = f.validateGroup()
if err != nil {
return err
}

return err
}

// Validate the state or set default value
func (f *File) validateState() error {
switch f.State {
case "": //nothing set, use default
f.State = defaultState
return nil
default:
for _, s := range validStates {
if f.State == s {
return nil
}
}
return fmt.Errorf("state should be one of %s, got %q", strings.Join(validStates, ", "), f.State)
}
}

// Validate the type or set default value
func (f *File) validateType() error {
switch f.Type {
case "": //use default if not set
f.Type = defaultType
return nil
default:
for _, t := range allTypes {
if f.Type == t {
return nil
}
}
return fmt.Errorf("type should be one of %s, got %q", strings.Join(allTypes, ", "), f.Type)
}
}

// A target needs to be set if you are creating a link
func (f *File) validateTarget() error {
switch f.Target {
case "":
if f.Type == "symlink" || f.Type == "hardlink" {
return fmt.Errorf("must define a target if you are using a %q", f.Type)
}
return nil
default:
// is target set for a file or directory type?
if f.Type == "symlink" || f.Type == "hardlink" {
return nil
}
return fmt.Errorf("cannot define target on a type of %q: target: %q", f.Type, f.Target)
}
}

// if a user is provided, ensure it exists on the system
func (f *File) validateUser() error {
if f.UserInfo == nil {
f.UserInfo = &user.User{}
return nil
}

if f.UserInfo.Username != "" {
u, err := user.Lookup(f.UserInfo.Username)
if err != nil {
return errors.Wrap(err, "validation error")
}
f.UserInfo = u
return err
}
return nil
}

//if a group is provided, make sure it exists on the system
func (f *File) validateGroup() error {
if f.GroupInfo == nil {
f.GroupInfo = &user.Group{}
return nil
}

if f.GroupInfo.Name != "" {
g, err := user.LookupGroup(f.GroupInfo.Name)
if err != nil {
return errors.Wrap(err, "validation error")
}
f.GroupInfo = g
}
return nil
}
129 changes: 129 additions & 0 deletions resource/file/validate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright © 2016 Asteris, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package file_test

import (
"fmt"
"os/user"
"runtime"
"testing"

"github.com/asteris-llc/converge/resource/file"
"github.com/stretchr/testify/assert"
)

func TestValidateHelpers(t *testing.T) {
t.Parallel()

t.Run("stateValidators", func(t *testing.T) {
var typeTests = []struct {
F file.File
Err error
}{
{file.File{Destination: "/aster/is", State: "present"}, nil},
{file.File{Destination: "/aster/is", State: "absent"}, nil},
{file.File{Destination: "/aster/is", State: ""}, nil},
{file.File{Destination: "/aster/is", State: "bad"}, fmt.Errorf("state should be one of present, absent, got %q", "bad")},
}
for _, tt := range typeTests {
err := tt.F.Validate()
assert.Equal(t, tt.Err, err)
}
})

t.Run("typeValidators", func(t *testing.T) {
var typeTests = []struct {
F file.File
Err error
}{
{file.File{Destination: "/aster/is", Type: "directory"}, nil},
{file.File{Destination: "/aster/is", Type: "file"}, nil},
{file.File{Destination: "/aster/is", Type: "hardlink", Target: "/converge"}, nil},
{file.File{Destination: "/aster/is", Type: "symlink", Target: "/converge"}, nil},
{file.File{Destination: "/aster/is", Type: "bad"}, fmt.Errorf("type should be one of directory, file, hardlink, symlink, got %q", "bad")},
}
for _, tt := range typeTests {
err := tt.F.Validate()
assert.Equal(t, tt.Err, err)
}
})

// validate link targets
t.Run("targetValidators", func(t *testing.T) {
var targetTests = []struct {
F file.File
Err error
}{
{file.File{Destination: "/aster/is", Type: "directory", Target: "/bad"}, fmt.Errorf("cannot define target on a type of %q: target: %q", "directory", "/bad")},
{file.File{Destination: "/aster/is", Type: "file", Target: "/bad"}, fmt.Errorf("cannot define target on a type of %q: target: %q", "file", "/bad")},
{file.File{Destination: "/aster/is", Type: "hardlink", Target: "/converge"}, nil},
{file.File{Destination: "/aster/is", Type: "symlink", Target: "/converge"}, nil},
{file.File{Destination: "/aster/is", Type: "hardlink"}, fmt.Errorf("must define a target if you are using a %q", "hardlink")},
{file.File{Destination: "/aster/is", Type: "symlink"}, fmt.Errorf("must define a target if you are using a %q", "symlink")},
}
for _, tt := range targetTests {
err := tt.F.Validate()
assert.EqualValues(t, err, tt.Err)
}
})

t.Run("groupValidators", func(t *testing.T) {
validGroup := &user.Group{Name: "root", Gid: "0"}
switch runtime.GOOS {
case "darwin":
validGroup.Name = "wheel"
}
var groupTests = []struct {
F file.File
Err error
}{
{file.File{Destination: "/aster/is", GroupInfo: validGroup}, nil},
{file.File{Destination: "/aster/is", GroupInfo: nil}, nil},
{file.File{Destination: "/aster/is", GroupInfo: &user.Group{Name: "badGroup", Gid: "0"}}, fmt.Errorf("validation error: group: unknown group badGroup")},
}
for _, tt := range groupTests {
err := tt.F.Validate()
switch tt.Err {
case nil:
assert.Nil(t, err)
default:
assert.EqualValues(t, tt.Err.Error(), err.Error())
}
}
})

t.Run("userValidators", func(t *testing.T) {
validUser := &user.User{Username: "root", Uid: "0"}

var userTests = []struct {
F file.File
Err error
}{
{file.File{Destination: "/aster/is", UserInfo: validUser}, nil},
{file.File{Destination: "/aster/is", UserInfo: nil}, nil},
{file.File{Destination: "/aster/is", UserInfo: &user.User{Username: "badUser", Uid: "4774"}}, fmt.Errorf("validation error: user: unknown user %s", "badUser")},
}
for _, tt := range userTests {
err := tt.F.Validate()
switch tt.Err {
case nil:
assert.Nil(t, err)
default:
assert.EqualValues(t, tt.Err.Error(), err.Error())
}
}
})

}

0 comments on commit 10e4536

Please sign in to comment.