├── .gitignore ├── COPYING ├── README.markdown ├── files └── network-restart.rb ├── lib └── puppet │ ├── provider │ ├── network_config │ │ ├── interfaces.rb │ │ └── network_scripts.rb │ ├── network_interface │ │ └── ip.rb │ └── network_route │ │ ├── interfaces.rb │ │ └── network_scripts.rb │ └── type │ ├── network_config.rb │ ├── network_interface.rb │ └── network_route.rb ├── manifests └── init.pp.example └── spec └── unit └── puppet └── provider ├── network_config └── network_scripts.rb └── network_interface └── ip.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | .*.swp 3 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright 2011 William Van Hevelingen, Camille Meulien, Elie Bleton 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # puppet-network 2 | 3 | Network management for puppet 4 | 5 | ## Deprecation Warning 6 | 7 | This module is no longer being maintained or updated. 8 | 9 | It has been supercededd by Adrien Thebo's module which can be found here: 10 | 11 | http://forge.puppetlabs.com/adrien/network 12 | 13 | ## Overview 14 | 15 | This module provides types for network management : 16 | 17 | * Device configuration files using the `network_config` type 18 | * Live network management using the `network_interface` type 19 | 20 | Note: `network_interface` and `network_config` types are not dependant on each other in any way. `network_interface` is experimental. 21 | 22 | **Word of warning** : if you choose to go for automatic network reconfiguration and you inject a mistake in your configuration, you probably willl loose network connectivity on the configured system. 23 | 24 | Ensure that you have a fallback ready before trying puppet-network, like physical access, a remote KVM, or similar devices so that you can restore connectivity in the event of configuration errors. 25 | 26 | ## The 'network_config' type 27 | 28 | The `network_config` type is used to maintain persistent network configuration. 29 | Only redhat-derivatives (RHEL,Fedora,CentOS) are currently supported. 30 | 31 | ### Important notes 32 | 33 | #### 'Exclusive' mode by default 34 | 35 | `puppet-network` will remove any device that is not configured through puppet-network. 36 | This may look harsh to some, but the alternative yields greater problems (read below). 37 | 38 | If you want `puppet-network` to leave your existing ifcfg files be, set `exclusive => false` in any of the existing network_config resources. 39 | 40 | In non-exclusive mode, you get the freedom to handle ifcfg files the way you prefer. Be aware though, that *leaving behind unwanted devices can have very adverse effects* (broadcast issues, non-functionning bridges, defective bonding etc..) that won't be solved by rebooting the machine, probably requiring manual intervention to restore connectivity. 41 | 42 | #### 'service network restart' issues 43 | 44 | Phasing out a configuration is dangerous. `service network restart` will only shut down devices configured that are configured (ie with a matching file in `/etc/sysconfig/network-scripts`). 45 | 46 | This can yield to problematic roll-outs, such as removing bridge devices. This would leave behind live bridge configuration, preventing regular use of the formerly bridged interfaces. 47 | 48 | **Workarounds**: 49 | 50 | * use `network-restart.rb` script that comes with puppet-network. this will `service network stop` then proceed to remove anything left that looks like network-configuration, then run `service network start`. Please review code first, be `--sure`, and send feedback at heliostech if you encounter issues. 51 | * use `brctl`/`ifenslave`/`ip` etc manually (ie. roll your own 'network-restart.xx') 52 | * use puppet in offline mode, trigger a `service network stop` before applying configuration changes (puppet code left as an exercise ..), apply changes, then do `service network start`. (*not tested*) 53 | * send patches for network_interface puppet type that can do the `brctl` (and ifenslave etc..) lifting. 54 | * worst case scenario, reset your computer using any appropriate way 55 | 56 | ### Samples 57 | 58 | #### Static configuration 59 |
 60 | network_config { "eth0":
 61 |     bootproto     => "none",
 62 |     onboot        => "yes",
 63 |     netmask       => "255.255.255.0",
 64 |     broadcast     => "192.168.56.255",
 65 |     ipaddr        => "192.168.56.101",
 66 |     userctl       => "no",
 67 |     hwaddr        => "08:00:27:34:05:15",
 68 |     domain        => "example.domain.com",
 69 |     nozeroconf    => "yes",
 70 | }
 71 | 
72 | 73 | You could also use `prefix => 24` instead of the `broadcast` parameter. 74 | 75 | #### DHCP 76 |
 77 | network_config { "eth0":
 78 |     bootproto     => "dhcp",
 79 |     onboot        => "yes",
 80 | }
 81 | 
82 | 83 | #### VLAN 84 |
 85 | network_config { "eth0.2":
 86 |     vlan          => "yes",
 87 | }
 88 | 
89 | 90 | #### Bridges 91 |
 92 | network_config { "eth0":
 93 |     bridge        => "br0"
 94 | }
 95 | 
 96 | network_config { "br1":
 97 |     type          => "Bridge",
 98 |     bootproto     => "dhcp",
 99 |     stp           => "on",
100 | }
101 | 
102 | 103 | #### Bonding 104 |
105 | network_config { "bond0":
106 |     type          => "Bonding",
107 |     bonding_module_opts => "mode=balance-rr miimon=100",
108 | }
109 | 
110 | network_config { "eth0": master => "bond0", slave => "yes" }
111 | network_config { "eth2": master => "bond0", slave => "yes" }
112 | network_config { "eth3": master => "bond0", slave => "yes" }
113 | 
114 | 115 | See [kernel documentation for bonding](http://www.kernel.org/doc/Documentation/networking/bonding.txt) for more information. 116 | 117 | ## The 'network_interface' type 118 | 119 | The `network_interface` maintains live state of the interface using the `ip` tool, likewise : 120 | 121 |
122 | network_interface { "eth0":
123 |     state     => "up",
124 |     mtu       => "1000",
125 |     qlen      => "1500",
126 |     address   => "aa:bb:cc:dd:ee:ff",
127 |     broadcast => "ff:ff:ff:ff:ff:ff",
128 | }
129 | 
130 | 131 | Source code 132 | ----------- 133 | 134 | The source code for this module is available online at 135 | http://github.com/heliostech/puppet-network.git 136 | 137 | You can checkout the source code by installing the `git` distributed version 138 | control system and running: 139 | 140 | git clone git://github.com/heliostech/puppet-network.git 141 | 142 | Authors 143 | ------- 144 | 145 | * William Van Hevelingen 146 | * Elie Bleton 147 | * Camille Meulien -------------------------------------------------------------------------------- /files/network-restart.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ### network-restart.rb : thorough redhat network restarter 4 | ### 5 | ### This attempts to remove what's left behind by `service network stop`, 6 | ### which happens when you remove device configuration files. 7 | ### 8 | ### Camille Meulien 9 | ### Elie Bleton 10 | 11 | # Copyright 2011 Helios Technologies SARL 12 | # 13 | # Licensed under the Apache License, Version 2.0 (the "License"); 14 | # you may not use this file except in compliance with the License. 15 | # You may obtain a copy of the License at 16 | # 17 | # http://www.apache.org/licenses/LICENSE-2.0 18 | # 19 | # Unless required by applicable law or agreed to in writing, software 20 | # distributed under the License is distributed on an "AS IS" BASIS, 21 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22 | # See the License for the specific language governing permissions and 23 | # limitations under the License. 24 | 25 | fail "usage: #{__FILE__} --sure" if ARGV.first != "--sure" 26 | 27 | def ifaces 28 | `ifconfig | grep -E '^[^ ]' | cut -d' ' -f1`.split.map { |d| d.chomp } 29 | end 30 | 31 | @cmds, @bridges, @bridge = [], {}, nil 32 | 33 | # Try to stop properly :) 34 | @cmds << "service network stop" 35 | 36 | # Remove interfaces 37 | ifaces.each { |iface| @cmds << "ip link set #{iface} down" } 38 | 39 | # Remove bridges 40 | `brctl show`.split("\n")[1..-1].each do |line| 41 | if line =~ /^([^\t]+)\t+[^\t]+\t+[^\t]+\t+([^\t]+)$/ 42 | @bridge, iface = $1, $2 43 | @bridges[@bridge] = [ iface ] 44 | elsif line =~ /^ +([^ ]+)$/ 45 | @bridges[@bridge] << (iface = $1) 46 | else 47 | STDERR.puts "brctl parse error:" + line 48 | end 49 | end 50 | 51 | @bridges.each_pair do |bridge, iflist| 52 | iflist.each { |iface| @cmds << "brctl delif #{bridge} #{iface}" } 53 | @cmds << "brctl delbr #{bridge}" 54 | end 55 | 56 | # Remove DHCP client 57 | daemons = ["dhclient", "udhcpcd", "dhcpcd"] 58 | daemons.each { |p| @cmds << "killall -KILL #{p}" if `ps x`.include?(p) } 59 | 60 | # Delete all links 61 | ifaces.each { |iface| @cmds << "ip link delete #{iface}" } 62 | 63 | # Remove bonding module 64 | @cmds << "rmmod bonding" if `lsmod | egrep '^bonding'`.chomp.empty? 65 | 66 | # Guess what ?? 67 | @cmds << "service network restart" 68 | 69 | # Exec 70 | @cmds.each do |cmd| 71 | puts cmd 72 | system cmd 73 | end 74 | -------------------------------------------------------------------------------- /lib/puppet/provider/network_config/interfaces.rb: -------------------------------------------------------------------------------- 1 | # This provider is in development and not ready for production 2 | 3 | Puppet::Type.type(:network_config).provide(:interfaces) do 4 | 5 | defaultfor :operatingsystem => [:ubuntu, :debian] 6 | # ... change this for interfaces 7 | # iface eth0-@map_value 8 | # key value 9 | # address 192.168.1.1 10 | # netmask 255.255.255.0 11 | # 12 | # lines beginning with the work "auto" ~ onboot => yes 13 | # record_line :parsed, :fields => %w{address netmask gateway broadcast family method}, 14 | end 15 | -------------------------------------------------------------------------------- /lib/puppet/provider/network_config/network_scripts.rb: -------------------------------------------------------------------------------- 1 | Puppet::Type.type(:network_config).provide(:network_scripts) do 2 | desc "Provider for configuration of network_scripts" 3 | 4 | defaultfor :operatingsystem => [:redhat, :fedora, :centos] 5 | 6 | @@exclusive = nil 7 | @@configured_devices = [] 8 | @@config_dir = "/etc/sysconfig/network-scripts/" 9 | @@instance_count = 0 10 | @@total_resource_count = 0 11 | 12 | # checks for network-script existence and correctness 13 | def exists? 14 | @@instance_count += 1 15 | @config_file = "#{@@config_dir}ifcfg-#{@resource[:name]}" 16 | 17 | # do not check file contents if the purpose is to ensure the file isn't there 18 | return File.exists?(@config_file) if @resource[:ensure] == :absent 19 | 20 | # load puppet configuration (`should`) 21 | @memory_values = {} 22 | 23 | # Get any property set in the resource that isn't a metaparameter 24 | mp = Puppet::Type::metaparams << :ensure << :provider << :exclusive 25 | @resource.to_hash.delete_if { |k,v| mp.include? k }.each_pair { |k, v| 26 | @memory_values[k.to_s.upcase.to_sym] = v.to_s unless v.nil? 27 | } 28 | 29 | # handle the special hack with :exclusive 30 | @@exclusive = @resource.to_hash[:exclusive] if @@exclusive.nil? 31 | 32 | # load on-disk configuration (`is`) 33 | @disk_values = load_disk_config() 34 | 35 | # if this is the last file to be checked, trigger exclusivity enforcement 36 | enforce_exclusivity if (@@instance_count == @@total_resource_count) 37 | 38 | return @disk_values == @memory_values 39 | end 40 | 41 | def create 42 | File.open(@config_file.to_s, 'w') do |f| 43 | f.write("# Generated by puppet-network on #{Time.now.strftime("%F %T")}\n") 44 | @memory_values.each_pair do |k, v| 45 | # only quote values that include space or equal characters 46 | quote_value = v.include?(' ') or v.include?('=') 47 | vs = v.nil? ? k : quote_value ? "#{k}=\"#{v}\"" : "#{k}=#{v}" 48 | f.write("#{vs}\n") 49 | end 50 | end 51 | end 52 | 53 | def destroy 54 | if File.exists?(@config_file) 55 | Puppet.notice "Destroying #{@config_file}" 56 | File.unlink(@config_file) 57 | end 58 | end 59 | 60 | # Reads the content in the config file and returns a hash of keys & values 61 | def load_disk_config 62 | return nil unless File.exists?(@config_file) 63 | 64 | config_hash = {} 65 | 66 | File.readlines(@config_file).each do |line| 67 | next unless line =~ /^\s*([A-Za-z][^=]+)="?([^"]+)"?$/ 68 | config_hash[$1.strip.upcase.to_sym] = $2.strip 69 | end 70 | 71 | Puppet.debug "Loaded file: #{@config_file}" 72 | return config_hash 73 | end 74 | 75 | # 76 | # `exclusive` related code 77 | # 78 | 79 | def initialize(args) 80 | super(args) 81 | @@configured_devices << "ifcfg-#{@resource[:device]}" 82 | @@total_resource_count += 1 83 | end 84 | 85 | def clear 86 | @@configured_devices = [] 87 | @@instance_count = 0 88 | @@total_resource_count = 0 89 | @@exclusive = nil 90 | end 91 | 92 | # gets called once every network_config resource has been declared. 93 | def enforce_exclusivity 94 | unless @@exclusive == :false 95 | existing = Dir["#{@@config_dir}ifcfg-*"].map { |f| File.basename(f) } 96 | (existing - @@configured_devices).each do |parasite_file| 97 | Puppet.notice "puppet-network exclusive: removing #{parasite_file}" 98 | File.delete("#{@@config_dir}#{parasite_file}") 99 | end 100 | end 101 | clear() 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /lib/puppet/provider/network_interface/ip.rb: -------------------------------------------------------------------------------- 1 | Puppet::Type.type(:network_interface).provide(:ip) do 2 | 3 | # ip command is preferred over ifconfig 4 | commands :ip => "/sbin/ip", :vconfig => "/sbin/vconfig" 5 | 6 | # Uses the ip command to determine if the device exists 7 | def exists? 8 | # ip('link', 'list', @resource[:name]) 9 | ip('addr', 'show', 'label', @resource[:device]).include?("inet") 10 | rescue Puppet::ExecutionFailure 11 | return false 12 | # raise Puppet::Error, "Network interface %s does not exist" % @resource[:name] 13 | end 14 | 15 | def create 16 | if @resource[:vlan] == :yes && ! ip('link', 'list').include?(@resource[:name].split(':').first) 17 | # Create vlan device 18 | vconfig('add', @resource[:device].split('.').first, @resource[:device].split('.').last) 19 | end 20 | unless self.netmask == @resource.should(:netmask) || self.broadcast == @resource.should(:broadcast) || self.ipaddr == @resource.should(:ipaddr) 21 | ip_addr_flush 22 | ip_addr_add 23 | end 24 | unless self.state == @resource.should(:state) 25 | self.state=(@resource.should(:state)) 26 | end 27 | end 28 | 29 | def destroy 30 | ip_addr_flush 31 | if @resource[:vlan] == :yes 32 | # Test if no ip addresses are configured on this vlan device 33 | if ! ip('addr', 'show', @resource[:device].split(':').first).include?("inet") 34 | # Destroy vlan device 35 | vconfig('rem', @resource[:device].split(':').first) 36 | end 37 | end 38 | end 39 | 40 | 41 | # NETMASK 42 | def netmask 43 | lines = ip('addr', 'show', 'label', @resource[:device]) 44 | lines.scan(/\s*inet (\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b)\/(\d+) b?r?d?\s*(\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b)?\s*scope (\w+) (\w+:?\d*)/) 45 | $2.nil? ? :absent : $2 46 | end 47 | 48 | def netmask=(value) 49 | ip_addr_flush 50 | ip_addr_add 51 | end 52 | 53 | # BROADCAST 54 | def broadcast 55 | lines = ip('addr', 'show', 'label', @resource[:device]) 56 | lines.scan(/\s*inet (\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b)\/(\d+) b?r?d?\s*(\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b)?\s*scope (\w+) (\w+:?\d*)/) 57 | $3.nil? ? :absent : $3 58 | end 59 | 60 | def broadcast=(value) 61 | ip_addr_flush 62 | ip_addr_add 63 | end 64 | 65 | # IPADDR 66 | def ipaddr 67 | lines = ip('addr', 'show', 'label', @resource[:device]) 68 | lines.scan(/\s*inet (\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b)\/(\d+) b?r?d?\s*(\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b)?\s*scope (\w+) (\w+:?\d*)/) 69 | $1.nil? ? :absent : $1 70 | end 71 | 72 | def ipaddr=(value) 73 | ip_addr_flush 74 | ip_addr_add 75 | end 76 | 77 | 78 | def ip_addr_flush 79 | ip('addr', 'flush', 'dev', @resource[:device], 'label', @resource[:device].sub(/:/, '\:')) 80 | end 81 | 82 | def ip_addr_add 83 | ip('addr', 'add', @resource[:ipaddr] + "/" + @resource[:netmask], 'broadcast', @resource[:broadcast], 'label', @resource[:device], 'dev', @resource[:device]) 84 | end 85 | 86 | def device 87 | config_values[:dev] 88 | end 89 | 90 | # Ensurable/ensure adds unnecessary complexity to this provider 91 | # Network interfaces are up or down, present/absent are unnecessary 92 | def state 93 | lines = ip('link', 'list', @resource[:name]) 94 | if lines.include?("UP") 95 | return "up" 96 | else 97 | return "down" 98 | end 99 | end 100 | 101 | # Set the interface's state 102 | # FIXME Facter bug #2211 prevents puppet from bringing up network devices 103 | def state=(value) 104 | ip('link', 'set', @resource[:name], value) 105 | end 106 | 107 | # Current state of the device via the ip command 108 | def state_values 109 | @values ||= read_ip_output 110 | end 111 | 112 | # Return the ip output of the device 113 | def ip_output 114 | ip('addr','show', 'dev', @resource[:name]) 115 | end 116 | 117 | # FIXME Back Named Reference Captures are supported in Ruby 1.9.x 118 | def read_ip_output 119 | output = ip_output 120 | lines = output.split("\n") 121 | line1 = lines.shift 122 | line2 = lines.shift 123 | i=0 124 | j=0 125 | p=0 126 | 127 | # Append ipv6 lines into one string 128 | lines.each do |line| 129 | if line.include?("inet6") 130 | lines[p] = lines[p] + lines[p+1] 131 | lines.delete_at(p+1) 132 | else 133 | # move along, nothing to see here 134 | end 135 | p += 1 136 | end 137 | 138 | #FIXME This should capture 'NOARP' and 'MULTICAST' 139 | # Scan the first line of the ip command output 140 | line1.scan(/\d: (\w+): <(\w+),(\w+),(\w+),?(\w*)> mtu (\d+) qdisc (\w+) state (\w+)\s*\w* (\d+)*/) 141 | values = { 142 | "device" => $1, 143 | "mtu" => $6, 144 | "qdisc" => $7, 145 | "state" => $8, 146 | "qlen" => $9, 147 | } 148 | 149 | # Scan the second line of the ip command output 150 | line2.scan(/\s*link\/\w+ ((?:[0-9a-f]{2}[:-]){5}[0-9a-f]{2}) brd ((?:[0-9a-f]{2}[:-]){5}[0-9a-f]{2})/) 151 | values["address"] = $1 152 | values["broadcast"] = $2 153 | 154 | # Scan all the inet and inet6 entries 155 | lines.each do |line| 156 | if line.include?("inet6") 157 | line.scan(/\s*inet6 ((?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})\/\w+ scope (\w+)\s*\w*\s*valid_lft (\w+) preferred_lft (\w+)/) 158 | values["inet6_#{j}"] = { 159 | "ip" => $1, 160 | "scope" => $2, 161 | "valid_lft" => $3, 162 | "preferred_lft" => $4, 163 | } 164 | j += 1 165 | else 166 | line.scan(/\s*inet (\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b)\/\d+ b?r?d?\s*(\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b)?\s*scope (\w+) (\w+:?\d*)/) 167 | values["inet_#{i}"] = { 168 | "ip" => $1, 169 | "brd" => $2, 170 | "scope" => $3, 171 | "dev" => $4, 172 | } 173 | i += 1 174 | end 175 | end 176 | 177 | return values 178 | 179 | end 180 | 181 | #FIXME Need to support multiple inet & inet6 hashes 182 | IP_ARGS = [ "qlen", "mtu", "address" ] 183 | 184 | IP_ARGS.each do |ip_arg| 185 | define_method(ip_arg.to_s.downcase) do 186 | state_values[ip_arg] 187 | end 188 | 189 | define_method("#{ip_arg}=".downcase) do |value| 190 | ip('link', 'set', "#{ip_arg}", value, 'dev', @resource[:name]) 191 | state_values[ip_arg] = value 192 | end 193 | end 194 | 195 | end 196 | -------------------------------------------------------------------------------- /lib/puppet/provider/network_route/interfaces.rb: -------------------------------------------------------------------------------- 1 | # This provider is in development and not ready for production 2 | 3 | Puppet::Type.type(:network_route).provide(:interfaces) do 4 | 5 | defaultfor :operatingsystem => [:ubuntu, :debian] 6 | # There are several ways to implement this 7 | # We can add a post-up/pre-down rules that call ip route 8 | # or we can scripts in /etc/network/if-up.d /etc/network/if-down.d 9 | end 10 | -------------------------------------------------------------------------------- /lib/puppet/provider/network_route/network_scripts.rb: -------------------------------------------------------------------------------- 1 | Puppet::Type.type(:network_route).provide(:network_scripts) do 2 | desc "Provider for configuration of network_route" 3 | 4 | defaultfor :operatingsystem => [:redhat, :fedora, :centos] 5 | 6 | @@exclusive_routes= nil 7 | @@configured_routes = [] 8 | @@config_dir_routes = "/etc/sysconfig/network-scripts/" 9 | @@instance_routes_count = 0 10 | @@total_resource_routes_count = 0 11 | 12 | # checks for network-script existence and correctness 13 | def exists? 14 | @@instance_routes_count += 1 15 | @config_file = "#{@@config_dir}route-#{@resource[:name]}" 16 | 17 | # do not check file contents if the purpose is to ensure the file isn't there 18 | return File.exists?(@config_file) if @resource[:ensure] == :absent 19 | 20 | # load puppet configuration (`should`) 21 | @memory_values = [] 22 | 23 | for route in @resource[:routes] 24 | @memory_values.push(route.strip) 25 | end 26 | 27 | # handle the special hack with :exclusive 28 | @@exclusive_routes = @resource.to_hash[:exclusive] if @@exclusive_routes.nil? 29 | 30 | # load on-disk configuration (`is`) 31 | @disk_values = load_disk_config() 32 | 33 | # if this is the last file to be checked, trigger exclusivity enforcement 34 | enforce_exclusivity if (@@instance_routes_count == @@total_resource_routes_count) 35 | 36 | return @disk_values == @memory_values 37 | end 38 | 39 | def create 40 | File.open(@config_file.to_s, 'w') do |f| 41 | f.write("# Generated by puppet-network on #{Time.now.strftime("%F %T")}\n") 42 | for line in @memory_values 43 | f.write("#{line.to_s.strip}\n") 44 | end 45 | end 46 | end 47 | 48 | def destroy 49 | if File.exists?(@config_file) 50 | Puppet.notice "Destroying #{@config_file}" 51 | File.unlink(@config_file) 52 | end 53 | end 54 | 55 | # Reads the content in the config file and returns a hash of keys & values 56 | def load_disk_config 57 | return nil unless File.exists?(@config_file) 58 | 59 | config_array = [] 60 | 61 | File.readlines(@config_file).each do |line| 62 | next if line =~ /^#.*$/ 63 | config_array.push(line.to_s.strip) 64 | end 65 | 66 | Puppet.debug "Loaded file: #{@config_file}" 67 | return config_array 68 | end 69 | 70 | # 71 | # `exclusive` related code 72 | # 73 | 74 | def initialize(args) 75 | super(args) 76 | @@configured_routes << "route-#{@resource[:device]}" 77 | @@total_resource_routes_count += 1 78 | end 79 | 80 | def clear 81 | @@configured_routes = [] 82 | @@instance_routes_count = 0 83 | @@total_resource_routes_count = 0 84 | @@exclusive_routes = nil 85 | end 86 | 87 | # gets called once every network_config resource has been declared. 88 | def enforce_exclusivity 89 | unless @@exclusive_routes == :false 90 | existing = Dir["#{@@config_dir}route-*"].map { |f| File.basename(f) } 91 | (existing - @@configured_routes).each do |parasite_file| 92 | Puppet.notice "puppet-network exclusive: removing #{parasite_file}" 93 | File.delete("#{@@config_dir}#{parasite_file}") 94 | end 95 | end 96 | clear() 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /lib/puppet/type/network_config.rb: -------------------------------------------------------------------------------- 1 | require 'puppet' 2 | 3 | module Puppet 4 | 5 | Puppet::Type.newtype(:network_config) do 6 | @doc = "The network configuration type" 7 | 8 | ensurable 9 | 10 | newparam(:exclusive) do 11 | d = "Enforces that no network configuration exists besides what puppet defines.\n" 12 | d << "Enabled by default, set it to false in any resource to disable globally." 13 | desc(d) 14 | 15 | newvalues(:true, :false) 16 | # this behaviorally defaults to true (see network_scripts.rb exists?()/initialize()) 17 | # using defaultto(:true) would prevent users from setting this to false 18 | end 19 | 20 | newparam(:device) do 21 | isnamevar 22 | desc "The network device to be configured" 23 | end 24 | 25 | newparam(:bootproto) do 26 | desc "Boot priority for the network device" 27 | newvalues(:dhcp, :static, :none) 28 | defaultto(:dhcp) 29 | end 30 | 31 | newparam(:onboot) do 32 | desc "Start the network device on boot" 33 | newvalues(:yes, :no) 34 | defaultto(:yes) 35 | end 36 | 37 | newparam(:nozeroconf) do 38 | desc "Skip zeroconf (aka local-link) configuration" 39 | newvalues(:yes, :no) 40 | end 41 | 42 | newparam(:netmask) do 43 | desc "Configure the subnetmask of the device" 44 | end 45 | 46 | newparam(:prefix) do 47 | desc "Configure the network prefix, Has precedence over NETMASK on redhat." 48 | end 49 | 50 | newparam(:network) do 51 | desc "Configure the network of the device" 52 | end 53 | 54 | newparam(:broadcast) do 55 | desc "Configure the broadcast of the device" 56 | end 57 | 58 | newparam(:ipaddr) do 59 | desc "Configure the IP address of the device" 60 | end 61 | 62 | newparam(:gateway) do 63 | desc "Configure the Gateway of the device" 64 | end 65 | 66 | newparam(:hwaddr) do 67 | desc "Hardware address of the device" 68 | end 69 | 70 | newparam(:domain) do 71 | desc "Configure the domain of the device" 72 | end 73 | 74 | newparam(:bridge) do 75 | desc "The bridge in which the device is enslaved (if any)" 76 | end 77 | 78 | newparam(:stp) do 79 | desc "Enable STP (only applicable to type=Bridge devices)" 80 | end 81 | 82 | newparam(:delay) do 83 | desc "Configure forward delay (only applicable to type=Bridge devices)" 84 | end 85 | 86 | newparam(:peerdns) do 87 | desc "modify /etc/resolv.conf if peer uses msdns extension (PPP only) or 88 | DNS{1,2} are set, or if using dhclient. default to 'yes'." 89 | newvalues(:yes, :no) 90 | end 91 | 92 | newparam(:dns1) do 93 | desc "primary DNS server IPADDR" 94 | end 95 | 96 | newparam(:dns2) do 97 | desc "secondary DNS server IPADDR" 98 | end 99 | 100 | newparam(:type) do 101 | desc "Type of the device" 102 | newvalues(:Ethernet, :Bridge, :Bonding) 103 | end 104 | 105 | newparam(:vlan) do 106 | desc "Is the device VLAN tagged (802.1q)" 107 | newvalues(:yes, :no) 108 | end 109 | 110 | newparam(:userctl) do 111 | desc "Non root users are allowed to control device if set to yes" 112 | newvalues(:yes, :no) 113 | defaultto(:no) 114 | end 115 | 116 | newparam(:bonding_opts) do 117 | desc "Configures bonding parameter" 118 | end 119 | 120 | newparam(:master) do 121 | desc "Configures the bonding device to which the device is enslaved (set 'slave=>yes' too)" 122 | end 123 | 124 | newparam(:slave) do 125 | desc "Configures whether or not the device is enslaved to a bonding device" 126 | newvalues(:yes, :no) 127 | end 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /lib/puppet/type/network_interface.rb: -------------------------------------------------------------------------------- 1 | require 'puppet' 2 | require 'ipaddr' 3 | 4 | module Puppet 5 | 6 | Puppet::Type.newtype(:network_interface) do 7 | @doc = "The network managment configuration type" 8 | 9 | ensurable 10 | 11 | newparam(:device) do 12 | isnamevar 13 | desc "The network device to be configured" 14 | end 15 | 16 | newproperty(:state) do 17 | desc "state of the interface" 18 | newvalues(:up, :down) 19 | defaultto(:up) 20 | end 21 | 22 | newproperty(:inet) do 23 | desc "Configure the IPV4 address of the device" 24 | end 25 | 26 | newproperty(:inet6) do 27 | desc "Configure the IPV6 address of the device" 28 | end 29 | 30 | newproperty(:gateway) do 31 | desc "Configure the Gateway of the device" 32 | end 33 | 34 | newproperty(:address) do 35 | desc "Hardware address of the device" 36 | end 37 | 38 | newproperty(:arp) do 39 | desc "Arp" 40 | newvalues(:on, :off) 41 | end 42 | 43 | newproperty(:multicast) do 44 | desc "multicast" 45 | newvalues(:on, :off) 46 | end 47 | 48 | newproperty(:dynamic) do 49 | desc "dynamic" 50 | newvalues(:on, :off) 51 | end 52 | 53 | newproperty(:qlen) do 54 | desc "txquelen" 55 | end 56 | 57 | newproperty(:mtu) do 58 | desc "mtu" 59 | end 60 | 61 | newparam(:vlan) do 62 | desc "Is the device VLAN tagged (802.1q)" 63 | newvalues(:yes, :no) 64 | defaultto(:no) 65 | end 66 | 67 | 68 | newproperty(:ipaddr) do 69 | desc "Configure the IP address of the device" 70 | end 71 | 72 | newproperty(:netmask) do 73 | desc "Configure the subnetmask of the device" 74 | 75 | munge do |value| 76 | if value.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/) 77 | IPAddr.new(value).to_i.to_s(2).count("1").to_s 78 | else 79 | super 80 | end 81 | end 82 | end 83 | 84 | newproperty(:broadcast) do 85 | desc "Configure the broadcast of the device" 86 | end 87 | 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /lib/puppet/type/network_route.rb: -------------------------------------------------------------------------------- 1 | require 'puppet' 2 | 3 | module Puppet 4 | 5 | Puppet::Type.newtype(:network_route) do 6 | @doc = "The route configuration type" 7 | 8 | ensurable 9 | 10 | newparam(:exclusive) do 11 | d = "Enforces that no route configuration exists besides what puppet defines.\n" 12 | d << "Enabled by default, set it to false in any resource to disable globally." 13 | desc(d) 14 | 15 | newvalues(:true, :false) 16 | # this behaviorally defaults to true (see network_scripts.rb exists?()/initialize()) 17 | # using defaultto(:true) would prevent users from setting this to false 18 | end 19 | 20 | newparam(:device) do 21 | isnamevar 22 | desc "The network device for which route will be configured" 23 | end 24 | 25 | newparam(:routes) do 26 | desc "The routes to be configured" 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /manifests/init.pp.example: -------------------------------------------------------------------------------- 1 | 2 | class puppet-network { 3 | 4 | network_config { "eth1": 5 | bootproto => static, 6 | onboot => no, 7 | netmask => "255.255.255.0", 8 | broadcast => "131.252.223.63", 9 | ipaddr => "131.252.214.173", 10 | gateway => "121.411.713.245", 11 | hwaddr => "FF:DD:CC:BB:AA", 12 | userctl => yes, 13 | domain => "example2.domain.com", 14 | ensure => present 15 | } 16 | 17 | network_config { "eth3": 18 | bootproto => "dhcp", 19 | onboot => "no", 20 | userctl => "yes", 21 | ensure => present 22 | } 23 | 24 | network_interface { "eth3": 25 | state => "up", 26 | mtu => "1111", 27 | qlen => "2000", 28 | address => "aa:bb:cc:dd:ee:ff", 29 | broadcast => "ff:ff:ff:ff:ff:ff", 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /spec/unit/puppet/provider/network_config/network_scripts.rb: -------------------------------------------------------------------------------- 1 | require 'puppet' 2 | require 'mocha' 3 | require '/etc/puppet/modules/puppet-network/lib/puppet/provider/network_config/network_scripts.rb' 4 | 5 | provider_class = Puppet::Type.type(:network_config).provider(:network_scripts) 6 | 7 | describe provider_class do 8 | before do 9 | @provider = provider_class.new 10 | end 11 | it "should read config file" do 12 | File.stubs(:exist?).returns true 13 | filemock = stub "Network File" 14 | File.stubs(:new).returns filemock 15 | filemock.stubs(:readlines).returns [ 16 | "#this is a comment", 17 | "USRCTL=no", 18 | "IPADDR=127.0.0.1", 19 | "BOOTPROTO=dhcp\n", 20 | "ONBOOT=yes", 21 | ] 22 | 23 | @provider.read_config.should == { 24 | :USRCTL => "no", 25 | :IPADDR => "127.0.0.1", 26 | :BOOTPROTO => "dhcp", 27 | :ONBOOT => "yes", 28 | } 29 | 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /spec/unit/puppet/provider/network_interface/ip.rb: -------------------------------------------------------------------------------- 1 | require 'puppet' 2 | require 'ruby-debug' 3 | require 'mocha' 4 | require 'lib/puppet/provider/network_interface/ip.rb' 5 | 6 | 7 | provider_class = Puppet::Type.type(:network_interface).provider(:ip) 8 | 9 | describe provider_class do 10 | 11 | before do 12 | @resource = stub("resource", :name => "lo") 13 | @resource.stubs(:[]).with(:name).returns "lo" 14 | @resource.stubs(:[]).returns "lo" 15 | @provider = provider_class.new(@resource) 16 | end 17 | 18 | it "should parse ip link show output for loopback" do 19 | ip_output = <<-HEREDOC 20 | 1: lo: mtu 16436 qdisc noqueue state UNKNOWN 21 | link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 22 | inet 127.0.0.1/8 scope host lo 23 | inet6 ::1/128 scope host 24 | valid_lft forever preferred_lft forever 25 | HEREDOC 26 | 27 | @provider.stubs(:ip_output).returns(ip_output) 28 | hash = { 29 | "device" => "lo", 30 | #"dynamic" => "on", 31 | #"multicast" => "on", 32 | #"arp" => "on", 33 | "mtu" => "16436", 34 | "qdisc" => "noqueue", 35 | "state" => "UNKNOWN", 36 | "qlen" => nil, 37 | "address" => "00:00:00:00:00:00", 38 | "broadcast" => "00:00:00:00:00:00", 39 | "inet_0" => { 40 | "ip" => "127.0.0.1", 41 | "brd" => nil, 42 | "scope" => "host", 43 | "dev" => "lo", 44 | }, 45 | "inet6_0" => { 46 | "ip" => "::1", 47 | "scope" => "host", 48 | "valid_lft" => "forever", 49 | "preferred_lft" => "forever", 50 | }, 51 | } 52 | @provider.read_ip_output.should == hash 53 | end 54 | 55 | before do 56 | @resource = stub("resource", :name => "eth0") 57 | @resource.stubs(:[]).with(:name).returns "eth0" 58 | @resource.stubs(:[]).returns "eth0" 59 | @provider = provider_class.new(@resource) 60 | end 61 | 62 | it "should parse ip addr show output with an ipv4" do 63 | ip_output = <<-HEREDOC 64 | 2: eth0: mtu 1500 qdisc pfifo_fast state UP qlen 1000 65 | link/ether 08:00:27:6c:c7:59 brd ff:ff:ff:ff:ff:ff 66 | inet 192.168.56.101/24 brd 192.168.56.255 scope global eth0 67 | inet6 fe80::a00:27ff:fe6c:c759/64 scope link 68 | valid_lft forever preferred_lft forever 69 | HEREDOC 70 | 71 | @provider.stubs(:ip_output).returns(ip_output) 72 | hash = { 73 | "device" => "eth0", 74 | #"dynamic" => "on", 75 | #"multicast" => "on", 76 | #"arp" => "on", 77 | "mtu" => "1500", 78 | "qdisc" => "pfifo_fast", 79 | "state" => "UP", 80 | "qlen" => "1000", 81 | "address" => "08:00:27:6c:c7:59", 82 | "broadcast" => "ff:ff:ff:ff:ff:ff", 83 | 84 | "inet_0" => { 85 | "ip" => "192.168.56.101", 86 | "brd" => "192.168.56.255", 87 | "scope" => "global", 88 | "dev" => "eth0", 89 | }, 90 | "inet6_0" => { 91 | "ip" => "fe80::a00:27ff:fe6c:c759", 92 | "scope" => "link", 93 | "valid_lft" => "forever", 94 | "preferred_lft" => "forever", 95 | } 96 | } 97 | @provider.read_ip_output.should == hash 98 | 99 | end 100 | 101 | it "should parse ip link show output with multiple ip addresses" do 102 | ip_output = <<-HEREDOC 103 | 2: eth0: mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000 104 | link/ether 54:52:00:27:fd:71 brd ff:ff:ff:ff:ff:ff 105 | inet 131.252.208.54/24 brd 131.252.208.255 scope global eth0 106 | inet 131.252.208.79/32 brd 131.252.208.255 scope global eth0:2 107 | inet 131.252.208.98/32 brd 131.252.208.255 scope global eth0:3 108 | inet 131.252.208.61/32 brd 131.252.208.255 scope global eth0:12 109 | inet 131.252.208.66/32 brd 131.252.208.255 scope global eth0:13 110 | inet6 2610:10:20:208:5652:ff:fe27:fd71/64 scope global dynamic 111 | valid_lft 2591858sec preferred_lft 604658sec 112 | inet6 2610:10:20:208::66/64 scope global 113 | valid_lft forever preferred_lft forever 114 | inet6 2610:10:20:208::79/64 scope global 115 | valid_lft forever preferred_lft forever 116 | inet6 fe80::5652:ff:fe27:fd71/64 scope link 117 | valid_lft forever preferred_lft forever 118 | HEREDOC 119 | @provider.stubs(:ip_output).returns(ip_output) 120 | hash = { 121 | "device" => "eth0", 122 | #"dynamic" => "on", 123 | #"multicast" => "on", 124 | #"arp" => "on", 125 | "mtu" => "1500", 126 | "qdisc" => "pfifo_fast", 127 | "state" => "UNKNOWN", 128 | "qlen" => "1000", 129 | "address" => "54:52:00:27:fd:71", 130 | "broadcast" => "ff:ff:ff:ff:ff:ff", 131 | "inet_0" => { 132 | "ip" => "131.252.208.54", 133 | "brd" => "131.252.208.255", 134 | "scope" => "global", 135 | "dev" => "eth0", 136 | }, 137 | "inet_1" => { 138 | "ip" => "131.252.208.79", 139 | "brd" => "131.252.208.255", 140 | "scope" => "global", 141 | "dev" => "eth0:2", 142 | }, 143 | "inet_2" => { 144 | "ip" => "131.252.208.98", 145 | "brd" => "131.252.208.255", 146 | "scope" => "global", 147 | "dev" => "eth0:3", 148 | }, 149 | "inet_3" => { 150 | "ip" => "131.252.208.61", 151 | "brd" => "131.252.208.255", 152 | "scope" => "global", 153 | "dev" => "eth0:12", 154 | }, 155 | "inet_4" => { 156 | "ip" => "131.252.208.66", 157 | "brd" => "131.252.208.255", 158 | "scope" => "global", 159 | "dev" => "eth0:13", 160 | }, 161 | "inet6_0" => { 162 | "ip" => "2610:10:20:208:5652:ff:fe27:fd71", 163 | "scope" => "global", 164 | "valid_lft" => "2591858sec", 165 | "preferred_lft" => "604658sec" 166 | }, 167 | "inet6_1" => { 168 | "ip" => "2610:10:20:208::66", 169 | "scope" => "global", 170 | "valid_lft" => "forever", 171 | "preferred_lft" => "forever", 172 | }, 173 | "inet6_2" => { 174 | "ip" => "2610:10:20:208::79", 175 | "scope" => "global", 176 | "valid_lft" => "forever", 177 | "preferred_lft" => "forever", 178 | }, 179 | "inet6_3" => { 180 | "ip" => "fe80::5652:ff:fe27:fd71", 181 | "scope" => "link", 182 | "valid_lft" => "forever", 183 | "preferred_lft" => "forever", 184 | }, 185 | } 186 | @provider.read_ip_output.should == hash 187 | end 188 | 189 | end 190 | --------------------------------------------------------------------------------