Testing: CI

The SecureDrop project uses CircleCI for running automated test suites on code changes.

The relevant files for configuring the CI tests are the Makefile in the main repo, the configuration file at .circleci/config.yml, and the scripts in devops/. You may want to consult the CircleCI Configuration Reference to interpret the configuration file. Review the workflows section of the configuration file to understand which jobs are run by CircleCI.

The files under devops/ are used to create a libvirt-compatible environment on GCE. The GCE host is used as the Ansible controller, mimicking a developer’s laptop, to provision the machines and run the tests against them.


We skip unnecessary jobs, such as the staging run, for pull requests that only affect the documentation; to do so, we check whether the branch name begins with docs-. These checks are enforced in different parts of the configuration, mainly within the Makefile.


In CI, we rebase branches in PRs on HEAD of the target branch. This rebase does not occur for branches that are not in PRs. When a branch is pushed to the shared freedomofpress remote, CI will run, a rebase will not occur, and since opening a PR does not trigger a re-build, the CI build results are not shown rebased on the latest of the target branch. This is important to maintain awareness of if your branch is behind the target branch. Once your branch is in a PR, you can rebuild, push an additional commit, or manually rebase your branch to update the CI results.

Running the CI Staging Environment

The staging environment tests will run automatically in CircleCI, when changes are submitted by Freedom of the Press Foundation staff (i.e. members of the freedomofpress GitHub organization). The tests also perform basic linting and validation, like checking for formatting errors in the Sphinx documentation.


You will need a Google Cloud Platform account to proceed. See the Google Cloud Platform Getting Started Guide for detailed instructions.

In addition to a GCP account, you will need a working Docker installation in order to run the container that builds the deb packages.

You can verify that your Docker installation is working by running docker run hello-world and confirming you see “Hello from Docker” in the output as shown below:

$ docker run hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.

Setup Environment Parameters

Source the setup script using the following command:

source ./devops/gce-nested/ci-env.sh

You will be prompted for the values of the required environment variables. There are some defaults set that you may want to change. You will need to export GOOGLE_CREDENTIALS with authentication details for your GCP account, which is outside the scope of this guide.

Use Makefile to Provision Hosts

Run make help to see the full list of CI commands in the Makefile:

$ make help
Makefile for developing and testing SecureDrop.
    ci-go                      Creates, provisions, tests, and destroys GCE host for testing staging environment.
    ci-lint                    Runs linting in linting container.
    ci-teardown                Destroys GCE host for testing staging environment.

To run the tests locally:

make ci-go

You can use ./devops/gce-nested/ci-runner.sh to provision the remote hosts while making changes, including rebuilding the Debian packages used in the Staging environment. See Virtual Environments: Servers for more information.

Debugging CI Issues and Connecting to Remote Instances

For the staging tests, a container will be spawned on CircleCI, which will then create a Google Compute instance with nested virtualization and will set up the virtual environment and run the playbooks on that remote.

Cloud instances are deleted after the test run is completed, whether a test run passes or fails. In order to debug the state of the remote instance, we must first ensure that the instance is not automatically destroyed. Note that there is also a cron job that destroys instances daily as well. The following is an example of a commit to apply to a branch in order disable the deletion for the Focal staging job:

diff --git a/.circleci/config.yml b/.circleci/config.yml
index 4d61769f1..af74672bc 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -251,13 +251,6 @@ jobs:
             make ci-go
           no_output_timeout: 35m

-      - run:
-          name: Ensure environment torn down
-          # Always report true, since env should will destroyed already
-          # if all tests passed.
-          command: make ci-teardown || true
-          when: always
       - store_test_results:
           path: ~/sd/junit

diff --git a/devops/gce-nested/ci-go.sh b/devops/gce-nested/ci-go.sh
index ff80aa107..65bbcd7b9 100755
--- a/devops/gce-nested/ci-go.sh
+++ b/devops/gce-nested/ci-go.sh
@@ -16,4 +16,3 @@ export BASE_OS="${BASE_OS:-focal}"

diff --git a/devops/scripts/create-staging-env b/devops/scripts/create-staging-env
index 8b296be94..df8a4d674 100755
--- a/devops/scripts/create-staging-env
+++ b/devops/scripts/create-staging-env
@@ -32,7 +32,7 @@ printf "Creating staging environment via '%s'...\\n" "${securedrop_staging_scena
 # Are we in CI? Then lets do full testing post install!
 if [ "$USER" = "sdci" ]; then
-    molecule test -s "${securedrop_staging_scenario}"
+    molecule test --destroy=never -s "${securedrop_staging_scenario}"
     molecule "${MOLECULE_ACTION:-converge}" -s "${securedrop_staging_scenario}" "${EXTRA_ANSIBLE_ARGS[@]}"

Once that commit is pushed, run the staging-test-with-rebase job with ssh using with CircleCI. Once logged into that container, you can ssh into the Google Compute host:

ssh -i /tmp/gce-nested/gce sdci@<ip address>

Once on the GCP host, the SecureDrop source is in /home/sdci/securedrop-source and you may activate the virtualenv, list the molecule instances and connect to VM instances:

cd securedrop-source
source .venv/bin/activate
molecule list
molecule login -s libvirt-staging-focal --host app-staging