fog-aws 3.5.2 → 3.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +16 -3
  4. data/LICENSE.md +1 -1
  5. data/README.md +39 -6
  6. data/bin/console +14 -0
  7. data/bin/setup +8 -0
  8. data/fog-aws.gemspec +2 -2
  9. data/lib/fog/aws.rb +4 -0
  10. data/lib/fog/aws/elasticache.rb +4 -2
  11. data/lib/fog/aws/elb.rb +1 -1
  12. data/lib/fog/aws/elbv2.rb +72 -0
  13. data/lib/fog/aws/models/compute/flavors.rb +1544 -122
  14. data/lib/fog/aws/models/compute/snapshot.rb +7 -6
  15. data/lib/fog/aws/models/compute/vpc.rb +7 -1
  16. data/lib/fog/aws/models/storage/directory.rb +0 -1
  17. data/lib/fog/aws/models/storage/file.rb +3 -0
  18. data/lib/fog/aws/parsers/compute/create_snapshot.rb +1 -1
  19. data/lib/fog/aws/parsers/compute/create_subnet.rb +33 -6
  20. data/lib/fog/aws/parsers/compute/describe_subnets.rb +33 -6
  21. data/lib/fog/aws/parsers/dns/create_hosted_zone.rb +1 -1
  22. data/lib/fog/aws/parsers/dns/get_hosted_zone.rb +3 -3
  23. data/lib/fog/aws/parsers/dns/list_hosted_zones.rb +3 -1
  24. data/lib/fog/aws/parsers/elbv2/create_load_balancer.rb +88 -0
  25. data/lib/fog/aws/parsers/elbv2/describe_listeners.rb +110 -0
  26. data/lib/fog/aws/parsers/elbv2/describe_load_balancers.rb +88 -0
  27. data/lib/fog/aws/parsers/elbv2/describe_tags.rb +53 -0
  28. data/lib/fog/aws/parsers/elbv2/empty.rb +10 -0
  29. data/lib/fog/aws/parsers/storage/get_object_tagging.rb +33 -0
  30. data/lib/fog/aws/parsers/sts/assume_role_with_web_identity.rb +1 -1
  31. data/lib/fog/aws/requests/compute/create_vpc.rb +2 -2
  32. data/lib/fog/aws/requests/elbv2/add_tags.rb +45 -0
  33. data/lib/fog/aws/requests/elbv2/create_load_balancer.rb +160 -0
  34. data/lib/fog/aws/requests/elbv2/describe_listeners.rb +38 -0
  35. data/lib/fog/aws/requests/elbv2/describe_load_balancers.rb +100 -0
  36. data/lib/fog/aws/requests/elbv2/describe_tags.rb +50 -0
  37. data/lib/fog/aws/requests/elbv2/remove_tags.rb +45 -0
  38. data/lib/fog/aws/requests/storage/get_object_tagging.rb +41 -0
  39. data/lib/fog/aws/requests/storage/put_object_tagging.rb +42 -0
  40. data/lib/fog/aws/requests/sts/assume_role_with_web_identity.rb +7 -6
  41. data/lib/fog/aws/storage.rb +2 -0
  42. data/lib/fog/aws/version.rb +1 -1
  43. data/tests/parsers/elbv2/create_load_balancer_tests.rb +48 -0
  44. data/tests/parsers/elbv2/describe_listeners_tests.rb +76 -0
  45. data/tests/parsers/elbv2/describe_load_balancers_tests.rb +54 -0
  46. data/tests/parsers/elbv2/describe_tags_tests.rb +35 -0
  47. data/tests/requests/compute/vpc_tests.rb +6 -0
  48. data/tests/requests/elbv2/helper.rb +66 -0
  49. data/tests/requests/elbv2/load_balancer_tests.rb +50 -0
  50. metadata +35 -10
@@ -7,12 +7,13 @@ module Fog
7
7
  attribute :description
8
8
  attribute :encrypted
9
9
  attribute :progress
10
- attribute :created_at, :aliases => 'startTime'
11
- attribute :owner_id, :aliases => 'ownerId'
12
- attribute :state, :aliases => 'status'
13
- attribute :tags, :aliases => 'tagSet'
14
- attribute :volume_id, :aliases => 'volumeId'
15
- attribute :volume_size, :aliases => 'volumeSize'
10
+ attribute :created_at, :aliases => 'startTime'
11
+ attribute :owner_id, :aliases => 'ownerId'
12
+ attribute :state, :aliases => 'status'
13
+ attribute :tags, :aliases => 'tagSet'
14
+ attribute :volume_id, :aliases => 'volumeId'
15
+ attribute :volume_size, :aliases => 'volumeSize'
16
+ attribute :status_message, :aliases => 'statusMessage'
16
17
 
17
18
  def destroy
18
19
  requires :id
@@ -106,7 +106,13 @@ module Fog
106
106
 
107
107
  def save
108
108
  requires :cidr_block
109
- data = service.create_vpc(cidr_block, { 'AmazonProvidedIpv6CidrBlock' => amazon_provided_ipv_6_cidr_block }).body['vpcSet'].first
109
+
110
+ options = {
111
+ 'AmazonProvidedIpv6CidrBlock' => amazon_provided_ipv_6_cidr_block,
112
+ 'InstanceTenancy' => tenancy
113
+ }
114
+
115
+ data = service.create_vpc(cidr_block, options).body['vpcSet'].first
110
116
  new_attributes = data.reject {|key,value| key == 'requestId'}
111
117
  new_attributes = data.reject {|key,value| key == 'requestId' || key == 'tagSet' }
112
118
  merge_attributes(new_attributes)
@@ -12,7 +12,6 @@ module Fog
12
12
  identity :key, :aliases => ['Name', 'name']
13
13
 
14
14
  attribute :creation_date, :aliases => 'CreationDate', :type => 'time'
15
- attribute :location, :aliases => 'LocationConstraint', :type => 'string'
16
15
 
17
16
  def acl=(new_acl)
18
17
  unless VALID_ACLS.include?(new_acl)
@@ -25,6 +25,7 @@ module Fog
25
25
  attribute :encryption_key, :aliases => 'x-amz-server-side-encryption-customer-key'
26
26
  attribute :version, :aliases => 'x-amz-version-id'
27
27
  attribute :kms_key_id, :aliases => 'x-amz-server-side-encryption-aws-kms-key-id'
28
+ attribute :tags, :aliases => 'x-amz-tagging'
28
29
 
29
30
  # @note Chunk size to use for multipart uploads.
30
31
  # Use small chunk sizes to minimize memory. E.g. 5242880 = 5mb
@@ -192,6 +193,7 @@ module Fog
192
193
  # @option options [String] expires sets number of seconds before AWS Object expires.
193
194
  # @option options [String] storage_class sets x-amz-storage-class HTTP header. Defaults to 'STANDARD'. Or, 'REDUCED_REDUNDANCY'
194
195
  # @option options [String] encryption sets HTTP encryption header. Set to 'AES256' to encrypt files at rest on S3
196
+ # @option options [String] tags sets x-amz-tagging HTTP header. For example, 'Org-Id=1' or 'Org-Id=1&Service=MyService'
195
197
  # @return [Boolean] true if no errors
196
198
  #
197
199
  def save(options = {})
@@ -208,6 +210,7 @@ module Fog
208
210
  options['Expires'] = expires if expires
209
211
  options.merge!(metadata)
210
212
  options['x-amz-storage-class'] = storage_class if storage_class
213
+ options['x-amz-tagging'] = tags if tags
211
214
  options.merge!(encryption_headers)
212
215
 
213
216
  # With a single PUT operation you can upload objects up to 5 GB in size. Automatically set MP for larger objects.
@@ -5,7 +5,7 @@ module Fog
5
5
  class CreateSnapshot < Fog::Parsers::Base
6
6
  def end_element(name)
7
7
  case name
8
- when 'description', 'ownerId', 'progress', 'snapshotId', 'status', 'volumeId'
8
+ when 'description', 'ownerId', 'progress', 'snapshotId', 'status', 'volumeId', 'statusMessage'
9
9
  @response[name] = value
10
10
  when 'requestId'
11
11
  @response[name] = value
@@ -7,6 +7,10 @@ module Fog
7
7
  @subnet = { 'tagSet' => {} }
8
8
  @response = { 'subnet' => [] }
9
9
  @tag = {}
10
+ @ipv6_cidr_block_association = {}
11
+ @in_tag_set = false
12
+ @in_ipv6_cidr_block_association_set = false
13
+ @in_cidr_block_state = false
10
14
  end
11
15
 
12
16
  def start_element(name, attrs = [])
@@ -14,19 +18,42 @@ module Fog
14
18
  case name
15
19
  when 'tagSet'
16
20
  @in_tag_set = true
21
+ when 'ipv6CidrBlockAssociationSet'
22
+ @in_ipv6_cidr_block_association_set = true
23
+ when 'ipv6CidrBlockState'
24
+ @in_cidr_block_state = true
17
25
  end
18
26
  end
19
27
 
20
28
  def end_element(name)
21
29
  if @in_tag_set
22
30
  case name
31
+ when 'item'
32
+ @subnet['tagSet'][@tag['key']] = @tag['value']
33
+ @tag = {}
34
+ when 'key', 'value'
35
+ @tag[name] = value
36
+ when 'tagSet'
37
+ @in_tag_set = false
38
+ end
39
+ elsif @in_ipv6_cidr_block_association_set
40
+ if @in_cidr_block_state
41
+ case name
42
+ when 'state'
43
+ @ipv6_cidr_block_association['ipv6CidrBlockState'] = { name => value }
44
+ when 'ipv6CidrBlockState'
45
+ @in_cidr_block_state = false
46
+ end
47
+ else
48
+ case name
23
49
  when 'item'
24
- @subnet['tagSet'][@tag['key']] = @tag['value']
25
- @tag = {}
26
- when 'key', 'value'
27
- @tag[name] = value
28
- when 'tagSet'
29
- @in_tag_set = false
50
+ @subnet['ipv6CidrBlockAssociationSet'] = @ipv6_cidr_block_association
51
+ @ipv6_cidr_block_association = {}
52
+ when 'ipv6CidrBlock', 'associationId'
53
+ @ipv6_cidr_block_association[name] = value
54
+ when 'ipv6CidrBlockAssociationSet'
55
+ @in_ipv6_cidr_block_association_set = false
56
+ end
30
57
  end
31
58
  else
32
59
  case name
@@ -7,6 +7,10 @@ module Fog
7
7
  @subnet = { 'tagSet' => {} }
8
8
  @response = { 'subnetSet' => [] }
9
9
  @tag = {}
10
+ @ipv6_cidr_block_association = {}
11
+ @in_tag_set = false
12
+ @in_ipv6_cidr_block_association_set = false
13
+ @in_cidr_block_state = false
10
14
  end
11
15
 
12
16
  def start_element(name, attrs = [])
@@ -14,19 +18,42 @@ module Fog
14
18
  case name
15
19
  when 'tagSet'
16
20
  @in_tag_set = true
21
+ when 'ipv6CidrBlockAssociationSet'
22
+ @in_ipv6_cidr_block_association_set = true
23
+ when 'ipv6CidrBlockState'
24
+ @in_cidr_block_state = true
17
25
  end
18
26
  end
19
27
 
20
28
  def end_element(name)
21
29
  if @in_tag_set
22
30
  case name
31
+ when 'item'
32
+ @subnet['tagSet'][@tag['key']] = @tag['value']
33
+ @tag = {}
34
+ when 'key', 'value'
35
+ @tag[name] = value
36
+ when 'tagSet'
37
+ @in_tag_set = false
38
+ end
39
+ elsif @in_ipv6_cidr_block_association_set
40
+ if @in_cidr_block_state
41
+ case name
42
+ when 'state'
43
+ @ipv6_cidr_block_association['ipv6CidrBlockState'] = { name => value }
44
+ when 'ipv6CidrBlockState'
45
+ @in_cidr_block_state = false
46
+ end
47
+ else
48
+ case name
23
49
  when 'item'
24
- @subnet['tagSet'][@tag['key']] = @tag['value']
25
- @tag = {}
26
- when 'key', 'value'
27
- @tag[name] = value
28
- when 'tagSet'
29
- @in_tag_set = false
50
+ @subnet['ipv6CidrBlockAssociationSet'] = @ipv6_cidr_block_association
51
+ @ipv6_cidr_block_association = {}
52
+ when 'ipv6CidrBlock', 'associationId'
53
+ @ipv6_cidr_block_association[name] = value
54
+ when 'ipv6CidrBlockAssociationSet'
55
+ @in_ipv6_cidr_block_association_set = false
56
+ end
30
57
  end
31
58
  else
32
59
  case name
@@ -16,7 +16,7 @@ module Fog
16
16
  case name
17
17
  when 'Id'
18
18
  @hosted_zone[name] = value.sub('/hostedzone/', '')
19
- when 'Name', 'CallerReference', 'Comment'
19
+ when 'Name', 'CallerReference', 'Comment', 'PrivateZone'
20
20
  @hosted_zone[name]= value
21
21
  when 'HostedZone'
22
22
  @response['HostedZone'] = @hosted_zone
@@ -17,14 +17,14 @@ module Fog
17
17
  case name
18
18
  when 'Id'
19
19
  @hosted_zone[name]= value.sub('/hostedzone/', '')
20
- when 'Name', 'CallerReference', 'Comment', 'PrivateZone', 'Config', 'ResourceRecordSetCount'
20
+ when 'Name', 'CallerReference', 'Comment', 'PrivateZone', 'Config'
21
21
  @hosted_zone[name]= value
22
+ when 'ResourceRecordSetCount'
23
+ @hosted_zone['ResourceRecordSetCount'] = value.to_i
22
24
  when 'HostedZone'
23
25
  @response['HostedZone'] = @hosted_zone
24
26
  @hosted_zone = {}
25
27
  @section = :name_servers
26
- when 'ResourceRecordSetCount'
27
- @response['ResourceRecordSetCount'] = value.to_i
28
28
  end
29
29
  elsif @section == :name_servers
30
30
  case name
@@ -13,8 +13,10 @@ module Fog
13
13
  case name
14
14
  when 'Id'
15
15
  @zone[name] = value.sub('/hostedzone/', '')
16
- when 'Name', 'CallerReference', 'Comment'
16
+ when 'Name', 'CallerReference', 'Comment', 'PrivateZone'
17
17
  @zone[name] = value
18
+ when 'ResourceRecordSetCount'
19
+ @zone['ResourceRecordSetCount'] = value.to_i
18
20
  when 'HostedZone'
19
21
  @hosted_zones << @zone
20
22
  @zone = {}
@@ -0,0 +1,88 @@
1
+ module Fog
2
+ module Parsers
3
+ module AWS
4
+ module ELBV2
5
+ class CreateLoadBalancer < Fog::Parsers::Base
6
+ def reset
7
+ reset_load_balancer
8
+ reset_availability_zone
9
+ @load_balancer_addresses = {}
10
+ @state = {}
11
+ @results = { 'LoadBalancers' => [] }
12
+ @response = { 'CreateLoadBalancerResult' => {}, 'ResponseMetadata' => {} }
13
+ end
14
+
15
+ def reset_load_balancer
16
+ @load_balancer = { 'SecurityGroups' => [], 'AvailabilityZones' => [] }
17
+ end
18
+
19
+ def reset_availability_zone
20
+ @availability_zone = { 'LoadBalancerAddresses' => [] }
21
+ end
22
+
23
+ def start_element(name, attrs = [])
24
+ super
25
+ case name
26
+ when 'AvailabilityZones'
27
+ @in_availability_zones = true
28
+ when 'LoadBalancerAddresses'
29
+ @in_load_balancer_addresses = true
30
+ when 'SecurityGroups'
31
+ @in_security_groups = true
32
+ when 'State'
33
+ @in_state = true
34
+ end
35
+ end
36
+
37
+ def end_element(name)
38
+ case name
39
+ when 'member'
40
+ if @in_availability_zones && @in_load_balancer_addresses
41
+ @availability_zone['LoadBalancerAddresses'] << @load_balancer_addresses
42
+ elsif @in_availability_zones
43
+ @load_balancer['AvailabilityZones'] << @availability_zone
44
+ reset_availability_zone
45
+ elsif @in_security_groups
46
+ @load_balancer['SecurityGroups'] << value
47
+ else
48
+ @results['LoadBalancers'] << @load_balancer
49
+ reset_load_balancer
50
+ end
51
+ when 'SubnetId', 'ZoneName'
52
+ @availability_zone[name] = value
53
+ when 'IpAddress', 'AllocationId'
54
+ @load_balancer_addresses[name] = value
55
+
56
+ when 'CanonicalHostedZoneName', 'CanonicalHostedZoneNameID', 'LoadBalancerName', 'DNSName', 'Scheme', 'Type',
57
+ 'LoadBalancerArn', 'IpAddressType', 'CanonicalHostedZoneId', 'VpcId'
58
+ @load_balancer[name] = value
59
+ when 'CreatedTime'
60
+ @load_balancer[name] = Time.parse(value)
61
+
62
+ when 'LoadBalancerAddresses'
63
+ @in_load_balancer_addresses = false
64
+ when 'AvailabilityZones'
65
+ @in_availability_zones = false
66
+ when 'SecurityGroups'
67
+ @in_security_groups = false
68
+ when 'State'
69
+ @in_state = false
70
+ @load_balancer[name] = @state
71
+ @state = {}
72
+ when 'Code'
73
+ @state[name] = value
74
+
75
+ when 'RequestId'
76
+ @response['ResponseMetadata'][name] = value
77
+
78
+ when 'NextMarker'
79
+ @results['NextMarker'] = value
80
+ when 'CreateLoadBalancerResponse'
81
+ @response['CreateLoadBalancerResult'] = @results
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,110 @@
1
+ module Fog
2
+ module Parsers
3
+ module AWS
4
+ module ELBV2
5
+ class DescribeListeners < Fog::Parsers::Base
6
+ def reset
7
+ reset_listener
8
+ @default_action = {}
9
+ @certificate = {}
10
+ @config = {}
11
+ @target_groups = []
12
+ @target_group = {}
13
+ @target_group_stickiness_config = {}
14
+ @results = { 'Listeners' => [] }
15
+ @response = { 'DescribeListenersResult' => {}, 'ResponseMetadata' => {} }
16
+ end
17
+
18
+ def reset_listener
19
+ @listener= { 'DefaultActions' => [], 'Certificates' => [] }
20
+ end
21
+
22
+ def start_element(name, attrs = [])
23
+ super
24
+ case name
25
+ when 'DefaultActions'
26
+ @in_default_actions = true
27
+ when 'Certificates'
28
+ @in_certificates = true
29
+ when 'TargetGroups'
30
+ @in_target_groups = true
31
+ when 'TargetGroupStickinessConfig'
32
+ @in_target_group_stickiness_config = true
33
+ end
34
+ end
35
+
36
+ def end_element(name)
37
+ if @in_default_actions
38
+ case name
39
+ when 'member'
40
+ if @in_target_groups
41
+ @target_groups << @target_group
42
+ @target_group = {}
43
+ else
44
+ @listener['DefaultActions'] << @default_action
45
+ @default_action = {}
46
+ end
47
+ when 'TargetGroupArn'
48
+ if @in_target_groups
49
+ @target_group[name] = value
50
+ else
51
+ @default_action[name] = value
52
+ end
53
+ when 'Weight'
54
+ @target_group[name] = value
55
+ when 'Type', 'Order'
56
+ @default_action[name] = value
57
+ when 'Path', 'Protocol', 'Port', 'Query', 'Host', 'StatusCode', 'ContentType',
58
+ 'MessageBody', 'StatusCode'
59
+ @config[name] = value
60
+ when 'RedirectConfig', 'ForwardConfig', 'FixedResponseConfig'
61
+ @default_action[name] = @config
62
+ @config = {}
63
+ when 'DurationSeconds', 'Enabled'
64
+ @target_group_stickiness_config[name] = value
65
+ when 'DefaultActions'
66
+ @in_default_actions = false
67
+ when 'TargetGroupStickinessConfig'
68
+ if @in_target_group_stickiness_config
69
+ @config['TargetGroupStickinessConfig'] = @target_group_stickiness_config
70
+ @in_target_group_stickiness_config = false
71
+ @target_group_stickiness_config = {}
72
+ end
73
+ when 'TargetGroups'
74
+ @config['TargetGroups'] = @target_groups
75
+ @in_target_groups = false
76
+ @target_groups = []
77
+ end
78
+ else
79
+ case name
80
+ when 'member'
81
+ if @in_certificates
82
+ @listener['Certificates'] << @certificate
83
+ @certificate = {}
84
+ else
85
+ @results['Listeners'] << @listener
86
+ reset_listener
87
+ end
88
+ when 'LoadBalancerArn', 'Protocol', 'Port', 'ListenerArn', 'SslPolicy'
89
+ @listener[name] = value
90
+ when 'CertificateArn'
91
+ @certificate[name] = value
92
+ when 'Certificates'
93
+ @in_certificates = false
94
+
95
+ when 'RequestId'
96
+ @response['ResponseMetadata'][name] = value
97
+
98
+ when 'NextMarker'
99
+ @results['NextMarker'] = value
100
+
101
+ when 'DescribeListenersResponse'
102
+ @response['DescribeListenersResult'] = @results
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,88 @@
1
+ module Fog
2
+ module Parsers
3
+ module AWS
4
+ module ELBV2
5
+ class DescribeLoadBalancers < Fog::Parsers::Base
6
+ def reset
7
+ reset_load_balancer
8
+ reset_availability_zone
9
+ @load_balancer_addresses = {}
10
+ @state = {}
11
+ @results = { 'LoadBalancers' => [] }
12
+ @response = { 'DescribeLoadBalancersResult' => {}, 'ResponseMetadata' => {} }
13
+ end
14
+
15
+ def reset_load_balancer
16
+ @load_balancer = { 'SecurityGroups' => [], 'AvailabilityZones' => [] }
17
+ end
18
+
19
+ def reset_availability_zone
20
+ @availability_zone = { 'LoadBalancerAddresses' => [] }
21
+ end
22
+
23
+ def start_element(name, attrs = [])
24
+ super
25
+ case name
26
+ when 'AvailabilityZones'
27
+ @in_availability_zones = true
28
+ when 'LoadBalancerAddresses'
29
+ @in_load_balancer_addresses = true
30
+ when 'SecurityGroups'
31
+ @in_security_groups = true
32
+ when 'State'
33
+ @in_state = true
34
+ end
35
+ end
36
+
37
+ def end_element(name)
38
+ case name
39
+ when 'member'
40
+ if @in_availability_zones && @in_load_balancer_addresses
41
+ @availability_zone['LoadBalancerAddresses'] << @load_balancer_addresses
42
+ elsif @in_availability_zones
43
+ @load_balancer['AvailabilityZones'] << @availability_zone
44
+ reset_availability_zone
45
+ elsif @in_security_groups
46
+ @load_balancer['SecurityGroups'] << value
47
+ else
48
+ @results['LoadBalancers'] << @load_balancer
49
+ reset_load_balancer
50
+ end
51
+ when 'SubnetId', 'ZoneName'
52
+ @availability_zone[name] = value
53
+ when 'IpAddress', 'AllocationId'
54
+ @load_balancer_addresses[name] = value
55
+
56
+ when 'CanonicalHostedZoneName', 'CanonicalHostedZoneNameID', 'LoadBalancerName', 'DNSName', 'Scheme', 'Type',
57
+ 'LoadBalancerArn', 'IpAddressType', 'CanonicalHostedZoneId', 'VpcId'
58
+ @load_balancer[name] = value
59
+ when 'CreatedTime'
60
+ @load_balancer[name] = Time.parse(value)
61
+
62
+ when 'LoadBalancerAddresses'
63
+ @in_load_balancer_addresses = false
64
+ when 'AvailabilityZones'
65
+ @in_availability_zones = false
66
+ when 'SecurityGroups'
67
+ @in_security_groups = false
68
+ when 'State'
69
+ @in_state = false
70
+ @load_balancer[name] = @state
71
+ @state = {}
72
+ when 'Code'
73
+ @state[name] = value
74
+
75
+ when 'RequestId'
76
+ @response['ResponseMetadata'][name] = value
77
+
78
+ when 'NextMarker'
79
+ @results['NextMarker'] = value
80
+ when 'DescribeLoadBalancersResponse'
81
+ @response['DescribeLoadBalancersResult'] = @results
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end