From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 15FF498733 for ; Thu, 11 May 2023 13:13:16 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id F012720128 for ; Thu, 11 May 2023 13:13:15 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS for ; Thu, 11 May 2023 13:13:14 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 93BD74196E for ; Thu, 11 May 2023 13:13:14 +0200 (CEST) From: Leo Nunner To: pve-devel@lists.proxmox.com Date: Thu, 11 May 2023 13:12:46 +0200 Message-Id: <20230511111249.171748-3-l.nunner@proxmox.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20230511111249.171748-1-l.nunner@proxmox.com> References: <20230511111249.171748-1-l.nunner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.127 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record T_SCC_BODY_TEXT_LINE -0.01 - Subject: [pve-devel] [PATCH RFC container 2/3] cloudinit: basic implementation X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 11 May 2023 11:13:16 -0000 The code to generate the actual configuration works pretty much the same as with the VM system. We generate an instance ID by hashing the user configuration, causing cloud-init to run every time said configuration changes. Instead of creating a config drive, we write files directly into the volume of the container. We create a folder at '/var/lib/cloud/seed/nocloud-net' and write the files 'user-data', 'vendor-data' and 'meta-data'. Cloud-init looks at the instance ID inside 'meta-data' to decide whether it should run (again) or not. Custom scripts need to be located inside the snippets directory, and overwrite the default generated configuration file. Signed-off-by: Leo Nunner --- src/PVE/LXC.pm | 1 + src/PVE/LXC/Cloudinit.pm | 114 ++++++++++++++++++++++++++++++++++++++ src/PVE/LXC/Makefile | 1 + src/lxc-pve-prestart-hook | 5 ++ 4 files changed, 121 insertions(+) create mode 100644 src/PVE/LXC/Cloudinit.pm diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm index d138161..ea01fbb 100644 --- a/src/PVE/LXC.pm +++ b/src/PVE/LXC.pm @@ -39,6 +39,7 @@ use PVE::Tools qw( use PVE::Syscall qw(:fsmount); use PVE::LXC::CGroup; +use PVE::LXC::Cloudinit; use PVE::LXC::Config; use PVE::LXC::Monitor; use PVE::LXC::Tools; diff --git a/src/PVE/LXC/Cloudinit.pm b/src/PVE/LXC/Cloudinit.pm new file mode 100644 index 0000000..e4bc67d --- /dev/null +++ b/src/PVE/LXC/Cloudinit.pm @@ -0,0 +1,114 @@ +package PVE::LXC::Cloudinit; + +use strict; +use warnings; + +use Digest::SHA; +use File::Path; + +use PVE::LXC; + +sub gen_cloudinit_metadata { + my ($user) = @_; + + my $uuid_str = Digest::SHA::sha1_hex($user); + return cloudinit_metadata($uuid_str); +} + +sub cloudinit_metadata { + my ($uuid) = @_; + my $raw = ""; + + $raw .= "instance-id: $uuid\n"; + + return $raw; +} + +sub cloudinit_userdata { + my ($conf) = @_; + + my $content = "#cloud-config\n"; + + my $username = $conf->{ciuser}; + my $password = $conf->{cipassword}; + + $content .= "user: $username\n" if defined($username); + $content .= "password: $password\n" if defined($password); + + if (defined(my $keys = $conf->{sshkeys})) { + $keys = URI::Escape::uri_unescape($keys); + $keys = [map { my $key = $_; chomp $key; $key } split(/\n/, $keys)]; + $keys = [grep { /\S/ } @$keys]; + $content .= "ssh_authorized_keys:\n"; + foreach my $k (@$keys) { + $content .= " - $k\n"; + } + } + $content .= "chpasswd:\n"; + $content .= " expire: False\n"; + + if (!defined($username) || $username ne 'root') { + $content .= "users:\n"; + $content .= " - default\n"; + } + + $content .= "package_upgrade: true\n" if $conf->{ciupdate}; + + return $content; +} + +sub read_cloudinit_snippets_file { + my ($storage_conf, $volid) = @_; + + my ($full_path, undef, $type) = PVE::Storage::path($storage_conf, $volid); + die "$volid is not in the snippets directory\n" if $type ne 'snippets'; + return PVE::Tools::file_get_contents($full_path, 1 * 1024 * 1024); +} + +sub read_custom_cloudinit_files { + my ($conf) = @_; + + my $cloudinit_conf = $conf->{cicustom}; + my $files = $cloudinit_conf ? PVE::JSONSchema::parse_property_string('pve-pct-cicustom', $cloudinit_conf) : {}; + + my $user_volid = $files->{user}; + my $vendor_volid = $files->{vendor}; + + my $storage_conf = PVE::Storage::config(); + + my $user_data; + if ($user_volid) { + $user_data = read_cloudinit_snippets_file($storage_conf, $user_volid); + } + + my $vendor_data; + if ($vendor_volid) { + $user_data = read_cloudinit_snippets_file($storage_conf, $vendor_volid); + } + + return ($user_data, $vendor_data); +} + +sub create_cloudinit_files { + my ($conf, $setup) = @_; + + my $cloudinit_dir = "/var/lib/cloud/seed/nocloud-net"; + + my ($user_data, $vendor_data) = read_custom_cloudinit_files($conf); + $user_data = cloudinit_userdata($conf) if !defined($user_data); + $vendor_data = '' if !defined($vendor_data); + + my $meta_data = gen_cloudinit_metadata($user_data); + + $setup->protected_call(sub { + my $plugin = $setup->{plugin}; + + $plugin->ct_make_path($cloudinit_dir); + + $plugin->ct_file_set_contents("$cloudinit_dir/user-data", $user_data); + $plugin->ct_file_set_contents("$cloudinit_dir/vendor-data", $vendor_data); + $plugin->ct_file_set_contents("$cloudinit_dir/meta-data", $meta_data); + }); +} + +1; diff --git a/src/PVE/LXC/Makefile b/src/PVE/LXC/Makefile index a190260..5d595ba 100644 --- a/src/PVE/LXC/Makefile +++ b/src/PVE/LXC/Makefile @@ -1,5 +1,6 @@ SOURCES= \ CGroup.pm \ + Cloudinit.pm \ Command.pm \ Config.pm \ Create.pm \ diff --git a/src/lxc-pve-prestart-hook b/src/lxc-pve-prestart-hook index 3bdf7e4..e5932d2 100755 --- a/src/lxc-pve-prestart-hook +++ b/src/lxc-pve-prestart-hook @@ -12,6 +12,7 @@ use POSIX; use PVE::CGroup; use PVE::Cluster; use PVE::LXC::Config; +use PVE::LXC::Cloudinit; use PVE::LXC::Setup; use PVE::LXC::Tools; use PVE::LXC; @@ -140,6 +141,10 @@ PVE::LXC::Tools::lxc_hook('pre-start', 'lxc', sub { my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir); $lxc_setup->pre_start_hook(); + if ($conf->{cienable}) { + PVE::LXC::Cloudinit::create_cloudinit_files($conf, $lxc_setup) + } + if (PVE::CGroup::cgroup_mode() == 2) { if (!$lxc_setup->unified_cgroupv2_support()) { log_warn($vmid, "old systemd (< v232) detected, container won't run in a pure cgroupv2" -- 2.30.2