| ... | ... |
@@ -1,14 +1,16 @@ |
| 1 | 1 |
# VERSION: 0.22 |
| 2 | 2 |
# DOCKER-VERSION 0.6.3 |
| 3 | 3 |
# AUTHOR: Daniel Mizyrycki <daniel@dotcloud.com> |
| 4 |
-# DESCRIPTION: Deploy docker-ci on Amazon EC2 |
|
| 4 |
+# DESCRIPTION: Deploy docker-ci on Digital Ocean |
|
| 5 | 5 |
# COMMENTS: |
| 6 | 6 |
# CONFIG_JSON is an environment variable json string loaded as: |
| 7 | 7 |
# |
| 8 | 8 |
# export CONFIG_JSON=' |
| 9 |
-# { "AWS_TAG": "EC2_instance_name",
|
|
| 10 |
-# "AWS_ACCESS_KEY": "EC2_access_key", |
|
| 11 |
-# "AWS_SECRET_KEY": "EC2_secret_key", |
|
| 9 |
+# { "DROPLET_NAME": "docker-ci",
|
|
| 10 |
+# "DO_CLIENT_ID": "Digital_Ocean_client_id", |
|
| 11 |
+# "DO_API_KEY": "Digital_Ocean_api_key", |
|
| 12 |
+# "DOCKER_KEY_ID": "Digital_Ocean_ssh_key_id", |
|
| 13 |
+# "DOCKER_CI_KEY_PATH": "docker-ci_private_key_path", |
|
| 12 | 14 |
# "DOCKER_CI_PUB": "$(cat docker-ci_ssh_public_key.pub)", |
| 13 | 15 |
# "DOCKER_CI_KEY": "$(cat docker-ci_ssh_private_key.key)", |
| 14 | 16 |
# "BUILDBOT_PWD": "Buildbot_server_password", |
| ... | ... |
@@ -37,7 +39,7 @@ run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' \ |
| 37 | 37 |
> /etc/apt/sources.list |
| 38 | 38 |
run apt-get update; apt-get install -y git python2.7 python-dev libevent-dev \ |
| 39 | 39 |
python-pip ssh rsync less vim |
| 40 |
-run pip install boto fabric |
|
| 40 |
+run pip install requests fabric |
|
| 41 | 41 |
|
| 42 | 42 |
# Add deployment code and set default container command |
| 43 | 43 |
add . /docker-ci |
| ... | ... |
@@ -1,11 +1,11 @@ |
| 1 | 1 |
#!/usr/bin/env python |
| 2 | 2 |
|
| 3 |
-import os, sys, re, json, base64 |
|
| 4 |
-from boto.ec2.connection import EC2Connection |
|
| 3 |
+import os, sys, re, json, requests, base64 |
|
| 5 | 4 |
from subprocess import call |
| 6 | 5 |
from fabric import api |
| 7 | 6 |
from fabric.api import cd, run, put, sudo |
| 8 | 7 |
from os import environ as env |
| 8 |
+from datetime import datetime |
|
| 9 | 9 |
from time import sleep |
| 10 | 10 |
|
| 11 | 11 |
# Remove SSH private key as it needs more processing |
| ... | ... |
@@ -20,42 +20,41 @@ for key in CONFIG: |
| 20 | 20 |
env['DOCKER_CI_KEY'] = re.sub('^.+"DOCKER_CI_KEY".+?"(.+?)".+','\\1',
|
| 21 | 21 |
env['CONFIG_JSON'],flags=re.DOTALL) |
| 22 | 22 |
|
| 23 |
- |
|
| 24 |
-AWS_TAG = env.get('AWS_TAG','docker-ci')
|
|
| 25 |
-AWS_KEY_NAME = 'dotcloud-dev' # Same as CONFIG_JSON['DOCKER_CI_PUB'] |
|
| 26 |
-AWS_AMI = 'ami-d582d6bc' # Ubuntu 13.04 |
|
| 27 |
-AWS_REGION = 'us-east-1' |
|
| 28 |
-AWS_TYPE = 'm1.small' |
|
| 29 |
-AWS_SEC_GROUPS = 'gateway' |
|
| 30 |
-AWS_IMAGE_USER = 'ubuntu' |
|
| 23 |
+DROPLET_NAME = env.get('DROPLET_NAME','docker-ci')
|
|
| 24 |
+TIMEOUT = 120 # Seconds before timeout droplet creation |
|
| 25 |
+IMAGE_ID = 1004145 # Docker on Ubuntu 13.04 |
|
| 26 |
+REGION_ID = 4 # New York 2 |
|
| 27 |
+SIZE_ID = 62 # memory 2GB |
|
| 28 |
+DO_IMAGE_USER = 'root' # Image user on Digital Ocean |
|
| 29 |
+API_URL = 'https://api.digitalocean.com/' |
|
| 31 | 30 |
DOCKER_PATH = '/go/src/github.com/dotcloud/docker' |
| 32 | 31 |
DOCKER_CI_PATH = '/docker-ci' |
| 33 | 32 |
CFG_PATH = '{}/buildbot'.format(DOCKER_CI_PATH)
|
| 34 | 33 |
|
| 35 | 34 |
|
| 36 |
-class AWS_EC2: |
|
| 37 |
- '''Amazon EC2''' |
|
| 38 |
- def __init__(self, access_key, secret_key): |
|
| 35 |
+class digital_ocean(): |
|
| 36 |
+ |
|
| 37 |
+ def __init__(self, key, client): |
|
| 39 | 38 |
'''Set default API parameters''' |
| 40 |
- self.handler = EC2Connection(access_key, secret_key) |
|
| 41 |
- def create_instance(self, tag, instance_type): |
|
| 42 |
- reservation = self.handler.run_instances(**instance_type) |
|
| 43 |
- instance = reservation.instances[0] |
|
| 44 |
- sleep(10) |
|
| 45 |
- while instance.state != 'running': |
|
| 46 |
- sleep(5) |
|
| 47 |
- instance.update() |
|
| 48 |
- print "Instance state: %s" % (instance.state) |
|
| 49 |
- instance.add_tag("Name",tag)
|
|
| 50 |
- print "instance %s done!" % (instance.id) |
|
| 51 |
- return instance.ip_address |
|
| 52 |
- def get_instances(self): |
|
| 53 |
- return self.handler.get_all_instances() |
|
| 54 |
- def get_tags(self): |
|
| 55 |
- return dict([(i.instances[0].id, i.instances[0].tags['Name']) |
|
| 56 |
- for i in self.handler.get_all_instances() if i.instances[0].tags]) |
|
| 57 |
- def del_instance(self, instance_id): |
|
| 58 |
- self.handler.terminate_instances(instance_ids=[instance_id]) |
|
| 39 |
+ self.key = key |
|
| 40 |
+ self.client = client |
|
| 41 |
+ self.api_url = API_URL |
|
| 42 |
+ |
|
| 43 |
+ def api(self, cmd_path, api_arg={}):
|
|
| 44 |
+ '''Make api call''' |
|
| 45 |
+ api_arg.update({'api_key':self.key, 'client_id':self.client})
|
|
| 46 |
+ resp = requests.get(self.api_url + cmd_path, params=api_arg).text |
|
| 47 |
+ resp = json.loads(resp) |
|
| 48 |
+ if resp['status'] != 'OK': |
|
| 49 |
+ raise Exception(resp['error_message']) |
|
| 50 |
+ return resp |
|
| 51 |
+ |
|
| 52 |
+ def droplet_data(self, name): |
|
| 53 |
+ '''Get droplet data''' |
|
| 54 |
+ data = self.api('droplets')
|
|
| 55 |
+ data = [droplet for droplet in data['droplets'] |
|
| 56 |
+ if droplet['name'] == name] |
|
| 57 |
+ return data[0] if data else {}
|
|
| 59 | 58 |
|
| 60 | 59 |
|
| 61 | 60 |
def json_fmt(data): |
| ... | ... |
@@ -63,20 +62,36 @@ def json_fmt(data): |
| 63 | 63 |
return json.dumps(data, sort_keys = True, indent = 2) |
| 64 | 64 |
|
| 65 | 65 |
|
| 66 |
-# Create EC2 API handler |
|
| 67 |
-ec2 = AWS_EC2(env['AWS_ACCESS_KEY'], env['AWS_SECRET_KEY']) |
|
| 66 |
+do = digital_ocean(env['DO_API_KEY'], env['DO_CLIENT_ID']) |
|
| 68 | 67 |
|
| 69 |
-# Stop processing if AWS_TAG exists on EC2 |
|
| 70 |
-if AWS_TAG in ec2.get_tags().values(): |
|
| 71 |
- print ('Instance: {} already deployed. Not further processing.'
|
|
| 72 |
- .format(AWS_TAG)) |
|
| 68 |
+# Get DROPLET_NAME data |
|
| 69 |
+data = do.droplet_data(DROPLET_NAME) |
|
| 70 |
+ |
|
| 71 |
+# Stop processing if DROPLET_NAME exists on Digital Ocean |
|
| 72 |
+if data: |
|
| 73 |
+ print ('Droplet: {} already deployed. Not further processing.'
|
|
| 74 |
+ .format(DROPLET_NAME)) |
|
| 73 | 75 |
exit(1) |
| 74 | 76 |
|
| 75 |
-ip = ec2.create_instance(AWS_TAG, {'image_id':AWS_AMI, 'instance_type':AWS_TYPE,
|
|
| 76 |
- 'security_groups':[AWS_SEC_GROUPS], 'key_name':AWS_KEY_NAME}) |
|
| 77 |
+# Create droplet |
|
| 78 |
+do.api('droplets/new', {'name':DROPLET_NAME, 'region_id':REGION_ID,
|
|
| 79 |
+ 'image_id':IMAGE_ID, 'size_id':SIZE_ID, |
|
| 80 |
+ 'ssh_key_ids':[env['DOCKER_KEY_ID']]}) |
|
| 81 |
+ |
|
| 82 |
+# Wait for droplet to be created. |
|
| 83 |
+start_time = datetime.now() |
|
| 84 |
+while (data.get('status','') != 'active' and (
|
|
| 85 |
+ datetime.now()-start_time).seconds < TIMEOUT): |
|
| 86 |
+ data = do.droplet_data(DROPLET_NAME) |
|
| 87 |
+ print data['status'] |
|
| 88 |
+ sleep(3) |
|
| 77 | 89 |
|
| 78 |
-# Wait 30 seconds for the machine to boot |
|
| 79 |
-sleep(30) |
|
| 90 |
+# Wait for the machine to boot |
|
| 91 |
+sleep(15) |
|
| 92 |
+ |
|
| 93 |
+# Get droplet IP |
|
| 94 |
+ip = str(data['ip_address']) |
|
| 95 |
+print 'droplet: {} ip: {}'.format(DROPLET_NAME, ip)
|
|
| 80 | 96 |
|
| 81 | 97 |
# Create docker-ci ssh private key so docker-ci docker container can communicate |
| 82 | 98 |
# with its EC2 instance |
| ... | ... |
@@ -86,7 +101,7 @@ os.chmod('/root/.ssh/id_rsa',0600)
|
| 86 | 86 |
open('/root/.ssh/config','w').write('StrictHostKeyChecking no\n')
|
| 87 | 87 |
|
| 88 | 88 |
api.env.host_string = ip |
| 89 |
-api.env.user = AWS_IMAGE_USER |
|
| 89 |
+api.env.user = DO_IMAGE_USER |
|
| 90 | 90 |
api.env.key_filename = '/root/.ssh/id_rsa' |
| 91 | 91 |
|
| 92 | 92 |
# Correct timezone |
| ... | ... |
@@ -106,13 +121,11 @@ open(DOCKER_CI_PATH + '/nightlyrelease/release_credentials.json', 'w').write( |
| 106 | 106 |
|
| 107 | 107 |
# Transfer docker |
| 108 | 108 |
sudo('mkdir -p ' + DOCKER_CI_PATH)
|
| 109 |
-sudo('chown {}.{} {}'.format(AWS_IMAGE_USER, AWS_IMAGE_USER, DOCKER_CI_PATH))
|
|
| 110 |
-call('/usr/bin/rsync -aH {} {}@{}:{}'.format(DOCKER_CI_PATH, AWS_IMAGE_USER, ip,
|
|
| 109 |
+sudo('chown {}.{} {}'.format(DO_IMAGE_USER, DO_IMAGE_USER, DOCKER_CI_PATH))
|
|
| 110 |
+call('/usr/bin/rsync -aH {} {}@{}:{}'.format(DOCKER_CI_PATH, DO_IMAGE_USER, ip,
|
|
| 111 | 111 |
os.path.dirname(DOCKER_CI_PATH)), shell=True) |
| 112 | 112 |
|
| 113 | 113 |
# Install Docker and Buildbot dependencies |
| 114 |
-sudo('addgroup docker')
|
|
| 115 |
-sudo('usermod -a -G docker ubuntu')
|
|
| 116 | 114 |
sudo('mkdir /mnt/docker; ln -s /mnt/docker /var/lib/docker')
|
| 117 | 115 |
sudo('wget -q -O - https://get.docker.io/gpg | apt-key add -')
|
| 118 | 116 |
sudo('echo deb https://get.docker.io/ubuntu docker main >'
|
| ... | ... |
@@ -122,7 +135,7 @@ sudo('echo -e "deb http://archive.ubuntu.com/ubuntu raring main universe\n'
|
| 122 | 122 |
' > /etc/apt/sources.list; apt-get update') |
| 123 | 123 |
sudo('DEBIAN_FRONTEND=noninteractive apt-get install -q -y wget python-dev'
|
| 124 | 124 |
' python-pip supervisor git mercurial linux-image-extra-$(uname -r)' |
| 125 |
- ' aufs-tools make libfontconfig libevent-dev') |
|
| 125 |
+ ' aufs-tools make libfontconfig libevent-dev libsqlite3-dev libssl-dev') |
|
| 126 | 126 |
sudo('wget -O - https://go.googlecode.com/files/go1.1.2.linux-amd64.tar.gz | '
|
| 127 | 127 |
'tar -v -C /usr/local -xz; ln -s /usr/local/go/bin/go /usr/bin/go') |
| 128 | 128 |
sudo('GOPATH=/go go get -d github.com/dotcloud/docker')
|
| ... | ... |
@@ -137,9 +150,6 @@ sudo('curl -s https://phantomjs.googlecode.com/files/'
|
| 137 | 137 |
# Preventively reboot docker-ci daily |
| 138 | 138 |
sudo('ln -s /sbin/reboot /etc/cron.daily')
|
| 139 | 139 |
|
| 140 |
-# Preventively reboot docker-ci daily |
|
| 141 |
-sudo('ln -s /sbin/reboot /etc/cron.daily')
|
|
| 142 |
- |
|
| 143 | 140 |
# Build docker-ci containers |
| 144 | 141 |
sudo('cd {}; docker build -t docker .'.format(DOCKER_PATH))
|
| 145 | 142 |
sudo('cd {}; docker build -t docker-ci .'.format(DOCKER_CI_PATH))
|