#!/usr/bin/env bash if ! command -v yq >/dev/null 2>&1 ; then echo "No 'yq' found" exit 1 fi DIR="$(dirname "$(readlink -f "$0")")" export DIR FILES=( "${DIR}/compose/db.yml" ) USE_USERHOSTS=${USE_USERHOSTS:-true} PULL_IMAGES=${PULL_IMAGES:-true} KEEP_VOLUMES=${KEEP_VOLUMES:-false} OPEN_TABS=${OPEN_TABS:-false} PRECREATE_BUCKETS=${PRECREATE_BUCKETS:-metadata,archivedrecordings,archivedreports,eventtemplates,probes,threaddumps,heapdumps,heapdumpreports,logs} LOG_LEVEL=0 CRYOSTAT_HTTP_HOST=${CRYOSTAT_HTTP_HOST:-cryostat} CRYOSTAT_HTTP_PORT=${CRYOSTAT_HTTP_PORT:-8080} USE_PROXY=${USE_PROXY:-true} DEPLOY_GRAFANA=${DEPLOY_GRAFANA:-true} DRY_RUN=${DRY_RUN:-false} USE_TLS=${USE_TLS:-true} SAMPLE_APPS_USE_TLS=${SAMPLE_APPS_USE_TLS:-true} ENFORCE_AGENT_TLS=${ENFORCE_AGENT_TLS:-false} display_usage() { echo "Usage:" column -t -s% <<< " %-h% print this Help text. %-O% Offline mode, do not attempt to pull container images that are already present on this machine. %-p% disable auth Proxy. %-s [none,$(ls -1 ${DIR}/compose/s3-*.yml | xargs basename -a | grep -v none | sort | cut -d/ -f2 | cut -d. -f1 | cut -d- -f2 | paste -sd ',' -)]% S3 implementation to spin up (default \"seaweed\"). %-G% exclude Grafana dashboard and jfr-datasource from deployment. %-r [replicas]% configure a cryostat-reports sidecar instance(s). Optional argument is the number of replicas, which defaults to 1. %-t [all|comma-list]% include sample applications for Testing. Leave blank or use \"all\" to deploy everything, otherwise use a comma-separated list from: $(find ${DIR}/compose/sample_apps -type f -name '*.yml' -exec basename {} \; | cut -d. -f1 | grep -v https | sort | tr '\n' ',' | sed 's/,$//') %-A% disable TLS on sample applications' Agents. %-V% do not discard data storage Volumes on exit. %-X% deploy additional development aid tools. %-c [/path/to/binary]% use specified Container engine (default \"\$(command -v podman)\"). %-b% open a Browser tab for each running service's first mapped port (ex. auth proxy login, database viewer). %-n% do Not apply configuration changes, instead emit the compose YAML that would have been used to stdout. %-k% disable TLS (inseKure) on the auth proxy. %-v% enable Verbose logging. Can be passed multiple times to increase verbosity. " } s3=seaweed container_engine="$(command -v podman)" while getopts "hs:prGtAOVXc:bnkv" opt; do case $opt in h) display_usage exit 0 ;; p) USE_PROXY=false ;; s) s3="${OPTARG}" ;; G) DEPLOY_GRAFANA=false ;; t) nextopt=${!OPTIND} deploy_all_samples=false if [[ -n $nextopt && $nextopt != -* ]] ; then OPTIND=$((OPTIND + 1)) if [ "${nextopt}" = "all" ]; then deploy_all_samples=true else for sample in ${nextopt//,/ }; do file="${DIR}/compose/sample_apps/${sample}.yml" if [ ! -f "${file}" ]; then echo "No such sample app file: ${file}" exit 1 fi FILES+=("${file}") done fi else # there is no nextopt, ie `-t` was passed and there is no argument or the next argument is another flag deploy_all_samples=true fi if [ "${deploy_all_samples}" = "true" ]; then for sample in $(find "${DIR}/compose/sample_apps" -type f -exec basename {} \; | cut -d. -f1 | grep -v https); do FILES+=("${DIR}/compose/sample_apps/${sample}.yml") done fi ;; A) SAMPLE_APPS_USE_TLS=false ENFORCE_AGENT_TLS=false ;; O) PULL_IMAGES=false ;; v) LOG_LEVEL=$((LOG_LEVEL+1)) ;; V) KEEP_VOLUMES=true DATABASE_GENERATION=update ;; X) FILES+=("${DIR}/compose/db-viewer.yml") FILES+=("${DIR}/compose/prometheus.yml") ;; c) container_engine="${OPTARG}" ;; b) OPEN_TABS=true ;; r) FILES+=('./compose/reports.yml') nextopt=${!OPTIND} if [[ -n $nextopt && $nextopt != -* ]] ; then OPTIND=$((OPTIND + 1)) REPORTS_REPLICAS="${nextopt}" else # there is no nextopt, ie `-r` was passed and there is no argument or the next argument is another flag REPORTS_REPLICAS=1 fi ;; n) DRY_RUN=true ;; k) USE_TLS=false ;; *) display_usage exit 1 ;; esac done if [ "${DEPLOY_GRAFANA}" = "true" ]; then FILES+=( "${DIR}/compose/cryostat-grafana.yml" "${DIR}/compose/jfr-datasource.yml" ) fi if [ "${SAMPLE_APPS_USE_TLS}" = "true" ]; then for sample in "${FILES[@]}"; do if [[ ! "${sample}" =~ sample_apps ]]; then continue fi cfg="$(echo "${sample}" | sed -r 's/(.*).yml$/\1_https.yml/')" if [ -f "${cfg}" ]; then FILES+=("${cfg}") fi done fi CRYOSTAT_PROXY_PORT=8080 CRYOSTAT_PROXY_PROTOCOL=http AUTH_PROXY_ALPHA_CONFIG_FILE=auth_proxy_alpha_config_http if [ "${USE_PROXY}" = "true" ]; then FILES+=("${DIR}/compose/auth_proxy.yml") CRYOSTAT_HTTP_HOST=auth CRYOSTAT_HTTP_PORT=8181 if [ "${USE_TLS}" = "true" ]; then FILES+=("${DIR}/compose/auth_proxy_https.yml") CRYOSTAT_PROXY_PORT=8443 CRYOSTAT_PROXY_PROTOCOL=https AUTH_PROXY_ALPHA_CONFIG_FILE=auth_proxy_alpha_config_https fi else FILES+=("${DIR}/compose/no_proxy.yml") if [ "${s3}" != "none" ]; then FILES+=("${DIR}/compose/s3_no_proxy.yml") fi if [ "${DEPLOY_GRAFANA}" = "true" ]; then FILES+=("${DIR}/compose/grafana_no_proxy.yml") fi GRAFANA_DASHBOARD_EXT_URL=http://grafana:3000/ fi if [ $LOG_LEVEL = 0 ]; then CRYOSTAT_LOG_LEVEL=INFO elif [ $LOG_LEVEL = 1 ]; then CRYOSTAT_LOG_LEVEL=DEBUG elif [ $LOG_LEVEL = 2 ]; then CRYOSTAT_LOG_LEVEL=TRACE else CRYOSTAT_LOG_LEVEL=ALL fi export CRYOSTAT_LOG_LEVEL export CRYOSTAT_HTTP_HOST export CRYOSTAT_HTTP_PORT export GRAFANA_DASHBOARD_EXT_URL export DATABASE_GENERATION export CRYOSTAT_PROXY_PORT export CRYOSTAT_PROXY_PROTOCOL export REPORTS_REPLICAS s3Manifest="${DIR}/compose/s3-${s3}.yml" if [ ! -f "${s3Manifest}" ]; then echo "Unknown S3 selection: ${s3}" display_usage exit 2 fi FILES+=("${s3Manifest}") if [ "${s3}" = "none" ]; then STORAGE_PORT="4566" else STORAGE_PORT="$(yq '.services.*.expose[0]' "${s3Manifest}" | grep -v null)" fi export STORAGE_PORT export PRECREATE_BUCKETS if [ "$(basename "${container_engine}")" = "podman" ]; then FILES+=("${DIR}/compose/cryostat.yml") elif [ "$(basename "${container_engine}")" = "docker" ]; then FILES+=("${DIR}/compose/cryostat_docker.yml") else echo "Unknown Container Engine selection: ${container_engine}" display_usage exit 2 fi if [ -z "${S3_INSTANCE_ID}" ]; then S3_INSTANCE_ID="$(openssl rand -hex 5)" fi export S3_INSTANCE_ID function bucketname() { echo "${1}-$(echo "${AWS_ACCESS_KEY_ID}" | head -c 6)-${S3_REGION}-${S3_INSTANCE_ID}" | tr -s '-' | head -c "${BUCKET_NAME_MAX_LENGTH:-63}" } if [ ! "${DRY_RUN}" = "true" ]; then set -xe CRYOSTAT_JAVA_OPTS="-XX:StartFlightRecording=filename=/tmp/onstart.jfr,name=onstart,settings=default,disk=true,maxage=5m -Dcom.sun.management.jmxremote.autodiscovery=true -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9091 -Dcom.sun.management.jmxremote.rmi.port=9091 -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.local.only=false" export CRYOSTAT_JAVA_OPTS else CRYOSTAT_STORAGE_BUCKETS_ARCHIVES_NAME="$(bucketname 'archives')" export CRYOSTAT_STORAGE_BUCKETS_ARCHIVES_NAME CRYOSTAT_STORAGE_BUCKETS_ARCHIVED_REPORTS_NAME="$(bucketname 'archivedreports')" export CRYOSTAT_STORAGE_BUCKETS_ARCHIVED_REPORTS_NAME CRYOSTAT_STORAGE_BUCKETS_EVENT_TEMPLATES_NAME="$(bucketname 'eventtemplates')" export CRYOSTAT_STORAGE_BUCKETS_EVENT_TEMPLATES_NAME CRYOSTAT_STORAGE_BUCKETS_PROBE_TEMPLATES_NAME="$(bucketname 'probetemplates')" export CRYOSTAT_STORAGE_BUCKETS_PROBE_TEMPLATES_NAME CRYOSTAT_STORAGE_BUCKETS_HEAP_DUMPS_NAME="$(bucketname 'heapdumps')" export CRYOSTAT_STORAGE_BUCKETS_HEAP_DUMPS_NAME CRYOSTAT_STORAGE_BUCKETS_THREAD_DUMPS_NAME="$(bucketname 'threaddumps')" export CRYOSTAT_STORAGE_BUCKETS_THREAD_DUMPS_NAME CRYOSTAT_STORAGE_BUCKETS_METADATA_NAME="$(bucketname 'metadata')" export CRYOSTAT_STORAGE_BUCKETS_METADATA_NAME fi CMD=() for file in "${FILES[@]}"; do CMD+=(-f "${file}") done createProxyCfgVolume() { "${container_engine}" volume create auth_proxy_cfg "${container_engine}" container create --name proxy_cfg_helper -v auth_proxy_cfg:/tmp registry.access.redhat.com/ubi9/ubi-micro local cfg cfg="$(mktemp)" chmod 644 "${cfg}" envsubst '$STORAGE_PORT' < "${DIR}/compose/${AUTH_PROXY_ALPHA_CONFIG_FILE}.yml" > "${cfg}" htpasswd="$(mktemp)" htpasswd -ibB "${htpasswd}" "${CRYOSTAT_USER:-user}" "${CRYOSTAT_PASS:-pass}" "${container_engine}" cp "${htpasswd}" proxy_cfg_helper:/tmp/auth_proxy_htpasswd "${container_engine}" cp "${cfg}" proxy_cfg_helper:/tmp/auth_proxy_alpha_config.yml if [ "${DRY_RUN}" = "true" ]; then "${container_engine}" volume export auth_proxy_cfg > auth_proxy_cfg.tar fi rm "${htpasswd}" } createProxyCertsVolume() { "${container_engine}" volume create auth_proxy_certs "${container_engine}" container create --name proxy_certs_helper -v auth_proxy_certs:/certs registry.access.redhat.com/ubi9/ubi-micro if [ -f "${DIR}/compose/auth_certs/certificate.pem" ] && [ -f "${DIR}/compose/auth_certs/private.key" ]; then chmod 644 "${DIR}/compose/auth_certs/private.key" "${container_engine}" cp "${DIR}/compose/auth_certs/certificate.pem" proxy_certs_helper:/certs/certificate.pem "${container_engine}" cp "${DIR}/compose/auth_certs/private.key" proxy_certs_helper:/certs/private.key fi if [ "${DRY_RUN}" = "true" ]; then "${container_engine}" volume export auth_proxy_certs > auth_proxy_certs.tar fi } createLocalstackCfgVolume() { "${container_engine}" volume create localstack_cfg "${container_engine}" container create --name localstack_cfg_helper -v localstack_cfg:/tmp registry.access.redhat.com/ubi9/ubi-micro "${container_engine}" cp "${DIR}/compose/localstack_buckets.sh" localstack_cfg_helper:/tmp if [ "${DRY_RUN}" = "true" ]; then "${container_engine}" volume export localstack_cfg > localstack_cfg.tar fi } createJmxTlsCertVolume() { "${container_engine}" volume create jmxtls_cfg "${container_engine}" container create --name jmxtls_cfg_helper -v jmxtls_cfg:/truststore registry.access.redhat.com/ubi9/ubi-micro if [ -d "${DIR}/truststore" ]; then "${container_engine}" cp "${DIR}/truststore" jmxtls_cfg_helper:/truststore fi if [ "${DRY_RUN}" = "true" ]; then "${container_engine}" volume export jmxtls_cfg > jmxtls_cfg.tar fi } createEventTemplateVolume() { "${container_engine}" volume create templates "${container_engine}" container create --name templates_helper -v templates:/templates registry.access.redhat.com/ubi9/ubi-micro if [ -d "${DIR}/templates" ]; then "${container_engine}" cp "${DIR}/templates" templates_helper:/templates fi if [ "${DRY_RUN}" = "true" ]; then "${container_engine}" volume export templates > templates.tar fi } createProbeTemplateVolume() { "${container_engine}" volume create probes "${container_engine}" container create --name probes_helper -v probes:/probes registry.access.redhat.com/ubi9/ubi-micro if [ -d "${DIR}/probes" ]; then "${container_engine}" cp "${DIR}/probes" probes_helper:/probes fi if [ "${DRY_RUN}" = "true" ]; then "${container_engine}" volume export probes > probes.tar fi } createCredentialVolume() { "${container_engine}" volume create credentials "${container_engine}" container create --name credentials_helper -v credentials:/credentials registry.access.redhat.com/ubi9/ubi-micro if [ -d "${DIR}/credentials" ]; then "${container_engine}" cp "${DIR}/credentials" credentials_helper:/credentials fi if [ "${DRY_RUN}" = "true" ]; then "${container_engine}" volume export credentials > credentials.tar fi } createPrometheusCfgVolume() { "${container_engine}" volume create prometheus_cfg "${container_engine}" container create --name prometheus_cfg_helper -v prometheus_cfg:/etc/prometheus registry.access.redhat.com/ubi9/ubi-micro "${container_engine}" cp "${DIR}/compose/prometheus_config.yml" prometheus_cfg_helper:/etc/prometheus/prometheus.yml if [ "${DRY_RUN}" = "true" ]; then "${container_engine}" volume export prometheus_cfg > prometheus_cfg.tar fi } createS3CertsVolume() { "${container_engine}" volume create s3_certs "${container_engine}" container create --name s3_certs_helper -v s3_certs:/certs registry.access.redhat.com/ubi9/ubi-micro if [ -f "${DIR}/compose/s3_certs/certificate.pem" ] && [ -f "${DIR}/compose/s3_certs/private.key" ]; then chmod 644 "${DIR}/compose/s3_certs/private.key" "${container_engine}" cp "${DIR}/compose/s3_certs/certificate.pem" s3_certs_helper:/certs/certificate.pem "${container_engine}" cp "${DIR}/compose/s3_certs/private.key" s3_certs_helper:/certs/private.key fi if [ "${DRY_RUN}" = "true" ]; then "${container_engine}" volume export s3_certs > s3_certs.tar fi } createDbCertsVolume() { "${container_engine}" volume create db_certs "${container_engine}" container create --name db_certs_helper -v db_certs:/certs registry.access.redhat.com/ubi9/ubi-micro if [ -f "${DIR}/compose/db_certs/certificate.pem" ] && [ -f "${DIR}/compose/db_certs/private.key" ]; then "${container_engine}" cp "${DIR}/compose/db_certs/certificate.pem" db_certs_helper:/certs/certificate.pem "${container_engine}" cp "${DIR}/compose/db_certs/private.key" db_certs_helper:/certs/private.key # Set ownership to postgres user (UID 26) and permissions for PostgreSQL's requirements "${container_engine}" run --rm -v db_certs:/certs registry.access.redhat.com/ubi9/ubi-micro chown -R 26:26 /certs "${container_engine}" run --rm -v db_certs:/certs registry.access.redhat.com/ubi9/ubi-micro chmod 600 /certs/private.key "${container_engine}" run --rm -v db_certs:/certs registry.access.redhat.com/ubi9/ubi-micro chmod 644 /certs/certificate.pem fi if [ "${DRY_RUN}" = "true" ]; then "${container_engine}" volume export db_certs > db_certs.tar fi } createVolumes() { if [ "${USE_PROXY}" = "true" ]; then createProxyCfgVolume fi if [ "${USE_PROXY}" = "true" ] && [ "${USE_TLS}" = "true" ]; then sh "${DIR}/compose/auth_certs/generate.sh" createProxyCertsVolume fi if [ "${s3}" = "localstack" ]; then createLocalstackCfgVolume fi if [ "${s3}" = "seaweed" ]; then sh "${DIR}/compose/s3_certs/generate.sh" createS3CertsVolume fi sh "${DIR}/compose/db_certs/generate.sh" createDbCertsVolume createJmxTlsCertVolume createEventTemplateVolume createProbeTemplateVolume createCredentialVolume if printf '%s\n' "${FILES[@]}" | grep -q "prometheus.yml"; then createPrometheusCfgVolume fi } cleanupVolumes() { if [ "${USE_PROXY}" = "true" ]; then ${container_engine} rm proxy_cfg_helper || true ${container_engine} rm proxy_certs_helper || true ${container_engine} volume rm auth_proxy_cfg || true ${container_engine} volume rm auth_proxy_certs || true fi if [ "${s3}" = "localstack" ]; then ${container_engine} rm localstack_cfg_helper || true ${container_engine} volume rm localstack_cfg || true fi if [ "${s3}" = "seaweed" ]; then ${container_engine} rm s3_certs_helper || true ${container_engine} volume rm s3_certs || true fi ${container_engine} rm db_certs_helper || true ${container_engine} volume rm db_certs || true ${container_engine} rm jmxtls_cfg_helper || true ${container_engine} volume rm jmxtls_cfg || true ${container_engine} rm templates_helper || true ${container_engine} volume rm templates || true ${container_engine} rm probes_helper || true ${container_engine} volume rm probes || true ${container_engine} rm credentials_helper || true ${container_engine} volume rm credentials || true ${container_engine} rm prometheus_cfg_helper || true ${container_engine} volume rm prometheus_cfg || true } if [ "${KEEP_VOLUMES}" != "true" ]; then cleanupVolumes 1>&2 fi if [ "${DRY_RUN}" = "true" ]; then set +xe "${container_engine}" compose \ "${CMD[@]}" \ config createVolumes 1>&2 cleanupVolumes 1>&2 exit 0 fi PIDS=() HOSTSFILE="${HOSTSFILE:-$HOME/.hosts}" cleanup() { set +xe local downFlags=('--remove-orphans') if [ "${KEEP_VOLUMES}" != "true" ]; then downFlags=('--volumes') fi "${container_engine}" compose \ "${CMD[@]}" \ down "${downFlags[@]}" if [ "${SAMPLE_APPS_USE_TLS}" = "true" ]; then bash "${DIR}/compose/agent_certs/generate-agent-certs.bash" clean || true fi if [ "${USE_TLS}" = "true" ]; then rm "${DIR}/compose/auth_certs/certificate.pem" || true rm "${DIR}/compose/auth_certs/private.key" || true fi if [ "${s3}" = "seaweed" ]; then rm "${DIR}/compose/s3_certs/certificate.pem" || true rm "${DIR}/compose/s3_certs/private.key" || true rm "${DIR}/truststore/s3_storage.cer" || true fi rm "${DIR}/compose/db_certs/certificate.pem" || true rm "${DIR}/compose/db_certs/private.key" || true rm "${DIR}/truststore/database.cer" || true cleanupVolumes truncate -s 0 "${HOSTSFILE}" for i in "${PIDS[@]}"; do kill -0 "${i}" && kill "${i}" done set -xe } trap cleanup EXIT cleanup if [ "${SAMPLE_APPS_USE_TLS}" = "true" ]; then bash "${DIR}/compose/agent_certs/generate-agent-certs.bash" generate fi createVolumes setupUserHosts() { # This requires https://github.com/figiel/hosts to work. See README. truncate -s 0 "${HOSTSFILE}" for file in "${FILES[@]}" ; do local hosts hosts="$(yq '.services.*.hostname' "${file}" | { grep -v null || test $? = 1 ; } | sed -e 's/^/localhost /')" echo "${hosts}" >> "${HOSTSFILE}" done } if [ "${USE_USERHOSTS}" = "true" ]; then setupUserHosts fi openBrowserTabs() { # TODO find a way to use 'podman wait --condition=healthy $containerId' instead of polling with curl set +xe local urls=() for file in "${FILES[@]}"; do local yaml yaml="$(yq '.services.* | [{"host": .hostname, "ports": .ports}]' "${file}")" local length length="$(echo "${yaml}" | yq 'length')" for (( i=0; i<"${length}"; i+=1 )) do local host local port if [ "${USE_USERHOSTS}" = "true" ]; then host="$(echo "${yaml}" | yq ".[${i}].host" | { grep -v null || test $? = 1 ; } )" if [ "${host}" = "auth" ]; then host="localhost" fi else host="localhost" fi port="$(echo "${yaml}" | yq ".[${i}].ports[0]" | { grep -v null || test $? = 1 ; } | envsubst | cut -d: -f1)" if [ -n "${host}" ] && [ -n "${port}" ]; then urls+=("http://${host}:${port}") fi done done set -xe echo "Service URLs:" "${urls[@]}" for url in "${urls[@]}"; do ( testSvc() { timeout 1s curl -s -f -o /dev/null "$1" local sc="$?" if [ "${sc}" = "0" ] || [ "${sc}" = "22" ]; then return 0 else return "${sc}" fi } until testSvc "${url}" do sleep 5 done xdg-open "${url}" echo "Opened ${url} in default browser." ) & PIDS+=($!) done } if [ "${OPEN_TABS}" = "true" ]; then openBrowserTabs fi if [ "${PULL_IMAGES}" = "true" ]; then IMAGES=() for file in "${FILES[@]}" ; do images="$(yq '.services.*.image' "${file}" | { grep -v null || test $? = 1; })" for img in ${images}; do IMAGES+=("$(eval echo "${img}")") done done "${container_engine}" pull "${IMAGES[@]}" || true fi "${container_engine}" compose \ "${CMD[@]}" \ up \ --renew-anon-volumes \ --remove-orphans \ --abort-on-container-exit