Skip to content

Commit

Permalink
Add support for file actions during a config apply (#464)
Browse files Browse the repository at this point in the history
  • Loading branch information
dhurley authored Sep 11, 2023
1 parent 04748e8 commit a668cb1
Show file tree
Hide file tree
Showing 13 changed files with 936 additions and 201 deletions.
98 changes: 60 additions & 38 deletions src/core/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import (

//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate
//counterfeiter:generate -o fake_environment_test.go . Environment
//go:generate sh -c "grep -v agent/product/nginx-agent/v2/core fake_environment_test.go | sed -e s\\/core\\\\.\\/\\/g > fake_environment_fixed.go"
//go:generate sh -c "grep -v github.com/nginx/agent/v2/src/core fake_environment_test.go | sed -e s\\/core\\\\.\\/\\/g > fake_environment_fixed.go"
//go:generate mv fake_environment_fixed.go fake_environment_test.go
type Environment interface {
NewHostInfo(agentVersion string, tags *[]string, configDirs string, clearCache bool) *proto.HostInfo
Expand All @@ -48,6 +48,8 @@ type Environment interface {
GetSystemUUID() (hostId string)
ReadDirectory(dir string, ext string) ([]string, error)
WriteFiles(backup ConfigApplyMarker, files []*proto.File, prefix string, allowedDirs map[string]struct{}) error
WriteFile(backup ConfigApplyMarker, file *proto.File, confPath string) error
DeleteFile(backup ConfigApplyMarker, fileName string) error
Processes() (result []*Process)
FileStat(path string) (os.FileInfo, error)
DiskDevices() ([]string, error)
Expand Down Expand Up @@ -236,13 +238,69 @@ func (env *EnvironmentType) WriteFiles(backup ConfigApplyMarker, files []*proto.
}

for _, file := range files {
if err = writeFile(backup, file, confPath); err != nil {
if err = env.WriteFile(backup, file, confPath); err != nil {
return err
}
}
return nil
}

// WriteFile writes the provided file content to disk. If the file.GetName() returns an absolute path, it'll be written
// to the path. Otherwise, it'll be written to the path relative to the provided confPath.
func (env *EnvironmentType) WriteFile(backup ConfigApplyMarker, file *proto.File, confPath string) error {
fileFullPath := file.GetName()
if !filepath.IsAbs(fileFullPath) {
fileFullPath = filepath.Join(confPath, fileFullPath)
}

if err := backup.MarkAndSave(fileFullPath); err != nil {
return err
}
permissions := files.GetFileMode(file.GetPermissions())

directory := filepath.Dir(fileFullPath)
_, err := os.Stat(directory)
if os.IsNotExist(err) {
log.Debugf("Creating directory %s with permissions 755", directory)
err = os.MkdirAll(directory, 0o755)
if err != nil {
return err
}
}

if err := os.WriteFile(fileFullPath, file.GetContents(), permissions); err != nil {
// If the file didn't exist originally and failed to be created
// Then remove that file from the backup so that the rollback doesn't try to delete the file
if _, err := os.Stat(fileFullPath); !errors.Is(err, os.ErrNotExist) {
backup.RemoveFromNotExists(fileFullPath)
}
return err
}

log.Debugf("Wrote file %s", fileFullPath)
return nil
}

func (env *EnvironmentType) DeleteFile(backup ConfigApplyMarker, fileName string) error {
if found, foundErr := FileExists(fileName); !found {
if foundErr == nil {
log.Debugf("skip delete for non-existing file: %s", fileName)
return nil
}
// possible perm deny, depends on platform
log.Warnf("file exists returned for %s: %s", fileName, foundErr)
return foundErr
}
if err := backup.MarkAndSave(fileName); err != nil {
return err
}
if err := os.Remove(fileName); err != nil {
return err
}

return nil
}

func (env *EnvironmentType) IsContainer() bool {
const (
dockerEnv = "/.dockerenv"
Expand Down Expand Up @@ -451,42 +509,6 @@ func allowedFile(path string, allowedDirs map[string]struct{}) bool {
return false
}

// writeFile writes the provided file content to disk. If the file.GetName() returns an absolute path, it'll be written
// to the path. Otherwise, it'll be written to the path relative to the provided confPath.
func writeFile(backup ConfigApplyMarker, file *proto.File, confPath string) error {
fileFullPath := file.GetName()
if !filepath.IsAbs(fileFullPath) {
fileFullPath = filepath.Join(confPath, fileFullPath)
}

if err := backup.MarkAndSave(fileFullPath); err != nil {
return err
}
permissions := files.GetFileMode(file.GetPermissions())

directory := filepath.Dir(fileFullPath)
_, err := os.Stat(directory)
if os.IsNotExist(err) {
log.Debugf("Creating directory %s with permissions 755", directory)
err = os.MkdirAll(directory, 0o755)
if err != nil {
return err
}
}

if err := os.WriteFile(fileFullPath, file.GetContents(), permissions); err != nil {
// If the file didn't exist originally and failed to be created
// Then remove that file from the backup so that the rollback doesn't try to delete the file
if _, err := os.Stat(fileFullPath); !errors.Is(err, os.ErrNotExist) {
backup.RemoveFromNotExists(fileFullPath)
}
return err
}

log.Debugf("Wrote file %s", fileFullPath)
return nil
}

func (env *EnvironmentType) FileStat(path string) (os.FileInfo, error) {
// TODO: check if allowed list
return os.Stat(path)
Expand Down
31 changes: 30 additions & 1 deletion src/core/environment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -700,14 +700,15 @@ func TestWriteFilesNotAllowed(t *testing.T) {
}

func TestWriteFile(t *testing.T) {
env := &EnvironmentType{}
file := &proto.File{
Name: "/tmp/sub-1/sub-2/write.conf",
Contents: []byte("contents"),
Permissions: "0777",
}
backup, err := sdk.NewConfigApplyWithIgnoreDirectives("", nil, []string{})
assert.NoError(t, err)
assert.NoError(t, writeFile(backup, file, "/tmp"))
assert.NoError(t, env.WriteFile(backup, file, "/tmp"))
assert.FileExists(t, file.GetName())

contents, err := os.ReadFile(file.GetName())
Expand All @@ -718,6 +719,34 @@ func TestWriteFile(t *testing.T) {
assert.NoFileExists(t, file.GetName())
}

func TestDeleteFile(t *testing.T) {
env := &EnvironmentType{}
tempDir := t.TempDir()
fileName := tempDir + "/test.txt"
file := &proto.File{
Name: fileName,
Contents: []byte("contents"),
Permissions: "0777",
}
backup, err := sdk.NewConfigApplyWithIgnoreDirectives("", nil, []string{})
assert.NoError(t, err)
assert.NoError(t, env.WriteFile(backup, file, "/tmp"))
assert.FileExists(t, file.GetName())

contents, err := os.ReadFile(file.GetName())
assert.NoError(t, err)
assert.Equal(t, file.GetContents(), contents)

assert.NoError(t, env.DeleteFile(backup, fileName))
assert.NoFileExists(t, file.GetName())

// verify that file being deleted is backed up in case we need to rollback
_, ok := backup.GetNotExists()[fileName]
assert.True(t, ok)
_, ok = backup.GetExisting()[fileName]
assert.False(t, ok)
}

func TestParseOsReleaseFile(t *testing.T) {
tests := []struct {
name string
Expand Down
160 changes: 154 additions & 6 deletions src/core/fake_environment_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a668cb1

Please sign in to comment.