diff --git a/lib/OpenQA/Schema/Result/Jobs.pm b/lib/OpenQA/Schema/Result/Jobs.pm index 49d4431b1a8..7d69007dadf 100644 --- a/lib/OpenQA/Schema/Result/Jobs.pm +++ b/lib/OpenQA/Schema/Result/Jobs.pm @@ -994,7 +994,7 @@ sub auto_duplicate ($self, $args = {}) { # report status back to GitHub for affected scheduled products my $scheduled_products = $rsource->schema->resultset('ScheduledProducts'); my %related_scheduled_products = (id => {-in => [keys %related_scheduled_product_ids]}); - $_->report_status_to_github for $scheduled_products->search(\%related_scheduled_products); + $_->report_status_to_git for $scheduled_products->search(\%related_scheduled_products); my $clone_id = $clones->{$job_id}->{clone}; my $dup = $rsource->resultset->find($clone_id); @@ -2158,7 +2158,7 @@ sub done ($self, %args) { # report back to GitHub if this job is part of a CI check which has concluded with this job if (my $sp_id = $self->related_scheduled_product_id) { - $self->result_source->schema->resultset('ScheduledProducts')->find($sp_id)->report_status_to_github; + $self->result_source->schema->resultset('ScheduledProducts')->find($sp_id)->report_status_to_git; } # enqueue the finalize job only after stopping the cluster so in case the job should be restarted the cluster diff --git a/lib/OpenQA/Schema/Result/ScheduledProducts.pm b/lib/OpenQA/Schema/Result/ScheduledProducts.pm index 175e284f6ac..d3fbb32e6c6 100644 --- a/lib/OpenQA/Schema/Result/ScheduledProducts.pm +++ b/lib/OpenQA/Schema/Result/ScheduledProducts.pm @@ -226,7 +226,7 @@ sub set_done ($self, $result) { } else { $self->update({status => SCHEDULED, results => $result}); # … SCHEDULED if remained SCHEDULING - $self->report_status_to_github; + $self->report_status_to_git; } } @@ -932,16 +932,16 @@ sub _format_check_description ($verb, $count, $total) { return "All $total openQA jobs $verb"; } -sub report_status_to_github ($self, $callback = undef) { +sub report_status_to_git ($self, $callback = undef) { my $id = $self->id; my $settings = $self->{_settings} // $self->settings; - return undef unless my $github_statuses_url = $settings->{GITHUB_STATUSES_URL}; + return undef unless $self->webhook_id; + my $vcs = OpenQA::VcsProvider->new(type => $self->webhook_id, app => OpenQA::App->singleton); + $vcs->read_settings($settings) or return undef; my ($state, $verb, $count, $total) = $self->state_for_ci_status; return undef unless $state; - my $vcs = OpenQA::VcsProvider->new(app => OpenQA::App->singleton); - my $base_url = $settings->{CI_TARGET_URL}; my %params = (state => $state, description => _format_check_description($verb, $count, $total)); - $vcs->report_status_to_github($github_statuses_url, \%params, $id, $base_url, $callback); + $vcs->report_status_to_git(\%params, $id, $callback); } sub cancel ($self, $reason = undef) { diff --git a/lib/OpenQA/Setup.pm b/lib/OpenQA/Setup.pm index d387191fce1..ffc845f8265 100644 --- a/lib/OpenQA/Setup.pm +++ b/lib/OpenQA/Setup.pm @@ -249,7 +249,7 @@ sub read_config ($app) { 'assets/storage_duration' => { # intentionally left blank for overview }, - secrets => {github_token => ''}, + secrets => {github_token => '', gitea_token => ''}, # allow dynamic config keys based on job results hooks => {}, influxdb => { diff --git a/lib/OpenQA/VcsProvider.pm b/lib/OpenQA/VcsProvider.pm index 9afbefdaff4..a5a93e61de4 100644 --- a/lib/OpenQA/VcsProvider.pm +++ b/lib/OpenQA/VcsProvider.pm @@ -3,34 +3,15 @@ package OpenQA::VcsProvider; -use Mojo::Base -base, -signatures; -use Mojo::JSON qw(encode_json); -use Mojo::URL; +use Mojo::Base -signatures; +use OpenQA::VcsProvider::GitHub; +use OpenQA::VcsProvider::Gitea; -has 'app'; - -sub report_status_to_github ($self, $statuses_url, $params, $scheduled_product_id, $base_url, $callback = undef) { - $params->{context} //= 'openqa'; - $params->{description} //= 'openQA test run'; - $params->{target_url} //= "$base_url/admin/productlog?id=$scheduled_product_id" - if $scheduled_product_id && $base_url; - - my $url = Mojo::URL->new($statuses_url); - my $app = $self->app; - my $ua = $app->ua; - my $tx = $ua->build_tx(POST => $url); - my $req = $tx->req; - my $headers = $req->headers; - my $github_token = $app->config->{secrets}->{github_token}; - my $json = encode_json($params); - $req->body($json); - $headers->content_type('application/json'); - $headers->content_length(length $json); - $headers->header(Accept => 'application/vnd.github+json'); - $headers->header(Authorization => "Bearer $github_token"); - $headers->header('X-GitHub-Api-Version' => '2022-11-28'); - $ua->start($tx, $callback); - return $tx; +sub new ($class, %args) { + my $type = delete $args{type}; + my ($provider) = split m/:/, $type; + $class = {gh => 'GitHub', gitea => 'Gitea'}->{$provider} or return undef; + return "OpenQA::VcsProvider::$class"->new(%args); } 1; diff --git a/lib/OpenQA/VcsProvider/Base.pm b/lib/OpenQA/VcsProvider/Base.pm new file mode 100644 index 00000000000..8aa55e362a1 --- /dev/null +++ b/lib/OpenQA/VcsProvider/Base.pm @@ -0,0 +1,49 @@ +# Copyright SUSE LLC +# SPDX-License-Identifier: GPL-2.0-or-later + +package OpenQA::VcsProvider::Base; + +use Mojo::Base -base, -signatures; +use Mojo::JSON qw(encode_json); +use Mojo::URL; + +has 'app'; +has 'base_url'; +has 'statuses_url'; + +sub add_params ($self, $params, $scheduled_product_id) { + $params->{context} //= 'openqa'; + $params->{description} //= 'openQA test run'; + my $base_url = $self->base_url; + $params->{target_url} //= "$base_url/admin/productlog?id=$scheduled_product_id" + if $scheduled_product_id && $base_url; +} + +sub create_request ($self, $params) { + my $app = $self->app; + my $ua = $app->ua; + # TODO Note that anyone who can create an openQA job can set settings + # with a webhook id and a statuses URL. Maybe we should configure the + # base url for each git provider and double check the url, because + # we are making an API request with a token to an otherwise unchecked URL + my $url = Mojo::URL->new($self->statuses_url); + my $tx = $ua->build_tx(POST => $url); + my $req = $tx->req; + my $json = encode_json($params); + $req->body($json); + my $headers = $req->headers; + $headers->content_type('application/json'); + $headers->content_length(length $json); + + return $tx; +} + +sub report_status_to_git ($self, $params, $scheduled_product_id, $callback = undef) { + $self->add_params($params, $scheduled_product_id); + + my $tx = $self->create_request($params); + $self->app->ua->start($tx, $callback); + return $tx; +} + +1; diff --git a/lib/OpenQA/VcsProvider/GitHub.pm b/lib/OpenQA/VcsProvider/GitHub.pm new file mode 100644 index 00000000000..223455809cc --- /dev/null +++ b/lib/OpenQA/VcsProvider/GitHub.pm @@ -0,0 +1,27 @@ +# Copyright SUSE LLC +# SPDX-License-Identifier: GPL-2.0-or-later + +package OpenQA::VcsProvider::GitHub; + +use Mojo::Base 'OpenQA::VcsProvider::Base', -signatures; + +sub read_settings ($self, $settings) { + $self->statuses_url($settings->{GITHUB_STATUSES_URL}); + $self->base_url($settings->{CI_TARGET_URL}); + return undef unless $self->statuses_url; + return 1; +} + +sub create_request ($self, $params) { + my $tx = $self->SUPER::create_request($params); + + my $headers = $tx->req->headers; + my $github_token = $self->app->config->{secrets}->{github_token}; + $headers->header(Accept => 'application/vnd.github+json'); + $headers->header(Authorization => "Bearer $github_token"); + $headers->header('X-GitHub-Api-Version' => '2022-11-28'); + + return $tx; +} + +1; diff --git a/lib/OpenQA/VcsProvider/Gitea.pm b/lib/OpenQA/VcsProvider/Gitea.pm new file mode 100644 index 00000000000..b1ac6e441c8 --- /dev/null +++ b/lib/OpenQA/VcsProvider/Gitea.pm @@ -0,0 +1,27 @@ +# Copyright SUSE LLC +# SPDX-License-Identifier: GPL-2.0-or-later + +package OpenQA::VcsProvider::Gitea; + +use Mojo::Base 'OpenQA::VcsProvider::Base', -signatures; + +sub read_settings ($self, $settings) { + $self->statuses_url($settings->{GITEA_STATUSES_URL}); + $self->base_url($settings->{CI_TARGET_URL}); + return undef unless $self->statuses_url; + return 1; +} + +sub create_request ($self, $params) { + my $tx = $self->SUPER::create_request($params); + + my $headers = $tx->req->headers; + # TODO there might be more than one gitea server -> add sections? + my $token = $self->app->config->{secrets}->{gitea_token}; + $headers->header(Accept => 'application/json'); + $headers->header(Authorization => "Bearer $token"); + + return $tx; +} + +1; diff --git a/lib/OpenQA/WebAPI/Controller/API/V1/Iso.pm b/lib/OpenQA/WebAPI/Controller/API/V1/Iso.pm index 0e2465add05..7b8537f3d2b 100644 --- a/lib/OpenQA/WebAPI/Controller/API/V1/Iso.pm +++ b/lib/OpenQA/WebAPI/Controller/API/V1/Iso.pm @@ -144,6 +144,7 @@ sub create { my $params = $self->req->params->to_hash; my $async = delete $params->{async}; # whether to run the operation as a Minion job + my $webhook_id = delete $params->{webhook_id}; my $scheduled_product_clone_id = delete $params->{scheduled_product_clone_id}; # ID of a previous product to clone settings from my $log = $self->app->log; @@ -192,7 +193,7 @@ sub create { return undef unless $self->validate_download_parameters(\%params); # add entry to ScheduledProducts table and log event - my $scheduled_product = $scheduled_products->create_with_event(\%params, $self->current_user); + my $scheduled_product = $scheduled_products->create_with_event(\%params, $self->current_user, $webhook_id); my $scheduled_product_id = $scheduled_product->id; # only spawn Minion job and return IDs if async flag has been passed diff --git a/lib/OpenQA/WebAPI/Controller/API/V1/Webhook.pm b/lib/OpenQA/WebAPI/Controller/API/V1/Webhook.pm index e981f5175ec..fd63140f597 100644 --- a/lib/OpenQA/WebAPI/Controller/API/V1/Webhook.pm +++ b/lib/OpenQA/WebAPI/Controller/API/V1/Webhook.pm @@ -144,7 +144,7 @@ sub product ($self) { # create scheduled product and enqueue minion job with parameter my $scheduled_product = $scheduled_products->create_with_event(\%params, $self->current_user, $webhook_id); - my $vcs = OpenQA::VcsProvider->new(app => $app); + my $vcs = OpenQA::VcsProvider->new(type => $webhook_id, app => $app); my $cb = sub ($ua, $tx, @) { if (my $err = $tx->error) { $scheduled_product->delete; @@ -153,7 +153,9 @@ sub product ($self) { return $self->render(json => $scheduled_product->enqueue_minion_job(\%params)); }; $scheduled_product->discard_changes; # load value of columns that have a default value - my $tx = $vcs->report_status_to_github($statuses_url, {state => 'pending'}, $scheduled_product->id, $base_url, $cb); + $vcs->statuses_url($statuses_url); + $vcs->base_url($base_url); + my $tx = $vcs->report_status_to_git({state => 'pending'}, $scheduled_product->id, $cb); } 1; diff --git a/t/16-utils-vcs-provider.t b/t/16-utils-vcs-provider.t index 956a557d6b3..5d14849ddea 100644 --- a/t/16-utils-vcs-provider.t +++ b/t/16-utils-vcs-provider.t @@ -4,25 +4,26 @@ use Test::Most; +use Mojo::Base -signatures; use FindBin; use lib "$FindBin::Bin/lib", "$FindBin::Bin/../external/os-autoinst-common/lib"; require OpenQA::Test::Database; use OpenQA::Test::TimeLimit '5'; -use OpenQA::VcsProvider; +use OpenQA::VcsProvider::GitHub; +use OpenQA::VcsProvider::Gitea; use Test::Mojo; use Test::Warnings ':report_warnings'; my $schema = OpenQA::Test::Database->new->create(fixtures_glob => '03-users.pl'); my $t = Test::Mojo->new('OpenQA::WebAPI'); -subtest 'reporting status to GitHub' => sub { +sub test_report_status_to_git ($app, $statuses_url_key, $webhook_id) { # avoid making an actual query to GitHub; this test just checks whether an expected request would have been done - my $app = $t->app; - $app->config->{secrets}->{github_token} = 'some-token'; - my $git = OpenQA::VcsProvider->new(app => $app); + my $git = OpenQA::VcsProvider->new(app => $app, type => $webhook_id); my $url = 'http://127.0.0.1/repos/some/repo/statuses/some-sha'; - my $tx = $git->report_status_to_github($url, {state => 'pending'}, '42', 'https://openqa.opensuse.org'); + $git->read_settings({$statuses_url_key => $url, CI_TARGET_URL => 'https://openqa.opensuse.org'}); + my $tx = $git->report_status_to_git({state => 'pending'}, '42'); my $req = $tx->req; is $req->method, 'POST', 'method'; is $req->url, $url, 'URL'; @@ -37,4 +38,17 @@ subtest 'reporting status to GitHub' => sub { ok $tx->is_finished, 'transaction has finished (and thus was started in first place)'; }; + +subtest 'reporting status to GitHub' => sub { + my $app = $t->app; + $app->config->{secrets}->{github_token} = 'some-token'; + test_report_status_to_git($app, 'GITHUB_STATUSES_URL', 'gh:pr:123'); +}; + +subtest 'reporting status to Gitea' => sub { + my $app = $t->app; + $app->config->{secrets}->{gitea_token} = 'some-token'; + test_report_status_to_git($app, 'GITEA_STATUSES_URL', 'gitea:pr:123'); +}; + done_testing(); diff --git a/t/api/16-webhooks.t b/t/api/16-webhooks.t index 4767b8796e2..895058cfba5 100644 --- a/t/api/16-webhooks.t +++ b/t/api/16-webhooks.t @@ -90,7 +90,7 @@ subtest 'failure when reporting status to GitHub' => sub { }; # mock reporting back to GitHub -my $vcs_mock = Test::MockModule->new('OpenQA::VcsProvider'); +my $vcs_mock = Test::MockModule->new('OpenQA::VcsProvider::GitHub'); my $minion_job_id; my $status_reports = 0; my $expected_sha = '04a3f669ea13a4aa7cbd4569f578a66f7403c43d'; @@ -100,10 +100,10 @@ my $expected_statuses_url = "https://127.0.0.1/repos/os-autoinst/openQA/statuses my $expected_ci_check_state = 'pending'; my $expected_ci_check_desc = undef; $vcs_mock->redefine( - report_status_to_github => - sub ($self, $statuses_url, $params, $scheduled_product_id, $base_url_from_req, $callback) { + report_status_to_git => + sub ($self, $params, $scheduled_product_id, $callback) { my $tx = $ua->build_tx(POST => 'http://dummy'); - is $statuses_url, $expected_statuses_url, 'URL from webhook payload used for reporting back'; + is $self->statuses_url, $expected_statuses_url, 'URL from webhook payload used for reporting back'; is $params->{state}, $expected_ci_check_state, "check reported to be $expected_ci_check_state"; is $params->{description}, $expected_ci_check_desc, 'check description'; ++$status_reports;