|
|
544061 |
From abea98a9b918c0771ad10b314238b32c570f0372 Mon Sep 17 00:00:00 2001
|
|
|
544061 |
From: François Cami <fcami@redhat.com>
|
|
|
544061 |
Date: Aug 29 2019 06:45:12 +0000
|
|
|
544061 |
Subject: ipatests: check that ipa-client-automount restores nsswitch.conf at uninstall time
|
|
|
544061 |
|
|
|
544061 |
|
|
|
544061 |
Check that using ipa-client-install, ipa-client-automount --no-ssd, then uninstalling
|
|
|
544061 |
both properly restores nsswitch.conf sequentially.
|
|
|
544061 |
|
|
|
544061 |
Related-to:: https://pagure.io/freeipa/issue/8038
|
|
|
544061 |
Signed-off-by: François Cami <fcami@redhat.com>
|
|
|
544061 |
Reviewed-By: Francois Cami <fcami@redhat.com>
|
|
|
544061 |
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
|
|
544061 |
Reviewed-By: Rob Critenden <rcritten@redhat.com>
|
|
|
544061 |
Reviewed-By: François Cami <fcami@redhat.com>
|
|
|
544061 |
|
|
|
544061 |
---
|
|
|
544061 |
|
|
|
544061 |
#diff --git a/ipatests/prci_definitions/nightly_ipa-4-8.yaml b/ipatests/prci_definitions/nightly_ipa-4-8.yaml
|
|
|
544061 |
#index ef5d2c6..f39e4b4 100644
|
|
|
544061 |
#--- a/ipatests/prci_definitions/nightly_ipa-4-8.yaml
|
|
|
544061 |
#+++ b/ipatests/prci_definitions/nightly_ipa-4-8.yaml
|
|
|
544061 |
#@@ -1257,6 +1257,18 @@ jobs:
|
|
|
544061 |
# timeout: 9000
|
|
|
544061 |
# topology: *master_3client
|
|
|
544061 |
#
|
|
|
544061 |
#+ fedora-30/nfs_nsswitch_restore:
|
|
|
544061 |
#+ requires: [fedora-30/build]
|
|
|
544061 |
#+ priority: 50
|
|
|
544061 |
#+ job:
|
|
|
544061 |
#+ class: RunPytest
|
|
|
544061 |
#+ args:
|
|
|
544061 |
#+ build_url: '{fedora-30/build_url}'
|
|
|
544061 |
#+ test_suite: test_integration/test_nfs.py::TestIpaClientAutomountFileRestore
|
|
|
544061 |
#+ template: *ci-master-f30
|
|
|
544061 |
#+ timeout: 3600
|
|
|
544061 |
#+ topology: *master_3client
|
|
|
544061 |
#+
|
|
|
544061 |
# fedora-30/mask:
|
|
|
544061 |
# requires: [fedora-30/build]
|
|
|
544061 |
# priority: 50
|
|
|
544061 |
diff --git a/ipatests/test_integration/test_nfs.py b/ipatests/test_integration/test_nfs.py
|
|
|
544061 |
index adfc19f..0e1ef6a 100644
|
|
|
544061 |
--- a/ipatests/test_integration/test_nfs.py
|
|
|
544061 |
+++ b/ipatests/test_integration/test_nfs.py
|
|
|
544061 |
@@ -15,6 +15,7 @@
|
|
|
544061 |
|
|
|
544061 |
from __future__ import absolute_import
|
|
|
544061 |
|
|
|
544061 |
+import pytest
|
|
|
544061 |
import os
|
|
|
544061 |
import re
|
|
|
544061 |
import time
|
|
|
544061 |
@@ -258,3 +259,74 @@ class TestNFS(IntegrationTest):
|
|
|
544061 |
time.sleep(WAIT_AFTER_UNINSTALL)
|
|
|
544061 |
|
|
|
544061 |
self.cleanup()
|
|
|
544061 |
+
|
|
|
544061 |
+
|
|
|
544061 |
+class TestIpaClientAutomountFileRestore(IntegrationTest):
|
|
|
544061 |
+
|
|
|
544061 |
+ num_clients = 1
|
|
|
544061 |
+ topology = 'line'
|
|
|
544061 |
+
|
|
|
544061 |
+ @classmethod
|
|
|
544061 |
+ def install(cls, mh):
|
|
|
544061 |
+ tasks.install_master(cls.master, setup_dns=True)
|
|
|
544061 |
+
|
|
|
544061 |
+ def teardown_method(self, method):
|
|
|
544061 |
+ tasks.uninstall_client(self.clients[0])
|
|
|
544061 |
+
|
|
|
544061 |
+ def nsswitch_backup_restore(
|
|
|
544061 |
+ self,
|
|
|
544061 |
+ no_sssd=False,
|
|
|
544061 |
+ ):
|
|
|
544061 |
+
|
|
|
544061 |
+ # In order to get a more pure sum, one that ignores the Generated
|
|
|
544061 |
+ # header and any white space we have to do a bit of work...
|
|
|
544061 |
+ sha256nsswitch_cmd = \
|
|
|
544061 |
+ 'egrep -v "Generated|^$" /etc/nsswitch.conf | sed "s/\\s//g" ' \
|
|
|
544061 |
+ '| sort | sha256sum'
|
|
|
544061 |
+
|
|
|
544061 |
+ cmd = self.clients[0].run_command(sha256nsswitch_cmd)
|
|
|
544061 |
+ orig_sha256 = cmd.stdout_text
|
|
|
544061 |
+
|
|
|
544061 |
+ grep_automount_command = \
|
|
|
544061 |
+ "grep automount /etc/nsswitch.conf | cut -d: -f2"
|
|
|
544061 |
+
|
|
|
544061 |
+ tasks.install_client(self.master, self.clients[0])
|
|
|
544061 |
+ cmd = self.clients[0].run_command(grep_automount_command)
|
|
|
544061 |
+ after_ipa_client_install = cmd.stdout_text.split()
|
|
|
544061 |
+
|
|
|
544061 |
+ if no_sssd:
|
|
|
544061 |
+ ipa_client_automount_command = [
|
|
|
544061 |
+ "ipa-client-automount", "--no-sssd", "-U"
|
|
|
544061 |
+ ]
|
|
|
544061 |
+ else:
|
|
|
544061 |
+ ipa_client_automount_command = [
|
|
|
544061 |
+ "ipa-client-automount", "-U"
|
|
|
544061 |
+ ]
|
|
|
544061 |
+ self.clients[0].run_command(ipa_client_automount_command)
|
|
|
544061 |
+ cmd = self.clients[0].run_command(grep_automount_command)
|
|
|
544061 |
+ after_ipa_client_automount = cmd.stdout_text.split()
|
|
|
544061 |
+ if no_sssd:
|
|
|
544061 |
+ assert after_ipa_client_automount == ['files', 'ldap']
|
|
|
544061 |
+ else:
|
|
|
544061 |
+ assert after_ipa_client_automount == ['sss', 'files']
|
|
|
544061 |
+
|
|
|
544061 |
+ cmd = self.clients[0].run_command(grep_automount_command)
|
|
|
544061 |
+ assert cmd.stdout_text.split() == after_ipa_client_automount
|
|
|
544061 |
+
|
|
|
544061 |
+ self.clients[0].run_command([
|
|
|
544061 |
+ "ipa-client-automount", "--uninstall", "-U"
|
|
|
544061 |
+ ])
|
|
|
544061 |
+
|
|
|
544061 |
+ cmd = self.clients[0].run_command(grep_automount_command)
|
|
|
544061 |
+ assert cmd.stdout_text.split() == after_ipa_client_install
|
|
|
544061 |
+
|
|
|
544061 |
+ tasks.uninstall_client(self.clients[0])
|
|
|
544061 |
+ cmd = self.clients[0].run_command(sha256nsswitch_cmd)
|
|
|
544061 |
+ assert cmd.stdout_text == orig_sha256
|
|
|
544061 |
+
|
|
|
544061 |
+ @pytest.mark.xfail(reason='freeipa ticket 8054', strict=True)
|
|
|
544061 |
+ def test_nsswitch_backup_restore_sssd(self):
|
|
|
544061 |
+ self.nsswitch_backup_restore()
|
|
|
544061 |
+
|
|
|
544061 |
+ def test_nsswitch_backup_restore_no_sssd(self):
|
|
|
544061 |
+ self.nsswitch_backup_restore(no_sssd=True)
|
|
|
544061 |
|
|
|
544061 |
From 2f0afeda6e66fcca5c184a4036112fcd315f2f6e Mon Sep 17 00:00:00 2001
|
|
|
544061 |
From: François Cami <fcami@redhat.com>
|
|
|
544061 |
Date: Aug 29 2019 06:45:12 +0000
|
|
|
544061 |
Subject: ipa-client-automount: always restore nsswitch.conf at uninstall time
|
|
|
544061 |
|
|
|
544061 |
|
|
|
544061 |
ipa-client-automount used to only restore nsswitch.conf when sssd was not
|
|
|
544061 |
used. However authselect's default profile is now sssd so always restore
|
|
|
544061 |
nsswitch.conf's automount configuration to 'files sssd'.
|
|
|
544061 |
Note that the behavior seen before commit:
|
|
|
544061 |
a0e846f56c8de3b549d1d284087131da13135e34
|
|
|
544061 |
would always restore nsswitch.conf to the previous state which in some cases
|
|
|
544061 |
was wrong.
|
|
|
544061 |
|
|
|
544061 |
Fixes: https://pagure.io/freeipa/issue/8038
|
|
|
544061 |
Signed-off-by: François Cami <fcami@redhat.com>
|
|
|
544061 |
Reviewed-By: Francois Cami <fcami@redhat.com>
|
|
|
544061 |
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
|
|
544061 |
Reviewed-By: Rob Critenden <rcritten@redhat.com>
|
|
|
544061 |
Reviewed-By: François Cami <fcami@redhat.com>
|
|
|
544061 |
|
|
|
544061 |
---
|
|
|
544061 |
|
|
|
544061 |
diff --git a/ipaclient/install/ipa_client_automount.py b/ipaclient/install/ipa_client_automount.py
|
|
|
544061 |
index fa07598..a1dc2a1 100644
|
|
|
544061 |
--- a/ipaclient/install/ipa_client_automount.py
|
|
|
544061 |
+++ b/ipaclient/install/ipa_client_automount.py
|
|
|
544061 |
@@ -177,18 +177,30 @@ def configure_xml(fstore):
|
|
|
544061 |
print("Configured %s" % authconf)
|
|
|
544061 |
|
|
|
544061 |
|
|
|
544061 |
-def configure_nsswitch(fstore, options):
|
|
|
544061 |
+def configure_nsswitch(statestore, options):
|
|
|
544061 |
"""
|
|
|
544061 |
- Point automount to ldap in nsswitch.conf. This function is for non-SSSD
|
|
|
544061 |
- setups only
|
|
|
544061 |
+ Point automount to ldap in nsswitch.conf.
|
|
|
544061 |
+ This function is for non-SSSD setups only.
|
|
|
544061 |
"""
|
|
|
544061 |
- fstore.backup_file(paths.NSSWITCH_CONF)
|
|
|
544061 |
-
|
|
|
544061 |
conf = ipachangeconf.IPAChangeConf("IPA Installer")
|
|
|
544061 |
conf.setOptionAssignment(':')
|
|
|
544061 |
|
|
|
544061 |
- nss_value = ' files ldap'
|
|
|
544061 |
+ with open(paths.NSSWITCH_CONF, 'r') as f:
|
|
|
544061 |
+ current_opts = conf.parse(f)
|
|
|
544061 |
+ current_nss_value = conf.findOpts(
|
|
|
544061 |
+ current_opts, name='automount', type='option'
|
|
|
544061 |
+ )[1]
|
|
|
544061 |
+ if current_nss_value is None:
|
|
|
544061 |
+ # no automount database present
|
|
|
544061 |
+ current_nss_value = False # None cannot be backed up
|
|
|
544061 |
+ else:
|
|
|
544061 |
+ current_nss_value = current_nss_value['value']
|
|
|
544061 |
+ statestore.backup_state(
|
|
|
544061 |
+ 'ipa-client-automount-nsswitch', 'previous-automount',
|
|
|
544061 |
+ current_nss_value
|
|
|
544061 |
+ )
|
|
|
544061 |
|
|
|
544061 |
+ nss_value = ' files ldap'
|
|
|
544061 |
opts = [
|
|
|
544061 |
{
|
|
|
544061 |
'name': 'automount',
|
|
|
544061 |
@@ -198,7 +210,6 @@ def configure_nsswitch(fstore, options):
|
|
|
544061 |
},
|
|
|
544061 |
{'name': 'empty', 'type': 'empty'},
|
|
|
544061 |
]
|
|
|
544061 |
-
|
|
|
544061 |
conf.changeConf(paths.NSSWITCH_CONF, opts)
|
|
|
544061 |
|
|
|
544061 |
print("Configured %s" % paths.NSSWITCH_CONF)
|
|
|
544061 |
@@ -322,19 +333,47 @@ def configure_autofs_common(fstore, statestore, options):
|
|
|
544061 |
def uninstall(fstore, statestore):
|
|
|
544061 |
RESTORE_FILES = [
|
|
|
544061 |
paths.SYSCONFIG_AUTOFS,
|
|
|
544061 |
- paths.NSSWITCH_CONF,
|
|
|
544061 |
paths.AUTOFS_LDAP_AUTH_CONF,
|
|
|
544061 |
paths.SYSCONFIG_NFS,
|
|
|
544061 |
paths.IDMAPD_CONF,
|
|
|
544061 |
]
|
|
|
544061 |
STATES = ['autofs', 'rpcidmapd', 'rpcgssd']
|
|
|
544061 |
|
|
|
544061 |
- # automount only touches /etc/nsswitch.conf if LDAP is
|
|
|
544061 |
- # used. Don't restore it otherwise.
|
|
|
544061 |
- if statestore.get_state('authconfig', 'sssd') or (
|
|
|
544061 |
- statestore.get_state('authselect', 'profile') == 'sssd'
|
|
|
544061 |
- ):
|
|
|
544061 |
- RESTORE_FILES.remove(paths.NSSWITCH_CONF)
|
|
|
544061 |
+ if statestore.get_state(
|
|
|
544061 |
+ 'ipa-client-automount-nsswitch', 'previous-automount'
|
|
|
544061 |
+ ) is False:
|
|
|
544061 |
+ # Previous nsswitch.conf had no automount database configured
|
|
|
544061 |
+ # so remove it.
|
|
|
544061 |
+ conf = ipachangeconf.IPAChangeConf("IPA automount installer")
|
|
|
544061 |
+ conf.setOptionAssignment(':')
|
|
|
544061 |
+ changes = [conf.rmOption('automount')]
|
|
|
544061 |
+ conf.changeConf(paths.NSSWITCH_CONF, changes)
|
|
|
544061 |
+ tasks.restore_context(paths.NSSWITCH_CONF)
|
|
|
544061 |
+ statestore.delete_state(
|
|
|
544061 |
+ 'ipa-client-automount-nsswitch', 'previous-automount'
|
|
|
544061 |
+ )
|
|
|
544061 |
+ elif statestore.get_state(
|
|
|
544061 |
+ 'ipa-client-automount-nsswitch', 'previous-automount'
|
|
|
544061 |
+ ) is not None:
|
|
|
544061 |
+ nss_value = statestore.get_state(
|
|
|
544061 |
+ 'ipa-client-automount-nsswitch', 'previous-automount'
|
|
|
544061 |
+ )
|
|
|
544061 |
+ opts = [
|
|
|
544061 |
+ {
|
|
|
544061 |
+ 'name': 'automount',
|
|
|
544061 |
+ 'type': 'option',
|
|
|
544061 |
+ 'action': 'set',
|
|
|
544061 |
+ 'value': nss_value,
|
|
|
544061 |
+ },
|
|
|
544061 |
+ {'name': 'empty', 'type': 'empty'},
|
|
|
544061 |
+ ]
|
|
|
544061 |
+ conf = ipachangeconf.IPAChangeConf("IPA automount installer")
|
|
|
544061 |
+ conf.setOptionAssignment(':')
|
|
|
544061 |
+ conf.changeConf(paths.NSSWITCH_CONF, opts)
|
|
|
544061 |
+ tasks.restore_context(paths.NSSWITCH_CONF)
|
|
|
544061 |
+ statestore.delete_state(
|
|
|
544061 |
+ 'ipa-client-automount-nsswitch', 'previous-automount'
|
|
|
544061 |
+ )
|
|
|
544061 |
|
|
|
544061 |
if not any(fstore.has_file(f) for f in RESTORE_FILES) or not any(
|
|
|
544061 |
statestore.has_state(s) for s in STATES
|
|
|
544061 |
@@ -588,7 +627,7 @@ def configure_automount():
|
|
|
544061 |
|
|
|
544061 |
try:
|
|
|
544061 |
if not options.sssd:
|
|
|
544061 |
- configure_nsswitch(fstore, options)
|
|
|
544061 |
+ configure_nsswitch(statestore, options)
|
|
|
544061 |
configure_nfs(fstore, statestore, options)
|
|
|
544061 |
if options.sssd:
|
|
|
544061 |
configure_autofs_sssd(fstore, statestore, autodiscover, options)
|
|
|
544061 |
|
|
|
544061 |
From 6e92776bfc199e9ca92e11ef3315dcecad3c9307 Mon Sep 17 00:00:00 2001
|
|
|
544061 |
From: Rob Critenden <rcritten@redhat.com>
|
|
|
544061 |
Date: Aug 29 2019 06:45:12 +0000
|
|
|
544061 |
Subject: Move ipachangeconf from ipaclient.install to ipapython
|
|
|
544061 |
|
|
|
544061 |
|
|
|
544061 |
This will let us call it from ipaplatform.
|
|
|
544061 |
|
|
|
544061 |
Mark the original location as deprecated.
|
|
|
544061 |
|
|
|
544061 |
Reviewed-By: Francois Cami <fcami@redhat.com>
|
|
|
544061 |
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
|
|
544061 |
Reviewed-By: Rob Critenden <rcritten@redhat.com>
|
|
|
544061 |
Reviewed-By: François Cami <fcami@redhat.com>
|
|
|
544061 |
|
|
|
544061 |
---
|
|
|
544061 |
|
|
|
544061 |
diff --git a/install/tools/ipa-replica-conncheck.in b/install/tools/ipa-replica-conncheck.in
|
|
|
544061 |
index 9208076..b22db11 100644
|
|
|
544061 |
--- a/install/tools/ipa-replica-conncheck.in
|
|
|
544061 |
+++ b/install/tools/ipa-replica-conncheck.in
|
|
|
544061 |
@@ -22,7 +22,7 @@ from __future__ import print_function
|
|
|
544061 |
|
|
|
544061 |
import logging
|
|
|
544061 |
|
|
|
544061 |
-import ipaclient.install.ipachangeconf
|
|
|
544061 |
+from ipapython import ipachangeconf
|
|
|
544061 |
from ipapython.config import IPAOptionParser
|
|
|
544061 |
from ipapython.dn import DN
|
|
|
544061 |
from ipapython import version
|
|
|
544061 |
@@ -229,7 +229,7 @@ def sigterm_handler(signum, frame):
|
|
|
544061 |
|
|
|
544061 |
def configure_krb5_conf(realm, kdc, filename):
|
|
|
544061 |
|
|
|
544061 |
- krbconf = ipaclient.install.ipachangeconf.IPAChangeConf("IPA Installer")
|
|
|
544061 |
+ krbconf = ipachangeconf.IPAChangeConf("IPA Installer")
|
|
|
544061 |
krbconf.setOptionAssignment((" = ", " "))
|
|
|
544061 |
krbconf.setSectionNameDelimiters(("[","]"))
|
|
|
544061 |
krbconf.setSubSectionDelimiters(("{","}"))
|
|
|
544061 |
diff --git a/ipaclient/install/ipachangeconf.py b/ipaclient/install/ipachangeconf.py
|
|
|
544061 |
index a13e0ea..c51e42e 100644
|
|
|
544061 |
--- a/ipaclient/install/ipachangeconf.py
|
|
|
544061 |
+++ b/ipaclient/install/ipachangeconf.py
|
|
|
544061 |
@@ -18,566 +18,18 @@
|
|
|
544061 |
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
544061 |
#
|
|
|
544061 |
|
|
|
544061 |
-import fcntl
|
|
|
544061 |
-import logging
|
|
|
544061 |
-import os
|
|
|
544061 |
-import shutil
|
|
|
544061 |
+import warnings
|
|
|
544061 |
+from ipapython.ipachangeconf import IPAChangeConf as realIPAChangeConf
|
|
|
544061 |
|
|
|
544061 |
-import six
|
|
|
544061 |
|
|
|
544061 |
-if six.PY3:
|
|
|
544061 |
- unicode = str
|
|
|
544061 |
+class IPAChangeConf(realIPAChangeConf):
|
|
|
544061 |
+ """Advertise the old name"""
|
|
|
544061 |
|
|
|
544061 |
-logger = logging.getLogger(__name__)
|
|
|
544061 |
-
|
|
|
544061 |
-def openLocked(filename, perms):
|
|
|
544061 |
- fd = -1
|
|
|
544061 |
- try:
|
|
|
544061 |
- fd = os.open(filename, os.O_RDWR | os.O_CREAT, perms)
|
|
|
544061 |
-
|
|
|
544061 |
- fcntl.lockf(fd, fcntl.LOCK_EX)
|
|
|
544061 |
- except OSError as e:
|
|
|
544061 |
- if fd != -1:
|
|
|
544061 |
- try:
|
|
|
544061 |
- os.close(fd)
|
|
|
544061 |
- except OSError:
|
|
|
544061 |
- pass
|
|
|
544061 |
- raise IOError(e.errno, e.strerror)
|
|
|
544061 |
- return os.fdopen(fd, "r+")
|
|
|
544061 |
-
|
|
|
544061 |
-
|
|
|
544061 |
- #TODO: add subsection as a concept
|
|
|
544061 |
- # (ex. REALM.NAME = { foo = x bar = y } )
|
|
|
544061 |
- #TODO: put section delimiters as separating element of the list
|
|
|
544061 |
- # so that we can process multiple sections in one go
|
|
|
544061 |
- #TODO: add a comment all but provided options as a section option
|
|
|
544061 |
-class IPAChangeConf:
|
|
|
544061 |
def __init__(self, name):
|
|
|
544061 |
- self.progname = name
|
|
|
544061 |
- self.indent = ("", "", "")
|
|
|
544061 |
- self.assign = (" = ", "=")
|
|
|
544061 |
- self.dassign = self.assign[0]
|
|
|
544061 |
- self.comment = ("#",)
|
|
|
544061 |
- self.dcomment = self.comment[0]
|
|
|
544061 |
- self.eol = ("\n",)
|
|
|
544061 |
- self.deol = self.eol[0]
|
|
|
544061 |
- self.sectnamdel = ("[", "]")
|
|
|
544061 |
- self.subsectdel = ("{", "}")
|
|
|
544061 |
- self.case_insensitive_sections = True
|
|
|
544061 |
-
|
|
|
544061 |
- def setProgName(self, name):
|
|
|
544061 |
- self.progname = name
|
|
|
544061 |
-
|
|
|
544061 |
- def setIndent(self, indent):
|
|
|
544061 |
- if type(indent) is tuple:
|
|
|
544061 |
- self.indent = indent
|
|
|
544061 |
- elif type(indent) is str:
|
|
|
544061 |
- self.indent = (indent, )
|
|
|
544061 |
- else:
|
|
|
544061 |
- raise ValueError('Indent must be a list of strings')
|
|
|
544061 |
-
|
|
|
544061 |
- def setOptionAssignment(self, assign):
|
|
|
544061 |
- if type(assign) is tuple:
|
|
|
544061 |
- self.assign = assign
|
|
|
544061 |
- else:
|
|
|
544061 |
- self.assign = (assign, )
|
|
|
544061 |
- self.dassign = self.assign[0]
|
|
|
544061 |
-
|
|
|
544061 |
- def setCommentPrefix(self, comment):
|
|
|
544061 |
- if type(comment) is tuple:
|
|
|
544061 |
- self.comment = comment
|
|
|
544061 |
- else:
|
|
|
544061 |
- self.comment = (comment, )
|
|
|
544061 |
- self.dcomment = self.comment[0]
|
|
|
544061 |
-
|
|
|
544061 |
- def setEndLine(self, eol):
|
|
|
544061 |
- if type(eol) is tuple:
|
|
|
544061 |
- self.eol = eol
|
|
|
544061 |
- else:
|
|
|
544061 |
- self.eol = (eol, )
|
|
|
544061 |
- self.deol = self.eol[0]
|
|
|
544061 |
-
|
|
|
544061 |
- def setSectionNameDelimiters(self, delims):
|
|
|
544061 |
- self.sectnamdel = delims
|
|
|
544061 |
-
|
|
|
544061 |
- def setSubSectionDelimiters(self, delims):
|
|
|
544061 |
- self.subsectdel = delims
|
|
|
544061 |
-
|
|
|
544061 |
- def matchComment(self, line):
|
|
|
544061 |
- for v in self.comment:
|
|
|
544061 |
- if line.lstrip().startswith(v):
|
|
|
544061 |
- return line.lstrip()[len(v):]
|
|
|
544061 |
- return False
|
|
|
544061 |
-
|
|
|
544061 |
- def matchEmpty(self, line):
|
|
|
544061 |
- if line.strip() == "":
|
|
|
544061 |
- return True
|
|
|
544061 |
- return False
|
|
|
544061 |
-
|
|
|
544061 |
- def matchSection(self, line):
|
|
|
544061 |
- cl = "".join(line.strip().split())
|
|
|
544061 |
- cl = cl.lower() if self.case_insensitive_sections else cl
|
|
|
544061 |
-
|
|
|
544061 |
- if len(self.sectnamdel) != 2:
|
|
|
544061 |
- return False
|
|
|
544061 |
- if not cl.startswith(self.sectnamdel[0]):
|
|
|
544061 |
- return False
|
|
|
544061 |
- if not cl.endswith(self.sectnamdel[1]):
|
|
|
544061 |
- return False
|
|
|
544061 |
- return cl[len(self.sectnamdel[0]):-len(self.sectnamdel[1])]
|
|
|
544061 |
-
|
|
|
544061 |
- def matchSubSection(self, line):
|
|
|
544061 |
- if self.matchComment(line):
|
|
|
544061 |
- return False
|
|
|
544061 |
-
|
|
|
544061 |
- parts = line.split(self.dassign, 1)
|
|
|
544061 |
- if len(parts) < 2:
|
|
|
544061 |
- return False
|
|
|
544061 |
-
|
|
|
544061 |
- if parts[1].strip() == self.subsectdel[0]:
|
|
|
544061 |
- return parts[0].strip()
|
|
|
544061 |
-
|
|
|
544061 |
- return False
|
|
|
544061 |
-
|
|
|
544061 |
- def matchSubSectionEnd(self, line):
|
|
|
544061 |
- if self.matchComment(line):
|
|
|
544061 |
- return False
|
|
|
544061 |
-
|
|
|
544061 |
- if line.strip() == self.subsectdel[1]:
|
|
|
544061 |
- return True
|
|
|
544061 |
-
|
|
|
544061 |
- return False
|
|
|
544061 |
-
|
|
|
544061 |
- def getSectionLine(self, section):
|
|
|
544061 |
- if len(self.sectnamdel) != 2:
|
|
|
544061 |
- return section
|
|
|
544061 |
- return self._dump_line(self.sectnamdel[0],
|
|
|
544061 |
- section,
|
|
|
544061 |
- self.sectnamdel[1],
|
|
|
544061 |
- self.deol)
|
|
|
544061 |
-
|
|
|
544061 |
- def _dump_line(self, *args):
|
|
|
544061 |
- return u"".join(unicode(x) for x in args)
|
|
|
544061 |
-
|
|
|
544061 |
- def dump(self, options, level=0):
|
|
|
544061 |
- output = []
|
|
|
544061 |
- if level >= len(self.indent):
|
|
|
544061 |
- level = len(self.indent) - 1
|
|
|
544061 |
-
|
|
|
544061 |
- for o in options:
|
|
|
544061 |
- if o['type'] == "section":
|
|
|
544061 |
- output.append(self._dump_line(self.sectnamdel[0],
|
|
|
544061 |
- o['name'],
|
|
|
544061 |
- self.sectnamdel[1]))
|
|
|
544061 |
- output.append(self.dump(o['value'], (level + 1)))
|
|
|
544061 |
- continue
|
|
|
544061 |
- if o['type'] == "subsection":
|
|
|
544061 |
- output.append(self._dump_line(self.indent[level],
|
|
|
544061 |
- o['name'],
|
|
|
544061 |
- self.dassign,
|
|
|
544061 |
- self.subsectdel[0]))
|
|
|
544061 |
- output.append(self.dump(o['value'], (level + 1)))
|
|
|
544061 |
- output.append(self._dump_line(self.indent[level],
|
|
|
544061 |
- self.subsectdel[1]))
|
|
|
544061 |
- continue
|
|
|
544061 |
- if o['type'] == "option":
|
|
|
544061 |
- delim = o.get('delim', self.dassign)
|
|
|
544061 |
- if delim not in self.assign:
|
|
|
544061 |
- raise ValueError('Unknown delim "%s" must be one of "%s"' % (delim, " ".join([d for d in self.assign])))
|
|
|
544061 |
- output.append(self._dump_line(self.indent[level],
|
|
|
544061 |
- o['name'],
|
|
|
544061 |
- delim,
|
|
|
544061 |
- o['value']))
|
|
|
544061 |
- continue
|
|
|
544061 |
- if o['type'] == "comment":
|
|
|
544061 |
- output.append(self._dump_line(self.dcomment, o['value']))
|
|
|
544061 |
- continue
|
|
|
544061 |
- if o['type'] == "empty":
|
|
|
544061 |
- output.append('')
|
|
|
544061 |
- continue
|
|
|
544061 |
- raise SyntaxError('Unknown type: [%s]' % o['type'])
|
|
|
544061 |
-
|
|
|
544061 |
- # append an empty string to the output so that we add eol to the end
|
|
|
544061 |
- # of the file contents in a single join()
|
|
|
544061 |
- output.append('')
|
|
|
544061 |
- return self.deol.join(output)
|
|
|
544061 |
-
|
|
|
544061 |
- def parseLine(self, line):
|
|
|
544061 |
-
|
|
|
544061 |
- if self.matchEmpty(line):
|
|
|
544061 |
- return {'name': 'empty', 'type': 'empty'}
|
|
|
544061 |
-
|
|
|
544061 |
- value = self.matchComment(line)
|
|
|
544061 |
- if value:
|
|
|
544061 |
- return {'name': 'comment',
|
|
|
544061 |
- 'type': 'comment',
|
|
|
544061 |
- 'value': value.rstrip()} # pylint: disable=E1103
|
|
|
544061 |
-
|
|
|
544061 |
- o = dict()
|
|
|
544061 |
- parts = line.split(self.dassign, 1)
|
|
|
544061 |
- if len(parts) < 2:
|
|
|
544061 |
- # The default assign didn't match, try the non-default
|
|
|
544061 |
- for d in self.assign[1:]:
|
|
|
544061 |
- parts = line.split(d, 1)
|
|
|
544061 |
- if len(parts) >= 2:
|
|
|
544061 |
- o['delim'] = d
|
|
|
544061 |
- break
|
|
|
544061 |
-
|
|
|
544061 |
- if 'delim' not in o:
|
|
|
544061 |
- raise SyntaxError('Syntax Error: Unknown line format')
|
|
|
544061 |
-
|
|
|
544061 |
- o.update({'name':parts[0].strip(), 'type':'option', 'value':parts[1].rstrip()})
|
|
|
544061 |
- return o
|
|
|
544061 |
-
|
|
|
544061 |
- def findOpts(self, opts, type, name, exclude_sections=False):
|
|
|
544061 |
-
|
|
|
544061 |
- num = 0
|
|
|
544061 |
- for o in opts:
|
|
|
544061 |
- if o['type'] == type and o['name'] == name:
|
|
|
544061 |
- return (num, o)
|
|
|
544061 |
- if exclude_sections and (o['type'] == "section" or
|
|
|
544061 |
- o['type'] == "subsection"):
|
|
|
544061 |
- return (num, None)
|
|
|
544061 |
- num += 1
|
|
|
544061 |
- return (num, None)
|
|
|
544061 |
-
|
|
|
544061 |
- def commentOpts(self, inopts, level=0):
|
|
|
544061 |
-
|
|
|
544061 |
- opts = []
|
|
|
544061 |
-
|
|
|
544061 |
- if level >= len(self.indent):
|
|
|
544061 |
- level = len(self.indent) - 1
|
|
|
544061 |
-
|
|
|
544061 |
- for o in inopts:
|
|
|
544061 |
- if o['type'] == 'section':
|
|
|
544061 |
- no = self.commentOpts(o['value'], (level + 1))
|
|
|
544061 |
- val = self._dump_line(self.dcomment,
|
|
|
544061 |
- self.sectnamdel[0],
|
|
|
544061 |
- o['name'],
|
|
|
544061 |
- self.sectnamdel[1])
|
|
|
544061 |
- opts.append({'name': 'comment',
|
|
|
544061 |
- 'type': 'comment',
|
|
|
544061 |
- 'value': val})
|
|
|
544061 |
- for n in no:
|
|
|
544061 |
- opts.append(n)
|
|
|
544061 |
- continue
|
|
|
544061 |
- if o['type'] == 'subsection':
|
|
|
544061 |
- no = self.commentOpts(o['value'], (level + 1))
|
|
|
544061 |
- val = self._dump_line(self.indent[level],
|
|
|
544061 |
- o['name'],
|
|
|
544061 |
- self.dassign,
|
|
|
544061 |
- self.subsectdel[0])
|
|
|
544061 |
- opts.append({'name': 'comment',
|
|
|
544061 |
- 'type': 'comment',
|
|
|
544061 |
- 'value': val})
|
|
|
544061 |
- opts.extend(no)
|
|
|
544061 |
- val = self._dump_line(self.indent[level], self.subsectdel[1])
|
|
|
544061 |
- opts.append({'name': 'comment',
|
|
|
544061 |
- 'type': 'comment',
|
|
|
544061 |
- 'value': val})
|
|
|
544061 |
- continue
|
|
|
544061 |
- if o['type'] == 'option':
|
|
|
544061 |
- delim = o.get('delim', self.dassign)
|
|
|
544061 |
- if delim not in self.assign:
|
|
|
544061 |
- val = self._dump_line(self.indent[level],
|
|
|
544061 |
- o['name'],
|
|
|
544061 |
- delim,
|
|
|
544061 |
- o['value'])
|
|
|
544061 |
- opts.append({'name':'comment', 'type':'comment', 'value':val})
|
|
|
544061 |
- continue
|
|
|
544061 |
- if o['type'] == 'comment':
|
|
|
544061 |
- opts.append(o)
|
|
|
544061 |
- continue
|
|
|
544061 |
- if o['type'] == 'empty':
|
|
|
544061 |
- opts.append({'name': 'comment',
|
|
|
544061 |
- 'type': 'comment',
|
|
|
544061 |
- 'value': ''})
|
|
|
544061 |
- continue
|
|
|
544061 |
- raise SyntaxError('Unknown type: [%s]' % o['type'])
|
|
|
544061 |
-
|
|
|
544061 |
- return opts
|
|
|
544061 |
-
|
|
|
544061 |
- def mergeOld(self, oldopts, newopts):
|
|
|
544061 |
-
|
|
|
544061 |
- opts = []
|
|
|
544061 |
-
|
|
|
544061 |
- for o in oldopts:
|
|
|
544061 |
- if o['type'] == "section" or o['type'] == "subsection":
|
|
|
544061 |
- _num, no = self.findOpts(newopts, o['type'], o['name'])
|
|
|
544061 |
- if not no:
|
|
|
544061 |
- opts.append(o)
|
|
|
544061 |
- continue
|
|
|
544061 |
- if no['action'] == "set":
|
|
|
544061 |
- mo = self.mergeOld(o['value'], no['value'])
|
|
|
544061 |
- opts.append({'name': o['name'],
|
|
|
544061 |
- 'type': o['type'],
|
|
|
544061 |
- 'value': mo})
|
|
|
544061 |
- continue
|
|
|
544061 |
- if no['action'] == "comment":
|
|
|
544061 |
- co = self.commentOpts(o['value'])
|
|
|
544061 |
- for c in co:
|
|
|
544061 |
- opts.append(c)
|
|
|
544061 |
- continue
|
|
|
544061 |
- if no['action'] == "remove":
|
|
|
544061 |
- continue
|
|
|
544061 |
- raise SyntaxError('Unknown action: [%s]' % no['action'])
|
|
|
544061 |
-
|
|
|
544061 |
- if o['type'] == "comment" or o['type'] == "empty":
|
|
|
544061 |
- opts.append(o)
|
|
|
544061 |
- continue
|
|
|
544061 |
-
|
|
|
544061 |
- if o['type'] == "option":
|
|
|
544061 |
- _num, no = self.findOpts(newopts, 'option', o['name'], True)
|
|
|
544061 |
- if not no:
|
|
|
544061 |
- opts.append(o)
|
|
|
544061 |
- continue
|
|
|
544061 |
- if no['action'] == 'comment' or no['action'] == 'remove':
|
|
|
544061 |
- if (no['value'] is not None and
|
|
|
544061 |
- o['value'] is not no['value']):
|
|
|
544061 |
- opts.append(o)
|
|
|
544061 |
- continue
|
|
|
544061 |
- if no['action'] == 'comment':
|
|
|
544061 |
- value = self._dump_line(self.dcomment,
|
|
|
544061 |
- o['name'],
|
|
|
544061 |
- self.dassign,
|
|
|
544061 |
- o['value'])
|
|
|
544061 |
- opts.append({'name': 'comment',
|
|
|
544061 |
- 'type': 'comment',
|
|
|
544061 |
- 'value': value})
|
|
|
544061 |
- continue
|
|
|
544061 |
- if no['action'] == 'set':
|
|
|
544061 |
- opts.append(no)
|
|
|
544061 |
- continue
|
|
|
544061 |
- if no['action'] == 'addifnotset':
|
|
|
544061 |
- opts.append({
|
|
|
544061 |
- 'name': 'comment',
|
|
|
544061 |
- 'type': 'comment',
|
|
|
544061 |
- 'value': self._dump_line(
|
|
|
544061 |
- ' ', no['name'], ' modified by IPA'
|
|
|
544061 |
- ),
|
|
|
544061 |
- })
|
|
|
544061 |
- opts.append({'name': 'comment', 'type': 'comment',
|
|
|
544061 |
- 'value': self._dump_line(no['name'],
|
|
|
544061 |
- self.dassign,
|
|
|
544061 |
- no['value'],
|
|
|
544061 |
- )})
|
|
|
544061 |
- opts.append(o)
|
|
|
544061 |
- continue
|
|
|
544061 |
- raise SyntaxError('Unknown action: [%s]' % no['action'])
|
|
|
544061 |
-
|
|
|
544061 |
- raise SyntaxError('Unknown type: [%s]' % o['type'])
|
|
|
544061 |
-
|
|
|
544061 |
- return opts
|
|
|
544061 |
-
|
|
|
544061 |
- def mergeNew(self, opts, newopts):
|
|
|
544061 |
-
|
|
|
544061 |
- cline = 0
|
|
|
544061 |
-
|
|
|
544061 |
- for no in newopts:
|
|
|
544061 |
-
|
|
|
544061 |
- if no['type'] == "section" or no['type'] == "subsection":
|
|
|
544061 |
- (num, o) = self.findOpts(opts, no['type'], no['name'])
|
|
|
544061 |
- if not o:
|
|
|
544061 |
- if no['action'] == 'set':
|
|
|
544061 |
- opts.append(no)
|
|
|
544061 |
- continue
|
|
|
544061 |
- if no['action'] == "set":
|
|
|
544061 |
- self.mergeNew(o['value'], no['value'])
|
|
|
544061 |
- continue
|
|
|
544061 |
- cline = num + 1
|
|
|
544061 |
- continue
|
|
|
544061 |
-
|
|
|
544061 |
- if no['type'] == "option":
|
|
|
544061 |
- (num, o) = self.findOpts(opts, no['type'], no['name'], True)
|
|
|
544061 |
- if not o:
|
|
|
544061 |
- if no['action'] == 'set' or no['action'] == 'addifnotset':
|
|
|
544061 |
- opts.append(no)
|
|
|
544061 |
- continue
|
|
|
544061 |
- cline = num + 1
|
|
|
544061 |
- continue
|
|
|
544061 |
-
|
|
|
544061 |
- if no['type'] == "comment" or no['type'] == "empty":
|
|
|
544061 |
- opts.insert(cline, no)
|
|
|
544061 |
- cline += 1
|
|
|
544061 |
- continue
|
|
|
544061 |
-
|
|
|
544061 |
- raise SyntaxError('Unknown type: [%s]' % no['type'])
|
|
|
544061 |
-
|
|
|
544061 |
- def merge(self, oldopts, newopts):
|
|
|
544061 |
- """
|
|
|
544061 |
- Uses a two pass strategy:
|
|
|
544061 |
- First we create a new opts tree from oldopts removing/commenting
|
|
|
544061 |
- the options as indicated by the contents of newopts
|
|
|
544061 |
- Second we fill in the new opts tree with options as indicated
|
|
|
544061 |
- in the newopts tree (this is becaus eentire (sub)sections may
|
|
|
544061 |
- in the newopts tree (this is becaus entire (sub)sections may
|
|
|
544061 |
- exist in the newopts that do not exist in oldopts)
|
|
|
544061 |
- """
|
|
|
544061 |
- opts = self.mergeOld(oldopts, newopts)
|
|
|
544061 |
- self.mergeNew(opts, newopts)
|
|
|
544061 |
- return opts
|
|
|
544061 |
-
|
|
|
544061 |
- #TODO: Make parse() recursive?
|
|
|
544061 |
- def parse(self, f):
|
|
|
544061 |
-
|
|
|
544061 |
- opts = []
|
|
|
544061 |
- sectopts = []
|
|
|
544061 |
- section = None
|
|
|
544061 |
- subsectopts = []
|
|
|
544061 |
- subsection = None
|
|
|
544061 |
- curopts = opts
|
|
|
544061 |
- fatheropts = opts
|
|
|
544061 |
-
|
|
|
544061 |
- # Read in the old file.
|
|
|
544061 |
- for line in f:
|
|
|
544061 |
-
|
|
|
544061 |
- # It's a section start.
|
|
|
544061 |
- value = self.matchSection(line)
|
|
|
544061 |
- if value:
|
|
|
544061 |
- if section is not None:
|
|
|
544061 |
- opts.append({'name': section,
|
|
|
544061 |
- 'type': 'section',
|
|
|
544061 |
- 'value': sectopts})
|
|
|
544061 |
- sectopts = []
|
|
|
544061 |
- curopts = sectopts
|
|
|
544061 |
- fatheropts = sectopts
|
|
|
544061 |
- section = value
|
|
|
544061 |
- continue
|
|
|
544061 |
-
|
|
|
544061 |
- # It's a subsection start.
|
|
|
544061 |
- value = self.matchSubSection(line)
|
|
|
544061 |
- if value:
|
|
|
544061 |
- if subsection is not None:
|
|
|
544061 |
- raise SyntaxError('nested subsections are not '
|
|
|
544061 |
- 'supported yet')
|
|
|
544061 |
- subsectopts = []
|
|
|
544061 |
- curopts = subsectopts
|
|
|
544061 |
- subsection = value
|
|
|
544061 |
- continue
|
|
|
544061 |
-
|
|
|
544061 |
- value = self.matchSubSectionEnd(line)
|
|
|
544061 |
- if value:
|
|
|
544061 |
- if subsection is None:
|
|
|
544061 |
- raise SyntaxError('Unmatched end subsection terminator '
|
|
|
544061 |
- 'found')
|
|
|
544061 |
- fatheropts.append({'name': subsection,
|
|
|
544061 |
- 'type': 'subsection',
|
|
|
544061 |
- 'value': subsectopts})
|
|
|
544061 |
- subsection = None
|
|
|
544061 |
- curopts = fatheropts
|
|
|
544061 |
- continue
|
|
|
544061 |
-
|
|
|
544061 |
- # Copy anything else as is.
|
|
|
544061 |
- try:
|
|
|
544061 |
- curopts.append(self.parseLine(line))
|
|
|
544061 |
- except SyntaxError as e:
|
|
|
544061 |
- raise SyntaxError('{error} in file {fname}: [{line}]'.format(
|
|
|
544061 |
- error=e, fname=f.name, line=line.rstrip()))
|
|
|
544061 |
-
|
|
|
544061 |
- #Add last section if any
|
|
|
544061 |
- if len(sectopts) is not 0:
|
|
|
544061 |
- opts.append({'name': section,
|
|
|
544061 |
- 'type': 'section',
|
|
|
544061 |
- 'value': sectopts})
|
|
|
544061 |
-
|
|
|
544061 |
- return opts
|
|
|
544061 |
-
|
|
|
544061 |
- def changeConf(self, file, newopts):
|
|
|
544061 |
- """
|
|
|
544061 |
- Write settings to configuration file
|
|
|
544061 |
- :param file: path to the file
|
|
|
544061 |
- :param options: set of dictionaries in the form:
|
|
|
544061 |
- {'name': 'foo', 'value': 'bar', 'action': 'set/comment'}
|
|
|
544061 |
- :param section: section name like 'global'
|
|
|
544061 |
- """
|
|
|
544061 |
- output = ""
|
|
|
544061 |
- f = None
|
|
|
544061 |
- try:
|
|
|
544061 |
- # Do not catch an unexisting file error
|
|
|
544061 |
- # we want to fail in that case
|
|
|
544061 |
- shutil.copy2(file, (file + ".ipabkp"))
|
|
|
544061 |
-
|
|
|
544061 |
- f = openLocked(file, 0o644)
|
|
|
544061 |
-
|
|
|
544061 |
- oldopts = self.parse(f)
|
|
|
544061 |
-
|
|
|
544061 |
- options = self.merge(oldopts, newopts)
|
|
|
544061 |
-
|
|
|
544061 |
- output = self.dump(options)
|
|
|
544061 |
-
|
|
|
544061 |
- # Write it out and close it.
|
|
|
544061 |
- f.seek(0)
|
|
|
544061 |
- f.truncate(0)
|
|
|
544061 |
- f.write(output)
|
|
|
544061 |
- finally:
|
|
|
544061 |
- try:
|
|
|
544061 |
- if f:
|
|
|
544061 |
- f.close()
|
|
|
544061 |
- except IOError:
|
|
|
544061 |
- pass
|
|
|
544061 |
- logger.debug("Updating configuration file %s", file)
|
|
|
544061 |
- logger.debug(output)
|
|
|
544061 |
- return True
|
|
|
544061 |
-
|
|
|
544061 |
- def newConf(self, file, options, file_perms=0o644):
|
|
|
544061 |
- """"
|
|
|
544061 |
- Write settings to a new file, backup the old
|
|
|
544061 |
- :param file: path to the file
|
|
|
544061 |
- :param options: a set of dictionaries in the form:
|
|
|
544061 |
- {'name': 'foo', 'value': 'bar', 'action': 'set/comment'}
|
|
|
544061 |
- :param file_perms: number defining the new file's permissions
|
|
|
544061 |
- """
|
|
|
544061 |
- output = ""
|
|
|
544061 |
- f = None
|
|
|
544061 |
- try:
|
|
|
544061 |
- try:
|
|
|
544061 |
- shutil.copy2(file, (file + ".ipabkp"))
|
|
|
544061 |
- except IOError as err:
|
|
|
544061 |
- if err.errno == 2:
|
|
|
544061 |
- # The orign file did not exist
|
|
|
544061 |
- pass
|
|
|
544061 |
-
|
|
|
544061 |
- f = openLocked(file, file_perms)
|
|
|
544061 |
-
|
|
|
544061 |
- # Trunkate
|
|
|
544061 |
- f.seek(0)
|
|
|
544061 |
- f.truncate(0)
|
|
|
544061 |
-
|
|
|
544061 |
- output = self.dump(options)
|
|
|
544061 |
-
|
|
|
544061 |
- f.write(output)
|
|
|
544061 |
- finally:
|
|
|
544061 |
- try:
|
|
|
544061 |
- if f:
|
|
|
544061 |
- f.close()
|
|
|
544061 |
- except IOError:
|
|
|
544061 |
- pass
|
|
|
544061 |
- logger.debug("Writing configuration file %s", file)
|
|
|
544061 |
- logger.debug(output)
|
|
|
544061 |
- return True
|
|
|
544061 |
-
|
|
|
544061 |
- @staticmethod
|
|
|
544061 |
- def setOption(name, value):
|
|
|
544061 |
- return {'name': name,
|
|
|
544061 |
- 'type': 'option',
|
|
|
544061 |
- 'action': 'set',
|
|
|
544061 |
- 'value': value}
|
|
|
544061 |
-
|
|
|
544061 |
- @staticmethod
|
|
|
544061 |
- def rmOption(name):
|
|
|
544061 |
- return {'name': name,
|
|
|
544061 |
- 'type': 'option',
|
|
|
544061 |
- 'action': 'remove',
|
|
|
544061 |
- 'value': None}
|
|
|
544061 |
-
|
|
|
544061 |
- @staticmethod
|
|
|
544061 |
- def setSection(name, options):
|
|
|
544061 |
- return {'name': name,
|
|
|
544061 |
- 'type': 'section',
|
|
|
544061 |
- 'action': 'set',
|
|
|
544061 |
- 'value': options}
|
|
|
544061 |
-
|
|
|
544061 |
- @staticmethod
|
|
|
544061 |
- def emptyLine():
|
|
|
544061 |
- return {'name': 'empty',
|
|
|
544061 |
- 'type': 'empty'}
|
|
|
544061 |
+ """something"""
|
|
|
544061 |
+ warnings.warn(
|
|
|
544061 |
+ "Use 'ipapython.ipachangeconf.IPAChangeConfg'",
|
|
|
544061 |
+ DeprecationWarning,
|
|
|
544061 |
+ stacklevel=2
|
|
|
544061 |
+ )
|
|
|
544061 |
+ super(IPAChangeConf, self).__init__(name)
|
|
|
544061 |
diff --git a/ipapython/ipachangeconf.py b/ipapython/ipachangeconf.py
|
|
|
544061 |
new file mode 100644
|
|
|
544061 |
index 0000000..cfb4a6e
|
|
|
544061 |
--- /dev/null
|
|
|
544061 |
+++ b/ipapython/ipachangeconf.py
|
|
|
544061 |
@@ -0,0 +1,590 @@
|
|
|
544061 |
+#
|
|
|
544061 |
+# ipachangeconf - configuration file manipulation classes and functions
|
|
|
544061 |
+# partially based on authconfig code
|
|
|
544061 |
+# Copyright (c) 1999-2007 Red Hat, Inc.
|
|
|
544061 |
+# Author: Simo Sorce <ssorce@redhat.com>
|
|
|
544061 |
+#
|
|
|
544061 |
+# This program is free software; you can redistribute it and/or modify
|
|
|
544061 |
+# it under the terms of the GNU General Public License as published by
|
|
|
544061 |
+# the Free Software Foundation, either version 3 of the License, or
|
|
|
544061 |
+# (at your option) any later version.
|
|
|
544061 |
+#
|
|
|
544061 |
+# This program is distributed in the hope that it will be useful,
|
|
|
544061 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
544061 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
544061 |
+# GNU General Public License for more details.
|
|
|
544061 |
+#
|
|
|
544061 |
+# You should have received a copy of the GNU General Public License
|
|
|
544061 |
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
544061 |
+#
|
|
|
544061 |
+
|
|
|
544061 |
+import fcntl
|
|
|
544061 |
+import logging
|
|
|
544061 |
+import os
|
|
|
544061 |
+import shutil
|
|
|
544061 |
+
|
|
|
544061 |
+import six
|
|
|
544061 |
+
|
|
|
544061 |
+if six.PY3:
|
|
|
544061 |
+ unicode = str
|
|
|
544061 |
+
|
|
|
544061 |
+logger = logging.getLogger(__name__)
|
|
|
544061 |
+
|
|
|
544061 |
+
|
|
|
544061 |
+def openLocked(filename, perms):
|
|
|
544061 |
+ fd = -1
|
|
|
544061 |
+ try:
|
|
|
544061 |
+ fd = os.open(filename, os.O_RDWR | os.O_CREAT, perms)
|
|
|
544061 |
+
|
|
|
544061 |
+ fcntl.lockf(fd, fcntl.LOCK_EX)
|
|
|
544061 |
+ except OSError as e:
|
|
|
544061 |
+ if fd != -1:
|
|
|
544061 |
+ try:
|
|
|
544061 |
+ os.close(fd)
|
|
|
544061 |
+ except OSError:
|
|
|
544061 |
+ pass
|
|
|
544061 |
+ raise IOError(e.errno, e.strerror)
|
|
|
544061 |
+ return os.fdopen(fd, "r+")
|
|
|
544061 |
+
|
|
|
544061 |
+ # TODO: add subsection as a concept
|
|
|
544061 |
+ # (ex. REALM.NAME = { foo = x bar = y } )
|
|
|
544061 |
+ # TODO: put section delimiters as separating element of the list
|
|
|
544061 |
+ # so that we can process multiple sections in one go
|
|
|
544061 |
+ # TODO: add a comment all but provided options as a section option
|
|
|
544061 |
+
|
|
|
544061 |
+
|
|
|
544061 |
+class IPAChangeConf:
|
|
|
544061 |
+ def __init__(self, name):
|
|
|
544061 |
+ self.progname = name
|
|
|
544061 |
+ self.indent = ("", "", "")
|
|
|
544061 |
+ self.assign = (" = ", "=")
|
|
|
544061 |
+ self.dassign = self.assign[0]
|
|
|
544061 |
+ self.comment = ("#",)
|
|
|
544061 |
+ self.dcomment = self.comment[0]
|
|
|
544061 |
+ self.eol = ("\n",)
|
|
|
544061 |
+ self.deol = self.eol[0]
|
|
|
544061 |
+ self.sectnamdel = ("[", "]")
|
|
|
544061 |
+ self.subsectdel = ("{", "}")
|
|
|
544061 |
+ self.case_insensitive_sections = True
|
|
|
544061 |
+
|
|
|
544061 |
+ def setProgName(self, name):
|
|
|
544061 |
+ self.progname = name
|
|
|
544061 |
+
|
|
|
544061 |
+ def setIndent(self, indent):
|
|
|
544061 |
+ if type(indent) is tuple:
|
|
|
544061 |
+ self.indent = indent
|
|
|
544061 |
+ elif type(indent) is str:
|
|
|
544061 |
+ self.indent = (indent, )
|
|
|
544061 |
+ else:
|
|
|
544061 |
+ raise ValueError('Indent must be a list of strings')
|
|
|
544061 |
+
|
|
|
544061 |
+ def setOptionAssignment(self, assign):
|
|
|
544061 |
+ if type(assign) is tuple:
|
|
|
544061 |
+ self.assign = assign
|
|
|
544061 |
+ else:
|
|
|
544061 |
+ self.assign = (assign, )
|
|
|
544061 |
+ self.dassign = self.assign[0]
|
|
|
544061 |
+
|
|
|
544061 |
+ def setCommentPrefix(self, comment):
|
|
|
544061 |
+ if type(comment) is tuple:
|
|
|
544061 |
+ self.comment = comment
|
|
|
544061 |
+ else:
|
|
|
544061 |
+ self.comment = (comment, )
|
|
|
544061 |
+ self.dcomment = self.comment[0]
|
|
|
544061 |
+
|
|
|
544061 |
+ def setEndLine(self, eol):
|
|
|
544061 |
+ if type(eol) is tuple:
|
|
|
544061 |
+ self.eol = eol
|
|
|
544061 |
+ else:
|
|
|
544061 |
+ self.eol = (eol, )
|
|
|
544061 |
+ self.deol = self.eol[0]
|
|
|
544061 |
+
|
|
|
544061 |
+ def setSectionNameDelimiters(self, delims):
|
|
|
544061 |
+ self.sectnamdel = delims
|
|
|
544061 |
+
|
|
|
544061 |
+ def setSubSectionDelimiters(self, delims):
|
|
|
544061 |
+ self.subsectdel = delims
|
|
|
544061 |
+
|
|
|
544061 |
+ def matchComment(self, line):
|
|
|
544061 |
+ for v in self.comment:
|
|
|
544061 |
+ if line.lstrip().startswith(v):
|
|
|
544061 |
+ return line.lstrip()[len(v):]
|
|
|
544061 |
+ return False
|
|
|
544061 |
+
|
|
|
544061 |
+ def matchEmpty(self, line):
|
|
|
544061 |
+ if line.strip() == "":
|
|
|
544061 |
+ return True
|
|
|
544061 |
+ return False
|
|
|
544061 |
+
|
|
|
544061 |
+ def matchSection(self, line):
|
|
|
544061 |
+ cl = "".join(line.strip().split())
|
|
|
544061 |
+ cl = cl.lower() if self.case_insensitive_sections else cl
|
|
|
544061 |
+
|
|
|
544061 |
+ if len(self.sectnamdel) != 2:
|
|
|
544061 |
+ return False
|
|
|
544061 |
+ if not cl.startswith(self.sectnamdel[0]):
|
|
|
544061 |
+ return False
|
|
|
544061 |
+ if not cl.endswith(self.sectnamdel[1]):
|
|
|
544061 |
+ return False
|
|
|
544061 |
+ return cl[len(self.sectnamdel[0]):-len(self.sectnamdel[1])]
|
|
|
544061 |
+
|
|
|
544061 |
+ def matchSubSection(self, line):
|
|
|
544061 |
+ if self.matchComment(line):
|
|
|
544061 |
+ return False
|
|
|
544061 |
+
|
|
|
544061 |
+ parts = line.split(self.dassign, 1)
|
|
|
544061 |
+ if len(parts) < 2:
|
|
|
544061 |
+ return False
|
|
|
544061 |
+
|
|
|
544061 |
+ if parts[1].strip() == self.subsectdel[0]:
|
|
|
544061 |
+ return parts[0].strip()
|
|
|
544061 |
+
|
|
|
544061 |
+ return False
|
|
|
544061 |
+
|
|
|
544061 |
+ def matchSubSectionEnd(self, line):
|
|
|
544061 |
+ if self.matchComment(line):
|
|
|
544061 |
+ return False
|
|
|
544061 |
+
|
|
|
544061 |
+ if line.strip() == self.subsectdel[1]:
|
|
|
544061 |
+ return True
|
|
|
544061 |
+
|
|
|
544061 |
+ return False
|
|
|
544061 |
+
|
|
|
544061 |
+ def getSectionLine(self, section):
|
|
|
544061 |
+ if len(self.sectnamdel) != 2:
|
|
|
544061 |
+ return section
|
|
|
544061 |
+ return self._dump_line(self.sectnamdel[0],
|
|
|
544061 |
+ section,
|
|
|
544061 |
+ self.sectnamdel[1],
|
|
|
544061 |
+ self.deol)
|
|
|
544061 |
+
|
|
|
544061 |
+ def _dump_line(self, *args):
|
|
|
544061 |
+ return u"".join(unicode(x) for x in args)
|
|
|
544061 |
+
|
|
|
544061 |
+ def dump(self, options, level=0):
|
|
|
544061 |
+ output = []
|
|
|
544061 |
+ if level >= len(self.indent):
|
|
|
544061 |
+ level = len(self.indent) - 1
|
|
|
544061 |
+
|
|
|
544061 |
+ for o in options:
|
|
|
544061 |
+ if o['type'] == "section":
|
|
|
544061 |
+ output.append(self._dump_line(self.sectnamdel[0],
|
|
|
544061 |
+ o['name'],
|
|
|
544061 |
+ self.sectnamdel[1]))
|
|
|
544061 |
+ output.append(self.dump(o['value'], (level + 1)))
|
|
|
544061 |
+ continue
|
|
|
544061 |
+ if o['type'] == "subsection":
|
|
|
544061 |
+ output.append(self._dump_line(self.indent[level],
|
|
|
544061 |
+ o['name'],
|
|
|
544061 |
+ self.dassign,
|
|
|
544061 |
+ self.subsectdel[0]))
|
|
|
544061 |
+ output.append(self.dump(o['value'], (level + 1)))
|
|
|
544061 |
+ output.append(self._dump_line(self.indent[level],
|
|
|
544061 |
+ self.subsectdel[1]))
|
|
|
544061 |
+ continue
|
|
|
544061 |
+ if o['type'] == "option":
|
|
|
544061 |
+ delim = o.get('delim', self.dassign)
|
|
|
544061 |
+ if delim not in self.assign:
|
|
|
544061 |
+ raise ValueError(
|
|
|
544061 |
+ 'Unknown delim "%s" must be one of "%s"' %
|
|
|
544061 |
+ (delim, " ".join([d for d in self.assign]))
|
|
|
544061 |
+ )
|
|
|
544061 |
+ output.append(self._dump_line(self.indent[level],
|
|
|
544061 |
+ o['name'],
|
|
|
544061 |
+ delim,
|
|
|
544061 |
+ o['value']))
|
|
|
544061 |
+ continue
|
|
|
544061 |
+ if o['type'] == "comment":
|
|
|
544061 |
+ output.append(self._dump_line(self.dcomment, o['value']))
|
|
|
544061 |
+ continue
|
|
|
544061 |
+ if o['type'] == "empty":
|
|
|
544061 |
+ output.append('')
|
|
|
544061 |
+ continue
|
|
|
544061 |
+ raise SyntaxError('Unknown type: [%s]' % o['type'])
|
|
|
544061 |
+
|
|
|
544061 |
+ # append an empty string to the output so that we add eol to the end
|
|
|
544061 |
+ # of the file contents in a single join()
|
|
|
544061 |
+ output.append('')
|
|
|
544061 |
+ return self.deol.join(output)
|
|
|
544061 |
+
|
|
|
544061 |
+ def parseLine(self, line):
|
|
|
544061 |
+
|
|
|
544061 |
+ if self.matchEmpty(line):
|
|
|
544061 |
+ return {'name': 'empty', 'type': 'empty'}
|
|
|
544061 |
+
|
|
|
544061 |
+ value = self.matchComment(line)
|
|
|
544061 |
+ if value:
|
|
|
544061 |
+ return {'name': 'comment',
|
|
|
544061 |
+ 'type': 'comment',
|
|
|
544061 |
+ 'value': value.rstrip()} # pylint: disable=E1103
|
|
|
544061 |
+
|
|
|
544061 |
+ o = dict()
|
|
|
544061 |
+ parts = line.split(self.dassign, 1)
|
|
|
544061 |
+ if len(parts) < 2:
|
|
|
544061 |
+ # The default assign didn't match, try the non-default
|
|
|
544061 |
+ for d in self.assign[1:]:
|
|
|
544061 |
+ parts = line.split(d, 1)
|
|
|
544061 |
+ if len(parts) >= 2:
|
|
|
544061 |
+ o['delim'] = d
|
|
|
544061 |
+ break
|
|
|
544061 |
+
|
|
|
544061 |
+ if 'delim' not in o:
|
|
|
544061 |
+ raise SyntaxError('Syntax Error: Unknown line format')
|
|
|
544061 |
+
|
|
|
544061 |
+ o.update({'name': parts[0].strip(), 'type': 'option',
|
|
|
544061 |
+ 'value': parts[1].rstrip()})
|
|
|
544061 |
+ return o
|
|
|
544061 |
+
|
|
|
544061 |
+ def findOpts(self, opts, type, name, exclude_sections=False):
|
|
|
544061 |
+
|
|
|
544061 |
+ num = 0
|
|
|
544061 |
+ for o in opts:
|
|
|
544061 |
+ if o['type'] == type and o['name'] == name:
|
|
|
544061 |
+ return (num, o)
|
|
|
544061 |
+ if exclude_sections and (o['type'] == "section" or
|
|
|
544061 |
+ o['type'] == "subsection"):
|
|
|
544061 |
+ return (num, None)
|
|
|
544061 |
+ num += 1
|
|
|
544061 |
+ return (num, None)
|
|
|
544061 |
+
|
|
|
544061 |
+ def commentOpts(self, inopts, level=0):
|
|
|
544061 |
+
|
|
|
544061 |
+ opts = []
|
|
|
544061 |
+
|
|
|
544061 |
+ if level >= len(self.indent):
|
|
|
544061 |
+ level = len(self.indent) - 1
|
|
|
544061 |
+
|
|
|
544061 |
+ for o in inopts:
|
|
|
544061 |
+ if o['type'] == 'section':
|
|
|
544061 |
+ no = self.commentOpts(o['value'], (level + 1))
|
|
|
544061 |
+ val = self._dump_line(self.dcomment,
|
|
|
544061 |
+ self.sectnamdel[0],
|
|
|
544061 |
+ o['name'],
|
|
|
544061 |
+ self.sectnamdel[1])
|
|
|
544061 |
+ opts.append({'name': 'comment',
|
|
|
544061 |
+ 'type': 'comment',
|
|
|
544061 |
+ 'value': val})
|
|
|
544061 |
+ for n in no:
|
|
|
544061 |
+ opts.append(n)
|
|
|
544061 |
+ continue
|
|
|
544061 |
+ if o['type'] == 'subsection':
|
|
|
544061 |
+ no = self.commentOpts(o['value'], (level + 1))
|
|
|
544061 |
+ val = self._dump_line(self.indent[level],
|
|
|
544061 |
+ o['name'],
|
|
|
544061 |
+ self.dassign,
|
|
|
544061 |
+ self.subsectdel[0])
|
|
|
544061 |
+ opts.append({'name': 'comment',
|
|
|
544061 |
+ 'type': 'comment',
|
|
|
544061 |
+ 'value': val})
|
|
|
544061 |
+ opts.extend(no)
|
|
|
544061 |
+ val = self._dump_line(self.indent[level], self.subsectdel[1])
|
|
|
544061 |
+ opts.append({'name': 'comment',
|
|
|
544061 |
+ 'type': 'comment',
|
|
|
544061 |
+ 'value': val})
|
|
|
544061 |
+ continue
|
|
|
544061 |
+ if o['type'] == 'option':
|
|
|
544061 |
+ delim = o.get('delim', self.dassign)
|
|
|
544061 |
+ if delim not in self.assign:
|
|
|
544061 |
+ val = self._dump_line(self.indent[level],
|
|
|
544061 |
+ o['name'],
|
|
|
544061 |
+ delim,
|
|
|
544061 |
+ o['value'])
|
|
|
544061 |
+ opts.append({'name': 'comment', 'type': 'comment',
|
|
|
544061 |
+ 'value': val})
|
|
|
544061 |
+ continue
|
|
|
544061 |
+ if o['type'] == 'comment':
|
|
|
544061 |
+ opts.append(o)
|
|
|
544061 |
+ continue
|
|
|
544061 |
+ if o['type'] == 'empty':
|
|
|
544061 |
+ opts.append({'name': 'comment',
|
|
|
544061 |
+ 'type': 'comment',
|
|
|
544061 |
+ 'value': ''})
|
|
|
544061 |
+ continue
|
|
|
544061 |
+ raise SyntaxError('Unknown type: [%s]' % o['type'])
|
|
|
544061 |
+
|
|
|
544061 |
+ return opts
|
|
|
544061 |
+
|
|
|
544061 |
+ def mergeOld(self, oldopts, newopts):
|
|
|
544061 |
+
|
|
|
544061 |
+ opts = []
|
|
|
544061 |
+
|
|
|
544061 |
+ for o in oldopts:
|
|
|
544061 |
+ if o['type'] == "section" or o['type'] == "subsection":
|
|
|
544061 |
+ _num, no = self.findOpts(newopts, o['type'], o['name'])
|
|
|
544061 |
+ if not no:
|
|
|
544061 |
+ opts.append(o)
|
|
|
544061 |
+ continue
|
|
|
544061 |
+ if no['action'] == "set":
|
|
|
544061 |
+ mo = self.mergeOld(o['value'], no['value'])
|
|
|
544061 |
+ opts.append({'name': o['name'],
|
|
|
544061 |
+ 'type': o['type'],
|
|
|
544061 |
+ 'value': mo})
|
|
|
544061 |
+ continue
|
|
|
544061 |
+ if no['action'] == "comment":
|
|
|
544061 |
+ co = self.commentOpts(o['value'])
|
|
|
544061 |
+ for c in co:
|
|
|
544061 |
+ opts.append(c)
|
|
|
544061 |
+ continue
|
|
|
544061 |
+ if no['action'] == "remove":
|
|
|
544061 |
+ continue
|
|
|
544061 |
+ raise SyntaxError('Unknown action: [%s]' % no['action'])
|
|
|
544061 |
+
|
|
|
544061 |
+ if o['type'] == "comment" or o['type'] == "empty":
|
|
|
544061 |
+ opts.append(o)
|
|
|
544061 |
+ continue
|
|
|
544061 |
+
|
|
|
544061 |
+ if o['type'] == "option":
|
|
|
544061 |
+ _num, no = self.findOpts(newopts, 'option', o['name'], True)
|
|
|
544061 |
+ if not no:
|
|
|
544061 |
+ opts.append(o)
|
|
|
544061 |
+ continue
|
|
|
544061 |
+ if no['action'] == 'comment' or no['action'] == 'remove':
|
|
|
544061 |
+ if (no['value'] is not None and
|
|
|
544061 |
+ o['value'] is not no['value']):
|
|
|
544061 |
+ opts.append(o)
|
|
|
544061 |
+ continue
|
|
|
544061 |
+ if no['action'] == 'comment':
|
|
|
544061 |
+ value = self._dump_line(self.dcomment,
|
|
|
544061 |
+ o['name'],
|
|
|
544061 |
+ self.dassign,
|
|
|
544061 |
+ o['value'])
|
|
|
544061 |
+ opts.append({'name': 'comment',
|
|
|
544061 |
+ 'type': 'comment',
|
|
|
544061 |
+ 'value': value})
|
|
|
544061 |
+ continue
|
|
|
544061 |
+ if no['action'] == 'set':
|
|
|
544061 |
+ opts.append(no)
|
|
|
544061 |
+ continue
|
|
|
544061 |
+ if no['action'] == 'addifnotset':
|
|
|
544061 |
+ opts.append({
|
|
|
544061 |
+ 'name': 'comment',
|
|
|
544061 |
+ 'type': 'comment',
|
|
|
544061 |
+ 'value': self._dump_line(
|
|
|
544061 |
+ ' ', no['name'], ' modified by IPA'
|
|
|
544061 |
+ ),
|
|
|
544061 |
+ })
|
|
|
544061 |
+ opts.append({'name': 'comment', 'type': 'comment',
|
|
|
544061 |
+ 'value': self._dump_line(no['name'],
|
|
|
544061 |
+ self.dassign,
|
|
|
544061 |
+ no['value'],
|
|
|
544061 |
+ )})
|
|
|
544061 |
+ opts.append(o)
|
|
|
544061 |
+ continue
|
|
|
544061 |
+ raise SyntaxError('Unknown action: [%s]' % no['action'])
|
|
|
544061 |
+
|
|
|
544061 |
+ raise SyntaxError('Unknown type: [%s]' % o['type'])
|
|
|
544061 |
+
|
|
|
544061 |
+ return opts
|
|
|
544061 |
+
|
|
|
544061 |
+ def mergeNew(self, opts, newopts):
|
|
|
544061 |
+
|
|
|
544061 |
+ cline = 0
|
|
|
544061 |
+
|
|
|
544061 |
+ for no in newopts:
|
|
|
544061 |
+
|
|
|
544061 |
+ if no['type'] == "section" or no['type'] == "subsection":
|
|
|
544061 |
+ (num, o) = self.findOpts(opts, no['type'], no['name'])
|
|
|
544061 |
+ if not o:
|
|
|
544061 |
+ if no['action'] == 'set':
|
|
|
544061 |
+ opts.append(no)
|
|
|
544061 |
+ continue
|
|
|
544061 |
+ if no['action'] == "set":
|
|
|
544061 |
+ self.mergeNew(o['value'], no['value'])
|
|
|
544061 |
+ continue
|
|
|
544061 |
+ cline = num + 1
|
|
|
544061 |
+ continue
|
|
|
544061 |
+
|
|
|
544061 |
+ if no['type'] == "option":
|
|
|
544061 |
+ (num, o) = self.findOpts(opts, no['type'], no['name'], True)
|
|
|
544061 |
+ if not o:
|
|
|
544061 |
+ if no['action'] == 'set' or no['action'] == 'addifnotset':
|
|
|
544061 |
+ opts.append(no)
|
|
|
544061 |
+ continue
|
|
|
544061 |
+ cline = num + 1
|
|
|
544061 |
+ continue
|
|
|
544061 |
+
|
|
|
544061 |
+ if no['type'] == "comment" or no['type'] == "empty":
|
|
|
544061 |
+ opts.insert(cline, no)
|
|
|
544061 |
+ cline += 1
|
|
|
544061 |
+ continue
|
|
|
544061 |
+
|
|
|
544061 |
+ raise SyntaxError('Unknown type: [%s]' % no['type'])
|
|
|
544061 |
+
|
|
|
544061 |
+ def merge(self, oldopts, newopts):
|
|
|
544061 |
+ """
|
|
|
544061 |
+ Uses a two pass strategy:
|
|
|
544061 |
+ First we create a new opts tree from oldopts removing/commenting
|
|
|
544061 |
+ the options as indicated by the contents of newopts
|
|
|
544061 |
+ Second we fill in the new opts tree with options as indicated
|
|
|
544061 |
+ in the newopts tree (this is becaus eentire (sub)sections may
|
|
|
544061 |
+ in the newopts tree (this is becaus entire (sub)sections may
|
|
|
544061 |
+ exist in the newopts that do not exist in oldopts)
|
|
|
544061 |
+ """
|
|
|
544061 |
+ opts = self.mergeOld(oldopts, newopts)
|
|
|
544061 |
+ self.mergeNew(opts, newopts)
|
|
|
544061 |
+ return opts
|
|
|
544061 |
+
|
|
|
544061 |
+ # TODO: Make parse() recursive?
|
|
|
544061 |
+ def parse(self, f):
|
|
|
544061 |
+
|
|
|
544061 |
+ opts = []
|
|
|
544061 |
+ sectopts = []
|
|
|
544061 |
+ section = None
|
|
|
544061 |
+ subsectopts = []
|
|
|
544061 |
+ subsection = None
|
|
|
544061 |
+ curopts = opts
|
|
|
544061 |
+ fatheropts = opts
|
|
|
544061 |
+
|
|
|
544061 |
+ # Read in the old file.
|
|
|
544061 |
+ for line in f:
|
|
|
544061 |
+
|
|
|
544061 |
+ # It's a section start.
|
|
|
544061 |
+ value = self.matchSection(line)
|
|
|
544061 |
+ if value:
|
|
|
544061 |
+ if section is not None:
|
|
|
544061 |
+ opts.append({'name': section,
|
|
|
544061 |
+ 'type': 'section',
|
|
|
544061 |
+ 'value': sectopts})
|
|
|
544061 |
+ sectopts = []
|
|
|
544061 |
+ curopts = sectopts
|
|
|
544061 |
+ fatheropts = sectopts
|
|
|
544061 |
+ section = value
|
|
|
544061 |
+ continue
|
|
|
544061 |
+
|
|
|
544061 |
+ # It's a subsection start.
|
|
|
544061 |
+ value = self.matchSubSection(line)
|
|
|
544061 |
+ if value:
|
|
|
544061 |
+ if subsection is not None:
|
|
|
544061 |
+ raise SyntaxError('nested subsections are not '
|
|
|
544061 |
+ 'supported yet')
|
|
|
544061 |
+ subsectopts = []
|
|
|
544061 |
+ curopts = subsectopts
|
|
|
544061 |
+ subsection = value
|
|
|
544061 |
+ continue
|
|
|
544061 |
+
|
|
|
544061 |
+ value = self.matchSubSectionEnd(line)
|
|
|
544061 |
+ if value:
|
|
|
544061 |
+ if subsection is None:
|
|
|
544061 |
+ raise SyntaxError('Unmatched end subsection terminator '
|
|
|
544061 |
+ 'found')
|
|
|
544061 |
+ fatheropts.append({'name': subsection,
|
|
|
544061 |
+ 'type': 'subsection',
|
|
|
544061 |
+ 'value': subsectopts})
|
|
|
544061 |
+ subsection = None
|
|
|
544061 |
+ curopts = fatheropts
|
|
|
544061 |
+ continue
|
|
|
544061 |
+
|
|
|
544061 |
+ # Copy anything else as is.
|
|
|
544061 |
+ try:
|
|
|
544061 |
+ curopts.append(self.parseLine(line))
|
|
|
544061 |
+ except SyntaxError as e:
|
|
|
544061 |
+ raise SyntaxError('{error} in file {fname}: [{line}]'.format(
|
|
|
544061 |
+ error=e, fname=f.name, line=line.rstrip()))
|
|
|
544061 |
+
|
|
|
544061 |
+ # Add last section if any
|
|
|
544061 |
+ if len(sectopts) is not 0:
|
|
|
544061 |
+ opts.append({'name': section,
|
|
|
544061 |
+ 'type': 'section',
|
|
|
544061 |
+ 'value': sectopts})
|
|
|
544061 |
+
|
|
|
544061 |
+ return opts
|
|
|
544061 |
+
|
|
|
544061 |
+ def changeConf(self, file, newopts):
|
|
|
544061 |
+ """
|
|
|
544061 |
+ Write settings to configuration file
|
|
|
544061 |
+ :param file: path to the file
|
|
|
544061 |
+ :param options: set of dictionaries in the form:
|
|
|
544061 |
+ {'name': 'foo', 'value': 'bar', 'action': 'set/comment'}
|
|
|
544061 |
+ :param section: section name like 'global'
|
|
|
544061 |
+ """
|
|
|
544061 |
+ output = ""
|
|
|
544061 |
+ f = None
|
|
|
544061 |
+ try:
|
|
|
544061 |
+ # Do not catch an unexisting file error
|
|
|
544061 |
+ # we want to fail in that case
|
|
|
544061 |
+ shutil.copy2(file, (file + ".ipabkp"))
|
|
|
544061 |
+
|
|
|
544061 |
+ f = openLocked(file, 0o644)
|
|
|
544061 |
+
|
|
|
544061 |
+ oldopts = self.parse(f)
|
|
|
544061 |
+
|
|
|
544061 |
+ options = self.merge(oldopts, newopts)
|
|
|
544061 |
+
|
|
|
544061 |
+ output = self.dump(options)
|
|
|
544061 |
+
|
|
|
544061 |
+ # Write it out and close it.
|
|
|
544061 |
+ f.seek(0)
|
|
|
544061 |
+ f.truncate(0)
|
|
|
544061 |
+ f.write(output)
|
|
|
544061 |
+ finally:
|
|
|
544061 |
+ try:
|
|
|
544061 |
+ if f:
|
|
|
544061 |
+ f.close()
|
|
|
544061 |
+ except IOError:
|
|
|
544061 |
+ pass
|
|
|
544061 |
+ logger.debug("Updating configuration file %s", file)
|
|
|
544061 |
+ logger.debug(output)
|
|
|
544061 |
+ return True
|
|
|
544061 |
+
|
|
|
544061 |
+ def newConf(self, file, options, file_perms=0o644):
|
|
|
544061 |
+ """"
|
|
|
544061 |
+ Write settings to a new file, backup the old
|
|
|
544061 |
+ :param file: path to the file
|
|
|
544061 |
+ :param options: a set of dictionaries in the form:
|
|
|
544061 |
+ {'name': 'foo', 'value': 'bar', 'action': 'set/comment'}
|
|
|
544061 |
+ :param file_perms: number defining the new file's permissions
|
|
|
544061 |
+ """
|
|
|
544061 |
+ output = ""
|
|
|
544061 |
+ f = None
|
|
|
544061 |
+ try:
|
|
|
544061 |
+ try:
|
|
|
544061 |
+ shutil.copy2(file, (file + ".ipabkp"))
|
|
|
544061 |
+ except IOError as err:
|
|
|
544061 |
+ if err.errno == 2:
|
|
|
544061 |
+ # The orign file did not exist
|
|
|
544061 |
+ pass
|
|
|
544061 |
+
|
|
|
544061 |
+ f = openLocked(file, file_perms)
|
|
|
544061 |
+
|
|
|
544061 |
+ # Trunkate
|
|
|
544061 |
+ f.seek(0)
|
|
|
544061 |
+ f.truncate(0)
|
|
|
544061 |
+
|
|
|
544061 |
+ output = self.dump(options)
|
|
|
544061 |
+
|
|
|
544061 |
+ f.write(output)
|
|
|
544061 |
+ finally:
|
|
|
544061 |
+ try:
|
|
|
544061 |
+ if f:
|
|
|
544061 |
+ f.close()
|
|
|
544061 |
+ except IOError:
|
|
|
544061 |
+ pass
|
|
|
544061 |
+ logger.debug("Writing configuration file %s", file)
|
|
|
544061 |
+ logger.debug(output)
|
|
|
544061 |
+ return True
|
|
|
544061 |
+
|
|
|
544061 |
+ @staticmethod
|
|
|
544061 |
+ def setOption(name, value):
|
|
|
544061 |
+ return {'name': name,
|
|
|
544061 |
+ 'type': 'option',
|
|
|
544061 |
+ 'action': 'set',
|
|
|
544061 |
+ 'value': value}
|
|
|
544061 |
+
|
|
|
544061 |
+ @staticmethod
|
|
|
544061 |
+ def rmOption(name):
|
|
|
544061 |
+ return {'name': name,
|
|
|
544061 |
+ 'type': 'option',
|
|
|
544061 |
+ 'action': 'remove',
|
|
|
544061 |
+ 'value': None}
|
|
|
544061 |
+
|
|
|
544061 |
+ @staticmethod
|
|
|
544061 |
+ def setSection(name, options):
|
|
|
544061 |
+ return {'name': name,
|
|
|
544061 |
+ 'type': 'section',
|
|
|
544061 |
+ 'action': 'set',
|
|
|
544061 |
+ 'value': options}
|
|
|
544061 |
+
|
|
|
544061 |
+ @staticmethod
|
|
|
544061 |
+ def emptyLine():
|
|
|
544061 |
+ return {'name': 'empty',
|
|
|
544061 |
+ 'type': 'empty'}
|
|
|
544061 |
diff --git a/ipaserver/install/adtrustinstance.py b/ipaserver/install/adtrustinstance.py
|
|
|
544061 |
index 7bb9431..47a5a92 100644
|
|
|
544061 |
--- a/ipaserver/install/adtrustinstance.py
|
|
|
544061 |
+++ b/ipaserver/install/adtrustinstance.py
|
|
|
544061 |
@@ -40,11 +40,11 @@ from ipaserver.install.replication import wait_for_task
|
|
|
544061 |
from ipalib import errors, api
|
|
|
544061 |
from ipalib.util import normalize_zone
|
|
|
544061 |
from ipapython.dn import DN
|
|
|
544061 |
+from ipapython import ipachangeconf
|
|
|
544061 |
from ipapython import ipaldap
|
|
|
544061 |
from ipapython import ipautil
|
|
|
544061 |
import ipapython.errors
|
|
|
544061 |
|
|
|
544061 |
-import ipaclient.install.ipachangeconf
|
|
|
544061 |
from ipaplatform import services
|
|
|
544061 |
from ipaplatform.constants import constants
|
|
|
544061 |
from ipaplatform.paths import paths
|
|
|
544061 |
@@ -639,7 +639,7 @@ class ADTRUSTInstance(service.Service):
|
|
|
544061 |
self.print_msg("Cannot modify /etc/krb5.conf")
|
|
|
544061 |
|
|
|
544061 |
krbconf = (
|
|
|
544061 |
- ipaclient.install.ipachangeconf.IPAChangeConf("IPA Installer"))
|
|
|
544061 |
+ ipachangeconf.IPAChangeConf("IPA Installer"))
|
|
|
544061 |
krbconf.setOptionAssignment((" = ", " "))
|
|
|
544061 |
krbconf.setSectionNameDelimiters(("[", "]"))
|
|
|
544061 |
krbconf.setSubSectionDelimiters(("{", "}"))
|
|
|
544061 |
diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py
|
|
|
544061 |
index 02c8f4d..6a81d57 100644
|
|
|
544061 |
--- a/ipaserver/install/server/install.py
|
|
|
544061 |
+++ b/ipaserver/install/server/install.py
|
|
|
544061 |
@@ -19,7 +19,7 @@ import six
|
|
|
544061 |
from ipaclient.install import timeconf
|
|
|
544061 |
from ipaclient.install.client import (
|
|
|
544061 |
check_ldap_conf, sync_time, restore_time_sync)
|
|
|
544061 |
-from ipaclient.install.ipachangeconf import IPAChangeConf
|
|
|
544061 |
+from ipapython.ipachangeconf import IPAChangeConf
|
|
|
544061 |
from ipalib.install import certmonger, sysrestore
|
|
|
544061 |
from ipapython import ipautil, version
|
|
|
544061 |
from ipapython.ipautil import (
|
|
|
544061 |
diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
|
|
|
544061 |
index 6da6804..7272640 100644
|
|
|
544061 |
--- a/ipaserver/install/server/replicainstall.py
|
|
|
544061 |
+++ b/ipaserver/install/server/replicainstall.py
|
|
|
544061 |
@@ -23,13 +23,13 @@ from pkg_resources import parse_version
|
|
|
544061 |
import six
|
|
|
544061 |
|
|
|
544061 |
from ipaclient.install.client import check_ldap_conf
|
|
|
544061 |
-from ipaclient.install.ipachangeconf import IPAChangeConf
|
|
|
544061 |
import ipaclient.install.timeconf
|
|
|
544061 |
from ipalib.install import certstore, sysrestore
|
|
|
544061 |
from ipalib.install.kinit import kinit_keytab
|
|
|
544061 |
from ipapython import ipaldap, ipautil
|
|
|
544061 |
from ipapython.dn import DN
|
|
|
544061 |
from ipapython.admintool import ScriptError
|
|
|
544061 |
+from ipapython.ipachangeconf import IPAChangeConf
|
|
|
544061 |
from ipaplatform import services
|
|
|
544061 |
from ipaplatform.tasks import tasks
|
|
|
544061 |
from ipaplatform.paths import paths
|
|
|
544061 |
diff --git a/ipatests/test_install/test_changeconf.py b/ipatests/test_install/test_changeconf.py
|
|
|
544061 |
index 2dc2b7d..40c8a1d 100644
|
|
|
544061 |
--- a/ipatests/test_install/test_changeconf.py
|
|
|
544061 |
+++ b/ipatests/test_install/test_changeconf.py
|
|
|
544061 |
@@ -3,7 +3,7 @@
|
|
|
544061 |
from __future__ import absolute_import
|
|
|
544061 |
|
|
|
544061 |
import pytest
|
|
|
544061 |
-from ipaclient.install.ipachangeconf import IPAChangeConf
|
|
|
544061 |
+from ipapython.ipachangeconf import IPAChangeConf
|
|
|
544061 |
|
|
|
544061 |
|
|
|
544061 |
@pytest.fixture(scope='function')
|
|
|
544061 |
|
|
|
544061 |
From 2da90887632c764a73866c9ad3824ebb53c0aa73 Mon Sep 17 00:00:00 2001
|
|
|
544061 |
From: Rob Critenden <rcritten@redhat.com>
|
|
|
544061 |
Date: Aug 29 2019 06:45:12 +0000
|
|
|
544061 |
Subject: Use tasks to configure automount nsswitch settings
|
|
|
544061 |
|
|
|
544061 |
|
|
|
544061 |
authselect doesn't allow one to directly write to
|
|
|
544061 |
/etc/nsswitch.conf. It will complain bitterly if it
|
|
|
544061 |
detects it and will refuse to work until reset.
|
|
|
544061 |
|
|
|
544061 |
Instead it wants the user to write to
|
|
|
544061 |
/etc/authselect/user-nsswitch.conf and then it will handle
|
|
|
544061 |
merging in any differences.
|
|
|
544061 |
|
|
|
544061 |
To complicate matters some databases are not user configurable
|
|
|
544061 |
like passwd, group and of course, automount. There are some
|
|
|
544061 |
undocumented options to allow one to override these though so
|
|
|
544061 |
we utilize that.
|
|
|
544061 |
|
|
|
544061 |
tasks are used so that authselect-based installations can still
|
|
|
544061 |
write directly to /etc/nsswitch.conf and operate as it used to.
|
|
|
544061 |
|
|
|
544061 |
Reviewed-By: Francois Cami <fcami@redhat.com>
|
|
|
544061 |
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
|
|
544061 |
Reviewed-By: Rob Critenden <rcritten@redhat.com>
|
|
|
544061 |
Reviewed-By: François Cami <fcami@redhat.com>
|
|
|
544061 |
|
|
|
544061 |
---
|
|
|
544061 |
|
|
|
544061 |
diff --git a/ipaclient/install/client.py b/ipaclient/install/client.py
|
|
|
544061 |
index 9492ca4..1e88ba1 100644
|
|
|
544061 |
--- a/ipaclient/install/client.py
|
|
|
544061 |
+++ b/ipaclient/install/client.py
|
|
|
544061 |
@@ -66,7 +66,7 @@ from ipapython import version
|
|
|
544061 |
|
|
|
544061 |
from . import automount, timeconf, sssd
|
|
|
544061 |
from ipaclient import discovery
|
|
|
544061 |
-from .ipachangeconf import IPAChangeConf
|
|
|
544061 |
+from ipapython.ipachangeconf import IPAChangeConf
|
|
|
544061 |
|
|
|
544061 |
NoneType = type(None)
|
|
|
544061 |
|
|
|
544061 |
@@ -281,72 +281,6 @@ def is_ipa_client_installed(fstore, on_master=False):
|
|
|
544061 |
return installed
|
|
|
544061 |
|
|
|
544061 |
|
|
|
544061 |
-def configure_nsswitch_database(fstore, database, services, preserve=True,
|
|
|
544061 |
- append=True, default_value=()):
|
|
|
544061 |
- """
|
|
|
544061 |
- Edits the specified nsswitch.conf database (e.g. passwd, group, sudoers)
|
|
|
544061 |
- to use the specified service(s).
|
|
|
544061 |
-
|
|
|
544061 |
- Arguments:
|
|
|
544061 |
- fstore - FileStore to backup the nsswitch.conf
|
|
|
544061 |
- database - database configuration that should be ammended,
|
|
|
544061 |
- e.g. 'sudoers'
|
|
|
544061 |
- service - list of services that should be added, e.g. ['sss']
|
|
|
544061 |
- preserve - if True, the already configured services will be preserved
|
|
|
544061 |
-
|
|
|
544061 |
- The next arguments modify the behaviour if preserve=True:
|
|
|
544061 |
- append - if True, the services will be appended, if False, prepended
|
|
|
544061 |
- default_value - list of services that are considered as default (if
|
|
|
544061 |
- the database is not mentioned in nsswitch.conf), e.g.
|
|
|
544061 |
- ['files']
|
|
|
544061 |
- """
|
|
|
544061 |
-
|
|
|
544061 |
- # Backup the original version of nsswitch.conf, we're going to edit it now
|
|
|
544061 |
- if not fstore.has_file(paths.NSSWITCH_CONF):
|
|
|
544061 |
- fstore.backup_file(paths.NSSWITCH_CONF)
|
|
|
544061 |
-
|
|
|
544061 |
- conf = IPAChangeConf("IPA Installer")
|
|
|
544061 |
- conf.setOptionAssignment(':')
|
|
|
544061 |
-
|
|
|
544061 |
- if preserve:
|
|
|
544061 |
- # Read the existing configuration
|
|
|
544061 |
- with open(paths.NSSWITCH_CONF, 'r') as f:
|
|
|
544061 |
- opts = conf.parse(f)
|
|
|
544061 |
- raw_database_entry = conf.findOpts(opts, 'option', database)[1]
|
|
|
544061 |
-
|
|
|
544061 |
- # Detect the list of already configured services
|
|
|
544061 |
- if not raw_database_entry:
|
|
|
544061 |
- # If there is no database entry, database is not present in
|
|
|
544061 |
- # the nsswitch.conf. Set the list of services to the
|
|
|
544061 |
- # default list, if passed.
|
|
|
544061 |
- configured_services = list(default_value)
|
|
|
544061 |
- else:
|
|
|
544061 |
- configured_services = raw_database_entry['value'].strip().split()
|
|
|
544061 |
-
|
|
|
544061 |
- # Make sure no service is added if already mentioned in the list
|
|
|
544061 |
- added_services = [s for s in services
|
|
|
544061 |
- if s not in configured_services]
|
|
|
544061 |
-
|
|
|
544061 |
- # Prepend / append the list of new services
|
|
|
544061 |
- if append:
|
|
|
544061 |
- new_value = ' ' + ' '.join(configured_services + added_services)
|
|
|
544061 |
- else:
|
|
|
544061 |
- new_value = ' ' + ' '.join(added_services + configured_services)
|
|
|
544061 |
-
|
|
|
544061 |
- else:
|
|
|
544061 |
- # Preserve not set, let's rewrite existing configuration
|
|
|
544061 |
- new_value = ' ' + ' '.join(services)
|
|
|
544061 |
-
|
|
|
544061 |
- # Set new services as sources for database
|
|
|
544061 |
- opts = [
|
|
|
544061 |
- conf.setOption(database, new_value),
|
|
|
544061 |
- conf.emptyLine(),
|
|
|
544061 |
- ]
|
|
|
544061 |
-
|
|
|
544061 |
- conf.changeConf(paths.NSSWITCH_CONF, opts)
|
|
|
544061 |
- logger.info("Configured %s in %s", database, paths.NSSWITCH_CONF)
|
|
|
544061 |
-
|
|
|
544061 |
-
|
|
|
544061 |
def configure_ipa_conf(
|
|
|
544061 |
fstore, cli_basedn, cli_realm, cli_domain, cli_server, hostname):
|
|
|
544061 |
ipaconf = IPAChangeConf("IPA Installer")
|
|
|
544061 |
@@ -948,9 +882,7 @@ def configure_sssd_conf(
|
|
|
544061 |
"Unable to activate the SUDO service in SSSD config.")
|
|
|
544061 |
|
|
|
544061 |
sssdconfig.activate_service('sudo')
|
|
|
544061 |
- configure_nsswitch_database(
|
|
|
544061 |
- fstore, 'sudoers', ['sss'],
|
|
|
544061 |
- default_value=['files'])
|
|
|
544061 |
+ tasks.enable_sssd_sudo(fstore)
|
|
|
544061 |
|
|
|
544061 |
domain.add_provider('ipa', 'id')
|
|
|
544061 |
|
|
|
544061 |
diff --git a/ipaclient/install/ipa_client_automount.py b/ipaclient/install/ipa_client_automount.py
|
|
|
544061 |
index a1dc2a1..3a0896b 100644
|
|
|
544061 |
--- a/ipaclient/install/ipa_client_automount.py
|
|
|
544061 |
+++ b/ipaclient/install/ipa_client_automount.py
|
|
|
544061 |
@@ -41,7 +41,8 @@ from six.moves.urllib.parse import urlsplit
|
|
|
544061 |
|
|
|
544061 |
# pylint: enable=import-error
|
|
|
544061 |
from optparse import OptionParser # pylint: disable=deprecated-module
|
|
|
544061 |
-from ipaclient.install import ipachangeconf, ipadiscovery
|
|
|
544061 |
+from ipapython import ipachangeconf
|
|
|
544061 |
+from ipaclient.install import ipadiscovery
|
|
|
544061 |
from ipaclient.install.client import (
|
|
|
544061 |
CLIENT_NOT_CONFIGURED,
|
|
|
544061 |
CLIENT_ALREADY_CONFIGURED,
|
|
|
544061 |
@@ -177,44 +178,6 @@ def configure_xml(fstore):
|
|
|
544061 |
print("Configured %s" % authconf)
|
|
|
544061 |
|
|
|
544061 |
|
|
|
544061 |
-def configure_nsswitch(statestore, options):
|
|
|
544061 |
- """
|
|
|
544061 |
- Point automount to ldap in nsswitch.conf.
|
|
|
544061 |
- This function is for non-SSSD setups only.
|
|
|
544061 |
- """
|
|
|
544061 |
- conf = ipachangeconf.IPAChangeConf("IPA Installer")
|
|
|
544061 |
- conf.setOptionAssignment(':')
|
|
|
544061 |
-
|
|
|
544061 |
- with open(paths.NSSWITCH_CONF, 'r') as f:
|
|
|
544061 |
- current_opts = conf.parse(f)
|
|
|
544061 |
- current_nss_value = conf.findOpts(
|
|
|
544061 |
- current_opts, name='automount', type='option'
|
|
|
544061 |
- )[1]
|
|
|
544061 |
- if current_nss_value is None:
|
|
|
544061 |
- # no automount database present
|
|
|
544061 |
- current_nss_value = False # None cannot be backed up
|
|
|
544061 |
- else:
|
|
|
544061 |
- current_nss_value = current_nss_value['value']
|
|
|
544061 |
- statestore.backup_state(
|
|
|
544061 |
- 'ipa-client-automount-nsswitch', 'previous-automount',
|
|
|
544061 |
- current_nss_value
|
|
|
544061 |
- )
|
|
|
544061 |
-
|
|
|
544061 |
- nss_value = ' files ldap'
|
|
|
544061 |
- opts = [
|
|
|
544061 |
- {
|
|
|
544061 |
- 'name': 'automount',
|
|
|
544061 |
- 'type': 'option',
|
|
|
544061 |
- 'action': 'set',
|
|
|
544061 |
- 'value': nss_value,
|
|
|
544061 |
- },
|
|
|
544061 |
- {'name': 'empty', 'type': 'empty'},
|
|
|
544061 |
- ]
|
|
|
544061 |
- conf.changeConf(paths.NSSWITCH_CONF, opts)
|
|
|
544061 |
-
|
|
|
544061 |
- print("Configured %s" % paths.NSSWITCH_CONF)
|
|
|
544061 |
-
|
|
|
544061 |
-
|
|
|
544061 |
def configure_autofs_sssd(fstore, statestore, autodiscover, options):
|
|
|
544061 |
try:
|
|
|
544061 |
sssdconfig = SSSDConfig.SSSDConfig()
|
|
|
544061 |
@@ -339,41 +302,8 @@ def uninstall(fstore, statestore):
|
|
|
544061 |
]
|
|
|
544061 |
STATES = ['autofs', 'rpcidmapd', 'rpcgssd']
|
|
|
544061 |
|
|
|
544061 |
- if statestore.get_state(
|
|
|
544061 |
- 'ipa-client-automount-nsswitch', 'previous-automount'
|
|
|
544061 |
- ) is False:
|
|
|
544061 |
- # Previous nsswitch.conf had no automount database configured
|
|
|
544061 |
- # so remove it.
|
|
|
544061 |
- conf = ipachangeconf.IPAChangeConf("IPA automount installer")
|
|
|
544061 |
- conf.setOptionAssignment(':')
|
|
|
544061 |
- changes = [conf.rmOption('automount')]
|
|
|
544061 |
- conf.changeConf(paths.NSSWITCH_CONF, changes)
|
|
|
544061 |
- tasks.restore_context(paths.NSSWITCH_CONF)
|
|
|
544061 |
- statestore.delete_state(
|
|
|
544061 |
- 'ipa-client-automount-nsswitch', 'previous-automount'
|
|
|
544061 |
- )
|
|
|
544061 |
- elif statestore.get_state(
|
|
|
544061 |
- 'ipa-client-automount-nsswitch', 'previous-automount'
|
|
|
544061 |
- ) is not None:
|
|
|
544061 |
- nss_value = statestore.get_state(
|
|
|
544061 |
- 'ipa-client-automount-nsswitch', 'previous-automount'
|
|
|
544061 |
- )
|
|
|
544061 |
- opts = [
|
|
|
544061 |
- {
|
|
|
544061 |
- 'name': 'automount',
|
|
|
544061 |
- 'type': 'option',
|
|
|
544061 |
- 'action': 'set',
|
|
|
544061 |
- 'value': nss_value,
|
|
|
544061 |
- },
|
|
|
544061 |
- {'name': 'empty', 'type': 'empty'},
|
|
|
544061 |
- ]
|
|
|
544061 |
- conf = ipachangeconf.IPAChangeConf("IPA automount installer")
|
|
|
544061 |
- conf.setOptionAssignment(':')
|
|
|
544061 |
- conf.changeConf(paths.NSSWITCH_CONF, opts)
|
|
|
544061 |
- tasks.restore_context(paths.NSSWITCH_CONF)
|
|
|
544061 |
- statestore.delete_state(
|
|
|
544061 |
- 'ipa-client-automount-nsswitch', 'previous-automount'
|
|
|
544061 |
- )
|
|
|
544061 |
+ if not statestore.get_state('autofs', 'sssd'):
|
|
|
544061 |
+ tasks.disable_ldap_automount(statestore)
|
|
|
544061 |
|
|
|
544061 |
if not any(fstore.has_file(f) for f in RESTORE_FILES) or not any(
|
|
|
544061 |
statestore.has_state(s) for s in STATES
|
|
|
544061 |
@@ -627,7 +557,7 @@ def configure_automount():
|
|
|
544061 |
|
|
|
544061 |
try:
|
|
|
544061 |
if not options.sssd:
|
|
|
544061 |
- configure_nsswitch(statestore, options)
|
|
|
544061 |
+ tasks.enable_ldap_automount(statestore)
|
|
|
544061 |
configure_nfs(fstore, statestore, options)
|
|
|
544061 |
if options.sssd:
|
|
|
544061 |
configure_autofs_sssd(fstore, statestore, autodiscover, options)
|
|
|
544061 |
diff --git a/ipaplatform/base/tasks.py b/ipaplatform/base/tasks.py
|
|
|
544061 |
index 8aa9c5c..7fd7d57 100644
|
|
|
544061 |
--- a/ipaplatform/base/tasks.py
|
|
|
544061 |
+++ b/ipaplatform/base/tasks.py
|
|
|
544061 |
@@ -32,6 +32,7 @@ from pkg_resources import parse_version
|
|
|
544061 |
from ipaplatform.constants import constants
|
|
|
544061 |
from ipaplatform.paths import paths
|
|
|
544061 |
from ipapython import ipautil
|
|
|
544061 |
+from ipapython.ipachangeconf import IPAChangeConf
|
|
|
544061 |
|
|
|
544061 |
logger = logging.getLogger(__name__)
|
|
|
544061 |
|
|
|
544061 |
@@ -337,5 +338,157 @@ class BaseTaskNamespace:
|
|
|
544061 |
"""
|
|
|
544061 |
raise NotImplementedError
|
|
|
544061 |
|
|
|
544061 |
+ def configure_nsswitch_database(self, fstore, database, services,
|
|
|
544061 |
+ preserve=True, append=True,
|
|
|
544061 |
+ default_value=()):
|
|
|
544061 |
+ """
|
|
|
544061 |
+ Edits the specified nsswitch.conf database (e.g. passwd, group,
|
|
|
544061 |
+ sudoers) to use the specified service(s).
|
|
|
544061 |
+
|
|
|
544061 |
+ Arguments:
|
|
|
544061 |
+ fstore - FileStore to backup the nsswitch.conf
|
|
|
544061 |
+ database - database configuration that should be ammended,
|
|
|
544061 |
+ e.g. 'sudoers'
|
|
|
544061 |
+ service - list of services that should be added, e.g. ['sss']
|
|
|
544061 |
+ preserve - if True, the already configured services will be
|
|
|
544061 |
+ preserved
|
|
|
544061 |
+
|
|
|
544061 |
+ The next arguments modify the behaviour if preserve=True:
|
|
|
544061 |
+ append - if True, the services will be appended, if False,
|
|
|
544061 |
+ prepended
|
|
|
544061 |
+ default_value - list of services that are considered as default (if
|
|
|
544061 |
+ the database is not mentioned in nsswitch.conf),
|
|
|
544061 |
+ e.g. ['files']
|
|
|
544061 |
+ """
|
|
|
544061 |
+
|
|
|
544061 |
+ # Backup the original version of nsswitch.conf, we're going to edit it
|
|
|
544061 |
+ # now
|
|
|
544061 |
+ if not fstore.has_file(paths.NSSWITCH_CONF):
|
|
|
544061 |
+ fstore.backup_file(paths.NSSWITCH_CONF)
|
|
|
544061 |
+
|
|
|
544061 |
+ conf = IPAChangeConf("IPA Installer")
|
|
|
544061 |
+ conf.setOptionAssignment(':')
|
|
|
544061 |
+
|
|
|
544061 |
+ if preserve:
|
|
|
544061 |
+ # Read the existing configuration
|
|
|
544061 |
+ with open(paths.NSSWITCH_CONF, 'r') as f:
|
|
|
544061 |
+ opts = conf.parse(f)
|
|
|
544061 |
+ raw_database_entry = conf.findOpts(opts, 'option', database)[1]
|
|
|
544061 |
+
|
|
|
544061 |
+ # Detect the list of already configured services
|
|
|
544061 |
+ if not raw_database_entry:
|
|
|
544061 |
+ # If there is no database entry, database is not present in
|
|
|
544061 |
+ # the nsswitch.conf. Set the list of services to the
|
|
|
544061 |
+ # default list, if passed.
|
|
|
544061 |
+ configured_services = list(default_value)
|
|
|
544061 |
+ else:
|
|
|
544061 |
+ configured_services = raw_database_entry[
|
|
|
544061 |
+ 'value'].strip().split()
|
|
|
544061 |
+
|
|
|
544061 |
+ # Make sure no service is added if already mentioned in the list
|
|
|
544061 |
+ added_services = [s for s in services
|
|
|
544061 |
+ if s not in configured_services]
|
|
|
544061 |
+
|
|
|
544061 |
+ # Prepend / append the list of new services
|
|
|
544061 |
+ if append:
|
|
|
544061 |
+ new_value = ' ' + ' '.join(configured_services +
|
|
|
544061 |
+ added_services)
|
|
|
544061 |
+ else:
|
|
|
544061 |
+ new_value = ' ' + ' '.join(added_services +
|
|
|
544061 |
+ configured_services)
|
|
|
544061 |
+
|
|
|
544061 |
+ else:
|
|
|
544061 |
+ # Preserve not set, let's rewrite existing configuration
|
|
|
544061 |
+ new_value = ' ' + ' '.join(services)
|
|
|
544061 |
+
|
|
|
544061 |
+ # Set new services as sources for database
|
|
|
544061 |
+ opts = [
|
|
|
544061 |
+ conf.setOption(database, new_value),
|
|
|
544061 |
+ conf.emptyLine(),
|
|
|
544061 |
+ ]
|
|
|
544061 |
+
|
|
|
544061 |
+ conf.changeConf(paths.NSSWITCH_CONF, opts)
|
|
|
544061 |
+ logger.info("Configured %s in %s", database, paths.NSSWITCH_CONF)
|
|
|
544061 |
+
|
|
|
544061 |
+ def enable_sssd_sudo(self, fstore):
|
|
|
544061 |
+ """Configure nsswitch.conf to use sssd for sudo"""
|
|
|
544061 |
+ self.configure_nsswitch_database(
|
|
|
544061 |
+ fstore, 'sudoers', ['sss'],
|
|
|
544061 |
+ default_value=['files'])
|
|
|
544061 |
+
|
|
|
544061 |
+ def enable_ldap_automount(self, statestore):
|
|
|
544061 |
+ """
|
|
|
544061 |
+ Point automount to ldap in nsswitch.conf.
|
|
|
544061 |
+ This function is for non-SSSD setups only.
|
|
|
544061 |
+ """
|
|
|
544061 |
+ conf = IPAChangeConf("IPA Installer")
|
|
|
544061 |
+ conf.setOptionAssignment(':')
|
|
|
544061 |
+
|
|
|
544061 |
+ with open(paths.NSSWITCH_CONF, 'r') as f:
|
|
|
544061 |
+ current_opts = conf.parse(f)
|
|
|
544061 |
+ current_nss_value = conf.findOpts(
|
|
|
544061 |
+ current_opts, name='automount', type='option'
|
|
|
544061 |
+ )[1]
|
|
|
544061 |
+ if current_nss_value is None:
|
|
|
544061 |
+ # no automount database present
|
|
|
544061 |
+ current_nss_value = False # None cannot be backed up
|
|
|
544061 |
+ else:
|
|
|
544061 |
+ current_nss_value = current_nss_value['value']
|
|
|
544061 |
+ statestore.backup_state(
|
|
|
544061 |
+ 'ipa-client-automount-nsswitch', 'previous-automount',
|
|
|
544061 |
+ current_nss_value
|
|
|
544061 |
+ )
|
|
|
544061 |
+
|
|
|
544061 |
+ nss_value = ' files ldap'
|
|
|
544061 |
+ opts = [
|
|
|
544061 |
+ {
|
|
|
544061 |
+ 'name': 'automount',
|
|
|
544061 |
+ 'type': 'option',
|
|
|
544061 |
+ 'action': 'set',
|
|
|
544061 |
+ 'value': nss_value,
|
|
|
544061 |
+ },
|
|
|
544061 |
+ {'name': 'empty', 'type': 'empty'},
|
|
|
544061 |
+ ]
|
|
|
544061 |
+ conf.changeConf(paths.NSSWITCH_CONF, opts)
|
|
|
544061 |
+
|
|
|
544061 |
+ logger.info("Configured %s", paths.NSSWITCH_CONF)
|
|
|
544061 |
+
|
|
|
544061 |
+ def disable_ldap_automount(self, statestore):
|
|
|
544061 |
+ """Disable automount using LDAP"""
|
|
|
544061 |
+ if statestore.get_state(
|
|
|
544061 |
+ 'ipa-client-automount-nsswitch', 'previous-automount'
|
|
|
544061 |
+ ) is False:
|
|
|
544061 |
+ # Previous nsswitch.conf had no automount database configured
|
|
|
544061 |
+ # so remove it.
|
|
|
544061 |
+ conf = IPAChangeConf("IPA automount installer")
|
|
|
544061 |
+ conf.setOptionAssignment(':')
|
|
|
544061 |
+ changes = [conf.rmOption('automount')]
|
|
|
544061 |
+ conf.changeConf(paths.NSSWITCH_CONF, changes)
|
|
|
544061 |
+ self.restore_context(paths.NSSWITCH_CONF)
|
|
|
544061 |
+ statestore.delete_state(
|
|
|
544061 |
+ 'ipa-client-automount-nsswitch', 'previous-automount'
|
|
|
544061 |
+ )
|
|
|
544061 |
+ elif statestore.get_state(
|
|
|
544061 |
+ 'ipa-client-automount-nsswitch', 'previous-automount'
|
|
|
544061 |
+ ) is not None:
|
|
|
544061 |
+ nss_value = statestore.get_state(
|
|
|
544061 |
+ 'ipa-client-automount-nsswitch', 'previous-automount'
|
|
|
544061 |
+ )
|
|
|
544061 |
+ opts = [
|
|
|
544061 |
+ {
|
|
|
544061 |
+ 'name': 'automount',
|
|
|
544061 |
+ 'type': 'option',
|
|
|
544061 |
+ 'action': 'set',
|
|
|
544061 |
+ 'value': nss_value,
|
|
|
544061 |
+ },
|
|
|
544061 |
+ {'name': 'empty', 'type': 'empty'},
|
|
|
544061 |
+ ]
|
|
|
544061 |
+ conf = IPAChangeConf("IPA automount installer")
|
|
|
544061 |
+ conf.setOptionAssignment(':')
|
|
|
544061 |
+ conf.changeConf(paths.NSSWITCH_CONF, opts)
|
|
|
544061 |
+ self.restore_context(paths.NSSWITCH_CONF)
|
|
|
544061 |
+ statestore.delete_state(
|
|
|
544061 |
+ 'ipa-client-automount-nsswitch', 'previous-automount'
|
|
|
544061 |
+ )
|
|
|
544061 |
|
|
|
544061 |
tasks = BaseTaskNamespace()
|
|
|
544061 |
diff --git a/ipaplatform/redhat/paths.py b/ipaplatform/redhat/paths.py
|
|
|
544061 |
index 8ccd04b..15bdef6 100644
|
|
|
544061 |
--- a/ipaplatform/redhat/paths.py
|
|
|
544061 |
+++ b/ipaplatform/redhat/paths.py
|
|
|
544061 |
@@ -39,6 +39,7 @@ class RedHatPathNamespace(BasePathNamespace):
|
|
|
544061 |
AUTHCONFIG = '/usr/sbin/authconfig'
|
|
|
544061 |
AUTHSELECT = '/usr/bin/authselect'
|
|
|
544061 |
SYSCONF_NETWORK = '/etc/sysconfig/network'
|
|
|
544061 |
+ NSSWITCH_CONF = '/etc/authselect/user-nsswitch.conf'
|
|
|
544061 |
|
|
|
544061 |
|
|
|
544061 |
paths = RedHatPathNamespace()
|
|
|
544061 |
diff --git a/ipaplatform/redhat/tasks.py b/ipaplatform/redhat/tasks.py
|
|
|
544061 |
index be0b641..e18f6fa 100644
|
|
|
544061 |
--- a/ipaplatform/redhat/tasks.py
|
|
|
544061 |
+++ b/ipaplatform/redhat/tasks.py
|
|
|
544061 |
@@ -744,4 +744,23 @@ class RedHatTaskNamespace(BaseTaskNamespace):
|
|
|
544061 |
|
|
|
544061 |
return filenames
|
|
|
544061 |
|
|
|
544061 |
+ def enable_ldap_automount(self, statestore):
|
|
|
544061 |
+ """
|
|
|
544061 |
+ Point automount to ldap in nsswitch.conf.
|
|
|
544061 |
+ This function is for non-SSSD setups only.
|
|
|
544061 |
+ """
|
|
|
544061 |
+ super(RedHatTaskNamespace, self).enable_ldap_automount(statestore)
|
|
|
544061 |
+
|
|
|
544061 |
+ authselect_cmd = [paths.AUTHSELECT, "enable-feature",
|
|
|
544061 |
+ "with-custom-automount"]
|
|
|
544061 |
+ ipautil.run(authselect_cmd)
|
|
|
544061 |
+
|
|
|
544061 |
+ def disable_ldap_automount(self, statestore):
|
|
|
544061 |
+ """Disable ldap-based automount"""
|
|
|
544061 |
+ super(RedHatTaskNamespace, self).disable_ldap_automount(statestore)
|
|
|
544061 |
+
|
|
|
544061 |
+ authselect_cmd = [paths.AUTHSELECT, "disable-feature",
|
|
|
544061 |
+ "with-custom-automount"]
|
|
|
544061 |
+ ipautil.run(authselect_cmd)
|
|
|
544061 |
+
|
|
|
544061 |
tasks = RedHatTaskNamespace()
|
|
|
544061 |
|