2
0
mirror of https://github.com/xcat2/confluent.git synced 2026-09-29 08:41:00 +00:00

Compare commits

...

59 Commits

Author SHA1 Message Date
Jarrod Johnson d64b3e6a4e Fix up build process for vtbufferd 2021-04-01 17:15:35 -04:00
Jarrod Johnson 637e0f0a81 Draft RPM spec for vtbufferd component 2021-04-01 16:44:40 -04:00
Jarrod Johnson e26218eb92 Implement SGR recreation and fix ECH
The tmt library had a bug with ECH with cursor position.

Flesh out the dump_vt to translate the attributes to VT codes
for the buffer to look the same as the attributes indicate.
2021-04-01 16:28:28 -04:00
Jarrod Johnson 03eb026b61 Improve VT dump behavior
Ensure the resultant dump has consistently long lines.  This
will matter when we start rendering attributes on blanks.

Additionally, simplify the code
significantly taking advantage
of first loop.
2021-04-01 12:51:32 -04:00
Jarrod Johnson beab6a3c02 Migrate VT buffering to c
C implementation to improve memory and cpu utilization.

Forked off to further move the work off the main process.

Still needs attribute rendition and packaging before merging to main
branch.
2021-03-31 17:28:26 -04:00
Jarrod Johnson 5e0ebce300 Add logs for offering boot
Make it easier to debug a failure
to boot due to misconfiguration.
2021-03-29 16:58:03 -04:00
Jarrod Johnson 85c4ec5654 Skip fqdn in cert generation
There are scenarios where getqfdn can induce a hang.
The certificate having FQDN isn't that useful anyway,
since confluent never uses it and external use of it
may need more carefully crafted certificate to have
a good chance of matching it anyway.

Also, the chances a user would import our cert as a
CA to something like a browser are low.
2021-03-29 14:29:42 -04:00
Jarrod Johnson 7957a6abd4 Add invoking syncfile activity to the default profiles 2021-03-29 14:03:11 -04:00
Jarrod Johnson 39a7d8d131 Move the syncfileclient into scripts
This does not need to be in initramfs,
so do not grow addons.cpio.
2021-03-29 13:55:44 -04:00
Jarrod Johnson 54c5bf128e Finish syncflieclient 2021-03-29 13:50:07 -04:00
Jarrod Johnson ca0c592044 Commence work on syncfileclient
This will be used to wait for deployer
to finish, then execute handlers
for 'MERGE' entries.
2021-03-25 16:55:56 -04:00
Jarrod Johnson 829d1316b2 Remove 'APPEND:'
If possible, we wannt to stick to 'MERGE', since the handlers
are designed to make that
idempotent for repeat.

APPEND: would not be idempotent...
2021-03-25 12:37:21 -04:00
Jarrod Johnson c2c1c85651 Increase verbosity of syncfiles 2021-03-25 08:59:16 -04:00
Jarrod Johnson c5833f1417 Reserve some characters for special syntax
May need to modify some behaviors in future, provide
a healthy supply of reserved characters toward that end.
2021-03-25 08:57:48 -04:00
Jarrod Johnson 07ae3593c3 Syncfiles fixes 2021-03-24 17:33:26 -04:00
Jarrod Johnson 8ab35b11cd Add -a notation about syncfiles relevance 2021-03-24 16:01:48 -04:00
Jarrod Johnson 35ef6170ba Implement syncfiles server side 2021-03-24 16:00:54 -04:00
Jarrod Johnson d650f11255 Begin work on syncfiles concept for confluent 2021-03-23 17:32:45 -04:00
Jarrod Johnson b4926b39fe Fix CA in installer env in ubuntu 2021-03-22 12:16:32 -04:00
Jarrod Johnson 53ccee734f Add more lvm dependencies 2021-03-22 11:00:21 -04:00
Jarrod Johnson fe9d0e84d3 Ubuntu firstboot ansible and scripts directory support 2021-03-22 10:36:14 -04:00
Jarrod Johnson 4e7e12f90f Add ansible to ubuntu post and scripts/post.d 2021-03-22 10:35:11 -04:00
Jarrod Johnson 8ae68cd34a Merge pull request #40 from vmaneagit/patch-60
Update nodesetboot.ronn
2021-03-22 08:05:59 -04:00
vmaneagit 607d69b1e1 Update nodesetboot.ronn
Changes made for Option chapter:
 - updated -m command
 - added -h command

Description chapter was updated
2021-03-22 13:47:04 +02:00
Jarrod Johnson 50e62c7b73 Bump release to pick up new lvm content 2021-03-19 19:24:31 -04:00
Jarrod Johnson 363eff831f Add devicemapper modules to genesis
This enables at least common lvm access for rescue purposes.
2021-03-19 18:52:18 -04:00
Jarrod Johnson 31820ddfd7 Fix ansible support of suse15
Must install python3 and we must have the python apiclient
intact with appropriate CA certs to proceed
2021-03-19 16:07:59 -04:00
Jarrod Johnson 153749aebe Change ssh duringe suse 15 post
Have port 22 be inside the target environment, and
2222 point to the installer. This improves ansible
experience.
2021-03-19 15:13:37 -04:00
Jarrod Johnson 7c5dd85e74 Copy in ansible to genesis profiles
Make it clearer that there could be ansible support in genesis
2021-03-19 13:09:21 -04:00
Jarrod Johnson 7a5bf8a06f Merge pull request #23 from vmaneagit/patch-25
Update noderun.ronn
2021-03-19 11:04:28 -04:00
Jarrod Johnson 9c05057deb Merge pull request #24 from vmaneagit/patch-26
Update nodemedia.ronn
2021-03-19 11:04:22 -04:00
Jarrod Johnson 8b1902601f Merge pull request #28 from vmaneagit/patch-30
Update nodepower.ronn
2021-03-19 11:04:17 -04:00
Jarrod Johnson d94d9d0a78 Merge pull request #32 from vmaneagit/patch-35
Update nodefirmware.ronn
2021-03-19 11:04:09 -04:00
Jarrod Johnson 457b0851c9 Merge pull request #33 from vmaneagit/patch-36
Update nodeeventlog.ronn
2021-03-19 11:04:05 -04:00
Jarrod Johnson d2cae9faca Merge pull request #34 from vmaneagit/patch-37
Update nodedeploy.ronn
2021-03-19 11:02:24 -04:00
Jarrod Johnson e27739eafd Merge pull request #37 from vmaneagit/patch-40
Update nodebmcreset.ronn
2021-03-19 11:02:16 -04:00
Jarrod Johnson 874302537d Merge pull request #36 from vmaneagit/patch-39
Update nodeboot.ronn
2021-03-19 11:01:56 -04:00
Jarrod Johnson cd3b67d4cc Merge pull request #38 from vmaneagit/patch-41
Update nodeattrib.ronn.tmpl
2021-03-19 11:01:41 -04:00
Jarrod Johnson 7aacac2fb7 Merge pull request #39 from vmaneagit/patch-42
Update confluentdbutil.ronn
2021-03-19 11:01:27 -04:00
Jarrod Johnson a69fb3fec3 Merge pull request #35 from vmaneagit/patch-38
Update nodeconfig.ronn
2021-03-19 11:01:13 -04:00
Jarrod Johnson 2d9cbe4f33 Merge pull request #22 from vmaneagit/patch-24
Update nodesupport
2021-03-19 11:00:57 -04:00
vmaneagit e33ed0afc8 Update confluentdbutil.ronn
Changes made for Option chapter:
 - added -h command
2021-03-19 16:55:09 +02:00
vmaneagit ace6544444 Update nodeattrib.ronn.tmpl
Changes made for Option chapter:
 - updated -m command
 - added -h command
2021-03-19 16:50:59 +02:00
vmaneagit 1468c56e1f Update nodebmcreset.ronn
Changes made for Option chapter:
 - updated -m command
 - added -h command
2021-03-19 16:48:40 +02:00
vmaneagit b36756ebbc Update nodeboot.ronn
Changes made for Option chapter:
 - updated -m command
 - added -h command

Synopsis chapter was reduced to single line
2021-03-19 15:28:33 +02:00
vmaneagit a8f378deb7 Update nodeconfig.ronn
Changes made for Option chapter:
 - updated -m command
 - added -h command

Synopsis chapter was reduced to single line
2021-03-19 15:22:14 +02:00
vmaneagit cb4e53c459 Update nodedeploy.ronn
Changes made for Option chapter:
 - updated -m command
 - added -h command

Synopsis chapter was reduced to single line
2021-03-19 15:20:08 +02:00
vmaneagit 3e0b283593 Update nodeeventlog.ronn
Changes made for Option chapter:
 - updated -m command
 - added -h command

Synopsis chapter was reduced to single line
2021-03-19 15:17:41 +02:00
vmaneagit 3b4e9fd2e4 Update nodefirmware.ronn
Changes made for Option chapter:
 - updated -m command
 - added -h command

Synopsis chapter was reduced to the same line as the usage:

nodefirmware <noderange> [list][update [--backup <file>]]|[<components>]
2021-03-19 15:06:14 +02:00
vmaneagit 6af6195798 Update nodepower.ronn
Changes made for Option chapter:

 - added -h command
2021-03-19 14:22:47 +02:00
vmaneagit a8d1f484b5 Update nodemedia.ronn
Changes made for Option chapter:
 - added -h command
- removed -c command
2021-03-19 13:17:30 +02:00
vmaneagit 619c60533d Update noderun.ronn
Changes made for Option chapter:
 - added -h command
 - updated -m command
2021-03-19 13:13:03 +02:00
vmaneagit c6fd760006 Update nodesupport
Updated line 119 with to show  "<filename or directory>"
2021-03-19 11:49:46 +02:00
Jarrod Johnson 03f88c6e3e Swap ssh during post phase to installed image
Preserve installer access on 2222

This enables easier ansible usage.
2021-03-18 15:53:30 -04:00
Jarrod Johnson 697b33ae80 Put a lower bound on autocons geometry
If some glitch happens during the read, do not end up with
absurdly low geometry.
2021-03-18 15:48:25 -04:00
Jarrod Johnson 79ef1c4811 Correct typo in functions update 2021-03-18 14:58:53 -04:00
Jarrod Johnson f442e949aa Add firstboot/post ansible play support to SUSE15 2021-03-18 14:37:54 -04:00
Jarrod Johnson 8050d3f69a Add playbook examples for default genesis 2021-03-18 14:30:18 -04:00
Jarrod Johnson 66efa1da5c Implement remote config for EL8 2021-03-18 14:12:19 -04:00
57 changed files with 2734 additions and 177 deletions
+1 -1
View File
@@ -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,3 +37,6 @@ the json files (password protected, removed from the files, or unprotected).
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
@@ -53,10 +53,13 @@ See nodegroupattrib(8) command on how to manage attributes on a group level.
Request interactive prompting to provide values rather than the command line
or environment variables.
* `-m`, `--maxnodes`:
* `-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:
`# nodeattrib n1-n2`
+4 -1
View File
@@ -11,9 +11,12 @@ nodebmcreset(8) -- Reset management controller
## OPTIONS
* `-m`, `--maxnodes`:
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
Number of nodes to affect before prompting for
confirmation
* `-h`, `--help`:
Show help message and exit
## EXAMPLES:
+5 -3
View File
@@ -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
@@ -26,9 +25,12 @@ device without inducing a reboot, see the `nodesetboot` command.
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`:
* `-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
+5 -3
View File
@@ -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
@@ -53,9 +52,12 @@ actually be in effect until a reboot.
configuration reset to default. Currently the only component implemented
is uefi.
* `-m`, `--maxnodes`:
* `-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:
+5 -4
View File
@@ -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
@@ -29,8 +27,11 @@ deployment status.
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`:
* `-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:
+6 -4
View File
@@ -3,8 +3,7 @@ nodeeventlog(8) -- Pull eventlog from confluent nodes
## SYNOPSIS
`nodeeventlog <noderange>`
`nodeeventlog <noderange> [clear]`
`nodeeventlog [options] <noderange> [clear]`
## DESCRIPTION
@@ -13,9 +12,12 @@ noderange.
## OPTIONS
* `-m`, `--maxnodes`:
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
Specify a maximum number of nodes to clear if clearing log, prompting if
over the threshold
over the threshold
* `-h`, `--help`:
Show help message and exit
## EXAMPLES
* Pull the event log from n2 and n3:
+5 -4
View File
@@ -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
@@ -31,9 +29,12 @@ the out of band facilities. Firmware updates can end in one of three states:
* `-b`, `--backup`:
Target a backup bank rather than primary
* `-m`, `--maxnodes`:
* `-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
+2 -2
View File
@@ -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:
+3
View File
@@ -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:
+4 -1
View File
@@ -21,9 +21,12 @@ themselves, see nodeshell(8).
* `-n`, `--nonodeprefix`:
Do not prefix output with node names
* `-m`, `--maxnodes`:
* `-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
+7 -3
View File
@@ -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`:
@@ -34,9 +35,12 @@ control.
* `-u`, `--uefi`:
This flag does nothing, it is for command compatibility with xCAT's rsetboot
* `-m`, `--maxnodes`:
* `-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
+1 -1
View File
@@ -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,8 +151,13 @@ if __name__ == '__main__':
outf.write(chunk)
chunk = reader.read(16384)
sys.exit(0)
elif len(sys.argv) > 2 and not os.path.exists(sys.argv[-1]):
data = sys.argv[-1]
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())
@@ -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()
@@ -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()
@@ -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
@@ -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()
@@ -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
@@ -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
@@ -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()
+2
View File
@@ -120,6 +120,8 @@ int main(int argc, char* argv[]) {
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);
+3 -1
View File
@@ -38,7 +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', action='store_true')
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')
@@ -101,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',
+2 -2
View File
@@ -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()
+60 -127
View File
@@ -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)
@@ -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):
+13
View File
@@ -1,4 +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
@@ -281,6 +282,18 @@ def handle_request(env, start_response):
start_response('200 OK', ())
yield ''
return
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:
+2 -1
View File
@@ -128,7 +128,8 @@ def initialize_root_key(generate, automation=False):
subprocess.check_call(
['ssh-keygen', '-t', 'ed25519',
'-f','/etc/confluent/ssh/automation', '-N', get_passphrase(),
'-C', 'Confluent Automation'], preexec_fn=normalize_uid)
'-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)
+158
View File
@@ -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)
+5
View File
@@ -0,0 +1,5 @@
vtbufferd: vtbufferd.c tmt.c
gcc -O3 -o vtbufferd vtbufferd.c tmt.c
clean:
rm vtbufferd
+22
View File
@@ -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/
+521
View File
@@ -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);
}
+140
View File
@@ -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
+209
View File
@@ -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);
}
}
}
}
+1
View File
@@ -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
+1
View File
@@ -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 -1
View File
@@ -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