summaryrefslogtreecommitdiff
path: root/lib/ohai/plugins/linux/platform.rb
blob: 492c61eeb5956c8153c02f07ae8876c4809864ca (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# frozen_string_literal: true
#
# Author:: Adam Jacob (<adam@chef.io>)
# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

Ohai.plugin(:Platform) do
  provides "platform", "platform_version", "platform_family"
  depends "lsb"

  # @deprecated
  def get_redhatish_platform(contents)
    contents[/^Red Hat/i] ? "redhat" : contents[/(\w+)/i, 1].downcase
  end

  # See https://rubular.com/r/78c1yXYa7zDhdV for example matches
  #
  # @param contents [String] the contents of /etc/redhat-release
  #
  # @returns [String] the version string
  #
  def get_redhatish_version(contents)
    contents[/(release)? ([\d\.]+)/, 2]
  end

  #
  # Reads an os-release-info file and parse it into a hash
  #
  # @param file [String] the filename to read (e.g. '/etc/os-release')
  #
  # @returns [Hash] the file parsed into a Hash or nil
  #
  def read_os_release_info(file)
    return nil unless file_exist?(file)

    file_read(file).split.inject({}) do |map, line|
      key, value = line.split("=")
      map[key] = value.gsub(/\A"|"\Z/, "") if value
      map
    end
  end

  #
  # Cached /etc/os-release info Hash. Also has logic for Cisco Nexus
  # switches that pulls the chained CISCO_RELEASE_INFO file into the Hash (other
  # distros can also reuse this method safely).
  #
  # @returns [Hash] the canonical, cached Hash of /etc/os-release info or nil
  #
  def os_release_info
    @os_release_info ||=
      begin
        os_release_info = read_os_release_info("/etc/os-release")
        cisco_release_info = os_release_info["CISCO_RELEASE_INFO"] if os_release_info
        if cisco_release_info && file_exist?(cisco_release_info)
          os_release_info.merge!(read_os_release_info(cisco_release_info))
        end
        os_release_info
      end
  end

  #
  # If /etc/os-release indicates we are Cisco based
  #
  # @returns [Boolean] if we are Cisco according to /etc/os-release
  #
  def os_release_file_is_cisco?
    file_exist?("/etc/os-release") && os_release_info["CISCO_RELEASE_INFO"]
  end

  #
  # Determines the platform version for F5 Big-IP systems
  #
  # @deprecated
  #
  # @returns [String] bigip Linux version from /etc/f5-release
  #
  def bigip_version
    release_contents = file_read("/etc/f5-release")
    release_contents.match(/BIG-IP release (\S*)/)[1] # http://rubular.com/r/O8nlrBVqSb
  rescue NoMethodError, Errno::ENOENT, Errno::EACCES # rescue regex failure, file missing, or permission denied
    logger.warn("Detected F5 Big-IP, but /etc/f5-release could not be parsed to determine platform_version")
    nil
  end

  # our platform names don't match os-release. given a time machine they would but ohai
  # came before the os-release file. This method remaps the os-release names to
  # the ohai names
  #
  # @param id [String] the platform ID from /etc/os-release
  #
  # @returns [String] the platform name to use in Ohai
  #
  def platform_id_remap(id)
    # this catches the centos guest shell in the nexus switch which identifies itself as centos
    return "nexus_centos" if id == "centos" && os_release_file_is_cisco?

    # the platform mappings between the 'ID' field in /etc/os-release and the value
    # ohai uses. If you're adding a new platform here and you want to change the name
    # you'll want to add it here and then add a spec for the platform_id_remap method
    {
      "alinux" => "alibabalinux",
      "amzn" => "amazon",
      "archarm" => "arch",
      "cumulus-linux" => "cumulus",
      "ol" => "oracle",
      "opensuse-leap" => "opensuseleap",
      "rhel" => "redhat",
      "sles_sap" => "suse",
      "sles" => "suse",
      "xenenterprise" => "xenserver",
    }[id.downcase] || id.downcase
  end

  #
  # Determines the platform_family based on the platform
  #
  # @param plat [String] the platform name
  #
  # @returns [String] platform_family value
  #
  def platform_family_from_platform(plat)
    case plat
    when /ubuntu/, /debian/, /linuxmint/, /raspbian/, /cumulus/, /kali/, /pop/
      # apt-get+dpkg almost certainly goes here
      "debian"
    when /centos/, /redhat/, /oracle/, /almalinux/, /rocky/, /scientific/, /enterpriseenterprise/, /xenserver/, /xcp-ng/, /cloudlinux/, /alibabalinux/, /sangoma/, /clearos/, /parallels/, /ibm_powerkvm/, /nexus_centos/, /bigip/, /virtuozzo/ # Note that 'enterpriseenterprise' is oracle's LSB "distributor ID"
      # NOTE: "rhel" should be reserved exclusively for recompiled rhel versions that are nearly perfectly compatible down to the platform_version.
      # The operating systems that are "rhel" should all be as compatible as rhel7 = centos7 = oracle7 = scientific7 (98%-ish core RPM version compatibility
      # and the version numbers MUST track the upstream). The appropriate EPEL version repo should work nearly perfectly.  Some variation like the
      # oracle kernel version differences and tuning and extra packages are clearly acceptable. Almost certainly some distros above (xenserver?)
      # should not be in this list. Please use fedora, below, instead. Also note that this is the only platform_family with this strict of a rule,
      # see the example of the debian platform family for how the rest of the platform_family designations should be used.
      #
      # TODO: when XCP-NG 7.4 support ends we can remove the xcp-ng match. 7.5+ reports as xenenterprise which we remap to xenserver
      "rhel"
    when /amazon/
      "amazon"
    # suse matches opensuse, suse-* opensuse-*, etc. sle[sd\-_] intends to match sles, sled, sle-*, sle_*
    when /suse/, /sle[sd\-_]/
      "suse"
    when /fedora/, /arista_eos/
      # In the broadest sense:  RPM-based, fedora-derived distributions which are not strictly re-compiled RHEL (if it uses RPMs, and smells more like redhat and less like
      # SuSE it probably goes here).
      "fedora"
    when /nexus/, /ios_xr/
      "wrlinux"
    when /gentoo/
      "gentoo"
    when /arch/, /manjaro/
      "arch"
    when /exherbo/
      "exherbo"
    when /alpine/
      "alpine"
    when /clearlinux/
      "clearlinux"
    when /mangeia/
      "mandriva"
    when /slackware/
      "slackware"
    end
  end

  # modern linux distros include a /etc/os-release file, which we now rely on for
  # OS detection. For older distros that do not include that file we fall back to
  # our pre-Ohai 15 detection logic, which is the method below. No new functionality
  # should be added to this logic.
  #
  # @deprecated
  def legacy_platform_detection
    # platform [ and platform_version ? ] should be lower case to avoid dealing with RedHat/Redhat/redhat matching
    if file_exist?("/etc/oracle-release")
      contents = file_read("/etc/oracle-release").chomp
      platform "oracle"
      platform_version get_redhatish_version(contents)
    elsif file_exist?("/etc/enterprise-release")
      contents = file_read("/etc/enterprise-release").chomp
      platform "oracle"
      platform_version get_redhatish_version(contents)
    elsif file_exist?("/etc/f5-release")
      platform "bigip"
      platform_version bigip_version
    elsif file_exist?("/etc/debian_version")
      # Ubuntu and Debian both have /etc/debian_version
      # Ubuntu should always have a working lsb, debian does not by default
      if /Ubuntu/i.match?(lsb[:id])
        platform "ubuntu"
        platform_version lsb[:release]
      else
        platform "debian"
        platform_version file_read("/etc/debian_version").chomp
      end
    elsif file_exist?("/etc/parallels-release")
      contents = file_read("/etc/parallels-release").chomp
      platform get_redhatish_platform(contents)
      platform_version contents.match(/(\d\.\d\.\d)/)[0]
    elsif file_exist?("/etc/Eos-release")
      platform "arista_eos"
      platform_version file_read("/etc/Eos-release").strip.split[-1]
    elsif file_exist?("/etc/redhat-release")
      contents = file_read("/etc/redhat-release").chomp
      platform get_redhatish_platform(contents)
      platform_version get_redhatish_version(contents)
    elsif file_exist?("/etc/system-release")
      contents = file_read("/etc/system-release").chomp
      platform get_redhatish_platform(contents)
      platform_version get_redhatish_version(contents)
    elsif file_exist?("/etc/SuSE-release")
      suse_release = file_read("/etc/SuSE-release")
      suse_version = suse_release.scan(/VERSION = (\d+)\nPATCHLEVEL = (\d+)/).flatten.join(".")
      suse_version = suse_release[/VERSION = ([\d\.]{2,})/, 1] if suse_version == ""
      platform_version suse_version
      if /^openSUSE/.match?(suse_release)
        # opensuse releases >= 42 are openSUSE Leap
        if platform_version.to_i < 42
          platform "opensuse"
        else
          platform "opensuseleap"
        end
      else
        platform "suse"
      end
    elsif os_release_file_is_cisco?
      raise "unknown Cisco /etc/os-release or /etc/cisco-release ID_LIKE field" if
        os_release_info["ID_LIKE"].nil? || !os_release_info["ID_LIKE"].include?("wrlinux")

      case os_release_info["ID"]
      when "nexus"
        platform "nexus"
      when "ios_xr"
        platform "ios_xr"
      else
        raise "unknown Cisco /etc/os-release or /etc/cisco-release ID field"
      end

      platform_version os_release_info["VERSION"]
    elsif file_exist?("/etc/slackware-version")
      platform "slackware"
      platform_version file_read("/etc/slackware-version").scan(/(\d+|\.+)/).join
    elsif file_exist?("/etc/exherbo-release")
      platform "exherbo"
      # no way to determine platform_version in a rolling release distribution
      # kernel release will be used - ex. 3.13
      platform_version shell_out("/bin/uname -r").stdout.strip
    elsif file_exist?("/usr/lib/os-release")
      contents = file_read("/usr/lib/os-release")
      if /clear-linux-os/.match?(contents) # Clear Linux https://clearlinux.org/
        platform "clearlinux"
        platform_version contents[/VERSION_ID=(\d+)/, 1]
      end
    elsif /RedHat/i.match?(lsb[:id])
      platform "redhat"
      platform_version lsb[:release]
    elsif /Amazon/i.match?(lsb[:id])
      platform "amazon"
      platform_version lsb[:release]
    elsif /ScientificSL/i.match?(lsb[:id])
      platform "scientific"
      platform_version lsb[:release]
    elsif /XenServer/i.match?(lsb[:id])
      platform "xenserver"
      platform_version lsb[:release]
    elsif lsb[:id] # LSB can provide odd data that changes between releases, so we currently fall back on it rather than dealing with its subtleties
      platform lsb[:id].downcase
      platform_version lsb[:release]
    end
  end

  # Grab the version from the VERSION_ID field and use the kernel release if that's not
  # available. It should be there for everything, but rolling releases like arch / gentoo
  # where we've traditionally used the kernel as the version
  # @return String the OS version
  def determine_os_version
    # centos only includes the major version in os-release for some reason
    if os_release_info["ID"] == "centos"
      get_redhatish_version(file_read("/etc/redhat-release").chomp)
    # debian testing and unstable don't have VERSION_ID set
    elsif os_release_info["ID"] == "debian"
      os_release_info["VERSION_ID"] || file_read("/etc/debian_version").chomp
    else
      os_release_info["VERSION_ID"] || shell_out("/bin/uname -r").stdout.strip
    end
  end

  collect_data(:linux) do
    if file_exist?("/etc/os-release")
      logger.trace("Plugin platform: Using /etc/os-release for platform detection")

      # fixup os-release names to ohai platform names
      platform platform_id_remap(os_release_info["ID"])

      platform_version determine_os_version
    else # we're on an old Linux distro
      legacy_platform_detection
    end

    # unless we set it in a specific way with the platform logic above set based on platform data
    platform_family platform_family_from_platform(platform) if platform_family.nil?
  end
end