{= include("docs.util"); start_docs_page(docs.technical_manual.page_titles.newsletters); =}
![]() |
Sawmill Newsletter July 15, 2008 |
Sawmill's "Create Many Profiles" feature makes it easy to add new
profiles automatically, by embedding Sawmill in a larger environment,
and calling the "create many profiles" Salang script to generate or
regenerate many similar profiles from a single template. See the November
15, 2007 Sawmill Newsletter for a discussion of Create Many
Profiles.
But what if you want to have a separate user for each profile, and want that to be created automatically too? This might be useful in a web hosting environment, where you want each user to be able to log into Sawmill to see only the reports for their own domains; so for each customer, you would create a Sawmill user, and then give it permission to access its own domains (profiles), and finally give the customer a direct link to the Sawmill web interface (or, including Sawmill as a "tab" in a larger interface).
This can be done using Salang scripting. This newsletter includes an
example script, and how to use it.
The Add User Script
Here is a script which adds a user (or modifies an existing user):
# This script adds a non-administrative user with access to one profile. # # Usage: sawmill -dp miscellaneous.add_user v.username <username> v.password <password> v.profile <profile> {= # Create the user node for v.username (in the "users" node, i.e., the users.cfg file in LogAnalysisInfo), # if it doesn't already exist set_subnode_value('users', v.username, ''); # Set the username to v.username set_subnode_value('users.' . v.username, 'username', v.username); # Set the password. This computes the MD5 checksum of v.password, and puts it in the password_checksum of the user node set_subnode_value('users.' . v.username, 'password_checksum', md5_digest(v.password) ); # Make this a non-administrative user set_subnode_value('users.' . v.username, 'administrator', false); # Add a "profiles" subnode in the user record, where we can list the profile accessible to this user. set_subnode_value('users.' . v.username, 'profiles', ''); # Give the user access to one profile: the one specified by v.profile set_subnode_value('users.' . v.username . '.profiles', v.profile, true); # Save the users node (users.cfg) save_node('users'); # Display the new (or modified) users node echo(node_as_string('users.' . v.username)); =} |
To use this script, put it in the miscellaneous folder, which is in
the LogAnalysisInfo folder of your Sawmill installation. Then, from the
command line, run this on Windows (assuming Sawmill is installed at
C:\Program Files\Sawmill 7):
C:
cd Program Files\Sawmill 7
SawmillCL -dp miscellaneous.add_user v.username username
v.password password v.profile profile
Or on other operating systems, run this command line:
cd sawmill-install-dir
./sawmill -dp miscellaneous.add_user v.username username
v.password password v.profile profile
(./sawmill may need to be qualified with the version number, e.g.,
./sawmill7.2.15).
1. The {= and =} tags.Conclusion
The {= before the script and =} after the scripts are used in a CFV ("configuration value") file to indicate a section of Salang code. Without these tags, the entire CFV file is treated as a literal string. When {= and =} are present, the section between them is compiled and executed, and its result is inserted in the resulting string. In this case, we're not using the result string at all--we just want to have an effect when we run the script--so the entire script is embedded in {= =}.
2. Comments start with #
All lines beginning with # are comments, and are ignored by Salang. These are for documentation purposes only, and have no effect on the code.
3. set_subnode_value('users', v.username, '');
The set_subnode_value() function in Salang sets a value within a node. A node is a general Salang data structure, which is similar to a perl hash (or, to a lesser degree, a C structure). Unlike perl hashes or C structures, however, Salang nodes can reside in memory, or on disk, or both. Referring to a node by the name 'users' indicates that it is a top-level node called 'users'; and because there is a file called "users.cfg" in LogAnalysisInfo, Sawmill automatically equates the two, and this function operates on the node whose content is described by the file users.cfg. Because that is the file which contains Sawmill User information, this line operates directly on the user information file. The contents of the file is loaded into memory automatically, when the node is referenced, and the modifications are made to the in-memory copy. The changes are not saved to disk until save_node() is called, below.
So, this operates on the "users" node, and in this case it is setting the value of a subnode. The name of the subnode is the value of v.username, which is a variable specified by the v.username command-line parameter. v.username is also a node: it is the "username" subnode of the top-level "v" node, which does not have an on-disk counterpart, so remains in memory. The "v" node is often used for temporary variables, but has no particular significance to Salang--it could have been called x.username, as long as both the command line and the script called it that.
The last parameter to set_subnode_value(), which specifies the value to assign to the subnode, is empty. So this line sets the subnode whose name is in the variable v.username to "". If the value v.username is "jenny", then a subnode "jenny" will be created in "users" (users.cfg), and set to empty. This creates a new user record, called "jenny."
By the way, this code uses single-quotes ('), but double-quotes (") and backticks (`) are all treated identically by Salang. So the script would work the same if all single quotes were double quotes, or if they were all backticks.
Assuming there were no users before this line ran, the "users" node would look like this after this line:
users = {
jenny = ""
} # users
So, the user record has been created, but has no values in it yet. Note that the file users.cfg has not yet been modified, and won't be until save_node() is called, below.
4. set_subnode_value('users.' . v.username, 'username', v.username);
This is similar to #2, above. But the first parameter uses the concatenation operator (.) to concatentate the value of the v.username variable to the literal string "users.". If the value of v.username was "jenny", the concatenation would be "users.jenny", so that is the node we are operating on. In node names (like "users.jenny"), a dot is a hierarchy divider, so "users.jenny" refers to the subnode "jenny" of the node "users". So this line sets the subnode "username" of the node "users.jenny" to the value of v.username. This adds a "username" parameter, with value "jenny", to the user record for "jenny".
The "users" node would look like this after this line:
users = {
jenny = {
username = "jenny"
} # jenny
} # users
5. set_subnode_value('users.' . v.username, 'password_checksum', md5_digest(v.password) );
As with #3, this sets a subnode of the user node (users.jenny). In this case, it's setting the password_checksum node. For security, the password is not stored plain-text in the users node, so it is first encoded using the built-in function md5_digest(), before being written to the password_checksum node.
The "users" node would look like this after this line:
users = {
jenny = {
username = "jenny"
password_checksum = "4ed9407630eb1000c0f6b63842defa7d"
} # jenny
} # users
6. set_subnode_value('users.' . v.username, 'administrator', false);
As with #3 and #4, this sets a subnode of the user node (users.jenny). Here, it sets the "administrator" node to false, indicating that this user is not an administrator.
The "users" node would look like this after this line:
users = {
jenny = {
username = "jenny"
password_checksum = "4ed9407630eb1000c0f6b63842defa7d"
administrator = false
} # jenny
} # users
7. set_subnode_value('users.' . v.username, 'profiles', '');
As with #3, #4, and #5 this sets a subnode of the user node. Here, it creates a "profiles" subnode of the users node. This node is empty at first, but can be filled with one or more profile names, indicating which profiles the user may access.
The "users" node would look like this after this line:
users = {
jenny = {
username = "jenny"
password_checksum = "4ed9407630eb1000c0f6b63842defa7d"
administrator = false
profiles = ""
} # jenny
} # users
8. set_subnode_value('users.' . v.username . '.profiles', v.profile, true);
This sets a subnode of the "profiles" node created in step 6. It uses the concatenation operator to concatenate "users.", the value of v.username, and ".profiles"; if v.profile is "jenny" (as above), this string is "users.jenny.profiles", which points to the "profiles" subnode of the "jenny" subnode of the "users" node (which is users.cfg in LogAnalysisInfo). This operates on the subnode specified by the value of v.profile. The v.profile value is specified on the command line, so for this example, we'll assume it is "jennys_site". This subnode does not exist, so it is created, and its value is set to true (the third parameter above).
The "users" node would look like this after this line:
users = {
jenny = {
username = "jenny"
password_checksum = "4ed9407630eb1000c0f6b63842defa7d"
administrator = false
profiles = {
jennys_site = true
} # profiles
} # jenny
} # users
9. save_node('users');
This saves the node 'users' to its natural position on disk, which is the users.cfg file in LogAnalysisInfo. The content of users.cfg is replaced by the "users" node shown in the box above. After this line, the user modification is complete, and Sawmill will immediately allow logins by the new user and there is no need to restart the service.
10. echo(node_as_string('users.' . v.username));
This displays to console (standard output) the contents of the subnode specified by v.username, in the "users" node. In the example above, it would display this to console:
jenny = {
username = "jenny"
password_checksum = "4ed9407630eb1000c0f6b63842defa7d"
administrator = false
profiles = {
jennys_site = true
} # profiles
} # jenny
This provides some feedback of what the script did, allowing you to verify the new or modified user record.