%PDF- %PDF-
Direktori : /snap/core20/current/usr/lib/cloud-init/ |
Current File : //snap/core20/current/usr/lib/cloud-init/ds-identify |
#!/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=all,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=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=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=all,notfound=${DI_DISABLED}" DI_DEFAULT_POLICY_NO_DMI="search,found=all,maybe=all,notfound=${DI_ENABLED}" 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 \ OVF SmartOS Scaleway Hetzner IBMCloud Oracle Exoscale RbxCloud UpCloud VMware \ LXD NWCS Akamai WSL" 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_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 # check config files written by maas for installed system. if check_config "MAAS"; then return "${DS_FOUND}" fi 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" && check_config "user-data" && check_config "meta-data"; then return ${DS_FOUND} 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 # 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" "$1")" _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" 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