Adding upstream version 2.25.15.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
This commit is contained in:
parent
10737b110a
commit
b543f2e88d
485 changed files with 191459 additions and 0 deletions
427
lib/Devscripts/Salsa.pm
Executable file
427
lib/Devscripts/Salsa.pm
Executable file
|
@ -0,0 +1,427 @@
|
|||
package Devscripts::Salsa;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Devscripts::Salsa - salsa(1) base object
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Devscripts::Salsa;
|
||||
exit Devscripts::Salsa->new->run
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Devscripts::Salsa provides salsa(1) command launcher and some common utilities
|
||||
methods.
|
||||
|
||||
=cut
|
||||
|
||||
use strict;
|
||||
|
||||
use Devscripts::Output;
|
||||
use Devscripts::Salsa::Config;
|
||||
|
||||
BEGIN {
|
||||
eval "use GitLab::API::v4;use GitLab::API::v4::Constants qw(:all)";
|
||||
if ($@) {
|
||||
print STDERR "You must install GitLab::API::v4\n";
|
||||
exit 1;
|
||||
}
|
||||
}
|
||||
use Moo;
|
||||
use File::Basename;
|
||||
use File::Path qw(make_path);
|
||||
|
||||
# Command aliases
|
||||
use constant cmd_aliases => {
|
||||
# Alias => Filename -> ./lib/Devscripts/Salsa/*.pm
|
||||
# Preferred terminology
|
||||
check_projects => 'check_repo',
|
||||
create_project => 'create_repo',
|
||||
delete_project => 'del_repo',
|
||||
delete_user => 'del_user',
|
||||
list_projects => 'list_repos',
|
||||
list_users => 'group',
|
||||
search_groups => 'search_group',
|
||||
search_projects => 'search_project',
|
||||
search_users => 'search_user',
|
||||
update_projects => 'update_repo',
|
||||
|
||||
# Catch possible typo (As able to-do multiple items at once)
|
||||
list_user => 'group',
|
||||
check_project => 'check_repo',
|
||||
list_project => 'list_repos',
|
||||
update_project => 'update_repo',
|
||||
|
||||
# Abbreviation
|
||||
co => 'checkout',
|
||||
ls => 'list_repos',
|
||||
mr => 'merge_request',
|
||||
mrs => 'merge_requests',
|
||||
schedule => 'pipeline_schedule',
|
||||
schedules => 'pipeline_schedules',
|
||||
|
||||
# Legacy
|
||||
search => 'search_project',
|
||||
search_repo => 'search_project',
|
||||
};
|
||||
|
||||
=head1 ACCESSORS
|
||||
|
||||
=over
|
||||
|
||||
=item B<config> : Devscripts::Salsa::Config object (parsed)
|
||||
|
||||
=cut
|
||||
|
||||
has config => (
|
||||
is => 'rw',
|
||||
default => sub { Devscripts::Salsa::Config->new->parse },
|
||||
);
|
||||
|
||||
=item B<cache> : Devscripts::JSONCache object
|
||||
|
||||
=cut
|
||||
|
||||
# File cache to avoid polling GitLab too much
|
||||
# (used to store ids, paths and names)
|
||||
has _cache => (
|
||||
is => 'rw',
|
||||
lazy => 1,
|
||||
default => sub {
|
||||
return {} unless ($_[0]->config->cache_file);
|
||||
my %h;
|
||||
eval {
|
||||
my ($cache_file, $cache_dir) = fileparse $_[0]->config->cache_file;
|
||||
if (!-d $cache_dir) {
|
||||
make_path $cache_dir;
|
||||
}
|
||||
require Devscripts::JSONCache;
|
||||
tie %h, 'Devscripts::JSONCache', $_[0]->config->cache_file;
|
||||
ds_debug "Cache opened";
|
||||
};
|
||||
if ($@) {
|
||||
ds_verbose "Unable to create cache object: $@";
|
||||
return {};
|
||||
}
|
||||
return \%h;
|
||||
},
|
||||
);
|
||||
has cache => (
|
||||
is => 'rw',
|
||||
lazy => 1,
|
||||
default => sub {
|
||||
$_[0]->_cache->{ $_[0]->config->api_url } //= {};
|
||||
return $_[0]->_cache->{ $_[0]->config->api_url };
|
||||
},
|
||||
);
|
||||
|
||||
# In memory cache (used to avoid querying the project id twice when using
|
||||
# update_safe
|
||||
has projectCache => (
|
||||
is => 'rw',
|
||||
default => sub { {} },
|
||||
);
|
||||
|
||||
=item B<api>: GitLab::API::v4 object
|
||||
|
||||
=cut
|
||||
|
||||
has api => (
|
||||
is => 'rw',
|
||||
lazy => 1,
|
||||
default => sub {
|
||||
my $r = GitLab::API::v4->new(
|
||||
url => $_[0]->config->api_url,
|
||||
(
|
||||
$_[0]->config->private_token
|
||||
? (private_token => $_[0]->config->private_token)
|
||||
: ()
|
||||
),
|
||||
);
|
||||
$r or ds_die "Unable to create GitLab::API::v4 object";
|
||||
return $r;
|
||||
},
|
||||
);
|
||||
|
||||
=item User or group in use
|
||||
|
||||
=over
|
||||
|
||||
=item B<username>
|
||||
|
||||
=item B<user_id>
|
||||
|
||||
=item B<group_id>
|
||||
|
||||
=item B<group_path>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
# Accessors that resolve names, ids or paths
|
||||
has username => (
|
||||
is => 'rw',
|
||||
lazy => 1,
|
||||
default => sub { $_[0]->id2username });
|
||||
|
||||
has user_id => (
|
||||
is => 'rw',
|
||||
lazy => 1,
|
||||
default => sub {
|
||||
$_[0]->config->user_id || $_[0]->username2id;
|
||||
},
|
||||
);
|
||||
|
||||
has group_id => (
|
||||
is => 'rw',
|
||||
lazy => 1,
|
||||
default => sub { $_[0]->config->group_id || $_[0]->group2id },
|
||||
);
|
||||
|
||||
has group_path => (
|
||||
is => 'rw',
|
||||
lazy => 1,
|
||||
default => sub {
|
||||
my ($self) = @_;
|
||||
return undef unless ($self->group_id);
|
||||
return $self->cache->{group_path}->{ $self->{group_id} }
|
||||
if $self->cache->{group_path}->{ $self->{group_id} };
|
||||
return $self->{group_path} if ($self->{group_path}); # Set if --group
|
||||
eval {
|
||||
$self->{group_path}
|
||||
= $self->api->group_without_projects($self->group_id)
|
||||
->{full_path};
|
||||
$self->cache->{group_path}->{ $self->{group_id} }
|
||||
= $self->{group_path};
|
||||
};
|
||||
if ($@) {
|
||||
ds_verbose $@;
|
||||
ds_warn "Unexistent group " . $self->group_id;
|
||||
return undef;
|
||||
}
|
||||
return $self->{group_path};
|
||||
},
|
||||
);
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item B<run>: main method, load and run command and return Unix result code.
|
||||
|
||||
=cut
|
||||
|
||||
sub run {
|
||||
my ($self, $args) = @_;
|
||||
binmode STDOUT, ':utf8';
|
||||
|
||||
# Check group or user id
|
||||
my $command = $self->config->command;
|
||||
if (my $tmp = cmd_aliases->{$command}) {
|
||||
$command = $tmp;
|
||||
}
|
||||
eval { with "Devscripts::Salsa::$command" };
|
||||
if ($@) {
|
||||
ds_verbose $@;
|
||||
ds_die "Unknown command $command";
|
||||
return 1;
|
||||
}
|
||||
return $self->$command(@ARGV);
|
||||
}
|
||||
|
||||
=back
|
||||
|
||||
=head2 Utilities
|
||||
|
||||
=over
|
||||
|
||||
=item B<levels_name>, B<levels_code>: convert strings to GitLab level codes
|
||||
(owner, maintainer, developer, reporter and guest)
|
||||
|
||||
=cut
|
||||
|
||||
sub levels_name {
|
||||
my $res = {
|
||||
|
||||
# needs GitLab::API::v4::Constants 0.11
|
||||
# no_access => $GITLAB_ACCESS_LEVEL_NO_ACCESS,
|
||||
guest => $GITLAB_ACCESS_LEVEL_GUEST,
|
||||
reporter => $GITLAB_ACCESS_LEVEL_REPORTER,
|
||||
developer => $GITLAB_ACCESS_LEVEL_DEVELOPER,
|
||||
maintainer => $GITLAB_ACCESS_LEVEL_MASTER,
|
||||
owner => $GITLAB_ACCESS_LEVEL_OWNER,
|
||||
}->{ $_[1] };
|
||||
ds_die "Unknown access level '$_[1]'" unless ($res);
|
||||
return $res;
|
||||
}
|
||||
|
||||
sub levels_code {
|
||||
return {
|
||||
$GITLAB_ACCESS_LEVEL_GUEST => 'guest',
|
||||
$GITLAB_ACCESS_LEVEL_REPORTER => 'reporter',
|
||||
$GITLAB_ACCESS_LEVEL_DEVELOPER => 'developer',
|
||||
$GITLAB_ACCESS_LEVEL_MASTER => 'maintainer',
|
||||
$GITLAB_ACCESS_LEVEL_OWNER => 'owner',
|
||||
}->{ $_[1] };
|
||||
}
|
||||
|
||||
=item B<username2id>, B<id2username>: convert username to an id an reverse
|
||||
|
||||
=cut
|
||||
|
||||
sub username2id {
|
||||
my ($self, $user) = @_;
|
||||
$user ||= $self->config->user || $self->api->current_user->{id};
|
||||
unless ($user) {
|
||||
return ds_warn "Token seems invalid";
|
||||
return 1;
|
||||
}
|
||||
unless ($user =~ /^\d+$/) {
|
||||
return $self->cache->{user_id}->{$user}
|
||||
if $self->cache->{user_id}->{$user};
|
||||
my $users = $self->api->users({ username => $user });
|
||||
return ds_die "Username '$user' not found"
|
||||
unless ($users and @$users);
|
||||
ds_verbose "$user id is $users->[0]->{id}";
|
||||
$self->cache->{user_id}->{$user} = $users->[0]->{id};
|
||||
return $users->[0]->{id};
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
sub id2username {
|
||||
my ($self, $id) = @_;
|
||||
$id ||= $self->config->user_id || $self->api->current_user->{id};
|
||||
return $self->cache->{user}->{$id} if $self->cache->{user}->{$id};
|
||||
my $res = eval { $self->api->user($id)->{username} };
|
||||
if ($@) {
|
||||
ds_verbose $@;
|
||||
return ds_die "$id not found";
|
||||
}
|
||||
ds_verbose "$id is $res";
|
||||
$self->cache->{user}->{$id} = $res;
|
||||
return $res;
|
||||
}
|
||||
|
||||
=item B<group2id>: convert group name to id
|
||||
|
||||
=cut
|
||||
|
||||
sub group2id {
|
||||
my ($self, $name) = @_;
|
||||
$name ||= $self->config->group;
|
||||
return unless $name;
|
||||
if ($self->cache->{group_id}->{$name}) {
|
||||
$self->group_path($self->cache->{group_id}->{$name}->{path});
|
||||
return $self->group_id($self->cache->{group_id}->{$name}->{id});
|
||||
}
|
||||
my $groups = $self->api->group_without_projects($name);
|
||||
if ($groups) {
|
||||
$groups = [$groups];
|
||||
} else {
|
||||
$self->api->groups({ search => $name });
|
||||
}
|
||||
return ds_die "No group found" unless ($groups and @$groups);
|
||||
if (scalar @$groups > 1) {
|
||||
ds_warn "More than one group found:";
|
||||
foreach (@$groups) {
|
||||
print <<END;
|
||||
Id : $_->{id}
|
||||
Name : $_->{name}
|
||||
Full name: $_->{full_name}
|
||||
Full path: $_->{full_path}
|
||||
|
||||
END
|
||||
}
|
||||
return ds_die "Set the chosen group id using --group-id.";
|
||||
}
|
||||
ds_verbose "$name id is $groups->[0]->{id}";
|
||||
$self->cache->{group_id}->{$name}->{path}
|
||||
= $self->group_path($groups->[0]->{full_path});
|
||||
$self->cache->{group_id}->{$name}->{id} = $groups->[0]->{id};
|
||||
return $self->group_id($groups->[0]->{id});
|
||||
}
|
||||
|
||||
=item B<project2id>: get id of a project.
|
||||
|
||||
=cut
|
||||
|
||||
sub project2id {
|
||||
my ($self, $project) = @_;
|
||||
return $project if ($project =~ /^\d+$/);
|
||||
my $res;
|
||||
$project = $self->project2path($project);
|
||||
if ($self->projectCache->{$project}) {
|
||||
ds_debug "use cached id for $project";
|
||||
return $self->projectCache->{$project};
|
||||
}
|
||||
unless ($project =~ /^\d+$/) {
|
||||
eval { $res = $self->api->project($project)->{id}; };
|
||||
if ($@) {
|
||||
ds_debug $@;
|
||||
ds_warn "Project $project not found";
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
ds_verbose "$project id is $res";
|
||||
$self->projectCache->{$project} = $res;
|
||||
return $res;
|
||||
}
|
||||
|
||||
=item B<project2path>: get full path of a project
|
||||
|
||||
=cut
|
||||
|
||||
sub project2path {
|
||||
my ($self, $project) = @_;
|
||||
return $project if ($project =~ m#/#);
|
||||
my $path = $self->main_path;
|
||||
return undef unless ($path);
|
||||
ds_verbose "Project $project => $path/$project";
|
||||
return "$path/$project";
|
||||
}
|
||||
|
||||
=item B<main_path>: build path using given group or user
|
||||
|
||||
=cut
|
||||
|
||||
sub main_path {
|
||||
my ($self) = @_;
|
||||
my $path;
|
||||
if ($self->config->path) {
|
||||
$path = $self->config->path;
|
||||
} elsif (my $tmp = $self->group_path) {
|
||||
$path = $tmp;
|
||||
} elsif ($self->user_id) {
|
||||
$path = $self->username;
|
||||
} else {
|
||||
ds_warn "Unable to determine project path";
|
||||
return undef;
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
# GitLab::API::v4 does not permit to call /groups/:id with parameters.
|
||||
# It takes too much time for the "debian" group, since it returns the list of
|
||||
# all projects together with all the details of the projects
|
||||
sub GitLab::API::v4::group_without_projects {
|
||||
my $self = shift;
|
||||
return $self->_call_rest_client('GET', 'groups/:group_id', [@_],
|
||||
{ query => { with_custom_attributes => 0, with_projects => 0 } });
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Xavier Guimard E<lt>yadd@debian.orgE<gt>
|
||||
|
||||
=head1 COPYRIGHT AND LICENSE
|
||||
|
||||
Copyright 2018, Xavier Guimard E<lt>yadd@debian.orgE<gt>
|
Loading…
Add table
Add a link
Reference in a new issue