aws-insight 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +84 -0
  5. data/.travis.yml +19 -0
  6. data/CHANGELOG.md +336 -0
  7. data/CODE_OF_CONDUCT.md +13 -0
  8. data/CONTRIBUTING.md +62 -0
  9. data/Gemfile +19 -0
  10. data/Guardfile +12 -0
  11. data/LICENSE +22 -0
  12. data/README.md +393 -0
  13. data/Rakefile +6 -0
  14. data/bin/terraforming +5 -0
  15. data/contrib/zsh-completion/_terraforming +65 -0
  16. data/lib/terraforming.rb +54 -0
  17. data/lib/terraforming/cli.rb +340 -0
  18. data/lib/terraforming/resource/alb.rb +98 -0
  19. data/lib/terraforming/resource/auto_scaling_group.rb +90 -0
  20. data/lib/terraforming/resource/cloud_watch_alarm.rb +87 -0
  21. data/lib/terraforming/resource/config.rb +98 -0
  22. data/lib/terraforming/resource/db_parameter_group.rb +58 -0
  23. data/lib/terraforming/resource/db_security_group.rb +57 -0
  24. data/lib/terraforming/resource/db_subnet_group.rb +52 -0
  25. data/lib/terraforming/resource/ec2.rb +130 -0
  26. data/lib/terraforming/resource/efs_file_system.rb +60 -0
  27. data/lib/terraforming/resource/eip.rb +66 -0
  28. data/lib/terraforming/resource/elasti_cache_cluster.rb +80 -0
  29. data/lib/terraforming/resource/elasti_cache_subnet_group.rb +56 -0
  30. data/lib/terraforming/resource/elb.rb +203 -0
  31. data/lib/terraforming/resource/iam_group.rb +54 -0
  32. data/lib/terraforming/resource/iam_group_membership.rb +63 -0
  33. data/lib/terraforming/resource/iam_group_policy.rb +71 -0
  34. data/lib/terraforming/resource/iam_instance_profile.rb +54 -0
  35. data/lib/terraforming/resource/iam_policy.rb +63 -0
  36. data/lib/terraforming/resource/iam_policy_attachment.rb +86 -0
  37. data/lib/terraforming/resource/iam_role.rb +56 -0
  38. data/lib/terraforming/resource/iam_role_policy.rb +71 -0
  39. data/lib/terraforming/resource/iam_user.rb +55 -0
  40. data/lib/terraforming/resource/iam_user_policy.rb +71 -0
  41. data/lib/terraforming/resource/internet_gateway.rb +54 -0
  42. data/lib/terraforming/resource/kms_alias.rb +55 -0
  43. data/lib/terraforming/resource/kms_key.rb +84 -0
  44. data/lib/terraforming/resource/launch_configuration.rb +106 -0
  45. data/lib/terraforming/resource/nat_gateway.rb +57 -0
  46. data/lib/terraforming/resource/network_acl.rb +83 -0
  47. data/lib/terraforming/resource/network_interface.rb +68 -0
  48. data/lib/terraforming/resource/rds.rb +74 -0
  49. data/lib/terraforming/resource/redshift.rb +67 -0
  50. data/lib/terraforming/resource/route53_record.rb +106 -0
  51. data/lib/terraforming/resource/route53_zone.rb +89 -0
  52. data/lib/terraforming/resource/route_table.rb +122 -0
  53. data/lib/terraforming/resource/route_table_association.rb +59 -0
  54. data/lib/terraforming/resource/s3.rb +69 -0
  55. data/lib/terraforming/resource/security_group.rb +188 -0
  56. data/lib/terraforming/resource/sns_topic.rb +75 -0
  57. data/lib/terraforming/resource/sns_topic_subscription.rb +83 -0
  58. data/lib/terraforming/resource/sqs.rb +70 -0
  59. data/lib/terraforming/resource/subnet.rb +55 -0
  60. data/lib/terraforming/resource/vpc.rb +67 -0
  61. data/lib/terraforming/resource/vpn_gateway.rb +55 -0
  62. data/lib/terraforming/template/tf/alb.erb +28 -0
  63. data/lib/terraforming/template/tf/auto_scaling_group.erb +28 -0
  64. data/lib/terraforming/template/tf/cloud_watch_alarm.erb +33 -0
  65. data/lib/terraforming/template/tf/db_parameter_group.erb +17 -0
  66. data/lib/terraforming/template/tf/db_security_group.erb +26 -0
  67. data/lib/terraforming/template/tf/db_subnet_group.erb +8 -0
  68. data/lib/terraforming/template/tf/ec2.erb +56 -0
  69. data/lib/terraforming/template/tf/eip.erb +11 -0
  70. data/lib/terraforming/template/tf/elasti_cache_cluster.erb +22 -0
  71. data/lib/terraforming/template/tf/elasti_cache_subnet_group.erb +8 -0
  72. data/lib/terraforming/template/tf/elastic_filesystem.erb +18 -0
  73. data/lib/terraforming/template/tf/elb.erb +51 -0
  74. data/lib/terraforming/template/tf/iam_group.erb +7 -0
  75. data/lib/terraforming/template/tf/iam_group_membership.erb +8 -0
  76. data/lib/terraforming/template/tf/iam_group_policy.erb +10 -0
  77. data/lib/terraforming/template/tf/iam_instance_profile.erb +8 -0
  78. data/lib/terraforming/template/tf/iam_policy.erb +12 -0
  79. data/lib/terraforming/template/tf/iam_policy_attachment.erb +10 -0
  80. data/lib/terraforming/template/tf/iam_role.erb +10 -0
  81. data/lib/terraforming/template/tf/iam_role_policy.erb +10 -0
  82. data/lib/terraforming/template/tf/iam_user.erb +7 -0
  83. data/lib/terraforming/template/tf/iam_user_policy.erb +10 -0
  84. data/lib/terraforming/template/tf/internet_gateway.erb +14 -0
  85. data/lib/terraforming/template/tf/kms_alias.erb +7 -0
  86. data/lib/terraforming/template/tf/kms_key.erb +13 -0
  87. data/lib/terraforming/template/tf/launch_configuration.erb +68 -0
  88. data/lib/terraforming/template/tf/nat_gateway.erb +9 -0
  89. data/lib/terraforming/template/tf/network_acl.erb +43 -0
  90. data/lib/terraforming/template/tf/network_interface.erb +22 -0
  91. data/lib/terraforming/template/tf/rds.erb +25 -0
  92. data/lib/terraforming/template/tf/redshift.erb +23 -0
  93. data/lib/terraforming/template/tf/route53_record.erb +52 -0
  94. data/lib/terraforming/template/tf/route53_zone.erb +18 -0
  95. data/lib/terraforming/template/tf/route_table.erb +34 -0
  96. data/lib/terraforming/template/tf/route_table_association.erb +9 -0
  97. data/lib/terraforming/template/tf/s3.erb +12 -0
  98. data/lib/terraforming/template/tf/security_group.erb +56 -0
  99. data/lib/terraforming/template/tf/sns_topic.erb +17 -0
  100. data/lib/terraforming/template/tf/sns_topic_subscription.erb +23 -0
  101. data/lib/terraforming/template/tf/sqs.erb +21 -0
  102. data/lib/terraforming/template/tf/subnet.erb +15 -0
  103. data/lib/terraforming/template/tf/vpc.erb +15 -0
  104. data/lib/terraforming/template/tf/vpn_gateway.erb +14 -0
  105. data/lib/terraforming/util.rb +30 -0
  106. data/lib/terraforming/version.rb +3 -0
  107. data/script/console +14 -0
  108. data/script/generate +97 -0
  109. data/script/setup +7 -0
  110. data/templates/resource.erb.erb +3 -0
  111. data/templates/resource.rb.erb +31 -0
  112. data/templates/resource_spec.rb.erb +39 -0
  113. data/terraforming.gemspec +31 -0
  114. metadata +271 -0
@@ -0,0 +1,74 @@
1
+ module Terraforming
2
+ module Resource
3
+ class RDS
4
+ include Terraforming::Util
5
+
6
+ def self.tf(client: Aws::RDS::Client.new)
7
+ self.new(client).tf
8
+ end
9
+
10
+ def self.tfstate(client: Aws::RDS::Client.new)
11
+ self.new(client).tfstate
12
+ end
13
+
14
+ def initialize(client)
15
+ @client = client
16
+ end
17
+
18
+ def tf
19
+ apply_template(@client, "tf/rds")
20
+ end
21
+
22
+ def tfstate
23
+ db_instances.inject({}) do |resources, instance|
24
+ attributes = {
25
+ "address" => instance.endpoint.address,
26
+ "allocated_storage" => instance.allocated_storage.to_s,
27
+ "availability_zone" => instance.availability_zone,
28
+ "backup_retention_period" => instance.backup_retention_period.to_s,
29
+ "backup_window" => instance.preferred_backup_window,
30
+ "db_subnet_group_name" => instance.db_subnet_group ? instance.db_subnet_group.db_subnet_group_name : "",
31
+ "endpoint" => instance.endpoint.address,
32
+ "engine" => instance.engine,
33
+ "engine_version" => instance.engine_version,
34
+ "final_snapshot_identifier" => "#{instance.db_instance_identifier}-final",
35
+ "id" => instance.db_instance_identifier,
36
+ "identifier" => instance.db_instance_identifier,
37
+ "instance_class" => instance.db_instance_class,
38
+ "maintenance_window" => instance.preferred_maintenance_window,
39
+ "multi_az" => instance.multi_az.to_s,
40
+ "name" => instance.db_name,
41
+ "parameter_group_name" => instance.db_parameter_groups[0].db_parameter_group_name,
42
+ "password" => "xxxxxxxx",
43
+ "port" => instance.endpoint.port.to_s,
44
+ "publicly_accessible" => instance.publicly_accessible.to_s,
45
+ "security_group_names.#" => instance.db_security_groups.length.to_s,
46
+ "status" => instance.db_instance_status,
47
+ "storage_type" => instance.storage_type,
48
+ "username" => instance.master_username,
49
+ "vpc_security_group_ids.#" => instance.vpc_security_groups.length.to_s,
50
+ }
51
+ resources["aws_db_instance.#{module_name_of(instance)}"] = {
52
+ "type" => "aws_db_instance",
53
+ "primary" => {
54
+ "id" => instance.db_instance_identifier,
55
+ "attributes" => attributes
56
+ }
57
+ }
58
+
59
+ resources
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def db_instances
66
+ @client.describe_db_instances.map(&:db_instances).flatten
67
+ end
68
+
69
+ def module_name_of(instance)
70
+ normalize_module_name(instance.db_instance_identifier)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,67 @@
1
+ module Terraforming
2
+ module Resource
3
+ class Redshift
4
+ include Terraforming::Util
5
+
6
+ def self.tf(client: Aws::Redshift::Client.new)
7
+ self.new(client).tf
8
+ end
9
+
10
+ def self.tfstate(client: Aws::Redshift::Client.new)
11
+ self.new(client).tfstate
12
+ end
13
+
14
+ def initialize(client)
15
+ @client = client
16
+ end
17
+
18
+ def tf
19
+ apply_template(@client, "tf/redshift")
20
+ end
21
+
22
+ def tfstate
23
+ clusters.inject({}) do |resources, cluster|
24
+ attributes = {
25
+ "cluster_identifier" => cluster.cluster_identifier,
26
+ "cluster_type" => cluster.number_of_nodes == 1 ? "single-node" : "multi-node",
27
+ "node_type" => cluster.node_type,
28
+ "master_password" => "xxxxxxxx",
29
+ "master_username" => cluster.master_username,
30
+ "availability_zone" => cluster.availability_zone,
31
+ "preferred_maintenance_window" => cluster.preferred_maintenance_window,
32
+ "cluster_parameter_group_name" => cluster.cluster_parameter_groups[0].parameter_group_name,
33
+ "automated_snapshot_retention_period" => cluster.automated_snapshot_retention_period.to_s,
34
+ "port" => cluster.endpoint.port.to_s,
35
+ "cluster_version" => cluster.cluster_version,
36
+ "allow_version_upgrade" => cluster.allow_version_upgrade.to_s,
37
+ "number_of_nodes" => cluster.number_of_nodes.to_s,
38
+ "publicly_accessible" => cluster.publicly_accessible.to_s,
39
+ "encrypted" => cluster.encrypted.to_s,
40
+ "skip_final_snapshot" => "true",
41
+ }
42
+ attributes["database_name"] = cluster.db_name if cluster.db_name
43
+
44
+ resources["aws_redshift_cluster.#{module_name_of(cluster)}"] = {
45
+ "type" => "aws_redshift_cluster",
46
+ "primary" => {
47
+ "id" => cluster.cluster_identifier,
48
+ "attributes" => attributes
49
+ }
50
+ }
51
+
52
+ resources
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def clusters
59
+ @client.describe_clusters.map(&:clusters).flatten
60
+ end
61
+
62
+ def module_name_of(cluster)
63
+ normalize_module_name(cluster.cluster_identifier)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,106 @@
1
+ module Terraforming
2
+ module Resource
3
+ class Route53Record
4
+ include Terraforming::Util
5
+
6
+ def self.tf(client: Aws::Route53::Client.new)
7
+ self.new(client).tf
8
+ end
9
+
10
+ def self.tfstate(client: Aws::Route53::Client.new)
11
+ self.new(client).tfstate
12
+ end
13
+
14
+ def initialize(client)
15
+ @client = client
16
+ end
17
+
18
+ def tf
19
+ apply_template(@client, "tf/route53_record")
20
+ end
21
+
22
+ def tfstate
23
+ records.inject({}) do |resources, r|
24
+ record, zone_id = r[:record], r[:zone_id]
25
+ counter = r[:counter]
26
+ record_id = record_id_of(record, zone_id)
27
+
28
+ attributes = {
29
+ "id" => record_id,
30
+ "name" => name_of(record.name.gsub(/\\052/, '*')),
31
+ "type" => record.type,
32
+ "zone_id" => zone_id,
33
+ }
34
+
35
+ attributes["alias.#"] = "1" if record.alias_target
36
+ attributes["records.#"] = record.resource_records.length.to_s unless record.resource_records.empty?
37
+ attributes["ttl"] = record.ttl.to_s if record.ttl
38
+ attributes["weight"] = record.weight ? record.weight.to_s : "-1"
39
+ attributes["region"] = record.region if record.region
40
+
41
+ if record.geo_location
42
+ attributes["continent"] = record.geo_location.continent_code if record.geo_location.continent_code
43
+ attributes["country"] = record.geo_location.country_code if record.geo_location.country_code
44
+ attributes["subdivision"] = record.geo_location.subdivision_code if record.geo_location.subdivision_code
45
+ end
46
+
47
+ attributes["set_identifier"] = record.set_identifier if record.set_identifier
48
+
49
+ resources["aws_route53_record.#{module_name_of(record, counter)}"] = {
50
+ "type" => "aws_route53_record",
51
+ "primary" => {
52
+ "id" => record_id,
53
+ "attributes" => attributes,
54
+ }
55
+ }
56
+
57
+ resources
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def hosted_zones
64
+ @client.list_hosted_zones.map(&:hosted_zones).flatten
65
+ end
66
+
67
+ def record_id_of(record, zone_id)
68
+ "#{zone_id}_#{name_of(record.name.gsub(/\\052/, '*'))}_#{record.type}"
69
+ end
70
+
71
+ def record_sets_of(hosted_zone)
72
+ @client.list_resource_record_sets(hosted_zone_id: zone_id_of(hosted_zone)).map do |response|
73
+ response.data.resource_record_sets
74
+ end.flatten
75
+ end
76
+
77
+ def records
78
+ to_return = hosted_zones.map do |hosted_zone|
79
+ record_sets_of(hosted_zone).map { |record| { record: record, zone_id: zone_id_of(hosted_zone) } }
80
+ end.flatten
81
+ count = {}
82
+ dups = to_return.group_by { |record| module_name_of(record[:record], nil) }.select { |_, v| v.size > 1 }.map(&:first)
83
+ to_return.each do |r|
84
+ module_name = module_name_of(r[:record], nil)
85
+ next unless dups.include?(module_name)
86
+ count[module_name] = count[module_name] ? count[module_name] + 1 : 0
87
+ r[:counter] = count[module_name]
88
+ end
89
+ to_return
90
+ end
91
+
92
+ # TODO(dtan4): change method name...
93
+ def name_of(dns_name)
94
+ dns_name.gsub(/\.\z/, "")
95
+ end
96
+
97
+ def module_name_of(record, counter)
98
+ normalize_module_name(name_of(record.name.gsub(/\\052/, 'wildcard')) + "-" + record.type + (!counter.nil? ? "-" + counter.to_s : ""))
99
+ end
100
+
101
+ def zone_id_of(hosted_zone)
102
+ hosted_zone.id.gsub(%r{\A/hostedzone/}, "")
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,89 @@
1
+ module Terraforming
2
+ module Resource
3
+ class Route53Zone
4
+ include Terraforming::Util
5
+
6
+ def self.tf(client: Aws::Route53::Client.new)
7
+ self.new(client).tf
8
+ end
9
+
10
+ def self.tfstate(client: Aws::Route53::Client.new)
11
+ self.new(client).tfstate
12
+ end
13
+
14
+ def initialize(client)
15
+ @client = client
16
+ end
17
+
18
+ def tf
19
+ apply_template(@client, "tf/route53_zone")
20
+ end
21
+
22
+ def tfstate
23
+ hosted_zones.inject({}) do |resources, hosted_zone|
24
+ zone_id = zone_id_of(hosted_zone)
25
+ vpc = vpc_of(hosted_zone)
26
+
27
+ attributes = {
28
+ "comment" => comment_of(hosted_zone),
29
+ "id" => zone_id,
30
+ "name" => name_of(hosted_zone),
31
+ "name_servers.#" => name_servers_of(hosted_zone).length.to_s,
32
+ "tags.#" => tags_of(hosted_zone).length.to_s,
33
+ "vpc_id" => vpc ? vpc.vpc_id : "",
34
+ "vpc_region" => vpc ? vpc.vpc_region : "",
35
+ "zone_id" => zone_id,
36
+ }
37
+ resources["aws_route53_zone.#{module_name_of(hosted_zone)}"] = {
38
+ "type" => "aws_route53_zone",
39
+ "primary" => {
40
+ "id" => zone_id,
41
+ "attributes" => attributes,
42
+ }
43
+ }
44
+
45
+ resources
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def hosted_zones
52
+ @client.list_hosted_zones.map(&:hosted_zones).flatten.map { |hosted_zone| @client.get_hosted_zone(id: hosted_zone.id) }
53
+ end
54
+
55
+ def tags_of(hosted_zone)
56
+ @client.list_tags_for_resource(resource_type: "hostedzone", resource_id: zone_id_of(hosted_zone)).resource_tag_set.tags
57
+ end
58
+
59
+ def comment_of(hosted_zone)
60
+ hosted_zone.hosted_zone.config.comment
61
+ end
62
+
63
+ def name_of(hosted_zone)
64
+ hosted_zone.hosted_zone.name.gsub(/\.\z/, "")
65
+ end
66
+
67
+ def name_servers_of(hosted_zone)
68
+ delegation_set = hosted_zone.delegation_set
69
+ delegation_set ? delegation_set.name_servers : []
70
+ end
71
+
72
+ def module_name_of(hosted_zone)
73
+ normalize_module_name(name_of(hosted_zone)) << "-#{private_hosted_zone?(hosted_zone) ? 'private' : 'public'}"
74
+ end
75
+
76
+ def private_hosted_zone?(hosted_zone)
77
+ hosted_zone.hosted_zone.config.private_zone
78
+ end
79
+
80
+ def vpc_of(hosted_zone)
81
+ hosted_zone.vp_cs[0]
82
+ end
83
+
84
+ def zone_id_of(hosted_zone)
85
+ hosted_zone.hosted_zone.id.gsub(%r{\A/hostedzone/}, "")
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,122 @@
1
+ module Terraforming
2
+ module Resource
3
+ class RouteTable
4
+ include Terraforming::Util
5
+
6
+ def self.tf(client: Aws::EC2::Client.new)
7
+ self.new(client).tf
8
+ end
9
+
10
+ def self.tfstate(client: Aws::EC2::Client.new)
11
+ self.new(client).tfstate
12
+ end
13
+
14
+ def initialize(client)
15
+ @client = client
16
+ end
17
+
18
+ def tf
19
+ apply_template(@client, "tf/route_table")
20
+ end
21
+
22
+ def tfstate
23
+ route_tables.inject({}) do |resources, route_table|
24
+ attributes = {
25
+ "id" => route_table.route_table_id,
26
+ "vpc_id" => route_table.vpc_id,
27
+ }
28
+
29
+ attributes.merge!(tags_attributes_of(route_table))
30
+ attributes.merge!(routes_attributes_of(route_table))
31
+ attributes.merge!(propagating_vgws_attributes_of(route_table))
32
+
33
+ resources["aws_route_table.#{module_name_of(route_table)}"] = {
34
+ "type" => "aws_route_table",
35
+ "primary" => {
36
+ "id" => route_table.route_table_id,
37
+ "attributes" => attributes
38
+ }
39
+ }
40
+
41
+ resources
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def routes_of(route_table)
48
+ route_table.routes.reject do |route|
49
+ route.gateway_id.to_s == 'local' ||
50
+ route.origin.to_s == 'EnableVgwRoutePropagation' ||
51
+ route.destination_prefix_list_id
52
+ end
53
+ end
54
+
55
+ def module_name_of(route_table)
56
+ normalize_module_name(name_from_tag(route_table, route_table.route_table_id))
57
+ end
58
+
59
+ def route_tables
60
+ @client.describe_route_tables.map(&:route_tables).flatten
61
+ end
62
+
63
+ def routes_attributes_of(route_table)
64
+ routes = routes_of(route_table)
65
+ attributes = { "route.#" => routes.length.to_s }
66
+
67
+ routes.each do |route|
68
+ attributes.merge!(route_attributes_of(route))
69
+ end
70
+
71
+ attributes
72
+ end
73
+
74
+ def route_attributes_of(route)
75
+ hashcode = route_hashcode_of(route)
76
+ attributes = {
77
+ "route.#{hashcode}.cidr_block" => route.destination_cidr_block.to_s,
78
+ "route.#{hashcode}.gateway_id" => route.gateway_id.to_s,
79
+ "route.#{hashcode}.instance_id" => route.instance_id.to_s,
80
+ "route.#{hashcode}.network_interface_id" => route.network_interface_id.to_s,
81
+ "route.#{hashcode}.vpc_peering_connection_id" => route.vpc_peering_connection_id.to_s
82
+ }
83
+
84
+ attributes
85
+ end
86
+
87
+ def route_hashcode_of(route)
88
+ string = "#{route.destination_cidr_block}-#{route.gateway_id}-"
89
+ instance_set = !route.instance_id.nil? && route.instance_id != ''
90
+
91
+ string << route.instance_id.to_s if instance_set
92
+ string << route.vpc_peering_connection_id.to_s
93
+ string << route.network_interface_id.to_s unless instance_set
94
+
95
+ Zlib.crc32(string)
96
+ end
97
+
98
+ def propagaving_vgws_of(route_table)
99
+ route_table.propagating_vgws.map(&:gateway_id).map(&:to_s)
100
+ end
101
+
102
+ def propagating_vgws_attributes_of(route_table)
103
+ vgws = propagaving_vgws_of(route_table)
104
+ attributes = { "propagating_vgws.#" => vgws.length.to_s }
105
+
106
+ vgws.each do |gateway_id|
107
+ hashcode = Zlib.crc32(gateway_id)
108
+ attributes["propagating_vgws.#{hashcode}"] = gateway_id
109
+ end
110
+
111
+ attributes
112
+ end
113
+
114
+ def tags_attributes_of(route_table)
115
+ tags = route_table.tags
116
+ attributes = { "tags.#" => tags.length.to_s }
117
+ tags.each { |tag| attributes["tags.#{tag.key}"] = tag.value }
118
+ attributes
119
+ end
120
+ end
121
+ end
122
+ end