5
5
package physical
6
6
7
7
import (
8
+ "context"
8
9
"fmt"
10
+ "strings"
9
11
12
+ "github.com/docker/docker/api/types"
13
+ "github.com/docker/docker/client"
14
+ "golang.org/x/mod/semver"
15
+
16
+ "gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/engine/postgres/tools"
10
17
"gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/engine/postgres/tools/defaults"
18
+ "gitlab.com/postgres-ai/database-lab/v3/pkg/log"
11
19
)
12
20
13
21
const (
14
- walgTool = "walg"
22
+ walgTool = "walg"
23
+ walgSplitCount = 3
24
+ walg11Version = "v1.1"
25
+ latestBackup = "LATEST"
15
26
)
16
27
17
28
// walg defines a WAL-G as an archival restoration tool.
18
29
type walg struct {
19
- pgDataDir string
20
- options walgOptions
30
+ dockerClient * client.Client
31
+ pgDataDir string
32
+ parsedBackupName string
33
+ options walgOptions
21
34
}
22
35
23
36
type walgOptions struct {
24
37
BackupName string `yaml:"backupName"`
25
38
}
26
39
27
- func newWALG (pgDataDir string , options walgOptions ) * walg {
28
- return & walg {
29
- pgDataDir : pgDataDir ,
30
- options : options ,
40
+ func newWALG (dockerClient * client.Client , pgDataDir string , options walgOptions ) * walg {
41
+ walg := & walg {
42
+ dockerClient : dockerClient ,
43
+ pgDataDir : pgDataDir ,
44
+ options : options ,
45
+ parsedBackupName : options .BackupName ,
31
46
}
47
+
48
+ return walg
32
49
}
33
50
34
51
// GetRestoreCommand returns a command to restore data.
35
52
func (w * walg ) GetRestoreCommand () string {
36
- return fmt .Sprintf ("wal-g backup-fetch %s %s" , w .pgDataDir , w .options . BackupName )
53
+ return fmt .Sprintf ("wal-g backup-fetch %s %s" , w .pgDataDir , w .parsedBackupName )
37
54
}
38
55
39
56
// GetRecoveryConfig returns a recovery config to restore data.
@@ -48,3 +65,104 @@ func (w *walg) GetRecoveryConfig(pgVersion float64) map[string]string {
48
65
49
66
return recoveryCfg
50
67
}
68
+
69
+ // Init initializes the wal-g tool to run in the provided container.
70
+ func (w * walg ) Init (ctx context.Context , containerID string ) error {
71
+ if strings .ToUpper (w .options .BackupName ) != latestBackup {
72
+ return nil
73
+ }
74
+ // workaround for issue with identification of last backup
75
+ // https://gitlab.com/postgres-ai/database-lab/-/issues/365
76
+ name , err := getLastBackupName (ctx , w .dockerClient , containerID )
77
+
78
+ if err != nil {
79
+ return err
80
+ }
81
+
82
+ if name != "" {
83
+ w .parsedBackupName = name
84
+ return nil
85
+ }
86
+
87
+ return fmt .Errorf ("failed to fetch last backup name from wal-g" )
88
+ }
89
+
90
+ // getLastBackupName returns the name of the latest backup from the wal-g backup list.
91
+ func getLastBackupName (ctx context.Context , dockerClient * client.Client , containerID string ) (string , error ) {
92
+ walgVersion , err := getWalgVersion (ctx , dockerClient , containerID )
93
+
94
+ if err != nil {
95
+ return "" , err
96
+ }
97
+
98
+ result := semver .Compare (walgVersion , walg11Version )
99
+
100
+ // Try to fetch the latest backup with the details command for WAL-G 1.1 and higher
101
+ if result >= 0 {
102
+ output , err := parseLastBackupFromDetails (ctx , dockerClient , containerID )
103
+
104
+ if err == nil {
105
+ return output , err
106
+ }
107
+
108
+ // fallback to fetching last backup from list
109
+ log .Err ("Failed to parse last backup from wal-g details" , err )
110
+ }
111
+
112
+ return parseLastBackupFromList (ctx , dockerClient , containerID )
113
+ }
114
+
115
+ // parseLastBackupFromList parses the name of the latest backup from "wal-g backup-list" output.
116
+ func parseLastBackupFromList (ctx context.Context , dockerClient * client.Client , containerID string ) (string , error ) {
117
+ output , err := tools .ExecCommandWithOutput (ctx , dockerClient , containerID , types.ExecConfig {
118
+ Cmd : []string {"bash" , "-c" , "wal-g backup-list | grep base | sort -nk1 | tail -1 | awk '{print $1}'" },
119
+ })
120
+ if err != nil {
121
+ return "" , err
122
+ }
123
+
124
+ log .Dbg ("The latest WAL-G backup from the list" , output )
125
+
126
+ return output , nil
127
+ }
128
+
129
+ // parseLastBackupFromDetails parses the name of the latest backup from "wal-g backup-list --detail" output.
130
+ func parseLastBackupFromDetails (ctx context.Context , dockerClient * client.Client , containerID string ) (string , error ) {
131
+ output , err := tools .ExecCommandWithOutput (ctx , dockerClient , containerID , types.ExecConfig {
132
+ Cmd : []string {"bash" , "-c" , "wal-g backup-list --detail | tail -1 | awk '{print $1}'" },
133
+ })
134
+ if err != nil {
135
+ return "" , err
136
+ }
137
+
138
+ log .Dbg ("The latest WAL-G backup from list details" , output )
139
+
140
+ return output , nil
141
+ }
142
+
143
+ // getWalgVersion fetches the WAL-G version installed in the provided container.
144
+ func getWalgVersion (ctx context.Context , dockerClient * client.Client , containerID string ) (string , error ) {
145
+ output , err := tools .ExecCommandWithOutput (ctx , dockerClient , containerID , types.ExecConfig {
146
+ Cmd : []string {"bash" , "-c" , "wal-g --version" },
147
+ })
148
+ if err != nil {
149
+ return "" , err
150
+ }
151
+
152
+ log .Dbg (output )
153
+
154
+ return parseWalGVersion (output )
155
+ }
156
+
157
+ // parseWalGVersion extracts the version from the 'wal-g --version' output.
158
+ // For example, "wal-g version v2.0.0 1eb88a5 2022.05.20_10:45:57 PostgreSQL".
159
+ func parseWalGVersion (output string ) (string , error ) {
160
+ walgVersion := strings .Split (output , "\t " )
161
+ versionParts := strings .Split (walgVersion [0 ], " " )
162
+
163
+ if len (versionParts ) < walgSplitCount {
164
+ return "" , fmt .Errorf ("failed to extract wal-g version number" )
165
+ }
166
+
167
+ return versionParts [walgSplitCount - 1 ], nil
168
+ }
0 commit comments