#!/usr/bin/perl
#
# Copyright 2007-2010 Timothy Kay
# http://timkay.com/aws/
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
require v5.10;
$ec2_version = "2013-10-15";
$sqs_version = "2012-11-05";
$elb_version = "2011-11-15";
$sdb_version = "2009-04-15";
$iam_version = "2010-05-08";
$sts_version = "2011-06-15";
$pa_version = "2009-01-06";
$r53_version = "2012-02-29";
$ebn_version = "2010-12-01";
$cloudformation_version = "2010-05-15";
$rds_version = "2013-09-09";
use Digest::SHA qw(hmac_sha1_base64 sha256_hex hmac_sha256 hmac_sha256_hex hmac_sha256_base64);
use MIME::Base64 qw(encode_base64);
#
# Need to implement:
#
# ConfirmProductInstance - not tested
# DescribeImageAttribute - not working "An internal error has occurred"
# ModifyImageAttribute
# ResetImageAttribute
#
# Windows support:
# BundleInstance
# DescribeBundleTasks
# CancelBundleTasks
#
@cmd = (
["aws", "xml-version", XMLVERSION, [
["", Service],
]],
["ec2", "add-group addgrp", CreateSecurityGroup, [
["", GroupName],
["d", GroupDescription],
["vpc c", VpcId],
]],
["ec2", "add-keypair addkey", CreateKeyPair, [["", KeyName]]],
["ec2", "add-placement-group", CreatePlacementGroup, [
["", GroupName],
["s", Strategy],
]],
["ec2", "allocate-address allad", AllocateAddress],
["ec2", "associate-address aad", AssociateAddress, [
["", PublicIp],
["a", AllocationId],
["i", InstanceId],
]],
["ec2", "attach-volume attvol", AttachVolume, [
["", VolumeId],
["i", InstanceId],
["d", Device],
]],
["ec2", "authorize auth", AuthorizeSecurityGroupIngress, [
["" => GroupName],
["egress e" => "__action__", undef, AuthorizeSecurityGroupEgress],
["groupId i" => "GroupId"],
["protocol P" => "IpPermissions.1.IpProtocol"],
[p => undef, undef, sub {
my($min, $max) = split(/-/, "$_[0]-$_[0]");
# parse 22-22, 0-65535, 20-21, -1--1 Yikes!
($min, $max) = (-1, -1) if $_[0] == -1;
["IpPermissions.1.FromPort" => $min, "IpPermissions.1.ToPort" => $max];
}],
[q => "IpPermissions.1.ToPort"],
#["t", icmp type code],
["source-group-user u" => "IpPermissions.1.Groups.N.UserId"],
["source-group o" => "IpPermissions.1.Groups.N.GroupName"],
["source-group-id oi" => "IpPermissions.1.Groups.N.GroupId"],
["s" => "IpPermissions.1.IpRanges.N.CidrIp"],
]],
["ec2", "cancel-spot-instance-requests cancel csir", CancelSpotInstanceRequests, [
["", SpotInstanceRequestIdN],
]],
["ec2", "confirm-product-instance", ConfirmProductInstance, [
["", ProductCode],
["i", InstanceId],
]],
["ec2", "create-image cimg", CreateImage, [
["", InstanceId],
["n", Name],
["d", Description, ""],
["no-reboot", NoReboot, false],
]],
["ec2", "create-snapshot csnap", CreateSnapshot, [
["", VolumeId],
["description", Description],
]],
["ec2", "create-spot-datafeed-subscription addsds", CreateSpotDatafeedSubscription, [
["", Bucket],
["b", Bucket],
["p", Prefix, "spot/datafeed"],
]],
["ec2", "delete-spot-datafeed-subscription delsds", DeleteSpotDatafeedSubscription, []],
["ec2", "describe-spot-datafeed-subscription dsds", DescribeSpotDatafeedSubscription, []],
["ec2", "describe-spot-instance-requests dsir", DescribeSpotInstanceRequests, [
["", SpotInstanceRequestIdN],
]],
["ec2", "describe-spot-price-history dsph", DescribeSpotPriceHistory, [
["start", StartTime],
["end", EndTime],
["instance-type", InstanceType, "m1.small"],
["description", ProductDescription, "Linux/UNIX"],
]],
["ec2", "create-volume cvol", CreateVolume, [
["size", Size],
["zone", AvailabilityZone],
["snapshot", SnapshotId],
]],
["ec2", "delete-group delgrp", DeleteSecurityGroup, [["", GroupName]]],
["ec2", "delete-placement-group", DeletePlacementGroup, [["", GroupName]]],
["ec2", "delete-keypair delkey", DeleteKeyPair, [["", KeyName]]],
["ec2", "delete-snapshot delsnap", DeleteSnapshot, [["", SnapshotId]]],
["ec2", "delete-volume delvol", DeleteVolume, [["", VolumeId]]],
["ec2", "deregister", DeregisterImage, [["", ImageId]]],
["ec2", "describe-addresses dad", DescribeAddresses, [["", PublicIpN]]],
["ec2", "describe-availability-zones daz", DescribeAvailabilityZones, [["", ZoneNameN]]],
["ec2", "describe-security-groups describe-group describe-groups dgrp", DescribeSecurityGroups, [
["", GroupNameN],
["GroupName g" => "GroupNameN"],
["GroupId i" => "GroupIdN"],
["filter F", undef, undef, \&parse_tags_describe],
]],
["ec2", "describe-image-attribute", DescribeImageAttribute, [
["", ImageId],
["l", launchPermission],
["p", productCodes],
["kernel", "kernel"],
["ramdisk", "ramdisk"],
["B", "blockDeviceMapping"],
]],
["ec2", "describe-images dim", DescribeImages, [
["", ImageIdN],
["o", OwnerN],
["x", ExecutableByN],
]],
["ec2", "describe-instances din", DescribeInstances, [["", InstanceIdN]]],
["ec2", "describe-instance-attributes dinatt", DescribeInstanceAttribute, [
["", InstanceId],
["attribute a" => "Attribute"],
]],
["ec2", "describe-instance-status dins", DescribeInstanceStatus, [
["", InstanceIdN],
["a", IncludeAllInstances],
]],
["ec2", "describe-keypairs dkey", DescribeKeyPairs, [["", KeyNameN]]],
["ec2", "describe-placement-groups", DescribePlacementGroups, [["", GroupNameN]]],
["ec2", "describe-regions dreg", DescribeRegions],
["ec2", "describe-reserved-instances", DescribeReservedInstances, [
["", ReservedInstanceIdN],
]],
["ec2", "describe-reserved-instances-offerings", DescribeReservedInstancesOfferings, [
["offering", ReservedInstancesOfferingIdN],
["instance-type", InstanceType],
["availability-zone", AvailabilityZone],
["z", AvailabilityZone],
["description", ProductDescription],
]],
["ec2", "describe-snapshot-attribute dsa", DescribeSnapshotAttribute, [
["", SnapshotIdN],
["attribute", Attribute],
]],
["ec2", "reset-snapshot-attribute rsa", ResetSnapshotAttribute, [
["", SnapshotIdN],
["attribute", Attribute],
]],
["ec2", "modify-snapshot-attribute msa", ModifySnapshotAttribute, [
["", SnapshotId],
["user", UserId],
["group", UserGroup],
["attribute", Attribute],
["type", OperationType],
]],
["ec2", "describe-snapshots dsnap", DescribeSnapshots, [
["", SnapshotIdN],
["owner", Owner, "self"],
["restorableby", RestorableBy],
]],
["ec2", "describe-volumes dvol", DescribeVolumes, [["", VolumeIdN]]],
["ec2", "describe-volume-status dvs", DescribeVolumeStatus, [
["", VolumeIdN],
["filter f", undef, undef, \&parse_filter],
]],
["ec2", "detach-volume detvol", DetachVolume, [["", VolumeId]]],
["ec2", "disassociate-address disad", DisassociateAddress, [["", PublicIp]]],
["ec2", "get-console-output gco", GetConsoleOutput, [["", InstanceId]]],
["ec2", "purchase-reserved-instance-offering", PurchaseReservedInstancesOffering, [
["offering-id", ReservedInstancesOfferingId],
["instance-count", InstanceCount],
]],
["ec2", "reboot-instances reboot", RebootInstances, [["", InstanceIdN]]],
["ec2", "release-address rad", ReleaseAddress, [["", PublicIp]]],
["ec2", "register-image register", RegisterImage, [
["", ImageLocation],
["name n" => Name],
["description d" => Description],
["architecture a" => Architecture],
[kernel => KernelId],
[ramdisk => RamdiskId],
["root-device-name" => RootDeviceName, "/dev/sda1"],
["block-device-mapping b", undef, undef, \&parse_block_device_mapping],
["device-name" => "BlockDeviceMapping.N.DeviceName"],
["no-device" => "BlockDeviceMapping.N.Ebs.NoDevice"],
["virtual-name" => "BlockDeviceMapping.N.VirtualName"],
[snapshot => "BlockDeviceMapping.N.Ebs.SnapshotId"],
["volume-size" => "BlockDeviceMapping.N.Ebs.VolumeSize"],
["delete-on-termination" => "BlockDeviceMapping.N.Ebs.DeleteOnTermination"],
]],
["ec2", "request-spot-instances req-spot rsi", RequestSpotInstances, [
["" => "LaunchSpecification.ImageId", "ami-4a0df923"],
["price p" => SpotPrice],
["instance-count n" => InstanceCount, 1],
["type r" => Type, "one-time"],
["valid-from-date" => ValidFrom],
["valid-until-date" => ValidUntil],
["launch-group" => LaunchGroup],
["availability-zone-group" => AvailabilityZoneGroup],
["user-data d" => "LaunchSpecification.UserData", undef,
sub {encode_base64($_[0], "")}],
["user-data-file f" => "LaunchSpecification.UserData", undef,
sub {encode_base64(load_file($_[0]))}],
["group g" => "LaunchSpecification.SecurityGroupN"],
["a", "LaunchSpecification.AddressingType"],
["key k" => "LaunchSpecification.KeyName"],
["instance-type t" => "LaunchSpecification.InstanceType", "t1.micro"],
["availability-zone z" => "LaunchSpecification.Placement.AvailabilityZone"],
[kernel => "LaunchSpecification.KernelId"],
[ramdisk => "LaunchSpecification.RamdiskId"],
[subnet => "LaunchSpecification.SubnetId"],
["block-device-mapping b", undef, undef, \&parse_block_device_mapping_with_launch_specification],
["device-name" => "LaunchSpecification.BlockDeviceMapping.N.DeviceName"],
["no-device" => "LaunchSpecification.BlockDeviceMapping.N.Ebs.NoDevice"],
["virtual-name" => "LaunchSpecification.BlockDeviceMapping.N.VirtualName"],
[snapshot => "LaunchSpecification.BlockDeviceMapping.N.Ebs.SnapshotId"],
["volume-size" => "LaunchSpecification.BlockDeviceMapping.N.Ebs.VolumeSize"],
["volume-type" => "LaunchSpecification.BlockDeviceMapping.N.Ebs.VolumeType"], # standard or io1
["volume-iops" => "LaunchSpecification.BlockDeviceMapping.N.Ebs.Iops"], # number
["profile-arn" => "LaunchSpecification.IamInstanceProfile.Arn"],
["profile-name" => "LaunchSpecification.IamInstanceProfile.Name"],
["delete-on-termination" => "LaunchSpecification.BlockDeviceMapping.N.Ebs.DeleteOnTermination"],
[monitor => "LaunchSpecification.Monitoring.Enabled"],
]],
["ec2", "revoke", RevokeSecurityGroupIngress, [
["" => GroupName],
["egress e" => "__action__", undef, RevokeSecurityGroupEgress],
["groupId i" => "GroupId"],
["protocol P" => "IpPermissions.1.IpProtocol"],
["p" => undef, undef, sub
{
my($min, $max) = split(/-/, "$_[0]-$_[0]");
($min, $max) = (-1, -1) if $_[0] eq "-1";
["IpPermissions.1.FromPort" => $min, "IpPermissions.1.ToPort" => $max];
}],
["q", "IpPermissions.1.ToPort"],
#["t", icmp type code],
["source-group-user u" => "IpPermissions.1.Groups.N.UserId"],
["source-group o" => "IpPermissions.1.Groups.N.GroupName"],
["source-group-id oi" => "IpPermissions.1.Groups.N.GroupId"],
["s" => "IpPermissions.1.IpRanges.N.CidrIp"],
]],
["ec2", "run-instances run-instance run", RunInstances, [
["", ImageId],
["instance-count n", undef, 1, sub
{
my($min, $max) = split(/-/, "$_[0]-$_[0]");
[MinCount => $min, MaxCount => $max];
}],
["group g", SecurityGroupN],
["group-id gi", SecurityGroupIdN],
["key k", KeyName],
["user-data d", UserData, undef, sub {encode_base64($_[0], "")}],
["user-data-file f", UserData, undef, sub {encode_base64(load_file($_[0]))}],
["a", AddressingType],
["instance-type type t i", InstanceType],
["availability-zone z", "Placement.AvailabilityZone"],
["kernel", KernelId],
["ramdisk", RamdiskId],
["block-device-mapping b", undef, undef, \&parse_block_device_mapping],
["device-name" => "BlockDeviceMapping.N.DeviceName"],
["no-device" => "BlockDeviceMapping.N.Ebs.NoDevice"],
["virtual-name" => "BlockDeviceMapping.N.VirtualName"],
["snapshot s" => "BlockDeviceMapping.N.Ebs.SnapshotId"],
["volume-size" => "BlockDeviceMapping.N.Ebs.VolumeSize"],
["profile-arn" => "IamInstanceProfile.Arn"],
["profile-name role" => "IamInstanceProfile.Name"],
["delete-on-termination" => "BlockDeviceMapping.N.Ebs.DeleteOnTermination"],
["monitor m" => "Monitoring.Enabled"],
["disable-api-termination" => DisableApiTermination],
["instance-initiated-shutdown-behavior" => InstanceInitiatedShutdownBehavior],
["placement-group" => "Placement.GroupName"],
["subnet s" => SubnetId],
["net-device-index" => "NetworkInterface.N.DeviceIndex"],
["net-security-group" => "NetworkInterface.N.SecurityGroupId.1"],
["net-subnet" => "NetworkInterface.N.SubnetId"],
["net-private-ip-address" => "NetworkInterface.N.PrivateIpAddress"],
["net-associate-public-ip-address" => "NetworkInterface.N.AssociatePublicIpAddress"],
["private-ip-address" => PrivateIpAddress],
["client-token" => ClientToken],
]],
["ec2", "start-instances start", StartInstances, [["", InstanceIdN]]],
["ec2", "stop-instances stop", StopInstances, [["", InstanceIdN]]],
["ec2", "modify-instance-attribute minatt", ModifyInstanceAttribute, [["", InstanceId],
["block-device-mapping b", undef, undef, \&parse_block_device_mapping],
["disable-api-termination", "DisableApiTermination.Value"],
["ebs-optimized", EbsOptimized],
["group-id g", "GroupIdN"],
["instance-initiated-shutdown-behavior", "InstanceInitiatedShutdownBehavior.Value"],
["instance-type t", "InstanceType.Value"],
["kernel", "Kernel.Value"],
["ramdisk", "Ramdisk.Value"],
["source-dest-check", "SourceDestCheck.Value"],
["user-data d", "UserData.Value", undef, sub {encode_base64($_[0], "")}],
["user-data-file f", "UserData.Value", undef, sub {encode_base64(load_file($_[0]))}],
]],
["ec2", "terminate-instances tin", TerminateInstances, [["", InstanceIdN]]],
["ec2", "create-tags ctags", CreateTags, [
["" => "ResourceIdN"],
["tag", undef, undef, \&parse_tags],
]],
["ec2", "describe-tags dtags", DescribeTags, [
["filter", undef, undef, \&parse_tags_describe],
]],
["ec2", "delete-tags deltags", DeleteTags, [
["" => ResourceIdN],
["tag", undef, undef, \&parse_tags_delete],
]],
####################################
### Relational Database Service ###
####################################
# Short command names are based on ec2 names with 'db' inserted
# Parameter names are the same as RDS CLI documentation, with some additions.
### Database Instances ###
# http://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_ModifyDBInstance.html
# http://docs.aws.amazon.com/AmazonRDS/latest/CommandLineReference/CLIReference-cmd-ModifyDBInstance.html
["rds", "modify-db-instance mdb", ModifyDBInstance, [
["", DBInstanceIdentifier],
["allocated-storage s", AllocatedStorage],
["allow-major-version-upgrade", AllowMajorVersionUpgrade],
["apply-immediately", ApplyImmediately],
["auto-minor-version-upgrade au", AutoMinorVersionUpgrade],
["backup-retention-period r", BackupRetentionPeriod],
["db-instance-class c", DBInstanceClass],
["db-parameter-group-name g", DBParameterGroupName],
["db-security-groups a", "DBSecurityGroups.memberN"],
["engine-version v", EngineVersion],
["iops", Iops],
["master-user-password p", MasterUserPassword],
["multi-az m", MultiAZ],
["new-db-instance-identifier n", NewDBInstanceIdentifier],
["option-group og", OptionGroupName],
["preferred-backup-window b", PreferredBackupWindow],
["preferred-maintenance-window w", PreferredMaintenanceWindow],
["vpc-security-group-ids sg", "VpcSecurityGroupIds.memberN"]
]],
# http://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_DeleteDBInstance.html
# http://docs.aws.amazon.com/AmazonRDS/latest/CommandLineReference/CLIReference-cmd-DeleteDBInstance.html
["rds", "delete-db-instance deldb", DeleteDBInstance, [
["", DBInstanceIdentifier],
["final-db-snapshot-identifier final", FinalDBSnapshotIdentifier],
["skip-final-snapshot s", SkipFinalSnapshot]
]],
# http://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_AddTagsToResource.html
["rds", "add-tags-to-resource cdbtags", AddTagsToResource, [
["", ResourceName],
["tag", undef, undef, \&parse_tags_member]
]],
# http://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_DescribeDBInstances.html
# http://docs.aws.amazon.com/AmazonRDS/latest/CommandLineReference/CLIReference-cmd-DescribeDBInstances.html
["rds", "describe-db-instances ddb", DescribeDBInstances, [
["", DBInstanceIdentifier],
["marker", Marker],
["max-records m", MaxRecords]
]],
### Database Snapshots and Point-In-Time Recovery ###
# http://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_CreateDBSnapshot.html
# http://docs.aws.amazon.com/AmazonRDS/latest/CommandLineReference/CLIReference-cmd-CreateDBSnapshot.html
["rds", "create-db-snapshot cdbsnap", CreateDBSnapshot, [
["", DBInstanceIdentifier],
["db-snapshot-identifier s snapshot", DBSnapshotIdentifier],
["tag", undef, undef, \&parse_tags_member]
]],
# http://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_DescribeDBSnapshots.html
# http://docs.aws.amazon.com/AmazonRDS/latest/CommandLineReference/CLIReference-cmd-DescribeDBSnapshots.html
["rds", "describe-db-snapshots ddbsnap", DescribeDBSnapshots, [
["db-instance-identifier i", DBInstanceIdentifier],
["db-snapshot-identifier s snapshot", DBSnapshotIdentifier],
["marker", Marker],
["max-records m", MaxRecords],
["snapshot-type t", SnapshotType]
]],
# http://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_CopyDBSnapshot.html
# http://docs.aws.amazon.com/AmazonRDS/latest/CommandLineReference/CLIReference-cmd-CopyDBSnapshot.html
["rds", "copy-db-snapshot cpdbsnap", CopyDBSnapshot, [
["", SourceDBSnapshotIdentifier],
["target-db-snapshot-identifier t", TargetDBSnapshotIdentifier],
["tag", undef, undef, \&parse_tags_member]
]],
# http://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_RestoreDBInstanceFromDBSnapshot.html
# http://docs.aws.amazon.com/AmazonRDS/latest/CommandLineReference/CLIReference-cmd-RestoreDBInstanceFromDBSnapshot.html
["rds", "restore-db-instance-from-db-snapshot rdb", RestoreDBInstanceFromDBSnapshot, [
["", DBInstanceIdentifier],
["db-snapshot-identifier s snapshot", DBSnapshotIdentifier],
["auto-minor-version-upgrade au", AutoMinorVersionUpgrade],
["availability-zone z", AvailabilityZone],
["db-instance-class c", DBInstanceClass],
["db-name n", DBName],
["db-subnet-group sn", DBSubnetGroupName],
["engine e", Engine],
["iops", Iops],
["license-model lm", LicenseModel],
["multi-az m", MultiAZ],
["option-group og", OptionGroupName],
["port", Port],
["publicly-accessible pub", PubliclyAccessible],
["tag", undef, undef, \&parse_tags_member]
]],
# http://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_DeleteDBSnapshot.html
# http://docs.aws.amazon.com/AmazonRDS/latest/CommandLineReference/CLIReference-cmd-DeleteDBSnapshot.html
["rds", "delete-db-snapshot deldbsnap", DeleteDBSnapshot, [
["", DBSnapshotIdentifier]
]],
# http://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_DownloadDBLogFilePortion.html
# http://docs.aws.amazon.com/AmazonRDS/latest/CommandLineReference/CLIReference-cmd-DownloadCompleteDBLogFile.html
["rds", "download-db-log-file-portion downdblog", DownloadDBLogFilePortion, [
["", DBInstanceIdentifier],
["log-file-name l", LogFileName],
["marker m", Marker],
["number-of-lines n", NumberOfLines]
]],
# http://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_DescribeDBLogFiles.html
# http://docs.aws.amazon.com/AmazonRDS/latest/CommandLineReference/CLIReference-cmd-DescribeDBLogFiles.html
["rds", "describe-db-log-files ddlf", DescribeDBLogFiles, [
["", DBInstanceIdentifier],
["filename-contains c", FilenameContains],
["file-last-written l", FileLastWritten],
["file-size s", FileSize],
["marker m", Marker],
["max-records x", MaxRecords]
]],
###########################
### Elastic BeaNstalk ###
###########################
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_CheckDNSAvailability.html
["ebn", "check-dns-vailability", CheckDNSAvailability, [
["", CNAMEPrefix],
]],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_CreateApplication.html
["ebn", "create-application", CreateApplication, [
["", ApplicationName],
["description", ApplicationDescription]
]],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_CreateApplicationVersion.html
["ebn", "create-application-version", CreateApplicationVersion, [
["", ApplicationName],
["autocreate", AutoCreateApplication],
["description", Description],
["sourcebucket", 'SourceBundle.S3Bucket'],
["sourcekey", 'SourceBundle.S3Key'],
["versionlabel", VersionLabel]
]],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_CreateConfigurationTemplate.html
["ebn", "create-configuration-template", CreateConfigurationTemplate, [
["", ApplicationName],
["description", Description],
["environmentid", EnvironmentId],
["solutionstackname", SolutionStackName],
["sourceconfiguration", SourceConfiguration],
["templatename", TemplateName]
]],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_CreateEnvironment.html
["ebn", "create-environment", CreateEnvironment, [
["", ApplicationName],
["cnameprefix", CNAMEPrefix],
["description", Description],
["environmentname", EnvironmentName],
["solutionstackname", SolutionStackName],
["versionlabel", VersionLabel],
["templatename", TemplateName]
]],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_CreateStorageLocation.html
["ebn", "create-storage-location", CreateStorageLocation, [
["", S3Bucket]
]],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_DeleteApplication.html
["ebn", "delete-application", DeleteApplication, [
["", ApplicationName],
["forceterminate", TerminateEnvByForce]
]],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_DeleteApplicationVersion.html
["ebn", "delete-application-version", DeleteApplicationVersion, [
["", ApplicationName],
["deletesourcebundle", DeleteSourceBundle],
["versionlabel", VersionLabel]
]],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_DeleteConfigurationTemplate.html
["ebn", "delete-configuration-template", DeleteConfigurationTemplate, [
["", ApplicationName],
["templatename", TemplateName]
]],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_DeleteEnvironmentConfiguration.html
["ebn", "delete-environment-configuration", DeleteEnvironmentConfiguration, [
["", ApplicationName],
["environmentname", EnvironmentName]
]],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_DescribeApplicationVersions.html
["ebn", "describe-application-versions", DescribeApplicationVersions, [
["", ApplicationName],
]],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_DescribeApplications.html
["ebn", "describe-applications", DescribeApplications, []],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_DescribeConfigurationOptions.html
["ebn", "describe-configuration-options", DescribeConfigurationOptions, [
["", ApplicationName],
["environmentname", EnvironmentName],
["solutionstackname", SolutionStackName],
["templatename", TemplateName]
]],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_DescribeConfigurationSettings.html
["ebn", "describe-configuration-settings", DescribeConfigurationSettings, [
["", ApplicationName],
["environmentname", EnvironmentName],
["templatename", TemplateName]
]],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_DescribeEnvironmentResources.html
["ebn", "describe-configuration-options", DescribeEnvironmentResources, [
["environmentid", EnvironmentId],
["environmentname", EnvironmentName]
]],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_DescribeEnvironmentResources.html
["ebn", "describe-environment-resources", DescribeEnvironmentResources, [
["environmentid", EnvironmentId],
["environmentname", EnvironmentName]
]],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_DescribeEnvironments.html
["ebn", "describe-environments", DescribeEnvironments, [
["", ApplicationName],
["environmentid", EnvironmentId],
["environmentname", EnvironmentName],
["includedeleted", IncludeDeleted],
["includedeletedbackto", IncludeDeletedBackTo],
["versionlabel", VersionLabel]
]],
###
### NOT Implemented: http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_DescribeEvents.html
###
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_ListAvailableSolutionStacks.html
["ebn", "list-solution-stacks", ListAvailableSolutionStacks, []],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_RebuildEnvironment.html
["ebn", "rebuild-environment", RebuildEnvironment, [
["environmentid", EnvironmentId],
["environmentname", EnvironmentName]
]],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_RequestEnvironmentInfo.html
["ebn", "request-environment-info", RequestEnvironmentInfo, [
["environmentid", EnvironmentId],
["environmentname", EnvironmentName],
["infotype", InfoType]
]],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_RestartAppServer.html
["ebn", "restart-app-server", RestartAppServer, [
["environmentid", EnvironmentId],
["environmentname", EnvironmentName]
]],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_RetrieveEnvironmentInfo.html
["ebn", "retrieve-environment-info", RetrieveEnvironmentInfo, [
["environmentid", EnvironmentId],
["environmentname", EnvironmentName],
["infotype", InfoType],
]],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_SwapEnvironmentCNAMEs.html
["ebn", "swap-environment-cnames", SwapEnvironmentCNAMEs, [
["destinationenvironmentid", DestinationEnvironmentId],
["destinationenvironmentname", DestinationEnvironmentName],
["sourceenvironmentid", SourceEnvironmentId],
["sourceenvironmentname", SourceEnvironmentName],
]],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_TerminateEnvironment.html
["ebn", "terminate-environment", TerminateEnvironment, [
["environmentid", EnvironmentId],
["environmentname", EnvironmentName]
]],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_UpdateApplication.html
["ebn", "update-application", UpdateApplication, [
["", ApplicationName],
["description", ApplicationDescription]
]],
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_UpdateApplicationVersion.html
["ebn", "update-application-version", UpdateApplicationVersion, [
["", ApplicationName],
["description", Description],
["versionlabel", VersionLabel]
]],
###
### NOT Implemented: http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_UpdateConfigurationTemplate.html
###
# http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_UpdateEnvironment.html
["ebn", "update-environment", UpdateEnvironment, [
["description", Description],
["environmentid", EnvironmentId],
["environmentname", EnvironmentName],
["templatename", TemplateName],
["versionlabel", VersionLabel]
]],
###
### NOT Implemented: http://docs.aws.amazon.com/elasticbeanstalk/latest/APIReference/API_ValidateConfigurationSettings.html
###
#############
### ELB ###
#############
["elb", "configure-healthcheck ch", ConfigureHealthCheck, [
["", LoadBalancerName],
["target", "HealthCheck.Target"],
["healthy-threshold", "HealthCheck.HealthyThreshold"],
["unhealthy-threshold", "HealthCheck.UnhealthyThreshold"],
["interval", "HealthCheck.Interval"],
["timeout", "HealthCheck.Timeout"],
]],
["elb", "create-app-cookie-stickiness-policy cacsp", CreateAppCookieStickinessPolicy, [
["", LoadBalancerName],
["policy-name", PolicyName],
["cookie-name", CookieName],
]],
["elb", "create-lb-cookie-stickiness-policy clbcsp", CreateLBCookieStickinessPolicy, [
["", LoadBalancerName],
["policy-name", PolicyName],
["expiration-period", "policy-name", PolicyName],
]],
["elb", "create-lb clb", CreateLoadBalancer, [
["", LoadBalancerName],
["availability-zone", "AvailabilityZones.memberN"],
["protocol", "Listeners.member.1.Protocol"],
["loadbalancerport", "Listeners.member.1.LoadBalancerPort"],
["instanceport", "Listeners.member.1.InstancePort"],
["sslcertificateid", "Listeners.member.1.SSLCertificateId"],
]],
["elb", "create-lb-listeners clbl", CreateLoadBalancerListeners, [
["", LoadBalancerName],
["listener", "Listeners.memberN"],
]],
["elb", "delete-lb dellb", DeleteLoadBalancer, [
["", LoadBalancerName],
]],
["elb", "delete-lb-listeners dlbl", DeleteLoadBalancerListeners, [
["", LoadBalancerName],
["loadbalancerport", "LoadBalancerPorts.member.1"],
]],
["elb", "delete-lb-policy dlbp", DeleteLoadBalancerPolicy, [
["", LoadBalancerName],
["policy-name", PolicyName],
]],
["elb", "describe-instance-health dih", DescribeInstanceHealth, [
["", LoadBalancerName],
["listener", "Listeners.memberN"],
]],
["elb", "describe-lbs dlb", DescribeLoadBalancers, [
["", LoadBalancerNameN],
]],
["elb", "disable-zones-for-lb dlbz", DisableAvailabilityZonesForLoadBalancer, [
["", LoadBalancerName],
["availability-zone", "AvailabilityZones.memberN"],
]],
["elb", "enable-zones-for-lb elbz", EnableAvailabilityZonesForLoadBalancer, [
["", LoadBalancerName],
["availability-zone", "AvailabilityZones.memberN"],
]],
["elb", "register-instances-with-lb rlbi", RegisterInstancesWithLoadBalancer, [
["", LoadBalancerName],
["instance", "Instances.member.N.InstanceId"],
]],
["elb", "deregister-instances-from-lb dlbi", DeregisterInstancesFromLoadBalancer, [
["", LoadBalancerName],
["instance", "Instances.member.N.InstanceId"],
]],
["elb", "set-lb-listener-ssl-cert slblsc", SetLoadBalancerListenerSSLCertificate, [
["", LoadBalancerName],
["lb-port", LoadBalancerPort],
["cert-id", SSLCertificateId],
]],
["elb", "set-lb-policies-of-listener slbpol", SetLoadBalancerPoliciesOfListener, [
["", LoadBalancerName],
["policy-name", "PolicyNames.memberN"],
]],
#############
### IAM ###
#############
["iam", "groupaddpolicy pgp", PutGroupPolicy, [
[" g" => GroupName],
[p => PolicyName],
[e, undef, undef, \&parse_addpolicy_effect],
[a, undef, undef, \&parse_addpolicy_action],
[r, undef, undef, \&parse_addpolicy_resource],
[o, undef, undef, \&parse_addpolicy_output],
]],
["iam", "groupadduser", AddUserToGroup, [
[" g" => GroupName],
[u => UserName],
]],
["iam", "groupcreate cg", CreateGroup, [
[" g" => GroupName],
[p => Path],
]],
["iam", "groupdel", DeleteGroup, [
[" g" => GroupName],
]],
["iam", "groupdelpolicy", DeleteGroupPolicy, [
[" g" => GroupName],
[p => PolicyName],
]],
["iam", "grouplistbypath lg", ListGroups],
["iam", "grouplistpolicies lgp", ListGroupPolicies, [
[" g" => GroupName],
[p => PolicyName],
]],
# GetGroupPolicy is automatically invoked when grouplistpolicies has a -p PolicyName
["iam", "groupgetpolicy", GetGroupPolicy, [
[" g" => GroupName],
[p => PolicyName],
]],
["iam", "grouplistusers gg", GetGroup, [
[" g" => GroupName],
]],
["iam", "groupmod", UpdateGroup, [
[" g" => GroupName],
[n => NewGroupName],
[p => NewPath],
]],
["iam", "groupremoveuser", RemoveUserFromGroup, [
[" g" => GroupName],
[u => UserName],
]],
["iam", "groupuploadpolicy", PutGroupPolicy, [
[" g" => GroupName],
[p => PolicyName],
[o => PolicyDocument],
[f => PolicyDocument, undef, sub {load_file($_[0])}],
]],
["iam", "useraddcert", UploadSigningCertificate, [
[" u" => UserName],
[c => CertificateBody],
[f => CertificateBody, undef, sub {load_file($_[0])}],
]],
["iam", "useraddkey cak", CreateAccessKey, [
[" u" => UserName],
]],
["iam", "useraddloginprofile clp", CreateLoginProfile, [
[" u" => UserName],
[p => Password],
]],
["iam", "useraddpolicy pup", PutUserPolicy, [
[" u" => UserName],
[p => PolicyName],
[e, undef, undef, \&parse_addpolicy_effect],
[a, undef, undef, \&parse_addpolicy_action],
[r, undef, undef, \&parse_addpolicy_resource],
[o, undef, undef, \&parse_addpolicy_output],
]],
["iam", "usercreate cu", CreateUser, [
[" u" => UserName],
[p => Path],
]],
["iam", "userdeactivatemfadevice", DeactivateMFADevice, [
[" u" => UserName],
[s => SerialNumber],
]],
["iam", "userdel", DeleteUser, [
[" u" => UserName],
[s => XXX],
]],
["iam", "userdelcert", DeleteSigningCertificate, [
[" u" => UserName],
[c => CertificateId],
]],
["iam", "userdelkey", DeleteAccessKey, [
[" u" => UserName],
[k => AccessKeyId],
]],
["iam", "userdelloginprofile dlp", DeleteLoginProfile, [
[" u" => UserName],
]],
["iam", "userdelpolicy", DeleteUserPolicy, [
[" u" => UserName],
[p => PolicyName],
]],
["iam", "userenablemfadevice", EnableMFADevice, [
[" u" => UserName],
[s => SerialNumber],
[c1 => AuthenticationCode1],
[c2 => AuthenticationCode2],
]],
["iam", "usergetattributes gu", GetUser, [
[" u" => UserName],
]],
["iam", "usergetloginprofile glp", GetLoginProfile, [
[" u" => UserName],
]],
["iam", "userlistbypath lu", ListUsers, [
[" p" => PathPrefix],
]],
["iam", "userlistcerts", ListSigningCertificates, [
[" u" => UserName],
]],
["iam", "userlistgroups", ListGroupsForUser, [
[" u" => Username],
]],
["iam", "userlistkeys", ListAccessKeys, [
[" u" => UserName],
]],
["iam", "userlistmfadevices", ListMFADevices, [
[" u" => UserName],
]],
["iam", "userlistpolicies lup", ListUserPolicies, [
[" u" => UserName],
[p => PolicyName],
]],
# GetUserPolicy is automatically invoked when userlistpolicies has a -p PolicyName
["iam", "usergetpolicy", GetUserPolicy, [
[" u" => UserName],
[p => PolicyName],
]],
["iam", "usermod", UpdateUser, [
[" u" => UserName],
[n => NewUserName],
[p => NewPath],
]],
["iam", "usermodcert", UpdateSigningCertificate, [
[" u" => UserName],
[c => CertificateId],
[s => Status],
]],
["iam", "usermodkey", UpdateAccessKey, [
[" u" => UserName],
[a => AccessKeyId],
[s => Status],
]],
["iam", "usermodloginprofile ulp", UpdateLoginProfile, [
[" u" => UserName],
[p => Password],
]],
["iam", "userresyncmfadevice", ResyncMFADevice, [
[" u" => UserName],
[s => SerialNumber],
[c1 => AuthenticationCode1],
[c2 => AuthenticationCode2],
]],
["iam", "useruploadpolicy", PutUserPolicy, [
[" u" => UserName],
[p => PolicyName],
[o => PolicyDocument],
[f => PolicyDocument, undef, sub {load_file($_[0])}],
]],
["iam", "servercertdel", DeleteServerCertificate, [
[" s" => ServerCertificateName],
]],
["iam", "servercertgetattributes", GetServerCertificate, [
[" s" => ServerCertificate],
]],
["iam", "servercertlistbypath", ListServerCertificates, [
[" p" => PathPrefix],
]],
["iam", "servercertmod", UpdateServerCertificate, [
[" p" => NewPath],
[s => ServerCertificateName],
[n => NewServerCertificateName],
]],
["iam", "servercertupload", UploadServerCertificate, [
[" s" => ServerCertificateName],
[p => Path],
[b => CertificateBody, undef, sub {load_file($_[0])}],
[k => PrivateKey, undef, sub {load_file($_[0])}],
[c => CertificateChain, undef, sub {load_file($_[0])}],
]],
["iam", "accountaliascreate caa", CreateAccountAlias,[
["" => AccountAlias],
]],
["iam", "accountaliasdelete daa", DeleteAccountAlias,[
["" => AccountAlias],
]],
["iam", "accountaliaslist laa", ListAccountAliases],
["iam", "listroles lr", ListRoles, [
[" p" => "PathPrefix", "/"],
["marker m" => "Marker"],
["maxitems i" => "MaxItems"],
]],
["cloudformation", "describestacks", DescribeStacks,[
["" => StackName],
]],
["cloudformation", "describestackresources", DescribeStackResources,[
["" => StackName],
["l" => LogicalResourceId],
["p" => PhysicalResourceId],
]],
["cloudformation", "describestackresource", DescribeStackResource,[
["" => StackName],
["l" => LogicalResourceId],
]],
["s3", "ls", LS],
["s3", "get cat", GET],
["s3", "head", HEAD],
["s3", "mkdir", MKDIR],
["s3", "put", PUT],
["s3", "delete rmdir rm", DELETE],
["s3", "copy cp", COPY],
["s3", "dmo", DMO],
["s3", "post", POST],
["sqs", "add-permission addperm", AddPermission, [
["" => QueueUri],
[label => Label],
[account => AWSAccountIdN],
[action => ActionNameN],
]],
["sqs", "change-message-visibility cmv", ChangeMessageVisibility, [
["" => QueueUri],
[handle => ReceiptHandle],
[timeout => VisibilityTimeout],
]],
["sqs", "create-queue cq", CreateQueue, [
["" => QueueName],
[timeout => DefaultVisibilityTimeout],
]],
["sqs", "delete-message dm", DeleteMessage, [
["" => QueueUri],
[handle => ReceiptHandle],
]],
["sqs", "delete-queue dq", DeleteQueue, [
["" => QueueUri],
]],
["sqs", "get-queue-attributes gqa", GetQueueAttributes, [
["" => QueueUri],
[attribute => AttributeNameN],
]],
["sqs", "list-queues lq", ListQueues, [
["" => QueueNamePrefix],
]],
["sqs", "receive-message recv", ReceiveMessage, [
["" => QueueUri],
[attribute => AttributeNameN],
[n => MaxNumberOfMessages],
[timeout => VisibilityTimeout],
[wait => WaitTimeSeconds],
]],
["sqs", "remove-permission remperm", RemovePermission, [
["" => QueueUri],
[label => Label],
]],
["sqs", "send-message send", SendMessage, [
["" => QueueUri],
[message => MessageBody, "", sub {encode_message($_[0])}],
]],
["sqs", "set-queue-attributes sqa", SetQueueAttributes, [
["" => QueueUri],
[attribute => "Attribute.Name"],
[value => "Attribute.Value"],
]],
["sdb", "create-domain cdom", CreateDomain, [
["" => DomainName],
]],
["sdb", "delete-attributes datt", DeleteAttributes, [
["" => DomainName],
[i => ItemName],
[n => "Attribute.N.Name"],
[v => "Attribute.N.Value"],
[xn => "Expected.N.Name"],
[xv => "Expected.N.Value"],
[exists => "Expected.N.Exists"],
]],
["sdb", "delete-domain ddom", DeleteDomain, [
["" => DomainName],
]],
["sdb", "get-attributes gatt", GetAttributes, [
["" => DomainName],
[i => ItemName],
[n => AttributeName],
[c => ConsistentRead],
]],
["sdb", "list-domains ldom", ListDomains, [
[max => MaxNumberOfDomains],
[next => NextToken],
]],
["sdb", "put-attributes patt", PutAttributes, [
["" => DomainName],
[i => ItemName],
[n => "Attribute.N.Name"],
[v => "Attribute.N.Value"],
[replace => "Attribute.N.Replace"],
[xn => "Expected.N.Name"],
[xv => "Expected.N.Value"],
[exists => "Expected.N.Exists"],
]],
["sdb", "select", Select, [
["" => SelectExpression],
[c => ConsistentRead],
[next => NextToken],
]],
["sdb", "domain-metadata", DomainMetadata, [
["" => DomainName],
]],
##### R53 (Route 53, DNS)
["r53", "list-resource-record-sets lrrs", 'GET|rrset', [
['' => zone_id],
[xml => __xml],
[simple => __simple],
[name => name],
[type => type],
[identifier => identifier],
[maxitems => maxitems],
]],
["r53", "get-change gch", 'GET|', [
['' => change_id],
]],
["r53", "get-hosted-zone ghz", 'GET|', [
['', zone_id],
]],
["r53", "create-hosted-zone chz", 'POST|', [
['' => Name],
['ref' => CallerReference],
['comment' => Comment],
]],
["r53", "delete-hosted-zone dhz", 'DELETE|', [
['' => zone_id],
]],
["r53", "change-resource-record-set crrs", 'POST|rrset', [
['' => zone_id],
['name n', Name],
['action a', Action],
['type t', Type],
['ttl l', TTL],
['value v', Value],
['comment c', Comment],
]],
["pa", "lookup", ItemLookup, [
["" => ItemId],
["type t" => IdType],
["r" => ResponseGroup],
["c" => Condition],
["a" => AssociateTag],
]],
);
$isUnix = guess_is_unix();
$home = get_home_directory();
# Figure out $cmd. If the program is run as other than "aws", then $0 contains
# the command. This way, you can link aws to the command names (with or without
# ec2 or s3 prefix) and not have to type "aws".
unshift @ARGV, $1 if $0 =~ /^(?:.*[\\\/])?(?:(?:ec2|pa|s3|sqs|sdb|rds)-?)?(.+?)(?:\..+)?$/ && $1 !~ /^\w*aws/;
# parse meta-parameters, leaving parameters in @argv
{
my(%keyword);
# The %need_arg items must have a value. If they aren't of the form
# --foo=bar, then slurp up the next item as the value. Thus, for example,
# --region=eu and --region eu both work, with or without the =.
my(%need_arg, $key_for_arg);
@needs_arg{qw(metadata region)} = undef;
my(%meta);
@meta{qw(1 assume AWS4 batch cmd0 content_length content_type credential_helper curl curl_options cut d default_curl_cipher delimiter dns_alias dump_xml
exec expire_time fail h help http insecure insecure_signing insecure_aws insecureaws install json l limit_rate link max_keys
max_time marker md5 metadata no_vhost netrc_machine parts prefix private progress public queue r region request requester retry role ruby quiet
s3host sanity_check no_sanity_check secrets_file set_acl sha1 silent simple sts_host t v verbose vv vvv wait xml yaml)} = undef;
my @awsrc = "";
for (split(/(?:\#.*?(?=\n)|'(.*?)'|"((?:\\[\\\"]|.)*?)"|((?:\\.|\$.|[^\s\'\"\#])+))/s, load_file_silent("$home/.awsrc")))
{
if (/^\s+$/)
{
push @awsrc, "" if length($awsrc[$#awsrc]);
}
else
{
$awsrc[$#awsrc] .= $_;
}
}
pop @awsrc unless length($awsrc[$#awsrc]);
for (@awsrc, @ARGV)
{
if ($key_for_arg)
{
$ {$key_for_arg} = $_;
undef $key_for_arg;
}
elsif (/^--([\w\-]+?)(?:=(.*))?$/s)
{
my($key0, $val) = ($1, $2);
(my $key = $key0) =~ s/-/_/g;
if (exists $needs_arg{$key} && !defined $val)
{
$key_for_arg = $key;
}
elsif (exists $keyword{$key})
{
push @argv, $_;
}
else
{
die "--$key0: mispelled meta parameter?\n" unless exists $meta{$key};
$ {$key} = defined $val? $val: 1;
# --cmd0 is used to call self but without getting the command from $0
undef $cmd if $key eq "cmd0" && $val;
}
}
elsif (/^-(\w+)$/)
{
if (exists $keyword{$1}
|| $_ eq "-1" && $cmd =~ /^(auth(?:orize)?)|(revoke)$/ && $argv[-1] =~ /^-(?:protocol|P|p)$/ # force -1 to be a protocol (or P) or port (p) parameter rather than set $d1
)
{
push @argv, $_;
}
else
{
for (split(//, $1))
{
die "-$_: mispelled meta parameter?\n" unless exists $meta{$_};
s/^(\d)$/d$1/;
$ {$_}++;
}
}
}
else
{
if ($cmd)
{
push @argv, $_;
}
else
{
$cmd = $_;
# moved this code here, so that arguments to specific ec2, s3, and sqs commands
# are active only if the particular command is indicated
# make a hash of aws keywords (%keyword), which are not treated as meta-parameters
for (@cmd)
{
next unless grep /^\Q$cmd\E$/, split(" ", $_->[1]);
$cmd_data = $_;
push @{$cmd_data->[3]}, ["filter", undef, undef, \&parse_filter] if $_->[1] =~ /\bdescribe/;
for (@{$cmd_data->[3]})
{
for (split(" ", $_->[0]))
{
(my $key = $_) =~ s/-/_/g;
$keyword{$key} = undef;
}
}
last;
}
}
}
}
}
$h ||= $help;
$v ||= $verbose;
$v = 2 if $vv;
$v = 3 if $vvv;
$curl ||= "curl";
$curl_q = "-q";
#$curl_q .= " --cipher RC4-SHA" unless $default_curl_cipher;
$curl_q .= " -K \"$home/.awscurlrc\"" if -e "$home/.awscurlrc";
$ENV{COLUMNS}-- if $ENV{COLUMNS} && $ENV{EMACS};
if ($cut)
{
my $columns = $ENV{COLUMNS};
($columns) = qx[stty -a <&2] =~ /;\s*columns\s*(\d+);/s unless $columns;
open STDOUT, "|cut -c -$columns" if $columns;
}
# Exercise for the reader: why is this END block here? (Hint: bug in Perl?)
END {close STDOUT}
# Don't know if the -gov- default for $s3host is correct... are there other GovCloud regions, and what endpoint do they use?
$s3host ||= $ENV{S3_URL} || ($region =~ /-gov-/? "s3-$region.amazonaws.com": "s3.amazonaws.com");
$sts_host ||= $ENV{STS_URL} || ($s3host =~ /^s3-(.*?)\.amazonaws\.com$/? "sts.$1.amazonaws.com": "sts.amazonaws.com");
print STDERR "aws versions: (ec2: $ec2_version, sqs: $sqs_version, elb: $elb_version, sdb: $sdb_version, iam: $iam_version, ebn: $ebn_version, cloudformation: $cloudformation_version, rds: $rds_version)\n" if $v;
$insecsign = "--insecure" if $insecure || $insecure_signing;
$insecureaws = "--insecure" if $insecureaws || $insecure_aws;
$scheme = $http? "http": "https";
$silent ||= !-t;
$retry = 3 unless length($retry);
$secrets_file ||= "$home/.awssecret" if -e "$home/.awssecret";
$secrets_file ||= "$home/.s3cfg" if -e "$home/.s3cfg";
$secrets_file ||= "$home/.passwd-s3fs" if -e "$home/.passwd-s3fs";
foreach (qw/authinfo netrc/)
{
$secrets_file ||= "$home/.$_" if -e "$home/.$_";
$secrets_file ||= "$home/.$_.gpg" if -e "$home/.$_.gpg";
}
$secrets_file ||= "$home/.awssecret";
# by default look for "machine AWS" in netrc files
$netrc_machine ||= 'AWS';
if ($role)
{
if ($role == 1)
{
my $cmd = qq[$curl -s --max-time 2 --fail http://169.254.169.254/latest/meta-data/iam/security-credentials/];
print "$cmd\n" if $v;
($role) = qx[$cmd];
}
if ($role)
{
my $cmd = qq[$curl -s --max-time 2 --fail http://169.254.169.254/latest/meta-data/iam/security-credentials/$role];
print "$cmd\n" if $v;
my $json = qx[$cmd];
($awskey) = $json =~ /"AccessKeyId" : "(.*?)"/;
($secret) = $json =~ /"SecretAccessKey" : "(.*?)"/;
($session) = $json =~ /"Token" : "(.*?)"/;
}
}
unless ($awskey && $secret)
{
($awskey, $secret, $session) = @ENV{qw(AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN)};
}
unless ($awskey && $secret)
{
($awskey, $secret, $session) = @ENV{qw(EC2_ACCESS_KEY EC2_SECRET_KEY AWS_SESSION_TOKEN)};
}
if ($credential_helper)
{
unless ($awskey && $secret)
{
require IPC::Open2;
my($out, $in);
my $pid = IPC::Open2::open2($out, $in, $credential_helper);
print $in "host=$netrc_machine\n";
close $in;
print STDERR "Waiting for [$credential_helper] to respond...\n" if $v;
while (<$out>)
{
print STDERR "Credential helper gave us $_" if $v;
chomp;
next unless m/(\w+)=(.+)/;
$awskey = $2 if $1 eq 'username';
$secret = $2 if $1 eq 'password';
}
waitpid($pid, 0);
}
}
unless ($awskey && $secret)
{
# try to resolve any globs
unless (-s $secrets_file)
{
$secrets_file = glob($secrets_file);
}
if (-s $secrets_file)
{
if ($secrets_file =~ /\.gpg$/)
{
# if the secrets_file ends with .gpg, try to decrypt it
$secrets_file = "gpg --decrypt $secrets_file|";
print STDERR "Overriding --secrets-file to [$secrets_file]\n" if $v;
}
if ($secrets_file =~ /s3cfg$/)
{
# if the secrets_file ends with s3cfg, treat it as a s3cmd init file
# (can be tested with --secrets-file=s3cfg)
# Poor man's ini parser
my $s3cfg = load_file($secrets_file);
($awskey, $secret, $session) = map {$s3cfg =~ /^$_\s*=\s*(\S+)/im} qw/access_key secret_key/;
}
else
{
my $secret_data = load_file($secrets_file);
# handle authinfo/netrc format
if ($secret_data =~ /^\s*
machine\s+(\S+)\s+.*?
login\s+(\S+)\s+.*?
password\s+(\S+)\s+.*?
(\s+session\s+(\S+?))?/xm)
{
# these are the netrc tokens we recognize
my %recognized = ( machine => undef,
login => undef,
session => undef,
password => undef);
LINE:
foreach my $line (split "\n", $secret_data)
{
my @tok;
# modeled after Net::Netrc (Perl Artistic License: This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.)
while (length $line &&
$line =~ s/^("((?:[^"]+|\\.)*)"|((?:[^\\\s]+|\\.)*))\s*//)
{
(my $tok = $+) =~ s/\\(.)/$1/g;
push(@tok, $tok);
}
# skip blank lines, comments, etc.
next LINE unless scalar @tok;
my %tokens;
while (@tok)
{
my ($k, $v) = (shift @tok, shift @tok);
next unless defined $v;
next unless exists $recognized{$k};
$recognized{$k} = $v;
}
# skip this line unless it had "machine AWS"
next LINE unless (defined $recognized{machine} &&
$recognized{machine} eq $netrc_machine);
# grab the AWS key from "login KEY",
# secret key from "password SECRET",
# session from "session SESS" (all optional)
($awskey, $secret, $session) = @recognized{login,password,session};
}
}
else # normal space-separated tokens
{
($awskey, $secret, $session) = split(/[\r\s:]+/s, $secret_data);
}
}
}
}
unless ($awskey && $secret)
{
($role) = qx[$curl -s --fail --max-time 1 http://169.254.169.254/latest/meta-data/iam/security-credentials/];
if ($role)
{
my $json = qx[$curl -s --fail http://169.254.169.254/latest/meta-data/iam/security-credentials/$role];
($awskey) = $json =~ /"AccessKeyId" : "(.*?)"/;
($secret) = $json =~ /"SecretAccessKey" : "(.*?)"/;
($session) = $json =~ /"Token" : "(.*?)"/;
}
}
if ($assume)
{
$assume =~ s/\'//g;
my($sec, $min, $hour, $mday, $mon, $year, undef, undef, undef) = gmtime(time + $time_offset);
my $zulu = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ", 1900 + $year, $mon + 1, $mday, $hour, $min, $sec;
my %data = (
AWSAccessKeyId => $awskey, SignatureMethod => ($sha1? HmacSHA1: HmacSHA256), SignatureVersion => 2, Version => $sts_version, Timestamp => $zulu,
Action => AssumeRole,
RollSessionName => TimKayAWS,
RoleArn => $assume,
);
$data{SecurityToken} = $session if $session;
my($url);
for (sort keys %data)
{
$url .= "&" if $url;
$url .= "$_=@{[encode_url($data{$_})]}";
}
my $sig = sign("GET\n$sts_host\n/\n$url", $data{SignatureMethod});
$url = "https://$sts_host/?Signature=@{[encode_url($sig)]}&$url";
my $xml = qx[$curl -s '$url'];
if ($xml !~ //)
{
print $xml unless $fail;
exit 22;
}
for ($xml)
{
($awskey) = /\s*(.*?)\s*<\/AccessKeyId>/s;
($secret) = /\s*(.*?)\s*<\/SecretAccessKey>/s;
($session) = /\s*(.*?)\s*<\/SessionToken>/s;
}
print "awskey = $awskey\n";
print "secret = $secret\n";
print "session = $session\n";
}
# unfortunately, you can't have a delimiter of "1" this way
if ($d || $delimiter == 1)
{
$delimiter = "/";
}
for ([m => 60], [h => 60 * 60], [d => 24 * 60 * 60], [w => 7 * 24 * 60 * 60], [mo => 30 * 24 * 60 * 60], [y => 365.25 * 24 * 60 * 60])
{
$expire_time = $1 * $_->[1] if $expire_time =~ /^(-?\d+)$_->[0]$/;
}
# run a sanity check if $home/.awsrc doesn't exists, or if it was requested
if ( !$no_sanity_check && (!-e "$home/.awsrc" || $sanity_check))
{
if (!$silent)
{
if ($role)
{
if ($role == 1)
{
if (qx[$curl -s --fail http://169.254.169.254/latest/meta-data/iam/security-credentials/] !~ /\w/)
{
warn "sanity-check: no role found\n";
}
}
}
elsif (($ENV{AWS_SECRET_ACCESS_KEY} && $ENV{AWS_ACCESS_KEY_ID}) || ($ENV{EC2_SECRET_KEY} && $ENV{EC2_ACCESS_KEY}))
{
}
elsif (!-e $secrets_file && $secrets_file !~ m/\|$/)
{
warn "sanity-check: \"$secrets_file\": file is missing. (Format: AccessKeyID\\nSecretAccessKey\\n)\n";
}
elsif (!-r $secrets_file)
{
warn "sanity-check: \"$secrets_file\": file is not readable\n";
}
elsif ($isUnix)
{
my $stat = (stat $secrets_file)[2] & 0777;
if (($stat & 0477) != 0400)
{
my @perm = (qw(x r w)) x 4;
my $perm = join("", map {my $s = shift @perm; $_? $s: "-"} (split//, (unpack("B*", pack("n", $stat))))[6 .. 15]);
warn "sanity-check: \"$secrets_file\": file permissions are $perm. Should be -rw-------\n";
}
}
}
my($curl_version) = qx[$curl -V] =~ /^curl\s+([\d\.]+)/s;
print "curl version: $curl_version\n" if $v >= 2;
if (xcmp($curl_version, "7.12.3") < 0)
{
$retry = undef;
warn "sanity-check: This curl (v$curl_version) does not support --retry (>= v7.12.3), so --retry is disabled\n" unless $silent;
}
my $aws = qx[$curl $curl_q -s $insecureaws --include $scheme://connection.$s3host/test];
print $aws if $v >= 2;
my($d, $mon, $y, $h, $m, $s) = $aws =~ /^Date: ..., (..) (...) (....) (..):(..):(..) GMT\r?$/m;
if (!$d)
{
$aws = qx[$curl $curl_q -s --insecure --include $scheme://connection.$s3host/test];
($d, $mon, $y, $h, $m, $s) = $aws =~ /^Date: ..., (..) (...) (....) (..):(..):(..) GMT\r?$/m;
if ($d)
{
warn "sanity-check: Your host SSL certificates are not working for curl.exe. Try using --insecure-aws (e.g., aws --insecure-aws ls)\n";
}
else
{
$aws = qx[$curl $curl_q -s --insecure --include http://connection.$s3host/test];
($d, $mon, $y, $h, $m, $s) = $aws =~ /^Date: ..., (..) (...) (....) (..):(..):(..) GMT\r?$/m;
if ($d)
{
die "sanity-check: Your curl doesn't seem to support SSL. Try using --http (e.g., aws --http ls)\n";
}
else
{
die "sanity-check: Problems accessing AWS. Is curl installed?\n";
}
}
}
if (eval {require Time::Local})
{
$mon = {Jan => 0, Feb => 1, Mar => 2, Apr => 3, May => 4, Jun => 5, Jul => 6, Aug => 7, Sep => 8, Oct => 9, Nov => 10, Dec => 11}->{$mon};
my $t = Time::Local::timegm($s, $m, $h, $d, $mon, $y);
$time_offset = $t - time;
warn "sanity-check: Your system clock is @{[abs($time_offset)]} seconds @{[$time_offset > 0? 'behind': 'ahead']}.\n" if !$silent && abs($time_offset) > 5;
}
}
$curl_options .= " $curl_q -g -S";
$curl_options .= " --remote-time";
$curl_options .= " --retry $retry" if length($retry);
$curl_options .= " --fail" if $fail;
$curl_options .= " --verbose" if $v >= 2;
$curl_options .= $progress? " --progress": " -s";
$curl_options .= " --max-time $max_time" if $max_time;
$curl_options .= " --limit-rate $limit_rate" if $limit_rate;
use IO::File;
use File::Temp qw(tempfile);
use Digest::MD5 qw(md5 md5_hex);
if ($install)
{
die "Usage: .../aws --install\n" if $install && @argv;
if (-w "/usr/bin")
{
chomp(my $dir = qx[pwd]);
my $path = $0;
$path = "$dir/$0" if $0 !~ /^\//;
if ($dir !~ /^\/usr\/bin$/)
{
print STDERR "copying aws to /usr/bin/\n";
my $aws = load_file($0) or die "installation failed (can't load script)\n";
if (-e "/usr/bin/aws")
{
unlink "/usr/bin/aws" or die "can't unlink old /usr/bin/aws\n";
}
save_file("/usr/bin/aws", $aws);
die "installation failed (can't copy script)\n" unless load_file("/usr/bin/aws") eq $aws;
chmod 0555, "/usr/bin/aws";
chdir "/usr/bin";
}
}
chmod 0555, $0;
make_links($0);
die "installation failed\n";
}
if ($link)
{
die "Usage: .../aws --link[=short|long] [-bare]\n" if $link && @argv;
make_links($0);
}
sub make_links
{
my($target) = @_;
#
# Create symlinks to this program named for all available
# commands. Then the script can be invoked as "s3mkdir foo"
# rather than "aws mkdir foo". (Run this command in /usr/bin
# or /usr/local/bin.)
#
# aws -link
# symlinks all command names (ec2-delete-group, ec2delgrp, ec2-describe-groups, ec2dgrp, etc.)
# aws -link=short
# symlinks only the short versions of command names (ec2delgrp, ec2dgrp, etc.)
# aws -link=long
# symlinks only the long versions of command names (ec2-delete-group, ec2-describe-groups, etc.)
#
# The -bare option creates symlinks without the ec2 and s3 prefixes
# (delete-group, delgrp, etc.). Be careful using this option, as
# commands named "ls", "mkdir", "rmdir", etc. are created.
for (@cmd)
{
my($service, $cmd, $action) = @$_;
for my $fn (split(' ', $cmd))
{
my($dash) = $fn =~ /(-)/;
next if $dash && $link eq "short";
next if !$dash && $link eq "long";
$fn = "$service$dash$fn" unless $bare;
unlink $fn;
symlink($target, $fn) or die "$fn: $!\n";
#print "symlink $fn --> $target\n";
}
}
exit;
}
if (!$cmd_data)
{
my $output = "$cmd: unknown command\n" if $cmd;
$output .= "Usage: aws ACTION [--help]\n\twhere ACTION is one of\n";
my(%output);
for (@cmd)
{
my($service, $cmd, $action, $param) = @$_;
$output{$service} .= " $cmd";
}
for my $service (sort keys %output)
{
$output .= "\t$service";
while ($output{$service} =~ /\s*(.{1,80})(?:\s|$)/g)
{
my($one) = ($1);
$output .= "\t" if $output =~ /\n$/;
$output .= "\t\t$one\n";
}
}
$output .= "aws versions: (ec2 $ec2_version, sqs $sqs_version, elb $elb_version, sdb $sdb_version, ebn $ebn_version, cloudformation $cloudformation_version, rds: $rds_version)\n";
$output .= "Documentation: Search in Google for 'aws - cli' e.g. 'aws rds-restore-db-instance-from-db-snapshot cli'\n";
$output .= "\tThe link with 'rds-' on the front will list all the parameter shortcuts\n";
die $output;
}
{
my($service, $cmd, $action, $param) = @$cmd_data;
if ($h)
{
my $help = "Usage: aws $cmd";
for (@$param)
{
my($aa, $key, $default) = @$_;
my(@help);
my @aa = split(/\s+/, $aa);
@aa = "" unless @aa;
for my $a (@aa)
{
my $x = "-$a " if $a;
$x = "--$a " if length($a) > 1;
my($name, $N) = $key =~ /^(.*?)(N?)$/;
my $ddd = "..." if $N eq "N";
if ($key =~ /\.N\./)
{
($name) = $key =~ /.*\.(.*)$/;
$ddd = "...";
}
my $def = " ($default)" if $default;
push @help, "$x$name$ddd$def";
}
$help .= " [" . join("|", @help) . "]";
}
$help .= " BUCKET[/OBJECT] [SOURCE]" if $service eq "s3";
$help .= "\n";
print STDERR $help;
exit;
}
my($result);
if ($service eq "aws")
{
if ($action eq "XMLVERSION")
{
my $versions = { "ec2" => $ec2_version, "sqs" => $sqs_version, "elb" => $elb_version, "sdb" => $sdb_version, "iam" => $iam_version, "ebn" => $ebn_version, "cloudformation" => $cloudformation_version, "rds" => $rds_version };
if ($#ARGV > 0)
{
print "$versions->{$argv[0]}\n";
}
else
{
for (keys %$versions)
{
print "$_: $versions->{$_}\n";
}
}
}
}
elsif ($service eq "ec2" || $service eq "sqs" || $service eq "elb" || $service eq "sdb" || $service eq "iam" || $service eq "pa" || $service eq "ebn" || $service eq "cloudformation" || $service eq "rds")
{
#print STDERR "(@{[join(', ', @argv)]})\n" if $v;
my(%count);
my @list = (Action => $action);
for (my $i = 0; $i < @argv; $i++)
{
my($b);
if ($argv[$i] =~ /^--?(.*)$/)
{
($b) = ($1);
++$i;
}
# The Amazon tools have special cases in them too
$list[1] = "GetGroupPolicy" if $action eq ListGroupPolicies && $b eq "p";
$list[1] = "GetUserPolicy" if $action eq ListUserPolicies && $b eq "p";
#
# find the right param
#
for (@$param)
{
my($a, $key, $default, $cref) = @$_;
# A leading space in $a is significant, so careful with split()...
next unless length($a) == 0 && length($b) == 0 || grep /^$b$/, split(/\s+/, $a);
my $data = $argv[$i];
my $count = ++$count{$a};
if ($key eq "__action__")
{
# Swap out the Action for a different one, such as AuthorizeSecurityGroupIngress and *Egress.
$list[1] = $cref;
# It's a side effect, and doesn't consume a parameter.
--$i;
}
elsif ($key)
{
$key =~ s/N$/\.$count/;
$key =~ s/\.N\./\.$count\./;
$data = $cref->($data) if $cref;
push @list, $key => $data;
}
else
{
$data = $cref->($data);
for (my $i = 0; $i < @$data; $i += 2)
{
my $key = $data->[$i];
$key =~ s/N$/\.$count/;
$key =~ s/\.N\./\.$count\./;
push @list, $key => $data->[$i + 1];
}
}
last;
}
}
# add the defaults
for (@$param)
{
my($a, $key, $default, $cref) = @$_;
if ($default && $count{$a} == 0)
{
my $count = ++$count{$a};
if ($key)
{
$key =~ s/N$/\.$count/;
$key =~ s/\.N\./\.$count\./;
$default = $cref->($data) if $cref;
push @list, $key => $default;
}
else
{
my $data = $cref->($default);
for (my $i = 0; $i < @$data; $i += 2)
{
my $key = $data->[$i];
$key =~ s/N$/\.$count/;
$key =~ s/\.N\./\.$count\./;
push @list, $key => $data->[$i + 1];
}
}
}
}
push @list, @final_list;
print STDERR "ec2(@{[join(', ', @list)]})\n" if $v;
if ($service eq "pa")
{
$result = pa(@list);
}
else
{
$result = ec2($service, @list);
}
}
elsif ($service eq "s3")
{
my(@file, @head);
for (@argv)
{
if (/^(?:x-amz-|Cache-|Content-|Expires:|If-|Range:)/i)
{
push @head, $_;
}
else
{
push @file, $_;
}
}
if ($metadata) {
for (split(/,/, $metadata)) {
my($key, $val) = split(/=/, $_, 2);
push @head, "$key: $val";
}
}
my $bucket = shift @file;
if ($action eq DMO)
{
my($temp_fh, $temp_fn) = tempfile(UNLINK => 1);
print $temp_fh "\n";
print $temp_fh "\ttrue\n" if $quiet;
die "missing object: specifify one or more objects to delete\n" if @file == 0;
for (@file)
{
if (/^-$/)
{
for (split(" ", load_file($_)))
{
print $temp_fh "\t\n";
}
next;
}
print $temp_fh "\t\n";
}
print $temp_fh "\n";
$temp_fh->flush;
system "cat $temp_fn" if $v;
$action = PUT;
$md5 = 1;
$bucket .= "?delete";
@file = $temp_fn;
print "bucket = $bucket\nfile = $file\naction = $action\n" if $v;
}
my $file = shift @file;
warn "ignored: @file\n" if @file;
my $MB5 = 5 * 1024 * 1024;
my $GB5 = 1024 * $MB5;
my $SZ = -s $file;
if ($action eq PUT && ($parts || $bucket !~ /\?/ && $SZ > $GB5))
{
$bucket =~ s/^([^\?\/]+)(\?|$ )/$1\/$2/xs; # turn abc or abc? into abc/ or abc/?
$bucket .= $file if $bucket =~ /\/$/;
# delete previous partial multi-part uploads
for (1..10)
{
print "deleting multipart uploads $bucket...\n" if $v;
my $xml = s3(DELETE, undef, "$bucket?upload");
if ($xml)
{
print $xml if $v;
last;
}
}
$parts = int(($SZ + $GB5 - 1) / $GB5) unless $parts > 1;
my $slice = int(($SZ + $parts - 1) / $parts);
if ($slice < $MB5 && $parts > 1)
{
$parts = int(($SZ + $MB5 - 1) / $MB5);
$slice = $MB5;
print STDERR "multipart upload: Too many parts makes slice too small; adjusting to $parts parts\n";
}
my($uploadId) = s3(POST, undef, "$bucket?uploads", undef, @head) =~ /(.*?)<\/UploadId>/;
print "uploadId = $uploadId\n" if $v;
die "missing uploadId\n" if !$uploadId;
for (my $i = 0; $i < $parts; $i++)
{
my $beg = $i * $slice + 1;
my $end = $beg + $slice - 1;
$end = $SZ if $end > $SZ;
local $content_length = $end - $beg + 1;
local($exit_code);
for my $iter (0..$retry)
{
print "failed to upload partNumber=@{[$i + 1]}... retrying #$iter of $retry...\n" if $iter && !$fail;
undef $exit_code;
my $cmd = "tail -c +$beg $file |head -c $content_length";
print "$cmd (bytes)\n" if $v;
open STDIN, "$cmd|" or die "part @{[$i + 1]}: $!";
s3(PUT, undef, "$bucket?partNumber=@{[$i + 1]}&uploadId=$uploadId", "-");
last unless $exit_code == 23;
}
if ($exit_code)
{
print "failed to upload partNumber=@{[$i + 1]}\n" if !$fail;
s3(DELETE, undef, "$bucket?uploadId=$uploadId");
exit $exit_code;
}
}
s3(POST, undef, "$bucket?uploadId=$uploadId");
}
else
{
my $marker = $batch;
my($last_marker);
for (;;)
{
my $r = s3($action, $marker, $bucket, $file, @head);
if ($r !~ /^<\?xml/)
{
print $r;
exit 255;
}
$r =~ s/<\?xml.*?>\r?\s*//;
$result .= $r;
($marker) = $r =~ /.*(.*?)<\/Key>/;
last if defined $batch || $r !~ /true<\/IsTruncated>/ || $marker le $last_marker;
$last_marker = $marker;
}
}
}
elsif ($service eq "r53") # which has its own different approach
{
my $idraw = shift @argv;
my ($id) = $idraw =~ /([A-Z0-9]+)/; # lose any /hostedzone/ or /change/ prefix
my ($pname, $aname) = @{ $param->[0] };
shift @$param unless $pname;
my $r53_endpoint = 'route53.amazonaws.com';
my $url="https://$r53_endpoint/$r53_version/";
my ($method,$reqaction) = split(/\|/, $action);
die "No method (action = '$action') for $cmd." unless $method;
my (%prefix) = (zone_id => 'hostedzone',
change_id => 'change');
$url .= "$prefix{$aname}/$id" if $prefix{$aname}; # /hostedzone/xxx or /change/xxx
$url .= "/$reqaction" if $reqaction;
$url .= "?" if $method eq 'GET';
my %args;
my $content;
while (@argv)
{
my $key = shift @argv;
my ($key1, $val1, $key2) = $key =~ /--(\w+)=(.*)|-(\w+)/;
$key1 ||= $key2;
$val1 ||= shift(@argv);
for (@$param)
{
my ($paramkey, $urlkey) = @$_;
next unless $paramkey =~ /( \s+ | ^) $key1 ( \s+ | $ )/x;
$args{$urlkey} = $val1;
last;
}
}
my (@days)=(qw(Sun Mon Tue Wed Thu Fri Sat));
my (@months)=(qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec));
my ($sec, $min, $hour, $mday, $mon, $year, $dow, undef, undef) = gmtime(time + $time_offset);
my $zulu = sprintf ("%s, %02d %s %04d %02d:%02d:%02d GMT",
$days[$dow], $mday, $months[$mon], 1900 + $year, $hour, $min, $sec);
my $algo = 'HmacSHA' . ($sha1 ? '1' : '256');
my $sig = sign ($zulu, $algo);
my $auth = "AWS3-HTTPS AWSAccessKeyId=$awskey,Algorithm=$algo,Signature=$sig";
my @curl = ('-H' => "'X-Amzn-Authorization: $auth'",
'-H' => "'x-amz-date: $zulu'" );
push (@curl, '-H' => "x-amz-security-token:$session") if $session;
my $cmd;
if ($method eq 'GET')
{
$url .= join ('&', map { encode_url($_)."=".encode_url($args{$_}) }
grep { ! /^__/ } sort keys %args);
}
elsif ($method eq 'PUT')
{
die "can't put yet";
}
elsif ($method eq 'DELETE')
{
# nothing special to do in this case. URL is set up.
push (@curl, '-X' => $method); # must make it use this method.
}
else # method must be POST
{
my $hdr = R53_xml_data() -> {'header'};
my $xmlsrc = R53_xml_data() -> {$action};
for (keys %args) {
my $val = $args{$_};
$xmlsrc =~ s/(<$_>)/$1$val/;
}
$xmlsrc =~ s/'/'\\''/g;
push (@curl, '--data' => "'$hdr$xmlsrc'");
push (@curl, '-H' => "'Content-type: text/xml'");
}
$cmd = qq[$curl $curl_options @curl $url];
print "$cmd\n" if $v;
$result = qx[$cmd];
print "$resp\n" if $v;
}
else
{
die;
}
if ($xml)
{
print xmlpp($result);
}
elsif ($yaml)
{
print xml2yaml($result);
}
elsif ($json)
{
print xml2json($result);
}
elsif ($result =~ /
#
# c1438ce900acb0db547b3708dc29ca60370d8174ee55305050d2990dcf27109c
# timkay681
#
#
#
# 3.14
# 2007-03-04T22:29:34.000Z
#
#
#
# boopsielog
#
#
# 1000
# false
#
# ec201-2008-08-20-access.log.gz
# 2008-08-21T03:01:51.000Z
# "baa27b2e8def9acf8c2f3690e230e37a"
# 2405563
#
# c1438ce900acb0db547b3708dc29ca60370d8174ee55305050d2990dcf27109c
# timkay681
#
# STANDARD
#
my $isdir = $result =~ /(.*?)<\/DisplayName>/s;
my(@result);
my($prefix) = $result =~ /(.*?)<\/Prefix>/;
while ($result =~ /<(?:Contents|Bucket|CommonPrefixes)>\s*(.*?)\s*<\/(?:Contents|Bucket|CommonPrefixes)>/sg)
{
my($item) = ($1);
my $key = dentity($item =~ /<(?:Key|Name|Prefix)>(.*?)<\/(?:Key|Name|Prefix)>/s);
my($size) = $item =~ /(.*?)<\/Size>/s;
my($mod) = $item =~ /<(?:LastModified|CreationDate)>(.*?)<\/(?:LastModified|CreationDate)>/s;
my($owner) = $item =~ /(.*?)<\/DisplayName>/s;
$key =~ s/^\Q$prefix\E// if $delimiter;
for ($mod)
{
s/T/ /g;
s/\.000Z//;
}
push @result, [$item, $key, $size, $mod, $owner || $owner1 || "unknown"];
}
if ($t)
{
@result = sort {$a->[3] cmp $b->[3]} @result;
}
if ($r)
{
@result = reverse @result;
}
for (@result)
{
my($item, $key, $size, $mod, $owner) = (@$_);
if ($l)
{
$key = printable($key);
if ($isdir)
{
print "drwx------ 2 $owner 0 $mod $key\n";
}
else
{
printf "-rw------- 1 $owner %10.0f $mod %s\n", $size, $key;
}
}
elsif ($d1)
{
print "$key\n";
}
elsif ($simple)
{
printf "%10.0f\t$mod\t%s\n", $size, $key;
}
elsif ($exec)
{
#local $_ = sprintf "%10.0f\t$mod\t$key\n", $size;
#local @_ = ($size, $mod, $key);
my($bucket, $prefix) = split(/\//, $argv[0], 2);
eval $exec;
last if $? & 127; # if the user hits control-c during a system() call...
}
}
}
elsif ($result =~ /(.*?)<\/keyMaterial>/s, "\n";
}
elsif ($result =~ /(.*?)<\/output>/s);
}
elsif ($result =~ /(.*?)<\/value>/, "\n";
}
elsif ($result =~ /)/sg)
{
my($result) = ($1);
while ($result =~ /(- |.)*?<\/item>)/sg)
{
my($result) = ($1);
my($instanceId) = $result =~ /(.*?)<\/instanceId>/s;
my($spotType) = $result =~ /(.*?)<\/type>/s;
my($spotState) = $result =~ /(.*?)<\/state>/s;
my($spotPrice) = $result =~ /(.*?)<\/spotPrice>/s;
push @instanceId, $instanceId;
push @spotType, $spotType;
push @spotState, $spotState;
push @spotPrice, $spotPrice;
}
}
my($open);
for (my $i = 0; $i < @instanceId; $i++)
{
$open += $spotState[$i] eq "open";
print "@{[$instanceId[$i] || ' ']}\t$spotState[$i] \t$spotType[$i]\t$spotPrice[$i]\n";
}
print "open = $open\n";
last unless $wait && $open;
sleep $wait;
$result = qx[$0 --cmd0 --xml --region=$region describe-spot-instance-requests];
}
$result = qx[$0 --cmd0 --xml --region=$region describe-instances @instanceId];
}
for (;;)
{
my(@instanceId, @instanceState, @dnsName, @groupId);
my($groupId) = $result =~ /(.*?)<\/groupId>/;
while ($result =~ /()/sg)
{
my($result) = ($1);
while ($result =~ /(
- |.)*?<\/item>)/sg)
{
my($result) = ($1);
my($instanceId) = $result =~ /(.*?)<\/instanceId>/s;
my($instanceState) = map {/(.*?)<\/name>/s} $result =~ /(.*?)<\/instanceState>/s;
my($dnsName) = $result =~ /(.*?)<\/dnsName>/s;
push @instanceId, $instanceId;
push @instanceState, $instanceState;
push @dnsName, $dnsName;
push @groupId, $groupId;
}
}
my($pending);
for (my $i = 0; $i < @instanceId; $i++)
{
$pending += $instanceState[$i] eq "pending" || $instanceState[$i] eq "stopping";
print "$instanceId[$i]\t$instanceState[$i]\t$dnsName[$i]\t$groupId[$i]\n";
}
last unless $wait && $pending;
sleep $wait;
$result = qx[$0 --cmd0 --xml --region=$region describe-instances @instanceId];
}
}
elsif ($result =~ /(.*?)<\/groupId>/;
# First result is from start/stop so different location for instanceState => currentState
while ($result =~ /()/sg)
{
my($result) = ($1);
while ($result =~ /(
- |.)*?<\/item>)/sg)
{
my($result) = ($1);
my($instanceId) = $result =~ /(.*?)<\/instanceId>/s;
my($instanceState) = map {/(.*?)<\/name>/s} $result =~ /(.*?)<\/currentState>/s;
my($dnsName) = $result =~ /(.*?)<\/dnsName>/s;
push @instanceId, $instanceId;
push @instanceState, $instanceState;
push @dnsName, $dnsName;
push @groupId, $groupId;
}
}
my($pending);
for (my $i = 0; $i < @instanceId; $i++)
{
$pending += $instanceState[$i] eq "pending" || $instanceState[$i] eq "stopping";
print "$instanceId[$i]\t$instanceState[$i]\t$dnsName[$i]\t$groupId[$i]\n";
}
last unless $wait && $pending;
sleep $wait;
$result = qx[$0 --cmd0 --xml --region=$region describe-instances @instanceId];
# Now the result needs to be handled for describe-instances. instanceState => instanceState
for (;;)
{
my(@instanceId, @instanceState, @dnsName, @groupId);
my($groupId) = $result =~ /(.*?)<\/groupId>/;
while ($result =~ /()/sg)
{
my($result) = ($1);
while ($result =~ /(
- |.)*?<\/item>)/sg)
{
my($result) = ($1);
my($instanceId) = $result =~ /(.*?)<\/instanceId>/s;
my($instanceState) = map {/(.*?)<\/name>/s} $result =~ /(.*?)<\/instanceState>/s;
my($dnsName) = $result =~ /(.*?)<\/dnsName>/s;
push @instanceId, $instanceId;
push @instanceState, $instanceState;
push @dnsName, $dnsName;
push @groupId, $groupId;
}
}
my($pending);
for (my $i = 0; $i < @instanceId; $i++)
{
$pending += $instanceState[$i] eq "pending" || $instanceState[$i] eq "stopping";
print "$instanceId[$i]\t$instanceState[$i]\t$dnsName[$i]\t$groupId[$i]\n";
}
last unless $wait && $pending;
sleep $wait;
$result = qx[$0 --cmd0 --xml --region=$region describe-instances @instanceId];
}
}
elsif ($result =~ /
#
#
# 3306
# default:mysql-5-1
# mysql
# creating
# manual
# general-public-license
# dbinstance
# 5.1.69
# asnapshot
# vpc-30zzzzz
# ap-southeast-2b
# 2014-07-16T05:27:55.909Z
# 0
# 5
# user
#
#
#
# 2f1b87fa-12ed-11e4-be67-5b99b2d10c58
#
#
for (;;)
{
my($instanceId) = $result =~ /(.*?)<\/DBInstanceIdentifier>/s;
my($snapshotId) = $result =~ /(.*?)<\/DBSnapshotIdentifier>/s;
my($progress) = $result =~ /(.*?)<\/PercentProgress>/s;
my($status) = $result =~ /(.*?)<\/Status>/s;
my($pending) += $status eq "creating" || $status eq "deleting";
print "$instanceId\t$snapshotId\t$status\t$progress\n";
last unless $wait && $pending;
sleep $wait;
$result = qx[$0 --cmd0 --xml --region=$region describe-db-snapshots -s $snapshotId];
}
}
elsif ($result =~ /(.*?)<\/DBInstanceIdentifier>/s;
my($zone) = $result =~ /(.*?)<\/AvailabilityZone>/s; # Zone is not available for RestoreDBInstanceFromDBSnapshotResponse
my($engine) = $result =~ /(.*?)<\/Engine>/s;
my($status) = $result =~ /(.*?)<\/DBInstanceStatus>/s;
my($pending) += $status ne "available";
print "$instanceId\t$engine\t$status\t$zone\n";
last unless $wait && $pending;
sleep $wait;
$result = qx[$0 --cmd0 --xml --region=$region describe-db-instances $instanceId];
}
}
elsif ($result =~ /(.*?)<\/QueueUrl>/g)
{
my($q) = ($1);
$q =~ s/^https?:\/\/.*?(?=\/)//;
print "$q\n";
}
}
else
{
print ary2tab(xml2ary(ListQueuesResult, $result), {title => "Queue URLs", empty => "no queues\n"});
}
}
elsif ($result =~ /(.*?)<\/Attribute>/sg)
{
if ($1 =~ /(.*?)<\/Name>.*?(.*?)<\/Value>/s)
{
print "$1\t$2\n";
}
}
}
elsif ($result =~ /[1], "\n";
}
}
else
{
print ary2tab([@$ary]);
}
}
elsif ($result =~ /(.*?)<\/Attribute>/sg)
{
my($name, $value) = $1 =~ /(.*?)<\/Name>\s*(.*?)<\/Value>/s;
print "$name\t$value\n";
}
}
elsif ($result =~ /(.*?)<\/Item>/sg)
{
my($item, $attr) = $1 =~ /(.*?)<\/Name>(.*)/;
while ($attr =~ /(.*?)<\/Name>\s*(.*?)<\/Value>/sg)
{
print "$item\t$1\t$2\n";
}
}
}
elsif ($result =~ /(.*?)<\/ReceiptHandle>/;
my $body = decode_url($result =~ /(.*?)<\/Body>/);
if ($handle && $body)
{
$exec = 'system "$body"' if $exec == 1;
my $rc = eval $exec;
if ($rc)
{
print "exec evaluated to non-zero ($rc): message not deleted from queue\n";
}
else
{
my $cmd = qq[$0 --cmd0 --region=$region dm $argv[0] --handle $handle];
print "$cmd\n" if $v;
my $dm = qx[$cmd];
print "$dm\n" if $v;
}
}
}
else
{
my $ary = xml2ary(Message, $result);
if ($simple)
{
my($id, $handle, $md5, $body);
for (@$ary)
{
$id = $_->[1] if $_->[0] eq MessageId;
$handle = $_->[1] if $_->[0] eq ReceiptHandle;
$md5 = $_->[1] if $_->[0] eq MD5OfBody;
$body = decode_url($_->[1]) if $_->[0] eq Body;
}
print "$handle\t$body\t$id\t$md5\n" if $handle;
}
else
{
print ary2tab($ary, {title => "Messages", empty => "no messages\n"});
}
}
}
elsif ($result =~ /(.*?)<\/MD5OfMessageBody>/;
my($id) = $result =~ /(.*?)<\/MessageId>/;
print "$md5\t$id\n";
}
elsif ($result =~ /<(?:GetGroupPolicyResponse|GetUserPolicyResponse)/)
{
my($doc) = $result =~ /(.*?)<\/PolicyDocument>/s;
$doc =~ s/%(..)/pack(H2,$1)/ge;
print $doc;
}
elsif ($result =~ /<(?:ListUserPoliciesResponse)/)
{
my @member = $result =~ /(.*?)<\/member>/g;
print join("\n", @member, undef);
}
elsif ($result =~ / Name, value => Value}), {title => "Attributes", empty => "no attributes\n"});
}
elsif ($result =~ /(.*?)<\/item>/sg)
{
my($item) = ($1);
my(@item);
while ($item =~ /<(.*?)>(.*?)<\/\1>/g)
{
push @item, $2;
}
print join("\t", @item), "\n";
}
}
elsif ($result =~ /(.*?)<\/$tag>/sg)
{
my($elt) = ($1);
my(@val);
while ($elt =~ /<(.*?)>(.*?)<\/\1>/sg)
{
my($key, $val) = ($1, $2);
push @key, $key if !@result;
push @val, $val;
}
push @result, \@key if !@result && $param->{head};
push @result, \@val;
}
\@result;
}
sub xml2ary
{
my($tag, $result, $param, @result) = @_;
for ($result =~ /<$tag.*?>(.*?)<\/$tag>/sg)
{
while (/<(.*?)>(.*?)<\/\1>/sg)
{
my($key, $val1) = ($1, $2);
my($val);
while ($val1 =~ /<(.+?)>(.*?)<\/\1>/sg)
{
if ($1 eq $param->{key})
{
$key = $2;
}
elsif ($1 eq $param->{value})
{
$val = $2;
}
else
{
$val .= " " if $val;
$val .= "$1=$2";
}
}
$val = $val1 unless length($val);
push @result, [$key, $val];
}
}
\@result;
}
sub ary2tab
{
my($ary, $param) = @_;
return $param->{empty} if exists $param->{empty} && !@$ary;
my(@width);
for (@$ary)
{
if (ref $_ eq SCALAR)
{
$_ = [$_];
}
if (ref $_ eq ARRAY)
{
for (my $i = 0; $i < @$_; $i++)
{
$width[$i] = length($_->[$i]) if $width[$i] < length($_->[$i]);
}
}
}
if ($param->{title})
{
my $width = -1;
$width += 2 + $_ for @width;
my $l = int(($width - length($param->{title})) / 2);
my $r = $width - length($param->{title}) - $l;
$output .= "+" . "-" x (@width - 1);
$output .= "-" x (2 + $_) for @width;
$output .= "+\n";
$output .= "| " . " " x $l . $param->{title} . " " x $r . " |\n";
}
$output .= "+" . "-" x (2 + $_) for @width;
$output .= "+\n";
for (@$ary)
{
for (my $i = 0; $i < @width; $i++)
{
$output .= "| " . $_->[$i] . " " x (1 + $width[$i] - length($_->[$i]));
}
$output .= "|\n";
}
$output .= "+" . "-" x (2 + $_) for @width;
$output .= "+\n";
}
sub xml2tab
{
my($xml) = @_;
my($output);
$xml =~ s/^<\?xml.*?>(\r?\n)*//;
my @xml = grep !/^\s*$/, split(/(<.*?>)/, $xml);
my(@tag, @depth);
my $depth = 0;
for (my $i = 0; $i < @xml; $i++)
{
if ($xml[$i] =~ /^<(\w+)\/>$/)
{
next;
}
elsif ($xml[$i] =~ /^<(\w+)/)
{
my($tag) = ($1);
$tag[$i] = $tag;
$depth[$i] = ++$depth;
}
elsif ($xml[$i] =~ /^<\/(\w+)/)
{
my($tag) = ($1);
for (my $j = $i - 1; $j >= 0; $j--)
{
next if $depth[$j] > $depth;
next if $tag[$j] ne $tag;
$depth = $depth[$j] - 1;
last;
}
}
else
{
$tag[$i] = $xml[$i];
$depth[$i] = 99;
}
}
my(@parent, $depth, %head, @head, @table, $col);
my $skipre = qr/^(?:amiLaunchIndex|ETag|HostId|ipPermissions|Owner)$/;
for (my $i = 0; $i <= @xml; $i++)
{
$parent[$depth[$i]] = $tag[$i];
if (@head && $i == @xml || $depth[$i] && $depth[$i] < $depth)
{
unless (@head == 1 && $head[0] eq "RequestId")
{
for (@table)
{
$_ = [map {printable(dentity($_))} @$_{@head}];
}
unshift @table, [@head];
my(@width);
for (@table)
{
for (my $i = 0; $i < @head; $i++)
{
my $length = length($_->[$i]);
$width[$i] = $length if $width[$i] < $length;
}
}
my $sep = "+";
for (my $i = 0; $i < @head; $i++)
{
next if $head[$i] =~ /$skipre/;
$sep .= "-" x (2 + $width[$i]) . "+";
}
for (my $j = 0; $j < @table; $j++)
{
$output .= "$sep\n" if $j < 2;
for (my $i = 0; $i < @head; $i++)
{
next if $head[$i] =~ /$skipre/;
my $len = length($table[$j]->[$i]);
my $pad = $width[$i] - $len;
my $l = 1 + int($pad / 2); # center justify
$l = 1 if $j; # left justify all but first row
my $r = 2 + $pad - $l;
$output .= "|" . " " x $l . $table[$j]->[$i] . " " x $r;
}
$output .= "|\n";
}
$output .= "$sep\n";
}
$depth = 0;
%head = ();
@head = ();
@table = ();
}
my $tag2 = "$parent[$depth[$i] - 1]-$tag[$i]";
if ($tag[$i] =~ /^(?:LocationConstraint|Grant
|AttachVolumeResponse|Bucket|Contents|CommonPrefixes|AuthorizeSecurityGroup(?:E|In)gressResponse|CopyObjectResult
|CreateKeyPairResponse|CreateSecurityGroupResponse|CreateImageResponse|CreateSnapshotResponse|CreateVolumeResponse
|DeleteSecurityGroupResponse|DeleteKeyPairResponse|DeleteSnapshotResponse|DeleteVolumeResponse
|DetachVolumeResponse|Error|GetConsoleOutputResponse|ListBucketResult|RebootInstancesResponse
|RevokeSecurityGroup(?:E|In)gressResponse|AllocateAddressResponse|ReleaseAddressResponse|AssociateAddressResponse|DescribeRegionsResponse
|CreateQueueResponse|ResponseMetadata|DescribeSnapshotAttributeResponse|ModifySnapshotAttributeResponse|ResetSnapshotAttributeResponse
|CreateLoadBalancerResponse|DeleteLoadBalancerResponse
|DescribeSpotInstanceRequestsResponse|CancelSpotInstanceRequestsResponse|RequestSpotInstancesResponse|DescribeSpotPriceHistoryResponse
|ListGroupPoliciesResult|GetGroupPolicyResult
|SendMessageResult|CreateTagsResponse|DeleteTagsResponse
)$/x
|| $tag2 =~ /^(?:addressesSet-item|availabilityZoneInfo-item|imagesSet-item|instancesSet-item|instanceStatusSet-item
|ipPermissions-item|keySet-item|reservedInstancesOfferingsSet-item|securityGroupInfo-item|volumeSet-item|snapshotSet-item|regionInfo-item
|ReceiveMessageResult-Message
|LoadBalancerDescriptions-member
|ReceiveMessageResult-Message|spotInstanceRequestSet-item|spotPriceHistorySet-item
|GetAttributesResult-Attribute|SelectResult-Item
|CreateGroupResult-Group|Groups-member|CreateUserResult-User|Users-member|GetUserResult-User
|CreateAccessKeyResult-AccessKey|AccessKeyMetadata-member|tagSet-item
)$/x
|| $i == @xml)
{
$depth = $depth[$i];
###push @table, {"" => $tag[$i]};
push @table, {};
}
next unless $depth;
if ($depth[$i] == $depth + 1)
{
$col = $tag[$i];
push @head, $col unless exists $head{$col};
$head{$col} = undef;
}
if ($depth[$i] >= $depth + 2)
{
$table[$#table]->{$col} .= " " if $table[$#table]->{$col} && $depth[$i] < 99;
$table[$#table]->{$col} .= $tag[$i];
$table[$#table]->{$col} .= "=" if $depth[$i] < 99;
}
}
if (!@table || $dump_xml)
{
print STDERR "$xml\n";
for (my $i = 0; $i < @xml; $i++)
{
next unless $tag[$i];
print STDERR $depth[$i], " " x $depth[$i], "$tag[$i]\n";
}
}
$output;
}
sub xmlpp
{
my($xml) = @_;
my($indent, @path, $defer, @defer, $result) = "\t";
for ($xml =~ /<.*?>|[^<]*/sg)
{
if (/^<\/(\w+)/ || /^<(!\[endif)/) # $/) # .../> or ...?>
{
$result .= "@{[$indent x @path]}@{[$defer =~ /^\s*(.*?)\s*$/s]}\n" if $defer;
push @path, @defer;
$result .= "@{[$indent x @path]}@{[/^\s*(.*?)\s*$/s]}\n" if $_;
$defer = "";
@defer = ();
}
elsif (/^(?:[^<]|\s]+)/;
}
}
$result .= "@{[$indent x @path]}@{[$defer =~ /^\s*(.*?)\s*$/s]}\n" if $defer;
$result;
}
# Convert xml string to YAML format
# Cf.: https://github.com/timkay/aws/pull/9
sub xml2yaml {
my($result) = xmlpp(@_);
my($rubySymbol) = "";
$rubySymbol = ":" if $ruby;
$result =~ s#> +#>#g; # remove trailing spaces after >
$result =~ s#\n# #g; # replace all \n by a single space
$result =~ s#> #>\n#g; # remove all '\n's
$result =~ s###g; # remove closing tags
$result =~ s#<([a-z0-9:]*).*>#$rubySymbol\1: #gi; # opening tags -> symbols
$result =~ s#($rubySymbol[^:]+): (.+)#\1: "\2"#g; # opening tags -> symbols
$result =~ s#:?(.*)/:#\1:#g; # empty values
$result =~ s#:?(item|bucket|member|ResourceRecordSet|HostedZone|ResourceRecord): #- #gi;
# array items
$result =~ s#\t# #g; # tabs -> spaces
$result =~ s#^[ :]*\n##mg; # remove all empty lines
$result =~ s#[ ]+$##gm; # remove all trailing spaces
$result =~ s#^[^ \n]+:\n#---\n#; # new document indicator
$result =~ s#^ ##mg; # shift left
$result;
}
# Convert xml string to JSON format
sub xml2json
{
my $xml = shift @_;
unless (eval {require XML::Simple} &&
eval {require JSON})
{
warn "Could not load the required modules XML::Simple and JSON";
return;
}
return unless $xml;
my $coder = JSON->new()->relaxed()->utf8()->allow_blessed->convert_blessed->allow_nonref();
my $ref = XML::Simple::XMLin($xml, ForceArray => [qw/Attribute Item/]);
return $coder->encode($ref);
}
sub s3
{
my($verb, $marker, $name, $file, @header) = @_;
$file ||= $name if $verb eq PUT && $ENV{S3_DIR};
$name = "$ENV{S3_DIR}/$name" if $ENV{S3_DIR};
$name =~ s/^([^\?\/]+)(\?|$ )/$1\/$2/xs; # turn abc or abc? into abc/ or abc/?
$name .= $file if $verb eq PUT && $name =~ /\/$/;
# read from stdin when
# aws put target
# aws put target -
# but not
# aws put target?acl
# what about
# aws put target?location
my($temp_fh);
if ($verb eq PUT && $file eq "-" && $content_length)
{
push @header, "Content-Length: $content_length";
push @header, "Transfer-Encoding:";
}
elsif ($verb eq PUT && ($file eq "-" || $file eq "" && $name !~ /\?acl$/))
{
# and not when a terminal
die "$0: will not to read from terminal (use \"-\" for filename to force)\n" if -t && $file ne "-";
($temp_fh, $file) = tempfile(UNLINK => 1);
while (STDIN->read(my $buf, 16_384))
{
print $temp_fh $buf;
}
$temp_fh->flush;
}
# add a Content-Type header using mime.types
if ($verb eq PUT)
{
my($found_content_type, $found_content_md5);
(push @header, "Content-Type: $content_type"), $found_content_type++ if $content_type;
for (@header)
{
$found_content_type++ if /^content-type:/i;
$found_content_md5++ if /^content-md5:/i;
}
if (!$found_content_type)
{
my($ext) = $name =~ /\.(\w+)$/;
if ($ext)
{
local(@ARGV);
for (qw(mime.types /etc/mime.types))
{
push @ARGV, $_ if -e $_;
}
if (@ARGV)
{
while (<>)
{
my($type, @ext) = split(/\s+/);
if (grep /^$ext$/, @ext)
{
push @header, "Content-Type: $type";
print STDERR "setting $header[$#header]\n" if $v;
last;
}
}
}
}
}
if (!$found_content_md5 && $md5)
{
# Too memory intensive:
#my $md5 = encode_base64(md5(load_file($file)), "");
my($md5);
if (!$isUnix)
{
# Uses Digest::MD5::File that isn't in base perl:
# (Use this choice for Windows, after installing the package)
require Digest::MD5::File;
$md5 = encode_base64(Digest::MD5::File::file_md5($file), "");
}
else
{
# Just right:
$md5 = encode_base64(pack("H*", (split(" ", qx[md5sum @{[cq($file)]}]))[0]), "");
}
push @header, "Content-MD5: $md5";
print STDERR "setting $header[$#header]\n" if $v;
}
}
$set_acl = "public-read" if $public;
$set_acl = "private" if $private;
# the multipart upload stuff is getting icky
push @header, "x-amz-acl: $set_acl" if $set_acl && $verb =~ /^(?:POST|PUT)$/ && $name !~ /\?partNumber=|\?uploadId=/;
$requester = "requester" if $requester == 1;
push @header, "x-amz-request-payer: $requester" if $requester;
# added a case for "copy", so that the source moves to a header
if ($verb eq COPY)
{
if ($name =~ /\/$/)
{
@stuff = split('/', $file);
shift(@stuff);
shift(@stuff);
my($what) = join('/', @stuff);
$name .= $what;
}
if ($file !~ /^\//)
{
(my $where = $name) =~ s/\/[^\/]+$/\//;
$file = "/$where$file";
}
push @header, "x-amz-copy-source: @{[encode_url($file)]}";
undef $file;
$verb = PUT;
}
my($prefix);
# added a case for "ls", so that a prefix can be specified
# (otherwise, the prefix looks like an object name)
if ($verb eq LS)
{
$name =~ s/^\///;
($name, $prefix) = split(/\//, $name, 2);
$name .= "/" if $name;
$prefix ||= $file;
undef $file;
$verb = GET;
}
my($ub, $uo, $uq) = $name =~ /^(.+?)(?:\/(.*?))?(\?(?:acl|delete|location|logging|bittorrent|lifecycle|policy|requestPayment
|uploadId=[\.\-\w]+
|uploads
|upload
|partNumber=\d+&uploadId=[\.\-\w]+
|partNumber=\d+
|part
|versioning|versions|website))?$/sx;
my $uname = encode_url($ub) . "/" . encode_url($uo) . $uq if $name;
if ($uq =~ /^\?(?:uploadId=(.*)|upload|partNumber=(\d+)(?:&uploadId=(.*))?|part)$/)
{
my($uploadId, $partNumber, @part) = ($1 || $3, $2);
unless ($uploadId)
{
my $xml = s3(GET, undef, "$ub?uploads");
while ($xml =~ /(.*?)<\/Upload>/sg)
{
my($upload) = ($1);
if ($upload =~ /(.*?)<\/Key>/ && $1 eq $uo)
{
if ($upload =~ /(.*?)<\/UploadId>/)
{
$uploadId = $1;
last;
}
}
}
}
if ($verb eq POST || $uq eq "?part") # look up partNumber
{
my $xml = s3(GET, undef, "$ub/$uo?uploadId=$uploadId");
while ($xml =~ /(.*?)<\/Part>/sg)
{
my($part) = ($1);
if ($part =~ /(.*?)<\/PartNumber>.*?(.*?)<\/ETag>/s)
{
push @part, [$1, $2];
$partNumber = $1;
}
}
$partNumber++;
}
if ($verb eq POST && $uq =~ /^\?(?:uploadId=.*|upload)$/)
{
($temp_fh, my $temp_fn) = tempfile(UNLINK => 1);
print $temp_fh "\n";
for (@part)
{
print $temp_fh " \n";
print $temp_fh " $_->[0]\n";
print $temp_fh " $_->[1]\n";
print $temp_fh " \n";
}
print $temp_fh "\n";
$temp_fh->flush;
$file = $temp_fn;
system "cat $file" if $v >= 2;
}
return "$uname: no matching multipart upload found\n" if $uq eq "?upload" && length($uploadId) == 0;
$uname .= "Id=$uploadId" if $uq eq "?upload"; # List Parts / Complete Multipart Upload / Abort Multipart Upload
$uname .= "&uploadId=$uploadId" if $uq =~ /^?partNumber=\d+$/; # Upload Part NNN
$uname .= "Number=$partNumber&uploadId=$uploadId" if $uq eq "?part"; # Upload Part
}
if ($uq eq "?delete")
{
$verb = POST;
}
if ($v >= 2)
{
print "name = $name\n";
print "ub = $ub\n";
print "uo = $uo\n";
print "uq = $uq\n";
print "uname = $uname\n";
}
my($vhost, $vname) = ($s3host, $uname);
if (!$no_vhost)
{
($vhost, $vname) = ($dns_alias? $1: "$1.$vhost", $2) if $uname =~ /^([0-9a-z][\-0-9a-z]{1,61}[0-9a-z])(?:\/(.*))?$/;
}
print STDERR "vhost=$vhost vname=$vname\n" if $v;
my $isGETobj = ($verb eq HEAD || $verb eq GET) && $uname =~ /\/./ && $uname !~ /\?/;
my $expire_time = ($expire_time || 30) + $time_offset; # note we rewrite $expire_time, only for S3
my $expires = time + $expire_time;
my($content_type, $content_md5);
for (@header)
{
if (/^(.*?):\s*(.*)$/)
{
$content_type = $2 if lc $1 eq "content-type";
$content_md5 = $2 if lc $1 eq "content-md5";
}
}
push @header, "x-amz-security-token:$session" if $session;
my $header_sign = join("\n", map({$_->[1]} sort {$a->[0] cmp $b->[0]} map {s/^(.*?):\s*/\L$1:/s; [lc $1, $_]} grep /^x-amz-/i, @header), "") if @header;
my $header = join(" --header ", undef, map {cq($_)} @header);
if ($isGETobj && ($verb eq HEAD || !$fail) && !$request)
{
my $url = "$scheme://$vhost/$vname";
if ($AWS4) {
$url = signAWS4("s3", HEAD, $url, $expire_time, [@header]);
} else {
my $data = "HEAD\n$content_md5\n$content_type\n$expires\n$header_sign/$uname";
$url .= "@{[$vname =~ /\?/? '&': '?']}Expires=$expires&AWSAccessKeyId=@{[encode_url($awskey)]}&Signature=@{[encode_url(sign($data))]}";
}
my $cmd = qq[$curl $curl_options $insecureaws $header --head @{[cq($url)]}];
print STDERR "$cmd\n" if $v;
my $head = qx[$cmd];
print STDERR $head if $v;
my($code) = $head =~ /^HTTP\/\d+\.\d+\s+(\d+\s+.*?)\r?\n/s;
if ($code !~ /^2\d\d\s/)
{
print STDERR "$code\n" unless $v;
$exit_code = 22;
return;
}
if ($verb eq HEAD)
{
print $head;
return;
}
}
my($content);
$content = "--upload-file @{[cq($file)]}" if $file;
if ($verb eq GET && $file)
{
if ($file =~ /\/$/ || -d $file)
{
$file .= "/" if $file !~ /\/$/;
#Why doesn't #1 work?
#$file .= "#1";
my($name) = $name =~ /(?:.*\/)?(.*)$/;
$file .= $name;
}
$content = "--create-dirs --output @{[cq($file)]}";
}
# added a case for "mkdir", so that "$name .= $file" gets defeated
# in the mkdir case - We don't want the file we are uploading to be
# appended to the name because we are creating the bucket, and the
# name is the location constraint file.
if ($verb eq MKDIR)
{
if ($region)
{
$region = {
eu => "eu-west-1",
us => "us-east-1",
uswest => "us-west-1",
uswest2 => "us-west-1",
ap => "ap-southeast-1",
ap2 => "ap-southeast-2",
sa => "sa-east-1",
}->{lc $region} || $region;
($temp_fh, my $xml) = tempfile(UNLINK => 1);
print $temp_fh qq[
$region
];
$temp_fh->flush;
$content = "--upload-file @{[cq($xml)]}";
}
$verb = PUT;
}
my $url = "$scheme://$vhost/$vname";
if ($AWS4) {
my(@qs);
push @qs, "marker=$marker" if $marker;
push @qs, "prefix=$prefix" if $prefix;
push @qs, "delimiter=$delimiter" if $delimiter;
push @qs, "max-keys=$max_keys" if $max_keys;
$url .= ($url =~ /\?/? '&': '?') . join("&", @qs) if @qs;
$url = signAWS4("s3", $verb, $url, $expire_time, [@header]); # sends a copy of @header, not the original
} else {
my(@qs);
push @qs, "marker=@{[encode_url($marker)]}" if $marker;
push @qs, "prefix=@{[encode_url($prefix)]}" if $prefix;
push @qs, "delimiter=$delimiter" if $delimiter;
push @qs, "max-keys=$max_keys" if $max_keys;
$url .= ($url =~ /\?/? '&': '?') . join("&", @qs) if @qs;
my $data = "$verb\n$content_md5\n$content_type\n$expires\n$header_sign/$uname";
$url .= "@{[$url =~ /\?/? '&': '?']}Expires=$expires&AWSAccessKeyId=@{[encode_url($awskey)]}&Signature=@{[encode_url(sign($data))]}";
}
return $url if $request;
# exec() is used because we can, but it doesn't work under Windows:
# curl.exe runs asynchronously, and control is returned to the caller
# before the file transfer request is complete. Thus, for Windows,
# no exec().
if ($isGETobj && !$md5 && $isUnix)
{
my $cmd = qq[$curl $curl_options $insecureaws $header --request $verb $content --location @{[cq($url)]}];
print STDERR "exec $cmd\n" if $v;
exec $cmd;
die;
}
# for a PUT, disable the "Expect: 100-Continue" header if the file is small
my($accept);
$accept = "--header \"Expect: \"" if ($verb eq PUT || $verb eq POST) && (-s $file) < 10_000;
my $cmd = qq[$curl $curl_options $insecureaws $accept $header --request $verb --dump-header - $content --location @{[cq($url)]}];
print STDERR "$cmd\n" if $v;
my $item = qx[$cmd];
exit $? >> 8 if $? && $fail;
# remove "HTTP/1.1 100 Continue"
$item =~ s/^HTTP\/\d+\.\d+\s+100\b.*?\r?\n\r?\n//s;
my($head, $body) = $item =~ /^((?:HTTP\/.*?\r?\n\r?\n)+)(.*)$/s;
print STDERR $head if $v;
my($code) = $head =~ /^HTTP\/\d+\.\d+\s+(\d+\s+.*?)\r?\n/s;
if ($code !~ /^2\d\d\s/)
{
print STDERR "$code\n" unless $v;
$exit_code = 22;
# exit code 23 means that there was no reply - happens with multipart upload sometimes
$exit_code = 23 if !length($code);
return if $fail;
}
if ($md5)
{
my($want) = $head =~ /^ETag:\s*"(.*?)"/m;
if ($want)
{
my($have);
if ($body)
{
$have = md5_hex($body);
}
else
{
$have = (split(" ", qx[md5sum @{[cq($file)]}]))[0];
}
print STDERR "MD5: checksum is $want\n" if $v;
if ($want ne $have)
{
print STDERR "MD5: checksum failed ($want at amz != $have here)\n";
exit 1 if $fail;
}
}
}
$body;
}
sub pa
{
my(@p) = @_;
$p[0] = "Operation" if $p[0] eq "Action";
my($sec, $min, $hour, $mday, $mon, $year, undef, undef, undef) = gmtime(time + $time_offset);
my $zulu = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ", 1900 + $year, $mon + 1, $mday, $hour, $min, $sec;
my $version = $pa_version;
my $endpoint = "webservices.amazon.com";
my $uri = "/onca/xml";
my %data = (Service => AWSECommerceService, AWSAccessKeyId => $awskey, SignatureMethod => ($sha1? HmacSHA1: HmacSHA256), SignatureVersion => 2, Version => $version, Timestamp => $zulu, @p);
for (sort keys %data)
{
if ($service eq "sqs" && $_ eq "QueueUri")
{
$queue = $data{$_};
next;
}
$url .= "&" if $url;
$url .= "$_=@{[encode_url($data{$_})]}";
}
my $sig = sign("GET\n$endpoint\n$uri\n$url", $data{SignatureMethod});
$url = "http://$endpoint$uri?Signature=@{[encode_url($sig)]}&$url";
return $url if $request;
local($/); # return a string regardless of wantarray
print "$url\n" if $v >= 2;
my $cmd = qq[$curl $curl_options $insecureaws @{[cq($url)]}];
print STDERR "cmd=[$cmd]\n" if $v;
qx[$cmd];
}
sub ec2
{
my $service = shift;
$expire_time ||= 30; # force it to use Expires rather than Timestamp, so it expires more quickly
my($sec, $min, $hour, $mday, $mon, $year, undef, undef, undef) = gmtime(time + $time_offset + $expire_time);
my $zulu = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ", 1900 + $year, $mon + 1, $mday, $hour, $min, $sec;
my($version, $endpoint);
$version .= $ec2_version if $service eq "ec2";
$version .= $sqs_version if $service eq "sqs";
$version .= $elb_version if $service eq "elb";
$version .= $sdb_version if $service eq "sdb";
$version .= $iam_version if $service eq "iam";
$version .= $ebn_version if $service eq "ebn";
$version .= $cloudformation_version if $service eq "cloudformation";
$version .= $rds_version if $service eq "rds";
# see http://developer.amazonwebservices.com/connect/entry!default.jspa?externalID=3912
$region = {eu => "eu-west-1", us => "us-east-1", uswest => "us-west-1", ap => "ap-southeast-1"}->{lc $region} || $region;
if ($service eq "ec2")
{
$endpoint = "ec2.amazonaws.com";
$endpoint = "ec2.$region.amazonaws.com" if $region;
$sha1 = 1 if $region; # sha256 does not seem to work with region specified
}
if ($service eq "sqs")
{
$endpoint = "queue.amazonaws.com";
$endpoint = "sqs.$region.amazonaws.com" if $region;
}
if ($service eq "elb")
{
$endpoint = "elasticloadbalancing.amazonaws.com";
$endpoint = "elasticloadbalancing.$region.amazonaws.com" if $region;
}
if ($service eq "sdb")
{
$endpoint = "sdb.amazonaws.com";
# go figure: us-east-1 isn't name served
$endpoint = "sdb.$region.amazonaws.com" if $region && $region ne "us-east-1";
}
if ($service eq "iam")
{
$endpoint = "iam.amazonaws.com";
# $endpoint = "iam.$region.amazonaws.com" if $region;
}
if ( $service eq "ebn" )
{
$endpoint = "elasticbeanstalk.us-east-1.amazonaws.com";
$endpoint = "elasticbeanstalk.$region.amazonaws.com" if $region;
}
if ( $service eq "cloudformation")
{
$endpoint = "cloudformation.us-east-1.amazonaws.com";
$endpoint = "cloudformation.$region.amazonaws.com" if $region;
}
if ( $service eq "rds")
{
$endpoint = "rds.us-east-1.amazonaws.com";
$endpoint = "rds.$region.amazonaws.com" if $region;
}
my @session = (SecurityToken => $session) if $session;
my %data = (AWSAccessKeyId => $awskey, @session, SignatureMethod => ($sha1? HmacSHA1: HmacSHA256), SignatureVersion => $AWS4? 4: 2, Version => $version, ($expire_time? Expires: Timestamp) => $zulu, @_);
$queue ||= "/";
my($url);
for (sort keys %data)
{
if ($service eq "sqs" && $_ eq "QueueUri")
{
$queue = $data{$_};
next;
}
$url .= "&" if $url;
$url .= "$_=@{[encode_url_slash($data{$_})]}";
}
if ($AWS4) {
$url = signAWS4($service, GET, "$scheme://$endpoint$queue?$url", $expire_time);
} else {
my $sig = sign("GET\n$endpoint\n$queue\n$url", $data{SignatureMethod});
$url = "$scheme://$endpoint$queue?Signature=@{[encode_url($sig)]}&$url";
}
return $url if $request;
local($/); # return a string regardless of wantarray
print "$url\n" if $v >= 2;
my $cmd = qq[$curl $curl_options $insecureaws @{[cq($url)]}];
print STDERR "cmd=[$cmd]\n" if $v;
my $ans = qx[$cmd];
$exit_code = $? >> 8;
$ans;
}
sub encode_url_slash
{
my($s) = @_;
$s =~ s/([^\-\.0-9a-z\_\~])/%@{[uc unpack(H2,$1)]}/ig;
$s;
}
sub encode_url
{
my($s) = @_;
$s =~ s/([^\/\-\.0-9a-z\_\~])/%@{[uc unpack(H2,$1)]}/ig;
$s;
}
sub decode_url
{
my($s) = @_;
$s =~ s/%(..)/@{[uc pack(H2,$1)]}/ig;
$s;
}
#
# Encode a sqs message. What characters should be encoded. According to http://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/Query_QuerySendMessage.html
# not very many characters should be encoded. However, I am disinclined to allow TAB, LF, and CR to be in the message unencoded, so we'll encode them for now. Also, the
# code currently does not handle Unicode characters. It should probably utf8-encode first.
#
# The existing decode_url can be used to decode.
#
sub encode_message
{
my($s) = @_;
$s =~ s/([^\x20-\x7e])/%@{[uc unpack(H2,$1)]}/ig;
$s;
}
sub dentity
{
my($s) = @_;
for ($s)
{
s/&\#x([0-9a-f]{1,2});/pack(C, hex($1))/iseg;
s/&(.*?);/({quot => '"', amp => "&", apos => "'", lt => "<", gt => ">"}->{$1} || "&$1;")/seg;
}
$s;
}
sub printable
{
my($s) = @_;
$s =~ s/[\x00-\x1f\x7f]/\?/sg;
$s;
}
sub load_file
{
my $fn = shift;
my $io = new IO::File($fn)
or die "$fn: $!\n";
local($/);
<$io>;
}
sub save_file
{
my $nfn = my $fn = shift;
$nfn = ">$fn" if $fn !~ /^\s*[\>\|]/;
my $out = IO::File->new($nfn) or die "$fn: $!\n";
print $out join("", @_);
}
sub load_file_silent
{
my $fn = shift;
my $io = new IO::File($fn) or return;
local($/);
<$io>;
}
sub guess_is_unix
{
return 1 if $ENV{OS} =~ /windows/i && $ENV{OSTYPE} =~ /cygwin/i;
return 1 if $ENV{OS} !~ /windows/i;
return 0;
}
sub get_home_directory
{
return $ENV{HOME} || "$ENV{HOMEDRIVE}$ENV{HOMEPATH}" || "C:" if !$isUnix;
return (getpwuid($<))[7];
}
sub signAWS4
{
my($service, $verb, $url, $expire_time, $headers) = @_;
my $region = $region || "us-east-1";
my $date = sub {sprintf "%4d%02d%02d", 1900 + $_[5], 1 + $_[4], $_[3]}->(gmtime);
my $time = sub {sprintf "%02d%02d%02d", $_[2], $_[1], $_[0]}->(gmtime);
my $timestamp = join("", $date, "T", $time, "Z");
my $scope = lc "$date/$region/$service/aws4_request";
my($CanonicalURL, $qs) = split(/\?/, $url, 2);
my $CanonicalURI = $CanonicalURL;
$CanonicalURI =~ s[^.+?://.+?/][/];
my @qs = split(/&/, $qs);
s/^(.+?)(?:=(.*))?$/$1=$2/ for @qs; # add = to parameters that have no value (e.g. &acl --> &acl=)
if (1)
{
push @qs, "X-Amz-Algorithm=AWS4-HMAC-SHA256";
push @qs, "X-Amz-Credential=$awskey/$scope";
push @qs, "X-Amz-Date=$timestamp";
push @qs, "X-Amz-Expires=$expire_time";
push @qs, "X-Amz-SignedHeaders=host";
}
@qs = sort @qs;
my $CanonicalQs = join("&", map {/^(.+?)=(.*)$/; encode_url_slash($1) . "=" . encode_url_slash($2)} @qs);
my($host) = $CanonicalURL =~ /^.+?:\/\/(.+?)\//;
push @$headers, "Host: $host";
if (0)
{
push @$headers, "X-Amz-Algorithm=AWS4-HMAC-SHA256";
push @$headers, "X-Amz-Credential=" . encode_url("$awskey/$scope");
push @$headers, "X-Amz-Date=$timestamp";
push @$headers, "X-Amz-Expires=$expire_time";
push @$headers, "X-Amz-SignedHeaders=host";
}
s/^(.+?):\s*(.*)$/\L$1\E:$2/ for @$headers;
my $CanonicalHeaders = join("\n", @$headers, undef);
my $SignedHeaders = join(";", sort map {/^(.+?):/; $1} @$headers);
my $HashedPayload = "UNSIGNED-PAYLOAD";
if ($service ne "s3") {
# S3 and the other services do not agree on this parameter.
# For UNSIGNED-PAYLOAD, see http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html.
# It doesn't work for EC2.
$HashedPayload = sha256_hex("")
};
my $creq = join("\n", $verb, $CanonicalURI, $CanonicalQs, $CanonicalHeaders, $SignedHeaders, $HashedPayload);
my $creq_hash = sha256_hex($creq);
my $sts = join("\n", "AWS4-HMAC-SHA256", $timestamp, $scope, $creq_hash);
my $key = "AWS4$secret";
for (split(/\//, $scope))
{
$key = hmac_sha256($_, $key);
}
my $sig = hmac_sha256_hex($sts, $key);
my $url = $CanonicalURL;
$url = $url . ($url =~ /\?/? '&': '?') . $CanonicalQs;
$url = $url . ($url =~ /\?/? '&': '?') . "X-Amz-Signature=$sig";
if ($v)
{
(my $pretty = $s) =~ s/\n/\\n/sg;
print STDERR "data = [$pretty]\n";
}
$url;
}
sub pad4
{
my($s) = @_;
$s . "=" x (3 - (length($s) + 3) % 4);
}
sub sign
{
my($data, $shaX) = @_;
if ($v)
{
(my $pretty = $data) =~ s/\n/\\n/sg;
print STDERR "data = [$pretty]\n";
}
return pad4(hmac_sha256_base64($data, $secret)) if $shaX eq HmacSHA256;
pad4(hmac_sha1_base64($data, $secret));
}
sub R53_xml_data {
return { 'header' => '',
'POST|' => '',
'POST|rrset' => ' ',
};
}
sub parse_filter
{
my($name, $value) = $_[0] =~ /^(.*?)=(.*)$/;
my(@result);
push @result, "Filter.N.Name" => $name, "Filter.N.Value.1" => $value;
[@result];
}
sub parse_block_device_mapping
{
my($dev, $vol, $size, $delete, $type, $iops) = $_[0] =~ /^(.*?)(?:=(.*?)(?::(.*?)(?::(.*?)(?::(.*?)(?::(.*?))?)?)?)?)?$/;
#print "dev=$dev\nvol=$vol\nsize=$size\ndelete=$delete\n";
my(@result);
push @result, "BlockDeviceMapping.N.DeviceName" => $dev;
if ($vol eq "none")
{
push @result, "BlockDeviceMapping.N.NoDevice" => undef; # no "true" - it's a non-value item.
}
elsif ($vol =~ /^ephemeral/)
{
push @result, "BlockDeviceMapping.N.VirtualName" => $vol;
}
else
{
push @result, "BlockDeviceMapping.N.Ebs.SnapshotId" => $vol if $vol;
push @result, "BlockDeviceMapping.N.Ebs.VolumeSize" => $size if $size;
push @result, "BlockDeviceMapping.N.Ebs.DeleteOnTermination" => $delete if $delete;
push @result, "BlockDeviceMapping.N.Ebs.VolumeType" => $type if $type;
push @result, "BlockDeviceMapping.N.Ebs.Iops" => $iops if defined($iops);
}
[@result];
}
sub parse_block_device_mapping_with_launch_specification
{
my $result = &parse_block_device_mapping;
for (my $i = 0; $i < @$result; $i += 2)
{
$result->[$i] = "LaunchSpecification.$result->[$i]";
}
$result;
}
sub parse_addpolicy_effect
{
parse_addpolicy_finish() if @parse_addpolicy_resource;
$parse_addpolicy_effect = lc $_[0] eq "allow"? "Allow": "Deny";
}
sub parse_addpolicy_action
{
@parse_addpolicy_action = () if @parse_addpolicy_resource;
@parse_addpolicy_resource = ();
parse_addpolicy_finish() if @parse_addpolicy_resource;
push @parse_addpolicy_action, $_[0];
}
sub parse_addpolicy_resource
{
push @parse_addpolicy_resource, $_[0];
my $effect = $parse_addpolicy_effect || "Allow";
my $action = qq["$parse_addpolicy_action[0]"];
$action = "[" . join(",", map{qq["$_"]} @parse_addpolicy_action) . "]" if @parse_addpolicy_action > 1;
my $resource = qq["$parse_addpolicy_resource[0]"];
$resource = "[" . join(",", map{qq["$_"]} @parse_addpolicy_resource) . "]" if @parse_addpolicy_resource > 1;
pop @parse_policy if @parse_addpolicy_resource > 1;
push @parse_policy, qq[\t\t{\n\t\t\t"Effect":"$effect",\n\t\t\t"Action":$action,\n\t\t\t"Resource":$resource\n\t\t}];
$parse_policy = qq[{\n\t"Statement":\n\t[\n@{[join(",\n", @parse_policy)]}\n\t]\n}\n];
@final_list = (PolicyDocument, $parse_policy);
}
sub parse_addpolicy_output
{
print $parse_policy;
}
sub parse_tags
{
my($key, $value) = split(/=/, $_[0], 2);
["Tag.N.Key" => $key, "Tag.N.Value" => $value];
}
sub parse_tags_describe
{
my($name, $value) = split(/=/, $_[0], 2);
my @value = split(/,/, $value);
my(@data);
push @data, "Filter.N.Name" => $name;
for (my $i = 1; $i <= @value; $i++)
{
push @data, , "Filter.N.Value.$i" => $value[$i - 1];
}
\@data;
}
sub parse_tags_delete
{
if ($_[0] =~ /=/)
{
my($name, $value) = split(/=/, $_[0], 2);
return ["Tag.N.Key" => $name, "Tag.N.Value" => $value];
}
["Tag.N.Key" => $_[0]];
}
sub parse_tags_member
{
my($key, $value) = split(/=/, $_[0], 2);
["Tags.member.N.Key" => $key, "Tags.member.N.Value" => $value];
}
sub cq
{
# quote for sending to curl via shell
my($s) = @_;
return "\"$s\"" if !$isUnix;
$s =~ s/\'/\'\\\'\'/g;
"'$s'";
}
sub curlq
{
# quote for sending URL to curl via shell
my($s) = @_;
return "\"$s\"" if !$isUnix;
$s =~ s/[\'\ \+\#]/%@{[unpack(H2, $1)]}/g;
$s
}
sub xcmp
{
my($a, $b) = @_? @_: ($a, $b);
my @a = split(//, $a);
my @b = split(//, $b);
for (;;)
{
return @a - @b unless @a && @b;
last if $a[0] cmp $b[0];
shift @a;
shift @b;
}
my $cmp = $a[0] cmp $b[0];
for (;;)
{
return ($a[0] =~ /\d/) - ($b[0] =~ /\d/) if ($a[0] =~ /\d/) - ($b[0] =~ /\d/);
last unless (shift @a) =~ /\d/ && (shift @b) =~ /\d/;
}
return $cmp;
}