aws_recon 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rubocop.yml +12 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +7 -0
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +1000 -0
  9. data/LICENSE.txt +21 -0
  10. data/Rakefile +10 -0
  11. data/aws_recon.gemspec +36 -0
  12. data/bin/aws_recon +5 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/lib/aws_recon.rb +19 -0
  16. data/lib/aws_recon/aws_recon.rb +115 -0
  17. data/lib/aws_recon/collectors/acm.rb +32 -0
  18. data/lib/aws_recon/collectors/apigateway.rb +50 -0
  19. data/lib/aws_recon/collectors/apigatewayv2.rb +37 -0
  20. data/lib/aws_recon/collectors/athena.rb +28 -0
  21. data/lib/aws_recon/collectors/autoscaling.rb +35 -0
  22. data/lib/aws_recon/collectors/cloudformation.rb +29 -0
  23. data/lib/aws_recon/collectors/cloudfront.rb +28 -0
  24. data/lib/aws_recon/collectors/cloudtrail.rb +33 -0
  25. data/lib/aws_recon/collectors/cloudwatch.rb +33 -0
  26. data/lib/aws_recon/collectors/cloudwatchlogs.rb +36 -0
  27. data/lib/aws_recon/collectors/codebuild.rb +29 -0
  28. data/lib/aws_recon/collectors/codepipeline.rb +27 -0
  29. data/lib/aws_recon/collectors/collectors.rb +2 -0
  30. data/lib/aws_recon/collectors/configservice.rb +80 -0
  31. data/lib/aws_recon/collectors/directconnect.rb +25 -0
  32. data/lib/aws_recon/collectors/directyservice.rb +27 -0
  33. data/lib/aws_recon/collectors/dms.rb +25 -0
  34. data/lib/aws_recon/collectors/dynamodb.rb +26 -0
  35. data/lib/aws_recon/collectors/ec2.rb +257 -0
  36. data/lib/aws_recon/collectors/ecr.rb +39 -0
  37. data/lib/aws_recon/collectors/ecs.rb +40 -0
  38. data/lib/aws_recon/collectors/efs.rb +25 -0
  39. data/lib/aws_recon/collectors/eks.rb +36 -0
  40. data/lib/aws_recon/collectors/elasticloadbalancing.rb +41 -0
  41. data/lib/aws_recon/collectors/elasticloadbalancingv2.rb +63 -0
  42. data/lib/aws_recon/collectors/elasticsearch.rb +27 -0
  43. data/lib/aws_recon/collectors/firehose.rb +29 -0
  44. data/lib/aws_recon/collectors/guardduty.rb +33 -0
  45. data/lib/aws_recon/collectors/iam.rb +136 -0
  46. data/lib/aws_recon/collectors/kafka.rb +27 -0
  47. data/lib/aws_recon/collectors/kinesis.rb +26 -0
  48. data/lib/aws_recon/collectors/kms.rb +71 -0
  49. data/lib/aws_recon/collectors/lambda.rb +42 -0
  50. data/lib/aws_recon/collectors/lightsail.rb +38 -0
  51. data/lib/aws_recon/collectors/organizations.rb +36 -0
  52. data/lib/aws_recon/collectors/rds.rb +81 -0
  53. data/lib/aws_recon/collectors/redshift.rb +40 -0
  54. data/lib/aws_recon/collectors/route53.rb +28 -0
  55. data/lib/aws_recon/collectors/route53domains.rb +25 -0
  56. data/lib/aws_recon/collectors/s3.rb +80 -0
  57. data/lib/aws_recon/collectors/sagemaker.rb +25 -0
  58. data/lib/aws_recon/collectors/servicequotas.rb +44 -0
  59. data/lib/aws_recon/collectors/ses.rb +28 -0
  60. data/lib/aws_recon/collectors/shield.rb +67 -0
  61. data/lib/aws_recon/collectors/sns.rb +38 -0
  62. data/lib/aws_recon/collectors/sqs.rb +28 -0
  63. data/lib/aws_recon/collectors/ssm.rb +41 -0
  64. data/lib/aws_recon/collectors/support.rb +43 -0
  65. data/lib/aws_recon/collectors/transfer.rb +24 -0
  66. data/lib/aws_recon/collectors/wafv2.rb +49 -0
  67. data/lib/aws_recon/collectors/workspaces.rb +24 -0
  68. data/lib/aws_recon/collectors/xray.rb +19 -0
  69. data/lib/aws_recon/lib/formatter.rb +32 -0
  70. data/lib/aws_recon/lib/mapper.rb +69 -0
  71. data/lib/aws_recon/options.rb +141 -0
  72. data/lib/aws_recon/services.yaml +134 -0
  73. data/lib/aws_recon/version.rb +3 -0
  74. data/readme.md +226 -0
  75. data/readme_gem.md +39 -0
  76. metadata +245 -0
@@ -0,0 +1,28 @@
1
+ class CloudFront < Mapper
2
+ #
3
+ # Returns an array of resources.
4
+ #
5
+ def collect
6
+ resources = []
7
+
8
+ #
9
+ # list_distributions
10
+ #
11
+ @client.list_distributions.each_with_index do |response, page|
12
+ log(response.context.operation_name, page)
13
+
14
+ # get_distribution
15
+ response.distribution_list.items.each do |dist|
16
+ struct = OpenStruct.new(dist.to_h)
17
+ struct.type = 'distribution'
18
+ struct.details = @client
19
+ .get_distribution({ id: dist.id })
20
+ .distribution.to_h
21
+
22
+ resources.push(struct.to_h)
23
+ end
24
+ end
25
+
26
+ resources
27
+ end
28
+ end
@@ -0,0 +1,33 @@
1
+ class CloudTrail < Mapper
2
+ #
3
+ # Returns an array of resources.
4
+ #
5
+ def collect
6
+ resources = []
7
+ #
8
+ # describe_trails
9
+ #
10
+ @client.describe_trails.each_with_index do |response, page|
11
+ log(response.context.operation_name, page)
12
+
13
+ response.trail_list.each do |trail|
14
+ # list_tags needs to call into home_region
15
+ client = if @region != trail.home_region
16
+ Aws::CloudTrail::Client.new({ region: trail.home_region })
17
+ else
18
+ @client
19
+ end
20
+
21
+ struct = OpenStruct.new(trail.to_h)
22
+ struct.tags = client.list_tags({ resource_id_list: [trail.trail_arn] }).resource_tag_list.first.tags_list
23
+ struct.type = 'cloud_trail'
24
+ struct.status = client.get_trail_status({ name: trail.name }).to_h
25
+ struct.arn = trail.trail_arn
26
+
27
+ resources.push(struct.to_h)
28
+ end
29
+ end
30
+
31
+ resources
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ class CloudWatch < Mapper
2
+ #
3
+ # Returns an array of resources.
4
+ #
5
+ def collect
6
+ resources = []
7
+
8
+ #
9
+ # describe_alarms
10
+ #
11
+ @client.describe_alarms.each_with_index do |response, page|
12
+ log(response.context.operation_name, page)
13
+
14
+ response.composite_alarms.each do |alarm|
15
+ struct = OpenStruct.new(alarm.to_h)
16
+ struct.type = 'composite_alarm'
17
+ struct.arn = alarm.alarm_arn
18
+
19
+ resources.push(struct.to_h)
20
+ end
21
+
22
+ response.metric_alarms.each do |alarm|
23
+ struct = OpenStruct.new(alarm.to_h)
24
+ struct.type = 'metric_alarm'
25
+ struct.arn = alarm.alarm_arn
26
+
27
+ resources.push(struct.to_h)
28
+ end
29
+ end
30
+
31
+ resources
32
+ end
33
+ end
@@ -0,0 +1,36 @@
1
+ class CloudWatchLogs < Mapper
2
+ #
3
+ # Returns an array of resources.
4
+ #
5
+ def collect
6
+ resources = []
7
+
8
+ #
9
+ # describe_log_groups
10
+ #
11
+ @client.describe_log_groups.each_with_index do |response, page|
12
+ log(response.context.operation_name, page)
13
+
14
+ response.log_groups.each do |log_group|
15
+ struct = OpenStruct.new(log_group.to_h)
16
+ struct.type = 'log_group'
17
+ struct.metric_filters = []
18
+
19
+ # describe_metric_filters
20
+ if log_group.metric_filter_count > 0
21
+ @client.describe_metric_filters.each_with_index do |response, page|
22
+ log(response.context.operation_name, log_group.log_group_name, page)
23
+
24
+ response.metric_filters.each do |filter|
25
+ struct.metric_filters.push(filter.to_h)
26
+ end
27
+ end
28
+ end
29
+
30
+ resources.push(struct.to_h)
31
+ end
32
+ end
33
+
34
+ resources
35
+ end
36
+ end
@@ -0,0 +1,29 @@
1
+ class CodeBuild < Mapper
2
+ #
3
+ # Returns an array of resources.
4
+ #
5
+ # TODO: group projects in chucks to minimize batch_get calls
6
+ #
7
+ def collect
8
+ resources = []
9
+
10
+ #
11
+ # list_projects
12
+ #
13
+ @client.list_projects.each_with_index do |response, page|
14
+ log(response.context.operation_name, page)
15
+
16
+ # batch_get_projects
17
+ response.projects.each do |project_name|
18
+ @client.batch_get_projects({ names: [project_name] }).projects.each do |project|
19
+ struct = OpenStruct.new(project.to_h)
20
+ struct.type = 'project'
21
+
22
+ resources.push(struct.to_h)
23
+ end
24
+ end
25
+ end
26
+
27
+ resources
28
+ end
29
+ end
@@ -0,0 +1,27 @@
1
+ class CodePipeline < Mapper
2
+ #
3
+ # Returns an array of resources.
4
+ #
5
+ def collect
6
+ resources = []
7
+
8
+ #
9
+ # list_pipelines
10
+ #
11
+ @client.list_pipelines.each_with_index do |response, page|
12
+ log(response.context.operation_name, page)
13
+
14
+ # get_pipeline
15
+ response.pipelines.each do |pipeline|
16
+ resp = @client.get_pipeline(name: pipeline.name)
17
+ struct = OpenStruct.new(resp.pipeline.to_h)
18
+ struct.type = 'pipeline'
19
+ struct.arn = resp.metadata.pipeline_arn
20
+
21
+ resources.push(struct.to_h)
22
+ end
23
+ end
24
+
25
+ resources
26
+ end
27
+ end
@@ -0,0 +1,2 @@
1
+ # require all collectors
2
+ Dir[File.join(__dir__, '*.rb')].each { |file| require file }
@@ -0,0 +1,80 @@
1
+ class ConfigService < Mapper
2
+ #
3
+ # Returns an array of resources.
4
+ #
5
+ def collect
6
+ resources = []
7
+
8
+ #
9
+ # describe_config_rules
10
+ #
11
+ @client.describe_config_rules.each_with_index do |response, page|
12
+ log(response.context.operation_name, page)
13
+
14
+ response.config_rules.each do |rule|
15
+ struct = OpenStruct.new(rule.to_h)
16
+ struct.type = 'rule'
17
+ struct.arn = rule.config_rule_arn
18
+
19
+ # describe_config_rule_evaluation_status
20
+ @client.describe_config_rule_evaluation_status({ config_rule_names: [rule.config_rule_name] }).each do |response|
21
+ log(response.context.operation_name, rule.config_rule_name, page)
22
+
23
+ response.config_rules_evaluation_status.each do |status|
24
+ struct.evaluation_status = status.to_h
25
+ end
26
+ end
27
+
28
+ resources.push(struct.to_h)
29
+ end
30
+ end
31
+
32
+ #
33
+ # describe_configuration_recorders
34
+ #
35
+ @client.describe_configuration_recorders.each_with_index do |response, page|
36
+ log(response.context.operation_name, page)
37
+
38
+ response.configuration_recorders.each do |recorder|
39
+ struct = OpenStruct.new(recorder.to_h)
40
+ struct.type = 'configuration_recorder'
41
+ struct.arn = "arn:aws:config:#{@region}:configuration_recorder/#{recorder.name}"
42
+
43
+ # describe_configuration_recorder_status (only accepts one recorder)
44
+ @client.describe_configuration_recorder_status({ configuration_recorder_names: [recorder.name] }).each do |response|
45
+ log(response.context.operation_name, recorder.name, page)
46
+
47
+ response.configuration_recorders_status.each do |status|
48
+ struct.status = status.to_h
49
+ end
50
+ end
51
+
52
+ resources.push(struct.to_h)
53
+ end
54
+ end
55
+
56
+ #
57
+ # describe_delivery_channels
58
+ #
59
+ @client.describe_delivery_channels.each_with_index do |response, page|
60
+ log(response.context.operation_name, page)
61
+
62
+ response.delivery_channels.each do |channel|
63
+ struct = OpenStruct.new(channel.to_h)
64
+ struct.type = 'delivery_channel'
65
+ struct.arn = "arn:aws:config:#{@region}:delivery_channel/#{channel.name}"
66
+
67
+ # describe_delivery_channel_status (only accepts one channel)
68
+ @client.describe_delivery_channel_status({ delivery_channel_names: [channel.name] }).each do |response|
69
+ response.delivery_channels_status.each do |status|
70
+ struct.status = status.to_h
71
+ end
72
+ end
73
+
74
+ resources.push(struct.to_h)
75
+ end
76
+ end
77
+
78
+ resources
79
+ end
80
+ end
@@ -0,0 +1,25 @@
1
+ class DirectConnect < Mapper
2
+ #
3
+ # Returns an array of resources.
4
+ #
5
+ def collect
6
+ resources = []
7
+
8
+ #
9
+ # describe_connections
10
+ #
11
+ @client.describe_connections.each_with_index do |response, page|
12
+ log(response.context.operation_name, page)
13
+
14
+ response.connections.each do |connection|
15
+ struct = OpenStruct.new(connection.to_h)
16
+ struct.type = 'connection'
17
+ struct.arn = "arn:aws:service:#{@service}::connection/#{connection.connection_id}"
18
+
19
+ resources.push(struct.to_h)
20
+ end
21
+ end
22
+
23
+ resources
24
+ end
25
+ end
@@ -0,0 +1,27 @@
1
+ class DirectoryService < Mapper
2
+ #
3
+ # Returns an array of resources.
4
+ #
5
+ # TODO: confirm paging behavior
6
+ #
7
+ def collect
8
+ resources = []
9
+
10
+ #
11
+ # describe_directories
12
+ #
13
+ @client.describe_directories.each_with_index do |response, page|
14
+ log(response.context.operation_name, page)
15
+
16
+ response.directory_descriptions.each do |directory|
17
+ struct = OpenStruct.new(directory.to_h)
18
+ struct.type = 'directory'
19
+ struct.arn = "arn:aws:#{@service}:#{@region}::directory/#{directory.directory_id}"
20
+
21
+ resources.push(struct.to_h)
22
+ end
23
+ end
24
+
25
+ resources
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ class DatabaseMigrationService < Mapper
2
+ #
3
+ # Returns an array of resources.
4
+ #
5
+ def collect
6
+ resources = []
7
+
8
+ #
9
+ # describe_replication_instances
10
+ #
11
+ @client.describe_replication_instances.each_with_index do |response, page|
12
+ log(response.context.operation_name, page)
13
+
14
+ response.replication_instances.each do |instance|
15
+ struct = OpenStruct.new(instance.to_h)
16
+ struct.type = 'replication_instance'
17
+ struct.arb = "arn:aws:#{@service}:#{@region}::replication_instance/#{instance.replication_instance_identifier}"
18
+
19
+ resources.push(struct.to_h)
20
+ end
21
+ end
22
+
23
+ resources
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ class DynamoDB < Mapper
2
+ #
3
+ # Returns an array of resources.
4
+ #
5
+ def collect
6
+ resources = []
7
+
8
+ #
9
+ # list_tables
10
+ #
11
+ @client.list_tables.each_with_index do |response, page|
12
+ log(response.context.operation_name, page)
13
+
14
+ # describe_table
15
+ response.table_names.each do |table_name|
16
+ struct = OpenStruct.new(@client.describe_table({ table_name: table_name }).table.to_h)
17
+ struct.type = 'table'
18
+ struct.arn = struct.table_arn
19
+
20
+ resources.push(struct.to_h)
21
+ end
22
+ end
23
+
24
+ resources
25
+ end
26
+ end
@@ -0,0 +1,257 @@
1
+ class EC2 < Mapper
2
+ #
3
+ # Returns an array of resources.
4
+ #
5
+ def collect
6
+ resources = []
7
+ image_params = {
8
+ filters: [{ name: 'state', values: ['available'] }],
9
+ owners: ['self']
10
+ }
11
+ snapshot_params = {
12
+ owner_ids: ['self']
13
+ }
14
+
15
+ # global calls
16
+ if @region == 'global'
17
+
18
+ #
19
+ # get_account_attributes
20
+ #
21
+ @client.describe_account_attributes.each do |response|
22
+ log(response.context.operation_name)
23
+
24
+ struct = OpenStruct.new
25
+ struct.attributes = response.account_attributes.map(&:to_h)
26
+ struct.type = 'account'
27
+
28
+ resources.push(struct.to_h)
29
+ end
30
+ end
31
+
32
+ # regional calls
33
+ if @region != 'global'
34
+ #
35
+ # describe_instances
36
+ #
37
+ @client.describe_instances.each_with_index do |response, page|
38
+ log(response.context.operation_name, page)
39
+
40
+ # reservations
41
+ response.reservations.each_with_index do |reservation, page|
42
+ log(response.context.operation_name, 'reservations', page)
43
+
44
+ # instances
45
+ reservation.instances.each do |instance|
46
+ struct = OpenStruct.new(instance.to_h)
47
+ struct.type = 'instance'
48
+ struct.arn = instance.instance_id # no true ARN
49
+ struct.reservation_id = reservation.reservation_id
50
+
51
+ resources.push(struct.to_h)
52
+ end
53
+ end
54
+ end
55
+
56
+ #
57
+ # describe_vpcs
58
+ #
59
+ @client.describe_vpcs.each_with_index do |response, page|
60
+ log(response.context.operation_name, page)
61
+
62
+ response.vpcs.each do |vpc|
63
+ struct = OpenStruct.new(vpc.to_h)
64
+ struct.type = 'vpc'
65
+ struct.arn = vpc.vpc_id # no true ARN
66
+ struct.flow_logs = @client
67
+ .describe_flow_logs({ filter: [{ name: 'resource-id', values: [vpc.vpc_id] }] })
68
+ .flow_logs.first.to_h
69
+
70
+ resources.push(struct.to_h)
71
+ end
72
+ end
73
+
74
+ #
75
+ # describe_security_groups
76
+ #
77
+ @client.describe_security_groups.each_with_index do |response, page|
78
+ log(response.context.operation_name, page)
79
+
80
+ response.security_groups.each do |security_group|
81
+ struct = OpenStruct.new(security_group.to_h)
82
+ struct.type = 'security_group'
83
+ struct.arn = security_group.group_id # no true ARN
84
+
85
+ resources.push(struct.to_h)
86
+ end
87
+ end
88
+
89
+ #
90
+ # describe_network_interfaces
91
+ #
92
+ @client.describe_network_interfaces.each_with_index do |response, page|
93
+ log(response.context.operation_name, page)
94
+
95
+ response.network_interfaces.each do |network_interface|
96
+ struct = OpenStruct.new(network_interface.to_h)
97
+ struct.type = 'network_interface'
98
+ struct.arn = network_interface.network_interface_id # no true ARN
99
+
100
+ resources.push(struct.to_h)
101
+ end
102
+ end
103
+
104
+ #
105
+ # describe_subnets
106
+ #
107
+ @client.describe_subnets.each_with_index do |response, page|
108
+ log(response.context.operation_name, page)
109
+
110
+ response.subnets.each do |subnet|
111
+ struct = OpenStruct.new(subnet.to_h)
112
+ struct.type = 'subnet'
113
+ struct.arn = subnet.subnet_arn
114
+
115
+ resources.push(struct.to_h)
116
+ end
117
+ end
118
+
119
+ #
120
+ # describe_addresses
121
+ #
122
+ @client.describe_addresses.each_with_index do |response, page|
123
+ log(response.context.operation_name, page)
124
+
125
+ response.addresses.each do |address|
126
+ struct = OpenStruct.new(address.to_h)
127
+ struct.type = 'eip_address'
128
+ struct.arn = address.allocation_id
129
+
130
+ resources.push(struct.to_h)
131
+ end
132
+ end
133
+
134
+ #
135
+ # describe_nat_gateways
136
+ #
137
+ @client.describe_nat_gateways.each_with_index do |response, page|
138
+ log(response.context.operation_name, page)
139
+
140
+ response.nat_gateways.each do |gateway|
141
+ struct = OpenStruct.new(gateway.to_h)
142
+ struct.type = 'nat_gateway'
143
+ struct.arn = gateway.nat_gateway_id # no true ARN
144
+
145
+ resources.push(struct.to_h)
146
+ end
147
+ end
148
+
149
+ #
150
+ # describe_route_tables
151
+ #
152
+ @client.describe_route_tables.each_with_index do |response, page|
153
+ log(response.context.operation_name, page)
154
+
155
+ response.route_tables.each do |table|
156
+ struct = OpenStruct.new(table.to_h)
157
+ struct.type = 'route_table'
158
+ struct.arn = table.route_table_id # no true ARN
159
+
160
+ resources.push(struct.to_h)
161
+ end
162
+ end
163
+
164
+ #
165
+ # describe_images
166
+ #
167
+ @client.describe_images(image_params).each_with_index do |response, page|
168
+ log(response.context.operation_name, page)
169
+
170
+ response.images.each do |image|
171
+ struct = OpenStruct.new(image.to_h)
172
+ struct.type = 'image'
173
+ struct.arn = image.image_id # no true ARN
174
+
175
+ resources.push(struct.to_h)
176
+ end
177
+ end
178
+
179
+ #
180
+ # describe_snapshots
181
+ #
182
+ @client.describe_snapshots(snapshot_params).each_with_index do |response, page|
183
+ log(response.context.operation_name, page)
184
+
185
+ response.snapshots.each do |snapshot|
186
+ struct = OpenStruct.new(snapshot.to_h)
187
+ struct.type = 'snapshot'
188
+ struct.arn = snapshot.snapshot_id # no true ARN
189
+
190
+ resources.push(struct.to_h)
191
+ end
192
+ end
193
+
194
+ #
195
+ # describe_flow_logs
196
+ #
197
+ @client.describe_flow_logs.each_with_index do |response, page|
198
+ log(response.context.operation_name, page)
199
+
200
+ response.flow_logs.each do |flow_log|
201
+ struct = OpenStruct.new(flow_log.to_h)
202
+ struct.type = 'flow_log'
203
+ struct.arn = flow_log.flow_log_id # no true ARN
204
+
205
+ resources.push(struct.to_h)
206
+ end
207
+ end
208
+
209
+ #
210
+ # describe_volumes
211
+ #
212
+ @client.describe_volumes.each_with_index do |response, page|
213
+ log(response.context.operation_name, page)
214
+
215
+ response.volumes.each do |volume|
216
+ struct = OpenStruct.new(volume.to_h)
217
+ struct.type = 'volume'
218
+ struct.arn = volume.volume_id # no true ARN
219
+
220
+ resources.push(struct.to_h)
221
+ end
222
+ end
223
+
224
+ #
225
+ # describe_vpn_gateways
226
+ #
227
+ @client.describe_vpn_gateways.each_with_index do |response, page|
228
+ log(response.context.operation_name, page)
229
+
230
+ response.vpn_gateways.each do |gateway|
231
+ struct = OpenStruct.new(gateway.to_h)
232
+ struct.type = 'vpn_gateway'
233
+ struct.arn = gateway.vpn_gateway_id # no true ARN
234
+
235
+ resources.push(struct.to_h)
236
+ end
237
+ end
238
+
239
+ #
240
+ # describe_vpc_peering_connections
241
+ #
242
+ @client.describe_vpc_peering_connections.each_with_index do |response, page|
243
+ log(response.context.operation_name, page)
244
+
245
+ response.vpc_peering_connections.each do |peer|
246
+ struct = OpenStruct.new(peer.to_h)
247
+ struct.type = 'peering_connection'
248
+ struct.arn = peer.vpc_peering_connection_id # no true ARN
249
+
250
+ resources.push(struct.to_h)
251
+ end
252
+ end
253
+ end
254
+
255
+ resources
256
+ end
257
+ end