Browse Source

Cleanly reimplement system/edit-config.in. (#13702)

* Cleanly reimplement system/edit-config.in

- Added support for pulling config files from Docker containers.
- Added auto-detection for Docker containers.
- Use directory the script is in for target directory for config files
  instead of templating it in at build time.
- Prefix error messages with `ERROR:`.
- Robustly check for a valid editor _before_ invoking it.
- Add support for actual command-line options, including a proper
  `--help` option.
- Use prefix matching of absolute paths to determine file validity
  instead of blindly excluding certain path types.
- If editing a non-existing file we do not provide a stock copy of,
  create an empty file instead of throwing an error.
- Make the whole script properly modular.

* Improve robustness of container autodetection.

Instead of relying on the lack of certain directories on a host system,
use some relatively standard checks to determine if we appear to be
running in a container.

* Auto-detect stock config paths at runtinme instead of hard-coding them at build time.

THis will simplify testing of the script, as well as making it a bit
more resilient to users moving things around.

* Add an option to list known config files.

* Fix container environment check to not require root.

* Fix help output.

* Fix path prefix check.

* Fix file path handling.

* Use correct variable when editing files.

* Use correct path for `env`.

* Source profile before running `set -e`.

To prevent questionablly written profiles from causing the script to
exit.

* Produce columnar output when listing valid files.

* Fix copy check.

* Fix build issues.

* fix build issues

* formatting

Co-authored-by: ilyam8 <ilya@netdata.cloud>
Austin S. Hemmelgarn 2 years ago
parent
commit
56e22c16e8
5 changed files with 315 additions and 86 deletions
  1. 0 1
      .gitignore
  2. 6 0
      packaging/docker/run.sh
  3. 0 2
      system/Makefile.am
  4. 309 0
      system/edit-config
  5. 0 83
      system/edit-config.in

+ 0 - 1
.gitignore

@@ -126,7 +126,6 @@ system/netdata-updater.service
 !system/netdata.service.*.in
 system/netdata.plist
 system/netdata-freebsd
-system/edit-config
 system/netdata.crontab
 system/install-service.sh
 

+ 6 - 0
packaging/docker/run.sh

@@ -49,6 +49,12 @@ if mountpoint -q /etc/netdata && [ -z "$(ls -A /etc/netdata)" ]; then
   cp -a /etc/netdata.stock/. /etc/netdata
 fi
 
+if mountpoint -q /etc/netdata; then
+  hostname > /etc/netdata/.container-hostname
+else
+  rm -f /etc/netdata/.container-hostname
+fi
+
 if [ -n "${NETDATA_CLAIM_URL}" ] && [ -n "${NETDATA_CLAIM_TOKEN}" ] && [ ! -f /var/lib/netdata/cloud.d/claimed_id ]; then
   # shellcheck disable=SC2086
   /usr/sbin/netdata-claim.sh -token="${NETDATA_CLAIM_TOKEN}" \

+ 0 - 2
system/Makefile.am

@@ -3,7 +3,6 @@
 
 MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
 CLEANFILES = \
-    edit-config \
     netdata-openrc \
     netdata.logrotate \
     netdata.service \
@@ -55,7 +54,6 @@ dist_libsys_DATA = \
     $(NULL)
 
 dist_noinst_DATA = \
-    edit-config.in \
     install-service.sh.in \
     netdata-openrc.in \
     netdata.logrotate.in \

+ 309 - 0
system/edit-config

@@ -0,0 +1,309 @@
+#!/usr/bin/env sh
+
+# shellcheck disable=SC1091
+[ -f /etc/profile ] && . /etc/profile
+
+set -e
+
+script_dir="$(CDPATH="" cd -- "$(dirname -- "$0")" && pwd -P)"
+
+usage() {
+  check_directories
+  cat <<EOF
+USAGE:
+  ${0} [options] FILENAME
+
+  Copy and edit the stock config file named: FILENAME
+  if FILENAME is already copied, it will be edited as-is.
+
+  Stock config files at: '${NETDATA_STOCK_CONFIG_DIR}'
+  User  config files at: '${NETDATA_USER_CONFIG_DIR}'
+
+  The editor to use can be specified either by setting the EDITOR
+  environment variable, or by using the --editor option.
+
+  The file to edit can also be specified using the --file option.
+
+  For a list of known config files, run '${0} --list'
+EOF
+  exit 0
+}
+
+error() {
+  echo >&2 "ERROR: ${1}"
+}
+
+abspath() {
+  if [ -d "${1}" ]; then
+    echo "$(cd "${1}" && /usr/bin/env PWD= pwd -P)/"
+  else
+    echo "$(cd "$(dirname "${1}")" && /usr/bin/env PWD= pwd -P)/$(basename "${1}")"
+  fi
+}
+
+is_prefix() {
+  echo "${2}" | grep -qE "^${1}"
+  return $?
+}
+
+check_directories() {
+  if [ -e "${script_dir}/.environment" ]; then
+    OLDPATH="${PATH}"
+    # shellcheck disable=SC1091
+    . "${script_dir}/.environment"
+    PATH="${OLDPATH}"
+  fi
+
+  if [ -n "${NETDATA_PREFIX}" ] && [ -d "${NETDATA_PREFIX}/usr/lib/netdata/conf.d" ]; then
+    stock_dir="${NETDATA_PREFIX}/usr/lib/netdata/conf.d"
+  elif [ -n "${NETDATA_PREFIX}" ] && [ -d "${NETDATA_PREFIX}/lib/netdata/conf.d" ]; then
+    stock_dir="${NETDATA_PREFIX}/lib/netdata/conf.d"
+  elif [ -d "${script_dir}/../../usr/lib/netdata/conf.d" ]; then
+    stock_dir="${script_dir}/../../usr/lib/netdata/conf.d"
+  elif [ -d "${script_dir}/../../lib/netdata/conf.d" ]; then
+    stock_dir="${script_dir}/../../lib/netdata/conf.d"
+  elif [ -d "/usr/lib/netdata/conf.d" ]; then
+    stock_dir="/usr/lib/netdata/conf.d"
+  fi
+
+  [ -z "${NETDATA_USER_CONFIG_DIR}" ] && NETDATA_USER_CONFIG_DIR="${script_dir}"
+  [ -z "${NETDATA_STOCK_CONFIG_DIR}" ] && NETDATA_STOCK_CONFIG_DIR="${stock_dir}"
+
+  if [ -z "${NETDATA_STOCK_CONFIG_DIR}" ]; then
+    error "Unable to find stock config directory."
+    exit 1
+  fi
+}
+
+check_editor() {
+  if [ -z "${editor}" ]; then
+    if [ -n "${EDITOR}" ] && command -v "${EDITOR}" >/dev/null 2>&1; then
+      editor="${EDITOR}"
+    elif command -v editor >/dev/null 2>&1; then
+      editor="editor"
+    elif command -v vi >/dev/null 2>&1; then
+      editor="vi"
+    else
+      error "Unable to find a usable editor, tried \${EDITOR} (${EDITOR}), editor, and vi."
+      exit 1
+    fi
+  elif ! command -v "${editor}" >/dev/null 2>&1; then
+    error "Unable to locate user specified editor ${editor}, is it in your PATH?"
+    exit 1
+  fi
+}
+
+running_in_container() {
+  [ -e /.dockerenv ] && return 0
+  [ -e /.dockerinit ] && return 0
+  [ -r /proc/1/environ ] && tr '\000' '\n' </proc/1/environ | grep -Eiq '^container=podman' && return 0
+  grep -qF -e /docker/ -e /libpod- /proc/self/cgroup 2>/dev/null && return 0
+}
+
+get_docker_command() {
+  if [ -x "${docker}" ]; then
+    return 0
+  elif command -v docker >/dev/null 2>&1; then
+    docker="$(command -v docker)"
+  elif command -v podman >/dev/null 2>&1; then
+    docker="$(command -v podman)"
+  else
+    error "Unable to find a usable container tool stack. I support Docker and Podman."
+    exit 1
+  fi
+}
+
+run_in_container() {
+  ${docker} exec "${1}" /bin/sh -c "${2}" || return 1
+  return 0
+}
+
+check_for_container() {
+  get_docker_command
+  ${docker} container inspect "${1}" >/dev/null 2>&1 || return 1
+  run_in_container "${1}" "[ -d \"${NETDATA_STOCK_CONFIG_DIR}\" ]" >/dev/null 2>&1 || return 1
+  return 0
+}
+
+handle_container() {
+  if running_in_container; then
+    return 0
+  elif [ -z "${container}" ] && [ -f "${script_dir}/.container-hostname" ]; then
+    echo >&2 "Autodetected containerized Netdata instance. Attempting to autodetect container ID."
+    possible_container="$(cat "${script_dir}/.container-hostname")"
+    if check_for_container "${possible_container}"; then
+      container="${possible_container}"
+    elif check_for_container netdata; then
+      container="netdata"
+    else
+      error "Could not autodetect container ID. It must be supplied on the command line with the --container option."
+      exit 1
+    fi
+
+    echo >&2 "Found Netdata container with ID or name ${container}"
+  elif [ -n "${container}" ]; then
+    if ! check_for_container "${container}"; then
+      error "No container with ID or name ${container} exists."
+      exit 1
+    fi
+  fi
+}
+
+list_files() {
+  check_directories
+  handle_container
+
+  if test -t; then
+    width="$(tput cols)"
+  fi
+
+  if [ -z "${container}" ]; then
+    if [ "$(uname -s)" = "Linux" ]; then
+      # shellcheck disable=SC2046,SC2086
+      files="$(cd "${NETDATA_STOCK_CONFIG_DIR}" && ls ${width:+-C} ${width:+-w ${width}} $(find . -type f | cut -d '/' -f 2-))"
+    elif [ "$(uname -s)" = "FreeBSD" ]; then
+      if [ -n "${width}" ]; then
+        export COLUMNS="${width}"
+      fi
+
+      # shellcheck disable=SC2046
+      files="$(cd "${NETDATA_STOCK_CONFIG_DIR}" && ls ${width:+-C} $(find . -type f | cut -d '/' -f 2-))"
+    else
+      # shellcheck disable=SC2046
+      files="$(cd "${NETDATA_STOCK_CONFIG_DIR}" && ls $(find . -type f | cut -d '/' -f 2-))"
+    fi
+  else
+    files="$(run_in_container "${container}" "cd /usr/lib/netdata/conf.d && ls ${width:+-C} ${width:+-w ${width}} \$(find . -type f | cut -d '/' -f 2-)")"
+  fi
+
+  if [ -z "${files}" ]; then
+    error "Failed to find any configuration files."
+    exit 1
+  fi
+
+  cat <<EOF
+The following configuration files are known to this script:
+
+${files}
+EOF
+  exit 0
+}
+
+parse_args() {
+  while [ -n "${1}" ]; do
+    case "${1}" in
+      "--help") usage ;;
+      "--list") list_files ;;
+      "--file")
+        if [ -n "${2}" ]; then
+          file="${2}"
+          shift 1
+        else
+          error "No file specified to edit."
+          exit 1
+        fi
+        ;;
+      "--container")
+        if [ -n "${2}" ]; then
+          container="${2}"
+          shift 1
+        else
+          error "No container ID or name specified with the --container option."
+          exit 1
+        fi
+        ;;
+      "--editor")
+        if [ -n "${2}" ]; then
+          editor="${2}"
+          shift 1
+        else
+          error "No editor specified with the --editor option."
+          exit 1
+        fi
+        ;;
+      *)
+        if [ -z "${2}" ]; then
+          file="${1}"
+        else
+          error "Unrecognized option ${1}."
+          exit 1
+        fi
+        ;;
+    esac
+    shift 1
+  done
+
+  [ -z "${file}" ] && usage
+
+  absfile="$(abspath "${file}")"
+  if ! is_prefix "${script_dir}" "${absfile}"; then
+    error "${file} is not located under ${script_dir}"
+    exit 1
+  fi
+
+  file="${absfile##"${script_dir}"}"
+}
+
+copy_native() {
+  if [ ! -w "${NETDATA_USER_CONFIG_DIR}" ]; then
+    error "Cannot write to ${NETDATA_USER_CONFIG_DIR}!"
+    exit 1
+  fi
+
+  if [ -f "${NETDATA_STOCK_CONFIG_DIR}/${1}" ]; then
+    echo >&2 "Copying '${NETDATA_STOCK_CONFIG_DIR}/${1}' to '${NETDATA_USER_CONFIG_DIR}/${1}' ... "
+    cp -p "${NETDATA_STOCK_CONFIG_DIR}/${1}" "${NETDATA_USER_CONFIG_DIR}/${1}" || exit 1
+  else
+    echo >&2 "Creating empty '${NETDATA_USER_CONFIG_DIR}/${1}' ... "
+    touch "${NETDATA_USER_CONFIG_DIR}/${1}" || exit 1
+  fi
+}
+
+copy_container() {
+  if [ ! -w "${NETDATA_USER_CONFIG_DIR}" ]; then
+    error "Cannot write to ${NETDATA_USER_CONFIG_DIR}!"
+    exit 1
+  fi
+
+  if run_in_container "${container}" "[ -f \"${NETDATA_STOCK_CONFIG_DIR}/${1}\" ]"; then
+    echo >&2 "Copying '${NETDATA_STOCK_CONFIG_DIR}/${1}' to '${NETDATA_USER_CONFIG_DIR}/${1}' ... "
+    ${docker} cp -a "${container}:${NETDATA_STOCK_CONFIG_DIR}/${1}" "${NETDATA_USER_CONFIG_DIR}/${1}" || exit 1
+  else
+    echo >&2 "Creating empty '${NETDATA_USER_CONFIG_DIR}/${1}' ... "
+    touch "${NETDATA_USER_CONFIG_DIR}/${1}" || exit 1
+  fi
+}
+
+copy() {
+  if [ -f "${NETDATA_USER_CONFIG_DIR}/${1}" ]; then
+    return 0
+  elif [ -n "${container}" ]; then
+    copy_container "${1}"
+  else
+    copy_native "${1}"
+  fi
+}
+
+edit() {
+  echo >&2 "Editing '${1}' ..."
+
+  # check we can edit
+  if [ ! -w "${1}" ]; then
+    error "Cannot write to ${1}!"
+    exit 1
+  fi
+
+  "${editor}" "${1}"
+  exit $?
+}
+
+main() {
+  parse_args "${@}"
+  check_directories
+  check_editor
+  handle_container
+  copy "${file}"
+  edit "${absfile}"
+}
+
+main "${@}"

+ 0 - 83
system/edit-config.in

@@ -1,83 +0,0 @@
-#!/usr/bin/env sh
-
-[ -f /etc/profile ] && . /etc/profile
-
-file="${1}"
-
-if [ "$(command -v editor)" ]; then
-  EDITOR="${EDITOR-editor}"
-else
-  EDITOR="${EDITOR-vi}"
-fi
-
-[ -z "${NETDATA_USER_CONFIG_DIR}" ] && NETDATA_USER_CONFIG_DIR="@configdir_POST@"
-[ -z "${NETDATA_STOCK_CONFIG_DIR}" ] && NETDATA_STOCK_CONFIG_DIR="@libconfigdir_POST@"
-
-if [ -z "${file}" ]; then
-  cat << EOF
-
-USAGE:
-  ${0} FILENAME
-
-  Copy and edit the stock config file named: FILENAME
-  if FILENAME is already copied, it will be edited as-is.
-
-  The EDITOR shell variable is used to define the editor to be used.
-
-  Stock config files at: '${NETDATA_STOCK_CONFIG_DIR}'
-  User  config files at: '${NETDATA_USER_CONFIG_DIR}'
-
-  Available files in '${NETDATA_STOCK_CONFIG_DIR}' to copy and edit:
-
-EOF
-
-  cd "${NETDATA_STOCK_CONFIG_DIR}" || exit 1
-  ls >&2 -R ./*.conf ./*/*.conf
-  exit 1
-
-fi
-
-edit() {
-  echo >&2 "Editing '${1}' ..."
-
-  # check we can edit
-  if [ ! -w "${1}" ]; then
-    echo >&2 "Cannot write to ${1}! Aborting ..."
-    exit 1
-  fi
-
-  "${EDITOR}" "${1}"
-  exit $?
-}
-
-copy_and_edit() {
-  # check we can copy
-  if [ ! -w "${NETDATA_USER_CONFIG_DIR}" ]; then
-    echo >&2 "Cannot write to ${NETDATA_USER_CONFIG_DIR}! Aborting ..."
-    exit 1
-  fi
-
-  if [ ! -f "${NETDATA_USER_CONFIG_DIR}/${1}" ]; then
-    echo >&2 "Copying '${NETDATA_STOCK_CONFIG_DIR}/${1}' to '${NETDATA_USER_CONFIG_DIR}/${1}' ... "
-    cp -p "${NETDATA_STOCK_CONFIG_DIR}/${1}" "${NETDATA_USER_CONFIG_DIR}/${1}" || exit 1
-  fi
-
-  edit "${NETDATA_USER_CONFIG_DIR}/${1}"
-}
-
-# make sure it is not absolute filename
-c1="$(echo "${file}" | cut -b 1)"
-if [ "${c1}" = "/" ] || [ "${c1}" = "." ]; then
-  echo >&2 "Please don't use filenames starting with '/' or '.'"
-  exit 1
-fi
-
-# already exists
-[ -f "${NETDATA_USER_CONFIG_DIR}/${file}" ] && edit "${NETDATA_USER_CONFIG_DIR}/${file}"
-
-# stock config is valid, copy and edit
-[ -f "${NETDATA_STOCK_CONFIG_DIR}/${file}" ] && copy_and_edit "${file}"
-
-# no such config found
-echo >&2 "File '${file}' is not found in '${NETDATA_STOCK_CONFIG_DIR}'"
-exit 1