summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Harper <ryan.harper@canonical.com>2020-08-17 11:06:20 -0500
committerGitHub <noreply@github.com>2020-08-17 12:06:20 -0400
commita4b6b96f30bdd994ab535b222cf4b4bf09f20668 (patch)
treeb663314d2bc85a48460306d215e107bb4af3787c
parentef041fd822a2cf3a4022525e942ce988b1f95180 (diff)
downloadvyos-cloud-init-a4b6b96f30bdd994ab535b222cf4b4bf09f20668.tar.gz
vyos-cloud-init-a4b6b96f30bdd994ab535b222cf4b4bf09f20668.zip
cli: add devel make-mime subcommand (#518)
* cli: add devel make-mime subcommand Cloud-init documents an in-source-tree tool, make-mime.py used to help users create multi-part mime user-data. This tool is not shipped in the cloud-init install and unavailable at runtime. This patch takes tools/make-mime.py and makes the functionality available via the devel subcommand. The primary interface of --attach file:content-type is still present. The cli now adds: -l, --list-types Print out a list of supported content-types -f, --force Ignore errors for unsupported content-types The tool will now raise a RunTime error if the supplied content-type is not supported (or more likely a typo: x-shell-script vs. x-shellscript) * make-mime: write to stderr and exit 1 instead of raising RuntimeError * Update example to match docs * Update docs for make-mime subcommand * Remove tools/make-mime.py; replaced by cloud-init devel make-mime Co-authored-by: Rick Harding <rharding@mitechie.com>
-rwxr-xr-xcloudinit/cmd/devel/make_mime.py112
-rw-r--r--cloudinit/cmd/devel/parser.py5
-rw-r--r--doc/rtd/topics/format.rst44
-rwxr-xr-xtools/make-mime.py62
4 files changed, 139 insertions, 84 deletions
diff --git a/cloudinit/cmd/devel/make_mime.py b/cloudinit/cmd/devel/make_mime.py
new file mode 100755
index 00000000..77e10540
--- /dev/null
+++ b/cloudinit/cmd/devel/make_mime.py
@@ -0,0 +1,112 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+"""Generate multi-part mime messages for user-data """
+
+import argparse
+import sys
+from email.mime.multipart import MIMEMultipart
+from email.mime.text import MIMEText
+
+from cloudinit import log
+from cloudinit.handlers import INCLUSION_TYPES_MAP
+from . import addLogHandlerCLI
+
+NAME = 'make-mime'
+LOG = log.getLogger(NAME)
+EPILOG = ("Example: make-mime -a config.yaml:cloud-config "
+ "-a script.sh:x-shellscript > user-data")
+
+
+def file_content_type(text):
+ """ Return file content type by reading the first line of the input. """
+ try:
+ filename, content_type = text.split(":", 1)
+ return (open(filename, 'r'), filename, content_type.strip())
+ except ValueError:
+ raise argparse.ArgumentError(text, "Invalid value for %r" % (text))
+
+
+def get_parser(parser=None):
+ """Build or extend and arg parser for make-mime utility.
+
+ @param parser: Optional existing ArgumentParser instance representing the
+ subcommand which will be extended to support the args of this utility.
+
+ @returns: ArgumentParser with proper argument configuration.
+ """
+ if not parser:
+ parser = argparse.ArgumentParser()
+ # update the parser's doc and add an epilog to show an example
+ parser.description = __doc__
+ parser.epilog = EPILOG
+ parser.add_argument("-a", "--attach", dest="files", type=file_content_type,
+ action='append', default=[],
+ metavar="<file>:<content-type>",
+ help=("attach the given file as the specified "
+ "content-type"))
+ parser.add_argument('-l', '--list-types', action='store_true',
+ default=False,
+ help='List support cloud-init content types.')
+ parser.add_argument('-f', '--force', action='store_true',
+ default=False,
+ help='Ignore unknown content-type warnings')
+ return parser
+
+
+def get_content_types(strip_prefix=False):
+ """ Return a list of cloud-init supported content types. Optionally
+ strip out the leading 'text/' of the type if strip_prefix=True.
+ """
+ return sorted([ctype.replace("text/", "") if strip_prefix else ctype
+ for ctype in INCLUSION_TYPES_MAP.values()])
+
+
+def handle_args(name, args):
+ """Create a multi-part MIME archive for use as user-data. Optionally
+ print out the list of supported content types of cloud-init.
+
+ Also setup CLI log handlers to report to stderr since this is a development
+ utility which should be run by a human on the CLI.
+
+ @return 0 on success, 1 on failure.
+ """
+ addLogHandlerCLI(LOG, log.DEBUG if args.debug else log.WARNING)
+ if args.list_types:
+ print("\n".join(get_content_types(strip_prefix=True)))
+ return 0
+
+ sub_messages = []
+ errors = []
+ for i, (fh, filename, format_type) in enumerate(args.files):
+ contents = fh.read()
+ sub_message = MIMEText(contents, format_type, sys.getdefaultencoding())
+ sub_message.add_header('Content-Disposition',
+ 'attachment; filename="%s"' % (filename))
+ content_type = sub_message.get_content_type().lower()
+ if content_type not in get_content_types():
+ level = "WARNING" if args.force else "ERROR"
+ msg = (level + ": content type %r for attachment %s "
+ "may be incorrect!") % (content_type, i + 1)
+ sys.stderr.write(msg + '\n')
+ errors.append(msg)
+ sub_messages.append(sub_message)
+ if len(errors) and not args.force:
+ sys.stderr.write("Invalid content-types, override with --force\n")
+ return 1
+ combined_message = MIMEMultipart()
+ for msg in sub_messages:
+ combined_message.attach(msg)
+ print(combined_message)
+ return 0
+
+
+def main():
+ args = get_parser().parse_args()
+ return(handle_args(NAME, args))
+
+
+if __name__ == '__main__':
+ sys.exit(main())
+
+
+# vi: ts=4 expandtab
diff --git a/cloudinit/cmd/devel/parser.py b/cloudinit/cmd/devel/parser.py
index 99a234ce..1a3c46a4 100644
--- a/cloudinit/cmd/devel/parser.py
+++ b/cloudinit/cmd/devel/parser.py
@@ -9,6 +9,7 @@ from cloudinit.config import schema
from . import net_convert
from . import render
+from . import make_mime
def get_parser(parser=None):
@@ -25,7 +26,9 @@ def get_parser(parser=None):
(net_convert.NAME, net_convert.__doc__,
net_convert.get_parser, net_convert.handle_args),
(render.NAME, render.__doc__,
- render.get_parser, render.handle_args)
+ render.get_parser, render.handle_args),
+ (make_mime.NAME, make_mime.__doc__,
+ make_mime.get_parser, make_mime.handle_args),
]
for (subcmd, helpmsg, get_parser, handler) in subcmds:
parser = subparsers.add_parser(subcmd, help=helpmsg)
diff --git a/doc/rtd/topics/format.rst b/doc/rtd/topics/format.rst
index 1d0a7097..d03e4caf 100644
--- a/doc/rtd/topics/format.rst
+++ b/doc/rtd/topics/format.rst
@@ -23,33 +23,35 @@ Using a mime-multi part file, the user can specify more than one type of data.
For example, both a user data script and a cloud-config type could be
specified.
-Supported content-types:
+Supported content-types are listed from the cloud-init subcommand make-mime::
-- text/cloud-boothook
-- text/cloud-config
-- text/cloud-config-archive
-- text/jinja2
-- text/part-handler
-- text/upstart-job
-- text/x-include-once-url
-- text/x-include-url
-- text/x-shellscript
+ % cloud-init devel make-mime --list-types
+ cloud-boothook
+ cloud-config
+ cloud-config-archive
+ cloud-config-jsonp
+ jinja2
+ part-handler
+ upstart-job
+ x-include-once-url
+ x-include-url
+ x-shellscript
-Helper script to generate mime messages
----------------------------------------
-The cloud-init codebase includes a helper script to generate MIME multi-part
-files: `make-mime.py`_.
+Helper subcommand to generate mime messages
+-------------------------------------------
-``make-mime.py`` takes pairs of (filename, "text/" mime subtype) separated by
-a colon (e.g. ``config.yaml:cloud-config``) and emits a MIME multipart
-message to stdout. An example invocation, assuming you have your cloud config
-in ``config.yaml`` and a shell script in ``script.sh`` and want to store the
-multipart message in ``user-data``::
+The cloud-init subcommand can generate MIME multi-part files: `make-mime`_.
- ./tools/make-mime.py -a config.yaml:cloud-config -a script.sh:x-shellscript > user-data
+``make-mime`` subcommand takes pairs of (filename, "text/" mime subtype)
+separated by a colon (e.g. ``config.yaml:cloud-config``) and emits a MIME
+multipart message to stdout. An example invocation, assuming you have your
+cloud config in ``config.yaml`` and a shell script in ``script.sh`` and want
+to store the multipart message in ``user-data``::
-.. _make-mime.py: https://github.com/canonical/cloud-init/blob/master/tools/make-mime.py
+ % cloud-init devel make-mime -a config.yaml:cloud-config -a script.sh:x-shellscript > user-data
+
+.. _make-mime: https://github.com/canonical/cloud-init/blob/master/cloudinit/cmd/devel/make_mime.py
User-Data Script
diff --git a/tools/make-mime.py b/tools/make-mime.py
deleted file mode 100755
index e0022302..00000000
--- a/tools/make-mime.py
+++ /dev/null
@@ -1,62 +0,0 @@
-#!/usr/bin/env python3
-
-import argparse
-import sys
-
-from email.mime.multipart import MIMEMultipart
-from email.mime.text import MIMEText
-
-KNOWN_CONTENT_TYPES = [
- 'text/x-include-once-url',
- 'text/x-include-url',
- 'text/cloud-config-archive',
- 'text/upstart-job',
- 'text/cloud-config',
- 'text/part-handler',
- 'text/x-shellscript',
- 'text/cloud-boothook',
-]
-
-
-def file_content_type(text):
- try:
- filename, content_type = text.split(":", 1)
- return (open(filename, 'r'), filename, content_type.strip())
- except ValueError:
- raise argparse.ArgumentError(text, "Invalid value for %r" % (text))
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument("-a", "--attach",
- dest="files",
- type=file_content_type,
- action='append',
- default=[],
- required=True,
- metavar="<file>:<content-type>",
- help="attach the given file in the specified "
- "content type")
- args = parser.parse_args()
- sub_messages = []
- for i, (fh, filename, format_type) in enumerate(args.files):
- contents = fh.read()
- sub_message = MIMEText(contents, format_type, sys.getdefaultencoding())
- sub_message.add_header('Content-Disposition',
- 'attachment; filename="%s"' % (filename))
- content_type = sub_message.get_content_type().lower()
- if content_type not in KNOWN_CONTENT_TYPES:
- sys.stderr.write(("WARNING: content type %r for attachment %s "
- "may be incorrect!\n") % (content_type, i + 1))
- sub_messages.append(sub_message)
- combined_message = MIMEMultipart()
- for msg in sub_messages:
- combined_message.attach(msg)
- print(combined_message)
- return 0
-
-
-if __name__ == '__main__':
- sys.exit(main())
-
-# vi: ts=4 expandtab