1616package postgres
1717
1818import (
19+ "bytes"
20+ "errors"
21+ "fmt"
1922 "os"
2023 "os/exec"
2124 "path/filepath"
@@ -26,6 +29,7 @@ import (
2629 corev1 "k8s.io/api/core/v1"
2730 "sigs.k8s.io/yaml"
2831
32+ "github.com/crunchydata/postgres-operator/internal/testing/cmp"
2933 "github.com/crunchydata/postgres-operator/internal/testing/require"
3034 "github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
3135)
@@ -57,6 +61,151 @@ func TestWALDirectory(t *testing.T) {
5761 assert .Equal (t , WALDirectory (cluster , instance ), "/pgwal/pg13_wal" )
5862}
5963
64+ func TestBashHalt (t * testing.T ) {
65+ t .Run ("NoPipeline" , func (t * testing.T ) {
66+ cmd := exec .Command ("bash" )
67+ cmd .Args = append (cmd .Args , "-c" , "--" , bashHalt + `; halt ab cd e` )
68+
69+ var exit * exec.ExitError
70+ stdout , err := cmd .Output ()
71+ assert .Assert (t , errors .As (err , & exit ))
72+ assert .Equal (t , string (stdout ), "" , "expected no stdout" )
73+ assert .Equal (t , string (exit .Stderr ), "ab cd e\n " )
74+ assert .Equal (t , exit .ExitCode (), 1 )
75+ })
76+
77+ t .Run ("PipelineZeroStatus" , func (t * testing.T ) {
78+ cmd := exec .Command ("bash" )
79+ cmd .Args = append (cmd .Args , "-c" , "--" , bashHalt + `; true && halt message` )
80+
81+ var exit * exec.ExitError
82+ stdout , err := cmd .Output ()
83+ assert .Assert (t , errors .As (err , & exit ))
84+ assert .Equal (t , string (stdout ), "" , "expected no stdout" )
85+ assert .Equal (t , string (exit .Stderr ), "message\n " )
86+ assert .Equal (t , exit .ExitCode (), 1 )
87+ })
88+
89+ t .Run ("PipelineNonZeroStatus" , func (t * testing.T ) {
90+ cmd := exec .Command ("bash" )
91+ cmd .Args = append (cmd .Args , "-c" , "--" , bashHalt + `; (exit 99) || halt $'multi\nline'` )
92+
93+ var exit * exec.ExitError
94+ stdout , err := cmd .Output ()
95+ assert .Assert (t , errors .As (err , & exit ))
96+ assert .Equal (t , string (stdout ), "" , "expected no stdout" )
97+ assert .Equal (t , string (exit .Stderr ), "multi\n line\n " )
98+ assert .Equal (t , exit .ExitCode (), 99 )
99+ })
100+
101+ t .Run ("Subshell" , func (t * testing.T ) {
102+ cmd := exec .Command ("bash" )
103+ cmd .Args = append (cmd .Args , "-c" , "--" , bashHalt + `; (halt 'err') || echo 'after'` )
104+
105+ stderr := new (bytes.Buffer )
106+ cmd .Stderr = stderr
107+
108+ stdout , err := cmd .Output ()
109+ assert .NilError (t , err )
110+ assert .Equal (t , string (stdout ), "after\n " )
111+ assert .Equal (t , stderr .String (), "err\n " )
112+ assert .Equal (t , cmd .ProcessState .ExitCode (), 0 )
113+ })
114+ }
115+
116+ func TestBashPermissions (t * testing.T ) {
117+ // macOS `stat` takes different arguments than BusyBox and GNU coreutils.
118+ if output , err := exec .Command ("stat" , "--help" ).CombinedOutput (); err != nil {
119+ t .Skip (`requires "stat" executable` )
120+ } else if ! strings .Contains (string (output ), "%A" ) {
121+ t .Skip (`requires "stat" with access format sequence` )
122+ }
123+
124+ dir := t .TempDir ()
125+ assert .NilError (t , os .Mkdir (filepath .Join (dir , "sub" ), 0o751 ))
126+ assert .NilError (t , os .Chmod (filepath .Join (dir , "sub" ), 0o751 ))
127+ assert .NilError (t , os .WriteFile (filepath .Join (dir , "sub" , "fn" ), nil , 0o624 )) // #nosec G306 OK permissions for a temp dir in a test
128+ assert .NilError (t , os .Chmod (filepath .Join (dir , "sub" , "fn" ), 0o624 ))
129+
130+ cmd := exec .Command ("bash" )
131+ cmd .Args = append (cmd .Args , "-c" , "--" ,
132+ bashPermissions + `; permissions "$@"` , "-" ,
133+ filepath .Join (dir , "sub" , "fn" ))
134+
135+ stdout , err := cmd .Output ()
136+ assert .NilError (t , err )
137+ assert .Assert (t , cmp .Regexp (`` +
138+ `drwxr-x--x\s+\d+\s+\d+\s+[^ ]+/sub\n` +
139+ `-rw--w-r--\s+\d+\s+\d+\s+[^ ]+/sub/fn\n` +
140+ `$` , string (stdout )))
141+ }
142+
143+ func TestBashRecreateDirectory (t * testing.T ) {
144+ // macOS `stat` takes different arguments than BusyBox and GNU coreutils.
145+ if output , err := exec .Command ("stat" , "--help" ).CombinedOutput (); err != nil {
146+ t .Skip (`requires "stat" executable` )
147+ } else if ! strings .Contains (string (output ), "%a" ) {
148+ t .Skip (`requires "stat" with access format sequence` )
149+ }
150+
151+ dir := t .TempDir ()
152+ assert .NilError (t , os .Mkdir (filepath .Join (dir , "d" ), 0o755 ))
153+ assert .NilError (t , os .WriteFile (filepath .Join (dir , "d" , ".hidden" ), nil , 0o644 )) // #nosec G306 OK permissions for a temp dir in a test
154+ assert .NilError (t , os .WriteFile (filepath .Join (dir , "d" , "file" ), nil , 0o644 )) // #nosec G306 OK permissions for a temp dir in a test
155+
156+ stat := func (args ... string ) string {
157+ cmd := exec .Command ("stat" , "-c" , "%i %#a %N" )
158+ cmd .Args = append (cmd .Args , args ... )
159+ out , err := cmd .CombinedOutput ()
160+
161+ t .Helper ()
162+ assert .NilError (t , err , string (out ))
163+ return string (out )
164+ }
165+
166+ var before , after struct { d , f , dInode , dPerms string }
167+
168+ before .d = stat (filepath .Join (dir , "d" ))
169+ before .f = stat (
170+ filepath .Join (dir , "d" , ".hidden" ),
171+ filepath .Join (dir , "d" , "file" ),
172+ )
173+
174+ cmd := exec .Command ("bash" )
175+ cmd .Args = append (cmd .Args , "-ceu" , "--" ,
176+ bashRecreateDirectory + ` recreate "$@"` , "-" ,
177+ filepath .Join (dir , "d" ), "0740" )
178+
179+ output , err := cmd .CombinedOutput ()
180+ assert .NilError (t , err , string (output ))
181+ assert .Assert (t , cmp .Regexp (`^` +
182+ `[+] chmod 0740 [^ ]+/tmp.[^ /]+\n` +
183+ `[+] mv [^ ]+/d/.hidden [^ ]+/d/file [^ ]+/tmp.[^ /]+\n` +
184+ `[+] rmdir [^ ]+/d\n` +
185+ `[+] mv [^ ]+/tmp.[^ /]+ [^ ]+/d\n` +
186+ `$` , string (output )))
187+
188+ after .d = stat (filepath .Join (dir , "d" ))
189+ after .f = stat (
190+ filepath .Join (dir , "d" , ".hidden" ),
191+ filepath .Join (dir , "d" , "file" ),
192+ )
193+
194+ _ , err = fmt .Sscan (before .d , & before .dInode , & before .dPerms )
195+ assert .NilError (t , err )
196+ _ , err = fmt .Sscan (after .d , & after .dInode , & after .dPerms )
197+ assert .NilError (t , err )
198+
199+ // New directory is new.
200+ assert .Assert (t , after .dInode != before .dInode )
201+
202+ // New directory has the requested permissions.
203+ assert .Equal (t , after .dPerms , "0740" )
204+
205+ // Files are in the new directory and unchanged.
206+ assert .DeepEqual (t , after .f , before .f )
207+ }
208+
60209func TestBashSafeLink (t * testing.T ) {
61210 // macOS lacks `realpath` which is part of GNU coreutils.
62211 if _ , err := exec .LookPath ("realpath" ); err != nil {
@@ -310,13 +459,6 @@ func TestBashSafeLink(t *testing.T) {
310459 })
311460}
312461
313- func TestBashSafeLinkPrettyYAML (t * testing.T ) {
314- b , err := yaml .Marshal (bashSafeLink )
315- assert .NilError (t , err )
316- assert .Assert (t , strings .HasPrefix (string (b ), `|` ),
317- "expected literal block scalar, got:\n %s" , b )
318- }
319-
320462func TestStartupCommand (t * testing.T ) {
321463 shellcheck := require .ShellCheck (t )
322464
@@ -329,14 +471,22 @@ func TestStartupCommand(t *testing.T) {
329471 // Expect a bash command with an inline script.
330472 assert .DeepEqual (t , command [:3 ], []string {"bash" , "-ceu" , "--" })
331473 assert .Assert (t , len (command ) > 3 )
474+ script := command [3 ]
332475
333476 // Write out that inline script.
334477 dir := t .TempDir ()
335478 file := filepath .Join (dir , "script.bash" )
336- assert .NilError (t , os .WriteFile (file , []byte (command [ 3 ] ), 0o600 ))
479+ assert .NilError (t , os .WriteFile (file , []byte (script ), 0o600 ))
337480
338481 // Expect shellcheck to be happy.
339482 cmd := exec .Command (shellcheck , "--enable=all" , file )
340483 output , err := cmd .CombinedOutput ()
341484 assert .NilError (t , err , "%q\n %s" , cmd .Args , output )
485+
486+ t .Run ("PrettyYAML" , func (t * testing.T ) {
487+ b , err := yaml .Marshal (script )
488+ assert .NilError (t , err )
489+ assert .Assert (t , strings .HasPrefix (string (b ), `|` ),
490+ "expected literal block scalar, got:\n %s" , b )
491+ })
342492}
0 commit comments