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

Commit c3ad41b

Browse files
authored
Merge pull request #561 from gnawux/cmdline_0.8
update the command line help/description messages
2 parents 47dc9ff + 612f2a8 commit c3ad41b

File tree

7 files changed

+420
-389
lines changed

7 files changed

+420
-389
lines changed

client/attach.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111

1212
func (cli *HyperClient) HyperCmdAttach(args ...string) error {
1313
var parser = gflag.NewParser(nil, gflag.Default)
14-
parser.Usage = "attach CONTAINER\n\nAttach to the tty of a specified container in a pod"
14+
parser.Usage = "attach CONTAINER\n\nAttach to the input/output of a specified container"
1515
args, err := parser.ParseArgs(args)
1616
if err != nil {
1717
if !strings.Contains(err.Error(), "Usage") {

client/common_options.go

Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
package client
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io/ioutil"
7+
"os"
8+
"path/filepath"
9+
"regexp"
10+
"strconv"
11+
"strings"
12+
13+
"github.com/docker/docker/pkg/namesgenerator"
14+
15+
apitype "github.com/hyperhq/hyperd/types"
16+
"github.com/hyperhq/hyperd/utils"
17+
)
18+
19+
type CommonFlags struct {
20+
PodFile string `short:"p" long:"podfile" value-name:"\"\"" description:"read spec from the pod file instead of command line"`
21+
Yaml bool `short:"y" long:"yaml" default-mask:"-" description:"pod file in Yaml format instead of JSON"`
22+
Name string `long:"name" value-name:"\"\"" description:"Assign a name to the container"`
23+
Workdir string `long:"workdir" value-name:"\"\"" default-mask:"-" description:"Working directory inside the container"`
24+
Tty bool `short:"t" long:"tty" default-mask:"-" description:"the run command in tty, such as bash shell"`
25+
Cpu int `long:"cpu" default:"1" value-name:"1" default-mask:"-" description:"CPU number for the VM"`
26+
Memory int `long:"memory" default:"128" value-name:"128" default-mask:"-" description:"Memory size (MB) for the VM"`
27+
Env []string `long:"env" value-name:"[]" default-mask:"-" description:"Set environment variables"`
28+
EntryPoint string `long:"entrypoint" value-name:"\"\"" default-mask:"-" description:"Overwrite the default ENTRYPOINT of the image"`
29+
RestartPolicy string `long:"restart" default:"never" value-name:"\"\"" default-mask:"-" description:"Restart policy to apply when a container exits (never, onFailure, always)"`
30+
LogDriver string `long:"log-driver" value-name:"\"\"" description:"Logging driver for Pod"`
31+
LogOpts []string `long:"log-opt" description:"Log driver options"`
32+
Portmap []string `long:"publish" value-name:"[]" default-mask:"-" description:"Publish a container's port to the host, format: --publish [tcp/udp:]hostPort:containerPort"`
33+
Labels []string `long:"label" value-name:"[]" default-mask:"-" description:"Add labels for Pod, format: --label key=value"`
34+
Volumes []string `short:"v" long:"volume" value-name:"[]" default-mask:"-" description:"Mount host file/directory as a data file/volume, format: -v|--volume=[[hostDir:]containerDir[:options]]"`
35+
}
36+
37+
type CreateFlags struct {
38+
CommonFlags
39+
Container bool `short:"c" long:"container" default-mast:"-" description:"Create container inside a pod"`
40+
}
41+
42+
type RunFlags struct {
43+
CommonFlags
44+
Attach bool `short:"a" long:"attach" default-mask:"-" description:"(from podfile) Attach the stdin, stdout and stderr to the container"`
45+
Detach bool `short:"d" long:"detach" default-mask:"-" description:"(from cmdline) Not Attach the stdin, stdout and stderr to the container"`
46+
Remove bool `long:"rm" default-mask:"-" description:"Automatically remove the pod when it exits"`
47+
}
48+
49+
func (cli *HyperClient) ParseCommonOptions(opts *CommonFlags, container bool, args ...string) ([]byte, error) {
50+
var (
51+
specJson string
52+
err error
53+
)
54+
55+
if opts.PodFile != "" {
56+
specJson, err = cli.JsonFromFile(opts.PodFile, container, opts.Yaml, false)
57+
} else {
58+
if len(args) == 0 {
59+
return nil, fmt.Errorf("%s: this command requires a minimum of 1 argument, please provide the image.", os.Args[0])
60+
}
61+
specJson, err = cli.JsonFromCmdline(container, args, opts.Env, opts.Portmap, opts.LogDriver, opts.LogOpts,
62+
opts.Name, opts.Workdir, opts.RestartPolicy, opts.Cpu, opts.Memory, opts.Tty, opts.Labels, opts.EntryPoint, opts.Volumes)
63+
}
64+
65+
if err != nil {
66+
return nil, err
67+
}
68+
69+
return []byte(specJson), nil
70+
}
71+
72+
func (cli *HyperClient) JsonFromFile(filename string, container, yaml, k8s bool) (string, error) {
73+
if _, err := os.Stat(filename); err != nil {
74+
return "", err
75+
}
76+
77+
jsonbody, err := ioutil.ReadFile(filename)
78+
if err != nil {
79+
return "", err
80+
}
81+
82+
if yaml == true {
83+
jsonbody, err = cli.ConvertYamlToJson(jsonbody, container)
84+
if err != nil {
85+
return "", err
86+
}
87+
}
88+
89+
return string(jsonbody), nil
90+
}
91+
92+
func (cli *HyperClient) JsonFromCmdline(container bool, cmdArgs, cmdEnvs, cmdPortmaps []string, cmdLogDriver string, cmdLogOpts []string,
93+
cmdName, cmdWorkdir, cmdRestartPolicy string, cpu, memory int, tty bool, cmdLabels []string, entrypoint string, cmdVols []string) (string, error) {
94+
95+
var (
96+
name = cmdName
97+
image = cmdArgs[0]
98+
command = []string{}
99+
env = []*apitype.EnvironmentVar{}
100+
ports = []*apitype.UserContainerPort{}
101+
logOpts = make(map[string]string)
102+
labels = make(map[string]string)
103+
volumesRef = []*apitype.UserVolumeReference{}
104+
)
105+
if len(cmdArgs) > 1 {
106+
command = cmdArgs[1:]
107+
}
108+
if name == "" {
109+
name = imageToName(image)
110+
}
111+
if memory == 0 {
112+
memory = 128
113+
}
114+
if cpu == 0 {
115+
cpu = 1
116+
}
117+
for _, v := range cmdEnvs {
118+
if eqlIndex := strings.Index(v, "="); eqlIndex > 0 {
119+
env = append(env, &apitype.EnvironmentVar{
120+
Env: v[:eqlIndex],
121+
Value: v[eqlIndex+1:],
122+
})
123+
}
124+
}
125+
126+
for _, v := range cmdLogOpts {
127+
eql := strings.Index(v, "=")
128+
if eql > 0 {
129+
logOpts[v[:eql]] = v[eql+1:]
130+
} else {
131+
logOpts[v] = ""
132+
}
133+
}
134+
135+
for _, v := range cmdPortmaps {
136+
p, err := parsePortMapping(v)
137+
if err != nil {
138+
return "", err
139+
}
140+
ports = append(ports, p)
141+
}
142+
143+
for _, v := range cmdLabels {
144+
label := strings.Split(v, "=")
145+
if len(label) == 2 {
146+
labels[label[0]] = label[1]
147+
} else {
148+
return "", fmt.Errorf("Label '%s' is not in 'k=v' format", v)
149+
}
150+
}
151+
152+
for _, v := range cmdVols {
153+
vol, volRef, err := parseVolume(v)
154+
if err != nil {
155+
return "", err
156+
}
157+
volRef.Detail = vol
158+
volumesRef = append(volumesRef, volRef)
159+
}
160+
161+
entrypoints := make([]string, 0, 1)
162+
if len(entrypoint) > 0 {
163+
entrypoints = append(entrypoints, entrypoint)
164+
}
165+
166+
c := &apitype.UserContainer{
167+
Name: name,
168+
Image: image,
169+
Command: command,
170+
Workdir: cmdWorkdir,
171+
Entrypoint: entrypoints,
172+
Ports: ports,
173+
Envs: env,
174+
Volumes: volumesRef,
175+
Files: []*apitype.UserFileReference{},
176+
RestartPolicy: cmdRestartPolicy,
177+
Tty: tty,
178+
}
179+
180+
var body interface{} = c
181+
if !container {
182+
userPod := &apitype.UserPod{
183+
Id: name,
184+
Containers: []*apitype.UserContainer{c},
185+
Labels: labels,
186+
Resource: &apitype.UserResource{Vcpu: int32(cpu), Memory: int32(memory)},
187+
Log: &apitype.PodLogConfig{
188+
Type: cmdLogDriver,
189+
Config: logOpts,
190+
},
191+
}
192+
body = userPod
193+
}
194+
195+
jsonString, _ := json.Marshal(body)
196+
return string(jsonString), nil
197+
}
198+
199+
func parseVolume(volStr string) (*apitype.UserVolume, *apitype.UserVolumeReference, error) {
200+
201+
var (
202+
srcName string
203+
destPath string
204+
volName string
205+
readOnly = false
206+
volDriver = "vfs"
207+
)
208+
209+
fields := strings.Split(volStr, ":")
210+
if len(fields) == 3 {
211+
// cmd: -v host-src:container-dest:rw
212+
srcName = fields[0]
213+
destPath = fields[1]
214+
if fields[2] != "ro" && fields[2] != "rw" {
215+
return nil, nil, fmt.Errorf("flag only support(ro or rw): --volume")
216+
}
217+
if fields[2] == "ro" {
218+
readOnly = true
219+
}
220+
} else if len(fields) == 2 {
221+
// cmd: -v host-src:container-dest
222+
srcName = fields[0]
223+
destPath = fields[1]
224+
} else if len(fields) == 1 {
225+
// -v container-dest
226+
destPath = fields[0]
227+
} else {
228+
return nil, nil, fmt.Errorf("flag format should be like : --volume=[host-src:]container-dest[:rw|ro]")
229+
}
230+
231+
if !strings.HasPrefix(destPath, "/") {
232+
return nil, nil, fmt.Errorf("The container-dir must always be an absolute path")
233+
}
234+
235+
if srcName == "" {
236+
// Set default volume driver and use destPath as volume Name
237+
volDriver = ""
238+
_, volName = filepath.Split(destPath)
239+
} else {
240+
srcName, _ = filepath.Abs(srcName)
241+
_, volName = filepath.Split(srcName)
242+
// Auto create the source folder on the host , otherwise hyperd will complain
243+
if _, err := os.Stat(srcName); err != nil && os.IsNotExist(err) {
244+
if err := os.MkdirAll(srcName, os.FileMode(0777)); err != nil {
245+
return nil, nil, err
246+
}
247+
}
248+
}
249+
250+
vol := apitype.UserVolume{
251+
// Avoid name collision
252+
Name: volName + utils.RandStr(5, "number"),
253+
Source: srcName,
254+
Format: volDriver,
255+
}
256+
257+
volRef := apitype.UserVolumeReference{
258+
Volume: vol.Name,
259+
Path: destPath,
260+
ReadOnly: readOnly,
261+
}
262+
263+
return &vol, &volRef, nil
264+
}
265+
266+
func parsePortMapping(portmap string) (*apitype.UserContainerPort, error) {
267+
268+
var (
269+
port = apitype.UserContainerPort{}
270+
proto string
271+
hPort string
272+
cPort string
273+
err error
274+
)
275+
276+
fields := strings.Split(portmap, ":")
277+
if len(fields) < 2 {
278+
return nil, fmt.Errorf("flag needs host port and container port: --publish")
279+
} else if len(fields) == 2 {
280+
proto = "tcp"
281+
hPort = fields[0]
282+
cPort = fields[1]
283+
} else {
284+
proto = fields[0]
285+
if proto != "tcp" && proto != "udp" {
286+
return nil, fmt.Errorf("flag needs protocol(tcp or udp): --publish")
287+
}
288+
hPort = fields[1]
289+
cPort = fields[2]
290+
}
291+
292+
port.Protocol = proto
293+
hp, err := strconv.Atoi(hPort)
294+
port.HostPort = int32(hp)
295+
if err != nil {
296+
return nil, fmt.Errorf("flag needs host port and container port: --publish: %v", err)
297+
}
298+
cp, err := strconv.Atoi(cPort)
299+
port.ContainerPort = int32(cp)
300+
if err != nil {
301+
return nil, fmt.Errorf("flag needs host port and container port: --publish: %v", err)
302+
}
303+
304+
return &port, nil
305+
}
306+
307+
func imageToName(image string) string {
308+
name := image
309+
fields := strings.Split(image, "/")
310+
if len(fields) > 1 {
311+
name = fields[len(fields)-1]
312+
}
313+
fields = strings.Split(name, ":")
314+
if len(fields) < 2 {
315+
name = name + "-" + utils.RandStr(10, "number")
316+
} else {
317+
name = fields[0] + "-" + fields[1] + "-" + utils.RandStr(10, "number")
318+
}
319+
320+
validContainerNameChars := `[a-zA-Z0-9][a-zA-Z0-9_.-]`
321+
validContainerNamePattern := regexp.MustCompile(`^/?` + validContainerNameChars + `+$`)
322+
if !validContainerNamePattern.MatchString(name) {
323+
name = namesgenerator.GetRandomName(0)
324+
}
325+
return name
326+
}

0 commit comments

Comments
 (0)