1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
|
#!/usr/bin/env python3
# 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.cmd.devel import addLogHandlerCLI
from cloudinit.handlers import INCLUSION_TYPES_MAP
NAME = "make-mime"
LOG = log.getLogger(NAME)
EPILOG = (
"Example: make-mime -a config.yaml:cloud-config "
"-a script.sh:x-shellscript > user-data"
)
def create_mime_message(files):
sub_messages = []
errors = []
for i, (fh, filename, format_type) in enumerate(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():
msg = (
"content type %r for attachment %s " "may be incorrect!"
) % (content_type, i + 1)
errors.append(msg)
sub_messages.append(sub_message)
combined_message = MIMEMultipart()
for msg in sub_messages:
combined_message.attach(msg)
return (combined_message, errors)
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 as e:
raise argparse.ArgumentError(
text, "Invalid value for %r" % (text)
) from e
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
combined_message, errors = create_mime_message(args.files)
if errors:
level = "WARNING" if args.force else "ERROR"
for error in errors:
sys.stderr.write(f"{level}: {error}\n")
sys.stderr.write("Invalid content-types, override with --force\n")
if not args.force:
return 1
print(combined_message)
return 0
def main():
args = get_parser().parse_args()
return handle_args(NAME, args)
if __name__ == "__main__":
sys.exit(main())
|