diff --git a/CHANGELOG.md b/CHANGELOG.md index 3893389..d207572 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # CHANGELOG +## 0.2.0 + +* major rework + - Added SNMPv3 support + - Added ACL management for v1 and v2c + - disk monitoring + - load average monitoring + +## 0.1.5 + +* added **dont_log_tcp_wrappers_connects** flag +* added support for: + - Ubuntu 16.04 + - Ubuntu 18.04 + - RHEL 8 + ## 0.1.4 * improved deletion support diff --git a/Gemfile b/Gemfile index be325d9..93c71a5 100644 --- a/Gemfile +++ b/Gemfile @@ -7,11 +7,10 @@ gem 'puppet-lint', '>= 0.3.2' gem 'facter', '>= 1.7.0' group :system_tests do - gem 'beaker', :require => false - gem 'beaker-rspec', :require => false - gem 'beaker_spec_helper', :require => false - gem 'beaker-puppet_install_helper', :require => false - gem 'serverspec', :require => false + gem 'beaker', '~>3.13', :require => false + gem 'beaker-rspec', '> 5', :require => false + gem 'beaker_spec_helper', :require => false + gem 'serverspec', :require => false gem 'rspec', '< 3.2', :require => false if RUBY_VERSION =~ /^1\.8/ gem 'rspec-puppet', :require => false gem 'metadata-json-lint', :require => false diff --git a/README.md b/README.md index 882e796..872fc0f 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,11 @@ SNMP agent management ## Module Description -This module is intended to disable snmpd +snmpd management: SNMP v1 and v2c ACL management and SNMPv3 user creation. + +Support for: + - disk monitoring + - load average monitoring ## Setup @@ -32,8 +36,6 @@ This module is intended to disable snmpd ### Setup Requirements -This module requires pluginsync enabled - ### Beginning with snmpd Install snmpd: @@ -44,24 +46,56 @@ class { 'snmpd': } ## Usage -disable snmpd: +### disable snmpd -``` +```puppet class { 'snmpd': service_ensure => 'stopped', service_enable => false, } ``` -purge snmpd: +### purge snmpd -``` +```puppet class { 'snmpd': package_ensure => 'purged', manage_service => false, } ``` +### SNMP v1 and v2c ACL + +```puppet +class { 'snmpd': + add_default_acls => false, +} + +snmpd::acl { 'demo': + community => '1234567890', +} +``` + +### SNMPv3 user creation + +```puppet +class { 'snmpd': + add_default_acls => false, +} + +snmpd::v3user { 'v3testuser': + authpass => '1234567890', + encpass => '1234567890', +} +``` + +This setup can be tested vis snmpwalk: + +``` +# snmpwalk -v3 -l authPriv -u v3testuser -a SHA -A "1234567890" -x AES -X "1234567890" 127.0.0.1 system +``` + + ## Reference ### snmpd @@ -74,13 +108,19 @@ class { 'snmpd': * service_ensure = 'running', * service_enable = true, +### snmp::acl + +### snmpd::disk + +Relies on fact **::eyp_snmpd_mountpoints** for autoconfiguring disk monitoring if **snmpd::add_disk_monit** is set to true (default is true) + +### snmpd::v3user + +It is asumed that **/usr/bin/net-snmp-create-v3-user** is a bash script that this module modifies for it's own purposes. The modified script is stored on **/usr/local/bin/puppet_net-snmp-create-v3-user** + ## Limitations -Tested on: -* CentOS 5 -* CentOS 5 -* CentOS 7 -* Ubuntu 14.04 +Mostly tested on CentOS 7 and Ubuntu 16.04 ## Development diff --git a/examples/demo.pp b/examples/demo.pp new file mode 100644 index 0000000..1bf6819 --- /dev/null +++ b/examples/demo.pp @@ -0,0 +1,16 @@ +class { 'snmpd': + add_default_acls => false, +} + +class { 'snmpd::loadavg': } + +snmpd::v3user { 'v3testuser': + authpass => '1234567890', + encpass => '1234567890', +} + +# snmpwalk -v3 -l authPriv -u v3testuser -a SHA -A "1234567890" -x AES -X "1234567890" 127.0.0.1 system + +snmpd::acl { 'demo': + community => '1234567890', +} diff --git a/lib/facter/eyp_snmpd_mountpoints.rb b/lib/facter/eyp_snmpd_mountpoints.rb new file mode 100644 index 0000000..0ece571 --- /dev/null +++ b/lib/facter/eyp_snmpd_mountpoints.rb @@ -0,0 +1,9 @@ +mounts = Facter::Util::Resolution.exec('bash -c \'df -TP | grep -v "\btmpfs\b\|\bdevtmpfs\b\|^Filesystem" | awk "{ print \$NF }"\'').to_s + +unless mounts.nil? or mounts.empty? + Facter.add('eyp_snmpd_mountpoints') do + setcode do + mounts.split("\n") + end + end +end diff --git a/manifests/acl.pp b/manifests/acl.pp new file mode 100644 index 0000000..58fb75c --- /dev/null +++ b/manifests/acl.pp @@ -0,0 +1,23 @@ +define snmpd::acl ( + $community, + $description = undef, + $order = '42', + $security_name = $name, + $group_name = $name, + $view_name = "view_${name}", + $allowed_hosts = [ '127.0.0.1/32' ], + $security_model = [ 'v1', 'v2c' ], + $included_subtrees = [ '.1' ], + $read = true, + $write = false, + $context = 'prefix', + ) { + include ::snmpd + + concat::fragment { "snmpd ACL ${security_name} ${community} ${group_name}": + target => '/etc/snmp/snmpd.conf', + order => "10-${order}", + content => template("${module_name}/acl.erb"), + } + +} diff --git a/manifests/config.pp b/manifests/config.pp index 8e9aa10..4cc97dd 100644 --- a/manifests/config.pp +++ b/manifests/config.pp @@ -2,12 +2,24 @@ if($snmpd::manage_package and $snmpd::package_ensure!='absent') { - file { '/etc/snmp/snmpd.conf': + concat { '/etc/snmp/snmpd.conf': ensure => 'present', owner => 'root', group => 'root', mode => '0600', + } + + concat::fragment { 'base snmpd v2': + target => '/etc/snmp/snmpd.conf', + order => '00', content => template("${module_name}/snmpdconf.erb"), } + + if($snmpd::add_disk_monit) + { + snmpd::disk { 'mountpoints': + disks => $::eyp_snmpd_mountpoints, + } + } } } diff --git a/manifests/disk.pp b/manifests/disk.pp new file mode 100644 index 0000000..4f9f3d8 --- /dev/null +++ b/manifests/disk.pp @@ -0,0 +1,20 @@ +define snmpd::disk( + $disks = [], + $disk = undef, + $description = $name, + $order = '42', + ) { + + # TODO: + # disk PATH [MIN=100000] + # + # PATH: mount path to the disk in question. + # MIN: Disks with space below this value will have the Mib's errorFlag set. + # Default value = 100000. + + concat::fragment { "eyp-snmpd - disk ${disk} ${disks}": + target => '/etc/snmp/snmpd.conf', + order => "01-${order}", + content => template("${module_name}/disk.erb"), + } +} diff --git a/manifests/init.pp b/manifests/init.pp index ae347eb..0f5119a 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -10,20 +10,21 @@ # @param syslocation System contact information: location (default: somewhere) # @param syscontact System contact information: contact (default: someone) class snmpd ( - $manage_package = true, - $package_ensure = 'installed', - $manage_service = true, - $manage_docker_service = true, - $service_ensure = 'running', - $service_enable = true, - $community = 'public', - $subtree = [ '.1.3.6.1.2.1.1', '.1.3.6.1.2.1.25.1.1' ], - $allowed_hosts = [ 'default' ], - $syslocation = 'somewhere', - $syscontact = 'someone', + $manage_package = true, + $package_ensure = 'installed', + $manage_service = true, + $manage_docker_service = true, + $service_ensure = 'running', + $service_enable = true, + $community = 'public', + $subtree = [ '.1.3.6.1.2.1.1', '.1.3.6.1.2.1.25.1.1' ], + $allowed_hosts = [ 'default' ], + $syslocation = 'somewhere', + $syscontact = 'someone', + $dont_log_tcp_wrappers_connects = true, + $add_disk_monit = true, + $add_default_acls = true, ) inherits snmpd::params{ - validate_array($subtree) - validate_array($allowed_hosts) class { '::snmpd::install': } -> class { '::snmpd::config': } ~> diff --git a/manifests/install.pp b/manifests/install.pp index 565aaf3..a056cfb 100644 --- a/manifests/install.pp +++ b/manifests/install.pp @@ -5,6 +5,20 @@ package { $snmpd::params::package_name: ensure => $snmpd::package_ensure, } + + exec { 'fix snmpdv3 create user': + command => "sed -e 's/^if.*snmpd[^;]*/${snmpd::params::servicectl_stop}\\nif false/' -e 's@/etc/snmp/snmpd.conf@/dev/null@' ${snmpd::params::create_user_snmpd_v3} > /usr/local/bin/puppet_net-snmp-create-v3-user", + unless => 'grep "if false" /usr/local/bin/puppet_net-snmp-create-v3-user', + path => '/usr/sbin:/usr/bin:/sbin:/bin', + require => Package[$snmpd::params::package_name], + } + + file { '/usr/local/bin/puppet_net-snmp-create-v3-user': + owner => 'root', + group => 'root', + mode => '0755', + require => Exec['fix snmpdv3 create user'], + } } } diff --git a/manifests/loadavg.pp b/manifests/loadavg.pp new file mode 100644 index 0000000..25f7dc9 --- /dev/null +++ b/manifests/loadavg.pp @@ -0,0 +1,14 @@ +class snmpd::loadavg( + $load1 = max(2, ceiling(sprintf('%d', ($::processorcount)*2.5))), + $load5 = max(4, ceiling(sprintf('%d', ($::processorcount)*2.6))), + $load15 = max(6, ceiling(sprintf('%d', ($::processorcount)*2.8))), + ) inherits snmpd::params{ + + include ::snmpd + + concat::fragment { 'eyp-snmpd - load': + target => '/etc/snmp/snmpd.conf', + order => '02', + content => template("${module_name}/load.erb"), + } +} diff --git a/manifests/params.pp b/manifests/params.pp index 6b37fdc..0ff8517 100644 --- a/manifests/params.pp +++ b/manifests/params.pp @@ -1,24 +1,32 @@ class snmpd::params { $service_name = 'snmpd' + $create_user_snmpd_v3 = '/usr/bin/net-snmp-create-v3-user' case $::osfamily { 'redhat': { $package_name = [ 'net-snmp', 'net-snmp-utils' ] + $net_snmpd_persistent_datafile = '/var/lib/net-snmp/snmpd.conf' case $::operatingsystemrelease { - /^[5-7].*$/: + /^[5-6].*$/: { + $servicectl_stop = "service ${service_name} stop" + } + /^[7-8].*$/: + { + $servicectl_stop = "systemctl stop ${service_name}" } default: { fail("Unsupported RHEL/CentOS version! - ${::operatingsystemrelease}") } } } 'Debian': { - $package_name = 'snmpd' + $package_name = [ 'snmpd', 'libsnmp-dev' ] + $net_snmpd_persistent_datafile = '/var/lib/snmp/snmpd.conf' case $::operatingsystem { @@ -26,8 +34,13 @@ { case $::operatingsystemrelease { - /^14.*$/: + /^1[4].*$/: + { + $servicectl_stop = "service ${service_name} stop" + } + /^1[68].*$/: { + $servicectl_stop = "systemctl stop ${service_name}" } default: { fail("Unsupported Ubuntu version! - ${::operatingsystemrelease}") } } diff --git a/manifests/service.pp b/manifests/service.pp index f92e2f7..2e171db 100644 --- a/manifests/service.pp +++ b/manifests/service.pp @@ -1,10 +1,4 @@ class snmpd::service inherits snmpd { - # - validate_bool($snmpd::manage_docker_service) - validate_bool($snmpd::manage_service) - validate_bool($snmpd::service_enable) - - validate_re($snmpd::service_ensure, [ '^running$', '^stopped$' ], "Not a valid daemon status: ${snmpd::service_ensure}") $is_docker_container_var=getvar('::eyp_docker_iscontainer') $is_docker_container=str2bool($is_docker_container_var) diff --git a/manifests/v3user.pp b/manifests/v3user.pp new file mode 100644 index 0000000..ca488aa --- /dev/null +++ b/manifests/v3user.pp @@ -0,0 +1,41 @@ +define snmpd::v3user( + $authpass, + $encpass, + $username = $name, + $ro = false, + $enc_algorithm = 'AES', + $auth_algorithm = 'SHA', + ) { + + include ::snmpd + # service snmpd stop + # net-snmp-create-v3-user -ro -A -a SHA -X -x AES + # service snmpd start + + if($ro) + { + $ro_flag = '-ro' + $kind_v3_user = 'rouser' + } + else + { + $ro_flag = '' + $kind_v3_user = 'rwuser' + } + + exec { "create snmpdv3 user ${username}": + command => "/usr/local/bin/puppet_net-snmp-create-v3-user ${ro_flag} -A ${authpass} -a ${auth_algorithm} -X ${authpass} -x ${enc_algorithm} ${username}", + unless => "grep '${username}' ${snmpd::params::net_snmpd_persistent_datafile}", + path => '/usr/sbin:/usr/bin:/sbin:/bin', + require => Class['::snmpd::install'], + notify => Class['::snmpd::service'], + } + + # rwuser demo + concat::fragment { "snmpv3 create user ${username}": + target => '/etc/snmp/snmpd.conf', + order => '99', + content => template("${module_name}/v3user.erb"), + } + +} diff --git a/metadata.json b/metadata.json index 408c9ac..82d50ac 100644 --- a/metadata.json +++ b/metadata.json @@ -1,6 +1,6 @@ { "name": "eyp-snmpd", - "version": "0.1.4", + "version": "0.2.0", "author": "eyp", "summary": "SNMP agent management", "license": "Apache-2.0", @@ -9,38 +9,39 @@ "issues_url": "https://github.com/NTTCom-MS/eyp-snmpd/issues", "dependencies": [ {"name":"puppetlabs/stdlib","version_requirement":">= 1.0.0"}, + {"name":"puppetlabs/concat","version_requirement":">= 1.2.3 < 9.9.9"}, {"name":"eyp/eyplib","version_requirement":">= 0.1.0 < 0.2.0"} ], "operatingsystem_support": [ { "operatingsystem": "RedHat", - "operatingsystemrelease": [ "5", "6", "7" ] + "operatingsystemrelease": [ "5", "6", "7", "8" ] }, { "operatingsystem": "CentOS", - "operatingsystemrelease": [ "5", "6", "7" ] + "operatingsystemrelease": [ "5", "6", "7", "8" ] }, { "operatingsystem": "Scientific", - "operatingsystemrelease": [ "5", "6", "7" ] + "operatingsystemrelease": [ "5", "6", "7", "8" ] }, { "operatingsystem": "OEL", - "operatingsystemrelease": [ "5", "6", "7" ] + "operatingsystemrelease": [ "5", "6", "7", "8" ] }, { "operatingsystem": "OracleLinux", - "operatingsystemrelease": [ "5", "6", "7" ] + "operatingsystemrelease": [ "5", "6", "7", "8" ] }, { "operatingsystem": "Ubuntu", - "operatingsystemrelease": [ "14.04" ] + "operatingsystemrelease": [ "14.04", "16.04", "18.04" ] } ], "requirements": [ { "name": "puppet", - "version_requirement": ">= 3.0.0" + "version_requirement": ">= 3.8.0" } ] } diff --git a/spec/spec_helper_acceptance.rb b/spec/spec_helper_acceptance.rb index ecea260..dd05d93 100644 --- a/spec/spec_helper_acceptance.rb +++ b/spec/spec_helper_acceptance.rb @@ -1,32 +1,10 @@ require 'beaker-rspec' -require 'beaker_spec_helper' -include BeakerSpecHelper - -hosts.each do |host| - - if host['platform'] =~ /^ubuntu-(15.04|15.10)-/ - on host, "wget -O /tmp/puppet.deb http://apt.puppetlabs.com/puppetlabs-release-pc1-trusty.deb" - on host, "dpkg -i --force-all /tmp/puppet.deb" - on host, "apt-get update" - host.install_package('puppet-agent') - else - install_puppet_agent_on host, {} - end - - # Install git so that we can install modules from github - if host['platform'] =~ /^el-5-/ - # git is only available on EPEL for el-5 - install_package host, 'epel-release' - end - install_package host, 'git' - - on host, "puppet cert generate $(facter fqdn)" -end +install_puppet_agent_on hosts, {} RSpec.configure do |c| - # Project root - proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) + module_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) + module_name = module_root.split('-').last # Readable test descriptions c.formatter = :documentation @@ -34,10 +12,10 @@ # Configure all nodes in nodeset c.before :suite do # Install module and dependencies - puppet_module_install(:source => proj_root, :module_name => 'snmpd') + puppet_module_install(:source => module_root, :module_name => module_name) hosts.each do |host| - # dependencies on host, puppet('module', 'install', 'puppetlabs-stdlib'), { :acceptable_exit_codes => [0,1] } + on host, puppet('module', 'install', 'puppetlabs-concat'), { :acceptable_exit_codes => [0,1] } on host, puppet('module', 'install', 'eyp-eyplib'), { :acceptable_exit_codes => [0,1] } end end diff --git a/templates/acl.erb b/templates/acl.erb new file mode 100644 index 0000000..b7ea08e --- /dev/null +++ b/templates/acl.erb @@ -0,0 +1,35 @@ +<% if defined?(@description) -%> +# +# <%= @description %> +# +<% end %> +# First, map the community name into a "security name" + +# sec.name source community +<% @allowed_hosts.each do | val | -%> +com2sec <%= @security_name %> <%= val %> <%= @community %> +<% end -%> + +#### +# Second, map the security name into a group name: + +# groupName securityModel securityName +<% @security_model.each do |val| -%> +group <%= @group_name %> <%= val %> <%= @security_name %> +<% end -%> + +#### +# Third, create a view for us to let the group have rights to: + +# name incl/excl subtree mask(optional) +<% @included_subtrees.each do | val | -%> +view <%= @view_name %> included <%= val %> +<% end -%> + +#### +# Finally, grant the group read-only access to the systemview view. + +# group context sec.model sec.level prefix read write notif +access <%= @group_name %> "" any noauth <%= @context %> <% if @read %><%= @view_name %><% else %>none<% end %> <% if @write %><%= @view_name %><% else %>none<% end %> none + +<% %> diff --git a/templates/disk.erb b/templates/disk.erb new file mode 100644 index 0000000..342e3c2 --- /dev/null +++ b/templates/disk.erb @@ -0,0 +1,10 @@ +<% if defined?(@description) -%> + +# <%= @description %> +<% end -%> +<% if defined?(@disk) -%> +disk <%= @disk %> +<% end -%> +<% @disks.each do |val| -%> +disk <%= val %> +<% end %> diff --git a/templates/load.erb b/templates/load.erb new file mode 100644 index 0000000..93881c9 --- /dev/null +++ b/templates/load.erb @@ -0,0 +1,35 @@ +############################################################################### +# load average checks +# + +# load [1MAX=12.0] [5MAX=12.0] [15MAX=12.0] +# +# 1MAX: If the 1 minute load average is above this limit at query +# time, the errorFlag will be set. +# 5MAX: Similar, but for 5 min average. +# 15MAX: Similar, but for 15 min average. + +# Check for loads: +load <%= @load1 %> <%= @load5 %> <%= @load15 %> + +# % snmpwalk -v 1 localhost -c <%= @community %> .1.3.6.1.4.1.2021.10 +# enterprises.ucdavis.loadTable.laEntry.loadaveIndex.1 = 1 +# enterprises.ucdavis.loadTable.laEntry.loadaveIndex.2 = 2 +# enterprises.ucdavis.loadTable.laEntry.loadaveIndex.3 = 3 +# enterprises.ucdavis.loadTable.laEntry.loadaveNames.1 = "Load-1" +# enterprises.ucdavis.loadTable.laEntry.loadaveNames.2 = "Load-5" +# enterprises.ucdavis.loadTable.laEntry.loadaveNames.3 = "Load-15" +# enterprises.ucdavis.loadTable.laEntry.loadaveLoad.1 = "0.49" Hex: 30 2E 34 39 +# enterprises.ucdavis.loadTable.laEntry.loadaveLoad.2 = "0.31" Hex: 30 2E 33 31 +# enterprises.ucdavis.loadTable.laEntry.loadaveLoad.3 = "0.26" Hex: 30 2E 32 36 +# enterprises.ucdavis.loadTable.laEntry.loadaveConfig.1 = "12.00" +# enterprises.ucdavis.loadTable.laEntry.loadaveConfig.2 = "14.00" +# enterprises.ucdavis.loadTable.laEntry.loadaveConfig.3 = "14.00" +# enterprises.ucdavis.loadTable.laEntry.loadaveErrorFlag.1 = 0 +# enterprises.ucdavis.loadTable.laEntry.loadaveErrorFlag.2 = 0 +# enterprises.ucdavis.loadTable.laEntry.loadaveErrorFlag.3 = 0 +# enterprises.ucdavis.loadTable.laEntry.loadaveErrMessage.1 = "" +# enterprises.ucdavis.loadTable.laEntry.loadaveErrMessage.2 = "" +# enterprises.ucdavis.loadTable.laEntry.loadaveErrMessage.3 = "" + +# ----------------------------------------------------------------------------- diff --git a/templates/snmpdconf.erb b/templates/snmpdconf.erb index af1f4be..2fa7742 100644 --- a/templates/snmpdconf.erb +++ b/templates/snmpdconf.erb @@ -7,6 +7,7 @@ # ############################################################################### +<% if @add_default_acls %> ############################################################################### # Access Control ############################################################################### @@ -41,7 +42,7 @@ view systemview included <%= val %> # group context sec.model sec.level prefix read write notif access notConfigGroup "" any noauth exact systemview none none - +<% end %> ############################################################################### # System contact information # @@ -71,7 +72,7 @@ syscontact <%= @syscontact %> # If the following option is commented out, snmpd will print each incoming # connection, which can be useful for debugging. -dontLogTCPWrappersConnects yes +dontLogTCPWrappersConnects <%= scope.function_bool2yesno([@dont_log_tcp_wrappers_connects]) %> # ----------------------------------------------------------------------------- @@ -227,43 +228,6 @@ dontLogTCPWrappersConnects yes # ----------------------------------------------------------------------------- -############################################################################### -# load average checks -# - -# load [1MAX=12.0] [5MAX=12.0] [15MAX=12.0] -# -# 1MAX: If the 1 minute load average is above this limit at query -# time, the errorFlag will be set. -# 5MAX: Similar, but for 5 min average. -# 15MAX: Similar, but for 15 min average. - -# Check for loads: -#load 12 14 14 - -# % snmpwalk -v 1 localhost -c <%= @community %> .1.3.6.1.4.1.2021.10 -# enterprises.ucdavis.loadTable.laEntry.loadaveIndex.1 = 1 -# enterprises.ucdavis.loadTable.laEntry.loadaveIndex.2 = 2 -# enterprises.ucdavis.loadTable.laEntry.loadaveIndex.3 = 3 -# enterprises.ucdavis.loadTable.laEntry.loadaveNames.1 = "Load-1" -# enterprises.ucdavis.loadTable.laEntry.loadaveNames.2 = "Load-5" -# enterprises.ucdavis.loadTable.laEntry.loadaveNames.3 = "Load-15" -# enterprises.ucdavis.loadTable.laEntry.loadaveLoad.1 = "0.49" Hex: 30 2E 34 39 -# enterprises.ucdavis.loadTable.laEntry.loadaveLoad.2 = "0.31" Hex: 30 2E 33 31 -# enterprises.ucdavis.loadTable.laEntry.loadaveLoad.3 = "0.26" Hex: 30 2E 32 36 -# enterprises.ucdavis.loadTable.laEntry.loadaveConfig.1 = "12.00" -# enterprises.ucdavis.loadTable.laEntry.loadaveConfig.2 = "14.00" -# enterprises.ucdavis.loadTable.laEntry.loadaveConfig.3 = "14.00" -# enterprises.ucdavis.loadTable.laEntry.loadaveErrorFlag.1 = 0 -# enterprises.ucdavis.loadTable.laEntry.loadaveErrorFlag.2 = 0 -# enterprises.ucdavis.loadTable.laEntry.loadaveErrorFlag.3 = 0 -# enterprises.ucdavis.loadTable.laEntry.loadaveErrMessage.1 = "" -# enterprises.ucdavis.loadTable.laEntry.loadaveErrMessage.2 = "" -# enterprises.ucdavis.loadTable.laEntry.loadaveErrMessage.3 = "" - -# ----------------------------------------------------------------------------- - - ############################################################################### # Extensible sections. # diff --git a/templates/v3user.erb b/templates/v3user.erb new file mode 100644 index 0000000..d80f03f --- /dev/null +++ b/templates/v3user.erb @@ -0,0 +1 @@ +<%= @kind_v3_user %> <%= @username %>