#!/usr/bin/env perl
# Copyright 2013-2021 SUSE LLC
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

=head1 NAME

openqa-clone-job - creates a new job based on an existing job

=head1 SYNOPSIS

Clones a job from the local or a remote openQA instance. Downloads all assets
associated with the job (unless --skip-download is specified). Optionally
settings can be modified.

  openqa-clone-job [OPTIONS] JOBREF [KEY=[VALUE] ...]

  # clones job 42 (and any existing parents) from "openqa.opensuse.org" to the local openQA instance
  # note: If job 42 is a parallel parent (e.g. a "server" job), its parallel children (e.g. "client"
  #       jobs) will be cloned as well.
  openqa-clone-job https://openqa.opensuse.org/t42
  openqa-clone-job --from https://openqa.opensuse.org/tests/42
  openqa-clone-job --from https://openqa.opensuse.org 42

  # clones job 42 (and any existing parents) from "openqa.opensuse.org" to the openQA instance "openqa.example.com"
  openqa-clone-job --skip-download --from https://openqa.opensuse.org --host openqa.example.com 42

  # clones job 42 (and any existing parents) within "openqa.opensuse.org" modifying some job settings
  openqa-clone-job --within-instance https://openqa.opensuse.org/t42 MAKETESTSNAPSHOTS=1 TEST+=:PR-123 FOOBAR=

  # clones job 42 including all of its direct children but excluding its chained parents
  openqa-clone-job --skip-chained-deps --clone-children https://openqa.opensuse.org/tests/42

=head1 DESCRIPTION

Call with either a full URL pointing to a test job to clone from or one of both
parameters C<--from> or C<--within-instance>. The job ID can be specified as
part of the URL or as its own parameter.

API key and secret are read from "client.conf" if not specified via CLI
arguments. The config file is checked for under "$OPENQA_CONFIG",
"~/.config/openqa" and "/etc/openqa" in this order. It must look like
this:

  [openqa.opensuse.org]
  key = 45ABCEB4562ACB04
  secret = 4BA0003086C4CB95
  [another.host]
  key = D7345DA7B9D86B3B
  secret = A98CDBA9C8DB87BD

Any parent jobs (chained or parallel) are also cloned unless C<--skip-deps> or
C<--skip-chained-deps> is specified. If C<--skip-chained-deps> is specified
published assets generated by parent jobs are downloaded to be directly used
instead of generated. Keep in mind that by default any additionally specified
job settings are NOT added to the also cloned parent jobs.

Note that the child job is the one which has the "START_AFTER_TEST" or
"PARALLEL_WITH" setting and the parent job is the one mentioned by that setting.

Cloning directly chained dependencies ("START_DIRECTLY_AFTER_TEST") is NOT
supported.

=head1 OPTIONS

=over 4

=item B<--host> HOST

Specifies the hostname of the target openQA instance (defaults to localhost).

Assets are still always downloaded to the local machine. When specifying a
remote host make sure the assets are already there and use C<--skip-download>.

=item B<--from> HOST

Specifies the hostname of the openQA instance to clone the job from (deduced
from JOBREF if it is a URL).

=item B<--dir> DIR

Specifies the directory to store test assets (defaults to
$OPENQA_SHAREDIR/factory).

=item B<--skip-deps>

Do NOT clone parent jobs (which is done by default).

=item B<--skip-chained-deps>

Do NOT clone chained parent jobs (jobs specified via "START_AFTER_TEST").

This makes the job use the downloaded HDD image instead of running the generator
job again which is of course only possible if --host is the local machine.

=item B<--skip-download>

Do NOT download assets. You need to ensure all required assets are provided
yourself.

=item B<--ignore-missing-assets>

Cloning a job will not fail if an asset is missing.

=item B<--clone-children>

Clone all direct child jobs as well. By default, only parallel child jobs are
cloned.

=item B<--max-depth>

Specifies the max depth for cloning children. By default, only direct children
are cloned. Use C<0> to denote infinity.

=item B<--within-instance> HOST

A shortcut for C<--skip-download --from HOST --host HOST> to clone a job within
a local or remote instance.

=item B<--repeat> N

Do the same clone operation N times; this can be useful for Statistical 
investigation. Example: 

openqa-clone-job --skip-chained-deps --repeat=50 --within-instance \
https://openqa.opensuse.org 123456 

Will create 50 clones of the same job.


=item B<--show-progress>

Displays a progress bar when downloading assets.

=item B<--parental-inheritance>

Provides parental job with settings specified via command line (they go to child
job by default).

=item B<--export-command>

Prints an `openqa-cli` command to create the jobs instead of creating them
directly. This is useful to customize the API call or to review it before
submitting.

=item B<--apikey> <value>

Specifies the public key needed for API authentication.

=item B<--apisecret> <value>

Specifies the secret key needed for API authentication.

=item B<--verbose, -v>

Increases verbosity.

=item B<--help, -h>

Prints help.

=back

=cut

use Mojo::Base -strict, -signatures;
use Getopt::Long;
Getopt::Long::Configure("no_ignore_case");
use FindBin;
use lib "$FindBin::RealBin/../lib";
use OpenQA::Script::CloneJob;
use OpenQA::Utils 'assetdir';
use Scalar::Util qw(looks_like_number);

my %options;
my $jobid;

sub usage ($r) {
    eval { require Pod::Usage; Pod::Usage::pod2usage(-exitval => $r, -verbose => 99) };
    die "cannot display help, install perl(Pod::Usage)\n" if $@;
}

sub parse_options () {
    GetOptions(
        \%options, "from=s", "host=s", "dir=s",
        "apikey:s", "apisecret:s", "verbose|v", "json-output|j",
        "skip-deps", "skip-chained-deps", "skip-download", "parental-inheritance",
        "help|h", "show-progress", "within-instance|w=s", "clone-children",
        "max-depth:i", "repeat|r:i", "ignore-missing-assets", "export-command",
    ) or usage(1);
    usage(0) if $options{help};
    usage(1) if $options{help} || ($options{'within-instance'} && $options{from});
    if ($options{'within-instance'}) {
        ($options{'within-instance'}, $jobid) = split_jobid($options{'within-instance'});
        $options{'skip-download'} = 1;
        $options{'from'} = $options{'within-instance'};
        $options{'host'} = $options{'within-instance'};
    }
    elsif ($options{'from'}) {
        ($options{'from'}, $jobid) = split_jobid($options{'from'});
    }
    $jobid = shift @ARGV unless $jobid;
    die "missing job reference, see --help for usage\n" unless $jobid;
    ($options{'from'}, $jobid) = split_jobid($jobid) unless $options{'from'};
    usage(1) unless ($jobid && $options{'from'});
    my $repeat = $options{'repeat'} //= 1;
    die "invalid repeat count, should be greater than or equal to 1\n" if !looks_like_number($repeat) || $repeat < 1;
    $options{'dir'} ||= assetdir();
    $options{'host'} ||= 'localhost';
    $options{'args'} = \@ARGV;
    return $jobid;
}

sub main () {
    my ($jobid, $host) = parse_options();
    return unless $jobid;
    clone_jobs($jobid, \%options);
}

main;

1;
