Skip to content
This repository was archived by the owner on Feb 8, 2021. It is now read-only.

Commit dd41717

Browse files
authored
Merge pull request #117 from bergwolf/init_volume
Init protocol based volume mapping in hyper run
2 parents c14d906 + b4e4e4e commit dd41717

File tree

2 files changed

+232
-1
lines changed

2 files changed

+232
-1
lines changed

api/client/exec.go

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ package client
33
import (
44
"fmt"
55
"io"
6+
"strings"
7+
"time"
68

79
"github.com/Sirupsen/logrus"
10+
"github.com/docker/engine-api/types"
811
Cli "github.com/hyperhq/hypercli/cli"
912
flag "github.com/hyperhq/hypercli/pkg/mflag"
1013
"github.com/hyperhq/hypercli/pkg/promise"
11-
"github.com/docker/engine-api/types"
1214
)
1315

1416
// CmdExec runs a command in a running container.
@@ -162,3 +164,45 @@ func ParseExec(cmd *flag.FlagSet, args []string) (*types.ExecConfig, error) {
162164

163165
return execConfig, nil
164166
}
167+
168+
func (cli *DockerCli) ExecCmd(user, contID string, cmd []string) (string, error) {
169+
execConfig := &types.ExecConfig{
170+
User: user,
171+
Container: contID,
172+
Detach: true,
173+
Cmd: cmd,
174+
}
175+
execCreateResponse, err := cli.client.ContainerExecCreate(*execConfig)
176+
if err != nil {
177+
return "", err
178+
}
179+
execID := execCreateResponse.ID
180+
if execID == "" {
181+
err = fmt.Errorf("Failed to exec %s: Empty exec ID", strings.Join(cmd, " "))
182+
return "", err
183+
}
184+
execStartCheck := types.ExecStartCheck{Detach: execConfig.Detach}
185+
if err := cli.client.ContainerExecStart(execID, execStartCheck); err != nil {
186+
return "", err
187+
}
188+
189+
return execID, nil
190+
}
191+
192+
func (cli *DockerCli) WaitExec(execID string) error {
193+
for {
194+
running, status, err := getExecExitCode(cli, execID)
195+
switch {
196+
case err != nil:
197+
return err
198+
case running:
199+
time.Sleep(100 * time.Millisecond)
200+
case status != 0:
201+
err = fmt.Errorf("Failed to init volume: %d", status)
202+
return err
203+
case status == 0:
204+
return nil
205+
}
206+
}
207+
208+
}

api/client/run.go

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
2231
func (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

Comments
 (0)