Merge branch 'master' of github.com:NixOS/hydra
This commit is contained in:
@ -15,6 +15,7 @@ use Email::Sender::Simple qw(sendmail);
|
||||
use Email::Sender::Transport::SMTP;
|
||||
use Config::General;
|
||||
|
||||
|
||||
sub nixMachines {
|
||||
my ($c) = @_;
|
||||
my $result = "# GENERATED BY HYDRA\n";
|
||||
@ -32,6 +33,7 @@ sub nixMachines {
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
sub saveNixMachines {
|
||||
my ($c) = @_;
|
||||
|
||||
@ -42,12 +44,14 @@ sub saveNixMachines {
|
||||
close (NIXMACHINES);
|
||||
}
|
||||
|
||||
|
||||
sub admin : Chained('/') PathPart('admin') CaptureArgs(0) {
|
||||
my ($self, $c) = @_;
|
||||
requireAdmin($c);
|
||||
$c->stash->{admin} = 1;
|
||||
}
|
||||
|
||||
|
||||
sub index : Chained('admin') PathPart('') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
$c->stash->{machines} = [$c->model('DB::BuildMachines')->search(
|
||||
@ -62,7 +66,8 @@ sub index : Chained('admin') PathPart('') Args(0) {
|
||||
, order_by => [ 'machine', 'stepnr' ]
|
||||
} ) ];
|
||||
$c->stash->{template} = 'admin.tt';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub updateUser {
|
||||
my ($c, $user) = @_;
|
||||
@ -88,6 +93,16 @@ sub updateUser {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub create_user : Chained('admin') PathPart('create-user') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
requireAdmin($c);
|
||||
|
||||
error($c, "Not implemented yet!"); # FIXME
|
||||
}
|
||||
|
||||
|
||||
sub user : Chained('admin') PathPart('user') CaptureArgs(1) {
|
||||
my ($self, $c, $username) = @_;
|
||||
|
||||
@ -99,6 +114,7 @@ sub user : Chained('admin') PathPart('user') CaptureArgs(1) {
|
||||
$c->stash->{user} = $user;
|
||||
}
|
||||
|
||||
|
||||
sub users : Chained('admin') PathPart('users') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
$c->stash->{users} = [$c->model('DB::Users')->search({}, {order_by => "username"})];
|
||||
@ -106,6 +122,7 @@ sub users : Chained('admin') PathPart('users') Args(0) {
|
||||
$c->stash->{template} = 'users.tt';
|
||||
}
|
||||
|
||||
|
||||
sub user_edit : Chained('user') PathPart('edit') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
@ -113,16 +130,23 @@ sub user_edit : Chained('user') PathPart('edit') Args(0) {
|
||||
$c->stash->{edit} = 1;
|
||||
}
|
||||
|
||||
|
||||
sub user_edit_submit : Chained('user') PathPart('submit') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
requirePost($c);
|
||||
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
updateUser($c, $c->stash->{user}) ;
|
||||
if (($c->request->params->{submit} || "") eq "delete") {
|
||||
$c->stash->{user}->delete;
|
||||
} else {
|
||||
updateUser($c, $c->stash->{user});
|
||||
}
|
||||
});
|
||||
|
||||
$c->res->redirect("/admin/users");
|
||||
}
|
||||
|
||||
|
||||
sub sendemail {
|
||||
my ($to, $subject, $body) = @_;
|
||||
|
||||
@ -141,6 +165,7 @@ sub sendemail {
|
||||
sendmail($email);
|
||||
}
|
||||
|
||||
|
||||
sub reset_password : Chained('user') PathPart('reset-password') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
@ -168,6 +193,7 @@ sub reset_password : Chained('user') PathPart('reset-password') Args(0) {
|
||||
$c->res->redirect("/admin/users");
|
||||
}
|
||||
|
||||
|
||||
sub machines : Chained('admin') PathPart('machines') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
$c->stash->{machines} = [$c->model('DB::BuildMachines')->search({}, {order_by => "hostname"})];
|
||||
@ -176,7 +202,8 @@ sub machines : Chained('admin') PathPart('machines') Args(0) {
|
||||
$c->stash->{nixMachinesWritable} = (-e "/etc/nix.machines" && -w "/etc/nix.machines");
|
||||
|
||||
$c->stash->{template} = 'machines.tt';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub machine : Chained('admin') PathPart('machine') CaptureArgs(1) {
|
||||
my ($self, $c, $machineName) = @_;
|
||||
@ -189,6 +216,7 @@ sub machine : Chained('admin') PathPart('machine') CaptureArgs(1) {
|
||||
$c->stash->{machine} = $machine;
|
||||
}
|
||||
|
||||
|
||||
sub machine_edit : Chained('machine') PathPart('edit') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
$c->stash->{template} = 'machine.tt';
|
||||
@ -196,19 +224,27 @@ sub machine_edit : Chained('machine') PathPart('edit') Args(0) {
|
||||
$c->stash->{edit} = 1;
|
||||
}
|
||||
|
||||
|
||||
sub machine_edit_submit : Chained('machine') PathPart('submit') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
requirePost($c);
|
||||
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
updateMachine($c, $c->stash->{machine}) ;
|
||||
if (($c->request->params->{submit} || "") eq "delete") {
|
||||
$c->stash->{machine}->delete;
|
||||
} else {
|
||||
updateMachine($c, $c->stash->{machine});
|
||||
}
|
||||
});
|
||||
|
||||
saveNixMachines($c);
|
||||
|
||||
$c->res->redirect("/admin/machines");
|
||||
}
|
||||
|
||||
|
||||
sub updateMachine {
|
||||
my ($c, $machine) = @_;
|
||||
my ($c, $machine) = @_;
|
||||
|
||||
my $hostname = trim $c->request->params->{"hostname"};
|
||||
my $username = trim $c->request->params->{"username"};
|
||||
@ -240,6 +276,7 @@ sub updateMachine {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub create_machine : Chained('admin') PathPart('create-machine') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
@ -269,16 +306,6 @@ sub create_machine_submit : Chained('admin') PathPart('create-machine/submit') A
|
||||
$c->res->redirect("/admin/machines");
|
||||
}
|
||||
|
||||
sub machine_delete : Chained('machine') PathPart('delete') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
requirePost($c);
|
||||
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
$c->stash->{machine}->delete;
|
||||
});
|
||||
saveNixMachines($c);
|
||||
$c->res->redirect("/admin/machines");
|
||||
}
|
||||
|
||||
sub machine_enable : Chained('machine') PathPart('enable') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
@ -287,6 +314,7 @@ sub machine_enable : Chained('machine') PathPart('enable') Args(0) {
|
||||
$c->res->redirect("/admin/machines");
|
||||
}
|
||||
|
||||
|
||||
sub machine_disable : Chained('machine') PathPart('disable') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
$c->stash->{machine}->update({ enabled => 0});
|
||||
@ -294,6 +322,7 @@ sub machine_disable : Chained('machine') PathPart('disable') Args(0) {
|
||||
$c->res->redirect("/admin/machines");
|
||||
}
|
||||
|
||||
|
||||
sub clear_queue_non_current : Chained('admin') Path('clear-queue-non-current') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
# !!! Mark the builds as cancelled instead.
|
||||
@ -301,6 +330,7 @@ sub clear_queue_non_current : Chained('admin') Path('clear-queue-non-current') A
|
||||
$c->res->redirect("/admin");
|
||||
}
|
||||
|
||||
|
||||
sub clear_queue : Chained('admin') Path('clear-queue') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
# !!! Mark the builds as cancelled instead.
|
||||
@ -308,6 +338,7 @@ sub clear_queue : Chained('admin') Path('clear-queue') Args(0) {
|
||||
$c->res->redirect("/admin");
|
||||
}
|
||||
|
||||
|
||||
sub clearfailedcache : Chained('admin') Path('clear-failed-cache') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
@ -316,6 +347,7 @@ sub clearfailedcache : Chained('admin') Path('clear-failed-cache') Args(0) {
|
||||
$c->res->redirect("/admin");
|
||||
}
|
||||
|
||||
|
||||
sub clearvcscache : Chained('admin') Path('clear-vcs-cache') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
@ -334,6 +366,7 @@ sub clearvcscache : Chained('admin') Path('clear-vcs-cache') Args(0) {
|
||||
$c->res->redirect("/admin");
|
||||
}
|
||||
|
||||
|
||||
sub managenews : Chained('admin') Path('news') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
@ -342,6 +375,7 @@ sub managenews : Chained('admin') Path('news') Args(0) {
|
||||
$c->stash->{template} = 'news.tt';
|
||||
}
|
||||
|
||||
|
||||
sub news_submit : Chained('admin') Path('news/submit') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
@ -359,6 +393,7 @@ sub news_submit : Chained('admin') Path('news/submit') Args(0) {
|
||||
$c->res->redirect("/admin/news");
|
||||
}
|
||||
|
||||
|
||||
sub news_delete : Chained('admin') Path('news/delete') Args(1) {
|
||||
my ($self, $c, $id) = @_;
|
||||
|
||||
@ -371,6 +406,7 @@ sub news_delete : Chained('admin') Path('news/delete') Args(1) {
|
||||
$c->res->redirect("/admin/news");
|
||||
}
|
||||
|
||||
|
||||
sub force_eval : Chained('admin') Path('eval') Args(2) {
|
||||
my ($self, $c, $projectName, $jobsetName) = @_;
|
||||
|
||||
@ -387,4 +423,5 @@ sub force_eval : Chained('admin') Path('eval') Args(2) {
|
||||
$c->res->redirect("/project/$projectName");
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
|
@ -351,9 +351,9 @@ sub restart : Chained('build') PathPart Args(0) {
|
||||
|
||||
requireProjectOwner($c, $build->project);
|
||||
|
||||
my $drvpath = $build->drvpath ;
|
||||
my $drvpath = $build->drvpath;
|
||||
error($c, "This build cannot be restarted.")
|
||||
unless $build->finished && -f $drvpath ;
|
||||
unless $build->finished && -f $drvpath;
|
||||
|
||||
restartBuild($c->model('DB')->schema, $build);
|
||||
|
||||
|
@ -221,7 +221,7 @@ sub updateJobset {
|
||||
my ($c, $jobset) = @_;
|
||||
|
||||
my $jobsetName = trim $c->request->params->{"name"};
|
||||
error($c, "Invalid jobset name: $jobsetName") unless $jobsetName =~ /^[[:alpha:]][\w\-]*$/;
|
||||
error($c, "Invalid jobset name: ‘$jobsetName’") if $jobsetName !~ /^$jobsetNameRE$/;
|
||||
|
||||
my ($nixExprPath, $nixExprInput) = nixExprPathFromParams $c;
|
||||
|
||||
@ -298,13 +298,13 @@ sub clone_submit : Chained('jobset') PathPart('clone/submit') Args(0) {
|
||||
requireProjectOwner($c, $jobset->project);
|
||||
requirePost($c);
|
||||
|
||||
my $newjobsetName = trim $c->request->params->{"newjobset"};
|
||||
error($c, "Invalid jobset name: $newjobsetName") unless $newjobsetName =~ /^[[:alpha:]][\w\-]*$/;
|
||||
my $newJobsetName = trim $c->request->params->{"newjobset"};
|
||||
error($c, "Invalid jobset name: $newJobsetName") unless $newJobsetName =~ /^[[:alpha:]][\w\-]*$/;
|
||||
|
||||
my $newjobset;
|
||||
my $newJobset;
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
$newjobset = $jobset->project->jobsets->create(
|
||||
{ name => $newjobsetName
|
||||
$newJobset = $jobset->project->jobsets->create(
|
||||
{ name => $newJobsetName
|
||||
, description => $jobset->description
|
||||
, nixexprpath => $jobset->nixexprpath
|
||||
, nixexprinput => $jobset->nixexprinput
|
||||
@ -314,14 +314,14 @@ sub clone_submit : Chained('jobset') PathPart('clone/submit') Args(0) {
|
||||
});
|
||||
|
||||
foreach my $input ($jobset->jobsetinputs) {
|
||||
my $newinput = $newjobset->jobsetinputs->create({name => $input->name, type => $input->type});
|
||||
my $newinput = $newJobset->jobsetinputs->create({name => $input->name, type => $input->type});
|
||||
foreach my $inputalt ($input->jobsetinputalts) {
|
||||
$newinput->jobsetinputalts->create({altnr => $inputalt->altnr, value => $inputalt->value});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$c->res->redirect($c->uri_for($c->controller('Jobset')->action_for("edit"), [$jobset->project->name, $newjobsetName]));
|
||||
$c->res->redirect($c->uri_for($c->controller('Jobset')->action_for("edit"), [$jobset->project->name, $newJobsetName]));
|
||||
}
|
||||
|
||||
|
||||
|
@ -32,13 +32,17 @@ sub view : Chained('eval') PathPart('') Args(0) {
|
||||
# Allow comparing this evaluation against the previous evaluation
|
||||
# (default), an arbitrary evaluation, or the latest completed
|
||||
# evaluation of another jobset.
|
||||
if (defined $compare && $compare =~ /^\d+$/) {
|
||||
$eval2 = $c->model('DB::JobsetEvals')->find($compare)
|
||||
or notFound($c, "Evaluation $compare doesn't exist.");
|
||||
} elsif (defined $compare && $compare =~ /^($jobNameRE)$/) {
|
||||
my $j = $c->stash->{project}->jobsets->find({name => $compare})
|
||||
or notFound($c, "Jobset $compare doesn't exist.");
|
||||
$eval2 = getLatestFinishedEval($c, $j);
|
||||
if (defined $compare) {
|
||||
if ($compare =~ /^\d+$/) {
|
||||
$eval2 = $c->model('DB::JobsetEvals')->find($compare)
|
||||
or notFound($c, "Evaluation $compare doesn't exist.");
|
||||
} elsif (defined $compare && $compare =~ /^($jobsetNameRE)$/) {
|
||||
my $j = $c->stash->{project}->jobsets->find({name => $compare})
|
||||
or notFound($c, "Jobset $compare doesn't exist.");
|
||||
$eval2 = getLatestFinishedEval($c, $j);
|
||||
} else {
|
||||
notFound($c, "Unknown comparison source ‘$compare’.");
|
||||
}
|
||||
} else {
|
||||
($eval2) = $eval->jobset->jobsetevals->search(
|
||||
{ hasnewbuilds => 1, id => { '<', $eval->id } },
|
||||
|
@ -45,6 +45,11 @@ sub submit : Chained('project') PathPart Args(0) {
|
||||
requireProjectOwner($c, $c->stash->{project});
|
||||
requirePost($c);
|
||||
|
||||
if (($c->request->params->{submit} || "") eq "delete") {
|
||||
$c->stash->{project}->delete;
|
||||
$c->res->redirect($c->uri_for("/"));
|
||||
}
|
||||
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
updateProject($c, $c->stash->{project});
|
||||
});
|
||||
@ -65,6 +70,7 @@ sub hide : Chained('project') PathPart Args(0) {
|
||||
$c->res->redirect($c->uri_for("/"));
|
||||
}
|
||||
|
||||
|
||||
sub unhide : Chained('project') PathPart Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
@ -77,19 +83,6 @@ sub unhide : Chained('project') PathPart Args(0) {
|
||||
$c->res->redirect($c->uri_for("/"));
|
||||
}
|
||||
|
||||
sub delete : Chained('project') PathPart Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
requireProjectOwner($c, $c->stash->{project});
|
||||
requirePost($c);
|
||||
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
$c->stash->{project}->delete;
|
||||
});
|
||||
|
||||
$c->res->redirect($c->uri_for("/"));
|
||||
}
|
||||
|
||||
|
||||
sub requireMayCreateProjects {
|
||||
my ($c) = @_;
|
||||
@ -119,6 +112,8 @@ sub create_submit : Path('/create-project/submit') {
|
||||
|
||||
my $projectName = trim $c->request->params->{name};
|
||||
|
||||
error($c, "Invalid project name: ‘$projectName’") if $projectName !~ /^$projectNameRE$/;
|
||||
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
# Note: $projectName is validated in updateProject,
|
||||
# which will abort the transaction if the name isn't
|
||||
@ -152,6 +147,8 @@ sub create_jobset_submit : Chained('project') PathPart('create-jobset/submit') A
|
||||
|
||||
my $jobsetName = trim $c->request->params->{name};
|
||||
|
||||
error($c, "Invalid jobset name: ‘$jobsetName’") if $jobsetName !~ /^$jobsetNameRE$/;
|
||||
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
# Note: $jobsetName is validated in updateProject, which will
|
||||
# abort the transaction if the name isn't valid.
|
||||
@ -167,12 +164,7 @@ sub create_jobset_submit : Chained('project') PathPart('create-jobset/submit') A
|
||||
|
||||
sub updateProject {
|
||||
my ($c, $project) = @_;
|
||||
my $projectName = trim $c->request->params->{name};
|
||||
error($c, "Invalid project name: " . ($projectName || "(empty)")) unless $projectName =~ /^[[:alpha:]][\w\-]*$/;
|
||||
|
||||
my $displayName = trim $c->request->params->{displayname};
|
||||
error($c, "Invalid display name: $displayName") if $displayName eq "";
|
||||
|
||||
my $owner = $project->owner;
|
||||
if ($c->check_user_roles('admin')) {
|
||||
$owner = trim $c->request->params->{owner};
|
||||
@ -180,6 +172,12 @@ sub updateProject {
|
||||
unless defined $c->model('DB::Users')->find({username => $owner});
|
||||
}
|
||||
|
||||
my $projectName = trim $c->request->params->{name};
|
||||
error($c, "Invalid project name: ‘$projectName’") if $projectName !~ /^$projectNameRE$/;
|
||||
|
||||
my $displayName = trim $c->request->params->{displayname};
|
||||
error($c, "Invalid display name: $displayName") if $displayName eq "";
|
||||
|
||||
$project->update(
|
||||
{ name => $projectName
|
||||
, displayname => $displayName
|
||||
|
@ -52,6 +52,8 @@ sub edit : Chained('release') PathPart('edit') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
requireProjectOwner($c, $c->stash->{project});
|
||||
$c->stash->{template} = 'edit-release.tt';
|
||||
$c->stash->{members} = [$c->stash->{release}->releasemembers->search({},
|
||||
{order_by => ["description"]})];
|
||||
}
|
||||
|
||||
|
||||
|
@ -116,6 +116,11 @@ sub edit : Chained('view') PathPart('edit') Args(0) {
|
||||
sub submit : Chained('view') PathPart('submit') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
requireProjectOwner($c, $c->stash->{project});
|
||||
if (($c->request->params->{submit} || "") eq "delete") {
|
||||
$c->stash->{view}->delete;
|
||||
$c->res->redirect($c->uri_for($c->controller('Project')->action_for('view'),
|
||||
[$c->stash->{project}->name]));
|
||||
}
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
updateView($c, $c->stash->{view});
|
||||
});
|
||||
@ -123,17 +128,6 @@ sub submit : Chained('view') PathPart('submit') Args(0) {
|
||||
}
|
||||
|
||||
|
||||
sub delete : Chained('view') PathPart('delete') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
requireProjectOwner($c, $c->stash->{project});
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
$c->stash->{view}->delete;
|
||||
});
|
||||
$c->res->redirect($c->uri_for($c->controller('Project')->action_for('view'),
|
||||
[$c->stash->{project}->name]));
|
||||
}
|
||||
|
||||
|
||||
sub latest : Chained('view') PathPart('latest') {
|
||||
my ($self, $c, @args) = @_;
|
||||
|
||||
|
@ -968,5 +968,11 @@ sub restartBuild {
|
||||
, busy => 0
|
||||
, locker => ""
|
||||
});
|
||||
|
||||
# Reset the stats for the evals to which this build belongs.
|
||||
# !!! Should do this in a trigger.
|
||||
foreach my $m ($build->jobsetevalmembers->all) {
|
||||
$m->eval->update({nrsucceeded => undef});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ our @EXPORT = qw(
|
||||
requireLogin requireProjectOwner requireAdmin requirePost isAdmin isProjectOwner
|
||||
trim
|
||||
getLatestFinishedEval
|
||||
$pathCompRE $relPathRE $relNameRE $jobNameRE $systemRE
|
||||
$pathCompRE $relPathRE $relNameRE $projectNameRE $jobsetNameRE $jobNameRE $systemRE
|
||||
@buildListColumns
|
||||
);
|
||||
|
||||
@ -181,12 +181,14 @@ sub getLatestFinishedEval {
|
||||
|
||||
|
||||
# Security checking of filenames.
|
||||
Readonly our $pathCompRE => "(?:[A-Za-z0-9-\+\._][A-Za-z0-9-\+\._]*)";
|
||||
Readonly our $relPathRE => "(?:$pathCompRE(?:/$pathCompRE)*)";
|
||||
Readonly our $relNameRE => "(?:[A-Za-z0-9-][A-Za-z0-9-\.]*)";
|
||||
Readonly our $attrNameRE => "(?:[A-Za-z_][A-Za-z0-9_]*)";
|
||||
Readonly our $jobNameRE => "(?:$attrNameRE(?:\\.$attrNameRE)*)";
|
||||
Readonly our $systemRE => "(?:[a-z0-9_]+-[a-z0-9_]+)";
|
||||
Readonly our $pathCompRE => "(?:[A-Za-z0-9-\+\._][A-Za-z0-9-\+\._]*)";
|
||||
Readonly our $relPathRE => "(?:$pathCompRE(?:/$pathCompRE)*)";
|
||||
Readonly our $relNameRE => "(?:[A-Za-z0-9-][A-Za-z0-9-\.]*)";
|
||||
Readonly our $attrNameRE => "(?:[A-Za-z_][A-Za-z0-9_]*)";
|
||||
Readonly our $projectNameRE => "(?:[A-Za-z_][A-Za-z0-9-_]*)";
|
||||
Readonly our $jobsetNameRE => "(?:[A-Za-z_][A-Za-z0-9-_]*)";
|
||||
Readonly our $jobNameRE => "(?:$attrNameRE(?:\\.$attrNameRE)*)";
|
||||
Readonly our $systemRE => "(?:[a-z0-9_]+-[a-z0-9_]+)";
|
||||
|
||||
|
||||
1;
|
||||
|
Reference in New Issue
Block a user