Skip to content

Commit 812c1f3

Browse files
authored
Fix edgecases for lineinfile module (#221)
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.
1 parent 856e157 commit 812c1f3

File tree

2 files changed

+64
-9
lines changed

2 files changed

+64
-9
lines changed

utils/lineinfile/lineinfile.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,23 @@ func NewAbsentParams() Params {
4747
}
4848

4949
func (l LineInFile) Present(params Params) error {
50-
if _, err := os.Stat(l.FilePath); err != nil {
50+
createFile := false
51+
if _, err := os.Stat(l.FilePath); os.IsNotExist(err) {
52+
// will be created by atomic.WriteFile
53+
createFile = true
54+
} else if err != nil {
5155
return err
5256
}
5357

54-
content, err := os.ReadFile(l.FilePath)
55-
if err != nil {
56-
return err
57-
}
58+
var lines []string
59+
if !createFile {
60+
content, err := os.ReadFile(l.FilePath)
61+
if err != nil {
62+
return err
63+
}
5864

59-
lines := strings.Split(string(content), "\n")
65+
lines = strings.Split(string(content), "\n")
66+
}
6067

6168
outLines, err := processPresent(lines, params)
6269
if err != nil {
@@ -132,7 +139,7 @@ func processPresent(inLines []string, params Params) ([]string, error) {
132139
afterIndex = idx
133140
continue
134141
}
135-
if (needsAfter && afterIndex >= 0 || needsBefore && beforeIndex >= 0) && foundIndex < 0 && params.Regexp.MatchString(curr) {
142+
if ((!needsAfter && !needsBefore) || needsAfter && afterIndex >= 0 || needsBefore && beforeIndex >= 0) && foundIndex < 0 && params.Regexp.MatchString(curr) {
136143
foundIndex = idx
137144
}
138145
}
@@ -186,7 +193,7 @@ func processAbsent(inLines []string, params Params) ([]string, error) {
186193
afterIndex = idx
187194
continue
188195
}
189-
if (needsAfter && afterIndex >= 0 || needsBefore && beforeIndex >= 0) && foundIndex < 0 && params.Regexp.MatchString(curr) {
196+
if ((!needsAfter && !needsBefore) || needsAfter && afterIndex >= 0 || needsBefore && beforeIndex >= 0) && foundIndex < 0 && params.Regexp.MatchString(curr) {
190197
foundIndex = idx
191198
}
192199
}
@@ -227,7 +234,7 @@ func processFind(regexp string, after string, inLines []string) *string {
227234
lineRegexp, _ := re.Compile(regexp)
228235
afterRegexp, _ := re.Compile(after)
229236

230-
var foundAfter = false
237+
var foundAfter = after == ""
231238

232239
for _, curr := range inLines {
233240
if !foundAfter && afterRegexp.MatchString(curr) {

utils/lineinfile/lineinfile_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ func TestFindMissingNotAfter(t *testing.T) {
6161
}
6262
}
6363

64+
func TestFindEmptyAfter(t *testing.T) {
65+
content := "KEY=value\nKEY2=value2"
66+
lines := strings.Split(content, "\n")
67+
result := processFind(`^KEY=`, "", lines)
68+
if *result != "KEY=value" {
69+
t.Errorf("Expected a result, got nil")
70+
return
71+
}
72+
}
73+
6474
func TestPresentSimple(t *testing.T) {
6575
params := NewPresentParams("NTP=ntp2.example.com")
6676
params.Regexp, _ = regexp.Compile(`^\s*#?\s*(NTP=).*$`)
@@ -130,6 +140,31 @@ func TestPresentAppendedEOF(t *testing.T) {
130140
}
131141
}
132142

143+
func TestPresentEmptyContent(t *testing.T) {
144+
params := NewPresentParams("NTP=ntp.example.com")
145+
params.Regexp, _ = regexp.Compile(`^\s*#?\s*(NTP=).*$`)
146+
params.After = `EOF`
147+
var lines []string
148+
processed, _ := processPresent(lines, params)
149+
result := strings.Join(processed, "\n")
150+
expected := "NTP=ntp.example.com"
151+
if result != expected {
152+
t.Errorf("Expected %s, got %s", expected, result)
153+
}
154+
}
155+
156+
func TestPresentNoBeforeAfterRegex(t *testing.T) {
157+
params := NewPresentParams("NTP=ntp.example.com")
158+
params.Regexp, _ = regexp.Compile(`^\s*#?\s*(NTP=).*$`)
159+
content := "NTP=ntp.example.com"
160+
lines := strings.Split(content, "\n")
161+
processed, _ := processPresent(lines, params)
162+
result := strings.Join(processed, "\n")
163+
if result != content {
164+
t.Errorf("Expected %s, got %s", content, result)
165+
}
166+
}
167+
133168
func TestPresentNoRegexp(t *testing.T) {
134169
params := NewPresentParams("NTP=ntp.example.com")
135170
params.After = `\[Time\]`
@@ -179,3 +214,16 @@ func TestAbsentNoRegexp(t *testing.T) {
179214
t.Errorf("Expected an error, got nil")
180215
}
181216
}
217+
218+
func TestAbsentNoBeforeAfterRegex(t *testing.T) {
219+
params := NewAbsentParams()
220+
params.Regexp, _ = regexp.Compile(`^\s*#?\s*(NTP=).*$`)
221+
content := "NTP=ntp.example.com"
222+
lines := strings.Split(content, "\n")
223+
processed, _ := processAbsent(lines, params)
224+
result := strings.Join(processed, "\n")
225+
expected := ""
226+
if result != expected {
227+
t.Errorf("Expected %s, got %s", expected, result)
228+
}
229+
}

0 commit comments

Comments
 (0)