6
6
package docker
7
7
8
8
import (
9
+ "encoding/json"
9
10
"fmt"
10
11
"os"
12
+ "path"
11
13
"strconv"
12
14
"strings"
13
15
16
+ "github.com/docker/docker/api/types"
14
17
"github.com/pkg/errors"
15
18
"github.com/shirou/gopsutil/host"
16
19
20
+ "gitlab.com/postgres-ai/database-lab/pkg/retrieval/engine/postgres/tools"
17
21
"gitlab.com/postgres-ai/database-lab/pkg/services/provision/resources"
18
22
"gitlab.com/postgres-ai/database-lab/pkg/services/provision/runners"
19
23
)
@@ -30,30 +34,101 @@ func RunContainer(r runners.Runner, c *resources.AppConfig) (string, error) {
30
34
}
31
35
32
36
// Directly mount PGDATA if Database Lab is running without any virtualization.
33
- socketVolume := fmt .Sprintf ("--volume %s:%s" , c .Datadir , c .Datadir )
37
+ volumes := [] string { fmt .Sprintf ("--volume %s:%s" , c .Datadir , c .Datadir )}
34
38
35
39
if hostInfo .VirtualizationRole == "guest" {
36
- // Use volumes from the Database Lab instance if it's running inside Docker container.
37
- socketVolume = "--volumes-from=" + hostInfo .Hostname
40
+ // Build custom mounts rely on mounts of the Database Lab instance if it's running inside Docker container.
41
+ // We cannot use --volumes-from because it removes the ZFS mount point.
42
+ volumes , err = buildMountVolumes (r , hostInfo .Hostname , c .Datadir , c .UnixSocketCloneDir )
43
+ if err != nil {
44
+ return "" , errors .Wrap (err , "failed to detect container volumes" )
45
+ }
38
46
}
39
47
40
48
if err := createSocketCloneDir (c .UnixSocketCloneDir ); err != nil {
41
49
return "" , errors .Wrap (err , "failed to create socket clone directory" )
42
50
}
43
51
44
- dockerRunCmd := "docker run " +
45
- "--name " + c .CloneName + " " +
46
- "--detach " +
47
- "--publish " + strconv .Itoa (int (c .Port )) + ":5432 " +
48
- "--env PGDATA=" + c .Datadir + " " + socketVolume + " " +
49
- "--label " + labelClone + " " +
50
- "--label " + c .ClonePool + " " +
51
- c .DockerImage + " -k " + c .UnixSocketCloneDir
52
+ dockerRunCmd := strings .Join ([]string {
53
+ "docker run" ,
54
+ "--name" , c .CloneName ,
55
+ "--detach" ,
56
+ "--publish" , strconv .Itoa (int (c .Port )) + ":5432" ,
57
+ "--env" , "PGDATA=" + c .Datadir ,
58
+ strings .Join (volumes , " " ),
59
+ "--label" , labelClone ,
60
+ "--label" , c .ClonePool ,
61
+ c .DockerImage ,
62
+ "-k" , c .UnixSocketCloneDir ,
63
+ }, " " )
52
64
53
65
return r .Run (dockerRunCmd , true )
54
66
}
55
67
68
+ func buildMountVolumes (r runners.Runner , containerID , dataDir , cloneDir string ) ([]string , error ) {
69
+ inspectCmd := "docker inspect -f '{{ json .Mounts }}' " + containerID
70
+
71
+ var mountPoints []types.MountPoint
72
+
73
+ out , err := r .Run (inspectCmd , true )
74
+ if err != nil {
75
+ return nil , errors .Wrap (err , "failed to get container mounts" )
76
+ }
77
+
78
+ if err := json .Unmarshal ([]byte (strings .TrimSpace (out )), & mountPoints ); err != nil {
79
+ return nil , errors .Wrap (err , "failed to interpret mount paths" )
80
+ }
81
+
82
+ mounts := tools .GetMountsFromMountPoints (dataDir , mountPoints )
83
+ volumes := make ([]string , 0 , len (mounts ))
84
+
85
+ for _ , mount := range mounts {
86
+ // Add extra mount for socket directories.
87
+ // TODO (akartasov): Add mountDir to global config.
88
+ if mount .Target == dataDir {
89
+ volumes = append (volumes , buildSocketMount (mount .Source , dataDir , cloneDir ))
90
+ }
91
+
92
+ volume := fmt .Sprintf ("--volume %s:%s" , mount .Source , mount .Target )
93
+
94
+ if mount .BindOptions != nil && mount .BindOptions .Propagation != "" {
95
+ volume += ":" + string (mount .BindOptions .Propagation )
96
+ }
97
+
98
+ volumes = append (volumes , volume )
99
+ }
100
+
101
+ return volumes , nil
102
+ }
103
+
104
+ // buildSocketMount builds a socket directory mounting rely on dataDir mounting.
105
+ func buildSocketMount (hostDataDir , dataDir , cloneDir string ) string {
106
+ mountDir := cloneDir
107
+
108
+ // Discover the common path prefix supposing it is the mount point.
109
+ for mountDir != "." {
110
+ mountDir , _ = path .Split (mountDir )
111
+
112
+ if strings .HasPrefix (dataDir , mountDir ) {
113
+ break
114
+ }
115
+
116
+ mountDir = path .Clean (mountDir )
117
+ }
118
+
119
+ clonePath := strings .TrimPrefix (cloneDir , mountDir )
120
+ dataPath := strings .TrimPrefix (dataDir , mountDir )
121
+ internalMount := strings .TrimSuffix (hostDataDir , dataPath )
122
+ internalMountPath := path .Join (internalMount , clonePath )
123
+
124
+ return fmt .Sprintf (" --volume %s:%s:rshared" , internalMountPath , cloneDir )
125
+ }
126
+
56
127
func createSocketCloneDir (socketCloneDir string ) error {
128
+ if err := os .RemoveAll (socketCloneDir ); err != nil {
129
+ return err
130
+ }
131
+
57
132
if err := os .MkdirAll (socketCloneDir , 0777 ); err != nil {
58
133
return err
59
134
}
0 commit comments