The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use strict;
use warnings;

package Footprintless::Service;
$Footprintless::Service::VERSION = '1.27';
# ABSTRACT: Performs an action on a service.
# PODNAME: Footprintless::Service

use parent qw(Footprintless::MixableBase);

use Carp;
use Footprintless::Command qw(
    batch_command
    command
);
use Footprintless::CommandOptionsFactory;
use Footprintless::InvalidEntityException;
use Footprintless::Localhost;
use Footprintless::Mixins qw(
    _command_options
    _entity
    _run_or_die
);
use Footprintless::Util qw(
    invalid_entity
);
use Log::Any;

my $logger = Log::Any->get_logger();

sub kill {
    my ( $self, %options ) = @_;
    $self->execute( 'kill', %options );
}

sub _command {
    my ( $self, $action ) = @_;
    my $command      = $self->{spec}{command};
    my $actions_spec = $self->{spec}{actions}{$action};
    if ($actions_spec) {
        if ( $actions_spec->{command} ) {
            return $actions_spec->{command};
        }
        elsif ( $actions_spec->{command_args} ) {
            $action = $actions_spec->{command_args};
        }
        elsif ( $actions_spec->{use_pid} ) {
            invalid_entity( $self->{coordinate},
                "pid_file or pid_command required for [$action]" )
                unless ( $self->{spec}{pid_file} || $self->{spec}{pid_command} );

            my $pid_file = $self->{spec}{pid_file};
            my $pid_command =
                $pid_file
                ? command(
                "cat $pid_file",
                $self->{command_options}->clone(
                    hostname     => undef,
                    ssh          => undef,
                    ssh_username => undef,
                )
                )
                : $self->{spec}{pid_command};
            if ( $action eq 'kill' ) {
                return "kill -KILL \$($pid_command)";
            }
            elsif ( $action eq 'status' ) {
                my $command_name = $actions_spec->{command_name} || $command || 'command';
                return command( "kill -0 \$($pid_command) 2> /dev/null "
                        . "&& echo '$command_name is running' "
                        . "|| echo '$command_name is stopped'" );
            }
            else {
                invalid_entity( $self->{coordinate}, "use_pid not supported for [$action]" );
            }
        }
    }

    invalid_entity( $self->{coordinate}, "no command specified for [$action]" )
        unless ($command);
    return "$command $action";
}

sub execute {
    my ( $self, $action, %options ) = @_;

    my $runner_options =
          %options && $options{runner_options}
        ? $options{runner_options}
        : { out_handle => \*STDOUT };

    $self->_run_or_die( command( $self->_command($action), $self->{command_options} ),
        $runner_options );
}

sub _init {
    my ( $self, %options ) = @_;

    $self->{entity}          = $self->{factory}->entities();
    $self->{spec}            = $self->_entity( $self->{coordinate}, 1 );
    $self->{command_options} = $self->_command_options();

    return $self;
}

sub start {
    my ( $self, %options ) = @_;
    $self->execute( 'start', %options );
}

sub status {
    my ( $self, %options ) = @_;
    $self->execute( 'status', %options );
}

sub stop {
    my ( $self, %options ) = @_;
    $self->execute( 'stop', %options );
}

1;

__END__

=pod

=head1 NAME

Footprintless::Service - Performs an action on a service.

=head1 VERSION

version 1.27

=head1 SYNOPSIS

    # Standard way of getting a service
    use Footprintless;
    my $service = Footprintless->new()->service();

    $service->stop();

    $service->start();

    $service->status();

    $service->kill();

=head1 DESCRIPTION

Manages services.  Allows you to start, stop, check the status of, and
kill services.  Additional actions can be configured as well.

=head1 ENTITIES

A simple service (the most common case) can be defined:

    service => {
        command => '/opt/foo/bar.sh',
        pid_file => '/var/run/bar/bar.pid'
    }

A more complex service might be defined:

    service => {
        actions => {
            debug => {command_args => "jpda start"},
            kill => {command_args => "stop -kill"},
            status => {use_pid => 1, command_name => 'tomcat'},
        },
        command => '/opt/tomcat/catalina.sh',
        hostname => 'tomcat.pastdev.com',
        pid_command => 'ps -aef|grep "/opt/tomcat/"|grep -v grep|awk \'{print \$2}\'',
        sudo_username => 'tomcat',
    }

In this case, an additional action, debug, was added, kill was redefined
as a special case of stop, and status was redefined to use the pid 
(ex: kill -0 $pid).  Also, the pid is found via command rather than a file.

=head1 CONSTRUCTORS

=head2 new($entity, $coordinate, %options)

Constructs a new service configured by C<$entities> at C<$coordinate>.  
The supported options are:

=over 4

=item command_options_factory

The command options factory to use.  Defaults to an instance of
L<Footprintless::CommandOptionsFactory> using the C<localhost> instance
of this object.

=item command_runner

The command runner to use.  Defaults to an instance of 
L<Footprintless::CommandRunner::IPCRun>.

=item localhost

The localhost alias resolver to use.  Defaults to an instance of
L<Footprintless::Localhost> configured with C<load_all()>.

=back

=head1 METHODS

=head2 execute($action)

Executes C<$action> on the service.

=head2 kill()

Kills the service.

=head2 start()

Starts the service.

=head2 status()

Prints out the status of the service.

=head2 stop()

Stops the service.

=head1 AUTHOR

Lucas Theisen <lucastheisen@pastdev.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2016 by Lucas Theisen.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=head1 SEE ALSO

Please see those modules/websites for more information related to this module.

=over 4

=item *

L<Footprintless|Footprintless>

=item *

L<Config::Entities|Config::Entities>

=item *

L<Footprintless|Footprintless>

=item *

L<Footprintless::CommandOptionsFactory|Footprintless::CommandOptionsFactory>

=item *

L<Footprintless::CommandRunner|Footprintless::CommandRunner>

=item *

L<Footprintless::Localhost|Footprintless::Localhost>

=back

=cut