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

3
from fabric.api import task, roles, env, local, run
4
5
6
7
from fabric.colors import red, green

import helpers as h

8

9
10
11
12
###########################################################
# Helper functions to manage docker images and containers #
###########################################################

13

14
15
16
17
18
19
20
21
22
23
24
25
26
27
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 = []
28

29
30
31
    if containername and containername in docker_ps(running_only=True):
        print green("%s already running" % containername)
        return False
32

33
34
35
36
37
38
39
40
41
    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
42

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

49
        opts += opt
50

51
52
53
54
    if containername:
        containername_opt = '--name %s' % containername
    else:
        containername_opt = ''
55

56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
    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 = ''
82

83
84
        local('docker create %s --name %s %s' % (volume_args, containername, base_image))
        return True
85

86
87
88
89
90
91
92
93
94
95
96
97
98
99
    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()
100

101
102
    # image name is supposed to be the first column
    assert lines[0].strip().startswith('REPOSITORY')
103

104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
    return [line.strip().split(' ')[0] for line in lines]


@task
@roles('local')
def connect(role='local'):
    """
    Connect to a docker container using "docker -it exec <name> bash".
    This is a better way to connect to the container than using ssh'
    :param role Default 'role' where to run the task
    """
    with h.fab_cd(role, env.workspace):
        if docker_isrunning('{}_container'.format(env.project_name)):
            h.fab_run(role, 'docker exec -it {}_container bash'.format(env.project_name))
        else:
            print(red('Docker container {}_container is not running, it should be running to be able to connect.'))


@task
@roles('local')
def image_create(role='local'):
    """
    Create docker images
    :param role Default 'role' where to run the task
    """
    with h.fab_cd(role, env.workspace):
        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:
133
            h.copy_public_ssh_keys(role)
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
            h.fab_run(role, 'docker build -t {}/drupal .'.format(env.project_name))
            print(green('Docker image {}/drupal was build successful'.format(env.project_name)))


@task
@roles('local')
def container_start(role='local'):
    """
    Run docker containers
    :param role Default 'role' where to run the task
    """
    with h.fab_cd(role, env.workspace):
        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)]):
151

152
                # If container was successful build, get the IP address and show it to the user.
153
                env.container_ip = h.fab_run(role, 'docker inspect -f "{{{{.NetworkSettings.IPAddress}}}}" '
154
                                             '{}_container'.format(env.project_name), capture=True)
155

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

162
                h.fab_update_container_ip(env.container_ip)
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
        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')
def container_stop(role='local'):
    """
    Stop docker containers
    :param role Default 'role' where to run the task
    """
    with h.fab_cd(role, env.workspace):
        if '{}_container'.format(env.project_name) in docker_ps():
178
            if env.get('always_use_pty', True):
179
                h.fab_remove_from_hosts(env.site_hostname)
180

181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
            h.fab_run(role, 'docker stop {}_container'.format(env.project_name))
            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')
def container_remove(role='local'):
    """
    Stop docker containers
    :param role Default 'role' where to run the task
    """
    with h.fab_cd(role, env.workspace):
        if '{}_container'.format(env.project_name) in docker_ps():
196

197
            if env.get('always_use_pty', True):
198
                h.fab_remove_from_hosts(env.site_hostname)
199

200
201
202
203
204
205
            h.fab_run(role, 'docker rm -f {}_container'.format(env.project_name))
            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
208
209
210
211
212
213
214
215
@roles('local')
def image_remove(role='local'):
    """
    Remove docker container and images
    :param role Default 'role' where to run the task
    """
    with h.fab_cd(role, env.workspace):
        if docker_isrunning('{}_container'.format(env.project_name)):
            print(red('Docker container {}_container is running, '
216
217
                      'you should stopped it after remove the image {}/drupal'.format(env.project_name,
                                                                                      env.project_name)))
218
219
        if '{}/drupal'.format(env.project_name) in docker_images():
            h.fab_run(role, 'docker rmi -f {}/drupal'.format(env.project_name))
220

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

225
226
227
228
229
            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)))


230
231
232
233
234
235
236
237
@task
@roles('docker')
def update_host():
    """
    Update hostname resolution in the container.
    """
    site_hostname = run("hostname")
    run("sed  '/{}/c\{} {}  localhost.domainlocal' "
238
        "/etc/hosts > /root/hosts.backup".format(env.container_ip, env.container_ip, site_hostname))
239

240
241
242
    run("cat /root/hosts.backup > /etc/hosts")

    h.fab_update_container_ip()