diff --git a/aws-sdk-core/lib/aws-sdk-core/partitions.rb b/aws-sdk-core/lib/aws-sdk-core/partitions.rb index cd05d8dda8a..a4c1a69341c 100644 --- a/aws-sdk-core/lib/aws-sdk-core/partitions.rb +++ b/aws-sdk-core/lib/aws-sdk-core/partitions.rb @@ -10,7 +10,7 @@ class << self # @param [Hash] new_partitions # @api private - def add_partitions(new_partitions) + def add(new_partitions) new_partitions['partitions'].each do |partition| default_list.add_partition(Partition.build(partition)) defaults['partitions'] << partition @@ -18,7 +18,7 @@ def add_partitions(new_partitions) end # @api private - def clear_partitions + def clear default_list.clear defaults['partitions'].clear end @@ -38,32 +38,16 @@ def default_list @default_list ||= PartitionList.build(defaults) end - # @param [String] svc_id - # @return [String] The service module name. - # @api priviate - def service_name(svc_id) - if service_names.key?(svc_id) - service_names[svc_id] - else - msg = "invalid service id #{svc_id.inspect}; valid service ids " - msg << "include %s" % [service_names.keys.join(', ')] - raise ArgumentError, msg - end - end - - # @return [Hash] Returns a hash of service id keys - # and service module name values. + # @return [Hash] Returns a map of service module names + # to their id as used in the endpoints.json document. # @api private - def service_names - @service_names ||= begin - service_models = "#{File.dirname(__FILE__)}/../../service-models.json" - service_models = Aws::Json.load_file(service_models) - service_models.inject({}) do |names, (name, artifact)| - svc_id = artifact.split('/').first - # TODO : Remove this once endpoints.json has been corrected - svc_id = 'data.iot' if svc_id == 'iot-data' - names[svc_id] = name - names + def service_ids + @service_ids ||= begin + services = "#{File.dirname(__FILE__)}/../../service-models.json" + services = Aws::Json.load_file(services) + services.inject({}) do |ids, (name, svc)| + ids[name] = svc['endpoint'] #if svc['endpoint'] + ids end end end diff --git a/aws-sdk-core/lib/aws-sdk-core/partitions/partition.rb b/aws-sdk-core/lib/aws-sdk-core/partitions/partition.rb index be53783591f..ed0ca6e12b1 100644 --- a/aws-sdk-core/lib/aws-sdk-core/partitions/partition.rb +++ b/aws-sdk-core/lib/aws-sdk-core/partitions/partition.rb @@ -77,10 +77,12 @@ def build_regions(partition) # @param [Hash] partition # @return [Hash] def build_services(partition) - partition['services'].inject({}) do |services, (svc_id, svc_data)| - if Partitions.service_names.key?(svc_id) - service = Service.build(svc_id, svc_data, partition) - services[service.name] = service + Partitions.service_ids.inject({}) do |services, (svc_name, svc_id)| + if partition['services'].key?(svc_id) + svc_data = partition['services'][svc_id] + services[svc_name] = Service.build(svc_name, svc_data, partition) + else + services[svc_name] = Service.build(svc_name, {'endpoints' => {}}, partition) end services end diff --git a/aws-sdk-core/lib/aws-sdk-core/partitions/region.rb b/aws-sdk-core/lib/aws-sdk-core/partitions/region.rb index c3fa0b755e5..839de4bca8d 100644 --- a/aws-sdk-core/lib/aws-sdk-core/partitions/region.rb +++ b/aws-sdk-core/lib/aws-sdk-core/partitions/region.rb @@ -7,14 +7,13 @@ class Region # @option options [required, String] :name # @option options [required, String] :description # @option options [required, String] :partition_name - # @option options [required, Set] :service_names + # @option options [required, Set] :services # @api private def initialize(options = {}) @name = options[:name] @description = options[:description] - @partition = options[:partition] @partition_name = options[:partition_name] - @service_names = options[:service_names] + @services = options[:services] end # @return [String] The name of this region, e.g. "us-east-1". @@ -30,7 +29,7 @@ def initialize(options = {}) # @return [Set] The list of services available in this region. # Service names are the module names as used by the AWS SDK # for Ruby. - attr_reader :service_names + attr_reader :services class << self @@ -40,21 +39,39 @@ def build(region_name, region, partition) name: region_name, description: region['description'], partition_name: partition['partition'], - service_names: region_services(region_name, partition) + services: region_services(region_name, partition) ) end private def region_services(region_name, partition) - partition['services'].inject(Set.new) do |services, (svc_id, svc)| - if svc['endpoints'].key?(region_name) && Partitions.service_names.key?(svc_id) - services << Partitions.service_name(svc_id) + Partitions.service_ids.inject(Set.new) do |services, (svc_name, svc_id)| + if svc = partition['services'][svc_id] + services << svc_name if service_in_region?(svc, region_name) + else + #raise "missing endpoints for #{svc_name} / #{svc_id}" end services end end + def service_in_region?(svc, region_name) + svc_endpoints_contains_region?(svc, region_name) || + svc_partition_endpoint_matches_region?(svc, region_name) + end + + def svc_endpoints_contains_region?(svc, region_name) + svc['endpoints'].key?(region_name) + end + + def svc_partition_endpoint_matches_region?(svc, region_name) + if pe = svc['partitionEndpoint'] + region = svc['endpoints'][pe].fetch('credentialScope', {})['region'] + region == region_name + end + end + end end end diff --git a/aws-sdk-core/lib/aws-sdk-core/partitions/service.rb b/aws-sdk-core/lib/aws-sdk-core/partitions/service.rb index 3431ec767bf..fb91df6a574 100644 --- a/aws-sdk-core/lib/aws-sdk-core/partitions/service.rb +++ b/aws-sdk-core/lib/aws-sdk-core/partitions/service.rb @@ -8,14 +8,15 @@ class Service # @option options [required, String] :partition_name # @option options [required, Set] :region_name # @option options [required, Boolean] :regionalized - # @option options [String] :partition_endpoint + # @option options [String] :partition_region # @api private def initialize(options = {}) @name = options[:name] @partition_name = options[:partition_name] - @region_names = options[:region_names] + @regions = options[:regions] @regionalized = options[:regionalized] - @partition_endpoint = options[:partition_endpoint] + @partition_region = options[:partition_region] + @regions << @partition_region if !@regionalized end # @return [String] The name of this service. The name is the module @@ -27,11 +28,11 @@ def initialize(options = {}) # @return [Set] The regions this service is available in. # Regions are scoped to the partition. - attr_reader :region_names + attr_reader :regions # @return [String,nil] The global patition endpoint for this service. # May be `nil`. - attr_reader :partition_endpoint + attr_reader :partition_region # Returns `false` if the service operates with a single global # endpoint for the current partition, returns `true` if the service @@ -47,23 +48,38 @@ def regionalized? class << self # @api private - def build(svc_id, service, partition) + def build(service_name, service, partition) Service.new( - name: Partitions.service_name(svc_id), + name: service_name, partition_name: partition['partition'], - region_names: region_names(service, partition), + regions: regions(service, partition), regionalized: service['isRegionalized'] != false, - partition_endpoint: service['partitionEndpoint'] + partition_region: partition_region(service) ) end private - def region_names(service, partition) + def regions(service, partition) names = Set.new(partition['regions'].keys & service['endpoints'].keys) names - ["#{partition['partition']}-global"] end + def partition_region(service) + if service['partitionEndpoint'] + endpoint = service['endpoints'][service['partitionEndpoint']] + region = if endpoint['credentialScope'] + endpoint['credentialScope']['region'] + elsif service['defaults'] && service['defaults']['credentialScope'] + service['defaults']['credentialScope']['region'] + end + unless region + raise "missing partition endpoint region for #{service.inspect}" + end + region + end + end + end end end diff --git a/aws-sdk-core/service-models.json b/aws-sdk-core/service-models.json index 573d0363a6d..cb76fab6f9a 100644 --- a/aws-sdk-core/service-models.json +++ b/aws-sdk-core/service-models.json @@ -1,70 +1,273 @@ { - "ACM": "acm/2015-12-08", - "APIGateway": "apigateway/2015-07-09", - "AutoScaling": "autoscaling/2011-01-01", - "CloudFormation": "cloudformation/2010-05-15", - "CloudFront": "cloudfront/2016-01-28", - "CloudHSM": "cloudhsm/2014-05-30", - "CloudSearch": "cloudsearch/2013-01-01", - "CloudSearchDomain": "cloudsearchdomain/2013-01-01", - "CloudTrail": "cloudtrail/2013-11-01", - "CloudWatch": "monitoring/2010-08-01", - "CloudWatchEvents": "events/2015-10-07", - "CloudWatchLogs": "logs/2014-03-28", - "CodeCommit": "codecommit/2015-04-13", - "CodeDeploy": "codedeploy/2014-10-06", - "CodePipeline": "codepipeline/2015-07-09", - "CognitoIdentity": "cognito-identity/2014-06-30", - "CognitoIdentityProvider": "cognito-idp/2016-04-18", - "CognitoSync": "cognito-sync/2014-06-30", - "ConfigService": "config/2014-11-12", - "DatabaseMigrationService": "dms/2016-01-01", - "DataPipeline": "datapipeline/2012-10-29", - "DeviceFarm": "devicefarm/2015-06-23", - "DirectConnect": "directconnect/2012-10-25", - "DirectoryService": "ds/2015-04-16", - "DynamoDB": "dynamodb/2012-08-10", - "DynamoDBStreams": "streams.dynamodb/2012-08-10", - "EC2": "ec2/2015-10-01", - "ECR": "ecr/2015-09-21", - "ECS": "ecs/2014-11-13", - "EFS": "elasticfilesystem/2015-02-01", - "ElastiCache": "elasticache/2015-02-02", - "ElasticBeanstalk": "elasticbeanstalk/2010-12-01", - "ElasticLoadBalancing": "elasticloadbalancing/2012-06-01", - "ElasticsearchService": "es/2015-01-01", - "ElasticTranscoder": "elastictranscoder/2012-09-25", - "EMR": "elasticmapreduce/2009-03-31", - "Firehose": "firehose/2015-08-04", - "GameLift": "gamelift/2015-10-01", - "Glacier": "glacier/2012-06-01", - "IAM": "iam/2010-05-08", - "ImportExport": "importexport/2010-06-01", - "Inspector": "inspector/2016-02-16", - "IoT": "iot/2015-05-28", - "IoTDataPlane": "iot-data/2015-05-28", - "Kinesis": "kinesis/2013-12-02", - "KMS": "kms/2014-11-01", - "Lambda": "lambda/2015-03-31", - "LambdaPreview": "lambda/2014-11-11", - "MachineLearning": "machinelearning/2014-12-12", - "MarketplaceCommerceAnalytics": "marketplacecommerceanalytics/2015-07-01", - "MarketplaceMetering": "meteringmarketplace/2016-01-14", - "OpsWorks": "opsworks/2013-02-18", - "RDS": "rds/2014-10-31", - "Redshift": "redshift/2012-12-01", - "Route53": "route53/2013-04-01", - "Route53Domains": "route53domains/2014-05-15", - "S3": "s3/2006-03-01", - "SES": "email/2010-12-01", - "SimpleDB": "sdb/2009-04-15", - "SNS": "sns/2010-03-31", - "SQS": "sqs/2012-11-05", - "SSM": "ssm/2014-11-06", - "StorageGateway": "storagegateway/2013-06-30", - "STS": "sts/2011-06-15", - "Support": "support/2013-04-15", - "SWF": "swf/2012-01-25", - "WAF": "waf/2015-08-24", - "WorkSpaces": "workspaces/2015-04-08" + "ACM": { + "models": "acm/2015-12-08", + "endpoint": "acm" + }, + "APIGateway": { + "models": "apigateway/2015-07-09", + "endpoint": "apigateway" + }, + "AutoScaling": { + "models": "autoscaling/2011-01-01", + "endpoint": "autoscaling" + }, + "CloudFormation": { + "models": "cloudformation/2010-05-15", + "endpoint": "cloudformation" + }, + "CloudFront": { + "models": "cloudfront/2016-01-28", + "endpoint": "cloudfront" + }, + "CloudHSM": { + "models": "cloudhsm/2014-05-30", + "endpoint": "cloudhsm" + }, + "CloudSearch": { + "models": "cloudsearch/2013-01-01", + "endpoint": "cloudsearch" + }, + "CloudSearchDomain": { + "models": "cloudsearchdomain/2013-01-01" + }, + "CloudTrail": { + "models": "cloudtrail/2013-11-01", + "endpoint": "cloudtrail" + }, + "CloudWatch": { + "models": "monitoring/2010-08-01", + "endpoint": "monitoring" + }, + "CloudWatchEvents": { + "models": "events/2015-10-07", + "endpoint": "events" + }, + "CloudWatchLogs": { + "models": "logs/2014-03-28", + "endpoint": "logs" + }, + "CodeCommit": { + "models": "codecommit/2015-04-13", + "endpoint": "codecommit" + }, + "CodeDeploy": { + "models": "codedeploy/2014-10-06", + "endpoint": "codedeploy" + }, + "CodePipeline": { + "models": "codepipeline/2015-07-09", + "endpoint": "codepipeline" + }, + "CognitoIdentity": { + "models": "cognito-identity/2014-06-30", + "endpoint": "cognito-identity" + }, + "CognitoIdentityProvider": { + "models": "cognito-idp/2016-04-18", + "endpoint": "cognito-idp" + }, + "CognitoSync": { + "models": "cognito-sync/2014-06-30", + "endpoint": "cognito-sync" + }, + "ConfigService": { + "models": "config/2014-11-12", + "endpoint": "config" + }, + "DatabaseMigrationService": { + "models": "dms/2016-01-01", + "endpoint": "dms" + }, + "DataPipeline": { + "models": "datapipeline/2012-10-29", + "endpoint": "datapipeline" + }, + "DeviceFarm": { + "models": "devicefarm/2015-06-23", + "endpoint": "devicefarm" + }, + "DirectConnect": { + "models": "directconnect/2012-10-25", + "endpoint": "directconnect" + }, + "DirectoryService": { + "models": "ds/2015-04-16", + "endpoint": "ds" + }, + "DynamoDB": { + "models": "dynamodb/2012-08-10", + "endpoint": "dynamodb" + }, + "DynamoDBStreams": { + "models": "streams.dynamodb/2012-08-10", + "endpoint": "streams.dynamodb" + }, + "EC2": { + "models": "ec2/2015-10-01", + "endpoint": "ec2" + }, + "ECR": { + "models": "ecr/2015-09-21", + "endpoint": "ecr" + }, + "ECS": { + "models": "ecs/2014-11-13", + "endpoint": "ecs" + }, + "EFS": { + "models": "elasticfilesystem/2015-02-01", + "endpoint": "elasticfilesystem" + }, + "ElastiCache": { + "models": "elasticache/2015-02-02", + "endpoint": "elasticache" + }, + "ElasticBeanstalk": { + "models": "elasticbeanstalk/2010-12-01", + "endpoint": "elasticbeanstalk" + }, + "ElasticLoadBalancing": { + "models": "elasticloadbalancing/2012-06-01", + "endpoint": "elasticloadbalancing" + }, + "ElasticsearchService": { + "models": "es/2015-01-01", + "endpoint": "es" + }, + "ElasticTranscoder": { + "models": "elastictranscoder/2012-09-25", + "endpoint": "elastictranscoder" + }, + "EMR": { + "models": "elasticmapreduce/2009-03-31", + "endpoint": "elasticmapreduce" + }, + "Firehose": { + "models": "firehose/2015-08-04", + "endpoint": "firehose" + }, + "GameLift": { + "models": "gamelift/2015-10-01", + "endpoint": "gamelift" + }, + "Glacier": { + "models": "glacier/2012-06-01", + "endpoint": "glacier" + }, + "IAM": { + "models": "iam/2010-05-08", + "endpoint": "iam" + }, + "ImportExport": { + "models": "importexport/2010-06-01", + "endpoint": "importexport" + }, + "Inspector": { + "models": "inspector/2016-02-16", + "endpoint": "inspector" + }, + "IoT": { + "models": "iot/2015-05-28", + "endpoint": "iot" + }, + "IoTDataPlane": { + "models": "iot-data/2015-05-28", + "endpoint": "data.iot" + }, + "Kinesis": { + "models": "kinesis/2013-12-02", + "endpoint": "kinesis" + }, + "KMS": { + "models": "kms/2014-11-01", + "endpoint": "kms" + }, + "Lambda": { + "models": "lambda/2015-03-31", + "endpoint": "lambda" + }, + "LambdaPreview": { + "models": "lambda/2014-11-11", + "endpoint": "lambda" + }, + "MachineLearning": { + "models": "machinelearning/2014-12-12", + "endpoint": "machinelearning" + }, + "MarketplaceCommerceAnalytics": { + "models": "marketplacecommerceanalytics/2015-07-01", + "endpoint": "marketplacecommerceanalytics" + }, + "MarketplaceMetering": { + "models": "meteringmarketplace/2016-01-14", + "endpoint": "metering.marketplace" + }, + "OpsWorks": { + "models": "opsworks/2013-02-18", + "endpoint": "opsworks" + }, + "RDS": { + "models": "rds/2014-10-31", + "endpoint": "rds" + }, + "Redshift": { + "models": "redshift/2012-12-01", + "endpoint": "redshift" + }, + "Route53": { + "models": "route53/2013-04-01", + "endpoint": "route53" + }, + "Route53Domains": { + "models": "route53domains/2014-05-15", + "endpoint": "route53domains" + }, + "S3": { + "models": "s3/2006-03-01", + "endpoint": "s3" + }, + "SES": { + "models": "email/2010-12-01", + "endpoint": "email" + }, + "SimpleDB": { + "models": "sdb/2009-04-15", + "endpoint": "sdb" + }, + "SNS": { + "models": "sns/2010-03-31", + "endpoint": "sns" + }, + "SQS": { + "models": "sqs/2012-11-05", + "endpoint": "sqs" + }, + "SSM": { + "models": "ssm/2014-11-06", + "endpoint": "ssm" + }, + "StorageGateway": { + "models": "storagegateway/2013-06-30", + "endpoint": "storagegateway" + }, + "STS": { + "models": "sts/2011-06-15", + "endpoint": "sts" + }, + "Support": { + "models": "support/2013-04-15", + "endpoint": "support" + }, + "SWF": { + "models": "swf/2012-01-25", + "endpoint": "swf" + }, + "WAF": { + "models": "waf/2015-08-24", + "endpoint": "waf" + }, + "WorkSpaces": { + "models": "workspaces/2015-04-08", + "endpoint": "workspaces" + } } diff --git a/aws-sdk-core/spec/aws/partitions_spec.rb b/aws-sdk-core/spec/aws/partitions_spec.rb new file mode 100644 index 00000000000..b599f604404 --- /dev/null +++ b/aws-sdk-core/spec/aws/partitions_spec.rb @@ -0,0 +1,230 @@ +require 'spec_helper' + +module Aws + module Partitions + describe 'Partitions' do + + let(:us_east_1_services) do + services = Aws::SERVICE_MODULE_NAMES.sort + services -= ['CloudSearchDomain'] # user endpoints only + services -= ['DeviceFarm'] # us-west-2 only + services -= ['EFS'] # us-west-2 only + services + end + + describe '.partition' do + + %w(aws aws-cn aws-us-gov).each do |p| + it "can return a partition by name: #{p.inspect}" do + partition = Aws.partition(p) + expect(partition).to be_kind_of(Partition) + expect(partition.name).to eq(p) + end + end + + it 'raises ArgumentError on unknown partition names' do + expect { + Aws.partition('fake-name') + }.to raise_error(ArgumentError, /invalid partition name/) + end + + end + + describe '.partitions' do + + it 'returns a list of Partition objects' do + partitions = Aws.partitions + expect(partitions.map(&:name)).to eq(%w(aws aws-cn aws-us-gov)) + partitions.each do |p| + expect(p).to be_kind_of(Partition) + end + end + + end + + describe Partition do + + describe '#name' do + + it 'returns the partition name' do + expect(Aws.partition('aws').name).to eq('aws') + end + + end + + describe '#region' do + + it 'gets a region by name' do + region = Aws.partition('aws').region('us-east-1') + expect(region).to be_kind_of(Partitions::Region) + expect(region.name).to eq('us-east-1') + end + + it 'returns a list of supported services with the region' do + region = Aws.partition('aws').region('us-east-1') + expect(region.services.sort).to eq(us_east_1_services) + end + + it 'raises ArgumentError for unknown regions' do + expect { + Aws.partition('aws').region('us-EAST-1') # wrong format + }.to raise_error(ArgumentError, /invalid region name/) + end + + it 'provides a list of valid region names in the argument error' do + expect { + Aws.partition('aws').region('fake-region') + }.to raise_error(ArgumentError, /us-east-1, /) + end + + end + + describe '#regions' do + + it 'returns an array of Region objects' do + expect(Aws.partition('aws').regions).to be_kind_of(Array) + end + + it 'includes regions from the current partition' do + { + 'aws' => 'us-east-1', + 'aws-cn' => 'cn-north-1', + 'aws-us-gov' => 'us-gov-west-1', + }.each_pair do |p, r| + expect(Aws.partition(p).regions.map(&:name)).to include(r) + end + end + + it 'does not include the partition global endpoint name' do + regions = Aws.partition('aws').regions + expect(regions.map(&:name)).not_to include('aws-global') + end + + end + + describe '#service' do + + Aws::SERVICE_MODULE_NAMES.each do |svc_name| + next if svc_name == 'CloudSearchDomain' + it "can return a service by name: #{svc_name}" do + service = Aws.partition('aws').service(svc_name) + expect(service).to be_kind_of(Partitions::Service) + expect(service.name).to eq(svc_name) + end + end + + it 'raises ArgumentError for unknown regions' do + expect { + Aws.partition('aws').region('us-EAST-1') # wrong format + }.to raise_error(ArgumentError, /invalid region name/) + end + + it 'provides a list of valid region names in the argument error' do + expect { + Aws.partition('aws').region('fake-region') + }.to raise_error(ArgumentError, /us-east-1, /) + end + end + + describe '#services' do + + it 'returns a list of supported services' do + services = Aws.partition('aws').services + expect(services.map(&:name).sort).to eq(Aws::SERVICE_MODULE_NAMES.sort) + end + + end + + end + + describe Region do + + it '#name returns the region name' do + region = Aws.partition('aws').region('us-east-1') + expect(region.name).to eq('us-east-1') + end + + it '#description returns the region name' do + region = Aws.partition('aws').region('us-east-1') + expect(region.description).to eq('US East (N. Virginia)') + end + + it '#partition_name returns the partition name' do + region = Aws.partition('aws').region('us-east-1') + expect(region.partition_name).to eq('aws') + end + + it '#services returns the list of services available in region' do + region = Aws.partition('aws').region('us-east-1') + expect(region.services.sort).to eq(us_east_1_services) + end + + end + + describe Service do + + it '#name returns the service name' do + expect(Aws.partition('aws').service('IAM').name).to eq('IAM') + end + + it '#partition_name returns the parition name' do + expect(Aws.partition('aws').service('IAM').partition_name).to eq('aws') + end + + it '#regions returns partition regions for the service' do + expect(Aws.partition('aws').service('IAM').partition_name).to eq('aws') + end + + it '#partition_region returns the partition global endpoint region' do + svc = Aws.partition('aws').service('IAM') + expect(svc.partition_region).to eq('us-east-1') + svc = Aws.partition('aws-cn').service('IAM') + expect(svc.partition_region).to eq('cn-north-1') + svc = Aws.partition('aws').service('EC2') + expect(svc.partition_region).to be(nil) + end + + it '#regionalized? returns true if the service is regionalized' do + svc = Aws.partition('aws').service('IAM') + expect(svc.regionalized?).to be(false) + svc = Aws.partition('aws').service('EC2') + expect(svc.regionalized?).to be(true) + end + + it '#regions returns the list of regions the service is available in' do + svc = Aws.partition('aws').service('IAM') + expect(svc.regions.sort).to eq(%w(us-east-1)) + svc = Aws.partition('aws').service('EC2') + expect(svc.regions).to include('us-east-1') + expect(svc.regions).to include('us-west-1') + expect(svc.regions).to include('us-west-2') + end + + end + + describe 'symmetry' do + Aws.partitions.each do |partition| + + partition.services.each do |service| + service.regions.each do |region| + it "#{partition.name} : service #{service.name} : region #{region}" do + expect(partition.region(region).services).to include(service.name) + end + end + end + + partition.regions.each do |region| + region.services.each do |service| + + it "#{partition.name} : region #{region.name} : service #{service}" do + expect(partition.service(service).regions).to include(region.name) + end + + end + end + + end + end + end + end +end