summaryrefslogtreecommitdiff
path: root/tools/ds-identify
diff options
context:
space:
mode:
authorScott Moser <smoser@ubuntu.com>2017-02-04 02:24:55 +0000
committerScott Moser <smoser@brickies.net>2017-02-03 21:24:55 -0500
commit9698b0ded3d7e72f54513f248d8da41e08472f68 (patch)
treeb6640cfdf3521d43970559b3fd4e36ad421e18aa /tools/ds-identify
parent9a061c1838c3e9d2ed3f3d73e30248f7a79af7da (diff)
downloadvyos-cloud-init-9698b0ded3d7e72f54513f248d8da41e08472f68.tar.gz
vyos-cloud-init-9698b0ded3d7e72f54513f248d8da41e08472f68.zip
Add tools/ds-identify to identify datasources available.
ds-identify is run here from the generator. If ds-identify does not see any datasources, it can completely disable cloud-init. The big value in this is that if there is no datasource, no python will ever be loaded, and cloud-init will be disabled.o The default policy being added here is: search,found=all,maybe=all,notfound=disabled That means: - enable (in 'datasource_list') all sources that are found. - if none are found, enable all 'maybe'. - if no maybe are found, then disable cloud-init. On platforms without DMI (everything except for aarch64 and x86), the default 'notfound' setting is 'enabled'. This is because many of the detection mechanisms rely on dmi data, which is present only on x86 and aarch64.
Diffstat (limited to 'tools/ds-identify')
-rwxr-xr-xtools/ds-identify1015
1 files changed, 1015 insertions, 0 deletions
diff --git a/tools/ds-identify b/tools/ds-identify
new file mode 100755
index 00000000..203eac0d
--- /dev/null
+++ b/tools/ds-identify
@@ -0,0 +1,1015 @@
+#!/bin/sh
+#
+# ds-identify is configured via /etc/cloud/ds-identify.cfg
+# or on the kernel command line. It takes primarily 2 inputs:
+# datasource: can specify the datasource that should be used.
+# kernel command line option: ci.datasource=<dsname>
+#
+# policy: a string that indicates how ds-identify should operate.
+# kernel command line option: ci.di.policy=<policy>
+# default setting is:
+# search,found=all,maybe=all,notfound=disable
+
+# report: write config to /run/cloud-init/cloud.cfg.report (instead of
+# /run/cloud-init/cloud.cfg, which effectively makes this dry-run).
+# enable: do nothing
+# ds-identify writes no config and just exits success
+# the caller (cloud-init-generator) then enables cloud-init to run
+# just without any aid from ds-identify.
+# disable: disable cloud-init
+#
+# [report,]found=value,maybe=value,notfound=value
+# found: (default=first)
+# first: use the first found do no further checking
+# all: enable all DS_FOUND
+#
+# maybe: (default=all)
+# if nothing returned 'found', then how to handle maybe.
+# no network sources are allowed to return 'maybe'.
+# all: enable all DS_MAYBE
+# none: ignore any DS_MAYBE
+#
+# notfound: (default=disable)
+# disable: disable cloud-init
+# enable: enable cloud-init
+#
+#
+# zesty:
+# policy: found=first,maybe=all,none=disable
+# xenial:
+# policy: found=all,maybe=all,none=enable
+# and then at a later date
+
+
+set -u
+set -f
+UNAVAILABLE="unavailable"
+CR="
+"
+ERROR="error"
+DI_ENABLED="enabled"
+DI_DISABLED="disabled"
+
+DI_DEBUG_LEVEL="${DEBUG_LEVEL:-1}"
+
+PATH_ROOT=${PATH_ROOT:-""}
+PATH_RUN=${PATH_RUN:-"${PATH_ROOT}/run"}
+PATH_SYS_CLASS_DMI_ID=${PATH_SYS_CLASS_DMI_ID:-${PATH_ROOT}/sys/class/dmi/id}
+PATH_SYS_HYPERVISOR=${PATH_SYS_HYPERVISOR:-${PATH_ROOT}/sys/hypervisor}
+PATH_SYS_CLASS_BLOCK=${PATH_SYS_CLASS_BLOCK:-${PATH_ROOT}/sys/class/block}
+PATH_DEV_DISK="${PATH_DEV_DISK:-${PATH_ROOT}/dev/disk}"
+PATH_VAR_LIB_CLOUD="${PATH_VAR_LIB_CLOUD:-${PATH_ROOT}/var/lib/cloud}"
+PATH_DI_CONFIG="${PATH_DI_CONFIG:-${PATH_ROOT}/etc/cloud/ds-identify.cfg}"
+PATH_PROC_CMDLINE="${PATH_PROC_CMDLINE:-${PATH_ROOT}/proc/cmdline}"
+PATH_PROC_1_CMDLINE="${PATH_PROC_1_CMDLINE:-${PATH_ROOT}/proc/1/cmdline}"
+PATH_CLOUD_CONFD="${PATH_CLOUD_CONFD:-${PATH_ROOT}/etc/cloud}"
+PATH_RUN_CI="${PATH_RUN_CI:-${PATH_RUN}/cloud-init}"
+PATH_RUN_CI_CFG=${PATH_RUN_CI_CFG:-${PATH_RUN_CI}/cloud.cfg}
+
+DI_LOG="${DI_LOG:-${PATH_RUN_CI}/ds-identify.log}"
+_DI_LOGGED=""
+
+# set DI_MAIN='noop' in environment to source this file with no main called.
+DI_MAIN=${DI_MAIN:-main}
+
+DI_DEFAULT_POLICY="search,found=all,maybe=all,notfound=${DI_DISABLED}"
+DI_DEFAULT_POLICY_NO_DMI="search,found=all,maybe=all,notfound=${DI_ENABLED}"
+DI_DMI_PRODUCT_NAME=""
+DI_DMI_SYS_VENDOR=""
+DI_DMI_PRODUCT_SERIAL=""
+DI_DMI_PRODUCT_UUID=""
+DI_FS_LABELS=""
+DI_KERNEL_CMDLINE=""
+DI_VIRT=""
+
+DI_UNAME_KERNEL_NAME=""
+DI_UNAME_KERNEL_RELEASE=""
+DI_UNAME_KERNEL_VERSION=""
+DI_UNAME_MACHINE=""
+DI_UNAME_NODENAME=""
+DI_UNAME_OPERATING_SYSTEM=""
+DI_UNAME_CMD_OUT=""
+
+DS_FOUND=0
+DS_NOT_FOUND=1
+DS_MAYBE=2
+
+DI_DSNAME=""
+# this has to match the builtin list in cloud-init, it is what will
+# be searched if there is no setting found in config.
+DI_DSLIST_DEFAULT="MAAS ConfigDrive NoCloud AltCloud Azure Bigstep \
+CloudSigma CloudStack DigitalOcean Ec2 OpenNebula OpenStack OVF SmartOS"
+DI_DSLIST=""
+DI_MODE=""
+DI_REPORT=""
+DI_ON_FOUND=""
+DI_ON_MAYBE=""
+DI_ON_NOTFOUND=""
+
+
+error() {
+ set -- "ERROR:" "$@";
+ debug 0 "$@"
+ stderr "$@"
+}
+warn() {
+ set -- "WARN:" "$@"
+ debug 0 "$@"
+ stderr "$@"
+}
+
+stderr() { echo "$@" 1>&2; }
+
+debug() {
+ local lvl="$1"
+ shift
+ [ "$lvl" -gt "${DI_DEBUG_LEVEL}" ] && return
+
+ if [ "$_DI_LOGGED" != "$DI_LOG" ]; then
+ # first time here, open file descriptor for append
+ case "$DI_LOG" in
+ stderr) :;;
+ ?*/*)
+ if [ ! -d "${DI_LOG%/*}" ]; then
+ mkdir -p "${DI_LOG%/*}" || {
+ stderr "ERROR:" "cannot write to $DI_LOG"
+ DI_LOG="stderr"
+ }
+ fi
+ esac
+ if [ "$DI_LOG" = "stderr" ]; then
+ exec 3>&2
+ else
+ ( exec 3>>"$DI_LOG" ) && exec 3>>"$DI_LOG" || {
+ stderr "ERROR: failed writing to $DI_LOG. logging to stderr.";
+ exec 3>&2
+ DI_LOG="stderr"
+ }
+ fi
+ _DI_LOGGED="$DI_LOG"
+ fi
+ echo "$@" 1>&3
+}
+
+get_dmi_field() {
+ local path="${PATH_SYS_CLASS_DMI_ID}/$1"
+ if [ ! -f "$path" ] || [ ! -r "$path" ]; then
+ _RET="$UNAVAILABLE"
+ return
+ fi
+ read _RET < "${path}" || _RET="$ERROR"
+}
+
+block_dev_with_label() {
+ local p="${PATH_DEV_DISK}/by-label/$1"
+ [ -b "$p" ] || return 1
+ _RET=$p
+ return 0
+}
+
+read_fs_labels() {
+ cached "${DI_FS_LABELS}" && return 0
+ # do not rely on links in /dev/disk which might not be present yet.
+ # note that older blkid versions do not report DEVNAME in 'export' output.
+ local out="" ret=0 oifs="$IFS" line="" delim=","
+ local labels=""
+ if is_container; then
+ # blkid will in a container, or at least currently in lxd
+ # not provide useful information.
+ DI_FS_LABELS="$UNAVAILABLE:container"
+ else
+ out=$(blkid -c /dev/null -o export) || {
+ ret=$?
+ error "failed running [$ret]: blkid -c /dev/null -o export"
+ return $ret
+ }
+ IFS="$CR"
+ set -- $out
+ IFS="$oifs"
+ for line in "$@"; do
+ case "${line}" in
+ LABEL=*) labels="${labels}${line#LABEL=}${delim}";;
+ esac
+ done
+ DI_FS_LABELS="${labels%${delim}}"
+ fi
+}
+
+cached() {
+ [ -n "$1" ] && _RET="$1" && return || return 1
+}
+
+
+has_cdrom() {
+ [ -e "${PATH_ROOT}/dev/cdrom" ]
+}
+
+read_virt() {
+ cached "$DI_VIRT" && return 0
+ local out="" r="" virt="${UNAVAILABLE}"
+ if [ -d /run/systemd ]; then
+ out=$(systemd-detect-virt 2>&1)
+ r=$?
+ if [ $r -eq 0 ] || { [ $r -ne 0 ] && [ "$out" = "none" ]; }; then
+ virt="$out"
+ fi
+ fi
+ DI_VIRT=$virt
+}
+
+is_container() {
+ case "${DI_VIRT}" in
+ lxc|lxc-libvirt|systemd-nspawn|docker|rkt) return 0;;
+ *) return 1;;
+ esac
+}
+
+read_kernel_cmdline() {
+ cached "${DI_KERNEL_CMDLINE}" && return
+ local cmdline="" fpath="${PATH_PROC_CMDLINE}"
+ if is_container; then
+ local p1path="${PATH_PROC_1_CMDLINE}" x=""
+ cmdline="${UNAVAILABLE}:container"
+ if [ -f "$p1path" ] && x=$(tr '\0' ' ' < "$p1path"); then
+ cmdline=$x
+ fi
+ elif [ -f "$fpath" ]; then
+ read cmdline <"$fpath"
+ else
+ cmdline="${UNAVAILABLE}:no-cmdline"
+ fi
+ DI_KERNEL_CMDLINE="$cmdline"
+}
+
+read_dmi_sys_vendor() {
+ cached "${DI_DMI_SYS_VENDOR}" && return
+ get_dmi_field sys_vendor
+ DI_DMI_SYS_VENDOR="$_RET"
+}
+
+read_dmi_product_name() {
+ cached "${DI_DMI_PRODUCT_NAME}" && return
+ get_dmi_field product_name
+ DI_DMI_PRODUCT_NAME="$_RET"
+}
+
+read_dmi_product_uuid() {
+ cached "${DI_DMI_PRODUCT_UUID}" && return
+ get_dmi_field product_uuid
+ DI_DMI_PRODUCT_UUID="$_RET"
+}
+
+read_dmi_product_serial() {
+ cached "${DI_DMI_PRODUCT_SERIAL}" && return
+ get_dmi_field product_serial
+ DI_DMI_PRODUCT_SERIAL="$_RET"
+}
+
+read_uname_info() {
+ # run uname, and parse output.
+ # uname is tricky to parse as it outputs always in a given order
+ # independent of option order. kernel-version is known to have spaces.
+ # 1 -s kernel-name
+ # 2 -n nodename
+ # 3 -r kernel-release
+ # 4.. -v kernel-version(whitespace)
+ # N-2 -m machine
+ # N-1 -o operating-system
+ cached "${DI_UNAME_CMD_OUT}" && return
+ local out="${1:-}" ret=0 buf=""
+ if [ -z "$out" ]; then
+ out=$(uname -snrvmo) || {
+ ret=$?
+ error "failed reading uname with 'uname -snrvmo'"
+ return $ret
+ }
+ fi
+ set -- $out
+ DI_UNAME_KERNEL_NAME="$1"
+ DI_UNAME_NODENAME="$2"
+ DI_UNAME_KERNEL_RELEASE="$3"
+ shift 3
+ while [ $# -gt 2 ]; do
+ buf="$buf $1"
+ shift
+ done
+ DI_UNAME_KERNEL_VERSION="${buf# }"
+ DI_UNAME_MACHINE="$1"
+ DI_UNAME_OPERATING_SYSTEM="$2"
+ DI_UNAME_CMD_OUT="$out"
+ return 0
+}
+
+parse_yaml_array() {
+ # parse a yaml single line array value ([1,2,3], not key: [1,2,3]).
+ # supported with or without leading and closing brackets
+ # ['1'] or [1]
+ # '1', '2'
+ local val="$1" oifs="$IFS" ret="" tok=""
+ val=${val#[}
+ val=${val%]}
+ IFS=","; set -- $val; IFS="$oifs"
+ for tok in "$@"; do
+ trim "$tok"
+ unquote "$_RET"
+ ret="${ret} $_RET"
+ done
+ _RET="${ret# }"
+}
+
+read_datasource_list() {
+ cached "$DI_DSLIST" && return
+ local dslist=""
+ # if DI_DSNAME is set directly, then avoid parsing config.
+ if [ -n "${DI_DSNAME}" ]; then
+ dslist="${DI_DSNAME}"
+ fi
+
+ # LP: #1582323. cc:{'datasource_list': ['name']}
+ # more generically cc:<yaml>[end_cc]
+ local cb="]" ob="["
+ case "$DI_KERNEL_CMDLINE" in
+ *cc:*datasource_list*)
+ t=${DI_KERNEL_CMDLINE##*datasource_list}
+ t=${t%%$cb*}
+ t=${t##*$ob}
+ parse_yaml_array "$t"
+ dslist=${_RET}
+ ;;
+ esac
+ if [ -z "$dslist" ] && check_config datasource_list; then
+ debug 1 "$_RET_fname set datasource_list: $_RET"
+ parse_yaml_array "$_RET"
+ dslist=${_RET}
+ fi
+ if [ -z "$dslist" ]; then
+ dslist=${DI_DSLIST_DEFAULT}
+ debug 1 "no datasource_list found, using default:" $dslist
+ fi
+ DI_DSLIST=$dslist
+ return 0
+}
+
+dmi_product_name_matches() {
+ is_container && return 1
+ case "${DI_DMI_PRODUCT_NAME}" in
+ $1) return 0;;
+ esac
+ return 1
+}
+
+dmi_product_name_is() {
+ is_container && return 1
+ [ "${DI_DMI_PRODUCT_NAME}" = "$1" ]
+}
+
+dmi_sys_vendor_is() {
+ is_container && return 1
+ [ "${DI_DMI_SYS_VENDOR}" = "$1" ]
+}
+
+has_fs_with_label() {
+ local label=",$1,"
+ case "${DI_FS_LABELS}" in
+ *,$label,*) return 0;;
+ esac
+ return 1
+}
+
+nocase_equal() {
+ # nocase_equal(a, b)
+ # return 0 if case insenstive comparision a.lower() == b.lower()
+ # different lengths
+ [ "${#1}" = "${#2}" ] || return 1
+ # case sensitive equal
+ [ "$1" = "$2" ] && return 0
+
+ local delim="-delim-"
+ out=$(echo "$1${delim}$2" | tr A-Z a-z)
+ [ "${out#*${delim}}" = "${out%${delim}*}" ]
+}
+
+check_seed_dir() {
+ # check_seed_dir(name, [required])
+ # check the seed dir /var/lib/cloud/seed/<name> for 'required'
+ # required defaults to 'meta-data'
+ local name="$1"
+ local dir="${PATH_VAR_LIB_CLOUD}/seed/$name"
+ [ -d "$dir" ] || return 1
+ shift
+ if [ $# -eq 0 ]; then
+ set -- meta-data
+ fi
+ local f=""
+ for f in "$@"; do
+ [ -f "$dir/$f" ] || return 1
+ done
+ return 0
+}
+
+probe_floppy() {
+ cached "${STATE_FLOPPY_PROBED}" && return "${STATE_FLOPPY_PROBED}"
+ local fpath=/dev/floppy
+
+ [ -b "$fpath" ] ||
+ { STATE_FLOPPY_PROBED=1; return 1; }
+
+ modprobe --use-blacklist floppy >/dev/null 2>&1 ||
+ { STATE_FLOPPY_PROBED=1; return 1; }
+
+ udevadm settle "--exit-if-exists=$fpath" ||
+ { STATE_FLOPPY_PROBED=1; return 1; }
+
+ [ -b "$fpath" ]
+ STATE_FLOPPY_PROBED=$?
+ return "${STATE_FLOPPY_PROBED}"
+}
+
+
+dscheck_CloudStack() {
+ is_container && return ${DS_NOT_FOUND}
+ dmi_product_name_matches "CloudStack*" && return $DS_FOUND
+ return $DS_NOT_FOUND
+}
+
+dscheck_CloudSigma() {
+ # http://paste.ubuntu.com/23624795/
+ dmi_product_name_is "CloudSigma" && return $DS_FOUND
+ return $DS_NOT_FOUND
+}
+
+check_config() {
+ # somewhat hackily read config for 'key' in files matching 'files'
+ # currently does not respect any hierarchy.
+ local key="$1" files="" bp="${PATH_CLOUD_CONFD}/cloud.cfg"
+ if [ $# -eq 1 ]; then
+ files="$bp ${bp}.d/*.cfg"
+ else
+ files="$*"
+ fi
+ shift
+ set +f; set -- $files; set +f;
+ if [ "$1" = "$files" -a ! -f "$1" ]; then
+ return 1
+ fi
+ local fname="" line="" ret="" found=0 found_fn=""
+ for fname in "$@"; do
+ [ -f "$fname" ] || continue
+ while read line; do
+ line=${line%%#*}
+ case "$line" in
+ $key:\ *|$key:)
+ ret=${line#*:};
+ ret=${ret# };
+ found=$((found+1))
+ found_fn="$fname";;
+ esac
+ done <"$fname"
+ done
+ if [ $found -ne 0 ]; then
+ _RET="$ret"
+ _RET_fname="$found_fn"
+ return 0
+ fi
+ return 1
+}
+
+dscheck_MAAS() {
+ is_container && return "${DS_NOT_FOUND}"
+ # heuristic check for ephemeral boot environment
+ # for maas that do not set 'ci.dsname=' in the ephemeral environment
+ # these have iscsi root and cloud-config-url on the cmdline.
+ local maasiqn="iqn.2004-05.com.ubuntu:maas"
+ case "${DI_KERNEL_CMDLINE}" in
+ *cloud-config-url=*${maasiqn}*|*${maasiqn}*cloud-config-url=*)
+ return ${DS_FOUND}
+ ;;
+ esac
+
+ # check config files written by maas for installed system.
+ local confd="${PATH_CLOUD_CONFD}"
+ local fnmatch="$confd/*maas*.cfg $confd/*kernel_cmdline*.cfg"
+ if check_config "MAAS" "$fnmatch"; then
+ return "${DS_FOUND}"
+ fi
+ return ${DS_NOT_FOUND}
+}
+
+dscheck_NoCloud() {
+ local fslabel="cidata" d=""
+ for d in nocloud nocloud-net; do
+ check_seed_dir "$d" meta-data user-data && return ${DS_FOUND}
+ done
+ if has_fs_with_label "${fslabel}"; then
+ return ${DS_FOUND}
+ fi
+ return ${DS_NOT_FOUND}
+}
+
+check_configdrive_v2() {
+ if has_fs_with_label "config-2"; then
+ return ${DS_FOUND}
+ fi
+ return ${DS_NOT_FOUND}
+}
+
+check_configdrive_v1() {
+ # FIXME: this has to check any file system that is vfat...
+ # for now, just return not found.
+ return ${DS_NOT_FOUND}
+}
+
+dscheck_ConfigDrive() {
+ local ret=""
+ check_configdrive_v2
+ ret=$?
+ [ $DS_FOUND -eq $ret ] && return $ret
+
+ check_configdrive_v1
+}
+
+dscheck_DigitalOcean() {
+ dmi_sys_vendor_is DigitalOcean && return ${DS_FOUND}
+ return ${DS_NOT_FOUND}
+}
+
+dscheck_OpenNebula() {
+ check_seed_dir opennebula && return ${DS_FOUND}
+ has_fs_with_label "CONTEXT" && return ${DS_FOUND}
+ return ${DS_NOT_FOUND}
+}
+
+dscheck_OVF() {
+ local p=""
+ check_seed_dir ovf ovf-env.xml && return "${DS_FOUND}"
+
+ has_cdrom || return ${DS_NOT_FOUND}
+
+ # FIXME: currently just return maybe if there is a cdrom
+ # ovf iso9660 transport does not specify an fs label.
+ # better would be to check if
+ return ${DS_MAYBE}
+}
+
+dscheck_Azure() {
+ # http://paste.ubuntu.com/23630873/
+ # $ grep /sr0 /run/blkid/blkid.tab
+ # <device DEVNO="0x0b00" TIME="1481737655.543841"
+ # UUID="112D211272645f72" LABEL="rd_rdfe_stable.161212-1209"
+ # TYPE="udf">/dev/sr0</device>
+ #
+ check_seed_dir azure ovf-env.xml && return ${DS_FOUND}
+
+ [ "${DI_VIRT}" = "microsoft" ] || return ${DS_NOT_FOUND}
+
+ has_fs_with_label "rd_rdfe_*" && return ${DS_FOUND}
+
+ return ${DS_NOT_FOUND}
+}
+
+dscheck_Bigstep() {
+ # bigstep is activated by presense of seed file 'url'
+ check_seed_dir "bigstep" url && return ${DS_FOUND}
+ return ${DS_NOT_FOUND}
+}
+
+dscheck_Ec2() {
+ # http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/identify_ec2_instances.html
+ # http://paste.ubuntu.com/23630859/
+ local uuid="" hvuuid="$PATH_ROOT/sys/hypervisor/uuid"
+ is_container && return ${DS_NOT_FOUND}
+ # if the (basically) xen specific /sys/hypervisor/uuid starts with 'ec2'
+ if [ -r "$hvuuid" ] && read uuid < "$hvuuid" &&
+ [ "${uuid#ec2}" != "$uuid" ]; then
+ return ${DS_FOUND}
+ fi
+
+ # product uuid and product serial start with case insensitive
+ local uuid=${DI_DMI_PRODUCT_UUID} serial=${DI_DMI_PRODUCT_SERIAL}
+ case "$uuid:$serial" in
+ [Ee][Cc]2*:[Ee][Cc]2)
+ # both start with ec2, now check for case insenstive equal
+ nocase_equal "$uuid" "$serial" && return ${DS_FOUND};;
+ esac
+
+ # search through config files to check for platform
+ local f="" match="${PATH_CLOUD_CONFD}/*ec2*.cfg"
+ # look for the key 'platform' (datasource/ec2/look_alike/behavior)
+ if check_config platform "$match"; then
+ if [ "$platform" != "Unknown" ]; then
+ _RET="$name"
+ return "${DS_FOUND}"
+ fi
+ fi
+
+ return ${DS_NOT_FOUND}
+}
+
+dscheck_GCE() {
+ if dmi_product_name_is "Google Compute Engine"; then
+ return ${DS_FOUND}
+ fi
+ return ${DS_NOT_FOUND}
+}
+
+dscheck_OpenStack() {
+ # the openstack metadata http service
+
+ # if there is a config drive, then do not check metadata
+ # FIXME: if config drive not in the search list, then we should not
+ # do this check.
+ check_configdrive_v2
+ if [ $? -eq ${DS_FOUND} ]; then
+ return ${DS_NOT_FOUND}
+ fi
+ if dmi_product_name_is "OpenStack Nova"; then
+ return ${DS_FOUND}
+ fi
+ case "${DI_VIRT}" in
+ lxc|lxc-libvirt)
+ # FIXME: This could be container on openstack (nova-lxd)
+ # or nova-libvirt-lxc
+ return ${DS_NOT_FOUND}
+ ;;
+ esac
+
+ return ${DS_NOT_FOUND}
+}
+
+dscheck_AliYun() {
+ # aliyun is not enabled by default (LP: #1638931)
+ # so if we are here, it is because the datasource_list was
+ # set to include it. Thus, 'maybe'.
+ return $DS_MAYBE
+}
+
+dscheck_AltCloud() {
+ # ctype: either the dmi product name, or contents of
+ # /etc/sysconfig/cloud-info
+ # if ctype == "vsphere"
+ # device = device with label 'CDROM'
+ # elif ctype == "rhev"
+ # device = /dev/floppy
+ # then, filesystem on that device must have
+ # user-data.txt or deltacloud-user-data.txt
+ local ctype="" dev=""
+ local match_rhev="[Rr][Hh][Ee][Vv]"
+ local match_vsphere="[Vv][Ss][Pp][Hh][Ee][Rr][Ee]"
+ local cinfo="${PATH_ROOT}/etc/sysconfig/cloud-info"
+ if [ -f "$cinfo" ]; then
+ read ctype < "$cinfo"
+ else
+ ctype="${DI_DMI_PRODUCT_NAME}"
+ fi
+ case "$ctype" in
+ ${match_rhev})
+ probe_floppy || return ${DS_NOT_FOUND}
+ dev="/dev/floppy"
+ ;;
+ ${match_vsphere})
+ block_dev_with_label CDROM || return ${DS_NOT_FOUND}
+ dev="$_RET"
+ ;;
+ *) return ${DS_NOT_FOUND};;
+ esac
+
+ # FIXME: need to check $dev for user-data.txt or deltacloud-user-data.txt
+ : "$dev"
+ return $DS_MAYBE
+}
+
+dscheck_SmartOS() {
+ # joyent cloud has two virt types: kvm and container
+ # on kvm, product name on joyent public cloud shows 'SmartDC HVM'
+ # on the container platform, uname's version has: BrandZ virtual linux
+ local smartdc_kver="BrandZ virtual linux"
+ dmi_product_name_matches "SmartDC*" && return $DS_FOUND
+ if [ "${DI_UNAME_KERNEL_VERSION}" = "${smartdc_kver}" ] &&
+ [ "${DI_VIRT}" = "container-other" ]; then
+ return ${DS_FOUND}
+ fi
+ return ${DS_NOT_FOUND}
+}
+
+dscheck_None() {
+ return ${DS_NOT_FOUND}
+}
+
+collect_info() {
+ read_virt
+ read_kernel_cmdline
+ read_uname_info
+ read_config
+ read_datasource_list
+ read_dmi_sys_vendor
+ read_dmi_product_name
+ read_dmi_product_serial
+ read_dmi_product_uuid
+ read_fs_labels
+}
+
+print_info() {
+ collect_info
+ _print_info
+}
+
+_print_info() {
+ local n="" v="" vars=""
+ vars="DMI_PRODUCT_NAME DMI_SYS_VENDOR DMI_PRODUCT_SERIAL"
+ vars="$vars DMI_PRODUCT_UUID"
+ vars="$vars FS_LABELS KERNEL_CMDLINE VIRT"
+ vars="$vars UNAME_KERNEL_NAME UNAME_KERNEL_RELEASE UNAME_KERNEL_VERSION"
+ vars="$vars UNAME_MACHINE UNAME_NODENAME UNAME_OPERATING_SYSTEM"
+ vars="$vars DSNAME DSLIST"
+ vars="$vars MODE REPORT ON_FOUND ON_MAYBE ON_NOTFOUND"
+ for v in ${vars}; do
+ eval n='${DI_'"$v"'}'
+ echo "$v=$n"
+ done
+ echo "pid=$$ ppid=$PPID"
+ is_container && echo "is_container=true" || echo "is_container=false"
+}
+
+write_result() {
+ local runcfg="${PATH_RUN_CI_CFG}" ret="" line=""
+ if [ "$DI_REPORT" = "true" ]; then
+ runcfg="$runcfg.report"
+ fi
+ for line in "$@"; do
+ echo "$line"
+ done > "$runcfg"
+ ret=$?
+ [ $ret -eq 0 ] || {
+ error "failed to write to ${runcfg}"
+ return $ret
+ }
+ return 0
+}
+
+found() {
+ local list="" ds=""
+ # always we write the None datasource last.
+ for ds in "$@" None; do
+ list="${list:+${list}, }$ds"
+ done
+ write_result "datasource_list: [ $list ]"
+ return
+}
+
+trim() {
+ set -- $*
+ _RET="$*"
+}
+
+unquote() {
+ # remove quotes from quoted value
+ local quote='"' tick="'"
+ local val="$1"
+ case "$val" in
+ ${quote}*${quote}|${tick}*${tick})
+ val=${val#?}; val=${val%?};;
+ esac
+ _RET="$val"
+}
+
+_read_config() {
+ # reads config from stdin, modifies _rc scoped environment vars.
+ # rc_policy and _rc_dsname
+ local line="" hash="#" ckey="" key="" val=""
+ while read line; do
+ line=${line%%${hash}*}
+ key="${line%%:*}"
+
+ # no : in the line.
+ [ "$key" = "$line" ] && continue
+ trim "$key"
+ key=${_RET}
+
+ val="${line#*:}"
+ trim "$val"
+ unquote "${_RET}"
+ val=${_RET}
+ case "$key" in
+ datasource) _rc_dsname="$val";;
+ policy) _rc_policy="$val";;
+ esac
+ done
+}
+
+parse_warn() {
+ echo "WARN: invalid value '$2' for key '$1'. Using $1=$3." 1>&2
+}
+
+parse_def_policy() {
+ local _rc_mode="" _rc_report="" _rc_found="" _rc_maybe="" _rc_notfound=""
+ local ret=""
+ parse_policy "$@"
+ ret=$?
+ _def_mode=$_rc_mode
+ _def_report=$_rc_report
+ _def_found=$_rc_found
+ _def_maybe=$_rc_maybe
+ _def_notfound=$_rc_notfound
+ return $ret
+}
+
+parse_policy() {
+ # parse_policy(policy, default)
+ # parse a policy string. sets
+ # _rc_mode (enable|disable,search)
+ # _rc_report true|false
+ # _rc_found first|all
+ # _rc_maybe all|none
+ # _rc_notfound enable|disable
+ local def=""
+ case "$DI_UNAME_MACHINE" in
+ # these have dmi data
+ i?86|x86_64|aarch64) def=${DI_DEFAULT_POLICY};;
+ *) def=${DI_DEFAULT_POLICY_NO_DMI};;
+ esac
+ local policy="$1"
+ local _def_mode="" _def_report="" _def_found="" _def_maybe=""
+ local _def_notfound=""
+ if [ $# -eq 1 ] || [ "$2" != "-" ]; then
+ def=${2:-${def}}
+ parse_def_policy "$def" -
+ fi
+
+ local mode="" report="" found="" maybe="" notfound=""
+ local oifs="$IFS" tok="" val=""
+ IFS=","; set -- $policy; IFS="$oifs"
+ for tok in "$@"; do
+ val=${tok#*=}
+ case "$tok" in
+ report) report=true;;
+ $DI_ENABLED|$DI_DISABLED|search) mode=$tok;;
+ found=all|found=first) found=$val;;
+ maybe=all|maybe=none) maybe=$val;;
+ notfound=$DI_ENABLED|notfound=$DI_DISABLED) notfound=$val;;
+ found=*)
+ parse_warn found "$val" "${_def_found}"
+ found=${_def_found};;
+ maybe=*)
+ parse_warn maybe "$val" "${_def_maybe}"
+ maybe=${_def_maybe};;
+ notfound=*)
+ parse_warn notfound "$val" "${_def_notfound}"
+ notfound=${_def_notfound};;
+ esac
+ done
+ report=${report:-${_def_report:-false}}
+ _rc_report=${report}
+ _rc_mode=${mode:-${_def_mode}}
+ _rc_found=${found:-${_def_found}}
+ _rc_maybe=${maybe:-${_def_maybe}}
+ _rc_notfound=${notfound:-${_def_notfound}}
+}
+
+read_config() {
+ local config=${PATH_DI_CONFIG}
+ local _rc_dsname="" _rc_policy="" ret=""
+ if [ -f "$config" ]; then
+ _read_config < "$config"
+ ret=$?
+ elif [ -e "$config" ]; then
+ error "$config exists but is not a file!"
+ ret=1
+ fi
+ local tok="" key="" val=""
+ for tok in ${DI_KERNEL_CMDLINE}; do
+ key=${tok%%=*}
+ val=${tok#*=}
+ case "$key" in
+ ci.ds) _rc_dsname="$val";;
+ ci.datasource) _rc_dsname="$val";;
+ ci.di.policy) _rc_policy="$val";;
+ esac
+ done
+
+ local _rc_mode _rc_report _rc_found _rc_maybe _rc_notfound
+ parse_policy "${_rc_policy}"
+ debug 1 "policy loaded: mode=${_rc_mode} report=${_rc_report}" \
+ "found=${_rc_found} maybe=${_rc_maybe} notfound=${_rc_notfound}"
+ DI_MODE=${_rc_mode}
+ DI_REPORT=${_rc_report}
+ DI_ON_FOUND=${_rc_found}
+ DI_ON_MAYBE=${_rc_maybe}
+ DI_ON_NOTFOUND=${_rc_notfound}
+
+ DI_DSNAME="${_rc_dsname}"
+ return $ret
+}
+
+
+manual_clean_and_existing() {
+ [ -f "${PATH_VAR_LIB_CLOUD}/instance/manual-clean" ]
+}
+
+main() {
+ local dscheck="" ret_dis=1 ret_en=0
+ collect_info
+
+ if [ ! -e "$PATH_RUN_CI_CFG" ]; then
+ # the first time the generator is run.
+ _print_info >> "$DI_LOG"
+ fi
+
+ case "$DI_MODE" in
+ $DI_DISABLED)
+ debug 1 "mode=$DI_DISABLED. returning $ret_dis"
+ return $ret_dis
+ ;;
+ $DI_ENABLED)
+ debug 1 "mode=$DI_ENABLED. returning $ret_en"
+ return $ret_en;;
+ search) :;;
+ esac
+
+ if [ -n "${DI_DSNAME}" ]; then
+ debug 1 "datasource '$DI_DSNAME' specified."
+ found "$DI_DSNAME"
+ return
+ fi
+
+ if manual_clean_and_existing; then
+ debug 1 "manual_cache_clean enabled. Not writing datasource_list."
+ write_result "# manual_cache_clean."
+ return
+ fi
+
+ # if there is only a single entry in $DI_DSLIST
+ set -- $DI_DSLIST
+ if [ $# -eq 1 ] || [ $# -eq 2 -a "$2" = "None" ] ; then
+ debug 1 "single entry in datasource_list ($DI_DSLIST) use that."
+ found "$@"
+ return
+ fi
+
+ local found="" ret="" ds="" maybe=""
+ for ds in ${DI_DSLIST}; do
+ dscheck_fn="dscheck_${ds}"
+ debug 2 "Checking for datasource '$ds' via '$dscheck_fn'"
+ if ! type "$dscheck_fn" >/dev/null 2>&1; then
+ warn "No check method '$dscheck_fn' for datasource '$ds'"
+ continue
+ fi
+ $dscheck_fn
+ ret="$?"
+ case "$ret" in
+ $DS_FOUND)
+ debug 1 "check for '$ds' returned found";
+ found="${found} $ds";;
+ $DS_MAYBE)
+ debug 1 "check for $ds returned maybe";
+ maybe="${maybe} $ds";;
+ *) debug 2 "check for $ds returned not-found[$ret]";;
+ esac
+ done
+
+ debug 2 "found=$found maybe=$maybe"
+ set -- $found
+ if [ $# -ne 0 ]; then
+ if [ $# -eq 1 ]; then
+ debug 1 "Found single datasource: $1"
+ else
+ # found=all
+ debug 1 "Found $# datasources found=${DI_ON_FOUND}: $*"
+ if [ "${DI_ON_FOUND}" = "first" ]; then
+ set -- "$1"
+ fi
+ fi
+ found "$@"
+ return
+ fi
+
+ set -- $maybe
+ if [ $# -ne 0 -a "${DI_ON_MAYBE}" != "none" ]; then
+ debug 1 "$# datasources returned maybe: $*"
+ found "$@"
+ return
+ fi
+
+ case "$DI_ON_NOTFOUND" in
+ $DI_DISABLED)
+ debug 1 "No result. notfound=$DI_DISABLED. returning $ret_dis."
+ return $ret_dis
+ ;;
+ $DI_ENABLED)
+ debug 1 "notfound=$DI_ENABLED. returning $ret_en"
+ return $ret_en;;
+ esac
+
+ error "Unexpected result"
+ return 3
+}
+
+noop() {
+ :
+}
+
+case "${DI_MAIN}" in
+ main|print_info|noop) "${DI_MAIN}" "$@";;
+ *) error "unexpected value for DI_MAIN"; exit 1;;
+esac
+
+# vi: syntax=sh ts=4 expandtab