@@ -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"
@@ -19,6 +22,12 @@ import (
1922 runconfigopts "github.com/hyperhq/hypercli/runconfig/opts"
2023)
2124
25+ type InitVolume struct {
26+ Source string
27+ Destination string
28+ Name string
29+ }
30+
2231func (cid * cidFile ) Close () error {
2332 cid .file .Close ()
2433
@@ -62,6 +71,145 @@ func runStartContainerErr(err error) error {
6271 return statusError
6372}
6473
74+ func parseProtoAndLocalBind (bind string ) (string , string , bool ) {
75+ switch {
76+ case strings .HasPrefix (bind , "git://" ):
77+ fallthrough
78+ case strings .HasPrefix (bind , "http://" ):
79+ fallthrough
80+ case strings .HasPrefix (bind , "https://" ):
81+ if strings .Count (bind , ":" ) < 2 {
82+ return "" , "" , false
83+ }
84+ case strings .HasPrefix (bind , "/" ):
85+ if strings .Count (bind , ":" ) < 1 {
86+ return "" , "" , false
87+ }
88+ default :
89+ return "" , "" , false
90+ }
91+
92+ pos := strings .LastIndex (bind , ":" )
93+ if pos < 0 || pos >= len (bind )- 1 {
94+ return "" , "" , false
95+ }
96+
97+ return bind [:pos ], bind [pos + 1 :], true
98+ }
99+
100+ func checkSourceType (source string ) string {
101+ part := strings .Split (source , ":" )
102+ count := len (part )
103+ switch {
104+ case strings .HasPrefix (source , "git://" ) || strings .HasSuffix (source , ".git" ) ||
105+ (count >= 2 && strings .HasSuffix (part [count - 2 ], ".git" )):
106+ return "git"
107+ case strings .HasPrefix (source , "http://" ):
108+ fallthrough
109+ case strings .HasPrefix (source , "https://" ):
110+ return "http"
111+ case strings .HasPrefix (source , "/" ):
112+ return "local"
113+ default :
114+ return "unknown"
115+ }
116+ }
117+
118+ func (cli * DockerCli ) initSpecialVolumes (config * container.Config , hostConfig * container.HostConfig , networkingConfig * networktypes.NetworkingConfig , initvols []* InitVolume ) error {
119+ const INIT_VOLUME_PATH = "/vol/"
120+ const INIT_VOLUME_IMAGE = "hyperhq/volume_uploader:latest"
121+ var (
122+ initConfig * container.Config
123+ initHostConfig * container.HostConfig
124+ execJobs []string
125+ execID string
126+ )
127+
128+ initConfig = & container.Config {
129+ User : config .User ,
130+ Env : config .Env ,
131+ Image : INIT_VOLUME_IMAGE ,
132+ StopSignal : config .StopSignal ,
133+ }
134+
135+ initHostConfig = & container.HostConfig {
136+ Binds : make ([]string , 0 ),
137+ DNS : hostConfig .DNS ,
138+ DNSOptions : hostConfig .DNSOptions ,
139+ DNSSearch : hostConfig .DNSSearch ,
140+ ExtraHosts : hostConfig .ExtraHosts ,
141+ }
142+
143+ for _ , vol := range initvols {
144+ initHostConfig .Binds = append (initHostConfig .Binds , vol .Name + ":" + INIT_VOLUME_PATH + vol .Destination )
145+ }
146+
147+ createResponse , err := cli .createContainer (initConfig , initHostConfig , networkingConfig , hostConfig .ContainerIDFile , "" )
148+ if err != nil {
149+ return err
150+ }
151+ defer func () {
152+ if err != nil {
153+ if _ , rmErr := cli .removeContainer (createResponse .ID , true , false , true ); rmErr != nil {
154+ fmt .Fprintf (cli .err , "clean up init container failed: %s\n " , rmErr .Error ())
155+ }
156+ }
157+ }()
158+
159+ if err = cli .client .ContainerStart (createResponse .ID ); err != nil {
160+ return err
161+ }
162+
163+ for _ , vol := range initvols {
164+ var cmd []string
165+ volType := checkSourceType (vol .Source )
166+ switch volType {
167+ case "git" :
168+ cmd = append (cmd , "git" , "clone" , vol .Source , INIT_VOLUME_PATH + vol .Destination )
169+ case "http" :
170+ parts := strings .Split (vol .Source , "/" )
171+ 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 )
172+ case "local" :
173+ // TODO
174+ default :
175+ continue
176+ }
177+ if len (cmd ) == 0 {
178+ continue
179+ }
180+ if execID , err = cli .ExecCmd (initConfig .User , createResponse .ID , cmd ); err != nil {
181+ return err
182+ } else {
183+ execJobs = append (execJobs , execID )
184+ }
185+ }
186+
187+ // wait results
188+ for _ , execID = range execJobs {
189+ if err = cli .WaitExec (execID ); err != nil {
190+ return err
191+ }
192+ }
193+
194+ // Need to sync before tearing down container because data might be still cached
195+ if len (execJobs ) > 0 {
196+ syncCmd := []string {"sync" }
197+ if execID , err = cli .ExecCmd (initConfig .User , createResponse .ID , syncCmd ); err != nil {
198+ return err
199+ }
200+ if err = cli .WaitExec (execID ); err != nil {
201+ return err
202+ }
203+ }
204+
205+ _ , err = cli .removeContainer (createResponse .ID , false , false , true )
206+ if err != nil {
207+ return err
208+ }
209+
210+ return nil
211+ }
212+
65213// CmdRun runs a command in a new container.
66214//
67215// Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
@@ -147,11 +295,50 @@ func (cli *DockerCli) CmdRun(args ...string) error {
147295 hostConfig .ConsoleSize [0 ], hostConfig .ConsoleSize [1 ] = cli .getTtySize ()
148296 }
149297
298+ // Check/create protocol and local volume
299+ var initvols []* InitVolume
300+ defer func () {
301+ for _ , vol := range initvols {
302+ cli .client .VolumeRemove (vol .Name )
303+ }
304+ }()
305+ for idx , bind := range hostConfig .Binds {
306+ if source , dest , ok := parseProtoAndLocalBind (bind ); ok {
307+ volReq := types.VolumeCreateRequest {
308+ Driver : "hyper" ,
309+ Labels : map [string ]string {
310+ "source" : source ,
311+ }}
312+ if vol , err := cli .client .VolumeCreate (volReq ); err != nil {
313+ cmd .ReportError (err .Error (), true )
314+ return runStartContainerErr (err )
315+ } else {
316+ initvols = append (initvols , & InitVolume {
317+ Source : source ,
318+ Destination : dest ,
319+ Name : vol .Name ,
320+ })
321+ hostConfig .Binds [idx ] = vol .Name + ":" + dest
322+ }
323+ }
324+ }
325+
326+ // initialize special volumes
327+ if len (initvols ) > 0 {
328+ err := cli .initSpecialVolumes (config , hostConfig , networkingConfig , initvols )
329+ if err != nil {
330+ cmd .ReportError (err .Error (), true )
331+ return runStartContainerErr (err )
332+ }
333+ }
334+
150335 createResponse , err := cli .createContainer (config , hostConfig , networkingConfig , hostConfig .ContainerIDFile , * flName )
151336 if err != nil {
152337 cmd .ReportError (err .Error (), true )
153338 return runStartContainerErr (err )
154339 }
340+ initvols = nil
341+
155342 if sigProxy {
156343 sigc := cli .forwardAllSignals (createResponse .ID )
157344 defer signal .StopCatch (sigc )
0 commit comments