mirror of
https://github.com/xcat2/confluent.git
synced 2026-09-29 08:41:00 +00:00
Compare commits
59 Commits
ansibleplay
...
vtbuffer
| Author | SHA1 | Date | |
|---|---|---|---|
| d64b3e6a4e | |||
| 637e0f0a81 | |||
| e26218eb92 | |||
| 03eb026b61 | |||
| beab6a3c02 | |||
| 5e0ebce300 | |||
| 85c4ec5654 | |||
| 7957a6abd4 | |||
| 39a7d8d131 | |||
| 54c5bf128e | |||
| ca0c592044 | |||
| 829d1316b2 | |||
| c2c1c85651 | |||
| c5833f1417 | |||
| 07ae3593c3 | |||
| 8ab35b11cd | |||
| 35ef6170ba | |||
| d650f11255 | |||
| b4926b39fe | |||
| 53ccee734f | |||
| fe9d0e84d3 | |||
| 4e7e12f90f | |||
| 8ae68cd34a | |||
| 607d69b1e1 | |||
| 50e62c7b73 | |||
| 363eff831f | |||
| 31820ddfd7 | |||
| 153749aebe | |||
| 7c5dd85e74 | |||
| 7a5bf8a06f | |||
| 9c05057deb | |||
| 8b1902601f | |||
| d94d9d0a78 | |||
| 457b0851c9 | |||
| d2cae9faca | |||
| e27739eafd | |||
| 874302537d | |||
| cd3b67d4cc | |||
| 7aacac2fb7 | |||
| a69fb3fec3 | |||
| 2d9cbe4f33 | |||
| e33ed0afc8 | |||
| ace6544444 | |||
| 1468c56e1f | |||
| b36756ebbc | |||
| a8f378deb7 | |||
| cb4e53c459 | |||
| 3e0b283593 | |||
| 3b4e9fd2e4 | |||
| 6af6195798 | |||
| a8d1f484b5 | |||
| 619c60533d | |||
| c6fd760006 | |||
| 03f88c6e3e | |||
| 697b33ae80 | |||
| 79ef1c4811 | |||
| f442e949aa | |||
| 8050d3f69a | |||
| 66efa1da5c |
@@ -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`
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -34,8 +34,8 @@ the host platform attempts.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-c`:
|
||||
Specify the maximum number of instances to run concurrently.
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
* Listing currently mounted media:
|
||||
|
||||
@@ -30,6 +30,9 @@ off will not react to this request.
|
||||
|
||||
* `-p`, '--showprevious':
|
||||
Show previous power state for all directives that may change power state.
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
* Get power state of nodes n1 through n4:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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);
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -72,7 +72,7 @@ def create_certificate(keyout=None, certout=None):
|
||||
if not keyout:
|
||||
raise Exception('Unable to locate TLS certificate path automatically')
|
||||
shortname = socket.gethostname().split('.')[0]
|
||||
longname = socket.getfqdn()
|
||||
longname = shortname # socket.getfqdn()
|
||||
subprocess.check_call(
|
||||
['openssl', 'ecparam', '-name', 'secp384r1', '-genkey', '-out',
|
||||
keyout])
|
||||
@@ -81,7 +81,7 @@ def create_certificate(keyout=None, certout=None):
|
||||
# there exists non-compliant clients that fail with them as IP
|
||||
san.extend(['DNS:{0}'.format(x) for x in get_ip_addresses()])
|
||||
san.append('DNS:{0}'.format(shortname))
|
||||
san.append('DNS:{0}'.format(longname))
|
||||
#san.append('DNS:{0}'.format(longname))
|
||||
san = ','.join(san)
|
||||
sslcfg = get_openssl_conf_location()
|
||||
tmpconfig = tempfile.mktemp()
|
||||
|
||||
@@ -33,42 +33,67 @@ import confluent.tlvdata as tlvdata
|
||||
import confluent.util as util
|
||||
import eventlet
|
||||
import eventlet.event
|
||||
import eventlet.green.os as os
|
||||
import eventlet.green.select as select
|
||||
import eventlet.green.socket as socket
|
||||
import eventlet.green.subprocess as subprocess
|
||||
import eventlet.green.ssl as ssl
|
||||
import pyte
|
||||
import eventlet.semaphore as semaphore
|
||||
import fcntl
|
||||
import random
|
||||
import struct
|
||||
import time
|
||||
import traceback
|
||||
|
||||
_handled_consoles = {}
|
||||
|
||||
_tracelog = None
|
||||
_bufferdaemon = None
|
||||
_bufferlock = None
|
||||
|
||||
try:
|
||||
range = xrange
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
pytecolors2ansi = {
|
||||
'black': 0,
|
||||
'red': 1,
|
||||
'green': 2,
|
||||
'brown': 3,
|
||||
'blue': 4,
|
||||
'magenta': 5,
|
||||
'cyan': 6,
|
||||
'white': 7,
|
||||
'default': 9,
|
||||
}
|
||||
# might be able to use IBMPC map from pyte charsets,
|
||||
# in that case, would have to mask out certain things (like ESC)
|
||||
# in the same way that Screen's draw method would do
|
||||
# for now at least get some of the arrows in there (note ESC is one
|
||||
# of those arrows... so skip it...
|
||||
ansichars = dict(zip((0x18, 0x19), u'\u2191\u2193'))
|
||||
|
||||
def chunk_output(output, n):
|
||||
for i in range(0, len(output), n):
|
||||
yield output[i:i + n]
|
||||
|
||||
def get_buffer_output(nodename):
|
||||
out = _bufferdaemon.stdin
|
||||
instream = _bufferdaemon.stdout
|
||||
if not isinstance(nodename, bytes):
|
||||
nodename = nodename.encode('utf8')
|
||||
outdata = b''
|
||||
with _bufferlock:
|
||||
out.write(struct.pack('I', len(nodename)))
|
||||
out.write(nodename)
|
||||
out.flush()
|
||||
select.select((instream,), (), (), 30)
|
||||
while not outdata or outdata[-1]:
|
||||
chunk = instream.read(128)
|
||||
if chunk:
|
||||
outdata += chunk
|
||||
else:
|
||||
select.select((instream,), (), (), 0)
|
||||
return outdata[:-1]
|
||||
|
||||
|
||||
def _utf8_normalize(data, shiftin, decoder):
|
||||
def send_output(nodename, output):
|
||||
if not isinstance(nodename, bytes):
|
||||
nodename = nodename.encode('utf8')
|
||||
with _bufferlock:
|
||||
_bufferdaemon.stdin.write(struct.pack('I', len(nodename) | (1 << 29)))
|
||||
_bufferdaemon.stdin.write(nodename)
|
||||
_bufferdaemon.stdin.flush()
|
||||
for chunk in chunk_output(output, 8192):
|
||||
_bufferdaemon.stdin.write(struct.pack('I', len(chunk) | (2 << 29)))
|
||||
_bufferdaemon.stdin.write(chunk)
|
||||
_bufferdaemon.stdin.flush()
|
||||
|
||||
def _utf8_normalize(data, decoder):
|
||||
# first we give the stateful decoder a crack at the byte stream,
|
||||
# we may come up empty in the event of a partial multibyte
|
||||
try:
|
||||
@@ -88,60 +113,9 @@ def _utf8_normalize(data, shiftin, decoder):
|
||||
# Finally, the low part of ascii is valid utf-8, but we are going to be
|
||||
# more interested in the cp437 versions (since this is console *output*
|
||||
# not input
|
||||
if shiftin is None:
|
||||
data = data.translate(ansichars)
|
||||
return data.encode('utf-8')
|
||||
|
||||
|
||||
def pytechars2line(chars, maxlen=None):
|
||||
line = b'\x1b[m' # start at default params
|
||||
lb = False # last bold
|
||||
li = False # last italic
|
||||
lu = False # last underline
|
||||
ls = False # last strikethrough
|
||||
lr = False # last reverse
|
||||
lfg = 'default' # last fg color
|
||||
lbg = 'default' # last bg color
|
||||
hasdata = False
|
||||
len = 1
|
||||
for charidx in range(maxlen):
|
||||
char = chars[charidx]
|
||||
csi = bytearray([])
|
||||
if char.fg != lfg:
|
||||
csi.append(30 + pytecolors2ansi.get(char.fg, 9))
|
||||
lfg = char.fg
|
||||
if char.bg != lbg:
|
||||
csi.append(40 + pytecolors2ansi.get(char.bg, 9))
|
||||
lbg = char.bg
|
||||
if char.bold != lb:
|
||||
lb = char.bold
|
||||
csi.append(1 if lb else 22)
|
||||
if char.italics != li:
|
||||
li = char.italics
|
||||
csi.append(3 if li else 23)
|
||||
if char.underscore != lu:
|
||||
lu = char.underscore
|
||||
csi.append(4 if lu else 24)
|
||||
if char.strikethrough != ls:
|
||||
ls = char.strikethrough
|
||||
csi.append(9 if ls else 29)
|
||||
if char.reverse != lr:
|
||||
lr = char.reverse
|
||||
csi.append(7 if lr else 27)
|
||||
if csi:
|
||||
line += b'\x1b[' + b';'.join(['{0}'.format(x).encode('utf-8') for x in csi]) + b'm'
|
||||
if not hasdata and char.data.rstrip():
|
||||
hasdata = True
|
||||
chardata = char.data
|
||||
if not isinstance(chardata, bytes):
|
||||
chardata = chardata.encode('utf-8')
|
||||
line += chardata
|
||||
if maxlen and len >= maxlen:
|
||||
break
|
||||
len += 1
|
||||
return line, hasdata
|
||||
|
||||
|
||||
class ConsoleHandler(object):
|
||||
_plugin_path = '/nodes/{0}/_console/session'
|
||||
_logtobuffer = True
|
||||
@@ -161,15 +135,15 @@ class ConsoleHandler(object):
|
||||
self.node = node
|
||||
self.connectstate = 'unconnected'
|
||||
self._isalive = True
|
||||
self.buffer = pyte.Screen(100, 31)
|
||||
self.termstream = pyte.ByteStream()
|
||||
self.termstream.attach(self.buffer)
|
||||
#self.buffer = pyte.Screen(100, 31)
|
||||
#self.termstream = pyte.ByteStream()
|
||||
#self.termstream.attach(self.buffer)
|
||||
self.livesessions = set([])
|
||||
self.utf8decoder = codecs.getincrementaldecoder('utf-8')()
|
||||
if self._logtobuffer:
|
||||
self.logger = log.Logger(node, console=True,
|
||||
tenant=configmanager.tenant)
|
||||
(text, termstate, timestamp) = (b'', 0, False)
|
||||
timestamp = False
|
||||
# when reading from log file, we will use wall clock
|
||||
# it should usually match walltime.
|
||||
self.lasttime = 0
|
||||
@@ -182,13 +156,7 @@ class ConsoleHandler(object):
|
||||
# guess
|
||||
self.lasttime = util.monotonic_time()
|
||||
self.clearbuffer()
|
||||
self.appmodedetected = False
|
||||
self.shiftin = None
|
||||
self.reconnect = None
|
||||
if termstate & 1:
|
||||
self.appmodedetected = True
|
||||
if termstate & 2:
|
||||
self.shiftin = b'0'
|
||||
self.users = {}
|
||||
self._attribwatcher = None
|
||||
self._console = None
|
||||
@@ -216,10 +184,7 @@ class ConsoleHandler(object):
|
||||
if not isinstance(data, bytes):
|
||||
data = data.encode('utf-8')
|
||||
try:
|
||||
self.termstream.feed(data)
|
||||
except StopIteration: # corrupt parser state, start over
|
||||
self.termstream = pyte.ByteStream()
|
||||
self.termstream.attach(self.buffer)
|
||||
send_output(self.node, data)
|
||||
except Exception:
|
||||
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
|
||||
event=log.Events.stacktrace)
|
||||
@@ -536,19 +501,7 @@ class ConsoleHandler(object):
|
||||
return
|
||||
if not isinstance(data, bytes):
|
||||
data = data.encode('utf-8')
|
||||
if b'\x1b[?1l' in data: # request for ansi mode cursor keys
|
||||
self.appmodedetected = False
|
||||
if b'\x1b[?1h' in data: # remember the session wants the client to use
|
||||
# 'application mode' Thus far only observed on esxi
|
||||
self.appmodedetected = True
|
||||
if b'\x1b)0' in data:
|
||||
# console indicates it wants access to special drawing characters
|
||||
self.shiftin = b'0'
|
||||
eventdata = 0
|
||||
if self.appmodedetected:
|
||||
eventdata |= 1
|
||||
if self.shiftin is not None:
|
||||
eventdata |= 2
|
||||
# TODO: analyze buffer for registered events, examples:
|
||||
# panics
|
||||
# certificate signing request
|
||||
@@ -557,7 +510,7 @@ class ConsoleHandler(object):
|
||||
self.clearerror = False
|
||||
self.feedbuffer(b'\x1bc\x1b[2J\x1b[1;1H')
|
||||
self._send_rcpts(b'\x1bc\x1b[2J\x1b[1;1H')
|
||||
self._send_rcpts(_utf8_normalize(data, self.shiftin, self.utf8decoder))
|
||||
self._send_rcpts(_utf8_normalize(data, self.utf8decoder))
|
||||
self.log(data, eventdata=eventdata)
|
||||
self.lasttime = util.monotonic_time()
|
||||
self.feedbuffer(data)
|
||||
@@ -583,36 +536,7 @@ class ConsoleHandler(object):
|
||||
'connectstate': self.connectstate,
|
||||
'clientcount': len(self.livesessions),
|
||||
}
|
||||
retdata = b'\x1b[H\x1b[J' # clear screen
|
||||
pendingbl = b'' # pending blank lines
|
||||
maxlen = 0
|
||||
for line in self.buffer.display:
|
||||
line = line.rstrip()
|
||||
if len(line) > maxlen:
|
||||
maxlen = len(line)
|
||||
for line in range(self.buffer.lines):
|
||||
nline, notblank = pytechars2line(self.buffer.buffer[line], maxlen)
|
||||
if notblank:
|
||||
if pendingbl:
|
||||
retdata += pendingbl
|
||||
pendingbl = b''
|
||||
retdata += nline + b'\r\n'
|
||||
else:
|
||||
pendingbl += nline + b'\r\n'
|
||||
if len(retdata) > 6:
|
||||
retdata = retdata[:-2] # remove the last \r\n
|
||||
cursordata = '\x1b[{0};{1}H'.format(self.buffer.cursor.y + 1,
|
||||
self.buffer.cursor.x + 1)
|
||||
if not isinstance(cursordata, bytes):
|
||||
cursordata = cursordata.encode('utf-8')
|
||||
retdata += cursordata
|
||||
if self.shiftin is not None: # detected that terminal requested a
|
||||
# shiftin character set, relay that to the terminal that cannected
|
||||
retdata += b'\x1b)' + self.shiftin
|
||||
#if self.appmodedetected:
|
||||
# retdata += b'\x1b[?1h'
|
||||
#else:
|
||||
# retdata += b'\x1b[?1l'
|
||||
retdata = get_buffer_output(self.node)
|
||||
return retdata, connstate
|
||||
|
||||
def write(self, data):
|
||||
@@ -661,7 +585,16 @@ def _start_tenant_sessions(cfm):
|
||||
|
||||
def start_console_sessions():
|
||||
global _tracelog
|
||||
global _bufferdaemon
|
||||
global _bufferlock
|
||||
_bufferlock = semaphore.Semaphore()
|
||||
_tracelog = log.Logger('trace')
|
||||
_bufferdaemon = subprocess.Popen(
|
||||
['/opt/confluent/bin/vtbufferd'], stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE)
|
||||
fl = fcntl.fcntl(_bufferdaemon.stdout.fileno(), fcntl.F_GETFL)
|
||||
fcntl.fcntl(_bufferdaemon.stdout.fileno(),
|
||||
fcntl.F_SETFL, fl | os.O_NONBLOCK)
|
||||
configmodule.hook_new_configmanagers(_start_tenant_sessions)
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2021 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import confluent.sshutil as sshutil
|
||||
import eventlet.green.subprocess as subprocess
|
||||
import eventlet
|
||||
|
||||
def mkdirp(path):
|
||||
try:
|
||||
os.makedirs(path)
|
||||
except OSError as e:
|
||||
if e.errno != 17:
|
||||
raise
|
||||
|
||||
class SyncList(object):
|
||||
def __init__(self, filename):
|
||||
slist = None
|
||||
self.replacemap = {}
|
||||
self.appendmap = {}
|
||||
self.mergemap = {}
|
||||
with open(filename, 'r') as slfile:
|
||||
slist = slfile.read()
|
||||
entries = slist.split('\n')
|
||||
currmap = self.replacemap
|
||||
for ent in entries:
|
||||
try:
|
||||
cmtidx = ent.index('#')
|
||||
ent = ent[:cmtidx]
|
||||
except ValueError:
|
||||
pass
|
||||
for special in '!@$%^&*()|{}':
|
||||
if special in ent:
|
||||
raise Exception(
|
||||
'Special character "{}" reserved for future use'.format(special))
|
||||
ent = ent.strip()
|
||||
if not ent:
|
||||
continue
|
||||
if ent[-1] == ':':
|
||||
if ent == 'MERGE:':
|
||||
currmap = self.mergemap
|
||||
else:
|
||||
raise Exception(
|
||||
'Section "{}" is not currently supported in syncfiles'.format(ent[:-1]))
|
||||
continue
|
||||
if '->' in ent:
|
||||
k, v = ent.split('->')
|
||||
k = k.strip()
|
||||
v = v.strip()
|
||||
else:
|
||||
k = ent
|
||||
v = ent
|
||||
currmap[k] = v
|
||||
|
||||
|
||||
def sync_list_to_node(synclist, node, suffixes):
|
||||
targdir = tempfile.mkdtemp('.syncto{}'.format(node))
|
||||
output = ''
|
||||
try:
|
||||
sl = SyncList(synclist)
|
||||
for ent in sl.replacemap:
|
||||
stage_ent(sl.replacemap, ent, targdir)
|
||||
if 'append' in suffixes:
|
||||
while suffixes['append'] and suffixes['append'][0] == '/':
|
||||
suffixes['append'] = suffixes['append'][1:]
|
||||
for ent in sl.appendmap:
|
||||
stage_ent(sl.appendmap, ent,
|
||||
os.path.join(targdir, suffixes['append']))
|
||||
if 'merge' in suffixes:
|
||||
while suffixes['merge'] and suffixes['merge'][0] == '/':
|
||||
suffixes['merge'] = suffixes['merge'][1:]
|
||||
for ent in sl.mergemap:
|
||||
stage_ent(sl.mergemap, ent,
|
||||
os.path.join(targdir, suffixes['merge']))
|
||||
sshutil.prep_ssh_key('/etc/confluent/ssh/automation')
|
||||
output = subprocess.check_output(
|
||||
['rsync', '-rvL', targdir + '/', 'root@{}:/'.format(node)])
|
||||
finally:
|
||||
shutil.rmtree(targdir)
|
||||
return output
|
||||
|
||||
def stage_ent(currmap, ent, targdir):
|
||||
dst = currmap[ent]
|
||||
everyfent = []
|
||||
allfents = ent.split()
|
||||
for tmpent in allfents:
|
||||
fents = glob.glob(tmpent)
|
||||
if fents:
|
||||
everyfent.extend(fents)
|
||||
else:
|
||||
everyfent.extend(os.path.dirname(tmpent))
|
||||
if not everyfent:
|
||||
raise Exception('No matching files for "{}"'.format(ent))
|
||||
while dst and dst[0] == '/':
|
||||
dst = dst[1:]
|
||||
fulltarg = os.path.join(targdir, dst)
|
||||
if dst[-1] == '/' or len(everyfent) > 1 or os.path.isdir(everyfent[0]):
|
||||
# target *must* be a directory
|
||||
fulltargdir = fulltarg
|
||||
else:
|
||||
fulltargdir = os.path.join(targdir, os.path.dirname(dst))
|
||||
mkdirp(fulltargdir)
|
||||
for targ in everyfent:
|
||||
if fulltargdir == fulltarg:
|
||||
os.symlink(
|
||||
targ, os.path.join(
|
||||
fulltargdir, os.path.basename(targ)))
|
||||
else:
|
||||
os.symlink(targ, fulltarg)
|
||||
|
||||
syncrunners = {}
|
||||
|
||||
|
||||
def start_syncfiles(nodename, cfg, suffixes):
|
||||
deployinfo = cfg.get_node_attributes(
|
||||
nodename, ('deployment.*',))
|
||||
deployinfo = deployinfo.get(nodename, {})
|
||||
profile = deployinfo.get(
|
||||
'deployment.pendingprofile', {}).get('value', '')
|
||||
if not profile:
|
||||
profile = deployinfo.get(
|
||||
'deployment.stagedprofile', {}).get('value', '')
|
||||
if not profile:
|
||||
profile = deployinfo.get(
|
||||
'deployment.profile', {}).get('value', '')
|
||||
if not profile:
|
||||
raise Exception('Cannot perform syncfiles without profile assigned')
|
||||
synclist = '/var/lib/confluent/public/os/{}/syncfiles'.format(profile)
|
||||
if not os.path.exists(synclist):
|
||||
return '200 OK' # not running
|
||||
syncrunners[nodename] = eventlet.spawn(
|
||||
sync_list_to_node, synclist, nodename, suffixes)
|
||||
return '202 Queued' # backgrounded
|
||||
|
||||
def get_syncresult(nodename):
|
||||
if nodename not in syncrunners:
|
||||
return ('204 Not Running', '')
|
||||
if not syncrunners[nodename].dead:
|
||||
return ('200 OK', '')
|
||||
result = syncrunners[nodename].wait()
|
||||
del syncrunners[nodename]
|
||||
return ('200 OK', result)
|
||||
@@ -0,0 +1,5 @@
|
||||
vtbufferd: vtbufferd.c tmt.c
|
||||
gcc -O3 -o vtbufferd vtbufferd.c tmt.c
|
||||
|
||||
clean:
|
||||
rm vtbufferd
|
||||
@@ -0,0 +1,22 @@
|
||||
VERSION=`git describe|cut -d- -f 1`
|
||||
NUMCOMMITS=`git describe|cut -d- -f 2`
|
||||
if [ "$NUMCOMMITS" != "$VERSION" ]; then
|
||||
VERSION=$VERSION.dev$NUMCOMMITS.g`git describe|cut -d- -f 3`
|
||||
fi
|
||||
mkdir -p dist/confluent_vtbufferd-$VERSION
|
||||
cp *.c *.h Makefile dist/confluent_vtbufferd-$VERSION
|
||||
cd dist
|
||||
tar czvf confluent_vtbufferd-$VERSION.tar.gz confluent_vtbufferd-$VERSION
|
||||
cd -
|
||||
cp dist/confluent_vtbufferd-$VERSION.tar.gz ~/rpmbuild/SOURCES
|
||||
sed -e 's/#VERSION#/'$VERSION/ confluent_vtbufferd.spec.tmpl > ~/rpmbuild/SPECS/confluent_vtbufferd.spec
|
||||
rpmbuild -ba ~/rpmbuild/SPECS/confluent_vtbufferd.spec 2> /dev/null |grep ^Wrote:
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "[ERROR] rpmbuild returned non-zero, run: rpmbuild -ba ~/rpmbuild/SPECS/$PKGNAME.spec"
|
||||
exit 1
|
||||
else
|
||||
# Clean up the generated files in this directory
|
||||
rm -rf dist
|
||||
fi
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
%define name confluent_vtbufferd
|
||||
%define version #VERSION#
|
||||
%define release 1
|
||||
%define debug_package %{nil}
|
||||
%define _build_id_links none
|
||||
|
||||
Summary: Console buffer manager for confluent
|
||||
Name: %{name}
|
||||
Version: %{version}
|
||||
Release: %{release}
|
||||
Source0: %{name}-%{version}.tar.gz
|
||||
License: Apache2
|
||||
Group: Development/Libraries
|
||||
Vendor: Lenovo HPC Organization <hpchelp@lenovo.com>
|
||||
Url: https://github.com/lenovo/confluent/
|
||||
|
||||
%description
|
||||
Service for managing in-memory VT emulation for confluent.
|
||||
|
||||
Contains third party open source code:
|
||||
|
||||
Copyright (c) 2017 Rob King
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the copyright holder nor the
|
||||
names of contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
%prep
|
||||
%setup -n %{name}-%{version} -n %{name}-%{version}
|
||||
|
||||
|
||||
%build
|
||||
make
|
||||
|
||||
%install
|
||||
mkdir -p $RPM_BUILD_ROOT/opt/confluent/bin
|
||||
cp vtbufferd $RPM_BUILD_ROOT/opt/confluent/bin/
|
||||
@@ -0,0 +1,521 @@
|
||||
/* Copyright (c) 2017 Rob King
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the copyright holder nor the
|
||||
* names of contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS,
|
||||
* COPYRIGHT HOLDERS, OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "tmt.h"
|
||||
|
||||
#define BUF_MAX 100
|
||||
#define PAR_MAX 8
|
||||
#define TAB 8
|
||||
#define MAX(x, y) (((size_t)(x) > (size_t)(y)) ? (size_t)(x) : (size_t)(y))
|
||||
#define MIN(x, y) (((size_t)(x) < (size_t)(y)) ? (size_t)(x) : (size_t)(y))
|
||||
#define CLINE(vt) (vt)->screen.lines[MIN((vt)->curs.r, (vt)->screen.nline - 1)]
|
||||
|
||||
#define P0(x) (vt->pars[x])
|
||||
#define P1(x) (vt->pars[x]? vt->pars[x] : 1)
|
||||
#define CB(vt, m, a) ((vt)->cb? (vt)->cb(m, vt, a, (vt)->p) : (void)0)
|
||||
#define INESC ((vt)->state)
|
||||
|
||||
#define COMMON_VARS \
|
||||
TMTSCREEN *s = &vt->screen; \
|
||||
TMTPOINT *c = &vt->curs; \
|
||||
TMTLINE *l = CLINE(vt); \
|
||||
TMTCHAR *t = vt->tabs->chars
|
||||
|
||||
#define HANDLER(name) static void name (TMT *vt) { COMMON_VARS;
|
||||
|
||||
struct TMT{
|
||||
TMTPOINT curs, oldcurs;
|
||||
TMTATTRS attrs, oldattrs;
|
||||
|
||||
bool dirty, acs, decdraw, ignored;
|
||||
TMTSCREEN screen;
|
||||
TMTLINE *tabs;
|
||||
|
||||
TMTCALLBACK cb;
|
||||
void *p;
|
||||
const wchar_t *acschars;
|
||||
const wchar_t *decchars;
|
||||
|
||||
mbstate_t ms;
|
||||
size_t nmb;
|
||||
char mb[BUF_MAX + 1];
|
||||
|
||||
size_t pars[PAR_MAX];
|
||||
size_t npar;
|
||||
size_t arg;
|
||||
enum {S_NUL, S_ESC, S_ARG, S_SCS} state;
|
||||
};
|
||||
|
||||
static TMTATTRS defattrs = {.fg = TMT_COLOR_DEFAULT, .bg = TMT_COLOR_DEFAULT};
|
||||
static void writecharatcurs(TMT *vt, wchar_t w);
|
||||
|
||||
static wchar_t
|
||||
decchar(const TMT *vt, unsigned char c)
|
||||
{
|
||||
if (c > 94 && c < 127)
|
||||
return vt->decchars[c - 95];
|
||||
return (wchar_t)c;
|
||||
}
|
||||
|
||||
static wchar_t
|
||||
tacs(const TMT *vt, unsigned char c)
|
||||
{
|
||||
/* The terminfo alternate character set for ANSI. */
|
||||
static unsigned char map[] = {0020U, 0021U, 0030U, 0031U, 0333U, 0004U,
|
||||
0261U, 0370U, 0361U, 0260U, 0331U, 0277U,
|
||||
0332U, 0300U, 0305U, 0176U, 0304U, 0304U,
|
||||
0304U, 0137U, 0303U, 0264U, 0301U, 0302U,
|
||||
0263U, 0363U, 0362U, 0343U, 0330U, 0234U,
|
||||
0376U};
|
||||
for (size_t i = 0; i < sizeof(map); i++) if (map[i] == c)
|
||||
return vt->acschars[i];
|
||||
return (wchar_t)c;
|
||||
}
|
||||
|
||||
static void
|
||||
dirtylines(TMT *vt, size_t s, size_t e)
|
||||
{
|
||||
vt->dirty = true;
|
||||
for (size_t i = s; i < e; i++)
|
||||
vt->screen.lines[i]->dirty = true;
|
||||
}
|
||||
|
||||
static void
|
||||
clearline(TMT *vt, TMTLINE *l, size_t s, size_t e)
|
||||
{
|
||||
vt->dirty = l->dirty = true;
|
||||
for (size_t i = s; i < e && i < vt->screen.ncol; i++){
|
||||
l->chars[i].a = defattrs;
|
||||
l->chars[i].c = L' ';
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clearlines(TMT *vt, size_t r, size_t n)
|
||||
{
|
||||
for (size_t i = r; i < r + n && i < vt->screen.nline; i++)
|
||||
clearline(vt, vt->screen.lines[i], 0, vt->screen.ncol);
|
||||
}
|
||||
|
||||
static void
|
||||
scrup(TMT *vt, size_t r, size_t n)
|
||||
{
|
||||
n = MIN(n, vt->screen.nline - 1 - r);
|
||||
|
||||
if (n){
|
||||
TMTLINE *buf[n];
|
||||
|
||||
memcpy(buf, vt->screen.lines + r, n * sizeof(TMTLINE *));
|
||||
memmove(vt->screen.lines + r, vt->screen.lines + r + n,
|
||||
(vt->screen.nline - n - r) * sizeof(TMTLINE *));
|
||||
memcpy(vt->screen.lines + (vt->screen.nline - n),
|
||||
buf, n * sizeof(TMTLINE *));
|
||||
|
||||
clearlines(vt, vt->screen.nline - n, n);
|
||||
dirtylines(vt, r, vt->screen.nline);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
scrdn(TMT *vt, size_t r, size_t n)
|
||||
{
|
||||
n = MIN(n, vt->screen.nline - 1 - r);
|
||||
|
||||
if (n){
|
||||
TMTLINE *buf[n];
|
||||
|
||||
memcpy(buf, vt->screen.lines + (vt->screen.nline - n),
|
||||
n * sizeof(TMTLINE *));
|
||||
memmove(vt->screen.lines + r + n, vt->screen.lines + r,
|
||||
(vt->screen.nline - n - r) * sizeof(TMTLINE *));
|
||||
memcpy(vt->screen.lines + r, buf, n * sizeof(TMTLINE *));
|
||||
|
||||
clearlines(vt, r, n);
|
||||
dirtylines(vt, r, vt->screen.nline);
|
||||
}
|
||||
}
|
||||
|
||||
HANDLER(ed)
|
||||
size_t b = 0;
|
||||
size_t e = s->nline;
|
||||
|
||||
switch (P0(0)){
|
||||
case 0: b = c->r + 1; clearline(vt, l, c->c, vt->screen.ncol); break;
|
||||
case 1: e = c->r - 1; clearline(vt, l, 0, c->c); break;
|
||||
case 2: /* use defaults */ break;
|
||||
default: /* do nothing */ return;
|
||||
}
|
||||
|
||||
clearlines(vt, b, e - b);
|
||||
}
|
||||
|
||||
HANDLER(ich)
|
||||
size_t n = P1(0); /* XXX use MAX */
|
||||
if (n > s->ncol - c->c - 1) n = s->ncol - c->c - 1;
|
||||
|
||||
memmove(l->chars + c->c + n, l->chars + c->c,
|
||||
MIN(s->ncol - 1 - c->c,
|
||||
(s->ncol - c->c - n - 1)) * sizeof(TMTCHAR));
|
||||
clearline(vt, l, c->c, n);
|
||||
}
|
||||
|
||||
HANDLER(dch)
|
||||
size_t n = P1(0); /* XXX use MAX */
|
||||
if (n > s->ncol - c->c) n = s->ncol - c->c;
|
||||
else if (n == 0) return;
|
||||
|
||||
memmove(l->chars + c->c, l->chars + c->c + n,
|
||||
(s->ncol - c->c - n) * sizeof(TMTCHAR));
|
||||
|
||||
clearline(vt, l, s->ncol - n, s->ncol);
|
||||
/* VT102 manual says the attribute for the newly empty characters
|
||||
* should be the same as the last character moved left, which isn't
|
||||
* what clearline() currently does.
|
||||
*/
|
||||
}
|
||||
|
||||
HANDLER(el)
|
||||
switch (P0(0)){
|
||||
case 0: clearline(vt, l, c->c, vt->screen.ncol); break;
|
||||
case 1: clearline(vt, l, 0, MIN(c->c + 1, s->ncol - 1)); break;
|
||||
case 2: clearline(vt, l, 0, vt->screen.ncol); break;
|
||||
}
|
||||
}
|
||||
|
||||
HANDLER(sgr)
|
||||
#define FGBG(c) *(P0(i) < 40? &vt->attrs.fg : &vt->attrs.bg) = c
|
||||
for (size_t i = 0; i < vt->npar; i++) switch (P0(i)){
|
||||
case 0: vt->attrs = defattrs; break;
|
||||
case 1: case 22: vt->attrs.bold = P0(0) < 20; break;
|
||||
case 2: case 23: vt->attrs.dim = P0(0) < 20; break;
|
||||
case 4: case 24: vt->attrs.underline = P0(0) < 20; break;
|
||||
case 5: case 25: vt->attrs.blink = P0(0) < 20; break;
|
||||
case 7: case 27: vt->attrs.reverse = P0(0) < 20; break;
|
||||
case 8: case 28: vt->attrs.invisible = P0(0) < 20; break;
|
||||
case 10: case 11: vt->acs = P0(0) > 10; break;
|
||||
case 30: case 40: FGBG(TMT_COLOR_BLACK); break;
|
||||
case 31: case 41: FGBG(TMT_COLOR_RED); break;
|
||||
case 32: case 42: FGBG(TMT_COLOR_GREEN); break;
|
||||
case 33: case 43: FGBG(TMT_COLOR_YELLOW); break;
|
||||
case 34: case 44: FGBG(TMT_COLOR_BLUE); break;
|
||||
case 35: case 45: FGBG(TMT_COLOR_MAGENTA); break;
|
||||
case 36: case 46: FGBG(TMT_COLOR_CYAN); break;
|
||||
case 37: case 47: FGBG(TMT_COLOR_WHITE); break;
|
||||
case 39: case 49: FGBG(TMT_COLOR_DEFAULT); break;
|
||||
}
|
||||
}
|
||||
|
||||
HANDLER(rep)
|
||||
if (!c->c) return;
|
||||
wchar_t r = l->chars[c->c - 1].c;
|
||||
for (size_t i = 0; i < P1(0); i++)
|
||||
writecharatcurs(vt, r);
|
||||
}
|
||||
|
||||
HANDLER(dsr)
|
||||
char r[BUF_MAX + 1] = {0};
|
||||
snprintf(r, BUF_MAX, "\033[%zd;%zdR", c->r + 1, c->c + 1);
|
||||
CB(vt, TMT_MSG_ANSWER, (const char *)r);
|
||||
}
|
||||
|
||||
HANDLER(resetparser)
|
||||
memset(vt->pars, 0, sizeof(vt->pars));
|
||||
vt->state = vt->npar = vt->arg = vt->ignored = (bool)0;
|
||||
}
|
||||
|
||||
HANDLER(consumearg)
|
||||
if (vt->npar < PAR_MAX)
|
||||
vt->pars[vt->npar++] = vt->arg;
|
||||
vt->arg = 0;
|
||||
}
|
||||
|
||||
HANDLER(fixcursor)
|
||||
c->r = MIN(c->r, s->nline - 1);
|
||||
c->c = MIN(c->c, s->ncol - 1);
|
||||
}
|
||||
|
||||
static bool
|
||||
handlechar(TMT *vt, char i)
|
||||
{
|
||||
COMMON_VARS;
|
||||
|
||||
char cs[] = {i, 0};
|
||||
#define ON(S, C, A) if (vt->state == (S) && strchr(C, i)){ A; return true;}
|
||||
#define DO(S, C, A) ON(S, C, consumearg(vt); if (!vt->ignored) {A;} \
|
||||
fixcursor(vt); resetparser(vt););
|
||||
|
||||
DO(S_NUL, "\x07", CB(vt, TMT_MSG_BELL, NULL))
|
||||
DO(S_NUL, "\x08", if (c->c) c->c--)
|
||||
DO(S_NUL, "\x09", while (++c->c < s->ncol - 1 && t[c->c].c != L'*'))
|
||||
DO(S_NUL, "\x0a", c->r < s->nline - 1? (void)c->r++ : scrup(vt, 0, 1))
|
||||
DO(S_NUL, "\x0d", c->c = 0)
|
||||
ON(S_NUL, "\x1b", vt->state = S_ESC)
|
||||
ON(S_ESC, "\x1b", vt->state = S_ESC)
|
||||
DO(S_ESC, "H", t[c->c].c = L'*')
|
||||
DO(S_ESC, "7", vt->oldcurs = vt->curs; vt->oldattrs = vt->attrs)
|
||||
DO(S_ESC, "8", vt->curs = vt->oldcurs; vt->attrs = vt->oldattrs)
|
||||
ON(S_ESC, "+*)", vt->ignored = true; vt->state = S_ARG)
|
||||
ON(S_ESC, "(", vt->state = S_SCS)
|
||||
DO(S_SCS, "0", vt->decdraw = true)
|
||||
DO(S_SCS, "B", vt->decdraw = false)
|
||||
DO(S_ESC, "c", tmt_reset(vt))
|
||||
DO(S_ESC, "M", if (c->r) c->r--)
|
||||
ON(S_ESC, "[", vt->state = S_ARG)
|
||||
ON(S_ARG, "\x1b", vt->state = S_ESC)
|
||||
ON(S_ARG, ";", consumearg(vt))
|
||||
ON(S_ARG, "?", (void)0)
|
||||
ON(S_ARG, "0123456789", vt->arg = vt->arg * 10 + atoi(cs))
|
||||
DO(S_ARG, "A", c->r = MAX(c->r - P1(0), 0))
|
||||
DO(S_ARG, "B", c->r = MIN(c->r + P1(0), s->nline - 1))
|
||||
DO(S_ARG, "C", c->c = MIN(c->c + P1(0), s->ncol - 1))
|
||||
DO(S_ARG, "D", c->c = MIN(c->c - P1(0), c->c))
|
||||
DO(S_ARG, "E", c->c = 0; c->r = MIN(c->r + P1(0), s->nline - 1))
|
||||
DO(S_ARG, "F", c->c = 0; c->r = MAX(c->r - P1(0), 0))
|
||||
DO(S_ARG, "G", c->c = MIN(P1(0) - 1, s->ncol - 1))
|
||||
DO(S_ARG, "d", c->r = MIN(P1(0) - 1, s->nline - 1))
|
||||
DO(S_ARG, "Hf", c->r = P1(0) - 1; c->c = P1(1) - 1)
|
||||
DO(S_ARG, "I", while (++c->c < s->ncol - 1 && t[c->c].c != L'*'))
|
||||
DO(S_ARG, "J", ed(vt))
|
||||
DO(S_ARG, "K", el(vt))
|
||||
DO(S_ARG, "L", scrdn(vt, c->r, P1(0)))
|
||||
DO(S_ARG, "M", scrup(vt, c->r, P1(0)))
|
||||
DO(S_ARG, "P", dch(vt))
|
||||
DO(S_ARG, "S", scrup(vt, 0, P1(0)))
|
||||
DO(S_ARG, "T", scrdn(vt, 0, P1(0)))
|
||||
DO(S_ARG, "X", clearline(vt, l, c->c, P1(0) + c->c))
|
||||
DO(S_ARG, "Z", while (c->c && t[--c->c].c != L'*'))
|
||||
DO(S_ARG, "b", rep(vt));
|
||||
DO(S_ARG, "c", CB(vt, TMT_MSG_ANSWER, "\033[?6c"))
|
||||
DO(S_ARG, "g", if (P0(0) == 3) clearline(vt, vt->tabs, 0, s->ncol))
|
||||
DO(S_ARG, "m", sgr(vt))
|
||||
DO(S_ARG, "n", if (P0(0) == 6) dsr(vt))
|
||||
DO(S_ARG, "h", if (P0(0) == 25) CB(vt, TMT_MSG_CURSOR, "t"))
|
||||
DO(S_ARG, "i", (void)0)
|
||||
DO(S_ARG, "l", if (P0(0) == 25) CB(vt, TMT_MSG_CURSOR, "f"))
|
||||
DO(S_ARG, "s", vt->oldcurs = vt->curs; vt->oldattrs = vt->attrs)
|
||||
DO(S_ARG, "u", vt->curs = vt->oldcurs; vt->attrs = vt->oldattrs)
|
||||
DO(S_ARG, "@", ich(vt))
|
||||
|
||||
return resetparser(vt), false;
|
||||
}
|
||||
|
||||
static void
|
||||
notify(TMT *vt, bool update, bool moved)
|
||||
{
|
||||
if (update) CB(vt, TMT_MSG_UPDATE, &vt->screen);
|
||||
if (moved) CB(vt, TMT_MSG_MOVED, &vt->curs);
|
||||
}
|
||||
|
||||
static TMTLINE *
|
||||
allocline(TMT *vt, TMTLINE *o, size_t n, size_t pc)
|
||||
{
|
||||
TMTLINE *l = realloc(o, sizeof(TMTLINE) + n * sizeof(TMTCHAR));
|
||||
if (!l) return NULL;
|
||||
|
||||
clearline(vt, l, pc, n);
|
||||
return l;
|
||||
}
|
||||
|
||||
static void
|
||||
freelines(TMT *vt, size_t s, size_t n, bool screen)
|
||||
{
|
||||
for (size_t i = s; vt->screen.lines && i < s + n; i++){
|
||||
free(vt->screen.lines[i]);
|
||||
vt->screen.lines[i] = NULL;
|
||||
}
|
||||
if (screen) free(vt->screen.lines);
|
||||
}
|
||||
|
||||
TMT *
|
||||
tmt_open(size_t nline, size_t ncol, TMTCALLBACK cb, void *p,
|
||||
const wchar_t *acs)
|
||||
{
|
||||
TMT *vt = calloc(1, sizeof(TMT));
|
||||
if (!nline || !ncol || !vt) return free(vt), NULL;
|
||||
|
||||
/* ASCII-safe defaults for box-drawing characters. */
|
||||
vt->acschars = acs? acs : L"→←↑↓■◆▒°±▒┘┐┌└┼⎺───⎽├┤┴┬│≤≥π≠£•"; //L"><^v#+:o##+++++~---_++++|<>*!fo";
|
||||
vt->decchars = L" ◆▒\t\f\r\n°±\n\v┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£•";
|
||||
vt->cb = cb;
|
||||
vt->p = p;
|
||||
|
||||
if (!tmt_resize(vt, nline, ncol)) return tmt_close(vt), NULL;
|
||||
return vt;
|
||||
}
|
||||
|
||||
void
|
||||
tmt_close(TMT *vt)
|
||||
{
|
||||
free(vt->tabs);
|
||||
freelines(vt, 0, vt->screen.nline, true);
|
||||
free(vt);
|
||||
}
|
||||
|
||||
bool
|
||||
tmt_resize(TMT *vt, size_t nline, size_t ncol)
|
||||
{
|
||||
if (nline < 2 || ncol < 2) return false;
|
||||
if (nline < vt->screen.nline)
|
||||
freelines(vt, nline, vt->screen.nline - nline, false);
|
||||
|
||||
TMTLINE **l = realloc(vt->screen.lines, nline * sizeof(TMTLINE *));
|
||||
if (!l) return false;
|
||||
|
||||
size_t pc = vt->screen.ncol;
|
||||
vt->screen.lines = l;
|
||||
vt->screen.ncol = ncol;
|
||||
for (size_t i = 0; i < nline; i++){
|
||||
TMTLINE *nl = NULL;
|
||||
if (i >= vt->screen.nline)
|
||||
nl = vt->screen.lines[i] = allocline(vt, NULL, ncol, 0);
|
||||
else
|
||||
nl = allocline(vt, vt->screen.lines[i], ncol, pc);
|
||||
|
||||
if (!nl) return false;
|
||||
vt->screen.lines[i] = nl;
|
||||
}
|
||||
vt->screen.nline = nline;
|
||||
|
||||
vt->tabs = allocline(vt, vt->tabs, ncol, 0);
|
||||
if (!vt->tabs) return free(l), false;
|
||||
vt->tabs->chars[0].c = vt->tabs->chars[ncol - 1].c = L'*';
|
||||
for (size_t i = 0; i < ncol; i++) if (i % TAB == 0)
|
||||
vt->tabs->chars[i].c = L'*';
|
||||
|
||||
fixcursor(vt);
|
||||
dirtylines(vt, 0, nline);
|
||||
notify(vt, true, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
writecharatcurs(TMT *vt, wchar_t w)
|
||||
{
|
||||
COMMON_VARS;
|
||||
|
||||
#ifdef TMT_HAS_WCWIDTH
|
||||
extern int wcwidth(wchar_t c);
|
||||
if (wcwidth(w) > 1) w = TMT_INVALID_CHAR;
|
||||
if (wcwidth(w) < 0) return;
|
||||
#endif
|
||||
|
||||
CLINE(vt)->chars[vt->curs.c].c = w;
|
||||
CLINE(vt)->chars[vt->curs.c].a = vt->attrs;
|
||||
CLINE(vt)->dirty = vt->dirty = true;
|
||||
|
||||
if (c->c < s->ncol - 1)
|
||||
c->c++;
|
||||
else{
|
||||
c->c = 0;
|
||||
c->r++;
|
||||
}
|
||||
|
||||
if (c->r >= s->nline){
|
||||
c->r = s->nline - 1;
|
||||
scrup(vt, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static inline size_t
|
||||
testmbchar(TMT *vt)
|
||||
{
|
||||
mbstate_t ts = vt->ms;
|
||||
return vt->nmb? mbrtowc(NULL, vt->mb, vt->nmb, &ts) : (size_t)-2;
|
||||
}
|
||||
|
||||
static inline wchar_t
|
||||
getmbchar(TMT *vt)
|
||||
{
|
||||
wchar_t c = 0;
|
||||
size_t n = mbrtowc(&c, vt->mb, vt->nmb, &vt->ms);
|
||||
vt->nmb = 0;
|
||||
return (n == (size_t)-1 || n == (size_t)-2)? TMT_INVALID_CHAR : c;
|
||||
}
|
||||
|
||||
void
|
||||
tmt_write(TMT *vt, const char *s, size_t n)
|
||||
{
|
||||
TMTPOINT oc = vt->curs;
|
||||
n = n? n : strlen(s);
|
||||
|
||||
for (size_t p = 0; p < n; p++){
|
||||
if (handlechar(vt, s[p]))
|
||||
continue;
|
||||
else if (vt->acs)
|
||||
writecharatcurs(vt, tacs(vt, (unsigned char)s[p]));
|
||||
else if (vt->decdraw)
|
||||
writecharatcurs(vt, decchar(vt, (unsigned char)s[p]));
|
||||
else if (vt->nmb >= BUF_MAX)
|
||||
writecharatcurs(vt, getmbchar(vt));
|
||||
else{
|
||||
switch (testmbchar(vt)){
|
||||
case (size_t)-1: writecharatcurs(vt, getmbchar(vt)); break;
|
||||
case (size_t)-2: vt->mb[vt->nmb++] = s[p]; break;
|
||||
}
|
||||
|
||||
if (testmbchar(vt) <= MB_LEN_MAX)
|
||||
writecharatcurs(vt, getmbchar(vt));
|
||||
}
|
||||
}
|
||||
|
||||
notify(vt, vt->dirty, memcmp(&oc, &vt->curs, sizeof(oc)) != 0);
|
||||
}
|
||||
|
||||
const TMTSCREEN *
|
||||
tmt_screen(const TMT *vt)
|
||||
{
|
||||
return &vt->screen;
|
||||
}
|
||||
|
||||
const TMTPOINT *
|
||||
tmt_cursor(const TMT *vt)
|
||||
{
|
||||
return &vt->curs;
|
||||
}
|
||||
|
||||
void
|
||||
tmt_clean(TMT *vt)
|
||||
{
|
||||
for (size_t i = 0; i < vt->screen.nline; i++)
|
||||
vt->dirty = vt->screen.lines[i]->dirty = false;
|
||||
}
|
||||
|
||||
void
|
||||
tmt_reset(TMT *vt)
|
||||
{
|
||||
vt->curs.r = vt->curs.c = vt->oldcurs.r = vt->oldcurs.c = vt->acs = (bool)0;
|
||||
resetparser(vt);
|
||||
vt->attrs = vt->oldattrs = defattrs;
|
||||
memset(&vt->ms, 0, sizeof(vt->ms));
|
||||
clearlines(vt, 0, vt->screen.nline);
|
||||
CB(vt, TMT_MSG_CURSOR, "t");
|
||||
notify(vt, true, true);
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/* Copyright (c) 2017 Rob King
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the copyright holder nor the
|
||||
* names of contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS,
|
||||
* COPYRIGHT HOLDERS, OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef TMT_H
|
||||
#define TMT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <wchar.h>
|
||||
|
||||
/**** INVALID WIDE CHARACTER */
|
||||
#ifndef TMT_INVALID_CHAR
|
||||
#define TMT_INVALID_CHAR ((wchar_t)0xfffd)
|
||||
#endif
|
||||
|
||||
/**** INPUT SEQUENCES */
|
||||
#define TMT_KEY_UP "\033[A"
|
||||
#define TMT_KEY_DOWN "\033[B"
|
||||
#define TMT_KEY_RIGHT "\033[C"
|
||||
#define TMT_KEY_LEFT "\033[D"
|
||||
#define TMT_KEY_HOME "\033[H"
|
||||
#define TMT_KEY_END "\033[Y"
|
||||
#define TMT_KEY_INSERT "\033[L"
|
||||
#define TMT_KEY_BACKSPACE "\x08"
|
||||
#define TMT_KEY_ESCAPE "\x1b"
|
||||
#define TMT_KEY_BACK_TAB "\033[Z"
|
||||
#define TMT_KEY_PAGE_UP "\033[V"
|
||||
#define TMT_KEY_PAGE_DOWN "\033[U"
|
||||
#define TMT_KEY_F1 "\033OP"
|
||||
#define TMT_KEY_F2 "\033OQ"
|
||||
#define TMT_KEY_F3 "\033OR"
|
||||
#define TMT_KEY_F4 "\033OS"
|
||||
#define TMT_KEY_F5 "\033OT"
|
||||
#define TMT_KEY_F6 "\033OU"
|
||||
#define TMT_KEY_F7 "\033OV"
|
||||
#define TMT_KEY_F8 "\033OW"
|
||||
#define TMT_KEY_F9 "\033OX"
|
||||
#define TMT_KEY_F10 "\033OY"
|
||||
|
||||
/**** BASIC DATA STRUCTURES */
|
||||
typedef struct TMT TMT;
|
||||
|
||||
typedef enum{
|
||||
TMT_COLOR_DEFAULT = 0,
|
||||
TMT_COLOR_BLACK = 1,
|
||||
TMT_COLOR_RED,
|
||||
TMT_COLOR_GREEN,
|
||||
TMT_COLOR_YELLOW,
|
||||
TMT_COLOR_BLUE,
|
||||
TMT_COLOR_MAGENTA,
|
||||
TMT_COLOR_CYAN,
|
||||
TMT_COLOR_WHITE,
|
||||
TMT_COLOR_MAX
|
||||
} tmt_color_t;
|
||||
|
||||
typedef struct TMTATTRS TMTATTRS;
|
||||
struct TMTATTRS{
|
||||
bool bold;
|
||||
bool dim;
|
||||
bool underline;
|
||||
bool blink;
|
||||
bool reverse;
|
||||
bool invisible;
|
||||
tmt_color_t fg;
|
||||
tmt_color_t bg;
|
||||
};
|
||||
|
||||
typedef struct TMTCHAR TMTCHAR;
|
||||
struct TMTCHAR{
|
||||
wchar_t c;
|
||||
TMTATTRS a;
|
||||
};
|
||||
|
||||
typedef struct TMTPOINT TMTPOINT;
|
||||
struct TMTPOINT{
|
||||
size_t r;
|
||||
size_t c;
|
||||
};
|
||||
|
||||
typedef struct TMTLINE TMTLINE;
|
||||
struct TMTLINE{
|
||||
bool dirty;
|
||||
TMTCHAR chars[];
|
||||
};
|
||||
|
||||
typedef struct TMTSCREEN TMTSCREEN;
|
||||
struct TMTSCREEN{
|
||||
size_t nline;
|
||||
size_t ncol;
|
||||
|
||||
TMTLINE **lines;
|
||||
};
|
||||
|
||||
/**** CALLBACK SUPPORT */
|
||||
typedef enum{
|
||||
TMT_MSG_MOVED,
|
||||
TMT_MSG_UPDATE,
|
||||
TMT_MSG_ANSWER,
|
||||
TMT_MSG_BELL,
|
||||
TMT_MSG_CURSOR
|
||||
} tmt_msg_t;
|
||||
|
||||
typedef void (*TMTCALLBACK)(tmt_msg_t m, struct TMT *v, const void *r, void *p);
|
||||
|
||||
/**** PUBLIC FUNCTIONS */
|
||||
TMT *tmt_open(size_t nline, size_t ncol, TMTCALLBACK cb, void *p,
|
||||
const wchar_t *acs);
|
||||
void tmt_close(TMT *vt);
|
||||
bool tmt_resize(TMT *vt, size_t nline, size_t ncol);
|
||||
void tmt_write(TMT *vt, const char *s, size_t n);
|
||||
const TMTSCREEN *tmt_screen(const TMT *vt);
|
||||
const TMTPOINT *tmt_cursor(const TMT *vt);
|
||||
void tmt_clean(TMT *vt);
|
||||
void tmt_reset(TMT *vt);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,209 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <locale.h>
|
||||
#include <unistd.h>
|
||||
#include "tmt.h"
|
||||
#define HASHSIZE 2053
|
||||
#define MAXNAMELEN 256
|
||||
#define MAXDATALEN 8192
|
||||
struct terment {
|
||||
struct terment *next;
|
||||
char *name;
|
||||
TMT *vt;
|
||||
};
|
||||
|
||||
#define SETNODE 1
|
||||
#define WRITE 2
|
||||
#define READBUFF 0
|
||||
static struct terment *buffers[HASHSIZE];
|
||||
|
||||
unsigned long hash(char *str)
|
||||
/* djb2a */
|
||||
{
|
||||
unsigned long idx = 5381;
|
||||
int c;
|
||||
|
||||
while ((c = *str++))
|
||||
idx = ((idx << 5) + idx) + c;
|
||||
return idx % HASHSIZE;
|
||||
}
|
||||
|
||||
TMT *get_termentbyname(char *name) {
|
||||
struct terment *ret;
|
||||
for (ret = buffers[hash(name)]; ret != NULL; ret = ret->next)
|
||||
if (strcmp(name, ret->name) == 0)
|
||||
return ret->vt;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
TMT *set_termentbyname(char *name) {
|
||||
struct terment *ret;
|
||||
int idx;
|
||||
|
||||
idx = hash(name);
|
||||
for (ret = buffers[idx]; ret != NULL; ret = ret->next)
|
||||
if (strcmp(name, ret->name) == 0)
|
||||
return ret->vt;
|
||||
ret = (struct terment *)malloc(sizeof(*ret));
|
||||
ret->next = buffers[idx];
|
||||
ret->name = strdup(name);
|
||||
ret->vt = tmt_open(31, 100, NULL, NULL, L"→←↑↓■◆▒°±▒┘┐┌└┼⎺───⎽├┤┴┬│≤≥π≠£•");
|
||||
buffers[idx] = ret;
|
||||
return ret->vt;
|
||||
}
|
||||
|
||||
void dump_vt(TMT* outvt) {
|
||||
const TMTSCREEN *out = tmt_screen(outvt);
|
||||
const TMTPOINT *curs = tmt_cursor(outvt);
|
||||
int line, idx, maxcol, maxrow;
|
||||
bool bold = false;
|
||||
bool dim = false;
|
||||
bool underline = false;
|
||||
bool blink = false;
|
||||
bool reverse = false;
|
||||
bool invisible = false;
|
||||
bool intensitychg = false;
|
||||
tmt_color_t fg = TMT_COLOR_DEFAULT;
|
||||
tmt_color_t bg = TMT_COLOR_DEFAULT;
|
||||
wchar_t sgrline[30];
|
||||
size_t srgidx = 0;
|
||||
char colorcode = 0;
|
||||
wprintf(L"\033c");
|
||||
maxcol = 0;
|
||||
maxrow = 0;
|
||||
for (line = out->nline - 1; line >= 0; --line) {
|
||||
for (idx = out->ncol - 1; idx > maxcol; --idx) {
|
||||
if (out->lines[line]->chars[idx].c != L' ') {
|
||||
if (maxrow < line)
|
||||
maxrow = line;
|
||||
maxcol = idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (line = 0; line <= maxrow; line++) {
|
||||
for (idx = 0; idx <= maxcol; idx++) {
|
||||
sgrline[0] = L'\x00';
|
||||
intensitychg = false;
|
||||
if (out->lines[line]->chars[idx].a.bold != bold) {
|
||||
bold = out->lines[line]->chars[idx].a.bold;
|
||||
intensitychg = true; // Can't unbold without changing dim
|
||||
}
|
||||
if (out->lines[line]->chars[idx].a.dim != dim) {
|
||||
dim = out->lines[line]->chars[idx].a.dim;
|
||||
intensitychg = true; // Can't undim without changing bold
|
||||
}
|
||||
if (intensitychg) {
|
||||
intensitychg = false;
|
||||
wcscat(sgrline, L"22;");
|
||||
if (bold)
|
||||
wcscat(sgrline, L"1;");
|
||||
if (dim)
|
||||
wcscat(sgrline, L"2;");
|
||||
}
|
||||
if (out->lines[line]->chars[idx].a.underline != underline) {
|
||||
underline = out->lines[line]->chars[idx].a.underline;
|
||||
if (underline)
|
||||
wcscat(sgrline, L"4;");
|
||||
else
|
||||
wcscat(sgrline, L"24;");
|
||||
}
|
||||
if (out->lines[line]->chars[idx].a.blink != blink) {
|
||||
blink = out->lines[line]->chars[idx].a.blink;
|
||||
if (blink)
|
||||
wcscat(sgrline, L"5;");
|
||||
else
|
||||
wcscat(sgrline, L"25;");
|
||||
}
|
||||
if (out->lines[line]->chars[idx].a.reverse != reverse) {
|
||||
reverse = out->lines[line]->chars[idx].a.reverse;
|
||||
if (reverse)
|
||||
wcscat(sgrline, L"7;");
|
||||
else
|
||||
wcscat(sgrline, L"27;");
|
||||
}
|
||||
if (out->lines[line]->chars[idx].a.invisible != invisible) {
|
||||
invisible = out->lines[line]->chars[idx].a.invisible;
|
||||
if (invisible)
|
||||
wcscat(sgrline, L"8;");
|
||||
else
|
||||
wcscat(sgrline, L"28;");
|
||||
}
|
||||
if (out->lines[line]->chars[idx].a.fg != fg) {
|
||||
fg = out->lines[line]->chars[idx].a.fg;
|
||||
if (fg == TMT_COLOR_DEFAULT)
|
||||
colorcode = 39;
|
||||
else
|
||||
colorcode = 29 + fg;
|
||||
swprintf(sgrline + wcslen(sgrline), 4, L"%d;", colorcode);
|
||||
}
|
||||
if (out->lines[line]->chars[idx].a.bg != bg) {
|
||||
bg = out->lines[line]->chars[idx].a.bg;
|
||||
if (bg == TMT_COLOR_DEFAULT)
|
||||
colorcode = 49;
|
||||
else
|
||||
colorcode = 39 + bg;
|
||||
swprintf(sgrline + wcslen(sgrline), 4, L"%d;", colorcode);
|
||||
}
|
||||
if (sgrline[0] != 0) {
|
||||
sgrline[wcslen(sgrline) - 1] = 0; // Trim last ;
|
||||
wprintf(L"\033[%lsm", sgrline);
|
||||
}
|
||||
wprintf(L"%lc", out->lines[line]->chars[idx].c);
|
||||
}
|
||||
if (line < maxrow)
|
||||
wprintf(L"\r\n");
|
||||
}
|
||||
fflush(stdout);
|
||||
wprintf(L"\x1b[%ld;%ldH", curs->r + 1, curs->c + 1);
|
||||
fflush(stdout);
|
||||
idx = write(1, "\x00", 1);
|
||||
if (idx < 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
int cmd, length;
|
||||
setlocale(LC_ALL, "");
|
||||
char cmdbuf[MAXDATALEN];
|
||||
char currnode[MAXNAMELEN];
|
||||
TMT *currvt = NULL;
|
||||
TMT *outvt = NULL;
|
||||
stdin = freopen(NULL, "rb", stdin);
|
||||
if (stdin == NULL) {
|
||||
exit(1);
|
||||
}
|
||||
while (1) {
|
||||
length = fread(&cmd, 4, 1, stdin);
|
||||
if (length < 0)
|
||||
continue;
|
||||
length = cmd & 536870911;
|
||||
cmd = cmd >> 29;
|
||||
if (cmd == SETNODE) {
|
||||
currnode[length] = 0;
|
||||
cmd = fread(currnode, 1, length, stdin);
|
||||
if (cmd < 0)
|
||||
continue;
|
||||
currvt = set_termentbyname(currnode);
|
||||
} else if (cmd == WRITE) {
|
||||
if (currvt == NULL)
|
||||
currvt = set_termentbyname("");
|
||||
cmdbuf[length] = 0;
|
||||
cmd = fread(cmdbuf, 1, length, stdin);
|
||||
if (cmd < 0)
|
||||
continue;
|
||||
tmt_write(currvt, cmdbuf, length);
|
||||
} else if (cmd == READBUFF) {
|
||||
cmdbuf[length] = 0;
|
||||
cmd = fread(cmdbuf, 1, length, stdin);
|
||||
if (cmd < 0)
|
||||
continue;
|
||||
outvt = get_termentbyname(cmdbuf);
|
||||
if (outvt != NULL) {
|
||||
dump_vt(outvt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ dracut_install /etc/udev/hwdb.bin
|
||||
dracut_install /usr/share/hwdata/pci.ids
|
||||
dracut_install ibstat ibstatus
|
||||
dracut_install opainfo
|
||||
dracut_install /usr/lib/udev/rules.d/10-dm.rules /usr/sbin/dmsetup /usr/lib/udev/rules.d/95-dm-notify.rules
|
||||
#dracut_install /usr/lib/opa-fm/bin/opafmd
|
||||
#dracut_install /usr/sbin/opensm /usr/libexec/opensm-launch
|
||||
dracut_install /usr/lib64/libibverbs/libhfi1verbs-rdmav25.so /etc/libibverbs.d/hfi1verbs.driver /etc/libibverbs.d/mlx4.driver /etc/libibverbs.d/mlx5.driver /usr/lib64/libibverbs/libmlx4-rdmav25.so /usr/lib64/libibverbs/libmlx5-rdmav25.so
|
||||
|
||||
@@ -5,3 +5,4 @@ instmods cdc_ether
|
||||
instmods mptctl
|
||||
instmods mlx4_ib mlx5_ub ib_umad ib_ipoib
|
||||
instmods i40e hfi1 bnxt_en qed qede
|
||||
instmods dm-mod dm-log raid0 raid1 raid10 raid456 dm-raid dm-thin-pool dm-crypt dm-snapshot linear dm-era
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%define arch x86_64
|
||||
Version: 3.2.0
|
||||
Release: 1
|
||||
Release: 3
|
||||
Name: confluent-genesis-%{arch}
|
||||
BuildArch: noarch
|
||||
Summary: Genesis servicing image for confluent
|
||||
|
||||
Reference in New Issue
Block a user