public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pve-devel] [PATCH container/manager/docs] Cloudinit support for LXC
@ 2023-06-02 11:57 Leo Nunner
  2023-06-02 11:57 ` [pve-devel] [PATCH container 1/4] cloudinit: introduce config parameters Leo Nunner
                   ` (7 more replies)
  0 siblings, 8 replies; 9+ messages in thread
From: Leo Nunner @ 2023-06-02 11:57 UTC (permalink / raw)
  To: pve-devel

This series introduces basic cloudinit support for containers. All in
all, it works quite similar to VMs, with the caveat that we only allow
network configuration through the alrady existing systems, and not via
cloud-init.

pve-container:

Leo Nunner (4):
  cloudinit: introduce config parameters
  cloudinit: basic implementation
  cloudinit: add dump command to pct
  cloudinit: add function dumping options for docs

 src/PVE/API2/LXC.pm        |  36 +++++++++++
 src/PVE/API2/LXC/Config.pm |   7 ++-
 src/PVE/CLI/pct.pm         |   4 ++
 src/PVE/LXC.pm             |   1 +
 src/PVE/LXC/Cloudinit.pm   | 125 +++++++++++++++++++++++++++++++++++++
 src/PVE/LXC/Config.pm      |  64 +++++++++++++++++++
 src/PVE/LXC/Makefile       |   1 +
 src/lxc-pve-prestart-hook  |   5 ++
 8 files changed, 242 insertions(+), 1 deletion(-)
 create mode 100644 src/PVE/LXC/Cloudinit.pm

pve-manager:

Leo Nunner (2):
  cloudinit: rename qemu cloudinit panel
  cloudinit: introduce panel for LXCs

 www/manager6/Makefile          |   1 +
 www/manager6/lxc/CloudInit.js  | 237 +++++++++++++++++++++++++++++++++
 www/manager6/lxc/Config.js     |   6 +
 www/manager6/qemu/CloudInit.js |   4 +-
 www/manager6/qemu/Config.js    |   2 +-
 5 files changed, 247 insertions(+), 3 deletions(-)
 create mode 100644 www/manager6/lxc/CloudInit.js

pve-docs:

Leo Nunner (2):
  pct: add script to generate cloudinit options
  pct: document cloudinit for LXC

 Makefile                   |   1 +
 gen-pct-cloud-init-opts.pl |  16 ++++++
 pct-cloud-init.adoc        | 114 +++++++++++++++++++++++++++++++++++++
 pct.adoc                   |   4 ++
 4 files changed, 135 insertions(+)
 create mode 100755 gen-pct-cloud-init-opts.pl
 create mode 100644 pct-cloud-init.adoc

-- 
2.30.2





^ permalink raw reply	[flat|nested] 9+ messages in thread

* [pve-devel] [PATCH container 1/4] cloudinit: introduce config parameters
  2023-06-02 11:57 [pve-devel] [PATCH container/manager/docs] Cloudinit support for LXC Leo Nunner
@ 2023-06-02 11:57 ` Leo Nunner
  2023-06-02 11:57 ` [pve-devel] [PATCH container 2/4] cloudinit: basic implementation Leo Nunner
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Leo Nunner @ 2023-06-02 11:57 UTC (permalink / raw)
  To: pve-devel

Introduce configuration parameters for cloud-init. Like with VMs, it's
possible to specify:
    - user
    - password
    - ssh keys
    - enable/disable updates on first boot

It's also possible to pass through custom config files for the user and
vendor settings. We don't allow configuring the network through
cloud-init, since it will clash with whatever configuration we already
did for the container.

Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
---
 src/PVE/API2/LXC.pm        |  3 ++
 src/PVE/API2/LXC/Config.pm |  7 ++++-
 src/PVE/LXC/Config.pm      | 61 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 70 insertions(+), 1 deletion(-)

diff --git a/src/PVE/API2/LXC.pm b/src/PVE/API2/LXC.pm
index 50c9eaf..e585509 100644
--- a/src/PVE/API2/LXC.pm
+++ b/src/PVE/API2/LXC.pm
@@ -2492,6 +2492,9 @@ __PACKAGE__->register_method({
 
 	my $pending_delete_hash = PVE::LXC::Config->parse_pending_delete($conf->{pending}->{delete});
 
+	$conf->{cipassword} = '**********' if defined($conf->{cipassword});
+	$conf->{pending}->{cipassword} = '********** ' if defined($conf->{pending}->{cipassword});
+
 	return PVE::GuestHelpers::config_with_pending_array($conf, $pending_delete_hash);
     }});
 
diff --git a/src/PVE/API2/LXC/Config.pm b/src/PVE/API2/LXC/Config.pm
index e6c0980..0ff4115 100644
--- a/src/PVE/API2/LXC/Config.pm
+++ b/src/PVE/API2/LXC/Config.pm
@@ -79,7 +79,7 @@ __PACKAGE__->register_method({
 	} else {
 	    $conf = PVE::LXC::Config->load_current_config($param->{vmid}, $param->{current});
 	}
-
+	$conf->{cipassword} = '**********' if $conf->{cipassword};
 	return $conf;
     }});
 
@@ -148,6 +148,11 @@ __PACKAGE__->register_method({
 	$param->{cpuunits} = PVE::CGroup::clamp_cpu_shares($param->{cpuunits})
 	    if defined($param->{cpuunits}); # clamp value depending on cgroup version
 
+	if (defined(my $cipassword = $param->{cipassword})) {
+	    $param->{cipassword} = PVE::Tools::encrypt_pw($cipassword)
+		if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
+	}
+
 	my $code = sub {
 
 	    my $conf = PVE::LXC::Config->load_config($vmid);
diff --git a/src/PVE/LXC/Config.pm b/src/PVE/LXC/Config.pm
index ac9db94..f8bfb95 100644
--- a/src/PVE/LXC/Config.pm
+++ b/src/PVE/LXC/Config.pm
@@ -442,6 +442,63 @@ my $features_desc = {
     },
 };
 
+my $cicustom_fmt = {
+    user => {
+	type => 'string',
+	optional => 1,
+	description => 'To pass a custom file containing all user data to the container via cloud-init.',
+	format => 'pve-volume-id',
+	format_description => 'volume',
+    },
+    vendor => {
+	type => 'string',
+	optional => 1,
+	description => 'To pass a custom file containing all vendor data to the container via cloud-init.',
+	format => 'pve-volume-id',
+	format_description => 'volume',
+    },
+};
+PVE::JSONSchema::register_format('pve-pct-cicustom', $cicustom_fmt);
+
+my $confdesc_cloudinit = {
+    cienable => {
+	optional => 1,
+	type => 'boolean',
+	description => "cloud-init: provide cloud-init configuration to container.",
+    },
+    ciuser => {
+	optional => 1,
+	type => 'string',
+	description => "cloud-init: User name to change ssh keys and password for instead of the"
+	    ." image's configured default user.",
+    },
+    cipassword => {
+	optional => 1,
+	type => 'string',
+	description => 'cloud-init: Password to assign the user. Using this is generally not'
+	    .' recommended. Use ssh keys instead. Also note that older cloud-init versions do not'
+	    .' support hashed passwords.',
+    },
+    ciupgrade => {
+	optional => 1,
+	type => 'boolean',
+	description => 'cloud-init: do an automatic package update on boot.'
+    },
+    cicustom => {
+	optional => 1,
+	type => 'string',
+	description => 'cloud-init: Specify custom files to replace the automatically generated'
+	    .' ones at start.',
+	format => 'pve-pct-cicustom',
+    },
+    sshkeys => {
+	optional => 1,
+	type => 'string',
+	format => 'urlencoded',
+	description => "cloud-init: Setup public SSH keys (one key per line, OpenSSH format).",
+    },
+};
+
 my $confdesc = {
     lock => {
 	optional => 1,
@@ -614,6 +671,10 @@ my $confdesc = {
     },
 };
 
+foreach my $key (keys %$confdesc_cloudinit) {
+    $confdesc->{$key} = $confdesc_cloudinit->{$key};
+}
+
 my $valid_lxc_conf_keys = {
     'lxc.apparmor.profile' => 1,
     'lxc.apparmor.allow_incomplete' => 1,
-- 
2.30.2





^ permalink raw reply	[flat|nested] 9+ messages in thread

* [pve-devel] [PATCH container 2/4] cloudinit: basic implementation
  2023-06-02 11:57 [pve-devel] [PATCH container/manager/docs] Cloudinit support for LXC Leo Nunner
  2023-06-02 11:57 ` [pve-devel] [PATCH container 1/4] cloudinit: introduce config parameters Leo Nunner
@ 2023-06-02 11:57 ` Leo Nunner
  2023-06-02 11:57 ` [pve-devel] [PATCH container 3/4] cloudinit: add dump command to pct Leo Nunner
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Leo Nunner @ 2023-06-02 11:57 UTC (permalink / raw)
  To: pve-devel

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 <l.nunner@proxmox.com>
---
 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 fe1a5cf..f4391c8 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..3e8617b
--- /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->{ciupgrade};
+
+    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





^ permalink raw reply	[flat|nested] 9+ messages in thread

* [pve-devel] [PATCH container 3/4] cloudinit: add dump command to pct
  2023-06-02 11:57 [pve-devel] [PATCH container/manager/docs] Cloudinit support for LXC Leo Nunner
  2023-06-02 11:57 ` [pve-devel] [PATCH container 1/4] cloudinit: introduce config parameters Leo Nunner
  2023-06-02 11:57 ` [pve-devel] [PATCH container 2/4] cloudinit: basic implementation Leo Nunner
@ 2023-06-02 11:57 ` Leo Nunner
  2023-06-02 11:57 ` [pve-devel] [PATCH container 4/4] cloudinit: add function dumping options for docs Leo Nunner
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Leo Nunner @ 2023-06-02 11:57 UTC (permalink / raw)
  To: pve-devel

Introduce a 'pct cloudinit dump <vmid> <section>' command to dump the
generated cloudinit configuration for a section.

Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
---
 src/PVE/API2/LXC.pm      | 33 +++++++++++++++++++++++++++++++++
 src/PVE/CLI/pct.pm       |  4 ++++
 src/PVE/LXC/Cloudinit.pm | 11 +++++++++++
 3 files changed, 48 insertions(+)

diff --git a/src/PVE/API2/LXC.pm b/src/PVE/API2/LXC.pm
index e585509..2cae727 100644
--- a/src/PVE/API2/LXC.pm
+++ b/src/PVE/API2/LXC.pm
@@ -2963,4 +2963,37 @@ __PACKAGE__->register_method({
 
 	return { socket => $socket };
     }});
+
+__PACKAGE__->register_method({
+    name => 'cloudinit_generated_config_dump',
+    path => '{vmid}/cloudinit/dump',
+    method => 'GET',
+    proxyto => 'node',
+    description => "Get automatically generated cloudinit config.",
+    permissions => {
+	check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
+    },
+    parameters => {
+	additionalProperties => 0,
+	properties => {
+	    node => get_standard_option('pve-node'),
+	    vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
+	    type => {
+		description => 'Config type.',
+		type => 'string',
+		enum => ['user', 'meta'],
+	    },
+	},
+    },
+    returns => {
+	type => 'string',
+    },
+    code => sub {
+	my ($param) = @_;
+
+	my $conf = PVE::LXC::Config->load_config($param->{vmid});
+
+	return PVE::LXC::Cloudinit::dump_cloudinit_config($conf, $param->{type});
+    }});
+
 1;
diff --git a/src/PVE/CLI/pct.pm b/src/PVE/CLI/pct.pm
index ff75d33..69f3560 100755
--- a/src/PVE/CLI/pct.pm
+++ b/src/PVE/CLI/pct.pm
@@ -1000,6 +1000,10 @@ our $cmddef = {
     rescan  => [ __PACKAGE__, 'rescan', []],
     cpusets => [ __PACKAGE__, 'cpusets', []],
     fstrim => [ __PACKAGE__, 'fstrim', ['vmid']],
+
+    cloudinit => {
+	dump => [ "PVE::API2::LXC", 'cloudinit_generated_config_dump', ['vmid', 'type'], { node => $nodename }, sub { print "$_[0]\n"; }],
+    },
 };
 
 1;
diff --git a/src/PVE/LXC/Cloudinit.pm b/src/PVE/LXC/Cloudinit.pm
index 3e8617b..b6fec2c 100644
--- a/src/PVE/LXC/Cloudinit.pm
+++ b/src/PVE/LXC/Cloudinit.pm
@@ -111,4 +111,15 @@ sub create_cloudinit_files {
     });
 }
 
+sub dump_cloudinit_config {
+    my ($conf, $type) = @_;
+
+    if ($type eq 'user') {
+	return cloudinit_userdata($conf);
+    } else { # metadata config
+	my $user = cloudinit_userdata($conf);
+	return gen_cloudinit_metadata($user);
+    }
+}
+
 1;
-- 
2.30.2





^ permalink raw reply	[flat|nested] 9+ messages in thread

* [pve-devel] [PATCH container 4/4] cloudinit: add function dumping options for docs
  2023-06-02 11:57 [pve-devel] [PATCH container/manager/docs] Cloudinit support for LXC Leo Nunner
                   ` (2 preceding siblings ...)
  2023-06-02 11:57 ` [pve-devel] [PATCH container 3/4] cloudinit: add dump command to pct Leo Nunner
@ 2023-06-02 11:57 ` Leo Nunner
  2023-06-02 11:57 ` [pve-devel] [PATCH manager 1/2] cloudinit: rename qemu cloudinit panel Leo Nunner
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Leo Nunner @ 2023-06-02 11:57 UTC (permalink / raw)
  To: pve-devel

Adds a cloudinit_config_properties function which dumps the config
parameters from the cloudinit config description. This is called
automatically when generating the docs.

Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
---
 src/PVE/LXC/Config.pm | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/PVE/LXC/Config.pm b/src/PVE/LXC/Config.pm
index f8bfb95..49d6479 100644
--- a/src/PVE/LXC/Config.pm
+++ b/src/PVE/LXC/Config.pm
@@ -1244,6 +1244,9 @@ sub check_type {
     }
 }
 
+sub cloudinit_config_properties {
+    return Storable::dclone($confdesc_cloudinit);
+}
 
 # add JSON properties for create and set function
 sub json_config_properties {
-- 
2.30.2





^ permalink raw reply	[flat|nested] 9+ messages in thread

* [pve-devel] [PATCH manager 1/2] cloudinit: rename qemu cloudinit panel
  2023-06-02 11:57 [pve-devel] [PATCH container/manager/docs] Cloudinit support for LXC Leo Nunner
                   ` (3 preceding siblings ...)
  2023-06-02 11:57 ` [pve-devel] [PATCH container 4/4] cloudinit: add function dumping options for docs Leo Nunner
@ 2023-06-02 11:57 ` Leo Nunner
  2023-06-02 11:57 ` [pve-devel] [PATCH manager 2/2] cloudinit: introduce panel for LXCs Leo Nunner
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Leo Nunner @ 2023-06-02 11:57 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
---
 www/manager6/qemu/CloudInit.js | 4 ++--
 www/manager6/qemu/Config.js    | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/www/manager6/qemu/CloudInit.js b/www/manager6/qemu/CloudInit.js
index 77ff93d41..14117ff6b 100644
--- a/www/manager6/qemu/CloudInit.js
+++ b/www/manager6/qemu/CloudInit.js
@@ -1,6 +1,6 @@
 Ext.define('PVE.qemu.CloudInit', {
     extend: 'Proxmox.grid.PendingObjectGrid',
-    xtype: 'pveCiPanel',
+    xtype: 'pveQemuCiPanel',
 
     onlineHelp: 'qm_cloud_init',
 
@@ -66,7 +66,7 @@ Ext.define('PVE.qemu.CloudInit', {
 	    xtype: 'proxmoxButton',
 	    disabled: true,
 	    enableFn: function(rec) {
-		let view = this.up('pveCiPanel');
+		let view = this.up('pveQemuCiPanel');
 		return !!view.rows[rec.data.key].editor;
 	    },
 	    handler: function() {
diff --git a/www/manager6/qemu/Config.js b/www/manager6/qemu/Config.js
index 94c540c59..03e1e6d8d 100644
--- a/www/manager6/qemu/Config.js
+++ b/www/manager6/qemu/Config.js
@@ -284,7 +284,7 @@ Ext.define('PVE.qemu.Config', {
 		title: 'Cloud-Init',
 		itemId: 'cloudinit',
 		iconCls: 'fa fa-cloud',
-		xtype: 'pveCiPanel',
+		xtype: 'pveQemuCiPanel',
 	    },
 	    {
 		title: gettext('Options'),
-- 
2.30.2





^ permalink raw reply	[flat|nested] 9+ messages in thread

* [pve-devel] [PATCH manager 2/2] cloudinit: introduce panel for LXCs
  2023-06-02 11:57 [pve-devel] [PATCH container/manager/docs] Cloudinit support for LXC Leo Nunner
                   ` (4 preceding siblings ...)
  2023-06-02 11:57 ` [pve-devel] [PATCH manager 1/2] cloudinit: rename qemu cloudinit panel Leo Nunner
@ 2023-06-02 11:57 ` Leo Nunner
  2023-06-02 11:57 ` [pve-devel] [PATCH docs 1/2] pct: add script to generate cloudinit options Leo Nunner
  2023-06-02 11:57 ` [pve-devel] [PATCH docs 2/2] pct: document cloudinit for LXC Leo Nunner
  7 siblings, 0 replies; 9+ messages in thread
From: Leo Nunner @ 2023-06-02 11:57 UTC (permalink / raw)
  To: pve-devel

based on the already existing panel for VMs. Some things have been
changed, there is no network configuration, and a separate "enable"
options toggles cloud-init (simillar to adding/removing a cloud-init
drive for VMs).

Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
---
 www/manager6/Makefile         |   1 +
 www/manager6/lxc/CloudInit.js | 237 ++++++++++++++++++++++++++++++++++
 www/manager6/lxc/Config.js    |   6 +
 3 files changed, 244 insertions(+)
 create mode 100644 www/manager6/lxc/CloudInit.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 336355abf..05a2fd558 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -168,6 +168,7 @@ JSSRC= 							\
 	dc/UserTagAccessEdit.js				\
 	dc/RegisteredTagsEdit.js			\
 	lxc/CmdMenu.js					\
+	lxc/CloudInit.js				\
 	lxc/Config.js					\
 	lxc/CreateWizard.js				\
 	lxc/DNS.js					\
diff --git a/www/manager6/lxc/CloudInit.js b/www/manager6/lxc/CloudInit.js
new file mode 100644
index 000000000..11d5448de
--- /dev/null
+++ b/www/manager6/lxc/CloudInit.js
@@ -0,0 +1,237 @@
+Ext.define('PVE.lxc.CloudInit', {
+    extend: 'Proxmox.grid.PendingObjectGrid',
+    xtype: 'pveLxcCiPanel',
+
+    tbar: [
+	{
+	    xtype: 'proxmoxButton',
+	    disabled: true,
+	    dangerous: true,
+	    confirmMsg: function(rec) {
+		let view = this.up('grid');
+		var warn = gettext('Are you sure you want to remove entry {0}');
+
+		var entry = rec.data.key;
+		var msg = Ext.String.format(warn, "'"
+		    + view.renderKey(entry, {}, rec) + "'");
+
+		return msg;
+	    },
+	    enableFn: function(record) {
+		let view = this.up('grid');
+		var caps = Ext.state.Manager.get('GuiCap');
+		if (view.rows[record.data.key].never_delete ||
+		    !caps.vms['VM.Config.Network']) {
+		    return false;
+		}
+
+		if (record.data.key === 'cipassword' && !record.data.value) {
+		    return false;
+		}
+		return true;
+	    },
+	    handler: function() {
+		let view = this.up('grid');
+		let records = view.getSelection();
+		if (!records || !records.length) {
+		    return;
+		}
+
+		var id = records[0].data.key;
+
+		var params = {};
+		params.delete = id;
+		Proxmox.Utils.API2Request({
+		    url: view.baseurl + '/config',
+		    waitMsgTarget: view,
+		    method: 'PUT',
+		    params: params,
+		    failure: function(response, opts) {
+			Ext.Msg.alert('Error', response.htmlStatus);
+		    },
+		    callback: function() {
+			view.reload();
+		    },
+		});
+	    },
+	    text: gettext('Remove'),
+	},
+	{
+	    xtype: 'proxmoxButton',
+	    disabled: true,
+	    enableFn: function(rec) {
+		let view = this.up('pveLxcCiPanel');
+		return !!view.rows[rec.data.key].editor;
+	    },
+	    handler: function() {
+		let view = this.up('grid');
+		view.run_editor();
+	    },
+	    text: gettext('Edit'),
+	},
+    ],
+
+    border: false,
+
+    renderKey: function(key, metaData, rec, rowIndex, colIndex, store) {
+	var me = this;
+	var rows = me.rows;
+	var rowdef = rows[key] || {};
+
+	var icon = "";
+	if (rowdef.iconCls) {
+	    icon = '<i class="' + rowdef.iconCls + '"></i> ';
+	}
+	return icon + (rowdef.header || key);
+    },
+
+    listeners: {
+	activate: function() {
+	    var me = this;
+	    me.rstore.startUpdate();
+	},
+	itemdblclick: function() {
+	    var me = this;
+	    me.run_editor();
+	},
+    },
+
+    initComponent: function() {
+	var me = this;
+
+	var nodename = me.pveSelNode.data.node;
+	if (!nodename) {
+	    throw "no node name specified";
+	}
+
+	var vmid = me.pveSelNode.data.vmid;
+	if (!vmid) {
+	    throw "no VM ID specified";
+	}
+	var caps = Ext.state.Manager.get('GuiCap');
+	me.baseurl = '/api2/extjs/nodes/' + nodename + '/lxc/' + vmid;
+	me.url = me.baseurl + '/pending';
+	me.editorConfig.url = me.baseurl + '/config';
+	me.editorConfig.pveSelNode = me.pveSelNode;
+
+	let caps_ci = caps.vms['VM.Config.Cloudinit'] || caps.vms['VM.Config.Network'];
+	/* editor is string and object */
+	me.rows = {
+	    cienable: {
+		header: gettext('Enable'),
+		iconCls: 'fa fa-cloud',
+		never_delete: true,
+		defaultValue: false,
+		editor: caps_ci ? {
+		    xtype: 'proxmoxWindowEdit',
+		    subject: gettext('Enable Cloud-Init'),
+		    items: [
+			{
+			    xtype: 'proxmoxcheckbox',
+			    deleteEmpty: true,
+			    fieldLabel: gettext('Enable'),
+			    name: 'cienable',
+			},
+		    ],
+		} : undefined,
+		renderer: Proxmox.Utils.format_boolean,
+	    },
+	    ciuser: {
+		header: gettext('User'),
+		iconCls: 'fa fa-user',
+		never_delete: true,
+		defaultValue: '',
+		editor: caps_ci ? {
+		    xtype: 'proxmoxWindowEdit',
+		    subject: gettext('User'),
+		    items: [
+			{
+			    xtype: 'proxmoxtextfield',
+			    deleteEmpty: true,
+			    emptyText: Proxmox.Utils.defaultText,
+			    fieldLabel: gettext('User'),
+			    name: 'ciuser',
+			},
+		    ],
+		} : undefined,
+		renderer: function(value) {
+		    return value || Proxmox.Utils.defaultText;
+		},
+	    },
+	    cipassword: {
+		header: gettext('Password'),
+		iconCls: 'fa fa-unlock',
+		defaultValue: '',
+		editor: caps_ci ? {
+		    xtype: 'proxmoxWindowEdit',
+		    subject: gettext('Password'),
+		    items: [
+			{
+			    xtype: 'proxmoxtextfield',
+			    inputType: 'password',
+			    deleteEmpty: true,
+			    emptyText: Proxmox.Utils.noneText,
+			    fieldLabel: gettext('Password'),
+			    name: 'cipassword',
+			},
+		    ],
+		} : undefined,
+		renderer: function(value) {
+		    return value || Proxmox.Utils.noneText;
+		},
+	    },
+	    sshkeys: {
+		header: gettext('SSH public key'),
+		iconCls: 'fa fa-key',
+		editor: caps_ci ? 'PVE.qemu.SSHKeyEdit' : undefined,
+		never_delete: true,
+		renderer: function(value) {
+		    value = decodeURIComponent(value);
+		    var keys = value.split('\n');
+		    var text = [];
+		    keys.forEach(function(key) {
+			if (key.length) {
+			    let res = PVE.Parser.parseSSHKey(key);
+			    if (res) {
+				key = Ext.String.htmlEncode(res.comment);
+				if (res.options) {
+				    key += ' <span style="color:gray">(' + gettext('with options') + ')</span>';
+				}
+				text.push(key);
+				return;
+			    }
+			    // Most likely invalid at this point, so just stick to
+			    // the old value.
+			    text.push(Ext.String.htmlEncode(key));
+			}
+		    });
+		    if (text.length) {
+			return text.join('<br>');
+		    } else {
+			return Proxmox.Utils.noneText;
+		    }
+		},
+		defaultValue: '',
+	    },
+	    ciupgrade: {
+		header: gettext('Upgrade packages'),
+		iconCls: 'fa fa-archive',
+		renderer: Proxmox.Utils.format_boolean,
+		defaultValue: '',
+		editor: {
+		    xtype: 'proxmoxWindowEdit',
+		    subject: gettext('Upgrade packages on boot'),
+		    items: {
+			xtype: 'proxmoxcheckbox',
+			name: 'ciupgrade',
+			uncheckedValue: 0,
+			defaultValue: 0,
+			fieldLabel: gettext('Upgrade packages'),
+			labelWidth: 140,
+		    },
+		},
+	    },
+	};
+	me.callParent();
+    },
+});
diff --git a/www/manager6/lxc/Config.js b/www/manager6/lxc/Config.js
index 23c17d2ea..bd86b93b9 100644
--- a/www/manager6/lxc/Config.js
+++ b/www/manager6/lxc/Config.js
@@ -260,6 +260,12 @@ Ext.define('PVE.lxc.Config', {
 		itemId: 'dns',
 		xtype: 'pveLxcDNS',
 	    },
+	    {
+		title: 'Cloud-Init',
+		itemId: 'cloudinit',
+		iconCls: 'fa fa-cloud',
+		xtype: 'pveLxcCiPanel',
+	    },
 	    {
 		title: gettext('Options'),
 		itemId: 'options',
-- 
2.30.2





^ permalink raw reply	[flat|nested] 9+ messages in thread

* [pve-devel] [PATCH docs 1/2] pct: add script to generate cloudinit options
  2023-06-02 11:57 [pve-devel] [PATCH container/manager/docs] Cloudinit support for LXC Leo Nunner
                   ` (5 preceding siblings ...)
  2023-06-02 11:57 ` [pve-devel] [PATCH manager 2/2] cloudinit: introduce panel for LXCs Leo Nunner
@ 2023-06-02 11:57 ` Leo Nunner
  2023-06-02 11:57 ` [pve-devel] [PATCH docs 2/2] pct: document cloudinit for LXC Leo Nunner
  7 siblings, 0 replies; 9+ messages in thread
From: Leo Nunner @ 2023-06-02 11:57 UTC (permalink / raw)
  To: pve-devel

…the same way as it's already being done for VMs.

Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
---
 gen-pct-cloud-init-opts.pl | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
 create mode 100755 gen-pct-cloud-init-opts.pl

diff --git a/gen-pct-cloud-init-opts.pl b/gen-pct-cloud-init-opts.pl
new file mode 100755
index 0000000..c22c4d1
--- /dev/null
+++ b/gen-pct-cloud-init-opts.pl
@@ -0,0 +1,16 @@
+#!/usr/bin/perl
+
+use lib '.';
+use strict;
+use warnings;
+use PVE::JSONSchema;
+use PVE::RESTHandler;
+use PVE::LXC::Config;
+
+my $prop = PVE::LXC::Config::cloudinit_config_properties();
+
+my $data = PVE::RESTHandler::dump_properties($prop, 'asciidoc', 'config');
+
+$data =~ s/cloud-init: //g;
+
+print $data;
-- 
2.30.2





^ permalink raw reply	[flat|nested] 9+ messages in thread

* [pve-devel] [PATCH docs 2/2] pct: document cloudinit for LXC
  2023-06-02 11:57 [pve-devel] [PATCH container/manager/docs] Cloudinit support for LXC Leo Nunner
                   ` (6 preceding siblings ...)
  2023-06-02 11:57 ` [pve-devel] [PATCH docs 1/2] pct: add script to generate cloudinit options Leo Nunner
@ 2023-06-02 11:57 ` Leo Nunner
  7 siblings, 0 replies; 9+ messages in thread
From: Leo Nunner @ 2023-06-02 11:57 UTC (permalink / raw)
  To: pve-devel

adds documentation for Cloud-Init for containers. Most of it has been
taken from the VM documentation, since the configuration mostly works
the same. Added a script to extract the cloudinit parameters the same
way it's already done for VMs.

Signed-off-by: Leo Nunner <l.nunner@proxmox.com>
---
 Makefile            |   1 +
 pct-cloud-init.adoc | 114 ++++++++++++++++++++++++++++++++++++++++++++
 pct.adoc            |   4 ++
 3 files changed, 119 insertions(+)
 create mode 100644 pct-cloud-init.adoc

diff --git a/Makefile b/Makefile
index b8a0666..ec5319a 100644
--- a/Makefile
+++ b/Makefile
@@ -53,6 +53,7 @@ GEN_SCRIPTS=					\
 	gen-pct.conf.5-opts.pl			\
 	gen-pct-network-opts.pl			\
 	gen-pct-mountpoint-opts.pl		\
+	gen-pct-cloud-init-opts.pl		\
 	gen-qm.conf.5-opts.pl			\
 	gen-cpu-models.conf.5-opts.pl 		\
 	gen-qm-cloud-init-opts.pl		\
diff --git a/pct-cloud-init.adoc b/pct-cloud-init.adoc
new file mode 100644
index 0000000..1398e7b
--- /dev/null
+++ b/pct-cloud-init.adoc
@@ -0,0 +1,114 @@
+[[pct_cloud_init]]
+Cloud-Init Support
+------------------
+ifdef::wiki[]
+:pve-toplevel:
+endif::wiki[]
+
+{pve} supports the Cloud-init 'nocloud' format for LXC.
+
+{pve} writes the Cloud-init configuration directly into the container.
+When the 'cienable' option is set to true, the configuration is updated
+directly before before every boot. Each configuration is identified by
+an 'instance id', which Cloud-Init uses to decide whether it should run
+again or not.
+
+Preparing Cloud-Init Templates
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The first step is to prepare your container. Any template will suffice.
+Simply install the Cloud-Init packages inside the CT that you want to
+prepare. On Debian/Ubuntu based systems this is as simple as:
+
+----
+apt install cloud-init
+----
+
+WARNING: This command is *not* intended to be executed on the {pve} host, but
+only inside the container.
+
+A simple preparation for a cloud-init capable container could look like this:
+
+----
+# download an image
+pveam download local ubuntu-22.10-standard_22.10-1_amd64.tar.zst
+
+# create a new container
+pct create 9000 local:vztmpl/ubuntu-22.10-standard_22.10-1_amd64.tar.zst \
+    --storage local-lvm --memory 512 \
+    --net0 name=eth0,bridge=vmbr0,ip=dhcp,type=veth
+----
+
+Now, the package can be installed inside the container:
+
+----
+# install the needed packages
+pct start 9000
+pct exec 9000 apt update
+pct exec 9000 apt install cloud-init
+pct stop 9000
+----
+
+Finally, it can be helpful to turn the container into a template, to be able
+to quickly create clones whenever needed.
+
+----
+pct template 9000
+----
+
+Deploying Cloud-Init Templates
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A template can easily be deployed by cloning:
+
+----
+pct clone 9000 3000 --hostname ubuntu
+----
+
+Cloud-Init can now be enabled, and will run automatically on the next boot.
+
+----
+pct set 3000 --cienable=1
+----
+
+Custom Cloud-Init Configuration
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The Cloud-Init integration also allows custom config files to be used instead
+of the automatically generated configs. This is done via the `cicustom`
+option on the command line:
+
+----
+pct set 9000 --cicustom "user=<volume>,meta=<volume>"
+----
+
+The custom config files have to be on a storage that supports snippets and have
+to be available on all nodes the container is going to be migrated to. Otherwise the
+container won't be able to start.
+For example:
+
+----
+qm set 9000 --cicustom "user=local:snippets/userconfig.yaml"
+----
+
+In contrast to the options for VMs, containers only support custom 'user'
+and 'vendor' scripts, but not 'network'. Network configuration is done through
+the already existing facilities integrated into {pve}. They can all be specified
+together or mixed and matched however needed.
+The automatically generated config will be used for any section that doesn't have a
+custom config file specified.
+
+The generated config can be dumped to serve as a base for custom configs:
+
+----
+pct cloudinit dump 9000 user
+----
+
+The same command exists for `meta`.
+
+
+Cloud-Init specific Options
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+include::pct-cloud-init-opts.adoc[]
+
diff --git a/pct.adoc b/pct.adoc
index fdeb6dd..7cfb09e 100644
--- a/pct.adoc
+++ b/pct.adoc
@@ -622,6 +622,10 @@ It will be called during various phases of the guests lifetime.  For an example
 and documentation see the example script under
 `/usr/share/pve-docs/examples/guest-example-hookscript.pl`.
 
+ifndef::wiki[]
+include::pct-cloud-init.adoc[]
+endif::wiki[]
+
 Security Considerations
 -----------------------
 
-- 
2.30.2





^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2023-06-02 11:58 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-06-02 11:57 [pve-devel] [PATCH container/manager/docs] Cloudinit support for LXC Leo Nunner
2023-06-02 11:57 ` [pve-devel] [PATCH container 1/4] cloudinit: introduce config parameters Leo Nunner
2023-06-02 11:57 ` [pve-devel] [PATCH container 2/4] cloudinit: basic implementation Leo Nunner
2023-06-02 11:57 ` [pve-devel] [PATCH container 3/4] cloudinit: add dump command to pct Leo Nunner
2023-06-02 11:57 ` [pve-devel] [PATCH container 4/4] cloudinit: add function dumping options for docs Leo Nunner
2023-06-02 11:57 ` [pve-devel] [PATCH manager 1/2] cloudinit: rename qemu cloudinit panel Leo Nunner
2023-06-02 11:57 ` [pve-devel] [PATCH manager 2/2] cloudinit: introduce panel for LXCs Leo Nunner
2023-06-02 11:57 ` [pve-devel] [PATCH docs 1/2] pct: add script to generate cloudinit options Leo Nunner
2023-06-02 11:57 ` [pve-devel] [PATCH docs 2/2] pct: document cloudinit for LXC Leo Nunner

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal