Group
Extension

Net-Heroku/lib/Net/Heroku.pm

package Net::Heroku;
use Mojo::Base -base;
use Net::Heroku::UserAgent;
use Mojo::JSON;
use Mojo::Util 'url_escape';

our $VERSION = 0.10;

has host => 'api.heroku.com';
has ua => sub { Net::Heroku::UserAgent->new(host => shift->host) };
has 'api_key';

sub new {
  my $self   = shift->SUPER::new(@_);
  my %params = @_;

  # Assume email & pass
  $self->ua->api_key(
    defined $params{email}
    ? $self->_retrieve_api_key(@params{qw/ email password /})
    : $params{api_key} ? $params{api_key}
    :                    ''
  );

  return $self;
}

sub error {
  my $self = shift;
  my $res  = $self->ua->tx->res;

  return if $res->code =~ /^2\d{2}$/;

  return (
    code    => $res->code,
    message => ($res->json ? $res->json->{error} : $res->body)
  );
}

sub _retrieve_api_key {
  my ($self, $email, $password) = @_;

  return $self->ua->post(
    '/login' => form => {email => $email, password => $password})
    ->res->json('/api_key');
}

sub apps {
  my ($self, $name) = @_;

  return @{$self->ua->get('/apps')->res->json || []};
}

sub app_created {
  my ($self, %params) = (shift, @_);

  return 1
    if $self->ua->put('/apps/' . $params{name} . '/status')->res->code == 201;
}

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

  my $res = $self->ua->delete('/apps/' . $params{name})->res;
  return 1 if $res->{code} == 200;
}

sub create {
  my ($self, %params) = (shift, @_);

  # Empty space names no longer allowed
  #delete $params{name} if !$params{name};

  my @ar = map +("app[$_]" => $params{$_}) => keys %params;
  %params = (
    'app[stack]' => 'cedar',
    @ar,
  );

  my $res = $self->ua->post('/apps' => form => \%params)->res;

  return $res->json && $res->code == 202 ? %{$res->json} : ();
}

sub add_config {
  my ($self, %params) = (shift, @_);

  return %{$self->ua->put(
          '/apps/'
        . (defined $params{name} and delete($params{name}))
        . '/config_vars' => Mojo::JSON->new->encode(\%params)
      )->res->json
      || {}
  };
}

sub config {
  my ($self, %params) = (shift, @_);

  return
    %{$self->ua->get('/apps/' . $params{name} . '/config_vars')->res->json
      || []};
}

sub add_key {
  my ($self, %params) = (shift, @_);

  return 1
    if $self->ua->post('/user/keys' => $params{key})->res->{code} == 200;
}

sub keys {
  my ($self, %params) = (shift, @_);

  return @{$self->ua->get('/user/keys')->res->json || []};
}

sub remove_key {
  my ($self, %params) = (shift, @_);

  my $res =
    $self->ua->delete('/user/keys/' . url_escape($params{key_name}))->res;
  return 1 if $res->{code} == 200;
}

sub ps {
  my ($self, %params) = (shift, @_);

  return @{$self->ua->get('/apps/' . $params{name} . '/ps')->res->json || []};
}

sub run {
  my ($self, %params) = (shift, @_);

  return
    %{$self->ua->post('/apps/' . $params{name} . '/ps' => form => \%params)
      ->res->json || {}};
}

sub restart {
  my ($self, %params) = (shift, @_);

  return 1
    if $self->ua->post(
    '/apps/' . $params{name} . '/ps/restart' => form => \%params)->res->code
    == 200;
}

sub stop {
  my ($self, %params) = (shift, @_);

  return 1
    if $self->ua->post(
    '/apps/' . $params{name} . '/ps/stop' => form => \%params)->res->code
    == 200;
}

sub releases {
  my ($self, %params) = (shift, @_);

  my $url =
      '/apps/'
    . $params{name}
    . '/releases'
    . ($params{release} ? '/' . $params{release} : '');

  my $releases = $self->ua->get($url)->res->json || [];

  return $params{release} ? %$releases : @$releases;
}

sub rollback {
  my ($self, %params) = (shift, @_);

  $params{rollback} = delete $params{release};

  return $params{rollback}
    if $self->ua->post(
    '/apps/' . $params{name} . '/releases' => form => \%params)->res->code
    == 200;
}

sub add_domain {
  my ($self, %params) = (shift, @_);

  my $url = '/apps/' . $params{name} . '/domains';

  return 1
    if $self->ua->post(
    $url => form => {'domain_name[domain]' => $params{domain}})->res->code
    == 200;
}

sub domains {
  my ($self, %params) = (shift, @_);

  my $url = '/apps/' . $params{name} . '/domains';

  return @{$self->ua->get($url)->res->json || []};
}

sub remove_domain {
  my ($self, %params) = (shift, @_);

  return 1
    if $self->ua->delete(
    '/apps/' . $params{name} . '/domains/' . url_escape($params{domain}))
    ->res->code == 200;
}

1;

=head1 NAME

Net::Heroku - Heroku API

=head1 DESCRIPTION

Heroku API

Requires Heroku account - free @ L<http://heroku.com>

=head1 USAGE

    my $h = Net::Heroku->new(api_key => api_key);
    - or -
    my $h = Net::Heroku->new(email => $email, password => $password);

    my %res = $h->create;

    $h->add_config(name => $res{name}, BUILDPACK_URL => ...);
    $h->restart(name => $res{name});

    say $_->{name} for $h->apps;

    $h->destroy(name => $res{name});


    warn 'Error:' . $h->error                     # Error: App not found.
      if not $h->destroy(name => $res{name});

    if (!$h->destroy(name => $res{name})) {
      my %err = $h->error;
      warn "$err{code}, $err{message}";           # 404, App not found.
    }

=head1 METHODS

=head2 new

    my $h = Net::Heroku->new(api_key => $api_key);
    - or -
    my $h = Net::Heroku->new(email => $email, password => $password);

Requires api key or user/pass. Returns Net::Heroku object.

=head2 apps

    my @apps = $h->apps;

Returns list of hash references with app information

=head2 destroy

    my $bool = $h->destroy(name => $name);

Requires app name.  Destroys app.  Returns true if successful.

=head2 create

    my $app = $h->create;

Creates a Heroku app.  Accepts optional hash list as values, returns hash list.  Returns empty list on failure.

=head2 add_config

    my %config = $h->add_config(name => $name, config_key => $config_value);

Requires app name.  Adds config variables passed in hash list.  Returns hash config.

=head2 config

    my %config = $h->config(name => $name);

Requires app name.  Returns hash reference of config variables.

=head2 add_key

    my $bool = $h->add_key(key => ...);

Requires key.  Adds ssh public key.

=head2 keys

    my @keys = $h->keys;

Returns list of keys

=head2 remove_key

    my $bool = $h->remove_key(key_name => $key_name);

Requires name associated with key.  Removes key.

=head2 ps

    my @processes = $h->ps(name => $name);

Requires app name.  Returns list of processes.

=head2 run

    my $process = $h->run(name => $name, command => $command);

Requires app name and command.  Runs command once.  Returns hash response.

=head2 restart

    my $bool = $h->restart(name => $name);
    my $bool = $h->restart(name => $name, ps => $ps, type => $type);

Requires app name.  Restarts app.  If ps is supplied, only process is restarted.

=head2 stop

    my $bool = $h->stop(name => $name, ps => $ps, type => $type);

Requires app name.  Stop app process.

=head2 releases

    my @releases = $h->releases(name => $name);
    my %release  = $h->releases(name => $name, release => $release);

Requires app name.  Returns list of hashrefs.
If release name specified, returns hash.

=head2 add_domain

    my $bool = $h->add_domain(name => $name, domain => $domain);

Requires app name.  Adds domain.

=head2 domains

    my @domains = $h->domains(name => $name);

Requires app name.  Returns list of hashrefs describing assigned domains.

=head2 remove_domain

    my $bool = $h->remove_domain(name => $name, domain => $domain);

Requires app name associated with domain.  Removes domain.

=head2 rollback

    my $bool = $h->rollback(name => $name, release => $release);

Rolls back to a specified releases

=head2 error

    my $message = $h->error;
    my %err     = $h->error;

In scalar context, returns error message from last request

In list context, returns hash with keys: code, message.

If the last request was successful, returns empty list.

=head1 SEE ALSO

L<Mojo::UserAgent>, L<http://mojolicio.us/perldoc/Mojo/UserAgent#DEBUGGING>, L<https://api-docs.heroku.com/>

=head1 SOURCE

L<http://github.com/tempire/net-heroku>

=head1 VERSION

0.10

=head1 AUTHOR

Glen Hinkle C<tempire@cpan.org>

=cut


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.