Skip to content

Commit f82b9bc

Browse files
committed
(CONT-670) Add Legacy Facts check
This commit adds the Legacy Facts check originally authored by @mmckinst. Additional contributions to the check were made by @seanmil, @rodjek, @baurmatt, @bart2 and @joshcooper
1 parent b56672d commit f82b9bc

File tree

1 file changed

+189
-0
lines changed

1 file changed

+189
-0
lines changed
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
# Public: A puppet-lint custom check to detect legacy facts.
2+
#
3+
# This check will optionally convert from legacy facts like $::operatingsystem
4+
# or legacy hashed facts like $facts['operatingsystem'] to the
5+
# new structured facts like $facts['os']['name'].
6+
#
7+
# This plugin was adopted in to puppet-lint from https://github.com/mmckinst/puppet-lint-legacy_facts-check
8+
# Thanks to @mmckinst, @seanmil, @rodjek, @baurmatt, @bart2 and @joshcooper for the original work.
9+
PuppetLint.new_check(:legacy_facts) do
10+
LEGACY_FACTS_VAR_TYPES = Set[:VARIABLE, :UNENC_VARIABLE]
11+
12+
# These facts that can't be converted to new facts.
13+
UNCONVERTIBLE_FACTS = ['memoryfree_mb', 'memorysize_mb', 'swapfree_mb',
14+
'swapsize_mb', 'blockdevices', 'interfaces', 'zones',
15+
'sshfp_dsa', 'sshfp_ecdsa', 'sshfp_ed25519',
16+
'sshfp_rsa'].freeze
17+
18+
# These facts will depend on how a system is set up and can't just be
19+
# enumerated like the EASY_FACTS below.
20+
#
21+
# For example a server might have two block devices named 'sda' and 'sdb' so
22+
# there would be a $blockdeivce_sda_vendor and $blockdeivce_sdb_vendor fact
23+
# for each device. Or it could have 26 block devices going all the way up to
24+
# 'sdz'. There is no way to know what the possibilities are so we have to use
25+
# a regex to match them.
26+
REGEX_FACTS = [%r{^blockdevice_(?<devicename>.*)_(?<attribute>model|size|vendor)$},
27+
%r{^(?<attribute>ipaddress|ipaddress6|macaddress|mtu|netmask|netmask6|network|network6)_(?<interface>.*)$},
28+
%r{^processor(?<id>[0-9]+)$},
29+
%r{^sp_(?<name>.*)$},
30+
%r{^ssh(?<algorithm>dsa|ecdsa|ed25519|rsa)key$},
31+
%r{^ldom_(?<name>.*)$},
32+
%r{^zone_(?<name>.*)_(?<attribute>brand|iptype|name|uuid|id|path|status)$}].freeze
33+
34+
# These facts have a one to one correlation between a legacy fact and a new
35+
# structured fact.
36+
EASY_FACTS = {
37+
'architecture' => "facts['os']['architecture']",
38+
'augeasversion' => "facts['augeas']['version']",
39+
'bios_release_date' => "facts['dmi']['bios']['release_date']",
40+
'bios_vendor' => "facts['dmi']['bios']['vendor']",
41+
'bios_version' => "facts['dmi']['bios']['version']",
42+
'boardassettag' => "facts['dmi']['board']['asset_tag']",
43+
'boardmanufacturer' => "facts['dmi']['board']['manufacturer']",
44+
'boardproductname' => "facts['dmi']['board']['product']",
45+
'boardserialnumber' => "facts['dmi']['board']['serial_number']",
46+
'chassisassettag' => "facts['dmi']['chassis']['asset_tag']",
47+
'chassistype' => "facts['dmi']['chassis']['type']",
48+
'domain' => "facts['networking']['domain']",
49+
'fqdn' => "facts['networking']['fqdn']",
50+
'gid' => "facts['identity']['group']",
51+
'hardwareisa' => "facts['processors']['isa']",
52+
'hardwaremodel' => "facts['os']['hardware']",
53+
'hostname' => "facts['networking']['hostname']",
54+
'id' => "facts['identity']['user']",
55+
'ipaddress' => "facts['networking']['ip']",
56+
'ipaddress6' => "facts['networking']['ip6']",
57+
'lsbdistcodename' => "facts['os']['distro']['codename']",
58+
'lsbdistdescription' => "facts['os']['distro']['description']",
59+
'lsbdistid' => "facts['os']['distro']['id']",
60+
'lsbdistrelease' => "facts['os']['distro']['release']['full']",
61+
'lsbmajdistrelease' => "facts['os']['distro']['release']['major']",
62+
'lsbminordistrelease' => "facts['os']['distro']['release']['minor']",
63+
'lsbrelease' => "facts['os']['distro']['release']['specification']",
64+
'macaddress' => "facts['networking']['mac']",
65+
'macosx_buildversion' => "facts['os']['build']",
66+
'macosx_productname' => "facts['os']['product']",
67+
'macosx_productversion' => "facts['os']['version']['full']",
68+
'macosx_productversion_major' => "facts['os']['version']['major']",
69+
'macosx_productversion_minor' => "facts['os']['version']['minor']",
70+
'manufacturer' => "facts['dmi']['manufacturer']",
71+
'memoryfree' => "facts['memory']['system']['available']",
72+
'memorysize' => "facts['memory']['system']['total']",
73+
'netmask' => "facts['networking']['netmask']",
74+
'netmask6' => "facts['networking']['netmask6']",
75+
'network' => "facts['networking']['network']",
76+
'network6' => "facts['networking']['network6']",
77+
'operatingsystem' => "facts['os']['name']",
78+
'operatingsystemmajrelease' => "facts['os']['release']['major']",
79+
'operatingsystemrelease' => "facts['os']['release']['full']",
80+
'osfamily' => "facts['os']['family']",
81+
'physicalprocessorcount' => "facts['processors']['physicalcount']",
82+
'processorcount' => "facts['processors']['count']",
83+
'productname' => "facts['dmi']['product']['name']",
84+
'rubyplatform' => "facts['ruby']['platform']",
85+
'rubysitedir' => "facts['ruby']['sitedir']",
86+
'rubyversion' => "facts['ruby']['version']",
87+
'selinux' => "facts['os']['selinux']['enabled']",
88+
'selinux_config_mode' => "facts['os']['selinux']['config_mode']",
89+
'selinux_config_policy' => "facts['os']['selinux']['config_policy']",
90+
'selinux_current_mode' => "facts['os']['selinux']['current_mode']",
91+
'selinux_enforced' => "facts['os']['selinux']['enforced']",
92+
'selinux_policyversion' => "facts['os']['selinux']['policy_version']",
93+
'serialnumber' => "facts['dmi']['product']['serial_number']",
94+
'swapencrypted' => "facts['memory']['swap']['encrypted']",
95+
'swapfree' => "facts['memory']['swap']['available']",
96+
'swapsize' => "facts['memory']['swap']['total']",
97+
'system32' => "facts['os']['windows']['system32']",
98+
'uptime' => "facts['system_uptime']['uptime']",
99+
'uptime_days' => "facts['system_uptime']['days']",
100+
'uptime_hours' => "facts['system_uptime']['hours']",
101+
'uptime_seconds' => "facts['system_uptime']['seconds']",
102+
'uuid' => "facts['dmi']['product']['uuid']",
103+
'xendomains' => "facts['xen']['domains']",
104+
'zonename' => "facts['solaris_zones']['current']",
105+
}.freeze
106+
107+
# A list of valid hash key token types
108+
HASH_KEY_TYPES = Set[
109+
:STRING, # Double quoted string
110+
:SSTRING, # Single quoted string
111+
:NAME, # Unquoted single word
112+
].freeze
113+
114+
def check
115+
tokens.select { |x| LEGACY_FACTS_VAR_TYPES.include?(x.type) }.each do |token|
116+
fact_name = ''
117+
118+
# Get rid of the top scope before we do our work. We don't need to
119+
# preserve it because it won't work with the new structured facts.
120+
if token.value.start_with?('::')
121+
fact_name = token.value.sub(%r{^::}, '')
122+
123+
# This matches using legacy facts in a the new structured fact. For
124+
# example this would match 'uuid' in $facts['uuid'] so it can be converted
125+
# to facts['dmi']['product']['uuid']"
126+
elsif token.value == 'facts'
127+
fact_name = hash_key_for(token)
128+
129+
elsif token.value.start_with?("facts['")
130+
fact_name = token.value.match(%r{facts\['(.*)'\]})[1]
131+
end
132+
133+
next unless EASY_FACTS.include?(fact_name) || UNCONVERTIBLE_FACTS.include?(fact_name) || fact_name.match(Regexp.union(REGEX_FACTS))
134+
notify :warning, {
135+
message: "legacy fact '#{fact_name}'",
136+
line: token.line,
137+
column: token.column,
138+
token: token,
139+
fact_name: fact_name,
140+
}
141+
end
142+
end
143+
144+
# If the variable is using the $facts hash represented internally by multiple
145+
# tokens, this helper simplifies accessing the hash key.
146+
def hash_key_for(token)
147+
lbrack_token = token.next_code_token
148+
return '' unless lbrack_token && lbrack_token.type == :LBRACK
149+
150+
key_token = lbrack_token.next_code_token
151+
return '' unless key_token && HASH_KEY_TYPES.include?(key_token.type)
152+
153+
key_token.value
154+
end
155+
156+
def fix(problem)
157+
fact_name = problem[:fact_name]
158+
159+
# Check if the variable is using the $facts hash represented internally by
160+
# multiple tokens and remove the tokens for the old legacy key if so.
161+
if problem[:token].value == 'facts'
162+
loop do
163+
t = problem[:token].next_token
164+
remove_token(t)
165+
break if t.type == :RBRACK
166+
end
167+
end
168+
169+
if EASY_FACTS.include?(fact_name)
170+
problem[:token].value = EASY_FACTS[fact_name]
171+
elsif fact_name.match(Regexp.union(REGEX_FACTS))
172+
if (m = fact_name.match(%r{^blockdevice_(?<devicename>.*)_(?<attribute>model|size|vendor)$}))
173+
problem[:token].value = "facts['disks']['" << m['devicename'] << "']['" << m['attribute'] << "']"
174+
elsif (m = fact_name.match(%r{^(?<attribute>ipaddress|ipaddress6|macaddress|mtu|netmask|netmask6|network|network6)_(?<interface>.*)$}))
175+
problem[:token].value = "facts['networking']['interfaces']['" << m['interface'] << "']['" << m['attribute'].sub('address', '') << "']"
176+
elsif (m = fact_name.match(%r{^processor(?<id>[0-9]+)$}))
177+
problem[:token].value = "facts['processors']['models'][" << m['id'] << ']'
178+
elsif (m = fact_name.match(%r{^sp_(?<name>.*)$}))
179+
problem[:token].value = "facts['system_profiler']['" << m['name'] << "']"
180+
elsif (m = fact_name.match(%r{^ssh(?<algorithm>dsa|ecdsa|ed25519|rsa)key$}))
181+
problem[:token].value = "facts['ssh']['" << m['algorithm'] << "']['key']"
182+
elsif (m = fact_name.match(%r{^ldom_(?<name>.*)$}))
183+
problem[:token].value = "facts['ldom']['" << m['name'] << "']"
184+
elsif (m = fact_name.match(%r{^zone_(?<name>.*)_(?<attribute>brand|iptype|name|uuid|id|path|status)$}))
185+
problem[:token].value = "facts['solaris_zones']['zones']['" << m['name'] << "']['" << m['attribute'] << "']"
186+
end
187+
end
188+
end
189+
end

0 commit comments

Comments
 (0)