docker.py 9.14 KB
Newer Older
1
from __future__ import unicode_literals
2

3
4
import os

5
from fabric.api import task, roles, env, local, run, lcd, execute
6
from fabric.colors import red, green
Samuel Sirois's avatar
Samuel Sirois committed
7
from fabric.contrib.console import confirm
8
9
10

import helpers as h

11

12
13
14
15
###########################################################
# Helper functions to manage docker images and containers #
###########################################################

16

17
18
19
20
21
22
23
24
25
26
27
28
29
30
def docker_ps(running_only=False):
    args = '' if running_only else '-a'
    result = local('docker ps %s' % args, capture=True)
    lines = result.stdout.splitlines()
    # container name is supposed to be the last column
    assert lines[0].strip().endswith('NAMES')
    return [line.strip().split(' ')[-1] for line in lines[1:]]


def docker_tryrun(imgname, containername=None, opts='', mounts=None, cmd='', restart=True):
    # mounts is a list of (from, to, canwrite) path tuples. ``from`` is relative to the project root.
    # Returns True if the container was effectively ran (false if it was restarted or aborted)
    if not mounts:
        mounts = []
31

32
33
34
    if containername and containername in docker_ps(running_only=True):
        print green("%s already running" % containername)
        return False
35

36
37
38
39
40
41
42
43
44
    if containername and containername in docker_ps(running_only=False):
        if restart:
            print green("%s already exists and is stopped. Restarting!" % containername)
            local('docker restart %s' % containername)
            return True
        else:
            print red("There's a dangling container %s! That's not supposed to happen. Aborting" % containername)
            print "Run 'docker rm %s' to remove that container" % containername
            return False
45

46
47
48
49
50
    for from_path, to_path, canwrite in mounts:
        abspath = from_path
        opt = ' -v %s:%s' % (abspath, to_path)
        if not canwrite:
            opt += ':ro'
51

52
        opts += opt
53

54
55
56
57
    if containername:
        containername_opt = '--name %s' % containername
    else:
        containername_opt = ''
58

59
60
61
    opts += ' -e USER_ID={}'.format(os.getuid())
    opts += ' -e GROUP_ID={}'.format(os.getgid())

62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
    local('docker run %s %s %s %s' % (opts, containername_opt, imgname, cmd))
    return True


def docker_ensureruns(containername):
    # Makes sure that containername runs. If it doesn't, try restarting it. If the container
    # doesn't exist, spew an error.
    if containername not in docker_ps(running_only=True):
        if containername in docker_ps(running_only=False):
            local('docker restart %s' % containername)
            return True
        else:
            return False
    else:
        return True


def docker_ensure_data_container(containername, volume_paths=None, base_image='busybox'):
    # Make sure that we have our data containers running. Data containers are *never* removed.
    # Their only purpose is to hold volume data.
    # Returns whether a container was created by this call
    if containername not in docker_ps(running_only=False):
        if volume_paths:
            volume_args = ' '.join('-v %s' % volpath for volpath in volume_paths)
        else:
            volume_args = ''
88

89
90
        local('docker create %s --name %s %s' % (volume_args, containername, base_image))
        return True
91

92
93
94
95
96
97
98
99
100
101
102
103
104
105
    return False


def docker_isrunning(containername):
    # Check if the containername is running.
    if containername not in docker_ps(running_only=True):
        return False
    else:
        return True


def docker_images():
    result = local('docker images', capture=True)
    lines = result.stdout.splitlines()
106

107
108
    # image name is supposed to be the first column
    assert lines[0].strip().startswith('REPOSITORY')
109

110
111
112
113
114
    return [line.strip().split(' ')[0] for line in lines]


@task
@roles('local')
115
def connect():
116
117
118
119
    """
    Connect to a docker container using "docker -it exec <name> bash".
    This is a better way to connect to the container than using ssh'
    """
120
    with lcd(env.workspace):
121
        if docker_isrunning('{}_container'.format(env.project_name)):
122
            local('ssh drupalizer@{} -i {}'.format(env.container_ip, h.fab_path('docker/ssh/id_rsa')))
123
124
125
126
127
128
        else:
            print(red('Docker container {}_container is not running, it should be running to be able to connect.'))


@task
@roles('local')
129
def image_create():
130
131
132
    """
    Create docker images
    """
133
    with lcd(env.workspace):
134
135
136
        if '{}/drupal'.format(env.project_name) in docker_images():
            print(red('Docker image {}/drupal was found, you has already build this image'.format(env.project_name)))
        else:
137
138
            dockerfile = h.fab_path('Dockerfile')
            local('docker build -t {}/drupal -f {} .'.format(env.project_name, dockerfile))
139
140
141
142
143
            print(green('Docker image {}/drupal was build successful'.format(env.project_name)))


@task
@roles('local')
144
def container_start():
145
146
147
    """
    Run docker containers
    """
148
    with lcd(env.workspace):
149
150
151
152
153
        if '{}/drupal'.format(env.project_name) in docker_images():
            if docker_tryrun('{}/drupal'.format(env.project_name),
                             '{}_container'.format(env.project_name),
                             '-d -p {}:80'.format(env.bind_port),
                             mounts=[(env.workspace, env.docker_workspace, True)]):
154

155
                # If container was successful build, get the IP address and show it to the user.
156
                env.container_ip = local('docker inspect -f "{{{{.NetworkSettings.IPAddress}}}}" '
157
                                             '{}_container'.format(env.project_name), capture=True)
158

159
                if env.get('always_use_pty', True):
160
                    h.fab_update_hosts(env.container_ip, env.site_hostname)
161
                    print(green('Docker container {}_container was build successful. '
162
163
                                'To visit the Website open a web browser in http://{} or '
                                'http://localhost:{}.'.format(env.project_name, env.site_hostname, env.bind_port)))
164

165
                h.fab_update_container_ip(env.container_ip)
166
167
168
169
170
171
172
173
        else:
            print(red('Docker image {}/drupal not found and is a requirement to run the {}_container.'
                      'Please, run first "fab create" in order to build the {}/drupal '
                      'image'.format(env.project_name, env.project_name, env.project_name)))


@task
@roles('local')
174
def container_stop():
175
176
177
    """
    Stop docker containers
    """
178
    with lcd(env.workspace):
179
        if '{}_container'.format(env.project_name) in docker_ps():
180
            if env.get('always_use_pty', True):
181
                h.fab_remove_from_hosts(env.site_hostname)
182

183
            local('docker stop {}_container'.format(env.project_name))
184
185
186
187
188
189
190
            print(green('Docker container {}_container was successful stopped'.format(env.project_name)))
        else:
            print(red('Docker container {}_container was not running or paused'.format(env.project_name)))


@task
@roles('local')
191
def container_remove():
192
193
194
    """
    Stop docker containers
    """
195
    with lcd(env.workspace):
196
        if '{}_container'.format(env.project_name) in docker_ps():
197
            if env.get('always_use_pty', True):
198
                h.fab_remove_from_hosts(env.site_hostname)
199

200
            local('docker rm -f {}_container'.format(env.project_name))
201
202
203
204
205
            print(green('Docker container {}_container was successful removed'.format(env.project_name)))
        else:
            print(red('Docker container {}_container was already removed'.format(env.project_name)))


206
@task
207
@roles('local')
208
def image_remove():
209
210
211
    """
    Remove docker container and images
    """
212
    with lcd(env.workspace):
213
214
        if docker_isrunning('{}_container'.format(env.project_name)):
            print(red('Docker container {}_container is running, '
215
216
                      'you should stopped it after remove the image {}/drupal'.format(env.project_name,
                                                                                      env.project_name)))
217
        if '{}/drupal'.format(env.project_name) in docker_images():
218
            local('docker rmi -f {}/drupal'.format(env.project_name))
219

220
221
            # Remove dangling docker images to free space.
            if '<none>' in docker_images():
222
                local('docker images --filter="dangling=true" -q | xargs docker rmi -f')
223

224
225
226
227
228
            print(green('Docker image {}/drupal was successful removed'.format(env.project_name)))
        else:
            print(red('Docker image {}/drupal was not found'.format(env.project_name)))


Samuel Sirois's avatar
Samuel Sirois committed
229
230
231
232
@task
@roles('local')
def clean():
    """
233
    Clean docker workspace (removes container & images)
Samuel Sirois's avatar
Samuel Sirois committed
234
235
236
237
238
    """
    if (confirm(
            red('This will stop, remove container and delete docker image ' +
                'related to this project. Do you want to continue?'),
            default=False)):
239
240
241
        execute(container_stop)
        execute(container_remove)
        execute(image_remove)
Samuel Sirois's avatar
Samuel Sirois committed
242
243


244
245
246
247
248
249
250
251
@task
@roles('docker')
def update_host():
    """
    Update hostname resolution in the container.
    """
    site_hostname = run("hostname")
    run("sed  '/{}/c\{} {}  localhost.domainlocal' "
252
        "/etc/hosts > /root/hosts.backup".format(env.container_ip, env.container_ip, site_hostname))
253

254
255
256
    run("cat /root/hosts.backup > /etc/hosts")

    h.fab_update_container_ip()