inspec 2.2.20 → 2.2.27
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -12
- data/docs/resources/file.md.erb +10 -3
- data/lib/inspec/base_cli.rb +2 -0
- data/lib/inspec/cli.rb +5 -0
- data/lib/inspec/dependencies/dependency_set.rb +3 -3
- data/lib/inspec/dependencies/requirement.rb +18 -11
- data/lib/inspec/profile.rb +9 -1
- data/lib/inspec/reporters/json.rb +1 -0
- data/lib/inspec/resource.rb +21 -9
- data/lib/inspec/runner_rspec.rb +2 -2
- data/lib/inspec/version.rb +1 -1
- data/lib/resources/aide_conf.rb +3 -5
- data/lib/resources/apache_conf.rb +8 -0
- data/lib/resources/auditd.rb +13 -15
- data/lib/resources/aws/aws_cloudtrail_trails.rb +4 -5
- data/lib/resources/aws/aws_ec2_instances.rb +3 -4
- data/lib/resources/aws/aws_iam_access_keys.rb +16 -18
- data/lib/resources/aws/aws_iam_groups.rb +2 -2
- data/lib/resources/aws/aws_iam_policies.rb +4 -5
- data/lib/resources/aws/aws_iam_users.rb +17 -22
- data/lib/resources/aws/aws_kms_keys.rb +4 -5
- data/lib/resources/aws/aws_route_tables.rb +4 -5
- data/lib/resources/aws/aws_s3_buckets.rb +3 -5
- data/lib/resources/aws/aws_security_groups.rb +3 -5
- data/lib/resources/aws/aws_sns_topics.rb +3 -5
- data/lib/resources/aws/aws_subnets.rb +6 -8
- data/lib/resources/aws/aws_vpcs.rb +6 -8
- data/lib/resources/azure/azure_generic_resource.rb +7 -11
- data/lib/resources/azure/azure_virtual_machine_data_disk.rb +15 -17
- data/lib/resources/crontab.rb +9 -11
- data/lib/resources/docker.rb +32 -38
- data/lib/resources/elasticsearch.rb +24 -26
- data/lib/resources/etc_fstab.rb +8 -10
- data/lib/resources/etc_hosts.rb +4 -6
- data/lib/resources/etc_hosts_allow_deny.rb +4 -6
- data/lib/resources/file.rb +1 -1
- data/lib/resources/firewalld.rb +6 -8
- data/lib/resources/groups.rb +6 -8
- data/lib/resources/nginx_conf.rb +4 -6
- data/lib/resources/packages.rb +5 -7
- data/lib/resources/passwd.rb +9 -11
- data/lib/resources/port.rb +7 -9
- data/lib/resources/postgres_hba_conf.rb +7 -9
- data/lib/resources/postgres_ident_conf.rb +4 -6
- data/lib/resources/processes.rb +13 -15
- data/lib/resources/ssl.rb +5 -7
- data/lib/resources/users.rb +15 -17
- data/lib/resources/xinetd.rb +9 -11
- metadata +2 -2
@@ -19,8 +19,8 @@ class AwsIamGroups < Inspec.resource(1)
|
|
19
19
|
|
20
20
|
# Underlying FilterTable implementation.
|
21
21
|
filter = FilterTable.create
|
22
|
-
filter.
|
23
|
-
filter.
|
22
|
+
filter.register_column(:group_names, field: :group_name)
|
23
|
+
filter.install_filter_methods_on_resource(self, :table)
|
24
24
|
|
25
25
|
def to_s
|
26
26
|
'IAM Groups'
|
@@ -18,11 +18,10 @@ class AwsIamPolicies < Inspec.resource(1)
|
|
18
18
|
|
19
19
|
# Underlying FilterTable implementation.
|
20
20
|
filter = FilterTable.create
|
21
|
-
filter.
|
22
|
-
|
23
|
-
.
|
24
|
-
|
25
|
-
filter.connect(self, :table)
|
21
|
+
filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
|
22
|
+
filter.register_column(:policy_names, field: :policy_name)
|
23
|
+
.register_column(:arns, field: :arn)
|
24
|
+
filter.install_filter_methods_on_resource(self, :table)
|
26
25
|
|
27
26
|
def to_s
|
28
27
|
'IAM Policies'
|
@@ -62,35 +62,30 @@ class AwsIamUsers < Inspec.resource(1)
|
|
62
62
|
end
|
63
63
|
|
64
64
|
filter = FilterTable.create
|
65
|
-
filter.add_accessor(:where)
|
66
|
-
.add_accessor(:entries)
|
67
|
-
# Summary methods
|
68
|
-
filter.add(:exists?) { |table| !table.params.empty? }
|
69
|
-
.add(:count) { |table| table.params.count }
|
70
65
|
|
71
66
|
# These are included on the initial fetch
|
72
|
-
filter.
|
73
|
-
.
|
74
|
-
.
|
75
|
-
.
|
76
|
-
.
|
67
|
+
filter.register_column(:usernames, field: :user_name)
|
68
|
+
.register_column(:username) { |res| res.entries.map { |row| row[:user_name] } } # We should deprecate this; plural resources get plural properties
|
69
|
+
.register_column(:password_ever_used?, field: :password_ever_used?)
|
70
|
+
.register_column(:password_never_used?, field: :password_never_used?)
|
71
|
+
.register_column(:password_last_used_days_ago, field: :password_last_used_days_ago)
|
77
72
|
|
78
73
|
# Remaining properties / criteria are handled lazily, grouped by fetcher
|
79
|
-
filter.
|
80
|
-
.
|
74
|
+
filter.register_column(:has_console_password?, field: :has_console_password?, lazy: method(:lazy_get_login_profile))
|
75
|
+
.register_column(:has_console_password, field: :has_console_password, lazy: method(:lazy_get_login_profile))
|
81
76
|
|
82
|
-
filter.
|
83
|
-
.
|
77
|
+
filter.register_column(:has_mfa_enabled?, field: :has_mfa_enabled?, lazy: method(:lazy_list_mfa_devices))
|
78
|
+
.register_column(:has_mfa_enabled, field: :has_mfa_enabled, lazy: method(:lazy_list_mfa_devices))
|
84
79
|
|
85
|
-
filter.
|
86
|
-
.
|
87
|
-
.
|
80
|
+
filter.register_column(:has_inline_policies?, field: :has_inline_policies?, lazy: method(:lazy_list_user_policies))
|
81
|
+
.register_column(:has_inline_policies, field: :has_inline_policies, lazy: method(:lazy_list_user_policies))
|
82
|
+
.register_column(:inline_policy_names, field: :inline_policy_names, style: :simple, lazy: method(:lazy_list_user_policies))
|
88
83
|
|
89
|
-
filter.
|
90
|
-
.
|
91
|
-
.
|
92
|
-
.
|
93
|
-
filter.
|
84
|
+
filter.register_column(:has_attached_policies?, field: :has_attached_policies?, lazy: method(:lazy_list_attached_policies))
|
85
|
+
.register_column(:has_attached_policies, field: :has_attached_policies, lazy: method(:lazy_list_attached_policies))
|
86
|
+
.register_column(:attached_policy_names, field: :attached_policy_names, style: :simple, lazy: method(:lazy_list_attached_policies))
|
87
|
+
.register_column(:attached_policy_arns, field: :attached_policy_arns, style: :simple, lazy: method(:lazy_list_attached_policies))
|
88
|
+
filter.install_filter_methods_on_resource(self, :table)
|
94
89
|
|
95
90
|
def validate_params(raw_params)
|
96
91
|
# No params yet
|
@@ -18,11 +18,10 @@ class AwsKmsKeys < Inspec.resource(1)
|
|
18
18
|
|
19
19
|
# Underlying FilterTable implementation.
|
20
20
|
filter = FilterTable.create
|
21
|
-
filter.
|
22
|
-
|
23
|
-
.
|
24
|
-
|
25
|
-
filter.connect(self, :table)
|
21
|
+
filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
|
22
|
+
filter.register_column(:key_arns, field: :key_arn)
|
23
|
+
.register_column(:key_ids, field: :key_id)
|
24
|
+
filter.install_filter_methods_on_resource(self, :table)
|
26
25
|
|
27
26
|
def to_s
|
28
27
|
'KMS Keys'
|
@@ -11,11 +11,10 @@ class AwsRouteTables < Inspec.resource(1)
|
|
11
11
|
include AwsPluralResourceMixin
|
12
12
|
# Underlying FilterTable implementation.
|
13
13
|
filter = FilterTable.create
|
14
|
-
filter.
|
15
|
-
|
16
|
-
.
|
17
|
-
|
18
|
-
filter.connect(self, :routes_data)
|
14
|
+
filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
|
15
|
+
filter.register_column(:vpc_ids, field: :vpc_id)
|
16
|
+
.register_column(:route_table_ids, field: :route_table_id)
|
17
|
+
filter.install_filter_methods_on_resource(self, :routes_data)
|
19
18
|
|
20
19
|
def routes_data
|
21
20
|
@table
|
@@ -14,11 +14,9 @@ class AwsS3Buckets < Inspec.resource(1)
|
|
14
14
|
|
15
15
|
# Underlying FilterTable implementation.
|
16
16
|
filter = FilterTable.create
|
17
|
-
filter.
|
18
|
-
|
19
|
-
|
20
|
-
.add(:bucket_names, field: :name)
|
21
|
-
filter.connect(self, :table)
|
17
|
+
filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
|
18
|
+
filter.register_column(:bucket_names, field: :name)
|
19
|
+
filter.install_filter_methods_on_resource(self, :table)
|
22
20
|
|
23
21
|
def to_s
|
24
22
|
'S3 Buckets'
|
@@ -18,11 +18,9 @@ EOX
|
|
18
18
|
|
19
19
|
# Underlying FilterTable implementation.
|
20
20
|
filter = FilterTable.create
|
21
|
-
filter.
|
22
|
-
|
23
|
-
|
24
|
-
.add(:group_ids, field: :group_id)
|
25
|
-
filter.connect(self, :table)
|
21
|
+
filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
|
22
|
+
filter.register_column(:group_ids, field: :group_id)
|
23
|
+
filter.install_filter_methods_on_resource(self, :table)
|
26
24
|
|
27
25
|
def to_s
|
28
26
|
'EC2 Security Groups'
|
@@ -33,11 +33,9 @@ class AwsSnsTopics < Inspec.resource(1)
|
|
33
33
|
|
34
34
|
# Underlying FilterTable implementation.
|
35
35
|
filter = FilterTable.create
|
36
|
-
filter.
|
37
|
-
|
38
|
-
|
39
|
-
.add(:topic_arns, field: :topic_arn)
|
40
|
-
filter.connect(self, :table)
|
36
|
+
filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
|
37
|
+
filter.register_column(:topic_arns, field: :topic_arn)
|
38
|
+
filter.install_filter_methods_on_resource(self, :table)
|
41
39
|
|
42
40
|
def to_s
|
43
41
|
'EC2 SNS Topics'
|
@@ -27,14 +27,12 @@ class AwsSubnets < Inspec.resource(1)
|
|
27
27
|
|
28
28
|
# Underlying FilterTable implementation.
|
29
29
|
filter = FilterTable.create
|
30
|
-
filter.
|
31
|
-
|
32
|
-
.
|
33
|
-
.
|
34
|
-
.
|
35
|
-
|
36
|
-
.add(:states, field: :state)
|
37
|
-
filter.connect(self, :table)
|
30
|
+
filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
|
31
|
+
filter.register_column(:vpc_ids, field: :vpc_id)
|
32
|
+
.register_column(:subnet_ids, field: :subnet_id)
|
33
|
+
.register_column(:cidr_blocks, field: :cidr_block)
|
34
|
+
.register_column(:states, field: :state)
|
35
|
+
filter.install_filter_methods_on_resource(self, :table)
|
38
36
|
|
39
37
|
def to_s
|
40
38
|
'EC2 VPC Subnets'
|
@@ -12,15 +12,13 @@ class AwsVpcs < Inspec.resource(1)
|
|
12
12
|
|
13
13
|
# Underlying FilterTable implementation.
|
14
14
|
filter = FilterTable.create
|
15
|
-
filter.
|
16
|
-
|
17
|
-
.
|
18
|
-
.add(:cidr_blocks, field: :cidr_block)
|
19
|
-
.add(:vpc_ids, field: :vpc_id)
|
15
|
+
filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
|
16
|
+
filter.register_column(:cidr_blocks, field: :cidr_block)
|
17
|
+
.register_column(:vpc_ids, field: :vpc_id)
|
20
18
|
# We need a dummy here, so FilterTable will define and populate the dhcp_options_id field
|
21
|
-
filter.
|
22
|
-
.
|
23
|
-
filter.
|
19
|
+
filter.register_column(:dummy, field: :dhcp_options_id)
|
20
|
+
.register_column(:dhcp_options_ids) { |obj| obj.entries.map(&:dhcp_options_id).uniq }
|
21
|
+
filter.install_filter_methods_on_resource(self, :table)
|
24
22
|
|
25
23
|
def validate_params(raw_params)
|
26
24
|
# No params yet
|
@@ -33,17 +33,13 @@ module Inspec::Resources
|
|
33
33
|
|
34
34
|
# Define the filter table so that it can be interrogated
|
35
35
|
@filter = FilterTable.create
|
36
|
-
@filter.
|
37
|
-
.
|
38
|
-
.
|
39
|
-
.
|
40
|
-
.
|
41
|
-
|
42
|
-
|
43
|
-
.add(:location, field: 'location')
|
44
|
-
.add(:properties, field: 'properties')
|
45
|
-
|
46
|
-
@filter.connect(self, :probes)
|
36
|
+
@filter.register_filter_method(:contains)
|
37
|
+
.register_column(:type, field: 'type')
|
38
|
+
.register_column(:name, field: 'name')
|
39
|
+
.register_column(:location, field: 'location')
|
40
|
+
.register_column(:properties, field: 'properties')
|
41
|
+
|
42
|
+
@filter.install_filter_methods_on_resource(self, :probes)
|
47
43
|
|
48
44
|
def parse_resource(resource)
|
49
45
|
# return a hash of information
|
@@ -15,23 +15,21 @@ module Inspec::Resources
|
|
15
15
|
|
16
16
|
# Create a filter table so that tests on the disk can be performed
|
17
17
|
filter = FilterTable.create
|
18
|
-
filter.
|
19
|
-
|
20
|
-
.
|
21
|
-
.
|
22
|
-
.
|
23
|
-
.
|
24
|
-
.
|
25
|
-
.
|
26
|
-
.
|
27
|
-
.
|
28
|
-
.
|
29
|
-
.
|
30
|
-
.
|
31
|
-
.
|
32
|
-
|
33
|
-
.add(:resource_group, field: :resource_group)
|
34
|
-
filter.connect(self, :datadisk_details)
|
18
|
+
filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
|
19
|
+
filter.register_column(:disk, field: :disk)
|
20
|
+
.register_column(:number, field: :number)
|
21
|
+
.register_column(:name, field: :name)
|
22
|
+
.register_column(:size, field: :size)
|
23
|
+
.register_column(:vhd_uri, field: :vhd_uri)
|
24
|
+
.register_column(:storage_account_name, field: :storage_account_name)
|
25
|
+
.register_column(:lun, field: :lun)
|
26
|
+
.register_column(:caching, field: :caching)
|
27
|
+
.register_column(:create_option, field: :create_option)
|
28
|
+
.register_column(:is_managed_disk?, field: :is_managed_disk?)
|
29
|
+
.register_column(:storage_account_type, field: :storage_account_type)
|
30
|
+
.register_column(:subscription_id, field: :subscription_id)
|
31
|
+
.register_column(:resource_group, field: :resource_group)
|
32
|
+
filter.install_filter_methods_on_resource(self, :datadisk_details)
|
35
33
|
|
36
34
|
# Constructor for the resource. This calls the parent constructor to
|
37
35
|
# get the generic resource for the specified machine. This will provide
|
data/lib/resources/crontab.rb
CHANGED
@@ -65,24 +65,22 @@ module Inspec::Resources
|
|
65
65
|
end
|
66
66
|
|
67
67
|
filter = FilterTable.create
|
68
|
-
filter.
|
69
|
-
.
|
70
|
-
.
|
71
|
-
.
|
72
|
-
.
|
73
|
-
.
|
74
|
-
.
|
75
|
-
.add(:user, field: 'user')
|
76
|
-
.add(:commands, field: 'command')
|
68
|
+
filter.register_column(:minutes, field: 'minute')
|
69
|
+
.register_column(:hours, field: 'hour')
|
70
|
+
.register_column(:days, field: 'day')
|
71
|
+
.register_column(:months, field: 'month')
|
72
|
+
.register_column(:weekdays, field: 'weekday')
|
73
|
+
.register_column(:user, field: 'user')
|
74
|
+
.register_column(:commands, field: 'command')
|
77
75
|
|
78
76
|
# rebuild the crontab line from raw content
|
79
|
-
filter.
|
77
|
+
filter.register_custom_property(:content) { |t, _|
|
80
78
|
t.entries.map do |e|
|
81
79
|
[e.minute, e.hour, e.day, e.month, e.weekday, e.user, e.command].compact.join(' ')
|
82
80
|
end.join("\n")
|
83
81
|
}
|
84
82
|
|
85
|
-
filter.
|
83
|
+
filter.install_filter_methods_on_resource(self, :params)
|
86
84
|
|
87
85
|
def to_s
|
88
86
|
if is_system_crontab?
|
data/lib/resources/docker.rb
CHANGED
@@ -10,25 +10,23 @@ module Inspec::Resources
|
|
10
10
|
class DockerContainerFilter
|
11
11
|
# use filtertable for containers
|
12
12
|
filter = FilterTable.create
|
13
|
-
filter.
|
14
|
-
|
15
|
-
.
|
16
|
-
.
|
17
|
-
.
|
18
|
-
.
|
19
|
-
.
|
20
|
-
.
|
21
|
-
.
|
22
|
-
.
|
23
|
-
.
|
24
|
-
.
|
25
|
-
.
|
26
|
-
.
|
27
|
-
.add(:exists?) { |x| !x.entries.empty? }
|
28
|
-
.add(:running?) { |x|
|
13
|
+
filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
|
14
|
+
filter.register_column(:commands, field: 'command')
|
15
|
+
.register_column(:ids, field: 'id')
|
16
|
+
.register_column(:images, field: 'image')
|
17
|
+
.register_column(:labels, field: 'labels')
|
18
|
+
.register_column(:local_volumes, field: 'localvolumes')
|
19
|
+
.register_column(:mounts, field: 'mounts')
|
20
|
+
.register_column(:names, field: 'names')
|
21
|
+
.register_column(:networks, field: 'networks')
|
22
|
+
.register_column(:ports, field: 'ports')
|
23
|
+
.register_column(:running_for, field: 'runningfor')
|
24
|
+
.register_column(:sizes, field: 'size')
|
25
|
+
.register_column(:status, field: 'status')
|
26
|
+
.register_custom_matcher(:running?) { |x|
|
29
27
|
x.where { status.downcase.start_with?('up') }
|
30
28
|
}
|
31
|
-
filter.
|
29
|
+
filter.install_filter_methods_on_resource(self, :containers)
|
32
30
|
|
33
31
|
attr_reader :containers
|
34
32
|
def initialize(containers)
|
@@ -38,17 +36,15 @@ module Inspec::Resources
|
|
38
36
|
|
39
37
|
class DockerImageFilter
|
40
38
|
filter = FilterTable.create
|
41
|
-
filter.
|
42
|
-
|
43
|
-
.
|
44
|
-
.
|
45
|
-
.
|
46
|
-
.
|
47
|
-
.
|
48
|
-
.
|
49
|
-
|
50
|
-
.add(:exists?) { |x| !x.entries.empty? }
|
51
|
-
filter.connect(self, :images)
|
39
|
+
filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
|
40
|
+
filter.register_column(:ids, field: 'id')
|
41
|
+
.register_column(:repositories, field: 'repository')
|
42
|
+
.register_column(:tags, field: 'tag')
|
43
|
+
.register_column(:sizes, field: 'size')
|
44
|
+
.register_column(:digests, field: 'digest')
|
45
|
+
.register_column(:created, field: 'createdat')
|
46
|
+
.register_column(:created_since, field: 'createdsize')
|
47
|
+
filter.install_filter_methods_on_resource(self, :images)
|
52
48
|
|
53
49
|
attr_reader :images
|
54
50
|
def initialize(images)
|
@@ -58,16 +54,14 @@ module Inspec::Resources
|
|
58
54
|
|
59
55
|
class DockerServiceFilter
|
60
56
|
filter = FilterTable.create
|
61
|
-
filter.
|
62
|
-
|
63
|
-
.
|
64
|
-
.
|
65
|
-
.
|
66
|
-
.
|
67
|
-
.
|
68
|
-
|
69
|
-
.add(:exists?) { |x| !x.entries.empty? }
|
70
|
-
filter.connect(self, :services)
|
57
|
+
filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
|
58
|
+
filter.register_column(:ids, field: 'id')
|
59
|
+
.register_column(:names, field: 'name')
|
60
|
+
.register_column(:modes, field: 'mode')
|
61
|
+
.register_column(:replicas, field: 'replicas')
|
62
|
+
.register_column(:images, field: 'image')
|
63
|
+
.register_column(:ports, field: 'ports')
|
64
|
+
filter.install_filter_methods_on_resource(self, :services)
|
71
65
|
|
72
66
|
attr_reader :services
|
73
67
|
def initialize(services)
|
@@ -24,35 +24,33 @@ module Inspec::Resources
|
|
24
24
|
"
|
25
25
|
|
26
26
|
filter = FilterTable.create
|
27
|
-
filter.
|
28
|
-
|
29
|
-
.
|
30
|
-
.
|
31
|
-
.
|
32
|
-
.
|
33
|
-
.
|
34
|
-
.
|
35
|
-
.
|
36
|
-
.
|
37
|
-
.
|
38
|
-
.
|
39
|
-
.
|
40
|
-
.
|
41
|
-
.
|
42
|
-
.
|
43
|
-
.
|
44
|
-
.
|
45
|
-
.
|
46
|
-
.
|
47
|
-
.
|
48
|
-
.
|
49
|
-
.
|
50
|
-
.add(:exists?) { |x| !x.entries.empty? }
|
51
|
-
.add(:node_count) { |t, _|
|
27
|
+
filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
|
28
|
+
filter.register_column(:cluster_name, field: 'cluster_name')
|
29
|
+
.register_column(:node_name, field: 'name')
|
30
|
+
.register_column(:transport_address, field: 'transport_address')
|
31
|
+
.register_column(:host, field: 'host')
|
32
|
+
.register_column(:ip, field: 'ip')
|
33
|
+
.register_column(:version, field: 'version')
|
34
|
+
.register_column(:build_hash, field: 'build_hash')
|
35
|
+
.register_column(:total_indexing_buffer, field: 'total_indexing_buffer')
|
36
|
+
.register_column(:roles, field: 'roles')
|
37
|
+
.register_column(:settings, field: 'settings')
|
38
|
+
.register_column(:os, field: 'os')
|
39
|
+
.register_column(:process, field: 'process')
|
40
|
+
.register_column(:jvm, field: 'jvm')
|
41
|
+
.register_column(:transport, field: 'transport')
|
42
|
+
.register_column(:http, field: 'http')
|
43
|
+
.register_column(:plugins, field: 'plugins')
|
44
|
+
.register_column(:plugin_list, field: 'plugin_list')
|
45
|
+
.register_column(:modules, field: 'modules')
|
46
|
+
.register_column(:module_list, field: 'module_list')
|
47
|
+
.register_column(:node_id, field: 'node_id')
|
48
|
+
.register_column(:ingest, field: 'ingest')
|
49
|
+
.register_custom_property(:node_count) { |t, _|
|
52
50
|
t.entries.length
|
53
51
|
}
|
54
52
|
|
55
|
-
filter.
|
53
|
+
filter.install_filter_methods_on_resource(self, :nodes)
|
56
54
|
|
57
55
|
attr_reader :nodes, :url
|
58
56
|
|