use PVE::Tools;
use PVE::Cluster;
+my $crs_format = {
+ ha => {
+ type => 'string',
+ enum => ['basic', 'static'],
+ optional => 1,
+ default => 'basic',
+ description => "Use this resource scheduler mode for HA.",
+ verbose_description => "Configures how the HA manager should select nodes to start or ".
+ "recover services. With 'basic', only the number of services is used, with 'static', ".
+ "static CPU and memory configuration of services is considered.",
+ },
+};
+
my $migration_format = {
type => {
default_key => 1,
},
};
+my $notification_format = {
+ 'package-updates' => {
+ type => 'string',
+ enum => ['auto', 'always', 'never'],
+ description => "Control when the daily update job should send out notification mails.",
+ verbose_description => "Control how often the daily update job should send out notification mails:\n"
+ ."* 'auto' daily for systems with a valid subscription, as those are assumed to be "
+ ." production-ready and thus should know about pending updates.\n"
+ ."* 'always' every update, if there are new pending updates.\n"
+ ."* 'never' never send a notification for new pending updates.\n",
+ default => 'auto',
+ },
+};
+
my $ha_format = {
shutdown_policy => {
type => 'string',
},
upper => {
type => 'integer',
- description => "Upper, inclusive boundary for free next-id API range.",
+ description => "Upper, exclusive boundary for free next-id API range.",
min => 100,
- max => 1000 * 1000 * 1000 - 1,
+ max => 1000 * 1000 * 1000,
default => 1000 * 1000, # lower than the maximum on purpose
optional => 1,
},
id => {
type => 'string',
description =>
- 'Relying part ID. Must be the domain name without protocol, port or location.'
+ 'Relying party ID. Must be the domain name without protocol, port or location.'
.' Changing this *will* break existing credentials.',
format_description => 'DOMAINNAME',
optional => 1,
},
+ 'allow-subdomains' => {
+ type => 'boolean',
+ description => 'Whether to allow the origin to be a subdomain, rather than the exact URL.',
+ optional => 1,
+ default => 1,
+ },
};
PVE::JSONSchema::register_format('mac-prefix', \&pve_verify_mac_prefix);
return $mac_prefix;
}
+my $COLOR_RE = '[0-9a-fA-F]{6}';
+my $TAG_COLOR_OVERRIDE_RE = "(?:${PVE::JSONSchema::PVE_TAG_RE}:${COLOR_RE}(?:\:${COLOR_RE})?)";
+
+my $tag_style_format = {
+ 'shape' => {
+ optional => 1,
+ type => 'string',
+ enum => ['full', 'circle', 'dense', 'none'],
+ default => 'circle',
+ description => "Tag shape for the web ui tree. 'full' draws the full tag. "
+ ."'circle' draws only a circle with the background color. "
+ ."'dense' only draws a small rectancle (useful when many tags are assigned to each guest)."
+ ."'none' disables showing the tags.",
+ },
+ 'color-map' => {
+ optional => 1,
+ type => 'string',
+ pattern => "${TAG_COLOR_OVERRIDE_RE}(?:\;$TAG_COLOR_OVERRIDE_RE)*",
+ typetext => '<tag>:<hex-color>[:<hex-color-for-text>][;<tag>=...]',
+ description => "Manual color mapping for tags (semicolon separated).",
+ },
+ ordering => {
+ optional => 1,
+ type => 'string',
+ enum => ['config', 'alphabetical'],
+ default => 'alphabetical',
+ description => 'Controls the sorting of the tags in the web-interface and the API update.',
+ },
+ 'case-sensitive' => {
+ type => 'boolean',
+ description => 'Controls if filtering for unique tags on update should check case-sensitive.',
+ optional => 1,
+ default => 0,
+ },
+};
+
+my $user_tag_privs_format = {
+ 'user-allow' => {
+ optional => 1,
+ type => 'string',
+ enum => ['none', 'list', 'existing', 'free'],
+ default => 'free',
+ description => "Controls tag usage for users without `Sys.Modify` on `/` by either "
+ ."allowing `none`, a `list`, already `existing` or anything (`free`).",
+ verbose_description => "Controls which tags can be set or deleted on resources a user "
+ ."controls (such as guests). Users with the `Sys.Modify` privilege on `/` are always "
+ ." unrestricted. "
+ ."* 'none' no tags are usable. "
+ ."* 'list' tags from 'user-allow-list' are usable. "
+ ."* 'existing' like list, but already existing tags of resources are also usable."
+ ."* 'free' no tag restrictions.",
+ },
+ 'user-allow-list' => {
+ optional => 1,
+ type => 'string',
+ pattern => "${PVE::JSONSchema::PVE_TAG_RE}(?:\;${PVE::JSONSchema::PVE_TAG_RE})*",
+ typetext => "<tag>[;<tag>...]",
+ description => "List of tags users are allowed to set and delete (semicolon separated) "
+ ."for 'user-allow' values 'list' and 'existing'.",
+ },
+};
+
my $datacenter_schema = {
type => "object",
additionalProperties => 0,
properties => {
+ crs => {
+ optional => 1,
+ type => 'string', format => $crs_format,
+ description => "Cluster resource scheduling settings.",
+ },
keyboard => {
optional => 1,
type => 'string',
description => "Specify external http proxy which is used for downloads (example: 'http://username:password\@host:port/')",
pattern => "http://.*",
},
+ # FIXME: remove with 8.0 (add check to pve7to8!), merged into "migration" since 4.3
migration_unsecure => {
optional => 1,
type => 'boolean',
console => {
optional => 1,
type => 'string',
- description => "Select the default Console viewer. You can either use the builtin java applet (VNC; deprecated and maps to html5), an external virt-viewer comtatible application (SPICE), an HTML5 based vnc viewer (noVNC), or an HTML5 based console client (xtermjs). If the selected viewer is not available (e.g. SPICE not activated for the VM), the fallback is noVNC.",
+ description => "Select the default Console viewer. You can either use the builtin java"
+ ." applet (VNC; deprecated and maps to html5), an external virt-viewer comtatible application (SPICE), an HTML5 based vnc viewer (noVNC), or an HTML5 based console client (xtermjs). If the selected viewer is not available (e.g. SPICE not activated for the VM), the fallback is noVNC.",
+ # FIXME: remove 'applet' with 8.0 (add pve7to8 check!)
enum => ['applet', 'vv', 'html5', 'xtermjs'],
},
email_from => {
format => 'mac-prefix',
description => 'Prefix for autogenerated MAC addresses.',
},
+ notify => {
+ optional => 1,
+ type => 'string', format => $notification_format,
+ description => "Cluster-wide notification settings.",
+ },
bwlimit => PVE::JSONSchema::get_standard_option('bwlimit'),
u2f => {
optional => 1,
maxLength => 64 * 1024,
optional => 1,
},
+ 'tag-style' => {
+ optional => 1,
+ type => 'string',
+ description => "Tag style options.",
+ format => $tag_style_format,
+ },
+ 'user-tag-access' => {
+ optional => 1,
+ type => 'string',
+ description => "Privilege options for user-settable tags",
+ format => $user_tag_privs_format,
+ },
+ 'registered-tags' => {
+ optional => 1,
+ type => 'string',
+ description => "A list of tags that require a `Sys.Modify` on '/' to set and delete. "
+ ."Tags set here that are also in 'user-tag-access' also require `Sys.Modify`.",
+ pattern => "(?:${PVE::JSONSchema::PVE_TAG_RE};)*${PVE::JSONSchema::PVE_TAG_RE}",
+ typetext => "<tag>[;<tag>...]",
+ },
},
};
# description may be comment or key-value pair (or both)
my $comment = '';
for my $line (split(/\n/, $raw)) {
- if ($line =~ /^\#(.*)\s*$/) {
+ if ($line =~ /^\#(.*)$/) {
$comment .= PVE::Tools::decode_text($1) . "\n";
}
}
$res->{description} = $comment;
+ if (my $crs = $res->{crs}) {
+ $res->{crs} = parse_property_string($crs_format, $crs);
+ }
+
if (my $migration = $res->{migration}) {
$res->{migration} = parse_property_string($migration_format, $migration);
}
if (my $ha = $res->{ha}) {
$res->{ha} = parse_property_string($ha_format, $ha);
}
+ if (my $notify = $res->{notify}) {
+ $res->{notify} = parse_property_string($notification_format, $notify);
+ }
if (my $u2f = $res->{u2f}) {
$res->{u2f} = parse_property_string($u2f_format, $u2f);
$res->{webauthn} = parse_property_string($webauthn_format, $webauthn);
}
+ if (my $tag_style = $res->{'tag-style'}) {
+ $res->{'tag-style'} = parse_property_string($tag_style_format, $tag_style);
+ }
+
+ if (my $user_tag_privs = $res->{'user-tag-access'}) {
+ $res->{'user-tag-access'} =
+ parse_property_string($user_tag_privs_format, $user_tag_privs);
+
+ if (my $user_tags = $res->{'user-tag-access'}->{'user-allow-list'}) {
+ $res->{'user-tag-access'}->{'user-allow-list'} = [split(';', $user_tags)];
+ }
+ }
+
+ if (my $admin_tags = $res->{'registered-tags'}) {
+ $res->{'registered-tags'} = [split(';', $admin_tags)];
+ }
+
# for backwards compatibility only, new migration property has precedence
if (defined($res->{migration_unsecure})) {
if (defined($res->{migration}->{type})) {
$cfg->{console} = 'html5';
}
+ if (ref(my $crs = $cfg->{crs})) {
+ $cfg->{crs} = PVE::JSONSchema::print_property_string($crs, $crs_format);
+ }
+
if (ref(my $migration = $cfg->{migration})) {
$cfg->{migration} = PVE::JSONSchema::print_property_string($migration, $migration_format);
}
my $lower = int($next_id->{lower} // $next_id_format->{lower}->{default});
my $upper = int($next_id->{upper} // $next_id_format->{upper}->{default});
- die "lower ($lower) <= upper ($upper) boundary rule broken" if $lower > $upper;
+ die "lower ($lower) <= upper ($upper) boundary rule broken\n" if $lower > $upper;
$cfg->{'next-id'} = PVE::JSONSchema::print_property_string($next_id, $next_id_format);
}
if (ref(my $ha = $cfg->{ha})) {
$cfg->{ha} = PVE::JSONSchema::print_property_string($ha, $ha_format);
}
+ if (ref(my $notify = $cfg->{notify})) {
+ $cfg->{notify} = PVE::JSONSchema::print_property_string($notify, $notification_format);
+ }
if (ref(my $u2f = $cfg->{u2f})) {
$cfg->{u2f} = PVE::JSONSchema::print_property_string($u2f, $u2f_format);
$cfg->{webauthn} = PVE::JSONSchema::print_property_string($webauthn, $webauthn_format);
}
+ if (ref(my $tag_style = $cfg->{'tag-style'})) {
+ $cfg->{'tag-style'} = PVE::JSONSchema::print_property_string($tag_style, $tag_style_format);
+ }
+
+ if (ref(my $user_tag_privs = $cfg->{'user-tag-access'})) {
+ if (my $user_tags = $user_tag_privs->{'user-allow-list'}) {
+ $user_tag_privs->{'user-allow-list'} = join(';', sort $user_tags->@*);
+ }
+ $cfg->{'user-tag-access'} =
+ PVE::JSONSchema::print_property_string($user_tag_privs, $user_tag_privs_format);
+ }
+
+ if (ref(my $admin_tags = $cfg->{'registered-tags'})) {
+ $cfg->{'registered-tags'} = join(';', sort $admin_tags->@*);
+ }
+
my $comment = '';
# add description as comment to top of file
my $description = $cfg->{description} || '';