Skip to content

Commit

Permalink
Adds initial builder-focal
Browse files Browse the repository at this point in the history
This will enable creating the builder image sd-builder-focal
from the Dockerfile in this commit. We can then update the next
image_hash in the next commit.
  • Loading branch information
kushaldas committed Aug 12, 2020
1 parent 172138e commit 30bf0bf
Show file tree
Hide file tree
Showing 16 changed files with 1,180 additions and 0 deletions.
46 changes: 46 additions & 0 deletions molecule/builder-focal/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# ubuntu:focal-20200720
FROM ubuntu@sha256:60f560e52264ed1cb7829a0d59b1ee7740d7580e0eb293aca2d722136edb1e24


# additional meta-data makes it easier to clean up, find
LABEL org="Freedom of the Press"
LABEL image_name="focal-sd-builder-app"
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get -y update && apt-get upgrade -y && apt-get install -y \
apache2-dev \
aptitude \
coreutils \
debhelper \
devscripts \
dh-python \
dh-systemd \
gdb \
git \
gnupg2 \
haveged \
inotify-tools \
libffi-dev \
libssl-dev \
make \
ntp \
paxctl \
python3-all \
python3-pip \
python3-setuptools \
rsync \
ruby \
sqlite \
sudo \
tzdata \
libevent-dev \
unzip

# TEMPORARY: install dh-virtualenv from debian unstable
# No pubkey verification is performed on this package, using only for
# research spike on focal package support.
RUN curl -s https://deb.debian.org/debian/pool/main/d/dh-virtualenv/dh-virtualenv_1.2-1_all.deb -o /tmp/dh-virtualenv_1.2-1_all.deb
RUN apt install -y /tmp/dh-virtualenv_1.2-1_all.deb

RUN paxctl -cm /usr/bin/python3.8 && mkdir -p /tmp/build
RUN apt-get clean \
&& rm -rf /var/lib/apt/lists/*
20 changes: 20 additions & 0 deletions molecule/builder-focal/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
DATE_STR := $(shell date +"%Y_%m_%d")
BUILDER_IMAGE ?= "quay.io/freedomofpress/sd-docker-builder-focal:$(DATE_STR)"

.PHONY: build-container
build-container: ## Build Docker image for Debian package creation
@echo "███Building Docker image $(BUILDER_IMAGE) for Debian package creation..."
@docker build --no-cache -t $(BUILDER_IMAGE) .

.PHONY: push-container
push-container: ## Push the Docker image for Debian package creation to quay.io
@echo "███Pushing Docker image for Debian package creation to quay.io..."
@./push.sh

.PHONY: help
help: ## Print this message and exit.
@printf "Molecule scenario for building a Docker container for Debian package creation.\n"
@printf "Subcommands:\n\n"
@awk 'BEGIN {FS = ":.*?## "} /^[0-9a-zA-Z_-]+:.*?## / {printf "\033[36m%s\033[0m : %s\n", $$1, $$2}' $(MAKEFILE_LIST) \
| sort \
| column -s ':' -t
2 changes: 2 additions & 0 deletions molecule/builder-focal/ansible-override-vars.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
securedrop_build_xenial_support: True
28 changes: 28 additions & 0 deletions molecule/builder-focal/create.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
- name: Create
hosts: localhost
connection: local
gather_facts: False
vars:
molecule_file: "{{ lookup('env', 'MOLECULE_FILE') }}"
molecule_ephemeral_directory: "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') }}"
molecule_scenario_directory: "{{ lookup('env', 'MOLECULE_SCENARIO_DIRECTORY') }}"
molecule_yml: "{{ lookup('file', molecule_file) | from_yaml }}"
image_hash: "{{ lookup('pipe', 'egrep -v ^# image_hash') }}"
default_image: "quay.io/freedomofpress/sd-docker-builder-focal@sha256:{{image_hash}}"
image: "{{ lookup('env', 'BUILDER_IMAGE') | default(default_image, true) }}"
tasks:
- debug:
msg: "Building with Docker image {{ image }}"

- name: Create builders
docker_container:
name: "{{ item.name }}"
hostname: "{{ item.name }}"
image: "{{ image }}"
state: started
command: "tail -f /dev/null"
privileged: "{{ item.privileged | default(omit) }}"
volumes: "{{ item.volumes | default(omit) }}"
capabilities: "{{ item.capabilities | default(omit) }}"
with_items: "{{ molecule_yml.platforms }}"
251 changes: 251 additions & 0 deletions molecule/builder-focal/deployment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2013 - 2014 Spotify AB

# This file is part of dh-virtualenv.

# dh-virtualenv is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 2 of the
# License, or (at your option) any later version.

# dh-virtualenv is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with dh-virtualenv. If not, see
# <http://www.gnu.org/licenses/>.

import os
import re
import shutil
import subprocess
import tempfile

ROOT_ENV_KEY = 'DH_VIRTUALENV_INSTALL_ROOT'
DEFAULT_INSTALL_DIR = '/usr/share/python/'


class Deployment(object):
def __init__(self, package, extra_urls=[], preinstall=[],
pypi_url=None, setuptools=False, python=None,
builtin_venv=False, sourcedirectory=None, verbose=False,
extra_pip_arg=[], use_system_packages=False,
skip_install=False,
install_suffix=None,
requirements_filename='requirements.txt'):

self.package = package
install_root = os.environ.get(ROOT_ENV_KEY, DEFAULT_INSTALL_DIR)
self.install_suffix = install_suffix

self.debian_root = os.path.join(
'debian', package, install_root.lstrip('/'))

if install_suffix is None:
self.virtualenv_install_dir = os.path.join(install_root, self.package)
self.package_dir = os.path.join(self.debian_root, package)
else:
self.virtualenv_install_dir = os.path.join(install_root, install_suffix)
self.package_dir = os.path.join(self.debian_root, install_suffix)

self.bin_dir = os.path.join(self.package_dir, 'bin')
self.local_bin_dir = os.path.join(self.package_dir, 'local', 'bin')

self.extra_urls = extra_urls
self.preinstall = preinstall
self.extra_pip_arg = extra_pip_arg
self.pypi_url = pypi_url
self.log_file = tempfile.NamedTemporaryFile()
self.verbose = verbose
self.setuptools = setuptools
self.python = python
self.builtin_venv = builtin_venv
self.sourcedirectory = '.' if sourcedirectory is None else sourcedirectory
self.use_system_packages = use_system_packages
self.skip_install = skip_install
self.requirements_filename = requirements_filename

@classmethod
def from_options(cls, package, options):
verbose = options.verbose or os.environ.get('DH_VERBOSE') == '1'
return cls(package,
extra_urls=options.extra_index_url,
preinstall=options.preinstall,
pypi_url=options.pypi_url,
setuptools=options.setuptools,
python=options.python,
builtin_venv=options.builtin_venv,
sourcedirectory=options.sourcedirectory,
verbose=verbose,
extra_pip_arg=options.extra_pip_arg,
use_system_packages=options.use_system_packages,
skip_install=options.skip_install,
install_suffix=options.install_suffix,
requirements_filename=options.requirements_filename)

def clean(self):
shutil.rmtree(self.debian_root)

def create_virtualenv(self):
if self.builtin_venv:
virtualenv = [self.python, '-m', 'venv']
else:
virtualenv = ['virtualenv']

if self.use_system_packages:
virtualenv.append('--system-site-packages')
else:
virtualenv.append('--no-site-packages')

if self.setuptools:
virtualenv.append('--setuptools')

if self.verbose:
virtualenv.append('--verbose')

if self.python:
virtualenv.extend(('--python', self.python))

virtualenv.append(self.package_dir)
subprocess.check_call(virtualenv)

# We need to prefix the pip run with the location of python
# executable. Otherwise it would just blow up due to too long
# shebang-line.
self.pip_prefix = [
os.path.abspath(os.path.join(self.bin_dir, 'python')),
os.path.abspath(os.path.join(self.bin_dir, 'pip')),
]


update_pip_command = [
os.path.abspath(os.path.join(self.bin_dir, 'python')),
"-m", "pip", "install", "--require-hashes", "--verbose", "--no-deps", "--no-compile", "--no-cache-dir", "--ignore-installed", "-r", "/root/update_virtualenv.txt"
]
subprocess.check_call(update_pip_command)
if self.verbose:
self.pip_prefix.append('-v')

self.pip_prefix.append('install')

if self.pypi_url:
self.pip_prefix.append('--pypi-url={0}'.format(self.pypi_url))
self.pip_prefix.extend([
'--extra-index-url={0}'.format(url) for url in self.extra_urls
])
self.pip_prefix.append('--log={0}'.format(os.path.abspath(self.log_file.name)))

# Add in any user supplied pip args
if self.extra_pip_arg:
self.pip_prefix.extend(self.extra_pip_arg)

def pip(self, *args):
return self.pip_prefix + list(args)

def install_dependencies(self):
# Install preinstall stage packages. This is handy if you need
# a custom package to install dependencies (think something
# along lines of setuptools), but that does not get installed
# by default virtualenv.
if self.preinstall:
subprocess.check_call(self.pip(*self.preinstall))

requirements_path = os.path.join(self.sourcedirectory, self.requirements_filename)
if os.path.exists(requirements_path):
subprocess.check_call(self.pip('-r', requirements_path))

def run_tests(self):
python = os.path.abspath(os.path.join(self.bin_dir, 'python'))
setup_py = os.path.join(self.sourcedirectory, 'setup.py')
if os.path.exists(setup_py):
subprocess.check_call([python, 'setup.py', 'test'], cwd=self.sourcedirectory)

def fix_shebangs(self):
"""Translate /usr/bin/python and /usr/bin/env python sheband
lines to point to our virtualenv python.
"""
grep_proc = subprocess.Popen(
['grep', '-l', '-r', '-e', r'^#!.*bin/\(env \)\?python',
self.bin_dir],
stdout=subprocess.PIPE
)
files, stderr = grep_proc.communicate()
files = files.strip()
if not files:
return

pythonpath = os.path.join(self.virtualenv_install_dir, 'bin/python')
for f in files.split('\n'):
subprocess.check_call(
['sed', '-i', r's|^#!.*bin/\(env \)\?python|#!{0}|'.format(
pythonpath),
f])

def fix_activate_path(self):
"""Replace the `VIRTUAL_ENV` path in bin/activate to reflect the
post-install path of the virtualenv.
"""
activate_settings = [
[
'VIRTUAL_ENV="{0}"'.format(self.virtualenv_install_dir),
r'^VIRTUAL_ENV=.*$',
"activate"
],
[
'setenv VIRTUAL_ENV "{0}"'.format(self.virtualenv_install_dir),
r'^setenv VIRTUAL_ENV.*$',
"activate.csh"
],
[
'set -gx VIRTUAL_ENV "{0}"'.format(self.virtualenv_install_dir),
r'^set -gx VIRTUAL_ENV.*$',
"activate.fish"
],
]

for activate_args in activate_settings:
virtualenv_path = activate_args[0]
pattern = re.compile(activate_args[1], flags=re.M)
activate_file = activate_args[2]

with open(os.path.join(self.bin_dir, activate_file), 'r+') as fh:
content = pattern.sub(virtualenv_path, fh.read())
fh.seek(0)
fh.truncate()
fh.write(content)

def install_package(self):
if not self.skip_install:
subprocess.check_call(self.pip('.'), cwd=os.path.abspath(self.sourcedirectory))

def fix_local_symlinks(self):
# The virtualenv might end up with a local folder that points outside the package
# Specifically it might point at the build environment that created it!
# Make those links relative
# See https://github.com/pypa/virtualenv/commit/5cb7cd652953441a6696c15bdac3c4f9746dfaa1
local_dir = os.path.join(self.package_dir, "local")
if not os.path.isdir(local_dir):
return
elif os.path.samefile(self.package_dir, local_dir):
# "local" points directly to its containing directory
os.unlink(local_dir)
os.symlink(".", local_dir)
return

for d in os.listdir(local_dir):
path = os.path.join(local_dir, d)
if not os.path.islink(path):
continue

existing_target = os.readlink(path)
if not os.path.isabs(existing_target):
# If the symlink is already relative, we don't
# want to touch it.
continue

new_target = os.path.relpath(existing_target, local_dir)
os.unlink(path)
os.symlink(new_target, path)

15 changes: 15 additions & 0 deletions molecule/builder-focal/destroy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
- name: Destroy
hosts: localhost
connection: local
gather_facts: False
vars:
molecule_file: "{{ lookup('env', 'MOLECULE_FILE') }}"
molecule_yml: "{{ lookup('file', molecule_file) | from_yaml }}"
tasks:
- name: Destroy molecule instance(s)
docker_container:
name: "{{ item.name }}"
state: absent
force_kill: "{{ item.force_kill | default(True) }}"
with_items: "{{ molecule_yml.platforms }}"
2 changes: 2 additions & 0 deletions molecule/builder-focal/image_hash
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# sha256 digest quay.io/freedomofpress/sd-docker-builder-xenial:2020_06_17
918f618085a22adb3259c090a019736b72912bb951fe41d03c0dc3ceeb6dbd26
Loading

0 comments on commit 30bf0bf

Please sign in to comment.