package Kubectl::CLIWrapper {
  use Moo;
  use IPC::Open3;
  use JSON::MaybeXS;
  use Kubectl::CLIWrapper::Result;
  use Type::Tiny::Union;
  use Types::Standard qw/Str CodeRef Bool/;

  our $VERSION = '0.06';

  my $TOKEN_TYPE = Type::Tiny::Union->new(
      type_constraints => [Str, CodeRef]
  );

  has kubeconfig => (is => 'ro', isa => Str, predicate => 'has_kubeconfig');
  has kubectl => (is => 'ro', isa => Str, default => 'kubectl');
  has namespace => (is => 'ro', isa => Str, predicate => 'has_namespace');
  has password => (is => 'ro', isa => Str, predicate => 'has_password');
  has server => (is => 'ro', isa => Str, predicate => 'has_server');
  has token => (is => 'ro', isa => $TOKEN_TYPE, predicate => 'has_token');
  has username => (is => 'ro', isa => Str, predicate => 'has_username');

  has insecure_tls => (is => 'ro', isa => Bool, default => 0);

  sub kube_options {
    my $self = shift;
    my %options = ();

    $options{server}     = $self->server     if $self->has_server;
    $options{username}   = $self->username   if $self->has_username;
    $options{password}   = $self->password   if $self->has_password;
    $options{namespace}  = $self->namespace  if $self->has_namespace;
    $options{kubeconfig} = $self->kubeconfig if $self->has_kubeconfig;
    $options{'insecure-skip-tls-verify'} = 'true' if $self->insecure_tls;

    if ($self->has_token) {
      $options{token} = ref($self->token) eq 'CODE'
        ? &{$self->token}()
        : $self->token;
    }

    return [ map { "--$_=$options{ $_ }" } keys %options ];
  }

  sub command_for {
    my ($self, @params) = @_;
    return ($self->kubectl, @{ $self->kube_options }, @params);
  }

  sub run {
    my ($self, @command) = @_;
    return $self->input(undef, @command);
  }

  sub json {
    my ($self, @command) = @_;

    push @command, '-o=json';

    my $result = $self->run(@command);
    my $struct = eval {
      JSON->new->decode($result->output);
    };
    if ($@) {
      return Kubectl::CLIWrapper::Result->new(
        rc => $result->rc,
        output => $result->output,
        success => 0
      );
    }

    return Kubectl::CLIWrapper::Result->new(
      rc => $result->rc,
      output => $result->output,
      json => $struct
    );
  }

  sub input {
    my ($self, $input, @params) = @_;

    my @final_command = $self->command_for(@params);

    my ($stdin, $stdout, $stderr);
    my $pid = open3($stdin, $stdout, $stderr, @final_command);
    print $stdin $input  if(defined $input);
    close $stdin;

    my $out = join '', <$stdout>;
    my $err = join '', <$stderr> if ($stderr);

    die "Unexpected contents in stderr $err" if ($err);

    waitpid( $pid, 0 );
    my $rc = $? >> 8;

    return Kubectl::CLIWrapper::Result->new(
      rc => $rc,
      output => $out,
    );
  }

}
1;
### main pod documentation begin ###

=encoding UTF-8

=head1 NAME

Kubectl::CLIWrapper - Module to use the Kubernetes API via the kubectl CLI

=head1 SYNOPSIS

  use Kubectl::CLIWrapper;

  my $kube = Kubectl::CLIWrapper->new(
    server => 'https://kubernetes.example.org/',
    username => 'user',
    password => 'pass',
  );

  my $result = $kube->run('explain', 'service');
  # $result->success == 1 if the command executed correctly
  # $result->output contains the output of the command

  my $result = $kube->json('get', 'pods');
  # $result->success == 1 if the command executed correctly
  # $result->output contains the output of the command
  # $result->json is a hashref with the result of the parsed JSON output of the command

  my $result = $kube->input('{"kind":"Service" ... }', 'create', '-f', '-');
  # $result->success == 1 if the command executed correctly
  # $result->output contains the output of the command 

=head1 DESCRIPTION

This module helps you use the Kubernetes API. It sends all it's commands
via the CLI command line tool C<kubectl>. You can find kubectl installation instructions
here L<https://kubernetes.io/docs/tasks/tools/install-kubectl/>.

=head1 CREDENTIALS

Kubectl::CLIWrapper attributes are mainly the options you can pass C<kubectl> to control
how it authenticates to the Kubernetes server. Run C<kubectl options> to discover what these
options do. If you don't initialize any attributes, kubectl will behave just like on the command 
line (loading ~/.kube/config) which may be already set to point to a Kubernetes server

=head1 ATTRIBUTES

=head2 kubectl

By default initialized to C<kubectl>. It will try to find kubectl in the PATH. You can
set it explicitly to specific kubectl excecutable.

=head2 kubeconfig

Path to your kube configuration, defaults to C<$HOME/.kube/config> via kubectl.

=head2 server

The URL of the Kubernetes service

=head2 username

The username for Basic Authentication

=head2 password

The password for Basic Authentication

=head2 token

The Bearer token for authentication. If it's a scalar, that value will be used. If it's
a coderef (sub {}), it will be invoked each time kubectl is called and it's return value
used as the value of the --token option

=head2 insecure_tls

A Boolean flag that tells kubectl to not verify the certificate of the server it connects to

=head2 namespace

The Kubernetes namespace to operate in.

=head1 METHODS

=head2 run(@parameters)

Will run kubectl with the parameters. Returns a L<Kubectl::CLIWrapper::Result> object
with C<output> set to the output of the command, and C<success> a Boolean to indicate
if the command reported successful execution.

=head2 json(@parameters)

Will run kubectl with the parameters, and C<'-o=json'>. Returns a L<Kubectl::CLIWrapper::Result> object
with C<output> set to the output of the command, and C<json> set to a hashref with the parsed
JSON. C<success> will be false if JSON parsing fails.

=head2 input($input_to_kubectl, @parameters)

Will run kubectl with the parametes, sending $input_to_kubectl on it's STDIN. 
Returns a L<Kubectl::CLIWrapper::Result> object with C<output> set to the output of the command. 
C<success> will be set accordingly.

=head1 SEE ALSO

L<https://kubernetes.io/docs/tasks/tools/install-kubectl/>

L<IO::K8s>

L<https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/>

L<Net::Kubernetes>

=head1 CONTRIBUTORS

waterkip: 
 - adding the possiblity to set a kubeconfig file
 - helping port to Moose

ureesoriano:
 - fix for token attribute being ignored
 - dynamic generation of the "token" command-line option 

=head1 AUTHOR

    Jose Luis Martinez
    CAPSiDE
    jlmartinez@capside.com

=head1 BUGS and SOURCE

The source code is located here: L<https://github.com/pplu/kubectl-cliwrapper>

Please report bugs to: L<https://github.com/pplu/kubectl-cliwrapper/issues>

=head1 COPYRIGHT and LICENSE

Copyright (c) 2018 by CAPSiDE
This code is distributed under the Apache 2 License. The full text of the 
license can be found in the LICENSE file included with this module.

=cut
