@@ -1739,6 +1739,9 @@ def block(
1739
1739
will either be prepended to the file (if both ``before`` and ``after``
1740
1740
are ``True``) or appended to the file (if both are ``False``).
1741
1741
1742
+ If the file is created, it is created with the default umask; otherwise the umask is preserved
1743
+ as is the owner.
1744
+
1742
1745
Removal ignores ``content`` and ``line``
1743
1746
1744
1747
Preventing shell expansion works by wrapping the content in '`' before passing to `awk`.
@@ -1797,32 +1800,46 @@ def block(
1797
1800
mark_1 = (marker or MARKER_DEFAULT ).format (mark = begin or MARKER_BEGIN_DEFAULT )
1798
1801
mark_2 = (marker or MARKER_DEFAULT ).format (mark = end or MARKER_END_DEFAULT )
1799
1802
1803
+ current = host .get_fact (Block , path = path , marker = marker , begin = begin , end = end )
1804
+ cmd = None
1805
+
1800
1806
# standard awk doesn't have an "in-place edit" option so we write to a tempfile and
1801
1807
# if edits were successful move to dest i.e. we do: <out_prep> ... do some work ... <real_out>
1802
1808
q_path = QuoteString (path )
1803
- out_prep = StringCommand ('OUT="$(TMPDIR=/tmp mktemp -t pyinfra.XXXXXX)" && ' )
1804
- if backup :
1809
+ mode_get = (
1810
+ ""
1811
+ if current is None
1812
+ else (
1813
+ 'MODE="$(stat -c %a' ,
1814
+ q_path ,
1815
+ "2>/dev/null || stat -f %Lp" ,
1816
+ q_path ,
1817
+ '2>/dev/null)" &&' ,
1818
+ )
1819
+ )
1820
+ out_prep = StringCommand (
1821
+ 'OUT="$(TMPDIR=/tmp mktemp -t pyinfra.XXXXXX)" && ' ,
1822
+ * mode_get ,
1823
+ 'OWNER="$(stat -c "%u:%g"' ,
1824
+ q_path ,
1825
+ '2>/dev/null || stat -f "%u:%g"' ,
1826
+ q_path ,
1827
+ '2>/dev/null || echo $(id -un):$(id -gn))" &&' ,
1828
+ )
1829
+
1830
+ mode_change = "" if current is None else ' && chmod "$MODE"'
1831
+ real_out = StringCommand (
1832
+ ' && mv "$OUT"' , q_path , ' && chown "$OWNER"' , q_path , mode_change , q_path
1833
+ )
1834
+
1835
+ if backup and (current is not None ): # can't back up something that doesn't exist
1805
1836
out_prep = StringCommand (
1806
1837
"cp" ,
1807
1838
q_path ,
1808
1839
QuoteString (f"{ path } .{ get_timestamp ()} " ),
1809
1840
"&&" ,
1810
1841
out_prep ,
1811
1842
)
1812
- real_out = StringCommand (
1813
- "chmod $(stat -c %a" ,
1814
- q_path ,
1815
- "2>/dev/null || stat -f %Lp" ,
1816
- q_path ,
1817
- ") $OUT && " ,
1818
- '(chown $(stat -c "%u:%g"' ,
1819
- q_path ,
1820
- "2>/dev/null || " ,
1821
- 'stat -f "%u:%g"' ,
1822
- q_path ,
1823
- '2>/dev/null ) $OUT) && mv "$OUT"' ,
1824
- q_path ,
1825
- )
1826
1843
1827
1844
current = host .get_fact (Block , path = path , marker = marker , begin = begin , end = end )
1828
1845
# None means file didn't exist, empty list means marker was not found
@@ -1842,25 +1859,36 @@ def block(
1842
1859
if isinstance (content , str ):
1843
1860
# convert string to list of lines
1844
1861
content = content .split ("\n " )
1845
- if try_prevent_shell_expansion and any ("'" in line for line in content ):
1846
- logger .warning ("content contains single quotes, shell expansion prevention may fail" )
1847
1862
1848
1863
the_block = "\n " .join ([mark_1 , * content , mark_2 ])
1864
+ if try_prevent_shell_expansion :
1865
+ the_block = f"'{ the_block } '"
1866
+ if any ("'" in line for line in content ):
1867
+ logger .warning (
1868
+ "content contains single quotes, shell expansion prevention may fail"
1869
+ )
1870
+ else :
1871
+ the_block = f'"{ the_block } "'
1849
1872
1850
1873
if (current is None ) or ((current == []) and (before == after )):
1851
- # a) no file or b) file but no markers and we're adding at start or end. Both use 'cat'
1852
- redirect = ">" if (current is None ) else ">>"
1853
- stdin = "- " if ((current == []) and before ) else ""
1854
- # here = hex(random.randint(0, 2147483647))
1874
+ # a) no file or b) file but no markers and we're adding at start or end.
1875
+ # here = hex(random.randint(0, 2147483647)) # not used as not testable
1855
1876
here = "PYINFRAHERE"
1877
+ original = q_path if current is not None else QuoteString ("/dev/null" )
1856
1878
cmd = StringCommand (
1857
- f"cat { stdin } { redirect } " ,
1858
- q_path ,
1859
- f"<<{ here } " if not try_prevent_shell_expansion else f"<<'{ here } '" ,
1860
- f"\n { the_block } \n { here } " ,
1879
+ out_prep ,
1880
+ "(" ,
1881
+ "awk '{{print}}'" ,
1882
+ original if not before else " - " ,
1883
+ original if before else " - " ,
1884
+ '> "$OUT"' ,
1885
+ f"<<{ here } \n { the_block [1 :- 1 ]} \n { here } \n " ,
1886
+ ")" ,
1887
+ real_out ,
1861
1888
)
1862
1889
elif current == []: # markers not found and have a pattern to match (not start or end)
1863
- assert isinstance (line , str )
1890
+ if not isinstance (line , str ):
1891
+ raise OperationTypeError ("'line' must be a regex or a string" )
1864
1892
regex = adjust_regex (line , escape_regex_characters )
1865
1893
print_before = "{ print }" if before else ""
1866
1894
print_after = "{ print }" if after else ""
@@ -1873,13 +1901,13 @@ def block(
1873
1901
out_prep ,
1874
1902
prog ,
1875
1903
q_path ,
1876
- f'" { the_block } "' if not try_prevent_shell_expansion else f"' { the_block } '" ,
1877
- "> $OUT &&" ,
1904
+ the_block ,
1905
+ '> " $OUT"' ,
1878
1906
real_out ,
1879
1907
)
1880
1908
else :
1881
1909
if (len (current ) != len (content )) or (
1882
- not all (lines [0 ] == lines [1 ] for lines in zip (content , current ))
1910
+ not all (lines [0 ] == lines [1 ] for lines in zip (content , current , strict = True ))
1883
1911
): # marked_block found but text is different
1884
1912
prog = (
1885
1913
'awk \' BEGIN {{f=1; x=ARGV[2]; ARGV[2]=""}}'
@@ -1894,7 +1922,7 @@ def block(
1894
1922
if not try_prevent_shell_expansion
1895
1923
else "'" + "\n " .join (content ) + "'"
1896
1924
),
1897
- "> $OUT &&" ,
1925
+ '> " $OUT"' ,
1898
1926
real_out ,
1899
1927
)
1900
1928
else :
@@ -1911,4 +1939,4 @@ def block(
1911
1939
host .noop ("no remove required: markers not found" )
1912
1940
else :
1913
1941
cmd = StringCommand (f"awk '/{ mark_1 } /,/{ mark_2 } / {{next}} 1'" )
1914
- yield StringCommand (out_prep , cmd , q_path , "> $OUT && " , real_out )
1942
+ yield StringCommand (out_prep , cmd , q_path , "> $OUT" , real_out )
0 commit comments