Commit e6040f54 authored by Roman Lacko's avatar Roman Lacko
Browse files

GitLab::Projects added

parent 7ac95366
......@@ -9,6 +9,7 @@ use Carp;
use GitLab::API;
use GitLab::Groups;
use GitLab::Namespaces;
use GitLab::Projects;
use GitLab::Users;
use Try::Tiny;
......@@ -18,6 +19,7 @@ our @EXPORT_OK = ();
our %EXPORT_TAGS = (
group_visibility => [ qw|GROUP_VISIBILITY GROUP_PRIVATE GROUP_INTERNAL GROUP_PUBLIC | ],
member_access => [ qw|MEMBER_ACCESS MEMBER_GUEST MEMBER_REPORTER MEMBER_DEVELOPER MEMBER_MASTER MEMBER_OWNER | ],
project_visibility => [ qw|PROJECT_PRIVATE PROJECT_INTERNAL PROJECT_PUBLIC| ],
utils => [ qw|group_visibility_name member_access_name| ],
);
......@@ -39,12 +41,19 @@ use constant {
MEMBER_DEVELOPER => 30,
MEMBER_MASTER => 40,
MEMBER_OWNER => 50,
# Project visibility
PROJECT_PRIVATE => 0,
PROJECT_INTERNAL => 10,
PROJECT_PUBLIC => 20,
};
use constant GROUP_VISIBILITY
=> (GROUP_PRIVATE, GROUP_INTERNAL, GROUP_PUBLIC);
use constant MEMBER_ACCESS
=> (MEMBER_GUEST, MEMBER_REPORTER, MEMBER_DEVELOPER, MEMBER_MASTER, MEMBER_OWNER);
use constant PROJECT_VISIBILITY
=> (PROJECT_PRIVATE, PROJECT_INTERNAL, PROJECT_PUBLIC);
#===============================================================================
# Utilities
......@@ -74,6 +83,17 @@ sub member_access_name {
return $member_access_map{$code} // "Invalid";
}
my %project_visibility_map = (
"0" => "Private",
"10" => "Internal",
"20" => "Public",
);
sub project_visibility_name {
my ($code) = @_;
return $project_visibility_map{$code} // "Invalid";
}
1;
__END__
......@@ -114,6 +134,14 @@ Import with C<:member_access> tag.
40 MEMBER_MASTER
50 MEMBER_OWNER
=head3 Project Visibility
Import with C<:project_visibility> tag.
0 PROJECT_PRIVATE
10 PROJECT_INTERNAL
20 PROJECT_PUBLIC
=head2 Utilities
Import with C<:utils> tag.
......@@ -134,6 +162,13 @@ If the parameter does not represent a valid GitLab Group Visibility code, return
Translates a Member Access code to its string representation, i.e. I<Guest>, I<Reporter>, I<Developer>, I<Master> and I<Owner>.
If the parameter does not represent a valid GitLab Member Access code, returns I<Invalid>.
=item project_visibility_name()
$name = GitLab::project_visibility_name($code);
Translates a Project Visibility code to its string representation, i.e. I<Private>, I<Internal> and I<Public>.
If the parameter does not represent a valid GitLab Project Visibility code, returns I<Invalid>.
=back
=head1 AUTHOR
......@@ -164,4 +199,8 @@ L<GitLab Namespaces|http://docs.gitlab.com/ce/api/namespaces.html> methods.
L<GitLab Users|http://docs.gitlab.com/ce/api/users.html> methods.
=item L<GitLab::Projects>
L<GitLab Users|http://docs.gitlab.com/ce/api/projects.html> methods.
=back
......@@ -12,6 +12,7 @@ use LWP::UserAgent;
use Log::Any qw($log);
use Scalar::Util qw(blessed);
use URI;
use URI::Encode;
use URI::QueryParam;
use parent "Exporter";
......@@ -135,12 +136,16 @@ sub create_uri {
# creare URI path
my $path = substr $tmpl->{path}, 0;
my %patharg = map { $_ => 1 } ($path =~ m!<([^<]*)>!g);
my %encode = map { $_ => 1 } @{$tmpl->{encode}};
foreach my $arg (keys %patharg) {
croak "Argument '$arg' is required for '$tmpl->{name}'"
unless defined $args->{$arg};
$path =~ s!<$arg>!$args->{$arg}!g;
my $val = URI::Encode::uri_encode($args->{$arg},
$encode{$arg} ? { encode_reserved => 1 } : {});
$path =~ s!<$arg>!$val!g;
delete $args->{$arg};
}
......@@ -278,6 +283,12 @@ sub exec_request {
croak "Unsupported method '$tmpl->{method}'";
}
if ($log->is_trace) {
$log->trace("-- HTTP REQUEST --\n", $response->request->as_string);
$log->trace("-- HTTP RESPONSE --\n", $response->as_string);
$log->trace("--------------------");
}
$log->debug("status: " . $response->status_line);
$data = undef if !$response->is_success;
$self->{response} = $response;
......@@ -588,6 +599,7 @@ The I<request template> is a hashref with the following keys:
required arrayref of required arguments
optional optional arguments
ret hashref of HTTP codes whose values will be used as status messages
encode arrayref of path components that should be URI-encoded
The C<path> value can contain placeholders in the format C<< <name> >>, e.g. C<< /groups/<gid>/projects/<projid> >> is a path that contains placeholders C<gid> and C<projid>.
When processing the template, these placeholders will automatically become I<required arguments>.
......@@ -601,8 +613,14 @@ The value of this key is an arrayref with strings.
As the name suggests, I<required arguments> must be provided, otherwise the L</exec_request> will croak.
All values for C<path> placeholders are encoded before replacement.
However, if the value can contain reserved URI characters (e.g. C</>), the encoding must be forced.
This can be done by specifying this placeholder in the C<encode> arrayref.
=head2 Examples
=head3 Simple
Consider the following template hashref:
my $user_by_id = {
......@@ -622,7 +640,7 @@ The C<uid> argument is required; the method will croak if it is not present.
If the given user is not found, the method will return status C<404> with reason C<User not found>.
For other status codes it will return the reason provided either by GitLab or underlying HTTP client.
Another example:
=head3 Required arguments
my $user_add_ssh_key = {
name => "user_add_ssh_key",
......@@ -634,7 +652,26 @@ Another example:
This request requires arguments C<title>, C<key> and C<uid> (since it appears in the C<path>).
The latter argument will be substituted in the path, the former two will be passed in the body of the request.
The third example uses pagination:
=head3 Encoding
my $project_by_id = {
method => "GET",
path => "/projects/<id>",
encode => [ qw(id) ],
};
From API documentation, this operation returns a project with the given C<id>, which can be a numerical value or a string C<"NAMESPACE/PROJECT_NAME">.
Without the C<encode> parameter in the template, the following request
$gitlab->project_by_id("user/project");
would result in the path C<gitlab_url/projects/user/project> which is invalid.
The slash, as a reserved character, needs to be encoded using C<encode_required> parameter for L<URI::Encode>.
This option is enabled by specifying C<id> in the C<encode> parameter in the template.
Final URL in this case would be C<gitlab_url/projects/user%2Fproject>.
=head3 Pagination and optional arguments
my $groups = {
name => "groups",
......
package GitLab::Projects;
use utf8;
use strict;
use warnings;
use vars qw($VERSION);
use GitLab::API;
use Log::Any qw($log);
our $VERSION = v8.12.1;
my $requests = {
projects => {
method => "GET",
path => "/projects",
query => [ qw(archived visibility order_by sort search) ],
paginated => 1,
},
projects_owned => {
method => "GET",
path => "/projects/owned",
query => [ qw(archived visibility order_by sort search) ],
paginated => 1,
},
projects_starred => {
method => "GET",
path => "/projects/starred",
query => [ qw(archived visibility order_by sort search) ],
paginated => 1,
},
projects_all => {
method => "GET",
path => "/projects/all",
query => [ qw(archived visibility order_by sort search) ],
paginated => 1,
},
project_by_id => {
method => "GET",
path => "/projects/<id>",
encode => [ qw(id) ],
},
project_events => {
method => "GET",
path => "/projects/<id>/events",
encode => [ qw(id) ],
paginated => 1,
},
projects_search => {
method => "GET",
path => "/projects/search/<query>",
encode => [ qw(query) ],
optional => [ qw(order_by sort) ],
paginated => 1,
},
project_create => {
method => "POST",
path => "/projects",
required => [ qw(name) ],
optional => [ qw(path namespace_id description issues_enabled
merge_requests_enabled builds_enabled wiki_enabled snippets_enabled
container_registry_enabled shared_runners_enabled public
visibility_level import_url public_builds
only_allow_merge_if_build_succeeds lfs_enabled
request_access_enabled) ],
},
project_create_for_user => {
method => "POST",
path => "/projects/user/<uid>",
required => [ qw(name) ],
optional => [ qw(path namespace_id description issues_enabled
merge_requests_enabled builds_enabled wiki_enabled snippets_enabled
container_registry_enabled shared_runners_enabled public
visibility_level import_url public_builds
only_allow_merge_if_build_succeeds lfs_enabled
request_access_enabled) ],
},
project_edit => {
method => "PUT",
path => "/projects/<id>",
optional => [ qw(name path namespace_id description issues_enabled
merge_requests_enabled builds_enabled wiki_enabled snippets_enabled
container_registry_enabled shared_runners_enabled public
visibility_level import_url public_builds
only_allow_merge_if_build_succeeds lfs_enabled
request_access_enabled) ],
},
project_fork => {
method => "POST",
path => "/projects/fork/<id>",
encode => [ qw(id namespace) ],
optional => [ qw(namespace) ],
},
project_star => {
method => "POST",
path => "/projects/<id>/star",
encode => [ qw(id) ],
},
project_unstar => {
method => "DELETE",
path => "/projects/<id>/unstar",
encode => [ qw(id) ],
},
project_archive => {
method => "POST",
path => "/projects/<id>/archive",
encode => [ qw(id) ],
},
project_unarchive => {
method => "DELETE",
path => "/projects/<id>/unarchive",
encode => [ qw(id) ],
},
project_delete => {
method => "DELETE",
path => "/projects/<id>",
encode => [ qw(id) ],
},
# DO NOT USE, not sure how this is supposed to work
# project_upload_file => {
# method => "POST",
# path => "/projects/<id>/uploads",
# encode => [ qw(id) ],
# required => [ qw(file) ],
# },
project_share => {
method => "POST",
path => "/projects/<id>/share",
required => [ qw(group_id group_access) ],
optional => [ qw(expires_at) ],
encode => [ qw(id) ],
},
project_hooks => {
method => "GET",
path => "/projects/<id>/hooks",
encode => [ qw(id) ],
paginated => 1,
},
project_get_hook => {
method => "GET",
path => "/projects/<id>/hooks/<hook_id>",
encode => [ qw(id) ],
},
project_add_hook => {
method => "POST",
path => "/projects/<id>/hooks",
encode => [ qw(id) ],
required => [ qw(url) ],
optional => [ qw(push_events issues_events merge_requests_events
tag_push_events note_events build_events pipeline_events
wiki_page_events enable_ssl_verification ) ],
},
project_edit_hook => {
method => "PUT",
path => "/projects/<id>/hooks/<hook_id>",
encode => [ qw(id) ],
optional => [ qw(push_events issues_events merge_requests_events
tag_push_events note_events build_events pipeline_events
wiki_page_events enable_ssl_verification ) ],
},
project_delete_hook => {
method => "DELETE",
path => "/projects/<id>/hooks/<hook_id>",
encode => [ qw(id) ],
},
project_create_forked_relationship => {
method => "POST",
path => "/projects/<id>/fork/<forked_from_id>",
encode => [ qw(id forked_from_id) ],
},
project_delete_forked_relationship => {
method => "DELETE",
path => "/projects/<id>/fork",
encode => [ qw(id) ],
},
};
sub import {
$log->debug("initializing " . __PACKAGE__);
while (my ($name, $tmpl) = each(%$requests)) {
$tmpl->{name} = $name unless exists $tmpl->{name};
GitLab::API->register($tmpl);
}
}
1;
__END__
=head1 NAME
GitLab::Projects - implements projects API calls
See L<GitLab API -- Projects|http://doc.gitlab.com/ce/api/projects.html> for details and
response formats.
=head1 VERSION
Implements API calls for GitLab CE C<v8.10.0>.
Checked 2016-09-29 for GitLab CE C<v8.12.1>.
=head1 DESCRIPTION
=head2 Notation
Please see the documentation for the L<GitLab::Users> module.
Note that not all optional arguments are listed.
Please refer to the official documentation for the full list.
=head2 Project listing
=over
=item projects()
$gitlab->projects([...]);
Returns a list of projects accessible to the authenticated user.
=item projects_owned()
$gitlab->projects_owned([...]);
Returns a list of projects owned by the authenticated user.
=item projects_starred()
$gitlab->projects_starred([...]);
Returns a list of projects starred by the authenticated user.
=item projects_all()
$gitlab->projects_all([...]);
Returns all projects.
B<This method is available only for administrators.>
=item project_by_id()
$gitlab->project_by_id( :id );
Returns the project with the given ID, which can be either a number or a string C<"namespace/project_name">.
=item project_events()
$gitlab->project_events( :id );
Returns the list of events for the given project.
=item projects_search()
$gitlab->projects_serach( :query, [...] );
Search for projects by name which are accessible to the authenticated user.
=back
=head2 Create, edit and delete projects
=over
=item project_create()
$gitlab->project_create( :name, [...] );
Creates a new project in the authenticated user's namespace.
Other namespaces can be specified by the optional C<namespace_id> parameter if the user can access it.
=item project_create_for_user()
$gitlab->project_create_for_user( :uid, :name );
Creates a new project for the user with the given C<uid>.
B<This method is available only for administrators.>
=item project_edit()
$gitlab->project_edit( :id, [...] );
Edits the project.
Takes the same arguments as L<project_create>, except they are all optional.
=item project_fork()
$gitlab->project_fork( :id, [:namespace ] );
Forks the project to the authenticated user's namespace or the namespace specified by the C<namespace> as an id or path.
=item project_star()
$gitlab->project_star( :id );
Stars a given project.
Returns C<304 Not Modified> if already stared.
=item project_unstar()
$gitlab->project_unstar( :id );
Unstars a given project.
Returns C<304 Not Modified> if not stared.
=item project_archive()
$gitlab->project_archive( :id );
Archives the given project.
The user must be either an administrator or the owner of the project.
=item project_unarchive()
$gitlab->project_unarchive( :id );
Unarchives the given project.
The user must be either an administrator or the owner of the project.
=item project_delete()
$gitlab->project_delete( :id );
Deletes the project with the given C<id>.
=back
=head2 Uploads
=over
=item project_upload_file()
B<NOT SUPPORTED YET>
=back
=head2 Project Members
See L<GitLab::Members> package.
=head2 Hooks
=over
=item project_hooks()
$gitlab->project_hooks( :id );
Get a list of project hooks.
=item project_get_hook()
$gitlab->project_get_hook( :id, :hook_id )
Get a specific hook for a project.
=item project_add_hook()
$gitlab->project_add_hook( :id, :url, [...]);
Adds a hook to a specified project.
=item project_edit_hook()
$gitlab->project_edit_hook( :id, :hook_id, [...]);
Edits a hook for a specified project.
=item project_delete_hook()
$gitlab->project_delete_hook( :id, :hook_id );
Removes a hook from a project.
This is an idempotent method and can be called multiple times.
Note the JSON response differs if the hook is available or not.
If the project hook is available before it is returned in the JSON response or an empty response is returned.
=back
=head2 Branches
See L<GitLab::Branches> module.
=head2 Miscellaneous
=over
=item project_create_forked_relationship()
$gitlab->project_create_forked_relationship( :id :forked_from_id );
Create a I<forked from> relation between existing projects.
B<Available only for admins.>
=item project_delete_forked_relationship()
$gitlab->project_delete_forked_relationship( :id );
Delete an existing I<forked from> relationship.
=back
=head1 AUTHOR
Roman Lacko <L<xlacko1@fi.muni.cz>>
=head1 SEE ALSO
=over
=item L<GitLab>
Wrapper around L<GitLab::API> and other C<GitLab::*> modules.
=back
......@@ -45,7 +45,7 @@ From GitLab API: _"Usernames and groupnames fall under a special category called
```
Note that the authenticated user must be an administrator to use [`sudo` in GitLab::API](pod-GitLab-API.md#sudo).
Also, if the user is an administrator, the call returns **all** namespaces.
# AUTHOR
......
# NAME
GitLab::Projects - implements projects API calls
See [GitLab API -- Projects](http://doc.gitlab.com/ce/api/projects.html) for details and
response formats.
# VERSION
Implements API calls for GitLab CE `v8.10.0`.
Checked 2016-09-29 for GitLab CE `v8.12.1`.