From 10e4536ec2643ceda45f473673842b08f50c1b9f Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Sun, 2 Oct 2016 19:24:40 -0500 Subject: [PATCH] break out validation code --- resource/file/validate.go | 143 +++++++++++++++++++++++++++++++++ resource/file/validate_test.go | 129 +++++++++++++++++++++++++++++ 2 files changed, 272 insertions(+) create mode 100644 resource/file/validate.go create mode 100644 resource/file/validate_test.go diff --git a/resource/file/validate.go b/resource/file/validate.go new file mode 100644 index 000000000..c6da1b72f --- /dev/null +++ b/resource/file/validate.go @@ -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 +} diff --git a/resource/file/validate_test.go b/resource/file/validate_test.go new file mode 100644 index 000000000..39d1e3762 --- /dev/null +++ b/resource/file/validate_test.go @@ -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()) + } + } + }) + +}