@@ -5,10 +5,13 @@ import (
55 "io"
66 "os"
77 "runtime"
8+ "strconv"
89 "strings"
910
1011 "github.com/Sirupsen/logrus"
1112 "github.com/docker/engine-api/types"
13+ "github.com/docker/engine-api/types/container"
14+ networktypes "github.com/docker/engine-api/types/network"
1215 "github.com/docker/libnetwork/resolvconf/dns"
1316 Cli "github.com/hyperhq/hypercli/cli"
1417 derr "github.com/hyperhq/hypercli/errors"
@@ -17,8 +20,15 @@ import (
1720 "github.com/hyperhq/hypercli/pkg/signal"
1821 "github.com/hyperhq/hypercli/pkg/stringid"
1922 runconfigopts "github.com/hyperhq/hypercli/runconfig/opts"
23+ "github.com/satori/go.uuid"
2024)
2125
26+ type InitVolume struct {
27+ Source string
28+ Destination string
29+ Name string
30+ }
31+
2232func (cid * cidFile ) Close () error {
2333 cid .file .Close ()
2434
@@ -62,6 +72,179 @@ func runStartContainerErr(err error) error {
6272 return statusError
6373}
6474
75+ func parseProtoAndLocalBind (bind string ) (string , string , bool ) {
76+ switch {
77+ case strings .HasPrefix (bind , "git://" ):
78+ fallthrough
79+ case strings .HasPrefix (bind , "http://" ):
80+ fallthrough
81+ case strings .HasPrefix (bind , "https://" ):
82+ if strings .Count (bind , ":" ) < 2 {
83+ return "" , "" , false
84+ }
85+ case strings .HasPrefix (bind , "/" ):
86+ if strings .Count (bind , ":" ) < 1 {
87+ return "" , "" , false
88+ }
89+ default :
90+ return "" , "" , false
91+ }
92+
93+ pos := strings .LastIndex (bind , ":" )
94+ if pos < 0 || pos >= len (bind )- 1 {
95+ return "" , "" , false
96+ }
97+
98+ return bind [:pos ], bind [pos + 1 :], true
99+ }
100+
101+ func checkSourceType (source string ) string {
102+ part := strings .Split (source , ":" )
103+ count := len (part )
104+ switch {
105+ case strings .HasPrefix (source , "git://" ) || strings .HasSuffix (source , ".git" ) ||
106+ (count >= 2 && strings .HasSuffix (part [count - 2 ], ".git" )):
107+ return "git"
108+ case strings .HasPrefix (source , "http://" ):
109+ fallthrough
110+ case strings .HasPrefix (source , "https://" ):
111+ return "http"
112+ case strings .HasPrefix (source , "/" ):
113+ return "local"
114+ default :
115+ return "unknown"
116+ }
117+ }
118+
119+ func (cli * DockerCli ) initSpecialVolumes (config * container.Config , hostConfig * container.HostConfig , networkingConfig * networktypes.NetworkingConfig , initvols []* InitVolume ) error {
120+ const INIT_VOLUME_PATH = "/vol/"
121+ const INIT_VOLUME_IMAGE = "hyperhq/volume_uploader:latest"
122+ var (
123+ initConfig * container.Config
124+ initHostConfig * container.HostConfig
125+ errCh chan error
126+ execCount uint32
127+ fip string
128+ )
129+
130+ initConfig = & container.Config {
131+ User : config .User ,
132+ Image : INIT_VOLUME_IMAGE ,
133+ StopSignal : config .StopSignal ,
134+ }
135+
136+ initHostConfig = & container.HostConfig {
137+ Binds : make ([]string , 0 ),
138+ DNS : hostConfig .DNS ,
139+ DNSOptions : hostConfig .DNSOptions ,
140+ DNSSearch : hostConfig .DNSSearch ,
141+ ExtraHosts : hostConfig .ExtraHosts ,
142+ }
143+
144+ for _ , vol := range initvols {
145+ initHostConfig .Binds = append (initHostConfig .Binds , vol .Name + ":" + INIT_VOLUME_PATH + vol .Destination )
146+ }
147+ passwd := uuid .NewV1 ()
148+ initConfig .Env = append (config .Env , "ROOTPASSWORD=" + passwd .String (), "LOCALROOT=" + INIT_VOLUME_PATH )
149+
150+ createResponse , err := cli .createContainer (initConfig , initHostConfig , networkingConfig , hostConfig .ContainerIDFile , "" )
151+ if err != nil {
152+ return err
153+ }
154+ defer func () {
155+ if err != nil {
156+ if _ , rmErr := cli .removeContainer (createResponse .ID , true , false , true ); rmErr != nil {
157+ fmt .Fprintf (cli .err , "clean up init container failed: %s\n " , rmErr .Error ())
158+ }
159+ }
160+ if fip != "" {
161+ if rmErr := cli .releaseFip (fip ); rmErr != nil {
162+ fmt .Fprintf (cli .err , "failed to clean up container fip %s: %s\n " , fip , rmErr .Error ())
163+ }
164+ }
165+ }()
166+
167+ if err = cli .client .ContainerStart (createResponse .ID ); err != nil {
168+ return err
169+ }
170+
171+ errCh = make (chan error , len (initvols ))
172+ for _ , vol := range initvols {
173+ var cmd []string
174+ volType := checkSourceType (vol .Source )
175+ switch volType {
176+ case "git" :
177+ cmd = append (cmd , "git" , "clone" , vol .Source , INIT_VOLUME_PATH + vol .Destination )
178+ case "http" :
179+ parts := strings .Split (vol .Source , "/" )
180+ cmd = append (cmd , "wget" , "--no-check-certificate" , "--tries=5" , "--mirror" , "--no-host-directories" , "--cut-dirs=" + strconv .Itoa (len (parts )), vol .Source , "--directory-prefix=" + INIT_VOLUME_PATH + vol .Destination )
181+ case "local" :
182+ execCount ++
183+ if fip == "" {
184+ fip , err = cli .associateNewFip (createResponse .ID )
185+ if err != nil {
186+ return err
187+ }
188+ }
189+ go func (vol * InitVolume ) {
190+ err := cli .uploadLocalResource (vol .Source , INIT_VOLUME_PATH + vol .Destination , fip , "root" , passwd .String ())
191+ if err != nil {
192+ err = fmt .Errorf ("Failed to upload %s: %s" , vol .Source , err .Error ())
193+ }
194+ errCh <- err
195+ }(vol )
196+ default :
197+ continue
198+ }
199+ if len (cmd ) == 0 {
200+ continue
201+ }
202+
203+ execCount ++
204+ go func () {
205+ execID , err := cli .ExecCmd (initConfig .User , createResponse .ID , cmd )
206+ if err != nil {
207+ errCh <- err
208+ } else {
209+ errCh <- cli .WaitExec (execID )
210+ }
211+ }()
212+ }
213+
214+ // wait results
215+ for ; execCount > 0 ; execCount -- {
216+ err = <- errCh
217+ if err != nil {
218+ return err
219+ }
220+ }
221+
222+ // release fip
223+ if fip != "" {
224+ if err = cli .releaseContainerFip (createResponse .ID ); err != nil {
225+ return err
226+ }
227+ fip = ""
228+ }
229+
230+ // Need to sync before tearing down container because data might be still cached
231+ syncCmd := []string {"sync" }
232+ execID , err := cli .ExecCmd (initConfig .User , createResponse .ID , syncCmd )
233+ if err != nil {
234+ return err
235+ }
236+ if err = cli .WaitExec (execID ); err != nil {
237+ return err
238+ }
239+
240+ _ , err = cli .removeContainer (createResponse .ID , false , false , true )
241+ if err != nil {
242+ return err
243+ }
244+
245+ return nil
246+ }
247+
65248// CmdRun runs a command in a new container.
66249//
67250// Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
@@ -147,11 +330,50 @@ func (cli *DockerCli) CmdRun(args ...string) error {
147330 hostConfig .ConsoleSize [0 ], hostConfig .ConsoleSize [1 ] = cli .getTtySize ()
148331 }
149332
333+ // Check/create protocol and local volume
334+ var initvols []* InitVolume
335+ defer func () {
336+ for _ , vol := range initvols {
337+ cli .client .VolumeRemove (vol .Name )
338+ }
339+ }()
340+ for idx , bind := range hostConfig .Binds {
341+ if source , dest , ok := parseProtoAndLocalBind (bind ); ok {
342+ volReq := types.VolumeCreateRequest {
343+ Driver : "hyper" ,
344+ Labels : map [string ]string {
345+ "source" : source ,
346+ }}
347+ if vol , err := cli .client .VolumeCreate (volReq ); err != nil {
348+ cmd .ReportError (err .Error (), true )
349+ return runStartContainerErr (err )
350+ } else {
351+ initvols = append (initvols , & InitVolume {
352+ Source : source ,
353+ Destination : dest ,
354+ Name : vol .Name ,
355+ })
356+ hostConfig .Binds [idx ] = vol .Name + ":" + dest
357+ }
358+ }
359+ }
360+
361+ // initialize special volumes
362+ if len (initvols ) > 0 {
363+ err := cli .initSpecialVolumes (config , hostConfig , networkingConfig , initvols )
364+ if err != nil {
365+ cmd .ReportError (err .Error (), true )
366+ return runStartContainerErr (err )
367+ }
368+ }
369+
150370 createResponse , err := cli .createContainer (config , hostConfig , networkingConfig , hostConfig .ContainerIDFile , * flName )
151371 if err != nil {
152372 cmd .ReportError (err .Error (), true )
153373 return runStartContainerErr (err )
154374 }
375+ initvols = nil
376+
155377 if sigProxy {
156378 sigc := cli .forwardAllSignals (createResponse .ID )
157379 defer signal .StopCatch (sigc )
0 commit comments