66package docker
77
88import (
9+ "encoding/json"
910 "fmt"
1011 "os"
12+ "path"
1113 "strconv"
1214 "strings"
1315
16+ "github.com/docker/docker/api/types"
1417 "github.com/pkg/errors"
1518 "github.com/shirou/gopsutil/host"
1619
20+ "gitlab.com/postgres-ai/database-lab/pkg/retrieval/engine/postgres/tools"
1721 "gitlab.com/postgres-ai/database-lab/pkg/services/provision/resources"
1822 "gitlab.com/postgres-ai/database-lab/pkg/services/provision/runners"
1923)
@@ -30,30 +34,101 @@ func RunContainer(r runners.Runner, c *resources.AppConfig) (string, error) {
3034 }
3135
3236 // 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 )}
3438
3539 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+ }
3846 }
3947
4048 if err := createSocketCloneDir (c .UnixSocketCloneDir ); err != nil {
4149 return "" , errors .Wrap (err , "failed to create socket clone directory" )
4250 }
4351
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+ }, " " )
5264
5365 return r .Run (dockerRunCmd , true )
5466}
5567
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+
56127func createSocketCloneDir (socketCloneDir string ) error {
128+ if err := os .RemoveAll (socketCloneDir ); err != nil {
129+ return err
130+ }
131+
57132 if err := os .MkdirAll (socketCloneDir , 0777 ); err != nil {
58133 return err
59134 }
0 commit comments