1package provision
2
3import (
4	"bytes"
5	"errors"
6	"fmt"
7	"regexp"
8	"text/template"
9
10	"github.com/docker/machine/libmachine/auth"
11	"github.com/docker/machine/libmachine/drivers"
12	"github.com/docker/machine/libmachine/engine"
13	"github.com/docker/machine/libmachine/log"
14	"github.com/docker/machine/libmachine/mcnutils"
15	"github.com/docker/machine/libmachine/provision/pkgaction"
16	"github.com/docker/machine/libmachine/provision/serviceaction"
17	"github.com/docker/machine/libmachine/swarm"
18)
19
20var (
21	ErrUnknownYumOsRelease = errors.New("unknown OS for Yum repository")
22
23	packageListTemplate = `[docker]
24name=Docker Stable Repository
25baseurl=https://yum.dockerproject.org/repo/main/{{.OsRelease}}/{{.OsReleaseVersion}}
26priority=1
27enabled=1
28gpgkey=https://yum.dockerproject.org/gpg
29`
30	engineConfigTemplate = `[Unit]
31Description=Docker Application Container Engine
32After=network.target
33
34[Service]
35Type=notify
36ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:{{.DockerPort}} -H unix:///var/run/docker.sock --storage-driver {{.EngineOptions.StorageDriver}} --tlsverify --tlscacert {{.AuthOptions.CaCertRemotePath}} --tlscert {{.AuthOptions.ServerCertRemotePath}} --tlskey {{.AuthOptions.ServerKeyRemotePath}} {{ range .EngineOptions.Labels }}--label {{.}} {{ end }}{{ range .EngineOptions.InsecureRegistry }}--insecure-registry {{.}} {{ end }}{{ range .EngineOptions.RegistryMirror }}--registry-mirror {{.}} {{ end }}{{ range .EngineOptions.ArbitraryFlags }}--{{.}} {{ end }}
37ExecReload=/bin/kill -s HUP $MAINPID
38MountFlags=slave
39LimitNOFILE=infinity
40LimitNPROC=infinity
41LimitCORE=infinity
42TimeoutStartSec=0
43Delegate=yes
44KillMode=process
45Environment={{range .EngineOptions.Env}}{{ printf "%q" . }} {{end}}
46
47[Install]
48WantedBy=multi-user.target
49`
50
51	majorVersionRE = regexp.MustCompile(`^(\d+)(\..*)?`)
52)
53
54type PackageListInfo struct {
55	OsRelease        string
56	OsReleaseVersion string
57}
58
59func init() {
60	Register("RedHat", &RegisteredProvisioner{
61		New: func(d drivers.Driver) Provisioner {
62			return NewRedHatProvisioner("rhel", d)
63		},
64	})
65}
66
67func NewRedHatProvisioner(osReleaseID string, d drivers.Driver) *RedHatProvisioner {
68	systemdProvisioner := NewSystemdProvisioner(osReleaseID, d)
69	systemdProvisioner.SSHCommander = RedHatSSHCommander{Driver: d}
70	return &RedHatProvisioner{
71		systemdProvisioner,
72	}
73}
74
75type RedHatProvisioner struct {
76	SystemdProvisioner
77}
78
79func (provisioner *RedHatProvisioner) String() string {
80	return "redhat"
81}
82
83func (provisioner *RedHatProvisioner) SetHostname(hostname string) error {
84	// we have to have SetHostname here as well to use the RedHat provisioner
85	// SSHCommand to add the tty allocation
86	if _, err := provisioner.SSHCommand(fmt.Sprintf(
87		"sudo hostname %s && echo %q | sudo tee /etc/hostname",
88		hostname,
89		hostname,
90	)); err != nil {
91		return err
92	}
93
94	if _, err := provisioner.SSHCommand(fmt.Sprintf(
95		"if grep -xq 127.0.1.1.* /etc/hosts; then sudo sed -i 's/^127.0.1.1.*/127.0.1.1 %s/g' /etc/hosts; else echo '127.0.1.1 %s' | sudo tee -a /etc/hosts; fi",
96		hostname,
97		hostname,
98	)); err != nil {
99		return err
100	}
101
102	return nil
103}
104
105func (provisioner *RedHatProvisioner) Package(name string, action pkgaction.PackageAction) error {
106	var packageAction string
107
108	switch action {
109	case pkgaction.Install:
110		packageAction = "install"
111	case pkgaction.Remove:
112		packageAction = "remove"
113	case pkgaction.Upgrade:
114		packageAction = "upgrade"
115	}
116
117	command := fmt.Sprintf("sudo -E yum %s -y %s", packageAction, name)
118
119	if _, err := provisioner.SSHCommand(command); err != nil {
120		return err
121	}
122
123	return nil
124}
125
126func installDocker(provisioner *RedHatProvisioner) error {
127	if err := installDockerGeneric(provisioner, provisioner.EngineOptions.InstallURL); err != nil {
128		return err
129	}
130
131	if err := provisioner.Service("docker", serviceaction.Restart); err != nil {
132		return err
133	}
134
135	if err := provisioner.Service("docker", serviceaction.Enable); err != nil {
136		return err
137	}
138
139	return nil
140}
141
142func (provisioner *RedHatProvisioner) dockerDaemonResponding() bool {
143	log.Debug("checking docker daemon")
144
145	if out, err := provisioner.SSHCommand("sudo docker version"); err != nil {
146		log.Warnf("Error getting SSH command to check if the daemon is up: %s", err)
147		log.Debugf("'sudo docker version' output:\n%s", out)
148		return false
149	}
150
151	// The daemon is up if the command worked.  Carry on.
152	return true
153}
154
155func (provisioner *RedHatProvisioner) Provision(swarmOptions swarm.Options, authOptions auth.Options, engineOptions engine.Options) error {
156	provisioner.SwarmOptions = swarmOptions
157	provisioner.AuthOptions = authOptions
158	provisioner.EngineOptions = engineOptions
159	swarmOptions.Env = engineOptions.Env
160
161	// set default storage driver for redhat
162	storageDriver, err := decideStorageDriver(provisioner, "devicemapper", engineOptions.StorageDriver)
163	if err != nil {
164		return err
165	}
166	provisioner.EngineOptions.StorageDriver = storageDriver
167
168	if err := provisioner.SetHostname(provisioner.Driver.GetMachineName()); err != nil {
169		return err
170	}
171
172	for _, pkg := range provisioner.Packages {
173		log.Debugf("installing base package: name=%s", pkg)
174		if err := provisioner.Package(pkg, pkgaction.Install); err != nil {
175			return err
176		}
177	}
178
179	// update OS -- this is needed for libdevicemapper and the docker install
180	if _, err := provisioner.SSHCommand("sudo -E yum -y update -x docker-*"); err != nil {
181		return err
182	}
183
184	// install docker
185	if err := installDocker(provisioner); err != nil {
186		return err
187	}
188
189	if err := mcnutils.WaitFor(provisioner.dockerDaemonResponding); err != nil {
190		return err
191	}
192
193	if err := makeDockerOptionsDir(provisioner); err != nil {
194		return err
195	}
196
197	provisioner.AuthOptions = setRemoteAuthOptions(provisioner)
198
199	if err := ConfigureAuth(provisioner); err != nil {
200		return err
201	}
202
203	if err := configureSwarm(provisioner, swarmOptions, provisioner.AuthOptions); err != nil {
204		return err
205	}
206
207	return nil
208}
209
210func (provisioner *RedHatProvisioner) GenerateDockerOptions(dockerPort int) (*DockerOptions, error) {
211	var (
212		engineCfg  bytes.Buffer
213		configPath = provisioner.DaemonOptionsFile
214	)
215
216	driverNameLabel := fmt.Sprintf("provider=%s", provisioner.Driver.DriverName())
217	provisioner.EngineOptions.Labels = append(provisioner.EngineOptions.Labels, driverNameLabel)
218
219	// systemd / redhat will not load options if they are on newlines
220	// instead, it just continues with a different set of options; yeah...
221	t, err := template.New("engineConfig").Parse(engineConfigTemplate)
222	if err != nil {
223		return nil, err
224	}
225
226	engineConfigContext := EngineConfigContext{
227		DockerPort:       dockerPort,
228		AuthOptions:      provisioner.AuthOptions,
229		EngineOptions:    provisioner.EngineOptions,
230		DockerOptionsDir: provisioner.DockerOptionsDir,
231	}
232
233	t.Execute(&engineCfg, engineConfigContext)
234
235	daemonOptsDir := configPath
236	return &DockerOptions{
237		EngineOptions:     engineCfg.String(),
238		EngineOptionsPath: daemonOptsDir,
239	}, nil
240}
241