2290 lines
66 KiB
Bash
Executable File
2290 lines
66 KiB
Bash
Executable File
#!/bin/sh
|
|
# shellcheck disable=2015,2039,2162,2166,3043
|
|
#
|
|
# ds-identify is configured via /etc/cloud/ds-identify.cfg
|
|
# or on the kernel command line. It takes the following inputs:
|
|
#
|
|
# datasource: can specify the datasource that should be used.
|
|
# kernel command line option: ci.datasource=<dsname> or ci.ds=<dsname>
|
|
# example line in /etc/cloud/ds-identify.cfg:
|
|
# datasource: Ec2
|
|
#
|
|
# policy: a string that indicates how ds-identify should operate.
|
|
#
|
|
# The format is:
|
|
# <mode>,found=value,maybe=value,notfound=value
|
|
# default setting is:
|
|
# search,found=all,maybe=none,notfound=disabled
|
|
#
|
|
# kernel command line option: ci.di.policy=<policy>
|
|
# example line in /etc/cloud/ds-identify.cfg:
|
|
# policy: search,found=all,maybe=none,notfound=disabled
|
|
#
|
|
#
|
|
# Mode:
|
|
# disabled: disable cloud-init
|
|
# enabled: enable cloud-init.
|
|
# 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.
|
|
# search: determine which source or sources should be used
|
|
# and write the result (datasource_list) to
|
|
# /run/cloud-init/cloud.cfg
|
|
# report: basically 'dry run' for search. results are still written
|
|
# to the file, but are namespaced under the top level key
|
|
# 'di_report' Thus cloud-init is not affected, but can still
|
|
# see the result.
|
|
#
|
|
# found,maybe,notfound:
|
|
# found: (default=all)
|
|
# first: use the first found do no further checking
|
|
# all: enable all DS_FOUND
|
|
#
|
|
# maybe: (default=none)
|
|
# 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=disabled)
|
|
# disabled: disable cloud-init
|
|
# enabled: enable cloud-init
|
|
#
|
|
# ci.datasource.ec2.strict_id: (true|false|warn[,0-9])
|
|
# if ec2 datasource does not strictly match,
|
|
# return not_found if true
|
|
# return maybe if false or warn*.
|
|
#
|
|
|
|
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_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_DI_ENV="${PATH_DI_ENV:-${PATH_ROOT}/usr/libexec/ds-identify-env}"
|
|
PATH_PROC_CMDLINE="${PATH_PROC_CMDLINE:-${PATH_ROOT}/proc/cmdline}"
|
|
PATH_PROC_1_CMDLINE="${PATH_PROC_1_CMDLINE:-${PATH_ROOT}/proc/1/cmdline}"
|
|
PATH_PROC_1_ENVIRON="${PATH_PROC_1_ENVIRON:-${PATH_ROOT}/proc/1/environ}"
|
|
PATH_PROC_UPTIME=${PATH_PROC_UPTIME:-${PATH_ROOT}/proc/uptime}
|
|
PATH_ETC_CLOUD="${PATH_ETC_CLOUD:-${PATH_ROOT}/etc/cloud}"
|
|
PATH_ETC_CI_CFG="${PATH_ETC_CI_CFG:-${PATH_ETC_CLOUD}/cloud.cfg}"
|
|
PATH_ETC_CI_CFG_D="${PATH_ETC_CI_CFG_D:-${PATH_ETC_CI_CFG}.d}"
|
|
|
|
# Declare global here, so they can be overwritten from the outside.
|
|
# if not overwritten from the outside, we'll populate them with system default
|
|
# paths, once we have determined the system we're running on.
|
|
# This is done in set_run_path(), which must run after read_uname_info().
|
|
PATH_RUN="${PATH_RUN:-}"
|
|
PATH_RUN_CI="${PATH_RUN_CI:-}"
|
|
PATH_RUN_CI_CFG="${PATH_RUN_CI_CFG:-}"
|
|
PATH_RUN_DI_RESULT="${PATH_RUN_DI_RESULT:-}"
|
|
|
|
DI_LOG="${DI_LOG:-}"
|
|
_DI_LOGGED=""
|
|
|
|
# set DI_MAIN='noop' in environment to source this file with no main called.
|
|
DI_MAIN=${DI_MAIN:-main}
|
|
|
|
DI_BLKID_EXPORT_OUT=""
|
|
DI_GEOM_LABEL_STATUS_OUT=""
|
|
DI_DEFAULT_POLICY="search,found=all,maybe=none,notfound=${DI_DISABLED}"
|
|
DI_DEFAULT_POLICY_NO_DMI="search,found=all,maybe=none,notfound=${DI_DISABLED}"
|
|
DI_DMI_BOARD_NAME=""
|
|
DI_DMI_CHASSIS_ASSET_TAG=""
|
|
DI_DMI_PRODUCT_NAME=""
|
|
DI_DMI_SYS_VENDOR=""
|
|
DI_DMI_PRODUCT_SERIAL=""
|
|
DI_DMI_PRODUCT_UUID=""
|
|
DI_FS_LABELS=""
|
|
DI_FS_UUIDS=""
|
|
DI_ISO9660_DEVS=""
|
|
DI_KERNEL_CMDLINE=""
|
|
DI_VIRT=""
|
|
DI_PID_1_PRODUCT_NAME=""
|
|
|
|
DI_UNAME_KERNEL_NAME=""
|
|
DI_UNAME_KERNEL_VERSION=""
|
|
DI_UNAME_MACHINE=""
|
|
DI_UNAME_CMD_OUT=""
|
|
|
|
DS_FOUND=0
|
|
DS_NOT_FOUND=1
|
|
DS_MAYBE=2
|
|
|
|
DI_SYSTEMD_VIRTUALIZATION=${SYSTEMD_VIRTUALIZATION:-}
|
|
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 Vultr AliYun Ec2 GCE OpenNebula OpenStack \
|
|
VMware OVF SmartOS Scaleway Hetzner IBMCloud Oracle Exoscale RbxCloud UpCloud \
|
|
LXD NWCS Akamai WSL CloudCIX"
|
|
DI_DSLIST=""
|
|
DI_MODE=""
|
|
DI_ON_FOUND=""
|
|
DI_ON_MAYBE=""
|
|
DI_ON_NOTFOUND=""
|
|
|
|
DI_EC2_STRICT_ID_DEFAULT="true"
|
|
|
|
_IS_IBM_CLOUD=""
|
|
|
|
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
|
|
|
|
[ "$DI_LOG" = "" ] && DI_LOG="stderr"
|
|
|
|
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_kenv_field() {
|
|
local sys_field="$1" kenv_field="" val=""
|
|
command -v kenv >/dev/null 2>&1 || {
|
|
warn "No kenv program. Cannot read $sys_field."
|
|
return 1
|
|
}
|
|
case "$sys_field" in
|
|
board_asset_tag) kenv_field="smbios.planar.tag";;
|
|
board_vendor) kenv_field='smbios.planar.maker';;
|
|
board_name) kenv_field='smbios.planar.product';;
|
|
board_serial) kenv_field='smbios.planar.serial';;
|
|
board_version) kenv_field='smbios.planar.version';;
|
|
bios_date) kenv_field='smbios.bios.reldate';;
|
|
bios_vendor) kenv_field='smbios.bios.vendor';;
|
|
bios_version) kenv_field='smbios.bios.version';;
|
|
chassis_asset_tag) kenv_field='smbios.chassis.tag';;
|
|
chassis_vendor) kenv_field='smbios.chassis.maker';;
|
|
chassis_serial) kenv_field='smbios.chassis.serial';;
|
|
chassis_version) kenv_field='smbios.chassis.version';;
|
|
sys_vendor) kenv_field='smbios.system.maker';;
|
|
product_name) kenv_field='smbios.system.product';;
|
|
product_serial) kenv_field='smbios.system.serial';;
|
|
product_uuid) kenv_field='smbios.system.uuid';;
|
|
*) error "Unknown field $sys_field. Cannot call kenv."
|
|
return 1;;
|
|
esac
|
|
val=$(kenv -q "$kenv_field" 2>/dev/null) || return 1
|
|
_RET="$val"
|
|
}
|
|
|
|
get_sysctl_field() {
|
|
local sys_field="$1" sysctl_field="" val=""
|
|
command -v sysctl >/dev/null 2>&1 || {
|
|
warn "No sysctl program. Cannot read $sys_field."
|
|
return 1
|
|
}
|
|
case "$sys_field" in
|
|
chassis_vendor) sysctl_field='hw.vendor';;
|
|
chassis_serial) sysctl_field='hw.type';;
|
|
chassis_version) sysctl_field='hw.uuid';;
|
|
sys_vendor) sysctl_field='hw.vendor';;
|
|
product_name) sysctl_field='hw.product';;
|
|
product_serial) sysctl_field='hw.uuid';;
|
|
product_uuid) sysctl_field='hw.uuid';;
|
|
*) error "Unknown field $sys_field. Cannot call sysctl."
|
|
return 1;;
|
|
esac
|
|
val=$(sysctl -nq "$sysctl_field" 2>/dev/null) || return 1
|
|
_RET="$val"
|
|
}
|
|
|
|
dmi_decode() {
|
|
local sys_field="$1" dmi_field="" val=""
|
|
command -v dmidecode >/dev/null 2>&1 || {
|
|
warn "No dmidecode program. Cannot read $sys_field."
|
|
return 1
|
|
}
|
|
case "$sys_field" in
|
|
sys_vendor) dmi_field="system-manufacturer";;
|
|
product_name) dmi_field="system-product-name";;
|
|
product_uuid) dmi_field="system-uuid";;
|
|
product_serial) dmi_field="system-serial-number";;
|
|
chassis_asset_tag) dmi_field="chassis-asset-tag";;
|
|
*) error "Unknown field $sys_field. Cannot call dmidecode."
|
|
return 1;;
|
|
esac
|
|
val=$(dmidecode --quiet "--string=$dmi_field" 2>/dev/null) || return 1
|
|
_RET="$val"
|
|
}
|
|
|
|
get_dmi_field() {
|
|
_RET="$UNAVAILABLE"
|
|
|
|
if [ "$DI_UNAME_KERNEL_NAME" = "FreeBSD" -o "$DI_UNAME_KERNEL_NAME" = "Dragonfly" ]; then
|
|
get_kenv_field "$1" || _RET="$ERROR"
|
|
return $?
|
|
elif [ "$DI_UNAME_KERNEL_NAME" = "OpenBSD" ]; then
|
|
get_sysctl_field "$1" || _RET="$ERROR"
|
|
return $?
|
|
fi
|
|
|
|
local path="${PATH_SYS_CLASS_DMI_ID}/$1"
|
|
if [ -d "${PATH_SYS_CLASS_DMI_ID}" ]; then
|
|
if [ -f "$path" ] && [ -r "$path" ]; then
|
|
read _RET < "${path}" || _RET="$ERROR"
|
|
return
|
|
fi
|
|
# if `/sys/class/dmi/id` exists, but not the object we're looking for,
|
|
# do *not* fallback to dmidecode!
|
|
return
|
|
fi
|
|
dmi_decode "$1" || _RET="$ERROR"
|
|
return
|
|
}
|
|
|
|
block_dev_with_label() {
|
|
local p="${PATH_DEV_DISK}/by-label/$1"
|
|
[ -b "$p" ] || return 1
|
|
_RET=$p
|
|
return 0
|
|
}
|
|
|
|
ensure_sane_path() {
|
|
local t
|
|
for t in /sbin /usr/sbin /bin /usr/bin; do
|
|
case ":$PATH:" in
|
|
*:$t:*|*:$t/:*) continue;;
|
|
esac
|
|
PATH="${PATH:+${PATH}:}$t"
|
|
done
|
|
}
|
|
|
|
blkid_export() {
|
|
# call 'blkid -c /dev/null export', set DI_BLKID_EXPORT_OUT
|
|
cached "$DI_BLKID_EXPORT_OUT" && return 0
|
|
local out="" ret=0
|
|
out=$(blkid -c /dev/null -o export) && DI_BLKID_EXPORT_OUT="$out" || {
|
|
ret=$?
|
|
error "failed running [$ret]: blkid -c /dev/null -o export"
|
|
DI_BLKID_EXPORT_OUT="$UNAVAILABLE"
|
|
}
|
|
return $ret
|
|
}
|
|
|
|
read_fs_info_linux() {
|
|
# do not rely on links in /dev/disk which might not be present yet.
|
|
# Note that blkid < 2.22 (centos6, trusty) do not output DEVNAME.
|
|
# that means that DI_ISO9660_DEVS will not be set.
|
|
if is_container; then
|
|
# blkid will in a container, or at least currently in lxd
|
|
# not provide useful information.
|
|
DI_FS_LABELS="$UNAVAILABLE:container"
|
|
DI_ISO9660_DEVS="$UNAVAILABLE:container"
|
|
return
|
|
fi
|
|
local oifs="$IFS" line="" delim=","
|
|
local ret=0 labels="" dev="" label="" ftype="" isodevs="" uuids=""
|
|
|
|
blkid_export
|
|
ret=$?
|
|
[ "$DI_BLKID_EXPORT_OUT" = "$UNAVAILABLE" ] && {
|
|
DI_FS_LABELS="$UNAVAILABLE:error"
|
|
DI_ISO9660_DEVS="$UNAVAILABLE:error"
|
|
DI_FS_UUIDS="$UNAVAILABLE:error"
|
|
return $ret
|
|
}
|
|
|
|
# 'set --' will collapse multiple consecutive entries in IFS for
|
|
# whitespace characters (\n, tab, " ") so we cannot rely on getting
|
|
# empty lines in "$@" below.
|
|
|
|
# shellcheck disable=2086
|
|
{ IFS="$CR"; set -- $DI_BLKID_EXPORT_OUT; IFS="$oifs"; }
|
|
|
|
for line in "$@"; do
|
|
case "${line}" in
|
|
DEVNAME=*)
|
|
[ -n "$dev" -a "$ftype" = "iso9660" ] &&
|
|
isodevs="${isodevs},${dev}=$label"
|
|
ftype=""; dev=""; label="";
|
|
dev=${line#DEVNAME=};;
|
|
LABEL=*|LABEL_FATBOOT=*)
|
|
label="${line#*=}";
|
|
labels="${labels}${label}${delim}";;
|
|
TYPE=*) ftype=${line#TYPE=};;
|
|
UUID=*) uuids="${uuids}${line#UUID=}$delim";;
|
|
esac
|
|
done
|
|
[ -n "$dev" -a "$ftype" = "iso9660" ] &&
|
|
isodevs="${isodevs},${dev}=$label"
|
|
|
|
DI_FS_LABELS="${labels%"${delim}"}"
|
|
DI_FS_UUIDS="${uuids%"${delim}"}"
|
|
DI_ISO9660_DEVS="${isodevs#,}"
|
|
}
|
|
|
|
geom_label_status_as() {
|
|
# call 'geom label status -as', set DI_GEOM_LABEL_STATUS_OUT
|
|
cached "$DI_GEOM_LABEL_STATUS_OUT" && return 0
|
|
local out="" ret=0
|
|
out=$(geom label status -as) && DI_GEOM_LABEL_STATUS_OUT="$out" || {
|
|
ret=$?
|
|
error "failed running [$ret]: geom label status -as"
|
|
DI_GEOM_LABEL_STATUS_OUT="$UNAVAILABLE"
|
|
}
|
|
return $ret
|
|
}
|
|
|
|
read_fs_info_freebsd() {
|
|
local oifs="$IFS" line="" delim=","
|
|
local ret=0 labels="" dev="" label="" ftype="" isodevs=""
|
|
|
|
geom_label_status_as
|
|
ret=$?
|
|
[ "$DI_GEOM_LABEL_STATUS_OUT" = "$UNAVAILABLE" ] && {
|
|
DI_FS_LABELS="$UNAVAILABLE:error"
|
|
DI_ISO9660_DEVS="$UNAVAILABLE:error"
|
|
return $ret
|
|
}
|
|
|
|
# The expected output looks like this:
|
|
# gpt/gptboot0 N/A vtbd1p1
|
|
# gpt/swap0 N/A vtbd1p2
|
|
# iso9660/cidata N/A vtbd2
|
|
|
|
# shellcheck disable=2086
|
|
{ IFS="$CR"; set -- $DI_GEOM_LABEL_STATUS_OUT; IFS="$oifs"; }
|
|
|
|
for line in "$@"; do
|
|
# shellcheck disable=2086
|
|
set -- $line
|
|
provider=$1
|
|
ftype="${provider%/*}"
|
|
label="${provider#*/}"
|
|
dev=$3
|
|
|
|
[ -n "$dev" -a "$ftype" = "iso9660" ] &&
|
|
isodevs="${isodevs},${dev}=$label"
|
|
|
|
labels="${labels}${label}${delim}"
|
|
done
|
|
|
|
DI_FS_LABELS="${labels%"${delim}"}"
|
|
DI_ISO9660_DEVS="${isodevs#,}"
|
|
}
|
|
|
|
read_fs_info() {
|
|
# After calling its subfunctions, read_fs_info() will set the following
|
|
# variables:
|
|
#
|
|
# - DI_FS_LABELS
|
|
# - DI_ISO9660_DEVS
|
|
# - DI_FS_UUIDS
|
|
|
|
if [ "$DI_UNAME_KERNEL_NAME" = "FreeBSD" -o "$DI_UNAME_KERNEL_NAME" = "Dragonfly" ]; then
|
|
read_fs_info_freebsd
|
|
return $?
|
|
else
|
|
read_fs_info_linux
|
|
return $?
|
|
fi
|
|
}
|
|
|
|
cached() {
|
|
[ -n "$1" ] && _RET="$1" && return || return 1
|
|
}
|
|
|
|
detect_virt() {
|
|
local virt="${UNAVAILABLE}" r="" out=""
|
|
if [ -d "${PATH_ROOT}/run/systemd" ]; then
|
|
if [ -n "$DI_SYSTEMD_VIRTUALIZATION" ]; then
|
|
virt=${DI_SYSTEMD_VIRTUALIZATION#*:}
|
|
debug 2 "detected $virt via env variable SYSTEMD_VIRTUALIZATION"
|
|
else
|
|
# required for compatibility with systemd version <251
|
|
out=$(systemd-detect-virt 2>&1)
|
|
r=$?
|
|
if [ $r -eq 0 ] || { [ $r -ne 0 ] && [ "$out" = "none" ]; }; then
|
|
virt="$out"
|
|
fi
|
|
debug 2 "detected $virt via ds-identify"
|
|
fi
|
|
elif command -v virt-what >/dev/null 2>&1; then
|
|
# Map virt-what's names to those systemd-detect-virt that
|
|
# don't match up.
|
|
out=$(virt-what 2>&1 | head -n 1) && {
|
|
case "$out" in
|
|
ibm_systemz-zvm) virt="zvm" ;;
|
|
hyperv) virt="microsoft" ;;
|
|
virtualbox) virt="oracle" ;;
|
|
xen-domU) virt="xen" ;;
|
|
*) virt="$out"
|
|
esac
|
|
}
|
|
elif [ "$DI_UNAME_KERNEL_NAME" = "FreeBSD" -o "$DI_UNAME_KERNEL_NAME" = "Dragonfly" ]; then
|
|
# Map FreeBSD's vm_guest names to those systemd-detect-virt that
|
|
# don't match up. See
|
|
# https://github.com/freebsd/freebsd/blob/master/sys/kern/subr_param.c#L144-L160
|
|
# https://www.freedesktop.org/software/systemd/man/systemd-detect-virt.html
|
|
#
|
|
# systemd | kern.vm_guest
|
|
# ---------------------+---------------
|
|
# none | none
|
|
# kvm | kvm
|
|
# vmware | vmware
|
|
# microsoft | hv
|
|
# oracle | vbox
|
|
# xen | xen
|
|
# parallels | parallels
|
|
# bhyve | bhyve
|
|
# vm-other | generic
|
|
out=$(sysctl -qn kern.vm_guest 2>/dev/null) && {
|
|
case "$out" in
|
|
hv) virt="microsoft" ;;
|
|
vbox) virt="oracle" ;;
|
|
generic) virt="vm-other";;
|
|
*) virt="$out"
|
|
esac
|
|
}
|
|
out=$(sysctl -qn security.jail.jailed 2>/dev/null) && {
|
|
if [ "$out" = "1" ]; then
|
|
virt="jail"
|
|
fi
|
|
}
|
|
fi
|
|
_RET="$virt"
|
|
}
|
|
|
|
read_virt() {
|
|
cached "$DI_VIRT" && return 0
|
|
detect_virt
|
|
DI_VIRT="${_RET}"
|
|
}
|
|
|
|
is_container() {
|
|
case "${DI_VIRT}" in
|
|
container-other|lxc|lxc-libvirt|systemd-nspawn|docker|rkt|jail) return 0;;
|
|
*) return 1;;
|
|
esac
|
|
}
|
|
|
|
is_socket_file() {
|
|
[ -S "$1" ] && return 0 || return 1
|
|
}
|
|
|
|
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_board_name() {
|
|
cached "${DI_DMI_BOARD_NAME}" && return
|
|
get_dmi_field board_name
|
|
DI_DMI_BOARD_NAME="$_RET"
|
|
}
|
|
|
|
read_dmi_chassis_asset_tag() {
|
|
cached "${DI_DMI_CHASSIS_ASSET_TAG}" && return
|
|
get_dmi_field chassis_asset_tag
|
|
DI_DMI_CHASSIS_ASSET_TAG="$_RET"
|
|
}
|
|
|
|
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.. -v kernel-version(whitespace)
|
|
# N-1 -m machine
|
|
cached "${DI_UNAME_CMD_OUT}" && return
|
|
local out="${1:-}" ret=0 buf=""
|
|
if [ -z "$out" ]; then
|
|
out=$(uname -svm) || {
|
|
ret=$?
|
|
error "failed reading uname with 'uname -svm'"
|
|
return $ret
|
|
}
|
|
fi
|
|
# shellcheck disable=2086
|
|
set -- $out
|
|
DI_UNAME_KERNEL_NAME="$1"
|
|
shift
|
|
while [ $# -gt 1 ]; do
|
|
buf="$buf $1"
|
|
shift
|
|
done
|
|
DI_UNAME_KERNEL_VERSION="${buf# }"
|
|
DI_UNAME_MACHINE="$1"
|
|
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=""
|
|
# i386/14.04 (dash=0.5.7-4ubuntu1): the following outputs "[foo"
|
|
# sh -c 'n="$1"; echo ${n#[}' -- "[foo"
|
|
# the fix was to quote the open bracket (val=${val#"["}) (LP: #1689648)
|
|
val=${val#"["}
|
|
val=${val%"]"}
|
|
# shellcheck disable=2086
|
|
{ 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="" key="datasource_list"
|
|
# 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 "$key" && get_single_line_flow_sequence "$key" "$_RET"; then
|
|
debug 1 "$_RET_fname set datasource_list: $_RET"
|
|
parse_yaml_array "$_RET"
|
|
dslist=${_RET}
|
|
fi
|
|
if [ -z "$dslist" ]; then
|
|
dslist=${DI_DSLIST_DEFAULT}
|
|
warn "no datasource_list found, using default: $dslist"
|
|
fi
|
|
DI_DSLIST=$dslist
|
|
return 0
|
|
}
|
|
|
|
read_pid1_product_name() {
|
|
local oifs="$IFS" out="" tok="" key="" val="" product_name="${UNAVAILABLE}"
|
|
cached "${DI_PID_1_PRODUCT_NAME}" && return
|
|
[ -r "${PATH_PROC_1_ENVIRON}" ] || return
|
|
out=$(tr '\0' '\n' <"${PATH_PROC_1_ENVIRON}")
|
|
# shellcheck disable=2086
|
|
{ IFS="$CR"; set -- $out; IFS="$oifs"; }
|
|
for tok in "$@"; do
|
|
key=${tok%%=*}
|
|
[ "$key" != "$tok" ] || continue
|
|
val=${tok#*=}
|
|
[ "$key" = "product_name" ] && product_name="$val" && break
|
|
done
|
|
DI_PID_1_PRODUCT_NAME="$product_name"
|
|
}
|
|
|
|
dmi_chassis_asset_tag_matches() {
|
|
is_container && return 1
|
|
# shellcheck disable=2254
|
|
case "${DI_DMI_CHASSIS_ASSET_TAG}" in
|
|
$1) return 0;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
dmi_product_name_matches() {
|
|
is_container && return 1
|
|
# shellcheck disable=2254
|
|
case "${DI_DMI_PRODUCT_NAME}" in
|
|
$1) return 0;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
dmi_product_serial_matches() {
|
|
is_container && return 1
|
|
# shellcheck disable=2254
|
|
case "${DI_DMI_PRODUCT_SERIAL}" in
|
|
$1) return 0;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
dmi_sys_vendor_is() {
|
|
is_container && return 1
|
|
[ "${DI_DMI_SYS_VENDOR}" = "$1" ]
|
|
}
|
|
|
|
has_fs_with_uuid() {
|
|
case ",${DI_FS_UUIDS}," in
|
|
*,$1,*) return 0;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
has_fs_with_label() {
|
|
# has_fs_with_label(label1[ ,label2 ..])
|
|
# return 0 if a there is a filesystem that matches any of the labels.
|
|
local label=""
|
|
for label in "$@"; do
|
|
case ",${DI_FS_LABELS}," in
|
|
*,$label,*) return 0;;
|
|
esac
|
|
done
|
|
return 1
|
|
}
|
|
|
|
nocase_equal() {
|
|
# nocase_equal(a, b)
|
|
# return 0 if case insensitive comparison 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 '[:upper:]' '[:lower:]')
|
|
# delim is known not to be a pattern, and some editors currently struggle
|
|
# with parsing the quoted output required to satisfy SC2295
|
|
# shellcheck disable=2295
|
|
# https://github.com/tree-sitter/tree-sitter-bash/issues/254
|
|
[ "${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
|
|
}
|
|
|
|
check_writable_seed_dir() {
|
|
# ubuntu core bind-mounts /writable/system-data/var/lib/cloud
|
|
# over the top of /var/lib/cloud, but the mount might not be done yet.
|
|
local wdir="/writable/system-data"
|
|
[ -d "${PATH_ROOT}$wdir" ] || return 1
|
|
local sdir="${PATH_ROOT}$wdir${PATH_VAR_LIB_CLOUD#"${PATH_ROOT}"}"
|
|
local PATH_VAR_LIB_CLOUD="$sdir"
|
|
check_seed_dir "$@"
|
|
}
|
|
|
|
probe_floppy() {
|
|
cached "${STATE_FLOPPY_PROBED}" && return "${STATE_FLOPPY_PROBED}"
|
|
local fpath=/dev/floppy
|
|
|
|
[ -b "$fpath" ] ||
|
|
{ STATE_FLOPPY_PROBED=1; return 1; }
|
|
|
|
# Use "-b" option as Busybox modprobe doesn't support long-option
|
|
modprobe -b floppy >/dev/null 2>&1 ||
|
|
{ STATE_FLOPPY_PROBED=1; return 1; }
|
|
|
|
# Some Linux distros/non-Linux OSes may not have udev
|
|
if command -v udevadm; then
|
|
udevadm settle "--exit-if-exists=$fpath" ||
|
|
{ STATE_FLOPPY_PROBED=1; return 1; }
|
|
fi
|
|
|
|
[ -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_CloudCIX() {
|
|
dmi_product_name_matches "CloudCIX" && return $DS_FOUND
|
|
return $DS_NOT_FOUND
|
|
}
|
|
|
|
dscheck_Exoscale() {
|
|
dmi_product_name_matches "Exoscale*" && return $DS_FOUND
|
|
return $DS_NOT_FOUND
|
|
}
|
|
|
|
dscheck_CloudSigma() {
|
|
# http://paste.ubuntu.com/23624795/
|
|
dmi_product_name_matches "CloudSigma" && return $DS_FOUND
|
|
return $DS_NOT_FOUND
|
|
}
|
|
|
|
dscheck_Akamai() {
|
|
dmi_sys_vendor_is Linode && return ${DS_FOUND}
|
|
dmi_sys_vendor_is Akamai && return ${DS_FOUND}
|
|
return ${DS_NOT_FOUND}
|
|
}
|
|
|
|
check_config() {
|
|
# check_config(key [,file_globs])
|
|
# somewhat hackily read through file_globs for 'key'
|
|
# file_globs are expanded via path expansion and
|
|
# default to /etc/cloud/cloud.cfg /etc/cloud/cloud.cfg.d/*.cfg
|
|
# currently does not respect any hierarchy in searching for key.
|
|
local key="$1" files=""
|
|
shift
|
|
if [ $# -eq 0 ]; then
|
|
files="${PATH_ETC_CI_CFG} ${PATH_ETC_CI_CFG_D}/*.cfg"
|
|
else
|
|
files="$*"
|
|
fi
|
|
# shellcheck disable=2086
|
|
{ set +f; set -- $files; set -f; }
|
|
if [ "$1" = "$files" -a ! -f "$1" ]; then
|
|
return 1
|
|
fi
|
|
local line="" ret="" found=0 found_fn="" oifs="$IFS" out=""
|
|
# check for a yaml key/value pair on a single line
|
|
#
|
|
# note that:
|
|
# - keys may be single or double quoted
|
|
# - spaces and tabs may exist between key and colon
|
|
#
|
|
# the following are all valid under the yaml spec (as of 1.2.2):
|
|
#
|
|
# key: string
|
|
# key: "quoted string"
|
|
# key: 1
|
|
# key: [ some_value ]
|
|
# key : [ "some value" ]
|
|
# key\t:\t[\tsome_value\t]\t
|
|
#
|
|
# The syntax warned about is not valid posix shell, and we are not
|
|
# attempting to access an index of arrays. Silence it.
|
|
# shellcheck disable=1087
|
|
out=$(grep "$key[\"\']*[[:space:]]*:" "$@" 2>/dev/null)
|
|
IFS=${CR}
|
|
for line in $out; do
|
|
# drop '# comment'
|
|
line=${line%%#*}
|
|
# if more than one file was 'grep'ed, then grep will output filename:
|
|
# but if only one file, line will not be prefixed.
|
|
if [ $# -eq 1 ]; then
|
|
found_fn="$1"
|
|
if [ "${#line}" -eq 0 ]; then
|
|
continue
|
|
fi
|
|
else
|
|
found_fn="${line%%:*}"
|
|
line=${line#"$found_fn:"}
|
|
if [ "${#line}" -eq 0 ]; then
|
|
continue
|
|
fi
|
|
fi
|
|
ret=${line#*: };
|
|
found=$((found+1))
|
|
done
|
|
IFS="$oifs"
|
|
if [ $found -ne 0 ]; then
|
|
_RET="$ret"
|
|
_RET_fname="$found_fn"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
get_value() {
|
|
# get_value(key, value)
|
|
# for a key / value pair, check that the value is non-empty and
|
|
# return the value if it exists
|
|
#
|
|
# This function is intended to be run on the output of check_config
|
|
# when the value for a key needs to be in the output.
|
|
#
|
|
# `check_config` returns true when no value is in the line, but this
|
|
# is insufficient when a value is required, as in the case of
|
|
# parsing 'datasource_list:'
|
|
local key="$1" value="$2" found=0 ret=""
|
|
|
|
# remove everything before final ':'
|
|
ret="${value##*:}"
|
|
|
|
# remove preceding and trailing whitespace
|
|
trim "$ret"
|
|
|
|
# check value length
|
|
if [ "${#_RET}" -ne 0 ]; then
|
|
return 0
|
|
fi
|
|
debug 1 "key $key didn't have a valid value"
|
|
return 1
|
|
}
|
|
|
|
get_single_line_flow_sequence() {
|
|
# get_single_line_flow_sequence(key, value)
|
|
# for a key / value pair, check that the value contains a single
|
|
# line flow sequence[1] with a value
|
|
#
|
|
# return 0 if a single line flow sequence is found, otherwise 1
|
|
# does not modify _RET
|
|
#
|
|
# [1] https://yaml.org/spec/1.2.2/#741-flow-sequences
|
|
local ret="" tmp=""
|
|
get_value "$1" "$2" || return 1
|
|
ret="$_RET"
|
|
tmp="$_RET"
|
|
|
|
# remove smallest ] suffix
|
|
tmp="${tmp%]}"
|
|
|
|
# remove smallest [ prefix
|
|
tmp="${tmp#[}"
|
|
|
|
# remove preceding and trailing whitespace
|
|
trim "$tmp"
|
|
_RET="$ret"
|
|
|
|
# check value length
|
|
if [ "${#_RET}" -ne 0 ]; then
|
|
return 0
|
|
fi
|
|
debug 1 "key $key didn't have a valid single line flow sequence"
|
|
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
|
|
return ${DS_NOT_FOUND}
|
|
}
|
|
|
|
# LXD datasource requires active /dev/lxd/sock
|
|
# https://documentation.ubuntu.com/lxd/en/latest/dev-lxd/
|
|
dscheck_LXD() {
|
|
if is_socket_file /dev/lxd/sock; then
|
|
return ${DS_FOUND}
|
|
fi
|
|
# On LXD KVM instances, /dev/lxd/sock is not yet setup by
|
|
# lxd-agent-loader's systemd lxd-agent.service.
|
|
# Rely on DMI product information that is present on all LXD images.
|
|
# Note "qemu" is returned on kvm instances launched from a host kernel
|
|
# kernels >=5.10, due to `hv_passthrough` option.
|
|
# systemd v. 251 should properly return "kvm" in this scenario
|
|
# https://github.com/systemd/systemd/issues/22709
|
|
if [ "${DI_VIRT}" = "kvm" -o "${DI_VIRT}" = "qemu" ]; then
|
|
[ "${DI_DMI_BOARD_NAME}" = "LXD" ] && return ${DS_FOUND}
|
|
fi
|
|
return ${DS_NOT_FOUND}
|
|
}
|
|
|
|
dscheck_NoCloud() {
|
|
local fslabel="cidata CIDATA" d=""
|
|
case " ${DI_DMI_PRODUCT_SERIAL} " in
|
|
*\ ds=nocloud*) return ${DS_FOUND};;
|
|
esac
|
|
|
|
for d in nocloud nocloud-net; do
|
|
check_seed_dir "$d" meta-data user-data && return ${DS_FOUND}
|
|
check_writable_seed_dir "$d" meta-data user-data && return ${DS_FOUND}
|
|
done
|
|
# shellcheck disable=2086
|
|
if has_fs_with_label $fslabel; then
|
|
return ${DS_FOUND}
|
|
fi
|
|
|
|
# This is a bit hacky, but a NoCloud false positive isn't the end of the world
|
|
if check_config "NoCloud"; then
|
|
if check_config "user-data" && check_config "meta-data"; then
|
|
return ${DS_FOUND}
|
|
elif check_config "seedfrom"; then
|
|
return ${DS_FOUND}
|
|
fi
|
|
fi
|
|
|
|
return ${DS_NOT_FOUND}
|
|
}
|
|
|
|
is_ds_enabled() {
|
|
local name="$1" pad=" ${DI_DSLIST} "
|
|
[ "${pad#* "${name}" }" != "${pad}" ]
|
|
}
|
|
|
|
check_configdrive_v2() {
|
|
# look in /config-drive <vlc>/seed/config_drive for a directory
|
|
# openstack/YYYY-MM-DD format with a file meta_data.json
|
|
local d=""
|
|
local vlc_config_drive_path="${PATH_VAR_LIB_CLOUD}/seed/config_drive"
|
|
for d in /config-drive $vlc_config_drive_path; do
|
|
set +f; set -- "$d/openstack/"2???-??-??/meta_data.json; set -f;
|
|
[ -f "$1" ] && return ${DS_FOUND}
|
|
done
|
|
# at least one cloud (softlayer) seeds config drive with only 'latest'.
|
|
local lpath="openstack/latest/meta_data.json"
|
|
if [ -e "$vlc_config_drive_path/$lpath" ]; then
|
|
debug 1 "config drive seeded directory had only 'latest'"
|
|
return ${DS_FOUND}
|
|
fi
|
|
|
|
local ibm_enabled=false
|
|
is_ds_enabled "IBMCloud" && ibm_enabled=true
|
|
debug 1 "is_ds_enabled(IBMCloud) = $ibm_enabled."
|
|
[ "$ibm_enabled" = "true" ] && is_ibm_cloud && return ${DS_NOT_FOUND}
|
|
|
|
if has_fs_with_label CONFIG-2 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" "CDROM" && return ${DS_FOUND}
|
|
return ${DS_NOT_FOUND}
|
|
}
|
|
|
|
dscheck_RbxCloud() {
|
|
has_fs_with_label "CLOUDMD" "cloudmd" && return ${DS_FOUND}
|
|
return ${DS_NOT_FOUND}
|
|
}
|
|
|
|
dscheck_UpCloud() {
|
|
dmi_sys_vendor_is UpCloud && return ${DS_FOUND}
|
|
return ${DS_NOT_FOUND}
|
|
}
|
|
|
|
vmware_guest_customization() {
|
|
# vmware guest customization
|
|
|
|
# virt provider must be vmware
|
|
[ "${DI_VIRT}" = "vmware" ] || return 1
|
|
|
|
# we have to have the plugin to do vmware customization
|
|
local found="" pkg="" pre="${PATH_ROOT}/usr/lib"
|
|
local x86="x86_64-linux-gnu" aarch="aarch64-linux-gnu" i386="i386-linux-gnu"
|
|
local ppath="plugins/vmsvc/libdeployPkgPlugin.so"
|
|
for pkg in vmware-tools open-vm-tools; do
|
|
if [ -f "$pre/$pkg/$ppath" -o -f "${pre}64/$pkg/$ppath" ]; then
|
|
found="$pkg"; break;
|
|
fi
|
|
# search in multiarch dir
|
|
if [ -f "$pre/$x86/$pkg/$ppath" ] || \
|
|
[ -f "$pre/$aarch/$pkg/$ppath" ] || \
|
|
[ -f "$pre/$i386/$pkg/$ppath" ]; then
|
|
found="$pkg"; break;
|
|
fi
|
|
done
|
|
[ -n "$found" ] || return 1
|
|
# vmware customization is disabled by default
|
|
# (disable_vmware_customization=true). If it is set to false, then
|
|
# user has requested customization.
|
|
local key="disable_vmware_customization"
|
|
if check_config "$key" && get_value "$key" "$_RET"; then
|
|
debug 2 "${_RET_fname} set $key to $_RET"
|
|
case "$_RET" in
|
|
0|false|False) return 0;;
|
|
*) return 1;;
|
|
esac
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
vmware_has_rpctool() {
|
|
command -v vmware-rpctool >/dev/null 2>&1
|
|
}
|
|
|
|
vmware_rpctool_guestinfo() {
|
|
vmware-rpctool "info-get guestinfo.${1}" 2>/dev/null | grep "[[:alnum:]]"
|
|
}
|
|
|
|
vmware_rpctool_guestinfo_err() {
|
|
vmware-rpctool "info-get guestinfo.${1}" 2>&1 | grep "[[:alnum:]]"
|
|
}
|
|
|
|
vmware_has_vmtoolsd() {
|
|
command -v vmtoolsd >/dev/null 2>&1
|
|
}
|
|
|
|
vmware_vmtoolsd_guestinfo() {
|
|
vmtoolsd --cmd "info-get guestinfo.${1}" 2>/dev/null | grep "[[:alnum:]]"
|
|
}
|
|
|
|
vmware_vmtoolsd_guestinfo_err() {
|
|
vmtoolsd --cmd "info-get guestinfo.${1}" 2>&1 | grep "[[:alnum:]]"
|
|
}
|
|
|
|
vmware_guestinfo() {
|
|
vmware_rpctool_guestinfo "${1}" || vmware_vmtoolsd_guestinfo "${1}"
|
|
}
|
|
|
|
vmware_guestinfo_err() {
|
|
vmware_rpctool_guestinfo_err "${1}" || vmware_vmtoolsd_guestinfo_err "${1}"
|
|
}
|
|
|
|
vmware_guestinfo_ovfenv_err() {
|
|
vmware_guestinfo_err "ovfEnv"
|
|
}
|
|
|
|
vmware_guestinfo_metadata() {
|
|
vmware_guestinfo "metadata"
|
|
}
|
|
|
|
vmware_guestinfo_userdata() {
|
|
|
|
vmware_guestinfo "userdata"
|
|
}
|
|
|
|
vmware_guestinfo_vendordata() {
|
|
|
|
vmware_guestinfo "vendordata"
|
|
}
|
|
|
|
ovf_vmware_transport_guestinfo() {
|
|
[ "${DI_VIRT}" = "vmware" ] || return 1
|
|
vmware_has_rpctool || vmware_has_vmtoolsd || return 1
|
|
local out="" ret=""
|
|
out=$(vmware_guestinfo_ovfenv_err)
|
|
ret=$?
|
|
if [ $ret -ne 0 ]; then
|
|
debug 1 "Running on vmware but query returned $ret: $out"
|
|
return 1
|
|
fi
|
|
case "$out" in
|
|
"<?xml"*|"<?XML"*) :;;
|
|
*) debug 1 "guestinfo.ovfEnv had non-xml content: $out";
|
|
return 1;;
|
|
esac
|
|
debug 1 "Found guestinfo transport."
|
|
return 0
|
|
}
|
|
|
|
is_cdrom_ovf() {
|
|
local dev="$1" label="$2"
|
|
# skip devices that don't look like cdrom paths.
|
|
case "$dev" in
|
|
/dev/sr[0-9]|/dev/hd[a-z]) :;;
|
|
*) debug 1 "skipping iso dev $dev"
|
|
return 1;;
|
|
esac
|
|
|
|
debug 1 "got label=$label"
|
|
# fast path known 'OVF' labels
|
|
case "$label" in
|
|
OVF-TRANSPORT|ovf-transport|OVFENV|ovfenv|OVF\ ENV|ovf\ env) return 0;;
|
|
esac
|
|
|
|
# explicitly skip known labels of other types. rd_rdfe is azure.
|
|
case "$label" in
|
|
config-2|CONFIG-2|rd_rdfe_stable*|cidata|CIDATA) return 1;;
|
|
esac
|
|
|
|
# skip device which size is 10MB or larger
|
|
local size="" sfile="${PATH_SYS_CLASS_BLOCK}/${dev##*/}/size"
|
|
[ -f "$sfile" ] || return 1
|
|
read size <"$sfile" || { warn "failed reading from $sfile"; return 1; }
|
|
# size is in 512 byte units. so convert to MB (integer division)
|
|
if [ $((size/2048)) -ge 10 ]; then
|
|
debug 2 "$dev: size $((size/2048))MB is considered too large for OVF"
|
|
return 1
|
|
fi
|
|
|
|
local idstr="http://schemas.dmtf.org/ovf/environment/1"
|
|
# POSIX grep only supports short-options, long-options are GNU-specific
|
|
grep -q -i "$idstr" "${PATH_ROOT}$dev"
|
|
}
|
|
|
|
has_ovf_cdrom() {
|
|
# DI_ISO9660_DEVS is <device>=label,<device>=label2
|
|
# like /dev/sr0=OVF-TRANSPORT,/dev/other=with spaces
|
|
if [ "${DI_ISO9660_DEVS#"${UNAVAILABLE}":}" = "${DI_ISO9660_DEVS}" ]; then
|
|
local oifs="$IFS"
|
|
# shellcheck disable=2086
|
|
{ IFS=","; set -- ${DI_ISO9660_DEVS}; IFS="$oifs"; }
|
|
for tok in "$@"; do
|
|
is_cdrom_ovf "${tok%%=*}" "${tok#*=}" && return 0
|
|
done
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
is_disabled() {
|
|
if [ -f /etc/cloud/cloud-init.disabled ]; then
|
|
debug 1 "disabled by marker file /etc/cloud/cloud-init.disabled"
|
|
return 0
|
|
fi
|
|
if [ "${KERNEL_CMDLINE:-}" = "cloud-init=disabled" ]; then
|
|
debug 1 "disabled by KERNEL_CMDLINE environment variable"
|
|
return 0
|
|
fi
|
|
case "$DI_KERNEL_CMDLINE" in
|
|
*cloud-init=disabled*)
|
|
debug 1 "disabled by kernel command line cloud-init=disabled"
|
|
return 0
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
dscheck_OVF() {
|
|
check_seed_dir ovf ovf-env.xml && return "${DS_FOUND}"
|
|
|
|
[ "${DI_VIRT}" = "none" ] && return ${DS_NOT_FOUND}
|
|
|
|
# Azure provides ovf. Skip false positive by dis-allowing.
|
|
is_azure_chassis && return $DS_NOT_FOUND
|
|
|
|
ovf_vmware_transport_guestinfo && return "${DS_FOUND}"
|
|
|
|
has_ovf_cdrom && return "${DS_FOUND}"
|
|
|
|
return ${DS_NOT_FOUND}
|
|
}
|
|
|
|
is_azure_chassis() {
|
|
local azure_chassis="7783-7084-3265-9085-8269-3286-77"
|
|
dmi_chassis_asset_tag_matches "${azure_chassis}"
|
|
}
|
|
|
|
dscheck_Azure() {
|
|
is_azure_chassis && return $DS_FOUND
|
|
check_seed_dir azure ovf-env.xml && return ${DS_FOUND}
|
|
|
|
return ${DS_NOT_FOUND}
|
|
}
|
|
|
|
dscheck_Bigstep() {
|
|
# bigstep is activated by presence of seed file 'url'
|
|
[ -f "${PATH_VAR_LIB_CLOUD}/data/seed/bigstep/url" ] &&
|
|
return ${DS_FOUND}
|
|
return ${DS_NOT_FOUND}
|
|
}
|
|
|
|
ec2_read_strict_setting() {
|
|
# the 'strict_id' setting for Ec2 controls behavior when
|
|
# the platform does not identify itself directly as Ec2.
|
|
# order of precedence is:
|
|
# 1. builtin setting here cloud-init/ds-identify builtin
|
|
# 2. ds-identify config
|
|
# 3. system config (/etc/cloud/cloud.cfg.d/*Ec2*.cfg)
|
|
# 4. kernel command line (undocumented)
|
|
# 5. user-data or vendor-data (not available here)
|
|
local default="$1" key="ci.datasource.ec2.strict_id" val=""
|
|
|
|
# 4. kernel command line
|
|
case " ${DI_KERNEL_CMDLINE} " in
|
|
*\ $key=*\ )
|
|
val=${DI_KERNEL_CMDLINE##*"${key}"=}
|
|
val=${val%% *};
|
|
_RET=${val:-$default}
|
|
return 0
|
|
esac
|
|
|
|
# 3. look for the key 'strict_id' (datasource/Ec2/strict_id)
|
|
# only in cloud.cfg or cloud.cfg.d/EC2.cfg (case insensitive)
|
|
local cfg="${PATH_ETC_CI_CFG}" cfg_d="${PATH_ETC_CI_CFG_D}"
|
|
if check_config strict_id "$cfg" "$cfg_d/*[Ee][Cc]2*.cfg"; then
|
|
debug 2 "${_RET_fname} set strict_id to $_RET"
|
|
return 0
|
|
fi
|
|
|
|
# 2. ds-identify config (datasource.ec2.strict)
|
|
local config="${PATH_DI_CONFIG}"
|
|
if [ -f "$config" ]; then
|
|
if _read_config "$key" < "$config"; then
|
|
_RET=${_RET:-$default}
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
# 1. Default
|
|
_RET=$default
|
|
return 0
|
|
}
|
|
|
|
ec2_identify_platform() {
|
|
local default="$1"
|
|
local serial="${DI_DMI_PRODUCT_SERIAL}"
|
|
|
|
case "$serial" in
|
|
*.brightbox.com) _RET="Brightbox"; return 0;;
|
|
esac
|
|
|
|
local asset_tag="${DI_DMI_CHASSIS_ASSET_TAG}"
|
|
case "$asset_tag" in
|
|
*.zstack.io) _RET="ZStack"; return 0;;
|
|
esac
|
|
|
|
local vendor="${DI_DMI_SYS_VENDOR}"
|
|
case "$vendor" in
|
|
e24cloud) _RET="E24cloud"; return 0;;
|
|
esac
|
|
|
|
|
|
local product_name="${DI_DMI_PRODUCT_NAME}"
|
|
if [ "${product_name}" = "3DS Outscale VM" ] && \
|
|
[ "${vendor}" = "3DS Outscale" ]; then
|
|
_RET="Outscale"; return 0
|
|
fi
|
|
|
|
# AWS http://docs.aws.amazon.com/AWSEC2/
|
|
# latest/UserGuide/identify_ec2_instances.html
|
|
local uuid="" hvuuid="${PATH_SYS_HYPERVISOR}/uuid"
|
|
# if the (basically) xen specific /sys/hypervisor/uuid starts with 'ec2'
|
|
if [ -r "$hvuuid" ] && read uuid < "$hvuuid" &&
|
|
[ "${uuid#ec2}" != "$uuid" ]; then
|
|
_RET="AWS"
|
|
return 0
|
|
fi
|
|
|
|
# keep only the first octet
|
|
local start_uuid="${DI_DMI_PRODUCT_UUID%%-*}"
|
|
case "$start_uuid" in
|
|
# example ec2 uuid:
|
|
# EC2E1916-9099-7CAF-FD21-012345ABCDEF
|
|
[Ee][Cc]2*)
|
|
_RET="AWS"
|
|
;;
|
|
# example ec2 uuid:
|
|
# 45E12AEC-DCD1-B213-94ED-012345ABCDEF
|
|
*2[0-9a-fA-F][Ee][Cc])
|
|
_RET="AWS"
|
|
;;
|
|
*)
|
|
_RET="$default"
|
|
;;
|
|
esac
|
|
return 0;
|
|
}
|
|
|
|
dscheck_Ec2() {
|
|
check_seed_dir "ec2" meta-data user-data && return ${DS_FOUND}
|
|
is_container && return ${DS_NOT_FOUND}
|
|
|
|
local unknown="Unknown" platform=""
|
|
if ec2_identify_platform "$unknown"; then
|
|
platform="$_RET"
|
|
else
|
|
warn "Failed to identify ec2 platform. Using '$unknown'."
|
|
platform=$unknown
|
|
fi
|
|
|
|
debug 1 "ec2 platform is '$platform'."
|
|
if [ "$platform" != "$unknown" ]; then
|
|
return $DS_FOUND
|
|
fi
|
|
|
|
local default="${DI_EC2_STRICT_ID_DEFAULT}"
|
|
if ec2_read_strict_setting "$default"; then
|
|
strict="$_RET"
|
|
else
|
|
debug 1 "ec2_read_strict returned non-zero: $?. using '$default'."
|
|
strict="$default"
|
|
fi
|
|
|
|
local key="datasource/Ec2/strict_id"
|
|
case "$strict" in
|
|
true|false|warn|warn,[0-9]*) :;;
|
|
*)
|
|
warn "$key was set to invalid '$strict'. using '$default'"
|
|
strict="$default";;
|
|
esac
|
|
|
|
_RET_excfg="datasource: {Ec2: {strict_id: \"$strict\"}}"
|
|
if [ "$strict" = "true" ]; then
|
|
return $DS_NOT_FOUND
|
|
else
|
|
return $DS_MAYBE
|
|
fi
|
|
}
|
|
|
|
dscheck_GCE() {
|
|
if dmi_product_name_matches "Google Compute Engine"; then
|
|
return ${DS_FOUND}
|
|
fi
|
|
# product name is not guaranteed (LP: #1674861)
|
|
if dmi_product_serial_matches "GoogleCloud-*"; 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
|
|
local nova="OpenStack Nova" compute="OpenStack Compute"
|
|
if dmi_product_name_matches "$nova"; then
|
|
return ${DS_FOUND}
|
|
fi
|
|
if dmi_product_name_matches "$compute"; then
|
|
# RDO installed nova (LP: #1675349).
|
|
return ${DS_FOUND}
|
|
fi
|
|
if [ "${DI_PID_1_PRODUCT_NAME}" = "$nova" ]; then
|
|
return ${DS_FOUND}
|
|
fi
|
|
|
|
if dmi_chassis_asset_tag_matches "OpenTelekomCloud"; then
|
|
return ${DS_FOUND}
|
|
fi
|
|
|
|
if dmi_chassis_asset_tag_matches "SAP CCloud VM"; then
|
|
return ${DS_FOUND}
|
|
fi
|
|
|
|
if dmi_chassis_asset_tag_matches "HUAWEICLOUD"; then
|
|
return ${DS_FOUND}
|
|
fi
|
|
|
|
if dmi_chassis_asset_tag_matches "Samsung Cloud Platform"; then
|
|
return ${DS_FOUND}
|
|
fi
|
|
|
|
# LP: #1669875 : allow identification of OpenStack by asset tag
|
|
if dmi_chassis_asset_tag_matches "$nova"; then
|
|
return ${DS_FOUND}
|
|
fi
|
|
if dmi_chassis_asset_tag_matches "$compute"; then
|
|
return ${DS_FOUND}
|
|
fi
|
|
|
|
# LP: #1715241 : arch other than intel are not identified properly.
|
|
case "$DI_UNAME_MACHINE" in
|
|
i?86|x86_64) :;;
|
|
*) return ${DS_MAYBE};;
|
|
esac
|
|
|
|
return ${DS_NOT_FOUND}
|
|
}
|
|
|
|
dscheck_AliYun() {
|
|
check_seed_dir "AliYun" meta-data user-data && return ${DS_FOUND}
|
|
if dmi_product_name_matches "Alibaba Cloud ECS"; then
|
|
return $DS_FOUND
|
|
fi
|
|
return $DS_NOT_FOUND
|
|
}
|
|
|
|
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
|
|
# for container, we also verify that the socketfile exists to protect
|
|
# against embedded containers (lxd running on brandz)
|
|
local smartdc_kver="BrandZ virtual linux"
|
|
local metadata_sockfile="${PATH_ROOT}/native/.zonecontrol/metadata.sock"
|
|
dmi_product_name_matches "SmartDC*" && return $DS_FOUND
|
|
[ "${DI_UNAME_KERNEL_VERSION}" = "${smartdc_kver}" ] &&
|
|
[ -e "${metadata_sockfile}" ] &&
|
|
return ${DS_FOUND}
|
|
return ${DS_NOT_FOUND}
|
|
}
|
|
|
|
dscheck_None() {
|
|
return ${DS_NOT_FOUND}
|
|
}
|
|
|
|
dscheck_Scaleway() {
|
|
if [ "${DI_DMI_SYS_VENDOR}" = "Scaleway" ]; then
|
|
return $DS_FOUND
|
|
fi
|
|
|
|
case " ${DI_KERNEL_CMDLINE} " in
|
|
*\ scaleway\ *) return ${DS_FOUND};;
|
|
esac
|
|
|
|
if [ -f "${PATH_ROOT}/var/run/scaleway" ]; then
|
|
return ${DS_FOUND}
|
|
fi
|
|
|
|
return ${DS_NOT_FOUND}
|
|
}
|
|
|
|
dscheck_Hetzner() {
|
|
dmi_sys_vendor_is Hetzner && return ${DS_FOUND}
|
|
return ${DS_NOT_FOUND}
|
|
}
|
|
|
|
dscheck_NWCS() {
|
|
dmi_sys_vendor_is NWCS && return ${DS_FOUND}
|
|
return ${DS_NOT_FOUND}
|
|
}
|
|
|
|
dscheck_Oracle() {
|
|
local asset_tag="OracleCloud.com"
|
|
dmi_chassis_asset_tag_matches "${asset_tag}" && return ${DS_FOUND}
|
|
return ${DS_NOT_FOUND}
|
|
}
|
|
|
|
is_ibm_provisioning() {
|
|
local pcfg="${PATH_ROOT}/root/provisioningConfiguration.cfg"
|
|
local logf="${PATH_ROOT}/root/swinstall.log"
|
|
local is_prov=false msg="config '$pcfg' did not exist."
|
|
if [ -f "$pcfg" ]; then
|
|
msg="config '$pcfg' exists."
|
|
is_prov=true
|
|
if [ -f "$logf" ]; then
|
|
# shellcheck disable=3013
|
|
if [ "$logf" -nt "$PATH_PROC_1_ENVIRON" ]; then
|
|
msg="$msg log '$logf' from current boot."
|
|
else
|
|
is_prov=false
|
|
msg="$msg log '$logf' from previous boot."
|
|
fi
|
|
else
|
|
msg="$msg log '$logf' did not exist."
|
|
fi
|
|
fi
|
|
debug 2 "ibm_provisioning=$is_prov: $msg"
|
|
[ "$is_prov" = "true" ]
|
|
}
|
|
|
|
is_ibm_cloud() {
|
|
cached "${_IS_IBM_CLOUD}" && return "${_IS_IBM_CLOUD}"
|
|
local ret=1
|
|
if [ "$DI_VIRT" = "xen" ]; then
|
|
if is_ibm_provisioning; then
|
|
ret=0
|
|
elif has_fs_with_label METADATA metadata; then
|
|
ret=0
|
|
elif has_fs_with_uuid 9796-932E &&
|
|
has_fs_with_label CONFIG-2 config-2; then
|
|
ret=0
|
|
fi
|
|
fi
|
|
_IS_IBM_CLOUD=$ret
|
|
return $ret
|
|
}
|
|
|
|
dscheck_IBMCloud() {
|
|
if is_ibm_provisioning; then
|
|
debug 1 "cloud-init disabled during provisioning on IBMCloud"
|
|
return ${DS_NOT_FOUND}
|
|
fi
|
|
is_ibm_cloud && return ${DS_FOUND}
|
|
return ${DS_NOT_FOUND}
|
|
}
|
|
|
|
dscheck_Vultr() {
|
|
dmi_sys_vendor_is Vultr && return $DS_FOUND
|
|
|
|
case " $DI_KERNEL_CMDLINE " in
|
|
*\ vultr\ *) return $DS_FOUND ;;
|
|
esac
|
|
|
|
if [ -f "${PATH_ROOT}/etc/vultr" ]; then
|
|
return $DS_FOUND
|
|
fi
|
|
|
|
return $DS_NOT_FOUND
|
|
}
|
|
|
|
vmware_has_envvar_vmx_guestinfo() {
|
|
[ -n "${VMX_GUESTINFO:-}" ]
|
|
}
|
|
|
|
vmware_has_envvar_vmx_guestinfo_metadata() {
|
|
[ -n "${VMX_GUESTINFO_METADATA:-}" ]
|
|
}
|
|
|
|
vmware_has_envvar_vmx_guestinfo_userdata() {
|
|
[ -n "${VMX_GUESTINFO_USERDATA:-}" ]
|
|
}
|
|
|
|
vmware_has_envvar_vmx_guestinfo_vendordata() {
|
|
[ -n "${VMX_GUESTINFO_VENDORDATA:-}" ]
|
|
}
|
|
|
|
dscheck_VMware() {
|
|
# Checks to see if there is valid data for the VMware datasource.
|
|
# The data transports are checked in the following order:
|
|
#
|
|
# * envvars
|
|
# * guestinfo
|
|
# * imc (VMware Guest Customization)
|
|
#
|
|
# Please note when updating this function with support for new data
|
|
# transports, the order should match the order in the _get_data
|
|
# function from the file DataSourceVMware.py.
|
|
|
|
# Check to see if running in a container and the VMware
|
|
# datasource is configured via environment variables.
|
|
if vmware_has_envvar_vmx_guestinfo; then
|
|
if vmware_has_envvar_vmx_guestinfo_metadata || \
|
|
vmware_has_envvar_vmx_guestinfo_userdata || \
|
|
vmware_has_envvar_vmx_guestinfo_vendordata; then
|
|
return "${DS_FOUND}"
|
|
fi
|
|
fi
|
|
|
|
# Do not proceed unless the detected platform is VMware.
|
|
if [ ! "${DI_VIRT}" = "vmware" ]; then
|
|
return "${DS_NOT_FOUND}"
|
|
fi
|
|
|
|
# Do not proceed if neither the vmware-rpctool or vmtoolsd command exists.
|
|
if ! vmware_has_rpctool && ! vmware_has_vmtoolsd; then
|
|
return "${DS_NOT_FOUND}"
|
|
fi
|
|
|
|
# Activate the VMware datasource only if any of the fields used
|
|
# by the datasource are present in the guestinfo table.
|
|
if { vmware_guestinfo_metadata || \
|
|
vmware_guestinfo_userdata || \
|
|
vmware_guestinfo_vendordata; } >/dev/null 2>&1; then
|
|
return "${DS_FOUND}"
|
|
fi
|
|
|
|
# Activate the VMware datasource only if tools plugin is available and
|
|
# guest customization is enabled.
|
|
vmware_guest_customization && return "${DS_FOUND}"
|
|
|
|
return "${DS_NOT_FOUND}"
|
|
}
|
|
|
|
WSL_path() {
|
|
local params="$1" path="$2" val=""
|
|
val="$(wslpath "$params" "$path")"
|
|
_RET="$val"
|
|
}
|
|
|
|
WSL_run_cmd() {
|
|
local val="" exepath="$1"
|
|
shift
|
|
_RET=$(/init "$exepath" /c "$@" 2>/dev/null)
|
|
}
|
|
|
|
WSL_profile_dir() {
|
|
# Determine where a suitable user profile home is located
|
|
_RET=""
|
|
local cmdexe="" profiledir="" val=""
|
|
# shellcheck disable=SC2068
|
|
for m in $@; do
|
|
cmdexe="$m/Windows/System32/cmd.exe"
|
|
if command -v "$cmdexe" > /dev/null 2>&1; then
|
|
# Here WSL's proprietary `/init` is used to start the Windows cmd.exe
|
|
# to output the Windows user profile directory path, which is
|
|
# held by the environment variable %USERPROFILE%.
|
|
WSL_run_cmd "$cmdexe" "echo %USERPROFILE%"
|
|
profiledir="${_RET%%[[:cntrl:]]}"
|
|
if [ -n "$profiledir" ]; then
|
|
# wslpath is a program supplied by WSL itself that translates Windows and Linux paths,
|
|
# respecting the mountpoints where the Windows drives are mounted.
|
|
# (in fact it's a symlink to /init).
|
|
WSL_path "-au" "$profiledir"
|
|
return $?
|
|
fi
|
|
fi
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
WSL_instance_name() {
|
|
local val="" instance_name=""
|
|
WSL_path "-am" "/"
|
|
instance_name="${_RET}"
|
|
# Extracts "Ubuntu/" from "//wsl.localhost/Ubuntu/"
|
|
val="${instance_name#//*/}"
|
|
# Extracts "Ubuntu" from "Ubuntu/"
|
|
_RET="${val%/}"
|
|
}
|
|
|
|
dscheck_WSL() {
|
|
local mountpoints="" cloudinitdir="" candidate="" instance_name=""
|
|
if [ "${DI_UNAME_KERNEL_NAME}" != "Linux" ]; then
|
|
return "${DS_NOT_FOUND}"
|
|
fi
|
|
|
|
if [ "${DI_VIRT}" != "wsl" ]; then
|
|
return "${DS_NOT_FOUND}"
|
|
fi
|
|
|
|
# The datasource needs to find the cloud-config files in the Windows host
|
|
# filesystem, which is exposed as 9p mount points, one per disk drive (partition).
|
|
# If none is found, the datasource cannot proceed.
|
|
# See https://youtu.be/lwhMThePdIo?t=2431&si=JKTHx39TyRgPbzkZ and
|
|
# https://learn.microsoft.com/en-us/windows/wsl/wsl-config#what-is-drvfs
|
|
# for more information.
|
|
mountpoints=$(grep '^[^[:space:]]* [^[:space:]]* 9p [^[:space:]]*aname=drvfs;.*' "${PATH_ROOT}/proc/mounts" | cut -f2 -d' ')
|
|
|
|
if [ -z "$mountpoints" ]; then
|
|
debug 1 "WSL datasource requires access to Windows drives mount points"
|
|
return "${DS_NOT_FOUND}"
|
|
fi
|
|
|
|
# We know we are under WSL and have acess to the host filesystem,
|
|
# so let's find the user's home directory
|
|
WSL_profile_dir "$mountpoints"
|
|
profile_dir="${_RET}"
|
|
if [ -z "$profile_dir" ]; then
|
|
debug 1 "%USERPROFILE% directory not found"
|
|
return "${DS_NOT_FOUND}"
|
|
fi
|
|
|
|
# Then we can check for any .cloud-init folders for the user
|
|
if [ ! -d "$profile_dir/.cloud-init/" ] && [ ! -d "$profile_dir/.ubuntupro/.cloud-init/" ]; then
|
|
debug 1 "No .cloud-init directories found in $profile_dir"
|
|
return "${DS_NOT_FOUND}"
|
|
fi
|
|
|
|
WSL_instance_name
|
|
instance_name="${_RET}"
|
|
# shellcheck source=/dev/null
|
|
. "${PATH_ROOT}/etc/os-release"
|
|
|
|
# and the applicable userdata file. Notice the ordering in the for-loop
|
|
# must match our expected precedence, so the file we find is what the
|
|
# datasource must process.
|
|
# We only care about ubuntupro configs if the distro is an Ubuntu distro.
|
|
# shellcheck disable=SC2153
|
|
if [ "$NAME" = "Ubuntu" ]; then
|
|
cloudinitdir="$profile_dir/.ubuntupro/.cloud-init"
|
|
for userdatafile in "${instance_name}.user-data" "agent.yaml"; do
|
|
candidate="$cloudinitdir/$userdatafile"
|
|
if [ -f "$candidate" ]; then
|
|
debug 1 "Found applicable pro data file for this instance at: $candidate"
|
|
return ${DS_FOUND}
|
|
fi
|
|
done
|
|
fi
|
|
|
|
cloudinitdir="$profile_dir/.cloud-init"
|
|
for userdatafile in "${instance_name}.user-data" "${ID:-linux}-${VERSION_ID:-${VERSION_CODENAME}}.user-data" "${ID:-linux}-all.user-data" "default.user-data"; do
|
|
candidate="$cloudinitdir/$userdatafile"
|
|
if [ -f "$candidate" ]; then
|
|
debug 1 "Found applicable user data file for this instance at: $candidate"
|
|
return ${DS_FOUND}
|
|
fi
|
|
done
|
|
|
|
debug 1 "Didn't find any applicable user data file for instance named $instance_name in $cloudinitdir"
|
|
|
|
return "${DS_NOT_FOUND}"
|
|
}
|
|
|
|
collect_info() {
|
|
read_pid1_product_name
|
|
read_config
|
|
read_datasource_list
|
|
read_dmi_sys_vendor
|
|
read_dmi_board_name
|
|
read_dmi_chassis_asset_tag
|
|
read_dmi_product_name
|
|
read_dmi_product_serial
|
|
read_dmi_product_uuid
|
|
read_fs_info
|
|
}
|
|
|
|
print_info() {
|
|
read_uname_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 PID_1_PRODUCT_NAME DMI_CHASSIS_ASSET_TAG"
|
|
vars="$vars DMI_BOARD_NAME FS_LABELS ISO9660_DEVS KERNEL_CMDLINE VIRT"
|
|
vars="$vars UNAME_KERNEL_NAME UNAME_KERNEL_VERSION UNAME_MACHINE"
|
|
vars="$vars DSNAME DSLIST"
|
|
vars="$vars MODE 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="" pre=""
|
|
{
|
|
if [ "$DI_MODE" = "report" ]; then
|
|
echo "di_report:"
|
|
pre=" "
|
|
fi
|
|
for line in "$@"; do
|
|
echo "${pre}$line";
|
|
done
|
|
} > "$runcfg"
|
|
ret=$?
|
|
[ $ret -eq 0 ] || {
|
|
error "failed to write to ${runcfg}"
|
|
return $ret
|
|
}
|
|
return 0
|
|
}
|
|
|
|
record_notfound() {
|
|
# in report mode, report nothing was found.
|
|
# if not report mode: only report the negative result.
|
|
# reporting an empty list would mean cloud-init would not search
|
|
# any datasources.
|
|
if [ "$DI_MODE" = "report" ]; then
|
|
found --
|
|
elif [ "$DI_MODE" = "search" ]; then
|
|
local msg="# reporting not found result. notfound=${DI_ON_NOTFOUND}."
|
|
local DI_MODE="report"
|
|
found -- "$msg"
|
|
fi
|
|
}
|
|
|
|
found() {
|
|
# found(ds1, [ds2 ...], [-- [extra lines]])
|
|
local list="" ds=""
|
|
while [ $# -ne 0 ]; do
|
|
if [ "$1" = "--" ]; then
|
|
shift
|
|
break
|
|
fi
|
|
list="${list:+${list}, }$1"
|
|
shift
|
|
done
|
|
if [ $# -eq 1 ] && [ -z "$1" ]; then
|
|
# do not pass an empty line through.
|
|
shift
|
|
fi
|
|
# if None is not already in the list, then add it last.
|
|
case " $list " in
|
|
*\ None,\ *|*\ None\ ) :;;
|
|
*) list=${list:+${list}, None};;
|
|
esac
|
|
write_result "datasource_list: [ $list ]" "$@"
|
|
return
|
|
}
|
|
|
|
trim() {
|
|
# trim all whitespace from the string, assign output to _RET
|
|
local tmp="" cur="$*"
|
|
until tmp="${cur#[[:space:]]}"; [ "$tmp" = "$cur" ]; do cur="$tmp"; done
|
|
until tmp="${cur%[[:space:]]}"; [ "$tmp" = "$cur" ]; do cur="$tmp"; done
|
|
_RET="$tmp"
|
|
}
|
|
|
|
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,
|
|
# if no parameters are set, modifies _rc scoped environment vars.
|
|
# if keyname is provided, then returns found value of that key.
|
|
local keyname="${1:-_unset}"
|
|
local line="" hash="#" key="" val=""
|
|
while read line; do
|
|
line=${line%%"${hash}"*}
|
|
key="${line%%:*}"
|
|
|
|
# no : in the line.
|
|
[ "$key" = "$line" ] && continue
|
|
trim "$key"
|
|
key=${_RET}
|
|
|
|
[ "$keyname" != "_unset" ] && [ "$keyname" != "$key" ] &&
|
|
continue
|
|
|
|
val="${line#*:}"
|
|
trim "$val"
|
|
unquote "${_RET}"
|
|
val=${_RET}
|
|
|
|
if [ "$keyname" = "$key" ]; then
|
|
_RET="$val"
|
|
return 0
|
|
fi
|
|
|
|
case "$key" in
|
|
datasource) _rc_dsname="$val";;
|
|
policy) _rc_policy="$val";;
|
|
esac
|
|
done
|
|
if [ "$keyname" = "_unset" ]; then
|
|
return 1
|
|
fi
|
|
_RET=""
|
|
return 0
|
|
}
|
|
|
|
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 (enabled|disabled|search|report)
|
|
# _rc_report true|false
|
|
# _rc_found first|all
|
|
# _rc_maybe all|none
|
|
# _rc_notfound enabled|disabled
|
|
local def=""
|
|
case "$DI_UNAME_MACHINE" in
|
|
# these have dmi data
|
|
i?86|x86_64) def=${DI_DEFAULT_POLICY};;
|
|
# aarch64 has dmi, but not currently used (LP: #1663304)
|
|
aarch64) def=${DI_DEFAULT_POLICY_NO_DMI};;
|
|
*) 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=""
|
|
# shellcheck disable=2086
|
|
{ IFS=","; set -- $policy; IFS="$oifs"; }
|
|
for tok in "$@"; do
|
|
val=${tok#*=}
|
|
case "$tok" in
|
|
"${DI_ENABLED}"|"${DI_DISABLED}"|search|report) 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#*=}
|
|
|
|
# discard anything after the first delimiter
|
|
val=${val%%;*}
|
|
case "$key" in
|
|
ds) _rc_dsname="$val";;
|
|
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_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" ]
|
|
}
|
|
|
|
read_uptime() {
|
|
local up _
|
|
_RET="${UNAVAILABLE}"
|
|
[ -f "$PATH_PROC_UPTIME" ] && read up _ < "$PATH_PROC_UPTIME" &&
|
|
_RET="$up"
|
|
return
|
|
}
|
|
|
|
set_run_path() {
|
|
if [ "$DI_UNAME_KERNEL_NAME" != "Linux" ]; then
|
|
PATH_RUN=${PATH_RUN:-"${PATH_ROOT}/var/run"}
|
|
else
|
|
PATH_RUN=${PATH_RUN:-"${PATH_ROOT}/run"}
|
|
fi
|
|
|
|
PATH_RUN_CI="${PATH_RUN_CI:-${PATH_RUN}/cloud-init}"
|
|
PATH_RUN_CI_CFG=${PATH_RUN_CI_CFG:-${PATH_RUN_CI}/cloud.cfg}
|
|
PATH_RUN_DI_RESULT=${PATH_RUN_DI_RESULT:-${PATH_RUN_CI}/.ds-identify.result}
|
|
|
|
DI_LOG="${DI_LOG:-${PATH_RUN_CI}/ds-identify.log}"
|
|
}
|
|
|
|
# set ds-identify internal variables by providing an env file
|
|
# testing only - NOT use for production code, it is NOT supported
|
|
get_environment() {
|
|
if [ -f "$PATH_DI_ENV" ]; then
|
|
debug 0 "WARN: loading environment file [${PATH_DI_ENV}]";
|
|
# shellcheck source=/dev/null
|
|
. "$PATH_DI_ENV"
|
|
fi
|
|
}
|
|
|
|
_main() {
|
|
local dscheck_fn="" ret_dis=1 ret_en=0
|
|
|
|
read_uptime
|
|
debug 1 "[up ${_RET}s]" "ds-identify $*"
|
|
read_virt
|
|
read_kernel_cmdline
|
|
if is_disabled; then
|
|
return 2
|
|
fi
|
|
collect_info
|
|
|
|
if [ "$DI_LOG" = "stderr" ]; then
|
|
_print_info 1>&2
|
|
else
|
|
_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|report) :;;
|
|
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
|
|
|
|
# shellcheck disable=2086
|
|
set -- $DI_DSLIST
|
|
# if there is only a single entry in $DI_DSLIST
|
|
if [ $# -eq 1 ] || [ $# -eq 2 -a "$2" = "None" ] ; then
|
|
debug 1 "single entry in datasource_list ($DI_DSLIST) use that."
|
|
if [ $# -eq 1 ]; then
|
|
write_result "datasource_list: [ $1 ]"
|
|
else
|
|
found "$@"
|
|
fi
|
|
return
|
|
fi
|
|
|
|
local found="" ret="" ds="" maybe="" _RET_excfg=""
|
|
local exfound_cfg="" exmaybe_cfg=""
|
|
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
|
|
_RET_excfg=""
|
|
$dscheck_fn
|
|
ret="$?"
|
|
case "$ret" in
|
|
"${DS_FOUND}")
|
|
debug 1 "check for '$ds' returned found";
|
|
exfound_cfg="${exfound_cfg:+${exfound_cfg}${CR}}${_RET_excfg}"
|
|
found="${found} $ds";;
|
|
"${DS_MAYBE}")
|
|
debug 1 "check for '$ds' returned maybe";
|
|
exmaybe_cfg="${exmaybe_cfg:+${exmaybe_cfg}${CR}}${_RET_excfg}"
|
|
maybe="${maybe} $ds";;
|
|
*) debug 2 "check for '$ds' returned not-found[$ret]";;
|
|
esac
|
|
done
|
|
|
|
debug 2 "found=${found# } maybe=${maybe# }"
|
|
# shellcheck disable=2086
|
|
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 "$@" -- "${exfound_cfg}"
|
|
return
|
|
fi
|
|
|
|
# shellcheck disable=2086
|
|
set -- $maybe
|
|
if [ $# -ne 0 -a "${DI_ON_MAYBE}" != "none" ]; then
|
|
debug 1 "$# datasources returned maybe: $*"
|
|
found "$@" -- "${exmaybe_cfg}"
|
|
return
|
|
fi
|
|
|
|
# record the empty result.
|
|
record_notfound
|
|
|
|
local basemsg="No ds found [mode=$DI_MODE, notfound=$DI_ON_NOTFOUND]."
|
|
local msg="" ret=3
|
|
case "$DI_MODE:$DI_ON_NOTFOUND" in
|
|
report:"${DI_DISABLED}")
|
|
msg="$basemsg Would disable cloud-init [$ret_dis]"
|
|
ret=$ret_en;;
|
|
report:"${DI_ENABLED}")
|
|
msg="$basemsg Would enable cloud-init [$ret_en]"
|
|
ret=$ret_en;;
|
|
search:"${DI_DISABLED}")
|
|
msg="$basemsg Disabled cloud-init [$ret_dis]"
|
|
ret=$ret_dis;;
|
|
search:"${DI_ENABLED}")
|
|
msg="$basemsg Enabled cloud-init [$ret_en]"
|
|
ret=$ret_en;;
|
|
*) error "Unexpected result";;
|
|
esac
|
|
debug 1 "$msg"
|
|
return "$ret"
|
|
}
|
|
|
|
main() {
|
|
local ret=""
|
|
get_environment
|
|
ensure_sane_path
|
|
read_uname_info
|
|
set_run_path
|
|
|
|
[ -d "$PATH_RUN_CI" ] || mkdir -p "$PATH_RUN_CI"
|
|
if [ "${1:+$1}" != "--force" ] && [ -f "$PATH_RUN_CI_CFG" ] &&
|
|
[ -f "$PATH_RUN_DI_RESULT" ]; then
|
|
if read ret < "$PATH_RUN_DI_RESULT"; then
|
|
if [ "$ret" = "0" ] || [ "$ret" = "1" ] || [ "$ret" = "2" ]; then
|
|
debug 2 "used cached result $ret. pass --force to re-run."
|
|
return "$ret";
|
|
fi
|
|
debug 1 "previous run returned unexpected '$ret'. Re-running."
|
|
else
|
|
error "failed to read result from $PATH_RUN_DI_RESULT!"
|
|
fi
|
|
fi
|
|
_main "$@"
|
|
ret=$?
|
|
echo "$ret" > "$PATH_RUN_DI_RESULT"
|
|
read_uptime
|
|
debug 1 "[up ${_RET}s]" "returning $ret"
|
|
return "$ret"
|
|
}
|
|
|
|
noop() {
|
|
:
|
|
}
|
|
|
|
get_environment
|
|
case "${DI_MAIN}" in
|
|
# builtin DI_MAIN implementations
|
|
main|print_info|noop) "${DI_MAIN}" "$@";;
|
|
|
|
# side-load an alternate implementation
|
|
# testing only - NOT use for production code, it is NOT supported
|
|
*)
|
|
debug 0 "WARN: side-loading alternate implementation: [${DI_MAIN}]";
|
|
exec "${DI_MAIN}" "$@";;
|
|
esac
|