#!/usr/bin/env perl

use strict;
use warnings;

our $home;

BEGIN {
  use FindBin;
  FindBin::again();

  $home = ($ENV{NETDISCO_HOME} || $ENV{HOME});

  # try to find a localenv if one isn't already in place.
  if (!exists $ENV{PERL_LOCAL_LIB_ROOT}) {
      use File::Spec;
      my $localenv = File::Spec->catfile($FindBin::RealBin, 'localenv');
      exec($localenv, $0, @ARGV) if -f $localenv;
      $localenv = File::Spec->catfile($home, 'perl5', 'bin', 'localenv');
      exec($localenv, $0, @ARGV) if -f $localenv;

      die "Sorry, can't find libs required for App::Netdisco.\n"
        if !exists $ENV{PERLBREW_PERL};
  }
}

BEGIN {
  use Path::Class;

  # stuff useful locations into @INC and $PATH
  unshift @INC,
    dir($FindBin::RealBin)->parent->subdir('lib')->stringify,
    dir($FindBin::RealBin, 'lib')->stringify;

  use Config;
  $ENV{PATH} = $FindBin::RealBin . $Config{path_sep} . $ENV{PATH};
}

use App::Netdisco;
use Dancer ':script';
use Dancer::Plugin::DBIC 'schema';

use Try::Tiny;

use App::Netdisco::DB::Deploy qw(parse_args run_deploy);
use App::Netdisco::DB::SchemaCheck;
use App::Netdisco::Util::PostgresFloor 'warn_postgres_floor_all_tenants';

=head1 NAME

netdisco-db-deploy - Database deployment for Netdisco

=head1 USAGE

This script upgrades or initializes a Netdisco database schema.

 ~/bin/netdisco-db-deploy [--redeploy-all | --force-step N | --check]

This script connects to the database and runs without user interaction. If
there's no Netdisco schema, it is deployed. If there's an unversioned schema
then versioning is added, and updates applied.  Otherwise only necessary
updates are applied to an already versioned schema.

Pre-existing requirements are that there's a working database connection and a
user with rights to create tables in that database. These settings are defined
in your environment YAML file (default C<~/environments/deployment.yml>).

From version 38 on, if a step fails, the script stops at that step and exits
non-zero.
Nothing from the failed step is applied. The message names the step file, the
failing statement and the PostgreSQL error, and prints the commands to go on.

=head1 OPTIONS

=over 4

=item C<--redeploy-all>

Replays every upgrade step from version 2, tolerating the errors that come
from steps already applied. It changes no data that Netdisco or an
administrator maintains. It repeats one cleanup: step 39-40 deletes vlan-0
node rows that duplicate the same MAC address, switch and port on another
VLAN.

=item C<--force-step N>

Forces step N (from version N to N+1) when it keeps failing and you cannot fix
the cause. N must be the next pending step. The failing statements in that
step are skipped, the version is stamped, and a row is written to the
C<schema_step_forced> table so the skip is recorded. The upgrade then carries
on with the following steps.
Run C<netdisco-db-deploy --check> afterwards to see what is missing. Once step
N is applied, the option is no longer needed; if it is left in place, the
script warns and carries on.

=item C<--check>

Compares this database's schema with a fresh install of the same version and
lists the differences, without changing anything. It exits 0 when they match
and 1 when they differ. It builds the fresh install in a temporary schema and
drops it afterwards, so the database user needs the CREATE privilege on the
database, which an owner has; without it the check exits 2 and prints how to
grant it. The database must already be at this version: on any other version
the check exits 2 and asks for the upgrade first, because steps not yet applied
would show as differences.

=back

=head1 CONTAINERS

In the Netdisco Docker images and the Helm chart, this script runs in the
db-init container. To pass it one of the options above, set
C<NETDISCO_DB_DEPLOY_ARGS> (for example
C<NETDISCO_DB_DEPLOY_ARGS=--force-step 57> in F<.env> for docker compose, or
C<dbInit.deployArgs> in the helm values), then rerun your C<docker compose up>
command or C<helm upgrade>. Remove the setting again once the upgrade has
finished.

For more database info see the
L<netdisco wiki|https://github.com/netdisco/netdisco/wiki/Database-Tips>.

=head1 VERSIONS

=over 4

=item *

Version 1 is a completely empty database schema with no tables

=item *

Version 2 is the "classic" Netdisco database schema as of Netdisco 1.1

=item *

Versions 5 to 16 add patches for Netdisco 1.2

=item *

Version 17 onwards deploys schema upgrades for Netdisco 2

=item *

Version 38 (2.023000) onwards applies each step completely or not at all
(except under C<--redeploy-all> or for a step named by C<--force-step>)

=back

=cut

my $options = try { parse_args(\@ARGV) } catch { print STDERR $_; exit 2 };

my $schema = schema('netdisco');
my $pg_ver = $schema->storage->dbh->{pg_server_version};

if ($pg_ver and $pg_ver < 90600) {
    printf "\nFATAL: minimum PostgreSQL version for Netdisco is 9.6, you have %s\n",
      (join '.', reverse map {scalar reverse} unpack("(A2)*", reverse $pg_ver));
    print "\nUnfortunately, if you are reading this, you already upgraded Netdisco,";
    print "\nso your only options now are to delete and reinstall Netdisco, or upgrade";
    print "\nyour PostgreSQL installation.\n";
    print "\nPlease always read the Release Notes before upgrading:";
    print "\nhttps://github.com/netdisco/netdisco/wiki/Release-Notes\n";
    die "\n";
}

warn_postgres_floor_all_tenants(sub { print STDERR "\nWARNING: ", shift, "\n" });

exit App::Netdisco::DB::SchemaCheck::run_check($schema) if $options->{check};

$options->{container} = $ENV{NETDISCO_DB_DEPLOY_CONTAINER} ? 1 : 0;
exit run_deploy($schema, $options);
