Skip to content

Commit

Permalink
Fix edgecases for lineinfile module (home-assistant#221)
Browse files Browse the repository at this point in the history
Edgecases where the file contains only a single line and no before/after
regexp was given were not handled correctly. Also the Find method should
accept empty string for searching from the beginning of the file.

Moreover, it's useful if the file can be created by Present (it's also
what Ansible does), so handle this case too.
  • Loading branch information
sairon authored Feb 28, 2025
1 parent 856e157 commit 812c1f3
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 9 deletions.
25 changes: 16 additions & 9 deletions utils/lineinfile/lineinfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,23 @@ func NewAbsentParams() Params {
}

func (l LineInFile) Present(params Params) error {
if _, err := os.Stat(l.FilePath); err != nil {
createFile := false
if _, err := os.Stat(l.FilePath); os.IsNotExist(err) {
// will be created by atomic.WriteFile
createFile = true
} else if err != nil {
return err
}

content, err := os.ReadFile(l.FilePath)
if err != nil {
return err
}
var lines []string
if !createFile {
content, err := os.ReadFile(l.FilePath)
if err != nil {
return err
}

lines := strings.Split(string(content), "\n")
lines = strings.Split(string(content), "\n")
}

outLines, err := processPresent(lines, params)
if err != nil {
Expand Down Expand Up @@ -132,7 +139,7 @@ func processPresent(inLines []string, params Params) ([]string, error) {
afterIndex = idx
continue
}
if (needsAfter && afterIndex >= 0 || needsBefore && beforeIndex >= 0) && foundIndex < 0 && params.Regexp.MatchString(curr) {
if ((!needsAfter && !needsBefore) || needsAfter && afterIndex >= 0 || needsBefore && beforeIndex >= 0) && foundIndex < 0 && params.Regexp.MatchString(curr) {
foundIndex = idx
}
}
Expand Down Expand Up @@ -186,7 +193,7 @@ func processAbsent(inLines []string, params Params) ([]string, error) {
afterIndex = idx
continue
}
if (needsAfter && afterIndex >= 0 || needsBefore && beforeIndex >= 0) && foundIndex < 0 && params.Regexp.MatchString(curr) {
if ((!needsAfter && !needsBefore) || needsAfter && afterIndex >= 0 || needsBefore && beforeIndex >= 0) && foundIndex < 0 && params.Regexp.MatchString(curr) {
foundIndex = idx
}
}
Expand Down Expand Up @@ -227,7 +234,7 @@ func processFind(regexp string, after string, inLines []string) *string {
lineRegexp, _ := re.Compile(regexp)
afterRegexp, _ := re.Compile(after)

var foundAfter = false
var foundAfter = after == ""

for _, curr := range inLines {
if !foundAfter && afterRegexp.MatchString(curr) {
Expand Down
48 changes: 48 additions & 0 deletions utils/lineinfile/lineinfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ func TestFindMissingNotAfter(t *testing.T) {
}
}

func TestFindEmptyAfter(t *testing.T) {
content := "KEY=value\nKEY2=value2"
lines := strings.Split(content, "\n")
result := processFind(`^KEY=`, "", lines)
if *result != "KEY=value" {
t.Errorf("Expected a result, got nil")
return
}
}

func TestPresentSimple(t *testing.T) {
params := NewPresentParams("NTP=ntp2.example.com")
params.Regexp, _ = regexp.Compile(`^\s*#?\s*(NTP=).*$`)
Expand Down Expand Up @@ -130,6 +140,31 @@ func TestPresentAppendedEOF(t *testing.T) {
}
}

func TestPresentEmptyContent(t *testing.T) {
params := NewPresentParams("NTP=ntp.example.com")
params.Regexp, _ = regexp.Compile(`^\s*#?\s*(NTP=).*$`)
params.After = `EOF`
var lines []string
processed, _ := processPresent(lines, params)
result := strings.Join(processed, "\n")
expected := "NTP=ntp.example.com"
if result != expected {
t.Errorf("Expected %s, got %s", expected, result)
}
}

func TestPresentNoBeforeAfterRegex(t *testing.T) {
params := NewPresentParams("NTP=ntp.example.com")
params.Regexp, _ = regexp.Compile(`^\s*#?\s*(NTP=).*$`)
content := "NTP=ntp.example.com"
lines := strings.Split(content, "\n")
processed, _ := processPresent(lines, params)
result := strings.Join(processed, "\n")
if result != content {
t.Errorf("Expected %s, got %s", content, result)
}
}

func TestPresentNoRegexp(t *testing.T) {
params := NewPresentParams("NTP=ntp.example.com")
params.After = `\[Time\]`
Expand Down Expand Up @@ -179,3 +214,16 @@ func TestAbsentNoRegexp(t *testing.T) {
t.Errorf("Expected an error, got nil")
}
}

func TestAbsentNoBeforeAfterRegex(t *testing.T) {
params := NewAbsentParams()
params.Regexp, _ = regexp.Compile(`^\s*#?\s*(NTP=).*$`)
content := "NTP=ntp.example.com"
lines := strings.Split(content, "\n")
processed, _ := processAbsent(lines, params)
result := strings.Join(processed, "\n")
expected := ""
if result != expected {
t.Errorf("Expected %s, got %s", expected, result)
}
}

0 comments on commit 812c1f3

Please sign in to comment.