Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added support for installing plugins such as apoc at runtime #176

Merged
merged 1 commit into from Jun 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion Makefile
Expand Up @@ -78,11 +78,16 @@ tmp/.image-id-%: tmp/local-context-%/.sentinel
$(<D)
> echo -n $$image >$@

tmp/local-context-%/.sentinel: tmp/image-%/.sentinel in/$(call tarball,%,$(NEO4J_VERSION))
tmp/neo4jlabs-plugins.json: ./neo4jlabs-plugins.json
> mkdir -p $(@D)
> cp $< $@

tmp/local-context-%/.sentinel: tmp/image-%/.sentinel in/$(call tarball,%,$(NEO4J_VERSION)) tmp/neo4jlabs-plugins.json
> rm -rf $(@D)
> mkdir -p $(@D)
> cp -r $(<D)/* $(@D)
> cp $(filter %.tar.gz,$^) $(@D)/local-package
> cp $(filter %.json,$^) $(@D)/local-package
> touch $@

tmp/image-%/.sentinel: src/$(series)/Dockerfile src/$(series)/docker-entrypoint.sh \
Expand Down
6 changes: 6 additions & 0 deletions README.md
Expand Up @@ -29,6 +29,12 @@ docker run \
neo4j:3.0
```

# Neo4j Labs Plugins

## Installation on container startup

Neo4j Labs plugins can be installed from github during container startup. Set the `NEO4JLABS_PLUGINS` environment variable to a json list of plugins to install. e.g. `["graph-algorithms", "graphql"]`.

# Getting support and contributing

Please create issues and pull requests in the Github repository.
6 changes: 6 additions & 0 deletions neo4jlabs-plugins.json
@@ -0,0 +1,6 @@
{
"apoc": "https://github.com/neo4j-contrib/neo4j-apoc-procedures/raw/master/versions.json",
"streams": "https://github.com/neo4j-contrib/neo4j-streams/raw/master/versions.json",
"graphql": "https://github.com/neo4j-contrib/neo4j-graphql/raw/master/versions.json",
"graph-algorithms": "https://github.com/neo4j-contrib/neo4j-graph-algorithms/raw/master/versions.json"
}
7 changes: 5 additions & 2 deletions src/3.3/Dockerfile
Expand Up @@ -13,7 +13,8 @@ RUN addgroup --system neo4j && adduser --system --no-create-home --home "${NEO4J
COPY ./local-package/* /tmp/

RUN apt update \
&& apt install -y curl gosu \
&& apt install -y curl gosu jq \
&& rm -rf /var/lib/apt/lists/* \
&& curl -L --fail --silent --show-error "https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini" > /sbin/tini \
&& echo "${TINI_SHA256} /sbin/tini" | sha256sum -c --strict --quiet \
&& chmod +x /sbin/tini \
Expand All @@ -31,7 +32,9 @@ RUN apt update \
&& chown -R neo4j:neo4j "${NEO4J_HOME}" \
&& chmod -R 777 "${NEO4J_HOME}" \
&& ln -s /data "${NEO4J_HOME}"/data \
&& ln -s /logs "${NEO4J_HOME}"/logs
&& ln -s /logs "${NEO4J_HOME}"/logs \
&& mv /tmp/neo4jlabs-plugins.json /neo4jlabs-plugins.json \
&& rm -rf /tmp/*

ENV PATH "${NEO4J_HOME}"/bin:$PATH

Expand Down
36 changes: 36 additions & 0 deletions src/3.3/docker-entrypoint.sh
Expand Up @@ -119,6 +119,32 @@ function check_mounted_folder_with_chown
fi
}

function load_plugin_from_github
{
# Load a plugin at runtime. The provided github repository must have a versions.json on the master branch with the
# correct format.
local _plugin_name="${1}" #e.g. apoc, graph-algorithms, graph-ql

local _plugins_dir="${NEO4J_HOME}/plugins"
if [ -d /plugins ]; then
local _plugins_dir="/plugins"
fi
local _versions_json="$(jq --raw-output "with_entries( select(.key==\"${_plugin_name}\") ) | to_entries[] | .value" /neo4jlabs-plugins.json )"
# Using the same name for the plugin irrespective of version ensures we don't end up with different versions of the same plugin
local _destination="${_plugins_dir}/${_plugin_name}.jar"
local _neo4j_version="$(neo4j --version | cut -d' ' -f2)"

# Now we call out to github to get the versions.json for this plugin and we parse that to find the url for the correct plugin jar for our neo4j version
local _plugin_jar_url="$(curl -L "${_versions_json}" | jq --raw-output ".[] | select(.neo4j==\"${_neo4j_version}\") | .jar")"
echo >&2 "Installing Plugin '${_plugin_name}' from ${_plugin_jar_url} to ${_destination} "
curl -o "${_destination}" -L "${_plugin_jar_url}"

if ! is_readable "${_destination}"; then
echo >&2 "Plugin at '${_destination}' is not readable"
exit 1
fi
}

# If we're running as root, then run as the neo4j user. Otherwise
# docker is running with --user and we simply use that user. Note
# that su-exec, despite its name, does not replicate the functionality
Expand Down Expand Up @@ -271,6 +297,10 @@ fi

if [ -d /plugins ]; then
if secure_mode_enabled; then
if [[ ! -z "${NEO4JLABS_PLUGINS:-}" ]]; then
# We need write permissions
check_mounted_folder_with_chown "/plugins"
fi
check_mounted_folder_readable "/plugins"
fi
NEO4J_dbms_directories_plugins="/plugins"
Expand Down Expand Up @@ -346,6 +376,12 @@ for i in $( set | grep ^NEO4J_ | awk -F'=' '{print $1}' | sort -rn ); do
fi
done

if [[ ! -z "${NEO4JLABS_PLUGINS:-}" ]]; then
# NEO4JLABS_PLUGINS should be a json array of plugins like '["graph-algorithms", "apoc-procedures", "streams", "graphql"]'
for plugin_name in $(echo "${NEO4JLABS_PLUGINS}" | jq --raw-output '.[]'); do
load_plugin_from_github "${plugin_name}"
done
fi

[ -f "${EXTENSION_SCRIPT:-}" ] && . ${EXTENSION_SCRIPT}

Expand Down
7 changes: 5 additions & 2 deletions src/3.4/Dockerfile
Expand Up @@ -13,7 +13,8 @@ RUN addgroup --system neo4j && adduser --system --no-create-home --home "${NEO4J
COPY ./local-package/* /tmp/

RUN apt update \
&& apt install -y curl gosu \
&& apt install -y curl gosu jq \
&& rm -rf /var/lib/apt/lists/* \
&& curl -L --fail --silent --show-error "https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini" > /sbin/tini \
&& echo "${TINI_SHA256} /sbin/tini" | sha256sum -c --strict --quiet \
&& chmod +x /sbin/tini \
Expand All @@ -31,7 +32,9 @@ RUN apt update \
&& chown -R neo4j:neo4j "${NEO4J_HOME}" \
&& chmod -R 777 "${NEO4J_HOME}" \
&& ln -s /data "${NEO4J_HOME}"/data \
&& ln -s /logs "${NEO4J_HOME}"/logs
&& ln -s /logs "${NEO4J_HOME}"/logs \
&& mv /tmp/neo4jlabs-plugins.json /neo4jlabs-plugins.json \
&& rm -rf /tmp/*

ENV PATH "${NEO4J_HOME}"/bin:$PATH

Expand Down
36 changes: 36 additions & 0 deletions src/3.4/docker-entrypoint.sh
Expand Up @@ -119,6 +119,32 @@ function check_mounted_folder_with_chown
fi
}

function load_plugin_from_github
{
# Load a plugin at runtime. The provided github repository must have a versions.json on the master branch with the
# correct format.
local _plugin_name="${1}" #e.g. apoc, graph-algorithms, graph-ql

local _plugins_dir="${NEO4J_HOME}/plugins"
if [ -d /plugins ]; then
local _plugins_dir="/plugins"
fi
local _versions_json="$(jq --raw-output "with_entries( select(.key==\"${_plugin_name}\") ) | to_entries[] | .value" /neo4jlabs-plugins.json )"
# Using the same name for the plugin irrespective of version ensures we don't end up with different versions of the same plugin
local _destination="${_plugins_dir}/${_plugin_name}.jar"
local _neo4j_version="$(neo4j --version | cut -d' ' -f2)"

# Now we call out to github to get the versions.json for this plugin and we parse that to find the url for the correct plugin jar for our neo4j version
local _plugin_jar_url="$(curl -L "${_versions_json}" | jq --raw-output ".[] | select(.neo4j==\"${_neo4j_version}\") | .jar")"
echo >&2 "Installing Plugin '${_plugin_name}' from ${_plugin_jar_url} to ${_destination} "
curl -o "${_destination}" -L "${_plugin_jar_url}"

if ! is_readable "${_destination}"; then
echo >&2 "Plugin at '${_destination}' is not readable"
exit 1
fi
}

# If we're running as root, then run as the neo4j user. Otherwise
# docker is running with --user and we simply use that user. Note
# that su-exec, despite its name, does not replicate the functionality
Expand Down Expand Up @@ -264,6 +290,10 @@ fi

if [ -d /plugins ]; then
if secure_mode_enabled; then
if [[ ! -z "${NEO4JLABS_PLUGINS:-}" ]]; then
# We need write permissions
check_mounted_folder_with_chown "/plugins"
fi
check_mounted_folder_readable "/plugins"
fi
NEO4J_dbms_directories_plugins="/plugins"
Expand Down Expand Up @@ -339,6 +369,12 @@ for i in $( set | grep ^NEO4J_ | awk -F'=' '{print $1}' | sort -rn ); do
fi
done

if [[ ! -z "${NEO4JLABS_PLUGINS:-}" ]]; then
# NEO4JLABS_PLUGINS should be a json array of plugins like '["graph-algorithms", "apoc-procedures", "streams", "graphql"]'
for plugin_name in $(echo "${NEO4JLABS_PLUGINS}" | jq --raw-output '.[]'); do
load_plugin_from_github "${plugin_name}"
done
fi

[ -f "${EXTENSION_SCRIPT:-}" ] && . ${EXTENSION_SCRIPT}

Expand Down
7 changes: 5 additions & 2 deletions src/3.5/Dockerfile
Expand Up @@ -13,7 +13,8 @@ RUN addgroup --system neo4j && adduser --system --no-create-home --home "${NEO4J
COPY ./local-package/* /tmp/

RUN apt update \
&& apt install -y curl gosu \
&& apt install -y curl gosu jq \
&& rm -rf /var/lib/apt/lists/* \
&& curl -L --fail --silent --show-error "https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini" > /sbin/tini \
&& echo "${TINI_SHA256} /sbin/tini" | sha256sum -c --strict --quiet \
&& chmod +x /sbin/tini \
Expand All @@ -31,7 +32,9 @@ RUN apt update \
&& chown -R neo4j:neo4j "${NEO4J_HOME}" \
&& chmod -R 777 "${NEO4J_HOME}" \
&& ln -s /data "${NEO4J_HOME}"/data \
&& ln -s /logs "${NEO4J_HOME}"/logs
&& ln -s /logs "${NEO4J_HOME}"/logs \
&& mv /tmp/neo4jlabs-plugins.json /neo4jlabs-plugins.json \
&& rm -rf /tmp/*

ENV PATH "${NEO4J_HOME}"/bin:$PATH

Expand Down
36 changes: 36 additions & 0 deletions src/3.5/docker-entrypoint.sh
Expand Up @@ -119,6 +119,32 @@ function check_mounted_folder_with_chown
fi
}

function load_plugin_from_github
{
# Load a plugin at runtime. The provided github repository must have a versions.json on the master branch with the
# correct format.
local _plugin_name="${1}" #e.g. apoc, graph-algorithms, graph-ql

local _plugins_dir="${NEO4J_HOME}/plugins"
if [ -d /plugins ]; then
local _plugins_dir="/plugins"
fi
local _versions_json="$(jq --raw-output "with_entries( select(.key==\"${_plugin_name}\") ) | to_entries[] | .value" /neo4jlabs-plugins.json )"
# Using the same name for the plugin irrespective of version ensures we don't end up with different versions of the same plugin
local _destination="${_plugins_dir}/${_plugin_name}.jar"
local _neo4j_version="$(neo4j --version | cut -d' ' -f2)"

# Now we call out to github to get the versions.json for this plugin and we parse that to find the url for the correct plugin jar for our neo4j version
local _plugin_jar_url="$(curl -L "${_versions_json}" | jq --raw-output ".[] | select(.neo4j==\"${_neo4j_version}\") | .jar")"
echo >&2 "Installing Plugin '${_plugin_name}' from ${_plugin_jar_url} to ${_destination} "
curl -o "${_destination}" -L "${_plugin_jar_url}"

if ! is_readable "${_destination}"; then
echo >&2 "Plugin at '${_destination}' is not readable"
exit 1
fi
}

# If we're running as root, then run as the neo4j user. Otherwise
# docker is running with --user and we simply use that user. Note
# that su-exec, despite its name, does not replicate the functionality
Expand Down Expand Up @@ -264,6 +290,10 @@ fi

if [ -d /plugins ]; then
if secure_mode_enabled; then
if [[ ! -z "${NEO4JLABS_PLUGINS:-}" ]]; then
# We need write permissions
check_mounted_folder_with_chown "/plugins"
fi
check_mounted_folder_readable "/plugins"
fi
NEO4J_dbms_directories_plugins="/plugins"
Expand Down Expand Up @@ -339,6 +369,12 @@ for i in $( set | grep ^NEO4J_ | awk -F'=' '{print $1}' | sort -rn ); do
fi
done

if [[ ! -z "${NEO4JLABS_PLUGINS:-}" ]]; then
# NEO4JLABS_PLUGINS should be a json array of plugins like '["graph-algorithms", "apoc-procedures", "streams", "graphql"]'
for plugin_name in $(echo "${NEO4JLABS_PLUGINS}" | jq --raw-output '.[]'); do
load_plugin_from_github "${plugin_name}"
done
fi

[ -f "${EXTENSION_SCRIPT:-}" ] && . ${EXTENSION_SCRIPT}

Expand Down
43 changes: 43 additions & 0 deletions test/test-apoc-download
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
set -o errexit -o nounset
[[ -n "${TRACE:-}" ]] && set -o xtrace

. "$(dirname "$0")/helpers.sh"

readonly image="$1"
readonly series="$2"
readonly cname="neo4j-$(uuidgen)"

# there is no apoc for 2.3
if [[ "${series}" == "2.3" ]] || [[ "${series}" == "3.0" ]]; then
echo "No jq in ${series}: skipping apoc-download test"
exit 0;
fi

# Testing the simple dumb docker run command
readonly result="$(docker run --rm --name "${cname}" --env NEO4JLABS_PLUGINS='["apoc"]' "${image}" ls plugins)"

if [[ "${result}" == *"apoc.jar"* ]]; then
echo "apoc jar in plugins."
else
echo >&2 "Test 1 Failed: missing apoc jar. Found ${result}"
exit 1
fi

# Testing with a custom user and mounted plugins directory
readonly cname2="neo4j-$(uuidgen)"
readonly plugins="$(mktemp -d)"
readonly result2="$(docker run --user "$(id -u):$(id -g)" --rm --name "${cname2}" --env SECURE_FILE_PERMISSIONS=yes --env NEO4JLABS_PLUGINS='["apoc"]' -v "${plugins}:/plugins" "${image}" ls /plugins)"

echo "${result2}"
if [[ "${result2}" == *"apoc.jar"* ]]; then
echo "apoc jar in plugins."
else
echo >&2 "Test 2 Failed: missing apoc jar. Found ${result2}"
exit 1
fi

if [[ ! -f "${plugins}/apoc.jar" ]]; then
echo >&2 "Test 3 Failed: missing apoc jar."
exit 1
fi