Config-AWS
view release on metacpan or search on metacpan
lib/Config/AWS.pm view on Meta::CPAN
package Config::AWS;
# ABSTRACT: Parse AWS config files
use strict;
use warnings;
use Carp ();
use Ref::Util;
use Scalar::Util;
use Exporter::Shiny qw(
read
read_all
list_profiles
read_file
read_string
read_handle
config_file
credentials_file
default_profile
);
our $VERSION = '0.12';
our %EXPORT_TAGS = (
ini => [qw( read_file read_string read_handle )],
aws => [qw( config_file default_profile credentials_file )],
read => [qw( read read_all list_profiles )],
all => [qw( :ini :aws :read )],
);
# Internal methods for parsing and validation
my $prepare = sub {
# Input is given argument or credentials file (if exists) or config file.
my $input = shift // do {
my $cred_file = credentials_file();
-r $cred_file ? $cred_file : config_file();
};
unless ( Ref::Util::is_ref $input ) {
require Path::Tiny;
my @lines = eval { Path::Tiny::path( $input )->lines };
if ($@) {
Carp::croak "Cannot read from $input: $@->{err}"
if ref $@ && $@->isa('Path::Tiny::Error');
Carp::croak $@;
}
return \@lines;
}
return [ $input->getlines ] if Scalar::Util::openhandle $input;
if ( Ref::Util::is_blessed_ref $input ) {
return [ $input->slurp ] if $input->isa('Path::Class::File');
return [ $input->lines ] if $input->isa('Path::Tiny');
Carp::croak 'Cannot read from objects of type ', ref $input;
}
return [ split /\R/, $$input ] if Ref::Util::is_scalarref $input;
return $input if Ref::Util::is_arrayref $input;
Carp::croak "Could not use $input as source for ", (caller 1)[3];
};
my $read = sub {
my ($lines, $target_profile) = @_;
Carp::carp 'Reading config with only one line or less. Faulty input?'
if @$lines <= 1;
my $hash = {};
my $nested = {};
my $profile = '';
for my $i (0 .. $#$lines) {
my $line = $lines->[$i];
$line =~ s/\R$//;
if ($line =~ /^\[(?:profile )?([\w\/.@%:_-]+)\]/) {
$profile = $1;
next;
}
next if $target_profile && $profile ne $target_profile;
next unless my ($indent, $key, $value) = $line =~ /^(\s*)(\w+)\s*=\s*(.*)/;
if (length $indent) {
$nested->{$key} = $value;
}
else {
# Add nested hash if the value is empty and next line is indented.
$hash->{$profile}{$key}
= !length $value && $i < $#$lines && $lines->[$i + 1] =~ /^\s+/
? $nested : $value;
$nested = {} if keys %$nested;
}
}
return $target_profile ? ( $hash->{$target_profile} // {} ) : $hash;
};
# Config parsing interface
sub read {
$read->( $prepare->(shift), shift // default_profile() );
}
sub read_all {
$read->( &$prepare );
}
sub list_profiles {
map /^\[(?:profile )?([\w\/.@%:_-]+)\]/, @{ &$prepare };
}
# AWS information methods
sub default_profile {
$ENV{AWS_DEFAULT_PROFILE} // 'default';
}
sub credentials_file {
$ENV{AWS_SHARED_CREDENTIALS_FILE} // ( glob '~/.aws/credentials' )[0];
}
sub config_file {
$ENV{AWS_CONFIG_FILE} // ( glob '~/.aws/config' )[0];
}
# Methods for compatibility with Config::INI interface
sub read_file {
Carp::croak 'Filename is missing' unless @_;
Carp::croak 'Argument was not a string' if Ref::Util::is_ref $_[0];
$read->( $prepare->(shift), @_ );
}
sub read_string {
Carp::croak 'String is missing' unless @_;
Carp::croak 'Argument was not a string' if Ref::Util::is_ref $_[0];
$read->( [ split /\R/, shift // '' ], @_ );
}
sub read_handle {
Carp::croak 'Handle is missing' unless @_;
Carp::croak 'Argument was not a handle' unless Scalar::Util::openhandle $_[0];
$read->( [ shift->getlines ], @_ );
}
1;
__END__
=encoding utf8
=head1 NAME
Config::AWS - Parse AWS config files
=head1 SYNOPSIS
use Config::AWS ':all';
# Read the data for a specific profile
$config = read( $source, $profile );
# Or read the default profile from the default file
$config = read();
# Which is the same as
$config = read(
-r credentials_file() ? credentials_file() : config_file(),
default_profile()
);
# Read all of the profiles from a file
$profiles = read_all( $source );
# Or if you have cycles to burn
$profiles = {
map { $_ => read( $source, $_ ) } list_profiles( $source )
};
=head1 DESCRIPTION
Config::AWS is a small distribution with generic methods to correctly parse
the contents of config files for the AWS CLI client as described in
L<the AWS documentation|https://docs.aws.amazon.com/cli/latest/topic/config-vars.html>.
Although it is common to see these files parsed as standard INI files, this
is not appropriate since AWS config files have an idiosyncratic format for
nested values (as shown in the link above).
Standard INI parsers (like L<Config::INI>) are not made to parse this sort of
structure (nor should they). So Config::AWS exists to provide a suitable
and lightweight ad-hoc parser that can be used in other applications.
=head1 ROUTINES
Config::AWS does not export anything by default. All the functions
( run in 2.048 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )