1package generic
2
3import (
4	"errors"
5	"fmt"
6	"net"
7	"os"
8	"path"
9	"strconv"
10	"time"
11
12	"github.com/docker/machine/libmachine/drivers"
13	"github.com/docker/machine/libmachine/engine"
14	"github.com/docker/machine/libmachine/log"
15	"github.com/docker/machine/libmachine/mcnflag"
16	"github.com/docker/machine/libmachine/mcnutils"
17	"github.com/docker/machine/libmachine/state"
18)
19
20type Driver struct {
21	*drivers.BaseDriver
22	EnginePort int
23	SSHKey     string
24}
25
26const (
27	defaultTimeout = 1 * time.Second
28)
29
30// GetCreateFlags registers the flags this driver adds to
31// "docker hosts create"
32func (d *Driver) GetCreateFlags() []mcnflag.Flag {
33	return []mcnflag.Flag{
34		mcnflag.IntFlag{
35			Name:   "generic-engine-port",
36			Usage:  "Docker engine port",
37			Value:  engine.DefaultPort,
38			EnvVar: "GENERIC_ENGINE_PORT",
39		},
40		mcnflag.StringFlag{
41			Name:   "generic-ip-address",
42			Usage:  "IP Address of machine",
43			EnvVar: "GENERIC_IP_ADDRESS",
44		},
45		mcnflag.StringFlag{
46			Name:   "generic-ssh-user",
47			Usage:  "SSH user",
48			Value:  drivers.DefaultSSHUser,
49			EnvVar: "GENERIC_SSH_USER",
50		},
51		mcnflag.StringFlag{
52			Name:   "generic-ssh-key",
53			Usage:  "SSH private key path (if not provided, default SSH key will be used)",
54			Value:  "",
55			EnvVar: "GENERIC_SSH_KEY",
56		},
57		mcnflag.IntFlag{
58			Name:   "generic-ssh-port",
59			Usage:  "SSH port",
60			Value:  drivers.DefaultSSHPort,
61			EnvVar: "GENERIC_SSH_PORT",
62		},
63	}
64}
65
66// NewDriver creates and returns a new instance of the driver
67func NewDriver(hostName, storePath string) drivers.Driver {
68	return &Driver{
69		EnginePort: engine.DefaultPort,
70		BaseDriver: &drivers.BaseDriver{
71			MachineName: hostName,
72			StorePath:   storePath,
73		},
74	}
75}
76
77// DriverName returns the name of the driver
78func (d *Driver) DriverName() string {
79	return "generic"
80}
81
82func (d *Driver) GetSSHHostname() (string, error) {
83	return d.GetIP()
84}
85
86func (d *Driver) GetSSHUsername() string {
87	return d.SSHUser
88}
89
90func (d *Driver) GetSSHKeyPath() string {
91	return d.SSHKeyPath
92}
93
94func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
95	d.EnginePort = flags.Int("generic-engine-port")
96	d.IPAddress = flags.String("generic-ip-address")
97	d.SSHUser = flags.String("generic-ssh-user")
98	d.SSHKey = flags.String("generic-ssh-key")
99	d.SSHPort = flags.Int("generic-ssh-port")
100
101	if d.IPAddress == "" {
102		return errors.New("generic driver requires the --generic-ip-address option")
103	}
104
105	return nil
106}
107
108func (d *Driver) PreCreateCheck() error {
109	if d.SSHKey != "" {
110		if _, err := os.Stat(d.SSHKey); os.IsNotExist(err) {
111			return fmt.Errorf("SSH key does not exist: %q", d.SSHKey)
112		}
113
114		// TODO: validate the key is a valid key
115	}
116
117	return nil
118}
119
120func (d *Driver) Create() error {
121	if d.SSHKey == "" {
122		log.Info("No SSH key specified. Assuming an existing key at the default location.")
123	} else {
124		log.Info("Importing SSH key...")
125
126		d.SSHKeyPath = d.ResolveStorePath(path.Base(d.SSHKey))
127		if err := copySSHKey(d.SSHKey, d.SSHKeyPath); err != nil {
128			return err
129		}
130
131		if err := copySSHKey(d.SSHKey+".pub", d.SSHKeyPath+".pub"); err != nil {
132			log.Infof("Couldn't copy SSH public key : %s", err)
133		}
134	}
135
136	log.Debugf("IP: %s", d.IPAddress)
137
138	return nil
139}
140
141func (d *Driver) GetURL() (string, error) {
142	if err := drivers.MustBeRunning(d); err != nil {
143		return "", err
144	}
145
146	ip, err := d.GetIP()
147	if err != nil {
148		return "", err
149	}
150
151	return fmt.Sprintf("tcp://%s", net.JoinHostPort(ip, strconv.Itoa(d.EnginePort))), nil
152}
153
154func (d *Driver) GetState() (state.State, error) {
155	address := net.JoinHostPort(d.IPAddress, strconv.Itoa(d.SSHPort))
156
157	_, err := net.DialTimeout("tcp", address, defaultTimeout)
158	if err != nil {
159		return state.Stopped, nil
160	}
161
162	return state.Running, nil
163}
164
165func (d *Driver) Start() error {
166	return errors.New("generic driver does not support start")
167}
168
169func (d *Driver) Stop() error {
170	return errors.New("generic driver does not support stop")
171}
172
173func (d *Driver) Restart() error {
174	_, err := drivers.RunSSHCommandFromDriver(d, "sudo shutdown -r now")
175	return err
176}
177
178func (d *Driver) Kill() error {
179	return errors.New("generic driver does not support kill")
180}
181
182func (d *Driver) Remove() error {
183	return nil
184}
185
186func copySSHKey(src, dst string) error {
187	if err := mcnutils.CopyFile(src, dst); err != nil {
188		return fmt.Errorf("unable to copy ssh key: %s", err)
189	}
190
191	if err := os.Chmod(dst, 0600); err != nil {
192		return fmt.Errorf("unable to set permissions on the ssh key: %s", err)
193	}
194
195	return nil
196}
197