diff --git a/lam/HISTORY b/lam/HISTORY index 9a8b547d..7c7be33e 100644 --- a/lam/HISTORY +++ b/lam/HISTORY @@ -1,5 +1,6 @@ September 2012 3.9 - Kolab 2.4 support + - Puppet support - LAM Pro -> support RFC2307bis automount entries -> read-only fields in self service diff --git a/lam/docs/manual-sources/howto.xml b/lam/docs/manual-sources/howto.xml index 29691eef..fc7200be 100644 --- a/lam/docs/manual-sources/howto.xml +++ b/lam/docs/manual-sources/howto.xml @@ -2477,6 +2477,55 @@ Have fun! + +
+ Puppet + + LAM supports to manage your Puppet configuration. You can + edit all attributes like environment, classes, variables and parent + node. + + Configuration + + To activate this feature please edit your LAM server profile and + add the host module "Puppet (puppetClient)" on tab "Modules". This + will add the Puppet tab to your host pages. + + + + + + + + + + On tab "Module settings" in your LAM server profile you may also + setup some common environment names. LAM will use them to provide + autocompletion hints when editing the environment for a node. + + + + + + + + + + Editing nodes + + When you edit a host entry then you will see the tab "Puppet". + Here you can add/remove the Puppet extension and edit all + attributes. + + + + + + + + +
diff --git a/lam/docs/manual-sources/images/mod_puppet1.png b/lam/docs/manual-sources/images/mod_puppet1.png new file mode 100644 index 00000000..a1415990 Binary files /dev/null and b/lam/docs/manual-sources/images/mod_puppet1.png differ diff --git a/lam/docs/manual-sources/images/mod_puppet2.png b/lam/docs/manual-sources/images/mod_puppet2.png new file mode 100644 index 00000000..a77113fc Binary files /dev/null and b/lam/docs/manual-sources/images/mod_puppet2.png differ diff --git a/lam/docs/manual-sources/images/mod_puppet3.png b/lam/docs/manual-sources/images/mod_puppet3.png new file mode 100644 index 00000000..5176d07c Binary files /dev/null and b/lam/docs/manual-sources/images/mod_puppet3.png differ diff --git a/lam/graphics/puppet.png b/lam/graphics/puppet.png new file mode 100644 index 00000000..2c69f11b Binary files /dev/null and b/lam/graphics/puppet.png differ diff --git a/lam/lib/modules/puppetClient.inc b/lam/lib/modules/puppetClient.inc new file mode 100644 index 00000000..33c51d1c --- /dev/null +++ b/lam/lib/modules/puppetClient.inc @@ -0,0 +1,519 @@ +autoAddObjectClasses = false; + } + + + /** + * Returns meta data that is interpreted by parent class + * + * @return array array with meta data + */ + function get_metaData() { + $return = array(); + // icon + $return['icon'] = 'puppet.png'; + // manages user accounts + $return["account_types"] = array("host"); + // alias name + $return["alias"] = _("Puppet"); + // module dependencies + $return['dependencies'] = array('depends' => array(), 'conflicts' => array()); + // managed object classes + $return['objectClasses'] = array('puppetClient'); + // managed attributes + $return['attributes'] = array('environment', 'parentnode', 'puppetclass', 'puppetvar'); + // help Entries + $return['help'] = array ( + 'environment' => array ( + "Headline" => _('Environment'), 'attr' => 'environment', + "Text" => _('Please enter the environment name for this node (e.g. production).') + ), + 'environmentList' => array ( + "Headline" => _('Environment'), 'attr' => 'environment', + "Text" => _('Please enter the environment name for this node (e.g. production).') . ' ' . _('Multiple values are separated by semicolon.') + ), + 'parentnode' => array ( + "Headline" => _('Parent node'), 'attr' => 'parentnode', + "Text" => _('This is this node\'s parent. All classes and variables are inherited from this node.') + ), + 'puppetclass' => array ( + "Headline" => _('Classes'), 'attr' => 'puppetclass', + "Text" => _('The list of configured Puppet classes for this node (e.g. ntp).') + ), + 'puppetclassList' => array ( + "Headline" => _('Classes'), 'attr' => 'puppetclass', + "Text" => _('The list of configured Puppet classes for this node (e.g. ntp).') . ' ' . _('Multiple values are separated by semicolon.') + ), + 'puppetvar' => array ( + "Headline" => _('Variables'), 'attr' => 'puppetvar', + "Text" => _('Please enter your Puppet variables for this node (e.g. config_exim=true).') + ), + 'puppetvarList' => array ( + "Headline" => _('Variables'), 'attr' => 'puppetvar', + "Text" => _('Please enter your Puppet variables for this node (e.g. config_exim=true).') . ' ' . _('Multiple values are separated by semicolon.') + ), + 'autoAdd' => array( + "Headline" => _('Automatically add this extension'), + "Text" => _('This will enable the extension automatically if this profile is loaded.') + ), + 'predefinedClasses' => array( + "Headline" => _('Predefined classes'), + "Text" => _('These classes will be available as autocompletion hints when adding new classes.') + ), + 'predefinedEnvironments' => array( + "Headline" => _('Predefined environments'), + "Text" => _('These environments will be available as autocompletion hints when setting the environment.') + ), + ); + // config options + $configContainer = new htmlTable(); + $configContainer->addElement(new htmlTableExtendedInputTextarea('puppetClient_environments', "production\r\ntesting", 30, 5, _('Predefined environments'), 'predefinedEnvironments'), true); + $return['config_options']['all'] = $configContainer; + // upload fields + $return['upload_columns'] = array( + array( + 'name' => 'puppetClient_environment', + 'description' => _('Environment'), + 'help' => 'environmentList', + 'example' => 'production' + ), + array( + 'name' => 'puppetClient_parentnode', + 'description' => _('Parent node'), + 'help' => 'parentnode', + 'example' => 'basenode' + ), + array( + 'name' => 'puppetClient_puppetclass', + 'description' => _('Classes'), + 'help' => 'puppetclassList', + 'example' => 'ntp;exim' + ), + array( + 'name' => 'puppetClient_puppetvar', + 'description' => _('Variables'), + 'help' => 'puppetvarList', + 'example' => 'config_exim=true;config_exim_trusted_users=root' + ), + ); + // available PDF fields + $return['PDF_fields'] = array( + 'environment' => _('Environment'), + 'parentnode' => _('Parent node'), + 'puppetclass' => _('Classes'), + 'puppetvar' => _('Variables'), + ); + // profile options + $profileContainer = new htmlTable(); + $profileContainer->addElement(new htmlTableExtendedInputCheckbox('puppetClient_addExt', false, _('Automatically add this extension'), 'autoAdd'), true); + $profileEnvironment = new htmlTableExtendedInputField(_('Environment'), 'puppetClient_environment', null, 'environment'); + $autocompleteEnvironment = array(); + if (isset($this->moduleSettings['puppetClient_environments'])) { + $autocompleteEnvironment = $this->moduleSettings['puppetClient_environments']; + } + $profileEnvironment->enableAutocompletion($autocompleteEnvironment); + $profileContainer->addElement($profileEnvironment, true); + $profileContainer->addElement(new htmlTableExtendedInputTextarea('puppetClient_puppetclass', '', 60, 5, _('Classes'), 'puppetclass'), true); + $profileContainer->addElement(new htmlTableExtendedInputTextarea('puppetClient_puppetvar', '', 60, 5, _('Variables'), 'puppetvar'), true); + $return['profile_options'] = $profileContainer; + // profile checks + $return['profile_checks']['puppetClient_environment'] = array('type' => 'ext_preg', 'regex' => 'ascii', 'error_message' => $this->messages['environment'][0]); + // profile mappings + $return['profile_mappings'] = array( + 'puppetClient_environment' => 'environment', + ); + return $return; + } + + /** + * This function fills the error message array with messages + */ + function load_Messages() { + $this->messages['environment'][0] = array('ERROR', _('The environment name may only contain ASCII characters.')); + $this->messages['environment'][1] = array('ERROR', _('Account %s:') . ' puppetClient_environment', _('The environment name may only contain ASCII characters.')); + $this->messages['puppetclass'][0] = array('ERROR', _('The class names may only contain ASCII characters.')); + $this->messages['puppetclass'][1] = array('ERROR', _('Account %s:') . ' puppetClient_puppetclass', _('The class names may only contain ASCII characters.')); + $this->messages['puppetvar'][0] = array('ERROR', _('The variables may only contain ASCII characters.')); + $this->messages['puppetvar'][1] = array('ERROR', _('Account %s:') . ' puppetClient_puppetvar', _('The variables may only contain ASCII characters.')); + $this->messages['parentnode'][0] = array('ERROR', _('Account %s:') . ' puppetClient_parentnode', _('Parent node not found.')); + } + + /** + * Returns a list of modifications which have to be made to the LDAP account. + * + * @return array list of modifications + *
This function returns an array with 3 entries: + *
array( DN1 ('add' => array($attr), 'remove' => array($attr), 'modify' => array($attr)), DN2 .... ) + *
DN is the DN to change. It may be possible to change several DNs (e.g. create a new user and add him to some groups via attribute memberUid) + *
"add" are attributes which have to be added to LDAP entry + *
"remove" are attributes which have to be removed from LDAP entry + *
"modify" are attributes which have to been modified in LDAP entry + *
"info" are values with informational value (e.g. to be used later by pre/postModify actions) + */ + function save_attributes() { + if (!in_array('puppetClient', $this->attributes['objectClass']) && !in_array('puppetClient', $this->orig['objectClass'])) { + // skip saving if the extension was not added/modified + return array(); + } + return parent::save_attributes(); + } + + /** + * Returns the HTML meta data for the main account page. + * + * @return htmlElement HTML meta data + */ + function display_html_attributes() { + if (isset($_POST['form_subpage_puppetClient_attributes_addObjectClass'])) { + $this->attributes['objectClass'][] = 'puppetClient'; + } + $return = new htmlTable(); + if (!in_array('puppetClient', $this->attributes['objectClass'])) { + $return->addElement(new htmlAccountPageButton('puppetClient', 'attributes', 'addObjectClass', _('Add Puppet extension'))); + return $return; + } + // environment + $autocompleteEnvironment = array(); + if (isset($this->moduleSettings['puppetClient_environments'])) { + $autocompleteEnvironment = $this->moduleSettings['puppetClient_environments']; + } + $environments = array(); + if (isset($this->attributes['environment'][0])) { + $environments = $this->attributes['environment']; + } + if (sizeof($environments) == 0) { + $environments[] = ''; + } + $environmentLabel = new htmlOutputText(_('Environment')); + $environmentLabel->alignment = htmlElement::ALIGN_TOP; + $return->addElement($environmentLabel); + $environmentContainer = new htmlGroup(); + for ($i = 0; $i < sizeof($environments); $i++) { + $environmentField = new htmlInputField('environment' . $i, $environments[$i]); + $environmentField->enableAutocompletion($autocompleteEnvironment); + $environmentContainer->addElement($environmentField); + if ($i < (sizeof($environments) - 1)) { + $environmentContainer->addElement(new htmlOutputText('
', false)); + } + else { + $environmentContainer->addElement(new htmlButton('addEnvironment', 'add.png', true)); + } + } + $return->addElement($environmentContainer); + $environmentHelp = new htmlHelpLink('environment'); + $environmentHelp->alignment = htmlElement::ALIGN_TOP; + $return->addElement($environmentHelp, true); + // parent node + $possibleParentNodes = $this->getPossibleParentNodes(); + array_unshift($possibleParentNodes, '-'); + $parentnode = '-'; + if (isset($this->attributes['parentnode'][0])) { + $parentnode = $this->attributes['parentnode'][0]; + } + $return->addElement(new htmlTableExtendedSelect('parentnode', $possibleParentNodes, array($parentnode), _('Parent node'), 'parentnode'), true); + // classes + $puppetclass = ''; + if (isset($this->attributes['puppetclass'])) { + $puppetclass = implode("\r\n", $this->attributes['puppetclass']); + } + $return->addElement(new htmlTableExtendedInputTextarea('puppetclass', $puppetclass, 60, 5, _('Classes'), 'puppetclass'), true); + // variables + $puppetvar = ''; + if (isset($this->attributes['puppetvar'])) { + $puppetvar = implode("\r\n", $this->attributes['puppetvar']); + } + $return->addElement(new htmlTableExtendedInputTextarea('puppetvar', $puppetvar, 60, 5, _('Variables'), 'puppetvar'), true); + + $return->addElement(new htmlSpacer(null, '10px'),true); + $remButton = new htmlAccountPageButton('puppetClient', 'attributes', 'remObjectClass', _('Remove Puppet extension')); + $remButton->colspan = 4; + $return->addElement($remButton); + return $return; + } + + /** + * Processes user input of the primary module page. + * It checks if all input values are correct and updates the associated LDAP attributes. + * + * @return array list of info/error messages + */ + function process_attributes() { + if (isset($_POST['form_subpage_puppetClient_attributes_remObjectClass'])) { + $this->attributes['objectClass'] = array_delete(array('puppetClient'), $this->attributes['objectClass']); + for ($i = 0; $i < sizeof($this->meta['attributes']); $i++) { + if (isset($this->attributes[$this->meta['attributes'][$i]])) { + unset($this->attributes[$this->meta['attributes'][$i]]); + } + } + return array(); + } + if (!in_array('puppetClient', $this->attributes['objectClass'])) { + return array(); + } + + $errors = array(); + // environment + $environmentCounter = 0; + while (isset($_POST['environment' . $environmentCounter])) { + $this->attributes['environment'][$environmentCounter] = $_POST['environment' . $environmentCounter]; + if (!get_preg($this->attributes['environment'][$environmentCounter], 'ascii')) $errors[] = $this->messages['environment'][0]; + if ($this->attributes['environment'][$environmentCounter] == '') { + unset($this->attributes['environment'][$environmentCounter]); + } + $environmentCounter++; + } + if (isset($_POST['addEnvironment'])) { + $this->attributes['environment'][] = ''; + } + $this->attributes['environment'] = array_values($this->attributes['environment']); + // parent node + if (isset($this->attributes['parentnode'][0]) && ($_POST['parentnode'] == '-')) { + unset($this->attributes['parentnode'][0]); + } + elseif ($_POST['parentnode'] != '-') { + $this->attributes['parentnode'][0] = $_POST['parentnode']; + } + // classes + $puppetclass = explode("\r\n", $_POST['puppetclass']); + for ($i = 0; $i < sizeof($puppetclass); $i++) { + if (trim($puppetclass[$i]) == '') { + unset($puppetclass[$i]); + continue; + } + if (!get_preg($puppetclass[$i], 'ascii')) { + $error = $this->messages['puppetclass'][0]; + $error[] = htmlspecialchars($puppetclass[$i]); + $errors[] = $error; + } + } + $this->attributes['puppetclass'] = array_values(array_unique($puppetclass)); + // variables + $puppetvar = explode("\r\n", $_POST['puppetvar']); + for ($i = 0; $i < sizeof($puppetvar); $i++) { + if (trim($puppetvar[$i]) == '') { + unset($puppetvar[$i]); + continue; + } + if (!get_preg($puppetvar[$i], 'ascii')) { + $error = $this->messages['puppetvar'][0]; + $error[] = htmlspecialchars($puppetvar[$i]); + $errors[] = $error; + } + } + $this->attributes['puppetvar'] = array_values(array_unique($puppetvar)); + + return $errors; + } + + /** + * In this function the LDAP account is built up. + * + * @param array $rawAccounts list of hash arrays (name => value) from user input + * @param array $ids list of IDs for column position (e.g. "posixAccount_uid" => 5) + * @param array $partialAccounts list of hash arrays (name => value) which are later added to LDAP + * @param array $selectedModules list of selected account modules + * @return array list of error messages if any + */ + function build_uploadAccounts($rawAccounts, $ids, &$partialAccounts, $selectedModules) { + $messages = array(); + for ($i = 0; $i < sizeof($rawAccounts); $i++) { + // add object class + if (!in_array("puppetClient", $partialAccounts[$i]['objectClass'])) $partialAccounts[$i]['objectClass'][] = "puppetClient"; + // parent node + if ($rawAccounts[$i][$ids['puppetClient_parentnode']] != "") { + if (!in_array($rawAccounts[$i][$ids['puppetClient_parentnode']], $this->getPossibleParentNodes())) { + $messages[] = $this->messages['parentnode'][0]; + } + else { + $partialAccounts[$i]['parentnode'][0] = $rawAccounts[$i][$ids['puppetClient_parentnode']]; + } + } + // environment + if ($rawAccounts[$i][$ids['puppetClient_environment']] != "") { + if (get_preg($rawAccounts[$i][$ids['puppetClient_environment']], 'ascii')) { + $partialAccounts[$i]['environment'] = explode(';', $rawAccounts[$i][$ids['puppetClient_environment']]); + } + else { + $errMsg = $this->messages['environment'][1]; + array_push($errMsg, array($i)); + $messages[] = $errMsg; + } + } + // classes + if ($rawAccounts[$i][$ids['puppetClient_puppetclass']] != "") { + if (get_preg($rawAccounts[$i][$ids['puppetClient_puppetclass']], 'ascii')) { + $partialAccounts[$i]['puppetclass'] = explode(';', $rawAccounts[$i][$ids['puppetClient_puppetclass']]); + } + else { + $errMsg = $this->messages['puppetclass'][1]; + array_push($errMsg, array($i)); + $messages[] = $errMsg; + } + } + // variables + if ($rawAccounts[$i][$ids['puppetClient_puppetvar']] != "") { + if (get_preg($rawAccounts[$i][$ids['puppetClient_puppetvar']], 'ascii')) { + $partialAccounts[$i]['puppetvar'] = explode(';', $rawAccounts[$i][$ids['puppetClient_puppetvar']]); + } + else { + $errMsg = $this->messages['puppetvar'][1]; + array_push($errMsg, array($i)); + $messages[] = $errMsg; + } + } + } + return $messages; + } + + /** + * Returns the PDF entries for this module. + * + * @return array list of possible PDF entries + */ + function get_pdfEntries() { + $return = array(); + if (in_array('puppetClient', $this->attributes['objectClass'])) { + if (isset($this->attributes['environment'][0])) { + $return['puppetClient_environment'][0] = '' . _('Environment') . '' . implode(', ', $this->attributes['environment']) . ''; + } + if (isset($this->attributes['parentnode'][0])) { + $return['puppetClient_parentnode'][0] = '' . _('Parent node') . '' . $this->attributes['parentnode'][0] . ''; + } + if (isset($this->attributes['puppetclass'][0])) { + $return['puppetClient_puppetclass'][0] = '' . _('Classes') . '' . $this->attributes['puppetclass'][0] . ''; + for ($i = 1; $i < sizeof($this->attributes['puppetclass']); $i++) { + $return['puppetClient_puppetclass'][] = '' . $this->attributes['puppetclass'][$i] . ''; + } + } + if (isset($this->attributes['puppetvar'][0])) { + $return['puppetClient_puppetvar'][0] = '' . _('Variables') . '' . $this->attributes['puppetvar'][0] . ''; + for ($i = 1; $i < sizeof($this->attributes['puppetvar']); $i++) { + $return['puppetClient_puppetvar'][] = '' . $this->attributes['puppetvar'][$i] . ''; + } + } + } + return $return; + } + + /** + * This function defines what attributes will be used in the account profiles and their appearance in the profile editor. + * + * Calling this method does not require the existence of an enclosing {@link accountContainer}.
+ *
+ * The return value is an object implementing htmlElement.
+ * The field name are used as keywords to load + * and save profiles. We recommend to use the module name as prefix for them + * (e.g. posixAccount_homeDirectory) to avoid naming conflicts. + * + * @return htmlElement meta HTML object + * + * @see baseModule::get_metaData() + * @see htmlElement + */ + public function get_profileOptions() { + $return = parent::get_profileOptions(); + $possibleParentNodes = $this->getPossibleParentNodes(); + if (sizeof($possibleParentNodes) > 0) { + $return->addElement(new htmlTableExtendedSelect('puppetClient_parentnode', $possibleParentNodes, array(), _('Parent node'), 'parentnode'), true); + } + return $return; + } + + /** + * Loads the values of an account profile into internal variables. + * + * @param array $profile hash array with profile values (identifier => value) + */ + function load_profile($profile) { + // profile mappings in meta data + parent::load_profile($profile); + // add extension + if (isset($profile['puppetClient_addExt'][0]) && ($profile['puppetClient_addExt'][0] == "true")) { + if (!in_array('puppetClient', $this->attributes['objectClass'])) { + $this->attributes['objectClass'][] = 'puppetClient'; + } + } + // parent node + if (isset($profile['puppetClient_parentnode'][0]) && ($profile['puppetClient_parentnode'][0] != "-")) { + $this->attributes['parentnode'][0] = $profile['puppetClient_parentnode'][0]; + } + // classes + if (isset($profile['puppetClient_puppetclass'][0]) && ($profile['puppetClient_puppetclass'][0] != '')) { + $this->attributes['puppetclass'] = $profile['puppetClient_puppetclass']; + } + // variables + if (isset($profile['puppetClient_puppetvar'][0]) && ($profile['puppetClient_puppetvar'][0] != '')) { + $this->attributes['puppetvar'] = $profile['puppetClient_puppetvar']; + } + } + + /** + * Reurns a list of valid parent nodes for this node. + * + * @return array parent nodes (e.g. array('node1', 'node2')) + */ + private function getPossibleParentNodes() { + $possibleParentNodes = array(); + $searchResult = searchLDAPByAttribute('cn', '*', 'puppetClient', array('cn'), array('host')); + $possibleParentNodes = array(); + for ($i = 0; $i < sizeof($searchResult); $i++) { + if (!get_preg($searchResult[$i]['cn'][0], 'ascii')) { + continue; + } + if (($this->getAccountContainer() == null) || $this->getAccountContainer()->isNewAccount + || (!isset($this->getAccountContainer()->attributes_orig['cn'][0]) + || ($this->getAccountContainer()->attributes_orig['cn'][0] != $searchResult[$i]['cn'][0]))) { + $possibleParentNodes[] = $searchResult[$i]['cn'][0]; + } + } + return $possibleParentNodes; + } + +} + +?>