Skip to content

Use --json for ip command output #251

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 29 additions & 33 deletions lib/linux_admin/network_interface.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
require 'ipaddr'

module LinuxAdmin
class NetworkInterface
require "ipaddr"
require "json"

# Cached class instance variable for what distro we are running on
@dist_class = nil

Expand Down Expand Up @@ -60,14 +61,16 @@ def reload
return false
end

parse_ip4(ip_output)
parse_ip6(ip_output, :global)
parse_ip6(ip_output, :link)
addr_info = ip_output["addr_info"]

@network_conf[:mac] = parse_ip_output(ip_output, %r{link/ether}, 1)
parse_ip4(addr_info)
parse_ip6(addr_info, "global")
parse_ip6(addr_info, "link")

@network_conf[:mac] = ip_output["address"]

[4, 6].each do |version|
@network_conf["gateway#{version}".to_sym] = parse_ip_output(ip_route(version), /^default/, 2)
@network_conf["gateway#{version}".to_sym] = ip_route(version, "default")&.dig("gateway")
end
true
end
Expand Down Expand Up @@ -168,23 +171,15 @@ def stop

private

# Parses the output of `ip addr show`
#
# @param output [String] The command output
# @param regex [Regexp] Regular expression to match the desired output line
# @param col [Fixnum] The whitespace delimited column to be returned
# @return [String] The parsed data
def parse_ip_output(output, regex, col)
the_line = output.split("\n").detect { |l| l =~ regex }
the_line.nil? ? nil : the_line.strip.split(' ')[col]
end

# Runs the command `ip addr show <interface>`
#
# @return [String] The command output
# @raise [NetworkInterfaceError] if the command fails
def ip_show
Common.run!(Common.cmd("ip"), :params => ["addr", "show", @interface]).output
output = Common.run!(Common.cmd("ip"), :params => ["--json", "addr", "show", @interface]).output
return {} if output.blank?

JSON.parse(output).first
rescue AwesomeSpawn::CommandResultError => e
raise NetworkInterfaceError.new(e.message, e.result)
end
Expand All @@ -194,35 +189,36 @@ def ip_show
# @param version [Fixnum] Version of IP protocol (4 or 6)
# @return [String] The command output
# @raise [NetworkInterfaceError] if the command fails
def ip_route(version)
Common.run!(Common.cmd("ip"), :params => ["-#{version}", 'route']).output
def ip_route(version, route = "default")
output = Common.run!(Common.cmd("ip"), :params => ["--json", "-#{version}", "route", "show", route]).output
return {} if output.blank?

JSON.parse(output).first
rescue AwesomeSpawn::CommandResultError => e
raise NetworkInterfaceError.new(e.message, e.result)
end

# Parses the IPv4 information from the output of `ip addr show <device>`
#
# @param ip_output [String] The command output
def parse_ip4(ip_output)
cidr_ip = parse_ip_output(ip_output, /inet /, 1)
return unless cidr_ip
def parse_ip4(addr_info)
inet = addr_info&.detect { |addr| addr["family"] == "inet" }
return if inet.nil?

parts = cidr_ip.split('/')
@network_conf[:address] = parts[0]
@network_conf[:prefix] = parts[1].to_i
@network_conf[:address] = inet["local"]
@network_conf[:prefix] = inet["prefixlen"]
end

# Parses the IPv6 information from the output of `ip addr show <device>`
#
# @param ip_output [String] The command output
# @param scope [Symbol] The IPv6 scope (either `:global` or `:local`)
def parse_ip6(ip_output, scope)
cidr_ip = parse_ip_output(ip_output, /inet6 .* scope #{scope}/, 1)
return unless cidr_ip
def parse_ip6(addr_info, scope)
inet6 = addr_info&.detect { |addr| addr["family"] == "inet6" && addr["scope"] == scope }
return if inet6.nil?

parts = cidr_ip.split('/')
@network_conf["address6_#{scope}".to_sym] = parts[0]
@network_conf["prefix6_#{scope}".to_sym] = parts[1].to_i
@network_conf["address6_#{scope}".to_sym] = inet6["local"]
@network_conf["prefix6_#{scope}".to_sym] = inet6["prefixlen"]
end
end
end
Expand Down
41 changes: 10 additions & 31 deletions spec/network_interface_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@

context "on all systems" do
let(:ip_link_args) { [LinuxAdmin::Common.cmd("ip"), {:params => %w[--json link]}] }
let(:ip_show_eth0_args) { [LinuxAdmin::Common.cmd("ip"), {:params => %w[addr show eth0]}] }
let(:ip_show_lo_args) { [LinuxAdmin::Common.cmd("ip"), {:params => %w[addr show lo]}] }
let(:ip_route_args) { [LinuxAdmin::Common.cmd("ip"), {:params => ['-4', 'route']}] }
let(:ip6_route_args) { [LinuxAdmin::Common.cmd("ip"), {:params => ['-6', 'route']}] }
let(:ip_show_eth0_args) { [LinuxAdmin::Common.cmd("ip"), {:params => %w[--json addr show eth0]}] }
let(:ip_show_lo_args) { [LinuxAdmin::Common.cmd("ip"), {:params => %w[--json addr show lo]}] }
let(:ip_route_args) { [LinuxAdmin::Common.cmd("ip"), {:params => %w[--json -4 route show default]}] }
let(:ip6_route_args) { [LinuxAdmin::Common.cmd("ip"), {:params => %w[--json -6 route show default]}] }
let(:ifup_args) { [LinuxAdmin::Common.cmd("ifup"), {:params => ["eth0"]}] }
let(:ifdown_args) { [LinuxAdmin::Common.cmd("ifdown"), {:params => ["eth0"]}] }
let(:ip_link_out) do
Expand All @@ -69,53 +69,32 @@
end
let(:ip_addr_eth0_out) do
<<~IP_OUT
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether 00:0c:29:ed:0e:8b brd ff:ff:ff:ff:ff:ff
inet 192.168.1.9/24 brd 192.168.1.255 scope global dynamic eth0
valid_lft 1297sec preferred_lft 1297sec
inet6 fe80::20c:29ff:feed:e8b/64 scope link
valid_lft forever preferred_lft forever
inet6 fd12:3456:789a:1::1/96 scope global
valid_lft forever preferred_lft forever
[{"ifindex":2,"ifname":"eth0","flags":["BROADCAST","MULTICAST","UP","LOWER_UP"],"mtu":1500,"qdisc":"fq_codel","operstate":"UP","group":"default","txqlen":1000,"link_type":"ether","address":"00:0c:29:ed:0e:8b","broadcast":"ff:ff:ff:ff:ff:ff","altnames":["enp0s2","ens2"],"addr_info":[{"family":"inet","local":"192.168.1.9","prefixlen":24,"broadcast":"192.168.255","scope":"global","noprefixroute":true,"label":"eth0","valid_life_time":4294967295,"preferred_life_time":4294967295},{"family":"inet6","local":"fe80::20c:29ff:feed:e8b","prefixlen":64,"scope":"link","noprefixroute":true,"valid_life_time":"forever","preferred_life_time":"forever"},{"family":"inet6","local":"fd12:3456:789a:1::1","prefixlen":96,"scope":"global","noprefixroute":true,"valid_life_time":"forever","preferred_life_time":"forever"}]}]
IP_OUT
end
let(:ip_addr_lo_out) do
<<~IP_OUT
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
[{"ifindex":1,"ifname":"lo","flags":["LOOPBACK","UP","LOWER_UP"],"mtu":65536,"qdisc":"noqueue","operstate":"UNKNOWN","group":"default","txqlen":1000,"link_type":"loopback","address":"00:00:00:00:00:00","broadcast":"00:00:00:00:00:00","addr_info":[{"family":"inet","local":"127.0.0.1","prefixlen":8,"scope":"host","label":"lo","valid_life_time":4294967295,"preferred_life_time":4294967295},{"family":"inet6","local":"::1","prefixlen":128,"scope":"host","valid_life_time":"forever","preferred_life_time":"forever"}]}]
IP_OUT
end
let(:ip6_addr_out) do
<<~IP_OUT
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether 00:0c:29:ed:0e:8b brd ff:ff:ff:ff:ff:ff
inet6 fe80::20c:29ff:feed:e8b/64 scope link
valid_lft forever preferred_lft forever
inet6 fd12:3456:789a:1::1/96 scope global
valid_lft forever preferred_lft forever
[{"ifindex":2,"ifname":"eth0","flags":["BROADCAST","MULTICAST","UP","LOWER_UP"],"mtu":1500,"qdisc":"fq_codel","operstate":"UP","group":"default","txqlen":1000,"link_type":"ether","address":"00:0c:29:ed:0e:8b","broadcast":"ff:ff:ff:ff:ff:ff","altnames":["enp0s2","ens2"],"addr_info":[{"family":"inet6","local":"fe80::20c:29ff:feed:e8b","prefixlen":64,"scope":"link","noprefixroute":true,"valid_life_time":"forever","preferred_life_time":"forever"},{"family":"inet6","local":"fd12:3456:789a:1::1","prefixlen":96,"scope":"global","noprefixroute":true,"valid_life_time":"forever","preferred_life_time":"forever"}]}]
IP_OUT
end
let(:ip_route_out) do
<<~IP_OUT
default via 192.168.1.1 dev eth0 proto static metric 100
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.9 metric 100
[{"dst":"default","gateway":"192.168.1.1","dev":"eth0","protocol":"static","metric":100,"flags":[]}]
IP_OUT
end
let(:ip6_route_out) do
<<~IP_OUT
default via d:e:a:d:b:e:e:f dev eth0 proto static metric 100
fc00:dead:beef:a::/64 dev virbr1 proto kernel metric 256 linkdown pref medium
fe80::/64 dev eth0 proto kernel scope link metric 100
[{"dst":"default","gateway":"d:e:a:d:b:e:e:f","dev":"eth0","protocol":"static","metric":100,"flags":[]}]
IP_OUT
end
let(:ip_none_addr_out) do
<<~IP_OUT
2: eth0: <BROADCAST,MULTICAST> mtu 1500 qdisc fq_codel master virbr1 state DOWN group default qlen 1000
link/ether 52:54:00:ce:b4:f4 brd ff:ff:ff:ff:ff:ff
[{"ifindex":2,"ifname":"eth0","flags":["BROADCAST","MULTICAST","UP","LOWER_UP"],"mtu":1500,"qdisc":"fq_codel","operstate":"UP","group":"default","txqlen":1000,"link_type":"ether","address":"00:0c:29:ed:0e:8b","broadcast":"ff:ff:ff:ff:ff:ff","altnames":["enp0s2","ens2"],"addr_info":[]}]
IP_OUT
end

Expand Down