Skip to content

Commit

Permalink
Create destination directory if missing
Browse files Browse the repository at this point in the history
Fixes: #14
  • Loading branch information
ema committed Feb 25, 2023
1 parent 1142b10 commit f0c4546
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 4 deletions.
49 changes: 45 additions & 4 deletions file.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@ import (
// PetsFile is the central data structure of the system: it is the in-memory
// representation of a configuration file (eg: sshd_config)
type PetsFile struct {
// Absolute path to the configuration file
Source string
Pkgs []PetsPackage
Dest string
User *user.User
Group *user.Group
// Full destination path where the file has to be installed
Dest string
// Directory where the file has to be installed. This is only set in
// case we have to create the destination directory
Directory string
User *user.User
Group *user.Group
// use string instead of os.FileMode to avoid converting back and forth
Mode string
Pre *exec.Cmd
Expand Down Expand Up @@ -116,6 +121,42 @@ func (pf *PetsFile) NeedsLink() PetsCause {
return NONE
}

// NeedsDir returns PetsCause DIR if there is no directory at Directory,
// meaning that it has to be created. Most of this is suspiciously similar to
// NeedsLink above.
func (pf *PetsFile) NeedsDir() PetsCause {
if pf.Directory == "" {
return NONE
}

fi, err := os.Lstat(pf.Directory)

if os.IsNotExist(err) {
// Directory does not exist yet. Happy path, we are gonna create it!
return DIR
}

if err != nil {
// There was an error calling lstat, putting all my money on
// permission denied.
log.Printf("[ERROR] cannot lstat Directory %s: %v\n", pf.Directory, err)
return NONE
}

// We are here because Directory already exists and lstat succeeded. At this
// point there are two options:
// (1) Dest is a directory \o/
// (2) Dest is a file, a symlink, or something else (a squirrel?) /o\
//
// In any case there is no action to take, but let's come up with a valid
// excuse for not doing anything.

if !fi.IsDir() {
log.Printf("[ERROR] %s already exists and it is not a directory\n", pf.Directory)
}
return NONE
}

func (pf *PetsFile) IsValid(pathErrorOK bool) bool {
// Check if the specified package(s) exists
for _, pkg := range pf.Pkgs {
Expand All @@ -133,8 +174,8 @@ func (pf *PetsFile) IsValid(pathErrorOK bool) bool {
}

func (pf *PetsFile) AddDest(dest string) {
// TODO: create dest if missing
pf.Dest = dest
pf.Directory = filepath.Dir(dest)
}

func (pf *PetsFile) AddLink(dest string) {
Expand Down
23 changes: 23 additions & 0 deletions file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,26 @@ func TestNeedsLinkDestIsSymlink(t *testing.T) {
f.AddLink("/etc/mtab")
assertEquals(t, int(f.NeedsLink()), int(NONE))
}

func TestNeedsDirNoDirectory(t *testing.T) {
f := NewPetsFile()
assertEquals(t, int(f.NeedsDir()), int(NONE))
}

func TestNeedsDirHappyPathDIR(t *testing.T) {
f := NewPetsFile()
f.Directory = "/etc/does/not/exist"
assertEquals(t, int(f.NeedsDir()), int(DIR))
}

func TestNeedsDirHappyPathNONE(t *testing.T) {
f := NewPetsFile()
f.Directory = "/etc"
assertEquals(t, int(f.NeedsDir()), int(NONE))
}

func TestNeedsDirDestIsFile(t *testing.T) {
f := NewPetsFile()
f.Directory = "/etc/passwd"
assertEquals(t, int(f.NeedsDir()), int(NONE))
}
28 changes: 28 additions & 0 deletions planner.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const (
CREATE // configuration file is missing and needs to be created
UPDATE // configuration file differs and needs to be updated
LINK // symbolic link needs to be created
DIR // directory needs to be created
OWNER // needs chown()
MODE // needs chmod()
POST // post-update command
Expand All @@ -31,6 +32,7 @@ func (pc PetsCause) String() string {
CREATE: "FILE_CREATE",
UPDATE: "FILE_UPDATE",
LINK: "LINK_CREATE",
DIR: "DIR_CREATE",
OWNER: "OWNER",
MODE: "CHMOD",
POST: "POST_UPDATE",
Expand Down Expand Up @@ -137,6 +139,26 @@ func LinkToCreate(trigger *PetsFile) *PetsAction {
}
}

// DirToCreate figures out if the given trigger represents a directory that
// needs to be created, and returns the corresponding PetsAction.
func DirToCreate(trigger *PetsFile) *PetsAction {
if trigger.Directory == "" {
return nil
}

cause := trigger.NeedsDir()

if cause == NONE {
return nil
} else {
return &PetsAction{
Cause: cause,
Command: NewCmd([]string{"/bin/mkdir", "-p", trigger.Directory}),
Trigger: trigger,
}
}
}

// Chown returns a chown PetsAction or nil if none is needed.
func Chown(trigger *PetsFile) *PetsAction {
// Build arg (eg: 'root:staff', 'root', ':staff')
Expand Down Expand Up @@ -273,6 +295,12 @@ func NewPetsActions(triggers []*PetsFile) []*PetsAction {
actionFired = true
}

// Any directory to create
if dirAction := DirToCreate(trigger); dirAction != nil {
actions = append(actions, dirAction)
actionFired = true
}

// Any owner changes needed
if chown := Chown(trigger); chown != nil {
actions = append(actions, chown)
Expand Down
26 changes: 26 additions & 0 deletions planner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,29 @@ func TestLn(t *testing.T) {
assertEquals(t, pa.Cause.String(), "LINK_CREATE")
assertEquals(t, pa.Command.String(), "/bin/ln -s sample_pet/vimrc /tmp/vimrc")
}

func TestMkdir(t *testing.T) {
pf := NewPetsFile()

pa := DirToCreate(pf)
if pa != nil {
t.Errorf("Expecting nil, got %v instead", pa)
}

pf.Directory = "/etc"

pa = DirToCreate(pf)
if pa != nil {
t.Errorf("Expecting nil, got %v instead", pa)
}

pf.Directory = "/etc/polpette/al/sugo"

pa = DirToCreate(pf)
if pa == nil {
t.Errorf("Expecting some action, got nil instead")
}

assertEquals(t, pa.Cause.String(), "DIR_CREATE")
assertEquals(t, pa.Command.String(), "/bin/mkdir -p /etc/polpette/al/sugo")
}

0 comments on commit f0c4546

Please sign in to comment.