From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id F1F221FF13E for ; Wed, 01 Jul 2026 10:04:55 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id CDCBB21434; Wed, 01 Jul 2026 10:04:54 +0200 (CEST) ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=OtnJVcvXFdXPQ/L9zjfcnt7ucYRBiNqeUGvURscwHX2pY/zdC3IJvFtAZneXbwXuVCU/opwRbVomDJ2MrwZ7s6Rbz6jlMt6kqhAXYjN0T/JI84cua4/D3rIDW3CvupiOpcXyPDbREPSBFvnIG4nqq43rD5i4uFV/mylkzWRX9utPW5C4V/Qx/aHpijJrbNaqz7J0DyYtf/4duqN78V9AuFIVmaw2FGrqhfl1SC8I+oo8SxDVAGZ+RGybRQD+ezgHUkXgJTsbVjDfmlQNfsBBG58qxgQTARvPqeSpIYrMaEbsY7S1kvYgxHareSTYtZcwqOluq8knEmBWBDKGeDPTsw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=7lfn7T9r0KUuVORIM2Yuhems+hlkiDcXp46PUWdv5Xk=; b=AVnhuALqrFzka/smiZM0bl+V6OTQ7Rhkb9JzXyBMiCQvTWc/jXiyGzLSmvML35tfLgl59Ua3YOF1lNzIrn788ZvMtq7mL9ZrrdIbHWErf+E5jSQGcfAhHQh770kBEUunUvRWo19ZPtJSUc5/i+cS0QUcakT5+6/ewl54mEZNVorJlhBHx4GmElLGc+G+jsznS/8lLrM0z/IFnbmcQZqqN5h4y1yHLVE3L29VXJH4ODjQmbjAuV9x6EmSPhZjB6uXOpaYs4C0b4s7JdxBwglSG0cpa0rzxfHfwabpESo3pNAjNroCWRzunE/RzKva0r5wc69CHmpHsVjtcLjYgt4m8A== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=none; dmarc=none; dkim=none; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=OUTLOOK.IT; s=selector1; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=7lfn7T9r0KUuVORIM2Yuhems+hlkiDcXp46PUWdv5Xk=; b=UPxX/r5kdLAyhrzspm5HV4x9zSkMfumC15O0nDCWSnzjKUjBlnLoLJcaKzWqSEhExzNkUSXj/fmuwW17jJl/eBipSOJWTYYe28ytPZhRCmLVFN48EGkZVaPR99mZoFjDxc+oCKOgqNnpXV267lePPA3reDRiz7j0Na7Wm/6JWHQ6WZjq2ww4COvLVxQL0KzXOryKIPGAA1DmmEzZ016lhvquHw666R3XsGpmN+BEfrWw5w84KlC3Mm8uAMDJwGeRQ1oWhqC0r2iBvRdrGpXiRGNufshtvKp8L2/uUPWzfkbvrrwhP661YM1pdOKdtD2+qI0wnl0PGEgM2w40LEotOA== From: Mauro de Pascale To: "pve-devel@lists.proxmox.com" Subject: [PATCH 1/1] storage: add streamed VM backup import endpoint Thread-Topic: [PATCH 1/1] storage: add streamed VM backup import endpoint Thread-Index: AQHdBYr0gp04R/pmVUuQdxWeI8Z2zA== Date: Fri, 26 Jun 2026 16:43:44 +0000 Message-ID: <20260626164339.44662-2-mauro.depascale.work@outlook.it> References: <20260626164339.44662-1-mauro.depascale.work@outlook.it> In-Reply-To: <20260626164339.44662-1-mauro.depascale.work@outlook.it> Accept-Language: it-IT, en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: x-ms-publictraffictype: Email x-ms-traffictypediagnostic: LOBP265MB8961:EE_|LO3P265MB1771:EE_ x-ms-office365-filtering-correlation-id: a8364c9c-c79c-49c2-a7bd-08ded3a2168a x-ms-exchange-slblob-mailprops: laRBL560oLSPhlJ95/U7v6W/AahuugGa2dNc/FJ8n2IVDldbwcxGpi5cjTQ7atyp5h9nq4afLnEo9+xvy4wGk1yKTO9JUEnlN8o/Haqne6BIIEXlOnM8a5KMJ49Tgvs4UVDhq+b8/4f6iqY1Nmh9AsyvmPiQ0UK7Z/tszOK8k0W25ZfKEjUHJ8zx4Dn0Z6sqXSlIOaScj32qo1tbYhgPmcgoXTgrF2DZDXSV6SG/pDWmbkut4ju0GGzYgVV8pVc8Yz7fYF/CHrH+2HqFX0CC535TdSiy4vVJqJq79jDw3uijCtsizlX3TG6t4502ZMQg8ovkGifG9sfU+Ek9IwKmu/ZmvpR0qv314ph3HFBRXe1ElzJGbjS8byCR5VlRYQkIBoKa6tL8ui/kcI73JTlr9J2MQ3SgSEhgtdLfUv7GAnssWW0nywQuM2nqFRxn6JFtIAF9a1i0hbqg9OavwzjlzBaT/o5riJQrqhhh1IQKfGWnKUqWjkSs9FlVLbcSEm4uU6QQwu4GKM5JTUXH9HtJ7yaHxLuFrjkqhKVI9ixmw4BW1MlciDsNwGDC8lUMPGS98CUH7ePa4pnx/07JSfvDtSYzIaQrFnOw/i/KSQfcgAxxPKv35rNxnr4or4fgryTbE4/Ajv1asNRBSCqrhDW9sIzQl7/OTA5l1b0trI9z+tRHwO4UqJt00YYx6VTtdLxTz2aHmptO9YwbIIyT28rkyVa2oUJVLZaQAb5A4Yv37BbEQZGoxRU1LX1oA+Uyc+JiFzuLrgn9DvQLcUtY9wxvSWEjsw96M2/z x-microsoft-antispam: BCL:0;ARA:14566002|15080799012|17081999003|38102599003|19110799012|8060799015|55001999006|8062599012|31061999003|10092599007|12121999013|51005399006|24021099003|25010399006|41001999006|40105399003|3412199025|440099028|102099032; x-microsoft-antispam-message-info: =?iso-8859-1?Q?0p1PI4c/ZUcojv3rsiTKv+DYwX+qFPuwDIykNutlmp0ciOzREYz1e5t0OM?= =?iso-8859-1?Q?PT7zJB3c6vs7mW5BW1MLdqnprRxKgc/x8SaNmKtpuNlVam4762/Wjmd9jG?= =?iso-8859-1?Q?FOMj8wwwLIBS7R1lbbudLOHpnjD8QH/gG7/Y5GqG6xIIKJfuDH43/h78dT?= =?iso-8859-1?Q?OlWBwGql8GyS5Lc8KDzKcAi6Sd08BVtqhDcspDLhy/1ysl9REaCzzWvYrt?= =?iso-8859-1?Q?MGlSGN+aDjylZqNkSnkRMm3eux4abOTeWgHmJtDUIctnkU5RVXsBOj7S9D?= =?iso-8859-1?Q?1WLgVPFzsktg1GWcPhWeTf/Etl1ofUaKfpoYuKH7QEj8DZVRkTykNBznxY?= =?iso-8859-1?Q?zH66F/XoYfc0l7sgKjDelRS32rP2uuDVw19fxHVOZCCgio0O8ZxGVe2gBe?= =?iso-8859-1?Q?PXZJjV/0j1rH3q4hd5K0XAPtMB4tgaJhNwAx7Spk5VLiGx2Naga2DZ3+aw?= =?iso-8859-1?Q?1qMwlp+cyO1Lw2hg/HyzrHinMvacv2OKiFlCGWHtrwdh2KP8T4+EXsqa6a?= =?iso-8859-1?Q?0hrP4V4Ctr/kFlhpdmD1i6zbavIKP3/l9e8zprsHVi9hAAHbf5oqeZ24ZY?= =?iso-8859-1?Q?55HAs7rVHULMBKtHVp0QBynH2rQfOV/bZ94Npi08LzKhWKQxOKJ622LyHI?= =?iso-8859-1?Q?OmGZbUGdK082SyII1+5cqGDRtALzki7x9afi/PMgA8Z/0G7StXvLzcf3gn?= =?iso-8859-1?Q?cRtHwGXceJFIWAZpbIRnpn9tbD9IxIFqSxu1sizz336bIOnwbgtHPXWEKp?= =?iso-8859-1?Q?TLOPfvVIVN8pMnu6XRXSIQNz0IgNs/zcSarEt1G27WYQertMbP8I+OWwIr?= =?iso-8859-1?Q?gpEZRk7q2zHb3O5N+r5/RJh4pV509R1nR7j9I9fteYX3AfjCNs9W6L4gE0?= =?iso-8859-1?Q?5ym+fqs6cpHi5Xv6aNaHNkmVahbVIWk/UAqBfVlNg5zDQ8ww+p6kfqoa/k?= =?iso-8859-1?Q?0QTtdUWRAPPKhvPwqH5m2+hyHcM8rAMP+9JXkrYdXVFLSoQuFwia9D4Ypz?= =?iso-8859-1?Q?L45A2EgMUkv6cHg3sA/+aWPBAacJNgDkK9WfizaM8F1nYjD+mNEG/qMKYk?= =?iso-8859-1?Q?1YDbgvwJq2DLiu/+89FvQTp/OhQVQ6ntKZeb4BQaPqkr6M/rHDX5R1kCrP?= =?iso-8859-1?Q?dEnKQ40KVw2hToDEAPGgkhmWTgRVdpmhruQVrrgV5sn0rlPVFr?= x-ms-exchange-antispam-messagedata-chunkcount: 1 x-ms-exchange-antispam-messagedata-0: =?iso-8859-1?Q?XpL6nOJcNe8ZuGX95fWtu0/ss2xYZk9F0w+OvkwVvTMMqQADt0/bBmFARh?= =?iso-8859-1?Q?mxb+4G+d/nUCJ4RHwQuejeAXwsnwH9X4GLcBf8ArQX6XtFmcxSxuZ31PN2?= =?iso-8859-1?Q?pXoQ8erBOqXT7qknYbf2Oyg6RWKNsC7LG4VePkCI8a2TaF/y3aRIMMkrq5?= =?iso-8859-1?Q?aotFbfk0cjeT3BncZr4GRuM2Q52I2xG7X2+rOQWJdjMnJ8NrMdqQFEMFuV?= =?iso-8859-1?Q?Nnn69LFNjS8pTrd3w2Dsj0Ph3gUbzwxEOEqbJWZ7E3E64DD2vgGHeErFdT?= =?iso-8859-1?Q?uW7B2aPxvcRudT5ViVrzIptXMtT+27lOtNWB1YG4u6lcBV/hwT3TGUhJjl?= =?iso-8859-1?Q?qSt12xGlYMmQb5Umr2s8W71+giEHa90aSOobe4WBodpg7Rm08nGNtxY5Cp?= =?iso-8859-1?Q?5wpEcNn9gxQWi6wo4tki6Wjh23ybprYA8GYsqd1szMuEtCOuM3+XgQa6o6?= =?iso-8859-1?Q?inn65/VjM4+PwQfoS795+Dhs/HjZUfUW+6tGUa5mPwA4lgzz+Y2FnGaIyc?= =?iso-8859-1?Q?YBs8nOz6dD5hA3DEvNANyHboaCZCVb/IVX7XNscr9ArLcLj+aLHJ14xyxj?= =?iso-8859-1?Q?DCkzSjWNCZ77K+PlKxWpwehGgWISRPyCso3uy+acrOjfauuo43EIIEu9yS?= =?iso-8859-1?Q?NY2NJtMqjCBQAM4+BzgcCwNlahvqqH+GMFakkuCZCDQSByY53nkknOze2i?= =?iso-8859-1?Q?ZBbKeYkkIaoAxTnSZoAwtSFrwyIcsTp2VPpxWqaW4uhvXH/MbsNf219Xkg?= =?iso-8859-1?Q?rY37aW9kg3qH53YypDDUN0AEIJMrOcUKCe3/NAlOxR5YuDDvgSu0UvRE0Y?= =?iso-8859-1?Q?9IHmmIhqA08NCe1KEF4BUHgV1qCb35gUZsIuoID0Ug6WxOj6pxmbEbGBVw?= =?iso-8859-1?Q?Wlx8Qy1JGCECHpzJEvady2jSdXYy07SYmwxULiraBiYKpAK9iiafAJDxnJ?= =?iso-8859-1?Q?HXkiIisuT0XuLmMKncRdlja7YiKy/LGa4NWyF+f7sWjDcy5Eu3OTYjOznE?= =?iso-8859-1?Q?5EruW3gX+CLeKhywsnYjQPRCpf5yDYZ/5EtnknXgIA8OlTHPRUPKwRWzzG?= =?iso-8859-1?Q?z0bv3orKeecgYUbyD5yP82NEwblVYCbLQ0DY3Ur72YjfaTeU5Fn/QUHivL?= =?iso-8859-1?Q?cLHYUO6x6aqaFg4iYmLL10NCRzIWf3RZ4bXf7+hzch5VEoO9dQnQrVEi2E?= =?iso-8859-1?Q?rC3V5AAUjVT3DTh/xkgBQqA/IkXbjUEW23q2OvOsDq4oiSERD0R/rRFS9Y?= =?iso-8859-1?Q?yg1evGz0fHrc0pQoEUqFUYQ3JVdyZYDqUX7TYw0Gcioo6G60cgjFanPu69?= =?iso-8859-1?Q?eZ6l4B+MVBnyZoT1UIoKdMflVWx6pVQp1Eviyf6SfixLJ9LR744QqAIvyQ?= =?iso-8859-1?Q?fGyCJIzlUkZ4TgiZ/wWvjnTiVVBM79HHODOAZVbi9s1aE8EkAobMhsuE9C?= =?iso-8859-1?Q?Fx3aFObtn2rym6GR3ux8GlYHXRUbc9YUUXqGAw=3D=3D?= Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 X-OriginatorOrg: sct-15-20-9412-4-msonline-outlook-7f0c1.templateTenant X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-AuthSource: LOBP265MB8961.GBRP265.PROD.OUTLOOK.COM X-MS-Exchange-CrossTenant-RMS-PersistedConsumerOrg: 00000000-0000-0000-0000-000000000000 X-MS-Exchange-CrossTenant-Network-Message-Id: a8364c9c-c79c-49c2-a7bd-08ded3a2168a X-MS-Exchange-CrossTenant-originalarrivaltime: 26 Jun 2026 16:43:44.9317 (UTC) X-MS-Exchange-CrossTenant-fromentityheader: Hosted X-MS-Exchange-CrossTenant-id: 84df9e7f-e9f6-40af-b435-aaaaaaaaaaaa X-MS-Exchange-CrossTenant-rms-persistedconsumerorg: 00000000-0000-0000-0000-000000000000 X-MS-Exchange-Transport-CrossTenantHeadersStamped: LO3P265MB1771 X-SPAM-LEVEL: Spam detection results: 0 BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain DMARC_PASS -0.1 DMARC pass policy FREEMAIL_FROM 0.001 Sender email is commonly abused enduser mail provider RCVD_IN_DNSWL_NONE -0.0001 Sender listed at https://www.dnswl.org/, no trust RCVD_IN_MSPIKE_H2 0.001 Average reputation (+2) SPF_HELO_PASS -0.001 SPF: HELO matches SPF record SPF_PASS -0.001 SPF: sender matches SPF record X-MailFrom: mauro.depascale.work@outlook.it X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation Message-ID-Hash: EGJ2DDGM46GANMEP6ZK4SKMSBL7ENROA X-Message-ID-Hash: EGJ2DDGM46GANMEP6ZK4SKMSBL7ENROA X-Mailman-Approved-At: Wed, 01 Jul 2026 10:04:53 +0200 CC: Mauro de Pascale X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Mauro de Pascale =0A= =0A= Add support for uploading VMA/VMA.ZST backups directly to storage and=0A= automatically restoring them through qmrestore in a background worker.=0A= =0A= The upload endpoint detects backup archives, starts an import task and=0A= streams the uploaded data directly to qmrestore without requiring manual=0A= intervention.=0A= ---=0A= src/PVE/API2/Storage/Status.pm | 2283 ++++++++++++++++----------------=0A= 1 file changed, 1178 insertions(+), 1105 deletions(-)=0A= =0A= diff --git a/src/PVE/API2/Storage/Status.pm b/src/PVE/API2/Storage/Status.p= m=0A= index 741d514..885c197 100644=0A= --- a/src/PVE/API2/Storage/Status.pm=0A= +++ b/src/PVE/API2/Storage/Status.pm=0A= @@ -5,7 +5,9 @@ use warnings;=0A= =0A= use File::Basename;=0A= use File::Path;=0A= +use IPC::Open2;=0A= use POSIX qw(ENOENT);=0A= +use PVE::SafeSyslog;=0A= =0A= use PVE::Cluster;=0A= use PVE::Exception qw(raise_param_exc);=0A= @@ -26,1156 +28,1227 @@ use base qw(PVE::RESTHandler);=0A= my $storage_type_enum =3D PVE::Storage::Plugin->lookup_types();=0A= =0A= __PACKAGE__->register_method({=0A= - subclass =3D> "PVE::API2::Storage::PruneBackups",=0A= - path =3D> '{storage}/prunebackups',=0A= + subclass =3D> "PVE::API2::Storage::PruneBackups",=0A= + path =3D> '{storage}/prunebackups',=0A= });=0A= =0A= __PACKAGE__->register_method({=0A= - subclass =3D> "PVE::API2::Storage::Content",=0A= - # set fragment delimiter (no subdirs) - we need that, because volume= =0A= - # IDs may contain a slash '/'=0A= - fragmentDelimiter =3D> '',=0A= - path =3D> '{storage}/content',=0A= + subclass =3D> "PVE::API2::Storage::Content",=0A= + # set fragment delimiter (no subdirs) - we need that, because volume=0A= + # IDs may contain a slash '/'=0A= + fragmentDelimiter =3D> '',=0A= + path =3D> '{storage}/content',=0A= });=0A= =0A= __PACKAGE__->register_method({=0A= - subclass =3D> "PVE::API2::Storage::FileRestore",=0A= - path =3D> '{storage}/file-restore',=0A= + subclass =3D> "PVE::API2::Storage::FileRestore",=0A= + path =3D> '{storage}/file-restore',=0A= });=0A= =0A= my sub assert_ova_contents {=0A= - my ($file) =3D @_;=0A= + my ($file) =3D @_;=0A= =0A= - # test if it's really a tar file with an ovf file inside=0A= - my $hasOvf =3D 0;=0A= - run_command(=0A= - ['tar', '-t', '-f', $file],=0A= - outfunc =3D> sub {=0A= - my ($line) =3D @_;=0A= + # test if it's really a tar file with an ovf file inside=0A= + my $hasOvf =3D 0;=0A= + run_command(=0A= + ['tar', '-t', '-f', $file],=0A= + outfunc =3D> sub {=0A= + my ($line) =3D @_;=0A= =0A= - if ($line =3D~ m/\.ovf$/) {=0A= - $hasOvf =3D 1;=0A= - }=0A= - },=0A= - );=0A= + if ($line =3D~ m/\.ovf$/) {=0A= + $hasOvf =3D 1;=0A= + }=0A= + },=0A= + );=0A= =0A= - die "ova archive has no .ovf file inside\n" if !$hasOvf;=0A= + die "ova archive has no .ovf file inside\n" if !$hasOvf;=0A= =0A= - return 1;=0A= + return 1;=0A= }=0A= =0A= __PACKAGE__->register_method({=0A= - name =3D> 'index',=0A= - path =3D> '',=0A= - method =3D> 'GET',=0A= - description =3D> "Get status for all datastores.",=0A= - permissions =3D> {=0A= - description =3D>=0A= - "Only list entries where you have 'Datastore.Audit' or 'Datast= ore.AllocateSpace' permissions on '/storage/'",=0A= - user =3D> 'all',=0A= - },=0A= - protected =3D> 1,=0A= - proxyto =3D> 'node',=0A= - parameters =3D> {=0A= - additionalProperties =3D> 0,=0A= - properties =3D> {=0A= - node =3D> get_standard_option('pve-node'),=0A= - storage =3D> get_standard_option(=0A= - 'pve-storage-id',=0A= - {=0A= - description =3D> "Only list status for specified stor= age",=0A= - optional =3D> 1,=0A= - completion =3D> \&PVE::Storage::complete_storage_enabl= ed,=0A= - },=0A= - ),=0A= - content =3D> {=0A= - description =3D> "Only list stores which support this cont= ent type.",=0A= - type =3D> 'string',=0A= - format =3D> 'pve-storage-content-list',=0A= - optional =3D> 1,=0A= - completion =3D> \&PVE::Storage::complete_content_type,=0A= - },=0A= - enabled =3D> {=0A= - description =3D> "Only list stores which are enabled (not = disabled in config).",=0A= - type =3D> 'boolean',=0A= - optional =3D> 1,=0A= - default =3D> 0,=0A= - },=0A= - target =3D> get_standard_option(=0A= - 'pve-node',=0A= - {=0A= - description =3D>=0A= - "If target is different to 'node', we only lists s= hared storages which "=0A= - . "content is accessible on this 'node' and the sp= ecified 'target' node.",=0A= - optional =3D> 1,=0A= - completion =3D> \&PVE::Cluster::get_nodelist,=0A= - },=0A= - ),=0A= - 'format' =3D> {=0A= - description =3D> "Include information about formats",=0A= - type =3D> 'boolean',=0A= - optional =3D> 1,=0A= - default =3D> 0,=0A= - },=0A= - },=0A= - },=0A= - returns =3D> {=0A= - type =3D> 'array',=0A= - items =3D> {=0A= - type =3D> "object",=0A= - properties =3D> {=0A= - storage =3D> get_standard_option('pve-storage-id'),=0A= - type =3D> {=0A= - description =3D> "Storage type.",=0A= - type =3D> 'string',=0A= - },=0A= - content =3D> {=0A= - description =3D> "Allowed storage content types.",=0A= - type =3D> 'string',=0A= - format =3D> 'pve-storage-content-list',=0A= - },=0A= - enabled =3D> {=0A= - description =3D> "Set when storage is enabled (not dis= abled).",=0A= - type =3D> 'boolean',=0A= - optional =3D> 1,=0A= - },=0A= - active =3D> {=0A= - description =3D> "Set when storage is accessible.",=0A= - type =3D> 'boolean',=0A= - optional =3D> 1,=0A= - },=0A= - shared =3D> {=0A= - description =3D> "Shared flag from storage configurati= on.",=0A= - type =3D> 'boolean',=0A= - optional =3D> 1,=0A= - },=0A= - total =3D> {=0A= - description =3D> "Total storage space in bytes.",=0A= - type =3D> 'integer',=0A= - renderer =3D> 'bytes',=0A= - optional =3D> 1,=0A= - },=0A= - used =3D> {=0A= - description =3D> "Used storage space in bytes.",=0A= - type =3D> 'integer',=0A= - renderer =3D> 'bytes',=0A= - optional =3D> 1,=0A= - },=0A= - avail =3D> {=0A= - description =3D> "Available storage space in bytes.",= =0A= - type =3D> 'integer',=0A= - renderer =3D> 'bytes',=0A= - optional =3D> 1,=0A= - },=0A= - used_fraction =3D> {=0A= - description =3D> "Used fraction (used/total).",=0A= - type =3D> 'number',=0A= - renderer =3D> 'fraction_as_percentage',=0A= - optional =3D> 1,=0A= - },=0A= - select_existing =3D> {=0A= - description =3D> "Instead of creating new volumes, one= must select one that"=0A= - . " is already existing. Only included if 'format'= parameter is set.",=0A= - type =3D> 'boolean',=0A= - optional =3D> 1,=0A= - },=0A= -=0A= - # FIXME: remove with 10.0=0A= - # we can't include this return schema, since we cannot pro= perly=0A= - # describe what it actually is with the json schema:=0A= - #=0A= - # a tuple in form of an array where the first element is a= n=0A= - # object, and the second is a string.=0A= - #format =3D> {=0A= - # description =3D> "Lists the supported and default for= mat."=0A= - # . "Deprecated (use 'formats' instead). Only inclu= ded "=0A= - # . "if 'format' parameter is set.",=0A= - # optional =3D> 1,=0A= - #},=0A= - formats =3D> {=0A= - description =3D> "Lists the supported and default form= at. Use"=0A= - . " 'formats' instead. Only included if 'format' p= arameter is set.",=0A= - optional =3D> 1,=0A= - type =3D> 'object',=0A= - properties =3D> {=0A= - supported =3D> {=0A= - type =3D> 'array',=0A= - description =3D> 'The list of supported format= s',=0A= - items =3D> {=0A= - type =3D> 'string',=0A= - enum =3D> [qw(qcow2 raw subvol vmdk)],=0A= - },=0A= - },=0A= - default =3D> {=0A= - description =3D> "The default format of the st= orage.",=0A= - type =3D> 'string',=0A= - enum =3D> [qw(qcow2 raw subvol vmdk)],=0A= - },=0A= - },=0A= - },=0A= - },=0A= - },=0A= - links =3D> [{ rel =3D> 'child', href =3D> "{storage}" }],=0A= - },=0A= - code =3D> sub {=0A= - my ($param) =3D @_;=0A= -=0A= - my $rpcenv =3D PVE::RPCEnvironment::get();=0A= - my $authuser =3D $rpcenv->get_user();=0A= -=0A= - my $localnode =3D PVE::INotify::nodename();=0A= -=0A= - my $target =3D $param->{target};=0A= -=0A= - undef $target if $target && ($target eq $localnode || $target eq '= localhost');=0A= -=0A= - my $cfg =3D PVE::Storage::config();=0A= -=0A= - my $info =3D PVE::Storage::storage_info($cfg, $param->{content}, $= param->{format});=0A= -=0A= - raise_param_exc({ storage =3D> "No such storage." })=0A= - if $param->{storage} && !defined($info->{ $param->{storage} })= ;=0A= -=0A= - my $res =3D {};=0A= - my @sids =3D PVE::Storage::storage_ids($cfg);=0A= - foreach my $storeid (@sids) {=0A= - my $data =3D $info->{$storeid};=0A= - next if !$data;=0A= - my $privs =3D ['Datastore.Audit', 'Datastore.AllocateSpace'];= =0A= - next if !$rpcenv->check_any($authuser, "/storage/$storeid", $p= rivs, 1);=0A= - next if $param->{storage} && $param->{storage} ne $storeid;=0A= -=0A= - my $scfg =3D PVE::Storage::storage_config($cfg, $storeid);=0A= -=0A= - next if $param->{enabled} && $scfg->{disable};=0A= -=0A= - if ($target) {=0A= - # check if storage content is accessible on local node and= specified target node=0A= - # we use this on the Clone GUI=0A= -=0A= - next if !$scfg->{shared};=0A= - next if !PVE::Storage::storage_check_node($cfg, $storeid, = undef, 1);=0A= - next if !PVE::Storage::storage_check_node($cfg, $storeid, = $target, 1);=0A= - }=0A= -=0A= - if ($data->{total}) {=0A= - # extract variables, otherwise the division converts used = and total to floating points=0A= - my $total =3D $data->{total};=0A= - my $used =3D $data->{used} // 0;=0A= - $data->{used_fraction} =3D $used / $total;=0A= - }=0A= -=0A= - $res->{$storeid} =3D $data;=0A= - }=0A= -=0A= - return PVE::RESTHandler::hash_to_array($res, 'storage');=0A= - },=0A= + name =3D> 'index',=0A= + path =3D> '',=0A= + method =3D> 'GET',=0A= + description =3D> "Get status for all datastores.",=0A= + permissions =3D> {=0A= + description =3D>=0A= + "Only list entries where you have 'Datastore.Audit' or 'Datastore.Allocat= eSpace' permissions on '/storage/'",=0A= + user =3D> 'all',=0A= + },=0A= + protected =3D> 1,=0A= + proxyto =3D> 'node',=0A= + parameters =3D> {=0A= + additionalProperties =3D> 0,=0A= + properties =3D> {=0A= + node =3D> get_standard_option('pve-node'),=0A= + storage =3D> get_standard_option(=0A= + 'pve-storage-id',=0A= + {=0A= + description =3D> "Only list status for specified storage",=0A= + optional =3D> 1,=0A= + completion =3D> \&PVE::Storage::complete_storage_enabled,=0A= + },=0A= + ),=0A= + content =3D> {=0A= + description =3D> "Only list stores which support this content type.",=0A= + type =3D> 'string',=0A= + format =3D> 'pve-storage-content-list',=0A= + optional =3D> 1,=0A= + completion =3D> \&PVE::Storage::complete_content_type,=0A= + },=0A= + enabled =3D> {=0A= + description =3D> "Only list stores which are enabled (not disabled in con= fig).",=0A= + type =3D> 'boolean',=0A= + optional =3D> 1,=0A= + default =3D> 0,=0A= + },=0A= + target =3D> get_standard_option(=0A= + 'pve-node',=0A= + {=0A= + description =3D>=0A= + "If target is different to 'node', we only lists shared storages which "= =0A= + . "content is accessible on this 'node' and the specified 'target' node."= ,=0A= + optional =3D> 1,=0A= + completion =3D> \&PVE::Cluster::get_nodelist,=0A= + },=0A= + ),=0A= + 'format' =3D> {=0A= + description =3D> "Include information about formats",=0A= + type =3D> 'boolean',=0A= + optional =3D> 1,=0A= + default =3D> 0,=0A= + },=0A= + },=0A= + },=0A= + returns =3D> {=0A= + type =3D> 'array',=0A= + items =3D> {=0A= + type =3D> "object",=0A= + properties =3D> {=0A= + storage =3D> get_standard_option('pve-storage-id'),=0A= + type =3D> {=0A= + description =3D> "Storage type.",=0A= + type =3D> 'string',=0A= + },=0A= + content =3D> {=0A= + description =3D> "Allowed storage content types.",=0A= + type =3D> 'string',=0A= + format =3D> 'pve-storage-content-list',=0A= + },=0A= + enabled =3D> {=0A= + description =3D> "Set when storage is enabled (not disabled).",=0A= + type =3D> 'boolean',=0A= + optional =3D> 1,=0A= + },=0A= + active =3D> {=0A= + description =3D> "Set when storage is accessible.",=0A= + type =3D> 'boolean',=0A= + optional =3D> 1,=0A= + },=0A= + shared =3D> {=0A= + description =3D> "Shared flag from storage configuration.",=0A= + type =3D> 'boolean',=0A= + optional =3D> 1,=0A= + },=0A= + total =3D> {=0A= + description =3D> "Total storage space in bytes.",=0A= + type =3D> 'integer',=0A= + renderer =3D> 'bytes',=0A= + optional =3D> 1,=0A= + },=0A= + used =3D> {=0A= + description =3D> "Used storage space in bytes.",=0A= + type =3D> 'integer',=0A= + renderer =3D> 'bytes',=0A= + optional =3D> 1,=0A= + },=0A= + avail =3D> {=0A= + description =3D> "Available storage space in bytes.",=0A= + type =3D> 'integer',=0A= + renderer =3D> 'bytes',=0A= + optional =3D> 1,=0A= + },=0A= + used_fraction =3D> {=0A= + description =3D> "Used fraction (used/total).",=0A= + type =3D> 'number',=0A= + renderer =3D> 'fraction_as_percentage',=0A= + optional =3D> 1,=0A= + },=0A= + select_existing =3D> {=0A= + description =3D> "Instead of creating new volumes, one must select one th= at"=0A= + . " is already existing. Only included if 'format' parameter is set.",=0A= + type =3D> 'boolean',=0A= + optional =3D> 1,=0A= + },=0A= +=0A= + # FIXME: remove with 10.0=0A= + # we can't include this return schema, since we cannot properly=0A= + # describe what it actually is with the json schema:=0A= + #=0A= + # a tuple in form of an array where the first element is an=0A= + # object, and the second is a string.=0A= + #format =3D> {=0A= + # description =3D> "Lists the supported and default format."=0A= + # . "Deprecated (use 'formats' instead). Only included "=0A= + # . "if 'format' parameter is set.",=0A= + # optional =3D> 1,=0A= + #},=0A= + formats =3D> {=0A= + description =3D> "Lists the supported and default format. Use"=0A= + . " 'formats' instead. Only included if 'format' parameter is set.",=0A= + optional =3D> 1,=0A= + type =3D> 'object',=0A= + properties =3D> {=0A= + supported =3D> {=0A= + type =3D> 'array',=0A= + description =3D> 'The list of supported formats',=0A= + items =3D> {=0A= + type =3D> 'string',=0A= + enum =3D> [qw(qcow2 raw subvol vmdk)],=0A= + },=0A= + },=0A= + default =3D> {=0A= + description =3D> "The default format of the storage.",=0A= + type =3D> 'string',=0A= + enum =3D> [qw(qcow2 raw subvol vmdk)],=0A= + },=0A= + },=0A= + },=0A= + },=0A= + },=0A= + links =3D> [{ rel =3D> 'child', href =3D> "{storage}" }],=0A= + },=0A= + code =3D> sub {=0A= + my ($param) =3D @_;=0A= +=0A= + my $rpcenv =3D PVE::RPCEnvironment::get();=0A= + my $authuser =3D $rpcenv->get_user();=0A= +=0A= + my $localnode =3D PVE::INotify::nodename();=0A= +=0A= + my $target =3D $param->{target};=0A= +=0A= + undef $target if $target && ($target eq $localnode || $target eq 'localho= st');=0A= +=0A= + my $cfg =3D PVE::Storage::config();=0A= +=0A= + my $info =3D PVE::Storage::storage_info($cfg, $param->{content}, $param->= {format});=0A= +=0A= + raise_param_exc({ storage =3D> "No such storage." })=0A= + if $param->{storage} && !defined($info->{ $param->{storage} });=0A= +=0A= + my $res =3D {};=0A= + my @sids =3D PVE::Storage::storage_ids($cfg);=0A= + foreach my $storeid (@sids) {=0A= + my $data =3D $info->{$storeid};=0A= + next if !$data;=0A= + my $privs =3D ['Datastore.Audit', 'Datastore.AllocateSpace'];=0A= + next if !$rpcenv->check_any($authuser, "/storage/$storeid", $privs, 1);= =0A= + next if $param->{storage} && $param->{storage} ne $storeid;=0A= +=0A= + my $scfg =3D PVE::Storage::storage_config($cfg, $storeid);=0A= +=0A= + next if $param->{enabled} && $scfg->{disable};=0A= +=0A= + if ($target) {=0A= + # check if storage content is accessible on local node and specified targ= et node=0A= + # we use this on the Clone GUI=0A= +=0A= + next if !$scfg->{shared};=0A= + next if !PVE::Storage::storage_check_node($cfg, $storeid, undef, 1);=0A= + next if !PVE::Storage::storage_check_node($cfg, $storeid, $target, 1);=0A= + }=0A= +=0A= + if ($data->{total}) {=0A= + # extract variables, otherwise the division converts used and total to fl= oating points=0A= + my $total =3D $data->{total};=0A= + my $used =3D $data->{used} // 0;=0A= + $data->{used_fraction} =3D $used / $total;=0A= + }=0A= +=0A= + $res->{$storeid} =3D $data;=0A= + }=0A= +=0A= + return PVE::RESTHandler::hash_to_array($res, 'storage');=0A= + },=0A= });=0A= =0A= __PACKAGE__->register_method({=0A= - name =3D> 'diridx',=0A= - path =3D> '{storage}',=0A= - method =3D> 'GET',=0A= - description =3D> "",=0A= - permissions =3D> {=0A= - check =3D> [=0A= - 'perm',=0A= - '/storage/{storage}',=0A= - ['Datastore.Audit', 'Datastore.AllocateSpace'],=0A= - any =3D> 1,=0A= - ],=0A= - },=0A= - parameters =3D> {=0A= - additionalProperties =3D> 0,=0A= - properties =3D> {=0A= - node =3D> get_standard_option('pve-node'),=0A= - storage =3D> get_standard_option('pve-storage-id'),=0A= - },=0A= - },=0A= - returns =3D> {=0A= - type =3D> 'array',=0A= - items =3D> {=0A= - type =3D> "object",=0A= - properties =3D> {=0A= - subdir =3D> { type =3D> 'string' },=0A= - },=0A= - },=0A= - links =3D> [{ rel =3D> 'child', href =3D> "{subdir}" }],=0A= - },=0A= - code =3D> sub {=0A= - my ($param) =3D @_;=0A= -=0A= - my $res =3D [=0A= - { subdir =3D> 'content' },=0A= - { subdir =3D> 'download-url' },=0A= - { subdir =3D> 'file-restore' },=0A= - { subdir =3D> 'import-metadata' },=0A= - { subdir =3D> 'identity' },=0A= - { subdir =3D> 'oci-registry-pull' },=0A= - { subdir =3D> 'prunebackups' },=0A= - { subdir =3D> 'rrd' },=0A= - { subdir =3D> 'rrddata' },=0A= - { subdir =3D> 'status' },=0A= - { subdir =3D> 'upload' },=0A= - ];=0A= -=0A= - return $res;=0A= - },=0A= + name =3D> 'diridx',=0A= + path =3D> '{storage}',=0A= + method =3D> 'GET',=0A= + description =3D> "",=0A= + permissions =3D> {=0A= + check =3D> [=0A= + 'perm',=0A= + '/storage/{storage}',=0A= + ['Datastore.Audit', 'Datastore.AllocateSpace'],=0A= + any =3D> 1,=0A= + ],=0A= + },=0A= + parameters =3D> {=0A= + additionalProperties =3D> 0,=0A= + properties =3D> {=0A= + node =3D> get_standard_option('pve-node'),=0A= + storage =3D> get_standard_option('pve-storage-id'),=0A= + },=0A= + },=0A= + returns =3D> {=0A= + type =3D> 'array',=0A= + items =3D> {=0A= + type =3D> "object",=0A= + properties =3D> {=0A= + subdir =3D> { type =3D> 'string' },=0A= + },=0A= + },=0A= + links =3D> [{ rel =3D> 'child', href =3D> "{subdir}" }],=0A= + },=0A= + code =3D> sub {=0A= + my ($param) =3D @_;=0A= +=0A= + my $res =3D [=0A= + { subdir =3D> 'content' },=0A= + { subdir =3D> 'download-url' },=0A= + { subdir =3D> 'file-restore' },=0A= + { subdir =3D> 'import-metadata' },=0A= + { subdir =3D> 'identity' },=0A= + { subdir =3D> 'oci-registry-pull' },=0A= + { subdir =3D> 'prunebackups' },=0A= + { subdir =3D> 'rrd' },=0A= + { subdir =3D> 'rrddata' },=0A= + { subdir =3D> 'status' },=0A= + { subdir =3D> 'upload' },=0A= + ];=0A= +=0A= + return $res;=0A= + },=0A= });=0A= =0A= __PACKAGE__->register_method({=0A= - name =3D> 'read_status',=0A= - path =3D> '{storage}/status',=0A= - method =3D> 'GET',=0A= - description =3D> "Read storage status.",=0A= - permissions =3D> {=0A= - check =3D> [=0A= - 'perm',=0A= - '/storage/{storage}',=0A= - ['Datastore.Audit', 'Datastore.AllocateSpace'],=0A= - any =3D> 1,=0A= - ],=0A= - },=0A= - protected =3D> 1,=0A= - proxyto =3D> 'node',=0A= - parameters =3D> {=0A= - additionalProperties =3D> 0,=0A= - properties =3D> {=0A= - node =3D> get_standard_option('pve-node'),=0A= - storage =3D> get_standard_option('pve-storage-id'),=0A= - },=0A= - },=0A= - returns =3D> {=0A= - type =3D> "object",=0A= - properties =3D> {=0A= - type =3D> {=0A= - description =3D> "Storage type.",=0A= - type =3D> 'string',=0A= - },=0A= - content =3D> {=0A= - description =3D> "Allowed storage content types.",=0A= - type =3D> 'string',=0A= - format =3D> 'pve-storage-content-list',=0A= - },=0A= - enabled =3D> {=0A= - description =3D> "Set when storage is enabled (not disable= d).",=0A= - type =3D> 'boolean',=0A= - optional =3D> 1,=0A= - },=0A= - active =3D> {=0A= - description =3D> "Set when storage is accessible.",=0A= - type =3D> 'boolean',=0A= - optional =3D> 1,=0A= - },=0A= - shared =3D> {=0A= - description =3D> "Shared flag from storage configuration."= ,=0A= - type =3D> 'boolean',=0A= - optional =3D> 1,=0A= - },=0A= - total =3D> {=0A= - description =3D> "Total storage space in bytes.",=0A= - type =3D> 'integer',=0A= - renderer =3D> 'bytes',=0A= - optional =3D> 1,=0A= - },=0A= - used =3D> {=0A= - description =3D> "Used storage space in bytes.",=0A= - type =3D> 'integer',=0A= - renderer =3D> 'bytes',=0A= - optional =3D> 1,=0A= - },=0A= - avail =3D> {=0A= - description =3D> "Available storage space in bytes.",=0A= - type =3D> 'integer',=0A= - renderer =3D> 'bytes',=0A= - optional =3D> 1,=0A= - },=0A= - },=0A= - },=0A= - code =3D> sub {=0A= - my ($param) =3D @_;=0A= -=0A= - my $cfg =3D PVE::Storage::config();=0A= -=0A= - my $info =3D PVE::Storage::storage_info($cfg, $param->{content});= =0A= -=0A= - my $data =3D $info->{ $param->{storage} };=0A= -=0A= - raise_param_exc({ storage =3D> "No such storage." })=0A= - if !defined($data);=0A= -=0A= - return $data;=0A= - },=0A= + name =3D> 'read_status',=0A= + path =3D> '{storage}/status',=0A= + method =3D> 'GET',=0A= + description =3D> "Read storage status.",=0A= + permissions =3D> {=0A= + check =3D> [=0A= + 'perm',=0A= + '/storage/{storage}',=0A= + ['Datastore.Audit', 'Datastore.AllocateSpace'],=0A= + any =3D> 1,=0A= + ],=0A= + },=0A= + protected =3D> 1,=0A= + proxyto =3D> 'node',=0A= + parameters =3D> {=0A= + additionalProperties =3D> 0,=0A= + properties =3D> {=0A= + node =3D> get_standard_option('pve-node'),=0A= + storage =3D> get_standard_option('pve-storage-id'),=0A= + },=0A= + },=0A= + returns =3D> {=0A= + type =3D> "object",=0A= + properties =3D> {=0A= + type =3D> {=0A= + description =3D> "Storage type.",=0A= + type =3D> 'string',=0A= + },=0A= + content =3D> {=0A= + description =3D> "Allowed storage content types.",=0A= + type =3D> 'string',=0A= + format =3D> 'pve-storage-content-list',=0A= + },=0A= + enabled =3D> {=0A= + description =3D> "Set when storage is enabled (not disabled).",=0A= + type =3D> 'boolean',=0A= + optional =3D> 1,=0A= + },=0A= + active =3D> {=0A= + description =3D> "Set when storage is accessible.",=0A= + type =3D> 'boolean',=0A= + optional =3D> 1,=0A= + },=0A= + shared =3D> {=0A= + description =3D> "Shared flag from storage configuration.",=0A= + type =3D> 'boolean',=0A= + optional =3D> 1,=0A= + },=0A= + total =3D> {=0A= + description =3D> "Total storage space in bytes.",=0A= + type =3D> 'integer',=0A= + renderer =3D> 'bytes',=0A= + optional =3D> 1,=0A= + },=0A= + used =3D> {=0A= + description =3D> "Used storage space in bytes.",=0A= + type =3D> 'integer',=0A= + renderer =3D> 'bytes',=0A= + optional =3D> 1,=0A= + },=0A= + avail =3D> {=0A= + description =3D> "Available storage space in bytes.",=0A= + type =3D> 'integer',=0A= + renderer =3D> 'bytes',=0A= + optional =3D> 1,=0A= + },=0A= + },=0A= + },=0A= + code =3D> sub {=0A= + my ($param) =3D @_;=0A= +=0A= + my $cfg =3D PVE::Storage::config();=0A= +=0A= + my $info =3D PVE::Storage::storage_info($cfg, $param->{content});=0A= +=0A= + my $data =3D $info->{ $param->{storage} };=0A= +=0A= + raise_param_exc({ storage =3D> "No such storage." })=0A= + if !defined($data);=0A= +=0A= + return $data;=0A= + },=0A= });=0A= =0A= __PACKAGE__->register_method({=0A= - name =3D> 'rrd',=0A= - path =3D> '{storage}/rrd',=0A= - method =3D> 'GET',=0A= - description =3D> "Read storage RRD statistics (returns PNG).",=0A= - permissions =3D> {=0A= - check =3D> [=0A= - 'perm',=0A= - '/storage/{storage}',=0A= - ['Datastore.Audit', 'Datastore.AllocateSpace'],=0A= - any =3D> 1,=0A= - ],=0A= - },=0A= - protected =3D> 1,=0A= - proxyto =3D> 'node',=0A= - parameters =3D> {=0A= - additionalProperties =3D> 0,=0A= - properties =3D> {=0A= - node =3D> get_standard_option('pve-node'),=0A= - storage =3D> get_standard_option('pve-storage-id'),=0A= - timeframe =3D> {=0A= - description =3D> "Specify the time frame you are intereste= d in.",=0A= - type =3D> 'string',=0A= - enum =3D> ['hour', 'day', 'week', 'month', 'year'],=0A= - },=0A= - ds =3D> {=0A= - description =3D> "The list of datasources you want to disp= lay.",=0A= - type =3D> 'string',=0A= - format =3D> 'pve-configid-list',=0A= - },=0A= - cf =3D> {=0A= - description =3D> "The RRD consolidation function",=0A= - type =3D> 'string',=0A= - enum =3D> ['AVERAGE', 'MAX'],=0A= - optional =3D> 1,=0A= - },=0A= - },=0A= - },=0A= - returns =3D> {=0A= - type =3D> "object",=0A= - properties =3D> {=0A= - filename =3D> { type =3D> 'string' },=0A= - },=0A= - },=0A= - code =3D> sub {=0A= - my ($param) =3D @_;=0A= -=0A= - return PVE::RRD::create_rrd_graph(=0A= - "pve2-storage/$param->{node}/$param->{storage}",=0A= - $param->{timeframe}, $param->{ds}, $param->{cf},=0A= - );=0A= - },=0A= + name =3D> 'rrd',=0A= + path =3D> '{storage}/rrd',=0A= + method =3D> 'GET',=0A= + description =3D> "Read storage RRD statistics (returns PNG).",=0A= + permissions =3D> {=0A= + check =3D> [=0A= + 'perm',=0A= + '/storage/{storage}',=0A= + ['Datastore.Audit', 'Datastore.AllocateSpace'],=0A= + any =3D> 1,=0A= + ],=0A= + },=0A= + protected =3D> 1,=0A= + proxyto =3D> 'node',=0A= + parameters =3D> {=0A= + additionalProperties =3D> 0,=0A= + properties =3D> {=0A= + node =3D> get_standard_option('pve-node'),=0A= + storage =3D> get_standard_option('pve-storage-id'),=0A= + timeframe =3D> {=0A= + description =3D> "Specify the time frame you are interested in.",=0A= + type =3D> 'string',=0A= + enum =3D> ['hour', 'day', 'week', 'month', 'year'],=0A= + },=0A= + ds =3D> {=0A= + description =3D> "The list of datasources you want to display.",=0A= + type =3D> 'string',=0A= + format =3D> 'pve-configid-list',=0A= + },=0A= + cf =3D> {=0A= + description =3D> "The RRD consolidation function",=0A= + type =3D> 'string',=0A= + enum =3D> ['AVERAGE', 'MAX'],=0A= + optional =3D> 1,=0A= + },=0A= + },=0A= + },=0A= + returns =3D> {=0A= + type =3D> "object",=0A= + properties =3D> {=0A= + filename =3D> { type =3D> 'string' },=0A= + },=0A= + },=0A= + code =3D> sub {=0A= + my ($param) =3D @_;=0A= +=0A= + return PVE::RRD::create_rrd_graph(=0A= + "pve2-storage/$param->{node}/$param->{storage}",=0A= + $param->{timeframe}, $param->{ds}, $param->{cf},=0A= + );=0A= + },=0A= });=0A= =0A= __PACKAGE__->register_method({=0A= - name =3D> 'rrddata',=0A= - path =3D> '{storage}/rrddata',=0A= - method =3D> 'GET',=0A= - description =3D> "Read storage RRD statistics.",=0A= - permissions =3D> {=0A= - check =3D> [=0A= - 'perm',=0A= - '/storage/{storage}',=0A= - ['Datastore.Audit', 'Datastore.AllocateSpace'],=0A= - any =3D> 1,=0A= - ],=0A= - },=0A= - protected =3D> 1,=0A= - proxyto =3D> 'node',=0A= - parameters =3D> {=0A= - additionalProperties =3D> 0,=0A= - properties =3D> {=0A= - node =3D> get_standard_option('pve-node'),=0A= - storage =3D> get_standard_option('pve-storage-id'),=0A= - timeframe =3D> {=0A= - description =3D> "Specify the time frame you are intereste= d in.",=0A= - type =3D> 'string',=0A= - enum =3D> ['hour', 'day', 'week', 'month', 'year'],=0A= - },=0A= - cf =3D> {=0A= - description =3D> "The RRD consolidation function",=0A= - type =3D> 'string',=0A= - enum =3D> ['AVERAGE', 'MAX'],=0A= - optional =3D> 1,=0A= - },=0A= - },=0A= - },=0A= - returns =3D> {=0A= - type =3D> "array",=0A= - items =3D> {=0A= - type =3D> "object",=0A= - properties =3D> {},=0A= - },=0A= - },=0A= - code =3D> sub {=0A= - my ($param) =3D @_;=0A= -=0A= - return PVE::RRD::create_rrd_data(=0A= - "pve-storage-9.0/$param->{node}/$param->{storage}",=0A= - $param->{timeframe},=0A= - $param->{cf},=0A= - );=0A= - },=0A= + name =3D> 'rrddata',=0A= + path =3D> '{storage}/rrddata',=0A= + method =3D> 'GET',=0A= + description =3D> "Read storage RRD statistics.",=0A= + permissions =3D> {=0A= + check =3D> [=0A= + 'perm',=0A= + '/storage/{storage}',=0A= + ['Datastore.Audit', 'Datastore.AllocateSpace'],=0A= + any =3D> 1,=0A= + ],=0A= + },=0A= + protected =3D> 1,=0A= + proxyto =3D> 'node',=0A= + parameters =3D> {=0A= + additionalProperties =3D> 0,=0A= + properties =3D> {=0A= + node =3D> get_standard_option('pve-node'),=0A= + storage =3D> get_standard_option('pve-storage-id'),=0A= + timeframe =3D> {=0A= + description =3D> "Specify the time frame you are interested in.",=0A= + type =3D> 'string',=0A= + enum =3D> ['hour', 'day', 'week', 'month', 'year'],=0A= + },=0A= + cf =3D> {=0A= + description =3D> "The RRD consolidation function",=0A= + type =3D> 'string',=0A= + enum =3D> ['AVERAGE', 'MAX'],=0A= + optional =3D> 1,=0A= + },=0A= + },=0A= + },=0A= + returns =3D> {=0A= + type =3D> "array",=0A= + items =3D> {=0A= + type =3D> "object",=0A= + properties =3D> {},=0A= + },=0A= + },=0A= + code =3D> sub {=0A= + my ($param) =3D @_;=0A= +=0A= + return PVE::RRD::create_rrd_data(=0A= + "pve-storage-9.0/$param->{node}/$param->{storage}",=0A= + $param->{timeframe},=0A= + $param->{cf},=0A= + );=0A= + },=0A= });=0A= =0A= # makes no sense for big images and backup files (because it=0A= # create a copy of the file).=0A= __PACKAGE__->register_method({=0A= - name =3D> 'upload',=0A= - path =3D> '{storage}/upload',=0A= - method =3D> 'POST',=0A= - description =3D> "Upload templates, ISO images, OVAs and VM images.",= =0A= - permissions =3D> {=0A= - check =3D> ['perm', '/storage/{storage}', ['Datastore.AllocateTemp= late']],=0A= - },=0A= - protected =3D> 1,=0A= - parameters =3D> {=0A= - additionalProperties =3D> 0,=0A= - properties =3D> {=0A= - node =3D> get_standard_option('pve-node'),=0A= - storage =3D> get_standard_option('pve-storage-id'),=0A= - content =3D> {=0A= - description =3D> "Content type.",=0A= - type =3D> 'string',=0A= - format =3D> 'pve-storage-content',=0A= - enum =3D> ['iso', 'vztmpl', 'import'],=0A= - },=0A= - filename =3D> {=0A= - description =3D>=0A= - "The name of the file to create. Caution: This will be= normalized!",=0A= - maxLength =3D> 255,=0A= - type =3D> 'string',=0A= - },=0A= - checksum =3D> {=0A= - description =3D> "The expected checksum of the file.",=0A= - type =3D> 'string',=0A= - requires =3D> 'checksum-algorithm',=0A= - optional =3D> 1,=0A= - },=0A= - 'checksum-algorithm' =3D> {=0A= - description =3D> "The algorithm to calculate the checksum = of the file.",=0A= - type =3D> 'string',=0A= - enum =3D> ['md5', 'sha1', 'sha224', 'sha256', 'sha384', 's= ha512'],=0A= - requires =3D> 'checksum',=0A= - optional =3D> 1,=0A= - },=0A= - tmpfilename =3D> {=0A= - description =3D>=0A= - "The source file name. This parameter is usually set b= y the REST handler. You can only overwrite it when connecting to the truste= d port on localhost.",=0A= - type =3D> 'string',=0A= - optional =3D> 1,=0A= - pattern =3D> '/var/tmp/pveupload-[0-9a-f]+',=0A= - },=0A= - },=0A= - },=0A= - returns =3D> { type =3D> "string" },=0A= - code =3D> sub {=0A= - my ($param) =3D @_;=0A= -=0A= - my $rpcenv =3D PVE::RPCEnvironment::get();=0A= -=0A= - my $user =3D $rpcenv->get_user();=0A= -=0A= - my $cfg =3D PVE::Storage::config();=0A= -=0A= - my ($node, $storage) =3D $param->@{qw(node storage)};=0A= - my $scfg =3D PVE::Storage::storage_check_enabled($cfg, $storage, $= node);=0A= -=0A= - die "can't upload to storage type '$scfg->{type}'\n"=0A= - if !defined($scfg->{path});=0A= -=0A= - my $content =3D $param->{content};=0A= -=0A= - my $tmpfilename =3D $param->{tmpfilename};=0A= - die "missing temporary file name\n" if !$tmpfilename;=0A= -=0A= - my $size =3D -s $tmpfilename;=0A= - die "temporary file '$tmpfilename' does not exist\n" if !defined($= size);=0A= -=0A= - my $filename =3D PVE::Storage::normalize_content_filename($param->= {filename});=0A= -=0A= - my $path;=0A= - my $is_ova =3D 0;=0A= - my $image_format;=0A= -=0A= - if ($content eq 'iso') {=0A= - if ($filename !~ m![^/]+$PVE::Storage::ISO_EXT_RE_0$!) {=0A= - raise_param_exc({ filename =3D> "wrong file extension" });= =0A= - }=0A= - $path =3D PVE::Storage::get_iso_dir($cfg, $storage);=0A= - } elsif ($content eq 'vztmpl') {=0A= - if ($filename !~ m![^/]+$PVE::Storage::VZTMPL_EXT_RE_1$!) {=0A= - raise_param_exc({ filename =3D> "wrong file extension" });= =0A= - }=0A= - $path =3D PVE::Storage::get_vztmpl_dir($cfg, $storage);=0A= - } elsif ($content eq 'import') {=0A= - if ($filename !~=0A= - m!${PVE::Storage::SAFE_CHAR_CLASS_RE}+$PVE::Storage::UPLOA= D_IMPORT_EXT_RE_1$!=0A= - ) {=0A= - raise_param_exc({ filename =3D> "invalid filename or wrong= extension" });=0A= - }=0A= - my $format =3D $1;=0A= -=0A= - if ($format eq 'ova') {=0A= - $is_ova =3D 1;=0A= - } else {=0A= - $image_format =3D $format;=0A= - }=0A= -=0A= - $path =3D PVE::Storage::get_import_dir($cfg, $storage);=0A= - } else {=0A= - raise_param_exc({ content =3D> "upload content type '$content'= not allowed" });=0A= - }=0A= -=0A= - die "storage '$storage' does not support '$content' content\n"=0A= - if !$scfg->{content}->{$content};=0A= -=0A= - my $dest =3D "$path/$filename";=0A= - my $dirname =3D dirname($dest);=0A= -=0A= - # best effort to match apl_download behaviour=0A= - chmod 0644, $tmpfilename;=0A= -=0A= - my $err_cleanup =3D sub { unlink $dest or $! =3D=3D ENOENT or die = "cleanup failed: $!\n" };=0A= -=0A= - my $cmd;=0A= - if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {= =0A= - my $remip =3D PVE::Cluster::remote_node_ip($node);=0A= -=0A= - my $ssh_options =3D=0A= - PVE::SSHInfo::ssh_info_to_ssh_opts({ ip =3D> $remip, name = =3D> $node });=0A= -=0A= - my @remcmd =3D ('/usr/bin/ssh', $ssh_options->@*, $remip, '--'= );=0A= -=0A= - eval { # activate remote storage=0A= - run_command([@remcmd, '/usr/sbin/pvesm', 'status', '--stor= age', $storage]);=0A= - };=0A= - die "can't activate storage '$storage' on node '$node': $@\n" = if $@;=0A= -=0A= - run_command(=0A= - [@remcmd, '/bin/mkdir', '-p', '--', PVE::Tools::shell_quot= e($dirname)],=0A= - errmsg =3D> "mkdir failed",=0A= - );=0A= -=0A= - $cmd =3D [=0A= - '/usr/bin/scp',=0A= - $ssh_options->@*,=0A= - '-p',=0A= - '--',=0A= - $tmpfilename,=0A= - "[$remip]:" . PVE::Tools::shell_quote($dest),=0A= - ];=0A= -=0A= - $err_cleanup =3D sub { run_command([@remcmd, 'rm', '-f', '--',= $dest]) };=0A= - } else {=0A= - PVE::Storage::activate_storage($cfg, $storage);=0A= - File::Path::make_path($dirname);=0A= - $cmd =3D ['cp', '--', $tmpfilename, $dest];=0A= - }=0A= -=0A= - # NOTE: we simply overwrite the destination file if it already exi= sts=0A= - my $worker =3D sub {=0A= - my $upid =3D shift;=0A= -=0A= - print "starting file import from: $tmpfilename\n";=0A= -=0A= - eval {=0A= - my ($checksum, $checksum_algorithm) =3D=0A= - $param->@{ 'checksum', 'checksum-algorithm' };=0A= - if ($checksum_algorithm) {=0A= - print "calculating checksum...";=0A= -=0A= - my $checksum_got =3D=0A= - PVE::Tools::get_file_hash($checksum_algorithm, $tm= pfilename);=0A= -=0A= - if (lc($checksum_got) eq lc($checksum)) {=0A= - print "OK, checksum verified\n";=0A= - } else {=0A= - print "\n"; # the front end expects the error to r= eside at the last line without any noise=0A= - die "checksum mismatch: got '$checksum_got' !=3D e= xpect '$checksum'\n";=0A= - }=0A= - }=0A= -=0A= - if ($content eq 'iso') {=0A= - PVE::Storage::assert_iso_content($tmpfilename);=0A= - }=0A= -=0A= - if ($is_ova) {=0A= - assert_ova_contents($tmpfilename);=0A= - } elsif (defined($image_format)) {=0A= - # checks untrusted image=0A= - PVE::Storage::file_size_info($tmpfilename, 10, $image_= format, 1);=0A= - }=0A= - };=0A= - if (my $err =3D $@) {=0A= - # unlinks only the temporary file from the http server=0A= - unlink $tmpfilename=0A= - or $! =3D=3D ENOENT=0A= - or warn "unable to clean up temporory file '$tmpfilena= me' - $!\n";=0A= - die $err;=0A= - }=0A= -=0A= - print "target node: $node\n";=0A= - print "target file: $dest\n";=0A= - print "file size is: $size\n";=0A= - print "command: " . join(' ', @$cmd) . "\n";=0A= -=0A= - eval { run_command($cmd, errmsg =3D> 'import failed'); };=0A= -=0A= - # the temporary file got only uploaded locally, no need to rm = remote=0A= - unlink $tmpfilename=0A= - or $! =3D=3D ENOENT=0A= - or warn "unable to clean up temporary file '$tmpfilename' = - $!\n";=0A= -=0A= - if (my $err =3D $@) {=0A= - eval { $err_cleanup->() };=0A= - warn "$@" if $@;=0A= - die $err;=0A= - }=0A= - print "finished file import successfully\n";=0A= - };=0A= -=0A= - return $rpcenv->fork_worker('imgcopy', undef, $user, $worker);=0A= - },=0A= + name =3D> 'upload',=0A= + path =3D> '{storage}/upload',=0A= + method =3D> 'POST',=0A= + description =3D> "Upload templates, ISO images, OVAs and VM images.",=0A= + permissions =3D> {=0A= + check =3D> ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']]= ,=0A= + },=0A= + protected =3D> 1,=0A= + parameters =3D> {=0A= + additionalProperties =3D> 0,=0A= + properties =3D> {=0A= + node =3D> get_standard_option('pve-node'),=0A= + storage =3D> get_standard_option('pve-storage-id'),=0A= + content =3D> {=0A= + description =3D> "Content type.",=0A= + type =3D> 'string',=0A= + format =3D> 'pve-storage-content',=0A= + enum =3D> ['iso', 'vztmpl', 'import'],=0A= + },=0A= + filename =3D> {=0A= + description =3D>=0A= + "The name of the file to create. Caution: This will be normalized!",=0A= + maxLength =3D> 255,=0A= + type =3D> 'string',=0A= + },=0A= + checksum =3D> {=0A= + description =3D> "The expected checksum of the file.",=0A= + type =3D> 'string',=0A= + requires =3D> 'checksum-algorithm',=0A= + optional =3D> 1,=0A= + },=0A= + 'checksum-algorithm' =3D> {=0A= + description =3D> "The algorithm to calculate the checksum of the file.",= =0A= + type =3D> 'string',=0A= + enum =3D> ['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'],=0A= + requires =3D> 'checksum',=0A= + optional =3D> 1,=0A= + },=0A= + tmpfilename =3D> {=0A= + description =3D>=0A= + "The source file name. This parameter is usually set by the REST handler.= You can only overwrite it when connecting to the trusted port on localhost= .",=0A= + type =3D> 'string',=0A= + optional =3D> 1,=0A= + pattern =3D> '/var/tmp/pveupload-[0-9a-f]+',=0A= + },=0A= + },=0A= + },=0A= + returns =3D> { type =3D> "string" },=0A= + code =3D> sub {=0A= + my ($param) =3D @_;=0A= +=0A= + my $rpcenv =3D PVE::RPCEnvironment::get();=0A= +=0A= + my $user =3D $rpcenv->get_user();=0A= +=0A= + my $cfg =3D PVE::Storage::config();=0A= +=0A= + my ($node, $storage) =3D $param->@{qw(node storage)};=0A= + my $scfg =3D PVE::Storage::storage_check_enabled($cfg, $storage, $node);= =0A= +=0A= + die "can't upload to storage type '$scfg->{type}'\n"=0A= + if !defined($scfg->{path});=0A= +=0A= + my $content =3D $param->{content};=0A= +=0A= + my $tmpfilename =3D $param->{tmpfilename};=0A= + die "missing temporary file name\n" if !$tmpfilename;=0A= +=0A= + my $size =3D -s $tmpfilename;=0A= + die "temporary file '$tmpfilename' does not exist\n" if !defined($size);= =0A= +=0A= + my $filename =3D PVE::Storage::normalize_content_filename($param->{filena= me});=0A= +=0A= + my $vmid =3D $param->{vmid};=0A= +=0A= + if ($content eq 'import' && $filename =3D~ /\.(vma|vma\.zst)$/i) {=0A= +=0A= + my $format =3D 'vma';=0A= + my $use_zstd =3D ($filename =3D~ /\.zst$/i);=0A= +=0A= + my $rpcenv =3D PVE::RPCEnvironment::get();=0A= + if (!defined($vmid)) {=0A= + ($vmid) =3D $filename =3D~ /-(\d+)\./;=0A= + }=0A= + syslog('info', "Performing qmrestore. Parameters are tmpfilename: $tmpfil= ename, filename: $filename, vmid: $vmid, storage: $storage");=0A= +=0A= + my $response =3D { success =3D> "Upload done. VM $vmid restore on going."= };=0A= +=0A= + my $worker =3D sub {=0A= + eval {=0A= +=0A= + my $task_description =3D "Restore VM $vmid from uploaded file $file= name";=0A= + syslog('info', $task_description );=0A= + my @cmd =3D ('/usr/sbin/qmrestore', '-', $vmid, '--force');=0A= +=0A= + my ($pid, $in, $out);=0A= +=0A= + # best effort to match apl_download behaviour=0A= + chmod 0644, $tmpfilename;=0A= +=0A= + # Open file and pipe it to cli command=0A= + open(my $file, '<', $tmpfilename) or do {=0A= + syslog('err', "Impossibile aprire il file $tmpfilename: $!");=0A= + die "Impossibile aprire il file $tmpfilename: $!\n";=0A= + };=0A= +=0A= + binmode $file;=0A= +=0A= + if ($use_zstd) {=0A= + my $pipeline =3D join(' | ', 'zstd -d -c', join(' ', @cmd));=0A= + $pid =3D open2($out, $in, $pipeline);=0A= + } else {=0A= + $pid =3D open2($out, $in, @cmd);=0A= + }=0A= +=0A= + binmode $in;=0A= +=0A= + my $buffer;=0A= + while (my $read =3D read($file, $buffer, 1024*1024)) {=0A= + print $in $buffer;=0A= + }=0A= + close($in);=0A= + close($file);=0A= +=0A= + waitpid($pid, 0);=0A= + my $exit_code =3D $?;=0A= + if ($exit_code !=3D 0) {=0A= + syslog('err', "Error while performing qmrestore, exit code: $exit_code= ");=0A= + die "Error while restoring the VM: $exit_code";=0A= + } else {=0A= + syslog('info', "Restore completed for VM $vmid");=0A= + }=0A= + };=0A= + if (my $err =3D $@) {=0A= + syslog('err', "Error restoring the VM: $err");=0A= + # Add cleanup and error handling=0A= + }=0A= + };=0A= +=0A= + $rpcenv->fork_worker('importvm', undef, $user, $worker);=0A= + return $response;=0A= +=0A= + }=0A= +=0A= + my $path;=0A= + my $is_ova =3D 0;=0A= + my $image_format;=0A= +=0A= + if ($content eq 'iso') {=0A= + if ($filename !~ m![^/]+$PVE::Storage::ISO_EXT_RE_0$!) {=0A= + raise_param_exc({ filename =3D> "wrong file extension" });=0A= + }=0A= + $path =3D PVE::Storage::get_iso_dir($cfg, $storage);=0A= + } elsif ($content eq 'vztmpl') {=0A= + if ($filename !~ m![^/]+$PVE::Storage::VZTMPL_EXT_RE_1$!) {=0A= + raise_param_exc({ filename =3D> "wrong file extension" });=0A= + }=0A= + $path =3D PVE::Storage::get_vztmpl_dir($cfg, $storage);=0A= + } elsif ($content eq 'import') {=0A= + if ($filename !~=0A= + m!${PVE::Storage::SAFE_CHAR_CLASS_RE}+$PVE::Storage::UPLOAD_IMPORT_EXT_RE= _1$!=0A= + ) {=0A= + raise_param_exc({ filename =3D> "invalid filename or wrong extension" });= =0A= + }=0A= + my $format =3D $1;=0A= +=0A= + if ($format eq 'ova') {=0A= + $is_ova =3D 1;=0A= + } else {=0A= + $image_format =3D $format;=0A= + }=0A= +=0A= + $path =3D PVE::Storage::get_import_dir($cfg, $storage);=0A= + } else {=0A= + raise_param_exc({ content =3D> "upload content type '$content' not allowe= d" });=0A= + }=0A= +=0A= + die "storage '$storage' does not support '$content' content\n"=0A= + if !$scfg->{content}->{$content};=0A= +=0A= + my $dest =3D "$path/$filename";=0A= + my $dirname =3D dirname($dest);=0A= +=0A= + # best effort to match apl_download behaviour=0A= + chmod 0644, $tmpfilename;=0A= +=0A= + my $err_cleanup =3D sub { unlink $dest or $! =3D=3D ENOENT or die "cleanu= p failed: $!\n" };=0A= +=0A= + my $cmd;=0A= + if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {=0A= + my $remip =3D PVE::Cluster::remote_node_ip($node);=0A= +=0A= + my $ssh_options =3D=0A= + PVE::SSHInfo::ssh_info_to_ssh_opts({ ip =3D> $remip, name =3D> $node });= =0A= +=0A= + my @remcmd =3D ('/usr/bin/ssh', $ssh_options->@*, $remip, '--');=0A= +=0A= + eval { # activate remote storage=0A= + run_command([@remcmd, '/usr/sbin/pvesm', 'status', '--storage', $storage]= );=0A= + };=0A= + die "can't activate storage '$storage' on node '$node': $@\n" if $@;=0A= +=0A= + run_command(=0A= + [@remcmd, '/bin/mkdir', '-p', '--', PVE::Tools::shell_quote($dirname)],= =0A= + errmsg =3D> "mkdir failed",=0A= + );=0A= +=0A= + $cmd =3D [=0A= + '/usr/bin/scp',=0A= + $ssh_options->@*,=0A= + '-p',=0A= + '--',=0A= + $tmpfilename,=0A= + "[$remip]:" . PVE::Tools::shell_quote($dest),=0A= + ];=0A= +=0A= + $err_cleanup =3D sub { run_command([@remcmd, 'rm', '-f', '--', $dest]) };= =0A= + } else {=0A= + PVE::Storage::activate_storage($cfg, $storage);=0A= + File::Path::make_path($dirname);=0A= + $cmd =3D ['cp', '--', $tmpfilename, $dest];=0A= + }=0A= +=0A= + # NOTE: we simply overwrite the destination file if it already exists=0A= + my $worker =3D sub {=0A= + my $upid =3D shift;=0A= +=0A= + print "starting file import from: $tmpfilename\n";=0A= +=0A= + eval {=0A= + my ($checksum, $checksum_algorithm) =3D=0A= + $param->@{ 'checksum', 'checksum-algorithm' };=0A= + if ($checksum_algorithm) {=0A= + print "calculating checksum...";=0A= +=0A= + my $checksum_got =3D=0A= + PVE::Tools::get_file_hash($checksum_algorithm, $tmpfilename);=0A= +=0A= + if (lc($checksum_got) eq lc($checksum)) {=0A= + print "OK, checksum verified\n";=0A= + } else {=0A= + print "\n"; # the front end expects the error to reside at the last line = without any noise=0A= + die "checksum mismatch: got '$checksum_got' !=3D expect '$checksum'\n";= =0A= + }=0A= + }=0A= +=0A= + if ($content eq 'iso') {=0A= + PVE::Storage::assert_iso_content($tmpfilename);=0A= + }=0A= +=0A= + if ($is_ova) {=0A= + assert_ova_contents($tmpfilename);=0A= + } elsif (defined($image_format)) {=0A= + # checks untrusted image=0A= + PVE::Storage::file_size_info($tmpfilename, 10, $image_format, 1);=0A= + }=0A= + };=0A= + if (my $err =3D $@) {=0A= + # unlinks only the temporary file from the http server=0A= + unlink $tmpfilename=0A= + or $! =3D=3D ENOENT=0A= + or warn "unable to clean up temporory file '$tmpfilename' - $!\n";=0A= + die $err;=0A= + }=0A= +=0A= + print "target node: $node\n";=0A= + print "target file: $dest\n";=0A= + print "file size is: $size\n";=0A= + print "command: " . join(' ', @$cmd) . "\n";=0A= +=0A= + eval { run_command($cmd, errmsg =3D> 'import failed'); };=0A= +=0A= + # the temporary file got only uploaded locally, no need to rm remote=0A= + unlink $tmpfilename=0A= + or $! =3D=3D ENOENT=0A= + or warn "unable to clean up temporary file '$tmpfilename' - $!\n";=0A= +=0A= + if (my $err =3D $@) {=0A= + eval { $err_cleanup->() };=0A= + warn "$@" if $@;=0A= + die $err;=0A= + }=0A= + print "finished file import successfully\n";=0A= + };=0A= +=0A= + return $rpcenv->fork_worker('imgcopy', undef, $user, $worker);=0A= + },=0A= });=0A= =0A= __PACKAGE__->register_method({=0A= - name =3D> 'download_url',=0A= - path =3D> '{storage}/download-url',=0A= - method =3D> 'POST',=0A= - description =3D> "Download templates, ISO images, OVAs and VM images b= y using an URL.",=0A= - proxyto =3D> 'node',=0A= - permissions =3D> {=0A= - description =3D>=0A= - 'Requires allocation access on the storage and as this allows = one to probe'=0A= - . ' the (local!) host network indirectly it also requires one = of Sys.Modify on / (for'=0A= - . ' backwards compatibility) or the newer Sys.AccessNetwork pr= ivilege on the node.',=0A= - check =3D> [=0A= - 'and',=0A= - ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']]= ,=0A= - [=0A= - 'or',=0A= - ['perm', '/', ['Sys.Audit', 'Sys.Modify']],=0A= - ['perm', '/nodes/{node}', ['Sys.AccessNetwork']],=0A= - ],=0A= - ],=0A= - },=0A= - protected =3D> 1,=0A= - parameters =3D> {=0A= - additionalProperties =3D> 0,=0A= - properties =3D> {=0A= - node =3D> get_standard_option('pve-node'),=0A= - storage =3D> get_standard_option('pve-storage-id'),=0A= - url =3D> {=0A= - description =3D> "The URL to download the file from.",=0A= - type =3D> 'string',=0A= - pattern =3D> 'https?://.*',=0A= - },=0A= - content =3D> {=0A= - description =3D> "Content type.", # TODO: could be optiona= l & detected in most cases=0A= - type =3D> 'string',=0A= - format =3D> 'pve-storage-content',=0A= - enum =3D> ['iso', 'vztmpl', 'import'],=0A= - },=0A= - filename =3D> {=0A= - description =3D>=0A= - "The name of the file to create. Caution: This will be= normalized!",=0A= - maxLength =3D> 255,=0A= - type =3D> 'string',=0A= - },=0A= - checksum =3D> {=0A= - description =3D> "The expected checksum of the file.",=0A= - type =3D> 'string',=0A= - requires =3D> 'checksum-algorithm',=0A= - optional =3D> 1,=0A= - },=0A= - compression =3D> {=0A= - description =3D>=0A= - "Decompress the downloaded file using the specified co= mpression algorithm.",=0A= - type =3D> 'string',=0A= - enum =3D> $PVE::Storage::Plugin::KNOWN_COMPRESSION_FORMATS= ,=0A= - optional =3D> 1,=0A= - },=0A= - 'checksum-algorithm' =3D> {=0A= - description =3D> "The algorithm to calculate the checksum = of the file.",=0A= - type =3D> 'string',=0A= - enum =3D> ['md5', 'sha1', 'sha224', 'sha256', 'sha384', 's= ha512'],=0A= - requires =3D> 'checksum',=0A= - optional =3D> 1,=0A= - },=0A= - 'verify-certificates' =3D> {=0A= - description =3D> "If false, no SSL/TLS certificates will b= e verified.",=0A= - type =3D> 'boolean',=0A= - optional =3D> 1,=0A= - default =3D> 1,=0A= - },=0A= - },=0A= - },=0A= - returns =3D> {=0A= - type =3D> "string",=0A= - },=0A= - code =3D> sub {=0A= - my ($param) =3D @_;=0A= -=0A= - my $rpcenv =3D PVE::RPCEnvironment::get();=0A= - my $user =3D $rpcenv->get_user();=0A= -=0A= - my $cfg =3D PVE::Storage::config();=0A= -=0A= - my ($node, $storage, $compression) =3D $param->@{qw(node storage c= ompression)};=0A= - my $scfg =3D PVE::Storage::storage_check_enabled($cfg, $storage, $= node);=0A= -=0A= - die "can't upload to storage type '$scfg->{type}', not a file base= d storage!\n"=0A= - if !defined($scfg->{path});=0A= -=0A= - my ($content, $url) =3D $param->@{ 'content', 'url' };=0A= -=0A= - die "storage '$storage' is not configured for content-type '$conte= nt'\n"=0A= - if !$scfg->{content}->{$content};=0A= -=0A= - my $filename =3D PVE::Storage::normalize_content_filename($param->= {filename});=0A= -=0A= - my $path;=0A= - my $is_ova =3D 0;=0A= - my $image_format;=0A= -=0A= - if ($content eq 'iso') {=0A= - if ($filename !~ m![^/]+$PVE::Storage::ISO_EXT_RE_0$!) {=0A= - raise_param_exc({ filename =3D> "wrong file extension" });= =0A= - }=0A= - $path =3D PVE::Storage::get_iso_dir($cfg, $storage);=0A= - } elsif ($content eq 'vztmpl') {=0A= - if ($filename !~ m![^/]+$PVE::Storage::VZTMPL_EXT_RE_1$!) {=0A= - raise_param_exc({ filename =3D> "wrong file extension" });= =0A= - }=0A= - $path =3D PVE::Storage::get_vztmpl_dir($cfg, $storage);=0A= - } elsif ($content eq 'import') {=0A= - if ($filename !~=0A= - m!${PVE::Storage::SAFE_CHAR_CLASS_RE}+$PVE::Storage::UPLOA= D_IMPORT_EXT_RE_1$!=0A= - ) {=0A= - raise_param_exc({ filename =3D> "invalid filename or wrong= extension" });=0A= - }=0A= - my $format =3D $1;=0A= -=0A= - if ($format eq 'ova') {=0A= - $is_ova =3D 1;=0A= - } else {=0A= - $image_format =3D $format;=0A= - }=0A= -=0A= - $path =3D PVE::Storage::get_import_dir($cfg, $storage);=0A= - } else {=0A= - raise_param_exc({ content =3D> "upload content-type '$content'= is not allowed" });=0A= - }=0A= -=0A= - PVE::Storage::activate_storage($cfg, $storage);=0A= - File::Path::make_path($path);=0A= -=0A= - my $dccfg =3D PVE::Cluster::cfs_read_file('datacenter.cfg');=0A= - my $opts =3D {=0A= - hash_required =3D> 0,=0A= - verify_certificates =3D> $param->{'verify-certificates'} // 1,= =0A= - http_proxy =3D> $dccfg->{http_proxy},=0A= - https_proxy =3D> $dccfg->{http_proxy},=0A= - };=0A= -=0A= - my ($checksum, $checksum_algorithm) =3D $param->@{ 'checksum', 'ch= ecksum-algorithm' };=0A= - if ($checksum) {=0A= - $opts->{"${checksum_algorithm}sum"} =3D $checksum;=0A= - $opts->{hash_required} =3D 1;=0A= - }=0A= -=0A= - $opts->{assert_file_validity} =3D sub {=0A= - my ($tmp_path) =3D @_;=0A= -=0A= - if ($content eq 'iso') {=0A= - PVE::Storage::assert_iso_content($tmp_path);=0A= - }=0A= -=0A= - if ($is_ova) {=0A= - assert_ova_contents($tmp_path);=0A= - } elsif (defined($image_format)) {=0A= - # checks untrusted image=0A= - PVE::Storage::file_size_info($tmp_path, 10, $image_format,= 1);=0A= - }=0A= - };=0A= -=0A= - my $worker =3D sub {=0A= - if ($compression) {=0A= - die "decompression not supported for $content\n" if $conte= nt ne 'iso';=0A= - my $info =3D PVE::Storage::decompressor_info('iso', $compr= ession);=0A= - die "no decompression method found\n" if !$info->{decompre= ssor};=0A= - $opts->{decompression_command} =3D $info->{decompressor};= =0A= - }=0A= -=0A= - PVE::Tools::download_file_from_url("$path/$filename", $url, $o= pts);=0A= - };=0A= -=0A= - my $worker_id =3D PVE::Tools::encode_text($filename); # must not p= ass : or the like as w-ID=0A= -=0A= - return $rpcenv->fork_worker('download', $worker_id, $user, $worker= );=0A= - },=0A= + name =3D> 'download_url',=0A= + path =3D> '{storage}/download-url',=0A= + method =3D> 'POST',=0A= + description =3D> "Download templates, ISO images, OVAs and VM images by u= sing an URL.",=0A= + proxyto =3D> 'node',=0A= + permissions =3D> {=0A= + description =3D>=0A= + 'Requires allocation access on the storage and as this allows one to prob= e'=0A= + . ' the (local!) host network indirectly it also requires one of Sys.Modi= fy on / (for'=0A= + . ' backwards compatibility) or the newer Sys.AccessNetwork privilege on = the node.',=0A= + check =3D> [=0A= + 'and',=0A= + ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],=0A= + [=0A= + 'or',=0A= + ['perm', '/', ['Sys.Audit', 'Sys.Modify']],=0A= + ['perm', '/nodes/{node}', ['Sys.AccessNetwork']],=0A= + ],=0A= + ],=0A= + },=0A= + protected =3D> 1,=0A= + parameters =3D> {=0A= + additionalProperties =3D> 0,=0A= + properties =3D> {=0A= + node =3D> get_standard_option('pve-node'),=0A= + storage =3D> get_standard_option('pve-storage-id'),=0A= + url =3D> {=0A= + description =3D> "The URL to download the file from.",=0A= + type =3D> 'string',=0A= + pattern =3D> 'https?://.*',=0A= + },=0A= + content =3D> {=0A= + description =3D> "Content type.", # TODO: could be optional & detected in= most cases=0A= + type =3D> 'string',=0A= + format =3D> 'pve-storage-content',=0A= + enum =3D> ['iso', 'vztmpl', 'import'],=0A= + },=0A= + filename =3D> {=0A= + description =3D>=0A= + "The name of the file to create. Caution: This will be normalized!",=0A= + maxLength =3D> 255,=0A= + type =3D> 'string',=0A= + },=0A= + checksum =3D> {=0A= + description =3D> "The expected checksum of the file.",=0A= + type =3D> 'string',=0A= + requires =3D> 'checksum-algorithm',=0A= + optional =3D> 1,=0A= + },=0A= + compression =3D> {=0A= + description =3D>=0A= + "Decompress the downloaded file using the specified compression algorithm= .",=0A= + type =3D> 'string',=0A= + enum =3D> $PVE::Storage::Plugin::KNOWN_COMPRESSION_FORMATS,=0A= + optional =3D> 1,=0A= + },=0A= + 'checksum-algorithm' =3D> {=0A= + description =3D> "The algorithm to calculate the checksum of the file.",= =0A= + type =3D> 'string',=0A= + enum =3D> ['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'],=0A= + requires =3D> 'checksum',=0A= + optional =3D> 1,=0A= + },=0A= + 'verify-certificates' =3D> {=0A= + description =3D> "If false, no SSL/TLS certificates will be verified.",= =0A= + type =3D> 'boolean',=0A= + optional =3D> 1,=0A= + default =3D> 1,=0A= + },=0A= + },=0A= + },=0A= + returns =3D> {=0A= + type =3D> "string",=0A= + },=0A= + code =3D> sub {=0A= + my ($param) =3D @_;=0A= +=0A= + my $rpcenv =3D PVE::RPCEnvironment::get();=0A= + my $user =3D $rpcenv->get_user();=0A= +=0A= + my $cfg =3D PVE::Storage::config();=0A= +=0A= + my ($node, $storage, $compression) =3D $param->@{qw(node storage compress= ion)};=0A= + my $scfg =3D PVE::Storage::storage_check_enabled($cfg, $storage, $node);= =0A= +=0A= + die "can't upload to storage type '$scfg->{type}', not a file based stora= ge!\n"=0A= + if !defined($scfg->{path});=0A= +=0A= + my ($content, $url) =3D $param->@{ 'content', 'url' };=0A= +=0A= + die "storage '$storage' is not configured for content-type '$content'\n"= =0A= + if !$scfg->{content}->{$content};=0A= +=0A= + my $filename =3D PVE::Storage::normalize_content_filename($param->{filena= me});=0A= +=0A= + my $path;=0A= + my $is_ova =3D 0;=0A= + my $image_format;=0A= +=0A= + if ($content eq 'iso') {=0A= + if ($filename !~ m![^/]+$PVE::Storage::ISO_EXT_RE_0$!) {=0A= + raise_param_exc({ filename =3D> "wrong file extension" });=0A= + }=0A= + $path =3D PVE::Storage::get_iso_dir($cfg, $storage);=0A= + } elsif ($content eq 'vztmpl') {=0A= + if ($filename !~ m![^/]+$PVE::Storage::VZTMPL_EXT_RE_1$!) {=0A= + raise_param_exc({ filename =3D> "wrong file extension" });=0A= + }=0A= + $path =3D PVE::Storage::get_vztmpl_dir($cfg, $storage);=0A= + } elsif ($content eq 'import') {=0A= + if ($filename !~=0A= + m!${PVE::Storage::SAFE_CHAR_CLASS_RE}+$PVE::Storage::UPLOAD_IMPORT_EXT_RE= _1$!=0A= + ) {=0A= + raise_param_exc({ filename =3D> "invalid filename or wrong extension" });= =0A= + }=0A= + my $format =3D $1;=0A= +=0A= + if ($format eq 'ova') {=0A= + $is_ova =3D 1;=0A= + } else {=0A= + $image_format =3D $format;=0A= + }=0A= +=0A= + $path =3D PVE::Storage::get_import_dir($cfg, $storage);=0A= + } else {=0A= + raise_param_exc({ content =3D> "upload content-type '$content' is not all= owed" });=0A= + }=0A= +=0A= + PVE::Storage::activate_storage($cfg, $storage);=0A= + File::Path::make_path($path);=0A= +=0A= + my $dccfg =3D PVE::Cluster::cfs_read_file('datacenter.cfg');=0A= + my $opts =3D {=0A= + hash_required =3D> 0,=0A= + verify_certificates =3D> $param->{'verify-certificates'} // 1,=0A= + http_proxy =3D> $dccfg->{http_proxy},=0A= + https_proxy =3D> $dccfg->{http_proxy},=0A= + };=0A= +=0A= + my ($checksum, $checksum_algorithm) =3D $param->@{ 'checksum', 'checksum-= algorithm' };=0A= + if ($checksum) {=0A= + $opts->{"${checksum_algorithm}sum"} =3D $checksum;=0A= + $opts->{hash_required} =3D 1;=0A= + }=0A= +=0A= + $opts->{assert_file_validity} =3D sub {=0A= + my ($tmp_path) =3D @_;=0A= +=0A= + if ($content eq 'iso') {=0A= + PVE::Storage::assert_iso_content($tmp_path);=0A= + }=0A= +=0A= + if ($is_ova) {=0A= + assert_ova_contents($tmp_path);=0A= + } elsif (defined($image_format)) {=0A= + # checks untrusted image=0A= + PVE::Storage::file_size_info($tmp_path, 10, $image_format, 1);=0A= + }=0A= + };=0A= +=0A= + my $worker =3D sub {=0A= + if ($compression) {=0A= + die "decompression not supported for $content\n" if $content ne 'iso';=0A= + my $info =3D PVE::Storage::decompressor_info('iso', $compression);=0A= + die "no decompression method found\n" if !$info->{decompressor};=0A= + $opts->{decompression_command} =3D $info->{decompressor};=0A= + }=0A= +=0A= + PVE::Tools::download_file_from_url("$path/$filename", $url, $opts);=0A= + };=0A= +=0A= + my $worker_id =3D PVE::Tools::encode_text($filename); # must not pass : o= r the like as w-ID=0A= +=0A= + return $rpcenv->fork_worker('download', $worker_id, $user, $worker);=0A= + },=0A= });=0A= =0A= __PACKAGE__->register_method({=0A= - name =3D> 'oci_registry_pull',=0A= - path =3D> '{storage}/oci-registry-pull',=0A= - method =3D> 'POST',=0A= - description =3D> "Pull an OCI image from a registry.",=0A= - proxyto =3D> 'node',=0A= - permissions =3D> {=0A= - check =3D> [=0A= - 'and',=0A= - ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']]= ,=0A= - ['perm', '/nodes/{node}', ['Sys.AccessNetwork']],=0A= - ],=0A= - },=0A= - protected =3D> 1,=0A= - parameters =3D> {=0A= - additionalProperties =3D> 0,=0A= - properties =3D> {=0A= - node =3D> get_standard_option('pve-node'),=0A= - storage =3D> get_standard_option('pve-storage-id'),=0A= - reference =3D> {=0A= - description =3D> "The reference to the OCI image to downlo= ad.",=0A= - type =3D> 'string',=0A= - pattern =3D> '^(?:(?:[a-zA-Z\d]|[a-zA-Z\d][a-zA-Z\d-]*[a-z= A-Z\d])'=0A= - . '(?:\.(?:[a-zA-Z\d]|[a-zA-Z\d][a-zA-Z\d-]*[a-zA-Z\d]= ))*(?::\d+)?/)?[a-z\d]+'=0A= - . '(?:(?:[._]|__|[-]*)[a-z\d]+)*(?:/[a-z\d]+(?:(?:[._]= |__|[-]*)[a-z\d]+)*)*'=0A= - . ':\w[\w.-]{0,127}$',=0A= - },=0A= - filename =3D> {=0A= - description =3D>=0A= - "Custom destination file name of the OCI image. Cautio= n: This will be normalized!",=0A= - optional =3D> 1,=0A= - minLength =3D> 1,=0A= - maxLength =3D> 255,=0A= - type =3D> 'string',=0A= - },=0A= - },=0A= - },=0A= - returns =3D> {=0A= - type =3D> "string",=0A= - },=0A= - code =3D> sub {=0A= - my ($param) =3D @_;=0A= -=0A= - die "Install 'skopeo' to pull OCI images from registries.\n" if (!= -f '/usr/bin/skopeo');=0A= -=0A= - my $rpcenv =3D PVE::RPCEnvironment::get();=0A= - my $user =3D $rpcenv->get_user();=0A= -=0A= - my $cfg =3D PVE::Storage::config();=0A= -=0A= - my ($node, $storage) =3D $param->@{qw(node storage)};=0A= - my $scfg =3D PVE::Storage::storage_check_enabled($cfg, $storage, $= node);=0A= -=0A= - die "can't upload to storage type '$scfg->{type}', not a file base= d storage!\n"=0A= - if !defined($scfg->{path});=0A= -=0A= - my $reference =3D $param->{reference};=0A= -=0A= - die "storage '$storage' is not configured for content-type 'vztmpl= '\n"=0A= - if !$scfg->{content}->{vztmpl};=0A= -=0A= - my $filename =3D=0A= - PVE::Storage::normalize_content_filename($param->{filename} //= $reference) . ".tar";=0A= - my $tmp_filename =3D "$filename.tmp.$$";=0A= - my $path =3D PVE::Storage::get_vztmpl_dir($cfg, $storage);=0A= - PVE::Storage::activate_storage($cfg, $storage);=0A= -=0A= - die "refusing to override existing file '$filename'\n" if -f "$pat= h/$filename";=0A= -=0A= - local $SIG{INT} =3D sub {=0A= - unlink "$path/$tmp_filename"=0A= - or $!{ENOENT}=0A= - or warn "could not cleanup temporary file: $!";=0A= - die "got interrupted by signal\n";=0A= - };=0A= -=0A= - my $worker =3D sub {=0A= - PVE::Tools::run_command(=0A= - [=0A= - "skopeo", "copy", "docker://$reference", "oci-archive:= $path/$tmp_filename",=0A= - ],=0A= - );=0A= - rename("$path/$tmp_filename", "$path/$filename")=0A= - or die "unable to rename temporary file: $!\n";=0A= - };=0A= -=0A= - my $worker_id =3D PVE::Tools::encode_text($filename); # must not p= ass : or the like as w-ID=0A= -=0A= - return $rpcenv->fork_worker('ociregistrypull', $worker_id, $user, = $worker);=0A= - },=0A= + name =3D> 'oci_registry_pull',=0A= + path =3D> '{storage}/oci-registry-pull',=0A= + method =3D> 'POST',=0A= + description =3D> "Pull an OCI image from a registry.",=0A= + proxyto =3D> 'node',=0A= + permissions =3D> {=0A= + check =3D> [=0A= + 'and',=0A= + ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],=0A= + ['perm', '/nodes/{node}', ['Sys.AccessNetwork']],=0A= + ],=0A= + },=0A= + protected =3D> 1,=0A= + parameters =3D> {=0A= + additionalProperties =3D> 0,=0A= + properties =3D> {=0A= + node =3D> get_standard_option('pve-node'),=0A= + storage =3D> get_standard_option('pve-storage-id'),=0A= + reference =3D> {=0A= + description =3D> "The reference to the OCI image to download.",=0A= + type =3D> 'string',=0A= + pattern =3D> '^(?:(?:[a-zA-Z\d]|[a-zA-Z\d][a-zA-Z\d-]*[a-zA-Z\d])'=0A= + . '(?:\.(?:[a-zA-Z\d]|[a-zA-Z\d][a-zA-Z\d-]*[a-zA-Z\d]))*(?::\d+)?/)?[a-z= \d]+'=0A= + . '(?:(?:[._]|__|[-]*)[a-z\d]+)*(?:/[a-z\d]+(?:(?:[._]|__|[-]*)[a-z\d]+)*= )*'=0A= + . ':\w[\w.-]{0,127}$',=0A= + },=0A= + filename =3D> {=0A= + description =3D>=0A= + "Custom destination file name of the OCI image. Caution: This will be nor= malized!",=0A= + optional =3D> 1,=0A= + minLength =3D> 1,=0A= + maxLength =3D> 255,=0A= + type =3D> 'string',=0A= + },=0A= + },=0A= + },=0A= + returns =3D> {=0A= + type =3D> "string",=0A= + },=0A= + code =3D> sub {=0A= + my ($param) =3D @_;=0A= +=0A= + die "Install 'skopeo' to pull OCI images from registries.\n" if (!-f '/us= r/bin/skopeo');=0A= +=0A= + my $rpcenv =3D PVE::RPCEnvironment::get();=0A= + my $user =3D $rpcenv->get_user();=0A= +=0A= + my $cfg =3D PVE::Storage::config();=0A= +=0A= + my ($node, $storage) =3D $param->@{qw(node storage)};=0A= + my $scfg =3D PVE::Storage::storage_check_enabled($cfg, $storage, $node);= =0A= +=0A= + die "can't upload to storage type '$scfg->{type}', not a file based stora= ge!\n"=0A= + if !defined($scfg->{path});=0A= +=0A= + my $reference =3D $param->{reference};=0A= +=0A= + die "storage '$storage' is not configured for content-type 'vztmpl'\n"=0A= + if !$scfg->{content}->{vztmpl};=0A= +=0A= + my $filename =3D=0A= + PVE::Storage::normalize_content_filename($param->{filename} // $reference= ) . ".tar";=0A= + my $tmp_filename =3D "$filename.tmp.$$";=0A= + my $path =3D PVE::Storage::get_vztmpl_dir($cfg, $storage);=0A= + PVE::Storage::activate_storage($cfg, $storage);=0A= +=0A= + die "refusing to override existing file '$filename'\n" if -f "$path/$file= name";=0A= +=0A= + local $SIG{INT} =3D sub {=0A= + unlink "$path/$tmp_filename"=0A= + or $!{ENOENT}=0A= + or warn "could not cleanup temporary file: $!";=0A= + die "got interrupted by signal\n";=0A= + };=0A= +=0A= + my $worker =3D sub {=0A= + PVE::Tools::run_command(=0A= + [=0A= + "skopeo", "copy", "docker://$reference", "oci-archive:$path/$tmp_filename= ",=0A= + ],=0A= + );=0A= + rename("$path/$tmp_filename", "$path/$filename")=0A= + or die "unable to rename temporary file: $!\n";=0A= + };=0A= +=0A= + my $worker_id =3D PVE::Tools::encode_text($filename); # must not pass : o= r the like as w-ID=0A= +=0A= + return $rpcenv->fork_worker('ociregistrypull', $worker_id, $user, $worker= );=0A= + },=0A= });=0A= =0A= __PACKAGE__->register_method({=0A= - name =3D> 'get_import_metadata',=0A= - path =3D> '{storage}/import-metadata',=0A= - method =3D> 'GET',=0A= - description =3D>=0A= - "Get the base parameters for creating a guest which imports data f= rom a foreign importable"=0A= - . " guest, like an ESXi VM",=0A= - proxyto =3D> 'node',=0A= - permissions =3D> {=0A= - description =3D> "You need read access for the volume.",=0A= - user =3D> 'all',=0A= - },=0A= - protected =3D> 1,=0A= - parameters =3D> {=0A= - additionalProperties =3D> 0,=0A= - properties =3D> {=0A= - node =3D> get_standard_option('pve-node'),=0A= - storage =3D> get_standard_option('pve-storage-id'),=0A= - volume =3D> {=0A= - description =3D> "Volume identifier for the guest archive/= entry.",=0A= - type =3D> 'string',=0A= - },=0A= - },=0A= - },=0A= - returns =3D> {=0A= - type =3D> "object",=0A= - description =3D> 'Information about how to import a guest.',=0A= - additionalProperties =3D> 0,=0A= - properties =3D> {=0A= - type =3D> {=0A= - type =3D> 'string',=0A= - enum =3D> ['vm'],=0A= - description =3D> 'The type of guest this is going to produ= ce.',=0A= - },=0A= - source =3D> {=0A= - type =3D> 'string',=0A= - enum =3D> ['esxi'],=0A= - description =3D> 'The type of the import-source of this gu= est volume.',=0A= - },=0A= - 'create-args' =3D> {=0A= - type =3D> 'object',=0A= - additionalProperties =3D> 1,=0A= - description =3D>=0A= - 'Parameters which can be used in a call to create a VM= or container.',=0A= - },=0A= - 'disks' =3D> {=0A= - type =3D> 'object',=0A= - additionalProperties =3D> 1,=0A= - optional =3D> 1,=0A= - description =3D> 'Recognised disk volumes as `$bus$id` =3D= > `$storeid:$path` map.',=0A= - },=0A= - 'net' =3D> {=0A= - type =3D> 'object',=0A= - additionalProperties =3D> 1,=0A= - optional =3D> 1,=0A= - description =3D>=0A= - 'Recognised network interfaces as `net$id` =3D> { ...p= arams } object.',=0A= - },=0A= - 'warnings' =3D> {=0A= - type =3D> 'array',=0A= - description =3D> 'List of known issues that can affect the= import of a guest.'=0A= - . ' Note that lack of warning does not imply that ther= e cannot be any problems.',=0A= - optional =3D> 1,=0A= - items =3D> {=0A= - type =3D> "object",=0A= - additionalProperties =3D> 1,=0A= - properties =3D> {=0A= - 'type' =3D> {=0A= - description =3D> 'What this warning is about.'= ,=0A= - enum =3D> [=0A= - 'cdrom-image-ignored',=0A= - 'efi-state-lost',=0A= - 'guest-is-running',=0A= - 'nvme-unsupported',=0A= - 'ova-needs-extracting',=0A= - 'ovmf-with-lsi-unsupported',=0A= - 'serial-port-socket-only',=0A= - ],=0A= - type =3D> 'string',=0A= - },=0A= - 'key' =3D> {=0A= - description =3D> 'Related subject (config) key= of warning.',=0A= - optional =3D> 1,=0A= - type =3D> 'string',=0A= - },=0A= - 'value' =3D> {=0A= - description =3D> 'Related subject (config) val= ue of warning.',=0A= - optional =3D> 1,=0A= - type =3D> 'string',=0A= - },=0A= - },=0A= - },=0A= - },=0A= - },=0A= - },=0A= - code =3D> sub {=0A= - my ($param) =3D @_;=0A= -=0A= - my $rpcenv =3D PVE::RPCEnvironment::get();=0A= - my $authuser =3D $rpcenv->get_user();=0A= -=0A= - my ($storeid, $volume) =3D $param->@{qw(storage volume)};=0A= - my $volid =3D "$storeid:$volume";=0A= -=0A= - my $cfg =3D PVE::Storage::config();=0A= -=0A= - PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef,= $volid);=0A= -=0A= - return PVE::Tools::run_with_timeout(=0A= - 30,=0A= - sub {=0A= - return PVE::Storage::get_import_metadata($cfg, $volid);=0A= - },=0A= - );=0A= - },=0A= + name =3D> 'get_import_metadata',=0A= + path =3D> '{storage}/import-metadata',=0A= + method =3D> 'GET',=0A= + description =3D>=0A= + "Get the base parameters for creating a guest which imports data from a f= oreign importable"=0A= + . " guest, like an ESXi VM",=0A= + proxyto =3D> 'node',=0A= + permissions =3D> {=0A= + description =3D> "You need read access for the volume.",=0A= + user =3D> 'all',=0A= + },=0A= + protected =3D> 1,=0A= + parameters =3D> {=0A= + additionalProperties =3D> 0,=0A= + properties =3D> {=0A= + node =3D> get_standard_option('pve-node'),=0A= + storage =3D> get_standard_option('pve-storage-id'),=0A= + volume =3D> {=0A= + description =3D> "Volume identifier for the guest archive/entry.",=0A= + type =3D> 'string',=0A= + },=0A= + },=0A= + },=0A= + returns =3D> {=0A= + type =3D> "object",=0A= + description =3D> 'Information about how to import a guest.',=0A= + additionalProperties =3D> 0,=0A= + properties =3D> {=0A= + type =3D> {=0A= + type =3D> 'string',=0A= + enum =3D> ['vm'],=0A= + description =3D> 'The type of guest this is going to produce.',=0A= + },=0A= + source =3D> {=0A= + type =3D> 'string',=0A= + enum =3D> ['esxi'],=0A= + description =3D> 'The type of the import-source of this guest volume.',= =0A= + },=0A= + 'create-args' =3D> {=0A= + type =3D> 'object',=0A= + additionalProperties =3D> 1,=0A= + description =3D>=0A= + 'Parameters which can be used in a call to create a VM or container.',=0A= + },=0A= + 'disks' =3D> {=0A= + type =3D> 'object',=0A= + additionalProperties =3D> 1,=0A= + optional =3D> 1,=0A= + description =3D> 'Recognised disk volumes as `$bus$id` =3D> `$storeid:$pa= th` map.',=0A= + },=0A= + 'net' =3D> {=0A= + type =3D> 'object',=0A= + additionalProperties =3D> 1,=0A= + optional =3D> 1,=0A= + description =3D>=0A= + 'Recognised network interfaces as `net$id` =3D> { ...params } object.',= =0A= + },=0A= + 'warnings' =3D> {=0A= + type =3D> 'array',=0A= + description =3D> 'List of known issues that can affect the import of a gu= est.'=0A= + . ' Note that lack of warning does not imply that there cannot be any pro= blems.',=0A= + optional =3D> 1,=0A= + items =3D> {=0A= + type =3D> "object",=0A= + additionalProperties =3D> 1,=0A= + properties =3D> {=0A= + 'type' =3D> {=0A= + description =3D> 'What this warning is about.',=0A= + enum =3D> [=0A= + 'cdrom-image-ignored',=0A= + 'efi-state-lost',=0A= + 'guest-is-running',=0A= + 'nvme-unsupported',=0A= + 'ova-needs-extracting',=0A= + 'ovmf-with-lsi-unsupported',=0A= + 'serial-port-socket-only',=0A= + ],=0A= + type =3D> 'string',=0A= + },=0A= + 'key' =3D> {=0A= + description =3D> 'Related subject (config) key of warning.',=0A= + optional =3D> 1,=0A= + type =3D> 'string',=0A= + },=0A= + 'value' =3D> {=0A= + description =3D> 'Related subject (config) value of warning.',=0A= + optional =3D> 1,=0A= + type =3D> 'string',=0A= + },=0A= + },=0A= + },=0A= + },=0A= + },=0A= + },=0A= + code =3D> sub {=0A= + my ($param) =3D @_;=0A= +=0A= + my $rpcenv =3D PVE::RPCEnvironment::get();=0A= + my $authuser =3D $rpcenv->get_user();=0A= +=0A= + my ($storeid, $volume) =3D $param->@{qw(storage volume)};=0A= + my $volid =3D "$storeid:$volume";=0A= +=0A= + my $cfg =3D PVE::Storage::config();=0A= +=0A= + PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $volid= );=0A= +=0A= + return PVE::Tools::run_with_timeout(=0A= + 30,=0A= + sub {=0A= + return PVE::Storage::get_import_metadata($cfg, $volid);=0A= + },=0A= + );=0A= + },=0A= });=0A= =0A= __PACKAGE__->register_method({=0A= - name =3D> 'identity',=0A= - path =3D> '{storage}/identity',=0A= - method =3D> 'GET',=0A= - description =3D> "Return identity information for this storage instanc= e.",=0A= - permissions =3D> {=0A= - check =3D> [=0A= - 'perm',=0A= - '/storage/{storage}',=0A= - ['Datastore.Audit', 'Datastore.AllocateSpace'],=0A= - any =3D> 1,=0A= - ],=0A= - },=0A= - protected =3D> 1,=0A= - proxyto =3D> 'node',=0A= - parameters =3D> {=0A= - additionalProperties =3D> 0,=0A= - properties =3D> {=0A= - node =3D> get_standard_option('pve-node'),=0A= - storage =3D> get_standard_option('pve-storage-id'),=0A= - },=0A= - },=0A= - returns =3D> {=0A= - type =3D> "object",=0A= - properties =3D> {=0A= - 'type' =3D> {=0A= - type =3D> 'string',=0A= - description =3D> 'The type of the storage.',=0A= - enum =3D> $storage_type_enum,=0A= - },=0A= - 'id' =3D> {=0A= - type =3D> 'string',=0A= - description =3D> 'Unique identifier for this storage insta= nce.'=0A= - . ' The exact format and semantics depend on the stora= ge plugin type.',=0A= - },=0A= - },=0A= - },=0A= - code =3D> sub {=0A= - my ($param) =3D @_;=0A= -=0A= - my $cfg =3D PVE::Storage::config();=0A= -=0A= - my ($node, $storeid) =3D $param->@{qw(node storage)};=0A= - my $scfg =3D PVE::Storage::storage_check_enabled($cfg, $storeid, $= node);=0A= -=0A= - my $type =3D $scfg->{type};=0A= - my $plugin =3D PVE::Storage::Plugin->lookup($type);=0A= -=0A= - my $id =3D $plugin->get_identity($scfg, $storeid);=0A= -=0A= - return {=0A= - type =3D> $type,=0A= - id =3D> $id,=0A= - };=0A= - },=0A= + name =3D> 'identity',=0A= + path =3D> '{storage}/identity',=0A= + method =3D> 'GET',=0A= + description =3D> "Return identity information for this storage instance."= ,=0A= + permissions =3D> {=0A= + check =3D> [=0A= + 'perm',=0A= + '/storage/{storage}',=0A= + ['Datastore.Audit', 'Datastore.AllocateSpace'],=0A= + any =3D> 1,=0A= + ],=0A= + },=0A= + protected =3D> 1,=0A= + proxyto =3D> 'node',=0A= + parameters =3D> {=0A= + additionalProperties =3D> 0,=0A= + properties =3D> {=0A= + node =3D> get_standard_option('pve-node'),=0A= + storage =3D> get_standard_option('pve-storage-id'),=0A= + },=0A= + },=0A= + returns =3D> {=0A= + type =3D> "object",=0A= + properties =3D> {=0A= + 'type' =3D> {=0A= + type =3D> 'string',=0A= + description =3D> 'The type of the storage.',=0A= + enum =3D> $storage_type_enum,=0A= + },=0A= + 'id' =3D> {=0A= + type =3D> 'string',=0A= + description =3D> 'Unique identifier for this storage instance.'=0A= + . ' The exact format and semantics depend on the storage plugin type.',= =0A= + },=0A= + },=0A= + },=0A= + code =3D> sub {=0A= + my ($param) =3D @_;=0A= +=0A= + my $cfg =3D PVE::Storage::config();=0A= +=0A= + my ($node, $storeid) =3D $param->@{qw(node storage)};=0A= + my $scfg =3D PVE::Storage::storage_check_enabled($cfg, $storeid, $node);= =0A= +=0A= + my $type =3D $scfg->{type};=0A= + my $plugin =3D PVE::Storage::Plugin->lookup($type);=0A= +=0A= + my $id =3D $plugin->get_identity($scfg, $storeid);=0A= +=0A= + return {=0A= + type =3D> $type,=0A= + id =3D> $id,=0A= + };=0A= + },=0A= });=0A= =0A= 1;=0A= -- =0A= 2.47.3=0A= =0A=