From aa0a7aebed4a0b14ec35967e4676cee407bd0a8b Mon Sep 17 00:00:00 2001 From: Faiq Date: Thu, 30 Jan 2025 14:59:35 -0500 Subject: [PATCH] feat: adds custom datasource for ubuntu 22.04 (#1255) --- .github/workflows/lint.yml | 2 + .../files/etc/cloud/cloud.cfg.d/90_dpkg.cfg | 1 + .../sources/DataSourceEc2Kubernetes.py | 135 ++++++++++++++++++ ansible/roles/providers/tasks/aws.yml | 18 +++ ansible/roles/providers/tasks/main.yml | 2 +- 5 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 ansible/roles/providers/files/etc/cloud/cloud.cfg.d/90_dpkg.cfg create mode 100644 ansible/roles/providers/files/usr/lib/python3/dist-packages/cloudinit/sources/DataSourceEc2Kubernetes.py diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 895a53630..4ce2ee1d7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -45,6 +45,8 @@ jobs: # we use terraform to provision *test* infrastructure that is deleted VALIDATE_TERRAFORM_TFLINT: false VALIDATE_ALL_CODEBASE: ${{ github.event_name == 'pull_request' }} + VALIDATE_PYTHON_ISORT: false + VALIDATE_PYTHON: false golangci-lint: runs-on: ubuntu-22.04 steps: diff --git a/ansible/roles/providers/files/etc/cloud/cloud.cfg.d/90_dpkg.cfg b/ansible/roles/providers/files/etc/cloud/cloud.cfg.d/90_dpkg.cfg new file mode 100644 index 000000000..3280cde4a --- /dev/null +++ b/ansible/roles/providers/files/etc/cloud/cloud.cfg.d/90_dpkg.cfg @@ -0,0 +1 @@ +datasource_list: [ Ec2Kubernetes ] diff --git a/ansible/roles/providers/files/usr/lib/python3/dist-packages/cloudinit/sources/DataSourceEc2Kubernetes.py b/ansible/roles/providers/files/usr/lib/python3/dist-packages/cloudinit/sources/DataSourceEc2Kubernetes.py new file mode 100644 index 000000000..b0d311bc0 --- /dev/null +++ b/ansible/roles/providers/files/usr/lib/python3/dist-packages/cloudinit/sources/DataSourceEc2Kubernetes.py @@ -0,0 +1,135 @@ +# This file is part of cloud-init. See LICENSE file for license information. + +import logging +import os + +from cloudinit import handlers, helpers, sources, util +from cloudinit.handlers.boot_hook import BootHookPartHandler +from cloudinit.handlers.jinja_template import JinjaTemplatePartHandler +from cloudinit.handlers.cloud_config import CloudConfigPartHandler +from cloudinit.handlers.shell_script import ShellScriptPartHandler +from cloudinit.settings import PER_ALWAYS +from cloudinit.sources import DataSourceEc2 +from cloudinit.handlers.jinja_template import ( + render_jinja_payload_from_file, +) + +LOG = logging.getLogger(__name__) + + +class BootHookPartHandlerModified(BootHookPartHandler): + def __init__(self, paths, datasource, **_kwargs): + super().__init__(paths, datasource) + self.output = None + + def handle_part(self, data, ctype, filename, payload, frequency): + """Save the output of the script""" + if ctype in handlers.CONTENT_SIGNALS: + return + + # modify the payload to not restart cloud-init + # TODO: work with upstream to remove this restart + restart_index = payload.find("systemctl restart cloud-init") + if -1 != restart_index: + LOG.warning( + "Kubernetes is trying to restart cloud-init. This is no " + "longer necessary and is temporarily circumvented by " + "cloud-init. This will be a hard error in the future." + ) + payload = payload[:restart_index] + "#" + payload[restart_index:] + super().handle_part(data, ctype, filename, payload, frequency) + + +class DataSourceEc2Kubernetes(DataSourceEc2.DataSourceEc2): + def _get_data(self): + super()._get_data() + + # Get initial user-data + user_data_msg = self.get_userdata(True) + LOG.info("User-data received:[\n%s]", user_data_msg) + self.persist_instance_data() + + # This is required to get path of the instance + self.paths.datasource = self + + # Boilerplate handler setup + c_handlers = helpers.ContentHandlers() + cloudconfig_handler = CloudConfigPartHandler(self.paths) + shellscript_handler = ShellScriptPartHandler(self.paths) + boothook_handler = BootHookPartHandlerModified(self.paths, self) + jinja_handler = JinjaTemplatePartHandler( + self.paths, + sub_handlers=[ + cloudconfig_handler, + shellscript_handler, + boothook_handler, + ], + ) + c_handlers.register(boothook_handler, overwrite=False) + c_handlers.register(jinja_handler, overwrite=False) + LOG.debug("Registered handlers %s and %s", boothook_handler, jinja_handler) + + # Walk the user data MIME + handlers.walk( + user_data_msg, + handlers.walker_callback, + data={ + "handlers": c_handlers, + "handlerdir": self.paths.get_ipath("handlers"), + "data": None, + "frequency": PER_ALWAYS, + "handlercount": 0, + "excluded": [], + }, + ) + LOG.info("User-data before update:[\n%s]", self.userdata_raw) + secret_userdata = "/etc/secret-userdata.txt" + # Get the boothook output, save it as user-data + # TODO: work with upstream to put this somewhere more sensible like: + # /var/lib/cloud/instances/{{v1.instance_id}}/ec2-kubernetes-userdata.txt + userdata_raw = util.load_text_file(secret_userdata) + LOG.info("Secret user-data:[\n%s]", self.userdata_raw) + + uid = os.getuid() + redacted_data_fn = self.paths.get_runpath("instance_data") + if uid == 0: + instance_data_fn = self.paths.get_runpath("instance_data_sensitive") + if not os.path.exists(instance_data_fn): + LOG.warning( + "Missing root-readable %s. Using redacted %s instead.", + instance_data_fn, + redacted_data_fn, + ) + instance_data_fn = redacted_data_fn + else: + instance_data_fn = redacted_data_fn + rendered_payload = render_jinja_payload_from_file( + payload=userdata_raw, + payload_fn=secret_userdata, + instance_data_file=instance_data_fn, + ) + util.write_file( + "/etc/cloud/cloud.cfg.d/99_kubeadm_bootstrap.cfg", rendered_payload + ) + self.userdata_raw = rendered_payload + return True + + +class DataSourceEc2KubernetesLocal(DataSourceEc2Kubernetes): + def _get_data(self): + return super(DataSourceEc2KubernetesLocal, self).get_data() + + +# Used to match classes to dependencies +datasources = [ + ( + DataSourceEc2KubernetesLocal, + (sources.DEP_FILESYSTEM,), + ), + (DataSourceEc2Kubernetes, (sources.DEP_FILESYSTEM, sources.DEP_NETWORK)), +] + + +# Return a list of data sources that match this set of dependencies +def get_datasource_list(depends): + return sources.list_from_depends(depends, datasources) diff --git a/ansible/roles/providers/tasks/aws.yml b/ansible/roles/providers/tasks/aws.yml index 3fc054939..43edbd4ff 100644 --- a/ansible/roles/providers/tasks/aws.yml +++ b/ansible/roles/providers/tasks/aws.yml @@ -112,3 +112,21 @@ state: started enabled: yes when: ansible_distribution == "Ubuntu" + +- name: Create cloud-init custom data source list + ansible.builtin.copy: + src: files/etc/cloud/cloud.cfg.d/90_dpkg.cfg + dest: /etc/cloud/cloud.cfg.d/90_dpkg.cfg + owner: root + group: root + mode: "0644" + when: ansible_distribution == "Ubuntu" and ansible_distribution_version is version('22.04', '>=') + +- name: Create custom cloud-init data source + ansible.builtin.copy: + src: usr/lib/python3/dist-packages/cloudinit/sources/DataSourceEc2Kubernetes.py + dest: /usr/lib/python3/dist-packages/cloudinit/sources/DataSourceEc2Kubernetes.py + owner: root + group: root + mode: "0644" + when: ansible_distribution == "Ubuntu" and ansible_distribution_version is version('22.04', '>=') diff --git a/ansible/roles/providers/tasks/main.yml b/ansible/roles/providers/tasks/main.yml index 448cf3640..d70f7e89e 100644 --- a/ansible/roles/providers/tasks/main.yml +++ b/ansible/roles/providers/tasks/main.yml @@ -32,6 +32,6 @@ when: packer_builder_type and packer_builder_type != "" - include_tasks: vmware-redhat.yaml - when: + when: - packer_builder_type is search('vmware') or packer_builder_type is search('vsphere') - ansible_os_family == "RedHat"