[POLICY-66] self-contained features support
installation/enable/disable of self contained package features:
Feature 3rd party dependencies, configuration files, and
custom installation scripts do not need to be packaged within the policy
core base to be used.
Change-Id: I35a472e63bd0f9f7aa6cd0c112d41d2b4604a892
Signed-off-by: Jorge Hernandez <jh1730@att.com>
diff --git a/packages/base/src/files/bin/features b/packages/base/src/files/bin/features
new file mode 100644
index 0000000..7b14644
--- /dev/null
+++ b/packages/base/src/files/bin/features
@@ -0,0 +1,640 @@
+#! /bin/bash
+
+###
+# ============LICENSE_START=======================================================
+# ONAP POLICY
+# ================================================================================
+# Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+# ================================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ============LICENSE_END=========================================================
+##
+
+# #############################################################
+# Features Directory Layout:
+#
+# POLICY_HOME/
+# └── features/
+# └── <feature-name>*/
+# └── [config]/
+# │ └── <config-file>*
+# └── lib/
+# │ └── [dependencies]/
+# │ │ └── <dependent-jar>*
+# │ └── feature/
+# │ └── <feature-jar>
+# └── [install]
+# └── [enable]
+# └── [disable]
+# └── [other-future-operations]
+# └── [other-files]
+#
+# <feature-name> directory should not have the "feature-" prefix.
+# <config-file> preferable with "feature-" prefix.
+#
+# Example:
+#
+# POLICY_HOME/
+# └── features/
+# ├── eelf/
+# │ ├── config/
+# │ │ ├── logback-eelf.xml
+# │ └── lib/
+# │ │ └── dependencies/
+# │ │ │ └── ECOMP-Logging-1.1.0-SNAPSHOT.jar
+# │ │ │ └── eelf-core-1.0.0.jar
+# │ │ └── feature/
+# │ │ └── feature-eelf-1.1.0-SNAPSHOT.jar
+# │ └── install/
+# │ └── enable
+# │ └── disable
+# └── healthcheck/
+# ├── config/
+# │ └── feature-healthcheck.properties
+# └── lib/
+# └── feature/
+# └── feature-healthcheck-1.1.0-SNAPSHOT.jar
+# #############################################################
+
+if [[ ${DEBUG} == y ]]; then
+ echo "-- MAIN --"
+ set -x
+fi
+
+# The directories at play
+
+LIB=${POLICY_HOME}/lib
+CONFIG=${POLICY_HOME}/config
+FEATURES=${POLICY_HOME}/features
+
+if [[ ! ( -d "${LIB}" && -x "${LIB}" ) ]]; then
+ echo "ERROR: no ${LIB} directory"
+ exit 1
+fi
+
+if [[ ! ( -d "${CONFIG}" && -x "${CONFIG}" ) ]]; then
+ echo "ERROR: no ${CONFIG} directory"
+ exit 2
+fi
+
+if [[ ! ( -d "${FEATURES}" && -x "${FEATURES}" ) ]]; then
+ echo "ERROR: no ${FEATURES} directory"
+ exit 3
+fi
+
+# relative per Feature Directory Paths
+
+FEATURE_DEPS="lib/dependencies"
+FEATURE_LIB="lib/feature"
+FEATURE_CONFIG="config"
+FEATURE_INSTALL="install"
+
+featureJars=$(find "${FEATURES}" -name "feature-*.jar" -type f -exec basename {} \; 2> /dev/null)
+if [[ -z ${featureJars} ]]; then
+ echo "no features"
+ usage
+ exit 0
+fi
+
+# default field lengths
+nameLength=20
+versionLength=15
+
+# update field lengths, if needed
+for jar in ${featureJars} ; do
+ # get file name without 'jar' suffix
+ tmp="${jar%\.jar}"
+
+ # remove feature prefix
+ tmp="${tmp#feature-}"
+
+ # get feature name by removing the version portion
+ name="${tmp%%-[0-9]*}"
+
+ # extract version portion of name
+ version="${tmp#${name}-}"
+
+ # grow the size of the name/version field, if needed
+ if (( "${#name}" > nameLength )) ; then
+ nameLength="${#name}"
+ fi
+ if (( "${#version}" > versionLength )) ; then
+ versionLength="${#version}"
+ fi
+done
+
+# ##########################################################
+# usage: usage information
+# ##########################################################
+function usage
+{
+ # print out usage information
+ cat >&2 <<-'EOF'
+ Usage: features status
+ Get enabled/disabled status on all features
+ features enable <feature> ...
+ Enable the specified feature
+ features disable <feature> ...
+ Disable the specified feature
+ EOF
+}
+
+# ##########################################################
+# status: dump out status information
+# ##########################################################
+function status
+{
+ if [[ ${DEBUG} == y ]]; then
+ echo "-- ${FUNCNAME[0]} $@ --"
+ set -x
+ fi
+
+ local tmp name version status
+ local format="%-${nameLength}s %-${versionLength}s %s\n"
+
+ printf "${format}" "name" "version" "status"
+ printf "${format}" "----" "-------" "------"
+
+ for jar in ${featureJars} ; do
+ # get file name without 'jar' suffix
+ tmp="${jar%\.jar}"
+
+ # remove feature prefix
+ tmp="${tmp#feature-}"
+
+ # get feature name by removing the version portion
+ name="${tmp%%-[0-9]*}"
+
+ # extract version portion of name
+ version="${tmp#${name}-}"
+
+ # determine status
+ status=disabled
+ if [[ -e "${LIB}/${jar}" ]] ; then
+ status=enabled
+ fi
+ printf "${format}" "${name}" "${version}" "${status}"
+ done
+}
+
+# ##########################################################
+# depEnableAnalysis(featureName):
+# reports on potential dependency conflicts
+# featureName: name of the feature
+# ##########################################################
+function depEnableAnalysis()
+{
+ if [[ ${DEBUG} == y ]]; then
+ echo "-- ${FUNCNAME[0]} $@ --"
+ set -x
+ fi
+
+ local featureName="$1"
+ local featureDepJars featureDepJarPath depJarName multiVersionJars
+
+ if [[ -z ${featureName} ]]; then
+ echo "WARN: no feature name"
+ return 1
+ fi
+
+ featureDepJars=$(ls "${FEATURES}"/"${featureName}"/"${FEATURE_DEPS}"/*.jar 2> /dev/null)
+ for featureDepJarPath in ${featureDepJars}; do
+ depJarName=$(basename "${featureDepJarPath}")
+
+ # it could be a base jar
+
+ if [[ -f "${LIB}"/"${depJarName}" ]]; then
+ echo "WARN: dependency ${depJarName} already in use"
+ continue
+ fi
+
+ # it could be a link from another feature
+
+ if [[ -L "${LIB}"/"${depJarName}" ]]; then
+ continue
+ fi
+
+ # unadvisable if multiple versions exist
+
+ multiVersionJars=$(ls "${LIB}"/"${depJarName%%-[0-9]*.jar}"-*.jar 2> /dev/null)
+ if [[ -n "${multiVersionJars}" ]]; then
+ echo "WARN: other version of library ${depJarName} present: ${multiVersionJars}"
+ return 2
+ fi
+ done
+}
+
+# ##########################################################
+# configEnableAnalysis(featureName):
+# reports on potential dependency conflicts
+# featureName: name of the feature
+# ##########################################################
+function configEnableAnalysis()
+{
+ if [[ ${DEBUG} == y ]]; then
+ echo "-- ${FUNCNAME[0]} $@ --"
+ set -x
+ fi
+
+ local featureName="$1"
+ local featureConfigs configPath configFileName
+
+ if [[ -z ${featureName} ]]; then
+ echo "WARN: no feature name"
+ return 1
+ fi
+
+ featureConfigs=$(ls "${FEATURES}"/"${featureName}"/"${FEATURE_CONFIG}"/ 2> /dev/null)
+ for configPath in ${featureConfigs}; do
+ configFileName=$(basename "${configPath}")
+ if [[ -e "${LIB}"/"${configFileName}" ]]; then
+ echo "ERROR: a config file of the same name is already in the base: ${configFileName}"
+ return 2
+ fi
+ done
+}
+
+# ##########################################################
+# enableFeatureDeps(featureName):
+# enables feature dependencies
+# featureName: name of the feature
+# ##########################################################
+function enableFeatureDeps()
+{
+ if [[ ${DEBUG} == y ]]; then
+ echo "-- ${FUNCNAME[0]} $@ --"
+ set -x
+ fi
+
+ local featureName="$1"
+ local featureDeps featureDepPath depJarName
+
+ if [[ -z ${featureName} ]]; then
+ echo "WARN: no feature name"
+ return 1
+ fi
+
+ featureDeps=$(ls "${FEATURES}"/"${featureName}"/"${FEATURE_DEPS}"/*.jar 2> /dev/null)
+ for featureDepPath in ${featureDeps}; do
+ depJarName=$(basename "${featureDepPath}")
+ if [[ ! -f "${LIB}"/"${depJarName}" ]]; then
+ ln -s -f "${featureDepPath}" "${LIB}/"
+ fi
+ done
+}
+
+# ##########################################################
+# enableFeatureConfig(featureName):
+# enables feature configuration
+# featureName: name of the feature
+# ##########################################################
+function enableFeatureConfig()
+{
+ if [[ ${DEBUG} == y ]]; then
+ echo "-- ${FUNCNAME[0]} $@ --"
+ set -x
+ fi
+
+ local featureName="$1"
+ local featureConfigs featureConfigPath
+
+ if [[ -z ${featureName} ]]; then
+ echo "WARN: no feature name"
+ return 1
+ fi
+
+ featureConfigs=$(find "${FEATURES}"/"${featureName}"/"${FEATURE_CONFIG}"/ -type f -maxdepth 1 2> /dev/null)
+ for featureConfigPath in ${featureConfigs}; do
+ ln -s -f "${featureConfigPath}" "${CONFIG}/"
+ done
+}
+
+# ##########################################################
+# enableFeatureOp(featureName): 'enable' feature operation
+# featureName: name of the feature
+# ##########################################################
+function enableFeatureOp()
+{
+ if [[ ${DEBUG} == y ]]; then
+ echo "-- ${FUNCNAME[0]} $@ --"
+ set -x
+ fi
+
+ local featureName="$1"
+
+ if [[ -z ${featureName} ]]; then
+ echo "WARN: no feature name"
+ return 1
+ fi
+
+ enableScript="${FEATURES}"/"${featureName}"/"${FEATURE_INSTALL}"/enable
+ if [[ -f ${enableScript} ]]; then
+ (
+ cd "${FEATURES}"/"${featureName}"/"${FEATURE_INSTALL}"
+ chmod u+x enable
+ ./enable
+ )
+ fi
+}
+
+# ##########################################################
+# enableFeature(featureName, featureJar): enables a feature
+# featureName: name of the feature
+# featureJar: path to feature jar implementation
+# ##########################################################
+function enableFeature()
+{
+ if [[ $DEBUG == y ]]; then
+ echo "-- ${FUNCNAME[0]} $@ --"
+ set -x
+ fi
+
+ local featureName="$1"
+ local featureJar="$2"
+
+ if [[ -z ${featureName} ]]; then
+ echo "WARN: no feature name"
+ return 1
+ fi
+
+ if [[ -z ${featureJar} ]]; then
+ echo "WARN: no feature jar"
+ return 2
+ fi
+
+ if ! depEnableAnalysis "${featureName}"; then
+ return 3
+ fi
+
+ if ! configEnableAnalysis "${featureName}"; then
+ return 4
+ fi
+
+ # enable feature itself
+
+ ln -s -f "${featureJar}" "${LIB}/"
+
+ # enable dependent libraries if any
+
+ enableFeatureDeps "${featureName}"
+
+ # enable configuration
+
+ enableFeatureConfig "${featureName}"
+
+ # TODO: run feature install DB scripts if any
+
+ # run custom enable if any
+
+ enableFeatureOp "${featureName}"
+}
+
+# ##########################################################
+# disableFeatureDeps(featureName):
+# disables feature dependencies
+# ##########################################################
+function disableFeatureDeps()
+{
+ if [[ ${DEBUG} == y ]]; then
+ echo "-- ${FUNCNAME[0]} $@ --"
+ set -x
+ fi
+
+ local featureName="$1"
+ local xDepsEnabledMap featureBaseDirs aFeatureDir aFeatureName
+ local featureDeps aFeatureDep
+ local depJarPath depJarName depJarRealPath
+
+ if [[ -z ${featureName} ]]; then
+ echo "WARN: no feature name"
+ return 1
+ fi
+
+ declare -A xDepsEnabledMap
+
+ featureBaseDirs=$(ls -d "${FEATURES}"/*/ 2> /dev/null)
+ for aFeatureDir in ${featureBaseDirs}; do
+ aFeatureName=$(basename "${aFeatureDir}")
+ if [[ "${aFeatureName}" == "${featureName}" ]]; then
+ continue
+ fi
+
+ depJarPaths=$(ls "${aFeatureDir}"/"${FEATURE_DEPS}"/*.jar 2> /dev/null)
+ for depJarPath in ${depJarPaths}; do
+ if [[ "$?" == 0 ]] ; then
+ depJarName=$(basename "${depJarPath}")
+ xDepsEnabledMap[${depJarName}]="${depJarPath}"
+ fi
+ done
+ done
+
+ if [[ ${DEBUG} == y ]]; then
+ echo "${!xDepsEnabledMap[@]}"
+ echo "${xDepsEnabledMap[@]}"
+ fi
+
+ featureDeps=$(ls "${FEATURES}"/"${featureName}"/"${FEATURE_DEPS}"/*.jar 2> /dev/null)
+ for aFeatureDep in ${featureDeps}; do
+ depJarName=$(basename "${aFeatureDep}")
+ if [[ -L "${LIB}"/"${depJarName}" ]]; then
+ depJarRealPath=$(readlink -f "${LIB}"/"${depJarName}")
+ if [[ "${depJarRealPath}" == "${aFeatureDep}" ]]; then
+ rm -f "${LIB}"/"${depJarName}"
+
+ # case there were multiple features using this library
+ # re-enable link fron an enabled feature
+
+ if [[ -n ${xDepsEnabledMap[${depJarName}]} ]]; then
+ ln -s -f "${xDepsEnabledMap[${depJarName}]}" "${LIB}/"
+ fi
+ fi
+ fi
+ done
+}
+
+# ##########################################################
+# disableFeatureConfig(featureName):
+# disables feature configuration
+# featureName: name of the feature
+# ##########################################################
+function disableFeatureConfig()
+{
+ if [[ ${DEBUG} == y ]]; then
+ echo "-- ${FUNCNAME[0]} $@ --"
+ set -x
+ fi
+
+ local featureName="$1"
+ local featureConfigs featureConfigPath
+
+ if [[ -z ${featureName} ]]; then
+ echo "WARN: no feature name"
+ return 1
+ fi
+
+ featureConfigs=$(find "${FEATURES}"/"${featureName}"/"${FEATURE_CONFIG}"/ -type f -maxdepth 1 2> /dev/null)
+ for featureConfigPath in ${featureConfigs}; do
+ configFileName=$(basename "${featureConfigPath}")
+ rm -f "${CONFIG}"/"${configFileName}" 2> /dev/null
+ done
+}
+
+# ##########################################################
+# disableFeatureOp(featureName): 'enable' feature operation
+# featureName: name of the feature
+# ##########################################################
+function disableFeatureOp()
+{
+ if [[ ${DEBUG} == y ]]; then
+ echo "-- ${FUNCNAME[0]} $@ --"
+ set -x
+ fi
+
+ local featureName="$1"
+
+ if [[ -z ${featureName} ]]; then
+ echo "WARN: no feature name"
+ return 1
+ fi
+
+ disableScript="${FEATURES}"/"${featureName}"/"${FEATURE_INSTALL}"/disable
+ if [[ -f ${disableScript} ]]; then
+ (
+ cd "${FEATURES}"/"${featureName}"/"${FEATURE_INSTALL}"
+ chmod u+x disable
+ ./disable
+ )
+ fi
+}
+
+# ##########################################################
+# disableFeature(featureName, featureJar): enables a feature
+# featureName: name of the feature
+# ##########################################################
+function disableFeature()
+{
+ if [[ ${DEBUG} == y ]]; then
+ echo "-- ${FUNCNAME[0]} $@ --"
+ set -x
+ fi
+
+ local featureName="$1"
+
+ if [[ -z ${featureName} ]]; then
+ echo "WARN: no feature name"
+ return
+ fi
+
+ # disable feature itself
+
+ (
+ cd "${LIB}"
+ rm -f feature-"${featureName}"-[0-9]*.jar 2> /dev/null
+ )
+
+ # disable dependencies if any
+
+ disableFeatureDeps "${featureName}"
+
+ # disable configuration if any
+
+ disableFeatureConfig "${featureName}"
+
+ # run feature uninstall DB scripts if any
+ # TODO: future
+
+ # run custom disable if any
+ disableFeatureOp "${featureName}"
+}
+
+case "$1" in
+ status)
+ {
+ # dump out status information
+ status
+ };;
+
+ enable)
+ {
+ if [[ -f "${POLICY_HOME}"/PID ]]; then
+ echo "ERROR: enable: not allowed when policy is running .."
+ echo
+ status
+ exit 10
+ fi
+
+ # enable the specified options
+ shift
+ match=
+ for name in "$@" ; do
+ # look for matches - 'file' has the full path name
+ file=$(ls "${FEATURES}"/"${name}"/"${FEATURE_LIB}"/feature-"${name}"-[0-9]*.jar 2> /dev/null)
+ if [[ "$?" != 0 ]] ; then
+ # no matching file
+ echo "${name}: no such option"
+ else
+ # make sure there is only one feature jar
+ countFeatureJars=$(echo "${file}" | wc -w)
+ if [[ ${countFeatureJars} != 1 ]]; then
+ echo "WARNING: skipping ${name}, ${countFeatureJars} feature libraries found"
+ continue
+ fi
+
+ # found a match (handle multiple matches, just in case)
+ match=true
+
+ enableFeature "${name}" "${file}"
+ fi
+ done
+ if [[ "${match}" ]] ; then
+ echo
+ status
+ fi
+ };;
+
+ disable)
+ {
+ if [[ -f "${POLICY_HOME}"/PID ]]; then
+ echo "ERROR: disable: not allowed when policy is running .."
+ echo
+ status
+ exit 11
+ fi
+
+ # disable the specified options
+ shift
+ match=
+ for name in "$@" ; do
+ # look for matches -- 'file' has the last segment of the path name
+ file=$(ls "${FEATURES}"/"${name}"/"${FEATURE_LIB}"/feature-"${name}"-[0-9]*.jar 2> /dev/null)
+ if [[ "$?" != 0 ]] ; then
+ echo "${name}: no such option"
+ else
+ # found a match (handle multiple matches, just in case)
+ match=true
+
+ disableFeature "${name}"
+ fi
+ done
+ if [[ "${match}" ]] ; then
+ echo
+ status
+ fi
+ };;
+
+ *)
+ {
+ usage
+ };;
+esac
+exit