docker.py 9.02 KB
Newer Older
1
from __future__ import unicode_literals
2
from fabric.api import task, roles, env, local, run
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
from fabric.colors import red, green

import helpers as h


###########################################################
# Helper functions to manage docker images and containers #
###########################################################

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 = []
    if containername and containername in docker_ps(running_only=True):
        print green("%s already running" % containername)
        return False
    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
    for from_path, to_path, canwrite in mounts:
        abspath = from_path
        opt = ' -v %s:%s' % (abspath, to_path)
        if not canwrite:
            opt += ':ro'
        opts += opt
    if containername:
        containername_opt = '--name %s' % containername
    else:
        containername_opt = ''
    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 = ''
        local('docker create %s --name %s %s' % (volume_args, containername, base_image))
        return True
    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()
    # image name is supposed to be the first column
    assert lines[0].strip().startswith('REPOSITORY')
    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:
123
            h.copy_public_ssh_keys(role)
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
            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)]):
                # If container was successful build, get the IP address and show it to the user.
142
                env.container_ip = h.fab_run(role, 'docker inspect -f "{{{{.NetworkSettings.IPAddress}}}}" '
143
                                             '{}_container'.format(env.project_name), capture=True)
144
                if env.interactive_mode:
145
                    h.fab_update_hosts(env.container_ip, env.site_hostname)
146

147
                    print(green('Docker container {}_container was build successful. '
148
149
                                'To visit the Website open a web browser in http://{} or '
                                'http://localhost:{}.'.format(env.project_name, env.site_hostname, env.bind_port)))
150

151
                h.fab_update_container_ip(env.container_ip)
152

153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
        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():
168
169
            if env.interactive_mode:
                h.fab_remove_from_hosts(env.site_hostname)
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
            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():
185
186
187
188

            if env.interactive_mode:
                h.fab_remove_from_hosts(env.site_hostname)
            
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
            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)))


@task()
@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, '
205
206
                      'you should stopped it after remove the image {}/drupal'.format(env.project_name,
                                                                                      env.project_name)))
207
208
209
210
211
212
213
214
215
216
        if '{}/drupal'.format(env.project_name) in docker_images():
            h.fab_run(role, 'docker rmi -f {}/drupal'.format(env.project_name))
            # 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')
            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)))


217
218
219
220
221
222
223
224
225
@task
@roles('docker')
def update_host():
    """
    Update hostname resolution in the container.
    """

    site_hostname = run("hostname")
    run("sed  '/{}/c\{} {}  localhost.domainlocal' "
226
        "/etc/hosts > /root/hosts.backup".format(env.container_ip, env.container_ip, site_hostname))
227
228
229
    run("cat /root/hosts.backup > /etc/hosts")

    h.fab_update_container_ip()