geoengineer 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +5 -0
  3. data.tar.gz.sig +0 -0
  4. data/README.md +5 -5
  5. data/lib/geoengineer/cli/geo_cli.rb +4 -5
  6. data/lib/geoengineer/cli/status_command.rb +7 -1
  7. data/lib/geoengineer/environment.rb +53 -51
  8. data/lib/geoengineer/project.rb +5 -24
  9. data/lib/geoengineer/resource.rb +89 -20
  10. data/lib/geoengineer/resources/aws_customer_gateway.rb +23 -0
  11. data/lib/geoengineer/resources/aws_eip.rb +43 -0
  12. data/lib/geoengineer/resources/aws_iam_group.rb +26 -0
  13. data/lib/geoengineer/resources/aws_iam_group_membership.rb +50 -0
  14. data/lib/geoengineer/resources/aws_iam_policy.rb +12 -4
  15. data/lib/geoengineer/resources/aws_iam_policy_attachment.rb +95 -0
  16. data/lib/geoengineer/resources/aws_iam_role.rb +45 -0
  17. data/lib/geoengineer/resources/aws_instance.rb +7 -4
  18. data/lib/geoengineer/resources/aws_internet_gateway.rb +23 -0
  19. data/lib/geoengineer/resources/aws_lambda_alias.rb +50 -0
  20. data/lib/geoengineer/resources/aws_lambda_event_source_mapping.rb +47 -0
  21. data/lib/geoengineer/resources/aws_lambda_function.rb +30 -0
  22. data/lib/geoengineer/resources/aws_lambda_permission.rb +74 -0
  23. data/lib/geoengineer/resources/aws_main_route_table_association.rb +51 -0
  24. data/lib/geoengineer/resources/aws_nat_gateway.rb +29 -0
  25. data/lib/geoengineer/resources/aws_network_acl.rb +38 -0
  26. data/lib/geoengineer/resources/aws_network_acl_rule.rb +50 -0
  27. data/lib/geoengineer/resources/aws_route.rb +47 -0
  28. data/lib/geoengineer/resources/aws_route53_record.rb +4 -0
  29. data/lib/geoengineer/resources/aws_route_table.rb +26 -0
  30. data/lib/geoengineer/resources/aws_route_table_association.rb +45 -0
  31. data/lib/geoengineer/resources/aws_security_group.rb +8 -5
  32. data/lib/geoengineer/resources/aws_subnet.rb +24 -0
  33. data/lib/geoengineer/resources/aws_vpc.rb +24 -0
  34. data/lib/geoengineer/resources/aws_vpc_dhcp_options.rb +29 -0
  35. data/lib/geoengineer/resources/aws_vpc_dhcp_options_association.rb +40 -0
  36. data/lib/geoengineer/resources/aws_vpc_endpoint.rb +26 -0
  37. data/lib/geoengineer/resources/aws_vpc_peering_connection.rb +29 -0
  38. data/lib/geoengineer/resources/aws_vpn_connection.rb +23 -0
  39. data/lib/geoengineer/resources/aws_vpn_connection_route.rb +35 -0
  40. data/lib/geoengineer/resources/aws_vpn_gateway.rb +22 -0
  41. data/lib/geoengineer/resources/aws_vpn_gateway_attachment.rb +41 -0
  42. data/lib/geoengineer/template.rb +20 -4
  43. data/lib/geoengineer/utils/aws_clients.rb +4 -0
  44. data/lib/geoengineer/utils/crc32.rb +61 -0
  45. data/lib/geoengineer/utils/has_attributes.rb +25 -11
  46. data/lib/geoengineer/utils/has_projects.rb +21 -0
  47. data/lib/geoengineer/utils/has_resources.rb +17 -4
  48. data/lib/geoengineer/utils/has_templates.rb +31 -0
  49. data/lib/geoengineer/utils/has_validations.rb +18 -3
  50. data/lib/geoengineer/version.rb +1 -1
  51. data/spec/environment_spec.rb +40 -19
  52. data/spec/project_spec.rb +2 -2
  53. data/spec/resource_spec.rb +87 -6
  54. data/spec/resources/aws_customer_gateway_spec.rb +24 -0
  55. data/spec/resources/aws_eip_spec.rb +29 -0
  56. data/spec/resources/aws_iam_group_membership_spec.rb +83 -0
  57. data/spec/resources/aws_iam_group_spec.rb +43 -0
  58. data/spec/resources/aws_iam_policy_attachment_spec.rb +80 -0
  59. data/spec/resources/{aws_iam_policy.rb → aws_iam_policy_spec.rb} +6 -5
  60. data/spec/resources/aws_iam_role_spec.rb +45 -0
  61. data/spec/resources/aws_internet_gateway_spec.rb +24 -0
  62. data/spec/resources/aws_lambda_alias_spec.rb +39 -0
  63. data/spec/resources/aws_lambda_event_source_mapping_spec.rb +53 -0
  64. data/spec/resources/aws_lambda_function_spec.rb +29 -0
  65. data/spec/resources/aws_lambda_permission_spec.rb +90 -0
  66. data/spec/resources/aws_main_route_table_association_spec.rb +57 -0
  67. data/spec/resources/aws_nat_gateway_spec.rb +31 -0
  68. data/spec/resources/aws_network_acl_rule_spec.rb +73 -0
  69. data/spec/resources/aws_network_acl_spec.rb +31 -0
  70. data/spec/resources/aws_route53_record_spec.rb +5 -0
  71. data/spec/resources/aws_route_spec.rb +47 -0
  72. data/spec/resources/aws_route_table_association_spec.rb +47 -0
  73. data/spec/resources/aws_route_table_spec.rb +24 -0
  74. data/spec/resources/aws_security_group_spec.rb +36 -2
  75. data/spec/resources/aws_subnet_spec.rb +24 -0
  76. data/spec/resources/aws_vpc_dhcp_options_association_spec.rb +43 -0
  77. data/spec/resources/aws_vpc_dhcp_options_spec.rb +24 -0
  78. data/spec/resources/aws_vpc_endpoint_spec.rb +41 -0
  79. data/spec/resources/aws_vpc_peering_connection_spec.rb +33 -0
  80. data/spec/resources/aws_vpc_spec.rb +24 -0
  81. data/spec/resources/aws_vpn_connection_route_spec.rb +43 -0
  82. data/spec/resources/aws_vpn_connection_spec.rb +41 -0
  83. data/spec/resources/aws_vpn_gateway_attachment_spec.rb +41 -0
  84. data/spec/resources/aws_vpn_gateway_spec.rb +39 -0
  85. data/spec/spec_helper.rb +3 -1
  86. data/spec/utils/crc32_spec.rb +14 -0
  87. data/spec/utils/has_attributes_spec.rb +22 -0
  88. data/spec/utils/has_resources_spec.rb +4 -0
  89. data/spec/utils/has_validations_spec.rb +45 -0
  90. metadata +117 -6
  91. metadata.gz.sig +1 -0
@@ -0,0 +1,31 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe("GeoEngineer::Resources::AwsNatGateway") do
4
+ common_resource_tests(GeoEngineer::Resources::AwsNatGateway, 'aws_nat_gateway')
5
+
6
+ describe "#_fetch_remote_resources" do
7
+ it 'should create list of hashes from returned AWS SDK' do
8
+ ec2 = AwsClients.ec2
9
+ stub = ec2.stub_data(
10
+ :describe_nat_gateways,
11
+ {
12
+ nat_gateways: [
13
+ {
14
+ nat_gateway_id: 'name1',
15
+ subnet_id: 's1',
16
+ nat_gateway_addresses: [{ allocation_id: 'a1' }]
17
+ },
18
+ {
19
+ nat_gateway_id: 'name2',
20
+ subnet_id: 's2',
21
+ nat_gateway_addresses: [{ allocation_id: 'a2' }]
22
+ }
23
+ ]
24
+ }
25
+ )
26
+ ec2.stub_responses(:describe_nat_gateways, stub)
27
+ remote_resources = GeoEngineer::Resources::AwsNatGateway._fetch_remote_resources
28
+ expect(remote_resources.length).to eq(2)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,73 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe("GeoEngineer::Resources::AwsNetworkAclRule") do
4
+ common_resource_tests(GeoEngineer::Resources::AwsNetworkAclRule, 'aws_network_acl_rule')
5
+
6
+ describe "#_fetch_remote_resources" do
7
+ let(:ec2) { AwsClients.ec2 }
8
+ before do
9
+ stub = ec2.stub_data(
10
+ :describe_network_acls,
11
+ {
12
+ network_acls: [
13
+ {
14
+ network_acl_id: 'name1',
15
+ vpc_id: "1",
16
+ tags: [{ key: 'Name', value: 'one' }],
17
+ entries: [
18
+ {
19
+ cidr_block: "0.0.0.0/0",
20
+ egress: true,
21
+ protocol: "-1",
22
+ rule_action: "deny",
23
+ rule_number: 1
24
+ }
25
+ ]
26
+ },
27
+ {
28
+ network_acl_id: 'name2',
29
+ vpc_id: "1",
30
+ tags: [{ key: 'Name', value: 'two' }],
31
+ entries: [
32
+ {
33
+ cidr_block: "0.0.0.0/0",
34
+ egress: true,
35
+ protocol: "-1",
36
+ rule_action: "deny",
37
+ rule_number: 2
38
+ }
39
+ ]
40
+ }
41
+ ]
42
+ }
43
+ )
44
+ ec2.stub_responses(:describe_network_acls, stub)
45
+ end
46
+
47
+ after do
48
+ ec2.stub_responses(:describe_network_acls, [])
49
+ end
50
+
51
+ it 'should create list of hashes from returned AWS SDK' do
52
+ remote_resources = GeoEngineer::Resources::AwsNetworkAclRule._fetch_remote_resources
53
+ expect(remote_resources.length).to eq(2)
54
+ end
55
+ end
56
+
57
+ describe "#_terraform_id" do
58
+ let(:subject) do
59
+ GeoEngineer::Resources::AwsNetworkAclRule.new('aws_network_acl_rule', 'test') {
60
+ cidr_block "0.0.0.0/0"
61
+ egress true
62
+ network_acl_id "acl-22820044"
63
+ protocol "all"
64
+ rule_action "allow"
65
+ rule_number "100"
66
+ }
67
+ end
68
+
69
+ it "matches one generated by terraform" do
70
+ expect(subject._terraform_id).to eq("nacl-883681663")
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,31 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe("GeoEngineer::Resources::AwsNetworkAcl") do
4
+ common_resource_tests(GeoEngineer::Resources::AwsNetworkAcl, 'aws_network_acl')
5
+ name_tag_geo_id_tests(GeoEngineer::Resources::AwsNetworkAcl)
6
+
7
+ describe "#_fetch_remote_resources" do
8
+ let(:ec2) { AwsClients.ec2 }
9
+ before do
10
+ stub = ec2.stub_data(
11
+ :describe_network_acls,
12
+ {
13
+ network_acls: [
14
+ { network_acl_id: 'name1', vpc_id: "1", tags: [{ key: 'Name', value: 'one' }] },
15
+ { network_acl_id: 'name2', vpc_id: "1", tags: [{ key: 'Name', value: 'two' }] }
16
+ ]
17
+ }
18
+ )
19
+ ec2.stub_responses(:describe_network_acls, stub)
20
+ end
21
+
22
+ after do
23
+ ec2.stub_responses(:describe_network_acls, [])
24
+ end
25
+
26
+ it 'should create list of hashes from returned AWS SDK' do
27
+ remote_resources = GeoEngineer::Resources::AwsNetworkAcl._fetch_remote_resources
28
+ expect(remote_resources.length).to eq(2)
29
+ end
30
+ end
31
+ end
@@ -33,6 +33,11 @@ describe("GeoEngineer::Resources::AwsRoute53Record") do
33
33
  )
34
34
  end
35
35
 
36
+ after do
37
+ aws_client.stub_responses(:list_hosted_zones, [])
38
+ aws_client.stub_responses(:list_resource_record_sets, [])
39
+ end
40
+
36
41
  it 'should create list of hashes from returned AWS SDK' do
37
42
  remote_resources = GeoEngineer::Resources::AwsRoute53Record._fetch_remote_resources
38
43
  expect(remote_resources.length).to eq(2)
@@ -0,0 +1,47 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe("GeoEngineer::Resources::AwsRoute") do
4
+ common_resource_tests(
5
+ GeoEngineer::Resources::AwsRoute,
6
+ 'aws_route'
7
+ )
8
+
9
+ describe "#_fetch_remote_resources" do
10
+ let(:ec2) { AwsClients.ec2 }
11
+ before do
12
+ stub = ec2.stub_data(
13
+ :describe_route_tables,
14
+ {
15
+ route_tables: [
16
+ {
17
+ route_table_id: 'name1',
18
+ vpc_id: "1",
19
+ tags: [{ key: 'Name', value: 'one' }],
20
+ routes: [
21
+ { destination_cidr_block: "0.0.0.0/0" }
22
+ ]
23
+ },
24
+ {
25
+ route_table_id: 'name2',
26
+ vpc_id: "1",
27
+ tags: [{ key: 'Name', value: 'two' }],
28
+ routes: [
29
+ { destination_cidr_block: "0.0.0.0/0" }
30
+ ]
31
+ }
32
+ ]
33
+ }
34
+ )
35
+ ec2.stub_responses(:describe_route_tables, stub)
36
+ end
37
+
38
+ after do
39
+ ec2.stub_responses(:describe_route_tables, [])
40
+ end
41
+
42
+ it 'should create list of hashes from returned AWS SDK' do
43
+ remote_resources = GeoEngineer::Resources::AwsRoute._fetch_remote_resources
44
+ expect(remote_resources.length).to eq(2)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,47 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe("GeoEngineer::Resources::AwsRouteTableAssociation") do
4
+ common_resource_tests(
5
+ GeoEngineer::Resources::AwsRouteTableAssociation,
6
+ 'aws_route_table_association'
7
+ )
8
+
9
+ describe "#_fetch_remote_resources" do
10
+ let(:ec2) { AwsClients.ec2 }
11
+ before do
12
+ stub = ec2.stub_data(
13
+ :describe_route_tables,
14
+ {
15
+ route_tables: [
16
+ {
17
+ route_table_id: 'name1',
18
+ vpc_id: "1",
19
+ tags: [{ key: 'Name', value: 'one' }],
20
+ associations: [
21
+ { route_table_association_id: '1', subnet_id: 's-1', route_table_id: 'r-1' }
22
+ ]
23
+ },
24
+ {
25
+ route_table_id: 'name2',
26
+ vpc_id: "1",
27
+ tags: [{ key: 'Name', value: 'two' }],
28
+ associations: [
29
+ { route_table_association_id: '2', subnet_id: 's-2', route_table_id: 'r-2' }
30
+ ]
31
+ }
32
+ ]
33
+ }
34
+ )
35
+ ec2.stub_responses(:describe_route_tables, stub)
36
+ end
37
+
38
+ after do
39
+ ec2.stub_responses(:describe_route_tables, [])
40
+ end
41
+
42
+ it 'should create list of hashes from returned AWS SDK' do
43
+ remote_resources = GeoEngineer::Resources::AwsRouteTableAssociation._fetch_remote_resources
44
+ expect(remote_resources.length).to eq(2)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,24 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe("GeoEngineer::Resources::AwsRouteTable") do
4
+ common_resource_tests(GeoEngineer::Resources::AwsRouteTable, 'aws_route_table')
5
+ name_tag_geo_id_tests(GeoEngineer::Resources::AwsRouteTable)
6
+
7
+ describe "#_fetch_remote_resources" do
8
+ it 'should create list of hashes from returned AWS SDK' do
9
+ ec2 = AwsClients.ec2
10
+ stub = ec2.stub_data(
11
+ :describe_route_tables,
12
+ {
13
+ route_tables: [
14
+ { route_table_id: 'name1', vpc_id: "1", tags: [{ key: 'Name', value: 'one' }] },
15
+ { route_table_id: 'name2', vpc_id: "1", tags: [{ key: 'Name', value: 'two' }] }
16
+ ]
17
+ }
18
+ )
19
+ ec2.stub_responses(:describe_route_tables, stub)
20
+ remote_resources = GeoEngineer::Resources::AwsRouteTable._fetch_remote_resources
21
+ expect(remote_resources.length).to eq(2)
22
+ end
23
+ end
24
+ end
@@ -61,8 +61,12 @@ describe("GeoEngineer::Resources::AwsSecurityGroup") do
61
61
  end
62
62
 
63
63
  describe "#_fetch_remote_resources" do
64
+ let(:ec2) { AwsClients.ec2 }
65
+ before do
66
+ ec2.stub_responses(:describe_security_groups, [])
67
+ end
68
+
64
69
  it 'should create list of hashes from returned AWS SDK' do
65
- ec2 = AwsClients.ec2
66
70
  stub = ec2.stub_data(
67
71
  :describe_security_groups,
68
72
  {
@@ -74,7 +78,37 @@ describe("GeoEngineer::Resources::AwsSecurityGroup") do
74
78
  )
75
79
  ec2.stub_responses(:describe_security_groups, stub)
76
80
  remote_resources = GeoEngineer::Resources::AwsSecurityGroup._fetch_remote_resources
77
- expect(remote_resources.length).to eq 2
81
+ expect(remote_resources.length).to eq(2)
82
+ end
83
+
84
+ it 'works if remote resources have no tags' do
85
+ stub = ec2.stub_data(
86
+ :describe_security_groups,
87
+ {
88
+ security_groups: [
89
+ { group_name: 'name1', group_id: 'id1' },
90
+ { group_name: 'name2', group_id: 'id2' }
91
+ ]
92
+ }
93
+ )
94
+ ec2.stub_responses(:describe_security_groups, stub)
95
+ remote_resources = GeoEngineer::Resources::AwsSecurityGroup._fetch_remote_resources
96
+ expect(remote_resources.length).to eq(2)
97
+ end
98
+
99
+ it 'works if remote resources have tags with Name' do
100
+ stub = ec2.stub_data(
101
+ :describe_security_groups,
102
+ {
103
+ security_groups: [
104
+ { group_name: 'name1', group_id: 'id1', tags: [{ key: 'Foo', value: 'one' }] },
105
+ { group_name: 'name2', group_id: 'id2', tags: [{ key: 'Bar', value: 'two' }] }
106
+ ]
107
+ }
108
+ )
109
+ ec2.stub_responses(:describe_security_groups, stub)
110
+ remote_resources = GeoEngineer::Resources::AwsSecurityGroup._fetch_remote_resources
111
+ expect(remote_resources.length).to eq(2)
78
112
  end
79
113
  end
80
114
  end
@@ -0,0 +1,24 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe("GeoEngineer::Resources::AwsSubnet") do
4
+ common_resource_tests(GeoEngineer::Resources::AwsSubnet, 'aws_subnet')
5
+ name_tag_geo_id_tests(GeoEngineer::Resources::AwsSubnet)
6
+
7
+ describe "#_fetch_remote_resources" do
8
+ it 'should create list of hashes from returned AWS SDK' do
9
+ ec2 = AwsClients.ec2
10
+ stub = ec2.stub_data(
11
+ :describe_subnets,
12
+ {
13
+ subnets: [
14
+ { subnet_id: '1', cidr_block: "10.120.0.0/24", tags: [{ key: 'Name', value: 'one' }] },
15
+ { subnet_id: '2', cidr_block: "10.120.1.0/24", tags: [{ key: 'Name', value: 'two' }] }
16
+ ]
17
+ }
18
+ )
19
+ ec2.stub_responses(:describe_subnets, stub)
20
+ remote_resources = GeoEngineer::Resources::AwsSubnet._fetch_remote_resources
21
+ expect(remote_resources.length).to eq(2)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,43 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe("GeoEngineer::Resources::AwsVpcDhcpOptionsAssociation") do
4
+ common_resource_tests(
5
+ GeoEngineer::Resources::AwsVpcDhcpOptionsAssociation,
6
+ 'aws_vpc_dhcp_options_association'
7
+ )
8
+
9
+ describe "#_fetch_remote_resources" do
10
+ let(:ec2) { AwsClients.ec2 }
11
+ before do
12
+ stub = ec2.stub_data(
13
+ :describe_vpcs,
14
+ {
15
+ vpcs: [
16
+ {
17
+ vpc_id: 'name1',
18
+ cidr_block: "10.120.0.0/24",
19
+ tags: [{ key: 'Name', value: 'one' }],
20
+ dhcp_options_id: '1'
21
+ },
22
+ {
23
+ vpc_id: 'name2',
24
+ cidr_block: "10.120.1.0/24",
25
+ tags: [{ key: 'Name', value: 'two' }],
26
+ dhcp_options_id: '2'
27
+ }
28
+ ]
29
+ }
30
+ )
31
+ ec2.stub_responses(:describe_vpcs, stub)
32
+ end
33
+
34
+ after do
35
+ ec2.stub_responses(:describe_vpcs, [])
36
+ end
37
+
38
+ it 'should create list of hashes from returned AWS SDK' do
39
+ remote_resources = GeoEngineer::Resources::AwsVpc._fetch_remote_resources
40
+ expect(remote_resources.length).to eq(2)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,24 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe("GeoEngineer::Resources::AwsVpcDhcpOptions") do
4
+ common_resource_tests(GeoEngineer::Resources::AwsVpcDhcpOptions, 'aws_vpc_dhcp_options')
5
+ name_tag_geo_id_tests(GeoEngineer::Resources::AwsVpcDhcpOptions)
6
+
7
+ describe "#_fetch_remote_resources" do
8
+ it 'should create list of hashes from returned AWS SDK' do
9
+ ec2 = AwsClients.ec2
10
+ stub = ec2.stub_data(
11
+ :describe_dhcp_options,
12
+ {
13
+ dhcp_options: [
14
+ { dhcp_options_id: 'name1', tags: [{ key: 'Name', value: 'one' }] },
15
+ { dhcp_options_id: 'name2', tags: [{ key: 'Name', value: 'two' }] }
16
+ ]
17
+ }
18
+ )
19
+ ec2.stub_responses(:describe_dhcp_options, stub)
20
+ remote_resources = GeoEngineer::Resources::AwsVpcDhcpOptions._fetch_remote_resources
21
+ expect(remote_resources.length).to eq(2)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,41 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe("GeoEngineer::Resources::AwsVpcEndpoint") do
4
+ common_resource_tests(
5
+ GeoEngineer::Resources::AwsVpcEndpoint,
6
+ 'aws_vpc_endpoint'
7
+ )
8
+
9
+ describe "#_fetch_remote_resources" do
10
+ let(:ec2) { AwsClients.ec2 }
11
+ before do
12
+ stub = ec2.stub_data(
13
+ :describe_vpc_endpoints,
14
+ {
15
+ vpc_endpoints: [
16
+ {
17
+ vpc_endpoint_id: 'name1',
18
+ vpc_id: "1",
19
+ service_name: "com.amazonaws.us-east-1.s3"
20
+ },
21
+ {
22
+ vpc_endpoint_id: 'name1',
23
+ vpc_id: "1",
24
+ service_name: "com.amazonaws.us-east-1.lambda"
25
+ }
26
+ ]
27
+ }
28
+ )
29
+ ec2.stub_responses(:describe_vpc_endpoints, stub)
30
+ end
31
+
32
+ after do
33
+ ec2.stub_responses(:describe_vpc_endpoints, [])
34
+ end
35
+
36
+ it 'should create list of hashes from returned AWS SDK' do
37
+ remote_resources = GeoEngineer::Resources::AwsVpcEndpoint._fetch_remote_resources
38
+ expect(remote_resources.length).to eq(2)
39
+ end
40
+ end
41
+ end