ds-identify 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640
  1. #!/bin/sh
  2. # shellcheck disable=2015,2039,2162,2166
  3. #
  4. # ds-identify is configured via /etc/cloud/ds-identify.cfg
  5. # or on the kernel command line. It takes the following inputs:
  6. #
  7. # datasource: can specify the datasource that should be used.
  8. # kernel command line option: ci.datasource=<dsname> or ci.ds=<dsname>
  9. # example line in /etc/cloud/ds-identify.cfg:
  10. # datasource: Ec2
  11. #
  12. # policy: a string that indicates how ds-identify should operate.
  13. #
  14. # The format is:
  15. # <mode>,found=value,maybe=value,notfound=value
  16. # default setting is:
  17. # search,found=all,maybe=all,notfound=disabled
  18. #
  19. # kernel command line option: ci.di.policy=<policy>
  20. # example line in /etc/cloud/ds-identify.cfg:
  21. # policy: search,found=all,maybe=none,notfound=disabled
  22. #
  23. #
  24. # Mode:
  25. # disabled: disable cloud-init
  26. # enabled: enable cloud-init.
  27. # ds-identify writes no config and just exits success.
  28. # the caller (cloud-init-generator) then enables cloud-init to
  29. # run just without any aid from ds-identify.
  30. # search: determine which source or sources should be used
  31. # and write the result (datasource_list) to
  32. # /run/cloud-init/cloud.cfg
  33. # report: basically 'dry run' for search. results are still written
  34. # to the file, but are namespaced under the top level key
  35. # 'di_report' Thus cloud-init is not affected, but can still
  36. # see the result.
  37. #
  38. # found,maybe,notfound:
  39. # found: (default=all)
  40. # first: use the first found do no further checking
  41. # all: enable all DS_FOUND
  42. #
  43. # maybe: (default=all)
  44. # if nothing returned 'found', then how to handle maybe.
  45. # no network sources are allowed to return 'maybe'.
  46. # all: enable all DS_MAYBE
  47. # none: ignore any DS_MAYBE
  48. #
  49. # notfound: (default=disabled)
  50. # disabled: disable cloud-init
  51. # enabled: enable cloud-init
  52. #
  53. # ci.datasource.ec2.strict_id: (true|false|warn[,0-9])
  54. # if ec2 datasource does not strictly match,
  55. # return not_found if true
  56. # return maybe if false or warn*.
  57. #
  58. set -u
  59. set -f
  60. UNAVAILABLE="unavailable"
  61. CR="
  62. "
  63. ERROR="error"
  64. DI_ENABLED="enabled"
  65. DI_DISABLED="disabled"
  66. DI_DEBUG_LEVEL="${DEBUG_LEVEL:-1}"
  67. PATH_ROOT=${PATH_ROOT:-""}
  68. PATH_RUN=${PATH_RUN:-"${PATH_ROOT}/run"}
  69. PATH_SYS_CLASS_DMI_ID=${PATH_SYS_CLASS_DMI_ID:-${PATH_ROOT}/sys/class/dmi/id}
  70. PATH_SYS_HYPERVISOR=${PATH_SYS_HYPERVISOR:-${PATH_ROOT}/sys/hypervisor}
  71. PATH_SYS_CLASS_BLOCK=${PATH_SYS_CLASS_BLOCK:-${PATH_ROOT}/sys/class/block}
  72. PATH_DEV_DISK="${PATH_DEV_DISK:-${PATH_ROOT}/dev/disk}"
  73. PATH_VAR_LIB_CLOUD="${PATH_VAR_LIB_CLOUD:-${PATH_ROOT}/var/lib/cloud}"
  74. PATH_DI_CONFIG="${PATH_DI_CONFIG:-${PATH_ROOT}/etc/cloud/ds-identify.cfg}"
  75. PATH_PROC_CMDLINE="${PATH_PROC_CMDLINE:-${PATH_ROOT}/proc/cmdline}"
  76. PATH_PROC_1_CMDLINE="${PATH_PROC_1_CMDLINE:-${PATH_ROOT}/proc/1/cmdline}"
  77. PATH_PROC_1_ENVIRON="${PATH_PROC_1_ENVIRON:-${PATH_ROOT}/proc/1/environ}"
  78. PATH_PROC_UPTIME=${PATH_PROC_UPTIME:-${PATH_ROOT}/proc/uptime}
  79. PATH_ETC_CLOUD="${PATH_ETC_CLOUD:-${PATH_ROOT}/etc/cloud}"
  80. PATH_ETC_CI_CFG="${PATH_ETC_CI_CFG:-${PATH_ETC_CLOUD}/cloud.cfg}"
  81. PATH_ETC_CI_CFG_D="${PATH_ETC_CI_CFG_D:-${PATH_ETC_CI_CFG}.d}"
  82. PATH_RUN_CI="${PATH_RUN_CI:-${PATH_RUN}/cloud-init}"
  83. PATH_RUN_CI_CFG=${PATH_RUN_CI_CFG:-${PATH_RUN_CI}/cloud.cfg}
  84. PATH_RUN_DI_RESULT=${PATH_RUN_DI_RESULT:-${PATH_RUN_CI}/.ds-identify.result}
  85. DI_LOG="${DI_LOG:-${PATH_RUN_CI}/ds-identify.log}"
  86. _DI_LOGGED=""
  87. # set DI_MAIN='noop' in environment to source this file with no main called.
  88. DI_MAIN=${DI_MAIN:-main}
  89. DI_BLKID_OUTPUT=""
  90. DI_DEFAULT_POLICY="search,found=all,maybe=all,notfound=${DI_DISABLED}"
  91. DI_DEFAULT_POLICY_NO_DMI="search,found=all,maybe=all,notfound=${DI_ENABLED}"
  92. DI_DMI_CHASSIS_ASSET_TAG=""
  93. DI_DMI_PRODUCT_NAME=""
  94. DI_DMI_SYS_VENDOR=""
  95. DI_DMI_PRODUCT_SERIAL=""
  96. DI_DMI_PRODUCT_UUID=""
  97. DI_FS_LABELS=""
  98. DI_FS_UUIDS=""
  99. DI_ISO9660_DEVS=""
  100. DI_KERNEL_CMDLINE=""
  101. DI_VIRT=""
  102. DI_PID_1_PRODUCT_NAME=""
  103. DI_UNAME_KERNEL_NAME=""
  104. DI_UNAME_KERNEL_RELEASE=""
  105. DI_UNAME_KERNEL_VERSION=""
  106. DI_UNAME_MACHINE=""
  107. DI_UNAME_NODENAME=""
  108. DI_UNAME_OPERATING_SYSTEM=""
  109. DI_UNAME_CMD_OUT=""
  110. DS_FOUND=0
  111. DS_NOT_FOUND=1
  112. DS_MAYBE=2
  113. DI_DSNAME=""
  114. # this has to match the builtin list in cloud-init, it is what will
  115. # be searched if there is no setting found in config.
  116. DI_DSLIST_DEFAULT="MAAS ConfigDrive NoCloud AltCloud Azure Bigstep \
  117. CloudSigma CloudStack DigitalOcean AliYun Ec2 GCE OpenNebula OpenStack \
  118. OVF SmartOS Scaleway Hetzner IBMCloud Oracle Exoscale RbxCloud"
  119. DI_DSLIST=""
  120. DI_MODE=""
  121. DI_ON_FOUND=""
  122. DI_ON_MAYBE=""
  123. DI_ON_NOTFOUND=""
  124. DI_EC2_STRICT_ID_DEFAULT="true"
  125. _IS_IBM_CLOUD=""
  126. error() {
  127. set -- "ERROR:" "$@";
  128. debug 0 "$@"
  129. stderr "$@"
  130. }
  131. warn() {
  132. set -- "WARN:" "$@"
  133. debug 0 "$@"
  134. stderr "$@"
  135. }
  136. stderr() { echo "$@" 1>&2; }
  137. debug() {
  138. local lvl="$1"
  139. shift
  140. [ "$lvl" -gt "${DI_DEBUG_LEVEL}" ] && return
  141. if [ "$_DI_LOGGED" != "$DI_LOG" ]; then
  142. # first time here, open file descriptor for append
  143. case "$DI_LOG" in
  144. stderr) :;;
  145. ?*/*)
  146. if [ ! -d "${DI_LOG%/*}" ]; then
  147. mkdir -p "${DI_LOG%/*}" || {
  148. stderr "ERROR:" "cannot write to $DI_LOG"
  149. DI_LOG="stderr"
  150. }
  151. fi
  152. esac
  153. if [ "$DI_LOG" = "stderr" ]; then
  154. exec 3>&2
  155. else
  156. ( exec 3>>"$DI_LOG" ) && exec 3>>"$DI_LOG" || {
  157. stderr "ERROR: failed writing to $DI_LOG. logging to stderr.";
  158. exec 3>&2
  159. DI_LOG="stderr"
  160. }
  161. fi
  162. _DI_LOGGED="$DI_LOG"
  163. fi
  164. echo "$@" 1>&3
  165. }
  166. dmi_decode() {
  167. local sys_field="$1" dmi_field="" val=""
  168. command -v dmidecode >/dev/null 2>&1 || {
  169. warn "No dmidecode program. Cannot read $sys_field."
  170. return 1
  171. }
  172. case "$1" in
  173. sys_vendor) dmi_field="system-manufacturer";;
  174. product_name) dmi_field="system-product-name";;
  175. product_uuid) dmi_field="system-uuid";;
  176. product_serial) dmi_field="system-serial-number";;
  177. chassis_asset_tag) dmi_field="chassis-asset-tag";;
  178. *) error "Unknown field $sys_field. Cannot call dmidecode."
  179. return 1;;
  180. esac
  181. val=$(dmidecode --quiet "--string=$dmi_field" 2>/dev/null) || return 1
  182. _RET="$val"
  183. }
  184. get_dmi_field() {
  185. local path="${PATH_SYS_CLASS_DMI_ID}/$1"
  186. _RET="$UNAVAILABLE"
  187. if [ -d "${PATH_SYS_CLASS_DMI_ID}" ]; then
  188. if [ -f "$path" ] && [ -r "$path" ]; then
  189. read _RET < "${path}" || _RET="$ERROR"
  190. return
  191. fi
  192. # if `/sys/class/dmi/id` exists, but not the object we're looking for,
  193. # do *not* fallback to dmidecode!
  194. return
  195. fi
  196. dmi_decode "$1" || _RET="$ERROR"
  197. return
  198. }
  199. block_dev_with_label() {
  200. local p="${PATH_DEV_DISK}/by-label/$1"
  201. [ -b "$p" ] || return 1
  202. _RET=$p
  203. return 0
  204. }
  205. ensure_sane_path() {
  206. local t
  207. for t in /sbin /usr/sbin /bin /usr/bin; do
  208. case ":$PATH:" in
  209. *:$t:*|*:$t/:*) continue;;
  210. esac
  211. PATH="${PATH:+${PATH}:}$t"
  212. done
  213. }
  214. read_fs_info() {
  215. cached "${DI_BLKID_OUTPUT}" && return 0
  216. # do not rely on links in /dev/disk which might not be present yet.
  217. # Note that blkid < 2.22 (centos6, trusty) do not output DEVNAME.
  218. # that means that DI_ISO9660_DEVS will not be set.
  219. if is_container; then
  220. # blkid will in a container, or at least currently in lxd
  221. # not provide useful information.
  222. DI_FS_LABELS="$UNAVAILABLE:container"
  223. DI_ISO9660_DEVS="$UNAVAILABLE:container"
  224. return
  225. fi
  226. local oifs="$IFS" line="" delim=","
  227. local ret=0 out="" labels="" dev="" label="" ftype="" isodevs="" uuids=""
  228. out=$(blkid -c /dev/null -o export) || {
  229. ret=$?
  230. error "failed running [$ret]: blkid -c /dev/null -o export"
  231. DI_FS_LABELS="$UNAVAILABLE:error"
  232. DI_ISO9660_DEVS="$UNAVAILABLE:error"
  233. return $ret
  234. }
  235. # 'set --' will collapse multiple consecutive entries in IFS for
  236. # whitespace characters (\n, tab, " ") so we cannot rely on getting
  237. # empty lines in "$@" below.
  238. # shellcheck disable=2086
  239. { IFS="$CR"; set -- $out; IFS="$oifs"; }
  240. for line in "$@"; do
  241. case "${line}" in
  242. DEVNAME=*)
  243. [ -n "$dev" -a "$ftype" = "iso9660" ] &&
  244. isodevs="${isodevs},${dev}=$label"
  245. ftype=""; dev=""; label="";
  246. dev=${line#DEVNAME=};;
  247. LABEL=*) label="${line#LABEL=}";
  248. labels="${labels}${line#LABEL=}${delim}";;
  249. TYPE=*) ftype=${line#TYPE=};;
  250. UUID=*) uuids="${uuids}${line#UUID=}$delim";;
  251. esac
  252. done
  253. [ -n "$dev" -a "$ftype" = "iso9660" ] &&
  254. isodevs="${isodevs},${dev}=$label"
  255. DI_FS_LABELS="${labels%${delim}}"
  256. DI_FS_UUIDS="${uuids%${delim}}"
  257. DI_ISO9660_DEVS="${isodevs#,}"
  258. }
  259. cached() {
  260. [ -n "$1" ] && _RET="$1" && return || return 1
  261. }
  262. detect_virt() {
  263. local virt="${UNAVAILABLE}" r="" out=""
  264. if [ -d /run/systemd ]; then
  265. out=$(systemd-detect-virt 2>&1)
  266. r=$?
  267. if [ $r -eq 0 ] || { [ $r -ne 0 ] && [ "$out" = "none" ]; }; then
  268. virt="$out"
  269. fi
  270. elif [ "$DI_UNAME_KERNEL_NAME" = "FreeBSD" ]; then
  271. # Map FreeBSD's vm_guest names to those systemd-detect-virt that
  272. # don't match up. See
  273. # https://github.com/freebsd/freebsd/blob/master/sys/kern/subr_param.c#L144-L160
  274. # https://www.freedesktop.org/software/systemd/man/systemd-detect-virt.html
  275. #
  276. # systemd | kern.vm_guest
  277. # ---------------------+---------------
  278. # none | none
  279. # kvm | kvm
  280. # vmware | vmware
  281. # microsoft | hv
  282. # oracle | vbox
  283. # xen | xen
  284. # parallels | parallels
  285. # bhyve | bhyve
  286. # vm-other | generic
  287. out=$(sysctl -qn kern.vm_guest 2>/dev/null) && {
  288. case "$out" in
  289. hv) virt="microsoft" ;;
  290. vbox) virt="oracle" ;;
  291. generic) "vm-other";;
  292. *) virt="$out"
  293. esac
  294. }
  295. fi
  296. _RET="$virt"
  297. }
  298. read_virt() {
  299. cached "$DI_VIRT" && return 0
  300. detect_virt
  301. DI_VIRT=${_RET}
  302. }
  303. is_container() {
  304. case "${DI_VIRT}" in
  305. container-other|lxc|lxc-libvirt|systemd-nspawn|docker|rkt) return 0;;
  306. *) return 1;;
  307. esac
  308. }
  309. read_kernel_cmdline() {
  310. cached "${DI_KERNEL_CMDLINE}" && return
  311. local cmdline="" fpath="${PATH_PROC_CMDLINE}"
  312. if is_container; then
  313. local p1path="${PATH_PROC_1_CMDLINE}" x=""
  314. cmdline="${UNAVAILABLE}:container"
  315. if [ -f "$p1path" ] && x=$(tr '\0' ' ' < "$p1path"); then
  316. cmdline=$x
  317. fi
  318. elif [ -f "$fpath" ]; then
  319. read cmdline <"$fpath"
  320. else
  321. cmdline="${UNAVAILABLE}:no-cmdline"
  322. fi
  323. DI_KERNEL_CMDLINE="$cmdline"
  324. }
  325. read_dmi_chassis_asset_tag() {
  326. cached "${DI_DMI_CHASSIS_ASSET_TAG}" && return
  327. get_dmi_field chassis_asset_tag
  328. DI_DMI_CHASSIS_ASSET_TAG="$_RET"
  329. }
  330. read_dmi_sys_vendor() {
  331. cached "${DI_DMI_SYS_VENDOR}" && return
  332. get_dmi_field sys_vendor
  333. DI_DMI_SYS_VENDOR="$_RET"
  334. }
  335. read_dmi_product_name() {
  336. cached "${DI_DMI_PRODUCT_NAME}" && return
  337. get_dmi_field product_name
  338. DI_DMI_PRODUCT_NAME="$_RET"
  339. }
  340. read_dmi_product_uuid() {
  341. cached "${DI_DMI_PRODUCT_UUID}" && return
  342. get_dmi_field product_uuid
  343. DI_DMI_PRODUCT_UUID="$_RET"
  344. }
  345. read_dmi_product_serial() {
  346. cached "${DI_DMI_PRODUCT_SERIAL}" && return
  347. get_dmi_field product_serial
  348. DI_DMI_PRODUCT_SERIAL="$_RET"
  349. }
  350. # shellcheck disable=2034
  351. read_uname_info() {
  352. # run uname, and parse output.
  353. # uname is tricky to parse as it outputs always in a given order
  354. # independent of option order. kernel-version is known to have spaces.
  355. # 1 -s kernel-name
  356. # 2 -n nodename
  357. # 3 -r kernel-release
  358. # 4.. -v kernel-version(whitespace)
  359. # N-2 -m machine
  360. # N-1 -o operating-system
  361. cached "${DI_UNAME_CMD_OUT}" && return
  362. local out="${1:-}" ret=0 buf=""
  363. if [ -z "$out" ]; then
  364. out=$(uname -snrvmo) || {
  365. ret=$?
  366. error "failed reading uname with 'uname -snrvmo'"
  367. return $ret
  368. }
  369. fi
  370. # shellcheck disable=2086
  371. set -- $out
  372. DI_UNAME_KERNEL_NAME="$1"
  373. DI_UNAME_NODENAME="$2"
  374. DI_UNAME_KERNEL_RELEASE="$3"
  375. shift 3
  376. while [ $# -gt 2 ]; do
  377. buf="$buf $1"
  378. shift
  379. done
  380. DI_UNAME_KERNEL_VERSION="${buf# }"
  381. DI_UNAME_MACHINE="$1"
  382. DI_UNAME_OPERATING_SYSTEM="$2"
  383. DI_UNAME_CMD_OUT="$out"
  384. return 0
  385. }
  386. parse_yaml_array() {
  387. # parse a yaml single line array value ([1,2,3], not key: [1,2,3]).
  388. # supported with or without leading and closing brackets
  389. # ['1'] or [1]
  390. # '1', '2'
  391. local val="$1" oifs="$IFS" ret="" tok=""
  392. # i386/14.04 (dash=0.5.7-4ubuntu1): the following outputs "[foo"
  393. # sh -c 'n="$1"; echo ${n#[}' -- "[foo"
  394. # the fix was to quote the open bracket (val=${val#"["}) (LP: #1689648)
  395. val=${val#"["}
  396. val=${val%"]"}
  397. # shellcheck disable=2086
  398. { IFS=","; set -- $val; IFS="$oifs"; }
  399. for tok in "$@"; do
  400. trim "$tok"
  401. unquote "$_RET"
  402. ret="${ret} $_RET"
  403. done
  404. _RET="${ret# }"
  405. }
  406. read_datasource_list() {
  407. cached "$DI_DSLIST" && return
  408. local dslist=""
  409. # if DI_DSNAME is set directly, then avoid parsing config.
  410. if [ -n "${DI_DSNAME}" ]; then
  411. dslist="${DI_DSNAME}"
  412. fi
  413. # LP: #1582323. cc:{'datasource_list': ['name']}
  414. # more generically cc:<yaml>[end_cc]
  415. local cb="]" ob="["
  416. case "$DI_KERNEL_CMDLINE" in
  417. *cc:*datasource_list*)
  418. t=${DI_KERNEL_CMDLINE##*datasource_list}
  419. t=${t%%$cb*}
  420. t=${t##*$ob}
  421. parse_yaml_array "$t"
  422. dslist=${_RET}
  423. ;;
  424. esac
  425. if [ -z "$dslist" ] && check_config datasource_list; then
  426. debug 1 "$_RET_fname set datasource_list: $_RET"
  427. parse_yaml_array "$_RET"
  428. dslist=${_RET}
  429. fi
  430. if [ -z "$dslist" ]; then
  431. dslist=${DI_DSLIST_DEFAULT}
  432. debug 1 "no datasource_list found, using default: $dslist"
  433. fi
  434. DI_DSLIST=$dslist
  435. return 0
  436. }
  437. read_pid1_product_name() {
  438. local oifs="$IFS" out="" tok="" key="" val="" product_name="${UNAVAILABLE}"
  439. cached "${DI_PID_1_PRODUCT_NAME}" && return
  440. [ -r "${PATH_PROC_1_ENVIRON}" ] || return
  441. out=$(tr '\0' '\n' <"${PATH_PROC_1_ENVIRON}")
  442. # shellcheck disable=2086
  443. { IFS="$CR"; set -- $out; IFS="$oifs"; }
  444. for tok in "$@"; do
  445. key=${tok%%=*}
  446. [ "$key" != "$tok" ] || continue
  447. val=${tok#*=}
  448. [ "$key" = "product_name" ] && product_name="$val" && break
  449. done
  450. DI_PID_1_PRODUCT_NAME="$product_name"
  451. }
  452. dmi_chassis_asset_tag_matches() {
  453. is_container && return 1
  454. case "${DI_DMI_CHASSIS_ASSET_TAG}" in
  455. $1) return 0;;
  456. esac
  457. return 1
  458. }
  459. dmi_product_name_matches() {
  460. is_container && return 1
  461. case "${DI_DMI_PRODUCT_NAME}" in
  462. $1) return 0;;
  463. esac
  464. return 1
  465. }
  466. dmi_product_serial_matches() {
  467. is_container && return 1
  468. case "${DI_DMI_PRODUCT_SERIAL}" in
  469. $1) return 0;;
  470. esac
  471. return 1
  472. }
  473. dmi_sys_vendor_is() {
  474. is_container && return 1
  475. [ "${DI_DMI_SYS_VENDOR}" = "$1" ]
  476. }
  477. has_fs_with_uuid() {
  478. case ",${DI_FS_UUIDS}," in
  479. *,$1,*) return 0;;
  480. esac
  481. return 1
  482. }
  483. has_fs_with_label() {
  484. # has_fs_with_label(label1[ ,label2 ..])
  485. # return 0 if a there is a filesystem that matches any of the labels.
  486. local label=""
  487. for label in "$@"; do
  488. case ",${DI_FS_LABELS}," in
  489. *,$label,*) return 0;;
  490. esac
  491. done
  492. return 1
  493. }
  494. nocase_equal() {
  495. # nocase_equal(a, b)
  496. # return 0 if case insenstive comparision a.lower() == b.lower()
  497. # different lengths
  498. [ "${#1}" = "${#2}" ] || return 1
  499. # case sensitive equal
  500. [ "$1" = "$2" ] && return 0
  501. local delim="-delim-"
  502. # shellcheck disable=2018,2019
  503. out=$(echo "$1${delim}$2" | tr A-Z a-z)
  504. [ "${out#*${delim}}" = "${out%${delim}*}" ]
  505. }
  506. check_seed_dir() {
  507. # check_seed_dir(name, [required])
  508. # check the seed dir /var/lib/cloud/seed/<name> for 'required'
  509. # required defaults to 'meta-data'
  510. local name="$1"
  511. local dir="${PATH_VAR_LIB_CLOUD}/seed/$name"
  512. [ -d "$dir" ] || return 1
  513. shift
  514. if [ $# -eq 0 ]; then
  515. set -- meta-data
  516. fi
  517. local f=""
  518. for f in "$@"; do
  519. [ -f "$dir/$f" ] || return 1
  520. done
  521. return 0
  522. }
  523. check_writable_seed_dir() {
  524. # ubuntu core bind-mounts /writable/system-data/var/lib/cloud
  525. # over the top of /var/lib/cloud, but the mount might not be done yet.
  526. local wdir="/writable/system-data"
  527. [ -d "${PATH_ROOT}$wdir" ] || return 1
  528. local sdir="${PATH_ROOT}$wdir${PATH_VAR_LIB_CLOUD#${PATH_ROOT}}"
  529. local PATH_VAR_LIB_CLOUD="$sdir"
  530. check_seed_dir "$@"
  531. }
  532. probe_floppy() {
  533. cached "${STATE_FLOPPY_PROBED}" && return "${STATE_FLOPPY_PROBED}"
  534. local fpath=/dev/floppy
  535. [ -b "$fpath" ] ||
  536. { STATE_FLOPPY_PROBED=1; return 1; }
  537. modprobe --use-blacklist floppy >/dev/null 2>&1 ||
  538. { STATE_FLOPPY_PROBED=1; return 1; }
  539. udevadm settle "--exit-if-exists=$fpath" ||
  540. { STATE_FLOPPY_PROBED=1; return 1; }
  541. [ -b "$fpath" ]
  542. STATE_FLOPPY_PROBED=$?
  543. return "${STATE_FLOPPY_PROBED}"
  544. }
  545. dscheck_CloudStack() {
  546. is_container && return ${DS_NOT_FOUND}
  547. dmi_product_name_matches "CloudStack*" && return $DS_FOUND
  548. return $DS_NOT_FOUND
  549. }
  550. dscheck_Exoscale() {
  551. dmi_product_name_matches "Exoscale*" && return $DS_FOUND
  552. return $DS_NOT_FOUND
  553. }
  554. dscheck_CloudSigma() {
  555. # http://paste.ubuntu.com/23624795/
  556. dmi_product_name_matches "CloudSigma" && return $DS_FOUND
  557. return $DS_NOT_FOUND
  558. }
  559. check_config() {
  560. # check_config(key [,file_globs])
  561. # somewhat hackily read through file_globs for 'key'
  562. # file_globs are expanded via path expansion and
  563. # default to /etc/cloud/cloud.cfg /etc/cloud/cloud.cfg.d/*.cfg
  564. # currently does not respect any hierarchy in searching for key.
  565. local key="$1" files=""
  566. shift
  567. if [ $# -eq 0 ]; then
  568. files="${PATH_ETC_CI_CFG} ${PATH_ETC_CI_CFG_D}/*.cfg"
  569. else
  570. files="$*"
  571. fi
  572. # shellcheck disable=2086
  573. { set +f; set -- $files; set -f; }
  574. if [ "$1" = "$files" -a ! -f "$1" ]; then
  575. return 1
  576. fi
  577. local fname="" line="" ret="" found=0 found_fn=""
  578. # shellcheck disable=2094
  579. for fname in "$@"; do
  580. [ -f "$fname" ] || continue
  581. while read line; do
  582. line=${line%%#*}
  583. case "$line" in
  584. $key:\ *|$key:)
  585. ret=${line#*:};
  586. ret=${ret# };
  587. found=$((found+1))
  588. found_fn="$fname";;
  589. esac
  590. done <"$fname"
  591. done
  592. if [ $found -ne 0 ]; then
  593. _RET="$ret"
  594. _RET_fname="$found_fn"
  595. return 0
  596. fi
  597. return 1
  598. }
  599. dscheck_MAAS() {
  600. is_container && return "${DS_NOT_FOUND}"
  601. # heuristic check for ephemeral boot environment
  602. # for maas that do not set 'ci.dsname=' in the ephemeral environment
  603. # these have iscsi root and cloud-config-url on the cmdline.
  604. local maasiqn="iqn.2004-05.com.ubuntu:maas"
  605. case "${DI_KERNEL_CMDLINE}" in
  606. *cloud-config-url=*${maasiqn}*|*${maasiqn}*cloud-config-url=*)
  607. return ${DS_FOUND}
  608. ;;
  609. esac
  610. # check config files written by maas for installed system.
  611. if check_config "MAAS"; then
  612. return "${DS_FOUND}"
  613. fi
  614. return ${DS_NOT_FOUND}
  615. }
  616. dscheck_NoCloud() {
  617. local fslabel="cidata CIDATA" d=""
  618. case " ${DI_KERNEL_CMDLINE} " in
  619. *\ ds=nocloud*) return ${DS_FOUND};;
  620. esac
  621. case " ${DI_DMI_PRODUCT_SERIAL} " in
  622. *\ ds=nocloud*) return ${DS_FOUND};;
  623. esac
  624. for d in nocloud nocloud-net; do
  625. check_seed_dir "$d" meta-data user-data && return ${DS_FOUND}
  626. check_writable_seed_dir "$d" meta-data user-data && return ${DS_FOUND}
  627. done
  628. if has_fs_with_label $fslabel; then
  629. return ${DS_FOUND}
  630. fi
  631. return ${DS_NOT_FOUND}
  632. }
  633. is_ds_enabled() {
  634. local name="$1" pad=" ${DI_DSLIST} "
  635. [ "${pad#* $name }" != "${pad}" ]
  636. }
  637. check_configdrive_v2() {
  638. # look in /config-drive <vlc>/seed/config_drive for a directory
  639. # openstack/YYYY-MM-DD format with a file meta_data.json
  640. local d=""
  641. local vlc_config_drive_path="${PATH_VAR_LIB_CLOUD}/seed/config_drive"
  642. for d in /config-drive $vlc_config_drive_path; do
  643. set +f; set -- "$d/openstack/"2???-??-??/meta_data.json; set -f;
  644. [ -f "$1" ] && return ${DS_FOUND}
  645. done
  646. # at least one cloud (softlayer) seeds config drive with only 'latest'.
  647. local lpath="openstack/latest/meta_data.json"
  648. if [ -e "$vlc_config_drive_path/$lpath" ]; then
  649. debug 1 "config drive seeded directory had only 'latest'"
  650. return ${DS_FOUND}
  651. fi
  652. local ibm_enabled=false
  653. is_ds_enabled "IBMCloud" && ibm_enabled=true
  654. debug 1 "is_ds_enabled(IBMCloud) = $ibm_enabled."
  655. [ "$ibm_enabled" = "true" ] && is_ibm_cloud && return ${DS_NOT_FOUND}
  656. if has_fs_with_label CONFIG-2 config-2; then
  657. return ${DS_FOUND}
  658. fi
  659. return ${DS_NOT_FOUND}
  660. }
  661. check_configdrive_v1() {
  662. # FIXME: this has to check any file system that is vfat...
  663. # for now, just return not found.
  664. return ${DS_NOT_FOUND}
  665. }
  666. dscheck_ConfigDrive() {
  667. local ret=""
  668. check_configdrive_v2
  669. ret=$?
  670. [ $DS_FOUND -eq $ret ] && return $ret
  671. check_configdrive_v1
  672. }
  673. dscheck_DigitalOcean() {
  674. dmi_sys_vendor_is DigitalOcean && return ${DS_FOUND}
  675. return ${DS_NOT_FOUND}
  676. }
  677. dscheck_OpenNebula() {
  678. check_seed_dir opennebula && return ${DS_FOUND}
  679. has_fs_with_label "CONTEXT" && return ${DS_FOUND}
  680. return ${DS_NOT_FOUND}
  681. }
  682. dscheck_RbxCloud() {
  683. has_fs_with_label "CLOUDMD" "cloudmd" && return ${DS_FOUND}
  684. return ${DS_NOT_FOUND}
  685. }
  686. ovf_vmware_guest_customization() {
  687. # vmware guest customization
  688. # virt provider must be vmware
  689. [ "${DI_VIRT}" = "vmware" ] || return 1
  690. # we have to have the plugin to do vmware customization
  691. local found="" pkg="" pre="${PATH_ROOT}/usr/lib"
  692. local ppath="plugins/vmsvc/libdeployPkgPlugin.so"
  693. for pkg in vmware-tools open-vm-tools; do
  694. if [ -f "$pre/$pkg/$ppath" -o -f "${pre}64/$pkg/$ppath" ]; then
  695. found="$pkg"; break;
  696. fi
  697. done
  698. [ -n "$found" ] || return 1
  699. # vmware customization is disabled by default
  700. # (disable_vmware_customization=true). If it is set to false, then
  701. # user has requested customization.
  702. local key="disable_vmware_customization"
  703. if check_config "$key"; then
  704. debug 2 "${_RET_fname} set $key to $_RET"
  705. case "$_RET" in
  706. 0|false|False) return 0;;
  707. *) return 1;;
  708. esac
  709. fi
  710. return 1
  711. }
  712. ovf_vmware_transport_guestinfo() {
  713. [ "${DI_VIRT}" = "vmware" ] || return 1
  714. command -v vmware-rpctool >/dev/null 2>&1 || return 1
  715. local out="" ret=""
  716. out=$(vmware-rpctool "info-get guestinfo.ovfEnv" 2>&1)
  717. ret=$?
  718. if [ $ret -ne 0 ]; then
  719. debug 1 "Running on vmware but rpctool query returned $ret: $out"
  720. return 1
  721. fi
  722. case "$out" in
  723. "<?xml"*|"<?XML"*) :;;
  724. *) debug 1 "guestinfo.ovfEnv had non-xml content: $out";
  725. return 1;;
  726. esac
  727. debug 1 "Found guestinfo transport."
  728. return 0
  729. }
  730. is_cdrom_ovf() {
  731. local dev="$1" label="$2"
  732. # skip devices that don't look like cdrom paths.
  733. case "$dev" in
  734. /dev/sr[0-9]|/dev/hd[a-z]) :;;
  735. *) debug 1 "skipping iso dev $dev"
  736. return 1;;
  737. esac
  738. debug 1 "got label=$label"
  739. # fast path known 'OVF' labels
  740. case "$label" in
  741. OVF-TRANSPORT|ovf-transport|OVFENV|ovfenv|OVF\ ENV|ovf\ env) return 0;;
  742. esac
  743. # explicitly skip known labels of other types. rd_rdfe is azure.
  744. case "$label" in
  745. config-2|CONFIG-2|rd_rdfe_stable*|cidata|CIDATA) return 1;;
  746. esac
  747. # skip device which size is 10MB or larger
  748. local size="" sfile="${PATH_SYS_CLASS_BLOCK}/${dev##*/}/size"
  749. [ -f "$sfile" ] || return 1
  750. read size <"$sfile" || { warn "failed reading from $sfile"; return 1; }
  751. # size is in 512 byte units. so convert to MB (integer division)
  752. if [ $((size/2048)) -ge 10 ]; then
  753. debug 2 "$dev: size $((size/2048))MB is considered too large for OVF"
  754. return 1
  755. fi
  756. local idstr="http://schemas.dmtf.org/ovf/environment/1"
  757. grep --quiet --ignore-case "$idstr" "${PATH_ROOT}$dev"
  758. }
  759. has_ovf_cdrom() {
  760. # DI_ISO9660_DEVS is <device>=label,<device>=label2
  761. # like /dev/sr0=OVF-TRANSPORT,/dev/other=with spaces
  762. if [ "${DI_ISO9660_DEVS#${UNAVAILABLE}:}" = "${DI_ISO9660_DEVS}" ]; then
  763. local oifs="$IFS"
  764. # shellcheck disable=2086
  765. { IFS=","; set -- ${DI_ISO9660_DEVS}; IFS="$oifs"; }
  766. for tok in "$@"; do
  767. is_cdrom_ovf "${tok%%=*}" "${tok#*=}" && return 0
  768. done
  769. fi
  770. return 1
  771. }
  772. dscheck_OVF() {
  773. check_seed_dir ovf ovf-env.xml && return "${DS_FOUND}"
  774. [ "${DI_VIRT}" = "none" ] && return ${DS_NOT_FOUND}
  775. # Azure provides ovf. Skip false positive by dis-allowing.
  776. is_azure_chassis && return $DS_NOT_FOUND
  777. ovf_vmware_transport_guestinfo && return "${DS_FOUND}"
  778. has_ovf_cdrom && return "${DS_FOUND}"
  779. ovf_vmware_guest_customization && return "${DS_FOUND}"
  780. return ${DS_NOT_FOUND}
  781. }
  782. is_azure_chassis() {
  783. local azure_chassis="7783-7084-3265-9085-8269-3286-77"
  784. dmi_chassis_asset_tag_matches "${azure_chassis}"
  785. }
  786. dscheck_Azure() {
  787. # http://paste.ubuntu.com/23630873/
  788. # $ grep /sr0 /run/blkid/blkid.tab
  789. # <device DEVNO="0x0b00" TIME="1481737655.543841"
  790. # UUID="112D211272645f72" LABEL="rd_rdfe_stable.161212-1209"
  791. # TYPE="udf">/dev/sr0</device>
  792. #
  793. is_azure_chassis && return $DS_FOUND
  794. check_seed_dir azure ovf-env.xml && return ${DS_FOUND}
  795. [ "${DI_VIRT}" = "microsoft" ] || return ${DS_NOT_FOUND}
  796. has_fs_with_label "rd_rdfe_*" && return ${DS_FOUND}
  797. return ${DS_NOT_FOUND}
  798. }
  799. dscheck_Bigstep() {
  800. # bigstep is activated by presense of seed file 'url'
  801. [ -f "${PATH_VAR_LIB_CLOUD}/data/seed/bigstep/url" ] &&
  802. return ${DS_FOUND}
  803. return ${DS_NOT_FOUND}
  804. }
  805. ec2_read_strict_setting() {
  806. # the 'strict_id' setting for Ec2 controls behavior when
  807. # the platform does not identify itself directly as Ec2.
  808. # order of precedence is:
  809. # 1. builtin setting here cloud-init/ds-identify builtin
  810. # 2. ds-identify config
  811. # 3. system config (/etc/cloud/cloud.cfg.d/*Ec2*.cfg)
  812. # 4. kernel command line (undocumented)
  813. # 5. user-data or vendor-data (not available here)
  814. local default="$1" key="ci.datasource.ec2.strict_id" val=""
  815. # 4. kernel command line
  816. case " ${DI_KERNEL_CMDLINE} " in
  817. *\ $key=*\ )
  818. val=${DI_KERNEL_CMDLINE##*$key=}
  819. val=${val%% *};
  820. _RET=${val:-$default}
  821. return 0
  822. esac
  823. # 3. look for the key 'strict_id' (datasource/Ec2/strict_id)
  824. # only in cloud.cfg or cloud.cfg.d/EC2.cfg (case insensitive)
  825. local cfg="${PATH_ETC_CI_CFG}" cfg_d="${PATH_ETC_CI_CFG_D}"
  826. if check_config strict_id "$cfg" "$cfg_d/*[Ee][Cc]2*.cfg"; then
  827. debug 2 "${_RET_fname} set strict_id to $_RET"
  828. return 0
  829. fi
  830. # 2. ds-identify config (datasource.ec2.strict)
  831. local config="${PATH_DI_CONFIG}"
  832. if [ -f "$config" ]; then
  833. if _read_config "$key" < "$config"; then
  834. _RET=${_RET:-$default}
  835. return 0
  836. fi
  837. fi
  838. # 1. Default
  839. _RET=$default
  840. return 0
  841. }
  842. ec2_identify_platform() {
  843. local default="$1"
  844. local serial="${DI_DMI_PRODUCT_SERIAL}"
  845. case "$serial" in
  846. *.brightbox.com) _RET="Brightbox"; return 0;;
  847. esac
  848. local asset_tag="${DI_DMI_CHASSIS_ASSET_TAG}"
  849. case "$asset_tag" in
  850. *.zstack.io) _RET="ZStack"; return 0;;
  851. esac
  852. local vendor="${DI_DMI_SYS_VENDOR}"
  853. case "$vendor" in
  854. e24cloud) _RET="E24cloud"; return 0;;
  855. esac
  856. # AWS http://docs.aws.amazon.com/AWSEC2/
  857. # latest/UserGuide/identify_ec2_instances.html
  858. local uuid="" hvuuid="${PATH_SYS_HYPERVISOR}/uuid"
  859. # if the (basically) xen specific /sys/hypervisor/uuid starts with 'ec2'
  860. if [ -r "$hvuuid" ] && read uuid < "$hvuuid" &&
  861. [ "${uuid#ec2}" != "$uuid" ]; then
  862. _RET="AWS"
  863. return 0
  864. fi
  865. # product uuid and product serial start with case insensitive
  866. local uuid="${DI_DMI_PRODUCT_UUID}"
  867. case "$uuid:$serial" in
  868. [Ee][Cc]2*:[Ee][Cc]2*)
  869. # both start with ec2, now check for case insenstive equal
  870. nocase_equal "$uuid" "$serial" &&
  871. { _RET="AWS"; return 0; };;
  872. esac
  873. _RET="$default"
  874. return 0;
  875. }
  876. dscheck_Ec2() {
  877. check_seed_dir "ec2" meta-data user-data && return ${DS_FOUND}
  878. is_container && return ${DS_NOT_FOUND}
  879. local unknown="Unknown" platform=""
  880. if ec2_identify_platform "$unknown"; then
  881. platform="$_RET"
  882. else
  883. warn "Failed to identify ec2 platform. Using '$unknown'."
  884. platform=$unknown
  885. fi
  886. debug 1 "ec2 platform is '$platform'."
  887. if [ "$platform" != "$unknown" ]; then
  888. return $DS_FOUND
  889. fi
  890. local default="${DI_EC2_STRICT_ID_DEFAULT}"
  891. if ec2_read_strict_setting "$default"; then
  892. strict="$_RET"
  893. else
  894. debug 1 "ec2_read_strict returned non-zero: $?. using '$default'."
  895. strict="$default"
  896. fi
  897. local key="datasource/Ec2/strict_id"
  898. case "$strict" in
  899. true|false|warn|warn,[0-9]*) :;;
  900. *)
  901. warn "$key was set to invalid '$strict'. using '$default'"
  902. strict="$default";;
  903. esac
  904. _RET_excfg="datasource: {Ec2: {strict_id: \"$strict\"}}"
  905. if [ "$strict" = "true" ]; then
  906. return $DS_NOT_FOUND
  907. else
  908. return $DS_MAYBE
  909. fi
  910. }
  911. dscheck_GCE() {
  912. if dmi_product_name_matches "Google Compute Engine"; then
  913. return ${DS_FOUND}
  914. fi
  915. # product name is not guaranteed (LP: #1674861)
  916. if dmi_product_serial_matches "GoogleCloud-*"; then
  917. return ${DS_FOUND}
  918. fi
  919. return ${DS_NOT_FOUND}
  920. }
  921. dscheck_OpenStack() {
  922. # the openstack metadata http service
  923. # if there is a config drive, then do not check metadata
  924. # FIXME: if config drive not in the search list, then we should not
  925. # do this check.
  926. check_configdrive_v2
  927. if [ $? -eq ${DS_FOUND} ]; then
  928. return ${DS_NOT_FOUND}
  929. fi
  930. local nova="OpenStack Nova" compute="OpenStack Compute"
  931. if dmi_product_name_matches "$nova"; then
  932. return ${DS_FOUND}
  933. fi
  934. if dmi_product_name_matches "$compute"; then
  935. # RDO installed nova (LP: #1675349).
  936. return ${DS_FOUND}
  937. fi
  938. if [ "${DI_PID_1_PRODUCT_NAME}" = "$nova" ]; then
  939. return ${DS_FOUND}
  940. fi
  941. if dmi_chassis_asset_tag_matches "OpenTelekomCloud"; then
  942. return ${DS_FOUND}
  943. fi
  944. # LP: #1669875 : allow identification of OpenStack by asset tag
  945. if dmi_chassis_asset_tag_matches "$nova"; then
  946. return ${DS_FOUND}
  947. fi
  948. if dmi_chassis_asset_tag_matches "$compute"; then
  949. return ${DS_FOUND}
  950. fi
  951. # LP: #1715241 : arch other than intel are not identified properly.
  952. case "$DI_UNAME_MACHINE" in
  953. i?86|x86_64) :;;
  954. *) return ${DS_MAYBE};;
  955. esac
  956. return ${DS_NOT_FOUND}
  957. }
  958. dscheck_AliYun() {
  959. check_seed_dir "AliYun" meta-data user-data && return ${DS_FOUND}
  960. if dmi_product_name_matches "Alibaba Cloud ECS"; then
  961. return $DS_FOUND
  962. fi
  963. return $DS_NOT_FOUND
  964. }
  965. dscheck_AltCloud() {
  966. # ctype: either the dmi product name, or contents of
  967. # /etc/sysconfig/cloud-info
  968. # if ctype == "vsphere"
  969. # device = device with label 'CDROM'
  970. # elif ctype == "rhev"
  971. # device = /dev/floppy
  972. # then, filesystem on that device must have
  973. # user-data.txt or deltacloud-user-data.txt
  974. local ctype="" dev=""
  975. local match_rhev="[Rr][Hh][Ee][Vv]"
  976. local match_vsphere="[Vv][Ss][Pp][Hh][Ee][Rr][Ee]"
  977. local cinfo="${PATH_ROOT}/etc/sysconfig/cloud-info"
  978. if [ -f "$cinfo" ]; then
  979. read ctype < "$cinfo"
  980. else
  981. ctype="${DI_DMI_PRODUCT_NAME}"
  982. fi
  983. case "$ctype" in
  984. ${match_rhev})
  985. probe_floppy || return ${DS_NOT_FOUND}
  986. dev="/dev/floppy"
  987. ;;
  988. ${match_vsphere})
  989. block_dev_with_label CDROM || return ${DS_NOT_FOUND}
  990. dev="$_RET"
  991. ;;
  992. *) return ${DS_NOT_FOUND};;
  993. esac
  994. # FIXME: need to check $dev for user-data.txt or deltacloud-user-data.txt
  995. : "$dev"
  996. return $DS_MAYBE
  997. }
  998. dscheck_SmartOS() {
  999. # joyent cloud has two virt types: kvm and container
  1000. # on kvm, product name on joyent public cloud shows 'SmartDC HVM'
  1001. # on the container platform, uname's version has: BrandZ virtual linux
  1002. # for container, we also verify that the socketfile exists to protect
  1003. # against embedded containers (lxd running on brandz)
  1004. local smartdc_kver="BrandZ virtual linux"
  1005. local metadata_sockfile="${PATH_ROOT}/native/.zonecontrol/metadata.sock"
  1006. dmi_product_name_matches "SmartDC*" && return $DS_FOUND
  1007. [ "${DI_UNAME_KERNEL_VERSION}" = "${smartdc_kver}" ] &&
  1008. [ -e "${metadata_sockfile}" ] &&
  1009. return ${DS_FOUND}
  1010. return ${DS_NOT_FOUND}
  1011. }
  1012. dscheck_None() {
  1013. return ${DS_NOT_FOUND}
  1014. }
  1015. dscheck_Scaleway() {
  1016. if [ "${DI_DMI_SYS_VENDOR}" = "Scaleway" ]; then
  1017. return $DS_FOUND
  1018. fi
  1019. case " ${DI_KERNEL_CMDLINE} " in
  1020. *\ scaleway\ *) return ${DS_FOUND};;
  1021. esac
  1022. if [ -f "${PATH_ROOT}/var/run/scaleway" ]; then
  1023. return ${DS_FOUND}
  1024. fi
  1025. return ${DS_NOT_FOUND}
  1026. }
  1027. dscheck_Hetzner() {
  1028. dmi_sys_vendor_is Hetzner && return ${DS_FOUND}
  1029. return ${DS_NOT_FOUND}
  1030. }
  1031. dscheck_Oracle() {
  1032. local asset_tag="OracleCloud.com"
  1033. dmi_chassis_asset_tag_matches "${asset_tag}" && return ${DS_FOUND}
  1034. return ${DS_NOT_FOUND}
  1035. }
  1036. is_ibm_provisioning() {
  1037. local pcfg="${PATH_ROOT}/root/provisioningConfiguration.cfg"
  1038. local logf="${PATH_ROOT}/root/swinstall.log"
  1039. local is_prov=false msg="config '$pcfg' did not exist."
  1040. if [ -f "$pcfg" ]; then
  1041. msg="config '$pcfg' exists."
  1042. is_prov=true
  1043. if [ -f "$logf" ]; then
  1044. if [ "$logf" -nt "$PATH_PROC_1_ENVIRON" ]; then
  1045. msg="$msg log '$logf' from current boot."
  1046. else
  1047. is_prov=false
  1048. msg="$msg log '$logf' from previous boot."
  1049. fi
  1050. else
  1051. msg="$msg log '$logf' did not exist."
  1052. fi
  1053. fi
  1054. debug 2 "ibm_provisioning=$is_prov: $msg"
  1055. [ "$is_prov" = "true" ]
  1056. }
  1057. is_ibm_cloud() {
  1058. cached "${_IS_IBM_CLOUD}" && return ${_IS_IBM_CLOUD}
  1059. local ret=1
  1060. if [ "$DI_VIRT" = "xen" ]; then
  1061. if is_ibm_provisioning; then
  1062. ret=0
  1063. elif has_fs_with_label METADATA metadata; then
  1064. ret=0
  1065. elif has_fs_with_uuid 9796-932E &&
  1066. has_fs_with_label CONFIG-2 config-2; then
  1067. ret=0
  1068. fi
  1069. fi
  1070. _IS_IBM_CLOUD=$ret
  1071. return $ret
  1072. }
  1073. dscheck_IBMCloud() {
  1074. if is_ibm_provisioning; then
  1075. debug 1 "cloud-init disabled during provisioning on IBMCloud"
  1076. return ${DS_NOT_FOUND}
  1077. fi
  1078. is_ibm_cloud && return ${DS_FOUND}
  1079. return ${DS_NOT_FOUND}
  1080. }
  1081. collect_info() {
  1082. read_virt
  1083. read_pid1_product_name
  1084. read_kernel_cmdline
  1085. read_uname_info
  1086. read_config
  1087. read_datasource_list
  1088. read_dmi_sys_vendor
  1089. read_dmi_chassis_asset_tag
  1090. read_dmi_product_name
  1091. read_dmi_product_serial
  1092. read_dmi_product_uuid
  1093. read_fs_info
  1094. }
  1095. print_info() {
  1096. collect_info
  1097. _print_info
  1098. }
  1099. _print_info() {
  1100. local n="" v="" vars=""
  1101. vars="DMI_PRODUCT_NAME DMI_SYS_VENDOR DMI_PRODUCT_SERIAL"
  1102. vars="$vars DMI_PRODUCT_UUID PID_1_PRODUCT_NAME DMI_CHASSIS_ASSET_TAG"
  1103. vars="$vars FS_LABELS ISO9660_DEVS KERNEL_CMDLINE VIRT"
  1104. vars="$vars UNAME_KERNEL_NAME UNAME_KERNEL_RELEASE UNAME_KERNEL_VERSION"
  1105. vars="$vars UNAME_MACHINE UNAME_NODENAME UNAME_OPERATING_SYSTEM"
  1106. vars="$vars DSNAME DSLIST"
  1107. vars="$vars MODE ON_FOUND ON_MAYBE ON_NOTFOUND"
  1108. for v in ${vars}; do
  1109. eval n='${DI_'"$v"'}'
  1110. echo "$v=$n"
  1111. done
  1112. echo "pid=$$ ppid=$PPID"
  1113. is_container && echo "is_container=true" || echo "is_container=false"
  1114. }
  1115. write_result() {
  1116. local runcfg="${PATH_RUN_CI_CFG}" ret="" line="" pre=""
  1117. {
  1118. if [ "$DI_MODE" = "report" ]; then
  1119. echo "di_report:"
  1120. pre=" "
  1121. fi
  1122. for line in "$@"; do
  1123. echo "${pre}$line";
  1124. done
  1125. } > "$runcfg"
  1126. ret=$?
  1127. [ $ret -eq 0 ] || {
  1128. error "failed to write to ${runcfg}"
  1129. return $ret
  1130. }
  1131. return 0
  1132. }
  1133. record_notfound() {
  1134. # in report mode, report nothing was found.
  1135. # if not report mode: only report the negative result.
  1136. # reporting an empty list would mean cloud-init would not search
  1137. # any datasources.
  1138. if [ "$DI_MODE" = "report" ]; then
  1139. found --
  1140. elif [ "$DI_MODE" = "search" ]; then
  1141. local msg="# reporting not found result. notfound=${DI_ON_NOTFOUND}."
  1142. local DI_MODE="report"
  1143. found -- "$msg"
  1144. fi
  1145. }
  1146. found() {
  1147. # found(ds1, [ds2 ...], [-- [extra lines]])
  1148. local list="" ds=""
  1149. while [ $# -ne 0 ]; do
  1150. if [ "$1" = "--" ]; then
  1151. shift
  1152. break
  1153. fi
  1154. list="${list:+${list}, }$1"
  1155. shift
  1156. done
  1157. if [ $# -eq 1 ] && [ -z "$1" ]; then
  1158. # do not pass an empty line through.
  1159. shift
  1160. fi
  1161. # if None is not already in the list, then add it last.
  1162. case " $list " in
  1163. *\ None,\ *|*\ None\ ) :;;
  1164. *) list=${list:+${list}, None};;
  1165. esac
  1166. write_result "datasource_list: [ $list ]" "$@"
  1167. return
  1168. }
  1169. trim() {
  1170. # shellcheck disable=2048,2086
  1171. set -- $*
  1172. _RET="$*"
  1173. }
  1174. unquote() {
  1175. # remove quotes from quoted value
  1176. local quote='"' tick="'"
  1177. local val="$1"
  1178. case "$val" in
  1179. ${quote}*${quote}|${tick}*${tick})
  1180. val=${val#?}; val=${val%?};;
  1181. esac
  1182. _RET="$val"
  1183. }
  1184. _read_config() {
  1185. # reads config from stdin,
  1186. # if no parameters are set, modifies _rc scoped environment vars.
  1187. # if keyname is provided, then returns found value of that key.
  1188. local keyname="${1:-_unset}"
  1189. local line="" hash="#" key="" val=""
  1190. while read line; do
  1191. line=${line%%${hash}*}
  1192. key="${line%%:*}"
  1193. # no : in the line.
  1194. [ "$key" = "$line" ] && continue
  1195. trim "$key"
  1196. key=${_RET}
  1197. [ "$keyname" != "_unset" ] && [ "$keyname" != "$key" ] &&
  1198. continue
  1199. val="${line#*:}"
  1200. trim "$val"
  1201. unquote "${_RET}"
  1202. val=${_RET}
  1203. if [ "$keyname" = "$key" ]; then
  1204. _RET="$val"
  1205. return 0
  1206. fi
  1207. case "$key" in
  1208. datasource) _rc_dsname="$val";;
  1209. policy) _rc_policy="$val";;
  1210. esac
  1211. done
  1212. if [ "$keyname" = "_unset" ]; then
  1213. return 1
  1214. fi
  1215. _RET=""
  1216. return 0
  1217. }
  1218. parse_warn() {
  1219. echo "WARN: invalid value '$2' for key '$1'. Using $1=$3." 1>&2
  1220. }
  1221. parse_def_policy() {
  1222. local _rc_mode="" _rc_report="" _rc_found="" _rc_maybe="" _rc_notfound=""
  1223. local ret=""
  1224. parse_policy "$@"
  1225. ret=$?
  1226. _def_mode=$_rc_mode
  1227. _def_report=$_rc_report
  1228. _def_found=$_rc_found
  1229. _def_maybe=$_rc_maybe
  1230. _def_notfound=$_rc_notfound
  1231. return $ret
  1232. }
  1233. parse_policy() {
  1234. # parse_policy(policy, default)
  1235. # parse a policy string. sets
  1236. # _rc_mode (enabled|disabled|search|report)
  1237. # _rc_report true|false
  1238. # _rc_found first|all
  1239. # _rc_maybe all|none
  1240. # _rc_notfound enabled|disabled
  1241. local def=""
  1242. case "$DI_UNAME_MACHINE" in
  1243. # these have dmi data
  1244. i?86|x86_64) def=${DI_DEFAULT_POLICY};;
  1245. # aarch64 has dmi, but not currently used (LP: #1663304)
  1246. aarch64) def=${DI_DEFAULT_POLICY_NO_DMI};;
  1247. *) def=${DI_DEFAULT_POLICY_NO_DMI};;
  1248. esac
  1249. local policy="$1"
  1250. local _def_mode="" _def_report="" _def_found="" _def_maybe=""
  1251. local _def_notfound=""
  1252. if [ $# -eq 1 ] || [ "$2" != "-" ]; then
  1253. def=${2:-${def}}
  1254. parse_def_policy "$def" -
  1255. fi
  1256. local mode="" report="" found="" maybe="" notfound=""
  1257. local oifs="$IFS" tok="" val=""
  1258. # shellcheck disable=2086
  1259. { IFS=","; set -- $policy; IFS="$oifs"; }
  1260. for tok in "$@"; do
  1261. val=${tok#*=}
  1262. case "$tok" in
  1263. $DI_ENABLED|$DI_DISABLED|search|report) mode=$tok;;
  1264. found=all|found=first) found=$val;;
  1265. maybe=all|maybe=none) maybe=$val;;
  1266. notfound=$DI_ENABLED|notfound=$DI_DISABLED) notfound=$val;;
  1267. found=*)
  1268. parse_warn found "$val" "${_def_found}"
  1269. found=${_def_found};;
  1270. maybe=*)
  1271. parse_warn maybe "$val" "${_def_maybe}"
  1272. maybe=${_def_maybe};;
  1273. notfound=*)
  1274. parse_warn notfound "$val" "${_def_notfound}"
  1275. notfound=${_def_notfound};;
  1276. esac
  1277. done
  1278. report=${report:-${_def_report:-false}}
  1279. _rc_report=${report}
  1280. _rc_mode=${mode:-${_def_mode}}
  1281. _rc_found=${found:-${_def_found}}
  1282. _rc_maybe=${maybe:-${_def_maybe}}
  1283. _rc_notfound=${notfound:-${_def_notfound}}
  1284. }
  1285. read_config() {
  1286. local config="${PATH_DI_CONFIG}"
  1287. local _rc_dsname="" _rc_policy="" ret=""
  1288. if [ -f "$config" ]; then
  1289. _read_config < "$config"
  1290. ret=$?
  1291. elif [ -e "$config" ]; then
  1292. error "$config exists but is not a file!"
  1293. ret=1
  1294. fi
  1295. local tok="" key="" val=""
  1296. for tok in ${DI_KERNEL_CMDLINE}; do
  1297. key=${tok%%=*}
  1298. val=${tok#*=}
  1299. case "$key" in
  1300. ci.ds) _rc_dsname="$val";;
  1301. ci.datasource) _rc_dsname="$val";;
  1302. ci.di.policy) _rc_policy="$val";;
  1303. esac
  1304. done
  1305. local _rc_mode _rc_report _rc_found _rc_maybe _rc_notfound
  1306. parse_policy "${_rc_policy}"
  1307. debug 1 "policy loaded: mode=${_rc_mode} report=${_rc_report}" \
  1308. "found=${_rc_found} maybe=${_rc_maybe} notfound=${_rc_notfound}"
  1309. DI_MODE=${_rc_mode}
  1310. DI_ON_FOUND=${_rc_found}
  1311. DI_ON_MAYBE=${_rc_maybe}
  1312. DI_ON_NOTFOUND=${_rc_notfound}
  1313. DI_DSNAME="${_rc_dsname}"
  1314. return $ret
  1315. }
  1316. manual_clean_and_existing() {
  1317. [ -f "${PATH_VAR_LIB_CLOUD}/instance/manual-clean" ]
  1318. }
  1319. read_uptime() {
  1320. local up _
  1321. _RET="${UNAVAILABLE}"
  1322. [ -f "$PATH_PROC_UPTIME" ] && read up _ < "$PATH_PROC_UPTIME" &&
  1323. _RET="$up"
  1324. return
  1325. }
  1326. _main() {
  1327. local dscheck_fn="" ret_dis=1 ret_en=0
  1328. read_uptime
  1329. debug 1 "[up ${_RET}s]" "ds-identify $*"
  1330. collect_info
  1331. if [ "$DI_LOG" = "stderr" ]; then
  1332. _print_info 1>&2
  1333. else
  1334. _print_info >> "$DI_LOG"
  1335. fi
  1336. case "$DI_MODE" in
  1337. $DI_DISABLED)
  1338. debug 1 "mode=$DI_DISABLED. returning $ret_dis"
  1339. return $ret_dis
  1340. ;;
  1341. $DI_ENABLED)
  1342. debug 1 "mode=$DI_ENABLED. returning $ret_en"
  1343. return $ret_en;;
  1344. search|report) :;;
  1345. esac
  1346. if [ -n "${DI_DSNAME}" ]; then
  1347. debug 1 "datasource '$DI_DSNAME' specified."
  1348. found "$DI_DSNAME"
  1349. return
  1350. fi
  1351. if manual_clean_and_existing; then
  1352. debug 1 "manual_cache_clean enabled. Not writing datasource_list."
  1353. write_result "# manual_cache_clean."
  1354. return
  1355. fi
  1356. # shellcheck disable=2086
  1357. set -- $DI_DSLIST
  1358. # if there is only a single entry in $DI_DSLIST
  1359. if [ $# -eq 1 ] || [ $# -eq 2 -a "$2" = "None" ] ; then
  1360. debug 1 "single entry in datasource_list ($DI_DSLIST) use that."
  1361. found "$@"
  1362. return
  1363. fi
  1364. local found="" ret="" ds="" maybe="" _RET_excfg=""
  1365. local exfound_cfg="" exmaybe_cfg=""
  1366. for ds in ${DI_DSLIST}; do
  1367. dscheck_fn="dscheck_${ds}"
  1368. debug 2 "Checking for datasource '$ds' via '$dscheck_fn'"
  1369. if ! type "$dscheck_fn" >/dev/null 2>&1; then
  1370. warn "No check method '$dscheck_fn' for datasource '$ds'"
  1371. continue
  1372. fi
  1373. _RET_excfg=""
  1374. $dscheck_fn
  1375. ret="$?"
  1376. case "$ret" in
  1377. $DS_FOUND)
  1378. debug 1 "check for '$ds' returned found";
  1379. exfound_cfg="${exfound_cfg:+${exfound_cfg}${CR}}${_RET_excfg}"
  1380. found="${found} $ds";;
  1381. $DS_MAYBE)
  1382. debug 1 "check for '$ds' returned maybe";
  1383. exmaybe_cfg="${exmaybe_cfg:+${exmaybe_cfg}${CR}}${_RET_excfg}"
  1384. maybe="${maybe} $ds";;
  1385. *) debug 2 "check for '$ds' returned not-found[$ret]";;
  1386. esac
  1387. done
  1388. debug 2 "found=${found# } maybe=${maybe# }"
  1389. # shellcheck disable=2086
  1390. set -- $found
  1391. if [ $# -ne 0 ]; then
  1392. if [ $# -eq 1 ]; then
  1393. debug 1 "Found single datasource: $1"
  1394. else
  1395. # found=all
  1396. debug 1 "Found $# datasources found=${DI_ON_FOUND}: $*"
  1397. if [ "${DI_ON_FOUND}" = "first" ]; then
  1398. set -- "$1"
  1399. fi
  1400. fi
  1401. found "$@" -- "${exfound_cfg}"
  1402. return
  1403. fi
  1404. # shellcheck disable=2086
  1405. set -- $maybe
  1406. if [ $# -ne 0 -a "${DI_ON_MAYBE}" != "none" ]; then
  1407. debug 1 "$# datasources returned maybe: $*"
  1408. found "$@" -- "${exmaybe_cfg}"
  1409. return
  1410. fi
  1411. # record the empty result.
  1412. record_notfound
  1413. local basemsg="No ds found [mode=$DI_MODE, notfound=$DI_ON_NOTFOUND]."
  1414. local msg="" ret=3
  1415. case "$DI_MODE:$DI_ON_NOTFOUND" in
  1416. report:$DI_DISABLED)
  1417. msg="$basemsg Would disable cloud-init [$ret_dis]"
  1418. ret=$ret_en;;
  1419. report:$DI_ENABLED)
  1420. msg="$basemsg Would enable cloud-init [$ret_en]"
  1421. ret=$ret_en;;
  1422. search:$DI_DISABLED)
  1423. msg="$basemsg Disabled cloud-init [$ret_dis]"
  1424. ret=$ret_dis;;
  1425. search:$DI_ENABLED)
  1426. msg="$basemsg Enabled cloud-init [$ret_en]"
  1427. ret=$ret_en;;
  1428. *) error "Unexpected result";;
  1429. esac
  1430. debug 1 "$msg"
  1431. return "$ret"
  1432. }
  1433. main() {
  1434. local ret=""
  1435. ensure_sane_path
  1436. [ -d "$PATH_RUN_CI" ] || mkdir -p "$PATH_RUN_CI"
  1437. if [ "${1:+$1}" != "--force" ] && [ -f "$PATH_RUN_CI_CFG" ] &&
  1438. [ -f "$PATH_RUN_DI_RESULT" ]; then
  1439. if read ret < "$PATH_RUN_DI_RESULT"; then
  1440. if [ "$ret" = "0" ] || [ "$ret" = "1" ]; then
  1441. debug 2 "used cached result $ret. pass --force to re-run."
  1442. return "$ret";
  1443. fi
  1444. debug 1 "previous run returned unexpected '$ret'. Re-running."
  1445. else
  1446. error "failed to read result from $PATH_RUN_DI_RESULT!"
  1447. fi
  1448. fi
  1449. _main "$@"
  1450. ret=$?
  1451. echo "$ret" > "$PATH_RUN_DI_RESULT"
  1452. read_uptime
  1453. debug 1 "[up ${_RET}s]" "returning $ret"
  1454. return "$ret"
  1455. }
  1456. noop() {
  1457. :
  1458. }
  1459. case "${DI_MAIN}" in
  1460. main|print_info|noop) "${DI_MAIN}" "$@";;
  1461. *) error "unexpected value for DI_MAIN"; exit 1;;
  1462. esac
  1463. # vi: syntax=sh ts=4 expandtab