Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

import from version 1.10

  • Loading branch information...
commit 1aa029e53d59ca557647241c9a549780835e7112 0 parents
@maddingue authored
Showing with 16,325 additions and 0 deletions.
  1. +60 −0 Build.PL
  2. +149 −0 Changes
  3. +119 −0 MANIFEST
  4. +86 −0 Makefile.PL
  5. +53 −0 NOTES
  6. +66 −0 README
  7. +72 −0 README.pod
  8. +390 −0 bin/cfengine-tags
  9. +412 −0 bin/cisco-status
  10. +577 −0 bin/rack
  11. +176 −0 cgi/rackapi
  12. +49 −0 cgi/rtlink
  13. +8 −0 etc/host.dhcp.conf.tmpl
  14. +31 −0 etc/host.kickstart.cfg.tmpl
  15. +5 −0 etc/host.pxe.conf.tmpl
  16. +86 −0 etc/rack.conf
  17. +310 −0 lib/RackMan.pm
  18. +234 −0 lib/RackMan/Config.pm
  19. +834 −0 lib/RackMan/Device.pm
  20. +116 −0 lib/RackMan/Device/PDU.pm
  21. +473 −0 lib/RackMan/Device/PDU/APC_RackPDU.pm
  22. +116 −0 lib/RackMan/Device/Server.pm
  23. +460 −0 lib/RackMan/Device/Server/HP_ProLiant.pm
  24. +116 −0 lib/RackMan/Device/Switch.pm
  25. +364 −0 lib/RackMan/Device/Switch/Cisco_Catalyst.pm
  26. +149 −0 lib/RackMan/File.pm
  27. +204 −0 lib/RackMan/Format/Bacula.pm
  28. +257 −0 lib/RackMan/Format/Cacti.pm
  29. +141 −0 lib/RackMan/Format/DHCP.pm
  30. +144 −0 lib/RackMan/Format/Kickstart.pm
  31. +183 −0 lib/RackMan/Format/LDAP.pm
  32. +270 −0 lib/RackMan/Format/Nagios.pm
  33. +140 −0 lib/RackMan/Format/PXE.pm
  34. +219 −0 lib/RackMan/SCM.pm
  35. +389 −0 lib/RackMan/Tasks.pm
  36. +247 −0 lib/RackMan/Template.pm
  37. +130 −0 lib/RackMan/Types.pm
  38. +176 −0 lib/RackMan/Utils.pm
  39. +50 −0 lib/RackTables/Schema.pm
  40. +73 −0 lib/RackTables/Schema/Result/Atom.pm
  41. +90 −0 lib/RackTables/Schema/Result/Attribute.pm
  42. +119 −0 lib/RackTables/Schema/Result/AttributeMap.pm
  43. +136 −0 lib/RackTables/Schema/Result/AttributeValue.pm
  44. +128 −0 lib/RackTables/Schema/Result/CachedPAV.pm
  45. +99 −0 lib/RackTables/Schema/Result/CachedPNV.pm
  46. +127 −0 lib/RackTables/Schema/Result/CachedPVM.pm
  47. +85 −0 lib/RackTables/Schema/Result/CactiGraph.pm
  48. +92 −0 lib/RackTables/Schema/Result/Chapter.pm
  49. +133 −0 lib/RackTables/Schema/Result/Config.pm
  50. +92 −0 lib/RackTables/Schema/Result/Dictionary.pm
  51. +111 −0 lib/RackTables/Schema/Result/EntityLink.pm
  52. +151 −0 lib/RackTables/Schema/Result/File.pm
  53. +115 −0 lib/RackTables/Schema/Result/FileLink.pm
  54. +73 −0 lib/RackTables/Schema/Result/IPv4Address.pm
  55. +106 −0 lib/RackTables/Schema/Result/IPv4Allocation.pm
  56. +164 −0 lib/RackTables/Schema/Result/IPv4LB.pm
  57. +156 −0 lib/RackTables/Schema/Result/IPv4NAT.pm
  58. +113 −0 lib/RackTables/Schema/Result/IPv4Network.pm
  59. +126 −0 lib/RackTables/Schema/Result/IPv4RS.pm
  60. +106 −0 lib/RackTables/Schema/Result/IPv4RSPool.pm
  61. +121 −0 lib/RackTables/Schema/Result/IPv4VS.pm
  62. +67 −0 lib/RackTables/Schema/Result/IPv6Address.pm
  63. +100 −0 lib/RackTables/Schema/Result/IPv6Allocation.pm
  64. +109 −0 lib/RackTables/Schema/Result/IPv6Network.pm
  65. +97 −0 lib/RackTables/Schema/Result/LDAPCache.pm
  66. +112 −0 lib/RackTables/Schema/Result/Link.pm
  67. +52 −0 lib/RackTables/Schema/Result/Molecule.pm
  68. +130 −0 lib/RackTables/Schema/Result/MountOperation.pm
  69. +110 −0 lib/RackTables/Schema/Result/ObjectLog.pm
  70. +54 −0 lib/RackTables/Schema/Result/ObjectParentCompat.pm
  71. +197 −0 lib/RackTables/Schema/Result/Port.pm
  72. +128 −0 lib/RackTables/Schema/Result/PortAllowedVLAN.pm
  73. +66 −0 lib/RackTables/Schema/Result/PortCompat.pm
  74. +72 −0 lib/RackTables/Schema/Result/PortInnerInterface.pm
  75. +92 −0 lib/RackTables/Schema/Result/PortInterfaceCompat.pm
  76. +99 −0 lib/RackTables/Schema/Result/PortNativeVLAN.pm
  77. +110 −0 lib/RackTables/Schema/Result/PortVLANMode.pm
  78. +137 −0 lib/RackTables/Schema/Result/Rack.pm
  79. +105 −0 lib/RackTables/Schema/Result/RackHistory.pm
  80. +326 −0 lib/RackTables/Schema/Result/RackObject.pm
  81. +148 −0 lib/RackTables/Schema/Result/RackObjectHistory.pm
  82. +78 −0 lib/RackTables/Schema/Result/RackRow.pm
  83. +147 −0 lib/RackTables/Schema/Result/RackSpace.pm
  84. +53 −0 lib/RackTables/Schema/Result/Script.pm
  85. +101 −0 lib/RackTables/Schema/Result/ServiceStorage.pm
  86. +127 −0 lib/RackTables/Schema/Result/ServiceTree.pm
  87. +104 −0 lib/RackTables/Schema/Result/TagStorage.pm
  88. +127 −0 lib/RackTables/Schema/Result/TagTree.pm
  89. +95 −0 lib/RackTables/Schema/Result/UserAccount.pm
  90. +95 −0 lib/RackTables/Schema/Result/UserConfig.pm
  91. +158 −0 lib/RackTables/Schema/Result/VLANDescription.pm
  92. +93 −0 lib/RackTables/Schema/Result/VLANDomain.pm
  93. +112 −0 lib/RackTables/Schema/Result/VLANIPv4.pm
  94. +112 −0 lib/RackTables/Schema/Result/VLANIPv6.pm
  95. +117 −0 lib/RackTables/Schema/Result/VLANSTRule.pm
  96. +225 −0 lib/RackTables/Schema/Result/VLANSwitch.pm
  97. +108 −0 lib/RackTables/Schema/Result/VLANSwitchTemplate.pm
  98. +99 −0 lib/RackTables/Schema/Result/VLANValidID.pm
  99. +141 −0 lib/RackTables/Schema/Result/viewAssociatedPorts.pm
  100. +91 −0 lib/RackTables/Schema/Result/viewIPv4AddressNetwork.pm
  101. +82 −0 lib/RackTables/Schema/Result/viewIPv4AddressRange.pm
  102. +93 −0 lib/RackTables/Schema/Result/viewRack.pm
  103. +76 −0 lib/RackTables/Types.pm
  104. +401 −0 pod/RackMan/Manual.fr.pod
  105. +36 −0 t/00-load.t
  106. +43 −0 t/01-api.t
  107. +33 −0 t/01-programs.t
  108. +42 −0 t/50-rackman-config.t
  109. +51 −0 t/50-rackman-file.t
  110. +74 −0 t/50-rackman-scm.t
  111. +32 −0 t/50-rackman-template.t
  112. +82 −0 t/80-rackman.t
  113. +16 −0 t/90-pod.t
  114. +34 −0 t/91-pod-coverage.t
  115. +9 −0 t/93-eol.t
  116. +8 −0 t/98-kwalitee.t
  117. +6 −0 t/99-distchk.t
  118. +50 −0 t/files/rack.conf
  119. BIN  t/files/racktables.sqlite
  120. +74 −0 tools/make_doc
  121. +12 −0 tools/schema
60 Build.PL
@@ -0,0 +1,60 @@
+use strict;
+use warnings;
+use Module::Build;
+
+my %compat_prereqs;
+
+if ($] < 5.012) {
+ $compat_prereqs{Socket6} = 0;
+}
+
+
+my $builder = Module::Build->new(
+ module_name => 'RackMan',
+ license => 'gpl',
+ dist_author => 'Sebastien Aperghis-Tramoni <sebastien@aperghis.net>',
+ dist_version_from => 'lib/RackMan.pm',
+ script_files => [
+ 'bin/rack', 'bin/cfengine-tags', 'bin/cisco-status'
+ ],
+
+ requires => {
+ 'Algorithm::Diff' => 0,
+ 'Carp' => 0,
+ 'Config::IniFiles' => 0,
+ 'Cwd' => 0,
+ 'DBD::mysql' => 0,
+ 'DBIx::Class' => 0,
+ 'DBIx::Class::Schema::Loader' => 0,
+ 'Exporter' => 0,
+ 'File::Basename' => 0,
+ 'File::Path', => 0,
+ 'File::Temp', => 0,
+ 'Getopt::Long' => 0,
+ 'HTML::Template' => '2.0',
+ 'HTML::Template::Filter::TT2' => '0.03',
+ 'IPC::Run', => 0,
+ 'JSON', => 0,
+ 'List::MoreUtils', => 0,
+ 'Moose' => '1.00',
+ 'MooseX::NonMoose' => 0,
+ 'namespace::autoclean' => 0,
+ 'NetAddr::IP' => '4.000',
+ 'Net::FTP', => 0,
+ 'Net::ILO', => "0.54",
+ 'Net::SNMP', => 0,
+ 'Net::Telnet::Cisco', => 0,
+ 'Path::Class' => 0,
+ 'Socket' => 0,
+ 'Term::ANSIColor' => 0,
+ 'YAML', => 0,
+ %compat_prereqs,
+ },
+ build_requires => {
+ 'Test::More' => '0.45',
+ },
+ add_to_cleanup => [ 'RackMan-*' ],
+);
+
+$builder->create_build_script();
+
149 Changes
@@ -0,0 +1,149 @@
+Release history for RackMan
+
+1.10 / 2012.11.08 / SAPER
+ - cfengine-tags: Support for device filtering on tags and attributes.
+ - The HP_ProLiant role now provides an ilo_name template parameter, an
+ ilo_fqdn attribute, and requires the IP address to have a valid reverse.
+ - Modified some formats to make them more parametrized.
+ - Anonymized what needed to be, for publication as a free software.
+
+1.09 / 2012.10.24 / SAPER
+ - cfengine-tags: Actually avoid duplicate names in tag files.
+ - Enforce some assertions about the network interfaces.
+
+1.08 / 2012.10.16 / SAPER
+ - cfengine-tags: Allow the use of attribute values as tag names.
+ - cfengine-tags: Avoid duplicate names in tag files.
+ - Added support for checking iLO subsystems in Nagios.
+ - Replaced Rackman::Device::*'s constant CONFIG_FORMATS with the method
+ formats(), thus allowing to specify the associated formats from the
+ configuration file. Tests and documentation were modified accordingly.
+ - Factored the RackTables types in a dedicated module.
+ - Moved all the attributes and methods related to iLO in the
+ HP_Proliant role.
+ - Fixed the default templates.
+ - Fixed t/93-eol.t
+
+1.07 / 2012.02.03 / LPN
+ - Added attributes and methods related to iLO
+ - Shortened the templates variable names
+
+1.06 / 2012.01.27 / SAPER
+ - Added bin/cfengine-tags to generate tags files for Cfengine.
+ - Added t/01-programs.t to test the commands.
+ - RackMan::Tasks::task_list() can now return the list of devices instead
+ of just printing it.
+ - Added RackMan::Types to handle types translation between RackTables
+ and RackMan, with provision for future types.
+ - Added mocked Cacti commands, for test purpose.
+
+1.05 / 2012.01.20 / SAPER
+ - Formats now throw an error when a mandatory attribute is missing.
+ - Template parameters have all been factored into RackMan::Template.
+ - Some template parameters have changed: host_ipaddr now is host_if0_ip,
+ host_macaddr now is host_if0_mac, host_name now is host_fqdn.
+ - New template parameters: host_name, host_if0_name.
+ - RackMan::Device::Server::HP_ProLiant now mandates the iLO subsystem
+ to have a FQDN.
+
+1.04 / 2012.01.18 / SAPER
+ - Verbose mode now prints the SCM commands being executed.
+
+1.03 / 2012.01.16 / SAPER
+ - Added RackMan::Format::PXE.
+ - The host_name parameter in Device::Format::DHCP and ::Kickstart now
+ contains the FQDN whenever possible.
+ - Added t/93-eol.t, t/98-kwalitee.t, t/99-distchk.t
+
+1.02 / 2012.01.13 / SAPER
+ - Added new template parameters in Device::Format::DHCP and ::Kickstart.
+ - Several internal fixes and adjustements.
+
+1.01 / 2012.01.11 / SAPER
+ - RackMan::Format::DHCP and ::Kickstart now support more template
+ parameters, which are now documented.
+ - bin/cisco-status no longer try to resolve VRRP addresses, and check that
+ the switch is actually a Cisco device.
+ - Added a workaround for a small bug in old versions of Term::ANSIcolor.
+ - Added cgi/rackapi to provide a small web service access.
+
+1.00 / 2012.01.03 / SAPER
+ - Improved the info task output by sorting the ports by their names.
+ - Fixed a cosmetic issue with the diff output.
+ - Handle some corner case with Cisco devices.
+ - Now install config file as rack.sample.conf to avoid overwriting
+ existing configuration.
+ - Fixed some incompatibilities between Perl before 5.12 and IPv6 support.
+ - Improved the documentation, which can now be easily converted to HTML
+ with the tools/make_doc script.
+
+0.99 / 2011.12.21 / SAPER
+ - Configuration file is now installed within the PREFIX hierarchy.
+ - Added bin/cisco-status to display the status of a Cisco network switch.
+
+0.98 / 2011.12.19 / SAPER
+ - Add support for DNS settings in the APC_RackPDU role.
+
+0.97 / 2011.12.16 / SAPER
+ - Support the --device_password option within the Cisco_Catalyst role.
+ - Handle Rackman::Device objects lacking an expected role.
+ - Adjusted installation process.
+
+0.96 / 2011.12.14 / SAPER
+ - Fixed a small bug that prevented to fetch implicit tags.
+
+0.95 / 2011.12.07 / SAPER
+ - Implemented RackMan::Format::Cacti.
+
+0.90 / 2011.12.02 / SAPER
+ - Tested and debugged the RackMan::Device::* roles PDU::APC_RackPDU,
+ Server::HP_ProLiant and Switch::Cisco_Catalyst.
+ - Now use Term::ANSIColor for outputing colors.
+
+0.80 / 2011.12.01 / SAPER
+ - Implemented RackMan::Device::Switch::Cisco_Catalyst.
+ - Factored out the diff code in Rackman::Utils.
+
+0.70 / 2011.11.30 / SAPER
+ - Implemented RackMan::Device::Server::HP_ProLiant
+
+0.60 / 2011.11.24 / SAPER
+ - Improved RackMan::Config to allow per-device configuration.
+ - Added a "list" action.
+ - Fixed and improved t/50-rackman-scm.t
+
+0.55 / 2011.11.17 / SAPER
+ - Completely finalised RackMan::Device::PDU::APC_RackPDU, with a new
+ implementation of its "diff" task, and the completion of the "push" task.
+ - Added RackTables::Schema::Result::viewRack
+
+0.50 / 2011.11.16 / SAPER
+ - Finalised most of RackMan::Device::PDU::APC_RackPDU, including a tentative
+ implementation of its "diff" task.
+ - Added RackMan::Config.
+
+0.45 / 2011.11.15 / SAPER
+ - Implemented a good chunk of RackMan::Device::PDU::APC_RackPDU, including
+ the config generation part.
+
+0.40 / 2011.11.10 / SAPER
+ - Finalised formats generation for DHCP, Kickstart, LDAP, Bacula, Nagios.
+ Prepared Cacti format.
+ - Added RackMan::SCM, RackMan::Template.
+
+0.30 / 2011.11.04 / SAPER
+ - Added Rackman::File.
+ - Working prototypes for some formats: Nagios, Kickstart, DHCP, LDAP.
+
+0.20 / 2011.11.01 / SAPER
+ - Finalised RackMan::Device.
+ - Finalised "info" task. Prepared "diff" and "write" tasks.
+
+0.10 / 2011.10.26 / SAPER
+ - Designed rack architecture.
+ - Added tools/schema to properly update the schema from the RackTables
+ database.
+
+0.01 / 2011.10.19 / SAPER
+ - First prototype to extract information from RackTables using DBIx::Class.
+
119 MANIFEST
@@ -0,0 +1,119 @@
+MANIFEST
+META.yml
+META.json
+Makefile.PL
+Build.PL
+README
+Changes
+bin/cfengine-tags
+bin/cisco-status
+bin/rack
+cgi/rackapi
+cgi/rtlink
+etc/host.dhcp.conf.tmpl
+etc/host.pxe.conf.tmpl
+etc/host.kickstart.cfg.tmpl
+etc/rack.conf
+lib/RackMan/Config.pm
+lib/RackMan/Device/PDU/APC_RackPDU.pm
+lib/RackMan/Device/PDU.pm
+lib/RackMan/Device.pm
+lib/RackMan/Device/Server/HP_ProLiant.pm
+lib/RackMan/Device/Server.pm
+lib/RackMan/Device/Switch/Cisco_Catalyst.pm
+lib/RackMan/Device/Switch.pm
+lib/RackMan/File.pm
+lib/RackMan/Format/Bacula.pm
+lib/RackMan/Format/Cacti.pm
+lib/RackMan/Format/DHCP.pm
+lib/RackMan/Format/Kickstart.pm
+lib/RackMan/Format/LDAP.pm
+lib/RackMan/Format/Nagios.pm
+lib/RackMan/Format/PXE.pm
+lib/RackMan.pm
+lib/RackMan/SCM.pm
+lib/RackMan/Tasks.pm
+lib/RackMan/Template.pm
+lib/RackMan/Types.pm
+lib/RackMan/Utils.pm
+lib/RackTables/Schema.pm
+lib/RackTables/Schema/Result/Atom.pm
+lib/RackTables/Schema/Result/AttributeMap.pm
+lib/RackTables/Schema/Result/Attribute.pm
+lib/RackTables/Schema/Result/AttributeValue.pm
+lib/RackTables/Schema/Result/CachedPAV.pm
+lib/RackTables/Schema/Result/CachedPNV.pm
+lib/RackTables/Schema/Result/CachedPVM.pm
+lib/RackTables/Schema/Result/CactiGraph.pm
+lib/RackTables/Schema/Result/Chapter.pm
+lib/RackTables/Schema/Result/Config.pm
+lib/RackTables/Schema/Result/Dictionary.pm
+lib/RackTables/Schema/Result/EntityLink.pm
+lib/RackTables/Schema/Result/FileLink.pm
+lib/RackTables/Schema/Result/File.pm
+lib/RackTables/Schema/Result/IPv4Address.pm
+lib/RackTables/Schema/Result/IPv4Allocation.pm
+lib/RackTables/Schema/Result/IPv4LB.pm
+lib/RackTables/Schema/Result/IPv4NAT.pm
+lib/RackTables/Schema/Result/IPv4Network.pm
+lib/RackTables/Schema/Result/IPv4RS.pm
+lib/RackTables/Schema/Result/IPv4RSPool.pm
+lib/RackTables/Schema/Result/IPv4VS.pm
+lib/RackTables/Schema/Result/IPv6Address.pm
+lib/RackTables/Schema/Result/IPv6Allocation.pm
+lib/RackTables/Schema/Result/IPv6Network.pm
+lib/RackTables/Schema/Result/LDAPCache.pm
+lib/RackTables/Schema/Result/Link.pm
+lib/RackTables/Schema/Result/Molecule.pm
+lib/RackTables/Schema/Result/MountOperation.pm
+lib/RackTables/Schema/Result/ObjectLog.pm
+lib/RackTables/Schema/Result/ObjectParentCompat.pm
+lib/RackTables/Schema/Result/PortAllowedVLAN.pm
+lib/RackTables/Schema/Result/PortCompat.pm
+lib/RackTables/Schema/Result/PortInnerInterface.pm
+lib/RackTables/Schema/Result/PortInterfaceCompat.pm
+lib/RackTables/Schema/Result/PortNativeVLAN.pm
+lib/RackTables/Schema/Result/Port.pm
+lib/RackTables/Schema/Result/PortVLANMode.pm
+lib/RackTables/Schema/Result/RackHistory.pm
+lib/RackTables/Schema/Result/RackObjectHistory.pm
+lib/RackTables/Schema/Result/RackObject.pm
+lib/RackTables/Schema/Result/Rack.pm
+lib/RackTables/Schema/Result/RackRow.pm
+lib/RackTables/Schema/Result/RackSpace.pm
+lib/RackTables/Schema/Result/Script.pm
+lib/RackTables/Schema/Result/ServiceStorage.pm
+lib/RackTables/Schema/Result/ServiceTree.pm
+lib/RackTables/Schema/Result/TagStorage.pm
+lib/RackTables/Schema/Result/TagTree.pm
+lib/RackTables/Schema/Result/UserAccount.pm
+lib/RackTables/Schema/Result/UserConfig.pm
+lib/RackTables/Schema/Result/viewAssociatedPorts.pm
+lib/RackTables/Schema/Result/viewIPv4AddressNetwork.pm
+lib/RackTables/Schema/Result/viewIPv4AddressRange.pm
+lib/RackTables/Schema/Result/viewRack.pm
+lib/RackTables/Schema/Result/VLANDescription.pm
+lib/RackTables/Schema/Result/VLANDomain.pm
+lib/RackTables/Schema/Result/VLANIPv4.pm
+lib/RackTables/Schema/Result/VLANIPv6.pm
+lib/RackTables/Schema/Result/VLANSTRule.pm
+lib/RackTables/Schema/Result/VLANSwitch.pm
+lib/RackTables/Schema/Result/VLANSwitchTemplate.pm
+lib/RackTables/Schema/Result/VLANValidID.pm
+lib/RackTables/Types.pm
+t/00-load.t
+t/01-api.t
+t/01-programs.t
+t/50-rackman-config.t
+t/50-rackman-file.t
+t/50-rackman-scm.t
+t/50-rackman-template.t
+t/80-rackman.t
+t/90-pod.t
+t/91-pod-coverage.t
+t/93-eol.t
+t/98-kwalitee.t
+t/99-distchk.t
+t/files/rack.conf
+tools/make_doc
+tools/schema
86 Makefile.PL
@@ -0,0 +1,86 @@
+use strict;
+use warnings;
+use ExtUtils::MakeMaker;
+
+my %compat_prereqs;
+
+if ($] < 5.012) {
+ $compat_prereqs{Socket6} = 0;
+}
+
+WriteMakefile(
+ NAME => 'RackMan',
+ LICENSE => 'gpl',
+ AUTHOR => 'Sebastien Aperghis-Tramoni <sebastien@aperghis.net>',
+ VERSION_FROM => 'lib/RackMan.pm',
+ ABSTRACT_FROM => 'lib/RackMan.pm',
+ EXE_FILES => [
+ 'bin/rack', 'bin/cfengine-tags', 'bin/cisco-status'
+ ],
+ PREREQ_PM => {
+ # prereqs
+ 'Algorithm::Diff' => 0,
+ 'Carp' => 0,
+ 'Config::IniFiles' => 0,
+ 'Cwd' => 0,
+ 'DBD::mysql' => 0,
+ 'DBIx::Class' => 0,
+ 'DBIx::Class::Schema::Loader' => 0,
+ 'Exporter' => 0,
+ 'File::Basename' => 0,
+ 'File::Path', => 0,
+ 'File::Temp', => 0,
+ 'Getopt::Long' => 0,
+ 'HTML::Template' => '2.0',
+ 'HTML::Template::Filter::TT2' => '0.03',
+ 'IPC::Run', => 0,
+ 'JSON', => 0,
+ 'List::MoreUtils', => 0,
+ 'Moose' => '1.00',
+ 'MooseX::NonMoose' => 0,
+ 'namespace::autoclean' => 0,
+ 'NetAddr::IP' => '4.000',
+ 'Net::FTP', => 0,
+ 'Net::ILO', => "0.54",
+ 'Net::SNMP', => 0,
+ 'Net::Telnet::Cisco', => 0,
+ 'Path::Class' => 0,
+ 'Socket' => 0,
+ 'Term::ANSIColor' => 0,
+ 'YAML', => 0,
+ %compat_prereqs,
+
+ # build/test prereqs
+ 'Test::More' => '0.45',
+ },
+ postamble => {
+ files => {
+ 'etc/rack.conf' => '$(PREFIX)/etc/rack.sample.conf',
+ },
+ },
+ PL_FILES => {},
+ dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
+ clean => { FILES => 'RackMan-*' },
+);
+
+
+sub MY::postamble {
+ my ($mm, %args) = @_;
+ my $postamble = "";
+
+ # install data files (in /etc, /usr/share, ...)
+ # first, we must add a target in install::
+ for my $makeline (@{ $mm->{RESULT} }) {
+ $makeline =~ s/(install *::.+)\n/$1 priv_data_files\n/;
+ }
+
+ # then, declare the target with the files
+ $postamble .= "\nINSTALL = install -D -p\n\npriv_data_files:\n";
+
+ while (my($file,$target) = each %{ $args{files} }) {
+ $postamble .= "\t\$(INSTALL) $file $target\n";
+ }
+
+ return $postamble
+}
+
53 NOTES
@@ -0,0 +1,53 @@
+NOTES
+
+Notes pour le futur mainteneur.
+
+- Il s'agit de mon premier gros projet avec DBIx::Class et Moose, donc
+ l'utilisation de ces modules n'est certainement pas optimale.
+
+- Exécuter tools/make_doc pour générer la documentation en HTML dans
+ le répertoire html/ (nécessite le module Pod::Tree). Ce n'est pas
+ révolutionnaire mais ça aide pour voir la documentation disponible.
+
+- IMPORTANT: Les modules DBIx::Class qui implémentent le schéma de
+ RackTables sont générés par DBIx::Class::Schema::Loader, cf. le script
+ tools/schema qui automatise son exécution avec les bons paramètres.
+ Certains modules sont modifiés pour ajouter quelques relations, mais
+ il faut bien faire attention à ne mettre du code que là où on a le
+ droit. Les quelques modules que j'ai écrit de zéro ont tous un nom qui
+ commencent par "view" pour les distinguer plus facilement des autres
+ (et parce qu'il s'agit souvent de requêtes imbriquées).
+
+- Les noms de certaines méthodes ne sont pas géniaux, en particulier les
+ plugins de formats qui doivent exporter une méthode "write". La raison
+ en est qu'au début, on ne pensait vraiment que écrire des fichiers de
+ configuration. Ce n'est qu'après que j'ai vu que cela n'était pas
+ possible pour les serveurs HP (protocole iLO) ou la configuration dans
+ Cacti (exécution des commandes), mais bon..
+
+- RackMan::Format::DHCP, ::PXE et ::Kickstart sont des quasi copiés-collés.
+ J'ai déjà fait une étape de refactoring en mettant une partie du code
+ commun dans RackMan::Template::populate_from() mais il y a peut-être moyen
+ de faire mieux.
+
+- J'ai commencé un test assez transversal de RackMan dans t/80-rackman.t
+ Toutefois, pour le rendre plus automatique, il faudrait qu'il s'appuie
+ sur une base SQLite plutôt que MySQL. J'ai écris un script pour convertir
+ un dump MySQL de RackTables en SQLite (tools/mysql2sqlite), mais j'ai dû
+ louper des trucs car il manque des données, en particulier des attributs,
+ et en particulier des attributs "HW type" qui sont nécessaires à la
+ composition des rôles d'équipement. Donc forcément, ça marche beaucoup
+ moins bien. J'ai mis les tests qui échouent en TODO pour ne pas être gêné
+ pour le moment.
+
+- RackMan::SCM a été conçu comme un moyen simple pour gérer le versionnement
+ des fichiers sans aller dans la complexité d'un framework comme VCI.
+ Maintenant, c'est peut-être un peu trop simpliste par endroits, et mériterait
+ d'être un peu mieux peaufiné, par exemple tester si le fichier est déjà
+ versionné avant de faire un add() et ainsi éviter des messages d'erreurs
+ disgracieux. Suggestion : capturer les sorties avec IPC::Run et ne les
+ afficher que quand c'est nécessaire ou approprié.
+
+
+Sébastien Aperghis-Tramoni <sebastien@aperghis.net>
+
66 README
@@ -0,0 +1,66 @@
+NAME
+
+ RackMan - Perl modules for connecting to a RackTables database
+
+
+DESCRIPTION
+
+ Rackman is a set of Perl modules for fetching information from a
+ RackTables database RackTables. The distribution also includes
+ some commands that show how to use the RackMan API.
+
+ * rack is a program that generates the configuration files for the
+ given RackObject, and talk with the corresponding devices to set
+ them up accordingly.
+
+ * cisco-status is a program that connects to a Cisco switch to list
+ the devices connected to it, with additionnal information resolved
+ from RackTables.
+
+ * cfengine-tags is a program that generates tag files for Cfengine.
+
+ A technical presentation of this software was made at the French
+ Perl Workshop 2012:
+ http://maddingue.org/conferences/fpw-2012/rackman/
+
+ Note: This software was written to perform very specific tasks.
+ Although it was tried to keep it generic, it certainly isn't, and
+ the documentation is very rough. There's a more comprehensive
+ tutorial (only in French for now) in pod/RackMan/Manual.fr.pod
+
+
+INSTALLATION
+
+ To install this module, run the following commands:
+
+ perl Makefile.PL
+ make
+ make test
+ make install
+
+ Alternatively, to install with Module::Build, you can use the
+ following commands:
+
+ perl Build.PL
+ ./Build
+ ./Build test
+ ./Build install
+
+
+SUPPORT AND DOCUMENTATION
+
+ After installing, you can find documentation for this module with
+ the perldoc command.
+
+
+LICENSE
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 3 or
+ later: http://www.fsf.org/licensing/licenses/gpl.txt
+
+
+AUTHOR
+
+ Sebastien Aperghis-Tramoni (sebastien@aperghis.net)
+
72 README.pod
@@ -0,0 +1,72 @@
+=head1 NAME
+
+RackMan - Perl modules for connecting to a RackTables database
+
+
+=head1 DESCRIPTION
+
+Rackman is a set of Perl modules for fetching information from a
+RackTables database RackTables. The distribution also includes
+some commands that show how to use the RackMan API.
+
+=over
+
+=item *
+rack is a program that generates the configuration files for the
+given RackObject, and talk with the corresponding devices to set
+them up accordingly.
+
+=item *
+cisco-status is a program that connects to a Cisco switch to list
+the devices connected to it, with additionnal information resolved
+from RackTables.
+
+=item *
+cfengine-tags is a program that generates tag files for Cfengine.
+
+=back
+
+A technical presentation of this software was made at the French
+Perl Workshop 2012: L<http://maddingue.org/conferences/fpw-2012/rackman/>
+
+Note: This software was written to perform very specific tasks.
+Although it was tried to keep it generic, it certainly isn't, and
+the documentation is very rough. There's a more comprehensive
+tutorial (only in French for now) in pod/RackMan/Manual.fr.pod
+
+
+=head1 INSTALLATION
+
+To install this module, run the following commands:
+
+ perl Makefile.PL
+ make
+ make test
+ make install
+
+Alternatively, to install with Module::Build, you can use the
+following commands:
+
+ perl Build.PL
+ ./Build
+ ./Build test
+ ./Build install
+
+
+=head1 SUPPORT AND DOCUMENTATION
+
+After installing, you can find documentation for this module with
+the perldoc command.
+
+
+=head1 LICENSE
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 3 or
+later: L<http://www.fsf.org/licensing/licenses/gpl.txt>
+
+
+=head1 AUTHOR
+
+Sebastien Aperghis-Tramoni (sebastien@aperghis.net)
+
390 bin/cfengine-tags
@@ -0,0 +1,390 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use File::Basename;
+use File::Path;
+use File::Spec::Functions;
+use Getopt::Long;
+use List::MoreUtils qw< uniq >;
+use RackMan;
+use RackMan::Config;
+use RackMan::Tasks;
+
+
+$::PROGRAM = "Cfengine tags generator";
+$::VERSION = "1.03";
+$::COMMAND = basename($0);
+
+
+#
+# main
+#
+MAIN: {
+ if (not caller()) {
+ run();
+ exit RackMan->status;
+ }
+}
+
+
+#
+# run()
+# ---
+sub run {
+ # detect if stdout is connected to a terminal
+ (-t STDOUT ? $Term::ANSIColor::AUTORESET : $ENV{ANSI_COLORS_DISABLED}) = 1;
+ $|++;
+
+ # default options
+ my %options = (
+ config => "/usr/local/etc/rack.conf",
+ );
+
+ # parse options
+ Getopt::Long::Configure(qw< no_auto_abbrev no_ignore_case >);
+ GetOptions(\%options, qw{
+ help|usage|h! man! version|V!
+ verbose|v! config|c=s
+ attrs|A=s tags|T=s filter|F=s
+ }) or pod2usage(0);
+
+ # handle --version, --help and --man
+ $options{man} and pod2usage(2);
+ $options{help} and pod2usage(1);
+ $options{version} and print "$::PROGRAM v$::VERSION\n" and exit;
+
+ # read configuration file
+ my $config = RackMan::Config->new(-file => $options{config});
+
+ # check for mandatory parameters
+ RackMan->error("missing config parameter [cfengine-tags]/path")
+ if not $config->val("cfengine-tags", "path");
+
+ # instanciate the backend object
+ my $rackman = RackMan->new({ options => \%options, config => $config });
+
+ # do the actual work
+ push @ARGV, $config->val("cfengine-tags", "type", "server") if not @ARGV;
+ process($rackman, @ARGV);
+}
+
+
+#
+# pod2usage()
+# ---------
+sub pod2usage {
+ my ($n) = @_;
+ require Pod::Usage;
+ Pod::Usage::pod2usage({ -exitval => 0, -verbose => $n, -noperldoc => 1 });
+}
+
+
+#
+# process()
+# -------
+sub process {
+ my ($rackman, @args) = @_;
+
+ # construct the list of wanted tags, if any
+ my $tags_list = $rackman->options->{tags}
+ || $rackman->config->val("cfengine-tags", "tags", "");
+ $tags_list =~ s/^ +| +$//g;
+ my %wanted_tag = map { $_ => 1 } split / *, */, $tags_list;
+
+ # construct the list of wanted attributes, if any
+ my @attrs = split / *, */, $rackman->options->{attrs}
+ || $rackman->config->val("cfengine-tags", "attrs", "");
+
+ # construct the filter
+ my %filter;
+ my $filter = $rackman->options->{filter}
+ || $rackman->config->val("cfengine-tags", "filter", "");
+ if ($filter) {
+ for my $token (split / *, */, $filter) {
+ if (index($token, "tag:") == 0) {
+ $filter{tag}{ lc substr($token, 4) } = 1;
+ next
+ }
+
+ if (index($token, "=") > 0) {
+ my ($name, $value) = split /=/, $token;
+ push @{ $filter{attr}{$name} }, $value;
+ next
+ }
+ }
+ }
+
+ # NOTE: I know that the way to first list the devices, then find their
+ # tags, then construct the reverse hash by tags, is pretty inefficient
+ # but it's also pretty easy to code, way easier for me than trying to
+ # find the correct SQL requests (or the DBIx::Class equivalents) :p
+
+ # fetch the list of devices of the given type(s)
+ my @types = map { split /[, ]+/ } @args;
+ my @devices = map {
+ RackMan::Tasks->task_list({ rackman => $rackman, type => $_ })
+ } @types;
+
+ # construct the lists of devices for each tag
+ my %devices_by_tag;
+
+ for my $device (@devices) {
+ next if $device->{name} eq "(null)";
+ my $rackdev = $rackman->device_by_id($device->{id});
+
+ # fetch the tags for this RackObject, filter them if needed
+ my @tags = map { s/\W+/_/g; $_ } map lc,
+ @{ $rackdev->explicit_tags },
+ @{ $rackdev->implicit_tags };
+ @tags = grep $wanted_tag{$_}, @tags if $tags_list;
+ next unless @tags;
+
+ # fetch the attributes of this RackObject
+ my %attr = %{ $rackdev->attributes };
+
+ # filter out devices without the requested tags or attributes
+ if ($filter) {
+ # filter on tags
+ if (exists $filter{tag}) {
+ next unless grep $filter{tag}{$_}, @tags;
+ }
+
+ # filter on attributes
+ if (exists $filter{attr}) {
+ my $keep = 0;
+
+ ATTR:
+ for my $name (keys %{ $filter{attr} }) {
+ for my $value (@{ $filter{attr}{$name} }) {
+ $keep ||= $value eq $attr{$name};
+ last ATTR if $keep;
+ }
+ }
+
+ next unless $keep;
+ }
+ }
+
+ # fetch the FQDN of this RackObject
+ my $fqdn = $rackdev->attributes->{FQDN};
+ RackMan->warning("RackObject '$device->{name}' lacks a FQDN")
+ and next unless $fqdn;
+
+ # check if it correctly resolves
+ my (undef, undef, $addrtype, $length, @addrs) = gethostbyname($fqdn);
+ RackMan->warning("RackObject '$device->{name}' has a non resolvable FQDN")
+ and next unless @addrs;
+
+ # mogrify it for Cfengine
+ $fqdn =~ s/[-.]/_/g;
+
+ # associate the FQDN to each corresponding tag
+ for my $tag (@tags) {
+ push @{ $devices_by_tag{$tag} }, $fqdn;
+ }
+
+ # also associate the FQDN to each requested attribute
+ for my $attr_name (@attrs) {
+ push @{ $devices_by_tag{ $attr{$attr_name} } }, $fqdn;
+ }
+ }
+
+ # write the tags files
+ my $dir = $rackman->config->val("cfengine-tags", "path");
+ mkpath $dir;
+
+ for my $tag (keys %devices_by_tag) {
+ my $file = catfile($dir, $tag);
+ open my $fh, ">", $file
+ or RackMan->error("can't write file '$file': $!");
+ print {$fh} map "$_\n", uniq sort @{ $devices_by_tag{$tag} };
+ close $fh;
+ }
+}
+
+
+1
+
+__END__
+
+=pod
+
+=head1 NAME
+
+cfengine-tags - Generate tags files for Cfengine
+
+=head1 SYNOPSIS
+
+ cfengine-tags [--config /etc/rack.conf] [--tags tag1,tag2,...] [type]
+ cfengine-tags { --help | --man | --version }
+
+
+=head1 OPTIONS
+
+=head2 Standard options
+
+=over
+
+=item B<-c>, B<--config> I<path>
+
+Specify the path to the configuration file.
+Default to F</usr/local/etc/rack.conf>
+
+=item B<-v>, B<--verbose>
+
+Run the program in verbose mode.
+
+=back
+
+=head2 Program options
+
+=over
+
+=item B<-T>, B<--tags> I<list of tags>
+
+Specify a comma-separated list of tags to process. This option overrides
+the C<[cfengine-tags]/tags> config parameter.
+
+=item B<-A>, B<--attrs> I<list of attributes>
+
+Specify a comma-separated list of attributes, which values will be used
+as tag names. This option overrides the C<[cfengine-tags]/attrs> config
+parameter.
+
+=item B<-F>, B<--filter> I<list of tokens>
+
+Specify a comma-separated list of tokens, defining tags and attribute
+values. This option overrides the C<[cfengine-tags]/filter> config
+parameter. See the corresponding documentation for more details.
+
+=back
+
+=head2 Help options
+
+=over
+
+=item B<-h>, B<--help>
+
+Print a short usage description, then exit.
+
+=item B<--man>
+
+Print the manual page of the program, then exit.
+
+=item B<-V>, B<--version>
+
+Print the program name and version, then exit.
+
+=back
+
+
+=head1 DESCRIPTION
+
+This program generate a bunch of definition files for Cfengine, one for
+each RackTables tag, containing the names of the devices with that tag.
+
+The list of tags to process can be given by the C<[cfengine-tags]/tags>
+config parameter or the C<--tags> option. If no explicit list is provided,
+process all the tags attached to the devices.
+
+In a similar way, a list of attributes can be given with the
+C<[cfengine-tags]/attrs> config parameter or the C<--attrs> option.
+The value of these attributes will be mogrified to generate additional tags.
+
+A filter can be given, either by the C<[cfengine-tags]/filter> config
+parameter or the C<--filter> option, to filter the result devices list:
+only the devices with any of the given tags or attribute name-value pairs
+will be included. A lack of tag or attribute definition disables the
+filtering of that type.
+
+
+=head1 CONFIGURATION
+
+cfengine-tags(1)'s configuration is stored in rack(1)'s configuration,
+with the following additional definitions.
+
+
+=head2 Section [cfengine-tags]
+
+=over
+
+=item *
+
+C<path> - specify the path where to write the tags files
+
+=item *
+
+C<type> - specify the default type if none is given to the command;
+default to C<"server">.
+
+=item *
+
+C<tags> - specify the defaults tags as a comma-separated list;
+will be overridden by the C<--tags> option
+
+=item *
+
+C<attrs> - specify some attributes, as a comma-separated list, which
+values will be used as tag names; will be overridden by the C<--attrs>
+option
+
+=item *
+
+C<filter> - specify a comma-separated list of tokens, defining tags
+and attribute values; will be overridden by the C<--filter> option.
+
+When defined, only the devices with the matching tags and attributes
+will be included in the resulting lists. When no tag or attribute
+pair is defined, the filtering fot that particular type is disabled.
+
+The sub-syntax of the tokens is:
+
+=over
+
+=item *
+
+a token in the form C<tag:name> defines the tag with the given name
+
+=item *
+
+a token in the form C<attr=value> defines the pair (attribute, value)
+
+=back
+
+Examples:
+
+=over
+
+=item *
+
+only keep the devices with the tags C<generic> and C<infra> (no attribute
+filtering):
+
+ tag:generic, tag:infra
+
+=item *
+
+only keep the devices with the attribute C<Use> set to C<prod> or C<preprod>
+(no tag filtering):
+
+ Use=prod, Use=preprod
+
+=item *
+
+only keep the devices with the tag C<cfengine> and the attribute C<Use>
+set to C<prod> or C<preprod>:
+
+ tag:cfengine, Use=prod, Use=preprod
+
+=back
+
+=back
+
+
+=head1 AUTHOR
+
+Sebastien Aperghis-Tramoni (sebastien@aperghis.net)
+
+=cut
+
412 bin/cisco-status
@@ -0,0 +1,412 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use File::Basename;
+use Getopt::Long;
+use List::Util qw< max >;
+use Net::Telnet::Cisco;
+use RackMan;
+use RackMan::Config;
+use RackMan::Device::Switch::Cisco_Catalyst;
+use Term::ANSIColor qw< :constants >;
+
+
+$::PROGRAM = "Cisco switch status";
+$::VERSION = "1.02";
+$::COMMAND = basename($0);
+
+
+use constant {
+ CONFIG_SECTION => RackMan::Device::Switch::Cisco_Catalyst->CONFIG_SECTION,
+};
+
+
+#
+# main
+#
+MAIN: {
+ if (not caller()) {
+ run();
+ exit RackMan->status;
+ }
+}
+
+
+#
+# run()
+# ---
+sub run {
+ # detect if stdout is connected to a terminal
+ (-t STDOUT ? $Term::ANSIColor::AUTORESET : $ENV{ANSI_COLORS_DISABLED}) = 1;
+ $|++;
+
+ # default options
+ my %options = (
+ config => "/usr/local/etc/rack.conf",
+ );
+
+ # parse options
+ Getopt::Long::Configure(qw< no_auto_abbrev no_ignore_case >);
+ GetOptions(\%options, qw{
+ help|usage|h! man! version|V!
+ verbose|v! config|c=s
+ device_login|device-login=s device_password|device-password=s
+ }) or pod2usage(0);
+
+ # handle --version, --help and --man
+ $options{man} and pod2usage(2);
+ $options{help} and pod2usage(1);
+ $options{version} and print "$::PROGRAM v$::VERSION\n" and exit;
+
+ # if there's no argument, print the usage
+ pod2usage(1) if @ARGV == 0;
+
+ # read configuration file
+ my $config = RackMan::Config->new(-file => $options{config});
+
+ # instanciate the backend object
+ my $rackman = RackMan->new({ options => \%options, config => $config });
+
+ # do the actual work
+ process($rackman, @ARGV);
+}
+
+
+#
+# pod2usage()
+# ---------
+sub pod2usage {
+ my ($n) = @_;
+ require Pod::Usage;
+ Pod::Usage::pod2usage({ -exitval => 0, -verbose => $n, -noperldoc => 1 });
+}
+
+
+#
+# process()
+# -------
+sub process {
+ my ($rackman, @args) = @_;
+
+ my $device_name = shift @args;
+ my $racktables = $rackman->racktables;
+
+ # fetch the corresponding RackObject
+ my $rackobj = $rackman->device($device_name);
+
+ # check that the device is actually a Cisco switch
+ print STDERR "error: '$device_name' is not a Cisco device\n" and return
+ unless $rackobj->DOES("RackMan::Device::Switch::Cisco_Catalyst");
+
+ # fetch the access password
+ $rackman->config->set_current_rackobject($rackobj);
+ my $ios_password = $rackman->options->{device_password}
+ || $rackman->config->val(CONFIG_SECTION, "ios_password")
+ or RackMan->error("missing IOS access password");
+
+ # connect to the device
+ my $host = $rackobj->attributes->{FQDN} || $rackobj->object_name;
+ print $rackobj->object_name, " ($host, ";
+ my $session = Net::Telnet::Cisco->new(Host => $host);
+ $session->login(Password => $ios_password);
+ $session->cmd("terminal length 0");
+
+ # fetch and parse information from the device
+ my @out = $session->cmd("show version");
+ my %version = parse_version(@out);
+ print "$version{model}, IOS $version{ios})\n";
+
+ @out = $session->cmd("show etherchannel summary");
+ my %group = parse_etherchannels(@out);
+
+ @out = $session->cmd("show mac address-table dynamic");
+ my %addr = parse_mac_address_table(@out);
+
+ @out = $session->cmd("show interfaces status");
+ my %interface = parse_interfaces_status(@out);
+
+ # associate the MAC addresses of a channel with the physical
+ # interfaces that compose the channel
+ for my $channel (keys %group) {
+ for my $port (keys %{ $group{$channel}{ports} }) {
+ $addr{$port} = $addr{$channel}
+ if not $addr{$port} or not @{ $addr{$port} };
+ }
+ }
+
+ my $m = max map length, keys %interface;
+ my %port_status = (
+ connected => GREEN ("[+]"),
+ disabled => RED ("[X]"),
+ notconnect => YELLOW("[ ]"),
+ );
+
+ for my $name (sort by_interface keys %interface) {
+ next if $group{$name}; # skip port channels
+ my $n = ref $addr{$name} ? @{ $addr{$name} } : 0;
+ my $hilight = $interface{$name}{status} eq "connected" ? BOLD : "";
+
+ printf "- %-${m}s %s%s %s%-7s %6s/%-6s%s ",
+ $name,
+ $hilight,
+ $port_status{ $interface{$name}{status} },
+ $hilight,
+ $interface{$name}{vlan} eq "trunk" ? "trunk"
+ : "vlan:$interface{$name}{vlan}",
+ $interface{$name}{duplex},
+ $interface{$name}{speed},
+ $interface{$name}{status} eq "connected" ? RESET : "",
+ ;
+
+ if (0 < $n and $n <= 2) {
+ # fetch the MAC address associated to the interface
+ my $peer_mac = lc shift @{ $addr{$name} };
+ my ($peer_name, $peer_iface) = ("", "");
+
+ if (index($peer_mac, "00005e0001") == 0) {
+ # detect VRRP addresses
+ $peer_name = "(VRRP)";
+ }
+ else {
+ # find the corresponding device
+ my $peer_obj = $racktables->resultset("RackObject")->search(
+ { l2address => $peer_mac },
+ { join => "ports" },
+ )->first;
+ $peer_name = eval { $peer_obj->name };
+
+ if ($peer_name) {
+ my $peer = $rackman->device($peer_name);
+ my ($peer_port)
+ = grep { lc($_->{l2address} || "") eq $peer_mac }
+ @{ $peer->ports };
+ $peer_iface = "[$peer_port->{name}]";
+ }
+ }
+
+ # format the MAC address in the usual way
+ my $peer_addr = $peer_mac;
+ $peer_addr =~ s/(\w\w)/$1:/g;
+ $peer_addr =~ s/:$//;
+
+ $peer_name ||= YELLOW("unknown");
+ print "$peer_addr = $peer_name $peer_iface ";
+ }
+
+ print $/;
+ }
+}
+
+
+#
+# by_interface()
+# ------------
+sub by_interface {
+ my @a = split "/", $a;
+ my @b = split "/", $b;
+ $a[0] cmp $b[0] || $a[1] <=> $b[1] || $a[2] <=> $b[2]
+}
+
+
+#
+# parse_version()
+# -------------
+sub parse_version {
+ my (@out) = @_;
+
+ chomp @out;
+ my %version = ( ios => "unkown", model => "unkown" );
+
+ for (@out) {
+ /Cisco IOS Software.*Version (\S+),/ and $version{ios} = $1;
+ /Model number\s*:\s*(\S+)/ and $version{model} = $1;
+ }
+
+ return %version
+}
+
+
+#
+# parse_etherchannels()
+# -------------------
+sub parse_etherchannels {
+ my (@out) = @_;
+
+ chomp @out;
+ my %channel;
+
+ for (@out) {
+ s/^ *//; # remove leading blanks
+ next if /^Flags/ .. /^----/; # skip the header
+ my ($num, $name, $proto, @ports) = split / +/;
+ next unless $num and $num =~ /^\d+$/;
+
+ # extract the group name and flags
+ $name =~ s/\((\w+)\)$//;
+ my %flags = map { $_ => 1 } split //, $1;
+
+ my %ports;
+
+ # extract each port name and flags
+ for my $port (@ports) {
+ (my $name = $port) =~ s/\((\w+)\)$//;
+ my %flags = map { $_ => 1 } split //, $1;
+ $ports{$name} = { flags => \%flags };
+ }
+
+ $channel{$name} = {
+ flags => \%flags,
+ proto => $proto,
+ ports => \%ports,
+ };
+ }
+
+ return %channel
+}
+
+
+#
+# parse_mac_address_table()
+# -----------------------
+sub parse_mac_address_table {
+ my (@out) = @_;
+
+ chomp @out;
+ my %table;
+
+ for (@out) {
+ s/^ *//; # remove leading blanks
+ next if /^Mac/ .. /^---- /; # skip the header
+ my ($num, $addr, $type, $port) = split / +/;
+ next unless $num and $num =~ /^\d+$/;
+
+ $addr =~ s/\W//g; # normalize MAC address
+ push @{ $table{$port} ||= [] }, $addr;
+ }
+
+ return %table
+}
+
+
+#
+# parse_interfaces_status()
+# -----------------------
+sub parse_interfaces_status {
+ my (@out) = @_;
+
+ chomp @out;
+ my %interface;
+
+ for my $line (@out) {
+ $line =~ s/^ *//; # remove leading blanks
+ next if $line =~ /^$/;
+
+ # extract the port name
+ $line =~ s/^(\S+)\s+//;
+ my $name = $1 or next;
+
+ # extract the fixed fields
+ $line =~ s/\s*((?:connected|disabled|notconnect).*)$// or next;
+ my $rest = $1;
+ my ($status, $vlan, $duplex, $speed, $type) = split / +/, $rest, 5;
+
+ # what's left should be the port description, if any
+ my $desc = length $line ? $line : "";
+ $line =~ s/^(\S+)\s+//;
+
+ $interface{$name} = {
+ desc => $desc, status => $status, vlan => $vlan,
+ duplex => $duplex, speed => $speed, type => $type,
+ };
+ }
+
+ return %interface
+}
+
+
+1
+
+__END__
+
+=pod
+
+=head1 NAME
+
+cisco-status - Display the status of a Cisco network switch
+
+=head1 SYNOPSIS
+
+ cisco-status [--config /etc/rack.conf] <switch-name>
+ cisco-status { --help | --man | --version }
+
+
+=head1 OPTIONS
+
+=head2 Standard options
+
+=over
+
+=item B<-c>, B<--config> I<path>
+
+Specify the path to the configuration file.
+Default to F</usr/local/etc/rack.conf>
+
+=item B<-v>, B<--verbose>
+
+Run the program in verbose mode.
+
+=back
+
+=head2 Program options
+
+=over
+
+=item B<--device-login> I<username>
+
+Specify an alternate login for connecting onto the device.
+
+=item B<--device-password> I<password>
+
+Specify an alternate password for connecting onto the device.
+
+=back
+
+=head2 Help options
+
+=over
+
+=item B<-h>, B<--help>
+
+Print a short usage description, then exit.
+
+=item B<--man>
+
+Print the manual page of the program, then exit.
+
+=item B<-V>, B<--version>
+
+Print the program name and version, then exit.
+
+=back
+
+
+=head1 DESCRIPTION
+
+This program prints the status of a Cisco network switch, displaying,
+whenever possible, for each physical interface, the MAC address of the
+device connected onto it, with its name and interface name (resolved
+from the RackTables database).
+
+
+=head1 CONFIGURATION
+
+See L<rack(1)>
+
+
+=head1 AUTHOR
+
+Sebastien Aperghis-Tramoni (sebastien@aperghis.net)
+
+=cut
+
577 bin/rack
@@ -0,0 +1,577 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use File::Basename;
+use Getopt::Long;
+use RackMan;
+use RackMan::Config;
+use Term::ANSIColor;
+
+
+$::PROGRAM = "RackManager";
+$::VERSION = RackMan->VERSION;
+$::COMMAND = basename($0);
+
+
+#
+# main
+#
+MAIN: {
+ if (not caller()) {
+ run();
+ exit RackMan->status;
+ }
+}
+
+
+#
+# run()
+# ---
+sub run {
+ # detect if stdout is connected to a terminal
+ (-t STDOUT ? $Term::ANSIColor::AUTORESET : $ENV{ANSI_COLORS_DISABLED}) = 1;
+ $|++;
+
+ # default options
+ my %options = (
+ config => "/etc/rack.conf",
+ scm => 1,
+ );
+
+ # parse options
+ Getopt::Long::Configure(qw< no_auto_abbrev no_ignore_case >);
+ GetOptions(\%options, qw{
+ help|usage|h! man! version|V!
+ verbose|v! config|c=s scm|S!
+ device_login|device-login=s device_password|device-password=s
+ read_community|read-community=s write_community|write-community=s
+ dhcp_template|dhcp-template=s
+ kickstart_template|kickstart-template|ks=s
+ pxe_template|pxe-template=s
+ }) or pod2usage(0);
+
+ # handle --version, --usage and --help
+ $options{man} and pod2usage(2);
+ $options{help} and pod2usage(1);
+ $options{version} and print "$::PROGRAM v$::VERSION\n" and exit;
+
+ # if there's no argument, print the usage
+ pod2usage(1) if @ARGV == 0;
+
+ # read configuration file
+ my $config = RackMan::Config->new(-file => $options{config});
+
+ # instanciate the backend object
+ my $rackman = RackMan->new({ options => \%options, config => $config });
+
+ # process the command
+ $rackman->process(@ARGV);
+}
+
+
+#
+# pod2usage()
+# ---------
+sub pod2usage {
+ my ($n) = @_;
+ require Pod::Usage;
+ Pod::Usage::pod2usage({ -exitval => 0, -verbose => $n, -noperldoc => 1 });
+}
+
+
+1
+
+__END__
+
+=pod
+
+=head1 NAME
+
+rack - Fetch information and generate config files for RackObjects
+
+=head1 SYNOPSIS
+
+ rack [--config /etc/rack.conf] <action> <action options> <object>
+ rack { --help | --man | --version }
+
+
+=head1 OPTIONS
+
+=head2 Standard options
+
+=over
+
+=item B<-c>, B<--config> I<path>
+
+Specify the path to the configuration file. Default to F</etc/rack.conf>
+
+=item B<-v>, B<--verbose>
+
+Run the program in verbose mode.
+
+=back
+
+=head2 Program options
+
+=over
+
+=item B<--device-login> I<username>
+
+Specify an alternate login for connecting onto the device.
+
+=item B<--device-password> I<password>
+
+Specify an alternate password for connecting onto the device.
+
+=item B<--dhcp-template> I<path>
+
+Specify an alternate template path for Device::Format::DHCP.
+
+=item B<--ks>, B<--kickstart-template> I<path>
+
+Specify an alternate template path for Device::Format::Kickstart.
+
+=item B<--pxe-template> I<path>
+
+Specify an alternate template path for Device::Format::PXE.
+
+=item B<--read-community> I<community>
+
+Specify an alternate SNMP read community.
+
+=item B<-S>, B<--scm>
+
+Use a SCM program to version generated files.
+Enabled by default (use C<--no-scm> to disable).
+
+=item B<--write-community> I<community>
+
+Specify an alternate SNMP write community.
+
+=back
+
+=head2 Help options
+
+=over
+
+=item B<-h>, B<--help>
+
+Print a short usage description, then exit.
+
+=item B<--man>
+
+Print the manual page of the program, then exit.
+
+=item B<-V>, B<--version>
+
+Print the program name and version, then exit.
+
+=back
+
+
+=head1 ARGUMENTS
+
+Arguments are expected to be:
+
+=over
+
+=item *
+
+the action name: diff, dump, info, list, push, write
+
+=item *
+
+optional action options
+
+=item *
+
+the RackObject name
+
+=back
+
+
+=head1 DESCRIPTION
+
+B<rack> is a command for fetching information from a RackTables
+database about selected RackObjects, generate the corresponding
+configuration files and talk with the corresponding devices to
+set them up accordingly.
+
+
+=head1 ACTIONS
+
+=over
+
+=item list
+
+Print the list of RackObjects of the given type (C<server>, C<pdu>,
+C<switch> or C<all>)
+
+B<Options>
+
+=over
+
+=item *
+
+C<as> - specify the format: C<ldif>, C<simple> (default)
+
+=back
+
+
+=item info
+
+Print information about the RackObject
+
+
+=item dump
+
+Print the structure of the RackObject
+
+B<Options>
+
+=over
+
+=item *
+
+C<as> - specify the format: C<json>, C<ldif>, C<perl>, C<yaml> (default)
+
+=back
+
+
+=item diff
+
+Print the differences between the actual and expected configuration
+of the device. Set exit status to 1 if there are differences, 0 otherwise.
+
+
+=item write
+
+Generate and write the configuration files corresponding to the RackObject
+
+
+=item push
+
+Push the configuration to the device corresponding to the RackObject.
+
+=back
+
+
+=head1 EXAMPLES
+
+List all the PDUs:
+
+ rack list pdu
+
+List all the servers in LDIF format:
+
+ rack list servers as ldif
+
+Print information about "front01.example.com" in default format (YAML):
+
+ rack info front01.example.com
+
+Dump information about "front01.example.com" in JSON format:
+
+ rack dump as json front01.example.com
+ rack dump front01.example.com as json
+
+
+=head1 CONFIGURATION
+
+The configuration file is expected to be in the C<.INI> format, with the
+following sections and parameters.
+
+All parameters can be overridden in a per-device file named
+F<rack.local.conf>, read from C<[general]/path> (read below).
+
+Parameters can contain placeholders that will be replaced with values
+corresponding to the given RackObjet:
+
+=over
+
+=item *
+
+C<%name%> - name of the RackObject, or C<"unknown"> if it is undefined
+
+=back
+
+
+=head2 Section [general]
+
+=over
+
+=item *
+
+C<default_scm> - specify the default SCM program
+
+=item *
+
+C<dns_servers> - specify the list of default DNS servers (space separated)
+
+=item *
+
+C<mail_server> - specify the default SMTP server
+
+=item *
+
+C<nagios_url> - specify the URL of the Nagios web application
+
+=item *
+
+C<ntp_servers> - specify the list of default NTP servers (space separated)
+
+=item *
+
+C<path> - I<(mandatory)> specify the path for device-specific
+configuration files, including F<rack.local.conf>; it is therefore
+strongly suggested to use the C<%name%> placeholder somewhere in
+the value
+
+=item *
+
+C<racktables_url> - specify the URL of the RackTables web application
+
+=item *
+
+C<timezone> - specify the default timezone
+
+=back
+
+
+=head2 Section [database]
+
+=over
+
+=item *
+
+C<dsn> - DBI data source name (e.g.: C<dbi:mysql:racktables>)
+
+=item *
+
+C<user> - database user name
+
+=item *
+
+C<password> - database password
+
+=back
+
+
+=head2 Section [device:pdu]
+
+=over
+
+=item *
+
+C<formats> - specify the formats associated with a PDU object as a
+space seperated list. Default is C<"Cacti Nagios">
+
+=back
+
+
+=head2 Section [device:pdu:apc_rackpdu]
+
+=over
+
+=item *
+
+C<ftp_login> - specify the FTP login; can be overridden with the
+C<--device-login> option
+
+=item *
+
+C<ftp_password> - specify the FTP password; can be overridden with the
+C<--device-password> option
+
+=item *
+
+C<mail_address> - specify the contact address for the device
+
+=item *
+
+C<write_community> - specify the SNMP write community (needed to restart
+the device); can be overridden with the C<--write-community> option
+
+=back
+
+
+=head2 Section [device:server]
+
+=over
+
+=item *
+
+C<formats> - specify the formats associated with a Server object as a
+space seperated list. Default is C<"DHCP PXE Kickstart Cacti Nagios Bacula">
+
+=back
+
+
+=head2 Section [device:server:hp_proliant]
+
+=over
+
+=item *
+
+C<admin_password> - I<(mandatory)> specify the iLO password for the
+custom account "admin"; can be overridden with the
+C<--device-password> option
+
+=item *
+
+C<ilo_password> - I<(mandatory)> specify the iLO password for the
+factory account "Administrator"
+
+=item *
+
+C<license_key> - specify the license key
+
+=item *
+
+C<serial_cli_speed> - specify the serial CLI speed; the value can
+be one of the following: 1 (9,600 bps), 2 (19,200 bps), 3 (38,400 bps),
+4 (57,600 bps), 5 (115,200 bps).
+
+=back
+
+
+=head2 Section [device:switch]
+
+=over
+
+=item *
+
+C<formats> - specify the formats associated with a Switch object as a
+space seperated list. Default is C<"Cacti Nagios">
+
+=back
+
+
+=head2 Section [device:switch:cisco_catalyst]
+
+=over
+
+=item *
+
+C<ios_password> - specify the IOS access password; can be overridden
+with the C<--device-password> option
+
+=item *
+
+C<enable_password> - specify the IOS admin password
+
+=back
+
+
+=head2 Section [format:bacula]
+
+=over
+
+=item *
+
+C<password> - specify Bacula password
+
+=item *
+
+C<path> - specify the location to store the generated files
+
+=back
+
+
+=head2 Section [format:cacti]
+
+=over
+
+=item *
+
+C<path> - specify the location of Cacti CLI programs
+
+=item *
+
+C<php> - specify the path of the php(1) CLI interpreter
+
+=item *
+
+C<sudo_as> - specify an optional user account to execute the Cacti
+programs under, using sudo(8)
+
+=back
+
+
+=head2 Section [format:dhcp]
+
+=over
+
+=item *
+
+C<path> - specify the location to store the generated files
+
+=item *
+
+C<template> - specify the path of the template.
+See L<RackMan::Template/"TEMPLATE PARAMETERS"> for the supported parameters.
+
+=back
+
+
+=head2 Section [format:kickstart]
+
+=over
+
+=item *
+
+C<path> - specify the location to store the generated files
+
+=item *
+
+C<template> - specify the path of the template.
+See L<RackMan::Template/"TEMPLATE PARAMETERS"> for the supported parameters.
+
+=back
+
+
+=head2 Section [format:ldap]
+
+=over
+
+=item *
+
+C<path> - specify the location to store the generated files
+
+=back
+
+
+=head2 Section [format:nagios]
+
+=over
+
+=item *
+
+C<path> - specify the location to store the generated files
+
+=back
+
+
+=head2 Section [format:pxe]
+
+=over
+
+=item *
+
+C<path> - specify the location to store the generated files
+
+=item *
+
+C<template> - specify the path of the template.
+See L<RackMan::Template/"TEMPLATE PARAMETERS"> for the supported parameters.
+
+=back
+
+
+=head1 AUTHOR
+
+Sebastien Aperghis-Tramoni (sebastien@aperghis.net)
+
+=cut
+
176 cgi/rackapi
@@ -0,0 +1,176 @@
+#!/usr/bin/perl
+use strict;
+use CGI;
+use RackMan;
+use RackMan::Config;
+
+
+use constant {
+ CONFIG_PATH => "/usr/local/etc/rack.conf",
+};
+
+my %handler = (
+ # action handlers
+ list => \&action_list,
+
+ # serializers
+ to_perl => \&to_dumper,
+ to_json => \&to_json,
+ to_yaml => \&to_yaml,
+);
+
+my %type = (
+ json => "application/json",
+ perl => "text/plain",
+ yaml => "application/yaml",
+);
+
+
+# fetch the requested action
+my $cgi = CGI->new;
+my $action = $cgi->param("action");
+error(404, "No such action '$action'") if not exists $handler{$action};
+
+# load the RackMan objects
+my $config = RackMan::Config->new(-file => CONFIG_PATH);
+my $rackman = RackMan->new({ config => $config });
+my $racktables = $rackman->racktables;
+
+# execute the corresponding handler
+my $result = $handler{$action}->();
+
+# serialize the result
+my $format = $cgi->param("to") || "json";
+$format = "json" unless $handler{"to_$format"};
+my $output = $handler{"to_$format"}->($result);
+
+print $cgi->header($type{$format}), $output;
+
+
+
+#
+# error()
+# -----
+sub error {
+ my ($code, $text) = @_;
+ print $cgi->header(-status => $code, -type => "text/plain"), "$text\n";
+ exit
+}
+
+
+#
+# action_list()
+# -----------
+sub action_list {
+ # fetch the list of entities with the requested tags
+ my $list = $racktables->resultset("TagStorage")->search(
+ { entity_realm => "object", tag => { -in => [ $cgi->param("tag") ] } },
+ { join => "tag" },
+ );
+
+ my @ids = map $_->entity_id, $list->all;
+
+ # find the name of the corresponding IDs
+ my @objects = map { eval {
+ RackMan::Device->new({ racktables => $racktables, id => $_ })
+ } } @ids;
+
+ # if requested to, filter out objects not of the given "Use" attribute
+ my ($use) = lc $cgi->param("use");
+ if ($use) {
+ @objects = grep { lc $_->attributes->{Use} eq $use } @objects
+ }
+
+ # if requested to, filter out objects not of the given type
+ my ($type) = lc $cgi->param("type");
+ if ($type) {
+ @objects = grep { lc $_->object_type eq $type } @objects
+ }
+
+ # return the result as the list of the remaining names
+ my @names = map { $_->object_name || "[anonymous $type]" } @objects;
+
+ return \@names
+}
+
+
+#
+# to_dumper()
+# ---------
+sub to_dumper {
+ require Data::Dumper;
+ my $dumper = Data::Dumper->new([$_[0]]);
+ $dumper->Indent(1)->Sortkeys(1)->Terse(1);
+ return $dumper->Dump
+}
+
+
+#
+# to_json()
+# -------
+sub to_json {
+ require JSON;
+ return JSON::to_json($_[0])
+}
+
+
+#
+# to_yaml()
+# -------
+sub to_yaml {
+ require YAML;
+ return YAML::Dump($_[0])
+}
+
+
+__END__
+
+=head1 NAME
+
+rackapi - Web service access to RackMan
+
+=head1 DESCRIPTION
+
+This program is a small CGI which provides a limited web service access
+to the RackTables database, through the RackMan modules.
+
+
+=head1 ACTIONS
+
+=head2 list
+
+Search and return the list of devices corresponding to the criterion
+defined by the given parameters.
+
+
+=head1 PARAMETERS
+
+=head2 tag
+
+Specify one or more tags to select the devices.
+Currently, at least one tag must be specified.
+
+
+=head2 to
+
+Specify the output format: C<perl>, C<json>, C<yaml>.
+Default is C<json>.
+
+
+=head2 type
+
+Specify the type of device to select: C<pdu>, C<server>, C<switch>.
+
+
+=head2 use
+
+Specify the C<Use> attribute of the device: C<dev>, C<preprod>, C<prod>,
+C<test>, C<dead>.
+
+
+=head1 AUTHOR
+
+Sebastien Aperghis-Tramoni
+
+=cut
+
49 cgi/rtlink
@@ -0,0 +1,49 @@
+#!/usr/bin/perl
+use strict;
+use CGI;
+use Config::IniFiles;
+
+use constant {
+ CONFIG_PATH => "/usr/local/etc/rack.conf",
+};
+
+
+# read RackMan configuration to fetch RackTables URL
+my $config = Config::IniFiles->new(-file => CONFIG_PATH);
+my $rt_url = $config->val(general => "racktables_url");
+
+# fetch the shortened params
+my $cgi = CGI->new;
+my $object_id = $cgi->param("oi");
+my $port_id = $cgi->param("pi");
+
+# construct the full URL
+my $url = "$rt_url/index.php?page=object&object_id=$object_id";
+$url .= "&hl_port_id=$port_id" if defined $port_id and length $port_id;
+
+print $cgi->redirect($url);
+
+__END__
+
+=head1 NAME
+
+rtlink - Trivial link redirecter to RackTables
+
+=head1 DESCRIPTION
+
+The sole purpose of this very small CGI program is to redirect to
+RackTables, allowing the use of shorter parameter names in order
+to work around the tight limits in some devices.
+
+ Translation table
+ -----------------
+ oi object_id
+ pi hl_port_id
+
+
+=head1 AUTHOR
+
+Sebastien Aperghis-Tramoni
+
+=cut
+
8 etc/host.dhcp.conf.tmpl
@@ -0,0 +1,8 @@
+host [% host_fqdn %] {
+ option routers [% gateway %];
+ hardware ethernet [% if0_mac %];
+ fixed-address [% if0_ip %];
+ next-server 192.168.0.13;
+ option root-path "/home/relmgr/base-release-8.2-amd64/dvd1";
+ filename "pxeboot.amd64";
+}
31 etc/host.kickstart.cfg.tmpl
@@ -0,0 +1,31 @@
+install
+url --url http://centos.crazyfrogs.org/5.4/os/x86_64
+lang en_US.UTF-8
+keyboard fr
+network --device eth0 --bootproto static --ip [% if0_ip %] --netmask [% netmask %] --gateway [% gateway %] --nameserver 192.168.0.17 --hostname [% fqdn %]
+rootpw --iscrypted secret
+firewall --enabled --port=22:tcp
+authconfig --enableshadow --enablemd5
+selinux --enforcing
+timezone --utc Europe/Paris
+bootloader --location=mbr --driveorder=sda
+# The following is the partition information you requested
+# Note that any partitions you deleted are not expressed
+# here so unless you clear all partitions first, this is
+# not guaranteed to work
+#clearpart --linux --drives=sda
+#part /boot --fstype ext3 --size=100 --ondisk=sda
+#part pv.2 --size=0 --grow --ondisk=sda
+#volgroup VolGroup00 --pesize=32768 pv.2
+#logvol swap --fstype swap --name=LogVol01 --vgname=VolGroup00 --size=1008 --grow --maxsize=2016
+#logvol / --fstype ext3 --name=LogVol00 --vgname=VolGroup00 --size=1024 --grow
+
+%packages
+@base
+@core
+@editors
+@server-cfg
+@text-internet
+keyutils
+trousers
+fipscheck
5 etc/host.pxe.conf.tmpl
@@ -0,0 +1,5 @@
+DEFAULT install
+LABEL install
+ KERNEL images/centos/6.2/vmlinuz
+ MENU LABEL ^CentOS 6.2
+ APPEND initrd=images/centos/6.2/initrd.img ramdisk_size=10000 text ks=http://install.infra/kickstart/[% fqdn %].ks
86 etc/rack.conf
@@ -0,0 +1,86 @@
+[general]
+path = /etc/rack/generated/device/%name%
+default_scm = hg
+dns_servers = 192.168.0.20 192.168.0.21
+ntp_servers = 192.168.0.20 192.168.0.21
+mail_server = mail.company.com
+racktables_url = https://localhost/racktables/
+short_url = https://localhost/cgi-bin/rtlink
+nagios_url = https://localhost/nagios/
+timezone = Europe/Paris
+
+[database]
+dsn = dbi:mysql:racktables
+user = rackuser
+password = s3kret
+
+[cfengine-tags]
+path = /var/cfengine/tags/
+#type = server
+#tags = ...
+#attrs = ...
+#filter = tag:cfengine, Use=prod, Use=preprod, Use=dev
+
+[device:pdu]
+formats = Cacti Nagios
+
+[device:pdu:apc_rackpdu]
+mail_address = apc@company.com
+ftp_login = apc
+ftp_password = apc
+write_community = private
+
+[device:server]
+formats = DHCP PXE Kickstart LDAP Cacti Nagios Bacula UniqueID
+
+[device:server:hp_proliant]
+#ilo_password = ... # must be defined in $path/rack.local.conf
+#admin_password = ... # must be defined in $path/rack.local.conf
+#license_key = ... # must be defined in $path/rack.local.conf
+
+[device:switch]
+formats = Cacti Nagios
+
+[device:switch:cisco_catalyst]
+#ios_password = ... # must be defined in $path/rack.local.conf
+#enable_password = ... # must be defined in $path/rack.local.conf
+
+[format:bacula]
+path = /etc/bacula/hosts
+password = sekr3t
+catalog = MyCatalog
+#file_retention = 100 days
+#job_retention = 3 months
+#max_jobs = 2
+#write_bootstrap = /var/lib/bacula/%c.bsr
+add_file = /etc
+add_file = /usr/local/etc
+add_file = /root
+
+[format:cacti]
+path = /var/lib/cacti/cli
+#php = /usr/bin/php
+#sudo_as = cacti
+
+[format:dhcp]
+path = /etc/dhcp/hosts
+template = /etc/dhcp/host.dhcp.conf.tmpl
+
+[format:pxe]
+path = /etc/pxe/hosts
+template = /etc/pxe/host.dhcp.conf.tmpl
+
+[format:kickstart]
+path = /etc/kickstart/hosts
+template = /etc/kickstart/host.kickstart.cfg.tmpl
+
+[format:ldap]
+path = /etc/ldap/hosts
+#base_dn = ou=servers,ou=systems,dc=company,dc=com
+
+[format:nagios]
+path = /etc/nagios/hosts
+
+[format:uniqueid]
+path = /var/cfengine/uniqueid
+
310 lib/RackMan.pm
@@ -0,0 +1,310 @@
+package RackMan;
+
+use Moose;
+use Term::ANSIColor qw< :constants >;
+use namespace::autoclean;
+
+
+our $VERSION = "1.10";
+our $STATUS = 0;
+$::COMMAND ||= __PACKAGE__;
+
+
+has config => (
+ is => "ro",
+ isa => "RackMan::Config",
+ default => sub {
+ require RackMan::Config;
+ RackMan::Config->new(-file => "/etc/rack.conf")
+ },
+);
+
+has options => (
+ is => "ro",
+ isa => "HashRef",
+);
+
+has racktables => (
+ is => "ro",
+ isa => "RackTables::Schema",
+ lazy => 1,
+ builder => "connection",
+);
+
+
+#
+# connection()
+# ----------
+sub connection {
+ my $self = shift;
+
+ # check that the connection parameters are correctly set
+ my @params = $self->config->val(database => "dsn")
+ or RackMan->error("[database]/dsn is missing from the config file");
+
+ push @params, map {
+ my $value = $self->config->val(database => $_);
+ defined $value ? $value
+ : RackMan->error("[database]/$_ is missing from the config file")
+ } qw< user password >;
+
+ require RackTables::Schema;
+ return RackTables::Schema->connect(@params)
+}
+
+
+#
+# device_by_id()
+# ------------
+sub device_by_id {
+ my ($self, $id) = @_;
+
+ require RackMan::Device;
+ return RackMan::Device->new({
+ id => $id, rackman => $self,
+ racktables => $self->racktables,
+ })
+}
+
+
+#
+# device_by_name()
+# --------------
+sub device_by_name {
+ my ($self, $name) = @_;
+
+ require RackMan::Device;
+ return RackMan::Device->new({
+ name => $name, rackman => $self,
+ racktables => $self->racktables,
+ })
+}
+
+
+#
+# device()
+# ------
+*device = \&device_by_name;
+
+
+#
+# process()
+# -------
+sub process {
+ my ($self, $action, @args) = @_;
+
+ my %opts;
+ my @rest;
+
+ # parse task options
+ while (my $arg = shift @args) {
+ if ($arg eq "as") {
+ $opts{$arg} = shift @args;
+ }
+ else {
+ push @rest, $arg;
+ }
+ }
+
+ my $name = pop @rest;
+ my $method = "task_$action";
+
+ require RackMan::Tasks;
+
+ RackMan->error("unknown action '$action'")
+ unless RackMan::Tasks->meta->has_method($method);
+
+ if ($action eq "list") {
+ RackMan::Tasks->$method({ rackman => $self, stdout => 1,
+ type => $name, %opts });
+ }
+ else {
+ my $object = $self->device($name);
+ $self->config->set_current_rackobject($object);
+ RackMan::Tasks->meta->apply($object);
+ $object->$method({ rackman => $self, stdout => 1, %opts });
+ }
+}
+
+
+#
+# get_scm()
+# -------
+sub get_scm {
+ my ($self, $args) = @_;
+
+ my $type = "none";
+ my $caller = caller();
+
+ if ($self->options->{scm}) {
+ # global default SCM tool
+ my $default = $self->config->val(general => "default_scm", "none");
+
+ # check if the caller has its own configuration section
+ if ($caller->can("CONFIG_SECTION")) {
+ my $section = $caller->CONFIG_SECTION;
+ $type = $self->config->val($section, "scm", $default);
+ }
+ }
+
+ # clean leading and trailing spaces
+ $type =~ s/^ +| +$//g;
+
+ require RackMan::SCM;
+ return RackMan::SCM->new({ type => $type, %$args,
+ verbose => $self->options->{verbose}, prefix => " x " })