Skip to content

Commit

Permalink
Add GitHub action for cloning an openQA job mentioned in a PR descrip…
Browse files Browse the repository at this point in the history
…tion

This GitHub action allows to clone a job when creating a PR by mentioning
it in the PR description via `@openqa: Clone https://…`.

Related ticket: https://progress.opensuse.org/issues/130934
  • Loading branch information
Martchus committed Feb 23, 2024
1 parent 42cd82a commit af60f9c
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 1 deletion.
14 changes: 14 additions & 0 deletions actions/clone-job/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
name: 'Clone job'
description: 'Clone openQA job mentioned in PR description'

runs:
using: composite
image: registry.opensuse.org/devel/openqa/containers/tumbleweed:client
steps:
- uses: actions/checkout@v4
with:
repository: os-autoinst/scripts
path: scripts
- name: Clone and monitor job mentioned in PR description
run: scripts/openqa-clone-and-monitor-job-from-pr
2 changes: 2 additions & 0 deletions cpanfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ requires 'Text::Markdown';
requires 'YAML::PP';

on 'test' => sub {
requires 'Test::MockModule';
requires 'Test::Most';
requires 'Test::Output';
requires 'Test::Warnings';

};
Expand Down
2 changes: 2 additions & 0 deletions dependencies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ main_requires:
html-xml-utils:
xmlstarlet:
test_requires:
perl(Test::Output):
perl(Test::Warnings):
perl(Test::MockModule):
perl(Test::Most):
devel_requires:
python3-yamllint:
Expand Down
2 changes: 1 addition & 1 deletion dist/rpm/os-autoinst-scripts-deps.spec
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Url: https://github.com/os-autoinst/scripts
# The following line is generated from dependencies.yaml
%define main_requires bash coreutils curl grep html-xml-utils jq openQA-client openssh-clients osc perl >= 5.010 perl(Data::Dumper) perl(FindBin) perl(Getopt::Long) perl(Mojo::File) perl(Text::Markdown) perl(YAML::PP) sed sudo xmlstarlet yq
# The following line is generated from dependencies.yaml
%define test_requires perl(Test::Most) perl(Test::Warnings)
%define test_requires perl(Test::MockModule) perl(Test::Most) perl(Test::Output) perl(Test::Warnings)
# The following line is generated from dependencies.yaml
%define devel_requires python3-yamllint shfmt
Requires: %main_requires
Expand Down
106 changes: 106 additions & 0 deletions openqa-clone-and-monitor-job-from-pr
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/env perl
# Copyright SUSE LLC

=head1 NAME
openqa-clone-and-monitor-job-from-pr
=head1 SYNOPSIS
Clones and monitors openQA jobs mentioned in a PR description as CI action. This
script is supposed to be used via the GitHub action defined in
C<actions/clone-job/action.yaml> like this:
=begin text
---
name: Run openQA tests
on:
pull_request_target:
workflow_dispatch:
env:
OPENQA_HOST: ${{ secrets.OPENQA_URL }}
OPENQA_API_KEY: ${{ secrets.OPENQA_API_KEY }}
OPENQA_API_SECRET: ${{ secrets.OPENQA_API_SECRET }}
GH_REPO: ${{ github.event.pull_request.head.repo.full_name }}
GH_REF: ${{ github.event.pull_request.head.ref }}
GH_PR_BODY: ${{ github.event.pull_request.body }}
jobs:
clone_and_monitor_job_from_pr:
steps:
- uses: os-autoinst/scripts/clone-job@master
=end text
It will then clone and monitor an openQA job when a PR mentioning one via e.g.
C<@openqa: Clone https://openqa.opensuse.org/tests/123456> is created. By
default it will clone the job on o3 into the "Development / GitHub" group. To
use a different openQA instance and group, set the environment variables
C<OPENQA_HOST> and C<OPENQA_SCHEDULE_GROUP_ID> accordingly.
For local testing you may also invoke the script manually. Have a look at the
handling of environment variables at the beginning of the script code as you
need to set certain additional environment variables that are normally supplied
by GitHub.
=back
=cut

package openqa_clone_and_monitor_job_from_pr; # for testing

use Mojo::Base -strict, -signatures;
use Mojo::JSON qw(decode_json);

my $expected_url = $ENV{OPENQA_HOST} // 'https://openqa.opensuse.org';
my $group_id = $ENV{OPENQA_SCHEDULE_GROUP_ID} // 118;
my $pr_body = $ENV{GH_PR_BODY} // '';
my $gh_repo = $ENV{GH_REPO} or die 'GH_REPO must be set';
my $gh_ref = $ENV{GH_REF} or die 'GH_REF must be set';
my $gh_srv = $ENV{GITHUB_SERVER_URL} or die 'GITHUB_SERVER_URL must be set';
my $api_key = $ENV{OPENQA_API_KEY} or die 'OPENQA_API_KEY must be set';
my $api_secret = $ENV{OPENQA_API_SECRET} or die 'OPENQA_API_SECRET must be set';
my @secrets = ('--apikey', $api_key, '--apisecret', $api_secret);
my @vars = ("BUILD=$gh_repo.git#$gh_ref", "_GROUP_ID=$group_id", "CASEDIR=$gh_srv/$gh_repo.git#$gh_ref");

sub _parse_urls ($text) {
my @urls;
while ($text =~ /(\@openqa:?\s+Clone\s+(https?:[^\s]+))/ig) {
push @urls, $2 if index($2, $expected_url) == 0;
}
return \@urls if @urls;
print 'No test cloned; the PR description does not contain '; # uncoverable statement
print "a command like '\@openqa: Clone $expected_url/tests/<JOB_ID>'.\n"; # uncoverable statement
exit 0;
}

sub _handle_cmd_error ($command, @args) {
if ($? == -1) { die "Failed to execute '$command @args': $!\n" }
elsif ($? & 127) { die sprintf("'$command' received signal %d\n", $? & 127) }
elsif ($? >> 8) { die sprintf("'$command' exited with non-zero exist status %d\n", $? >> 8) }
}

sub _run_cmd ($command, @args) { system $command, @args; _handle_cmd_error $command, @args }

sub _run_cmd_capturing_output ($command, @args) {
open my $fh, '-|', $command, @args or die "Failed to execute '$command @args': $!\n";
my $output = do { local $/; <$fh> };
close $fh;
_handle_cmd_error $command, @args;
return $output;
}

sub _clone_job ($url) {
my @args = (@secrets, qw(--json-output --skip-chained-deps --within-instance), $url, @vars);
my $json = _run_cmd_capturing_output 'openqa-clone-job', @args;
return values %{decode_json($json)};
}

sub run () {
my $urls = _parse_urls($pr_body);
my @job_ids = map { _clone_job $_ } @$urls;
my @quoted_url_list = join(', ', map { "'$_'" } @$urls);
my @job_url_list = map { "- $expected_url/tests/$_\n" } @job_ids;
print "Cloned @quoted_url_list into:\n @job_url_list";
_run_cmd 'openqa-cli', 'monitor', '--host', $expected_url, @secrets, @job_ids;
}

run unless caller;
47 changes: 47 additions & 0 deletions test/05-clone-and-monitor.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env perl

use Mojo::Base -strict, -signatures;

use FindBin;
use Test::More;
use Test::Output qw(combined_like);
use Test::MockModule;

$ENV{GH_REPO} = 'foo';
$ENV{GH_REF} = 'bar';
$ENV{OPENQA_HOST} = 'http://127.0.0.1:9526';
$ENV{OPENQA_API_KEY} = 'key';
$ENV{OPENQA_API_SECRET} = 'secret';
$ENV{GITHUB_SERVER_URL} = 'gh-srv-url';
$ENV{GH_PR_BODY} = 'Merge my changes
@openqa: Clone http://127.0.0.1:9526/tests/4240
@openqa: Clone http://127.0.0.1:9526/tests/4239
footnote';

require "$FindBin::RealBin/../openqa-clone-and-monitor-job-from-pr";

my $mock = Test::MockModule->new('openqa_clone_and_monitor_job_from_pr', no_auto => 1);
my @invoked_commands;
my @clone_responses = ('{"4240" : 4246}', '{"4239" : 4245}');
$mock->redefine(_run_cmd => sub ($command, @args) {
push @invoked_commands, [$command, @args];
$mock->original('_run_cmd')->('echo', @args);
});
$mock->redefine(_run_cmd_capturing_output => sub ($command, @args) {
push @invoked_commands, [$command, @args];
$mock->original('_run_cmd_capturing_output')->('echo', shift @clone_responses);
});

combined_like { openqa_clone_and_monitor_job_from_pr::run() }
qr(Cloned.*4240.*4239.*into:.*monitor --host http://127\.0\.0\.1:9526 --apikey key --apisecret secret 4246 4245)s,
'monitor command invoked as expected';

my @expected_secrets = qw(--apikey key --apisecret secret);
my @expected_clone_options = (@expected_secrets, qw(--json-output --skip-chained-deps --within-instance));
my @expected_vars = ('BUILD=foo.git#bar', '_GROUP_ID=118', 'CASEDIR=gh-srv-url/foo.git#bar');
my @expected_invocations;
push @expected_invocations, [qw(openqa-clone-job), @expected_clone_options, "http://127.0.0.1:9526/tests/$_", @expected_vars] for 4240, 4239;
push @expected_invocations, [qw(openqa-cli monitor --host http://127.0.0.1:9526), @expected_secrets, 4246, 4245];
is_deeply \@invoked_commands, \@expected_invocations, 'expected commands invoked' or diag explain \@invoked_commands;

done_testing;

0 comments on commit af60f9c

Please sign in to comment.