diff --git a/.gitignore b/.gitignore index 54acbdde..79be2eb2 100644 --- a/.gitignore +++ b/.gitignore @@ -159,3 +159,5 @@ packages/*/dist packages/*/buid packages/ui-toolkit/styleguide/ packages/*/package-lock.json + +_env diff --git a/docker-compose-api/Dockerfile b/docker-compose-api/Dockerfile new file mode 100644 index 00000000..f28a0296 --- /dev/null +++ b/docker-compose-api/Dockerfile @@ -0,0 +1,41 @@ +FROM quay.io/yldio/docker-compose-api + + +RUN apk add --update bash + + +# Install Consul +# Releases at https://releases.hashicorp.com/consul +RUN set -ex \ + && export CONSUL_VERSION=0.7.5 \ + && export CONSUL_CHECKSUM=40ce7175535551882ecdff21fdd276cef6eaab96be8a8260e0599fadb6f1f5b8 \ + && curl --retry 7 --fail -vo /tmp/consul.zip "https://releases.hashicorp.com/consul/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_linux_amd64.zip" \ + && echo "${CONSUL_CHECKSUM} /tmp/consul.zip" | sha256sum -c \ + && unzip /tmp/consul -d /usr/local/bin \ + && rm /tmp/consul.zip \ + # Create empty directories for Consul config and data \ + && mkdir -p /etc/consul \ + && mkdir -p /var/lib/consul \ + && mkdir /config + + + +# Add Containerpilot and set its configuration +ENV CONTAINERPILOT_VERSION 3.0.0 +ENV CONTAINERPILOT /etc/containerpilot.json + +RUN export CONTAINERPILOT_CHECKSUM=6da4a4ab3dd92d8fd009cdb81a4d4002a90c8b7c \ + && export archive=containerpilot-${CONTAINERPILOT_VERSION}.tar.gz \ + && curl -Lso /tmp/${archive} \ + "https://github.com/joyent/containerpilot/releases/download/${CONTAINERPILOT_VERSION}/${archive}" \ + && echo "${CONTAINERPILOT_CHECKSUM} /tmp/${archive}" | sha1sum -c \ + && tar zxf /tmp/${archive} -C /usr/local/bin \ + && rm /tmp/${archive} + +# Add Containerpilot configuration +COPY etc/containerpilot.json /etc +ENV CONTAINERPILOT /etc/containerpilot.json +COPY bin /bin + +ENTRYPOINT [] +CMD ["/usr/local/bin/containerpilot"] diff --git a/docker-compose-api/bin/prestart.sh b/docker-compose-api/bin/prestart.sh new file mode 100755 index 00000000..795c45b4 --- /dev/null +++ b/docker-compose-api/bin/prestart.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Copy creds from env vars to files on disk +if [ -n ${!TRITON_CREDS_PATH} ] \ + && [ -n ${!TRITON_CA} ] \ + && [ -n ${!TRITON_CERT} ] \ + && [ -n ${!TRITON_KEY} ] +then + mkdir -p ${TRITON_CREDS_PATH} + echo -e "${TRITON_CA}" | tr '#' '\n' > ${TRITON_CREDS_PATH}/ca.pem + echo -e "${TRITON_CERT}" | tr '#' '\n' > ${TRITON_CREDS_PATH}/cert.pem + echo -e "${TRITON_KEY}" | tr '#' '\n' > ${TRITON_CREDS_PATH}/key.pem +fi diff --git a/docker-compose-api/etc/containerpilot.json b/docker-compose-api/etc/containerpilot.json new file mode 100644 index 00000000..ed5494f6 --- /dev/null +++ b/docker-compose-api/etc/containerpilot.json @@ -0,0 +1,45 @@ +{ + "consul": "localhost:8500", + "jobs": [ + { + "name": "setup-config", + "exec": "/bin/prestart.sh" + }, + { + "name": "docker-compose-api", + "port": 4242, + "exec": [ + "python", + "-u", + "./bin/docker-compose" + ], + "health": { + "exec": "true", + "interval": 10, + "ttl": 25 + }, + "when": { + "source": "setup-config", + "once": "exitSuccess" + }, + "restarts": "unlimited" + }, + { + "name": "consul-agent", + "exec": ["/usr/local/bin/consul", "agent", + "-data-dir=/data", + "-config-dir=/config", + "-log-level=err", + "-rejoin", + "-retry-join", "{{ .CONSUL | default "consul" }}", + "-retry-max", "10", + "-retry-interval", "10s"], + "health": { + "exec": "curl -so /dev/null http://localhost:8500", + "interval": 10, + "ttl": 25 + }, + "restarts": "unlimited" + } + ] +} diff --git a/local-compose.yml b/local-compose.yml index 20ffe4b0..1eaeada2 100644 --- a/local-compose.yml +++ b/local-compose.yml @@ -41,6 +41,22 @@ prometheus: dns: - 127.0.0.1 +# Docker-compose wrapper +# Create _env file from running ./setup.sh +docker-compose-api: + build: ./docker-compose-api + links: + - consul:consul + expose: + - 4242 + env_file: + - _env + environment: + - CONSUL=consul + restart: always + dns: + - 127.0.0.1 + traefik: image: d0cker/traefik ports: @@ -93,7 +109,8 @@ rethinkdb: image: rethinkdb restart: always mem_limit: 1g + ports: + - 8081:8080 expose: - - 8080 - 28015 - 29015 diff --git a/packages/portal-api/etc/containerpilot.json5 b/packages/portal-api/etc/containerpilot.json5 index f44b995f..30c489e9 100644 --- a/packages/portal-api/etc/containerpilot.json5 +++ b/packages/portal-api/etc/containerpilot.json5 @@ -67,6 +67,20 @@ interval: '60s' }, restarts: 'unlimited' + }, + { + name: 'onchange-compose-api', + exec: 'pkill -SIGHUP node', + when: { + source: 'watch.docker-compose-api', + each: 'changed' + } + }, + ], + watches: [ + { + name: 'docker-compose-api', + interval: 3 } ], telemetry: { diff --git a/packages/portal-api/lib/index.js b/packages/portal-api/lib/index.js index 9c536d40..c29df3e0 100644 --- a/packages/portal-api/lib/index.js +++ b/packages/portal-api/lib/index.js @@ -2,12 +2,21 @@ const Schema = require('joyent-cp-gql-schema'); const Graphi = require('graphi'); +const Piloted = require('piloted'); const PortalData = require('portal-data'); const Pack = require('../package.json'); const Resolvers = require('./resolvers'); +const internals = {}; + + module.exports = function (server, options, next) { + const docker = Piloted.service('docker-compose-api'); + if (docker) { + options.data.dockerComposeHost = `tcp://${docker.address}:${docker.port}` + } + const data = new PortalData(options.data); data.connect((err) => { if (err) { @@ -16,6 +25,8 @@ module.exports = function (server, options, next) { server.bind(data); + Piloted.on('refresh', internals.refresh(data)); + server.register([ { register: Graphi, @@ -36,3 +47,15 @@ module.exports.attributes = { once: true, multiple: false }; + + +internals.refresh = function (data) { + return () => { + const docker = Piloted.service('docker-compose-api'); + if (!docker) { + return; + } + + data._dockerCompose.client.connect(`tcp://${docker.address}:${docker.port}`); + }; +} diff --git a/packages/portal-api/package.json b/packages/portal-api/package.json index 01e396e0..08f11011 100644 --- a/packages/portal-api/package.json +++ b/packages/portal-api/package.json @@ -32,6 +32,7 @@ "hoek": "^4.1.1", "joi": "^10.6.0", "joyent-cp-gql-schema": "^1.0.4", + "piloted": "^3.1.1", "portal-data": "^1.1.0", "toppsy": "^1.1.0" } diff --git a/setup.sh b/setup.sh new file mode 100755 index 00000000..3d8ee105 --- /dev/null +++ b/setup.sh @@ -0,0 +1,41 @@ +#!/bin/bash +set -e -o pipefail + +# Check for correct configuration +check() { + + command -v docker >/dev/null 2>&1 || { + echo + tput rev # reverse + tput bold # bold + echo 'Docker is required, but does not appear to be installed.' + tput sgr0 # clear + echo 'See https://docs.joyent.com/public-cloud/api-access/docker' + exit 1 + } + + command -v triton >/dev/null 2>&1 || { + echo + tput rev # reverse + tput bold # bold + echo 'Error! Joyent Triton CLI is required, but does not appear to be installed.' + tput sgr0 # clear + echo 'See https://www.joyent.com/blog/introducing-the-triton-command-line-tool' + exit 1 + } + + echo '# docker-compose-client for Triton' > _env + TRITON_CREDS_PATH=/root/.triton + echo TRITON_CREDS_PATH=${TRITON_CREDS_PATH} >> _env + echo DOCKER_CERT_PATH=${TRITON_CREDS_PATH} >> _env + echo TRITON_CA=$(cat "${DOCKER_CERT_PATH}"/ca.pem | tr '\n' '#') >> _env + echo TRITON_CA_PATH=${TRITON_CREDS_PATH}/ca.pem >> _env + echo TRITON_KEY=$(cat "${DOCKER_CERT_PATH}"/key.pem | tr '\n' '#') >> _env + echo TRITON_KEY_PATH=${TRITON_CREDS_PATH}/key.pem >> _env + echo TRITON_CERT=$(cat "${DOCKER_CERT_PATH}"/cert.pem | tr '\n' '#') >> _env + echo TRITON_CERT_PATH=${TRITON_CREDS_PATH}/cert.pem >> _env + echo >> _env +} + +# default behavior +check