Merge ~smoser/cloud-init:feature/login-warn into cloud-init:master

Proposed by Scott Moser
Status: Merged
Merged at revision: c81ea53bbdc4ada9d2b52430e106aeb3c38b4e0a
Proposed branch: ~smoser/cloud-init:feature/login-warn
Merge into: cloud-init:master
Diff against target: 469 lines (+250/-56)
7 files modified
cloudinit/cmd/main.py (+39/-0)
cloudinit/helpers.py (+1/-0)
cloudinit/sources/DataSourceEc2.py (+5/-42)
cloudinit/warnings.py (+139/-0)
packages/debian/rules.in (+1/-0)
tools/Z99-cloudinit-warnings.sh (+30/-0)
tools/ds-identify (+35/-14)
Reviewer Review Type Date Requested Status
cloud-init Commiters Pending
Review via email: mp+318844@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Scott Moser (smoser) wrote :

There is a series of commits here that together create add support for
a.) cloud-init showing warnings to a user who ssh's in
    This is accomplished by adding a profile.d script that shows content
    in /var/lib/cloud/instance/warnings/ if it exists.

b.) warns the user when they have used a datasource that ds-identify in
    report mode did not identify.

Revision history for this message
Ryan Harper (raharper) wrote :

fix "create add support" -> "add support" in the above message (or at least fix in commit message).

Two comments below; generally looks good.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/cmd/main.py b/cloudinit/cmd/main.py
2index 7c65257..6ff4e1c 100644
3--- a/cloudinit/cmd/main.py
4+++ b/cloudinit/cmd/main.py
5@@ -29,6 +29,7 @@ from cloudinit import templater
6 from cloudinit import url_helper
7 from cloudinit import util
8 from cloudinit import version
9+from cloudinit import warnings
10
11 from cloudinit import reporting
12 from cloudinit.reporting import events
13@@ -413,10 +414,48 @@ def main_init(name, args):
14 # give the activated datasource a chance to adjust
15 init.activate_datasource()
16
17+ di_report_warn(datasource=init.datasource, cfg=init.cfg)
18+
19 # Stage 10
20 return (init.datasource, run_module_section(mods, name, name))
21
22
23+def di_report_warn(datasource, cfg):
24+ if 'di_report' not in cfg:
25+ LOG.debug("no di_report found in config.")
26+ return
27+
28+ dicfg = cfg.get('di_report', {})
29+ if not isinstance(dicfg, dict):
30+ LOG.warn("di_report config not a dictionary: %s", dicfg)
31+ return
32+
33+ dslist = dicfg.get('datasource_list')
34+ if dslist is None:
35+ LOG.warn("no 'datasource_list' found in di_report.")
36+ return
37+ elif not isinstance(dslist, list):
38+ LOG.warn("di_report/datasource_list not a list: %s", dslist)
39+ return
40+
41+ # ds.__module__ is like cloudinit.sources.DataSourceName
42+ # where Name is the thing that shows up in datasource_list.
43+ modname = datasource.__module__.rpartition(".")[2]
44+ if modname.startswith(sources.DS_PREFIX):
45+ modname = modname[len(sources.DS_PREFIX):]
46+ else:
47+ LOG.warn("Datasource '%s' came from unexpected module '%s'.",
48+ datasource, modname)
49+
50+ if modname in dslist:
51+ LOG.debug("used datasource '%s' from '%s' was in di_report's list: %s",
52+ datasource, modname, dslist)
53+ return
54+
55+ warnings.show_warning('dsid_missing_source', cfg,
56+ source=modname, dslist=str(dslist))
57+
58+
59 def main_modules(action_name, args):
60 name = args.mode
61 # Cloud-init 'modules' stages are broken up into the following sub-stages
62diff --git a/cloudinit/helpers.py b/cloudinit/helpers.py
63index 38f5f89..7435d58 100644
64--- a/cloudinit/helpers.py
65+++ b/cloudinit/helpers.py
66@@ -340,6 +340,7 @@ class Paths(object):
67 "vendordata": "vendor-data.txt.i",
68 "instance_id": ".instance-id",
69 "manual_clean_marker": "manual-clean",
70+ "warnings": "warnings",
71 }
72 # Set when a datasource becomes active
73 self.datasource = ds
74diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py
75index c7df806..6f01a13 100644
76--- a/cloudinit/sources/DataSourceEc2.py
77+++ b/cloudinit/sources/DataSourceEc2.py
78@@ -9,7 +9,6 @@
79 # This file is part of cloud-init. See LICENSE file for license information.
80
81 import os
82-import textwrap
83 import time
84
85 from cloudinit import ec2_utils as ec2
86@@ -17,6 +16,7 @@ from cloudinit import log as logging
87 from cloudinit import sources
88 from cloudinit import url_helper as uhelp
89 from cloudinit import util
90+from cloudinit import warnings
91
92 LOG = logging.getLogger(__name__)
93
94@@ -224,7 +224,8 @@ class DataSourceEc2(sources.DataSource):
95 return
96 if self.cloud_platform == Platforms.UNKNOWN:
97 warn_if_necessary(
98- util.get_cfg_by_path(cfg, STRICT_ID_PATH, STRICT_ID_DEFAULT))
99+ util.get_cfg_by_path(cfg, STRICT_ID_PATH, STRICT_ID_DEFAULT),
100+ cfg)
101
102
103 def read_strict_mode(cfgval, default):
104@@ -265,7 +266,7 @@ def parse_strict_mode(cfgval):
105 return mode, sleep
106
107
108-def warn_if_necessary(cfgval):
109+def warn_if_necessary(cfgval, cfg):
110 try:
111 mode, sleep = parse_strict_mode(cfgval)
112 except ValueError as e:
113@@ -275,45 +276,7 @@ def warn_if_necessary(cfgval):
114 if mode == "false":
115 return
116
117- show_warning(sleep)
118-
119-
120-def show_warning(sleep):
121- message = textwrap.dedent("""
122- ****************************************************************
123- # This system is using the EC2 Metadata Service, but does not #
124- # appear to be running on Amazon EC2 or one of cloud-init's #
125- # known platforms that provide a EC2 Metadata service. In the #
126- # future, cloud-init may stop reading metadata from the EC2 #
127- # Metadata Service unless the platform can be identified #
128- # #
129- # If you are seeing this message, please file a bug against #
130- # cloud-init at https://bugs.launchpad.net/cloud-init/+filebug #
131- # Make sure to include the cloud provider your instance is #
132- # running on. #
133- # #
134- # For more information see #
135- # https://bugs.launchpad.net/cloud-init/+bug/1660385 #
136- # #
137- # After you have filed a bug, you can disable this warning by #
138- # launching your instance with the cloud-config below, or #
139- # putting that content into #
140- # /etc/cloud/cloud.cfg.d/99-ec2-datasource.cfg #
141- # #
142- # #cloud-config #
143- # datasource: #
144- # Ec2: #
145- # strict_id: false #
146- # #
147- """)
148- closemsg = ""
149- if sleep:
150- closemsg = " [sleeping for %d seconds] " % sleep
151- message += closemsg.center(64, "*")
152- print(message)
153- LOG.warn(message)
154- if sleep:
155- time.sleep(sleep)
156+ warnings.show_warning('non_ec2_md', cfg, mode=True, sleep=sleep)
157
158
159 def identify_aws(data):
160diff --git a/cloudinit/warnings.py b/cloudinit/warnings.py
161new file mode 100644
162index 0000000..3206d4e
163--- /dev/null
164+++ b/cloudinit/warnings.py
165@@ -0,0 +1,139 @@
166+# This file is part of cloud-init. See LICENSE file for license information.
167+
168+from cloudinit import helpers
169+from cloudinit import log as logging
170+from cloudinit import util
171+
172+import os
173+import time
174+
175+LOG = logging.getLogger()
176+
177+WARNINGS = {
178+ 'non_ec2_md': """
179+This system is using the EC2 Metadata Service, but does not appear to
180+be running on Amazon EC2 or one of cloud-init's known platforms that
181+provide a EC2 Metadata service. In the future, cloud-init may stop
182+reading metadata from the EC2 Metadata Service unless the platform can
183+be identified.
184+
185+If you are seeing this message, please file a bug against
186+cloud-init at
187+ https://bugs.launchpad.net/cloud-init/+filebug?field.tags=dsid
188+Make sure to include the cloud provider your instance is
189+running on.
190+
191+For more information see
192+ https://bugs.launchpad.net/bugs/1660385
193+
194+After you have filed a bug, you can disable this warning by
195+launching your instance with the cloud-config below, or
196+putting that content into
197+ /etc/cloud/cloud.cfg.d/99-ec2-datasource.cfg
198+
199+#cloud-config
200+datasource:
201+ Ec2:
202+ strict_id: false""",
203+ 'dsid_missing_source': """
204+A new feature in cloud-init identified possible datasources for
205+this system as:
206+ {dslist}
207+However, the datasource used was: {source}
208+
209+In the future, cloud-init will only attempt to use datasources that
210+are identified or specifically configured.
211+For more information see
212+ https://bugs.launchpad.net/bugs/1669675
213+
214+If you are seeing this message, please file a bug against
215+cloud-init at
216+ https://bugs.launchpad.net/cloud-init/+filebug?field.tags=dsid
217+Make sure to include the cloud provider your instance is
218+running on.
219+
220+After you have filed a bug, you can disable this warning by launching
221+your instance with the cloud-config below, or putting that content
222+into /etc/cloud/cloud.cfg.d/99-warnings.cfg
223+
224+#cloud-config
225+warnings:
226+ dsid_missing_source: off""",
227+}
228+
229+
230+def _get_warn_dir(cfg):
231+ paths = helpers.Paths(
232+ path_cfgs=cfg.get('system_info', {}).get('paths', {}))
233+ return paths.get_ipath_cur('warnings')
234+
235+
236+def _load_warn_cfg(cfg, name, mode=True, sleep=None):
237+ # parse cfg['warnings']['name'] returning boolean, sleep
238+ # expected value is form of:
239+ # (on|off|true|false|sleep)[,sleeptime]
240+ # boolean True == on, False == off
241+ default = (mode, sleep)
242+ if not cfg or not isinstance(cfg, dict):
243+ return default
244+
245+ ncfg = util.get_cfg_by_path(cfg, ('warnings', name))
246+ if ncfg is None:
247+ return default
248+
249+ if ncfg in ("on", "true", True):
250+ return True, None
251+
252+ if ncfg in ("off", "false", False):
253+ return False, None
254+
255+ mode, _, csleep = ncfg.partition(",")
256+ if mode != "sleep":
257+ return default
258+
259+ if csleep:
260+ try:
261+ sleep = int(csleep)
262+ except ValueError:
263+ return default
264+
265+ return True, sleep
266+
267+
268+def show_warning(name, cfg=None, sleep=None, mode=True, **kwargs):
269+ # kwargs are used for .format of the message.
270+ # sleep and mode are default values used if
271+ # cfg['warnings']['name'] is not present.
272+ if cfg is None:
273+ cfg = {}
274+
275+ mode, sleep = _load_warn_cfg(cfg, name, mode=mode, sleep=sleep)
276+ if not mode:
277+ return
278+
279+ msg = WARNINGS[name].format(**kwargs)
280+ msgwidth = 70
281+ linewidth = msgwidth + 4
282+
283+ fmt = "# %%-%ds #" % msgwidth
284+ topline = "*" * linewidth + "\n"
285+ fmtlines = []
286+ for line in msg.strip("\n").splitlines():
287+ fmtlines.append(fmt % line)
288+
289+ closeline = topline
290+ if sleep:
291+ sleepmsg = " [sleeping for %d seconds] " % sleep
292+ closeline = sleepmsg.center(linewidth, "*") + "\n"
293+
294+ util.write_file(
295+ os.path.join(_get_warn_dir(cfg), name),
296+ topline + "\n".join(fmtlines) + "\n" + topline)
297+
298+ LOG.warn(topline + "\n".join(fmtlines) + "\n" + closeline)
299+
300+ if sleep:
301+ LOG.debug("sleeping %d seconds for warning '%s'" % (sleep, name))
302+ time.sleep(sleep)
303+
304+# vi: ts=4 expandtab
305diff --git a/packages/debian/rules.in b/packages/debian/rules.in
306index 3df6053..053b764 100755
307--- a/packages/debian/rules.in
308+++ b/packages/debian/rules.in
309@@ -12,6 +12,7 @@ override_dh_install:
310 install -d debian/cloud-init/etc/rsyslog.d
311 cp tools/21-cloudinit.conf debian/cloud-init/etc/rsyslog.d/21-cloudinit.conf
312 install -D ./tools/Z99-cloud-locale-test.sh debian/cloud-init/etc/profile.d/Z99-cloud-locale-test.sh
313+ install -D ./tools/Z99-cloudinit-warnings.sh debian/cloud-init/etc/profile.d/Z99-cloudinit-warnings.sh
314
315 override_dh_auto_test:
316 ifeq (,$(findstring nocheck,$(DEB_BUILD_OPTIONS)))
317diff --git a/tools/Z99-cloudinit-warnings.sh b/tools/Z99-cloudinit-warnings.sh
318new file mode 100644
319index 0000000..b237786
320--- /dev/null
321+++ b/tools/Z99-cloudinit-warnings.sh
322@@ -0,0 +1,30 @@
323+#!/bin/sh
324+# This file is part of cloud-init. See LICENSE file for license information.
325+
326+# Purpose: show user warnings on login.
327+
328+cloud_init_warnings() {
329+ local skipf="" warning="" idir="/var/lib/cloud/instance" n=0
330+ local warndir="$idir/warnings"
331+ local ufile="$HOME/.cloud-warnings.skip" sfile="$warndir/.skip"
332+ [ -d "$warndir" ] || return 0
333+ [ ! -f "$ufile" ] || return 0
334+ [ ! -f "$skipf" ] || return 0
335+
336+ for warning in "$warndir"/*; do
337+ [ -f "$warning" ] || continue
338+ cat "$warning"
339+ n=$((n+1))
340+ done
341+ [ $n -eq 0 ] && return 0
342+ echo ""
343+ echo "Disable the warnings above by:"
344+ echo " touch $ufile"
345+ echo "or"
346+ echo " touch $sfile"
347+}
348+
349+cloud_init_warnings 1>&2
350+unset cloud_init_warnings
351+
352+# vi: syntax=sh ts=4 expandtab
353diff --git a/tools/ds-identify b/tools/ds-identify
354index 9711a23..d7b2a0b 100755
355--- a/tools/ds-identify
356+++ b/tools/ds-identify
357@@ -10,8 +10,9 @@
358 # default setting is:
359 # search,found=all,maybe=all,notfound=disable
360 #
361-# report: write config to /run/cloud-init/cloud.cfg.report (instead of
362-# /run/cloud-init/cloud.cfg, which effectively makes this dry-run).
363+# report: write config to /run/cloud-init/cloud.cfg, but
364+# namespaced under 'di_report'. Thus cloud-init can still see
365+# the result, but has no affect.
366 # enable: do nothing
367 # ds-identify writes no config and just exits success
368 # the caller (cloud-init-generator) then enables cloud-init to run
369@@ -108,6 +109,7 @@ DI_ON_FOUND=""
370 DI_ON_MAYBE=""
371 DI_ON_NOTFOUND=""
372
373+DI_EC2_STRICT_ID_DEFAULT="true"
374
375 error() {
376 set -- "ERROR:" "$@";
377@@ -720,7 +722,7 @@ dscheck_Ec2() {
378 return $DS_FOUND
379 fi
380
381- local default="true"
382+ local default="${DI_EC2_STRICT_ID_DEFAULT}"
383 if ec2_read_strict_setting "$default"; then
384 strict="$_RET"
385 else
386@@ -867,15 +869,16 @@ _print_info() {
387 }
388
389 write_result() {
390- local runcfg="${PATH_RUN_CI_CFG}" ret="" line=""
391- if [ "$DI_REPORT" = "true" ]; then
392- # if report is true, then we write to .report, but touch the other.
393- : > "$runcfg"
394- runcfg="$runcfg.report"
395- fi
396- for line in "$@"; do
397- echo "$line"
398- done > "$runcfg"
399+ local runcfg="${PATH_RUN_CI_CFG}" ret="" line="" pre=""
400+ {
401+ if [ "$DI_REPORT" = "true" ]; then
402+ echo "di_report:"
403+ pre=" "
404+ fi
405+ for line in "$@"; do
406+ echo "${pre}$line";
407+ done
408+ } > "$runcfg"
409 ret=$?
410 [ $ret -eq 0 ] || {
411 error "failed to write to ${runcfg}"
412@@ -884,10 +887,23 @@ write_result() {
413 return 0
414 }
415
416+record_notfound() {
417+ # in report mode, report nothing was found.
418+ # if not report mode: only report the negative result.
419+ # reporting an empty list would mean cloud-init would not search
420+ # any datasources.
421+ if [ "$DI_REPORT" = "true" ]; then
422+ found --
423+ else
424+ local msg="# reporting not found result. notfound=${DI_ON_NOTFOUND}."
425+ local DI_REPORT="true"
426+ found -- "$msg"
427+ fi
428+}
429+
430 found() {
431 # found(ds1, [ds2 ...], [-- [extra lines]])
432 local list="" ds=""
433- # always we write the None datasource last.
434 while [ $# -ne 0 ]; do
435 if [ "$1" = "--" ]; then
436 shift
437@@ -900,6 +916,8 @@ found() {
438 # do not pass an empty line through.
439 shift
440 fi
441+ # always write the None datasource last.
442+ list="${list:+${list}, }None"
443 write_result "datasource_list: [ $list ]" "$@"
444 return
445 }
446@@ -956,6 +974,7 @@ _read_config() {
447 if [ "$keyname" = "_unset" ]; then
448 return 1
449 fi
450+ _RET=""
451 return 0
452 }
453
454@@ -1170,13 +1189,15 @@ _main() {
455 return
456 fi
457
458+ # record the empty result.
459+ record_notfound
460 case "$DI_ON_NOTFOUND" in
461 $DI_DISABLED)
462 debug 1 "No result. notfound=$DI_DISABLED. returning $ret_dis."
463 return $ret_dis
464 ;;
465 $DI_ENABLED)
466- debug 1 "notfound=$DI_ENABLED. returning $ret_en"
467+ debug 1 "No result. notfound=$DI_ENABLED. returning $ret_en"
468 return $ret_en;;
469 esac
470

Subscribers

People subscribed via source and target branches