AWS-CLIWrapper

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

Revision history for AWS::CLIWrapper

1.27  2023-06-29
    [IMPROVEMENTS]
        - Optionally catch aws-cli errors and retry (PR #23 by @nohuhu)
        - Add servics with aws-cli/1.27.163

1.26  2023-05-25
    [IMPROVEMENTS]
        - Add `region` method to allow introspection on constructor arguments (PR #22 by @nohuhu)

1.25  2023-03-16
    [BUG FIXES]
        - Fix AWS_CLIWRAPPER_TIMEOUT test (PR #20 by @nohuhu)

1.24  2023-03-15
    [IMPROVEMENTS]
        - Allow overriding aws-cli execution timeout via environment variable (PR #19 by @nohuhu)
        - Add servics with aws-cli/1.27.91

1.23  2022-03-23
    [IMPROVEMENTS]
        - Fix test suite fails with aws-cli v2 (rt 141885)

1.22  2022-03-17
    [IMPROVEMENTS]
        - optionally croak() on errors (PR #18 by @nohuhu)
        - Add servics with aws-cli/1.22.76

1.21  2021-05-20
    [IMPROVEMENTS]
        - Add servics with aws-cli/1.19.76

1.20  2021-02-12
    [IMPROVEMENTS]
        - Add servics with aws-cli/1.19.6

Changes  view on Meta::CPAN

        - Specified min perl version both in module and dist metadata
        - Added github repo to dist metadata
        - Add servics with aws-cli/1.10.9 (PR #11 by @mithun)

1.09  2015-10-02
    [IMPROVEMENTS]
        - Support ec2 wait (PR #9 by @negachov)

1.08  2015-08-19
    [IMPROVEMENTS]
        - write stdout/stderr message in debug mode (PR #8 by @limitusus)

1.07  2015-07-21
    [IMPROVEMENTS]
        - Don't execute aws command in load phase

1.06  2014-11-25
    [IMPROVEMENTS]
        - s3 OPERATION can take --include or --exclude option multiple times

1.05  2014-09-29

MANIFEST  view on Meta::CPAN

cpanfile
lib/AWS/CLIWrapper.pm
Makefile.PL
MANIFEST			This list of files
t/00_compile.t
t/01_new.t
t/03_awscli_path.t
t/03_awscli_timeout.t
t/03_awscli_version.t
t/03_region.t
t/04_errors.t
t/05_catch_error_options.t
t/06_catch_errors.t
t/bin/mock-aws
xt/01_podspell.t
xt/02_perlcritic.t
xt/03_pod.t
xt/05_dependencies.t
xt/10_ec2.t
xt/11_struct-in-list.t
xt/12_nested-boolean.t
xt/19_error.t
xt/20_s3-sync.t
xt/30_compat.t
xt/90_dependencies.t
xt/91_usedmodules.t
xt/92_usedfunctions.t
xt/perlcriticrc
META.yml                                 Module YAML meta-data (added by MakeMaker)
META.json                                Module JSON meta-data (added by MakeMaker)

lib/AWS/CLIWrapper.pm  view on Meta::CPAN

            push @opt, param2opt($k, $v);
        }
    }

    my $self = bless {
        region => $region,
        opt  => \@opt,
        json => JSON->new,
        param => \%param,
        awscli_path => $param{awscli_path} || 'aws',
        croak_on_error => !!$param{croak_on_error},
        timeout => (defined $ENV{AWS_CLIWRAPPER_TIMEOUT}) ? $ENV{AWS_CLIWRAPPER_TIMEOUT} : 30,
    }, $class;

    return $self;
}

sub region { shift->{region} }

sub awscli_path {
    my ($self) = @_;

lib/AWS/CLIWrapper.pm  view on Meta::CPAN

                $v = $1;
            } else {
                $v = 0;
            }
            version->parse($v);
        };
    }
    return $AWSCLI_VERSION;
}

sub catch_error_pattern {
    my ($self) = @_;

    return $ENV{AWS_CLIWRAPPER_CATCH_ERROR_PATTERN}
        if defined $ENV{AWS_CLIWRAPPER_CATCH_ERROR_PATTERN};

    return $self->{param}->{catch_error_pattern}
        if defined $self->{param}->{catch_error_pattern};
    
    return;
}

sub catch_error_retries {
    my ($self) = @_;

    my $retries = defined $ENV{AWS_CLIWRAPPER_CATCH_ERROR_RETRIES}
        ? $ENV{AWS_CLIWRAPPER_CATCH_ERROR_RETRIES}
        : defined $self->{param}->{catch_error_retries}
            ? $self->{param}->{catch_error_retries}
            : $DEFAULT_CATCH_ERROR_RETRIES;

    $retries = $DEFAULT_CATCH_ERROR_RETRIES if $retries < 0;

    return $retries;
}

sub catch_error_min_delay {
    my ($self) = @_;

    my $min_delay = defined $ENV{AWS_CLIWRAPPER_CATCH_ERROR_MIN_DELAY}
        ? $ENV{AWS_CLIWRAPPER_CATCH_ERROR_MIN_DELAY}
        : defined $self->{param}->{catch_error_min_delay}
            ? $self->{param}->{catch_error_min_delay}
            : $DEFAULT_CATCH_ERROR_MIN_DELAY;
    
    $min_delay = $DEFAULT_CATCH_ERROR_MIN_DELAY if $min_delay < 0;

    return $min_delay;
}

sub catch_error_max_delay {
    my ($self) = @_;

    my $min_delay = $self->catch_error_min_delay;

    my $max_delay = defined $ENV{AWS_CLIWRAPPER_CATCH_ERROR_MAX_DELAY}
        ? $ENV{AWS_CLIWRAPPER_CATCH_ERROR_MAX_DELAY}
        : defined $self->{param}->{catch_error_max_delay}
            ? $self->{param}->{catch_error_max_delay}
            : $DEFAULT_CATCH_ERROR_MAX_DELAY;
    
    $max_delay = $DEFAULT_CATCH_ERROR_MAX_DELAY if $max_delay < 0;

    $max_delay = $min_delay if $min_delay > $max_delay;

    return $max_delay;
}

sub catch_error_delay {
    my ($self) = @_;

    my $min = $self->catch_error_min_delay;
    my $max = $self->catch_error_max_delay;

    return $min == $max ? $min : $min + (int rand $max - $min);
}

sub param2opt {
    my($k, $v) = @_;

    my @v;

    $k =~ s/_/-/g;

lib/AWS/CLIWrapper.pm  view on Meta::CPAN

        my @o = param2opt($k, $v);
        if ($service eq 's3' && $k =~ /^(?:include|exclude)$/) {
            my $optk = shift @o;
            @o = map { $optk => $_ } @o;
        }
        push @cmd, @o;
    }
    @cmd = map { shell_quote($_) } @cmd;
    warn "cmd: ".join(' ', @cmd) if $ENV{AWSCLI_DEBUG};

    my $error_re = $self->catch_error_pattern;
    my $retries = $error_re ? $self->catch_error_retries : 0;

    RETRY: {
        $Error = { Message => '', Code => '' };

        my $exit_value = $self->_run(\%opt, \@cmd);
        my $ret = $self->_handle($service, $operation, $exit_value);

        return $ret unless $Error->{Code};

        if ($retries-- > 0 and $Error->{Message} =~ $error_re) {
            my $delay = $self->catch_error_delay;

            warn "Caught error matching $error_re, sleeping $delay seconds before retrying\n"
                if $ENV{AWSCLI_DEBUG};

            sleep $delay;

            redo RETRY;
        }

        croak $Error->{Message} if $self->{croak_on_error};

        return $ret;
    }
}

sub _run {
    my ($self, $opt, $cmd) = @_;

    my $ret;
    if (exists $opt->{'nofork'} && $opt->{'nofork'}) {
        # better for perl debugger
        my($ok, $err, $buf, $stdout_buf, $stderr_buf) = IPC::Cmd::run(
            command => join(' ', @$cmd),
            timeout => $opt->{timeout} || $self->{timeout},
        );
        $ret->{stdout} = join "", @$stdout_buf;
        $ret->{err_msg} = (defined $err ? "$err\n" : "") . join "", @$stderr_buf;
        if ($ok) {
            $ret->{exit_code} = 0;
            $ret->{timeout} = 0;
        } else {
            $ret->{exit_code} = 2;
            $ret->{timeout} = 1 if defined $err && $err =~ /^IPC::Cmd::TimeOut:/;
        }
        print "";
    } else {
        $ret = IPC::Cmd::run_forked(join(' ', @$cmd), {
            timeout => $opt->{timeout} || $self->{timeout},
        });
    }

    return $ret;
}

lib/AWS/CLIWrapper.pm  view on Meta::CPAN

            # exception from decode() from catching by outer
            # __DIE__ handler.
            local $SIG{__DIE__} = sub {};

            $self->json->decode($json);
        };
        if ($@) {
            if ($ENV{AWSCLI_DEBUG}) {
                warn $@;
                warn qq|stdout: "$ret->{stdout}"|;
                warn qq|err_msg: "$ret->{err_msg}"|;
            }
            return $json || 'success';
        }
        return $ret;
    } else {
        my $stdout_str = $ret->{stdout};
        if ($stdout_str && $stdout_str =~ /^{/) {
            my $json = $stdout_str;
            warn sprintf("%s.%s[%s]: %s\n",
                         $service, $operation, 'NG', $json,

lib/AWS/CLIWrapper.pm  view on Meta::CPAN

            my($ret) = $self->json->decode_prefix($json);
            if (exists $ret->{Errors} && ref($ret->{Errors}) eq 'ARRAY') {
                $Error = $ret->{Errors}[0];
            } elsif (exists $ret->{Response}{Errors}{Error}) {
                # old structure (maybe botocore < 0.7.0)
                $Error = $ret->{Response}{Errors}{Error};
            } else {
                $Error = { Message => 'Unknown', Code => 'Unknown' };
            }
        } else {
            my $msg = $ret->{err_msg};
            warn sprintf("%s.%s[%s]: %s\n",
                         $service, $operation, 'NG', $msg,
                        ) if $ENV{AWSCLI_DEBUG};
            $Error = { Message => $msg, Code => 'Unknown' };
        }

        return;
    }
}

lib/AWS/CLIWrapper.pm  view on Meta::CPAN

Constructor of AWS::CLIWrapper. Acceptable AWS CLI params are:

    region       region_name:Str
    profile      profile_name:Str
    endpoint_url endpoint_url:Str

Additionally, the these params can be used to control the wrapper behavior:

    nofork                  Truthy to avoid forking when executing `aws`
    timeout                 `aws` execution timeout
    croak_on_error          Truthy to croak() with the error message when `aws`
                            exits with non-zero code
    catch_error_pattern     Regexp pattern to match for error handling.
    catch_error_retries     Retries for handling errors.
    catch_error_min_delay   Minimal delay before retrying `aws` call
                            when an error was caught.
    catch_error_max_delay   Maximal delay before retrying `aws` call.

See below for more detailed explanation.

=item B<accessanalyzer>($operation:Str, $param:HashRef, %opt:Hash)

=item B<account>($operation:Str, $param:HashRef, %opt:Hash)

=item B<acm>($operation:Str, $param:HashRef, %opt:Hash)

=item B<acm_pca>($operation:Str, $param:HashRef, %opt:Hash)

lib/AWS/CLIWrapper.pm  view on Meta::CPAN

Special case: s3 OPERATION can take --include and --exclude option multiple times. For example "aws s3 sync --exclude 'foo' --exclude 'bar' LocalPath s3://S3Path", Pass ARRAYREF as value of C<include> or C<exclude> in this case:

    my $res = $aws->s3('sync', ['LocalPath', 's3://S3Path'], {
        exclude     => ['foo', 'bar'],
    })

Third arg "opt" is optional. Available key/values are below:

  timeout => Int
    Maximum time the "aws" command is allowed to run before aborting.
    default is 30 seconds, unless overridden with AWS_CLIWRAPPER_TIMEOUT environment variable.

  nofork => Int (>0)
    Call IPC::Cmd::run vs. IPC::Cmd::run_forked (mostly useful if/when in perl debugger).  Note: 'timeout', if used with 'nofork', will merely cause an alarm and return.  ie. 'run' will NOT kill the awscli command like 'run_forked' will.

  croak_on_error => Int (>0)
    When set to a truthy value, this will make AWS::CLIWrapper to croak() with error message when `aws` command exits with non-zero status. Default behavior is to set $AWS::CLIWrapper::Error and return.

  catch_error_pattern => RegExp
    When defined, this option will enable catching `aws-cli` errors matching this pattern
    and retrying `aws-cli` command execution. Environment variable
    AWS_CLIWRAPPER_CATCH_ERROR_PATTERN takes precedence over this option, if both
    are defined.

    Default is undef.

  catch_error_retries => Int (>= 0)
    When defined, this option will set the number of retries to make when `aws-cli` error
    was caught with catch_error_pattern, before giving up. Environment variable
    AWS_CLIWRAPPER_CATCH_ERROR_RETRIES takes precedence over this option, if both
    are defined.

    0 (zero) retries is a valid way to turn off error catching via environment variable
    in certain scenarios. Negative values are invalid and will be reset to default.

    Default is 3.

  catch_error_min_delay => Int (>= 0)
    When defined, this option will set the minimum delay in seconds before attempting
    a retry of failed `aws-cli` execution when the error was caught. Environment variable
    AWS_CLIWRAPPER_CATCH_ERROR_MIN_DELAY takes precedence over this option, if both
    are defined.

    0 (zero) is a valid value. Negative values are invalid and will be reset to default.

    Default is 3.

  catch_error_max_delay => Int (>= 0)
    When defined, this option will set the maximum delay in seconds before attempting
    a retry of failed `aws-cli` execution. Environment variable AWS_CLIWRAPPER_CATCH_ERROR_MAX_DELAY
    takes precedence over this option, if both are defined.

    0 (zero) is a valid value. Negative values are invalid and will be reset to default.
    If catch_error_min_delay is greater than catch_error_max_delay, both are set
    to catch_error_min_delay value.

    Default is 10.

=back

=head1 ENVIRONMENT

=over 4

=item HOME: used by default by /usr/bin/aws utility to find it's credentials (if none are specified)

lib/AWS/CLIWrapper.pm  view on Meta::CPAN

invocation of `aws-cli` that does not have a timeout value provided in the options argument of the
called function.

=item AWS_CLIWRAPPER_CATCH_ERROR_PATTERN

If this variable is set, AWS::CLIWrapper will retry `aws-cli` execution if stdout output
of failed `aws-cli` command matches the pattern. See L<ERROR HANDLING>.

=item AWS_CLIWRAPPER_CATCH_ERROR_RETRIES

How many times to retry command execution if an error was caught. Default is 3.

=item AWS_CLIWRAPPER_CATCH_ERROR_MIN_DELAY

Minimal delay before retrying command execution if an error was caught, in seconds.

Default is 3.

=item AWS_CLIWRAPPER_CATCH_ERROR_MAX_DELAY

Maximal delay before retrying command execution, in seconds. Default is 10.

=item AWS_CONFIG_FILE

=item AWS_ACCESS_KEY_ID

lib/AWS/CLIWrapper.pm  view on Meta::CPAN

=item AWS_DEFAULT_REGION

See documents of aws-cli.

=back

=head1 ERROR HANDLING

=over 4

By default, when `aws-cli` exits with an error code (> 0), AWS::CLIWrapper will set
the error code and message to $AWS::CLIWrapper::Error (and optionally croak), thus
relaying the error to calling code. While this approach is beneficial 99% of the time,
in some use cases `aws-cli` execution fails for a temporary reason unrelated to
both calling code and AWS::CLIWrapper, and can be safely retried after a short delay.

One of this use cases is executing `aws-cli` on AWS EC2 instances, where `aws-cli`
retrieves its configuration and credentials from the API exposed to the EC2 instance;
at certain times these credentials may be rotated and calling `aws-cli` at exactly
the right moment will cause it to fail with `Unable to locate credentials` error.

To prevent this kind of errors from failing the calling code, AWS::CLIWrapper allows
configuring an RegExp pattern and retry `aws-cli` execution if it fails with an error
matching the configured pattern.

The error catching pattern, as well as other configuration, can be defined either
as AWS::CLIWrapper options in the code, or as respective environment variables
(see L<ENVIRONMENT>).

The actual delay before retrying a failed `aws-cli` execution is computed as a
random value of seconds between catch_error_min_delay (default 3) and catch_error_max_delay
(default 10). Backoff is not supported at this moment.

=back 

=head1 AUTHOR

HIROSE Masaaki E<lt>hirose31 _at_ gmail.comE<gt>

=head1 REPOSITORY

t/04_errors.t  view on Meta::CPAN

use strict;
use Test::More;

use AWS::CLIWrapper;

# Default error handling
my $aws = AWS::CLIWrapper->new;
if ($aws->awscli_version == 0) {
    plan skip_all => 'not found aws command';
} else {
    plan tests => 4;
}

my $res = $aws->elbv2();

is $res, undef, "default result is undefined";

# Is this a TODO?
is $AWS::CLIWrapper::Error->{Code}, "Unknown", "default error code match";

my $want_err_msg = qr!exited with code \[\d+\]
stderr:
.*
usage: aws \[options\] <command> <subcommand> \[<subcommand> ...\] \[parameters\]
To see help text, you can run:

  aws help
  aws <command> help
  aws <command> <subcommand> help
!ms;

like $AWS::CLIWrapper::Error->{Message}, $want_err_msg, "default error message match";

# Croaking
my $aws_croak = AWS::CLIWrapper->new(croak_on_error => 1);

eval {
    $aws_croak->elbv2();
};


like $@, $want_err_msg, "croak on error message match";

t/05_catch_error_options.t  view on Meta::CPAN


done_testing;

__DATA__
# line 41
{
  'mock-aws version' => {
    method => 'awscli_version',
    want => '2.42.4242',
  },
  'default-catch_error_pattern' => {
    method => 'catch_error_pattern',
    want => undef,
  },
  'default-catch_error_retries' => {
    method => 'catch_error_retries',
    want => 3,
  },
  'default-catch_error_min_delay' => {
    method => 'catch_error_min_delay',
    want => 3,
  },
  'default-catch_error_max_delay' => {
    method => 'catch_error_max_delay',
    want => 10,
  },
  'default-catch_error_delay' => {
    method => 'catch_error_delay',
    want => [['>=', 3], ['<=', 10]],
  },
  'env-catch_error_pattern' => {
    env => {
      AWS_CLIWRAPPER_CATCH_ERROR_PATTERN => 'foo',
    },
    method => 'catch_error_pattern',
    want => 'foo',
  },
  'env-catch_error_retries' => {
    env => {
      AWS_CLIWRAPPER_CATCH_ERROR_RETRIES => 10,
    },
    method => 'catch_error_retries',
    want => 10,
  },
  'env-catch_error_retries-invalid' => {
    env => {
      AWS_CLIWRAPPER_CATCH_ERROR_RETRIES => -10,
    },
    method => 'catch_error_retries',
    want => 3,
  },
  'env-catch_error_min_delay' => {
    env => {
      AWS_CLIWRAPPER_CATCH_ERROR_MIN_DELAY => 15,
    },
    method => 'catch_error_min_delay',
    want => 15,
  },
  'env-catch_error_min_delay-invalid' => {
    env => {
      AWS_CLIWRAPPER_CATCH_ERROR_MIN_DELAY => -15,
    },
    method => 'catch_error_min_delay',
    want => 3,
  },
  'env-catch_error_max_delay' => {
    env => {
      AWS_CLIWRAPPER_CATCH_ERROR_MAX_DELAY => 30,
    },
    method => 'catch_error_max_delay',
    want => 30,
  },
  'env-catch_error_max_delay-invalid' => {
    env => {
      AWS_CLIWRAPPER_CATCH_ERROR_MAX_DELAY => -30,
    },
    method => 'catch_error_max_delay',
    want => 10,
  },
  'env-catch_error_max_delay-gt-min_delay' => {
    env => {
      AWS_CLIWRAPPER_CATCH_ERROR_MIN_DELAY => 30,
      AWS_CLIWRAPPER_CATCH_ERROR_MAX_DELAY => 15,
    },
    method => 'catch_error_max_delay',
    want => 30,
  },
  'args-catch_error_pattern' => {
    args => {
      catch_error_pattern => 'bar',
    },
    method => 'catch_error_pattern',
    want => 'bar',
  },
  'env-over-args-catch_error_pattern' => {
    args => {
      catch_error_pattern => 'qux',
    },
    env => {
      AWS_CLIWRAPPER_CATCH_ERROR_PATTERN => 'baz',
    },
    method => 'catch_error_pattern',
    want => 'baz',
  },
  'args-catch_error_retries' => {
    args => {
      catch_error_retries => 10,
    },
    method => 'catch_error_retries',
    want => 10,
  },
  'env-over-args-catch_error_retries' => {
    env => {
      AWS_CLIWRAPPER_CATCH_ERROR_RETRIES => 20,
    },
    args => {
      catch_error_retries => 10,
    },
    method => 'catch_error_retries',
    want => 20,
  },
  'args-catch_error_min_delay' => {
    args => {
      catch_error_min_delay => 20,
    },
    method => 'catch_error_min_delay',
    want => 20,
  },
  'env-over-args-catch_error_min_delay' => {
    env => {
      AWS_CLIWRAPPER_CATCH_ERROR_MIN_DELAY => 40,
    },
    args => {
      catch_error_min_delay => 20,
    },
    method => 'catch_error_min_delay',
    want => 40,
  },
  'args-catch_error_max_delay' => {
    args => {
      catch_error_max_delay => 60,
    },
    method => 'catch_error_max_delay',
    want => 60,
  },
  'env-over-args-catch_error_max_delay' => {
    env => {
      AWS_CLIWRAPPER_CATCH_ERROR_MAX_DELAY => 120,
    },
    args => {
      catch_error_max_delay => 60,
    },
    method => 'catch_error_max_delay',
    want => 120,
  },
  'min-max-catch_error_delay' => {
    args => {
      catch_error_min_delay => 30,
      catch_error_max_delay => 30,
    },
    method => 'catch_error_delay',
    want => 30,
  },
  'zero-catch_error_delay' => {
    args => {
      catch_error_min_delay => 0,
      catch_error_max_delay => 0,
    },
    method => 'catch_error_delay',
    want => 0,
  },
}

t/06_catch_errors.t  view on Meta::CPAN

  my ($wrapper_args, $env, $command, $subcommand, $cmd_args)
    = @$test{qw(wrapper_args env command subcommand cmd_args)};
  
  $env = {} unless $env;

  my ($tmp_fh, $tmp_name) = tempfile;
  print $tmp_fh $test->{retries} || 1;
  close $tmp_fh;

  local $ENV{AWS_CLIWRAPPER_TEST_ERROR_COUNTER_FILE} = $tmp_name;
  local $ENV{AWS_CLIWRAPPER_TEST_DIE_WITH_ERROR} = $test->{error_to_die_with}
    if $test->{error_to_die_with};
  
  local @ENV{keys %$env} = values %$env;
  
  $AWS::CLIWrapper::Error = { Message => '', Code => '' };

  my $aws = AWS::CLIWrapper->new(%default_wrapper_args, %{$wrapper_args || {}});
  my $res = eval { $aws->$command($subcommand, @{$cmd_args || []}) };

  if ($test->{retries} > 0) {
    open my $fh, "<", $tmp_name;
    my $counter = <$fh>;
    close $fh;

    is $counter, 0, "$test_name retry counter exhausted";
  }

  like "$@", $test->{exception}, "$test_name exception";
  like $AWS::CLIWrapper::Error->{Message}, $test->{error_msg_re},
    "$test_name error message";

  is_deeply $res, $test->{want}, "$test_name result";
}

done_testing;

__DATA__
# line 60
{
  'no-error' => {
    command => 'ecs',
    subcommand => 'list-clusters',
    error_to_die_with => undef,
    error_msg_re => qr{^$},
    exception => qr{^$},
    want => {
      clusterArns => [
        "arn:aws:ecs:us-foo-1:123456789:cluster/foo",
        "arn:aws:ecs:us-foo-1:123456789:cluster/bar",
        "arn:aws:ecs:us-foo-1:123456789:cluster/baz"
      ],
    }
  },
  'no-croak' => {
    command => 'ecs',
    subcommand => 'list-clusters',
    error_to_die_with => 'uh-oh',
    error_msg_re => qr{uh-oh},
    exception => qr{^$},
    want => undef,
  },
  'with-croak' => {
    wrapper_args => { croak_on_error => 1 },
    command => 'ecs',
    subcommand => 'list-clusters',
    error_to_die_with => 'foobaroo!',
    error_msg_re => qr{foobaroo},
    exception => qr{foobaroo},
    want => undef,
  },
  'catch-no-croak' => {
    env => {
      AWS_CLIWRAPPER_CATCH_ERROR_PATTERN => 'FUBAR',
      AWS_CLIWRAPPER_CATCH_ERROR_MIN_DELAY => 0,
      AWS_CLIWRAPPER_CATCH_ERROR_MAX_DELAY => 0,
    },
    command => 'ecs',
    subcommand => 'list-clusters',
    error_to_die_with => 'FUBAR',
    retries => 2,
    error_msg_re => qr{^$},
    exception => qr{^$},
    want => {
      clusterArns => [
        "arn:aws:ecs:us-foo-1:123456789:cluster/foo",
        "arn:aws:ecs:us-foo-1:123456789:cluster/bar",
        "arn:aws:ecs:us-foo-1:123456789:cluster/baz"
      ],
    }
  },
  'catch-with-croak' => {
    env => {
      AWS_CLIWRAPPER_CATCH_ERROR_PATTERN => 'throbbe',
      AWS_CLIWRAPPER_CATCH_ERROR_MIN_DELAY => 0,
      AWS_CLIWRAPPER_CATCH_ERROR_MAX_DELAY => 0,
    },
    command => 'ecs',
    subcommand => 'list-clusters',
    error_to_die_with => 'zong throbbe fung',
    retries => 3,
    error_msg_re => qr{^$},
    exception => qr{^$},
    want => {
      clusterArns => [
        "arn:aws:ecs:us-foo-1:123456789:cluster/foo",
        "arn:aws:ecs:us-foo-1:123456789:cluster/bar",
        "arn:aws:ecs:us-foo-1:123456789:cluster/baz"
      ],
    }
  },
}

t/bin/mock-aws  view on Meta::CPAN


eval 'exec /usr/bin/perl -wS $0 ${1+"$@"}'
  if 0;

use strict;
use warnings;
no warnings 'uninitialized';

version() if $ARGV[0] eq "--version";

handle_die_with_error() if $ENV{AWS_CLIWRAPPER_TEST_DIE_WITH_ERROR};

my $cmd = shift @ARGV;
my $subcmd = shift @ARGV;

handle($cmd, $subcmd);

sub handle {
  my ($cmd, $subcmd) = @_;

  $subcmd =~ s/-/_/g;

t/bin/mock-aws  view on Meta::CPAN

  if ('CODE' eq ref $handler) {
    $handler->();

    exit 0;
  }
  else {
    help();
  }
}

sub handle_die_with_error {
  my $counter_file = $ENV{AWS_CLIWRAPPER_TEST_ERROR_COUNTER_FILE};

  return unless -f $counter_file;

  open my $fh, "<", $counter_file or die "Cannot open $counter_file for read: $!";
  my $counter = <$fh>;
  close $fh;

  # This logic is the opposite of usual retries: we throw an error for the counter
  # number of times and then proceed normally after.
  if ($counter-- > 0) {
    open $fh, ">", $counter_file or die "Cannot open $counter_file for write: $!";
    print $fh $counter;
    close $fh;

    die $ENV{AWS_CLIWRAPPER_TEST_DIE_WITH_ERROR};
  }
}

t/bin/mock-aws  view on Meta::CPAN


sub help {
    die <<__END__;
usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:

  aws help
  aws <command> help
  aws <command> <subcommand> help

aws: error: the following arguments are required: operation

__END__
}

sub ecs_list_clusters {
    print <<__END__;
{
    "clusterArns": [
        "arn:aws:ecs:us-foo-1:123456789:cluster/foo",
        "arn:aws:ecs:us-foo-1:123456789:cluster/bar",

xt/12_nested-boolean.t  view on Meta::CPAN

# -*- mode: cperl -*-
use strict;
use Test::More;

use AWS::CLIWrapper;

my $AMI_ID = 'ami-0cc905e12087478be'; # Ubuntu 18.04

my $aws = AWS::CLIWrapper->new;
my $res;
my $err;

$res = $aws->ec2('run-instances', {
    count              => 1,
    image_id           => $AMI_ID,
    instance_type      => 't2.micro',
    key_name           => 'hirose31-aws-tokyo',
    network_interfaces => [
        {
            DeviceIndex              => 0,
            SubnetId                 => 'subnet-00c69dad8729ad024',

xt/19_error.t  view on Meta::CPAN

# -*- mode: cperl -*-
use strict;
use Test::More;

use AWS::CLIWrapper;

my $aws = AWS::CLIWrapper->new;
my $res;
my $err;

### unknown operation
$res = $aws->ec2('unknown-operation');
$err = $AWS::CLIWrapper::Error;
ok(!$res, 'unknown operation');

  is($err->{Code},    'Unknown',                     'err Code');
like($err->{Message}, qr/operation: Invalid choice/i, 'err Message');

### invalid option
$res = $aws->ec2('describe-instances', { invalid_option => 'blah' });
$err = $AWS::CLIWrapper::Error;
ok(!$res, 'invalid option');

  is($err->{Code},    'Unknown',                    'err Code');
like($err->{Message}, qr/(Unknown options:|Something is wrong)/,         'err Message');

### invalid option value
$res = $aws->ec2('describe-instances', { instance_ids => ['blah'] });
$err = $AWS::CLIWrapper::Error;
ok(!$res, 'invalid option value');

like($err->{Code},    qr/(Unknown|InvalidInstanceID.Malformed)/, 'err Code');
like($err->{Message}, qr/(Invalid id:|Unknown)/, 'err Message');

### required option
$res = $aws->ec2('run-instances');
$err = $AWS::CLIWrapper::Error;
ok(!$res, 'required option');

  is($err->{Code},    'Unknown',       'err Code');
like($err->{Message}, qr/(?:is required|MissingParameter)/, 'err Message');


###
done_testing;



( run in 1.328 second using v1.01-cache-2.11-cpan-49f99fa48dc )