Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
d8488b1
first commit for first cloud-init PR
beantaxi Jan 1, 2021
bd88965
cleaned up log statements, in part to fix travis
beantaxi Jan 1, 2021
ccb2435
flake8 fixes part 2 (of 2?)
beantaxi Jan 1, 2021
0e4d9de
pylint fixes
beantaxi Jan 1, 2021
6523f80
Merge branch 'master' into shell-script-handlers-by-freq
beantaxi Jan 5, 2021
445ba33
Merge branch 'master' into shell-script-handlers-by-freq
beantaxi Jan 9, 2021
30fd096
ok lets do this
beantaxi Feb 28, 2021
b57f5a6
Merge remote-tracking branch 'mother/master' into shell-script-handle…
beantaxi Feb 28, 2021
0d9acbe
First commit of the changes suggested/requested by smoser and raharper
beantaxi Feb 28, 2021
d511f7d
Change scripts_dir initialization to match shell_script.py to fix cir…
beantaxi Feb 28, 2021
56ea2cc
fixed typos
beantaxi Feb 28, 2021
45e1320
more fixes
beantaxi Feb 28, 2021
3439b25
flake8 fixes
beantaxi Feb 28, 2021
888e70b
pytest fixes
beantaxi Feb 28, 2021
a834b2e
pytest fixes
beantaxi Feb 28, 2021
87fddb9
Changed to get_cpath() and added some comments
beantaxi Mar 4, 2021
f33429e
Common base class for the part handlers. Required # pylint:disable; m…
beantaxi Mar 4, 2021
b7f6518
using prefixes for list_types now
beantaxi Mar 4, 2021
3b98863
Merge remote-tracking branch 'mother/master' into shell-script-handle…
beantaxi Mar 4, 2021
ab9d238
Changes suggested by TheRealFalcon
beantaxi Mar 5, 2021
3251001
Fixes for flake8; prematurely pushed the last one
beantaxi Mar 5, 2021
d8cd589
Removed subclasses - now there's just one class with frequency-specif…
beantaxi Mar 5, 2021
45cfa7c
start of integration test ... plus cloudinit-tester scripts for fun
beantaxi Apr 8, 2021
bee79ad
Still trying to get integration test working
chrislalos Jan 1, 2022
1e58a42
Trying to get integration test working ...
chrislalos Jan 1, 2022
0a1bdc8
dunno
Jan 1, 2022
1db9696
merge
chrislalos Jan 1, 2022
400ac42
added print statements
chrislalos Jan 1, 2022
0671d08
Added print() statements for debugging
chrislalos Jan 1, 2022
74bf2fc
second pass at incorporating falcons changes
chrislalos Jan 1, 2022
b56c2f8
more logging
chrislalos Jan 1, 2022
69bf964
dunno
chrislalos Jan 1, 2022
e2f0b9a
merge
chrislalos Jan 1, 2022
09a3006
added more tests
chrislalos Jan 1, 2022
9741cc0
merge
chrislalos Jan 1, 2022
2d32b53
moved Docker test stuff to cloudinit/cmd/devel
chrislalos Jan 3, 2022
56db4bc
Merge branch 'shell-script-handlers-by-freq' of https://github.com/be…
chrislalos Jan 3, 2022
776a51d
removed Dockerfiles from this branch - they will be added to another …
chrislalos Jan 3, 2022
8d8229f
merged in upstream/main
chrislalos Jan 3, 2022
ea2a262
passes tox -e flake8
chrislalos Jan 21, 2022
325cb00
passes tox -e format
chrislalos Jan 21, 2022
854a871
Incorporated changes from James Falcons suggestions on 1/11/22
chrislalos Jan 21, 2022
e5db3cd
final changes to pass tox -e format
chrislalos Jan 21, 2022
0fb03aa
Merge remote-tracking branch 'upstream/main' into shell-script-handle…
chrislalos Jan 21, 2022
8aed6f2
Fixed mistake I made in changing test_builtin_handlers that was break…
chrislalos Jan 21, 2022
c5f677f
Take 2: Fixed mistake I made in changing test_builtin_handlers that w…
chrislalos Jan 21, 2022
7077f3d
flake8 + black
chrislalos Jan 21, 2022
d072fcd
one more commit; tbh this has changes I thought were committed
chrislalos Jan 23, 2022
e3645ac
print() removal
chrislalos Jan 26, 2022
29442ec
falcojrs more significant suggestions
chrislalos Jan 26, 2022
e1cf569
fixed flake8 pylint and black tests
chrislalos Jan 26, 2022
c0fb855
slight change to test
chrislalos Jan 26, 2022
4a6b25e
added trailing newline to .gitignore
chrislalos Jan 26, 2022
0e8f81b
marking my int tests with @pytest.mark.ci
chrislalos Jan 26, 2022
bd3b7b8
renamed test file
chrislalos Jan 26, 2022
383731f
changes for falconjrs comments
chrislalos Jan 26, 2022
7ea8dd4
Fixed flake8 black & isort failures
chrislalos Jan 26, 2022
eae159d
Consolidate 3 handler classes into 1 as per falconjr
chrislalos Jan 26, 2022
e5f79d6
fixed test failures (missed pathMap -> path_map)
chrislalos Jan 26, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 29 additions & 22 deletions cloudinit/cmd/devel/make_mime.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,28 @@
)


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:
Expand Down Expand Up @@ -97,29 +119,14 @@ def handle_args(name, args):
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:
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")
return 1
combined_message = MIMEMultipart()
for msg in sub_messages:
combined_message.attach(msg)
if not args.force:
return 1
print(combined_message)
return 0

Expand Down
7 changes: 7 additions & 0 deletions cloudinit/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@
"#cloud-config-archive": "text/cloud-config-archive",
"#cloud-config-jsonp": "text/cloud-config-jsonp",
"## template: jinja": "text/jinja2",
# Note: for the next 3 entries, the prefix doesn't matter because these
# are for types that can only be used as part of a MIME message. However,
# including these entries supresses warnings during `cloudinit devel
# make-mime`, which otherwise would require `--force`.
"text/x-shellscript-per-boot": "text/x-shellscript-per-boot",
"text/x-shellscript-per-instance": "text/x-shellscript-per-instance",
"text/x-shellscript-per-once": "text/x-shellscript-per-once",
}

# Sorted longest first
Expand Down
62 changes: 62 additions & 0 deletions cloudinit/handlers/shell_script_by_frequency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import os

from cloudinit import log, util
from cloudinit.handlers import Handler
from cloudinit.settings import PER_ALWAYS, PER_INSTANCE, PER_ONCE

LOG = log.getLogger(__name__)

# cloudinit/settings.py defines PER_*** frequency constants. It makes sense to
# use them here, instead of hardcodes, and map them to the 'per-***' frequency-
# specific folders in /v/l/c/scripts. It might make sense to expose this at a
# higher level or in a more general module -- eg maybe in cloudinit/settings.py
# itself -- but for now it's here.
path_map = {
PER_ALWAYS: "per-boot",
PER_INSTANCE: "per-instance",
PER_ONCE: "per-once",
}


def get_mime_type_by_frequency(freq):
mime_type = f"text/x-shellscript-{path_map[freq]}"
return mime_type


def get_script_folder_by_frequency(freq, scripts_dir):
"""Return the frequency-specific subfolder for a given frequency constant
and parent folder."""
freqPath = path_map[freq]
folder = os.path.join(scripts_dir, freqPath)
return folder


def write_script_by_frequency(script_path, payload, frequency, scripts_dir):
"""Given a filename, a payload, a frequency, and a scripts folder, write
the payload to the correct frequency-specific path"""
filename = os.path.basename(script_path)
filename = util.clean_filename(filename)
folder = get_script_folder_by_frequency(frequency, scripts_dir)
path = os.path.join(folder, filename)
payload = util.dos2unix(payload)
util.write_file(path, payload, 0o700)


class ShellScriptByFreqPartHandler(Handler):
"""Common base class for the frequency-specific script handlers."""

def __init__(self, script_frequency, paths, **_kwargs):
Handler.__init__(self, PER_ALWAYS)
self.prefixes = [get_mime_type_by_frequency(script_frequency)]
self.script_frequency = script_frequency
self.scripts_dir = paths.get_cpath("scripts")
if "script_path" in _kwargs:
self.scripts_dir = paths.get_cpath(_kwargs["script_path"])

def handle_part(self, data, ctype, script_path, payload, frequency):
if script_path is not None:
filename = os.path.basename(script_path)
filename = util.clean_filename(filename)
write_script_by_frequency(
script_path, payload, self.script_frequency, self.scripts_dir
)
7 changes: 7 additions & 0 deletions cloudinit/stages.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@
from cloudinit.handlers.cloud_config import CloudConfigPartHandler
from cloudinit.handlers.jinja_template import JinjaTemplatePartHandler
from cloudinit.handlers.shell_script import ShellScriptPartHandler
from cloudinit.handlers.shell_script_by_frequency import (
ShellScriptByFreqPartHandler,
)
from cloudinit.handlers.upstart_job import UpstartJobPartHandler
from cloudinit.net import cmdline
from cloudinit.reporting import events
from cloudinit.settings import (
CLOUD_CONFIG,
FREQUENCIES,
PER_ALWAYS,
PER_INSTANCE,
PER_ONCE,
RUN_CLOUD_CONFIG,
Expand Down Expand Up @@ -519,6 +523,9 @@ def _default_handlers(self, opts=None):
def_handlers = [
cloudconfig_handler,
shellscript_handler,
ShellScriptByFreqPartHandler(PER_ALWAYS, **opts),
ShellScriptByFreqPartHandler(PER_INSTANCE, **opts),
ShellScriptByFreqPartHandler(PER_ONCE, **opts),
BootHookPartHandler(**opts),
UpstartJobPartHandler(**opts),
]
Expand Down
48 changes: 48 additions & 0 deletions tests/integration_tests/test_shell_script_by_frequency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""Integration tests for various handlers."""

from io import StringIO

import pytest

from cloudinit.cmd.devel.make_mime import create_mime_message
from tests.integration_tests.instances import IntegrationInstance

PER_FREQ_TEMPLATE = """\
#!/bin/bash
touch /tmp/test_per_freq_{}
"""

PER_ALWAYS_FILE = StringIO(PER_FREQ_TEMPLATE.format("always"))
PER_INSTANCE_FILE = StringIO(PER_FREQ_TEMPLATE.format("instance"))
PER_ONCE_FILE = StringIO(PER_FREQ_TEMPLATE.format("once"))

FILES = [
(PER_ALWAYS_FILE, "always.sh", "x-shellscript-per-boot"),
(PER_INSTANCE_FILE, "instance.sh", "x-shellscript-per-instance"),
(PER_ONCE_FILE, "once.sh", "x-shellscript-per-once"),
]

USER_DATA, errors = create_mime_message(FILES)


@pytest.mark.ci
Comment thread
chrislalos marked this conversation as resolved.
@pytest.mark.user_data(USER_DATA)
def test_per_freq(client: IntegrationInstance):
# Sanity test for scripts folder
cmd = "test -d /var/lib/cloud/scripts"
assert client.execute(cmd).ok
# Test per-boot
cmd = "test -f /var/lib/cloud/scripts/per-boot/always.sh"
assert client.execute(cmd).ok
cmd = "test -f /tmp/test_per_freq_always"
assert client.execute(cmd).ok
# Test per-instance
cmd = "test -f /var/lib/cloud/scripts/per-instance/instance.sh"
assert client.execute(cmd).ok
cmd = "test -f /tmp/test_per_freq_instance"
assert client.execute(cmd).ok
# Test per-once
cmd = "test -f /var/lib/cloud/scripts/per-once/once.sh"
assert client.execute(cmd).ok
cmd = "test -f /tmp/test_per_freq_once"
assert client.execute(cmd).ok
25 changes: 24 additions & 1 deletion tests/unittests/test_builtin_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,20 @@
import pytest

from cloudinit import handlers, helpers, subp, util
from cloudinit.cmd.devel import read_cfg_paths
from cloudinit.handlers.cloud_config import CloudConfigPartHandler
from cloudinit.handlers.jinja_template import (
JinjaTemplatePartHandler,
convert_jinja_instance_data,
render_jinja_payload,
)
from cloudinit.handlers.shell_script import ShellScriptPartHandler
from cloudinit.handlers.shell_script_by_frequency import (
get_script_folder_by_frequency,
path_map,
)
from cloudinit.handlers.upstart_job import UpstartJobPartHandler
from cloudinit.settings import PER_ALWAYS, PER_INSTANCE
from cloudinit.settings import PER_ALWAYS, PER_INSTANCE, PER_ONCE
from tests.unittests.helpers import (
CiTestCase,
FilesystemMockingTestCase,
Expand Down Expand Up @@ -473,4 +478,22 @@ def test_render_jinja_payload_replaces_missing_variables_and_warns(self):
self.assertIn(expected_log, self.logs.getvalue())


class TestShellScriptByFrequencyHandlers:
def do_test_frequency(self, frequency):
ci_paths = read_cfg_paths()
scripts_dir = ci_paths.get_cpath("scripts")
testFolder = os.path.join(scripts_dir, path_map[frequency])
folder = get_script_folder_by_frequency(frequency, scripts_dir)
assert testFolder == folder

def test_get_script_folder_per_boot(self):
self.do_test_frequency(PER_ALWAYS)

def test_get_script_folder_per_instance(self):
self.do_test_frequency(PER_INSTANCE)

def test_get_script_folder_per_once(self):
self.do_test_frequency(PER_ONCE)


# vi: ts=4 expandtab
1 change: 1 addition & 0 deletions tools/.github-cla-signers
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ andrewbogott
andrewlukoshko
antonyc
aswinrajamannar
beantaxi
beezly
bipinbachhao
BirknerAlex
Expand Down