Skip to content

Commit f13c3c7

Browse files
authored
Merge branch 'master' into dependabot/go_modules/github.com/golang/glog-1.2.4
2 parents 021d4fe + 444c5a0 commit f13c3c7

File tree

8 files changed

+332
-20
lines changed

8 files changed

+332
-20
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
apiVersion: cr.kanister.io/v1alpha1
2+
kind: Blueprint
3+
metadata:
4+
name: postgres-bp
5+
actions:
6+
backup:
7+
kind: StatefulSet
8+
outputArtifacts:
9+
cloudObject:
10+
keyValue:
11+
backupArtifactLocation: "{{ .Phases.copyFiles.Output.backupArtifactLocation }}"
12+
backupID: "{{ .Phases.copyFiles.Output.backupID }}"
13+
backupTag: "{{ .Phases.copyFiles.Output.backupTag }}"
14+
pvc: '{{- range $key, $_ := index .StatefulSet.PersistentVolumeClaims (index .StatefulSet.Pods 0) -}}{{$key}}{{break}}{{end}}'
15+
16+
deferPhase:
17+
func: KubeOps
18+
args:
19+
operation: delete
20+
objectReference:
21+
apiVersion: v1
22+
resource: "pods"
23+
name: "{{ .Phases.createBackupPod.Output.name }}"
24+
namespace: '{{ .StatefulSet.Namespace }}'
25+
26+
phases:
27+
- name: createBackupPod
28+
func: KubeOps
29+
objects:
30+
pgSecret:
31+
kind: Secret
32+
name: '{{ index .Object.metadata.labels "app.kubernetes.io/instance" }}-postgresql'
33+
namespace: '{{ .StatefulSet.Namespace }}'
34+
args:
35+
operation: create
36+
namespace: '{{ .StatefulSet.Namespace }}'
37+
spec: |-
38+
apiVersion: v1
39+
kind: Pod
40+
metadata:
41+
generateName: postgres-backup-session
42+
spec:
43+
restartPolicy: Never
44+
containers:
45+
- name: container
46+
image: bitnami/postgresql:16
47+
command:
48+
- bash
49+
- -o
50+
- errexit
51+
- -o
52+
- pipefail
53+
- -c
54+
- |
55+
export PGHOST='{{ index .Object.metadata.labels "app.kubernetes.io/instance" }}-postgresql.{{ .StatefulSet.Namespace }}.svc.cluster.local'
56+
export PGUSER='postgres'
57+
export PGPASSWORD='{{ index .Phases.createBackupPod.Secrets.pgSecret.Data "postgres-password" | toString }}'
58+
## Create file descriptor to send commands to psql
59+
mkfifo /tmp/pg_in
60+
## Create "holder" process to keep pg_in open
61+
while sleep 1; do :; done >/tmp/pg_in &
62+
## Save "holder" PID to a file to kill it later
63+
echo $! > /tmp/holder_pid
64+
## Run psql session reading from pg_in and writing ot pg_out
65+
## Using tee here to keep the pod logs (might need to replace with just `> /tmp/pg_out`)
66+
## TODO: should we track stderr here?
67+
cat /tmp/pg_in | psql -U ${PGUSER} | tee /tmp/pg_out
68+
69+
- func: WaitV2
70+
name: waitForPodReady
71+
args:
72+
timeout: 5m
73+
conditions:
74+
anyOf:
75+
- condition: '{{ $available := false }}{{ range $condition := $.status.conditions }}{{ if and (eq .type "ContainersReady") (eq .status "True") }}{{ $available = true }}{{ end }}{{ end }}{{ $available }}'
76+
objectReference:
77+
apiVersion: "v1"
78+
name: "{{ .Phases.createBackupPod.Output.name }}"
79+
namespace: '{{ .StatefulSet.Namespace }}'
80+
resource: "pods"
81+
82+
- name: startBackup
83+
func: KubeExec
84+
args:
85+
namespace: '{{ .StatefulSet.Namespace }}'
86+
pod: "{{ .Phases.createBackupPod.Output.name }}"
87+
command:
88+
- bash
89+
- -o
90+
- errexit
91+
- -o
92+
- pipefail
93+
- -c
94+
- |
95+
## Send pg_backup_start command to psql session
96+
echo "SELECT pg_backup_start(label => 'kanister_backup', fast => false);" > /tmp/pg_in
97+
## Make sure operation completed
98+
## TODO: maybe there's a better way to fail/log here?
99+
grep -q pg_backup_start <(tail -f /tmp/pg_out)
100+
101+
- name: copyFiles
102+
func: CopyVolumeData
103+
args:
104+
namespace: '{{ .StatefulSet.Namespace }}'
105+
## TODO: maybe there's a better way of doing that in go templates
106+
volume: '{{- range $key, $_ := index .StatefulSet.PersistentVolumeClaims (index .StatefulSet.Pods 0) -}}{{$key}}{{break}}{{end}}'
107+
# volume: '{{ index .StatefulSet.PersistentVolumeClaims 0 }}'
108+
dataArtifactPrefix: s3-bucket/path/artifactPrefix
109+
110+
- name: stopBackup
111+
func: KubeExec
112+
args:
113+
namespace: '{{ .StatefulSet.Namespace }}'
114+
pod: "{{ .Phases.createBackupPod.Output.name }}"
115+
command:
116+
- bash
117+
- -o
118+
- errexit
119+
- -o
120+
- pipefail
121+
- -c
122+
- |
123+
## Send pg_backup_stop command to psql session
124+
echo "SELECT * FROM pg_backup_stop(wait_for_archive => true);" > /tmp/pg_in
125+
## Make sure operation completed
126+
## TODO: maybe there's a better way to fail/log here?
127+
grep -q "LABEL: kanister_backup" <(tail -f /tmp/pg_out)
128+
129+
restore:
130+
kind: StatefulSet
131+
inputArtifactNames:
132+
- cloudObject
133+
phases:
134+
- func: ScaleWorkload
135+
name: ShutdownApplication
136+
args:
137+
namespace: '{{.StatefulSet.Namespace }}'
138+
name: '{{ .StatefulSet.Name }}'
139+
kind: StatefulSet
140+
replicas: 0
141+
142+
- func: RestoreData
143+
name: RestoreFromObjectStore
144+
args:
145+
namespace: '{{.StatefulSet.Namespace }}'
146+
# pod: '{{ index .StatefulSet.Pods 0 }}'
147+
volumes:
148+
'{{ .ArtifactsIn.cloudObject.KeyValue.pvc }}': '/mnt/vol_data/{{ .ArtifactsIn.cloudObject.KeyValue.pvc }}'
149+
150+
image: ghcr.io/kanisterio/kanister-tools:0.110.0
151+
backupArtifactPrefix: s3-bucket/path/artifactPrefix
152+
backupTag: '{{ .ArtifactsIn.cloudObject.KeyValue.backupTag }}'
153+
154+
deferPhase:
155+
func: ScaleWorkload
156+
name: StartupApplication
157+
args:
158+
namespace: '{{.StatefulSet.Namespace }}'
159+
name: '{{ .StatefulSet.Name }}'
160+
kind: StatefulSet
161+
replicas: '{{ len .StatefulSet.Pods }}'
162+
163+
delete:
164+
inputArtifactNames:
165+
- cloudObject
166+
phases:
167+
- func: DeleteData
168+
name: deleteFromObjectStore
169+
args:
170+
namespace: '{{.StatefulSet.Namespace }}'
171+
backupArtifactPrefix: s3-bucket/path/artifactPrefix
172+
backupID: "{{ .ArtifactsIn.cloudObject.KeyValue.backupID }}"

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ require (
6565
k8s.io/code-generator v0.31.5
6666
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340
6767
k8s.io/kubectl v0.31.5
68-
sigs.k8s.io/controller-runtime v0.19.4
68+
sigs.k8s.io/controller-runtime v0.19.5
6969
sigs.k8s.io/structured-merge-diff/v4 v4.4.3
7070
sigs.k8s.io/yaml v1.4.0
7171
)

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -867,8 +867,8 @@ k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/
867867
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
868868
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
869869
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
870-
sigs.k8s.io/controller-runtime v0.19.4 h1:SUmheabttt0nx8uJtoII4oIP27BVVvAKFvdvGFwV/Qo=
871-
sigs.k8s.io/controller-runtime v0.19.4/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4=
870+
sigs.k8s.io/controller-runtime v0.19.5 h1:rsE2cRYe0hK/rAAwiS1bwqgEcgCxTz9lavs3FMgLW0c=
871+
sigs.k8s.io/controller-runtime v0.19.5/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4=
872872
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
873873
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
874874
sigs.k8s.io/kustomize/api v0.17.2 h1:E7/Fjk7V5fboiuijoZHgs4aHuexi5Y2loXlVOAVAG5g=

pkg/kando/kanx_cmd_test.go

Lines changed: 90 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ type KanXCmdSuite struct{}
1818

1919
var _ = Suite(&KanXCmdSuite{})
2020

21-
func startServer(ctx context.Context, addr string, stdout, stderr io.Writer) error {
21+
func startServer(ctx context.Context, addr string) error {
2222
rc := newRootCommand()
2323
rc.SetArgs([]string{"process", "server", "-a", addr})
24-
rc.SetOut(stdout)
25-
rc.SetErr(stderr)
24+
rc.SetOut(nil)
25+
rc.SetErr(nil)
2626
return rc.ExecuteContext(ctx)
2727
}
2828

@@ -75,7 +75,7 @@ func (s *KanXCmdSuite) TestProcessClientCreate(c *C) {
7575
ctx, can := context.WithCancel(context.Background())
7676
defer can()
7777
go func() {
78-
err := startServer(ctx, addr, nil, nil)
78+
err := startServer(ctx, addr)
7979
c.Assert(err, IsNil)
8080
}()
8181
err := waitSock(ctx, addr)
@@ -95,19 +95,20 @@ func (s *KanXCmdSuite) TestProcessClientCreate(c *C) {
9595
c.Assert(stderr.String(), Equals, "")
9696
}
9797

98+
// TestProcessClientOutput check that output command outputs stdout and stderr to their respective FDs.
9899
func (s *KanXCmdSuite) TestProcessClientOutput(c *C) {
99100
addr := c.MkDir() + "/kanister.sock"
100101
ctx, can := context.WithCancel(context.Background())
101102
defer can()
102103
go func() {
103-
err := startServer(ctx, addr, nil, nil)
104+
err := startServer(ctx, addr)
104105
c.Assert(err, IsNil)
105106
}()
106107
err := waitSock(ctx, addr)
107108
c.Assert(err, IsNil)
108109
stdout := &bytes.Buffer{}
109110
stderr := &bytes.Buffer{}
110-
err = executeCommand(ctx, stdout, stderr, "process", "client", "--as-json", "-a", addr, "create", "echo", "hello world")
111+
err = executeCommand(ctx, stdout, stderr, "process", "client", "--as-json", "-a", addr, "create", "--", "bash", "-c", "echo 'hello world 1' && echo 'hello world 2' 1>&2")
111112
c.Assert(err, IsNil)
112113
pr := &ProcessResult{}
113114
err = json.Unmarshal(stdout.Bytes(), pr)
@@ -116,16 +117,97 @@ func (s *KanXCmdSuite) TestProcessClientOutput(c *C) {
116117
// get output
117118
err = executeCommandWithReset(ctx, stdout, stderr, "process", "client", "-a", addr, "output", pr.Pid)
118119
c.Assert(err, IsNil)
119-
c.Assert(stdout.String(), Equals, "hello world\n")
120+
c.Assert(stdout.String(), Equals, "hello world 1\n")
121+
c.Assert(stderr.String(), Equals, "hello world 2\n")
122+
}
123+
124+
// TestProcessClientExecute_RedirectStdout checks that stdout contains JSON process metadata and process output without additional output from logging.
125+
func (s *KanXCmdSuite) TestProcessClientExecute_RedirectStdout(c *C) {
126+
addr := c.MkDir() + "/kanister.sock"
127+
ctx, can := context.WithCancel(context.Background())
128+
defer can()
129+
go func() {
130+
err := startServer(ctx, addr)
131+
c.Assert(err, IsNil)
132+
}()
133+
err := waitSock(ctx, addr)
134+
c.Assert(err, IsNil)
135+
stdout := &bytes.Buffer{}
136+
stderr := &bytes.Buffer{}
137+
err = executeCommand(ctx, stdout, stderr, "process", "client", "--as-json", "-a", addr, "execute", "echo", "hello world")
138+
c.Assert(err, IsNil)
139+
bs := stdout.Bytes()
140+
pr := &ProcessResult{}
141+
dc := json.NewDecoder(bytes.NewReader(bs))
142+
err = dc.Decode(pr)
143+
c.Assert(err, IsNil)
144+
c.Assert(dc.More(), Equals, true)
145+
rest := dc.InputOffset()
146+
c.Assert(string(bs[rest:]), Equals, "hello world\n")
120147
c.Assert(stderr.String(), Equals, "")
121148
}
122149

150+
// TestProcessClientExecute_RedirectStderr checks that stderr without additional output from logging.
151+
func (s *KanXCmdSuite) TestProcessClientExecute_RedirectStderr(c *C) {
152+
addr := c.MkDir() + "/kanister.sock"
153+
ctx, can := context.WithCancel(context.Background())
154+
defer can()
155+
go func() {
156+
err := startServer(ctx, addr)
157+
c.Assert(err, IsNil)
158+
}()
159+
err := waitSock(ctx, addr)
160+
c.Assert(err, IsNil)
161+
stdout := &bytes.Buffer{}
162+
stderr := &bytes.Buffer{}
163+
err = executeCommand(ctx, stdout, stderr, "process", "client", "--as-json", "-a", addr, "execute", "--", "bash", "-c", "echo 'hello world' 1>&2")
164+
c.Assert(err, IsNil)
165+
bs := stdout.Bytes()
166+
pr := &ProcessResult{}
167+
dc := json.NewDecoder(bytes.NewReader(bs))
168+
err = dc.Decode(pr)
169+
c.Assert(err, IsNil)
170+
c.Assert(stderr.String(), Equals, "hello world\n")
171+
}
172+
173+
// TestProcessClientExecute_Exit1 test that non-zero exit code from the child process is reflected in the kando command.
174+
func (s *KanXCmdSuite) TestProcessClientExecute_Exit1(c *C) {
175+
exitCode := 0
176+
addr := c.MkDir() + "/kanister.sock"
177+
exit = func(n int) {
178+
exitCode = n
179+
}
180+
ctx, can := context.WithCancel(context.Background())
181+
defer can()
182+
go func() {
183+
err := startServer(ctx, addr)
184+
c.Assert(err, IsNil)
185+
}()
186+
err := waitSock(ctx, addr)
187+
c.Assert(err, IsNil)
188+
stdout := &bytes.Buffer{}
189+
stderr := &bytes.Buffer{}
190+
err = executeCommand(ctx, stdout, stderr, "process", "client", "--as-json", "-a", addr, "execute", "--", "/bin/bash", "-c", "exit 1")
191+
c.Assert(err, NotNil)
192+
c.Assert(err.Error(), Equals, "exit status 1")
193+
c.Assert(exitCode, Equals, 1)
194+
bs := stdout.Bytes()
195+
pr := &ProcessResult{}
196+
dc := json.NewDecoder(bytes.NewReader(bs))
197+
err = dc.Decode(pr)
198+
c.Assert(err, IsNil)
199+
c.Assert(pr.Pid, Not(Equals), "")
200+
c.Assert(pr.State, Equals, "PROCESS_STATE_RUNNING")
201+
c.Assert(string(stdout.Bytes()[dc.InputOffset():]), Equals, "\n")
202+
c.Assert(stderr.String(), Equals, "Error: exit status 1\n")
203+
}
204+
123205
func (s *KanXCmdSuite) TestProcessClientGet(c *C) {
124206
addr := c.MkDir() + "/kanister.sock"
125207
ctx, can := context.WithCancel(context.Background())
126208
defer can()
127209
go func() {
128-
err := startServer(ctx, addr, nil, nil)
210+
err := startServer(ctx, addr)
129211
c.Assert(err, IsNil)
130212
}()
131213
err := waitSock(ctx, addr)

0 commit comments

Comments
 (0)