Skip to content

Commit

Permalink
Deployment script
Browse files Browse the repository at this point in the history
Factor some bits out of redeploy.py, so that they can be used in a deployment
script suitable for riot.im/app.
  • Loading branch information
richvdh committed Jan 17, 2017
1 parent 641a5c2 commit 27851a8
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 83 deletions.
156 changes: 156 additions & 0 deletions scripts/deploy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
#!/usr/bin/env python
#
# download and unpack a riot-web tarball.
#
# Allows `bundles` to be extracted to a common directory, and a link to
# config.json to be added.

from __future__ import print_function

import argparse
import os
import os.path
import tarfile
import urllib

class DeployException(Exception):
pass

def create_relative_symlink(linkname, target):
relpath = os.path.relpath(target, os.path.dirname(linkname))
print ("Symlink %s -> %s" % (linkname, relpath))
os.symlink(relpath, linkname)


def move_bundles(source, dest):
"""Move the contents of the 'bundles' directory to a common dir
We check that we will not be overwriting anything before we proceed.
Args:
source (str): path to 'bundles' within the extracted tarball
dest (str): target common directory
"""

if not os.path.isdir(dest):
os.mkdir(dest)

# build a map from source to destination, checking for non-existence as we go.
renames = {}
for f in os.listdir(source):
dst = os.path.join(dest, f)
if os.path.exists(dst):
raise DeployException(
"Not deploying. The bundle includes '%s' which we have previously deployed."
% f
)
renames[os.path.join(source, f)] = dst

for (src, dst) in renames.iteritems():
print ("Move %s -> %s" % (src, dst))
os.rename(src, dst)

class Deployer:
def __init__(self):
self.packages_path = "."
self.bundles_path = None
self.should_clean = False
self.config_location = None

def deploy(self, tarball, extract_path):
"""Download a tarball if necessary, and unpack it
Returns:
(str) the path to the unpacked deployment
"""
print("Deploying %s to %s" % (tarball, extract_path))

downloaded = False
if tarball.startswith("http://") or tarball.startswith("https://"):
tarball = self.download_file(tarball)
print("Downloaded file: %s" % tarball)
downloaded = True

try:
with tarfile.open(tarball) as tar:
tar.extractall(extract_path)
finally:
if self.should_clean and downloaded:
os.remove(tarball)

name_str = os.path.basename(tarball).replace(".tar.gz", "")
extracted_dir = os.path.join(extract_path, name_str)
print ("Extracted into: %s" % extracted_dir)

if self.config_location:
create_relative_symlink(
target=self.config_location,
linkname=os.path.join(extracted_dir, 'config.json')
)

if self.bundles_path:
extracted_bundles = os.path.join(extracted_dir, 'bundles')
move_bundles(source=extracted_bundles, dest=self.bundles_path)

# replace the (hopefully now empty) extracted_bundles dir with a
# symlink to the common dir.
os.rmdir(extracted_bundles)
create_relative_symlink(
target=self.bundles_path,
linkname=extracted_bundles,
)
return extracted_dir

def download_file(self, url):
local_filename = os.path.join(self.packages_path,
url.split('/')[-1])
urllib.urlretrieve(url, local_filename)
return local_filename

if __name__ == "__main__":
parser = argparse.ArgumentParser("Deploy a Riot build on a web server.")
parser.add_argument(
"-p", "--packages-dir", dest="packages_dir", default="./packages", help=(
"The directory to download the tarball into. (Default: '%(default)s')"
)
)
parser.add_argument(
"-e", "--extract", dest="extract_path", default="./deploys", help=(
"The location to extract .tar.gz files to. (Default: '%(default)s')"
)
)
parser.add_argument(
"-b", "--bundles-dir", dest="bundles_dir", help=(
"A directory to move the contents of the 'bundles' directory to. A \
symlink to the bundles directory will also be written inside the \
extracted tarball. Example: './bundles'. \
(Default: bundles will not be moved.)"
)
)
parser.add_argument(
"-c", "--clean", dest="clean", action="store_true", default=False, help=(
"Remove .tar.gz files after they have been downloaded and extracted. \
(Default: %(default)s)"
)
)
parser.add_argument(
"--config", dest="config", help=(
"Write a symlink at config.json in the extracted tarball to this \
location. (Default: no config.json symlink.)"
)
)
parser.add_argument(
"tarball", help=(
"filename of tarball, or URL to download."
),
)

args = parser.parse_args()

deployer = Deployer()
deployer.packages_path = args.packages_dir
deployer.bundles_path = args.bundles_dir
deployer.should_clean = args.clean
deployer.config_location = args.config

deployer.deploy(args.tarball, args.extract_path)
107 changes: 24 additions & 83 deletions scripts/redeploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,17 @@
import json, requests, tarfile, argparse, os, errno
import time
from urlparse import urljoin

from flask import Flask, jsonify, request, abort

from deploy import Deployer, DeployException

app = Flask(__name__)

arg_jenkins_url = None
deployer = None
arg_extract_path = None
arg_bundles_path = None
arg_should_clean = None
arg_symlink = None
arg_config_location = None

class DeployException(Exception):
pass

def download_file(url):
local_filename = url.split('/')[-1]
r = requests.get(url, stream=True)
with open(local_filename, 'wb') as f:
for chunk in r.iter_content(chunk_size=1024):
if chunk: # filter out keep-alive new chunks
f.write(chunk)
return local_filename

def untar_to(tarball, dest):
with tarfile.open(tarball) as tar:
tar.extractall(dest)

def create_symlink(source, linkname):
try:
Expand Down Expand Up @@ -137,17 +122,20 @@ def fetch_jenkins_build(job_name, build_num):
# see half-written files.
build_dir = os.path.join(arg_extract_path, "%s-#%s" % (job_name, build_num))
try:
deploy_tarball(tar_gz_url, build_dir)
extracted_dir = deploy_tarball(tar_gz_url, build_dir)
except DeployException as e:
abort(400, e.message)

create_symlink(source=extracted_dir, linkname=arg_symlink)

return jsonify({})

def deploy_tarball(tar_gz_url, build_dir):
"""Download a tarball from jenkins and deploy it as the new version
"""
print("Deploying %s to %s" % (tar_gz_url, build_dir))
"""Download a tarball from jenkins and unpack it
Returns:
(str) the path to the unpacked deployment
"""
if os.path.exists(build_dir):
raise DeployException(
"Not deploying. We have previously deployed this build."
Expand All @@ -156,62 +144,8 @@ def deploy_tarball(tar_gz_url, build_dir):

# we rely on the fact that flask only serves one request at a time to
# ensure that we do not overwrite a tarball from a concurrent request.
filename = download_file(tar_gz_url)
print("Downloaded file: %s" % filename)

try:
untar_to(filename, build_dir)
print("Extracted to: %s" % build_dir)
finally:
if arg_should_clean:
os.remove(filename)

name_str = filename.replace(".tar.gz", "")
extracted_dir = os.path.join(build_dir, name_str)

if arg_config_location:
create_symlink(source=arg_config_location, linkname=os.path.join(extracted_dir, 'config.json'))

if arg_bundles_path:
extracted_bundles = os.path.join(extracted_dir, 'bundles')
move_bundles(source=extracted_bundles, dest=arg_bundles_path)

# replace the (hopefully now empty) extracted_bundles dir with a
# symlink to the common dir.
relpath = os.path.relpath(arg_bundles_path, extracted_dir)
os.rmdir(extracted_bundles)
print ("Symlink %s -> %s" % (extracted_bundles, relpath))
os.symlink(relpath, extracted_bundles)

create_symlink(source=extracted_dir, linkname=arg_symlink)

def move_bundles(source, dest):
"""Move the contents of the 'bundles' directory to a common dir
We check that we will not be overwriting anything before we proceed.
Args:
source (str): path to 'bundles' within the extracted tarball
dest (str): target common directory
"""

if not os.path.isdir(dest):
os.mkdir(dest)

# build a map from source to destination, checking for non-existence as we go.
renames = {}
for f in os.listdir(source):
dst = os.path.join(dest, f)
if os.path.exists(dst):
raise DeployException(
"Not deploying. The bundle includes '%s' which we have previously deployed."
% f
)
renames[os.path.join(source, f)] = dst

for (src, dst) in renames.iteritems():
print ("Move %s -> %s" % (src, dst))
os.rename(src, dst)
return deployer.deploy(tar_gz_url, build_dir)


if __name__ == "__main__":
Expand Down Expand Up @@ -270,21 +204,28 @@ def move_bundles(source, dest):
else:
arg_jenkins_url = args.jenkins + "/"
arg_extract_path = args.extract
arg_bundles_path = args.bundles_dir
arg_should_clean = args.clean
arg_symlink = args.symlink
arg_config_location = args.config

if not os.path.isdir(arg_extract_path):
os.mkdir(arg_extract_path)

deployer = Deployer()
deployer.bundles_path = args.bundles_dir
deployer.should_clean = args.clean
deployer.config_location = args.config

if args.tarball_uri is not None:
build_dir = os.path.join(arg_extract_path, "test-%i" % (time.time()))
deploy_tarball(args.tarball_uri, build_dir)
else:
print(
"Listening on port %s. Extracting to %s%s. Symlinking to %s. Jenkins URL: %s. Config location: %s" %
(args.port, arg_extract_path,
" (clean after)" if arg_should_clean else "", arg_symlink, arg_jenkins_url, arg_config_location)
(args.port,
arg_extract_path,
" (clean after)" if deployer.should_clean else "",
arg_symlink,
arg_jenkins_url,
deployer.config_location,
)
)
app.run(host="0.0.0.0", port=args.port, debug=True)

0 comments on commit 27851a8

Please sign in to comment.