1bundle common inventory_any
2# @brief Do inventory for any OS
3#
4# This common bundle is for any OS work not handled by specific
5# bundles.
6{
7
8  vars:
9      "release_data" string => "$(this.promise_dirname)/../cf_promises_release_id";
10
11      "data"
12        data => readjson( $(release_data), inf ),
13        if => fileexists( $(release_data) );
14
15      "id"
16        string => "$(data[releaseId])",
17        meta => { "inventory", "attribute_name=Policy Release Id" };
18
19  reports:
20      "DEBUG|DEBUG_$(this.bundle)"::
21        "DEBUG $(this.bundle): Inventory Policy Release Id=$(id)";
22}
23
24bundle agent inventory_autorun
25# @brief Autorun some inventory bundles
26#
27# This agent bundle runs other "autorun" inventory agent bundles
28# explicitly.  It will use bundlesmatching() when CFEngine 3.5 and
29# earlier are no longer supported.
30{
31  methods:
32    !disable_inventory_cmdb::
33      "cmdb" usebundle => cfe_autorun_inventory_cmdb(),
34      handle => "cfe_internal_autorun_inventory_cmdb";
35
36    !disable_inventory_LLDP::
37      "LLDP" usebundle => cfe_autorun_inventory_LLDP(),
38      handle => "cfe_internal_autorun_inventory_LLDP";
39
40    !disable_inventory_package_refresh::
41      "packages_refresh" usebundle => cfe_autorun_inventory_packages(),
42      handle => "cfe_internal_autorun_inventory_packages";
43
44    !disable_inventory_proc::
45      "proc" usebundle => cfe_autorun_inventory_proc(),
46      handle => "cfe_internal_autorun_inventory_proc";
47
48      "proc_cpuinfo" usebundle => cfe_autorun_inventory_proc_cpuinfo(),
49      handle => "cfe_internal_autorun_inventory_proc_cpuinfo";
50
51    !disable_inventory_cpuinfo::
52      "cpuinfo" usebundle => cfe_autorun_inventory_cpuinfo(),
53      handle => "cfe_internal_autorun_inventory_cpuinfo";
54
55    !disable_inventory_fstab::
56      "fstab" usebundle => cfe_autorun_inventory_fstab(),
57      handle => "cfe_internal_autorun_inventory_fstab";
58
59    !disable_inventory_mtab::
60      "mtab" usebundle => cfe_autorun_inventory_mtab(),
61      handle => "cfe_internal_autorun_inventory_mtab";
62
63    !disable_inventory_dmidecode::
64      "dmidecode" usebundle => cfe_autorun_inventory_dmidecode(),
65      handle => "cfe_internal_autorun_inventory_dmidecode";
66
67    !disable_inventory_aws::
68      "aws" usebundle => cfe_autorun_inventory_aws(),
69        handle => "cfe_internal_autorun_inventory_aws";
70
71    !disable_inventory_aws|disable_inventory_aws_ec2_metadata::
72      "aws" usebundle => cfe_autorun_inventory_aws_ec2_metadata(),
73        handle => "cfe_internal_autorun_inventory_ec2_metadata";
74
75    !disable_inventory_setuid::
76      "Inventory SetUID Files" -> { "ENT-4158" }
77        usebundle => cfe_autorun_inventory_setuid(),
78        handle => "cfe_internal_autorun_inventory_setuid";
79
80    any::
81      "listening ports" usebundle => cfe_autorun_inventory_listening_ports(),
82      handle => "cfe_internal_autorun_listening_ports";
83
84      "disk" usebundle => cfe_autorun_inventory_disk(),
85      handle => "cfe_internal_autorun_disk";
86
87      "memory" usebundle => cfe_autorun_inventory_memory(),
88      handle => "cfe_internal_autorun_memory";
89
90      "loadaverage" usebundle => cfe_autorun_inventory_loadaverage(),
91      handle => "cfe_internal_autorun_loadaverage";
92
93      "IP addresses" -> { "ENT-2552", "ENT-4987" }
94        usebundle => cfe_autorun_inventory_ip_addresses,
95        handle => "cfe_internal_autorun_ip_addresses";
96}
97
98bundle agent cfe_autorun_inventory_listening_ports
99# @brief Inventory the listening ports
100#
101# This bundle uses `mon.listening_ports` and is always enabled by
102# default, as it runs instantly and has no side effects.
103{
104  vars:
105      "ports" -> { "ENT-150" }
106        slist => sort( "mon.listening_ports", "int"),
107        meta => { "inventory", "attribute_name=Ports listening" },
108        ifvarclass => some("[0-9]+", "mon.listening_ports"),
109        comment => "We only want to inventory the listening ports if we have
110                    values that make sense.";
111}
112
113bundle agent cfe_autorun_inventory_ip_addresses
114# @brief Inventory ipv4 addresses
115# This will filter the ipv4 and ipv4 loopback address (127.0.0.1, ::as it is likely not very interesting)
116{
117  vars:
118      "ipv4_regex" -> { "ENT-4987" }
119        string => "\b(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\b";
120
121      "ipv4_loopback_regex" -> { "ENT-2552" }
122        string => "127\.0\.0\.1",
123        comment => "Addresses that match this regular expression will be filtered
124                    from the inventory for ipv4 addresses";
125
126      "ipv6_loopback_regex" -> { "ENT-4987" }
127        string => "::1",
128        comment => "Addresses that match this regular expression will be filtered
129                    from the inventory for ipv4 addresses";
130
131    # Strings are displayed more beautifully in Mission Portal than lists, so
132    # we first generate the list of addresses to be inventoried and then do
133    # inventory using an array.
134      "ipv4_addresses"
135        slist => sort( filter( $(ipv4_regex), "sys.ip_addresses", "true", "false", inf), lex ),
136        if => not( isvariable( $(this.promiser) ));
137
138      "ipv4_addresses_non_loopback" -> { "ENT-2552" }
139        slist => sort( filter( $(ipv4_loopback_regex), "$(this.bundle).ipv4_addresses", "true", "true", inf)),
140        if => not( isvariable( $(this.promiser) ));
141
142      "ipv4[$(ipv4_addresses_non_loopback)]" -> { "ENT-2552" }
143        string => "$(ipv4_addresses_non_loopback)",
144        meta => { "inventory", "attribute_name=IPv4 addresses" };
145
146      # sys.ip_addresses contains ipv4 and (as of 3.15.0) ipv6 addresses. We get
147      # the ipv6 addresses indirectly, based on excluding the ipv4 addresses
148      # (which we identify using a regular expression)
149
150      "ipv6_addresses" -> { "ENT-4987" }
151        slist => sort( difference( "sys.ip_addresses", "$(this.bundle).ipv4_addresses" ), lex),
152        if => not( isvariable( $(this.promiser) ));
153
154      "ipv4_addresses_non_loopback" -> { "ENT-4987" }
155        slist => sort( filter( $(ipv6_loopback_regex), "$(this.bundle).ipv4_addresses", "true", "true", inf)),
156        if => not( isvariable( $(this.promiser) ));
157
158      "ipv6[$(ipv6_addresses_non_loopback)]" -> { "ENT-4987" }
159        string => "$(ipv6_addresses_non_loopback)",
160        meta => { "inventory", "attribute_name=IPv6 addresses" };
161
162  reports:
163    DEBUG|DEBUG_cfe_autorun_inventory_ipv4_addresses::
164      "DEBUG $(this.bundle)";
165      "$(const.t)Inventorying: '$(ipv4_addresses)'";
166      "$(const.t)Inventorying: '$(ipv6_addresses)'";
167}
168
169bundle agent cfe_autorun_inventory_disk
170# @brief Inventory the disk (Enterprise only)
171{
172  vars:
173    enterprise::
174      "free" -> { "ENT-5190" }
175        string => "$(mon.value_diskfree)",
176        meta => { "inventory", "attribute_name=Disk free (%)" },
177        if => isvariable( "mon.value_diskfree" );
178}
179
180bundle agent cfe_autorun_inventory_memory
181# @brief Inventory the memory (Enterprise only)
182{
183  vars:
184@if minimum_version(3.11)
185      # The `with` attribute is necessary for this to work in a single promise.
186
187    enterprise_edition.windows::
188
189      # wmic returns "TotalVisibleMemorySize=10760224" so split on = and take
190      # the second item (0-based with nth())
191
192      "total" -> { "ENT-4188" }
193        meta => { "inventory", "attribute_name=Memory size (MB)" },
194        string => format( "%d", eval("$(with)/1024", "math", "infix" )),
195        if => not( isvariable( "total" ) ),
196        with => nth( string_split( execresult("wmic OS get TotalVisibleMemorySize /format:list", useshell ),
197                                   "=", 2), 1);
198
199      "totalPhysical" -> { "CFE-2896" }
200        meta => { "inventory", "attribute_name=Physical memory (MB)" },
201        string => format( "%d", eval("$(with)/1024", "math", "infix" )),
202        if => not( isvariable( "total" ) ),
203        with => nth( string_split( execresult("wmic ComputerSystem get TotalPhysicalMemory /format:list", useshell ),
204                                   "=", 2), 1);
205
206      # This is a volatile metric, perhaps  not well suited for inventory
207      "free"
208        meta => { "report" },
209        string => format( "%d", eval("$(with)/1024", "math", "infix" )),
210        if => not( isvariable( "free" ) ),
211        with => nth( string_split( execresult("wmic OS get FreePhysicalMemory /format:list", useshell ),
212                                   "=", 2), 1);
213@endif
214    enterprise_edition.aix::
215      "total" -> { "CFE-2797", "CFE-2803" }
216        string => execresult("/usr/bin/lparstat -i | awk '/Online Memory/ { print $4 }'", "useshell"),
217        meta => { "inventory", "attribute_name=Memory size (MB)" };
218
219    enterprise_edition.hpux::
220       "total" -> { "ENT-4188" }
221         string => execresult( "machinfo | awk '/^Memory =/ {print $3}'", useshell ),
222         meta => { "inventory", "attribute_name=Memory size (MB)" };
223
224    enterprise_edition.!(aix|windows|hpux)::
225      "total" string => "$(mon.value_mem_total)",
226        meta => { "inventory", "attribute_name=Memory size (MB)" },
227        if => isvariable( "mon.value_mem_total" );
228
229      "free" string => "$(mon.value_mem_free)",
230        if => and( not( isvariable( "free" ) ),
231                   isvariable( "mon.value_mem_free" )),
232        meta => { "report" };
233
234}
235
236bundle agent cfe_autorun_inventory_setuid
237# @brief Inventory setuid files and prune invalid entries from the setuid log
238{
239  vars:
240    !disable_inventory_setuid::
241      "candidates" slist => lsdir( "$(sys.workdir)", "cfagent\..*\.log", true );
242
243      "setuid_log_path"
244        comment => "We select the file that matches the downcased version of the
245                    hostname since sys.fqhost always returns lower case",
246        string => "$(candidates)",
247        if => strcmp( "$(sys.workdir)/cfagent.$(sys.fqhost).log",
248                      string_downcase($(candidates)));
249
250      "files" slist => readstringlist( $(setuid_log_path), "", "$(const.n)", inf, inf);
251
252      "setuid[$(files)]"
253        string => "$(files)",
254        meta => { "inventory", "attribute_name=Setuid files" },
255        if => regcmp( "104\d+", filestat( $(files), modeoct ) );
256
257      "rootsetuid[$(files)]"
258        string => "$(files)",
259        meta => { "inventory", "attribute_name=Root owned setuid files" },
260        if => and( regcmp( "104\d+", filestat( $(files), modeoct ) ),
261                   regcmp( "0", filestat( $(files), uid ) ));
262
263    files:
264    !disable_inventory_setuid::
265      "$(setuid_log_path)"
266        comment => "If the logged file is not currently setuid then we can
267                    safely purge it from the list to avoid unnecessary work.",
268        edit_line => delete_lines_matching( escape( $(files) ) ),
269        if => not( regcmp( "104\d+", filestat( $(files), modeoct ) ) );
270
271  reports:
272    !disable_inventory_setuid.(DEBUG|DEBUG_cfe_autorun_inventory_setuid)::
273      "$(setuid_log_path) present"
274        if =>  fileexists( $(setuid_log_path) );
275
276@if minimum_version(3.11)
277
278      "Candidate: setuid Files: $(files) modeoct=$(with)"
279        with => filestat( $(files), modeoct );
280
281      "Remove $(files) from log by matching $(with)"
282        comment => "If the logged file is not currently setuid then we can
283                    safely purge it from the list to avoid unnecessary work.",
284        with =>  escape( $(files) ),
285        if => not( regcmp( "104\d+", filestat( $(files), modeoct ) ) );
286
287      # The `with` attribute was introduced in 3.11
288      "Inventory: setuid Files: $(files) modeoct=$(with)"
289        with => filestat( $(files), modeoct ),
290        if => regcmp( "104\d+", filestat( $(files), modeoct ) );
291
292      "Inventory: root owned setuid Files: $(files) modeoct=$(with)"
293        with => filestat( $(files), modeoct ),
294        if => and( regcmp( "104\d+", filestat( $(files), modeoct ) ),
295                   regcmp( "0", filestat( $(files), uid ) ));
296@endif
297
298}
299
300bundle agent cfe_autorun_inventory_loadaverage
301# @brief Inventory the loadaverage (Enterprise only)
302{
303  vars:
304    enterprise::
305      "value" -> { "ENT-5190" }
306        string => "$(mon.value_loadavg)",
307        meta => { "report" },
308        if => isvariable( "mon.value_loadavg" );
309}
310
311bundle agent cfe_autorun_inventory_proc
312# @brief Do procfs inventory
313#
314# This bundle will parse these /proc files: consoles, cpuinfo,
315# meminfo, modules, partitions, version, vmstat.  There are
316# some general patterns you can follow to extend it for other /proc
317# items of interest.
318#
319# Contributions welcome.  /proc/net and /proc/sys in general are of
320# wide interest, if you're looking for something fun.  For instance,
321# the network interfaces could be extracted here without calling
322# `ifconfig`.
323{
324  vars:
325      "basefiles" slist => { "consoles", "cpuinfo", "modules", "partitions", "version" };
326      "files[$(basefiles)]" string => "$(inventory_control.proc)/$(basefiles)";
327
328    _have_proc_consoles::
329      "console_count" int =>  readstringarrayidx("consoles",
330                                                 "$(files[consoles])",
331                                                 "\s*#[^\n]*",
332                                                 "\s+",
333                                                 500,
334                                                 50000);
335
336      "console_idx" slist => getindices("consoles");
337
338    _have_proc_modules::
339      "module_count" int =>  readstringarrayidx("modules",
340                                                "$(files[modules])",
341                                                "\s*#[^\n]*",
342                                                "\s+",
343                                                2500,
344                                                250000);
345
346      "module_idx" slist => getindices("modules");
347
348    _have_proc_cpuinfo::
349      # this will extract all the keys in one bunch, so you won't get
350      # detailed info for processor 0 for example
351      "cpuinfo_count" int =>  readstringarrayidx("cpuinfo_array",
352                                                 "$(files[cpuinfo])",
353                                                 "\s*#[^\n]*",
354                                                 "\s*:\s*",
355                                                 500,
356                                                 50000);
357
358      "cpuinfo_idx" slist => getindices("cpuinfo_array");
359      "cpuinfo[$(cpuinfo_array[$(cpuinfo_idx)][0])]" string => "$(cpuinfo_array[$(cpuinfo_idx)][1])";
360      "cpuinfo_keys" slist => getindices("cpuinfo");
361
362    _have_proc_partitions::
363      "partitions_count" int =>  readstringarrayidx("partitions_array",
364                                                    "$(files[partitions])",
365                                                    "major[^\n]*",
366                                                    "\s+",
367                                                    500,
368                                                    50000);
369
370      "partitions_idx" slist => getindices("partitions_array");
371      "partitions[$(partitions_array[$(partitions_idx)][4])]" string => "$(partitions_array[$(partitions_idx)][3])";
372      "partitions_keys" slist => getindices("partitions");
373
374    _have_proc_version::
375      "version" string => readfile("$(files[version])", 2048);
376
377  classes:
378      "have_proc" expression => isdir($(inventory_control.proc));
379
380    have_proc::
381      "_have_proc_$(basefiles)"
382      expression => fileexists("$(files[$(basefiles)])");
383
384    _have_proc_consoles::
385      "have_console_$(consoles[$(console_idx)][0])"
386      expression => "any",
387      scope => "namespace";
388
389    _have_proc_modules::
390      "have_module_$(modules[$(module_idx)][0])"
391      expression => "any",
392      scope => "namespace";
393
394  reports:
395    _have_proc_consoles.verbose_mode::
396      "$(this.bundle): we have console $(consoles[$(console_idx)][0])";
397    _have_proc_modules.verbose_mode::
398      "$(this.bundle): we have module $(modules[$(module_idx)][0])";
399    _have_proc_cpuinfo.verbose_mode::
400      "$(this.bundle): we have cpuinfo $(cpuinfo_keys) = $(cpuinfo[$(cpuinfo_keys)])";
401    _have_proc_partitions.verbose_mode::
402      "$(this.bundle): we have partitions $(partitions_keys) with $(partitions[$(partitions_keys)]) blocks";
403    _have_proc_version.verbose_mode::
404      "$(this.bundle): we have kernel version '$(version)'";
405}
406
407bundle agent cfe_autorun_inventory_proc_cpuinfo
408# @brief Inventory cpu information from proc
409{
410  classes:
411    "_have_cpuinfo" expression => isvariable("default:cfe_autorun_inventory_proc.cpuinfo_idx");
412
413      # So that we don't inventory non dereferenced variables we check to see
414      # if we have the info first This is only necessary because its currently
415      # invalid to do isvariable on an array key that contains a space
416      # Ref: redmine#7088 https://dev.cfengine.com/issues/7088
417      "have_cpuinfo_cpu_cores" expression => strcmp("cpu cores", "$(default:cfe_autorun_inventory_proc.cpuinfo_array[$(default:cfe_autorun_inventory_proc.cpuinfo_idx)][0])");
418      "have_cpuinfo_model_name" expression => strcmp("model name", "$(default:cfe_autorun_inventory_proc.cpuinfo_array[$(default:cfe_autorun_inventory_proc.cpuinfo_idx)][0])");
419
420  vars:
421    _have_cpuinfo::
422      "cpuinfo_physical_cores"
423        string => "$(default:cfe_autorun_inventory_proc.cpuinfo[cpu cores])",
424        ifvarclass => "have_cpuinfo_cpu_cores";
425
426      "cpuinfo_cpu_model_name"
427        string => "$(default:cfe_autorun_inventory_proc.cpuinfo[model name])",
428        ifvarclass => "have_cpuinfo_model_name";
429
430    # We need to be able to count the number of unique physical id lines in
431    # /proc/cpu in order to get a physical processor count.
432      "cpuinfo_lines" slist => readstringlist(
433                                                 "$(default:cfe_autorun_inventory_proc.files[cpuinfo])",
434                                                 "\s*#[^\n]*",
435                                                 "\n",
436                                                 500,
437                                                 50000);
438
439      "cpuinfo_processor_lines"
440        slist => grep("processor\s+:\s\d+", "cpuinfo_lines"),
441        comment => "The number of processor entries in $(default:cfe_autorun_inventory_proc.files[cpuinfo]). If no
442                    'physical id' entries are found this is the processor count";
443
444      "cpuinfo_processor_lines_count"
445        int => length("cpuinfo_processor_lines");
446
447      "cpuinfo_physical_id_lines"
448        slist => grep("physical id.*", "cpuinfo_lines"),
449        comment => "This identifies which physical socket a logical core is on,
450                    the count of the unique physical id lines tells you how
451                    many physical sockets you have. THis would not be present
452                    on systems that are not multicore.";
453
454      "cpuinfo_physical_id_lines_unique"
455        slist => unique("cpuinfo_physical_id_lines");
456
457      "cpuinfo_physical_id_lines_unique_count"
458        int => length("cpuinfo_physical_id_lines_unique");
459
460
461      # If we have physical id lines in cpu info use that for socket inventory,
462      # else we should use the number of processor lines. physical id lines
463      # seem to only be present when multiple cores are active.
464      "cpuinfo_physical_socket_inventory"
465        string => ifelse(isgreaterthan( length("cpuinfo_physical_id_lines"), 0 ), "$(cpuinfo_physical_id_lines_unique_count)",
466                      "$(cpuinfo_processor_lines_count)"),
467        meta => { "inventory", "attribute_name=CPU sockets" };
468
469  reports:
470    DEBUG|DEBUG_cfe_autorun_inventory_proc::
471     "DEBUG $(this.bundle)";
472     "$(const.t)cpuinfo[$(default:cfe_autorun_inventory_proc.cpuinfo_array[$(default:cfe_autorun_inventory_proc.cpuinfo_idx)][0])] = $(default:cfe_autorun_inventory_proc.cpuinfo[$(default:cfe_autorun_inventory_proc.cpuinfo_array[$(default:cfe_autorun_inventory_proc.cpuinfo_idx)][0])])";
473     "$(const.t)CPU physical cores: '$(cpuinfo_physical_cores)'"
474        ifvarclass => "have_cpuinfo_cpu_cores";
475     "$(const.t)CPU model name: '$(cpuinfo_cpu_model_name)'"
476        ifvarclass => "have_cpuinfo_model_name";
477     "$(const.t)CPU Physical Sockets: '$(cpuinfo_physical_socket_inventory)'";
478}
479
480bundle agent cfe_autorun_inventory_cpuinfo
481# @brief Inventory cpu information
482{
483  classes:
484    "_have_proc_cpu_model_name" expression => isvariable("default:cfe_autorun_inventory_proc_cpuinfo.cpuinfo_cpu_model_name");
485    "_have_proc_cpu_physical_cores" expression => isvariable("default:cfe_autorun_inventory_proc_cpuinfo.cpuinfo_physical_cores");
486
487    # We only accept dmidecode values that don't look like cfengine variables,
488    # (starting with dollar), or that have an apparent empty value.
489    "_have_dmidecode_cpu_model_name"
490      not => regcmp("($(const.dollar)\(.*\)|^$)", "$(default:cfe_autorun_inventory_dmidecode.dmi[processor-version])");
491
492  vars:
493    _have_proc_cpu_physical_cores::
494      "cpuinfo_physical_cores"
495        string => "$(default:cfe_autorun_inventory_proc.cpuinfo[cpu cores])",
496        #ifvarclass => "have_cpuinfo_cpu_cores",
497        meta => { "inventory", "attribute_name=CPU physical cores", "derived-from=$(default:cfe_autorun_inventory_proc.files[cpuinfo])" };
498
499    _have_proc_cpu_model_name::
500      "cpu_model"
501        string => "$(default:cfe_autorun_inventory_proc_cpuinfo.cpuinfo_cpu_model_name)",
502        meta => { "inventory", "attribute_name=CPU model", "derived-from=$(default:cfe_autorun_inventory_proc.files[cpuinfo])" };
503
504    _have_dmidecode_cpu_model_name.!_have_proc_cpu_model_name::
505      "cpu_model"
506        string => "$(default:cfe_autorun_inventory_dmidecode.dmi[processor-version])",
507        meta => { "inventory", "attribute_name=CPU model", "derived-from=$(inventory_control.dmidecoder) -s processor-version" };
508
509  reports:
510    DEBUG|DEBUG_cfe_autorun_inventory_cpuinfo::
511      "DEBUG $(this.bundle)";
512      "$(const.t) CPU model: $(cpu_model)";
513      "$(const.t) CPU physical cores: $(cpuinfo_physical_cores)";
514}
515
516bundle common cfe_autorun_inventory_aws
517# @brief inventory AWS EC2 instances
518#
519# Provides:
520#  ec2_instance class based on Amazon markers in dmidecode's system-uuid, bios-version or bios-vendor
521{
522  classes:
523    !disable_inventory_aws::
524      "ec2_instance" -> { "CFE-2924" }
525        comment => "See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/identify_ec2_instances.html",
526        scope => "namespace",
527        expression => regcmp("^[eE][cC]2.*", "$(cfe_autorun_inventory_dmidecode.dmi[system-uuid])"),
528        if => isvariable("cfe_autorun_inventory_dmidecode.dmi[system-uuid]");
529
530      "ec2_instance" -> { "CFE-2924" }
531        expression => regcmp(".*[aA]mazon.*", "$(cfe_autorun_inventory_dmidecode.dmi[bios-version])"),
532        scope => "namespace",
533        if => isvariable("cfe_autorun_inventory_dmidecode.dmi[bios-version]");
534
535      "ec2_instance" -> { "CFE-2924" }
536        expression => regcmp(".*[aA]mazon.*", "$(cfe_autorun_inventory_dmidecode.dmi[bios-vendor])"),
537        scope => "namespace",
538        if => isvariable("cfe_autorun_inventory_dmidecode.dmi[bios-vendor]");
539
540      "ec2_instance" -> { "CFE-2924" }
541        expression => regline( "^ec2.*", "/sys/hypervisor/uuid" ),
542        scope => "namespace",
543        if => fileexists("/sys/hypervisor/uuid");
544
545  reports:
546    (DEBUG|DEBUG_inventory_aws)::
547      "DEBUG $(this.bundle)";
548
549      "$(const.t)+ec2_instance"
550        if => "ec2_instance";
551}
552
553bundle agent cfe_autorun_inventory_aws_ec2_metadata
554# @brief Inventory ec2 metadata
555# Provides:
556{
557  methods:
558    !(disable_inventory_aws|disable_inventory_aws_ec2_metadata)::
559      "cfe_autorun_inventory_aws_ec2_metadata_cache";
560      "cfe_aws_ec2_metadata_from_cache";
561}
562bundle agent cfe_autorun_inventory_aws_ec2_metadata_cache
563# @brief Cache ec2 metadata from http request
564#
565# Provides cache of ec2 instance metadata for inventory
566{
567
568  vars:
569    ec2_instance.!(disable_inventory_aws|disable_inventory_aws_ec2_metadata)::
570      "URL"       string => "http://169.254.169.254/latest/dynamic/instance-identity/document";
571      "cache"     string => "$(sys.statedir)/aws_ec2_metadata";
572
573      "v" -> { "ENT-5233" }
574        string => ifelse( isgreaterthan( $(sys.cf_version_minor), 14), "suppress_inform_capable",
575                          "suppress_inform_incapable");
576
577      "response" -> { "ENT-4900" }
578        data => url_get($(URL), '{"url.max_content": 512000, "url.timeout": 1}'),
579        # To prevent `url_get` from firing on every agent run, this variable
580        # depends on a dummy command which does nothing but fires only once a day
581        depends_on => { "daily_dummy_job_$(v)" };
582
583  commands:
584    _stdlib_path_exists_true::
585
586      "$(paths.true)"
587        comment => "This promise is used for delaying execution of url_get, since locking does not work directly on vars type promises",
588        classes => kept_successful_command,
589        handle => "daily_dummy_job_suppress_inform_incapable",
590        action => if_elapsed_day,
591        if => strcmp( "daily_dummy_job_$(v)", "daily_dummy_job_suppress_inform_incapable" );
592
593@if minimum_version(3.15.0)
594      "$(paths.true)"
595        comment => "This promise is used for delaying execution of url_get, since locking does not work directly on vars type promises",
596        classes => kept_successful_command,
597        handle => "daily_dummy_job_suppress_inform_capable",
598        inform => "false",
599        action => if_elapsed_day;
600@endif
601
602  files:
603    ec2_instance.!(disable_inventory_aws|disable_inventory_aws_ec2_metadata)::
604
605    cfengine_3_10::
606      "$(cache)"
607        create => "true",
608        edit_line => lines_present( "$(response[content])" ),
609        edit_defaults => empty,
610        if => isvariable( response );
611
612@if minimum_version(3.11)
613      # template_method inline_mustache introduced in 3.11
614    !cfengine_3_10::
615      "$(cache)"
616        template_method => "inline_mustache",
617        edit_template_string => "{{{content}}}",
618        template_data => @(response),
619        create =>   "true",
620        if => isvariable( response );
621@endif
622}
623
624bundle agent cfe_aws_ec2_metadata_from_cache
625# @brief Inventory ec2 metadata from cache
626#
627# Provides inventory for EC2 Region, EC2 Instance ID, EC2 Instance Type, EC2
628# Image ID, and EC2 Availability Zone
629{
630  classes:
631
632    ec2_instance.!(disable_inventory_aws|disable_inventory_aws_ec2_metadata)::
633
634      "have_cached_instance_identity"
635        expression => fileexists( $(cfe_autorun_inventory_aws_ec2_metadata_cache.cache) );
636
637  vars:
638
639    have_cached_instance_identity.ec2_instance.!(disable_inventory_aws|disable_inventory_aws_ec2_metadata)::
640
641      "data" data => readjson( $(cfe_autorun_inventory_aws_ec2_metadata_cache.cache), 100K);
642
643      "region" string => "$(data[region])", meta => { "inventory", "attribute_name=EC2 Region" };
644      "instanceId" string => "$(data[instanceId])", meta => { "inventory", "attribute_name=EC2 Instance ID" };
645      "instanceType" string => "$(data[instanceType])", meta => { "inventory", "attribute_name=EC2 Instance Type" };
646      "imageId" string => "$(data[imageId])", meta => { "inventory", "attribute_name=EC2 Image ID" };
647      "availabilityZone" string => "$(data[availabilityZone])", meta => { "inventory", "attribute_name=EC2 Availability Zone" };
648
649  reports:
650
651    DEBUG|DEBUG_inventory_ec2_metadata|DEBUG_inventory_ec2_metadata_from_cache::
652      "DEBUG $(this.bundle):";
653      "$(const.t)Inventory 'EC2 Region' = '$(region)'";
654      "$(const.t)Inventory 'EC2 Instance ID' = '$(instanceId)'";
655      "$(const.t)Inventory 'EC2 Instance Type' = '$(instanceType)'";
656      "$(const.t)Inventory 'EC2 Image ID' = '$(imageId)'";
657      "$(const.t)Inventory 'EC2 Availability Zone' = '$(availabilityZone)'";
658}
659
660bundle agent cfe_autorun_inventory_mtab
661# @brief Do mtab inventory
662#
663# The mtab format is simple: each line looks like this format:
664# `/dev/sda1 / ext4 rw,noatime,data=ordered 0 0` (in order: `DEV
665# MOUNTPOINT FSTYPE OPTIONS DUMP-FREQ PASS`).  Some older Unices have
666# a different format and it's really not portable, so enable this only
667# if you know you want it.  It's very handy if you want to check if a
668# file system is mounted.
669{
670  vars:
671    have_mtab::
672      "mount_count" int =>  readstringarrayidx("mounts",
673                                               $(inventory_control.mtab),
674                                               "\s*#[^\n]*",
675                                               "\s+",
676                                               500,
677                                               50000);
678
679      "idx" slist => getindices("mounts");
680
681  classes:
682      "have_mtab" expression => fileexists($(inventory_control.mtab));
683
684      # define classes like have_mount_ext4__var for a ext4 /var mount
685      "have_mount_$(mounts[$(idx)][2])_$(mounts[$(idx)][1])"
686      expression => "any",
687      scope => "namespace";
688
689      # define classes like have_mount_ext4 if there is a ext4 mount
690      "have_mount_$(mounts[$(idx)][2])"
691      expression => "any",
692      scope => "namespace";
693
694  reports:
695    verbose_mode::
696      "$(this.bundle): we have a $(mounts[$(idx)][2]) mount under $(mounts[$(idx)][1])";
697}
698
699bundle agent cfe_autorun_inventory_fstab
700# @brief Do fstab inventory
701#
702# The fstab format is simple: each line looks like this format:
703# `/dev/sda1 / auto noatime 0 1` (in order: `DEV MOUNTPOINT FSTYPE
704# OPTIONS DUMP-FREQ PASS`).  Note the FSTYPE is not known from the
705# fstab.
706#
707# Solaris has 'MOUNTDEV FSCKDEV MOUNTPOINT FSTYPE PASS MOUNT-AD-BOOT
708# OPTIONS' but is not supported here.  Contributions welcome.
709{
710  vars:
711    have_fstab::
712      "mount_count" int =>  readstringarrayidx("mounts",
713                                               $(sys.fstab),
714                                               "\s*#[^\n]*",
715                                               "\s+",
716                                               500,
717                                               50000);
718
719      "idx" slist => getindices("mounts");
720
721  classes:
722      "have_fstab" expression => fileexists($(sys.fstab));
723
724      # define classes like have_fs_ext4__var for a ext4 /var entry
725      "have_fs_$(mounts[$(idx)][2])_$(mounts[$(idx)][1])"
726      expression => "any",
727      scope => "namespace";
728
729      # define classes like have__var for a /var entry
730      "have_fs_$(mounts[$(idx)][1])"
731      expression => "any",
732      scope => "namespace";
733
734      # define classes like have_fs_ext4 if there is a ext4 entry
735      "have_fs_$(mounts[$(idx)][2])"
736      expression => "any",
737      scope => "namespace";
738
739  reports:
740    verbose_mode::
741      "$(this.bundle): we have a $(mounts[$(idx)][2]) fstab entry under $(mounts[$(idx)][1])";
742}
743
744bundle agent cfe_autorun_inventory_dmidecode
745# @brief Do hardware related inventory
746#
747# This agent bundle reads dmi information from the sysfs and/or from dmidecode.
748# Sysfs is preferred for most variables, but if no sysfs (e.g. on RHEL 5),
749# or no sysfs equivalent to a dmidecode variable (e.g. system-version),
750# then dmidecode is run to collect the info.
751# For system-uuid, a parsed version of dmidecode is the preferred source.
752#
753# The variable names dmi[...] are all based on dmidecode string keywords.
754#
755# Information collected is:
756# - BIOS vendor
757# - BIOS version
758# - System serial number
759# - System manufacturer
760# - System version
761# - System product name
762# - Physical memory (MB)
763#
764# On windows where powershell is available this bundle runs gwmi to inventory:
765# - BIOS vendor
766# - BIOS version
767# - System serial number
768# - System manufacturer
769{
770
771  vars:
772    any::
773      "sysfs_name_for"
774        comment => "The names in /sys/devices/virtual/dmi/id/ don't match
775                    the strings to be passed to dmidecode, even though the
776                    values do.  We use the dmidecode string names for our
777                    variables since that was the original source (i.e. for
778                    backward compatibility with policies based on prior
779                    versions of this code).",
780        # system-version has no equivalent in sysfs that I can find.
781        # Items after the line break aren't currently collected, but mapping is provided
782        # in case someone adds them to a custom dmidefs (so that they could be gotten
783        # from sysfs in that case).
784        data => parsejson('
785          {
786            "bios-vendor": "bios_vendor",
787            "bios-version": "bios_version",
788            "system-serial-number": "product_serial",
789            "system-manufacturer": "sys_vendor",
790            "system-product-name": "product_name",
791            "system-uuid": "product_uuid",
792
793            "baseboard-manufacturer": "board_vendor",
794            "baseboard-product-name": "board_name",
795            "baseboard-serial-number": "board_serial",
796            "baseboard-version": "board_version",
797            "bios-release-date": "bios_date",
798            "chassis-manufacturer": "chassis_vendor",
799          }');
800
801  vars:
802    any::
803      # The dmidefs variable controls which values are collected
804      # (and what are their inventory tags)
805      "dmidefs" data => parsejson('
806{
807  "bios-vendor": "BIOS vendor",
808  "bios-version": "BIOS version",
809  "system-serial-number": "System serial number",
810  "system-manufacturer": "System manufacturer",
811  "system-version": "System version",
812  "system-product-name": "System product name",
813  "system-uuid": "System UUID",
814}');
815
816      # We override dmidefs from augments when we can.
817
818      "dmidefs" -> { "CFE-2927" }
819        data => mergedata( "def.cfe_autorun_inventory_dmidecode[dmidefs]" ),
820        if => isvariable( "def.cfe_autorun_inventory_dmidecode[dmidefs]");
821
822      # other dmidecode variables you may want:
823      # baseboard-asset-tag
824      # baseboard-manufacturer
825      # baseboard-product-name
826      # baseboard-serial-number
827      # baseboard-version
828      # bios-release-date
829      # chassis-asset-tag
830      # chassis-manufacturer
831      # chassis-serial-number
832      # chassis-type
833      # chassis-version
834      # processor-family
835      # processor-frequency
836      # processor-manufacturer
837      #"processor-version": "CPU model" <- Collected by default, but not by iterating over the list
838
839      "dmivars" slist => getindices(dmidefs);
840
841    have_dmidecode::
842      "decoder" string => "$(inventory_control.dmidecoder)";
843
844    have_dmidecode._stdlib_path_exists_awk.!(redhat_4|redhat_3)::
845      # Awk script from https://kb.vmware.com/s/article/53609
846      # Edited only to add "-t1" (an improvement tested on RHEL 4/5/6/7 and FreeBSD)
847      # and to take out the "UUID: " prefix in the output.
848      # This works on a superset of systems where dmidecode -s system-uuid works,
849      # e.g. RHEL 5 with dmidecode-2.7-1.28.2.el5 where system-uuid is not one of the valid keywords;
850      # also, this returns the correct UUID on systems (such as VMWare VMs with hardware version 13)
851      # where dmidecode -s system-uuid shows the wrong UUID.  Some such VMWare VMs also show the
852      # wrong UUID in sysfs, which is why we prefer the "dmidecode | awk" version to sysfs for UUID.
853      # (We still need to check sysfs for UUID to handle hosts without dmidecode such as CoreOS.)
854      "dmi[system-uuid]"
855        string => execresult(
856          "$(decoder) -u -t1 |
857            $(paths.awk) '
858              BEGIN { in1 = 0; hd = 0}
859              /, DMI type / { in1 = 0 }
860              /Strings:/ { hd = 0 }
861              { if (hd == 2) { printf \"%s-%s\n\", $1 $2, $3 $4 $5 $6 $7 $8; hd = 0 } }
862              { if (hd == 1) { printf \"%s-%s-%s-\", $9 $10 $11 $12, $13 $14, $15 $16; hd = 2 } }
863              /, DMI type 1,/ { in1 = 1 }
864              /Header and Data:/ { if (in1 != 0) { hd = 1 } }
865            '",
866          "useshell" ),
867        if => isvariable("dmidefs[system-uuid]"), # Only run this if system-uuid is marked for collection in dmidefs
868        meta => { "inventory", "attribute_name=$(dmidefs[system-uuid])" };
869
870    !disable_inventory_dmidecode.!windows::
871    # The reason disable_inventory_dmidecode is referenced here but not in the other context lines
872    # is because those vars depend on have_dmidecode which won't be set during pre-eval (and won't
873    # be set at all if this bundle isn't called).  Without this guard here, we would attempt to
874    # read sysfs even if dmi inventory were turned off on the host via disable_inventory_dmidecode,
875    # which would be undesirable.
876      "dmi[$(dmivars)]"
877        unless => isvariable("dmi[$(dmivars)]"), # This is just for system-uuid really, which we get from the awk script above by preference.
878        if =>   fileexists("/sys/devices/virtual/dmi/id/$(sysfs_name_for[$(dmivars)])"),
879        string => readfile("/sys/devices/virtual/dmi/id/$(sysfs_name_for[$(dmivars)])", 0),
880        meta => { "inventory", "attribute_name=$(dmidefs[$(dmivars)])" };
881
882    # Redhat 4 can support the -s option to dmidecode if
883    # kernel-utils-2.4-15.el4 or greater is installed.
884    have_dmidecode.!(redhat_4|redhat_3)::
885      "dmi[$(dmivars)]" string => execresult("$(decoder) -s $(dmivars)",
886                                             "useshell"),
887      unless => isvariable("dmi[$(dmivars)]"), # If already defined from sysfs, don't run dmidecode
888      meta => { "inventory", "attribute_name=$(dmidefs[$(dmivars)])" };
889
890      # We do not want to inventory the model name from here, as inventory for
891      # CPU info has been abstracted away from DMI so we just collect it
892      # manually.
893
894      "dmi[processor-version]" string => execresult("$(decoder) -s processor-version",
895                                             "useshell");
896
897    windows.powershell::
898      "dmi[bios-vendor]" string => $(bios_array[1]),
899      meta => { "inventory", "attribute_name=BIOS vendor" };
900
901      "dmi[system-serial-number]" string => $(bios_array[2]),
902      meta => { "inventory", "attribute_name=System serial number" };
903
904      "dmi[bios-version]" string => $(bios_array[3]),
905      meta => { "inventory", "attribute_name=BIOS version" };
906
907      "dmi[system-version]" string => $(bios_array[4]),
908      meta => { "inventory", "attribute_name=System version" };
909
910      "dmi[processor-version]" string => $(processor_array[1]);
911
912      "split_pscomputername"
913        slist => string_split($(system_array[1]), "PSComputerName\s.*", 2),
914        comment => "Work around weird appearance of PSComputerName into System manufacturer";
915
916      "dmi[system-manufacturer]" string => nth(split_pscomputername, 0),
917      meta => { "inventory", "attribute_name=System manufacturer" };
918
919  classes:
920      "have_dmidecode" expression => fileexists($(inventory_control.dmidecoder));
921
922    windows.powershell::
923      "bios_match" expression => regextract(".*Manufacturer\s+:\s([a-zA-Z0-9 ]+)\n.*SerialNumber\W+([a-zA-Z0-9 ]+).*SMBIOSBIOSVersion\W+([a-zA-Z0-9 ]+).*Version\W+([a-zA-Z0-9 -]+)",
924                                            execresult("gwmi -query 'SELECT SMBIOSBIOSVersion, Manufacturer, SerialNumber, Version FROM WIN32_BIOS'", "powershell"),
925                                            "bios_array");
926
927      "processor_match" expression => regextract(".*Name\W+(.*)",
928                                                 execresult("gwmi -query 'SELECT Name FROM WIN32_PROCESSOR'", "powershell"),
929                                                 "processor_array");
930
931      "system_match" expression => regextract(".*Manufacturer\W+(.*)",
932                                              execresult("gwmi -query 'SELECT Manufacturer FROM WIN32_COMPUTERSYSTEM'", "powershell"),
933                                              "system_array");
934
935  # BEGIN Inventory Total Physical Memory MB
936  vars:
937
938      "total_physical_memory_MB" -> { "CFE-2896" }
939        string => readfile( "$(sys.statedir)/inventory-$(this.bundle)-total-physical-memory-MB.txt", 100),
940        meta => { "inventory", "attribute_name=Physical memory (MB)" },
941        if => fileexists( "$(sys.statedir)/inventory-$(this.bundle)-total-physical-memory-MB.txt" );
942
943  commands:
944
945    have_dmidecode::
946
947      "$(decoder) -t 17 | $(paths.awk) '/Size.*MB/ {s+=$2} END {print s}' > '$(sys.statedir)/inventory-$(this.bundle)-total-physical-memory-MB.txt'" -> { "CFE-2896" }
948        contain => in_shell,
949        if => not( fileexists( "$(sys.statedir)/inventory-$(this.bundle)-total-physical-memory-MB.txt") );
950
951  files:
952
953      "$(sys.statedir)/inventory-$(this.bundle)-total-physical-memory-MB.txt" -> { "CFE-2896" }
954        delete => tidy,
955        file_select => older_than(0, 0, 1, 0, 0, 0),
956        comment => "Clear the cached value for total physical memory MB once a day.";
957
958  # END Inventory Total Physical Memory MB
959
960  reports:
961    DEBUG|DEBUG_cfe_autorun_inventory_dmidecode::
962      "DEBUG $(this.bundle): Obtained $(dmidefs[$(dmivars)]) = '$(dmi[$(dmivars)])'";
963      "DEBUG $(this.bundle): Obtained Physical memory (MB) = '$(total_physical_memory_MB)'";
964}
965
966bundle agent cfe_autorun_inventory_LLDP
967# @brief Do LLDP-based inventory
968#
969# This agent bundle runs lldpctl to discover information.  See
970# http://vincentbernat.github.io/lldpd/ to run this yourself for
971# testing, and your Friendly Network Admin may be of help too.
972{
973  classes:
974      "lldpctl_exec_exists" expression => fileexists($(inventory_control.lldpctl_exec));
975
976  vars:
977    !disable_inventory_LLDP.lldpctl_exec_exists::
978      # TODO When CFE-3108 is DONE, migrate to capturing only stdout
979      "info" -> { "CFE-3109", "CFE-3108" }
980        data => parsejson(execresult("$(inventory_control.lldpctl_json) 2>/dev/null", "useshell")),
981        if => not(isvariable("def.lldpctl_json")),
982        comment => "Not all versions of lldpctl support json, and because an
983        absent lldpd will result in an error on stderr resulting noisy logs and
984        failure to parse the json we redirect to dev null";
985
986      "info" -> { "CFE-3109" }
987        data => parsejson(execresult($(inventory_control.lldpctl_json), "noshell")),
988        if => isvariable("def.lldpctl_json"),
989        comment => "For safety, we do not run lldpctl in a shell if the path to
990        lldpctl is customized via augments";
991}
992
993bundle agent cfe_autorun_inventory_packages
994# @brief Package inventory auto-refresh
995#
996# This bundle is for refreshing the package inventory.  It runs on
997# startup, unless disabled.  Other package methods can be added below.
998{
999  classes:
1000      "have_patches" or => { "community_edition", # not in Community
1001                             fileexists("$(sys.workdir)/state/software_patches_avail.csv") };
1002
1003      "have_inventory" and => { "have_patches",
1004                                fileexists("$(sys.workdir)/state/software_packages.csv"),
1005      };
1006
1007  vars:
1008      # if we have the patches, 7 days; otherwise keep trying
1009      "refresh" string => ifelse("have_inventory", "10080",
1010                                 "0");
1011
1012  packages:
1013
1014    # The legacy implementation (package_method) of the packages type promise
1015    # requires a packages promise to be triggered in order to generate package
1016    # inventory. The following promises ensure that package inventory data
1017    # exists. As package modules become available the package_methods should be
1018    # removed.
1019
1020    suse|sles::
1021      "cfe_internal_non_existing_package"
1022      package_policy => "add",
1023      package_method => inventory_zypper($(refresh)),
1024      action => if_elapsed_day;
1025
1026    aix::
1027      "cfe_internal_non_existing_package"
1028      package_policy => "add",
1029      package_method => inventory_lslpp($(refresh)),
1030      action => if_elapsed_day;
1031
1032    gentoo::
1033      "cfe_internal_non_existing_package"
1034      package_policy => "add",
1035      package_method => emerge,
1036      action => if_elapsed_day;
1037
1038    !redhat.!debian.!gentoo.!(suse|sles).!aix::
1039      "cfe_internal_non_existing_package"
1040      package_policy => "add",
1041      package_method => generic,
1042      action => if_elapsed_day;
1043
1044  reports:
1045    DEBUG|DEBUG_cfe_autorun_inventory_packages::
1046      "DEBUG $(this.bundle): refresh interval is $(refresh)";
1047      "DEBUG $(this.bundle): we have the inventory files."
1048        ifvarclass => "have_inventory";
1049      "DEBUG $(this.bundle): we don't have the inventory files."
1050        ifvarclass => "!have_inventory";
1051}
1052
1053body package_method inventory_lslpp(update_interval)
1054# @brief AIX lslpp installation method for inventory purposes only
1055# @param update_interval how often to update the package and patch list
1056{
1057      package_changes => "individual";
1058
1059      package_list_update_command => "/usr/bin/true";
1060      package_list_update_ifelapsed => $(update_interval);
1061
1062      package_list_command       => "/usr/bin/lslpp -Lqc"; # list RPMs too
1063      package_list_version_regex => "[^:]+:[^:]+:([^:]+):.*";
1064      # Make sure version is not included in the name, that indicates RPM
1065      # packages, which we should ignore.
1066      package_list_name_regex    => "[^:]+:(([^-:]|-[^0-9])+):.*";
1067      package_installed_regex    => "[^:]+:(([^-:]|-[^0-9])+):[^:]+:[^:]+:.*";
1068
1069      package_name_convention    => "$(name)-$(version).+";
1070
1071      package_add_command    => "/usr/bin/true";
1072      package_update_command => "/usr/bin/true";
1073      package_patch_command  => "/usr/bin/true";
1074      package_delete_command => "/usr/bin/true";
1075      package_verify_command => "/usr/bin/true";
1076}
1077
1078body package_method inventory_zypper(update_interval)
1079# @depends common_knowledge rpm_knowledge suse_knowledge
1080# @brief SUSE zypper installation method for inventory purposes only
1081# @param update_interval how often to update the package and patch list
1082#
1083# This package method is a copy of the SUSE zypper method just for
1084# inventory purposes.
1085{
1086      package_changes => "bulk";
1087
1088      package_list_command => "$(paths.path[rpm]) -qa --queryformat \"i | repos | %{name} | %{version}-%{release} | %{arch}\n\"";
1089
1090      # set it to "0" to avoid caching of list during upgrade
1091      package_list_update_command => "$(suse_knowledge.call_zypper) list-updates";
1092      package_list_update_ifelapsed => $(update_interval);
1093
1094      package_patch_list_command => "$(suse_knowledge.call_zypper) patches";
1095      package_installed_regex => "i.*";
1096      package_list_name_regex    => "$(rpm_knowledge.rpm_name_regex)";
1097      package_list_version_regex => "$(rpm_knowledge.rpm_version_regex)";
1098      package_list_arch_regex    => "$(rpm_knowledge.rpm_arch_regex)";
1099
1100      package_patch_installed_regex => ".*Installed.*|.*Not Applicable.*";
1101      package_patch_name_regex    => "[^|]+\|\s+([^\s]+).*";
1102      package_patch_version_regex => "[^|]+\|[^|]+\|\s+([^\s]+).*";
1103
1104      package_name_convention => "$(name)";
1105      package_add_command => "$(suse_knowledge.call_zypper) --help >/dev/null 2>&1 ; /bin/true";
1106      package_delete_command => "$(suse_knowledge.call_zypper) --non-interactive remove --force-resolution";
1107      package_update_command => "$(suse_knowledge.call_zypper) --non-interactive update";
1108      package_patch_command => "$(suse_knowledge.call_zypper) --non-interactive patch$"; # $ means no args
1109      package_verify_command => "$(suse_knowledge.call_zypper) --non-interactive verify$";
1110}
1111
1112bundle agent cfe_autorun_inventory_cmdb
1113# @brief Copy and load the CMDB inventory
1114#
1115# This bundle is for refreshing the CMDB inventory.  It copies the
1116# file me.json from the server, then loads it to create variables and
1117# classes.
1118{
1119  vars:
1120      "cmdb_dir" string => "$(sys.workdir)/cmdb",
1121      comment => "CMDB directory location",
1122      meta => { "cmdb" };
1123
1124      "cmdb_file" string => "$(cmdb_dir)/me.json",
1125      comment => "CMDB file location",
1126      meta => { "cmdb" };
1127
1128  files:
1129      "$(cmdb_file)"
1130      copy_from => inventory_cmdb_copy_from,
1131      classes => inventory_scoped_classes_generic("bundle", "cmdb_file");
1132
1133  methods:
1134    cmdb_file_ok::
1135      "load CMDB file" usebundle => inventory_cmdb_load($(cmdb_file));
1136}
1137
1138bundle agent inventory_cmdb_load(file)
1139# @brief Load the CMDB inventory
1140# @param file Load the CMDB inventory
1141#
1142# This bundle is for loading the CMDB inventory.
1143{
1144  classes:
1145      "have_cmdb_data" expression => isvariable("cmdb");
1146
1147      "$(ckeys)" expression => "any", scope => "namespace";
1148
1149  vars:
1150      "cmdb"
1151        data => readjson($(file), "999999"),
1152        ifvarclass => fileexists( $(file) );
1153
1154      "cmdb_string"
1155      string => format("%S", cmdb),
1156        ifvarclass => isvariable( cmdb );
1157
1158      "bkeys" slist => getindices("cmdb[vars]");
1159      "vkeys_$(bkeys)" slist => getindices("cmdb[vars][$(bkeys)]");
1160      "$(vkeys_$(bkeys))" string => nth("cmdb[vars][$(bkeys)]", $(vkeys));
1161
1162      "ckeys" slist => getindices("cmdb[classes]");
1163
1164  reports:
1165    DEBUG|DEBUG_inventory_cmdb_load::
1166      "DEBUG $(this.bundle): Got CMDB data from $(file): $(cmdb_string)"
1167        ifvarclass => "have_cmdb_data";
1168      "DEBUG $(this.bundle): Got CMDB key = $(vkeys_$(bkeys)), CMDB value = $((vkeys_$(bkeys)))"
1169        ifvarclass => "have_cmdb_data";
1170      "DEBUG $(this.bundle): Got CMDB class = $(ckeys)"
1171        ifvarclass => "have_cmdb_data";
1172      "DEBUG $(this.bundle): Could not read the CMDB data from $(file)"
1173        ifvarclass => "!have_cmdb_data";
1174}
1175
1176body copy_from inventory_cmdb_copy_from
1177# @brief Copy from the CMDB source
1178{
1179    !cfe_inventory_cmdb_override_file::
1180      source      => "me.json";
1181      servers     => { "$(sys.policy_hub)" };
1182    cfe_inventory_cmdb_override_file::
1183      source      => "$(sys.inputdir)/me.json";
1184    any::
1185      compare     => "digest";
1186      encrypt     => "true";
1187      verify      => "true";
1188}
1189
1190body classes inventory_scoped_classes_generic(scope, x)
1191# @brief Define `x` prefixed/suffixed with promise outcome
1192# **See also:** `scope`
1193#
1194# @param scope The scope in which the class should be defined
1195# @param x The unique part of the classes to be defined
1196#
1197# Copy of `scoped_classes_generic`, which see.
1198{
1199      scope => "$(scope)";
1200      promise_repaired => { "promise_repaired_$(x)", "$(x)_repaired", "$(x)_ok", "$(x)_reached" };
1201      repair_failed => { "repair_failed_$(x)", "$(x)_failed", "$(x)_not_ok", "$(x)_not_kept", "$(x)_not_repaired", "$(x)_reached" };
1202      repair_denied => { "repair_denied_$(x)", "$(x)_denied", "$(x)_not_ok", "$(x)_not_kept", "$(x)_not_repaired", "$(x)_reached" };
1203      repair_timeout => { "repair_timeout_$(x)", "$(x)_timeout", "$(x)_not_ok", "$(x)_not_kept", "$(x)_not_repaired", "$(x)_reached" };
1204      promise_kept => { "promise_kept_$(x)", "$(x)_kept", "$(x)_ok", "$(x)_not_repaired", "$(x)_reached" };
1205}
1206
1207body contain inventory_in_shell
1208# @brief run command in shell
1209#
1210# Copy of `in_shell`, which see.
1211{
1212      useshell => "true"; # canonical "useshell" but this is backwards-compatible
1213}
1214