Skip to content

Commit

Permalink
Merge pull request #1145 from yast/write_nm_changes
Browse files Browse the repository at this point in the history
Write NetworkManager config files
  • Loading branch information
teclator committed Jan 20, 2021
2 parents b3ee308 + ae3bb24 commit 2334a87
Show file tree
Hide file tree
Showing 8 changed files with 304 additions and 9 deletions.
52 changes: 46 additions & 6 deletions src/lib/cfa/nm_connection.rb
Expand Up @@ -22,28 +22,68 @@
module CFA
# Class to handle NetworkManager connection configuration files
#
# @see https://developer.gnome.org/NetworkManager/stable/nm-settings-keyfile.html
# @example Reading the connection name
# file = NmConnection.new("/etc/NetworkManager/system-connections/eth0.nmconnection")
# file.load
# puts file.connection["id"]
class NmConnection < BaseModel
KNOWN_SECTIONS = [
"bridge", "connection", "ethernet", "ipv4", "ipv6", "vlan", "wifi", "wifi_security"
].freeze

# @return [String] file path
attr_reader :path

# Constructor
#
# @param path [String] File path
# @param file_handler [.read, .write] Object to read/write the file.
def initialize(path, file_handler: nil)
super(AugeasParser.new("Networkmanager.lns"), path, file_handler: file_handler)
@path = path
# FIXME: The Networkmanager lense writes the values surrounded by double
# quotes which is not valid
super(AugeasParser.new("Puppet.lns"), @path, file_handler: file_handler)
end

# Returns the augeas tree for the connection section
# Returns the augeas tree for the given section
#
# If the "[connection]" section does not exist, it returns an empty one.
# If the given section does not exist, it returns an empty one
#
# @param name [String] section name
# @return [AugeasTree]
def connection
return data["connection"] if data["connection"]
def section_for(name)
sname = name.gsub("_", "-")
return data[sname] if data[sname]

data[sname] ||= CFA::AugeasTree.new
end

# Sets an array's property under an specific section
#
# Array properties are written as a numbered variable. This method takes
# care of numbering them according to the variable name.
#
# @example Write IPv4 addresses
# ipv4_addresses = ["192.168.1.100/24", "192.168.20.200/32"]
# file.add_collection("ipv4", "address", ipv4_addresses)
#
# # Writes:
# # [ipv4]
# # address1="192.168.1.100/24"
# # address2="192.168.20.200/32"
#
# @param section [String] section name
# @param name [String] variable name to be used
# @param values [Array<String>] variable values
def add_collection(section, name, values)
section = section_for(section)

data["connection"] ||= CFA::AugeasTree.new
values.each_with_index do |ip, index|
section["#{name}#{index + 1}"] = ip
end
end

KNOWN_SECTIONS.each { |s| define_method(s) { section_for(s) } }
end
end
10 changes: 9 additions & 1 deletion src/lib/y2network/network_manager/connection_config_writer.rb
Expand Up @@ -31,16 +31,24 @@ class ConnectionConfigWriter
def write(conn, old_conn = nil)
return if conn == old_conn

file = CFA::NmConnection.new(SYSTEM_CONNECTIONS_PATH.join(conn.name).sub_ext(FILE_EXT))
path = SYSTEM_CONNECTIONS_PATH.join(conn.name).sub_ext(FILE_EXT)
file = CFA::NmConnection.new(path)
handler_class = find_handler_class(conn.type)
return nil if handler_class.nil?

ensure_permissions(path) unless ::File.exist?(path)

handler_class.new(file).write(conn)
file.save
end

private

def ensure_permissions(path)
::FileUtils.touch(path)
::File.chmod(0o600, path)
end

# Returns the class to handle a given interface type
#
# @param type [Y2Network::InterfaceType] Interface type
Expand Down
Expand Up @@ -17,6 +17,9 @@
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require "y2network/boot_protocol"
require "securerandom"

module Y2Network
module NetworkManager
module ConnectionConfigWriters
Expand All @@ -39,11 +42,45 @@ def initialize(file)
# @param conn [Y2Network::ConnectionConfig::Base] Connection to take settings from
def write(conn)
file.connection["id"] = conn.name
file.connection["autoconnect"] = "false" if ["manual", "off"].include? conn.startmode.name
file.connection["permissions"] = nil
file.connection["interface-name"] = conn.interface
file.connection["zone"] = conn.firewall_zone unless ["", nil].include? conn.firewall_zone
conn.bootproto.dhcp? ? configure_dhcp(conn) : add_ips(conn)
update_file(conn)
end

private

# FIXME: Gateway is missing
# Convenience method for writing the static IP configuration
#
# @param conn [Y2Network::ConnectionConfig::Base] Connection to take settings from
def add_ips(conn)
ips_to_add = conn.ip_aliases.clone
ips_to_add.append(conn.ip) if conn.ip
ipv4 = ips_to_add.select { |i| i&.address&.ipv4? }.map { |i| i.address.to_s }
ipv6 = ips_to_add.select { |i| i&.address&.ipv6? }.map { |i| i.address.to_s }

unless ipv4.empty?
file.ipv4["method"] = "manual"
file.add_collection("ipv4", "address", ipv4)
end

return if ipv6.empty?

file.ipv6["method"] = "manual"
file.add_collection("ipv6", "address", ipv6)
end

# Convenience method for writing the DHCP config
#
# @param conn [Y2Network::ConnectionConfig::Base] Connection to take settings from
def configure_dhcp(conn)
file.ipv4["method"] = "auto" if conn.bootproto != Y2Network::BootProtocol::DHCP6
file.ipv6["method"] = "auto" if conn.bootproto != Y2Network::BootProtocol::DHCP4
end

# Sets file values from the given connection configuration
#
# @note This method should be redefined by derived classes.
Expand Down
Expand Up @@ -26,7 +26,9 @@ module ConnectionConfigWriters
# object to the underlying system.
class Ethernet < Base
# @see Y2Network::ConnectionConfigWriters::Base#update_file
def update_file(_conn); end
def update_file(_conn)
file.connection["type"] = "ethernet"
end
end
end
end
Expand Down
@@ -0,0 +1,38 @@
# Copyright (c) [2021] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require "y2network/network_manager/connection_config_writers/base"

module Y2Network
module NetworkManager
module ConnectionConfigWriters
# This class is responsible for writing the information from a ConnectionConfig::Vlan
# object to the underlying system.
class Vlan < Base
# @see Y2Network::ConnectionConfigWriters::Base#update_file
# @param conn [Y2Network::ConnectionConfig::Vlan] Configuration to write
def update_file(conn)
file.vlan["id"] = conn.vlan_id
file.vlan["parent"] = conn.parent_device
file.vlan["type"] = "vlan"
end
end
end
end
end
@@ -0,0 +1,83 @@
# Copyright (c) [2021] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require "y2network/network_manager/connection_config_writers/base"

module Y2Network
module NetworkManager
module ConnectionConfigWriters
# This class is responsible for writing the information from a ConnectionConfig::Wireless
# object to the underlying system.
class Wireless < Base
MODE = { "ad-hoc" => "ad-hoc", "master" => "ap", "managed" => "infrastructure" }.freeze

# @see Y2Network::ConnectionConfigWriters::Base#update_file
def update_file(conn)
file.connection["type"] = "wifi"
file.wifi["ssid"] = conn.essid unless conn.essid.to_s.empty?
file.wifi["mode"] = MODE[conn.mode]
file.wifi["channel"] = con.channel if conn.channel

write_auth_settings(conn)
end

# Writes autentication settings for WPA-EAP networks
#
# @param _conn [Y2Network::ConnectionConfig::Base] Configuration to write
def write_eap_auth_settings(_conn)
# FIXME: incomplete
file.wifi_security["key-mgmt"] = "wpa-eap"
# wrong section name
#
# file.802_1x["eap"] = conn.eap_mode
# file.802_1x["phase2-auth"] = conn.eap_auth
end

# Writes authentication settings
#
# This method relies in `write_*_auth_settings` methods.
#
#
# @param conn [Y2Network::ConnectionConfig::Base] Configuration to write
#
# @see #write_eap_auth_settings
# @see #write_psk_auth_settings
# @see #write_shared_auth_settings
def write_auth_settings(conn)
meth = "write_#{conn.auth_mode}_auth_settings".to_sym
send(meth, conn) if respond_to?(meth, true)
end

# Writes autentication settings for WPA-PSK networks
#
# @param conn [Y2Network::ConnectionConfig::Base] Configuration to write
def write_psk_auth_settings(conn)
file.wifi_security["auth-alg"] = "open"
file.wifi_security["key-mgmt"] = "wpa-psk"
file.wifi_security["psk"] = conn.wpa_psk
end

# Writes autentication settings for WEP networks
#
# @param _conn [Y2Network::ConnectionConfig::Base] Configuration to write
def write_shared_auth_settings(_conn); end
end
end
end
end
17 changes: 16 additions & 1 deletion test/y2network/network_manager/connection_config_writer_test.rb
Expand Up @@ -51,8 +51,10 @@
Y2Network::ConnectionConfig::IPConfig.new(Y2Network::IPAddress.from_string("10.100.0.1/24"))
end

let(:path) { "/etc/NetworkManager/system-connections/eth0.nmconnection" }

let(:file) do
instance_double(CFA::NmConnection, save: nil)
instance_double(CFA::NmConnection, save: nil, path: path)
end

describe "#write" do
Expand All @@ -67,6 +69,8 @@
allow(Y2Network::NetworkManager::ConnectionConfigWriters::Ethernet).to receive(:new)
.and_return(handler)
allow(CFA::NmConnection).to receive(:new).and_return(file)
allow(writer).to receive(:ensure_permissions)
allow(::File).to receive(:exist?).with(Pathname.new(path)).and_return(true)
end

it "uses the appropiate handler" do
Expand All @@ -75,6 +79,17 @@
writer.write(conn)
end

context "when the file does not exist" do
before do
allow(::File).to receive(:exist?).with(Pathname.new(path)).and_return(false)
end

it "ensures the file is created with the the correct permissions" do
expect(writer).to receive(:ensure_permissions).with(Pathname.new(path))
writer.write(conn)
end
end

it "does nothing if the connection has not changed"
end
end

0 comments on commit 2334a87

Please sign in to comment.