mirror of
https://github.com/xcat2/confluent.git
synced 2026-09-29 00:31:09 +00:00
Compare commits
160 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d64b3e6a4e | |||
| 637e0f0a81 | |||
| e26218eb92 | |||
| 03eb026b61 | |||
| beab6a3c02 | |||
| 5e0ebce300 | |||
| 85c4ec5654 | |||
| 7957a6abd4 | |||
| 39a7d8d131 | |||
| 54c5bf128e | |||
| ca0c592044 | |||
| 829d1316b2 | |||
| c2c1c85651 | |||
| c5833f1417 | |||
| 07ae3593c3 | |||
| 8ab35b11cd | |||
| 35ef6170ba | |||
| d650f11255 | |||
| b4926b39fe | |||
| 53ccee734f | |||
| fe9d0e84d3 | |||
| 4e7e12f90f | |||
| 8ae68cd34a | |||
| 607d69b1e1 | |||
| 50e62c7b73 | |||
| 363eff831f | |||
| 31820ddfd7 | |||
| 153749aebe | |||
| 7c5dd85e74 | |||
| 7a5bf8a06f | |||
| 9c05057deb | |||
| 8b1902601f | |||
| d94d9d0a78 | |||
| 457b0851c9 | |||
| d2cae9faca | |||
| e27739eafd | |||
| 874302537d | |||
| cd3b67d4cc | |||
| 7aacac2fb7 | |||
| a69fb3fec3 | |||
| 2d9cbe4f33 | |||
| e33ed0afc8 | |||
| ace6544444 | |||
| 1468c56e1f | |||
| b36756ebbc | |||
| a8f378deb7 | |||
| cb4e53c459 | |||
| 3e0b283593 | |||
| 3b4e9fd2e4 | |||
| 6af6195798 | |||
| a8d1f484b5 | |||
| 619c60533d | |||
| c6fd760006 | |||
| 03f88c6e3e | |||
| 697b33ae80 | |||
| 79ef1c4811 | |||
| f442e949aa | |||
| 8050d3f69a | |||
| 66efa1da5c | |||
| df97e808c6 | |||
| 98d14344ce | |||
| a149b0bcd0 | |||
| 0be1e350e9 | |||
| 4818bd57bb | |||
| 7763327a63 | |||
| dac957364f | |||
| 528dd4c3e8 | |||
| e40c362743 | |||
| 7fdb4dccca | |||
| a3c8c305c1 | |||
| aeb1b704b3 | |||
| f2cb6ea535 | |||
| 5a81279b9d | |||
| 9954bf5b51 | |||
| 309ea00f10 | |||
| cafbc1d1c2 | |||
| 797e197bc7 | |||
| 19d4a3a83f | |||
| df328d6812 | |||
| 7da0dfa0bb | |||
| d3a699a8fb | |||
| 82598d02d8 | |||
| 874947076d | |||
| 8032ea36be | |||
| 8ad130e14c | |||
| ffe0754dd9 | |||
| 5aa6fbc019 | |||
| 7026fdfa3a | |||
| 9dbecd4c95 | |||
| d85219d5fa | |||
| bbb8e50c3e | |||
| 26782811d6 | |||
| e38dbc4470 | |||
| a26624a614 | |||
| 4f246e6a41 | |||
| c53e758170 | |||
| 1a7c238b54 | |||
| 6a88f35fc2 | |||
| 7a27fba94b | |||
| 94a4a7e20b | |||
| 2a87c32800 | |||
| d99bc37ed3 | |||
| 2c40d2fd0d | |||
| 611927d2ff | |||
| eb9ae61e75 | |||
| 4ae102018a | |||
| 4f01545328 | |||
| 762fb8259d | |||
| 2ca146c1aa | |||
| 16f40efb69 | |||
| a51a428761 | |||
| a5261cc1da | |||
| c8166b7cce | |||
| a11317c994 | |||
| 279f622026 | |||
| 32fa07d9ce | |||
| c9157b90eb | |||
| 2e63ff3aca | |||
| 5d0423c38b | |||
| e3c17491e5 | |||
| da7b7c8923 | |||
| 5861af799c | |||
| 2b0353c771 | |||
| bc6c3c5e68 | |||
| 37ec16058f | |||
| b3efd2f31d | |||
| 357ebfca3f | |||
| 7e0cd9d709 | |||
| ba321551e4 | |||
| d96026ef5f | |||
| 08aa667b13 | |||
| 0cb93c4bea | |||
| 7b3ecc6e4f | |||
| 5a5b3f9927 | |||
| 4455226ee0 | |||
| 0881d74731 | |||
| 55d7191573 | |||
| bc9d0a9d3b | |||
| 866f138652 | |||
| ee8ad66959 | |||
| be0e47142b | |||
| 4131f1ba4f | |||
| f2966bfeaf | |||
| eee6330aaf | |||
| aa9ece529f | |||
| 865d18d367 | |||
| e583d34555 | |||
| b27d07f304 | |||
| 50e60a5a17 | |||
| 8bf7e045d5 | |||
| f0f4427dc3 | |||
| b1f1f15ba8 | |||
| 09ca449eab | |||
| d08ce9e563 | |||
| ce92ea4084 | |||
| 0695f8e827 | |||
| 7609a46594 | |||
| 247838f70c | |||
| 594d419ca0 | |||
| da0c83513a |
@@ -193,5 +193,7 @@ def main():
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
handler(noderange, media)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
sys.exit(exitcode)
|
||||
|
||||
@@ -116,7 +116,7 @@ class OptParser(optparse.OptionParser):
|
||||
def main():
|
||||
argparser = OptParser(
|
||||
usage="Usage: %prog <noderange> servicedata "
|
||||
"<filename>",
|
||||
"<directory or filename>",
|
||||
epilog='\nservicedata will save service data to the given '
|
||||
'directory. It is saved to the location on the relevant '
|
||||
'management server (the confluent server if running remote, '
|
||||
|
||||
@@ -37,6 +37,8 @@ except ImportError:
|
||||
|
||||
def plot(gui, output, plotdata, bins):
|
||||
import matplotlib as mpl
|
||||
if gui and mpl.get_backend() == 'agg':
|
||||
sys.stderr.write('Error: No GUI backend available and -g specified!\n')
|
||||
if not gui:
|
||||
mpl.use('Agg')
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
@@ -3,7 +3,7 @@ collate(1) -- Organize text input by node
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`<other command> | collate [-a] [-d] [-w] [-s] [-c] [-r] [-l lognametemplate]`
|
||||
`<other command> | collate [-a] [-b] [-d] [-w] [-g] [-s] [-c] [-r] [-l lognametemplate]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -21,19 +21,23 @@ node and group names sorted alphanumerically.
|
||||
when output differs along a telling group boundary. For example, if
|
||||
output suggests a large number of nodes are unreachable, abbreviate
|
||||
showing 'rack1' as being unreachable may make more obvious a possible cause.
|
||||
|
||||
* `-b`, `--base`:
|
||||
Use given node as reference for comparison when using
|
||||
-d, instead of using the most common result
|
||||
|
||||
* `-d`, `--diff`:
|
||||
Express all but the most common result group in terms of diff from
|
||||
the most common result group
|
||||
|
||||
* `-l`, `--log`:
|
||||
Save output per node to individual log files, replacing {node} in the name
|
||||
with the nodename of each
|
||||
|
||||
|
||||
* `-w`, `--watch`:
|
||||
Update results dynamically as data becomes available, rather than
|
||||
waiting for the command to fully complete.
|
||||
|
||||
|
||||
* `-g`, `--groupcount`:
|
||||
Show count of output groups rather than the actual
|
||||
output
|
||||
|
||||
* `-s`, `--skipcommon`:
|
||||
Suppress printing of the most common result text group. This is used to
|
||||
focus on stray output against a well known and expected result.
|
||||
@@ -45,6 +49,10 @@ node and group names sorted alphanumerically.
|
||||
* `-r`, `--reverse`:
|
||||
Rather than starting with most common to least common, start with
|
||||
the least common and print the most common last.
|
||||
|
||||
* `-l`, `--log`:
|
||||
Save output per node to individual log files, replacing {node} in the name
|
||||
with the nodename of each
|
||||
|
||||
## EXAMPLES
|
||||
* Organizing power state of multiple nodes:
|
||||
|
||||
@@ -6,7 +6,8 @@ collective(1) -- Check and manage a confluent collective
|
||||
`collective invite <server>`
|
||||
`collective join <server> [-i TOKEN]`
|
||||
`collective show`
|
||||
`collective gencert`
|
||||
`collective gencert`
|
||||
`collective delete`
|
||||
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -15,6 +15,18 @@ be mostly an aid for developing client software, with
|
||||
day to day administration generally being easier with
|
||||
the various function specific commands.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-s`, `--server`:
|
||||
Confluent instance to connect to
|
||||
|
||||
* `-c`, `--control`:
|
||||
Path to offer terminal control
|
||||
|
||||
* `-m`, `--mintime`:
|
||||
Minimum time to run or else pause for input (used to
|
||||
keep a terminal from closing quickly on error)
|
||||
|
||||
## COMMANDS
|
||||
|
||||
The CLI may be navigated by shell commands and some other
|
||||
|
||||
@@ -3,8 +3,7 @@ confluentdbutil(8) -- Backup or restore confluent database
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
|
||||
`confluentdbutil [options] <dump|restore> <path>`
|
||||
`confluentdbutil [options] [dump|restore] <path>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -19,8 +18,25 @@ the json files (password protected, removed from the files, or unprotected).
|
||||
* `-p`, `--password`:
|
||||
If specified, information such as usernames and passwords will be encrypted
|
||||
using the given password.
|
||||
|
||||
* `-i`, `--interactivepassword`:
|
||||
Prompt for password.
|
||||
|
||||
* `-r`, `--redact`:
|
||||
Indicates to replace usernames and passwords with a dummy string rather
|
||||
than included.
|
||||
|
||||
* `-u`, `--unprotected`:
|
||||
The keys.json file will include the encryption keys without any protection.
|
||||
The keys.json file will include the encryption keys without any protection.
|
||||
|
||||
* `-s`, `--skipkeys`:
|
||||
This specifies to dump the encrypted data without
|
||||
dumping the keys needed to decrypt it. This is
|
||||
suitable for an automated incremental backup, where an
|
||||
earlier password protected dump has a protected
|
||||
keys.json file, and only the protected data is needed.
|
||||
keys do not change and as such they do not require
|
||||
incremental backup.
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
@@ -40,12 +40,25 @@ See nodegroupattrib(8) command on how to manage attributes on a group level.
|
||||
|
||||
* `-b`, `--blame`:
|
||||
Annotate inherited and expression based attributes to show their base value.
|
||||
|
||||
* `-c`, `--clear`:
|
||||
Clear specified nodeattributes
|
||||
Clear specified nodeattributes.
|
||||
|
||||
* `-e`, `--environment`:
|
||||
Set specified attributes based on exported environment variable of matching name. Environment variable names may be lower case or all upper case. Replace . with _ as needed (e.g. info.note may be specified as either $info_note or $INFO_NOTE
|
||||
Set specified attributes based on exported environment variable of matching name.
|
||||
Environment variable names may be lower case or all upper case.
|
||||
Replace . with _ as needed (e.g. info.note may be specified as either $info_note or $INFO_NOTE
|
||||
|
||||
* `-p`, `--prompt`:
|
||||
Request interactive prompting to provide values rather than the command line or environment variables.
|
||||
Request interactive prompting to provide values rather than the command line
|
||||
or environment variables.
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Prompt if trying to set attributes on more than
|
||||
specified number of nodes.
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
* Listing matching nodes of a simple noderange:
|
||||
|
||||
@@ -9,6 +9,15 @@ nodebmcreset(8) -- Reset management controller
|
||||
|
||||
`nodebmcreset` allows you to reset the management controller of the specified noderange
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Number of nodes to affect before prompting for
|
||||
confirmation
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES:
|
||||
|
||||
* Reset the management controller for nodes n1 through n4:
|
||||
|
||||
@@ -3,8 +3,7 @@ nodeboot(8) -- Reboot a confluent node to a specific device
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodeboot <noderange>`
|
||||
`nodeboot [options] <noderange>` [default|cd|network|setup|hd]
|
||||
`nodeboot [options] <noderange> [default|cd|network|setup|hd]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -25,6 +24,13 @@ device without inducing a reboot, see the `nodesetboot` command.
|
||||
* `-p`, `--persist`:
|
||||
For a system that supports it, mark the boot override to persist rather than
|
||||
be a one time change. Many systems do not support this functionality.
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to boot, prompting
|
||||
if over the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
* `default`:
|
||||
Request a normal default boot with no particular device override
|
||||
|
||||
@@ -3,8 +3,7 @@ nodeconfig(8) -- Show or change node configuration
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodeconfig <noderange> [options] [<configuration>..]`
|
||||
`nodeconfig <noderange> [options] [<configuration=value>..]`
|
||||
`nodeconfig [options] <noderange> [setting|setting=value]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -27,6 +26,13 @@ actually be in effect until a reboot.
|
||||
no configuration values are specified, it will show only those that differ.
|
||||
If combined with `-x`, will show all differing values except those indicated
|
||||
by `-x`
|
||||
|
||||
* `-b`, `--batch`:
|
||||
Provide arguments as lines of a file, rather than the command line.
|
||||
|
||||
* `-d`, `--detail`:
|
||||
Provide detailed data as available. This can include help text and valid
|
||||
values for a setting.
|
||||
|
||||
* `-e`, `--extra`:
|
||||
Read settings that are generally not needed, but may be slow to retrieve.
|
||||
@@ -37,18 +43,21 @@ actually be in effect until a reboot.
|
||||
Rather than listing only the specified configuration parameters, list all
|
||||
attributes except for the specified ones
|
||||
|
||||
* `-d`, `--detail`:
|
||||
Provide detailed data as available. This can include help text and valid
|
||||
values for a setting.
|
||||
* `-a`, `--advanced`:
|
||||
Include advanced settings, which are normally not intended to be used
|
||||
without direction from the relevant server vendor.
|
||||
|
||||
* `-r`, `--restoredefault`:
|
||||
Request that the specified component of the targeted nodes will have its
|
||||
configuration reset to default. Currently the only component implemented
|
||||
is uefi.
|
||||
|
||||
* `-b`, '--batch':
|
||||
Provide arguments as lines of a file, rather than the command line.
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to configure, prompting if over
|
||||
the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
* Showing the current IP configuration of noderange BMC/IMM/XCC:
|
||||
|
||||
@@ -3,9 +3,7 @@ nodedeploy(8) -- Request preparation and/or initiating a node deployment
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodedeploy <noderange>`
|
||||
`nodedeploy <noderange> -c`
|
||||
`nodedeploy <noderange> [-n] [-p] <name of profile>`
|
||||
`nodedeploy [-h] [-c] [-n] [-p] [-m MAXNODES] <noderange> [profile]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -19,7 +17,7 @@ deployment status.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* '-c', '--clear`:
|
||||
* `-c`, `--clear`:
|
||||
Remove any pending deployment action
|
||||
|
||||
* `-n`, `--network`:
|
||||
@@ -28,6 +26,12 @@ deployment status.
|
||||
* `-p`, `--prepareonly`:
|
||||
Prepare the network services for deployment, but do not interact with BMCs. This is intended for scenarios where
|
||||
the boot device control and server restart will be handled outside of confluent.
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specifiy a maximum nodes to be deployed.
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
* Begin the instalalation of a profile of CentOS 8.2:
|
||||
|
||||
@@ -3,14 +3,22 @@ nodeeventlog(8) -- Pull eventlog from confluent nodes
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodeeventlog <noderange>`
|
||||
`nodeeventlog <noderange> [clear]`
|
||||
`nodeeventlog [options] <noderange> [clear]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodeeventlog` pulls and optionally clears the event log from the requested
|
||||
noderange.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to clear if clearing log, prompting if
|
||||
over the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
* Pull the event log from n2 and n3:
|
||||
`# nodeeventlog n2,n3`
|
||||
|
||||
@@ -3,9 +3,7 @@ nodefirmware(8) -- Report firmware information on confluent nodes
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodefirmware <noderange>`
|
||||
`nodefirmware <noderange> list|<components>|core`
|
||||
`nodefirmware <noderange> update [--backup] <filename>`
|
||||
`nodefirmware <noderange> [list][update [--backup <file>]]|[<components>]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -26,6 +24,18 @@ the out of band facilities. Firmware updates can end in one of three states:
|
||||
* `pending`: The firmware update process has completed, but the firmware will not be active until the relevant component next resets. Generally speaking, for UEFI update the system will need a reboot, and for BMC updates, the `nodebmcreset` command will begin the process to activate the firmware.
|
||||
* `complete`: The firmware update process has completed and activation has proceeded. Note that while the activation process has commenced, the component may still be in the process of rebooting when nodefirmware exits.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-b`, `--backup`:
|
||||
Target a backup bank rather than primary
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
When updating, prompt if more than the specified number of servers will
|
||||
be affected
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Pull firmware from a node:
|
||||
|
||||
@@ -25,8 +25,17 @@ the attributes are set on the node versus a group to which a node belongs.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-b`, `--blame`:
|
||||
Show information about how attributes inherited
|
||||
|
||||
* `-e`, `--environment`:
|
||||
Set attributes, but from environment variable of same name
|
||||
|
||||
* `-c`, `--clear`:
|
||||
Clear specified nodeattributes.
|
||||
|
||||
* `-p`, `--prompt`:
|
||||
Prompt for attribute values interactively
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
|
||||
@@ -15,3 +15,7 @@ to that directory. The `install` command will take the specified filename and in
|
||||
argument may be of the form xcc_fod_0034_7X21{id.serial}.key to have the serial number substituted
|
||||
to allow unique licenses to be specified in a single command.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m`, `--maxnodes`:
|
||||
Specify a maximum number of nodes to delete licenses from, prompting if over the threshold
|
||||
|
||||
@@ -34,8 +34,8 @@ the host platform attempts.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-c`:
|
||||
Specify the maximum number of instances to run concurrently.
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
* Listing currently mounted media:
|
||||
|
||||
@@ -30,6 +30,9 @@ off will not react to this request.
|
||||
|
||||
* `-p`, '--showprevious':
|
||||
Show previous power state for all directives that may change power state.
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
* Get power state of nodes n1 through n4:
|
||||
|
||||
@@ -9,6 +9,11 @@ noderemove(8) -- Remove nodes from the confluent management service
|
||||
|
||||
`noderemove` simply removes the given noderange from the confluent database.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m`, `--maxnodes`:
|
||||
Specify a maximum number of nodes to delete, prompting if over the
|
||||
threshold
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
|
||||
@@ -11,9 +11,13 @@ nodereseat(8) -- Request a reseat of a node
|
||||
node's slot. This should be equivalent to removing the system entirely from
|
||||
the chassis and putting it back in, but without actually having to do so.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m`, `--maxnodes`:
|
||||
Specify a maximum number of nodes to reseat, prompting if over the threshold
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Reseating the node `s1`:
|
||||
`# nodereseat s1`
|
||||
`s1: Reseat successful`
|
||||
|
||||
|
||||
@@ -12,10 +12,12 @@ noderange. This will present progress as percentage for all nodes.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m`:
|
||||
Specify maximum number of nodes for noderange max.
|
||||
|
||||
* `-c`:
|
||||
* `-c`, `-f`, `--count`:
|
||||
Specify how many rsync executions to do concurrently. If noderange
|
||||
exceeds the count, then excess nodes will wait until one of the
|
||||
active count completes.
|
||||
active count completes.
|
||||
|
||||
* `-m`, `--maxnodes`:
|
||||
Specify a maximum number of nodes to run rsync to, prompting if over the
|
||||
threshold
|
||||
|
||||
|
||||
@@ -13,6 +13,21 @@ nodeattribexpressions(5) are expanded prior to execution of the command. For
|
||||
noderun, the commands are locally executed. To execute commands on the nodes
|
||||
themselves, see nodeshell(8).
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-f`, `-c`, `--count`:
|
||||
Number of commands to run at a time
|
||||
|
||||
* `-n`, `--nonodeprefix`:
|
||||
Do not prefix output with node names
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to run the command with, prompting if over
|
||||
the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Run ping against nodes n1 through n4:
|
||||
|
||||
@@ -2,8 +2,7 @@ nodesetboot(8) -- Check or set next boot device for noderange
|
||||
====================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodesetboot <noderange>`
|
||||
|
||||
`nodesetboot [options] <noderange> [default|cd|network|setup|hd|usb|floppy]`
|
||||
|
||||
## DESCRIPTION
|
||||
@@ -21,6 +20,8 @@ to use `nodesetboot <noderange> setup` and then initiate a reboot from within
|
||||
the operating system with ssh or similar rather than using the remote hardware
|
||||
control.
|
||||
|
||||
Running the command with no target queries the current setting.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-b`, `--bios`:
|
||||
@@ -33,6 +34,13 @@ control.
|
||||
|
||||
* `-u`, `--uefi`:
|
||||
This flag does nothing, it is for command compatibility with xCAT's rsetboot
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to modify next boot device, prompting if
|
||||
over the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
* `default`:
|
||||
Request a normal default boot with no particular device override
|
||||
|
||||
@@ -3,7 +3,7 @@ nodeshell(8) -- Execute command on many nodes in a noderange through ssh
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodeshell <noderange> [options] <command to execute on each node>`
|
||||
`nodeshell [options] <noderange> <command to execute on each node>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -13,10 +13,20 @@ nodeattribexpressions(5). `nodeshell` provides stdout as stdout and stderr
|
||||
as stderr, unlike psh which combines all stdout and stderr into stdout.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-c COUNT`, `-f COUNT`, `--count=COUNT`
|
||||
Specify the maximum number of instances to run concurrently
|
||||
|
||||
* `-c`:
|
||||
Specify the maximum number of instances to run concurrently.
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`
|
||||
Specify a maximum number of nodes to run remote ssh command to, prompting
|
||||
if over the threshold
|
||||
|
||||
* `-n`, `--nonodeprefix`
|
||||
Do not prefix output with node names
|
||||
|
||||
* `-p PORT`, `--port=PORT`
|
||||
Specify a custom port for ssh
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Running `echo hi` on for nodes:
|
||||
|
||||
@@ -19,6 +19,12 @@ running in collective mode.
|
||||
Note that due to vendor filename requirements, any filename may have vendor
|
||||
specific suffixes added to any file produced.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m`, `--maxnodes`:
|
||||
Specify a maximum number of nodes to download diagnostic data from, prompting
|
||||
if over the threshold
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Download support data from a single node to a specific filename
|
||||
|
||||
@@ -5,7 +5,7 @@ osdeploy(8) --- Configure general OS deployment facilities of confluent
|
||||
|
||||
`osdeploy import <iso>`
|
||||
`osdeploy updateboot <profile>`
|
||||
`osdeploy initialize [-h] [-g] [-u] [-s] [-k] [-t] [-p] [-i] [-l]`
|
||||
`osdeploy initialize [-h] [-g] [-u] [-s] [-k] [-t] [-p] [-i] [-l] [-a]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
|
||||
@@ -100,6 +100,9 @@ class HTTPSClient(client.HTTPConnection, object):
|
||||
self.sock = ctx.wrap_socket(psock, server_hostname=host)
|
||||
|
||||
def grab_url(self, url, data=None, returnrsp=False):
|
||||
return self.grab_url_with_status(url, data, returnrsp)[1]
|
||||
|
||||
def grab_url_with_status(self, url, data=None, returnrsp=False):
|
||||
if data:
|
||||
method = 'POST'
|
||||
else:
|
||||
@@ -108,9 +111,9 @@ class HTTPSClient(client.HTTPConnection, object):
|
||||
rsp = self.getresponse()
|
||||
if rsp.status >= 200 and rsp.status < 300:
|
||||
if returnrsp:
|
||||
return rsp
|
||||
return rsp.status, rsp
|
||||
else:
|
||||
return rsp.read()
|
||||
return rsp.status, rsp.read()
|
||||
raise Exception(rsp.read())
|
||||
|
||||
if __name__ == '__main__':
|
||||
@@ -121,8 +124,26 @@ if __name__ == '__main__':
|
||||
if len(sys.argv) == 1:
|
||||
HTTPSClient()
|
||||
sys.exit(0)
|
||||
if sys.argv[-2] == '-o':
|
||||
with open(sys.argv[3], 'wb') as outf:
|
||||
try:
|
||||
outbin = sys.argv.index('-o')
|
||||
sys.argv.pop(outbin)
|
||||
outbin = sys.argv.pop(outbin)
|
||||
except ValueError:
|
||||
outbin = None
|
||||
try:
|
||||
waitfor = sys.argv.index('-w')
|
||||
sys.argv.pop(waitfor)
|
||||
waitfor = int(sys.argv.pop(waitfor))
|
||||
except ValueError:
|
||||
waitfor = None
|
||||
try:
|
||||
data = sys.argv.index('-d')
|
||||
sys.argv.pop(data)
|
||||
data = sys.argv.pop(data)
|
||||
except ValueError:
|
||||
data = None
|
||||
if outbin:
|
||||
with open(outbin, 'wb') as outf:
|
||||
reader = HTTPSClient(json=json).grab_url(
|
||||
sys.argv[1], data, returnrsp=True)
|
||||
chunk = reader.read(16384)
|
||||
@@ -130,6 +151,13 @@ if __name__ == '__main__':
|
||||
outf.write(chunk)
|
||||
chunk = reader.read(16384)
|
||||
sys.exit(0)
|
||||
if os.path.exists(sys.argv[-1]):
|
||||
if len(sys.argv) > 2 and os.path.exists(sys.argv[-1]):
|
||||
data = open(sys.argv[-1]).read()
|
||||
sys.stdout.write(HTTPSClient(json=json).grab_url(sys.argv[1], data).decode())
|
||||
if waitfor:
|
||||
client = HTTPSClient(json=json)
|
||||
status = 201
|
||||
while status != waitfor:
|
||||
status, rsp = client.grab_url_with_status(sys.argv[1], data)
|
||||
sys.stdout.write(rsp.decode())
|
||||
else:
|
||||
sys.stdout.write(HTTPSClient(json=json).grab_url(sys.argv[1], data).decode())
|
||||
|
||||
@@ -12,7 +12,7 @@ sed -i 's/install::/install:*:/' /sysroot/etc/shadow
|
||||
sed -i 's/root::/root:*:/' /sysroot/etc/shadow
|
||||
mkdir -p /sysroot/root/.ssh
|
||||
#chmod 700 /sysroot/root/.ssh
|
||||
cat /ssh/*.rootpubkey > /sysroot/root/.ssh/authorized_keys
|
||||
cat /ssh/*pubkey > /sysroot/root/.ssh/authorized_keys
|
||||
#chmod 600 /sysroot/root/.ssh/authorized_keys
|
||||
mkdir -p /sysroot/etc/ssh/
|
||||
for i in /ssh/*.ca; do
|
||||
|
||||
@@ -9,12 +9,11 @@ if lspci -d 15b3:: -n |grep 15b3 > /dev/null; then
|
||||
# two lines to use the .iso instead of the tgz packaging
|
||||
#fetch_remote infiniband/mofed.iso
|
||||
#mkdir MLNX_OFED
|
||||
#mount -o loop ofed.iso MLNX_OFED
|
||||
#mount -o loop infiniband/mofed.iso MLNX_OFED
|
||||
fetch_remote infiniband/mofed.tgz
|
||||
tar xf infiniband/mofed.tgz
|
||||
# The rest is common between tar and iso
|
||||
cd MLNX_OFED*
|
||||
mount -o loop ofed
|
||||
./mlnxofedinstall --force
|
||||
fi
|
||||
|
||||
|
||||
@@ -30,6 +30,10 @@ run_remote_python add_local_repositories
|
||||
# run_remote_python will use the appropriate python interpreter path to run the specified script
|
||||
# A post.custom is provided to more conveniently hold customizations, see the post.custom file.
|
||||
|
||||
# This will induce server side processing of the syncfile contents if
|
||||
# present
|
||||
run_remote_python syncfileclient
|
||||
|
||||
# run_remote example.sh
|
||||
# run_remote_python example.py
|
||||
run_remote post.custom
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/python
|
||||
import importlib
|
||||
import tempfile
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from importlib.machinery import SourceFileLoader
|
||||
try:
|
||||
apiclient = SourceFileLoader('apiclient', '/opt/confluent/bin/apiclient').load_module()
|
||||
except FileNotFoundError:
|
||||
apiclient = SourceFileLoader('apiclient', '/etc/confluent/apiclient').load_module()
|
||||
|
||||
|
||||
def partitionhostsline(line):
|
||||
comment = ''
|
||||
try:
|
||||
cmdidx = line.index('#')
|
||||
comment = line[cmdidx:]
|
||||
line = line[:cmdidx].strip()
|
||||
except ValueError:
|
||||
pass
|
||||
if not line:
|
||||
return '', [], comment
|
||||
ipaddr, names = line.split(maxsplit=1)
|
||||
names = names.split()
|
||||
return ipaddr, names, comment
|
||||
|
||||
class HostMerger:
|
||||
def __init__(self):
|
||||
self.byip = {}
|
||||
self.byname = {}
|
||||
self.sourcelines = []
|
||||
self.targlines = []
|
||||
|
||||
def read_source(self, sourcefile):
|
||||
with open(sourcefile, 'r') as hfile:
|
||||
self.sourcelines = hfile.read().split('\n')
|
||||
while not self.sourcelines[-1]:
|
||||
self.sourcelines = self.sourcelines[:-1]
|
||||
for x in range(len(self.sourcelines)):
|
||||
line = self.sourcelines[x]
|
||||
currip, names, comment = partitionhostsline(line)
|
||||
if currip:
|
||||
self.byip[currip] = x
|
||||
for name in names:
|
||||
self.byname[name] = x
|
||||
|
||||
def read_target(self, targetfile):
|
||||
with open(targetfile, 'r') as hfile:
|
||||
lines = hfile.read().split('\n')
|
||||
if not lines[-1]:
|
||||
lines = lines[:-1]
|
||||
for y in range(len(lines)):
|
||||
line = lines[y]
|
||||
currip, names, comment = partitionhostsline(line)
|
||||
if currip in self.byip:
|
||||
x = self.byip[currip]
|
||||
if self.sourcelines[x] is None:
|
||||
# have already consumed this enntry
|
||||
continue
|
||||
self.targlines.append(self.sourcelines[x])
|
||||
self.sourcelines[x] = None
|
||||
continue
|
||||
for name in names:
|
||||
if name in self.byname:
|
||||
x = self.byname[name]
|
||||
if self.sourcelines[x] is None:
|
||||
break
|
||||
self.targlines.append(self.sourcelines[x])
|
||||
self.sourcelines[x] = None
|
||||
break
|
||||
else:
|
||||
self.targlines.append(line)
|
||||
|
||||
def write_out(self, targetfile):
|
||||
while not self.targlines[-1]:
|
||||
self.targlines = self.targlines[:-1]
|
||||
while not self.sourcelines[-1]:
|
||||
self.sourcelines = self.sourcelines[:-1]
|
||||
with open(targetfile, 'w') as hosts:
|
||||
for line in self.targlines:
|
||||
hosts.write(line + '\n')
|
||||
for line in self.sourcelines:
|
||||
if line is not None:
|
||||
hosts.write(line + '\n')
|
||||
|
||||
|
||||
class CredMerger:
|
||||
def __init__(self):
|
||||
try:
|
||||
with open('/etc/login.defs', 'r') as ldefs:
|
||||
defs = ldefs.read().split('\n')
|
||||
except FileNotFoundError:
|
||||
defs = []
|
||||
lkup = {}
|
||||
self.discardnames = {}
|
||||
self.shadowednames = {}
|
||||
for line in defs:
|
||||
try:
|
||||
line = line[:line.index('#')]
|
||||
except ValueError:
|
||||
pass
|
||||
keyval = line.split()
|
||||
if len(keyval) < 2:
|
||||
continue
|
||||
lkup[keyval[0]] = keyval[1]
|
||||
self.uidmin = int(lkup.get('UID_MIN', 1000))
|
||||
self.uidmax = int(lkup.get('UID_MAX', 60000))
|
||||
self.gidmin = int(lkup.get('GID_MIN', 1000))
|
||||
self.gidmax = int(lkup.get('GID_MAX', 60000))
|
||||
self.shadowlines = None
|
||||
|
||||
def read_passwd(self, source, targfile=False):
|
||||
self.read_generic(source, self.uidmin, self.uidmax, targfile)
|
||||
|
||||
def read_group(self, source, targfile=False):
|
||||
self.read_generic(source, self.gidmin, self.gidmax, targfile)
|
||||
|
||||
def read_generic(self, source, minid, maxid, targfile):
|
||||
if targfile:
|
||||
self.targdata = []
|
||||
else:
|
||||
self.sourcedata = []
|
||||
with open(source, 'r') as inputfile:
|
||||
for line in inputfile.read().split('\n'):
|
||||
try:
|
||||
name, _, uid, _ = line.split(':', 3)
|
||||
uid = int(uid)
|
||||
except ValueError:
|
||||
continue
|
||||
if targfile:
|
||||
if uid <= minid or uid >= maxid:
|
||||
self.targdata.append(line)
|
||||
else:
|
||||
self.discardnames[name] = 1
|
||||
else:
|
||||
if uid >= minid and uid <= maxid:
|
||||
self.sourcedata.append(line)
|
||||
|
||||
def read_shadow(self, source):
|
||||
self.shadowlines = []
|
||||
try:
|
||||
with open(source, 'r') as inshadow:
|
||||
for line in inshadow.read().split('\n'):
|
||||
try:
|
||||
name, _ = line.split(':' , 1)
|
||||
except ValueError:
|
||||
continue
|
||||
if name in self.discardnames:
|
||||
continue
|
||||
self.shadowednames[name] = 1
|
||||
self.shadowlines.append(line)
|
||||
except FileNotFoundError:
|
||||
return
|
||||
|
||||
def write_out(self, outfile):
|
||||
with open(outfile, 'w') as targ:
|
||||
for line in self.targdata:
|
||||
targ.write(line + '\n')
|
||||
for line in self.sourcedata:
|
||||
targ.write(line + '\n')
|
||||
if outfile == '/etc/passwd':
|
||||
if self.shadowlines is None:
|
||||
self.read_shadow('/etc/shadow')
|
||||
with open('/etc/shadow', 'w') as shadout:
|
||||
for line in self.shadowlines:
|
||||
shadout.write(line + '\n')
|
||||
for line in self.sourcedata:
|
||||
name, _ = line.split(':', 1)
|
||||
if name in self.shadowednames:
|
||||
continue
|
||||
shadout.write(name + ':!:::::::\n')
|
||||
if outfile == '/etc/group':
|
||||
if self.shadowlines is None:
|
||||
self.read_shadow('/etc/gshadow')
|
||||
with open('/etc/gshadow', 'w') as shadout:
|
||||
for line in self.shadowlines:
|
||||
shadout.write(line + '\n')
|
||||
for line in self.sourcedata:
|
||||
name, _ = line.split(':' , 1)
|
||||
if name in self.shadowednames:
|
||||
continue
|
||||
shadout.write(name + ':!::\n')
|
||||
|
||||
def synchronize():
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
try:
|
||||
ac = apiclient.HTTPSClient()
|
||||
data = json.dumps({'merge': tmpdir})
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles', data)
|
||||
if status == 202:
|
||||
while status != 204:
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles')
|
||||
if not isinstance(rsp, str):
|
||||
rsp = rsp.decode('utf8')
|
||||
pendpasswd = os.path.join(tmpdir, 'etc/passwd')
|
||||
if os.path.exists(pendpasswd):
|
||||
cm = CredMerger()
|
||||
cm.read_passwd(pendpasswd, targfile=False)
|
||||
cm.read_passwd('/etc/passwd', targfile=True)
|
||||
cm.write_out('/etc/passwd')
|
||||
pendgroup = os.path.join(tmpdir, 'etc/group')
|
||||
if os.path.exists(pendgroup):
|
||||
cm = CredMerger()
|
||||
cm.read_group(pendgroup, targfile=False)
|
||||
cm.read_group('/etc/group', targfile=True)
|
||||
cm.write_out('/etc/group')
|
||||
pendhosts = os.path.join(tmpdir, 'etc/hosts')
|
||||
if os.path.exists(pendhosts):
|
||||
cm = HostMerger()
|
||||
cm.read_source(pendhosts)
|
||||
cm.read_target('/etc/hosts')
|
||||
cm.write_out('/etc/hosts')
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
synchronize()
|
||||
@@ -13,7 +13,7 @@ sed -i 's/install::/install:*:/' /sysroot/etc/shadow
|
||||
sed -i 's/root::/root:*:/' /sysroot/etc/shadow
|
||||
mkdir -p /sysroot/root/.ssh
|
||||
chmod 700 /sysroot/root/.ssh
|
||||
cat /ssh/*.rootpubkey > /sysroot/root/.ssh/authorized_keys
|
||||
cat /ssh/*pubkey > /sysroot/root/.ssh/authorized_keys
|
||||
chmod 600 /sysroot/root/.ssh/authorized_keys
|
||||
mkdir -p /sysroot/etc/ssh/
|
||||
for i in /ssh/*.ca; do
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
Ansible playbooks ending in .yml or .yaml that are placed into this directory will be executed at the
|
||||
appropriate phase of the install process.
|
||||
|
||||
The 'hosts' may be omitted, and if included will be ignored, replaced with the host that is specifically
|
||||
requesting the playbooks be executed.
|
||||
|
||||
Also, the playbooks will be executed on the deployment server. Hence it may be slower in aggregate than
|
||||
running content under scripts/ which ask much less of the deployment server
|
||||
|
||||
Here is an example of what a playbook would look like broadly:
|
||||
|
||||
- name: Example
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: Example1
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
line: 1.2.3.4 test1
|
||||
create: yes
|
||||
- name: Example2
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
line: 1.2.3.5 test2
|
||||
create: yes
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
Ansible playbooks ending in .yml or .yaml that are placed into this directory will be executed at the
|
||||
appropriate phase of the install process.
|
||||
|
||||
The 'hosts' may be omitted, and if included will be ignored, replaced with the host that is specifically
|
||||
requesting the playbooks be executed.
|
||||
|
||||
Also, the playbooks will be executed on the deployment server. Hence it may be slower in aggregate than
|
||||
running content under scripts/ which ask much less of the deployment server
|
||||
|
||||
Here is an example of what a playbook would look like broadly:
|
||||
|
||||
- name: Example
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: Example1
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
line: 1.2.3.4 test1
|
||||
create: yes
|
||||
- name: Example2
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
line: 1.2.3.5 test2
|
||||
create: yes
|
||||
|
||||
@@ -19,6 +19,9 @@ run_remote firstboot.custom
|
||||
run_remote_parts firstboot
|
||||
|
||||
|
||||
# Induce execution of remote configuration, e.g. ansible plays in ansible/firstboot.d/
|
||||
run_remote_config firstboot
|
||||
|
||||
curl -X POST -d 'status: complete' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/updatestatus
|
||||
systemctl disable firstboot
|
||||
rm /etc/systemd/system/firstboot.service
|
||||
|
||||
@@ -73,3 +73,21 @@ run_remote_python() {
|
||||
cd - > /dev/null
|
||||
return $retcode
|
||||
}
|
||||
|
||||
run_remote_config() {
|
||||
echo
|
||||
set_confluent_vars
|
||||
apiclient=/opt/confluent/bin/apiclient
|
||||
if [ -f /etc/confluent/apiclient ]; then
|
||||
apiclient=/etc/confluent/apiclient
|
||||
fi
|
||||
echo '---------------------------------------------------------------------------'
|
||||
echo Requesting to run remote configuration for "'$*'" from $mgr under profile $profile
|
||||
/usr/libexec/platform-python $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
|
||||
/usr/libexec/platform-python $apiclient /confluent-api/self/remoteconfig/status -w 204
|
||||
echo
|
||||
echo 'Completed remote configuration'
|
||||
echo '---------------------------------------------------------------------------'
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@ chmod og-rwx /etc/confluent/*
|
||||
export mgr profile nodename
|
||||
. /etc/confluent/functions
|
||||
|
||||
curl -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/updatestatus
|
||||
|
||||
|
||||
if [ -f /tmp/cryptboot ]; then
|
||||
run_remote tpm_luks.sh
|
||||
@@ -30,9 +28,17 @@ run_remote_python add_local_repositories
|
||||
# run_remote_python will use the appropriate python interpreter path to run the specified script
|
||||
# A post.custom is provided to more conveniently hold customizations, see the post.custom file.
|
||||
|
||||
# This will induce server side processing of the syncfile contents if
|
||||
# present
|
||||
run_remote_python syncfileclient
|
||||
|
||||
# run_remote example.sh
|
||||
# run_remote_python example.py
|
||||
run_remote post.custom
|
||||
|
||||
# Also, scripts may be placed into 'post.d', e.g. post.d/01-runfirst.sh, post.d/02-runsecond.sh
|
||||
run_remote_parts post
|
||||
|
||||
# Induce execution of remote configuration, e.g. ansible plays in ansible/post.d/
|
||||
run_remote_config post
|
||||
curl -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/updatestatus
|
||||
|
||||
@@ -14,6 +14,10 @@ chmod -R og-rwx /mnt/sysimage/etc/confluent
|
||||
cp /tmp/functions /mnt/sysimage/etc/confluent/
|
||||
. /tmp/functions
|
||||
cp /tmp/cryptboot /mnt/sysimage/tmp/
|
||||
echo Port 2222 >> /etc/ssh/sshd_config.anaconda
|
||||
echo Match LocalPort 22 >> /etc/ssh/sshd_config.anaconda
|
||||
echo " ChrootDirectory /mnt/sysimage" >> /etc/ssh/sshd_config.anaconda
|
||||
kill -HUP $(cat /run/sshd.pid)
|
||||
|
||||
# Preserve the ssh setup work done for the installer
|
||||
# by copying into the target system and setting up
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/python
|
||||
import importlib
|
||||
import tempfile
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from importlib.machinery import SourceFileLoader
|
||||
try:
|
||||
apiclient = SourceFileLoader('apiclient', '/opt/confluent/bin/apiclient').load_module()
|
||||
except FileNotFoundError:
|
||||
apiclient = SourceFileLoader('apiclient', '/etc/confluent/apiclient').load_module()
|
||||
|
||||
|
||||
def partitionhostsline(line):
|
||||
comment = ''
|
||||
try:
|
||||
cmdidx = line.index('#')
|
||||
comment = line[cmdidx:]
|
||||
line = line[:cmdidx].strip()
|
||||
except ValueError:
|
||||
pass
|
||||
if not line:
|
||||
return '', [], comment
|
||||
ipaddr, names = line.split(maxsplit=1)
|
||||
names = names.split()
|
||||
return ipaddr, names, comment
|
||||
|
||||
class HostMerger:
|
||||
def __init__(self):
|
||||
self.byip = {}
|
||||
self.byname = {}
|
||||
self.sourcelines = []
|
||||
self.targlines = []
|
||||
|
||||
def read_source(self, sourcefile):
|
||||
with open(sourcefile, 'r') as hfile:
|
||||
self.sourcelines = hfile.read().split('\n')
|
||||
while not self.sourcelines[-1]:
|
||||
self.sourcelines = self.sourcelines[:-1]
|
||||
for x in range(len(self.sourcelines)):
|
||||
line = self.sourcelines[x]
|
||||
currip, names, comment = partitionhostsline(line)
|
||||
if currip:
|
||||
self.byip[currip] = x
|
||||
for name in names:
|
||||
self.byname[name] = x
|
||||
|
||||
def read_target(self, targetfile):
|
||||
with open(targetfile, 'r') as hfile:
|
||||
lines = hfile.read().split('\n')
|
||||
if not lines[-1]:
|
||||
lines = lines[:-1]
|
||||
for y in range(len(lines)):
|
||||
line = lines[y]
|
||||
currip, names, comment = partitionhostsline(line)
|
||||
if currip in self.byip:
|
||||
x = self.byip[currip]
|
||||
if self.sourcelines[x] is None:
|
||||
# have already consumed this enntry
|
||||
continue
|
||||
self.targlines.append(self.sourcelines[x])
|
||||
self.sourcelines[x] = None
|
||||
continue
|
||||
for name in names:
|
||||
if name in self.byname:
|
||||
x = self.byname[name]
|
||||
if self.sourcelines[x] is None:
|
||||
break
|
||||
self.targlines.append(self.sourcelines[x])
|
||||
self.sourcelines[x] = None
|
||||
break
|
||||
else:
|
||||
self.targlines.append(line)
|
||||
|
||||
def write_out(self, targetfile):
|
||||
while not self.targlines[-1]:
|
||||
self.targlines = self.targlines[:-1]
|
||||
while not self.sourcelines[-1]:
|
||||
self.sourcelines = self.sourcelines[:-1]
|
||||
with open(targetfile, 'w') as hosts:
|
||||
for line in self.targlines:
|
||||
hosts.write(line + '\n')
|
||||
for line in self.sourcelines:
|
||||
if line is not None:
|
||||
hosts.write(line + '\n')
|
||||
|
||||
|
||||
class CredMerger:
|
||||
def __init__(self):
|
||||
try:
|
||||
with open('/etc/login.defs', 'r') as ldefs:
|
||||
defs = ldefs.read().split('\n')
|
||||
except FileNotFoundError:
|
||||
defs = []
|
||||
lkup = {}
|
||||
self.discardnames = {}
|
||||
self.shadowednames = {}
|
||||
for line in defs:
|
||||
try:
|
||||
line = line[:line.index('#')]
|
||||
except ValueError:
|
||||
pass
|
||||
keyval = line.split()
|
||||
if len(keyval) < 2:
|
||||
continue
|
||||
lkup[keyval[0]] = keyval[1]
|
||||
self.uidmin = int(lkup.get('UID_MIN', 1000))
|
||||
self.uidmax = int(lkup.get('UID_MAX', 60000))
|
||||
self.gidmin = int(lkup.get('GID_MIN', 1000))
|
||||
self.gidmax = int(lkup.get('GID_MAX', 60000))
|
||||
self.shadowlines = None
|
||||
|
||||
def read_passwd(self, source, targfile=False):
|
||||
self.read_generic(source, self.uidmin, self.uidmax, targfile)
|
||||
|
||||
def read_group(self, source, targfile=False):
|
||||
self.read_generic(source, self.gidmin, self.gidmax, targfile)
|
||||
|
||||
def read_generic(self, source, minid, maxid, targfile):
|
||||
if targfile:
|
||||
self.targdata = []
|
||||
else:
|
||||
self.sourcedata = []
|
||||
with open(source, 'r') as inputfile:
|
||||
for line in inputfile.read().split('\n'):
|
||||
try:
|
||||
name, _, uid, _ = line.split(':', 3)
|
||||
uid = int(uid)
|
||||
except ValueError:
|
||||
continue
|
||||
if targfile:
|
||||
if uid <= minid or uid >= maxid:
|
||||
self.targdata.append(line)
|
||||
else:
|
||||
self.discardnames[name] = 1
|
||||
else:
|
||||
if uid >= minid and uid <= maxid:
|
||||
self.sourcedata.append(line)
|
||||
|
||||
def read_shadow(self, source):
|
||||
self.shadowlines = []
|
||||
try:
|
||||
with open(source, 'r') as inshadow:
|
||||
for line in inshadow.read().split('\n'):
|
||||
try:
|
||||
name, _ = line.split(':' , 1)
|
||||
except ValueError:
|
||||
continue
|
||||
if name in self.discardnames:
|
||||
continue
|
||||
self.shadowednames[name] = 1
|
||||
self.shadowlines.append(line)
|
||||
except FileNotFoundError:
|
||||
return
|
||||
|
||||
def write_out(self, outfile):
|
||||
with open(outfile, 'w') as targ:
|
||||
for line in self.targdata:
|
||||
targ.write(line + '\n')
|
||||
for line in self.sourcedata:
|
||||
targ.write(line + '\n')
|
||||
if outfile == '/etc/passwd':
|
||||
if self.shadowlines is None:
|
||||
self.read_shadow('/etc/shadow')
|
||||
with open('/etc/shadow', 'w') as shadout:
|
||||
for line in self.shadowlines:
|
||||
shadout.write(line + '\n')
|
||||
for line in self.sourcedata:
|
||||
name, _ = line.split(':', 1)
|
||||
if name in self.shadowednames:
|
||||
continue
|
||||
shadout.write(name + ':!:::::::\n')
|
||||
if outfile == '/etc/group':
|
||||
if self.shadowlines is None:
|
||||
self.read_shadow('/etc/gshadow')
|
||||
with open('/etc/gshadow', 'w') as shadout:
|
||||
for line in self.shadowlines:
|
||||
shadout.write(line + '\n')
|
||||
for line in self.sourcedata:
|
||||
name, _ = line.split(':' , 1)
|
||||
if name in self.shadowednames:
|
||||
continue
|
||||
shadout.write(name + ':!::\n')
|
||||
|
||||
def synchronize():
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
try:
|
||||
ac = apiclient.HTTPSClient()
|
||||
data = json.dumps({'merge': tmpdir})
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles', data)
|
||||
if status == 202:
|
||||
while status != 204:
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles')
|
||||
if not isinstance(rsp, str):
|
||||
rsp = rsp.decode('utf8')
|
||||
pendpasswd = os.path.join(tmpdir, 'etc/passwd')
|
||||
if os.path.exists(pendpasswd):
|
||||
cm = CredMerger()
|
||||
cm.read_passwd(pendpasswd, targfile=False)
|
||||
cm.read_passwd('/etc/passwd', targfile=True)
|
||||
cm.write_out('/etc/passwd')
|
||||
pendgroup = os.path.join(tmpdir, 'etc/group')
|
||||
if os.path.exists(pendgroup):
|
||||
cm = CredMerger()
|
||||
cm.read_group(pendgroup, targfile=False)
|
||||
cm.read_group('/etc/group', targfile=True)
|
||||
cm.write_out('/etc/group')
|
||||
pendhosts = os.path.join(tmpdir, 'etc/hosts')
|
||||
if os.path.exists(pendhosts):
|
||||
cm = HostMerger()
|
||||
cm.read_source(pendhosts)
|
||||
cm.read_target('/etc/hosts')
|
||||
cm.write_out('/etc/hosts')
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
synchronize()
|
||||
@@ -8,14 +8,16 @@ if ! grep console= /proc/cmdline >& /dev/null; then
|
||||
echo $autocons > /tmp/01-autocons.devnode
|
||||
if [ ! -z "$autocons" ]; then
|
||||
echo "Using $(cat /tmp/01-autocons.conf)"
|
||||
tmux a <> $autocons >&0 2>&1 &
|
||||
(while :; do tmux a <> $autocons >&0 2>&1; done) &
|
||||
(while :; do TERM=linux tmux a <> /dev/tty1 >&0 2>&1; done) &
|
||||
else
|
||||
tmux a <> /dev/console >&0 2>&1 &
|
||||
(while :; do tmux a <> /dev/console >&0 2>&1; done) &
|
||||
fi
|
||||
else
|
||||
tmux a <> /dev/console >&0 2>&1 &
|
||||
tmux a <> /dev/tty1 >&0 2>&1 &
|
||||
(while :; do tmux a <> /dev/console >&0 2>&1; done) &
|
||||
(while :; do TERM=linux tmux a <> /dev/tty1 >&0 2>&1; done) &
|
||||
fi
|
||||
(while :; do TERM=linux tmux <> /dev/tty2 >&0 2>&1; done) &
|
||||
echo -n "udevd: "
|
||||
/usr/lib/systemd/systemd-udevd --daemon
|
||||
echo -n "Loading drivers..."
|
||||
@@ -35,7 +37,7 @@ PermitRootLogin yes
|
||||
AuthorizedKeysFile .ssh/authorized_keys
|
||||
EOF
|
||||
mkdir ~/.ssh
|
||||
cat /ssh/*.rootpubkey > ~/.ssh/authorized_keys
|
||||
cat /ssh/*pubkey > ~/.ssh/authorized_keys 2>/dev/null
|
||||
cat /tls/*.pem > /etc/confluent/ca.pem
|
||||
mkdir -p /etc/pki/tls/certs
|
||||
cat /tls/*.pem > /etc/pki/tls/certs/ca-bundle.crt
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
Ansible playbooks ending in .yml or .yaml that are placed into this directory will be executed at the
|
||||
appropriate phase of the install process.
|
||||
|
||||
The 'hosts' may be omitted, and if included will be ignored, replaced with the host that is specifically
|
||||
requesting the playbooks be executed.
|
||||
|
||||
Also, the playbooks will be executed on the deployment server. Hence it may be slower in aggregate than
|
||||
running content under scripts/ which ask much less of the deployment server
|
||||
|
||||
Here is an example of what a playbook would look like broadly:
|
||||
|
||||
- name: Example
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: Example1
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
line: 1.2.3.4 test1
|
||||
create: yes
|
||||
- name: Example2
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
line: 1.2.3.5 test2
|
||||
create: yes
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
label: Genesis
|
||||
kernelarags: quiet
|
||||
kernelargs: quiet
|
||||
|
||||
@@ -411,7 +411,7 @@ def main():
|
||||
dotwait()
|
||||
while awaitprefix and not check_subnet(s, bmccfg['prefixv4'], channel):
|
||||
dotwait()
|
||||
while awaitprefix and not check_gateway(s, bmccfg['bmcgw'], channel):
|
||||
while awaitgw and not check_gateway(s, bmccfg['bmcgw'], channel):
|
||||
dotwait()
|
||||
sys.stdout.write('done\n')
|
||||
sys.stdout.flush()
|
||||
|
||||
@@ -63,3 +63,20 @@ run_remote_python() {
|
||||
cd - > /dev/null
|
||||
return $retcode
|
||||
}
|
||||
|
||||
run_remote_config() {
|
||||
echo
|
||||
set_confluent_vars
|
||||
apiclient=/opt/confluent/bin/apiclient
|
||||
if [ -f /etc/confluent/apiclient ]; then
|
||||
apiclient=/etc/confluent/apiclient
|
||||
fi
|
||||
echo '---------------------------------------------------------------------------'
|
||||
echo Requesting to run remote configuration for "'$*'" from $mgr under profile $profile
|
||||
/usr/libexec/platform-python $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
|
||||
/usr/libexec/platform-python $apiclient /confluent-api/self/remoteconfig/status -w 204
|
||||
echo
|
||||
echo 'Completed remote configuration'
|
||||
echo '---------------------------------------------------------------------------'
|
||||
return
|
||||
}
|
||||
|
||||
@@ -6,6 +6,13 @@
|
||||
# run_remote and run_remote_python are available to download scripts and
|
||||
# execute them.
|
||||
|
||||
# This will induce server side processing of the syncfile contents if
|
||||
# present
|
||||
run_remote_python syncfileclient
|
||||
|
||||
# Induce execution of remote configuration, e.g. ansible plays in ansible/onboot.d/
|
||||
run_remote_config onboot
|
||||
|
||||
# This is an example to request the BMC be configured on the network
|
||||
# according to how confluent has things configured:
|
||||
# run_remote_python configbmc -c
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/python
|
||||
import importlib
|
||||
import tempfile
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from importlib.machinery import SourceFileLoader
|
||||
try:
|
||||
apiclient = SourceFileLoader('apiclient', '/opt/confluent/bin/apiclient').load_module()
|
||||
except FileNotFoundError:
|
||||
apiclient = SourceFileLoader('apiclient', '/etc/confluent/apiclient').load_module()
|
||||
|
||||
|
||||
def partitionhostsline(line):
|
||||
comment = ''
|
||||
try:
|
||||
cmdidx = line.index('#')
|
||||
comment = line[cmdidx:]
|
||||
line = line[:cmdidx].strip()
|
||||
except ValueError:
|
||||
pass
|
||||
if not line:
|
||||
return '', [], comment
|
||||
ipaddr, names = line.split(maxsplit=1)
|
||||
names = names.split()
|
||||
return ipaddr, names, comment
|
||||
|
||||
class HostMerger:
|
||||
def __init__(self):
|
||||
self.byip = {}
|
||||
self.byname = {}
|
||||
self.sourcelines = []
|
||||
self.targlines = []
|
||||
|
||||
def read_source(self, sourcefile):
|
||||
with open(sourcefile, 'r') as hfile:
|
||||
self.sourcelines = hfile.read().split('\n')
|
||||
while not self.sourcelines[-1]:
|
||||
self.sourcelines = self.sourcelines[:-1]
|
||||
for x in range(len(self.sourcelines)):
|
||||
line = self.sourcelines[x]
|
||||
currip, names, comment = partitionhostsline(line)
|
||||
if currip:
|
||||
self.byip[currip] = x
|
||||
for name in names:
|
||||
self.byname[name] = x
|
||||
|
||||
def read_target(self, targetfile):
|
||||
with open(targetfile, 'r') as hfile:
|
||||
lines = hfile.read().split('\n')
|
||||
if not lines[-1]:
|
||||
lines = lines[:-1]
|
||||
for y in range(len(lines)):
|
||||
line = lines[y]
|
||||
currip, names, comment = partitionhostsline(line)
|
||||
if currip in self.byip:
|
||||
x = self.byip[currip]
|
||||
if self.sourcelines[x] is None:
|
||||
# have already consumed this enntry
|
||||
continue
|
||||
self.targlines.append(self.sourcelines[x])
|
||||
self.sourcelines[x] = None
|
||||
continue
|
||||
for name in names:
|
||||
if name in self.byname:
|
||||
x = self.byname[name]
|
||||
if self.sourcelines[x] is None:
|
||||
break
|
||||
self.targlines.append(self.sourcelines[x])
|
||||
self.sourcelines[x] = None
|
||||
break
|
||||
else:
|
||||
self.targlines.append(line)
|
||||
|
||||
def write_out(self, targetfile):
|
||||
while not self.targlines[-1]:
|
||||
self.targlines = self.targlines[:-1]
|
||||
while not self.sourcelines[-1]:
|
||||
self.sourcelines = self.sourcelines[:-1]
|
||||
with open(targetfile, 'w') as hosts:
|
||||
for line in self.targlines:
|
||||
hosts.write(line + '\n')
|
||||
for line in self.sourcelines:
|
||||
if line is not None:
|
||||
hosts.write(line + '\n')
|
||||
|
||||
|
||||
class CredMerger:
|
||||
def __init__(self):
|
||||
try:
|
||||
with open('/etc/login.defs', 'r') as ldefs:
|
||||
defs = ldefs.read().split('\n')
|
||||
except FileNotFoundError:
|
||||
defs = []
|
||||
lkup = {}
|
||||
self.discardnames = {}
|
||||
self.shadowednames = {}
|
||||
for line in defs:
|
||||
try:
|
||||
line = line[:line.index('#')]
|
||||
except ValueError:
|
||||
pass
|
||||
keyval = line.split()
|
||||
if len(keyval) < 2:
|
||||
continue
|
||||
lkup[keyval[0]] = keyval[1]
|
||||
self.uidmin = int(lkup.get('UID_MIN', 1000))
|
||||
self.uidmax = int(lkup.get('UID_MAX', 60000))
|
||||
self.gidmin = int(lkup.get('GID_MIN', 1000))
|
||||
self.gidmax = int(lkup.get('GID_MAX', 60000))
|
||||
self.shadowlines = None
|
||||
|
||||
def read_passwd(self, source, targfile=False):
|
||||
self.read_generic(source, self.uidmin, self.uidmax, targfile)
|
||||
|
||||
def read_group(self, source, targfile=False):
|
||||
self.read_generic(source, self.gidmin, self.gidmax, targfile)
|
||||
|
||||
def read_generic(self, source, minid, maxid, targfile):
|
||||
if targfile:
|
||||
self.targdata = []
|
||||
else:
|
||||
self.sourcedata = []
|
||||
with open(source, 'r') as inputfile:
|
||||
for line in inputfile.read().split('\n'):
|
||||
try:
|
||||
name, _, uid, _ = line.split(':', 3)
|
||||
uid = int(uid)
|
||||
except ValueError:
|
||||
continue
|
||||
if targfile:
|
||||
if uid <= minid or uid >= maxid:
|
||||
self.targdata.append(line)
|
||||
else:
|
||||
self.discardnames[name] = 1
|
||||
else:
|
||||
if uid >= minid and uid <= maxid:
|
||||
self.sourcedata.append(line)
|
||||
|
||||
def read_shadow(self, source):
|
||||
self.shadowlines = []
|
||||
try:
|
||||
with open(source, 'r') as inshadow:
|
||||
for line in inshadow.read().split('\n'):
|
||||
try:
|
||||
name, _ = line.split(':' , 1)
|
||||
except ValueError:
|
||||
continue
|
||||
if name in self.discardnames:
|
||||
continue
|
||||
self.shadowednames[name] = 1
|
||||
self.shadowlines.append(line)
|
||||
except FileNotFoundError:
|
||||
return
|
||||
|
||||
def write_out(self, outfile):
|
||||
with open(outfile, 'w') as targ:
|
||||
for line in self.targdata:
|
||||
targ.write(line + '\n')
|
||||
for line in self.sourcedata:
|
||||
targ.write(line + '\n')
|
||||
if outfile == '/etc/passwd':
|
||||
if self.shadowlines is None:
|
||||
self.read_shadow('/etc/shadow')
|
||||
with open('/etc/shadow', 'w') as shadout:
|
||||
for line in self.shadowlines:
|
||||
shadout.write(line + '\n')
|
||||
for line in self.sourcedata:
|
||||
name, _ = line.split(':', 1)
|
||||
if name in self.shadowednames:
|
||||
continue
|
||||
shadout.write(name + ':!:::::::\n')
|
||||
if outfile == '/etc/group':
|
||||
if self.shadowlines is None:
|
||||
self.read_shadow('/etc/gshadow')
|
||||
with open('/etc/gshadow', 'w') as shadout:
|
||||
for line in self.shadowlines:
|
||||
shadout.write(line + '\n')
|
||||
for line in self.sourcedata:
|
||||
name, _ = line.split(':' , 1)
|
||||
if name in self.shadowednames:
|
||||
continue
|
||||
shadout.write(name + ':!::\n')
|
||||
|
||||
def synchronize():
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
try:
|
||||
ac = apiclient.HTTPSClient()
|
||||
data = json.dumps({'merge': tmpdir})
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles', data)
|
||||
if status == 202:
|
||||
while status != 204:
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles')
|
||||
if not isinstance(rsp, str):
|
||||
rsp = rsp.decode('utf8')
|
||||
pendpasswd = os.path.join(tmpdir, 'etc/passwd')
|
||||
if os.path.exists(pendpasswd):
|
||||
cm = CredMerger()
|
||||
cm.read_passwd(pendpasswd, targfile=False)
|
||||
cm.read_passwd('/etc/passwd', targfile=True)
|
||||
cm.write_out('/etc/passwd')
|
||||
pendgroup = os.path.join(tmpdir, 'etc/group')
|
||||
if os.path.exists(pendgroup):
|
||||
cm = CredMerger()
|
||||
cm.read_group(pendgroup, targfile=False)
|
||||
cm.read_group('/etc/group', targfile=True)
|
||||
cm.write_out('/etc/group')
|
||||
pendhosts = os.path.join(tmpdir, 'etc/hosts')
|
||||
if os.path.exists(pendhosts):
|
||||
cm = HostMerger()
|
||||
cm.read_source(pendhosts)
|
||||
cm.read_target('/etc/hosts')
|
||||
cm.write_out('/etc/hosts')
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
synchronize()
|
||||
@@ -12,7 +12,7 @@ sed -i 's/install::/install:*:/' /sysroot/etc/shadow
|
||||
sed -i 's/root::/root:*:/' /sysroot/etc/shadow
|
||||
mkdir -p /sysroot/root/.ssh
|
||||
#chmod 700 /sysroot/root/.ssh
|
||||
cat /ssh/*.rootpubkey > /sysroot/root/.ssh/authorized_keys
|
||||
cat /ssh/*pubkey > /sysroot/root/.ssh/authorized_keys
|
||||
#chmod 600 /sysroot/root/.ssh/authorized_keys
|
||||
mkdir -p /sysroot/etc/ssh/
|
||||
for i in /ssh/*.ca; do
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
echo "Installing certificates"
|
||||
echo '<authorized_keys xmlns="http://www.suse.com/1.0/yast2ns" xmlns:config="http://www.suse.com/1.0/configns" config:type="list">' > /tmp/rootkeys.xml
|
||||
for pub in /ssh/*.rootpubkey; do
|
||||
for pub in /ssh/*pubkey; do
|
||||
echo '<listentry>'$(cat $pub)'</listentry>' >> /tmp/rootkeys.xml
|
||||
done
|
||||
echo '</authorized_keys>' >> /tmp/rootkeys.xml
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
Ansible playbooks ending in .yml or .yaml that are placed into this directory will be executed at the
|
||||
appropriate phase of the install process.
|
||||
|
||||
The 'hosts' may be omitted, and if included will be ignored, replaced with the host that is specifically
|
||||
requesting the playbooks be executed.
|
||||
|
||||
Also, the playbooks will be executed on the deployment server. Hence it may be slower in aggregate than
|
||||
running content under scripts/ which ask much less of the deployment server
|
||||
|
||||
Here is an example of what a playbook would look like broadly:
|
||||
|
||||
- name: Example
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: Example1
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
line: 1.2.3.4 test1
|
||||
create: yes
|
||||
- name: Example2
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
line: 1.2.3.5 test2
|
||||
create: yes
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
Ansible playbooks ending in .yml or .yaml that are placed into this directory will be executed at the
|
||||
appropriate phase of the install process.
|
||||
|
||||
The 'hosts' may be omitted, and if included will be ignored, replaced with the host that is specifically
|
||||
requesting the playbooks be executed.
|
||||
|
||||
Also, the playbooks will be executed on the deployment server. Hence it may be slower in aggregate than
|
||||
running content under scripts/ which ask much less of the deployment server
|
||||
|
||||
Here is an example of what a playbook would look like broadly:
|
||||
|
||||
- name: Example
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: Example1
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
line: 1.2.3.4 test1
|
||||
create: yes
|
||||
- name: Example2
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
line: 1.2.3.5 test2
|
||||
create: yes
|
||||
|
||||
@@ -39,6 +39,7 @@ dynamic behavior and replace with static configuration.
|
||||
<pattern>base</pattern>
|
||||
</patterns>
|
||||
<packages config:type="list">
|
||||
<package>python3</package>
|
||||
<package>openssl</package>
|
||||
<package>chrony</package>
|
||||
<package>rsync</package>
|
||||
|
||||
@@ -10,4 +10,11 @@ apikey=$(cat /etc/confluent/confluent.apikey)
|
||||
. /etc/confluent/functions
|
||||
|
||||
run_remote firstboot.custom
|
||||
|
||||
# Firstboot scripts may be placed into firstboot.d, e.g. firstboot.d/01-firstaction.sh, firstboot.d/02-secondaction.sh
|
||||
run_remote_parts firstboot
|
||||
|
||||
# Induce execution of remote configuration, e.g. ansible plays in ansible/firstboot.d/
|
||||
run_remote_config firstboot
|
||||
|
||||
curl --capath /etc/confluent/tls -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" -f -X POST -d "status: complete" https://$mgr/confluent-api/self/updatestatus
|
||||
|
||||
@@ -33,3 +33,20 @@ run_remote_python() {
|
||||
cd - > /dev/null
|
||||
return $retcode
|
||||
}
|
||||
|
||||
run_remote_config() {
|
||||
echo
|
||||
set_confluent_vars
|
||||
apiclient=/opt/confluent/bin/apiclient
|
||||
if [ -f /etc/confluent/apiclient ]; then
|
||||
apiclient=/etc/confluent/apiclient
|
||||
fi
|
||||
echo '---------------------------------------------------------------------------'
|
||||
echo Requesting to run remote configuration for "'$*'" from $mgr under profile $profile
|
||||
python3 $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
|
||||
python3 $apiclient /confluent-api/self/remoteconfig/status -w 204
|
||||
echo
|
||||
echo 'Completed remote configuration'
|
||||
echo '---------------------------------------------------------------------------'
|
||||
return
|
||||
}
|
||||
|
||||
@@ -21,7 +21,17 @@ chmod og-rwx /etc/confluent/*
|
||||
export mgr profile nodename
|
||||
. /etc/confluent/functions
|
||||
|
||||
# This will induce server side processing of the syncfile contents if
|
||||
# present
|
||||
run_remote_python syncfileclient
|
||||
|
||||
run_remote post.custom
|
||||
|
||||
# Also, scripts may be placed into 'post.d', e.g. post.d/01-runfirst.sh, post.d/02-runsecond.sh
|
||||
run_remote_parts post
|
||||
|
||||
# Induce execution of remote configuration, e.g. ansible plays in ansible/post.d/
|
||||
run_remote_config post
|
||||
|
||||
curl -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" https://$mgr/confluent-api/self/updatestatus
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ if [ "$cryptboot" != "" ] && [ "$cryptboot" != "none" ] && [ "$cryptboot" != "n
|
||||
fi
|
||||
|
||||
mkdir ~/.ssh
|
||||
cat /ssh/*.rootpubkey > ~/.ssh/authorized_keys
|
||||
cat /ssh/*pubkey > ~/.ssh/authorized_keys 2>/dev/null
|
||||
|
||||
ssh-keygen -A
|
||||
for i in /etc/ssh/ssh_host*key.pub; do
|
||||
certname=${i/.pub/-cert.pub}
|
||||
|
||||
@@ -17,5 +17,14 @@ cp -a /tls /mnt/etc/confluent/
|
||||
cp -a /tls/* /mnt/var/lib/ca-certificates/openssl
|
||||
cp -a /tls/* /mnt/var/lib/ca-certificates/pem
|
||||
cp -a /tls/*.pem /mnt/etc/pki/trust/anchors
|
||||
cat /tls/*.pem > /mnt/etc/confluent/ca.pem
|
||||
cp /opt/confluent/bin/apiclient /mnt/etc/confluent
|
||||
|
||||
run_remote setupssh.sh
|
||||
|
||||
echo Port 22 >> /etc/ssh/sshd_config
|
||||
echo Port 2222 >> /etc/ssh/sshd_config
|
||||
echo Match LocalPort 22 >> /etc/ssh/sshd_config
|
||||
echo " ChrootDirectory /mnt" >> /etc/ssh/sshd_config
|
||||
kill -HUP $(cat /run/sshd.pid)
|
||||
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/python
|
||||
import importlib
|
||||
import tempfile
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from importlib.machinery import SourceFileLoader
|
||||
try:
|
||||
apiclient = SourceFileLoader('apiclient', '/opt/confluent/bin/apiclient').load_module()
|
||||
except FileNotFoundError:
|
||||
apiclient = SourceFileLoader('apiclient', '/etc/confluent/apiclient').load_module()
|
||||
|
||||
|
||||
def partitionhostsline(line):
|
||||
comment = ''
|
||||
try:
|
||||
cmdidx = line.index('#')
|
||||
comment = line[cmdidx:]
|
||||
line = line[:cmdidx].strip()
|
||||
except ValueError:
|
||||
pass
|
||||
if not line:
|
||||
return '', [], comment
|
||||
ipaddr, names = line.split(maxsplit=1)
|
||||
names = names.split()
|
||||
return ipaddr, names, comment
|
||||
|
||||
class HostMerger:
|
||||
def __init__(self):
|
||||
self.byip = {}
|
||||
self.byname = {}
|
||||
self.sourcelines = []
|
||||
self.targlines = []
|
||||
|
||||
def read_source(self, sourcefile):
|
||||
with open(sourcefile, 'r') as hfile:
|
||||
self.sourcelines = hfile.read().split('\n')
|
||||
while not self.sourcelines[-1]:
|
||||
self.sourcelines = self.sourcelines[:-1]
|
||||
for x in range(len(self.sourcelines)):
|
||||
line = self.sourcelines[x]
|
||||
currip, names, comment = partitionhostsline(line)
|
||||
if currip:
|
||||
self.byip[currip] = x
|
||||
for name in names:
|
||||
self.byname[name] = x
|
||||
|
||||
def read_target(self, targetfile):
|
||||
with open(targetfile, 'r') as hfile:
|
||||
lines = hfile.read().split('\n')
|
||||
if not lines[-1]:
|
||||
lines = lines[:-1]
|
||||
for y in range(len(lines)):
|
||||
line = lines[y]
|
||||
currip, names, comment = partitionhostsline(line)
|
||||
if currip in self.byip:
|
||||
x = self.byip[currip]
|
||||
if self.sourcelines[x] is None:
|
||||
# have already consumed this enntry
|
||||
continue
|
||||
self.targlines.append(self.sourcelines[x])
|
||||
self.sourcelines[x] = None
|
||||
continue
|
||||
for name in names:
|
||||
if name in self.byname:
|
||||
x = self.byname[name]
|
||||
if self.sourcelines[x] is None:
|
||||
break
|
||||
self.targlines.append(self.sourcelines[x])
|
||||
self.sourcelines[x] = None
|
||||
break
|
||||
else:
|
||||
self.targlines.append(line)
|
||||
|
||||
def write_out(self, targetfile):
|
||||
while not self.targlines[-1]:
|
||||
self.targlines = self.targlines[:-1]
|
||||
while not self.sourcelines[-1]:
|
||||
self.sourcelines = self.sourcelines[:-1]
|
||||
with open(targetfile, 'w') as hosts:
|
||||
for line in self.targlines:
|
||||
hosts.write(line + '\n')
|
||||
for line in self.sourcelines:
|
||||
if line is not None:
|
||||
hosts.write(line + '\n')
|
||||
|
||||
|
||||
class CredMerger:
|
||||
def __init__(self):
|
||||
try:
|
||||
with open('/etc/login.defs', 'r') as ldefs:
|
||||
defs = ldefs.read().split('\n')
|
||||
except FileNotFoundError:
|
||||
defs = []
|
||||
lkup = {}
|
||||
self.discardnames = {}
|
||||
self.shadowednames = {}
|
||||
for line in defs:
|
||||
try:
|
||||
line = line[:line.index('#')]
|
||||
except ValueError:
|
||||
pass
|
||||
keyval = line.split()
|
||||
if len(keyval) < 2:
|
||||
continue
|
||||
lkup[keyval[0]] = keyval[1]
|
||||
self.uidmin = int(lkup.get('UID_MIN', 1000))
|
||||
self.uidmax = int(lkup.get('UID_MAX', 60000))
|
||||
self.gidmin = int(lkup.get('GID_MIN', 1000))
|
||||
self.gidmax = int(lkup.get('GID_MAX', 60000))
|
||||
self.shadowlines = None
|
||||
|
||||
def read_passwd(self, source, targfile=False):
|
||||
self.read_generic(source, self.uidmin, self.uidmax, targfile)
|
||||
|
||||
def read_group(self, source, targfile=False):
|
||||
self.read_generic(source, self.gidmin, self.gidmax, targfile)
|
||||
|
||||
def read_generic(self, source, minid, maxid, targfile):
|
||||
if targfile:
|
||||
self.targdata = []
|
||||
else:
|
||||
self.sourcedata = []
|
||||
with open(source, 'r') as inputfile:
|
||||
for line in inputfile.read().split('\n'):
|
||||
try:
|
||||
name, _, uid, _ = line.split(':', 3)
|
||||
uid = int(uid)
|
||||
except ValueError:
|
||||
continue
|
||||
if targfile:
|
||||
if uid <= minid or uid >= maxid:
|
||||
self.targdata.append(line)
|
||||
else:
|
||||
self.discardnames[name] = 1
|
||||
else:
|
||||
if uid >= minid and uid <= maxid:
|
||||
self.sourcedata.append(line)
|
||||
|
||||
def read_shadow(self, source):
|
||||
self.shadowlines = []
|
||||
try:
|
||||
with open(source, 'r') as inshadow:
|
||||
for line in inshadow.read().split('\n'):
|
||||
try:
|
||||
name, _ = line.split(':' , 1)
|
||||
except ValueError:
|
||||
continue
|
||||
if name in self.discardnames:
|
||||
continue
|
||||
self.shadowednames[name] = 1
|
||||
self.shadowlines.append(line)
|
||||
except FileNotFoundError:
|
||||
return
|
||||
|
||||
def write_out(self, outfile):
|
||||
with open(outfile, 'w') as targ:
|
||||
for line in self.targdata:
|
||||
targ.write(line + '\n')
|
||||
for line in self.sourcedata:
|
||||
targ.write(line + '\n')
|
||||
if outfile == '/etc/passwd':
|
||||
if self.shadowlines is None:
|
||||
self.read_shadow('/etc/shadow')
|
||||
with open('/etc/shadow', 'w') as shadout:
|
||||
for line in self.shadowlines:
|
||||
shadout.write(line + '\n')
|
||||
for line in self.sourcedata:
|
||||
name, _ = line.split(':', 1)
|
||||
if name in self.shadowednames:
|
||||
continue
|
||||
shadout.write(name + ':!:::::::\n')
|
||||
if outfile == '/etc/group':
|
||||
if self.shadowlines is None:
|
||||
self.read_shadow('/etc/gshadow')
|
||||
with open('/etc/gshadow', 'w') as shadout:
|
||||
for line in self.shadowlines:
|
||||
shadout.write(line + '\n')
|
||||
for line in self.sourcedata:
|
||||
name, _ = line.split(':' , 1)
|
||||
if name in self.shadowednames:
|
||||
continue
|
||||
shadout.write(name + ':!::\n')
|
||||
|
||||
def synchronize():
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
try:
|
||||
ac = apiclient.HTTPSClient()
|
||||
data = json.dumps({'merge': tmpdir})
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles', data)
|
||||
if status == 202:
|
||||
while status != 204:
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles')
|
||||
if not isinstance(rsp, str):
|
||||
rsp = rsp.decode('utf8')
|
||||
pendpasswd = os.path.join(tmpdir, 'etc/passwd')
|
||||
if os.path.exists(pendpasswd):
|
||||
cm = CredMerger()
|
||||
cm.read_passwd(pendpasswd, targfile=False)
|
||||
cm.read_passwd('/etc/passwd', targfile=True)
|
||||
cm.write_out('/etc/passwd')
|
||||
pendgroup = os.path.join(tmpdir, 'etc/group')
|
||||
if os.path.exists(pendgroup):
|
||||
cm = CredMerger()
|
||||
cm.read_group(pendgroup, targfile=False)
|
||||
cm.read_group('/etc/group', targfile=True)
|
||||
cm.write_out('/etc/group')
|
||||
pendhosts = os.path.join(tmpdir, 'etc/hosts')
|
||||
if os.path.exists(pendhosts):
|
||||
cm = HostMerger()
|
||||
cm.read_source(pendhosts)
|
||||
cm.read_target('/etc/hosts')
|
||||
cm.write_out('/etc/hosts')
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
synchronize()
|
||||
@@ -11,4 +11,8 @@ apikey=$(cat /etc/confluent/confluent.apikey)
|
||||
mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
hostnamectl set-hostname $(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
|
||||
touch /etc/cloud/cloud-init.disabled
|
||||
source /etc/confluent/functions
|
||||
|
||||
run_remote_parts firstboot
|
||||
run_remote_config firstboot
|
||||
curl --capath /etc/confluent/tls -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $apikey" -X POST -d "status: complete" https://$mgr/confluent-api/self/updatestatus
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
function set_confluent_vars() {
|
||||
if [ -z "$mgr" ]; then
|
||||
mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
|
||||
fi
|
||||
if [ -z "$profile" ]; then
|
||||
profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
|
||||
fi
|
||||
}
|
||||
|
||||
fetch_remote() {
|
||||
if [ -f /etc/confluent/ca.pem ]; then
|
||||
curlargs=" --cacert /etc/confluent/ca.pem"
|
||||
fi
|
||||
set_confluent_vars
|
||||
mkdir -p $(dirname $1)
|
||||
curl -f -sS $curlargs https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
|
||||
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
|
||||
}
|
||||
|
||||
run_remote_parts() {
|
||||
scriptlist=$(python3 /etc/confluent/apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
|
||||
for script in $scriptlist; do
|
||||
run_remote $1.d/$script
|
||||
done
|
||||
}
|
||||
|
||||
run_remote() {
|
||||
requestedcmd="'$*'"
|
||||
curlargs=""
|
||||
if [ -f /etc/confluent/ca.pem ]; then
|
||||
curlargs=" --cacert /etc/confluent/ca.pem"
|
||||
fi
|
||||
set_confluent_vars
|
||||
echo
|
||||
echo '---------------------------------------------------------------------------'
|
||||
echo Running $requestedcmd from https://$mgr/confluent-public/os/$profile/scripts/
|
||||
tmpdir=$(mktemp -d)
|
||||
echo Executing in $tmpdir
|
||||
cd $tmpdir
|
||||
mkdir -p $(dirname $1)
|
||||
curl -f -sS $curlargs https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
|
||||
if [ $? != 0 ]; then echo $requestedcmd failed to download; return 1; fi
|
||||
chmod +x $1
|
||||
cmd=$1
|
||||
#if [ -x /usr/bin/chcon ]; then
|
||||
# chcon system_u:object_r:bin_t:s0 $cmd
|
||||
#fi
|
||||
shift
|
||||
./$cmd $*
|
||||
retcode=$?
|
||||
echo "$requestedcmd exited with code $retcode"
|
||||
cd - > /dev/null
|
||||
return $retcode
|
||||
}
|
||||
|
||||
run_remote_python() {
|
||||
echo
|
||||
set_confluent_vars
|
||||
if [ -f /etc/confluent/ca.pem ]; then
|
||||
curlargs=" --cacert /etc/confluent/ca.pem"
|
||||
fi
|
||||
echo '---------------------------------------------------------------------------'
|
||||
echo Running python script "'$*'" from https://$mgr/confluent-public/os/$profile/scripts/
|
||||
tmpdir=$(mktemp -d)
|
||||
echo Executing in $tmpdir
|
||||
cd $tmpdir
|
||||
mkdir -p $(dirname $1)
|
||||
curl -f -sS $curlargs https://$mgr/confluent-public/os/$profile/scripts/$1 > $1
|
||||
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
|
||||
python3 $*
|
||||
retcode=$?
|
||||
echo "'$*' exited with code $retcode"
|
||||
cd - > /dev/null
|
||||
return $retcode
|
||||
}
|
||||
|
||||
run_remote_config() {
|
||||
echo
|
||||
set_confluent_vars
|
||||
apiclient=/opt/confluent/bin/apiclient
|
||||
if [ -f /etc/confluent/apiclient ]; then
|
||||
apiclient=/etc/confluent/apiclient
|
||||
fi
|
||||
echo '---------------------------------------------------------------------------'
|
||||
echo Requesting to run remote configuration for "'$*'" from $mgr under profile $profile
|
||||
python3 $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
|
||||
python3 $apiclient /confluent-api/self/remoteconfig/status -w 204
|
||||
echo
|
||||
echo 'Completed remote configuration'
|
||||
echo '---------------------------------------------------------------------------'
|
||||
return
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ echo ' EnableSSHKeysign yes' >> $sshconf
|
||||
echo ' HostbasedKeyTypes *ed25519*' >> $sshconf
|
||||
|
||||
curl -f https://$mgr/confluent-public/os/$profile/scripts/firstboot.sh > /target/etc/confluent/firstboot.sh
|
||||
curl -f https://$mgr/confluent-public/os/$profile/scripts/functions > /target/etc/confluent/functions
|
||||
source /target/etc/confluent/functions
|
||||
chmod +x /target/etc/confluent/firstboot.sh
|
||||
cp /tmp/allnodes /target/root/.shosts
|
||||
cp /tmp/allnodes /target/etc/ssh/shosts.equiv
|
||||
@@ -44,11 +46,30 @@ kargs=$(curl https://$mgr/confluent-public/os/$profile/profile.yaml | grep ^inst
|
||||
if [ ! -z "$kargs" ]; then
|
||||
sed -i 's/GRUB_CMDLINE_LINUX="\([^"]*\)"/GRUB_CMDLINE_LINUX="\1 '"${kargs}"'"/' /target/etc/default/grub
|
||||
fi
|
||||
if [ 1 = $updategrub ]; then
|
||||
mount -o bind /dev /target/dev
|
||||
mount -o bind /proc /target/proc
|
||||
mount -o bind /sys /target/sys
|
||||
chroot /target update-grub
|
||||
umount /target/sys /target/dev /target/proc
|
||||
fi
|
||||
mkdir -p /opt/confluent/bin
|
||||
mkdir -p /etc/confluent
|
||||
cp -a /target/etc/confluent/* /etc/confluent
|
||||
cp /custom-installation/confluent/bin/apiclient /opt/confluent/bin/
|
||||
cp /custom-installation/confluent/bin/apiclient /target/etc/confluent/
|
||||
|
||||
mount -o bind /dev /target/dev
|
||||
mount -o bind /proc /target/proc
|
||||
mount -o bind /sys /target/sys
|
||||
if [ 1 = $updategrub ]; then
|
||||
chroot /target update-grub
|
||||
fi
|
||||
echo "Port 22" >> /etc/ssh/sshd_config
|
||||
echo "Port 2222" >> /etc/ssh/sshd_config
|
||||
echo "Match LocalPort 22" >> /etc/ssh/sshd_config
|
||||
echo " ChrootDirectory /target" >> /etc/ssh/sshd_config
|
||||
kill -HUP $(cat /run/sshd.pid)
|
||||
cat /target/etc/confluent/tls/*.pem > /target/etc/confluent/ca.pem
|
||||
cat /target/etc/confluent/tls/*.pem > /etc/confluent/ca.pem
|
||||
chroot /target bash -c "source /etc/confluent/functions; run_remote_python syncfileclient"
|
||||
chroot /target bash -c "source /etc/confluent/functions; run_remote_parts post"
|
||||
source /target/etc/confluent/functions
|
||||
|
||||
run_remote_config post
|
||||
|
||||
umount /target/sys /target/dev /target/proc
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ if [ "$cryptboot" != "" ] && [ "$cryptboot" != "none" ] && [ "$cryptboot" != "n
|
||||
fi
|
||||
|
||||
|
||||
cat /custom-installation/ssh/*.rootpubkey > /root/.ssh/authorized_keys
|
||||
cat /custom-installation/ssh/*pubkey > /root/.ssh/authorized_keys
|
||||
nodename=$(grep ^NODENAME: /custom-installation/confluent/confluent.info|awk '{print $2}')
|
||||
apikey=$(cat /custom-installation/confluent/confluent.apikey)
|
||||
for pubkey in /etc/ssh/ssh_host*key.pub; do
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/python
|
||||
import importlib
|
||||
import tempfile
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from importlib.machinery import SourceFileLoader
|
||||
try:
|
||||
apiclient = SourceFileLoader('apiclient', '/opt/confluent/bin/apiclient').load_module()
|
||||
except FileNotFoundError:
|
||||
apiclient = SourceFileLoader('apiclient', '/etc/confluent/apiclient').load_module()
|
||||
|
||||
|
||||
def partitionhostsline(line):
|
||||
comment = ''
|
||||
try:
|
||||
cmdidx = line.index('#')
|
||||
comment = line[cmdidx:]
|
||||
line = line[:cmdidx].strip()
|
||||
except ValueError:
|
||||
pass
|
||||
if not line:
|
||||
return '', [], comment
|
||||
ipaddr, names = line.split(maxsplit=1)
|
||||
names = names.split()
|
||||
return ipaddr, names, comment
|
||||
|
||||
class HostMerger:
|
||||
def __init__(self):
|
||||
self.byip = {}
|
||||
self.byname = {}
|
||||
self.sourcelines = []
|
||||
self.targlines = []
|
||||
|
||||
def read_source(self, sourcefile):
|
||||
with open(sourcefile, 'r') as hfile:
|
||||
self.sourcelines = hfile.read().split('\n')
|
||||
while not self.sourcelines[-1]:
|
||||
self.sourcelines = self.sourcelines[:-1]
|
||||
for x in range(len(self.sourcelines)):
|
||||
line = self.sourcelines[x]
|
||||
currip, names, comment = partitionhostsline(line)
|
||||
if currip:
|
||||
self.byip[currip] = x
|
||||
for name in names:
|
||||
self.byname[name] = x
|
||||
|
||||
def read_target(self, targetfile):
|
||||
with open(targetfile, 'r') as hfile:
|
||||
lines = hfile.read().split('\n')
|
||||
if not lines[-1]:
|
||||
lines = lines[:-1]
|
||||
for y in range(len(lines)):
|
||||
line = lines[y]
|
||||
currip, names, comment = partitionhostsline(line)
|
||||
if currip in self.byip:
|
||||
x = self.byip[currip]
|
||||
if self.sourcelines[x] is None:
|
||||
# have already consumed this enntry
|
||||
continue
|
||||
self.targlines.append(self.sourcelines[x])
|
||||
self.sourcelines[x] = None
|
||||
continue
|
||||
for name in names:
|
||||
if name in self.byname:
|
||||
x = self.byname[name]
|
||||
if self.sourcelines[x] is None:
|
||||
break
|
||||
self.targlines.append(self.sourcelines[x])
|
||||
self.sourcelines[x] = None
|
||||
break
|
||||
else:
|
||||
self.targlines.append(line)
|
||||
|
||||
def write_out(self, targetfile):
|
||||
while not self.targlines[-1]:
|
||||
self.targlines = self.targlines[:-1]
|
||||
while not self.sourcelines[-1]:
|
||||
self.sourcelines = self.sourcelines[:-1]
|
||||
with open(targetfile, 'w') as hosts:
|
||||
for line in self.targlines:
|
||||
hosts.write(line + '\n')
|
||||
for line in self.sourcelines:
|
||||
if line is not None:
|
||||
hosts.write(line + '\n')
|
||||
|
||||
|
||||
class CredMerger:
|
||||
def __init__(self):
|
||||
try:
|
||||
with open('/etc/login.defs', 'r') as ldefs:
|
||||
defs = ldefs.read().split('\n')
|
||||
except FileNotFoundError:
|
||||
defs = []
|
||||
lkup = {}
|
||||
self.discardnames = {}
|
||||
self.shadowednames = {}
|
||||
for line in defs:
|
||||
try:
|
||||
line = line[:line.index('#')]
|
||||
except ValueError:
|
||||
pass
|
||||
keyval = line.split()
|
||||
if len(keyval) < 2:
|
||||
continue
|
||||
lkup[keyval[0]] = keyval[1]
|
||||
self.uidmin = int(lkup.get('UID_MIN', 1000))
|
||||
self.uidmax = int(lkup.get('UID_MAX', 60000))
|
||||
self.gidmin = int(lkup.get('GID_MIN', 1000))
|
||||
self.gidmax = int(lkup.get('GID_MAX', 60000))
|
||||
self.shadowlines = None
|
||||
|
||||
def read_passwd(self, source, targfile=False):
|
||||
self.read_generic(source, self.uidmin, self.uidmax, targfile)
|
||||
|
||||
def read_group(self, source, targfile=False):
|
||||
self.read_generic(source, self.gidmin, self.gidmax, targfile)
|
||||
|
||||
def read_generic(self, source, minid, maxid, targfile):
|
||||
if targfile:
|
||||
self.targdata = []
|
||||
else:
|
||||
self.sourcedata = []
|
||||
with open(source, 'r') as inputfile:
|
||||
for line in inputfile.read().split('\n'):
|
||||
try:
|
||||
name, _, uid, _ = line.split(':', 3)
|
||||
uid = int(uid)
|
||||
except ValueError:
|
||||
continue
|
||||
if targfile:
|
||||
if uid <= minid or uid >= maxid:
|
||||
self.targdata.append(line)
|
||||
else:
|
||||
self.discardnames[name] = 1
|
||||
else:
|
||||
if uid >= minid and uid <= maxid:
|
||||
self.sourcedata.append(line)
|
||||
|
||||
def read_shadow(self, source):
|
||||
self.shadowlines = []
|
||||
try:
|
||||
with open(source, 'r') as inshadow:
|
||||
for line in inshadow.read().split('\n'):
|
||||
try:
|
||||
name, _ = line.split(':' , 1)
|
||||
except ValueError:
|
||||
continue
|
||||
if name in self.discardnames:
|
||||
continue
|
||||
self.shadowednames[name] = 1
|
||||
self.shadowlines.append(line)
|
||||
except FileNotFoundError:
|
||||
return
|
||||
|
||||
def write_out(self, outfile):
|
||||
with open(outfile, 'w') as targ:
|
||||
for line in self.targdata:
|
||||
targ.write(line + '\n')
|
||||
for line in self.sourcedata:
|
||||
targ.write(line + '\n')
|
||||
if outfile == '/etc/passwd':
|
||||
if self.shadowlines is None:
|
||||
self.read_shadow('/etc/shadow')
|
||||
with open('/etc/shadow', 'w') as shadout:
|
||||
for line in self.shadowlines:
|
||||
shadout.write(line + '\n')
|
||||
for line in self.sourcedata:
|
||||
name, _ = line.split(':', 1)
|
||||
if name in self.shadowednames:
|
||||
continue
|
||||
shadout.write(name + ':!:::::::\n')
|
||||
if outfile == '/etc/group':
|
||||
if self.shadowlines is None:
|
||||
self.read_shadow('/etc/gshadow')
|
||||
with open('/etc/gshadow', 'w') as shadout:
|
||||
for line in self.shadowlines:
|
||||
shadout.write(line + '\n')
|
||||
for line in self.sourcedata:
|
||||
name, _ = line.split(':' , 1)
|
||||
if name in self.shadowednames:
|
||||
continue
|
||||
shadout.write(name + ':!::\n')
|
||||
|
||||
def synchronize():
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
try:
|
||||
ac = apiclient.HTTPSClient()
|
||||
data = json.dumps({'merge': tmpdir})
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles', data)
|
||||
if status == 202:
|
||||
while status != 204:
|
||||
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles')
|
||||
if not isinstance(rsp, str):
|
||||
rsp = rsp.decode('utf8')
|
||||
pendpasswd = os.path.join(tmpdir, 'etc/passwd')
|
||||
if os.path.exists(pendpasswd):
|
||||
cm = CredMerger()
|
||||
cm.read_passwd(pendpasswd, targfile=False)
|
||||
cm.read_passwd('/etc/passwd', targfile=True)
|
||||
cm.write_out('/etc/passwd')
|
||||
pendgroup = os.path.join(tmpdir, 'etc/group')
|
||||
if os.path.exists(pendgroup):
|
||||
cm = CredMerger()
|
||||
cm.read_group(pendgroup, targfile=False)
|
||||
cm.read_group('/etc/group', targfile=True)
|
||||
cm.write_out('/etc/group')
|
||||
pendhosts = os.path.join(tmpdir, 'etc/hosts')
|
||||
if os.path.exists(pendhosts):
|
||||
cm = HostMerger()
|
||||
cm.read_source(pendhosts)
|
||||
cm.read_target('/etc/hosts')
|
||||
cm.write_out('/etc/hosts')
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
synchronize()
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <errno.h>
|
||||
#include <termios.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <fcntl.h>
|
||||
@@ -20,20 +21,29 @@
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
struct termios tty;
|
||||
struct termios tty2;
|
||||
struct winsize ws;
|
||||
unsigned width, height;
|
||||
int ttyf;
|
||||
int spcr;
|
||||
int tmpi;
|
||||
int currspeed;
|
||||
int flags;
|
||||
speed_t cspeed;
|
||||
char buff[128];
|
||||
int bufflen;
|
||||
fd_set set;
|
||||
struct timeval timeout;
|
||||
char* offset;
|
||||
uint64_t address;
|
||||
spcr = open("/sys/firmware/acpi/tables/SPCR", O_RDONLY);
|
||||
if (spcr < 0) {
|
||||
bufflen = 0;
|
||||
tmpi = open("/sys/firmware/acpi/tables/SPCR", O_RDONLY);
|
||||
if (tmpi < 0) {
|
||||
exit(0);
|
||||
}
|
||||
if (read(spcr, buff, 80) < 80) {
|
||||
if (read(tmpi, buff, 80) < 80) {
|
||||
exit(0);
|
||||
}
|
||||
close(tmpi);
|
||||
if (buff[8] != 2) exit(0); //revision 2
|
||||
if (buff[36] != 0) exit(0); //16550 only
|
||||
if (buff[40] != 1) exit(0); //IO only
|
||||
@@ -69,12 +79,51 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
tcgetattr(ttyf, &tty);
|
||||
if (cspeed) {
|
||||
cfsetospeed(&tty, B115200);
|
||||
cfsetispeed(&tty, B115200);
|
||||
cfsetospeed(&tty, cspeed);
|
||||
cfsetispeed(&tty, cspeed);
|
||||
}
|
||||
printf("%s\n", buff);
|
||||
tcgetattr(ttyf, &tty2);
|
||||
cfmakeraw(&tty2);
|
||||
tcsetattr(ttyf, TCSANOW, &tty2);
|
||||
flags = fcntl(ttyf, F_GETFL, 0);
|
||||
fcntl(ttyf, F_SETFL, flags | O_NONBLOCK);
|
||||
while (read(ttyf, buff, 64) > 0) {
|
||||
// Drain any pending reads
|
||||
}
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = 500000;
|
||||
FD_ZERO(&set);
|
||||
FD_SET(ttyf, &set);
|
||||
write(ttyf, "\0337\033[999;999H\033[6n\0338", 18);
|
||||
while (select(ttyf + 1, &set, NULL, NULL, &timeout) > 0) {
|
||||
if ((tmpi = read(ttyf, buff + bufflen, 127 - bufflen)) < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
bufflen += tmpi;
|
||||
buff[bufflen] = 0;
|
||||
if (strchr(buff, 'R')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
fcntl(ttyf, F_SETFL, flags);
|
||||
ws.ws_xpixel = 0;
|
||||
ws.ws_ypixel = 0;
|
||||
if (sscanf(buff, "\033[%u;%uR", &height, &width) == 2) {
|
||||
ws.ws_col = width;
|
||||
ws.ws_row = height;
|
||||
} else {
|
||||
ws.ws_col = 100;
|
||||
ws.ws_row = 31;
|
||||
}
|
||||
if (ws.ws_col < 80) { ws.ws_col = 80; }
|
||||
if (ws.ws_row < 24) { ws.ws_col = 24; }
|
||||
ioctl(ttyf, TIOCSWINSZ, &ws);
|
||||
tcsetattr(ttyf, TCSANOW, &tty);
|
||||
ioctl(ttyf, TIOCCONS, 0);
|
||||
printf("%s\n", buff);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ def main(args):
|
||||
ap = argparse.ArgumentParser(description='Manage OS deployment resources')
|
||||
sp = ap.add_subparsers(dest='command')
|
||||
wiz = sp.add_parser('initialize', help='Do OS deployment preparation')
|
||||
wiz.add_argument('-a', help='Initialize SSH access by confluent to nodes for automation such as ansible playbook execution or syncfiles', action='store_true')
|
||||
wiz.add_argument('-g', help='Initialize a Genesis profile to boot systems into a rescue or staging environment', action='store_true')
|
||||
wiz.add_argument('-u', help='Pull in root user key for node deployment', action='store_true')
|
||||
wiz.add_argument('-s', help='Set up SSH CA for managing node to node ssh and known hosts', action='store_true')
|
||||
@@ -100,6 +101,8 @@ def initialize_genesis():
|
||||
'/var/lib/confluent/public/os/genesis-x86_64/boot/initramfs/addons.cpio')
|
||||
os.symlink('/opt/confluent/genesis/x86_64/boot/kernel',
|
||||
'/var/lib/confluent/public/os/genesis-x86_64/boot/kernel')
|
||||
shutil.copytree('/opt/confluent/lib/osdeploy/genesis/profiles/default/ansible/',
|
||||
'/var/lib/confluent/public/os/genesis-x86_64/ansible/')
|
||||
shutil.copytree('/opt/confluent/lib/osdeploy/genesis/profiles/default/scripts/',
|
||||
'/var/lib/confluent/public/os/genesis-x86_64/scripts/')
|
||||
shutil.copyfile('/opt/confluent/lib/osdeploy/genesis/profiles/default/profile.yaml',
|
||||
@@ -206,6 +209,8 @@ def initialize(cmdset):
|
||||
cmdset.l = input().strip().lower().startswith('y')
|
||||
sys.stdout.write('Update tftp directory with binaries to support PXE (-p) (y/n): ')
|
||||
cmdset.p = input().strip().lower().startswith('y')
|
||||
sys.stdout.write('Initialize confluent ssh user key so confluent can execute remote automation (e.g. Ansible plays) (-a) (y/n): ')
|
||||
cmdset.a = input().strip().lower().startswith('y')
|
||||
sys.stdout.write('Generate new TLS certificates for HTTP, replacing any existing certificate (-t)? (y/n): ')
|
||||
cmdset.t = input().strip().lower().startswith('y')
|
||||
if not cmdset.t:
|
||||
@@ -241,6 +246,9 @@ def initialize(cmdset):
|
||||
if cmdset.s:
|
||||
didsomething = True
|
||||
sshutil.initialize_ca()
|
||||
if cmdset.a:
|
||||
didsomething = True
|
||||
sshutil.initialize_root_key(True, True)
|
||||
if cmdset.p:
|
||||
install_tftp_content()
|
||||
if cmdset.l:
|
||||
|
||||
@@ -72,7 +72,7 @@ def create_certificate(keyout=None, certout=None):
|
||||
if not keyout:
|
||||
raise Exception('Unable to locate TLS certificate path automatically')
|
||||
shortname = socket.gethostname().split('.')[0]
|
||||
longname = socket.getfqdn()
|
||||
longname = shortname # socket.getfqdn()
|
||||
subprocess.check_call(
|
||||
['openssl', 'ecparam', '-name', 'secp384r1', '-genkey', '-out',
|
||||
keyout])
|
||||
@@ -81,7 +81,7 @@ def create_certificate(keyout=None, certout=None):
|
||||
# there exists non-compliant clients that fail with them as IP
|
||||
san.extend(['DNS:{0}'.format(x) for x in get_ip_addresses()])
|
||||
san.append('DNS:{0}'.format(shortname))
|
||||
san.append('DNS:{0}'.format(longname))
|
||||
#san.append('DNS:{0}'.format(longname))
|
||||
san = ','.join(san)
|
||||
sslcfg = get_openssl_conf_location()
|
||||
tmpconfig = tempfile.mktemp()
|
||||
|
||||
@@ -33,42 +33,67 @@ import confluent.tlvdata as tlvdata
|
||||
import confluent.util as util
|
||||
import eventlet
|
||||
import eventlet.event
|
||||
import eventlet.green.os as os
|
||||
import eventlet.green.select as select
|
||||
import eventlet.green.socket as socket
|
||||
import eventlet.green.subprocess as subprocess
|
||||
import eventlet.green.ssl as ssl
|
||||
import pyte
|
||||
import eventlet.semaphore as semaphore
|
||||
import fcntl
|
||||
import random
|
||||
import struct
|
||||
import time
|
||||
import traceback
|
||||
|
||||
_handled_consoles = {}
|
||||
|
||||
_tracelog = None
|
||||
_bufferdaemon = None
|
||||
_bufferlock = None
|
||||
|
||||
try:
|
||||
range = xrange
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
pytecolors2ansi = {
|
||||
'black': 0,
|
||||
'red': 1,
|
||||
'green': 2,
|
||||
'brown': 3,
|
||||
'blue': 4,
|
||||
'magenta': 5,
|
||||
'cyan': 6,
|
||||
'white': 7,
|
||||
'default': 9,
|
||||
}
|
||||
# might be able to use IBMPC map from pyte charsets,
|
||||
# in that case, would have to mask out certain things (like ESC)
|
||||
# in the same way that Screen's draw method would do
|
||||
# for now at least get some of the arrows in there (note ESC is one
|
||||
# of those arrows... so skip it...
|
||||
ansichars = dict(zip((0x18, 0x19), u'\u2191\u2193'))
|
||||
|
||||
def chunk_output(output, n):
|
||||
for i in range(0, len(output), n):
|
||||
yield output[i:i + n]
|
||||
|
||||
def get_buffer_output(nodename):
|
||||
out = _bufferdaemon.stdin
|
||||
instream = _bufferdaemon.stdout
|
||||
if not isinstance(nodename, bytes):
|
||||
nodename = nodename.encode('utf8')
|
||||
outdata = b''
|
||||
with _bufferlock:
|
||||
out.write(struct.pack('I', len(nodename)))
|
||||
out.write(nodename)
|
||||
out.flush()
|
||||
select.select((instream,), (), (), 30)
|
||||
while not outdata or outdata[-1]:
|
||||
chunk = instream.read(128)
|
||||
if chunk:
|
||||
outdata += chunk
|
||||
else:
|
||||
select.select((instream,), (), (), 0)
|
||||
return outdata[:-1]
|
||||
|
||||
|
||||
def _utf8_normalize(data, shiftin, decoder):
|
||||
def send_output(nodename, output):
|
||||
if not isinstance(nodename, bytes):
|
||||
nodename = nodename.encode('utf8')
|
||||
with _bufferlock:
|
||||
_bufferdaemon.stdin.write(struct.pack('I', len(nodename) | (1 << 29)))
|
||||
_bufferdaemon.stdin.write(nodename)
|
||||
_bufferdaemon.stdin.flush()
|
||||
for chunk in chunk_output(output, 8192):
|
||||
_bufferdaemon.stdin.write(struct.pack('I', len(chunk) | (2 << 29)))
|
||||
_bufferdaemon.stdin.write(chunk)
|
||||
_bufferdaemon.stdin.flush()
|
||||
|
||||
def _utf8_normalize(data, decoder):
|
||||
# first we give the stateful decoder a crack at the byte stream,
|
||||
# we may come up empty in the event of a partial multibyte
|
||||
try:
|
||||
@@ -88,60 +113,9 @@ def _utf8_normalize(data, shiftin, decoder):
|
||||
# Finally, the low part of ascii is valid utf-8, but we are going to be
|
||||
# more interested in the cp437 versions (since this is console *output*
|
||||
# not input
|
||||
if shiftin is None:
|
||||
data = data.translate(ansichars)
|
||||
return data.encode('utf-8')
|
||||
|
||||
|
||||
def pytechars2line(chars, maxlen=None):
|
||||
line = b'\x1b[m' # start at default params
|
||||
lb = False # last bold
|
||||
li = False # last italic
|
||||
lu = False # last underline
|
||||
ls = False # last strikethrough
|
||||
lr = False # last reverse
|
||||
lfg = 'default' # last fg color
|
||||
lbg = 'default' # last bg color
|
||||
hasdata = False
|
||||
len = 1
|
||||
for charidx in range(maxlen):
|
||||
char = chars[charidx]
|
||||
csi = bytearray([])
|
||||
if char.fg != lfg:
|
||||
csi.append(30 + pytecolors2ansi.get(char.fg, 9))
|
||||
lfg = char.fg
|
||||
if char.bg != lbg:
|
||||
csi.append(40 + pytecolors2ansi.get(char.bg, 9))
|
||||
lbg = char.bg
|
||||
if char.bold != lb:
|
||||
lb = char.bold
|
||||
csi.append(1 if lb else 22)
|
||||
if char.italics != li:
|
||||
li = char.italics
|
||||
csi.append(3 if li else 23)
|
||||
if char.underscore != lu:
|
||||
lu = char.underscore
|
||||
csi.append(4 if lu else 24)
|
||||
if char.strikethrough != ls:
|
||||
ls = char.strikethrough
|
||||
csi.append(9 if ls else 29)
|
||||
if char.reverse != lr:
|
||||
lr = char.reverse
|
||||
csi.append(7 if lr else 27)
|
||||
if csi:
|
||||
line += b'\x1b[' + b';'.join(['{0}'.format(x).encode('utf-8') for x in csi]) + b'm'
|
||||
if not hasdata and char.data.rstrip():
|
||||
hasdata = True
|
||||
chardata = char.data
|
||||
if not isinstance(chardata, bytes):
|
||||
chardata = chardata.encode('utf-8')
|
||||
line += chardata
|
||||
if maxlen and len >= maxlen:
|
||||
break
|
||||
len += 1
|
||||
return line, hasdata
|
||||
|
||||
|
||||
class ConsoleHandler(object):
|
||||
_plugin_path = '/nodes/{0}/_console/session'
|
||||
_logtobuffer = True
|
||||
@@ -161,15 +135,15 @@ class ConsoleHandler(object):
|
||||
self.node = node
|
||||
self.connectstate = 'unconnected'
|
||||
self._isalive = True
|
||||
self.buffer = pyte.Screen(100, 31)
|
||||
self.termstream = pyte.ByteStream()
|
||||
self.termstream.attach(self.buffer)
|
||||
#self.buffer = pyte.Screen(100, 31)
|
||||
#self.termstream = pyte.ByteStream()
|
||||
#self.termstream.attach(self.buffer)
|
||||
self.livesessions = set([])
|
||||
self.utf8decoder = codecs.getincrementaldecoder('utf-8')()
|
||||
if self._logtobuffer:
|
||||
self.logger = log.Logger(node, console=True,
|
||||
tenant=configmanager.tenant)
|
||||
(text, termstate, timestamp) = (b'', 0, False)
|
||||
timestamp = False
|
||||
# when reading from log file, we will use wall clock
|
||||
# it should usually match walltime.
|
||||
self.lasttime = 0
|
||||
@@ -182,13 +156,7 @@ class ConsoleHandler(object):
|
||||
# guess
|
||||
self.lasttime = util.monotonic_time()
|
||||
self.clearbuffer()
|
||||
self.appmodedetected = False
|
||||
self.shiftin = None
|
||||
self.reconnect = None
|
||||
if termstate & 1:
|
||||
self.appmodedetected = True
|
||||
if termstate & 2:
|
||||
self.shiftin = b'0'
|
||||
self.users = {}
|
||||
self._attribwatcher = None
|
||||
self._console = None
|
||||
@@ -216,10 +184,7 @@ class ConsoleHandler(object):
|
||||
if not isinstance(data, bytes):
|
||||
data = data.encode('utf-8')
|
||||
try:
|
||||
self.termstream.feed(data)
|
||||
except StopIteration: # corrupt parser state, start over
|
||||
self.termstream = pyte.ByteStream()
|
||||
self.termstream.attach(self.buffer)
|
||||
send_output(self.node, data)
|
||||
except Exception:
|
||||
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
|
||||
event=log.Events.stacktrace)
|
||||
@@ -536,19 +501,7 @@ class ConsoleHandler(object):
|
||||
return
|
||||
if not isinstance(data, bytes):
|
||||
data = data.encode('utf-8')
|
||||
if b'\x1b[?1l' in data: # request for ansi mode cursor keys
|
||||
self.appmodedetected = False
|
||||
if b'\x1b[?1h' in data: # remember the session wants the client to use
|
||||
# 'application mode' Thus far only observed on esxi
|
||||
self.appmodedetected = True
|
||||
if b'\x1b)0' in data:
|
||||
# console indicates it wants access to special drawing characters
|
||||
self.shiftin = b'0'
|
||||
eventdata = 0
|
||||
if self.appmodedetected:
|
||||
eventdata |= 1
|
||||
if self.shiftin is not None:
|
||||
eventdata |= 2
|
||||
# TODO: analyze buffer for registered events, examples:
|
||||
# panics
|
||||
# certificate signing request
|
||||
@@ -557,7 +510,7 @@ class ConsoleHandler(object):
|
||||
self.clearerror = False
|
||||
self.feedbuffer(b'\x1bc\x1b[2J\x1b[1;1H')
|
||||
self._send_rcpts(b'\x1bc\x1b[2J\x1b[1;1H')
|
||||
self._send_rcpts(_utf8_normalize(data, self.shiftin, self.utf8decoder))
|
||||
self._send_rcpts(_utf8_normalize(data, self.utf8decoder))
|
||||
self.log(data, eventdata=eventdata)
|
||||
self.lasttime = util.monotonic_time()
|
||||
self.feedbuffer(data)
|
||||
@@ -583,36 +536,7 @@ class ConsoleHandler(object):
|
||||
'connectstate': self.connectstate,
|
||||
'clientcount': len(self.livesessions),
|
||||
}
|
||||
retdata = b'\x1b[H\x1b[J' # clear screen
|
||||
pendingbl = b'' # pending blank lines
|
||||
maxlen = 0
|
||||
for line in self.buffer.display:
|
||||
line = line.rstrip()
|
||||
if len(line) > maxlen:
|
||||
maxlen = len(line)
|
||||
for line in range(self.buffer.lines):
|
||||
nline, notblank = pytechars2line(self.buffer.buffer[line], maxlen)
|
||||
if notblank:
|
||||
if pendingbl:
|
||||
retdata += pendingbl
|
||||
pendingbl = b''
|
||||
retdata += nline + b'\r\n'
|
||||
else:
|
||||
pendingbl += nline + b'\r\n'
|
||||
if len(retdata) > 6:
|
||||
retdata = retdata[:-2] # remove the last \r\n
|
||||
cursordata = '\x1b[{0};{1}H'.format(self.buffer.cursor.y + 1,
|
||||
self.buffer.cursor.x + 1)
|
||||
if not isinstance(cursordata, bytes):
|
||||
cursordata = cursordata.encode('utf-8')
|
||||
retdata += cursordata
|
||||
if self.shiftin is not None: # detected that terminal requested a
|
||||
# shiftin character set, relay that to the terminal that cannected
|
||||
retdata += b'\x1b)' + self.shiftin
|
||||
#if self.appmodedetected:
|
||||
# retdata += b'\x1b[?1h'
|
||||
#else:
|
||||
# retdata += b'\x1b[?1l'
|
||||
retdata = get_buffer_output(self.node)
|
||||
return retdata, connstate
|
||||
|
||||
def write(self, data):
|
||||
@@ -661,7 +585,16 @@ def _start_tenant_sessions(cfm):
|
||||
|
||||
def start_console_sessions():
|
||||
global _tracelog
|
||||
global _bufferdaemon
|
||||
global _bufferlock
|
||||
_bufferlock = semaphore.Semaphore()
|
||||
_tracelog = log.Logger('trace')
|
||||
_bufferdaemon = subprocess.Popen(
|
||||
['/opt/confluent/bin/vtbufferd'], stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE)
|
||||
fl = fcntl.fcntl(_bufferdaemon.stdout.fileno(), fcntl.F_GETFL)
|
||||
fcntl.fcntl(_bufferdaemon.stdout.fileno(),
|
||||
fcntl.F_SETFL, fl | os.O_NONBLOCK)
|
||||
configmodule.hook_new_configmanagers(_start_tenant_sessions)
|
||||
|
||||
|
||||
|
||||
@@ -811,7 +811,11 @@ def get_smm_neighbor_fingerprints(smmaddr, cv):
|
||||
if ':' in smmaddr:
|
||||
smmaddr = '[{0}]'.format(smmaddr)
|
||||
wc = webclient.SecureHTTPConnection(smmaddr, verifycallback=cv)
|
||||
neighs = wc.grab_json_response('/scripts/neighdata.json')
|
||||
try:
|
||||
neighs = wc.grab_json_response('/scripts/neighdata.json')
|
||||
except Exception:
|
||||
log.log({'error': 'Failure getting LLDP information from {}'.format(smmaddr)})
|
||||
return
|
||||
if not neighs:
|
||||
return
|
||||
for neigh in neighs:
|
||||
|
||||
@@ -494,6 +494,7 @@ def check_reply(node, info, packet, sock, cfg, reqview):
|
||||
log.log({'error': nicerr})
|
||||
if niccfg.get('ipv4_broken', False):
|
||||
# Received a request over a nic with no ipv4 configured, ignore it
|
||||
log.log({'error': 'Skipping boot reply to {0} due to no viable IPv4 configuration on deployment system'.format(node)})
|
||||
return
|
||||
clipn = None
|
||||
if niccfg['ipv4_address'] and niccfg['ipv4_method'] != 'firmwaredhcp':
|
||||
@@ -557,6 +558,16 @@ def check_reply(node, info, packet, sock, cfg, reqview):
|
||||
staticassigns[fulladdr] = (clipn, repview[:replen + 28].tobytes())
|
||||
elif fulladdr in staticassigns:
|
||||
del staticassigns[fulladdr]
|
||||
if httpboot:
|
||||
boottype = 'HTTP'
|
||||
else:
|
||||
boottype = 'PXE'
|
||||
if clipn:
|
||||
ipinfo = 'with static address {0}'.format(niccfg['ipv4_address'])
|
||||
else:
|
||||
ipinfo = 'without address'
|
||||
log.log({
|
||||
'info': 'Offering {0} boot {1} to {2}'.format(boottype, ipinfo, node)})
|
||||
send_raw_packet(repview, replen + 28, reqview, info)
|
||||
|
||||
def send_raw_packet(repview, replen, reqview, info):
|
||||
|
||||
@@ -107,6 +107,8 @@ def _parse_slp_packet(packet, peer, rsps, xidmap):
|
||||
if '%' in addr:
|
||||
addr = addr[:addr.index('%')]
|
||||
mac = None
|
||||
if addr not in neighutil.neightable:
|
||||
neighutil.update_neigh()
|
||||
if addr in neighutil.neightable:
|
||||
identifier = neighutil.neightable[addr]
|
||||
mac = identifier
|
||||
@@ -540,6 +542,8 @@ def active_scan(handler, protocol=None):
|
||||
for scanned in scan():
|
||||
for addr in scanned['addresses']:
|
||||
ip = addr[0].partition('%')[0] # discard scope if present
|
||||
if ip not in neighutil.neightable:
|
||||
neighutil.update_neigh()
|
||||
if ip not in neighutil.neightable:
|
||||
continue
|
||||
if addr in known_peers:
|
||||
|
||||
@@ -62,6 +62,8 @@ def active_scan(handler, protocol=None):
|
||||
for scanned in scan(['urn:dmtf-org:service:redfish-rest:1']):
|
||||
for addr in scanned['addresses']:
|
||||
ip = addr[0].partition('%')[0] # discard scope if present
|
||||
if ip not in neighutil.neightable:
|
||||
neighutil.update_neigh()
|
||||
if ip not in neighutil.neightable:
|
||||
continue
|
||||
if addr in known_peers:
|
||||
@@ -311,6 +313,8 @@ def _parse_ssdp(peer, rsp, peerdata):
|
||||
ip = peer[0].partition('%')[0]
|
||||
nid = ip
|
||||
mac = None
|
||||
if ip not in neighutil.neightable:
|
||||
neighutil.update_neigh()
|
||||
if ip in neighutil.neightable:
|
||||
nid = neighutil.neightable[ip]
|
||||
mac = nid
|
||||
|
||||
@@ -1054,7 +1054,7 @@ class InputNetworkConfiguration(ConfluentInputMessage):
|
||||
if 'ipv4_gateway' not in inputdata:
|
||||
inputdata['ipv4_gateway'] = None
|
||||
|
||||
if 'ipv4_configuration' in inputdata:
|
||||
if 'ipv4_configuration' in inputdata and inputdata['ipv4_configuration']:
|
||||
if inputdata['ipv4_configuration'].lower() not in ['dhcp','static']:
|
||||
raise exc.InvalidArgumentException(
|
||||
'Unrecognized ipv4_configuration')
|
||||
|
||||
@@ -82,8 +82,11 @@ _idxtoifnamemap = {}
|
||||
def _rebuildidxmap():
|
||||
_idxtoifnamemap.clear()
|
||||
for iname in os.listdir('/sys/class/net'):
|
||||
ci = int(open('/sys/class/net/{0}/ifindex'.format(iname)).read())
|
||||
_idxtoifnamemap[ci] = iname
|
||||
try:
|
||||
ci = int(open('/sys/class/net/{0}/ifindex'.format(iname)).read())
|
||||
_idxtoifnamemap[ci] = iname
|
||||
except Exception: # there may be non interface in /sys/class/net
|
||||
pass
|
||||
|
||||
|
||||
def myiptonets(svrip):
|
||||
|
||||
@@ -139,7 +139,10 @@ def get_fingerprint(switch, port, configmanager, portmatch):
|
||||
continue
|
||||
if info.get('switch', None) != switch:
|
||||
continue
|
||||
if portmatch(info.get('port'), port):
|
||||
if portmatch(info.get('portid', None), port):
|
||||
return ('sha256$' + b64tohex(info['peersha256fingerprint']),
|
||||
info.get('verified', False))
|
||||
elif portmatch(info.get('port', None), port):
|
||||
return ('sha256$' + b64tohex(info['peersha256fingerprint']),
|
||||
info.get('verified', False))
|
||||
return None, False
|
||||
@@ -235,19 +238,23 @@ def _extract_neighbor_data_b(args):
|
||||
idx = oidindex[0][-1]
|
||||
idxtoifname[idx] = _lldpdesc_to_ifname(sid, idx, str(oidindex[1]))
|
||||
for remotedesc in conn.walk('1.0.8802.1.1.2.1.4.1.1.10'):
|
||||
iname = idxtoifname[remotedesc[0][-2]]
|
||||
iname = idxtoifname.get(remotedesc[0][-2],
|
||||
idxtoportid[remotedesc[0][-2]])
|
||||
_init_lldp(lldpdata, iname, remotedesc[0][-2], idxtoportid, switch)
|
||||
_extract_extended_desc(lldpdata[iname], remotedesc[1], user)
|
||||
for remotename in conn.walk('1.0.8802.1.1.2.1.4.1.1.9'):
|
||||
iname = idxtoifname[remotename[0][-2]]
|
||||
iname = idxtoifname.get(remotename[0][-2],
|
||||
idxtoportid[remotename[0][-2]])
|
||||
_init_lldp(lldpdata, iname, remotename[0][-2], idxtoportid, switch)
|
||||
lldpdata[iname]['peername'] = str(remotename[1])
|
||||
for remotename in conn.walk('1.0.8802.1.1.2.1.4.1.1.7'):
|
||||
iname = idxtoifname[remotename[0][-2]]
|
||||
iname = idxtoifname.get(remotename[0][-2],
|
||||
idxtoportid[remotename[0][-2]])
|
||||
_init_lldp(lldpdata, iname, remotename[0][-2], idxtoportid, switch)
|
||||
lldpdata[iname]['peerportid'] = sanitize(remotename[1])
|
||||
for remoteid in conn.walk('1.0.8802.1.1.2.1.4.1.1.5'):
|
||||
iname = idxtoifname[remoteid[0][-2]]
|
||||
iname = idxtoifname.get(remoteid[0][-2],
|
||||
idxtoportid[remoteid[0][-2]])
|
||||
_init_lldp(lldpdata, iname, remoteid[0][-2], idxtoportid, switch)
|
||||
lldpdata[iname]['peerchassisid'] = sanitize(remoteid[1])
|
||||
for entry in lldpdata:
|
||||
|
||||
@@ -82,6 +82,8 @@ _blacklistnames = (
|
||||
|
||||
|
||||
def _namesmatch(switchdesc, userdesc):
|
||||
if switchdesc is None:
|
||||
return False
|
||||
if switchdesc == userdesc:
|
||||
return True
|
||||
try:
|
||||
@@ -217,13 +219,18 @@ def _map_switch_backend(args):
|
||||
*([int(x) for x in oid[-6:]])
|
||||
)
|
||||
mactobridge[macaddr] = int(bridgeport)
|
||||
#ciscoiftovlanmap = {}
|
||||
vlanstocheck = set([])
|
||||
for vb in conn.walk('.1.3.6.1.4.1.9.9.68.1.2.2.1.2'):
|
||||
vlanstocheck.add(vb[1])
|
||||
#ciscotrunktovlanmap = {}
|
||||
for vb in conn.walk('.1.3.6.1.4.1.9.9.46.1.6.1.1.5'):
|
||||
vlanstocheck.add(vb[1])
|
||||
try:
|
||||
#ciscoiftovlanmap = {}
|
||||
for vb in conn.walk('.1.3.6.1.4.1.9.9.68.1.2.2.1.2'):
|
||||
vlanstocheck.add(vb[1])
|
||||
#ciscotrunktovlanmap = {}
|
||||
for vb in conn.walk('.1.3.6.1.4.1.9.9.46.1.6.1.1.5'):
|
||||
vlanstocheck.add(vb[1])
|
||||
except Exception:
|
||||
# We might have crashed snmp on a non-cisco switch
|
||||
# in such a case, delay 8 seconds to allow recovery to complete
|
||||
eventlet.sleep(8)
|
||||
if not vlanstocheck:
|
||||
vlanstocheck.add(None)
|
||||
bridgetoifmap = {}
|
||||
|
||||
@@ -216,6 +216,7 @@ def extract_entries(entries, flags=0, callback=None, totalsize=None, extractlist
|
||||
os.chmod(str(entry), 0o644)
|
||||
if callback:
|
||||
callback({'progress': float(sizedone) / float(totalsize)})
|
||||
return float(sizedone) / float(totalsize)
|
||||
|
||||
|
||||
def extract_file(archfile, flags=0, callback=lambda x: None, imginfo=(), extractlist=None):
|
||||
@@ -225,9 +226,16 @@ def extract_file(archfile, flags=0, callback=lambda x: None, imginfo=(), extract
|
||||
if not imginfo[img]:
|
||||
continue
|
||||
totalsize += imginfo[img]
|
||||
archfile.seek(0)
|
||||
with libarchive.fd_reader(archfile.fileno()) as archive:
|
||||
extract_entries(archive, flags, callback, totalsize, extractlist)
|
||||
dfd = os.dup(archfile.fileno())
|
||||
os.lseek(dfd, 0, 0)
|
||||
pctdone = 0
|
||||
try:
|
||||
with libarchive.fd_reader(dfd) as archive:
|
||||
pctdone = extract_entries(archive, flags, callback, totalsize,
|
||||
extractlist)
|
||||
finally:
|
||||
os.close(dfd)
|
||||
return pctdone
|
||||
|
||||
|
||||
def check_alma(isoinfo):
|
||||
@@ -444,27 +452,31 @@ def check_rhel(isoinfo):
|
||||
def scan_iso(archive):
|
||||
filesizes = {}
|
||||
filecontents = {}
|
||||
with libarchive.fd_reader(archive.fileno()) as reader:
|
||||
for ent in reader:
|
||||
if str(ent).endswith('TRANS.TBL'):
|
||||
continue
|
||||
eventlet.sleep(0)
|
||||
filesizes[str(ent)] = ent.size
|
||||
if str(ent) in READFILES:
|
||||
filecontents[str(ent)] = b''
|
||||
for block in ent.get_blocks():
|
||||
filecontents[str(ent)] += bytes(block)
|
||||
dfd = os.dup(archive.fileno())
|
||||
os.lseek(dfd, 0, 0)
|
||||
try:
|
||||
with libarchive.fd_reader(dfd) as reader:
|
||||
for ent in reader:
|
||||
if str(ent).endswith('TRANS.TBL'):
|
||||
continue
|
||||
eventlet.sleep(0)
|
||||
filesizes[str(ent)] = ent.size
|
||||
if str(ent) in READFILES:
|
||||
filecontents[str(ent)] = b''
|
||||
for block in ent.get_blocks():
|
||||
filecontents[str(ent)] += bytes(block)
|
||||
finally:
|
||||
os.close(dfd)
|
||||
return filesizes, filecontents
|
||||
|
||||
|
||||
def fingerprint(archive):
|
||||
archive.seek(0)
|
||||
header = archive.read(32768)
|
||||
archive.seek(32769)
|
||||
if archive.read(6) == b'CD001\x01':
|
||||
# ISO image
|
||||
archive.seek(0)
|
||||
isoinfo = scan_iso(archive)
|
||||
archive.seek(0)
|
||||
name = None
|
||||
for fun in globals():
|
||||
if fun.startswith('check_'):
|
||||
@@ -490,7 +502,6 @@ def import_image(filename, callback, backend=False, mfd=None):
|
||||
archive = os.fdopen(int(mfd), 'rb')
|
||||
else:
|
||||
archive = open(filename, 'rb')
|
||||
archive.seek(0)
|
||||
identity = fingerprint(archive)
|
||||
if not identity:
|
||||
return -1
|
||||
@@ -505,22 +516,37 @@ def import_image(filename, callback, backend=False, mfd=None):
|
||||
os.chdir(targpath)
|
||||
if not backend:
|
||||
print('Importing OS to ' + targpath + ':')
|
||||
printit({'progress': 0.0})
|
||||
callback({'progress': 0.0})
|
||||
pct = 0.0
|
||||
if EXTRACT & identity['method']:
|
||||
extract_file(archive, callback=callback, imginfo=imginfo, extractlist=identity.get('extractlist', None))
|
||||
pct = extract_file(archive, callback=callback, imginfo=imginfo,
|
||||
extractlist=identity.get('extractlist', None))
|
||||
if COPY & identity['method']:
|
||||
basename = identity.get('copyto', os.path.basename(filename))
|
||||
targpath = os.path.join(targpath, basename)
|
||||
archive.seek(0)
|
||||
with open(targpath, 'wb') as targ:
|
||||
shutil.copyfileobj(archive, targ)
|
||||
targiso = os.path.join(targpath, basename)
|
||||
archive.seek(0, 2)
|
||||
totalsz = archive.tell()
|
||||
currsz = 0
|
||||
modpct = 1.0 - pct
|
||||
archive.seek(0, 0)
|
||||
printat = 0
|
||||
with open(targiso, 'wb') as targ:
|
||||
buf = archive.read(32768)
|
||||
while buf:
|
||||
currsz += len(buf)
|
||||
pgress = pct + ((float(currsz) / float(totalsz)) * modpct)
|
||||
if time.time() > printat:
|
||||
callback({'progress': pgress})
|
||||
printat = time.time() + 0.5
|
||||
targ.write(buf)
|
||||
buf = archive.read(32768)
|
||||
with open(targpath + '/distinfo.yaml', 'w') as distinfo:
|
||||
distinfo.write(yaml.dump(identity, default_flow_style=False))
|
||||
if 'subname' in identity:
|
||||
del identity['subname']
|
||||
with open(distpath + '/distinfo.yaml', 'w') as distinfo:
|
||||
distinfo.write(yaml.dump(identity, default_flow_style=False))
|
||||
printit({'progress': 1.0})
|
||||
callback({'progress': 1.0})
|
||||
sys.stdout.write('\n')
|
||||
|
||||
def printit(info):
|
||||
|
||||
@@ -24,11 +24,8 @@ def update(nodes, element, configmanager, inputdata):
|
||||
em = emebs[node]['enclosure.manager']['value']
|
||||
eb = emebs[node]['enclosure.bay']['value']
|
||||
except KeyError:
|
||||
yield msg.ConfluentNodeError(
|
||||
node,
|
||||
'Reseat is only supported on servers in an enclosure, and '
|
||||
'with enclosure.manager and enclosure.bay defined')
|
||||
continue
|
||||
em = node
|
||||
eb = -1
|
||||
try:
|
||||
for rsp in core.handle_path(
|
||||
'/nodes/{0}/_enclosure/reseat_bay'.format(em),
|
||||
|
||||
@@ -603,7 +603,7 @@ class IpmiHandler(object):
|
||||
self.output.put(msg.CreatedResource(
|
||||
'nodes/{0}/media/uploads/{1}'.format(self.node, u.name)))
|
||||
|
||||
def get_diags(self, savefile, progress):
|
||||
def get_diags(self, savefile, progress, data=None):
|
||||
return self.ipmicmd.get_diagnostic_data(
|
||||
savefile, progress=progress, autosuffix=True)
|
||||
|
||||
|
||||
@@ -468,7 +468,7 @@ class IpmiHandler(object):
|
||||
self.output.put(msg.CreatedResource(
|
||||
'nodes/{0}/media/uploads/{1}'.format(self.node, u.name)))
|
||||
|
||||
def get_diags(self, savefile, progress):
|
||||
def get_diags(self, savefile, progress, data=None):
|
||||
return self.ipmicmd.get_diagnostic_data(
|
||||
savefile, progress=progress, autosuffix=True)
|
||||
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2021 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
try:
|
||||
import confluent.sshutil as sshutil
|
||||
except ImportError:
|
||||
pass
|
||||
import eventlet
|
||||
import eventlet.green.subprocess as subprocess
|
||||
import json
|
||||
import msgpack
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
|
||||
running_status = {}
|
||||
class PlayRunner(object):
|
||||
def __init__(self, playfiles, nodes):
|
||||
self.playfiles = playfiles
|
||||
self.nodes = nodes
|
||||
self.worker = None
|
||||
self.results = []
|
||||
self.complete = False
|
||||
|
||||
def _start_playbooks(self):
|
||||
self.worker = eventlet.spawn(self._really_run_playbooks)
|
||||
|
||||
def get_available_results(self):
|
||||
avail = self.results
|
||||
self.results = []
|
||||
return avail
|
||||
|
||||
def dump_text(self):
|
||||
retinfo = self.dump_dict()
|
||||
textout = ''
|
||||
for result in retinfo['results']:
|
||||
textout += 'TASK [{}] *******************************\n'.format(
|
||||
result['task_name'])
|
||||
for warning in result['warnings']:
|
||||
textout += '[WARNING]: ' + warning + '\n'
|
||||
if 'errorinfo' in result:
|
||||
textout += '{} => {}\n'.format(result['state'],
|
||||
result['errorinfo'])
|
||||
else:
|
||||
if result['changed']:
|
||||
textout += 'changed\n'
|
||||
else:
|
||||
textout += result['state'] + '\n'
|
||||
textout += '\n'
|
||||
return textout
|
||||
|
||||
def dump_json(self):
|
||||
return json.dumps(self.dump_dict())
|
||||
|
||||
def dump_dict(self):
|
||||
return {
|
||||
'complete': self.complete,
|
||||
'results': self.get_available_results()
|
||||
}
|
||||
|
||||
def _really_run_playbooks(self):
|
||||
with open(os.devnull, 'w+') as devnull:
|
||||
targnodes = ','.join(self.nodes)
|
||||
for playfilename in self.playfiles:
|
||||
worker = subprocess.Popen(
|
||||
[sys.executable, __file__, targnodes, playfilename],
|
||||
stdin=devnull, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
stdout, self.stderr = worker.communicate()
|
||||
current = memoryview(stdout)
|
||||
while len(current):
|
||||
sz = struct.unpack('=q', current[:8])[0]
|
||||
result = msgpack.unpackb(current[8:8+sz], raw=False)
|
||||
self.results.append(result)
|
||||
current = current[8+sz:]
|
||||
self.complete = True
|
||||
|
||||
|
||||
def run_playbooks(playfiles, nodes):
|
||||
sshutil.prep_ssh_key('/etc/confluent/ssh/automation')
|
||||
runner = PlayRunner(playfiles, nodes)
|
||||
for node in nodes:
|
||||
running_status[node] = runner
|
||||
runner._start_playbooks()
|
||||
|
||||
|
||||
def print_result(result, state, collector=None):
|
||||
output = {
|
||||
'task_name': result.task_name,
|
||||
'changed': result._result['changed'],
|
||||
}
|
||||
output['state'] = state
|
||||
output['warnings'] = result._result.get('warnings', [])
|
||||
try:
|
||||
del result._result['warnings']
|
||||
except KeyError:
|
||||
pass
|
||||
if collector:
|
||||
output['errorinfo'] = collector._dump_results(result._result)
|
||||
msg = msgpack.packb(output, use_bin_type=True)
|
||||
msglen = len(msg)
|
||||
sys.stdout.buffer.write(struct.pack('=q', msglen))
|
||||
sys.stdout.buffer.write(msg)
|
||||
|
||||
if __name__ == '__main__':
|
||||
from ansible.inventory.manager import InventoryManager
|
||||
from ansible.parsing.dataloader import DataLoader
|
||||
from ansible.executor.task_queue_manager import TaskQueueManager
|
||||
from ansible.vars.manager import VariableManager
|
||||
from ansible.playbook.play import Play
|
||||
from ansible import context
|
||||
from ansible.module_utils.common.collections import ImmutableDict
|
||||
from ansible.plugins.callback import CallbackBase
|
||||
import yaml
|
||||
|
||||
class ResultsCollector(CallbackBase):
|
||||
|
||||
def v2_runner_on_unreachable(self, result):
|
||||
print_result(result, 'UNREACHABLE', self)
|
||||
|
||||
def v2_runner_on_ok(self, result, *args, **kwargs):
|
||||
print_result(result, 'ok')
|
||||
|
||||
def v2_runner_on_failed(self, result, *args, **kwargs):
|
||||
print_result(result, 'FAILED', self)
|
||||
|
||||
context.CLIARGS = ImmutableDict(
|
||||
connection='smart', module_path=['/usr/share/ansible'], forks=10,
|
||||
become=None, become_method=None, become_user=None, check=False,
|
||||
diff=False, verbosity=0, remote_user='root')
|
||||
|
||||
|
||||
invlist = sys.argv[1] + ','
|
||||
loader = DataLoader()
|
||||
invman = InventoryManager(loader=loader, sources=invlist)
|
||||
varman = VariableManager(loader=loader, inventory=invman)
|
||||
|
||||
plays = yaml.safe_load(open(sys.argv[2]))
|
||||
if isinstance(plays, dict):
|
||||
plays = [plays]
|
||||
taskman = TaskQueueManager(inventory=invman, loader=loader, passwords={},
|
||||
variable_manager=varman, stdout_callback=ResultsCollector())
|
||||
for currplay in plays:
|
||||
currplay['hosts'] = sys.argv[1]
|
||||
play = Play().load(currplay, loader=loader)
|
||||
try:
|
||||
taskman.run(play)
|
||||
finally:
|
||||
taskman.cleanup()
|
||||
if loader:
|
||||
loader.cleanup_all_tmp_files()
|
||||
@@ -1,3 +1,5 @@
|
||||
import confluent.runansible as runansible
|
||||
import confluent.syncfiles as syncfiles
|
||||
import confluent.config.configmanager as configmanager
|
||||
import confluent.collective.manager as collective
|
||||
import confluent.netutil as netutil
|
||||
@@ -78,7 +80,8 @@ def handle_request(env, start_response):
|
||||
start_response('406 Not supported', [])
|
||||
yield 'Unsupported content type in ACCEPT: ' + retype
|
||||
return
|
||||
if env['REQUEST_METHOD'] not in ('HEAD', 'GET') and 'CONTENT_LENGTH' in env and int(env['CONTENT_LENGTH']) > 0:
|
||||
operation = env['REQUEST_METHOD']
|
||||
if operation not in ('HEAD', 'GET') and 'CONTENT_LENGTH' in env and int(env['CONTENT_LENGTH']) > 0:
|
||||
reqbody = env['wsgi.input'].read(int(env['CONTENT_LENGTH']))
|
||||
if env['PATH_INFO'] == '/self/bmcconfig':
|
||||
hmattr = cfg.get_node_attributes(nodename, 'hardwaremanagement.*')
|
||||
@@ -193,7 +196,7 @@ def handle_request(env, start_response):
|
||||
ncfg['dnsdomain'] = dnsdomain
|
||||
start_response('200 OK', (('Content-Type', retype),))
|
||||
yield dumper(ncfg)
|
||||
elif env['PATH_INFO'] == '/self/sshcert':
|
||||
elif env['PATH_INFO'] == '/self/sshcert' and reqbody:
|
||||
if not sshutil.ca_exists():
|
||||
start_response('500 Unconfigured', ())
|
||||
yield 'CA is not configured on this system (run ...)'
|
||||
@@ -211,14 +214,11 @@ def handle_request(env, start_response):
|
||||
else:
|
||||
start_response('200 OK', (('Content-Type', retype),))
|
||||
yield dumper(sorted(nodes))
|
||||
elif env['PATH_INFO'] == '/self/remoteconfigbmc':
|
||||
if reqbody:
|
||||
try:
|
||||
reqbody = yaml.safe_load(reqbody)
|
||||
except Exception:
|
||||
reqbody = None
|
||||
if not reqbody:
|
||||
start_response('400 bad request', ())
|
||||
elif env['PATH_INFO'] == '/self/remoteconfigbmc' and reqbody:
|
||||
try:
|
||||
reqbody = yaml.safe_load(reqbody)
|
||||
except Exception:
|
||||
reqbody = None
|
||||
cfgmod = reqbody.get('configmod', 'unspecified')
|
||||
if cfgmod == 'xcc':
|
||||
xcc.remote_nodecfg(nodename, cfg)
|
||||
@@ -229,7 +229,7 @@ def handle_request(env, start_response):
|
||||
yield 'Unsupported configmod "{}"'.format(cfgmod)
|
||||
start_response('200 Ok', ())
|
||||
yield 'complete'
|
||||
elif env['PATH_INFO'] == '/self/updatestatus':
|
||||
elif env['PATH_INFO'] == '/self/updatestatus' and reqbody:
|
||||
update = yaml.safe_load(reqbody)
|
||||
if update['status'] == 'staged':
|
||||
targattr = 'deployment.stagedprofile'
|
||||
@@ -258,33 +258,58 @@ def handle_request(env, start_response):
|
||||
else:
|
||||
start_response('500 Error', (('Content-Type', 'text/plain'),))
|
||||
yield 'No pending profile detected, unable to accept status update'
|
||||
elif env['PATH_INFO'] == '/self/saveapikey':
|
||||
elif env['PATH_INFO'] == '/self/saveapikey' and reqbody:
|
||||
cfg.set_node_attributes({
|
||||
nodename: {'deployment.sealedapikey': {'value': reqbody}}})
|
||||
start_response('200 OK', ())
|
||||
yield ''
|
||||
elif env['PATH_INFO'].startswith('/self/scriptlist/'):
|
||||
scriptcat = env['PATH_INFO'].replace('/self/scriptlist/', '')
|
||||
if '..' in scriptcat:
|
||||
start_response('400 Bad Requst', ())
|
||||
elif env['PATH_INFO'].startswith('/self/remoteconfig/') and 'POST' == operation:
|
||||
scriptcat = env['PATH_INFO'].replace('/self/remoteconfig/', '')
|
||||
slist, profile = get_scriptlist(
|
||||
scriptcat, cfg, nodename,
|
||||
'/var/lib/confluent/public/os/{0}/ansible/{1}.d/')
|
||||
playlist = []
|
||||
dirname = '/var/lib/confluent/public/os/{0}/ansible/{1}.d/'.format(
|
||||
profile, scriptcat)
|
||||
for filename in slist:
|
||||
if filename.endswith('.yaml') or filename.endswith('.yml'):
|
||||
playlist.append(os.path.join(dirname, filename))
|
||||
if playlist:
|
||||
runansible.run_playbooks(playlist, [nodename])
|
||||
start_response('202 Queued', ())
|
||||
yield ''
|
||||
else:
|
||||
start_response('200 OK', ())
|
||||
yield ''
|
||||
return
|
||||
deployinfo = cfg.get_node_attributes(
|
||||
nodename, ('deployment.*',))
|
||||
deployinfo = deployinfo.get(nodename, {})
|
||||
profile = deployinfo.get(
|
||||
'deployment.pendingprofile', {}).get('value', '')
|
||||
if not profile:
|
||||
profile = deployinfo.get(
|
||||
'deployment.stagedprofile', {}).get('value', '')
|
||||
if not profile:
|
||||
profile = deployinfo.get(
|
||||
'deployment.profile', {}).get('value', '')
|
||||
slist = None
|
||||
try:
|
||||
slist = os.listdir('/var/lib/confluent/public/os/{0}/scripts/{1}.d/'.format(profile, scriptcat))
|
||||
except OSError:
|
||||
pass
|
||||
elif env['PATH_INFO'].startswith('/self/remotesyncfiles'):
|
||||
if 'POST' == operation:
|
||||
result = syncfiles.start_syncfiles(
|
||||
nodename, cfg, json.loads(reqbody))
|
||||
start_response(result, ())
|
||||
yield ''
|
||||
return
|
||||
if 'GET' == operation:
|
||||
status, output = syncfiles.get_syncresult(nodename)
|
||||
start_response(status, ())
|
||||
yield output
|
||||
return
|
||||
elif env['PATH_INFO'].startswith('/self/remoteconfig/status'):
|
||||
rst = runansible.running_status.get(nodename, None)
|
||||
if not rst:
|
||||
start_response('204 Not Running', ())
|
||||
yield ''
|
||||
return
|
||||
start_response('200 OK', ())
|
||||
if rst.complete:
|
||||
del runansible.running_status[nodename]
|
||||
yield rst.dump_text()
|
||||
return
|
||||
elif env['PATH_INFO'].startswith('/self/scriptlist/'):
|
||||
scriptcat = env['PATH_INFO'].replace('/self/scriptlist/', '')
|
||||
slist, _ = get_scriptlist(
|
||||
scriptcat, cfg, nodename,
|
||||
'/var/lib/confluent/public/os/{0}/scripts/{1}.d/')
|
||||
if slist:
|
||||
start_response('200 OK', (('Content-Type', 'application/yaml'),))
|
||||
yield yaml.safe_dump(util.natural_sort(slist), default_flow_style=False)
|
||||
@@ -295,6 +320,27 @@ def handle_request(env, start_response):
|
||||
start_response('404 Not Found', ())
|
||||
yield 'Not found'
|
||||
|
||||
def get_scriptlist(scriptcat, cfg, nodename, pathtemplate):
|
||||
if '..' in scriptcat:
|
||||
return None, None
|
||||
deployinfo = cfg.get_node_attributes(
|
||||
nodename, ('deployment.*',))
|
||||
deployinfo = deployinfo.get(nodename, {})
|
||||
profile = deployinfo.get(
|
||||
'deployment.pendingprofile', {}).get('value', '')
|
||||
if not profile:
|
||||
profile = deployinfo.get(
|
||||
'deployment.stagedprofile', {}).get('value', '')
|
||||
if not profile:
|
||||
profile = deployinfo.get(
|
||||
'deployment.profile', {}).get('value', '')
|
||||
slist = []
|
||||
try:
|
||||
slist = os.listdir(pathtemplate.format(profile, scriptcat))
|
||||
except OSError:
|
||||
pass
|
||||
return slist, profile
|
||||
|
||||
|
||||
def get_cluster_list(nodename=None, cfg=None):
|
||||
if cfg is None:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import base64
|
||||
import confluent.config.configmanager as cfm
|
||||
import confluent.collective.manager as collective
|
||||
import eventlet.green.subprocess as subprocess
|
||||
import glob
|
||||
@@ -7,6 +9,9 @@ import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
agent_pid = None
|
||||
ready_keys = {}
|
||||
|
||||
def normalize_uid():
|
||||
curruid = os.geteuid()
|
||||
neededuid = os.stat('/etc/confluent').st_uid
|
||||
@@ -17,6 +22,34 @@ def normalize_uid():
|
||||
return curruid
|
||||
|
||||
|
||||
def assure_agent():
|
||||
global agent_pid
|
||||
if agent_pid is None:
|
||||
sai = subprocess.check_output(['ssh-agent'])
|
||||
for line in sai.split(b'\n'):
|
||||
if b';' not in line:
|
||||
continue
|
||||
line, _ = line.split(b';', 1)
|
||||
if b'=' not in line:
|
||||
continue
|
||||
k, v = line.split(b'=', 1)
|
||||
if not isinstance(k, str):
|
||||
k = k.decode('utf8')
|
||||
v = v.decode('utf8')
|
||||
if k == 'SSH_AGENT_PID':
|
||||
agent_pid = v
|
||||
os.environ[k] = v
|
||||
|
||||
def get_passphrase():
|
||||
# convert the master key to base64
|
||||
# for use in ssh passphrase context
|
||||
if cfm._masterkey is None:
|
||||
cfm.init_masterkey()
|
||||
phrase = base64.b64encode(cfm._masterkey)
|
||||
if not isinstance(phrase, str):
|
||||
phrase = phrase.decode('utf8')
|
||||
return phrase
|
||||
|
||||
def initialize_ca():
|
||||
ouid = normalize_uid()
|
||||
try:
|
||||
@@ -27,10 +60,11 @@ def initialize_ca():
|
||||
finally:
|
||||
os.seteuid(ouid)
|
||||
myname = collective.get_myname()
|
||||
caname = '{0} SSH CA'.format(myname)
|
||||
comment = '{0} SSH CA'.format(myname)
|
||||
subprocess.check_call(
|
||||
['ssh-keygen', '-C', caname, '-t', 'ed25519', '-f',
|
||||
'/etc/confluent/ssh/ca', '-N', ''], preexec_fn=normalize_uid)
|
||||
['ssh-keygen', '-C', comment, '-t', 'ed25519', '-f',
|
||||
'/etc/confluent/ssh/ca', '-N', get_passphrase()],
|
||||
preexec_fn=normalize_uid)
|
||||
try:
|
||||
os.makedirs('/var/lib/confluent/public/site/ssh/', mode=0o755)
|
||||
except OSError as e:
|
||||
@@ -41,9 +75,31 @@ def initialize_ca():
|
||||
# newent = '@cert-authority * ' + capub.read()
|
||||
|
||||
|
||||
def prep_ssh_key(keyname):
|
||||
assure_agent()
|
||||
if keyname in ready_keys:
|
||||
return
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
try:
|
||||
askpass = os.path.join(tmpdir, 'askpass.sh')
|
||||
with open(askpass, 'w') as ap:
|
||||
ap.write('#!/bin/sh\necho $CONFLUENT_SSH_PASSPHRASE\n')
|
||||
os.chmod(askpass, 0o700)
|
||||
os.environ['CONFLUENT_SSH_PASSPHRASE'] = get_passphrase()
|
||||
os.environ['DISPLAY'] = 'NONE'
|
||||
os.environ['SSH_ASKPASS'] = askpass
|
||||
with open(os.devnull, 'wb') as devnull:
|
||||
subprocess.check_call(['ssh-add', keyname], stdin=devnull)
|
||||
del os.environ['CONFLUENT_SSH_PASSPHRASE']
|
||||
ready_keys[keyname] = 1
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
def sign_host_key(pubkey, nodename, principals=()):
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
try:
|
||||
prep_ssh_key('/etc/confluent/ssh/ca')
|
||||
ready_keys['ca.pub'] = 1
|
||||
pkeyname = os.path.join(tmpdir, 'hostkey.pub')
|
||||
with open(pkeyname, 'wb') as pubfile:
|
||||
pubfile.write(pubkey)
|
||||
@@ -51,7 +107,7 @@ def sign_host_key(pubkey, nodename, principals=()):
|
||||
principals.add(nodename)
|
||||
principals = ','.join(sorted(principals))
|
||||
subprocess.check_call(
|
||||
['ssh-keygen', '-s', '/etc/confluent/ssh/ca', '-I', nodename,
|
||||
['ssh-keygen', '-Us', '/etc/confluent/ssh/ca.pub', '-I', nodename,
|
||||
'-n', principals, '-h', pkeyname])
|
||||
certname = pkeyname.replace('.pub', '-cert.pub')
|
||||
with open(certname) as cert:
|
||||
@@ -59,15 +115,22 @@ def sign_host_key(pubkey, nodename, principals=()):
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
def initialize_root_key(generate):
|
||||
def initialize_root_key(generate, automation=False):
|
||||
authorized = []
|
||||
myname = collective.get_myname()
|
||||
for currkey in glob.glob('/root/.ssh/*.pub'):
|
||||
authorized.append(currkey)
|
||||
if generate and not authorized:
|
||||
if generate and not authorized and not automation:
|
||||
subprocess.check_call(['ssh-keygen', '-t', 'ed25519', '-f', '/root/.ssh/id_ed25519', '-N', ''])
|
||||
for currkey in glob.glob('/root/.ssh/*.pub'):
|
||||
authorized.append(currkey)
|
||||
if automation and generate:
|
||||
subprocess.check_call(
|
||||
['ssh-keygen', '-t', 'ed25519',
|
||||
'-f','/etc/confluent/ssh/automation', '-N', get_passphrase(),
|
||||
'-C', 'Confluent Automation by {}'.format(myname)],
|
||||
preexec_fn=normalize_uid)
|
||||
authorized = ['/etc/confluent/ssh/automation.pub']
|
||||
try:
|
||||
os.makedirs('/var/lib/confluent/public/site/ssh', mode=0o755)
|
||||
neededuid = os.stat('/etc/confluent').st_uid
|
||||
@@ -79,15 +142,19 @@ def initialize_root_key(generate):
|
||||
if e.errno != 17:
|
||||
raise
|
||||
neededuid = os.stat('/etc/confluent').st_uid
|
||||
if automation:
|
||||
suffix = 'automationpubkey'
|
||||
else:
|
||||
suffix = 'rootpubkey'
|
||||
for auth in authorized:
|
||||
shutil.copy(
|
||||
auth,
|
||||
'/var/lib/confluent/public/site/ssh/{0}.rootpubkey'.format(
|
||||
myname))
|
||||
os.chmod('/var/lib/confluent/public/site/ssh/{0}.rootpubkey'.format(
|
||||
myname), 0o644)
|
||||
os.chown('/var/lib/confluent/public/site/ssh/{0}.rootpubkey'.format(
|
||||
myname), neededuid, -1)
|
||||
'/var/lib/confluent/public/site/ssh/{0}.{1}'.format(
|
||||
myname, suffix))
|
||||
os.chmod('/var/lib/confluent/public/site/ssh/{0}.{1}'.format(
|
||||
myname, suffix), 0o644)
|
||||
os.chown('/var/lib/confluent/public/site/ssh/{0}.{1}'.format(
|
||||
myname, suffix), neededuid, -1)
|
||||
|
||||
|
||||
def ca_exists():
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2021 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import confluent.sshutil as sshutil
|
||||
import eventlet.green.subprocess as subprocess
|
||||
import eventlet
|
||||
|
||||
def mkdirp(path):
|
||||
try:
|
||||
os.makedirs(path)
|
||||
except OSError as e:
|
||||
if e.errno != 17:
|
||||
raise
|
||||
|
||||
class SyncList(object):
|
||||
def __init__(self, filename):
|
||||
slist = None
|
||||
self.replacemap = {}
|
||||
self.appendmap = {}
|
||||
self.mergemap = {}
|
||||
with open(filename, 'r') as slfile:
|
||||
slist = slfile.read()
|
||||
entries = slist.split('\n')
|
||||
currmap = self.replacemap
|
||||
for ent in entries:
|
||||
try:
|
||||
cmtidx = ent.index('#')
|
||||
ent = ent[:cmtidx]
|
||||
except ValueError:
|
||||
pass
|
||||
for special in '!@$%^&*()|{}':
|
||||
if special in ent:
|
||||
raise Exception(
|
||||
'Special character "{}" reserved for future use'.format(special))
|
||||
ent = ent.strip()
|
||||
if not ent:
|
||||
continue
|
||||
if ent[-1] == ':':
|
||||
if ent == 'MERGE:':
|
||||
currmap = self.mergemap
|
||||
else:
|
||||
raise Exception(
|
||||
'Section "{}" is not currently supported in syncfiles'.format(ent[:-1]))
|
||||
continue
|
||||
if '->' in ent:
|
||||
k, v = ent.split('->')
|
||||
k = k.strip()
|
||||
v = v.strip()
|
||||
else:
|
||||
k = ent
|
||||
v = ent
|
||||
currmap[k] = v
|
||||
|
||||
|
||||
def sync_list_to_node(synclist, node, suffixes):
|
||||
targdir = tempfile.mkdtemp('.syncto{}'.format(node))
|
||||
output = ''
|
||||
try:
|
||||
sl = SyncList(synclist)
|
||||
for ent in sl.replacemap:
|
||||
stage_ent(sl.replacemap, ent, targdir)
|
||||
if 'append' in suffixes:
|
||||
while suffixes['append'] and suffixes['append'][0] == '/':
|
||||
suffixes['append'] = suffixes['append'][1:]
|
||||
for ent in sl.appendmap:
|
||||
stage_ent(sl.appendmap, ent,
|
||||
os.path.join(targdir, suffixes['append']))
|
||||
if 'merge' in suffixes:
|
||||
while suffixes['merge'] and suffixes['merge'][0] == '/':
|
||||
suffixes['merge'] = suffixes['merge'][1:]
|
||||
for ent in sl.mergemap:
|
||||
stage_ent(sl.mergemap, ent,
|
||||
os.path.join(targdir, suffixes['merge']))
|
||||
sshutil.prep_ssh_key('/etc/confluent/ssh/automation')
|
||||
output = subprocess.check_output(
|
||||
['rsync', '-rvL', targdir + '/', 'root@{}:/'.format(node)])
|
||||
finally:
|
||||
shutil.rmtree(targdir)
|
||||
return output
|
||||
|
||||
def stage_ent(currmap, ent, targdir):
|
||||
dst = currmap[ent]
|
||||
everyfent = []
|
||||
allfents = ent.split()
|
||||
for tmpent in allfents:
|
||||
fents = glob.glob(tmpent)
|
||||
if fents:
|
||||
everyfent.extend(fents)
|
||||
else:
|
||||
everyfent.extend(os.path.dirname(tmpent))
|
||||
if not everyfent:
|
||||
raise Exception('No matching files for "{}"'.format(ent))
|
||||
while dst and dst[0] == '/':
|
||||
dst = dst[1:]
|
||||
fulltarg = os.path.join(targdir, dst)
|
||||
if dst[-1] == '/' or len(everyfent) > 1 or os.path.isdir(everyfent[0]):
|
||||
# target *must* be a directory
|
||||
fulltargdir = fulltarg
|
||||
else:
|
||||
fulltargdir = os.path.join(targdir, os.path.dirname(dst))
|
||||
mkdirp(fulltargdir)
|
||||
for targ in everyfent:
|
||||
if fulltargdir == fulltarg:
|
||||
os.symlink(
|
||||
targ, os.path.join(
|
||||
fulltargdir, os.path.basename(targ)))
|
||||
else:
|
||||
os.symlink(targ, fulltarg)
|
||||
|
||||
syncrunners = {}
|
||||
|
||||
|
||||
def start_syncfiles(nodename, cfg, suffixes):
|
||||
deployinfo = cfg.get_node_attributes(
|
||||
nodename, ('deployment.*',))
|
||||
deployinfo = deployinfo.get(nodename, {})
|
||||
profile = deployinfo.get(
|
||||
'deployment.pendingprofile', {}).get('value', '')
|
||||
if not profile:
|
||||
profile = deployinfo.get(
|
||||
'deployment.stagedprofile', {}).get('value', '')
|
||||
if not profile:
|
||||
profile = deployinfo.get(
|
||||
'deployment.profile', {}).get('value', '')
|
||||
if not profile:
|
||||
raise Exception('Cannot perform syncfiles without profile assigned')
|
||||
synclist = '/var/lib/confluent/public/os/{}/syncfiles'.format(profile)
|
||||
if not os.path.exists(synclist):
|
||||
return '200 OK' # not running
|
||||
syncrunners[nodename] = eventlet.spawn(
|
||||
sync_list_to_node, synclist, nodename, suffixes)
|
||||
return '202 Queued' # backgrounded
|
||||
|
||||
def get_syncresult(nodename):
|
||||
if nodename not in syncrunners:
|
||||
return ('204 Not Running', '')
|
||||
if not syncrunners[nodename].dead:
|
||||
return ('200 OK', '')
|
||||
result = syncrunners[nodename].wait()
|
||||
del syncrunners[nodename]
|
||||
return ('200 OK', result)
|
||||
@@ -0,0 +1,5 @@
|
||||
vtbufferd: vtbufferd.c tmt.c
|
||||
gcc -O3 -o vtbufferd vtbufferd.c tmt.c
|
||||
|
||||
clean:
|
||||
rm vtbufferd
|
||||
@@ -0,0 +1,22 @@
|
||||
VERSION=`git describe|cut -d- -f 1`
|
||||
NUMCOMMITS=`git describe|cut -d- -f 2`
|
||||
if [ "$NUMCOMMITS" != "$VERSION" ]; then
|
||||
VERSION=$VERSION.dev$NUMCOMMITS.g`git describe|cut -d- -f 3`
|
||||
fi
|
||||
mkdir -p dist/confluent_vtbufferd-$VERSION
|
||||
cp *.c *.h Makefile dist/confluent_vtbufferd-$VERSION
|
||||
cd dist
|
||||
tar czvf confluent_vtbufferd-$VERSION.tar.gz confluent_vtbufferd-$VERSION
|
||||
cd -
|
||||
cp dist/confluent_vtbufferd-$VERSION.tar.gz ~/rpmbuild/SOURCES
|
||||
sed -e 's/#VERSION#/'$VERSION/ confluent_vtbufferd.spec.tmpl > ~/rpmbuild/SPECS/confluent_vtbufferd.spec
|
||||
rpmbuild -ba ~/rpmbuild/SPECS/confluent_vtbufferd.spec 2> /dev/null |grep ^Wrote:
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "[ERROR] rpmbuild returned non-zero, run: rpmbuild -ba ~/rpmbuild/SPECS/$PKGNAME.spec"
|
||||
exit 1
|
||||
else
|
||||
# Clean up the generated files in this directory
|
||||
rm -rf dist
|
||||
fi
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
%define name confluent_vtbufferd
|
||||
%define version #VERSION#
|
||||
%define release 1
|
||||
%define debug_package %{nil}
|
||||
%define _build_id_links none
|
||||
|
||||
Summary: Console buffer manager for confluent
|
||||
Name: %{name}
|
||||
Version: %{version}
|
||||
Release: %{release}
|
||||
Source0: %{name}-%{version}.tar.gz
|
||||
License: Apache2
|
||||
Group: Development/Libraries
|
||||
Vendor: Lenovo HPC Organization <hpchelp@lenovo.com>
|
||||
Url: https://github.com/lenovo/confluent/
|
||||
|
||||
%description
|
||||
Service for managing in-memory VT emulation for confluent.
|
||||
|
||||
Contains third party open source code:
|
||||
|
||||
Copyright (c) 2017 Rob King
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the copyright holder nor the
|
||||
names of contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
%prep
|
||||
%setup -n %{name}-%{version} -n %{name}-%{version}
|
||||
|
||||
|
||||
%build
|
||||
make
|
||||
|
||||
%install
|
||||
mkdir -p $RPM_BUILD_ROOT/opt/confluent/bin
|
||||
cp vtbufferd $RPM_BUILD_ROOT/opt/confluent/bin/
|
||||
@@ -0,0 +1,521 @@
|
||||
/* Copyright (c) 2017 Rob King
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the copyright holder nor the
|
||||
* names of contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS,
|
||||
* COPYRIGHT HOLDERS, OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "tmt.h"
|
||||
|
||||
#define BUF_MAX 100
|
||||
#define PAR_MAX 8
|
||||
#define TAB 8
|
||||
#define MAX(x, y) (((size_t)(x) > (size_t)(y)) ? (size_t)(x) : (size_t)(y))
|
||||
#define MIN(x, y) (((size_t)(x) < (size_t)(y)) ? (size_t)(x) : (size_t)(y))
|
||||
#define CLINE(vt) (vt)->screen.lines[MIN((vt)->curs.r, (vt)->screen.nline - 1)]
|
||||
|
||||
#define P0(x) (vt->pars[x])
|
||||
#define P1(x) (vt->pars[x]? vt->pars[x] : 1)
|
||||
#define CB(vt, m, a) ((vt)->cb? (vt)->cb(m, vt, a, (vt)->p) : (void)0)
|
||||
#define INESC ((vt)->state)
|
||||
|
||||
#define COMMON_VARS \
|
||||
TMTSCREEN *s = &vt->screen; \
|
||||
TMTPOINT *c = &vt->curs; \
|
||||
TMTLINE *l = CLINE(vt); \
|
||||
TMTCHAR *t = vt->tabs->chars
|
||||
|
||||
#define HANDLER(name) static void name (TMT *vt) { COMMON_VARS;
|
||||
|
||||
struct TMT{
|
||||
TMTPOINT curs, oldcurs;
|
||||
TMTATTRS attrs, oldattrs;
|
||||
|
||||
bool dirty, acs, decdraw, ignored;
|
||||
TMTSCREEN screen;
|
||||
TMTLINE *tabs;
|
||||
|
||||
TMTCALLBACK cb;
|
||||
void *p;
|
||||
const wchar_t *acschars;
|
||||
const wchar_t *decchars;
|
||||
|
||||
mbstate_t ms;
|
||||
size_t nmb;
|
||||
char mb[BUF_MAX + 1];
|
||||
|
||||
size_t pars[PAR_MAX];
|
||||
size_t npar;
|
||||
size_t arg;
|
||||
enum {S_NUL, S_ESC, S_ARG, S_SCS} state;
|
||||
};
|
||||
|
||||
static TMTATTRS defattrs = {.fg = TMT_COLOR_DEFAULT, .bg = TMT_COLOR_DEFAULT};
|
||||
static void writecharatcurs(TMT *vt, wchar_t w);
|
||||
|
||||
static wchar_t
|
||||
decchar(const TMT *vt, unsigned char c)
|
||||
{
|
||||
if (c > 94 && c < 127)
|
||||
return vt->decchars[c - 95];
|
||||
return (wchar_t)c;
|
||||
}
|
||||
|
||||
static wchar_t
|
||||
tacs(const TMT *vt, unsigned char c)
|
||||
{
|
||||
/* The terminfo alternate character set for ANSI. */
|
||||
static unsigned char map[] = {0020U, 0021U, 0030U, 0031U, 0333U, 0004U,
|
||||
0261U, 0370U, 0361U, 0260U, 0331U, 0277U,
|
||||
0332U, 0300U, 0305U, 0176U, 0304U, 0304U,
|
||||
0304U, 0137U, 0303U, 0264U, 0301U, 0302U,
|
||||
0263U, 0363U, 0362U, 0343U, 0330U, 0234U,
|
||||
0376U};
|
||||
for (size_t i = 0; i < sizeof(map); i++) if (map[i] == c)
|
||||
return vt->acschars[i];
|
||||
return (wchar_t)c;
|
||||
}
|
||||
|
||||
static void
|
||||
dirtylines(TMT *vt, size_t s, size_t e)
|
||||
{
|
||||
vt->dirty = true;
|
||||
for (size_t i = s; i < e; i++)
|
||||
vt->screen.lines[i]->dirty = true;
|
||||
}
|
||||
|
||||
static void
|
||||
clearline(TMT *vt, TMTLINE *l, size_t s, size_t e)
|
||||
{
|
||||
vt->dirty = l->dirty = true;
|
||||
for (size_t i = s; i < e && i < vt->screen.ncol; i++){
|
||||
l->chars[i].a = defattrs;
|
||||
l->chars[i].c = L' ';
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clearlines(TMT *vt, size_t r, size_t n)
|
||||
{
|
||||
for (size_t i = r; i < r + n && i < vt->screen.nline; i++)
|
||||
clearline(vt, vt->screen.lines[i], 0, vt->screen.ncol);
|
||||
}
|
||||
|
||||
static void
|
||||
scrup(TMT *vt, size_t r, size_t n)
|
||||
{
|
||||
n = MIN(n, vt->screen.nline - 1 - r);
|
||||
|
||||
if (n){
|
||||
TMTLINE *buf[n];
|
||||
|
||||
memcpy(buf, vt->screen.lines + r, n * sizeof(TMTLINE *));
|
||||
memmove(vt->screen.lines + r, vt->screen.lines + r + n,
|
||||
(vt->screen.nline - n - r) * sizeof(TMTLINE *));
|
||||
memcpy(vt->screen.lines + (vt->screen.nline - n),
|
||||
buf, n * sizeof(TMTLINE *));
|
||||
|
||||
clearlines(vt, vt->screen.nline - n, n);
|
||||
dirtylines(vt, r, vt->screen.nline);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
scrdn(TMT *vt, size_t r, size_t n)
|
||||
{
|
||||
n = MIN(n, vt->screen.nline - 1 - r);
|
||||
|
||||
if (n){
|
||||
TMTLINE *buf[n];
|
||||
|
||||
memcpy(buf, vt->screen.lines + (vt->screen.nline - n),
|
||||
n * sizeof(TMTLINE *));
|
||||
memmove(vt->screen.lines + r + n, vt->screen.lines + r,
|
||||
(vt->screen.nline - n - r) * sizeof(TMTLINE *));
|
||||
memcpy(vt->screen.lines + r, buf, n * sizeof(TMTLINE *));
|
||||
|
||||
clearlines(vt, r, n);
|
||||
dirtylines(vt, r, vt->screen.nline);
|
||||
}
|
||||
}
|
||||
|
||||
HANDLER(ed)
|
||||
size_t b = 0;
|
||||
size_t e = s->nline;
|
||||
|
||||
switch (P0(0)){
|
||||
case 0: b = c->r + 1; clearline(vt, l, c->c, vt->screen.ncol); break;
|
||||
case 1: e = c->r - 1; clearline(vt, l, 0, c->c); break;
|
||||
case 2: /* use defaults */ break;
|
||||
default: /* do nothing */ return;
|
||||
}
|
||||
|
||||
clearlines(vt, b, e - b);
|
||||
}
|
||||
|
||||
HANDLER(ich)
|
||||
size_t n = P1(0); /* XXX use MAX */
|
||||
if (n > s->ncol - c->c - 1) n = s->ncol - c->c - 1;
|
||||
|
||||
memmove(l->chars + c->c + n, l->chars + c->c,
|
||||
MIN(s->ncol - 1 - c->c,
|
||||
(s->ncol - c->c - n - 1)) * sizeof(TMTCHAR));
|
||||
clearline(vt, l, c->c, n);
|
||||
}
|
||||
|
||||
HANDLER(dch)
|
||||
size_t n = P1(0); /* XXX use MAX */
|
||||
if (n > s->ncol - c->c) n = s->ncol - c->c;
|
||||
else if (n == 0) return;
|
||||
|
||||
memmove(l->chars + c->c, l->chars + c->c + n,
|
||||
(s->ncol - c->c - n) * sizeof(TMTCHAR));
|
||||
|
||||
clearline(vt, l, s->ncol - n, s->ncol);
|
||||
/* VT102 manual says the attribute for the newly empty characters
|
||||
* should be the same as the last character moved left, which isn't
|
||||
* what clearline() currently does.
|
||||
*/
|
||||
}
|
||||
|
||||
HANDLER(el)
|
||||
switch (P0(0)){
|
||||
case 0: clearline(vt, l, c->c, vt->screen.ncol); break;
|
||||
case 1: clearline(vt, l, 0, MIN(c->c + 1, s->ncol - 1)); break;
|
||||
case 2: clearline(vt, l, 0, vt->screen.ncol); break;
|
||||
}
|
||||
}
|
||||
|
||||
HANDLER(sgr)
|
||||
#define FGBG(c) *(P0(i) < 40? &vt->attrs.fg : &vt->attrs.bg) = c
|
||||
for (size_t i = 0; i < vt->npar; i++) switch (P0(i)){
|
||||
case 0: vt->attrs = defattrs; break;
|
||||
case 1: case 22: vt->attrs.bold = P0(0) < 20; break;
|
||||
case 2: case 23: vt->attrs.dim = P0(0) < 20; break;
|
||||
case 4: case 24: vt->attrs.underline = P0(0) < 20; break;
|
||||
case 5: case 25: vt->attrs.blink = P0(0) < 20; break;
|
||||
case 7: case 27: vt->attrs.reverse = P0(0) < 20; break;
|
||||
case 8: case 28: vt->attrs.invisible = P0(0) < 20; break;
|
||||
case 10: case 11: vt->acs = P0(0) > 10; break;
|
||||
case 30: case 40: FGBG(TMT_COLOR_BLACK); break;
|
||||
case 31: case 41: FGBG(TMT_COLOR_RED); break;
|
||||
case 32: case 42: FGBG(TMT_COLOR_GREEN); break;
|
||||
case 33: case 43: FGBG(TMT_COLOR_YELLOW); break;
|
||||
case 34: case 44: FGBG(TMT_COLOR_BLUE); break;
|
||||
case 35: case 45: FGBG(TMT_COLOR_MAGENTA); break;
|
||||
case 36: case 46: FGBG(TMT_COLOR_CYAN); break;
|
||||
case 37: case 47: FGBG(TMT_COLOR_WHITE); break;
|
||||
case 39: case 49: FGBG(TMT_COLOR_DEFAULT); break;
|
||||
}
|
||||
}
|
||||
|
||||
HANDLER(rep)
|
||||
if (!c->c) return;
|
||||
wchar_t r = l->chars[c->c - 1].c;
|
||||
for (size_t i = 0; i < P1(0); i++)
|
||||
writecharatcurs(vt, r);
|
||||
}
|
||||
|
||||
HANDLER(dsr)
|
||||
char r[BUF_MAX + 1] = {0};
|
||||
snprintf(r, BUF_MAX, "\033[%zd;%zdR", c->r + 1, c->c + 1);
|
||||
CB(vt, TMT_MSG_ANSWER, (const char *)r);
|
||||
}
|
||||
|
||||
HANDLER(resetparser)
|
||||
memset(vt->pars, 0, sizeof(vt->pars));
|
||||
vt->state = vt->npar = vt->arg = vt->ignored = (bool)0;
|
||||
}
|
||||
|
||||
HANDLER(consumearg)
|
||||
if (vt->npar < PAR_MAX)
|
||||
vt->pars[vt->npar++] = vt->arg;
|
||||
vt->arg = 0;
|
||||
}
|
||||
|
||||
HANDLER(fixcursor)
|
||||
c->r = MIN(c->r, s->nline - 1);
|
||||
c->c = MIN(c->c, s->ncol - 1);
|
||||
}
|
||||
|
||||
static bool
|
||||
handlechar(TMT *vt, char i)
|
||||
{
|
||||
COMMON_VARS;
|
||||
|
||||
char cs[] = {i, 0};
|
||||
#define ON(S, C, A) if (vt->state == (S) && strchr(C, i)){ A; return true;}
|
||||
#define DO(S, C, A) ON(S, C, consumearg(vt); if (!vt->ignored) {A;} \
|
||||
fixcursor(vt); resetparser(vt););
|
||||
|
||||
DO(S_NUL, "\x07", CB(vt, TMT_MSG_BELL, NULL))
|
||||
DO(S_NUL, "\x08", if (c->c) c->c--)
|
||||
DO(S_NUL, "\x09", while (++c->c < s->ncol - 1 && t[c->c].c != L'*'))
|
||||
DO(S_NUL, "\x0a", c->r < s->nline - 1? (void)c->r++ : scrup(vt, 0, 1))
|
||||
DO(S_NUL, "\x0d", c->c = 0)
|
||||
ON(S_NUL, "\x1b", vt->state = S_ESC)
|
||||
ON(S_ESC, "\x1b", vt->state = S_ESC)
|
||||
DO(S_ESC, "H", t[c->c].c = L'*')
|
||||
DO(S_ESC, "7", vt->oldcurs = vt->curs; vt->oldattrs = vt->attrs)
|
||||
DO(S_ESC, "8", vt->curs = vt->oldcurs; vt->attrs = vt->oldattrs)
|
||||
ON(S_ESC, "+*)", vt->ignored = true; vt->state = S_ARG)
|
||||
ON(S_ESC, "(", vt->state = S_SCS)
|
||||
DO(S_SCS, "0", vt->decdraw = true)
|
||||
DO(S_SCS, "B", vt->decdraw = false)
|
||||
DO(S_ESC, "c", tmt_reset(vt))
|
||||
DO(S_ESC, "M", if (c->r) c->r--)
|
||||
ON(S_ESC, "[", vt->state = S_ARG)
|
||||
ON(S_ARG, "\x1b", vt->state = S_ESC)
|
||||
ON(S_ARG, ";", consumearg(vt))
|
||||
ON(S_ARG, "?", (void)0)
|
||||
ON(S_ARG, "0123456789", vt->arg = vt->arg * 10 + atoi(cs))
|
||||
DO(S_ARG, "A", c->r = MAX(c->r - P1(0), 0))
|
||||
DO(S_ARG, "B", c->r = MIN(c->r + P1(0), s->nline - 1))
|
||||
DO(S_ARG, "C", c->c = MIN(c->c + P1(0), s->ncol - 1))
|
||||
DO(S_ARG, "D", c->c = MIN(c->c - P1(0), c->c))
|
||||
DO(S_ARG, "E", c->c = 0; c->r = MIN(c->r + P1(0), s->nline - 1))
|
||||
DO(S_ARG, "F", c->c = 0; c->r = MAX(c->r - P1(0), 0))
|
||||
DO(S_ARG, "G", c->c = MIN(P1(0) - 1, s->ncol - 1))
|
||||
DO(S_ARG, "d", c->r = MIN(P1(0) - 1, s->nline - 1))
|
||||
DO(S_ARG, "Hf", c->r = P1(0) - 1; c->c = P1(1) - 1)
|
||||
DO(S_ARG, "I", while (++c->c < s->ncol - 1 && t[c->c].c != L'*'))
|
||||
DO(S_ARG, "J", ed(vt))
|
||||
DO(S_ARG, "K", el(vt))
|
||||
DO(S_ARG, "L", scrdn(vt, c->r, P1(0)))
|
||||
DO(S_ARG, "M", scrup(vt, c->r, P1(0)))
|
||||
DO(S_ARG, "P", dch(vt))
|
||||
DO(S_ARG, "S", scrup(vt, 0, P1(0)))
|
||||
DO(S_ARG, "T", scrdn(vt, 0, P1(0)))
|
||||
DO(S_ARG, "X", clearline(vt, l, c->c, P1(0) + c->c))
|
||||
DO(S_ARG, "Z", while (c->c && t[--c->c].c != L'*'))
|
||||
DO(S_ARG, "b", rep(vt));
|
||||
DO(S_ARG, "c", CB(vt, TMT_MSG_ANSWER, "\033[?6c"))
|
||||
DO(S_ARG, "g", if (P0(0) == 3) clearline(vt, vt->tabs, 0, s->ncol))
|
||||
DO(S_ARG, "m", sgr(vt))
|
||||
DO(S_ARG, "n", if (P0(0) == 6) dsr(vt))
|
||||
DO(S_ARG, "h", if (P0(0) == 25) CB(vt, TMT_MSG_CURSOR, "t"))
|
||||
DO(S_ARG, "i", (void)0)
|
||||
DO(S_ARG, "l", if (P0(0) == 25) CB(vt, TMT_MSG_CURSOR, "f"))
|
||||
DO(S_ARG, "s", vt->oldcurs = vt->curs; vt->oldattrs = vt->attrs)
|
||||
DO(S_ARG, "u", vt->curs = vt->oldcurs; vt->attrs = vt->oldattrs)
|
||||
DO(S_ARG, "@", ich(vt))
|
||||
|
||||
return resetparser(vt), false;
|
||||
}
|
||||
|
||||
static void
|
||||
notify(TMT *vt, bool update, bool moved)
|
||||
{
|
||||
if (update) CB(vt, TMT_MSG_UPDATE, &vt->screen);
|
||||
if (moved) CB(vt, TMT_MSG_MOVED, &vt->curs);
|
||||
}
|
||||
|
||||
static TMTLINE *
|
||||
allocline(TMT *vt, TMTLINE *o, size_t n, size_t pc)
|
||||
{
|
||||
TMTLINE *l = realloc(o, sizeof(TMTLINE) + n * sizeof(TMTCHAR));
|
||||
if (!l) return NULL;
|
||||
|
||||
clearline(vt, l, pc, n);
|
||||
return l;
|
||||
}
|
||||
|
||||
static void
|
||||
freelines(TMT *vt, size_t s, size_t n, bool screen)
|
||||
{
|
||||
for (size_t i = s; vt->screen.lines && i < s + n; i++){
|
||||
free(vt->screen.lines[i]);
|
||||
vt->screen.lines[i] = NULL;
|
||||
}
|
||||
if (screen) free(vt->screen.lines);
|
||||
}
|
||||
|
||||
TMT *
|
||||
tmt_open(size_t nline, size_t ncol, TMTCALLBACK cb, void *p,
|
||||
const wchar_t *acs)
|
||||
{
|
||||
TMT *vt = calloc(1, sizeof(TMT));
|
||||
if (!nline || !ncol || !vt) return free(vt), NULL;
|
||||
|
||||
/* ASCII-safe defaults for box-drawing characters. */
|
||||
vt->acschars = acs? acs : L"→←↑↓■◆▒°±▒┘┐┌└┼⎺───⎽├┤┴┬│≤≥π≠£•"; //L"><^v#+:o##+++++~---_++++|<>*!fo";
|
||||
vt->decchars = L" ◆▒\t\f\r\n°±\n\v┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£•";
|
||||
vt->cb = cb;
|
||||
vt->p = p;
|
||||
|
||||
if (!tmt_resize(vt, nline, ncol)) return tmt_close(vt), NULL;
|
||||
return vt;
|
||||
}
|
||||
|
||||
void
|
||||
tmt_close(TMT *vt)
|
||||
{
|
||||
free(vt->tabs);
|
||||
freelines(vt, 0, vt->screen.nline, true);
|
||||
free(vt);
|
||||
}
|
||||
|
||||
bool
|
||||
tmt_resize(TMT *vt, size_t nline, size_t ncol)
|
||||
{
|
||||
if (nline < 2 || ncol < 2) return false;
|
||||
if (nline < vt->screen.nline)
|
||||
freelines(vt, nline, vt->screen.nline - nline, false);
|
||||
|
||||
TMTLINE **l = realloc(vt->screen.lines, nline * sizeof(TMTLINE *));
|
||||
if (!l) return false;
|
||||
|
||||
size_t pc = vt->screen.ncol;
|
||||
vt->screen.lines = l;
|
||||
vt->screen.ncol = ncol;
|
||||
for (size_t i = 0; i < nline; i++){
|
||||
TMTLINE *nl = NULL;
|
||||
if (i >= vt->screen.nline)
|
||||
nl = vt->screen.lines[i] = allocline(vt, NULL, ncol, 0);
|
||||
else
|
||||
nl = allocline(vt, vt->screen.lines[i], ncol, pc);
|
||||
|
||||
if (!nl) return false;
|
||||
vt->screen.lines[i] = nl;
|
||||
}
|
||||
vt->screen.nline = nline;
|
||||
|
||||
vt->tabs = allocline(vt, vt->tabs, ncol, 0);
|
||||
if (!vt->tabs) return free(l), false;
|
||||
vt->tabs->chars[0].c = vt->tabs->chars[ncol - 1].c = L'*';
|
||||
for (size_t i = 0; i < ncol; i++) if (i % TAB == 0)
|
||||
vt->tabs->chars[i].c = L'*';
|
||||
|
||||
fixcursor(vt);
|
||||
dirtylines(vt, 0, nline);
|
||||
notify(vt, true, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
writecharatcurs(TMT *vt, wchar_t w)
|
||||
{
|
||||
COMMON_VARS;
|
||||
|
||||
#ifdef TMT_HAS_WCWIDTH
|
||||
extern int wcwidth(wchar_t c);
|
||||
if (wcwidth(w) > 1) w = TMT_INVALID_CHAR;
|
||||
if (wcwidth(w) < 0) return;
|
||||
#endif
|
||||
|
||||
CLINE(vt)->chars[vt->curs.c].c = w;
|
||||
CLINE(vt)->chars[vt->curs.c].a = vt->attrs;
|
||||
CLINE(vt)->dirty = vt->dirty = true;
|
||||
|
||||
if (c->c < s->ncol - 1)
|
||||
c->c++;
|
||||
else{
|
||||
c->c = 0;
|
||||
c->r++;
|
||||
}
|
||||
|
||||
if (c->r >= s->nline){
|
||||
c->r = s->nline - 1;
|
||||
scrup(vt, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static inline size_t
|
||||
testmbchar(TMT *vt)
|
||||
{
|
||||
mbstate_t ts = vt->ms;
|
||||
return vt->nmb? mbrtowc(NULL, vt->mb, vt->nmb, &ts) : (size_t)-2;
|
||||
}
|
||||
|
||||
static inline wchar_t
|
||||
getmbchar(TMT *vt)
|
||||
{
|
||||
wchar_t c = 0;
|
||||
size_t n = mbrtowc(&c, vt->mb, vt->nmb, &vt->ms);
|
||||
vt->nmb = 0;
|
||||
return (n == (size_t)-1 || n == (size_t)-2)? TMT_INVALID_CHAR : c;
|
||||
}
|
||||
|
||||
void
|
||||
tmt_write(TMT *vt, const char *s, size_t n)
|
||||
{
|
||||
TMTPOINT oc = vt->curs;
|
||||
n = n? n : strlen(s);
|
||||
|
||||
for (size_t p = 0; p < n; p++){
|
||||
if (handlechar(vt, s[p]))
|
||||
continue;
|
||||
else if (vt->acs)
|
||||
writecharatcurs(vt, tacs(vt, (unsigned char)s[p]));
|
||||
else if (vt->decdraw)
|
||||
writecharatcurs(vt, decchar(vt, (unsigned char)s[p]));
|
||||
else if (vt->nmb >= BUF_MAX)
|
||||
writecharatcurs(vt, getmbchar(vt));
|
||||
else{
|
||||
switch (testmbchar(vt)){
|
||||
case (size_t)-1: writecharatcurs(vt, getmbchar(vt)); break;
|
||||
case (size_t)-2: vt->mb[vt->nmb++] = s[p]; break;
|
||||
}
|
||||
|
||||
if (testmbchar(vt) <= MB_LEN_MAX)
|
||||
writecharatcurs(vt, getmbchar(vt));
|
||||
}
|
||||
}
|
||||
|
||||
notify(vt, vt->dirty, memcmp(&oc, &vt->curs, sizeof(oc)) != 0);
|
||||
}
|
||||
|
||||
const TMTSCREEN *
|
||||
tmt_screen(const TMT *vt)
|
||||
{
|
||||
return &vt->screen;
|
||||
}
|
||||
|
||||
const TMTPOINT *
|
||||
tmt_cursor(const TMT *vt)
|
||||
{
|
||||
return &vt->curs;
|
||||
}
|
||||
|
||||
void
|
||||
tmt_clean(TMT *vt)
|
||||
{
|
||||
for (size_t i = 0; i < vt->screen.nline; i++)
|
||||
vt->dirty = vt->screen.lines[i]->dirty = false;
|
||||
}
|
||||
|
||||
void
|
||||
tmt_reset(TMT *vt)
|
||||
{
|
||||
vt->curs.r = vt->curs.c = vt->oldcurs.r = vt->oldcurs.c = vt->acs = (bool)0;
|
||||
resetparser(vt);
|
||||
vt->attrs = vt->oldattrs = defattrs;
|
||||
memset(&vt->ms, 0, sizeof(vt->ms));
|
||||
clearlines(vt, 0, vt->screen.nline);
|
||||
CB(vt, TMT_MSG_CURSOR, "t");
|
||||
notify(vt, true, true);
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/* Copyright (c) 2017 Rob King
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the copyright holder nor the
|
||||
* names of contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS,
|
||||
* COPYRIGHT HOLDERS, OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef TMT_H
|
||||
#define TMT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <wchar.h>
|
||||
|
||||
/**** INVALID WIDE CHARACTER */
|
||||
#ifndef TMT_INVALID_CHAR
|
||||
#define TMT_INVALID_CHAR ((wchar_t)0xfffd)
|
||||
#endif
|
||||
|
||||
/**** INPUT SEQUENCES */
|
||||
#define TMT_KEY_UP "\033[A"
|
||||
#define TMT_KEY_DOWN "\033[B"
|
||||
#define TMT_KEY_RIGHT "\033[C"
|
||||
#define TMT_KEY_LEFT "\033[D"
|
||||
#define TMT_KEY_HOME "\033[H"
|
||||
#define TMT_KEY_END "\033[Y"
|
||||
#define TMT_KEY_INSERT "\033[L"
|
||||
#define TMT_KEY_BACKSPACE "\x08"
|
||||
#define TMT_KEY_ESCAPE "\x1b"
|
||||
#define TMT_KEY_BACK_TAB "\033[Z"
|
||||
#define TMT_KEY_PAGE_UP "\033[V"
|
||||
#define TMT_KEY_PAGE_DOWN "\033[U"
|
||||
#define TMT_KEY_F1 "\033OP"
|
||||
#define TMT_KEY_F2 "\033OQ"
|
||||
#define TMT_KEY_F3 "\033OR"
|
||||
#define TMT_KEY_F4 "\033OS"
|
||||
#define TMT_KEY_F5 "\033OT"
|
||||
#define TMT_KEY_F6 "\033OU"
|
||||
#define TMT_KEY_F7 "\033OV"
|
||||
#define TMT_KEY_F8 "\033OW"
|
||||
#define TMT_KEY_F9 "\033OX"
|
||||
#define TMT_KEY_F10 "\033OY"
|
||||
|
||||
/**** BASIC DATA STRUCTURES */
|
||||
typedef struct TMT TMT;
|
||||
|
||||
typedef enum{
|
||||
TMT_COLOR_DEFAULT = 0,
|
||||
TMT_COLOR_BLACK = 1,
|
||||
TMT_COLOR_RED,
|
||||
TMT_COLOR_GREEN,
|
||||
TMT_COLOR_YELLOW,
|
||||
TMT_COLOR_BLUE,
|
||||
TMT_COLOR_MAGENTA,
|
||||
TMT_COLOR_CYAN,
|
||||
TMT_COLOR_WHITE,
|
||||
TMT_COLOR_MAX
|
||||
} tmt_color_t;
|
||||
|
||||
typedef struct TMTATTRS TMTATTRS;
|
||||
struct TMTATTRS{
|
||||
bool bold;
|
||||
bool dim;
|
||||
bool underline;
|
||||
bool blink;
|
||||
bool reverse;
|
||||
bool invisible;
|
||||
tmt_color_t fg;
|
||||
tmt_color_t bg;
|
||||
};
|
||||
|
||||
typedef struct TMTCHAR TMTCHAR;
|
||||
struct TMTCHAR{
|
||||
wchar_t c;
|
||||
TMTATTRS a;
|
||||
};
|
||||
|
||||
typedef struct TMTPOINT TMTPOINT;
|
||||
struct TMTPOINT{
|
||||
size_t r;
|
||||
size_t c;
|
||||
};
|
||||
|
||||
typedef struct TMTLINE TMTLINE;
|
||||
struct TMTLINE{
|
||||
bool dirty;
|
||||
TMTCHAR chars[];
|
||||
};
|
||||
|
||||
typedef struct TMTSCREEN TMTSCREEN;
|
||||
struct TMTSCREEN{
|
||||
size_t nline;
|
||||
size_t ncol;
|
||||
|
||||
TMTLINE **lines;
|
||||
};
|
||||
|
||||
/**** CALLBACK SUPPORT */
|
||||
typedef enum{
|
||||
TMT_MSG_MOVED,
|
||||
TMT_MSG_UPDATE,
|
||||
TMT_MSG_ANSWER,
|
||||
TMT_MSG_BELL,
|
||||
TMT_MSG_CURSOR
|
||||
} tmt_msg_t;
|
||||
|
||||
typedef void (*TMTCALLBACK)(tmt_msg_t m, struct TMT *v, const void *r, void *p);
|
||||
|
||||
/**** PUBLIC FUNCTIONS */
|
||||
TMT *tmt_open(size_t nline, size_t ncol, TMTCALLBACK cb, void *p,
|
||||
const wchar_t *acs);
|
||||
void tmt_close(TMT *vt);
|
||||
bool tmt_resize(TMT *vt, size_t nline, size_t ncol);
|
||||
void tmt_write(TMT *vt, const char *s, size_t n);
|
||||
const TMTSCREEN *tmt_screen(const TMT *vt);
|
||||
const TMTPOINT *tmt_cursor(const TMT *vt);
|
||||
void tmt_clean(TMT *vt);
|
||||
void tmt_reset(TMT *vt);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,209 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <locale.h>
|
||||
#include <unistd.h>
|
||||
#include "tmt.h"
|
||||
#define HASHSIZE 2053
|
||||
#define MAXNAMELEN 256
|
||||
#define MAXDATALEN 8192
|
||||
struct terment {
|
||||
struct terment *next;
|
||||
char *name;
|
||||
TMT *vt;
|
||||
};
|
||||
|
||||
#define SETNODE 1
|
||||
#define WRITE 2
|
||||
#define READBUFF 0
|
||||
static struct terment *buffers[HASHSIZE];
|
||||
|
||||
unsigned long hash(char *str)
|
||||
/* djb2a */
|
||||
{
|
||||
unsigned long idx = 5381;
|
||||
int c;
|
||||
|
||||
while ((c = *str++))
|
||||
idx = ((idx << 5) + idx) + c;
|
||||
return idx % HASHSIZE;
|
||||
}
|
||||
|
||||
TMT *get_termentbyname(char *name) {
|
||||
struct terment *ret;
|
||||
for (ret = buffers[hash(name)]; ret != NULL; ret = ret->next)
|
||||
if (strcmp(name, ret->name) == 0)
|
||||
return ret->vt;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
TMT *set_termentbyname(char *name) {
|
||||
struct terment *ret;
|
||||
int idx;
|
||||
|
||||
idx = hash(name);
|
||||
for (ret = buffers[idx]; ret != NULL; ret = ret->next)
|
||||
if (strcmp(name, ret->name) == 0)
|
||||
return ret->vt;
|
||||
ret = (struct terment *)malloc(sizeof(*ret));
|
||||
ret->next = buffers[idx];
|
||||
ret->name = strdup(name);
|
||||
ret->vt = tmt_open(31, 100, NULL, NULL, L"→←↑↓■◆▒°±▒┘┐┌└┼⎺───⎽├┤┴┬│≤≥π≠£•");
|
||||
buffers[idx] = ret;
|
||||
return ret->vt;
|
||||
}
|
||||
|
||||
void dump_vt(TMT* outvt) {
|
||||
const TMTSCREEN *out = tmt_screen(outvt);
|
||||
const TMTPOINT *curs = tmt_cursor(outvt);
|
||||
int line, idx, maxcol, maxrow;
|
||||
bool bold = false;
|
||||
bool dim = false;
|
||||
bool underline = false;
|
||||
bool blink = false;
|
||||
bool reverse = false;
|
||||
bool invisible = false;
|
||||
bool intensitychg = false;
|
||||
tmt_color_t fg = TMT_COLOR_DEFAULT;
|
||||
tmt_color_t bg = TMT_COLOR_DEFAULT;
|
||||
wchar_t sgrline[30];
|
||||
size_t srgidx = 0;
|
||||
char colorcode = 0;
|
||||
wprintf(L"\033c");
|
||||
maxcol = 0;
|
||||
maxrow = 0;
|
||||
for (line = out->nline - 1; line >= 0; --line) {
|
||||
for (idx = out->ncol - 1; idx > maxcol; --idx) {
|
||||
if (out->lines[line]->chars[idx].c != L' ') {
|
||||
if (maxrow < line)
|
||||
maxrow = line;
|
||||
maxcol = idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (line = 0; line <= maxrow; line++) {
|
||||
for (idx = 0; idx <= maxcol; idx++) {
|
||||
sgrline[0] = L'\x00';
|
||||
intensitychg = false;
|
||||
if (out->lines[line]->chars[idx].a.bold != bold) {
|
||||
bold = out->lines[line]->chars[idx].a.bold;
|
||||
intensitychg = true; // Can't unbold without changing dim
|
||||
}
|
||||
if (out->lines[line]->chars[idx].a.dim != dim) {
|
||||
dim = out->lines[line]->chars[idx].a.dim;
|
||||
intensitychg = true; // Can't undim without changing bold
|
||||
}
|
||||
if (intensitychg) {
|
||||
intensitychg = false;
|
||||
wcscat(sgrline, L"22;");
|
||||
if (bold)
|
||||
wcscat(sgrline, L"1;");
|
||||
if (dim)
|
||||
wcscat(sgrline, L"2;");
|
||||
}
|
||||
if (out->lines[line]->chars[idx].a.underline != underline) {
|
||||
underline = out->lines[line]->chars[idx].a.underline;
|
||||
if (underline)
|
||||
wcscat(sgrline, L"4;");
|
||||
else
|
||||
wcscat(sgrline, L"24;");
|
||||
}
|
||||
if (out->lines[line]->chars[idx].a.blink != blink) {
|
||||
blink = out->lines[line]->chars[idx].a.blink;
|
||||
if (blink)
|
||||
wcscat(sgrline, L"5;");
|
||||
else
|
||||
wcscat(sgrline, L"25;");
|
||||
}
|
||||
if (out->lines[line]->chars[idx].a.reverse != reverse) {
|
||||
reverse = out->lines[line]->chars[idx].a.reverse;
|
||||
if (reverse)
|
||||
wcscat(sgrline, L"7;");
|
||||
else
|
||||
wcscat(sgrline, L"27;");
|
||||
}
|
||||
if (out->lines[line]->chars[idx].a.invisible != invisible) {
|
||||
invisible = out->lines[line]->chars[idx].a.invisible;
|
||||
if (invisible)
|
||||
wcscat(sgrline, L"8;");
|
||||
else
|
||||
wcscat(sgrline, L"28;");
|
||||
}
|
||||
if (out->lines[line]->chars[idx].a.fg != fg) {
|
||||
fg = out->lines[line]->chars[idx].a.fg;
|
||||
if (fg == TMT_COLOR_DEFAULT)
|
||||
colorcode = 39;
|
||||
else
|
||||
colorcode = 29 + fg;
|
||||
swprintf(sgrline + wcslen(sgrline), 4, L"%d;", colorcode);
|
||||
}
|
||||
if (out->lines[line]->chars[idx].a.bg != bg) {
|
||||
bg = out->lines[line]->chars[idx].a.bg;
|
||||
if (bg == TMT_COLOR_DEFAULT)
|
||||
colorcode = 49;
|
||||
else
|
||||
colorcode = 39 + bg;
|
||||
swprintf(sgrline + wcslen(sgrline), 4, L"%d;", colorcode);
|
||||
}
|
||||
if (sgrline[0] != 0) {
|
||||
sgrline[wcslen(sgrline) - 1] = 0; // Trim last ;
|
||||
wprintf(L"\033[%lsm", sgrline);
|
||||
}
|
||||
wprintf(L"%lc", out->lines[line]->chars[idx].c);
|
||||
}
|
||||
if (line < maxrow)
|
||||
wprintf(L"\r\n");
|
||||
}
|
||||
fflush(stdout);
|
||||
wprintf(L"\x1b[%ld;%ldH", curs->r + 1, curs->c + 1);
|
||||
fflush(stdout);
|
||||
idx = write(1, "\x00", 1);
|
||||
if (idx < 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
int cmd, length;
|
||||
setlocale(LC_ALL, "");
|
||||
char cmdbuf[MAXDATALEN];
|
||||
char currnode[MAXNAMELEN];
|
||||
TMT *currvt = NULL;
|
||||
TMT *outvt = NULL;
|
||||
stdin = freopen(NULL, "rb", stdin);
|
||||
if (stdin == NULL) {
|
||||
exit(1);
|
||||
}
|
||||
while (1) {
|
||||
length = fread(&cmd, 4, 1, stdin);
|
||||
if (length < 0)
|
||||
continue;
|
||||
length = cmd & 536870911;
|
||||
cmd = cmd >> 29;
|
||||
if (cmd == SETNODE) {
|
||||
currnode[length] = 0;
|
||||
cmd = fread(currnode, 1, length, stdin);
|
||||
if (cmd < 0)
|
||||
continue;
|
||||
currvt = set_termentbyname(currnode);
|
||||
} else if (cmd == WRITE) {
|
||||
if (currvt == NULL)
|
||||
currvt = set_termentbyname("");
|
||||
cmdbuf[length] = 0;
|
||||
cmd = fread(cmdbuf, 1, length, stdin);
|
||||
if (cmd < 0)
|
||||
continue;
|
||||
tmt_write(currvt, cmdbuf, length);
|
||||
} else if (cmd == READBUFF) {
|
||||
cmdbuf[length] = 0;
|
||||
cmd = fread(cmdbuf, 1, length, stdin);
|
||||
if (cmd < 0)
|
||||
continue;
|
||||
outvt = get_termentbyname(cmdbuf);
|
||||
if (outvt != NULL) {
|
||||
dump_vt(outvt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ dracut_install /etc/udev/hwdb.bin
|
||||
dracut_install /usr/share/hwdata/pci.ids
|
||||
dracut_install ibstat ibstatus
|
||||
dracut_install opainfo
|
||||
dracut_install /usr/lib/udev/rules.d/10-dm.rules /usr/sbin/dmsetup /usr/lib/udev/rules.d/95-dm-notify.rules
|
||||
#dracut_install /usr/lib/opa-fm/bin/opafmd
|
||||
#dracut_install /usr/sbin/opensm /usr/libexec/opensm-launch
|
||||
dracut_install /usr/lib64/libibverbs/libhfi1verbs-rdmav25.so /etc/libibverbs.d/hfi1verbs.driver /etc/libibverbs.d/mlx4.driver /etc/libibverbs.d/mlx5.driver /usr/lib64/libibverbs/libmlx4-rdmav25.so /usr/lib64/libibverbs/libmlx5-rdmav25.so
|
||||
|
||||
@@ -5,3 +5,4 @@ instmods cdc_ether
|
||||
instmods mptctl
|
||||
instmods mlx4_ib mlx5_ub ib_umad ib_ipoib
|
||||
instmods i40e hfi1 bnxt_en qed qede
|
||||
instmods dm-mod dm-log raid0 raid1 raid10 raid456 dm-raid dm-thin-pool dm-crypt dm-snapshot linear dm-era
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%define arch x86_64
|
||||
Version: 3.2.0
|
||||
Release: 1
|
||||
Release: 3
|
||||
Name: confluent-genesis-%{arch}
|
||||
BuildArch: noarch
|
||||
Summary: Genesis servicing image for confluent
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
uidmin = 1000
|
||||
uidmax = 60000
|
||||
gidmin = 1000
|
||||
gidmax = 60000
|
||||
for line in open('/etc/login.defs').read().split('\n'):
|
||||
try:
|
||||
line = line[:line.index('#')]
|
||||
except ValueError:
|
||||
pass
|
||||
keyval = line.split()
|
||||
if len(keyval) < 2:
|
||||
continue
|
||||
if keyval[0] == 'UID_MIN':
|
||||
uidmin = int(keyval[1])
|
||||
if keyval[0] == 'UID_MAX':
|
||||
uidmax = int(keyval[1])
|
||||
if keyval[0] == 'GID_MIN':
|
||||
gidmin = int(keyval[1])
|
||||
if keyval[0] == 'GID_MAX':
|
||||
gidmax = int(keyval[1])
|
||||
|
||||
def show_passwd(shadowmode=False):
|
||||
for line in open('/etc/passwd').read().split('\n'):
|
||||
try:
|
||||
user, _, uid, _ = line.split(':', 3)
|
||||
except ValueError:
|
||||
continue
|
||||
uid = int(uid)
|
||||
if uid >= uidmin and uid <= uidmax:
|
||||
if shadowmode:
|
||||
yield '{0}:!!:::::::'.format(user)
|
||||
else:
|
||||
yield line
|
||||
|
||||
def show_group(shadowmode=False):
|
||||
for line in open('/etc/group').read().split('\n'):
|
||||
try:
|
||||
_, _, gid, _ = line.split(':', 3)
|
||||
except ValueError:
|
||||
continue
|
||||
gid = int(gid)
|
||||
if gid >= gidmin and gid <= gidmax:
|
||||
yield line
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if 'passwd' in sys.argv[1]:
|
||||
for line in show_passwd():
|
||||
print(line)
|
||||
elif 'shadow' in sys.argv[1]:
|
||||
for line in show_passwd(True):
|
||||
print(line)
|
||||
elif 'group' in sys.argv[1]:
|
||||
for line in show_group():
|
||||
print(line)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ fi
|
||||
sed -i "s!root:[^:]*:!root:$rootpw:!" /sysroot/etc/shadow
|
||||
mkdir -p /sysroot/root/.ssh
|
||||
chmod 700 /sysroot/root/.ssh
|
||||
cat /ssh/*.rootpubkey > /sysroot/root/.ssh/authorized_keys
|
||||
cat /ssh/*pubkey > /sysroot/root/.ssh/authorized_keys
|
||||
chmod 600 /sysroot/root/.ssh/authorized_keys
|
||||
mkdir -p /sysroot/etc/ssh/
|
||||
for i in /ssh/*.ca; do
|
||||
|
||||
Reference in New Issue
Block a user