Add a dev container configuration for VS Code (#27)

This creates a development environment with a FoundationDB server and
an erlfdb client in two containers, sharing a network through Docker
Compose.

It uses the FDB image published to Docker Hub for the FDB container, and
downloads the FDB client packages from foundationdb.org to provide the
development headers and libraries. Once the Docker Compose setup is
created, VS Code executes the `create_cluster_file.bash` script to write
down a cluster file containing the IP address in the compose network
where the FDB service can be found, and initializes the FDB server with
a new database.

The use of an external FDB container allows the erlfdb container to be
re-created as needed without losing the underlying data in the FDB
container.
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 0000000..4a42017
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,48 @@
+ARG FDB_VERSION
+ARG ERLANG_VERSION
+
+# Grab fdbcli and client library from same image as server
+FROM foundationdb/foundationdb:${FDB_VERSION} as fdb
+
+# Debian image with Erlang installed
+FROM erlang:${ERLANG_VERSION}
+
+# The FROM directive above sweeps out the ARGs so we need to re-declare here
+# in order to use it again to download the FDB client package
+ARG FDB_VERSION
+
+# Install the FDB client used underneath erlfdb
+RUN set -ex; \
+    wget https://www.foundationdb.org/downloads/${FDB_VERSION}/ubuntu/installers/foundationdb-clients_${FDB_VERSION}-1_amd64.deb; \
+    mkdir /var/lib/foundationdb; \
+    dpkg -i foundationdb-clients_${FDB_VERSION}-1_amd64.deb; \
+    rm foundationdb-clients_${FDB_VERSION}-1_amd64.deb
+
+# FDB bindings tester uses the Python bindings; install them from a
+# package to avoid building FDB from source
+# TODO FDB 6.3+ uses python3, we'll need to update it here
+RUN set -ex; \
+    wget https://www.foundationdb.org/downloads/${FDB_VERSION}/bindings/python/foundationdb-${FDB_VERSION}.tar.gz; \
+    tar zxf foundationdb-${FDB_VERSION}.tar.gz; \
+    cd foundationdb-${FDB_VERSION}; \
+    python setup.py install; \
+    rm ../foundationdb-${FDB_VERSION}.tar.gz
+
+# Clone FoundationDB repo to retrieve bindings tester package and
+# patch it to support erlfdb
+COPY add_erlang_bindings.patch /tmp/
+RUN set -ex; \
+    git clone --branch ${FDB_VERSION} --depth 1 https://github.com/apple/foundationdb /usr/src/foundationdb; \
+    cd /usr/src/foundationdb; \
+    git apply /tmp/add_erlang_bindings.patch
+
+# `dig` is used by the script that creates the FDB cluster file
+RUN set -ex; \
+    apt-get update; \
+    apt-get install -y --no-install-recommends \
+        dnsutils; \
+    rm -rf /var/lib/apt/lists/*
+
+COPY --from=fdb /var/fdb/scripts/create_cluster_file.bash /usr/local/bin/
+
+CMD sleep infinity
diff --git a/.devcontainer/add_erlang_bindings.patch b/.devcontainer/add_erlang_bindings.patch
new file mode 100644
index 0000000..6c431c0
--- /dev/null
+++ b/.devcontainer/add_erlang_bindings.patch
@@ -0,0 +1,24 @@
+diff --git a/bindings/bindingtester/__init__.py b/bindings/bindingtester/__init__.py
+index 75454625c..fa20d37ab 100644
+--- a/bindings/bindingtester/__init__.py
++++ b/bindings/bindingtester/__init__.py
+@@ -22,7 +22,6 @@ import math
+ import sys
+ import os
+ 
+-sys.path[:0] = [os.path.join(os.path.dirname(__file__), '..', '..', 'bindings', 'python')]
+ 
+ import util
+ 
+diff --git a/bindings/bindingtester/known_testers.py b/bindings/bindingtester/known_testers.py
+index 2c5211a3d..30c1fafdc 100644
+--- a/bindings/bindingtester/known_testers.py
++++ b/bindings/bindingtester/known_testers.py
+@@ -57,6 +57,7 @@ _java_cmd = 'java -ea -cp %s:%s com.apple.foundationdb.test.' % (
+ 
+ # We could set min_api_version lower on some of these if the testers were updated to support them
+ testers = {
++    'erlang': Tester('erlang', '/usr/src/erlfdb/test/tester.es', 2040, 610, MAX_API_VERSION, types=ALL_TYPES),
+     'python': Tester('python', 'python ' + _absolute_path('python/tests/tester.py'), 2040, 23, MAX_API_VERSION, types=ALL_TYPES),
+     'python3': Tester('python3', 'python3 ' + _absolute_path('python/tests/tester.py'), 2040, 23, MAX_API_VERSION, types=ALL_TYPES),
+     'ruby': Tester('ruby', _absolute_path('ruby/tests/tester.rb'), 2040, 23, MAX_API_VERSION),
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..8683283
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,17 @@
+{
+    "dockerComposeFile": "docker-compose.yaml",
+    "service": "erlfdb",
+    "workspaceFolder": "/usr/src/erlfdb",
+
+    // Create the fdb.cluster file, resolving the FDB service name to an IP
+    "onCreateCommand": ["bash", "/usr/local/bin/create_cluster_file.bash"],
+
+    // Initialize a new database. If the erlfdb container is being re-created,
+    // a database may already exist. In that case an error message will be
+    // printed in the logs and the existing database will be reused.
+    "postCreateCommand": "fdbcli --exec 'configure new single ssd'",
+
+    "extensions": [
+        "erlang-ls.erlang-ls"
+    ]
+}
diff --git a/.devcontainer/docker-compose.yaml b/.devcontainer/docker-compose.yaml
new file mode 100644
index 0000000..1199af7
--- /dev/null
+++ b/.devcontainer/docker-compose.yaml
@@ -0,0 +1,38 @@
+services:
+  erlfdb:
+    build:
+      context: .
+      dockerfile: Dockerfile
+      args:
+        ERLANG_VERSION: "24"
+
+        # This should always match the value in fdb.image
+        FDB_VERSION: "6.2.30"
+
+    environment:
+      # This needs to match the name of the FoundationDB service below
+      FDB_COORDINATOR: fdb
+
+      # The location where the Dockerfile installs the FDB cluster file
+      # retrieved from the `fdb` image. CouchDB looks for the cluster file in
+      # this location by default. If you want to install it somewhere else you
+      # you need to change "[erlfdb] cluster_file" and ERL_ZFLAGS to match.
+      FDB_CLUSTER_FILE: /usr/local/etc/foundationdb/fdb.cluster
+
+      # The test suite will default to trying to start its own fdbserver
+      # process. This environment variable tells it to use the fdbserver
+      # running in the `fdb` image instead. Quite a hacky solution.
+      ERL_ZFLAGS: "-erlfdb test_cluster_file <<\\\"/usr/local/etc/foundationdb/fdb.cluster\\\">>"
+
+    volumes:
+      # Mounts the project folder to '/usr/src/erlfdb'. The target path inside
+      # the container should match what your application expects. In this case,
+      # the compose file is in a sub-folder, so you will mount '..'. You would
+      # then reference this path as the 'workspaceFolder' in
+      # '.devcontainer/devcontainer.json' so VS Code starts here.
+      - ..:/usr/src/erlfdb:cached
+
+    network_mode: service:fdb
+
+  fdb:
+    image: foundationdb/foundationdb:6.2.30
diff --git a/BINDING_TESTER.md b/BINDING_TESTER.md
index bc726bf..41b1b6f 100644
--- a/BINDING_TESTER.md
+++ b/BINDING_TESTER.md
@@ -1,6 +1,19 @@
 Running the bindingstester
 ===
 
+# Easy Button: devcontainer
+
+The image build takes care of setting up an environment where the Erlang
+bindings can be tested. It clones the FDB repo and patches the necessary
+files to register Erlang as a known binding. Assuming erlfdb has been built
+using `make`; the bindings tests be run directly via
+
+```bash
+ERL_LIBS=/usr/src/erlfdb/_build/test/lib/erlfdb/ /usr/src/foundationdb/bindings/bindingtester/bindingtester.py erlang
+```
+
+# Manual Approach
+
 This assumes that all FoundationDB dependencies are installed properly. See
 the FoundationDB documentation for information on the dependencies.
 
@@ -65,9 +78,11 @@
 
 ```bash
 $ cd /Users/davisp/github/davisp/foundationdb/bindings/bindingtester
-$ ERL_LIBS=/Users/davisp/github/labs-cloudant/couchdb-erlfdb/ PYTHONPATH=/Users/davisp/github/davisp/foundationdb/_build/bindings/python/ ./bindingtester.py --cluster-file /Users/davisp/tmp/fdbtest/fdb.cluster erlang
+$ ERL_LIBS=/Users/davisp/github/labs-cloudant/couchdb-erlfdb/_build/test/lib/erlfdb/ PYTHONPATH=/Users/davisp/github/davisp/foundationdb/_build/bindings/python/ ./bindingtester.py --cluster-file /Users/davisp/tmp/fdbtest/fdb.cluster erlang
 ```
 
+# Testing Notes
+
 By default, `bindingtester.py` runs the `scripted.py` test which is a deterministic set of tests. To really try and soak test the bindings you should add the following command line parameters:
 
 `--test-name api --num-ops 10000`
diff --git a/devcontainer.config b/devcontainer.config
new file mode 100644
index 0000000..d9356aa
--- /dev/null
+++ b/devcontainer.config
@@ -0,0 +1,5 @@
+[
+    {erlfdb, [
+        {test_cluster_file, <<"/usr/local/etc/foundationdb/fdb.cluster">>}
+    ]}
+].
diff --git a/rebar.config b/rebar.config
index 617594c..b4d2930 100644
--- a/rebar.config
+++ b/rebar.config
@@ -30,6 +30,11 @@
 ]}.
 
 {profiles, [
+    {devcontainer, [
+        {eunit_opts, [
+            {sys_config, "devcontainer.config"}
+        ]}
+    ]},
     {win32_external_fdbserver, [
         {eunit_opts, [
             {sys_config, "win32_external_fdbserver.config"}