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

Compare commits

...

302 Commits

Author SHA1 Message Date
Jarrod Johnson a54a9a5d09 Enable ipmi user if required
If redfish models ipmi as an account type, and user wants ipmi, add it to the account.
2022-06-27 14:34:37 -04:00
Jarrod Johnson 9b79da9522 Add a catch all to redfish for xcc
Newer xcc changes things yet again, but
we are comfortably in the firmware that can be bootstrapped
with redfish, so use that instead once we've cleared the
redfish incapable variants.
2022-06-27 14:12:51 -04:00
Jarrod Johnson 04c2b1a322 Provide an authenticated path for discovery registration 2022-06-22 16:47:40 -04:00
Jarrod Johnson 559a7ca7b8 Merge branch 'master' into remote_discovery 2022-06-21 14:56:56 -04:00
Jarrod Johnson 0a10311fea Add more verbose ssh connection feedback
Make the nature of connecting more explicit and errors
more reliably holding the session up and asking for relog
2022-06-17 15:35:06 -04:00
Jarrod Johnson 715bcb2f50 Prevent shell session from playing console output 2022-06-17 14:54:41 -04:00
Jarrod Johnson 8d5ad0d4a6 Fix EL7 spec file syntax 2022-06-17 13:11:08 -04:00
Jarrod Johnson 1387a67b61 Correct spec syntax error 2022-06-17 12:37:20 -04:00
Jarrod Johnson 4b08273379 Move EL7 back to python2
EL7's python3 support is barren and would require a lot of dependency
work.

Move EL7 back to python2 and will deprecate EL7 support when we ditch
python2
2022-06-17 12:10:44 -04:00
Jarrod Johnson e0c3232180 Fix syntax error in el7 prescript 2022-06-17 09:30:26 -04:00
Jarrod Johnson bd95f00680 Fix EL7 typeo in postscript 2022-06-17 09:08:12 -04:00
Jarrod Johnson bfecaa389d Adjust to correct ntp for 9 and up 2022-06-17 09:06:33 -04:00
Jarrod Johnson 7aca7a19f9 Install apiclient in a normal location for EL7 2022-06-15 16:42:50 -04:00
Jarrod Johnson c400671ad7 Add old profile compatible apiclient location 2022-06-15 16:39:55 -04:00
Jarrod Johnson c96955b369 Add catch-all packaging for EL7
EL7 now precompiles and flags it in the packaging
2022-06-15 16:34:57 -04:00
Jarrod Johnson 2bb519c158 Fix EL7 deployment compatibility 2022-06-15 16:10:57 -04:00
Jarrod Johnson e5c33cacd5 Change SuSE rpm name to pyyaml 2022-06-15 15:11:07 -04:00
Jarrod Johnson 03f0f16ed8 Change to dnspython for suse 2022-06-15 15:06:52 -04:00
Jarrod Johnson 0f815dc7d3 Switch to python3 for all builds 2022-06-15 15:00:19 -04:00
Jarrod Johnson dbdc99905d Update confluent server to python3 dependencies only 2022-06-15 14:31:32 -04:00
Jarrod Johnson 2d665fb816 Merge branch 'py2to3' 2022-06-15 12:42:04 -04:00
Jarrod Johnson 26e0fa8c91 Add swraid example to misc 2022-06-15 09:50:06 -04:00
Jarrod Johnson 83da1359df Add a sample vroc 'pre' script for reference 2022-06-15 08:40:23 -04:00
Jarrod Johnson 0f1581f8f7 Add a sample script to fetch user/pass info from confluent db 2022-06-10 14:18:35 -04:00
Jarrod Johnson 13d4d1dd98 Ensure that python3 will execute before doing migrate
If python3 is not the executable name, prevent the restoration
attempt from happening.
2022-06-10 11:19:21 -04:00
Jarrod Johnson db5c31030d Migrate DB on start
If python2 db format detected, use python2 to
dump to text, then python3 to restore to
get the python3 native version
2022-06-09 16:23:35 -04:00
Jarrod Johnson 6f484aab53 Allow restore to replace unsupported format
Going from python 2 to python 3, the dbm format
goes from the default to unsupported.

This allows a python3 confluentdbutil restore to handle
a python2 dump of unsupported format.
2022-06-09 15:49:06 -04:00
Jarrod Johnson 44cf56857e Add fallbacks to PyCrypto compatible names
Cryptodome is not always packaged with the
explicit form, fall back to the Crypto names
hoping that the user wouldn't be trying to use
PyCrypto is this day and age.
2022-06-09 10:48:12 -04:00
Jarrod Johnson 17e223e21c Suppress stray fakecmdline output 2022-06-09 09:15:56 -04:00
Jarrod Johnson 5ee6d0ca5d Fix ordering of arguments
The -text was between startdate and its argument
2022-06-09 08:30:03 -04:00
Jarrod Johnson 2825af19c4 Do not clutter the pem file with text 2022-06-09 08:04:15 -04:00
Jarrod Johnson e9ac43f49e Add autocons to bootloader config
In newer suse, the fake cmdline trick is fouled, so carry into
autoyast file
2022-06-09 07:55:42 -04:00
Jarrod Johnson 5ee0572f54 Change to using 'full' openssl ca
OpenSSL does not allow access to custom start
date without the full 'ca' facility, do the
work to set up the full CA and then
backdate certificates.

This does open the way for managed CA if required.
2022-06-08 17:10:11 -04:00
Jarrod Johnson 3da67db806 Fix early bailout of nodeconfig
Do not have nodeconfig bail out on first sign of trouble, attempt to continue.
2022-06-08 13:43:25 -04:00
Jarrod Johnson 826c9eedb0 Document -l in nodeconsole man page 2022-06-08 11:14:51 -04:00
Jarrod Johnson af19e98ce8 Remove use of eventlet in client side
The client side does not use eventlet, so allow
fallback to the normal socket and select
to keep the client module whole in the
face of that missing.
2022-06-08 11:03:56 -04:00
Jarrod Johnson 0dcd1442c9 Avoid nodeshell/noderun getting stuck on stdin
Some applications will tend to hang on stdin if
stdin is readable. Provide /dev/null to suppress that behavior.
2022-06-08 10:21:08 -04:00
Jarrod Johnson 2f904d10e9 Fix identity yaml parsing
Switch to sed rather than trying to use head.  There may or
may not be a second match, so need sed logic to understand
whether it's a match or not to keep or throw out.
2022-06-08 09:23:56 -04:00
Jarrod Johnson fdaecf2cbc Relay input errors to caller in dispatch 2022-06-07 13:36:31 -04:00
Jarrod Johnson 8bb565e938 Implement error on invalid attribute filter
This will cause noderange to probably error out.

Note that invalid attribute names starting with net, power, or custom are treated as always valid.
2022-06-07 11:45:06 -04:00
Jarrod Johnson 0b20e0b634 Fix confuent scan for python 3.9
Python 3.9 removes the scope from the string
address, put it back if missing since it's required
to actually be usable.
2022-06-07 10:55:56 -04:00
Jarrod Johnson b32c343a0c Switch to importlib for newer python
imp is deprecated, despite being much simpler,
so use importlib in python3.6+ distributions.
2022-06-07 10:35:08 -04:00
Jarrod Johnson 942121d73a Remove unavaiable clear from el9 diskless 2022-06-07 09:48:52 -04:00
Jarrod Johnson 55f42bc909 Add el9 imgutil 2022-06-07 09:06:59 -04:00
Jarrod Johnson 84037420d1 Add el9 tree for diskless 2022-06-06 13:18:37 -04:00
Jarrod Johnson e7bea0df45 Advance work on EL9 diskless support 2022-06-06 13:10:22 -04:00
Jarrod Johnson 94955dd091 Bring down track number
Use 2 heads with 4 sectors per track to get the track number down for
reasonable images.

Newer mtools forbids large track numbers.
2022-06-06 11:57:19 -04:00
Jarrod Johnson 739890471d Support newer python that removes fromstring
Python 3.9 does not understand the fromstring function anymore.
2022-06-06 11:37:39 -04:00
Jarrod Johnson 0baa2eecab Break out on deployment failure 2022-06-06 11:05:22 -04:00
Jarrod Johnson 46fec89460 Fix routed deployment api key usage 2022-06-06 10:15:54 -04:00
Jarrod Johnson 997bd3ac34 Implement outlet control for Eaton PDU 2022-06-03 17:06:37 -04:00
Jarrod Johnson 799050fea2 In-progress Eaton PDU support 2022-06-03 16:26:07 -04:00
Jarrod Johnson 80186f2c77 Add a delta pdu plugin
This has received limited testing, and the PDUs cannot be used with secure protocols
2022-06-03 10:24:44 -04:00
Jarrod Johnson 64b8893b80 Add a delta pdu plugin
This has received limited testing, and the PDUs cannot be used with secure protocols
2022-06-03 10:24:25 -04:00
Jarrod Johnson ae4b5c5100 Amend nodeping behavior and documentation 2022-06-02 16:04:19 -04:00
Jarrod Johnson f13df801fd Merge branch 'master' into remote_discovery 2022-05-31 13:53:47 -04:00
Jarrod Johnson 83acdab216 Amend python 3.9 compatibility fix
Avoid checking None for is_alive or isAlive
2022-05-31 11:23:27 -04:00
Jarrod Johnson b42dafb085 Fix python 3.9 compatibility
In python 3.9, the % is no longer included
in the first member of the address tuple.

Handle both scenarios by adding '%' in for versions
that seem to want to remove it.
2022-05-31 11:01:09 -04:00
Jarrod Johnson b5f5ba69d2 Fix python 3.9 compatibility
Python 3.9 renamed an attribute on a threading object, try
both names to support old and new python
2022-05-31 10:18:42 -04:00
Jarrod Johnson 6d07dac46c Fix ipmi missing logging state and trim el9 dependencies 2022-05-27 16:10:08 -04:00
Jarrod Johnson e4e859c798 Fix invocation of monotonic_time 2022-05-26 18:07:19 -04:00
Jarrod Johnson ff698ea06b Merge branch 'webauthn' 2022-05-26 17:42:15 -04:00
Jarrod Johnson 9c7e23f29e Add missing nodepower statuses 2022-05-26 17:41:54 -04:00
Jarrod Johnson b638bb69f9 Change webauthn to use confluent user db
This allows persistence with the rest of the
configuration.
2022-05-26 17:41:16 -04:00
Jarrod Johnson 7072a85d79 Transition to multi-authenticator support
Provide a way to store a plurality of keys for a user.
This enables use of 'backup' authenticators.
2022-05-26 16:34:18 -04:00
Jarrod Johnson 9b6114f523 Break if stuck in loop for over a minute 2022-05-26 15:01:47 -04:00
Jarrod Johnson 3c3d6bb314 Fix auth handling of the session/info 2022-05-25 17:40:35 -04:00
Jarrod Johnson f6a17b5f32 Have validate serve as session info request
This should facilitate login.

Further, provide a quick persistence for the
credential test backend
2022-05-25 15:58:20 -04:00
Jarrod Johnson 4773acb37d Adapt geist to work with python2 2022-05-25 10:58:35 -04:00
Jarrod Johnson e0079b5a86 Amend webauthn validation api 2022-05-25 10:58:02 -04:00
Jarrod Johnson c93f09bc91 Provide hook to get registered credentials
This has to relax the session in getting and requesting validation.
2022-05-24 19:17:31 -04:00
Jarrod Johnson 9b39c96135 Begin work on webauthn support
Provide appropriate registration options as a first
step.
2022-05-24 10:22:34 -04:00
Jarrod Johnson 48d46bcfae Add resolv setup to genesis 2022-05-24 08:27:36 -04:00
Jarrod Johnson 371942b0ce Add myhostname to improve name resolution errors in genesis 2022-05-24 08:17:04 -04:00
Jarrod Johnson a3cce144bc Extend manager principals for ssh
When doing osdeploy initialize -l (not recommended usually),
add on more forms of the name and ip addresses
to be consistent with node ssh behavior.
2022-05-24 07:24:56 -04:00
Jarrod Johnson f9d47bb0d3 Fix markingrequest as not implemented
The Geist PDU support inadvertently took down
unrelated parts of a request, fix by
properly showing not implemented in
a node specific way.
2022-05-20 08:35:43 -04:00
Jarrod Johnson 6068a29434 Merge branch 'master' of github.com:lenovo/confluent 2022-05-17 10:45:17 -04:00
Jarrod Johnson 8deadec7a6 Provide hook to override rungesis
Some applications may want much of the genesis
distribution, but want to have a bootstrap process
independent of confluent.  Provide hooks either
through the identity image or the boot image itself.
2022-05-17 10:44:07 -04:00
Jarrod Johnson da764fb99e Merge pull request #83 from erderial/master
ndeping man page
2022-05-16 14:36:38 -04:00
erderial 56b112519d Merge pull request #1 from erderial/Er-Nodeping
Adding nodeping man page
2022-05-16 21:25:39 +03:00
erderial abb85d5034 Created nodeping file 2022-05-16 21:21:42 +03:00
Jarrod Johnson eb99fbd8b2 Switch from clear buffer to sized memoryview
Use the recvmsg hint to mask out the buffer rather
than zeroing the entire buffer. This is more efficient
and further improves efficiency of parsing of the packet.
2022-05-14 18:35:15 -04:00
Jarrod Johnson 54741517f1 Clear DHCP buffer between recv
Very large PXE requests can leave residual
information that small, non-pxe requests will
interact poorly with, leading to spurious pxe-client
with cloned uuid of most recent large request.

Clearing between IO normalizes the state to avoid the bleed
over.
2022-05-14 18:12:19 -04:00
Jarrod Johnson 459c9a5210 Wait for a login attempt to run its course
If an existing session was not quite logged in, but
may be getting there, join in and wait for result
instead of starting over again.
2022-05-12 16:39:58 -04:00
Jarrod Johnson c328fea49a Cleaner output on cli
Based on feedback, remove the added 'inlet_' from pdu
output.

Also, fix geist plugin to block unsupported
features for now.
2022-05-11 16:01:43 -04:00
Jarrod Johnson caba650143 Add nodepower arguments for PDU operations 2022-05-11 14:59:54 -04:00
Jarrod Johnson e4d7be649a Fix single inlet operations 2022-05-11 13:31:19 -04:00
Jarrod Johnson d8a0f111db Implement changing PDU state on set 2022-05-11 08:53:24 -04:00
Jarrod Johnson 8dbcc804ed Pull outlets into the generic hierarchy
This will more easily facilitate adding pdus without dependent nodes.
2022-05-10 16:05:37 -04:00
Jarrod Johnson 6229cb23e8 Begin PDU implementation 2022-05-10 16:00:08 -04:00
Jarrod Johnson 0e879dc3de Add el7 to alternat squashfs name 2022-05-09 09:57:59 -04:00
Jarrod Johnson ee1a763c68 Add el7 to alternat squashfs name 2022-05-09 09:57:35 -04:00
Jarrod Johnson f138b4513c Merge branch 'master' into remote_discovery 2022-05-09 08:33:24 -04:00
Jarrod Johnson 2925c291cf Increase ipmi concurrency
128 can be a bit limiting, try 512 for improved responsiveness
2022-05-05 09:28:09 -04:00
Jarrod Johnson 86891eb2e5 Rework resolv watcher
Handle symlinks better and do not trigger overly
eagerly
2022-05-05 09:26:55 -04:00
Jarrod Johnson 754cdfdd78 Fix rpm spec syntax for imgutil 2022-05-04 14:35:03 -04:00
Jarrod Johnson ffadb9cc3a Add squashfs dependency
It has been confusing when imgutil cannot pack by default
2022-05-04 14:23:41 -04:00
Jarrod Johnson d3129847b4 Add recognition of Alma 9 and (presumably) rocky 9 2022-05-04 14:16:51 -04:00
Jarrod Johnson 548111f4d1 Fix executable status of the confluent casper hook 2022-05-04 12:11:24 -04:00
Jarrod Johnson 62b27b1fcc Back off diskless profile for ubuntu 22.04, not yet done 2022-05-04 11:42:14 -04:00
Jarrod Johnson bf37c05d71 Add ubuntu 22.04 to packaging 2022-05-04 11:31:03 -04:00
Jarrod Johnson e2e4014db3 Make ubuntu case insensitive on import
Ubuntu changes their minds about case of some files.
2022-05-04 11:01:59 -04:00
Jarrod Johnson b2feb62d8a Add ubuntu22.04 profile
Ubuntu 22.04 makes some changes, notably removing the
custom-installation hooks.

Change to injecting our modifications more directly to where the
custom-installation hooks used to be.
2022-05-04 09:25:49 -04:00
Jarrod Johnson 0301f5ca68 Fix imgutil pack expecting addpackagelist
The attribute only populates on a different subcommand,
so only process if the attribute exists.
2022-05-04 06:44:07 -04:00
Jarrod Johnson 98d22ffabc Add a utility to induce an inventory 2022-05-03 08:29:43 -04:00
Jarrod Johnson ac762c7ad5 Note addition of enclosure uuid to nodediscover
This will help sort out dense invorenments more easily, particularly
if no switch or chained.
2022-05-02 17:04:13 -04:00
Jarrod Johnson 48fd496637 Fix discovery uuid cross-reference
The SSDP does an endian scramble of the smm uuid,
fix it, and also update the core to prefer the
processed enclosure.uuid attribute.
2022-04-27 15:09:40 -04:00
Jarrod Johnson a738be3b53 Add license file to imgutil packaging 2022-04-26 08:18:31 -04:00
Jarrod Johnson 7da8a2ec4c Properly stage the license file for osdeploy 2022-04-26 08:16:12 -04:00
Jarrod Johnson e842c2ddc8 Add licensing to osdeploy package 2022-04-26 08:09:50 -04:00
Jarrod Johnson 46ffe3f5f2 Fix license directory name for confluent server 2022-04-26 08:00:13 -04:00
Jarrod Johnson 060f639ab3 Flag license files appropriately 2022-04-25 17:02:35 -04:00
Jarrod Johnson 69b3aca815 Remove errant copy of LICENSE file in client 2022-04-25 16:54:06 -04:00
Jarrod Johnson 9333c999c9 Adjust setup.py licensing for older setuptools 2022-04-25 16:48:31 -04:00
Jarrod Johnson 5538f99376 Support older setuptools for license packaging 2022-04-25 16:47:19 -04:00
Jarrod Johnson 6270d57729 Add tmt license terms to vtbufferd package 2022-04-25 16:31:51 -04:00
Jarrod Johnson 66f2ba98ec Set up confluent licensisng in setupdist context 2022-04-25 16:15:49 -04:00
Jarrod Johnson 7234310519 Move licensing to files instead of rpm description for vtbuffer 2022-04-25 16:05:22 -04:00
Jarrod Johnson 2c058fe634 Properly mark license files as license 2022-04-25 15:46:29 -04:00
Jarrod Johnson f009f6b8c2 Trim to appropriate content for the license 2022-04-25 15:18:27 -04:00
Jarrod Johnson 061d4217ff Further amend license handling 2022-04-25 14:46:50 -04:00
Jarrod Johnson 5d03eb8863 Try to normalize license paths 2022-04-25 14:15:07 -04:00
Jarrod Johnson 4d218757f3 Amend license carry over logic 2022-04-25 14:02:08 -04:00
Jarrod Johnson 71a698f771 Carry over license content from donor rpms 2022-04-25 13:52:36 -04:00
Jarrod Johnson 2f8846ee75 Further change directory traversal 2022-04-25 13:14:02 -04:00
Jarrod Johnson 128cb61eaa Fix directory traversal 2022-04-25 13:12:04 -04:00
Jarrod Johnson d8a2671dd6 Fix directory traversal 2022-04-25 13:11:43 -04:00
Jarrod Johnson 8947ab08da Add file to show the rpm versions at build time of genesis 2022-04-25 13:05:16 -04:00
Jarrod Johnson 14efec3603 Add file to show the rpm versions at build time of genesis 2022-04-25 13:04:45 -04:00
Jarrod Johnson 00589d06f5 Refactor relay and add to XCC/SMM
Extract relay versus direct determination and
inject into XCC and SMM handlers
2022-04-21 09:40:09 -04:00
Jarrod Johnson 334ce65919 Add facility for forwarding agent
Remote discovery will require that the reporting agent
provides port forwarding facility for https access, to
allow access without route to the managing confluent server.

This will allow the info reported to indicate use of this
forwarding facility and the base handler adds ability to
generically get the certificate.
2022-04-20 17:11:01 -04:00
Jarrod Johnson f6a16a89f2 Prevent spaces in nodenames 2022-04-20 08:55:54 -04:00
Jarrod Johnson 3297667ef8 Prune out backspaces
Some utilities backspace prior to printing anything,
prevent such applications from encroaching on the node name space.
2022-04-19 09:13:18 -04:00
Jarrod Johnson f4eec09a41 ADd a sample for converting mac to lla 2022-04-18 10:42:35 -04:00
Jarrod Johnson f15802a9aa Fix capture of non-selinux systems 2022-04-18 09:15:09 -04:00
Jarrod Johnson 449b2662fa Provide addpkglist support for imgutil build
This allows packagelists to be supplied to addon to the basic list, without
having to duplicate and modify
2022-04-14 14:35:26 -04:00
Jarrod Johnson fb9546cbd6 Kill redundant udevd prior to starting the root fs
Having multiple udevd produces ambiguity and confusing
2022-04-14 13:56:42 -04:00
Jarrod Johnson 193d37029f Relabel target path if selinux disabled on build host 2022-04-14 12:47:14 -04:00
Jarrod Johnson 5285691344 Correct fix for the python3 compatibility 2022-04-13 17:32:43 -04:00
Jarrod Johnson c533c973b1 Fix python3 compatibility with termhandler 2022-04-13 17:00:27 -04:00
Jarrod Johnson 212aa5c6e2 Add ability to unsubscribe from a terminal session 2022-04-13 16:44:01 -04:00
Jarrod Johnson 2bf9a6d415 Add support for consolidated term websocket
Since browsers can be stingy with websockets, have
the consoles all share a single websocket.
2022-04-13 16:08:13 -04:00
Jarrod Johnson 272398884c Appl consistent name to apiclient for EL8 2022-04-13 11:27:30 -04:00
Jarrod Johnson 79bc27bc7a Have el8 diskless systems add local repositories
This sets up local yum action by default.
2022-04-13 09:50:39 -04:00
Jarrod Johnson 8c54b6eaf8 Fix path to apiclient in add_local_repositories 2022-04-13 09:48:33 -04:00
Jarrod Johnson 1e77448b3a Fix comment support in pkglist for imgutil 2022-04-12 09:57:06 -04:00
Jarrod Johnson 918658c2ac Cleanup loop device allocation on image unpack
loop device was left configured after an unpack
2022-04-11 16:06:08 -04:00
Jarrod Johnson dfc9b0901d Improve feedback on no match found for log search 2022-04-08 12:59:36 -04:00
Jarrod Johnson 4b988e0633 Integrate logreader into nodeconsole as a feature 2022-04-08 12:29:45 -04:00
Jarrod Johnson 1ef0a61ed1 Move logreader to be a library in client 2022-04-08 12:12:02 -04:00
Jarrod Johnson b42a4bd722 Pretty-ify search interaction
Have the search text and feedback at bottom of screen.
2022-04-08 12:09:40 -04:00
Jarrod Johnson 198d8b3e47 Add flag to disable expression expansion
Some scenarios that do not use the {} feature may
find it difficult to pre-format.  Allow
such cases to opt out.
2022-04-08 10:28:11 -04:00
Jarrod Johnson fc64d2d93f Add a SLES variant to profiles for SUSE 2022-04-07 14:18:10 -04:00
Jarrod Johnson 4b8be913ee Logreader enhancements
Highlight all visible matches on screen

Restore cursor visibility if trashed.

Backspace in search string works.
2022-04-06 15:28:48 -04:00
Jarrod Johnson eba668b96c First pass at a search function
Currently, the 'n' for next result fails to find the next result
if it is currently on screen.  Also, the feedback on
failed search could use improvement.
2022-04-06 14:19:28 -04:00
Jarrod Johnson ded3cc4467 Fix logreader for python2/3 compatibility 2022-04-06 09:32:03 -04:00
Jarrod Johnson 7a0dee8af8 Fix keepalive/logout behavior in ws async
Use the websocket liveness as the keepalive, so
the reaper is not scheduled for such sockets.

Additionally, register the async thread as to be killed on logout.
2022-04-05 16:56:36 -04:00
Jarrod Johnson 105536656e Implement async session on websocket
This eliminates long polling and sets the stage to restore
socket sharing by terminals and shells.
2022-04-05 16:33:03 -04:00
Jarrod Johnson 8ef91c16c0 Do not let an existing /var/lib/confluent block the requisite chown 2022-04-05 09:35:05 -04:00
Jarrod Johnson 6fab77264c Make paginate to handle multiple screen clear
Grub moves cursor and clears as two separate commands,
so recognize that sequence as clear screen as well
2022-04-02 07:33:50 -04:00
Jarrod Johnson ab7f0e0528 Fix error checking in imginfo
imginfo was checking fread when it should
have been checking number of records.
2022-04-01 09:32:43 -04:00
Jarrod Johnson 777bdfac5c Fix the incorrect parameter name in setting password policy
The mistake was keeping the requisite rule from applying
2022-03-30 11:16:33 -04:00
Jarrod Johnson d651c29149 Add password expiration recovery to smm discovery
SMM discover can now also unexpire password during onboarding.
2022-03-30 08:50:08 -04:00
Jarrod Johnson b4f021cfad Fix omission in SSDP snoop 2022-03-30 08:24:25 -04:00
Jarrod Johnson 69a06a6923 Implement a password unexpiration mechanism in xcc
If the node has expired password, do what is necessary to unexpire the
password
to get through assignment.
2022-03-30 08:07:25 -04:00
Jarrod Johnson d214e7e442 Normalize blank strings in cfgdata
In cfgdata, make '' replaced by None for
consistent behavior for cleared and blanked
attributes.
2022-03-25 08:59:46 -04:00
Jarrod Johnson e4e15d87a7 Background redfish check on snoop
When snooping, if a redfish device comes along, background the
query so that it is unable to block the main SSDP receive routine.
2022-03-24 17:18:05 -04:00
Jarrod Johnson 21bfc29a89 Make more clear the default behavior when prompting 2022-03-24 11:18:37 -04:00
Jarrod Johnson 7a66567625 Add missing monotic dependency 2022-03-24 09:20:14 -04:00
Jarrod Johnson b830a317f6 Change suse from team to bonding
Suse more naturally supports team, and the industry push to team
has evaporated
2022-03-24 09:10:42 -04:00
Jarrod Johnson 4e4fe03b62 Properly honor explicit interface_names
A mistake resulted in plural interface_names being treated as singular.
2022-03-24 07:44:13 -04:00
Jarrod Johnson 08264f277b Fix location of apiclient in suse 2022-03-23 15:46:37 -04:00
Jarrod Johnson ac1ba5cbc5 Correct mistakes in the first pass of routed deployment 2022-03-18 16:04:29 -04:00
Jarrod Johnson acd8cb9055 First pass at media based routed deployment 2022-03-18 15:39:22 -04:00
Jarrod Johnson 8a3688c1d6 Fix mistake in pre.sh for relocation of apiclient 2022-03-18 12:09:43 -04:00
Jarrod Johnson cd3d248a78 Add identimage to rpm build 2022-03-17 13:04:24 -04:00
Jarrod Johnson 2299ccc32f Handle VROC devices in autoyast
At time of running pre, array is not assembled by default,
inject a scan to check for them.

VROC must not be specified by 'md' name, or yast thinks it
needs to own making it, and complains that the setup doesn't
specify members.
2022-03-17 12:19:42 -04:00
Jarrod Johnson bfd40b51de Correct name of ident_image api 2022-03-17 09:35:49 -04:00
Jarrod Johnson 94ab644f5c Create mechanism to create node identity images
These images are used in the flow of routed deployment.
2022-03-16 15:41:07 -04:00
Jarrod Johnson 40a187d2aa Reverse ordering of reboot and api arming.
Technically there's room for a race condition where boot is attempted
before the profile is ready, but it's highly unlikely.

Conversely, there is a potential confusing race condition today where
restarting a deploymennt without armed api causes
it to be disarmed before the boot is attempted.
2022-03-16 14:57:46 -04:00
Jarrod Johnson fdd3ec4233 Fix check for confluent service having started
Give confluent full chance to set things up prior
to proceeding.
2022-03-16 10:28:44 -04:00
Jarrod Johnson b2603aa1f8 Set ownership of /var/lib/confluent on installation
Some paths fail to initialize ownership earlier, give it
a head start
2022-03-16 10:26:16 -04:00
Jarrod Johnson b6034f2e71 Update to fix new profiles and accomodate old profiles
/etc/confluent/apiclient is expected by older profiles
2022-03-16 09:40:55 -04:00
Jarrod Johnson fe40d7c15e Fix mispelling of confluent 2022-03-16 09:01:22 -04:00
Jarrod Johnson 32081edec8 Workaround ':' format specifier syntax
Older python will break by assuming that
: always means a format expression is coming.

Move the field value fetch to format_field, and ascertain if some of the
expression was shunted to format specification
by mistake.
2022-03-11 12:21:09 -05:00
Jarrod Johnson dc0183fdf4 Add [] slicing/indexing to confluent attribute expression syntax
This permits expressions like:
node[:-3]
To say nodename, but leave out 3 chars.
Or:
node[3:]
To skip the first three characters.
2022-03-11 11:23:43 -05:00
Jarrod Johnson f168c4be2b Allow free ordering of noderange/arguments in nodeping
With nodeping, no concern about passing arguments to arbitrary subcommand, so
allow arguments to be anywhere.
2022-03-11 10:55:00 -05:00
Jarrod Johnson 2194ca9018 Create a nodeping script for quick ping wrapping 2022-03-11 10:23:11 -05:00
Jarrod Johnson ceada3b7d9 Provide API for using one-time shared secret to register api key
This permits long haul node api key registration over a single port. It cannot validate that
the requester is privileged, but the auto-invalidation
offsets the risk of subsequent users having read access to the remote mount.
2022-03-10 16:06:02 -05:00
Jarrod Johnson ad40c46509 Remove now-redundant genpasshmac.c file 2022-03-10 09:32:44 -05:00
Jarrod Johnson 301ed7a798 Fix mistake in b64e invocation 2022-03-10 09:15:26 -05:00
Jarrod Johnson b42e2e4932 Change to b64 output for hmac
base64 utility is not always available, so natively
use base64 format for hmac output.
2022-03-10 09:00:54 -05:00
Jarrod Johnson 61d037ae31 Combine genpasshmac with clortho
This permits saving on addons size by using the same
binary for both networked api grant and hmac api
grant.
2022-03-09 13:36:47 -05:00
Jarrod Johnson 6a30afa31e Have SSDP ignore multicast disabled interfaces 2022-03-09 11:01:01 -05:00
Jarrod Johnson 625434fcaf Fix mistake in deploycfg parsing
More strictly match the field name.
2022-03-08 16:29:49 -05:00
Jarrod Johnson a8c2f859e4 Add a genpasshmac utility
For far edge deployment, create utility
that can hmac a password for use in a REST
api call to skip need for tcp port 13001 access.
2022-03-08 16:27:37 -05:00
Jarrod Johnson 31dad09b0c Update makefile to build in sh256 to clortho 2022-03-08 14:46:33 -05:00
Jarrod Johnson 0abe978bd9 Implement hmac of apikey
For routed deployment, we have to preshare some information.

Additionally, the API arm mechanism gets too open ended.

Add support for using a shared secret over another
channel to do HMAC of a key to authenticate peer,
which has an alternate api arming mechanism
that is hardened.
2022-03-08 14:46:00 -05:00
Jarrod Johnson e67bab4f12 Place cap on api password length
No more than 48 characters should ever be in
an api token. Cap it to avoid outrageous crypt
behavior at large password length.
2022-03-08 09:15:13 -05:00
Jarrod Johnson 21c0372a5b Support get_full_net_config without serverip
When trying to get a configuration
without a network context, it would fail.

Now, as intended, it generates network configuration without autosense in such a case.
2022-03-07 15:28:04 -05:00
Jarrod Johnson 98d8aaffe8 Merge branch '3.4' 2022-03-07 15:22:54 -05:00
Jarrod Johnson ecd114ca5a Add script for setting up ssh
A frequent scenario is to 'refresh' ssh configuration toward the
end of:
-changing trust nodes
-Adding a collective member
-Repairing a broken configuration
-As part of 'confluent-ifying' a node that wasn't confluent deployed
2022-03-03 12:34:37 -05:00
Jarrod Johnson 5fb766e62b Move apiclient consistently to /opt/confluent/bin
It's more reasonable to have
it in a bin directory
2022-03-03 11:11:29 -05:00
Jarrod Johnson 76fdf59122 Change genesis functions location
Put it in a place consistent with more normal use.
2022-03-03 08:34:57 -05:00
Jarrod Johnson 003196bc9e Allow -o with data file
This makes things like ssh key signing easier.
2022-03-03 08:25:04 -05:00
Jarrod Johnson 15e7e4464e Keep known_hosts cleaner
When repeating osdeploy initialize
of local known_hosts, more
gracefeully avoid duplicate entries.
2022-03-02 16:04:01 -05:00
Jarrod Johnson 687136131e Place Confluent CA certs into TLS anchors
When processes may update the certificate authorities, the confluent
CA trust would be lost. Place it appropriately so that
update-ca-trust will keep it in the appropriate place.
2022-03-02 08:41:47 -05:00
Jarrod Johnson 5f610b64b7 Place Confluent CA certs into TLS anchors
When processes may update the certificate authorities, the confluent
CA trust would be lost. Place it appropriately so that
update-ca-trust will keep it in the appropriate place.
2022-03-02 08:40:27 -05:00
Jarrod Johnson 6f194f26c0 Fix contents and permissions
NetworkManager demands specific
permissions
2022-02-25 16:18:54 -05:00
Jarrod Johnson 71c60be659 Fix el8 dns configuration
The modification to add dns search must only be suggested
if the respective ip version section is enabled.
2022-02-25 15:22:45 -05:00
Jarrod Johnson 58a9aa03ef Add DNS domain to el8 network manager 2022-02-25 09:48:56 -05:00
Jarrod Johnson 19a370b0f5 Add explicit client version dependency 2022-02-25 07:31:12 -05:00
Jarrod Johnson 47a517aec1 Decrease retries to do https retries with bad TLS cert 2022-02-24 16:37:48 -05:00
Jarrod Johnson 1f7bd1a28a Fix autoconsole output on diskless 2022-02-24 16:27:32 -05:00
Jarrod Johnson 89cc49c4fc Add loginname to nodeshell man page 2022-02-24 16:08:50 -05:00
Jarrod Johnson 50da83b4f5 Fix api token message not being pushed 2022-02-24 15:56:29 -05:00
Jarrod Johnson 15f4cc085d Aggressively flush out error output 2022-02-24 15:46:38 -05:00
Jarrod Johnson d7df1e7891 Prevent users from dupe group memberships 2022-02-24 15:06:41 -05:00
Jarrod Johnson 1a5f5aea3a Try an alternative approach to autoconsole errors 2022-02-24 12:18:41 -05:00
Jarrod Johnson 7068287ba3 Fix autocons spurious output 2022-02-24 10:25:59 -05:00
Jarrod Johnson fb1f6b70bb Improve error handling on bad TLS cert
Bad TLS cert is a common problem, provide better feedback.
2022-02-24 09:27:40 -05:00
Jarrod Johnson 2c9be7a4c4 Remove slp snoop of XCC
SSDP snoop catches XCC, and do only
SSDP for consistent format of
snoop info coming into the
xcc handler.
2022-02-24 08:08:50 -05:00
Jarrod Johnson 24ef12e029 Disable autoconf of ipv6 in el
If autoconf is allowed when link is brought up, it scan
confuse redhat network configuration when it already finds
an ipv6 address.
2022-02-23 16:58:29 -05:00
Jarrod Johnson e390618dd9 Fix handling without olduuid in database 2022-02-23 10:13:06 -05:00
Jarrod Johnson 8f4846c248 Fix for partial returns
full_net_config may not always apply,
be sure to gracefully degrade.
2022-02-22 17:08:23 -05:00
Jarrod Johnson ac8918c2b9 Add ips to ssh principals
For any static address, also grant
certificate for that.
2022-02-22 16:48:58 -05:00
Jarrod Johnson fdc9d94408 Also register to run before coreos-ignition-setup-user
For coreos, make sure we preempt either name.
2022-02-22 14:30:48 -05:00
Jarrod Johnson 3cf9edeeb8 Stub out buffering for shell sessions
This is not yet handled anyway.

For future, establish norm of a nodeid
to prefix multiple distinct sessions.
2022-02-22 08:49:31 -05:00
Jarrod Johnson 8fab8238ed Disambiguate console from shell buffer
There is room for the console replay to get confused,
fix by fully qualifying the console name.
2022-02-18 17:31:13 -05:00
Jarrod Johnson 80293efe57 Address coverity false-positives
Technically, the fread won't reach the
length index, but change the order
anyway to reassure coverity.
2022-02-17 17:09:21 -05:00
Jarrod Johnson b463a53146 Cleanup per coverity
Fix a number of concerns that coverity reports
2022-02-17 17:05:00 -05:00
Jarrod Johnson 58b55b6ef6 Error on trying to double-add nodes or groups
Prevent user from repeatedly adding the
same group to a node or same node to a group.
2022-02-16 11:58:22 -05:00
Jarrod Johnson 33be75a9a2 Markup bandit exceptions
Apply bandit exceptions and explain
the rationale in each case
2022-02-16 09:10:33 -05:00
Jarrod Johnson f10a27fd7a Switch to mkstemp
Use mkstemp to more confidently reserve a filename as expected.
2022-02-15 17:13:04 -05:00
Jarrod Johnson 3f53cb939a Add mkdir -p to build script for genesis 2022-02-15 10:32:39 -05:00
Jarrod Johnson 961398e34e Bump version to cover 8.6 update 2022-02-15 10:29:18 -05:00
Jarrod Johnson 13b3b081cf Update to new rdma version in el8.6 2022-02-15 10:28:19 -05:00
Jarrod Johnson 3e6b67f9a7 Error if user omits attributes for options
If user indicates manipulation of
attributes through -c, -p, or -e,
but none are specified, print an error
instead.
2022-02-14 09:24:15 -05:00
Jarrod Johnson 93a5496899 Reject reverse range noderange
It has been stated that no one would want to do this
on purpose, and thus it should reject. The rationale
being that if no one wants to do this, but did it
anyway, they presumably made a mistake.

So now such attempts will be blocked.

Keep the dead logic around for now
in case a future opinion changes things back.
2022-02-11 15:20:41 -05:00
Jarrod Johnson bd428790ce Try for more informative messoge an expression syntax error 2022-02-11 14:51:53 -05:00
Jarrod Johnson f0c4943612 Merge branch 'master' of github.com:lenovo/confluent 2022-02-11 14:33:47 -05:00
Jarrod Johnson f547071d38 Warn user of unworkable syntax
When used in {} expressions, attributes must obey python syntax rules,
try out the attribute name and report the issue when it would be a problem.
2022-02-11 14:32:52 -05:00
Jarrod Johnson fbd3a442ac Support numeric owner/group in syncfile list 2022-02-08 17:38:01 -05:00
Jarrod Johnson b809514ef9 Fix osdeploy initialize dependency on master key
Make sure confluent has made /etc/confluent, and further always initialize the
encryption key, as it will almost certainly
be needed and easiest to just always
generate on first startup.
2022-02-08 16:40:41 -05:00
Jarrod Johnson b1032d8c4c Specify write mode for confluent_uuid file 2022-02-08 12:31:04 -05:00
Jarrod Johnson 00bedf6946 Shuffle confluenntuuid to earlier in copernicus
Currently, ssdp handler behavior needs confluentuuid first, if
it is to have any effect.
2022-02-08 12:06:52 -05:00
Jarrod Johnson e7b1791df3 Modify input in bandit-friendly way
bandit erroneously flags 'input' based
on possible python2-ism.  Avoid the
error by using 'getinput', making that
input or raw_input based on the python version.
2022-02-08 10:59:15 -05:00
Jarrod Johnson 6e03f6ee0a Correct syntax typo 2022-02-08 10:49:42 -05:00
Jarrod Johnson 358b719cec Implement deployment binding for new installs
When doing osdeploy initialize,
save the uuid and have deployment
targets specifically pair back with site via
uuid.
2022-02-08 10:41:27 -05:00
Jarrod Johnson b0b8ee128b Correct imgutil typo 2022-02-08 09:35:10 -05:00
Jarrod Johnson 094c97696f Merge tag '3.3.5'
Fixes for esxi7 and el7
2022-02-07 13:07:36 -05:00
Jarrod Johnson c2b66958d7 Update imgutil to not need rpm-python
EL7 does not have rpm for python3, use the cli instead for
such a case.
2022-02-07 12:23:44 -05:00
Jarrod Johnson 4a38a88136 Add recognition of RHEL9 media 2022-02-07 09:20:05 -05:00
Jarrod Johnson b1cbca414a Fix compatibility with CentOS7 for imgutil build -s 2022-02-07 08:39:45 -05:00
Jarrod Johnson 522d7e2b59 Fix configmanager issue witht more recent python3 2022-02-04 16:21:39 -05:00
Jarrod Johnson 7c169f635a Actually correct deb builder for vtbufferd 2022-02-04 16:06:12 -05:00
Jarrod Johnson 82f85b8aef Fix deb build of vtbuffered 2022-02-04 16:04:27 -05:00
Jarrod Johnson 97476f51da Remove some el7-irrelevant ramfs content 2022-02-03 12:26:09 -05:00
Jarrod Johnson 638a1b3587 Fix confignet for python2 2022-02-03 11:50:34 -05:00
Jarrod Johnson 5f4a565feb Use python2-compatible apiclient load for el7 diskless 2022-02-03 10:10:23 -05:00
Jarrod Johnson e94d0a5236 Fix for EL7 compatibility
Some python 3 exclusive assumptions were made.
Unfortunately, EL7 is python2
centric.
2022-02-03 10:09:28 -05:00
Jarrod Johnson 4665fb96a2 Fix relative hostpath for mismatched build platform
Relative path was erroneously
resulting in the host system
being consulted rather than the target.
2022-02-03 09:07:02 -05:00
Jarrod Johnson 33761e8870 Exlicitly add AES and DRBG for EL7 2022-02-02 16:54:41 -05:00
Jarrod Johnson 0bca6e0852 Apply more fixes for EL7 diskless 2022-02-02 15:22:58 -05:00
Jarrod Johnson c6816099f7 Add el7 diskless draft to packaging 2022-02-02 13:23:34 -05:00
Jarrod Johnson 71bcdc2e4e Continuation of work on draft el7 diskless 2022-02-02 13:11:38 -05:00
Jarrod Johnson eaa0921420 Draft EL7 diskless support 2022-02-02 13:09:26 -05:00
Jarrod Johnson 554e25d6cb Assume libcrypt.so.1 if not 2 in more generic way 2022-02-02 11:17:08 -05:00
Jarrod Johnson f346cae683 Use more widely known paths for libcrypt 2022-02-02 10:53:46 -05:00
Jarrod Johnson 1431f9ce13 Change to python 2/3 agnostic syntax 2022-02-02 08:37:22 -05:00
Jarrod Johnson 485c323608 Stage uploads in memory
The strategy of duping file descriptors
is inadequate. The copies share
identical offsets.

Fix this by reading the file once into
memory, and using BytesIO to fake a file.

This is relatively memory intensive in theory, but in practice
pyghmi library had been duping everything to memory
anyway, so it is a wash for now.
2022-01-27 17:29:17 -05:00
Jarrod Johnson 2bc080f4e0 Improve noderun message when command not found
Rather than raise an ominous looking trace, simply
state that thte command was not found and continue.
2022-01-26 11:35:43 -05:00
Jarrod Johnson effaba9661 Fix adding nodes with uuid for pxe
When adding nodes, they wouldn't be tracked for PXE
until id.uuid changed, or service restarted.

Fix by explicitly remapping all added and new names.
2022-01-26 08:35:10 -05:00
Jarrod Johnson fd00a70075 Merge branch 'master' of github.com:lenovo/confluent 2022-01-25 11:21:16 -05:00
Jarrod Johnson daa6b8208c Handle update being cancelled server side
The result may not be a nicely formed result, handle less
formed results.
2022-01-25 11:09:49 -05:00
Jarrod Johnson 9f071c1fdb Provide more details in example syncfiles 2022-01-20 14:10:30 -05:00
Jarrod Johnson 3f90c35bc9 Raise error on missing piece of syncfiles entry 2022-01-20 13:13:04 -05:00
Jarrod Johnson 456b43eeb7 Correctly align rtattr and nlmsg
When advancing through messages,
must pad to nearest multiple of
4.  This resolves erroneously landing on incorrect offsets.
2022-01-19 13:21:59 -05:00
Jarrod Johnson 2d13921d54 Amend deployment initialize for consistent ownership
Depending on the options selected/not selected, the
/var/lib/confluent directory may have been initialized
incorrectly.  Have all the potential paths begin with
ensuring /var/lib/confluent is correct, and then
use seteuid consistently to take care of the rest.
2022-01-14 15:08:19 -05:00
Jarrod Johnson 8b95e8f507 Improve osdeploy import error handling
Before, conditions that were unexpected
would result in hangs.  Now
transition to and report error state.
2022-01-14 09:23:54 -05:00
Jarrod Johnson fdc3d1c457 Broaden scope of logging issues with connecting to a leader.
Notably, certificate mismatch should be caught.
2022-01-14 07:39:25 -05:00
Jarrod Johnson 8d5d8ef5ed Have later syncfiles entries supersede earlier
This allows a vague entry to
be followed by a specific
entry to allow 'common' content
and then a specific override.
2022-01-13 16:52:10 -05:00
Jarrod Johnson aa816c0f1d Have attribute update support wildcards
This makes nodeattrib behave more like nodeconfig.
2022-01-13 13:27:07 -05:00
Jarrod Johnson b7a786dc83 Choose more accurate description of ambiguous switch situation
Technically, it need not be an aggregation, but link between switches generically covers it.
2022-01-12 09:31:44 -05:00
Jarrod Johnson f46fc9f7de Provide error about in-use dhcp
The events will more clearly indicate
when dnsmasq is not adequately configured.
2022-01-12 09:27:49 -05:00
Jarrod Johnson 2d95fb1d74 Improve 'ambiguous' notification in macmap.
When two nodes are ambiguous, provide more
info (mac) and also a suggestion of
which of the two appears most
trunk-like if one of them looks
suspiciously like a trunk.
2022-01-12 08:31:13 -05:00
Jarrod Johnson 04b462712d Tolerate redfish variations and fix loop
Some redfish omit the trailing /, and the correct move on non-redfish
is to continue to next candidate,
not break.
2022-01-11 16:53:46 -05:00
Jarrod Johnson 9c40569429 Add el9 to spec files 2022-01-11 16:02:47 -05:00
Jarrod Johnson 89187ef97d Merge branch '3.3' 2022-01-11 09:57:27 -05:00
Jarrod Johnson dbaae417e9 Have configbmc stall on TSM
TSM will stall out for an extended
period of time after a network change.

Accomodate by forcing a pause
on detection of TSM.
2022-01-10 12:01:15 -05:00
Jarrod Johnson 181f704331 Correct configbmc misidentification of lan chan
The lan channel get mac address was incorrect, change
to correctly request.
2022-01-10 09:29:40 -05:00
Jarrod Johnson 6df73d88b3 If SSDP happens but HTTPS not ready, ignore 2022-01-07 10:45:10 -05:00
Jarrod Johnson d5a8e881ab Defer handler processing
This was done in slp, do it also for ssdp. This
hopefully does it's best to flush udp buffer more
quickly in at scale scenarios.
2022-01-07 09:55:49 -05:00
Jarrod Johnson c475e4801f Fix incorrect quotes in autocons.c 2022-01-07 09:28:24 -05:00
Jarrod Johnson e54277f8f8 Fix ssdp snoop of XCC
SSDP was declaring victory too early, wait until
the right volume of information is confirmed available
before commiting to shared structures.
2022-01-07 09:00:44 -05:00
Jarrod Johnson d18d7592d3 Suppress extraneous slp trace errors
If a half-behaving peer appears, suppress the error and permit a retry later.
2022-01-07 07:17:27 -05:00
Jarrod Johnson 60398bf8f7 Add passive detection of XCC via SSDP
Take ssdp alive packets to pick up
XCCs outside of active scans.
2022-01-06 16:48:39 -05:00
Jarrod Johnson 22024b23b8 Prefer shortname for collective
When a colleective begins, default
to using the shortname, even
if gethostname returns a long one.
2022-01-06 12:38:15 -05:00
Jarrod Johnson 8da513b152 Fix TSM to lower case uuid for discovery
TSM were inconsistent by presenting
all caps UUID sometimes.
2022-01-05 12:50:31 -05:00
Jarrod Johnson 6f272b9d50 Skip generic redfish check
For now, it won't do anything but waste
resources.
2022-01-05 12:35:42 -05:00
Jarrod Johnson b3c0810574 Fix XCC preconfig with SSDP change
The SSDP unfortunately changes
some structure, change XCC
preconfig to expect the SSDP structure.
2022-01-05 12:10:34 -05:00
Jarrod Johnson b90756d2a6 Move XCC detection to SSDP
XCC will be retiring SLP and to prepare
for that, move over to SSDP.

This required that the snoop be wired
up to detection correctly, that slp
no longer report XCC, that
the model/serial number be moved to xcc handler, and various fixes to SSDP.
2022-01-05 11:43:04 -05:00
200 changed files with 9008 additions and 676 deletions
-25
View File
@@ -175,28 +175,3 @@
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
+5 -5
View File
@@ -21,17 +21,17 @@ def create_image(directory, image, label=None):
currsz = (currsz // 512 +1) * 512
datasz += currsz
datasz += ents * 32768
datasz = datasz // 512 + 1
datasz = datasz // 4096 + 1
with open(image, 'wb') as imgfile:
imgfile.seek(datasz * 512 - 1)
imgfile.seek(datasz * 4096 - 1)
imgfile.write(b'\x00')
if label:
subprocess.check_call(['mformat', '-i', image, '-v', label,
'-r', '16', '-d', '1', '-t', str(datasz),
'-s', '1','-h', '1', '::'])
'-s', '4','-h', '2', '::'])
else:
subprocess.check_call(['mformat', '-i', image, '-r', '16', '-d', '1', '-t',
str(datasz), '-s', '1','-h', '1', '::'])
str(datasz), '-s', '4','-h', '2', '::'])
# Some clustered filesystems will have the lock from mformat
# linger after close (mformat doesn't unlock)
# do a blocking wait for shared lock and then explicitly
@@ -58,4 +58,4 @@ if __name__ == '__main__':
label = None
if len(sys.argv) > 3:
label = sys.argv[3]
create_image(sys.argv[1], sys.argv[2], label)
create_image(sys.argv[1], sys.argv[2], label)
+4 -1
View File
@@ -105,7 +105,10 @@ if len(args) > 1:
requestargs=args[1:]
except:
pass
elif options.clear or options.environment or options.prompt:
sys.stderr.write('Attribute names required with specified options\n')
argparser.print_help()
exitcode = 400
if exitcode != 0:
sys.exit(exitcode)
+2 -4
View File
@@ -289,13 +289,11 @@ else:
for path in queryparms:
if options.comparedefault:
continue
rc = client.print_attrib_path(path, session, list(queryparms[path]),
rcode |= client.print_attrib_path(path, session, list(queryparms[path]),
NullOpt(), queryparms[path])
if rc:
sys.exit(rc)
if printsys == 'all' or printextbmc or printbmc or printallbmc:
if printbmc or not printextbmc:
rcode = client.print_attrib_path(
rcode |= client.print_attrib_path(
'/noderange/{0}/configuration/management_controller/extended/all'.format(noderange),
session, printbmc, options, attrprefix='bmc.')
if options.extra:
+12
View File
@@ -25,6 +25,7 @@ if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.sortutil as sortutil
import confluent.logreader as logreader
confettypath = os.path.join(os.path.dirname(sys.argv[0]), 'confetty')
argparser = optparse.OptionParser(
@@ -35,10 +36,21 @@ argparser = optparse.OptionParser(
"console")
argparser.add_option('-t', '--tile', action='store_true', default=False,
help='Tile console windows in the terminal')
argparser.add_option('-l', '--log', action='store_true', default=False,
help='Enter log replay mode instead of showing a live console')
(options, args) = argparser.parse_args()
if len(args) != 1:
argparser.print_help()
sys.exit(1)
if options.log:
logname = args[0]
if not os.path.exists(logname) and logname[0] != '/':
logname = os.path.join('/var/log/confluent/consoles', logname)
if not os.path.exists(logname):
sys.stderr.write('Unable to locate {0} on local system\n'.format(logname))
sys.exit(1)
logreader.replay_to_console(logname)
sys.exit(0)
if options.tile:
null = open('/dev/null', 'w')
nodes = []
+12 -11
View File
@@ -85,6 +85,18 @@ def main(args):
sys.stderr.write('Unrecognized arguments: ' + repr(extra) + '\n')
c = client.Command()
c.stop_if_noderange_over(args.noderange, args.maxnodes)
errnodes = set([])
if not args.clear and args.network and not args.prepareonly:
rc = c.simple_noderange_command(args.noderange, '/boot/nextdevice', 'network',
bootmode='uefi',
persistent=False,
errnodes=errnodes)
if errnodes:
sys.stderr.write(
'Unable to set boot device for following nodes: {0}\n'.format(
','.join(errnodes)))
return 1
rc |= c.simple_noderange_command(args.noderange, '/power/state', 'boot')
if args.clear:
cleararm(args.noderange, c)
clearpending(args.noderange, c)
@@ -139,18 +151,7 @@ def main(args):
armed = ''
print('{0}: {1}{2}'.format(node, profile, armed))
sys.exit(0)
errnodes = set([])
if args.network and not args.prepareonly:
rc = c.simple_noderange_command(args.noderange, '/boot/nextdevice', 'network',
bootmode='uefi',
persistent=False,
errnodes=errnodes)
if errnodes:
sys.stderr.write(
'Unable to set boot device for following nodes: {0}\n'.format(
','.join(errnodes)))
return 1
rc |= c.simple_noderange_command(args.noderange, '/power/state', 'boot')
return rc
return 0
+1
View File
@@ -48,6 +48,7 @@ columnmapping = {
'Advertised IP': 'otheripaddrs',
'Other IP': 'otheripaddrs',
}
#TODO: add chassis uuid
def print_disco(options, session, currmac, outhandler, columns):
+3 -3
View File
@@ -88,9 +88,9 @@ client.check_globbing(noderange)
def get_update_progress(session, url):
for res in session.read(url):
status = res['phase']
percent = res['progress']
detail = res['detail']
status = res.get('phase', 'error')
percent = res.get('progress', None)
detail = res.get('detail', repr(res)),
if status == 'error':
text = 'error!'
else:
+140
View File
@@ -0,0 +1,140 @@
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2017 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.
from collections import deque
import optparse
import os
import select
import signal
import subprocess
import sys
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.sortutil as sortutil
def run():
argparser = optparse.OptionParser(
usage="Usage: %prog [options] noderange")
argparser.add_option('-f', '-c', '--count', type='int', default=168,
help='Number of commands to run at a time')
argparser.add_option('-s', '--substitutename',
help='Use a different name other than the nodename for ping, with {}, it is the entire name evaluated as an expression, otherwise it is used as a suffix')
# among other things, FD_SETSIZE limits. Besides, spawning too many
# processes can be unkind for the unaware on memory pressure and such...
(options, args) = argparser.parse_args()
if len(args) < 1:
argparser.print_help()
sys.exit(1)
client.check_globbing(args[0])
concurrentprocs = options.count
c = client.Command()
currprocs = 0
all = set([])
pipedesc = {}
pendingexecs = deque()
exitcode = 0
nodemap = {}
if options.substitutename:
subname = options.substitutename
if '{' not in subname:
subname = '{node}' + subname
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
{'expression': subname}):
if 'error' in exp:
sys.stderr.write(exp['error'] + '\n')
exitcode |= exp.get('errorcode', 1)
ex = exp.get('databynode', ())
for node in ex:
nodemap[node] = ex[node]['value']
for rsp in c.read('/noderange/{0}/nodes/'.format(args[0])):
if 'error' in rsp:
sys.stderr.write(rsp['error'] + '\n')
exitcode |= rsp.get('errorcode', 1)
if rsp.get('item', {}).get('href', None):
node = rsp['item']['href'][:-1]
pingnode = nodemap.get(node, node)
cmdv = ['ping', '-c', '1', '-W', '1', pingnode]
if currprocs < concurrentprocs:
currprocs += 1
run_cmdv(pingnode, cmdv, all, pipedesc)
else:
pendingexecs.append((pingnode, cmdv))
if not all or exitcode:
sys.exit(exitcode)
rdy, _, _ = select.select(all, [], [], 10)
while all:
pernodeout = {}
for r in rdy:
desc = pipedesc[r]
node = desc['node']
data = True
while data and select.select([r], [], [], 0)[0]:
data = r.readline()
if not data:
pop = desc['popen']
ret = pop.poll()
if ret is not None:
exitcode = exitcode | ret
all.discard(r)
r.close()
if desc['type'] == 'stdout':
if ret:
print('{0}: no_ping'.format(node))
else:
print('{0}: ping'.format(node))
if pendingexecs:
node, cmdv = pendingexecs.popleft()
run_cmdv(node, cmdv, all, pipedesc)
for node in sortutil.natural_sort(pernodeout):
for line in pernodeout[node]:
line = client.stringify(line)
if options.nonodeprefix:
sys.stdout.write(line)
else:
sys.stdout.write('{0}: {1}'.format(node, line))
sys.stdout.flush()
if all:
rdy, _, _ = select.select(all, [], [], 10)
sys.exit(exitcode)
def run_cmdv(node, cmdv, all, pipedesc):
nopen = subprocess.Popen(
cmdv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
pipedesc[nopen.stdout] = {'node': node, 'popen': nopen,
'type': 'stdout'}
pipedesc[nopen.stderr] = {'node': node, 'popen': nopen,
'type': 'stderr'}
all.add(nopen.stdout)
all.add(nopen.stderr)
if __name__ == '__main__':
run()
+14 -6
View File
@@ -33,7 +33,7 @@ import confluent.client as client
argparser = optparse.OptionParser(
usage="Usage: %prog [options] <noderange> "
"([status|on|off|shutdown|boot|reset])")
"([status|on|off|shutdown|boot|reset|pdu_status|pdu_off|pdu_on])")
argparser.add_option('-p', '--showprevious', dest='previous',
action='store_true', default=False,
help='Show previous power state')
@@ -51,22 +51,27 @@ except IndexError:
client.check_globbing(noderange)
setstate = None
if len(args) > 1:
setstate = args[1]
if setstate == 'softoff':
setstate = 'shutdown'
elif not args[1] in ('stat', 'state', 'status'):
setstate = args[1]
if setstate not in (None, 'on', 'off', 'shutdown', 'boot', 'reset'):
if setstate not in (None, 'on', 'off', 'shutdown', 'boot', 'reset', 'pdu_status', 'pdu_stat', 'pdu_on', 'pdu_off', 'status', 'stat', 'state'):
argparser.print_help()
sys.exit(1)
session = client.Command()
exitcode = 0
session.add_precede_key('oldstate')
powurl = 'state'
if setstate and setstate.startswith('pdu_'):
setstate = setstate.replace('pdu_', '')
powurl = 'inlets/all'
if setstate in ('status', 'state', 'stat'):
setstate = None
if options.previous:
# get previous states
prev = {}
for rsp in session.read("/noderange/{0}/power/state".format(noderange)):
for rsp in session.read("/noderange/{0}/power/{1}".format(noderange, powurl)):
# gets previous (current) states
databynode = rsp["databynode"]
@@ -77,4 +82,7 @@ if options.previous:
# add dictionary to session
session.add_precede_dict(prev)
sys.exit(session.simple_noderange_command(noderange, '/power/state', setstate, promptover=options.maxnodes))
def outhandler(node, res):
for k in res[node]:
client.cprint('{0}: {1}: {2}'.format(node, k.replace('inlet_', ''), res[node][k]))
sys.exit(session.simple_noderange_command(noderange, '/power/{0}'.format(powurl), setstate, promptover=options.maxnodes, key='state', outhandler=outhandler))
+11 -2
View File
@@ -35,9 +35,12 @@ if path.startswith('/opt'):
import confluent.client as client
import confluent.sortutil as sortutil
devnull = None
def run():
global devnull
devnull = open(os.devnull, 'rb')
argparser = optparse.OptionParser(
usage="Usage: %prog [options] <noderange> <command expression>",
epilog="Expressions are the same as in attributes, e.g. "
@@ -131,8 +134,14 @@ def run():
def run_cmdv(node, cmdv, all, pipedesc):
nopen = subprocess.Popen(
cmdv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
try:
nopen = subprocess.Popen(
cmdv, stdin=devnull, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
except OSError as e:
if e.errno == 2:
sys.stderr.write('{0}: Unable to find local executable file "{1}"'.format(node, cmdv[0]))
return
raise
pipedesc[nopen.stdout] = {'node': node, 'popen': nopen,
'type': 'stdout'}
pipedesc[nopen.stderr] = {'node': node, 'popen': nopen,
+40 -25
View File
@@ -34,10 +34,11 @@ if path.startswith('/opt'):
import confluent.client as client
import confluent.sortutil as sortutil
devnull = None
def run():
global devnull
devnull = open(os.devnull, 'rb')
argparser = optparse.OptionParser(
usage="Usage: %prog [options] noderange commandexpression",
epilog="Expressions are the same as in attributes, e.g. "
@@ -52,6 +53,8 @@ def run():
help='Specify a custom port for ssh')
argparser.add_option('-s', '--substitutename',
help='Use a different name other than the nodename for ssh')
argparser.add_option('-x', '--noexpression', action='store_true',
help='Suppress expression expansion of command')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to run remote ssh command to, '
@@ -88,28 +91,39 @@ def run():
ex = exp.get('databynode', ())
for node in ex:
nodemap[node] = ex[node]['value']
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
{'expression': cmdstr}):
if 'error' in exp:
sys.stderr.write(exp['error'] + '\n')
exitcode |= exp.get('errorcode', 1)
ex = exp.get('databynode', ())
for node in ex:
cmd = ex[node]['value']
sshnode = nodemap.get(node, node)
if not isinstance(cmd, str) and not isinstance(cmd, bytes):
cmd = cmd.encode('utf-8')
cmdv = ['ssh']
if options.port:
cmdv += ['-p', '{0}'.format(options.port)]
if options.loginname:
cmdv += ['-l', options.loginname]
cmdv += [sshnode, cmd]
if currprocs < concurrentprocs:
currprocs += 1
run_cmdv(node, cmdv, all, pipedesc)
else:
pendingexecs.append((node, cmdv))
cmdparms = []
if options.noexpression:
for res in c.read('/noderange/{0}/nodes/'.format(args[0])):
if 'error' in res:
sys.stderr.write(res['error'] + '\n')
exitcode |= res.get('errorcode', 1)
break
node = res['item']['href'][:-1]
cmdparms.append((node, cmdstr))
else:
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
{'expression': cmdstr}):
if 'error' in exp:
sys.stderr.write(exp['error'] + '\n')
exitcode |= exp.get('errorcode', 1)
ex = exp.get('databynode', ())
for node in ex:
cmdparms.append((node, ex[node]['value']))
for node, cmd in cmdparms:
sshnode = nodemap.get(node, node)
if not isinstance(cmd, str) and not isinstance(cmd, bytes):
cmd = cmd.encode('utf-8')
cmdv = ['ssh']
if options.port:
cmdv += ['-p', '{0}'.format(options.port)]
if options.loginname:
cmdv += ['-l', options.loginname]
cmdv += [sshnode, cmd]
if currprocs < concurrentprocs:
currprocs += 1
run_cmdv(node, cmdv, all, pipedesc)
else:
pendingexecs.append((node, cmdv))
if not all or exitcode:
sys.exit(exitcode)
rdy, _, _ = select.select(all, [], [], 10)
@@ -149,6 +163,7 @@ def run():
if options.nonodeprefix:
sys.stdout.write(line)
else:
line = line.lstrip('\x08')
sys.stdout.write('{0}: {1}'.format(node, line))
sys.stdout.flush()
if all:
@@ -158,7 +173,7 @@ def run():
def run_cmdv(node, cmdv, all, pipedesc):
nopen = subprocess.Popen(
cmdv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
cmdv, stdin=devnull, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
pipedesc[nopen.stdout] = {'node': node, 'popen': nopen,
'type': 'stdout'}
pipedesc[nopen.stderr] = {'node': node, 'popen': nopen,
+11 -9
View File
@@ -42,9 +42,9 @@ _attraliases = {
}
try:
input = raw_input
getinput = raw_input
except NameError:
pass
getinput = input
class NestedDict(dict):
@@ -208,7 +208,7 @@ class Command(object):
def add_precede_dict(self, dict):
self._prevdict = dict
def handle_results(self, ikey, rc, res, errnodes=None):
def handle_results(self, ikey, rc, res, errnodes=None, outhandler=None):
if 'error' in res:
if errnodes is not None:
errnodes.add(self._currnoderange)
@@ -245,10 +245,12 @@ class Command(object):
node, val, self._prevdict[node]))
else:
cprint('{0}: {1}'.format(node, val))
elif outhandler:
outhandler(node, res)
return rc
def simple_noderange_command(self, noderange, resource, input=None,
key=None, errnodes=None, promptover=None, **kwargs):
key=None, errnodes=None, promptover=None, outhandler=None, **kwargs):
try:
self._currnoderange = noderange
rc = 0
@@ -262,13 +264,13 @@ class Command(object):
if input is None:
for res in self.read('/noderange/{0}/{1}'.format(
noderange, resource)):
rc = self.handle_results(ikey, rc, res, errnodes)
rc = self.handle_results(ikey, rc, res, errnodes, outhandler)
else:
self.stop_if_noderange_over(noderange, promptover)
kwargs[ikey] = input
for res in self.update('/noderange/{0}/{1}'.format(
noderange, resource), kwargs):
rc = self.handle_results(ikey, rc, res, errnodes)
rc = self.handle_results(ikey, rc, res, errnodes, outhandler)
self._currnoderange = None
return rc
except KeyboardInterrupt:
@@ -284,9 +286,9 @@ class Command(object):
nodename = list(self.read(
'/noderange/{0}/nodes/'.format(noderange)))[0].get('item', {}).get('href', None)
nodename = nodename[:-1]
p = input('Command is about to affect node {0}, continue (y/n)? '.format(nodename))
p = getinput('Command is about to affect node {0}, continue (y/n)? '.format(nodename))
else:
p = input('Command is about to affect {0} nodes, continue (y/n)? '.format(nsize))
p = getinput('Command is about to affect {0} nodes, continue (y/n)? '.format(nsize))
if p.lower() != 'y':
sys.stderr.write('Aborting at user request\n')
sys.exit(1)
@@ -401,7 +403,7 @@ class Command(object):
if fingerprint == khf[hostid]:
return
else:
replace = input(
replace = getinput(
"MISMATCHED CERTIFICATE DATA, ACCEPT NEW? (y/n):")
if replace not in ('y', 'Y'):
raise Exception("BAD CERTIFICATE")
@@ -10,12 +10,13 @@ import termios
import tty
def writeout(data):
done = False
if isinstance(data, str) and not isinstance(data, bytes):
data = data.encode('utf8')
try:
sys.stdout.buffer.write(data)
done = True
if hasattr(sys.stdout, 'buffer'):
sys.stdout.buffer.write(data)
else:
sys.stdout.write(data)
except IOError:
time.sleep(0.1)
pass
@@ -31,6 +32,7 @@ class LogReplay(object):
self.priordata = collections.deque([])
self.laststamp = None
self.needclear = False
self.lasttxt = ''
def _rewind(self, datasize=None):
curroffset = self.bin.tell() - 16
@@ -99,10 +101,8 @@ class LogReplay(object):
if reverse and self.bin.tell() < endoffset:
output += txtout
continue
if b'\x1b[2J' in txtout:
self.cleardata = txtout.split(b'\x1b[2J')
for idx in range(1, len(self.cleardata)):
self.cleardata[idx] = b'\x1b[2J' + self.cleardata[idx]
self.paginate(txtout)
if self.cleardata:
self.clearidx = 0
if not self.cleardata[0]:
self.cleardata = self.cleardata[1:]
@@ -118,8 +118,26 @@ class LogReplay(object):
break
if endoffset is not None and endoffset >= 0:
self.bin.seek(endoffset)
self.lasttxt = output
return output, 1
def paginate(self, txtout):
cleardata = [txtout]
nextcleardata = []
for sep in (b'\x1b[2J', b'\x1b[H\x1b[J'):
replacementcleardata = []
for txtout in cleardata:
nextcleardata = txtout.split(sep)
if len(nextcleardata) > 1:
for idx in range(1, len(nextcleardata)):
nextcleardata[idx] = sep + nextcleardata[idx]
replacementcleardata.extend(nextcleardata)
nextcleardata = []
cleardata = replacementcleardata
if len(cleardata) > 1:
self.cleardata = cleardata
return
def begin(self):
self.needclear = True
self.bin.seek(0)
@@ -127,8 +145,36 @@ class LogReplay(object):
def end(self):
self.bin.seek(0, 2)
def search(self, searchstr, skipfirst=False):
overlap = len(searchstr)
firstoffset = self.bin.tell()
lastoffset = firstoffset
output = self.lasttxt
while searchstr not in output:
output, delay = self.get_output()
if not self.cleardata and lastoffset == self.bin.tell():
self.bin.seek(firstoffset)
return b'', 0
lastoffset = self.bin.tell()
if skipfirst:
output, delay = self.get_output()
while searchstr not in output:
output, delay = self.get_output()
if not self.cleardata and lastoffset == self.bin.tell():
self.bin.seek(firstoffset)
return b'', 0
lastoffset = self.bin.tell()
clear = b'\x1b[2J\x1b[H'
txtchunks = self.lasttxt.split(searchstr)
output = clear + txtchunks[0]
txtchunks = txtchunks[1:]
while txtchunks:
output += b'\x1b[7m' + searchstr + b'\x1b[27m' + txtchunks[0]
txtchunks = txtchunks[1:]
return output, 0
def main(txtfile, binfile):
def _replay_to_console(txtfile, binfile):
replay = LogReplay(txtfile, binfile)
oldtcattr = termios.tcgetattr(sys.stdin.fileno())
tty.setraw(sys.stdin.fileno())
@@ -137,7 +183,9 @@ def main(txtfile, binfile):
reverse = False
skipnext = False
quitit = False
searchstr = None
writeout('\x1b[2J\x1b[;H')
prepend = ''
try:
while not quitit:
if not skipnext:
@@ -145,11 +193,18 @@ def main(txtfile, binfile):
skipnext = False
reverse = False
if newdata:
if prepend:
newdata = prepend + newdata
prepend = b''
writeout(newdata)
newdata = ''
writeout('\x1b]0;[Time: {0}]\x07'.format(
time.strftime('%m/%d %H:%M:%S', time.localtime(replay.laststamp))))
try:
sys.stdout.buffer.flush()
if hasattr(sys.stdout, 'buffer'):
sys.stdout.buffer.flush()
else:
sys.stdout.flush()
except IOError:
pass
while True:
@@ -173,32 +228,68 @@ def main(txtfile, binfile):
break
elif myinput.lower() == 'd':
writeout('\x1b];{0}\x07'.format(replay.debuginfo()))
if hasattr(sys.stdout, 'buffer'):
sys.stdout.buffer.flush()
else:
sys.stdout.flush()
elif myinput.lower() == '/':
sys.stdout.write('\x1b7\x1b[99999;0H\x1b[2K')
searchstr = ''
nxtchr = '/'
while '\r' not in searchstr:
sys.stdout.write(nxtchr)
sys.stdout.flush()
select.select((sys.stdin,), (), (), 86400)
nxtchr = sys.stdin.read(1)
if nxtchr in ('\x08', '\x7f'):
searchstr = searchstr[:-1]
nxtchr = '\x08 \x08'
else:
searchstr += nxtchr
if not isinstance(searchstr, bytes):
searchstr = searchstr.encode('utf8')
searchstr = searchstr[:-1]
newdata, delay = replay.search(searchstr)
if not newdata:
sys.stdout.write('\x1b[1K\rNo match found')
prepend = b'\x1b[1K\x1b8'
else:
sys.stdout.write('\x1b[1K\x1b8')
sys.stdout.flush()
skipnext = True
break
elif myinput.lower() == 'n' and searchstr:
newdata, delay = replay.search(searchstr, True)
if not newdata:
sys.stdout.write('\x1b7\x1b[99999;0H\x1b[2KNo more matches found')
sys.stdout.flush()
prepend = b'\x1b[1K\x1b8'
skipnext = True
break
else:
pass # print(repr(myinput))
except Exception:
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl ^ os.O_NONBLOCK)
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, oldtcattr)
writeout('\x1b[m')
writeout('\x1b[m\x1b[?25h\n')
raise
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl ^ os.O_NONBLOCK)
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, oldtcattr)
writeout('\x1b[m')
writeout('\x1b[m\x1b[?25h\n')
def replay_to_console(txtfile):
if os.path.exists(txtfile + '.cbl'):
binfile = txtfile + '.cbl'
else:
fileparts = txtfile.split('.')
prefix = '.'.join(fileparts[:-1])
binfile = prefix + '.cbl.' + fileparts[-1]
if not os.path.exists(binfile):
sys.stderr.write('Unable to locate cbl file\n')
sys.exit(1)
_replay_to_console(txtfile, binfile)
if __name__ == '__main__':
txtfile = sys.argv[1]
if len(sys.argv) > 2:
binfile = sys.argv[2]
else:
if os.path.exists(txtfile + '.cbl'):
binfile = txtfile + '.cbl'
else:
fileparts = txtfile.split('.')
prefix = '.'.join(fileparts[:-1])
binfile = prefix + '.cbl.' + fileparts[-1]
if not os.path.exists(binfile):
sys.stderr.write('Unable to locate cbl file\n')
sys.exit(1)
main(txtfile, binfile)
replay_to_console(sys.argv[1])
+7 -4
View File
@@ -53,13 +53,16 @@ class TermHandler(object):
connection = None
try:
connection, address = self.socket.accept()
connection.sendall("confetty control v1--\n")
connection.sendall(b"confetty control v1--\n")
cmd = connection.recv(8)
if 'GETWINID' == cmd:
connection.sendall(os.environ['WINDOWID'])
if b'GETWINID' == cmd:
winid = os.environ['WINDOWID']
if not isinstance(winid, bytes):
winid = winid.encode('utf8')
connection.sendall(winid)
connection.close()
except BaseException:
pass
finally:
if connection is not None:
connection.close()
connection.close()
+12 -4
View File
@@ -19,8 +19,12 @@ import array
import ctypes
import ctypes.util
import confluent.tlv as tlv
import eventlet.green.socket as socket
import eventlet.green.select as select
try:
import eventlet.green.socket as socket
import eventlet.green.select as select
except ImportError:
import socket
import select
from datetime import datetime
import json
import os
@@ -240,8 +244,12 @@ def recv(handle):
cdata = cmsgarr[CMSG_LEN(0).value:]
data = rawbuffer[:i]
if cmsg.cmsg_level == socket.SOL_SOCKET and cmsg.cmsg_type == SCM_RIGHTS:
filehandles.fromstring(bytes(
cdata[:len(cdata) - len(cdata) % filehandles.itemsize]))
try:
filehandles.fromstring(bytes(
cdata[:len(cdata) - len(cdata) % filehandles.itemsize]))
except AttributeError:
filehandles.frombytes(bytes(
cdata[:len(cdata) - len(cdata) % filehandles.itemsize]))
data = json.loads(bytes(data))
return ClientFile(data['filename'], data['mode'], filehandles[0])
else:
+8 -6
View File
@@ -24,17 +24,17 @@ a confluent server.
%setup -n %{name}-%{version} -n %{name}-%{version}
%build
%if "%{dist}" == ".el8"
python3 setup.py build
%else
%if "%{dist}" == ".el7"
python2 setup.py build
%else
python3 setup.py build
%endif
%install
%if "%{dist}" == ".el8"
python3 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-scripts=/opt/confluent/bin --install-purelib=/opt/confluent/lib/python
%else
%if "%{dist}" == ".el7"
python2 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-scripts=/opt/confluent/bin --install-purelib=/opt/confluent/lib/python
%else
python3 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-scripts=/opt/confluent/bin --install-purelib=/opt/confluent/lib/python
%endif
@@ -42,4 +42,6 @@ python2 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUI
rm -rf $RPM_BUILD_ROOT
%files -f INSTALLED_FILES
%license /opt/confluent/share/licenses/confluent_client/LICENSE
%defattr(-,root,root)
/opt/confluent
@@ -22,6 +22,11 @@ a new session.
Use tmux to arrange consoles of the given noderange into a tiled layout on
the terminal screen
* `-l`, `--log`:
Perform a log reply on the current, local log in /var/log/confluent/consoles.
If in collective mode, this only makes sense to use on the current collective
manager at this time.
## ESCAPE SEQUENCE COMMANDS
While connected to a console, a number of commands may be performed through escape
+47
View File
@@ -0,0 +1,47 @@
nodeping(8) -- Pings a node or a noderange.
==============================
## SYNOPSIS
`nodeping [options] noderange`
## DESCRIPTION
**nodeping** is a command that pings the default NIC on a node.
It can also be used with the `-s` flag to change the ping location to something that is 'non primary'
## OPTIONS
* ` -f` COUNT, `-c` COUNT, --count=COUNT
Number of commands to run at a time
* `-h`, `--help`:
Show help message and exit
* `-s` SUBSTITUTENAME, --substitutename=SUBSTITUTENAME
Use a different name other than the nodename for ping. This may be a
expression, such as {bmc} or, if no { character is present, it is treated as a suffix. -s -eth1 would make n1 become n1-eth1, for example.
## EXAMPLES
* Pinging a node :
`# nodeping <node>`
`node : ping`
* Pinging a group:
`# nodeping <groupname>`
`Node1 : ping
Node2 : ping
Node3 : ping`
* Pinging BMC on a node:
`# nodeping -s {bmc} <noderange>`
` Node-bmc : ping`
* Pinging by specifying a suffix:
`# nodeping d1-d4 -s -eth1`
`d2-eth1: no_ping`
`d1-eth1: no_ping`
`d3-eth1: no_ping`
`d4-eth1: no_ping`
* Fail to ping node:
`# nodeping <node>`
`node : no_ping`
+3
View File
@@ -24,6 +24,9 @@ respond.
* `reset`: Request immediate reset of nodes of the noderange. Nodes that are
off will not react to this request.
* `status`: Behave identically to having no argument passed at all.
* `pdu_status`: Query state of associated PDU outlets, if configured.
* `pdu_on`: Energize all PDU outlets associated with the noderange.
* `pdu_off`: De-energize all PDU outlets associated with the noderange.
## OPTIONS
+3
View File
@@ -17,6 +17,9 @@ as stderr, unlike psh which combines all stdout and stderr into stdout.
* `-c COUNT`, `-f COUNT`, `--count=COUNT`
Specify the maximum number of instances to run concurrently
* `-l LOGINNAME`, `--loginname=LOGINNAME`
Username to use when connecting, defaults to current user.
* `-m MAXNODES`, `--maxnodes=MAXNODES`
Specify a maximum number of nodes to run remote ssh command to, prompting
if over the threshold
+3 -1
View File
@@ -1,7 +1,9 @@
from setuptools import setup
import os
data_files = [('/etc/profile.d', ['confluent_env.sh', 'confluent_env.csh'])]
data_files = [('/etc/profile.d', ['confluent_env.sh', 'confluent_env.csh']),
('/opt/confluent/share/licenses/confluent_client/', ['LICENSE'])
]
try:
scriptlist = ['bin/{0}'.format(d) for d in os.listdir('bin/')]
data_files.append(('/opt/confluent/share/man/man1', ['man/man1/' + x for x in os.listdir('man/man1')]))
+1
View File
@@ -5,6 +5,7 @@ if [ "$NUMCOMMITS" != "$VERSION" ]; then
fi
sed -e "s/#VERSION#/$VERSION/" confluent_osdeploy.spec.tmpl > confluent_osdeploy.spec
cd ..
cp ../LICENSE .
tar Jcvf confluent_osdeploy.tar.xz confluent_osdeploy
mv confluent_osdeploy.tar.xz ~/rpmbuild/SOURCES/
cd -
@@ -18,12 +18,15 @@ import time
class InvalidApiKey(Exception):
pass
def msg_align(len):
return (len + 3) & ~3
cryptname = ctypes.util.find_library('crypt')
if not cryptname:
if os.path.exists('/lib64/libcrypt.so.1'):
cryptname = 'libcrypt.so.1'
elif os.path.exists('/lib64/libcrypt.so.2'):
if os.path.exists('/lib64/libcrypt.so.2') or os.path.exists('/usr/lib64/libcrypt.so.2'):
cryptname = 'libcrypt.so.2'
else:
cryptname = 'libcrypt.so.1'
c_libcrypt = ctypes.CDLL(cryptname)
c_crypt = c_libcrypt.crypt
c_crypt.argtypes = (ctypes.c_char_p, ctypes.c_char_p)
@@ -59,8 +62,8 @@ def get_my_addresses():
break
if rtatyp == 1:
addrs.append((fam, rta[4:rtalen], plen, ridx))
rta = rta[rtalen:]
v = v[length:]
rta = rta[msg_align(rtalen):]
v = v[msg_align(length):]
return addrs
@@ -129,8 +132,11 @@ def scan_confluents():
current['mgtiface'] = line.replace(b'MGTIFACE: ', b'').strip().decode('utf8')
if len(peer) > 2:
current['myidx'] = peer[-1]
srvs[peer[0]] = current
srvlist.append(peer[0])
currip = peer[0]
if currip.startswith('fe80::') and '%' not in currip:
currip = '{0}%{1}'.format(currip, peer[-1])
srvs[currip] = current
srvlist.append(currip)
r = select.select((s4, s6), (), (), 2)
if r:
r = r[0]
@@ -180,7 +186,7 @@ def get_net_apikey(nodename, mgr):
return ''
def get_apikey(nodename, hosts):
def get_apikey(nodename, hosts, errout=None):
apikey = ""
if os.path.exists('/etc/confluent/confluent.apikey'):
apikey = open('/etc/confluent/confluent.apikey').read().strip()
@@ -204,8 +210,10 @@ def get_apikey(nodename, hosts):
if apikey:
break
if not apikey:
sys.stderr.write(
"Failed getting API token, check deployment.apiarmed attribute on {}\n".format(nodename))
errmsg = "Failed getting API token, check deployment.apiarmed attribute on {}\n".format(nodename)
sys.stderr.write(errmsg)
if errout:
errout.write(errmsg)
time.sleep(10)
with open('/etc/confluent/confluent.apikey', 'w+') as apiout:
apiout.write(apikey)
@@ -214,7 +222,11 @@ def get_apikey(nodename, hosts):
return apikey
class HTTPSClient(client.HTTPConnection, object):
def __init__(self, usejson=False, port=443, host=None):
def __init__(self, usejson=False, port=443, host=None, errout=None):
self.errout = None
if errout:
self.errout = open(errout, 'w')
self.errout.flush()
self.stdheaders = {}
mgtiface = None
if usejson:
@@ -257,7 +269,7 @@ class HTTPSClient(client.HTTPConnection, object):
for line in info:
if line.startswith('deploy_server: ') or line.startswith('deploy_server_v6: '):
self.hosts.append(line.split(': ', 1)[1])
self.stdheaders['CONFLUENT_APIKEY'] = get_apikey(node, self.hosts)
self.stdheaders['CONFLUENT_APIKEY'] = get_apikey(node, self.hosts, errout=self.errout)
if mgtiface:
self.stdheaders['CONFLUENT_MGTIFACE'] = mgtiface
self.port = port
@@ -325,7 +337,15 @@ class HTTPSClient(client.HTTPConnection, object):
self.stdheaders['Host'] = '{0}'.format(host)
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.check_hostname = True
self.sock = ctx.wrap_socket(psock, server_hostname=host)
try:
self.sock = ctx.wrap_socket(psock, server_hostname=host)
except ssl.SSLError:
errmsg = 'Error validating certificate on deployer (try `osdeploy initialize -t` on the deployment server {0})\n'.format(host)
sys.stderr.write(errmsg)
if self.errout:
self.errout.write(errmsg)
self.errout.flush()
sys.exit(1)
def grab_url(self, url, data=None, returnrsp=False):
return self.grab_url_with_status(url, data, returnrsp)[1]
@@ -351,7 +371,7 @@ class HTTPSClient(client.HTTPConnection, object):
with open('/etc/confluent/confluent.apikey', 'w+') as akfile:
akfile.write('')
self.stdheaders['CONFLUENT_APIKEY'] = get_apikey(
self.node, [self.host])
self.node, [self.host], errout=self.errout)
raise Exception(rsp.read())
if __name__ == '__main__':
@@ -380,22 +400,28 @@ if __name__ == '__main__':
data = sys.argv.pop(data)
except ValueError:
data = None
try:
errout = sys.argv.index('-e')
sys.argv.pop(errout)
errout = sys.argv.pop(errout)
except ValueError:
errout = None
if len(sys.argv) > 2 and os.path.exists(sys.argv[-1]):
data = open(sys.argv[-1]).read()
if outbin:
with open(outbin, 'ab+') as outf:
reader = HTTPSClient(usejson=usejson).grab_url(
reader = HTTPSClient(usejson=usejson, errout=errout).grab_url(
sys.argv[1], data, returnrsp=True)
chunk = reader.read(16384)
while chunk:
outf.write(chunk)
chunk = reader.read(16384)
sys.exit(0)
if len(sys.argv) > 2 and os.path.exists(sys.argv[-1]):
data = open(sys.argv[-1]).read()
if waitfor:
client = HTTPSClient(usejson)
client = HTTPSClient(usejson, errout=errout)
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(usejson).grab_url(sys.argv[1], data).decode())
sys.stdout.write(HTTPSClient(usejson, errout=errout).grab_url(sys.argv[1], data).decode())
@@ -154,12 +154,14 @@ class WickedManager(object):
sys.stderr.write("Warning, multiple interfaces ({0}) without a team_mode, skipping setup\n".format(','.join(cfg['interfaces'])))
return
if not stgs.get('connection_name', None):
stgs['connection_name'] = 'team{0}'.format(self.teamidx)
stgs['connection_name'] = 'bond{0}'.format(self.teamidx)
self.teamidx += 1
cname = stgs['connection_name']
with open('/etc/sysconfig/network/ifcfg-{0}'.format(cname), 'w') as teamout:
teamout.write(ipcfg)
teamout.write('TEAM_RUNNER={0}\n'.format(stgs['team_mode']))
if stgs['team_mode'] == 'lacp':
stgs['team_mode'] = '802.3ad'
teamout.write("BONDING_MODULE_OPTS='mode={0} miimon=100'\nBONDING_MASTER=yes\n".format(stgs['team_mode']))
idx = 1
for iface in cfg['interfaces']:
subprocess.call(['wicked', 'ifdown', iface])
@@ -168,7 +170,7 @@ class WickedManager(object):
os.remove('/etc/sysconfig/network/ifroute-{0}'.format(iface))
except OSError:
pass
teamout.write('TEAM_PORT_DEVICE_{0}={1}\n'.format(idx, iface))
teamout.write('BONDING_SLAVE{0}={1}\n'.format(idx, iface))
idx += 1
else:
cname = list(cfg['interfaces'])[0]
@@ -325,18 +327,20 @@ if __name__ == '__main__':
nc = json.loads(nc)
iname = get_interface_name(idxmap[curridx], nc.get('default', {}))
if iname:
if 'default' in netname_to_interfaces:
netname_to_interfaces['default']['interfaces'].add(iname)
else:
netname_to_interfaces['default'] = {'interfaces': set([iname]), 'settings': nc['default']}
for iname in iname.split(','):
if 'default' in netname_to_interfaces:
netname_to_interfaces['default']['interfaces'].add(iname)
else:
netname_to_interfaces['default'] = {'interfaces': set([iname]), 'settings': nc['default']}
for netname in nc.get('extranets', {}):
uname = '_' + netname
iname = get_interface_name(idxmap[curridx], nc['extranets'][netname])
if iname:
if uname in netname_to_interfaces:
netname_to_interfaces[uname]['interfaces'].add(iname)
else:
netname_to_interfaces[uname] = {'interfaces': set([iname]), 'settings': nc['extranets'][netname]}
for iname in iname.split(','):
if uname in netname_to_interfaces:
netname_to_interfaces[uname]['interfaces'].add(iname)
else:
netname_to_interfaces[uname] = {'interfaces': set([iname]), 'settings': nc['extranets'][netname]}
doneidxs.add(curridx)
rm_tmp_llas(tmpllas)
if os.path.exists('/usr/bin/nmcli'):
@@ -29,8 +29,7 @@ cp confluent_imginfo copernicus clortho autocons ../opt/confluent/bin
cp start_root urlmount ../stateless-bin/
cd ..
ln -s el8 el9
ln -s el8-diskless el9-diskless
for os in rhvh4 el7 genesis el8 suse15 ubuntu20.04 coreos el9; do
for os in rhvh4 el7 genesis el8 suse15 ubuntu20.04 ubuntu22.04 coreos el9; do
mkdir ${os}out
cd ${os}out
if [ -d ../${os}bin ]; then
@@ -44,7 +43,7 @@ for os in rhvh4 el7 genesis el8 suse15 ubuntu20.04 coreos el9; do
mv ../addons.cpio .
cd ..
done
for os in el8 suse15 el9 ubuntu20.04; do
for os in el7 el8 suse15 el9 ubuntu20.04; do
mkdir ${os}disklessout
cd ${os}disklessout
if [ -d ../${os}bin ]; then
@@ -76,7 +75,9 @@ cp -a esxi7out esxi6out
cp -a esxi7 esxi6
%install
for os in rhvh4 el7 el8 el9 genesis suse15 ubuntu20.04 esxi6 esxi7 coreos; do
mkdir -p %{buildroot}/opt/confluent/share/licenses/confluent_osdeploy/
cp LICENSE %{buildroot}/opt/confluent/share/licenses/confluent_osdeploy/
for os in rhvh4 el7 el8 el9 genesis suse15 ubuntu20.04 ubuntu22.04 esxi6 esxi7 coreos; do
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/$os/profiles
cp ${os}out/addons.* %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs
@@ -98,3 +99,4 @@ find %{buildroot}/opt/confluent/lib/osdeploy/ -name .gitignore -exec rm -f {} +
%files
/opt/confluent/lib/osdeploy
%license /opt/confluent/share/licenses/confluent_osdeploy/LICENSE
@@ -5,6 +5,54 @@ umask 0077
mkdir -p /etc/confluent
echo -n > /etc/confluent/confluent.info
umask $oum
if [ -e /dev/disk/by-label/CNFLNT_IDNT ]; then
tmnt=$(mktemp -d)
tcfg=$(mktemp)
mount /dev/disk/by-label/CNFLNT_IDNT $tmnt
cd $tmnt
deploysrvs=$(sed -n '/^deploy_servers:/, /^[^-]/p' cnflnt.yml |grep ^-|sed -e 's/^- //'|grep -v :)
nodename=$(grep ^nodename: cnflnt.yml|awk '{print $2}')
sed -n '/^net_cfgs:/, /^[^- ]/{/^[^- ]/!p}' cnflnt.yml |sed -n '/^-/, /^-/{/^-/!p}'| sed -e 's/^[- ]*//'> $tcfg
autoconfigmethod=$(grep ^ipv4_method: $tcfg)
autoconfigmethod=${autoconfigmethod#ipv4_method: }
if [ "$autoconfigmethod" = "dhcp" ]; then
/usr/libexec/nm-initrd-generator ip=:dhcp
else
v4addr=$(grep ^ipv4_address: $tcfg)
v4addr=${v4addr#ipv4_address: }
v4gw=$(grep ^ipv4_gateway: $tcfg)
v4gw=${v4gw#ipv4_gateway: }
if [ "$v4gw" = "null" ]; then
v4gw=""
fi
v4nm=$(grep ipv4_netmask: $tcfg)
v4nm=${v4nm#ipv4_netmask: }
/usr/libexec/nm-initrd-generator ip=$v4addr::$v4gw:$v4nm:$hostname::none
fi
NetworkManager --configure-and-quit=initrd --no-daemon
hmackeyfile=$(mktemp)
echo -n $(grep ^apitoken: cnflnt.yml|awk '{print $2}') > $hmackeyfile
cd -
umount $tmnt
passfile=$(mktemp)
passcrypt=$(mktemp)
hmacfile=$(mktemp)
ln -s /opt/confluent/bin/clortho /opt/confluent/bin/genpasshmac
/opt/confluent/bin/genpasshmac $passfile $passcrypt $hmacfile $hmackeyfile
for deployer in $deploysrvs; do
if curl -f -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_CRYPTHMAC: $(cat $hmacfile)" -d@$passcrypt -k https://$deployer/confluent-api/self/registerapikey; then
cp $passfile /etc/confluent/confluent.apikey
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$deployer/confluent-api/self/deploycfg > /etc/confluent/confluent.deploycfg
curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$deployer/confluent-api/self/profileprivate/pending/config.ign > /config.ign
[ -s /config.ign ] || rm /config.ign
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg)
confluent_profile=${confluent_profile#profile: }
curl -sf https://$deployer/confluent-public/os/$confluent_profile/rootfs.img | rdcore stream-hash /etc/coreos-live-want-rootfs | bsdtar -xf - -C /
exit 0
fi
done
fi
cd /sys/class/net
echo "Searching for confluent deployment server...." > /dev/console
while ! grep ^NODE /etc/confluent/confluent.info; do
@@ -18,6 +66,7 @@ while ! grep ^NODE /etc/confluent/confluent.info; do
if ! grep ^NODE /etc/confluent/confluent.info; then
echo 'Current net config:' > /dev/console
ip -br a > /dev/console
exit 1
fi
done
echo "Found confluent deployment services on local network" > /dev/console
@@ -65,7 +114,7 @@ curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_api
[ -s /config.ign ] || rm /config.ign
umask $oldumask
autoconfigmethod=$(grep ipv4_method /etc/confluent/confluent.deploycfg)
autoconfigmethod=$(grep ^ipv4_method: /etc/confluent/confluent.deploycfg)
autoconfigmethod=${autoconfigmethod#ipv4_method: }
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg)
confluent_profile=${confluent_profile#profile: }
@@ -6,6 +6,7 @@ After=basic.target
# Network is enabled here
After=dracut-initqueue.service
Before=ignition-setup-user.service
Before=coreos-ignition-setup-user.service
Before=coreos-livepxe-rootfs.service
@@ -0,0 +1,245 @@
get_remote_apikey() {
while [ -z "$confluent_apikey" ]; do
/opt/confluent/bin/clortho $nodename $confluent_mgr > /etc/confluent/confluent.apikey
if grep ^SEALED: /etc/confluent/confluent.apikey > /dev/null; then
# we don't support remote sealed api keys anymore
echo > /etc/confluent/confluent.apikey
fi
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
if [ -z "$confluent_apikey" ]; then
echo "Unable to acquire node api key, set deployment.apiarmed=once on node '$nodename', retrying..."
sleep 10
elif [ -c /dev/tpmrm0 -a -x /usr/bin/tpm2_startauthsession ]; then
tmpdir=$(mktemp -d)
cd $tmpdir
tpm2_startauthsession --session=session.ctx
tpm2_policypcr -Q --session=session.ctx --pcr-list="sha256:15" --policy=pcr15.sha256.policy
tpm2_createprimary -G ecc -Q --key-context=prim.ctx
(echo -n "CONFLUENT_APIKEY:";cat /etc/confluent/confluent.apikey) | tpm2_create -Q --policy=pcr15.sha256.policy --public=data.pub --private=data.priv -i - -C prim.ctx
tpm2_load -Q --parent-context=prim.ctx --public=data.pub --private=data.priv --name=confluent.apikey --key-context=data.ctx
tpm2_evictcontrol -Q -c data.ctx
tpm2_flushcontext session.ctx
cd - > /dev/null
rm -rf $tmpdir
fi
done
}
root=1
rootok=1
netroot=confluent
clear
mkdir -p /etc/ssh
mkdir -p /var/tmp/
mkdir -p /var/empty/sshd
mkdir -p /etc/confluent
sed -i '/^root:x/d' /etc/passwd
echo root:x:0:0::/:/bin/bash >> /etc/passwd
echo sshd:x:30:30:SSH User:/var/empty/sshd:/sbin/nologin >> /etc/passwd
if ! grep console= /proc/cmdline >& /dev/null; then
autocons=$(/opt/confluent/bin/autocons)
autocons=${autocons##*/}
echo "Automatic console configured for $autocons"
fi
echo "Initializing confluent diskless environment"
echo -n "udevd: "
/usr/lib/systemd/systemd-udevd --daemon
echo -n "Loading drivers..."
udevadm trigger
udevadm trigger --type=devices --action=add
udevadm settle
modprobe ib_ipoib
modprobe ib_umad
modprobe hfi1
modprobe mlx5_ib
echo "done"
cat > /etc/ssh/sshd_config << EOF
Port 2222
Subsystem sftp /usr/libexec/openssh/sftp-server
PermitRootLogin yes
AuthorizedKeysFile .ssh/authorized_keys
EOF
mkdir /root/.ssh
mkdir /.ssh
cat /ssh/*pubkey > /root/.ssh/authorized_keys 2>/dev/null
cp /root/.ssh/authorized_keys /.ssh/
cat /tls/*.pem > /etc/confluent/ca.pem
mkdir -p /etc/pki/tls/certs
cat /tls/*.pem > /etc/pki/tls/certs/ca-bundle.crt
TRIES=0
oldumask=$(umask)
umask 0077
tpmdir=$(mktemp -d)
cd $tpmdir
lasthdl=""
if [ -c /dev/tpmrm0 -a -x /usr/bin/tpm2_getcap ]; then
for hdl in $(tpm2_getcap handles-persistent|awk '{print $2}'); do
tpm2_startauthsession --policy-session --session=session.ctx
tpm2_policypcr -Q --session=session.ctx --pcr-list="sha256:15" --policy=pcr15.sha256.policy
unsealeddata=$(tpm2_unseal --auth=session:session.ctx -Q -c $hdl 2>/dev/null)
tpm2_flushcontext session.ctx
if [[ $unsealeddata == "CONFLUENT_APIKEY:"* ]]; then
confluent_apikey=${unsealeddata#CONFLUENT_APIKEY:}
echo $confluent_apikey > /etc/confluent/confluent.apikey
if [ -n "$lasthdl" ]; then
tpm2_evictcontrol -c $lasthdl
fi
lasthdl=$hdl
fi
done
fi
cd - > /dev/null
rm -rf $tpmdir
touch /etc/confluent/confluent.info
cd /sys/class/net
echo -n "Scanning for network configuration..."
while ! grep ^EXTMGRINFO: /etc/confluent/confluent.info | awk -F'|' '{print $3}' | grep 1 >& /dev/null && [ "$TRIES" -lt 30 ]; do
TRIES=$((TRIES + 1))
for i in *; do
ip link set $i up
done
/opt/confluent/bin/copernicus -t > /etc/confluent/confluent.info
done
cd /
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
hostname $nodename
confluent_mgr=$(grep '^EXTMGRINFO:.*1$' /etc/confluent/confluent.info | head -n 1 | awk -F': ' '{print $2}' | awk -F'|' '{print $1}')
if [ -z "$confluent_mgr" ]; then
confluent_mgr=$(grep ^MANAGER: /etc/confluent/confluent.info|head -n 1 | awk '{print $2}')
fi
if [[ $confluent_mgr == *%* ]]; then
echo $confluent_mgr | awk -F% '{print $2}' > /tmp/confluent.ifidx
ifidx=$(cat /tmp/confluent.ifidx)
ifname=$(ip link |grep ^$ifidx:|awk '{print $2}')
ifname=${ifname%:}
fi
ready=0
while [ $ready = "0" ]; do
get_remote_apikey
if [[ $confluent_mgr == *:* ]]; then
confluent_mgr="[${confluent_mgr/\%/%25}]"
fi
tmperr=$(mktemp)
curl -gsSf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/deploycfg > /etc/confluent/confluent.deploycfg 2> $tmperr
if grep 401 $tmperr > /dev/null; then
confluent_apikey=""
if [ -n "$lasthdl" ]; then
tpm2_evictcontrol -c $lasthdl
fi
confluent_mgr=${confluent_mgr#[}
confluent_mgr=${confluent_mgr%]}
else
ready=1
fi
rm $tmperr
done
if [ ! -z "$autocons" ] && grep textconsole: true /etc/confluent/confluent.deploycfg > /dev/null; then /opt/confluent/bin/autocons -c > /dev/null; fi
if [ -c /dev/tpmrm0 -a -x /usr/bin/tpm2_pcrextend ]; then
tpm2_pcrextend 15:sha256=2fbe96c50dde38ce9cd2764ddb79c216cfbcd3499568b1125450e60c45dd19f2
fi
umask $oldumask
autoconfigmethod=$(grep ^ipv4_method: /etc/confluent/confluent.deploycfg |awk '{print $2}')
if [ "$autoconfigmethod" = "dhcp" ]; then
echo -n "Attempting to use dhcp to bring up $ifname..."
dhclient $ifname
echo "Complete:"
ip addr show dev $ifname
else
v4addr=$(grep ^ipv4_address: /etc/confluent/confluent.deploycfg)
v4addr=${v4addr#ipv4_address: }
v4gw=$(grep ^ipv4_gateway: /etc/confluent/confluent.deploycfg)
v4gw=${v4gw#ipv4_gateway: }
if [ "$v4gw" = "null" ]; then
v4gw=""
fi
v4nm=$(grep ^prefix: /etc/confluent/confluent.deploycfg)
v4nm=${v4nm#prefix: }
echo "Setting up $ifname as static at $v4addr/$v4nm"
ip addr add dev $ifname $v4addr/$v4nm
if [ ! -z "$v4gw" ]; then
ip route add default via $v4gw
fi
mkdir -p /run/NetworkManager/system-connections
cat > /run/NetworkManager/system-connections/$ifname.nmconnection << EOC
[connection]
EOC
echo id=${ifname} >> /run/NetworkManager/system-connections/$ifname.nmconnection
echo uuid=$(uuidgen) >> /run/NetworkManager/system-connections/$ifname.nmconnection
cat >> /run/NetworkManager/system-connections/$ifname.nmconnection << EOC
type=ethernet
autoconnect-retries=1
EOC
echo interface-name=$ifname >> /run/NetworkManager/system-connections/$ifname.nmconnection
cat >> /run/NetworkManager/system-connections/$ifname.nmconnection << EOC
multi-connect=1
permissions=
wait-device-timeout=60000
[ethernet]
mac-address-blacklist=
[ipv4]
EOC
echo address1=$v4addr/$v4nm >> /run/NetworkManager/system-connections/$ifname.nmconnection
if [ ! -z "$v4gw" ]; then
echo gateway=$v4gw >> /run/NetworkManager/system-connections/$ifname.nmconnection
fi
nameserversec=0
nameservers=""
while read -r entry; do
if [ $nameserversec = 1 ]; then
if [[ $entry == "-"* ]]; then
nameservers="$nameservers"${entry#- }";"
continue
fi
fi
nameserversec=0
if [ "${entry%:*}" = "nameservers" ]; then
nameserversec=1
continue
fi
done < /etc/confluent/confluent.deploycfg
echo dns=$nameservers >> /run/NetworkManager/system-connections/$ifname.nmconnection
dnsdomain=$(grep ^dnsdomain: /etc/confluent/confluent.deploycfg)
dnsdomain=${dnsdomain#dnsdomain: }
echo dns-search=$dnsdomain >> /run/NetworkManager/system-connections/$ifname.nmconnection
cat >> /run/NetworkManager/system-connections/$ifname.nmconnection << EOC
may-fail=false
method=manual
[ipv6]
addr-gen-mode=eui64
method=auto
[proxy]
EOC
fi
chmod 600 /run/NetworkManager/system-connections/*.nmconnection
echo -n "Initializing ssh..."
ssh-keygen -A
for pubkey in /etc/ssh/ssh_host*key.pub; do
certfile=${pubkey/.pub/-cert.pub}
privfile=${pubkey%.pub}
curl -gsf -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" -d @$pubkey https://$confluent_mgr/confluent-api/self/sshcert > $certfile
if [ -s $certfile ]; then
echo HostCertificate $certfile >> /etc/ssh/sshd_config
fi
echo HostKey $privfile >> /etc/ssh/sshd_config
done
/usr/sbin/sshd
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg| awk '{print $2}')
confluent_proto=$(grep ^protocol: /etc/confluent/confluent.deploycfg| awk '{print $2}')
confluent_urls=""
for addr in $(grep ^MANAGER: /etc/confluent/confluent.info|awk '{print $2}'|sed -e s/%/%25/); do
if [[ $addr == *:* ]]; then
confluent_urls="$confluent_urls $confluent_proto://[$addr]/confluent-public/os/$confluent_profile/rootimg.sfs"
else
confluent_urls="$confluent_urls $confluent_proto://$addr/confluent-public/os/$confluent_profile/rootimg.sfs"
fi
done
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg| awk '{print $2}')
mkdir -p /etc/confluent
curl -gsf https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/functions > /etc/confluent/functions
. /etc/confluent/functions
source_remote imageboot.sh
@@ -0,0 +1,4 @@
. /etc/confluent/functions
# This is a convenient place to keep customizations separate from modifying the stock scripts
# While modification of the stock scripts is fine, it may be easier to rebase to a newer
# stock profile if the '.custom' files are used.
@@ -0,0 +1,11 @@
[Unit]
Description=First Boot Process
Requires=network-online.target
After=network-online.target
[Service]
ExecStart=/opt/confluent/bin/firstboot.sh
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,40 @@
#!/bin/sh
# This script is executed on the first boot after install has
# completed. It is best to edit the middle of the file as
# noted below so custom commands are executed before
# the script notifies confluent that install is fully complete.
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
export nodename confluent_mgr confluent_profile
. /etc/confluent/functions
exec >> /var/log/confluent/confluent-firstboot.log
exec 2>> /var/log/confluent/confluent-firstboot.log
chmod 600 /var/log/confluent/confluent-firstboot.log
tail -f /var/log/confluent/confluent-firstboot.log > /dev/console &
logshowpid=$!
while ! ping -c 1 $confluent_mgr >& /dev/null; do
sleep 1
done
if [ ! -f /etc/confluent/firstboot.ran ]; then
touch /etc/confluent/firstboot.ran
cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
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.d
# Induce execution of remote configuration, e.g. ansible plays in ansible/firstboot.d/
run_remote_config firstboot.d
fi
curl -X POST -d 'status: complete' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
systemctl disable firstboot
rm /etc/systemd/system/firstboot.service
rm /etc/confluent/firstboot.ran
kill $logshowpid
@@ -0,0 +1,196 @@
#!/bin/bash
function test_mgr() {
if curl -s https://${1}/confluent-api/ > /dev/null; then
return 0
fi
return 1
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
elif [ -x /usr/bin/python3 ]; then
/usr/bin/python3 $*
elif [ -x /usr/bin/python ]; then
/usr/bin/python $*
elif [ -x /usr/bin/python2 ]; then
/usr/bin/python2 $*
fi
}
function set_confluent_vars() {
if [ -z "$nodename" ]; then
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
fi
if [[ "$confluent_mgr" == *"%"* ]]; then
confluent_mgr=""
fi
if [ -z "$confluent_mgr" ]; then
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
if ! test_mgr $confluent_mgr; then
confluent_mgr=$(grep ^deploy_server_v6: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
if [[ "$confluent_mgr" = *":"* ]]; then
confluent_mgr="[$confluent_mgr]"
fi
fi
if ! test_mgr $confluent_mgr; then
BESTMGRS=$(grep ^EXTMGRINFO: /etc/confluent/confluent.info | grep '|1$' | sed -e 's/EXTMGRINFO: //' -e 's/|.*//')
OKMGRS=$(grep ^EXTMGRINFO: /etc/confluent/confluent.info | grep '|0$' | sed -e 's/EXTMGRINFO: //' -e 's/|.*//')
for confluent_mgr in $BESTMGRS $OKMGRS; do
if [[ $confluent_mgr == *":"* ]]; then
confluent_mgr="[$confluent_mgr]"
fi
if test_mgr $confluent_mgr; then
break
fi
done
fi
fi
if [ -z "$confluent_profile" ]; then
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
}
fetch_remote() {
curlargs=""
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://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
fi
scriptlist=$(confluentpython $apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
for script in $scriptlist; do
source_remote $1/$script
done
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
fi
scriptlist=$(confluentpython $apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
for script in $scriptlist; do
run_remote $1/$script
done
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
}
source_remote() {
set_confluent_vars
unsettmpdir=0
echo
echo '---------------------------------------------------------------------------'
echo Sourcing $1 from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
if [ -z "$confluentscripttmpdir" ]; then
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unsettmpdir=1
fi
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
chmod +x $1
cmd=$1
shift
source ./$cmd
cd - > /dev/null
if [ "$unsettmpdir" = 1 ]; then
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
unsettmpdir=0
fi
rm -rf $confluentscripttmpdir
return $retcode
}
run_remote() {
requestedcmd="'$*'"
unsettmpdir=0
set_confluent_vars
echo
echo '---------------------------------------------------------------------------'
echo Running $requestedcmd from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
if [ -z "$confluentscripttmpdir" ]; then
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unsettmpdir=1
fi
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $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=$?
if [ $retcode -ne 0 ]; then
echo "$requestedcmd exited with code $retcode"
fi
cd - > /dev/null
if [ "$unsettmpdir" = 1 ]; then
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
unsettmpdir=0
fi
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://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
curl -f -sS $curlargs https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
confluentpython $*
retcode=$?
echo "'$*' exited with code $retcode"
cd - > /dev/null
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
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 $confluent_mgr under profile $confluent_profile
confluentpython $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
confluentpython $apiclient /confluent-api/self/remoteconfig/status -w 204
echo
echo 'Completed remote configuration'
echo '---------------------------------------------------------------------------'
return
}
#If invoked as a command, use the arguments to actually run a function
(return 0 2>/dev/null) || $1 "${@:2}"
@@ -0,0 +1,93 @@
import subprocess
import os
class DiskInfo(object):
def __init__(self, devname):
self.name = devname
self.wwn = None
self.path = None
self.model = ''
self.size = 0
self.driver = None
self.mdcontainer = ''
devnode = '/dev/{0}'.format(devname)
qprop = subprocess.check_output(
['udevadm', 'info', '--query=property', devnode])
if not isinstance(qprop, str):
qprop = qprop.decode('utf8')
for prop in qprop.split('\n'):
if '=' not in prop:
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
self.path = v
elif k == 'ID_WWN':
self.wwn = v
elif k == 'MD_CONTAINER':
self.mdcontainer = v
attrs = subprocess.check_output(['udevadm', 'info', '-a', devnode])
if not isinstance(attrs, str):
attrs = attrs.decode('utf8')
for attr in attrs.split('\n'):
if '==' not in attr:
continue
k, v = attr.split('==', 1)
k = k.strip()
if k == 'ATTRS{size}':
self.size = v.replace('"', '')
elif (k == 'DRIVERS' and not self.driver
and v not in ('"sd"', '""')):
self.driver = v.replace('"', '')
if not self.driver and 'imsm' not in self.mdcontainer:
raise Exception("No driver detected")
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
@property
def priority(self):
if self.model.lower() in ('thinksystem_m.2_vd', 'thinksystem m.2', 'thinksystem_m.2'):
return 0
if 'imsm' in self.mdcontainer:
return 1
if self.driver == 'ahci':
return 2
if self.driver.startswith('megaraid'):
return 3
if self.driver.startswith('mpt'):
return 4
return 99
def __repr__(self):
return repr({
'name': self.name,
'path': self.path,
'wwn': self.wwn,
'driver': self.driver,
'size': self.size,
'model': self.model,
})
def main():
disks = []
for disk in sorted(os.listdir('/sys/class/block')):
try:
disk = DiskInfo(disk)
disks.append(disk)
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
if __name__ == '__main__':
main()
@@ -0,0 +1,376 @@
#!/usr/bin/python3
import glob
import json
import os
import re
import time
import shutil
import socket
import stat
import struct
import sys
import subprocess
def get_next_part_meta(img, imgsize):
if img.tell() == imgsize:
return None
pathlen = struct.unpack('!H', img.read(2))[0]
mountpoint = img.read(pathlen).decode('utf8')
jsonlen = struct.unpack('!I', img.read(4))[0]
metadata = json.loads(img.read(jsonlen).decode('utf8'))
img.seek(16, 1) # skip the two 64-bit values we don't use, they are in json
nextlen = struct.unpack('!H', img.read(2))[0]
img.seek(nextlen, 1) # skip filesystem type
nextlen = struct.unpack('!H', img.read(2))[0]
img.seek(nextlen, 1) # skip orig devname (redundant with json)
nextlen = struct.unpack('!H', img.read(2))[0]
img.seek(nextlen, 1) # skip padding
nextlen = struct.unpack('!Q', img.read(8))[0]
img.seek(nextlen, 1) # go to next section
return metadata
def get_multipart_image_meta(img):
img.seek(0, 2)
imgsize = img.tell()
img.seek(16)
seekamt = img.read(1)
img.seek(struct.unpack('B', seekamt)[0], 1)
partinfo = get_next_part_meta(img, imgsize)
while partinfo:
yield partinfo
partinfo = get_next_part_meta(img, imgsize)
def get_image_metadata(imgpath):
with open(imgpath, 'rb') as img:
header = img.read(16)
if header == b'\x63\x7b\x9d\x26\xb7\xfd\x48\x30\x89\xf9\x11\xcf\x18\xfd\xff\xa1':
for md in get_multipart_image_meta(img):
yield md
else:
raise Exception('Installation from single part image not supported')
class PartedRunner():
def __init__(self, disk):
self.disk = disk
def run(self, command):
command = command.split()
command = ['parted', '-a', 'optimal', '-s', self.disk] + command
return subprocess.check_output(command).decode('utf8')
def fixup(rootdir, vols):
devbymount = {}
for vol in vols:
devbymount[vol['mount']] = vol['targetdisk']
fstabfile = os.path.join(rootdir, 'etc/fstab')
with open(fstabfile) as tfile:
fstab = tfile.read().split('\n')
while not fstab[0]:
fstab = fstab[1:]
if os.path.exists(os.path.join(rootdir, '.autorelabel')):
os.unlink(os.path.join(rootdir, '.autorelabel'))
with open(fstabfile, 'w') as tfile:
for tab in fstab:
entry = tab.split()
if tab.startswith('#ORIGFSTAB#'):
if entry[1] in devbymount:
targetdev = devbymount[entry[1]]
if targetdev.startswith('/dev/localstorage/'):
entry[0] = targetdev
else:
uuid = subprocess.check_output(['blkid', '-s', 'UUID', '-o', 'value', targetdev]).decode('utf8')
uuid = uuid.strip()
entry[0] = 'UUID={}'.format(uuid)
elif entry[2] == 'swap':
entry[0] = '/dev/mapper/localstorage-swap'
entry[0] = entry[0].ljust(42)
entry[1] = entry[1].ljust(16)
entry[3] = entry[3].ljust(28)
tab = '\t'.join(entry)
tfile.write(tab + '\n')
with open(os.path.join(rootdir, 'etc/hostname'), 'w') as nameout:
nameout.write(socket.gethostname() + '\n')
selinuxconfig = os.path.join(rootdir, 'etc/selinux/config')
policy = None
if os.path.exists(selinuxconfig):
with open(selinuxconfig) as cfgin:
sec = cfgin.read().split('\n')
for l in sec:
l = l.split('#', 1)[0]
if l.startswith('SELINUXTYPE='):
_, policy = l.split('=')
for sshkey in glob.glob(os.path.join(rootdir, 'etc/ssh/*_key*')):
os.unlink(sshkey)
for sshkey in glob.glob('/etc/ssh/*_key*'):
newkey = os.path.join(rootdir, sshkey[1:])
shutil.copy2(sshkey, newkey)
finfo = os.stat(sshkey)
os.chown(newkey, finfo[stat.ST_UID], finfo[stat.ST_GID])
for ifcfg in glob.glob(os.path.join(rootdir, 'etc/sysconfig/network-scripts/*')):
os.unlink(ifcfg)
for ifcfg in glob.glob(os.path.join(rootdir, 'etc/NetworkManager/system-connections/*')):
os.unlink(ifcfg)
for ifcfg in glob.glob('/run/NetworkManager/system-connections/*'):
newcfg = ifcfg.split('/')[-1]
newcfg = os.path.join(rootdir, 'etc/NetworkManager/system-connections/{0}'.format(newcfg))
shutil.copy2(ifcfg, newcfg)
shutil.rmtree(os.path.join(rootdir, 'etc/confluent/'))
shutil.copytree('/etc/confluent', os.path.join(rootdir, 'etc/confluent'))
if policy:
sys.stdout.write('Applying SELinux labeling...')
sys.stdout.flush()
subprocess.check_call(['setfiles', '-r', rootdir, os.path.join(rootdir, 'etc/selinux/{}/contexts/files/file_contexts'.format(policy)), os.path.join(rootdir, 'etc')])
subprocess.check_call(['setfiles', '-r', rootdir, os.path.join(rootdir, 'etc/selinux/{}/contexts/files/file_contexts'.format(policy)), os.path.join(rootdir, 'opt')])
sys.stdout.write('Done\n')
sys.stdout.flush()
for metafs in ('proc', 'sys', 'dev'):
subprocess.check_call(['mount', '-o', 'bind', '/{}'.format(metafs), os.path.join(rootdir, metafs)])
with open(os.path.join(rootdir, 'etc/sysconfig/grub')) as defgrubin:
defgrub = defgrubin.read().split('\n')
with open(os.path.join(rootdir, 'etc/sysconfig/grub'), 'w') as defgrubout:
for gline in defgrub:
gline = gline.split()
newline = []
for ent in gline:
if ent.startswith('resume=') or ent.startswith('rd.lvm.lv'):
continue
newline.append(ent)
defgrubout.write(' '.join(newline) + '\n')
grubcfg = subprocess.check_output(['find', os.path.join(rootdir, 'boot'), '-name', 'grub.cfg']).decode('utf8').strip().replace(rootdir, '/')
subprocess.check_call(['chroot', rootdir, 'grub2-mkconfig', '-o', grubcfg])
newroot = None
with open('/etc/shadow') as shadowin:
shents = shadowin.read().split('\n')
for shent in shents:
shent = shent.split(':')
if not shent:
continue
if shent[0] == 'root' and shent[1] not in ('*', '!!', ''):
newroot = shent[1]
if newroot:
shlines = None
with open(os.path.join(rootdir, 'etc/shadow')) as oshadow:
shlines = oshadow.read().split('\n')
with open(os.path.join(rootdir, 'etc/shadow'), 'w') as oshadow:
for line in shlines:
if line.startswith('root:'):
line = line.split(':')
line[1] = newroot
line = ':'.join(line)
oshadow.write(line + '\n')
partnum = None
targblock = None
for vol in vols:
if vol['mount'] == '/boot/efi':
targdev = vol['targetdisk']
partnum = re.search('(\d+)$', targdev).group(1)
targblock = re.search('(.*)\d+$', targdev).group(1)
if targblock:
shimpath = subprocess.check_output(['find', os.path.join(rootdir, 'boot/efi'), '-name', 'shimx64.efi']).decode('utf8').strip()
shimpath = shimpath.replace(rootdir, '/').replace('/boot/efi', '').replace('//', '/').replace('/', '\\')
subprocess.check_call(['efibootmgr', '-c', '-d', targblock, '-l', shimpath, '--part', partnum])
#other network interfaces
def had_swap():
with open('/etc/fstab') as tabfile:
tabs = tabfile.read().split('\n')
for tab in tabs:
tab = tab.split()
if len(tab) < 3:
continue
if tab[2] == 'swap':
return True
return False
def install_to_disk(imgpath):
lvmvols = {}
deftotsize = 0
mintotsize = 0
deflvmsize = 0
minlvmsize = 0
biggestsize = 0
biggestfs = None
plainvols = {}
allvols = []
swapsize = 0
if had_swap():
with open('/proc/meminfo') as meminfo:
swapsize = meminfo.read().split('\n')[0]
swapsize = int(swapsize.split()[1])
if swapsize < 2097152:
swapsize = swapsize * 2
elif swapsize > 8388608 and swapsize < 67108864:
swapsize = swapsize * 0.5
elif swapsize >= 67108864:
swapsize = 33554432
swapsize = int(swapsize * 1024)
deftotsize = swapsize
mintotsize = swapsize
for fs in get_image_metadata(imgpath):
allvols.append(fs)
deftotsize += fs['initsize']
mintotsize += fs['minsize']
if fs['initsize'] > biggestsize:
biggestfs = fs
biggestsize = fs['initsize']
if fs['device'].startswith('/dev/mapper'):
lvmvols[fs['device'].replace('/dev/mapper/', '')] = fs
deflvmsize += fs['initsize']
minlvmsize += fs['minsize']
else:
plainvols[int(re.search('(\d+)$', fs['device'])[0])] = fs
with open('/tmp/installdisk') as diskin:
instdisk = diskin.read()
instdisk = '/dev/' + instdisk
parted = PartedRunner(instdisk)
dinfo = parted.run('unit s print')
dinfo = dinfo.split('\n')
sectors = 0
sectorsize = 0
for inf in dinfo:
if inf.startswith('Disk {0}:'.format(instdisk)):
_, sectors = inf.split(': ')
sectors = int(sectors.replace('s', ''))
if inf.startswith('Sector size (logical/physical):'):
_, sectorsize = inf.split(':')
sectorsize = sectorsize.split('/')[0]
sectorsize = sectorsize.replace('B', '')
sectorsize = int(sectorsize)
# for now, only support resizing/growing the largest partition
minexcsize = deftotsize - biggestfs['initsize']
mintotsize = deftotsize - biggestfs['initsize'] + biggestfs['minsize']
minsectors = mintotsize // sectorsize
if sectors < (minsectors + 65536):
raise Exception('Disk too small to fit image')
biggestsectors = sectors - (minexcsize // sectorsize)
biggestsize = sectorsize * biggestsectors
parted.run('mklabel gpt')
curroffset = 2048
for volidx in sorted(plainvols):
vol = plainvols[volidx]
if vol is not biggestfs:
size = vol['initsize'] // sectorsize
else:
size = biggestsize // sectorsize
size += 2047 - (size % 2048)
end = curroffset + size
if end > sectors:
end = sectors
parted.run('mkpart primary {}s {}s'.format(curroffset, end))
vol['targetdisk'] = instdisk + '{0}'.format(volidx)
curroffset += size + 1
if not lvmvols:
if swapsize:
swapsize = swapsize // sectorsize
swapsize += 2047 - (size % 2048)
end = curroffset + swapsize
if end > sectors:
end = sectors
parted.run('mkpart swap {}s {}s'.format(curroffset, end))
subprocess.check_call(['mkswap', instdisk + '{}'.format(volidx + 1)])
else:
parted.run('mkpart lvm {}s 100%'.format(curroffset))
lvmpart = instdisk + '{}'.format(volidx + 1)
subprocess.check_call(['pvcreate', '-ff', '-y', lvmpart])
subprocess.check_call(['vgcreate', 'localstorage', lvmpart])
vginfo = subprocess.check_output(['vgdisplay', 'localstorage', '--units', 'b']).decode('utf8')
vginfo = vginfo.split('\n')
pesize = 0
pes = 0
for infline in vginfo:
infline = infline.split()
if len(infline) >= 3 and infline[:2] == ['PE', 'Size']:
pesize = int(infline[2])
if len(infline) >= 5 and infline[:2] == ['Free', 'PE']:
pes = int(infline[4])
takeaway = swapsize // pesize
for volidx in lvmvols:
vol = lvmvols[volidx]
if vol is biggestfs:
continue
takeaway += vol['initsize'] // pesize
takeaway += 1
biggestextents = pes - takeaway
for volidx in lvmvols:
vol = lvmvols[volidx]
if vol is biggestfs:
extents = biggestextents
else:
extents = vol['initsize'] // pesize
extents += 1
if vol['mount'] == '/':
lvname = 'root'
else:
lvname = vol['mount'].replace('/', '_')
subprocess.check_call(['lvcreate', '-l', '{}'.format(extents), '-y', '-n', lvname, 'localstorage'])
vol['targetdisk'] = '/dev/localstorage/{}'.format(lvname)
if swapsize:
subprocess.check_call(['lvcreate', '-y', '-l', '{}'.format(swapsize // pesize), '-n', 'swap', 'localstorage'])
subprocess.check_call(['mkswap', '/dev/localstorage/swap'])
os.makedirs('/run/imginst/targ')
for vol in allvols:
with open(vol['targetdisk'], 'wb') as partition:
partition.write(b'\x00' * 1 * 1024 * 1024)
subprocess.check_call(['mkfs.{}'.format(vol['filesystem']), vol['targetdisk']])
subprocess.check_call(['mount', vol['targetdisk'], '/run/imginst/targ'])
source = vol['mount'].replace('/', '_')
source = '/run/imginst/sources/' + source
blankfsstat = os.statvfs('/run/imginst/targ')
blankused = (blankfsstat.f_blocks - blankfsstat.f_bfree) * blankfsstat.f_bsize
sys.stdout.write('\nWriting {0}: '.format(vol['mount']))
with subprocess.Popen(['cp', '-ax', source + '/.', '/run/imginst/targ']) as copier:
stillrunning = copier.poll()
lastprogress = 0.0
while stillrunning is None:
currfsstat = os.statvfs('/run/imginst/targ')
currused = (currfsstat.f_blocks - currfsstat.f_bfree) * currfsstat.f_bsize
currused -= blankused
with open('/proc/meminfo') as meminf:
for line in meminf.read().split('\n'):
if line.startswith('Dirty:'):
_, dirty, _ = line.split()
dirty = int(dirty) * 1024
progress = (currused - dirty) / vol['minsize']
if progress < lastprogress:
progress = lastprogress
if progress > 0.99:
progress = 0.99
lastprogress = progress
progress = progress * 100
sys.stdout.write('\x1b[1K\rWriting {0}: {1:3.2f}%'.format(vol['mount'], progress))
sys.stdout.flush()
time.sleep(0.5)
stillrunning = copier.poll()
if stillrunning != 0:
raise Exception("Error copying volume")
with subprocess.Popen(['sync']) as syncrun:
stillrunning = syncrun.poll()
while stillrunning is None:
with open('/proc/meminfo') as meminf:
for line in meminf.read().split('\n'):
if line.startswith('Dirty:'):
_, dirty, _ = line.split()
dirty = int(dirty) * 1024
progress = (vol['minsize'] - dirty) / vol['minsize']
if progress < lastprogress:
progress = lastprogress
if progress > 0.99:
progress = 0.99
lastprogress = progress
progress = progress * 100
sys.stdout.write('\x1b[1K\rWriting {0}: {1:3.2f}%'.format(vol['mount'], progress))
sys.stdout.flush()
time.sleep(0.5)
stillrunning = syncrun.poll()
sys.stdout.write('\x1b[1K\rDone writing {0}'.format(vol['mount']))
sys.stdout.write('\n')
sys.stdout.flush()
subprocess.check_call(['umount', '/run/imginst/targ'])
for vol in allvols:
subprocess.check_call(['mount', vol['targetdisk'], '/run/imginst/targ/' + vol['mount']])
fixup('/run/imginst/targ', allvols)
if __name__ == '__main__':
install_to_disk(os.environ['mountsrc'])
@@ -0,0 +1,128 @@
. /lib/dracut-lib.sh
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay
if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then
mount -t tmpfs untethered /mnt/remoteimg
curl https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs
else
confluent_urls="$confluent_urls https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs"
modprobe fuse
/opt/confluent/bin/urlmount $confluent_urls /mnt/remoteimg
fi
/opt/confluent/bin/confluent_imginfo /mnt/remoteimg/rootimg.sfs > /tmp/rootimg.info
modprobe loop
loopdev=$(losetup -f)
export mountsrc=$loopdev
losetup -r $loopdev /mnt/remoteimg/rootimg.sfs
if grep '^Format: confluent_crypted' /tmp/rootimg.info > /dev/null; then
while ! curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$confluent_mgr/confluent-api/self/profileprivate/pending/rootimg.key > /tmp/rootimg.key; do
echo "Unable to retrieve private key from $confluent_mgr (verify that confluent can access /var/lib/confluent/private/$confluent_profile/pending/rootimg.key)"
sleep 1
done
cipher=$(head -n 1 /tmp/rootimg.key)
key=$(tail -n 1 /tmp/rootimg.key)
len=$(wc -c /mnt/remoteimg/rootimg.sfs | awk '{print $1}')
len=$(((len-4096)/512))
dmsetup create cryptimg --table "0 $len crypt $cipher $key 0 $loopdev 8"
/opt/confluent/bin/confluent_imginfo /dev/mapper/cryptimg > /tmp/rootimg.info
mountsrc=/dev/mapper/cryptimg
fi
if grep '^Format: squashfs' /tmp/rootimg.info > /dev/null; then
mount -o ro $mountsrc /mnt/remote
elif grep '^Format: confluent_multisquash' /tmp/rootimg.info; then
tail -n +3 /tmp/rootimg.info | awk '{gsub("/", "_"); print "echo 0 " $4 " linear '$mountsrc' " $3 " | dmsetup create mproot" $7}' > /tmp/setupmount.sh
. /tmp/setupmount.sh
cat /tmp/setupmount.sh |awk '{printf "mount /dev/mapper/"$NF" "; sub("mproot", ""); gsub("_", "/"); print "/mnt/remote"$NF}' > /tmp/mountparts.sh
. /tmp/mountparts.sh
fi
#mount -t tmpfs overlay /mnt/overlay
modprobe zram
memtot=$(grep ^MemTotal: /proc/meminfo|awk '{print $2}')
memtot=$((memtot/2))$(grep ^MemTotal: /proc/meminfo | awk '{print $3'})
echo $memtot > /sys/block/zram0/disksize
mkfs.xfs /dev/zram0 > /dev/null
mount -o discard /dev/zram0 /mnt/overlay
if [ ! -f /tmp/mountparts.sh ]; then
mkdir -p /mnt/overlay/upper /mnt/overlay/work
mount -t overlay -o upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work,lowerdir=/mnt/remote disklessroot /sysroot
else
for srcmount in $(cat /tmp/mountparts.sh | awk '{print $3}'); do
mkdir -p /mnt/overlay${srcmount}/upper /mnt/overlay${srcmount}/work
mount -t overlay -o upperdir=/mnt/overlay${srcmount}/upper,workdir=/mnt/overlay${srcmount}/work,lowerdir=${srcmount} disklesspart /sysroot${srcmount#/mnt/remote}
done
fi
mkdir -p /sysroot/etc/ssh
mkdir -p /sysroot/etc/confluent
mkdir -p /sysroot/root/.ssh
cp /root/.ssh/* /sysroot/root/.ssh
chmod 700 /sysroot/root/.ssh
cp /etc/confluent/* /sysroot/etc/confluent/
cp /etc/ssh/*key* /sysroot/etc/ssh/
for pubkey in /etc/ssh/ssh_host*key.pub; do
certfile=${pubkey/.pub/-cert.pub}
privfile=${pubkey%.pub}
if [ -s $certfile ]; then
echo HostCertificate $certfile >> /sysroot/etc/ssh/sshd_config
fi
echo HostKey $privfile >> /sysroot/etc/ssh/sshd_config
done
mkdir -p /sysroot/dev /sysroot/sys /sysroot/proc /sysroot/run
if [ ! -z "$autocons" ]; then
autocons=${autocons%,*}
mkdir -p /run/systemd/generator/getty.target.wants
ln -s /usr/lib/systemd/system/serial-getty@.service /run/systemd/generator/getty.target.wants/serial-getty@${autocons}.service
fi
while [ ! -e /sysroot/sbin/init ]; do
echo "Failed to access root filesystem or it is missing /sbin/init"
echo "System should be accessible through ssh at port 2222 with the appropriate key"
while [ ! -e /sysroot/sbin/init ]; do
sleep 1
done
done
rootpassword=$(grep ^rootpassword: /etc/confluent/confluent.deploycfg)
rootpassword=${rootpassword#rootpassword: }
if [ "$rootpassword" = "null" ]; then
rootpassword=""
fi
if [ ! -z "$rootpassword" ]; then
sed -i "s@root:[^:]*:@root:$rootpassword:@" /sysroot/etc/shadow
fi
for i in /ssh/*.ca; do
echo '@cert-authority *' $(cat $i) >> /sysroot/etc/ssh/ssh_known_hosts
done
echo HostbasedAuthentication yes >> /sysroot/etc/ssh/sshd_config
echo HostbasedUsesNameFromPacketOnly yes >> /sysroot/etc/ssh/sshd_config
echo IgnoreRhosts no >> /sysroot/etc/ssh/sshd_config
sshconf=/sysroot/etc/ssh/ssh_config
if [ -d /sysroot/etc/ssh/ssh_config.d/ ]; then
sshconf=/sysroot/etc/ssh/ssh_config.d/01-confluent.conf
fi
echo 'Host *' >> $sshconf
echo ' HostbasedAuthentication yes' >> $sshconf
echo ' EnableSSHKeysign yes' >> $sshconf
echo ' HostbasedKeyTypes *ed25519*' >> $sshconf
curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$confluent_mgr/confluent-api/self/nodelist > /sysroot/etc/ssh/shosts.equiv
cp /sysroot/etc/ssh/shosts.equiv /sysroot/root/.shosts
chmod 640 /sysroot/etc/ssh/*_key
chroot /sysroot chgrp ssh_keys /etc/ssh/*_key
cp /tls/*.pem /sysroot/etc/pki/ca-trust/source/anchors/
chroot /sysroot/ update-ca-trust
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/onboot.service > /sysroot/etc/systemd/system/onboot.service
mkdir -p /sysroot/opt/confluent/bin
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/onboot.sh > /sysroot/opt/confluent/bin/onboot.sh
chmod +x /sysroot/opt/confluent/bin/onboot.sh
cp /opt/confluent/bin/apiclient /sysroot/opt/confluent/bin
ln -s /etc/systemd/system/onboot.service /sysroot/etc/systemd/system/multi-user.target.wants/onboot.service
cp /etc/confluent/functions /sysroot/etc/confluent/functions
if grep installtodisk /proc/cmdline > /dev/null; then
. /etc/confluent/functions
run_remote installimage
exec reboot -f
fi
mv /lib/modules/$(uname -r) /lib/modules/$(uname -r)-ramfs
ln -s /sysroot/lib/modules/$(uname -r) /lib/modules/
exec /opt/confluent/bin/start_root
@@ -0,0 +1,43 @@
#!/bin/bash
. /etc/confluent/functions
# the image will be used to deploy itself
# provide both access to image (for parsing metadata)
# and existing mounts of image (to take advantage of caching)
mount -o bind /sys /sysroot/sys
mount -o bind /dev /sysroot/dev
mount -o bind /proc /sysroot/proc
mount -o bind /run /sysroot/run
if [ ! -f /tmp/mountparts.sh ]; then
mkdir -p /sysroot/run/imginst/sources/_
mount -o bind /mnt/remote /sysroot/run/imginst/sources/_
else
for srcmount in $(cat /tmp/mountparts.sh | awk '{print $2}'); do
srcname=${srcmount#/dev/mapper/mproot}
srcdir=$(echo $srcmount | sed -e 's!/dev/mapper/mproot!/mnt/remote!' -e 's!_!/!g')
mkdir -p /sysroot/run/imginst/sources/$srcname
mount -o bind $srcdir /sysroot/run/imginst/sources/$srcname
done
fi
cd /sysroot/run
chroot /sysroot/ bash -c "source /etc/confluent/functions; run_remote_python getinstalldisk"
chroot /sysroot/ bash -c "source /etc/confluent/functions; run_remote_parts pre.d"
if [ ! -f /sysroot/tmp/installdisk ]; then
echo 'Unable to find a suitable installation target device, ssh to port 2222 to investigate'
while [ ! -f /sysroot/tmp/installdisk ]; do
sleep 1
done
fi
lvm vgchange -a n
udevadm control -e
chroot /sysroot /usr/lib/systemd/systemd-udevd --daemon
chroot /sysroot bash -c "source /etc/confluent/functions; run_remote_python image2disk.py"
echo "Port 22" >> /etc/ssh/sshd_config
echo 'Match LocalPort 22' >> /etc/ssh/sshd_config
echo ' ChrootDirectory /sysroot/run/imginst/targ' >> /etc/ssh/sshd_config
kill -HUP $(cat /run/sshd.pid)
chroot /sysroot/run/imginst/targ bash -c "source /etc/confluent/functions; run_remote post.sh"
chroot /sysroot bash -c "umount \$(tac /proc/mounts|awk '{print \$2}'|grep ^/run/imginst/targ)"
@@ -0,0 +1,11 @@
[Unit]
Description=Confluent onboot hook
Requires=network-online.target
After=network-online.target
[Service]
ExecStart=/opt/confluent/bin/onboot.sh
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,33 @@
#!/bin/sh
# This script is executed on each boot as it is
# completed. It is best to edit the middle of the file as
# noted below so custom commands are executed before
# the script notifies confluent that install is fully complete.
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
export nodename confluent_mgr confluent_profile
. /etc/confluent/functions
mkdir -p /var/log/confluent
chmod 700 /var/log/confluent
exec >> /var/log/confluent/confluent-onboot.log
exec 2>> /var/log/confluent/confluent-onboot.log
chmod 600 /var/log/confluent/confluent-onboot.log
tail -f /var/log/confluent/confluent-onboot.log > /dev/console &
logshowpid=$!
run_remote_python syncfileclient
run_remote_python confignet
run_remote onboot.custom
# onboot scripts may be placed into onboot.d, e.g. onboot.d/01-firstaction.sh, onboot.d/02-secondaction.sh
run_remote_parts onboot.d
# Induce execution of remote configuration, e.g. ansible plays in ansible/onboot.d/
run_remote_config onboot.d
#curl -X POST -d 'status: booted' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
kill $logshowpid
@@ -0,0 +1,39 @@
#!/bin/sh
# This script is executed 'chrooted' into a cloned disk target before rebooting
#
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
export nodename confluent_mgr confluent_profile
. /etc/confluent/functions
mkdir -p /var/log/confluent
chmod 700 /var/log/confluent
exec >> /var/log/confluent/confluent-post.log
exec 2>> /var/log/confluent/confluent-post.log
chmod 600 /var/log/confluent/confluent-post.log
tail -f /var/log/confluent/confluent-post.log > /dev/console &
logshowpid=$!
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/firstboot.service > /etc/systemd/system/firstboot.service
mkdir -p /opt/confluent/bin
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/firstboot.sh > /opt/confluent/bin/firstboot.sh
chmod +x /opt/confluent/bin/firstboot.sh
systemctl enable firstboot
selinuxpolicy=$(grep ^SELINUXTYPE /etc/selinux/config |awk -F= '{print $2}')
if [ ! -z "$selinuxpolicy" ]; then
setfiles /etc/selinux/${selinuxpolicy}/contexts/files/file_contexts /etc/
fi
run_remote_python syncfileclient
run_remote post.custom
# post scripts may be placed into post.d, e.g. post.d/01-firstaction.sh, post.d/02-secondaction.sh
run_remote_parts post.d
# Induce execution of remote configuration, e.g. ansible plays in ansible/post.d/
run_remote_config post.d
curl -sf -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
kill $logshowpid
@@ -0,0 +1,279 @@
#!/usr/bin/python
import importlib
import tempfile
import json
import os
import shutil
import pwd
import grp
try:
from importlib.machinery import SourceFileLoader
def load_source(mod, path):
return SourceFileLoader(mod, path).load_module()
except ImportError:
from imp import load_source
try:
apiclient = load_source('apiclient', '/opt/confluent/bin/apiclient')
except IOError:
apiclient = load_source('apiclient', '/etc/confluent/apiclient')
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(object):
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]
if not self.targlines:
break
while not self.sourcelines[-1]:
self.sourcelines = self.sourcelines[:-1]
if not self.sourcelines:
break
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 name[0] in ('+', '#', '@'):
self.sourcedata.append(line)
elif 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[0] in ('+', '#', '@'):
continue
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 appendonce(basepath, filename):
with open(filename, 'rb') as filehdl:
thedata = filehdl.read()
targname = filename.replace(basepath, '')
try:
with open(targname, 'rb') as filehdl:
targdata = filehdl.read()
except IOError:
targdata = b''
if thedata in targdata:
return
with open(targname, 'ab') as targhdl:
targhdl.write(thedata)
def synchronize():
tmpdir = tempfile.mkdtemp()
appendoncedir = tempfile.mkdtemp()
try:
ac = apiclient.HTTPSClient()
data = json.dumps({'merge': tmpdir, 'appendonce': appendoncedir})
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles', data)
if status == 202:
lastrsp = ''
while status != 204:
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles')
if not isinstance(rsp, str):
rsp = rsp.decode('utf8')
if status == 200:
lastrsp = rsp
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')
for dirn in os.walk(appendoncedir):
for filen in dirn[2]:
appendonce(appendoncedir, os.path.join(dirn[0], filen))
if lastrsp:
lastrsp = json.loads(lastrsp)
opts = lastrsp.get('options', {})
for fname in opts:
uid = -1
gid = -1
for opt in opts[fname]:
if opt == 'owner':
try:
uid = pwd.getpwnam(opts[fname][opt]['name']).pw_uid
except KeyError:
uid = opts[fname][opt]['id']
elif opt == 'group':
try:
gid = grp.getgrnam(opts[fname][opt]['name']).gr_gid
except KeyError:
gid = opts[fname][opt]['id']
elif opt == 'permissions':
os.chmod(fname, int(opts[fname][opt], 8))
if uid != -1 or gid != -1:
os.chown(fname, uid, gid)
finally:
shutil.rmtree(tmpdir)
shutil.rmtree(appendoncedir)
if __name__ == '__main__':
synchronize()
@@ -0,0 +1,29 @@
# It is advised to avoid /var/lib/confluent/public as a source for syncing. /var/lib/confluent/public
# is served without authentication and thus any sensitive content would be a risk. If wanting to host
# syncfiles on a common share, it is suggested to have /var/lib/confluent be the share and use some other
# subdirectory other than public.
#
# Syncing is performed as the 'confluent' user, so all source files must be accessible by the confluent user.
#
# This file lists files to synchronize or merge to the deployed systems from the deployment server
# To specify taking /some/path/hosts on the deployment server and duplicating it to /etc/hosts:
# Note particularly the use of '->' to delineate source from target.
# /some/path/hosts -> /etc/hosts
# If wanting to simply use the same path for source and destinaiton, the -> may be skipped:
# /etc/hosts
# More function is available, for example to limit the entry to run only on n1 through n8, and to set
# owner, group, and permissions in octal notation:
# /example/source -> n1-n8:/etc/target (owner=root,group=root,permissions=600)
# Entries under APPENDONCE: will be added to specified target, only if the target does not already
# contain the data in the source already in its entirety. This allows append in a fashion that
# is friendly to being run repeatedly
# Entries under MERGE: will attempt to be intelligently merged. This supports /etc/group and /etc/passwd
# Any supporting entries in /etc/shadow or /etc/gshadow are added automatically, with password disabled
# It also will not inject 'system' ids (under 1,000 usually) as those tend to be local and rpm managed.
MERGE:
# /etc/passwd
# /etc/group
@@ -18,4 +18,6 @@ mkdir -p /sysroot/etc/ssh/
for i in /ssh/*.ca; do
echo '@cert-authority *' $(cat $i) >> /sysroot/etc/ssh/ssh_known_hosts
done
cp /opt/confluent/bin/apiclient /sysroot/etc/confluent
mkdir -p /sysroot/opt/confluent/bin
cp /opt/confluent/bin/apiclient /sysroot/opt/confluent/bin
cp /opt/confluent/bin/apiclient /sysroot/etc/confluent/
@@ -79,5 +79,9 @@ if [ -e /tmp/installdisk -a ! -e /tmp/partitioning ]; then
echo ignoredisk --only-use $(cat /tmp/installdisk) >> /tmp/partitioning
echo autopart --nohome $LUKSPARTY >> /tmp/partitioning
fi
python /etc/confluent/apiclient /confluent-public/os/$confluent_profile/kickstart.custom -o /tmp/kickstart.custom
if [ -f /opt/confluent/bin/apiclient ]; then
python /opt/confluent/bin/apiclient /confluent-public/os/$confluent_profile/kickstart.custom -o /tmp/kickstart.custom
else
python /etc/confluent/apiclient /confluent-public/os/$confluent_profile/kickstart.custom -o /tmp/kickstart.custom
fi
kill $logshowpid
@@ -10,6 +10,8 @@
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
export confluent_mgr confluent_profile nodename
cp -a /etc/confluent /mnt/sysimage/etc
mkdir -p /mnt/sysimage/opt/confluent/bin
cp /opt/confluent/bin/apiclient /mnt/sysimage/opt/confluent/bin/
chmod -R og-rwx /mnt/sysimage/etc/confluent
cp /tmp/functions /mnt/sysimage/etc/confluent/
hostnamectl set-hostname $nodename
@@ -1,5 +1,13 @@
# It is advised to avoid /var/lib/confluent/public as a source for syncing. /var/lib/confluent/public
# is served without authentication and thus any sensitive content would be a risk. If wanting to host
# syncfiles on a common share, it is suggested to have /var/lib/confluent be the share and use some other
# subdirectory other than public.
#
# Syncing is performed as the 'confluent' user, so all source files must be accessible by the confluent user.
#
# This file lists files to synchronize or merge to the deployed systems from the deployment server
# To specify taking /some/path/hosts on the deploymennt server and duplicating it to /etc/hosts:
# To specify taking /some/path/hosts on the deployment server and duplicating it to /etc/hosts:
# Note particularly the use of '->' to delineate source from target.
# /some/path/hosts -> /etc/hosts
# If wanting to simply use the same path for source and destinaiton, the -> may be skipped:
@@ -8,6 +8,7 @@ get_remote_apikey() {
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
if [ -z "$confluent_apikey" ]; then
echo "Unable to acquire node api key, set deployment.apiarmed=once on node '$nodename', retrying..."
if [ ! -z "$autoconsdev" ]; then echo "Unable to acquire node api key, set deployment.apiarmed=once on node '$nodename', retrying..." > $autoconsdev; fi
sleep 10
elif [ -c /dev/tpmrm0 ]; then
tmpdir=$(mktemp -d)
@@ -38,6 +39,7 @@ echo sshd:x:30:30:SSH User:/var/empty/sshd:/sbin/nologin >> /etc/passwd
if ! grep console= /proc/cmdline >& /dev/null; then
autocons=$(/opt/confluent/bin/autocons)
autoconsdev=${autocons%,*}
autocons=${autocons##*/}
echo "Automatic console configured for $autocons"
fi
@@ -129,12 +131,18 @@ while [ $ready = "0" ]; do
fi
confluent_mgr=${confluent_mgr#[}
confluent_mgr=${confluent_mgr%]}
elif grep 'SSL' $tmperr > /dev/null; then
confluent_mgr=${confluent_mgr#[}
confluent_mgr=${confluent_mgr%]}
echo 'Failure establishing TLS conneection to '$confluent_mgr' (try `osdeploy initialize -t` on the deployment server)'
if [ ! -z "$autoconsdev" ]; then echo 'Failure establishing TLS conneection to '$confluent_mgr' (try `osdeploy initialize -t` on the deployment server)' > $autoconsdev; fi
sleep 10
else
ready=1
fi
rm $tmperr
done
if [ ! -z "$autocons" ] && grep textconsole: true /etc/confluent/confluent.deploycfg > /dev/null; then /opt/confluent/bin/autocons -c > /dev/null; fi
if [ ! -z "$autocons" ] && grep "textconsole: true" /etc/confluent/confluent.deploycfg > /dev/null; then /opt/confluent/bin/autocons -c > /dev/null; fi
if [ -c /dev/tpmrm0 ]; then
tpm2_pcrextend 15:sha256=2fbe96c50dde38ce9cd2764ddb79c216cfbcd3499568b1125450e60c45dd19f2
fi
@@ -0,0 +1,43 @@
try:
import configparser
except ImportError:
import ConfigParser as configparser
import cStringIO
import imp
import sys
apiclient = imp.load_source('apiclient', '/opt/confluent/bin/apiclient')
repo = None
server = None
profile = None
with open('/etc/confluent/confluent.deploycfg') as dplcfgfile:
lines = dplcfgfile.read().split('\n')
for line in lines:
if line.startswith('deploy_server:'):
_, server = line.split(' ', 1)
if line.startswith('profile: '):
_, profile = line.split(' ', 1)
path = '/confluent-public/os/{0}/distribution/'.format(profile)
clnt = apiclient.HTTPSClient()
cfgdata = clnt.grab_url(path + '.treeinfo').decode()
c = configparser.ConfigParser()
try:
c.read_string(cfgdata)
except AttributeError:
f = cStringIO.StringIO(cfgdata)
c.readfp(f)
for sec in c.sections():
if sec.startswith('variant-'):
try:
repopath = c.get(sec, 'repository')
except Exception:
continue
_, varname = sec.split('-', 1)
reponame = '/etc/yum.repos.d/local-{0}.repo'.format(varname.lower())
with open(reponame, 'w') as repout:
repout.write('[local-{0}]\n'.format(varname.lower()))
repout.write('name=Local install repository for {0}\n'.format(varname))
if repopath[0] == '.':
repopath = repopath[1:]
repout.write('baseurl=https://{}/confluent-public/os/{}/distribution/{}\n'.format(server, profile, repopath))
repout.write('enabled=1\n')
@@ -123,4 +123,5 @@ if grep installtodisk /proc/cmdline > /dev/null; then
fi
mv /lib/modules/$(uname -r) /lib/modules/$(uname -r)-ramfs
ln -s /sysroot/lib/modules/$(uname -r) /lib/modules/
kill $(grep -l ^/usr/lib/systemd/systemd-udevd /proc/*/cmdline|cut -d/ -f 3)
exec /opt/confluent/bin/start_root
@@ -19,6 +19,9 @@ chmod 600 /var/log/confluent/confluent-onboot.log
tail -f /var/log/confluent/confluent-onboot.log > /dev/console &
logshowpid=$!
rpm --import /etc/pki/rpm-gpg/*
run_remote_python add_local_repositories
run_remote_python syncfileclient
run_remote_python confignet
@@ -1,5 +1,13 @@
# It is advised to avoid /var/lib/confluent/public as a source for syncing. /var/lib/confluent/public
# is served without authentication and thus any sensitive content would be a risk. If wanting to host
# syncfiles on a common share, it is suggested to have /var/lib/confluent be the share and use some other
# subdirectory other than public.
#
# Syncing is performed as the 'confluent' user, so all source files must be accessible by the confluent user.
#
# This file lists files to synchronize or merge to the deployed systems from the deployment server
# To specify taking /some/path/hosts on the deploymennt server and duplicating it to /etc/hosts:
# To specify taking /some/path/hosts on the deployment server and duplicating it to /etc/hosts:
# Note particularly the use of '->' to delineate source from target.
# /some/path/hosts -> /etc/hosts
# If wanting to simply use the same path for source and destinaiton, the -> may be skipped:
@@ -1,5 +1,6 @@
#!/bin/bash
echo -n "" >> /tmp/net.ifaces
echo -n "" > /tmp/01-autocons.devnode
cat /tls/*.0 >> /etc/pki/tls/certs/ca-bundle.crt
if ! grep console= /proc/cmdline >& /dev/null; then
autocons=$(/opt/confluent/bin/autocons)
@@ -19,4 +19,6 @@ mkdir -p /sysroot/etc/ssh/
for i in /ssh/*.ca; do
echo '@cert-authority *' $(cat $i) >> /sysroot/etc/ssh/ssh_known_hosts
done
cp /opt/confluent/bin/apiclient /sysroot/etc/confluent
mkdir -p /sysroot/opt/confluent/bin
cp /opt/confluent/bin/apiclient /sysroot/opt/confluent/bin
cp /opt/confluent/bin/apiclient /sysroot/etc/confluent/
@@ -42,6 +42,7 @@ cd /sys/class/net
while ! grep ^EXTMGRINFO: /etc/confluent/confluent.info | awk -F'|' '{print $3}' | grep 1 >& /dev/null && [ "$TRIES" -lt 60 ]; do
TRIES=$((TRIES + 1))
for currif in *; do
echo 0 > /proc/sys/net/ipv6/conf/${currif}/autoconf
ip link set $currif up
done
/opt/confluent/bin/copernicus -t > /etc/confluent/confluent.info
@@ -56,7 +57,14 @@ nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
#TODO: blkid --label <whatever> to find mounted api
cat /tls/*.pem > /etc/confluent/ca.pem
confluentpython /opt/confluent/bin/apiclient /confluent-api/self/deploycfg2 > /etc/confluent/confluent.deploycfg
autocons=$(cat /tmp/01-autocons.devnode)
errout=""
if [ ! -z "$autocons" ]; then
errout="-e $autocons"
fi
while ! confluentpython /opt/confluent/bin/apiclient $errout /confluent-api/self/deploycfg2 > /etc/confluent/confluent.deploycfg; do
sleep 10
done
ifidx=$(cat /tmp/confluent.ifidx)
ifname=$(ip link |grep ^$ifidx:|awk '{print $2}')
ifname=${ifname%:}
@@ -92,6 +100,7 @@ if [ "$textconsole" = "true" ] && ! grep console= /proc/cmdline > /dev/null; the
if [ ! -z "$autocons" ]; then
echo Auto-configuring installed system to use text console
echo Auto-configuring installed system to use text console > $autocons
/opt/confluent/bin/autocons -c > /dev/null
cp /tmp/01-autocons.conf /etc/cmdline.d/
else
echo "Unable to automatically detect requested text console"
@@ -136,10 +145,14 @@ if [ "$v6cfg" = "static" ]; then
echo ip=$v6addr::$v6gw:$v6nm:$hostname:$ifname:none >> /etc/cmdline.d/01-confluent.conf
fi
nameserversec=0
v4dns=0
v6dns=0
while read -r entry; do
if [ $nameserversec = 1 ]; then
if [[ $entry == "-"* ]] && [[ $entry != "- ''" ]]; then
echo nameserver=${entry#- } >> /etc/cmdline.d/01-confluent.conf
[[ "$entry" == *:* ]] && v6dns=1
[[ "$entry" == *.* ]] && v4dns=1
continue
fi
fi
@@ -152,6 +165,24 @@ done < /etc/confluent/confluent.deploycfg
if [ -e /lib/nm-lib.sh ]; then
. /lib/nm-lib.sh
nm_generate_connections
if [ ! -z "$dnsdomain" ] && [ "$dnsdomain" != "null" ]; then
grep -v ^dns-search= /run/NetworkManager/system-connections/$ifname.nmconnection > /run/NetworkManager/system-connections/$ifname.nmconnection.new
mv /run/NetworkManager/system-connections/$ifname.nmconnection.new /run/NetworkManager/system-connections/$ifname.nmconnection
if [ "$v4dns" = 1 ]; then
awk '/^\[ipv4\]/ {print;print "dns-search='"$dnsdomain"'";next}1' /run/NetworkManager/system-connections/$ifname.nmconnection > /run/NetworkManager/system-connections/$ifname.nmconnection.new
else
awk '/^\[ipv4\]/ {print;print "dns-search=";next}1' /run/NetworkManager/system-connections/$ifname.nmconnection > /run/NetworkManager/system-connections/$ifname.nmconnection.new
fi
mv /run/NetworkManager/system-connections/$ifname.nmconnection.new /run/NetworkManager/system-connections/$ifname.nmconnection
if [ "$v6dns" = 1 ]; then
awk '/^\[ipv6\]/ {print;print "dns-search='"$dnsdomain"'";next}1' /run/NetworkManager/system-connections/$ifname.nmconnection > /run/NetworkManager/system-connections/$ifname.nmconnection.new
else
awk '/^\[ipv6\]/ {print;print "dns-search=";next}1' /run/NetworkManager/system-connections/$ifname.nmconnection > /run/NetworkManager/system-connections/$ifname.nmconnection.new
fi
mv /run/NetworkManager/system-connections/$ifname.nmconnection.new /run/NetworkManager/system-connections/$ifname.nmconnection
chmod 600 /run/NetworkManager/system-connections/$ifname.nmconnection
fi
if [[ "$ifname" == ib* ]]; then
sed -i s/type=ethernet/type=infiniband/ /run/NetworkManager/system-connections/$ifname.nmconnection
if ! grep '\[infiniband\]' /run/NetworkManager/system-connections/$ifname.nmconnection > /dev/null; then
@@ -94,6 +94,7 @@ chmod +x /mnt/sysimage/opt/confluent/bin/firstboot.sh
%post
cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
cp /etc/confluent/tls/*.pem /etc/pki/ca-trust/source/anchors
systemctl enable firstboot
chgrp ssh_keys /etc/ssh/ssh*key
restorecon /etc/ssh/ssh*key /root/.shosts /etc/ssh/shosts.equiv /etc/ssh/ssh_config.d/* /opt/confluent/bin/firstboot.sh
@@ -3,9 +3,13 @@ try:
except ImportError:
import ConfigParser as configparser
import cStringIO
import imp
import importlib.util
import importlib.machinery
import sys
apiclient = imp.load_source('apiclient', '/etc/confluent/apiclient')
modloader = importlib.machinery.SourceFileLoader('apiclient', '/opt/confluent/bin/apiclient')
modspec = importlib.util.spec_from_file_location('apiclient', '/opt/confluent/bin/apiclient', loader=modloader)
apiclient = importlib.util.module_from_spec(modspec)
modspec.loader.exec_module(apiclient)
repo = None
server = None
profile = None
@@ -42,11 +42,20 @@ echo lang $locale > /tmp/langinfo
echo keyboard --vckeymap=$keymap >> /tmp/langinfo
tz=$(grep ^timezone: /etc/confluent/confluent.deploycfg)
tz=${tz#timezone: }
MVER=$(grep VERSION_ID /etc/os-release|cut -d = -f 2 |cut -d . -f 1|cut -d '"' -f 2)
ntpsrvs=""
if grep ^ntpservers: /etc/confluent/confluent.deploycfg > /dev/null; then
ntpsrvs="--ntpservers="$(sed -n '/^ntpservers:/,/^[^-]/p' /etc/confluent/confluent.deploycfg|sed 1d|sed '$d' | sed -e 's/^- //' | paste -sd,)
if [ "$MVER" -ge 9 ]; then
if grep ^ntpservers: /etc/confluent/confluent.deploycfg > /dev/null; then
for ntpsrv in $(sed -n '/^ntpservers:/,/^[^-]/p' /etc/confluent/confluent.deploycfg|sed 1d|sed '$d' | sed -e 's/^- //'); do
echo timesource --ntp-server $ntpsrv >> /tmp/timezone
done
fi
else
if grep ^ntpservers: /etc/confluent/confluent.deploycfg > /dev/null; then
ntpsrvs="--ntpservers="$(sed -n '/^ntpservers:/,/^[^-]/p' /etc/confluent/confluent.deploycfg|sed 1d|sed '$d' | sed -e 's/^- //' | paste -sd,)
fi
fi
echo timezone $ntpsrvs $tz --utc > /tmp/timezone
echo timezone $ntpsrvs $tz --utc >> /tmp/timezone
rootpw=$(grep ^rootpassword /etc/confluent/confluent.deploycfg | awk '{print $2}')
if [ "$rootpw" = null ]; then
echo "rootpw --lock" > /tmp/rootpw
@@ -88,7 +97,7 @@ fi
export confluent_mgr confluent_profile nodename
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/functions > /tmp/functions
. /tmp/functions
confluentpython /etc/confluent/apiclient /confluent-public/os/$confluent_profile/kickstart.custom -o /tmp/kickstart.custom
confluentpython /opt/confluent/bin/apiclient /confluent-public/os/$confluent_profile/kickstart.custom -o /tmp/kickstart.custom
run_remote pre.custom
run_remote_parts pre.d
confluentpython /etc/confluent/apiclient /confluent-public/os/$confluent_profile/kickstart -o /tmp/kickstart.base
@@ -10,6 +10,8 @@
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
export confluent_mgr confluent_profile nodename
cp -a /etc/confluent /mnt/sysimage/etc
mkdir -p /mnt/sysimage/opt/confluent/bin
cp /opt/confluent/bin/apiclient /mnt/sysimage/opt/confluent/bin/
chmod -R og-rwx /mnt/sysimage/etc/confluent
cp /tmp/functions /mnt/sysimage/etc/confluent/
hostnamectl set-hostname $nodename
@@ -1,5 +1,13 @@
# It is advised to avoid /var/lib/confluent/public as a source for syncing. /var/lib/confluent/public
# is served without authentication and thus any sensitive content would be a risk. If wanting to host
# syncfiles on a common share, it is suggested to have /var/lib/confluent be the share and use some other
# subdirectory other than public.
#
# Syncing is performed as the 'confluent' user, so all source files must be accessible by the confluent user.
#
# This file lists files to synchronize or merge to the deployed systems from the deployment server
# To specify taking /some/path/hosts on the deploymennt server and duplicating it to /etc/hosts:
# To specify taking /some/path/hosts on the deployment server and duplicating it to /etc/hosts:
# Note particularly the use of '->' to delineate source from target.
# /some/path/hosts -> /etc/hosts
# If wanting to simply use the same path for source and destinaiton, the -> may be skipped:
@@ -0,0 +1,254 @@
get_remote_apikey() {
while [ -z "$confluent_apikey" ]; do
/opt/confluent/bin/clortho $nodename $confluent_mgr > /etc/confluent/confluent.apikey
if grep ^SEALED: /etc/confluent/confluent.apikey > /dev/null; then
# we don't support remote sealed api keys anymore
echo > /etc/confluent/confluent.apikey
fi
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
if [ -z "$confluent_apikey" ]; then
echo "Unable to acquire node api key, set deployment.apiarmed=once on node '$nodename', retrying..."
if [ ! -z "$autoconsdev" ]; then echo "Unable to acquire node api key, set deployment.apiarmed=once on node '$nodename', retrying..." > $autoconsdev; fi
sleep 10
elif [ -c /dev/tpmrm0 ]; then
tmpdir=$(mktemp -d)
cd $tmpdir
tpm2_startauthsession --session=session.ctx
tpm2_policypcr -Q --session=session.ctx --pcr-list="sha256:15" --policy=pcr15.sha256.policy
tpm2_createprimary -G ecc -Q --key-context=prim.ctx
(echo -n "CONFLUENT_APIKEY:";cat /etc/confluent/confluent.apikey) | tpm2_create -Q --policy=pcr15.sha256.policy --public=data.pub --private=data.priv -i - -C prim.ctx
tpm2_load -Q --parent-context=prim.ctx --public=data.pub --private=data.priv --name=confluent.apikey --key-context=data.ctx
tpm2_evictcontrol -Q -c data.ctx
tpm2_flushcontext session.ctx
cd - > /dev/null
rm -rf $tmpdir
fi
done
}
root=1
rootok=1
netroot=confluent
#clear
mkdir -p /etc/ssh
mkdir -p /var/tmp/
mkdir -p /var/empty/sshd
mkdir -p /usr/share/empty.sshd
mkdir -p /etc/confluent
sed -i '/^root:x/d' /etc/passwd
echo root:x:0:0::/:/bin/bash >> /etc/passwd
echo sshd:x:30:30:SSH User:/var/empty/sshd:/sbin/nologin >> /etc/passwd
if ! grep console= /proc/cmdline >& /dev/null; then
autocons=$(/opt/confluent/bin/autocons)
autoconsdev=${autocons%,*}
autocons=${autocons##*/}
echo "Automatic console configured for $autocons"
fi
echo "Initializing confluent diskless environment"
echo -n "udevd: "
/usr/lib/systemd/systemd-udevd --daemon
echo -n "Loading drivers..."
udevadm trigger
udevadm trigger --type=devices --action=add
udevadm settle
modprobe ib_ipoib
modprobe ib_umad
modprobe hfi1
modprobe mlx5_ib
echo "done"
cat > /etc/ssh/sshd_config << EOF
Port 2222
Subsystem sftp /usr/libexec/openssh/sftp-server
PermitRootLogin yes
AuthorizedKeysFile .ssh/authorized_keys
EOF
mkdir /root/.ssh
mkdir /.ssh
cat /ssh/*pubkey > /root/.ssh/authorized_keys 2>/dev/null
cp /root/.ssh/authorized_keys /.ssh/
cat /tls/*.pem > /etc/confluent/ca.pem
mkdir -p /etc/pki/tls/certs
cat /tls/*.pem > /etc/pki/tls/certs/ca-bundle.crt
TRIES=0
oldumask=$(umask)
umask 0077
tpmdir=$(mktemp -d)
cd $tpmdir
lasthdl=""
if [ -c /dev/tpmrm0 ]; then
for hdl in $(tpm2_getcap handles-persistent|awk '{print $2}'); do
tpm2_startauthsession --policy-session --session=session.ctx
tpm2_policypcr -Q --session=session.ctx --pcr-list="sha256:15" --policy=pcr15.sha256.policy
unsealeddata=$(tpm2_unseal --auth=session:session.ctx -Q -c $hdl 2>/dev/null)
tpm2_flushcontext session.ctx
if [[ $unsealeddata == "CONFLUENT_APIKEY:"* ]]; then
confluent_apikey=${unsealeddata#CONFLUENT_APIKEY:}
echo $confluent_apikey > /etc/confluent/confluent.apikey
if [ -n "$lasthdl" ]; then
tpm2_evictcontrol -c $lasthdl
fi
lasthdl=$hdl
fi
done
fi
cd - > /dev/null
rm -rf $tpmdir
touch /etc/confluent/confluent.info
cd /sys/class/net
echo -n "Scanning for network configuration..."
while ! grep ^EXTMGRINFO: /etc/confluent/confluent.info | awk -F'|' '{print $3}' | grep 1 >& /dev/null && [ "$TRIES" -lt 30 ]; do
TRIES=$((TRIES + 1))
for i in *; do
ip link set $i up
done
/opt/confluent/bin/copernicus -t > /etc/confluent/confluent.info
done
cd /
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
hostname $nodename
confluent_mgr=$(grep '^EXTMGRINFO:.*1$' /etc/confluent/confluent.info | head -n 1 | awk -F': ' '{print $2}' | awk -F'|' '{print $1}')
if [ -z "$confluent_mgr" ]; then
confluent_mgr=$(grep ^MANAGER: /etc/confluent/confluent.info|head -n 1 | awk '{print $2}')
fi
if [[ $confluent_mgr == *%* ]]; then
echo $confluent_mgr | awk -F% '{print $2}' > /tmp/confluent.ifidx
ifidx=$(cat /tmp/confluent.ifidx)
ifname=$(ip link |grep ^$ifidx:|awk '{print $2}')
ifname=${ifname%:}
fi
ready=0
while [ $ready = "0" ]; do
get_remote_apikey
if [[ $confluent_mgr == *:* ]]; then
confluent_mgr="[$confluent_mgr]"
fi
tmperr=$(mktemp)
curl -sSf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/deploycfg > /etc/confluent/confluent.deploycfg 2> $tmperr
if grep 401 $tmperr > /dev/null; then
confluent_apikey=""
if [ -n "$lasthdl" ]; then
tpm2_evictcontrol -c $lasthdl
fi
confluent_mgr=${confluent_mgr#[}
confluent_mgr=${confluent_mgr%]}
elif grep 'SSL' $tmperr > /dev/null; then
confluent_mgr=${confluent_mgr#[}
confluent_mgr=${confluent_mgr%]}
echo 'Failure establishing TLS conneection to '$confluent_mgr' (try `osdeploy initialize -t` on the deployment server)'
if [ ! -z "$autoconsdev" ]; then echo 'Failure establishing TLS conneection to '$confluent_mgr' (try `osdeploy initialize -t` on the deployment server)' > $autoconsdev; fi
sleep 10
else
ready=1
fi
rm $tmperr
done
if [ ! -z "$autocons" ] && grep "textconsole: true" /etc/confluent/confluent.deploycfg > /dev/null; then /opt/confluent/bin/autocons -c > /dev/null; fi
if [ -c /dev/tpmrm0 ]; then
tpm2_pcrextend 15:sha256=2fbe96c50dde38ce9cd2764ddb79c216cfbcd3499568b1125450e60c45dd19f2
fi
umask $oldumask
autoconfigmethod=$(grep ^ipv4_method: /etc/confluent/confluent.deploycfg |awk '{print $2}')
if [ "$autoconfigmethod" = "dhcp" ]; then
echo -n "Attempting to use dhcp to bring up $ifname..."
dhclient $ifname
echo "Complete:"
ip addr show dev $ifname
else
v4addr=$(grep ^ipv4_address: /etc/confluent/confluent.deploycfg)
v4addr=${v4addr#ipv4_address: }
v4gw=$(grep ^ipv4_gateway: /etc/confluent/confluent.deploycfg)
v4gw=${v4gw#ipv4_gateway: }
if [ "$v4gw" = "null" ]; then
v4gw=""
fi
v4nm=$(grep ^prefix: /etc/confluent/confluent.deploycfg)
v4nm=${v4nm#prefix: }
echo "Setting up $ifname as static at $v4addr/$v4nm"
ip addr add dev $ifname $v4addr/$v4nm
if [ ! -z "$v4gw" ]; then
ip route add default via $v4gw
fi
mkdir -p /run/NetworkManager/system-connections
cat > /run/NetworkManager/system-connections/$ifname.nmconnection << EOC
[connection]
EOC
echo id=${ifname} >> /run/NetworkManager/system-connections/$ifname.nmconnection
echo uuid=$(uuidgen) >> /run/NetworkManager/system-connections/$ifname.nmconnection
cat >> /run/NetworkManager/system-connections/$ifname.nmconnection << EOC
type=ethernet
autoconnect-retries=1
EOC
echo interface-name=$ifname >> /run/NetworkManager/system-connections/$ifname.nmconnection
cat >> /run/NetworkManager/system-connections/$ifname.nmconnection << EOC
multi-connect=1
permissions=
wait-device-timeout=60000
[ethernet]
mac-address-blacklist=
[ipv4]
EOC
echo address1=$v4addr/$v4nm >> /run/NetworkManager/system-connections/$ifname.nmconnection
if [ ! -z "$v4gw" ]; then
echo gateway=$v4gw >> /run/NetworkManager/system-connections/$ifname.nmconnection
fi
nameserversec=0
nameservers=""
while read -r entry; do
if [ $nameserversec = 1 ]; then
if [[ $entry == "-"* ]]; then
nameservers="$nameservers"${entry#- }";"
continue
fi
fi
nameserversec=0
if [ "${entry%:*}" = "nameservers" ]; then
nameserversec=1
continue
fi
done < /etc/confluent/confluent.deploycfg
echo dns=$nameservers >> /run/NetworkManager/system-connections/$ifname.nmconnection
dnsdomain=$(grep ^dnsdomain: /etc/confluent/confluent.deploycfg)
dnsdomain=${dnsdomain#dnsdomain: }
echo dns-search=$dnsdomain >> /run/NetworkManager/system-connections/$ifname.nmconnection
cat >> /run/NetworkManager/system-connections/$ifname.nmconnection << EOC
may-fail=false
method=manual
[ipv6]
addr-gen-mode=eui64
method=auto
[proxy]
EOC
fi
chmod 600 /run/NetworkManager/system-connections/*.nmconnection
echo -n "Initializing ssh..."
ssh-keygen -A
for pubkey in /etc/ssh/ssh_host*key.pub; do
certfile=${pubkey/.pub/-cert.pub}
privfile=${pubkey%.pub}
curl -sf -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" -d @$pubkey https://$confluent_mgr/confluent-api/self/sshcert > $certfile
if [ -s $certfile ]; then
echo HostCertificate $certfile >> /etc/ssh/sshd_config
fi
echo HostKey $privfile >> /etc/ssh/sshd_config
done
/usr/sbin/sshd
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg| awk '{print $2}')
confluent_proto=$(grep ^protocol: /etc/confluent/confluent.deploycfg| awk '{print $2}')
confluent_urls=""
for addr in $(grep ^MANAGER: /etc/confluent/confluent.info|awk '{print $2}'|sed -e s/%/%25/); do
if [[ $addr == *:* ]]; then
confluent_urls="$confluent_urls $confluent_proto://[$addr]/confluent-public/os/$confluent_profile/rootimg.sfs"
else
confluent_urls="$confluent_urls $confluent_proto://$addr/confluent-public/os/$confluent_profile/rootimg.sfs"
fi
done
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg| awk '{print $2}')
mkdir -p /etc/confluent
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/functions > /etc/confluent/functions
. /etc/confluent/functions
source_remote imageboot.sh
@@ -0,0 +1,47 @@
try:
import configparser
except ImportError:
import ConfigParser as configparser
import cStringIO
import importlib.util
import importlib.machinery
import sys
modloader = importlib.machinery.SourceFileLoader('apiclient', '/opt/confluent/bin/apiclient')
modspec = importlib.util.spec_from_file_location('apiclient', '/opt/confluent/bin/apiclient', loader=modloader)
apiclient = importlib.util.module_from_spec(modspec)
modspec.loader.exec_module(apiclient)
repo = None
server = None
profile = None
with open('/etc/confluent/confluent.deploycfg') as dplcfgfile:
lines = dplcfgfile.read().split('\n')
for line in lines:
if line.startswith('deploy_server:'):
_, server = line.split(' ', 1)
if line.startswith('profile: '):
_, profile = line.split(' ', 1)
path = '/confluent-public/os/{0}/distribution/'.format(profile)
clnt = apiclient.HTTPSClient()
cfgdata = clnt.grab_url(path + '.treeinfo').decode()
c = configparser.ConfigParser()
try:
c.read_string(cfgdata)
except AttributeError:
f = cStringIO.StringIO(cfgdata)
c.readfp(f)
for sec in c.sections():
if sec.startswith('variant-'):
try:
repopath = c.get(sec, 'repository')
except Exception:
continue
_, varname = sec.split('-', 1)
reponame = '/etc/yum.repos.d/local-{0}.repo'.format(varname.lower())
with open(reponame, 'w') as repout:
repout.write('[local-{0}]\n'.format(varname.lower()))
repout.write('name=Local install repository for {0}\n'.format(varname))
if repopath[0] == '.':
repopath = repopath[1:]
repout.write('baseurl=https://{}/confluent-public/os/{}/distribution/{}\n'.format(server, profile, repopath))
repout.write('enabled=1\n')
@@ -0,0 +1,4 @@
. /etc/confluent/functions
# This is a convenient place to keep customizations separate from modifying the stock scripts
# While modification of the stock scripts is fine, it may be easier to rebase to a newer
# stock profile if the '.custom' files are used.
@@ -0,0 +1,11 @@
[Unit]
Description=First Boot Process
Requires=network-online.target
After=network-online.target
[Service]
ExecStart=/opt/confluent/bin/firstboot.sh
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,40 @@
#!/bin/sh
# This script is executed on the first boot after install has
# completed. It is best to edit the middle of the file as
# noted below so custom commands are executed before
# the script notifies confluent that install is fully complete.
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
export nodename confluent_mgr confluent_profile
. /etc/confluent/functions
exec >> /var/log/confluent/confluent-firstboot.log
exec 2>> /var/log/confluent/confluent-firstboot.log
chmod 600 /var/log/confluent/confluent-firstboot.log
tail -f /var/log/confluent/confluent-firstboot.log > /dev/console &
logshowpid=$!
while ! ping -c 1 $confluent_mgr >& /dev/null; do
sleep 1
done
if [ ! -f /etc/confluent/firstboot.ran ]; then
touch /etc/confluent/firstboot.ran
cat /etc/confluent/tls/*.pem >> /etc/pki/tls/certs/ca-bundle.crt
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.d
# Induce execution of remote configuration, e.g. ansible plays in ansible/firstboot.d/
run_remote_config firstboot.d
fi
curl -X POST -d 'status: complete' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
systemctl disable firstboot
rm /etc/systemd/system/firstboot.service
rm /etc/confluent/firstboot.ran
kill $logshowpid
@@ -0,0 +1,196 @@
#!/bin/bash
function test_mgr() {
if curl -s https://${1}/confluent-api/ > /dev/null; then
return 0
fi
return 1
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
elif [ -x /usr/bin/python3 ]; then
/usr/bin/python3 $*
elif [ -x /usr/bin/python ]; then
/usr/bin/python $*
elif [ -x /usr/bin/python2 ]; then
/usr/bin/python2 $*
fi
}
function set_confluent_vars() {
if [ -z "$nodename" ]; then
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
fi
if [[ "$confluent_mgr" == *"%"* ]]; then
confluent_mgr=""
fi
if [ -z "$confluent_mgr" ]; then
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
if ! test_mgr $confluent_mgr; then
confluent_mgr=$(grep ^deploy_server_v6: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
if [[ "$confluent_mgr" = *":"* ]]; then
confluent_mgr="[$confluent_mgr]"
fi
fi
if ! test_mgr $confluent_mgr; then
BESTMGRS=$(grep ^EXTMGRINFO: /etc/confluent/confluent.info | grep '|1$' | sed -e 's/EXTMGRINFO: //' -e 's/|.*//')
OKMGRS=$(grep ^EXTMGRINFO: /etc/confluent/confluent.info | grep '|0$' | sed -e 's/EXTMGRINFO: //' -e 's/|.*//')
for confluent_mgr in $BESTMGRS $OKMGRS; do
if [[ $confluent_mgr == *":"* ]]; then
confluent_mgr="[$confluent_mgr]"
fi
if test_mgr $confluent_mgr; then
break
fi
done
fi
fi
if [ -z "$confluent_profile" ]; then
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
}
fetch_remote() {
curlargs=""
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://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
fi
scriptlist=$(confluentpython $apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
for script in $scriptlist; do
source_remote $1/$script
done
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
fi
scriptlist=$(confluentpython $apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
for script in $scriptlist; do
run_remote $1/$script
done
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
}
source_remote() {
set_confluent_vars
unsettmpdir=0
echo
echo '---------------------------------------------------------------------------'
echo Sourcing $1 from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
if [ -z "$confluentscripttmpdir" ]; then
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unsettmpdir=1
fi
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
chmod +x $1
cmd=$1
shift
source ./$cmd
cd - > /dev/null
if [ "$unsettmpdir" = 1 ]; then
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
unsettmpdir=0
fi
rm -rf $confluentscripttmpdir
return $retcode
}
run_remote() {
requestedcmd="'$*'"
unsettmpdir=0
set_confluent_vars
echo
echo '---------------------------------------------------------------------------'
echo Running $requestedcmd from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
if [ -z "$confluentscripttmpdir" ]; then
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unsettmpdir=1
fi
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $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=$?
if [ $retcode -ne 0 ]; then
echo "$requestedcmd exited with code $retcode"
fi
cd - > /dev/null
if [ "$unsettmpdir" = 1 ]; then
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
unsettmpdir=0
fi
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://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
curl -f -sS $curlargs https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
confluentpython $*
retcode=$?
echo "'$*' exited with code $retcode"
cd - > /dev/null
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
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 $confluent_mgr under profile $confluent_profile
confluentpython $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
confluentpython $apiclient /confluent-api/self/remoteconfig/status -w 204
echo
echo 'Completed remote configuration'
echo '---------------------------------------------------------------------------'
return
}
#If invoked as a command, use the arguments to actually run a function
(return 0 2>/dev/null) || $1 "${@:2}"
@@ -0,0 +1,93 @@
import subprocess
import os
class DiskInfo(object):
def __init__(self, devname):
self.name = devname
self.wwn = None
self.path = None
self.model = ''
self.size = 0
self.driver = None
self.mdcontainer = ''
devnode = '/dev/{0}'.format(devname)
qprop = subprocess.check_output(
['udevadm', 'info', '--query=property', devnode])
if not isinstance(qprop, str):
qprop = qprop.decode('utf8')
for prop in qprop.split('\n'):
if '=' not in prop:
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
self.path = v
elif k == 'ID_WWN':
self.wwn = v
elif k == 'MD_CONTAINER':
self.mdcontainer = v
attrs = subprocess.check_output(['udevadm', 'info', '-a', devnode])
if not isinstance(attrs, str):
attrs = attrs.decode('utf8')
for attr in attrs.split('\n'):
if '==' not in attr:
continue
k, v = attr.split('==', 1)
k = k.strip()
if k == 'ATTRS{size}':
self.size = v.replace('"', '')
elif (k == 'DRIVERS' and not self.driver
and v not in ('"sd"', '""')):
self.driver = v.replace('"', '')
if not self.driver and 'imsm' not in self.mdcontainer:
raise Exception("No driver detected")
if os.path.exists('/sys/block/{0}/size'.format(self.name)):
with open('/sys/block/{0}/size'.format(self.name), 'r') as sizesrc:
self.size = int(sizesrc.read()) * 512
if int(self.size) < 536870912:
raise Exception("Device too small for install")
@property
def priority(self):
if self.model.lower() in ('thinksystem_m.2_vd', 'thinksystem m.2', 'thinksystem_m.2'):
return 0
if 'imsm' in self.mdcontainer:
return 1
if self.driver == 'ahci':
return 2
if self.driver.startswith('megaraid'):
return 3
if self.driver.startswith('mpt'):
return 4
return 99
def __repr__(self):
return repr({
'name': self.name,
'path': self.path,
'wwn': self.wwn,
'driver': self.driver,
'size': self.size,
'model': self.model,
})
def main():
disks = []
for disk in sorted(os.listdir('/sys/class/block')):
try:
disk = DiskInfo(disk)
disks.append(disk)
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
if __name__ == '__main__':
main()
@@ -0,0 +1,376 @@
#!/usr/bin/python3
import glob
import json
import os
import re
import time
import shutil
import socket
import stat
import struct
import sys
import subprocess
def get_next_part_meta(img, imgsize):
if img.tell() == imgsize:
return None
pathlen = struct.unpack('!H', img.read(2))[0]
mountpoint = img.read(pathlen).decode('utf8')
jsonlen = struct.unpack('!I', img.read(4))[0]
metadata = json.loads(img.read(jsonlen).decode('utf8'))
img.seek(16, 1) # skip the two 64-bit values we don't use, they are in json
nextlen = struct.unpack('!H', img.read(2))[0]
img.seek(nextlen, 1) # skip filesystem type
nextlen = struct.unpack('!H', img.read(2))[0]
img.seek(nextlen, 1) # skip orig devname (redundant with json)
nextlen = struct.unpack('!H', img.read(2))[0]
img.seek(nextlen, 1) # skip padding
nextlen = struct.unpack('!Q', img.read(8))[0]
img.seek(nextlen, 1) # go to next section
return metadata
def get_multipart_image_meta(img):
img.seek(0, 2)
imgsize = img.tell()
img.seek(16)
seekamt = img.read(1)
img.seek(struct.unpack('B', seekamt)[0], 1)
partinfo = get_next_part_meta(img, imgsize)
while partinfo:
yield partinfo
partinfo = get_next_part_meta(img, imgsize)
def get_image_metadata(imgpath):
with open(imgpath, 'rb') as img:
header = img.read(16)
if header == b'\x63\x7b\x9d\x26\xb7\xfd\x48\x30\x89\xf9\x11\xcf\x18\xfd\xff\xa1':
for md in get_multipart_image_meta(img):
yield md
else:
raise Exception('Installation from single part image not supported')
class PartedRunner():
def __init__(self, disk):
self.disk = disk
def run(self, command):
command = command.split()
command = ['parted', '-a', 'optimal', '-s', self.disk] + command
return subprocess.check_output(command).decode('utf8')
def fixup(rootdir, vols):
devbymount = {}
for vol in vols:
devbymount[vol['mount']] = vol['targetdisk']
fstabfile = os.path.join(rootdir, 'etc/fstab')
with open(fstabfile) as tfile:
fstab = tfile.read().split('\n')
while not fstab[0]:
fstab = fstab[1:]
if os.path.exists(os.path.join(rootdir, '.autorelabel')):
os.unlink(os.path.join(rootdir, '.autorelabel'))
with open(fstabfile, 'w') as tfile:
for tab in fstab:
entry = tab.split()
if tab.startswith('#ORIGFSTAB#'):
if entry[1] in devbymount:
targetdev = devbymount[entry[1]]
if targetdev.startswith('/dev/localstorage/'):
entry[0] = targetdev
else:
uuid = subprocess.check_output(['blkid', '-s', 'UUID', '-o', 'value', targetdev]).decode('utf8')
uuid = uuid.strip()
entry[0] = 'UUID={}'.format(uuid)
elif entry[2] == 'swap':
entry[0] = '/dev/mapper/localstorage-swap'
entry[0] = entry[0].ljust(42)
entry[1] = entry[1].ljust(16)
entry[3] = entry[3].ljust(28)
tab = '\t'.join(entry)
tfile.write(tab + '\n')
with open(os.path.join(rootdir, 'etc/hostname'), 'w') as nameout:
nameout.write(socket.gethostname() + '\n')
selinuxconfig = os.path.join(rootdir, 'etc/selinux/config')
policy = None
if os.path.exists(selinuxconfig):
with open(selinuxconfig) as cfgin:
sec = cfgin.read().split('\n')
for l in sec:
l = l.split('#', 1)[0]
if l.startswith('SELINUXTYPE='):
_, policy = l.split('=')
for sshkey in glob.glob(os.path.join(rootdir, 'etc/ssh/*_key*')):
os.unlink(sshkey)
for sshkey in glob.glob('/etc/ssh/*_key*'):
newkey = os.path.join(rootdir, sshkey[1:])
shutil.copy2(sshkey, newkey)
finfo = os.stat(sshkey)
os.chown(newkey, finfo[stat.ST_UID], finfo[stat.ST_GID])
for ifcfg in glob.glob(os.path.join(rootdir, 'etc/sysconfig/network-scripts/*')):
os.unlink(ifcfg)
for ifcfg in glob.glob(os.path.join(rootdir, 'etc/NetworkManager/system-connections/*')):
os.unlink(ifcfg)
for ifcfg in glob.glob('/run/NetworkManager/system-connections/*'):
newcfg = ifcfg.split('/')[-1]
newcfg = os.path.join(rootdir, 'etc/NetworkManager/system-connections/{0}'.format(newcfg))
shutil.copy2(ifcfg, newcfg)
shutil.rmtree(os.path.join(rootdir, 'etc/confluent/'))
shutil.copytree('/etc/confluent', os.path.join(rootdir, 'etc/confluent'))
if policy:
sys.stdout.write('Applying SELinux labeling...')
sys.stdout.flush()
subprocess.check_call(['setfiles', '-r', rootdir, os.path.join(rootdir, 'etc/selinux/{}/contexts/files/file_contexts'.format(policy)), os.path.join(rootdir, 'etc')])
subprocess.check_call(['setfiles', '-r', rootdir, os.path.join(rootdir, 'etc/selinux/{}/contexts/files/file_contexts'.format(policy)), os.path.join(rootdir, 'opt')])
sys.stdout.write('Done\n')
sys.stdout.flush()
for metafs in ('proc', 'sys', 'dev'):
subprocess.check_call(['mount', '-o', 'bind', '/{}'.format(metafs), os.path.join(rootdir, metafs)])
with open(os.path.join(rootdir, 'etc/sysconfig/grub')) as defgrubin:
defgrub = defgrubin.read().split('\n')
with open(os.path.join(rootdir, 'etc/sysconfig/grub'), 'w') as defgrubout:
for gline in defgrub:
gline = gline.split()
newline = []
for ent in gline:
if ent.startswith('resume=') or ent.startswith('rd.lvm.lv'):
continue
newline.append(ent)
defgrubout.write(' '.join(newline) + '\n')
grubcfg = subprocess.check_output(['find', os.path.join(rootdir, 'boot'), '-name', 'grub.cfg']).decode('utf8').strip().replace(rootdir, '/')
subprocess.check_call(['chroot', rootdir, 'grub2-mkconfig', '-o', grubcfg])
newroot = None
with open('/etc/shadow') as shadowin:
shents = shadowin.read().split('\n')
for shent in shents:
shent = shent.split(':')
if not shent:
continue
if shent[0] == 'root' and shent[1] not in ('*', '!!', ''):
newroot = shent[1]
if newroot:
shlines = None
with open(os.path.join(rootdir, 'etc/shadow')) as oshadow:
shlines = oshadow.read().split('\n')
with open(os.path.join(rootdir, 'etc/shadow'), 'w') as oshadow:
for line in shlines:
if line.startswith('root:'):
line = line.split(':')
line[1] = newroot
line = ':'.join(line)
oshadow.write(line + '\n')
partnum = None
targblock = None
for vol in vols:
if vol['mount'] == '/boot/efi':
targdev = vol['targetdisk']
partnum = re.search('(\d+)$', targdev).group(1)
targblock = re.search('(.*)\d+$', targdev).group(1)
if targblock:
shimpath = subprocess.check_output(['find', os.path.join(rootdir, 'boot/efi'), '-name', 'shimx64.efi']).decode('utf8').strip()
shimpath = shimpath.replace(rootdir, '/').replace('/boot/efi', '').replace('//', '/').replace('/', '\\')
subprocess.check_call(['efibootmgr', '-c', '-d', targblock, '-l', shimpath, '--part', partnum])
#other network interfaces
def had_swap():
with open('/etc/fstab') as tabfile:
tabs = tabfile.read().split('\n')
for tab in tabs:
tab = tab.split()
if len(tab) < 3:
continue
if tab[2] == 'swap':
return True
return False
def install_to_disk(imgpath):
lvmvols = {}
deftotsize = 0
mintotsize = 0
deflvmsize = 0
minlvmsize = 0
biggestsize = 0
biggestfs = None
plainvols = {}
allvols = []
swapsize = 0
if had_swap():
with open('/proc/meminfo') as meminfo:
swapsize = meminfo.read().split('\n')[0]
swapsize = int(swapsize.split()[1])
if swapsize < 2097152:
swapsize = swapsize * 2
elif swapsize > 8388608 and swapsize < 67108864:
swapsize = swapsize * 0.5
elif swapsize >= 67108864:
swapsize = 33554432
swapsize = int(swapsize * 1024)
deftotsize = swapsize
mintotsize = swapsize
for fs in get_image_metadata(imgpath):
allvols.append(fs)
deftotsize += fs['initsize']
mintotsize += fs['minsize']
if fs['initsize'] > biggestsize:
biggestfs = fs
biggestsize = fs['initsize']
if fs['device'].startswith('/dev/mapper'):
lvmvols[fs['device'].replace('/dev/mapper/', '')] = fs
deflvmsize += fs['initsize']
minlvmsize += fs['minsize']
else:
plainvols[int(re.search('(\d+)$', fs['device'])[0])] = fs
with open('/tmp/installdisk') as diskin:
instdisk = diskin.read()
instdisk = '/dev/' + instdisk
parted = PartedRunner(instdisk)
dinfo = parted.run('unit s print')
dinfo = dinfo.split('\n')
sectors = 0
sectorsize = 0
for inf in dinfo:
if inf.startswith('Disk {0}:'.format(instdisk)):
_, sectors = inf.split(': ')
sectors = int(sectors.replace('s', ''))
if inf.startswith('Sector size (logical/physical):'):
_, sectorsize = inf.split(':')
sectorsize = sectorsize.split('/')[0]
sectorsize = sectorsize.replace('B', '')
sectorsize = int(sectorsize)
# for now, only support resizing/growing the largest partition
minexcsize = deftotsize - biggestfs['initsize']
mintotsize = deftotsize - biggestfs['initsize'] + biggestfs['minsize']
minsectors = mintotsize // sectorsize
if sectors < (minsectors + 65536):
raise Exception('Disk too small to fit image')
biggestsectors = sectors - (minexcsize // sectorsize)
biggestsize = sectorsize * biggestsectors
parted.run('mklabel gpt')
curroffset = 2048
for volidx in sorted(plainvols):
vol = plainvols[volidx]
if vol is not biggestfs:
size = vol['initsize'] // sectorsize
else:
size = biggestsize // sectorsize
size += 2047 - (size % 2048)
end = curroffset + size
if end > sectors:
end = sectors
parted.run('mkpart primary {}s {}s'.format(curroffset, end))
vol['targetdisk'] = instdisk + '{0}'.format(volidx)
curroffset += size + 1
if not lvmvols:
if swapsize:
swapsize = swapsize // sectorsize
swapsize += 2047 - (size % 2048)
end = curroffset + swapsize
if end > sectors:
end = sectors
parted.run('mkpart swap {}s {}s'.format(curroffset, end))
subprocess.check_call(['mkswap', instdisk + '{}'.format(volidx + 1)])
else:
parted.run('mkpart lvm {}s 100%'.format(curroffset))
lvmpart = instdisk + '{}'.format(volidx + 1)
subprocess.check_call(['pvcreate', '-ff', '-y', lvmpart])
subprocess.check_call(['vgcreate', 'localstorage', lvmpart])
vginfo = subprocess.check_output(['vgdisplay', 'localstorage', '--units', 'b']).decode('utf8')
vginfo = vginfo.split('\n')
pesize = 0
pes = 0
for infline in vginfo:
infline = infline.split()
if len(infline) >= 3 and infline[:2] == ['PE', 'Size']:
pesize = int(infline[2])
if len(infline) >= 5 and infline[:2] == ['Free', 'PE']:
pes = int(infline[4])
takeaway = swapsize // pesize
for volidx in lvmvols:
vol = lvmvols[volidx]
if vol is biggestfs:
continue
takeaway += vol['initsize'] // pesize
takeaway += 1
biggestextents = pes - takeaway
for volidx in lvmvols:
vol = lvmvols[volidx]
if vol is biggestfs:
extents = biggestextents
else:
extents = vol['initsize'] // pesize
extents += 1
if vol['mount'] == '/':
lvname = 'root'
else:
lvname = vol['mount'].replace('/', '_')
subprocess.check_call(['lvcreate', '-l', '{}'.format(extents), '-y', '-n', lvname, 'localstorage'])
vol['targetdisk'] = '/dev/localstorage/{}'.format(lvname)
if swapsize:
subprocess.check_call(['lvcreate', '-y', '-l', '{}'.format(swapsize // pesize), '-n', 'swap', 'localstorage'])
subprocess.check_call(['mkswap', '/dev/localstorage/swap'])
os.makedirs('/run/imginst/targ')
for vol in allvols:
with open(vol['targetdisk'], 'wb') as partition:
partition.write(b'\x00' * 1 * 1024 * 1024)
subprocess.check_call(['mkfs.{}'.format(vol['filesystem']), vol['targetdisk']])
subprocess.check_call(['mount', vol['targetdisk'], '/run/imginst/targ'])
source = vol['mount'].replace('/', '_')
source = '/run/imginst/sources/' + source
blankfsstat = os.statvfs('/run/imginst/targ')
blankused = (blankfsstat.f_blocks - blankfsstat.f_bfree) * blankfsstat.f_bsize
sys.stdout.write('\nWriting {0}: '.format(vol['mount']))
with subprocess.Popen(['cp', '-ax', source + '/.', '/run/imginst/targ']) as copier:
stillrunning = copier.poll()
lastprogress = 0.0
while stillrunning is None:
currfsstat = os.statvfs('/run/imginst/targ')
currused = (currfsstat.f_blocks - currfsstat.f_bfree) * currfsstat.f_bsize
currused -= blankused
with open('/proc/meminfo') as meminf:
for line in meminf.read().split('\n'):
if line.startswith('Dirty:'):
_, dirty, _ = line.split()
dirty = int(dirty) * 1024
progress = (currused - dirty) / vol['minsize']
if progress < lastprogress:
progress = lastprogress
if progress > 0.99:
progress = 0.99
lastprogress = progress
progress = progress * 100
sys.stdout.write('\x1b[1K\rWriting {0}: {1:3.2f}%'.format(vol['mount'], progress))
sys.stdout.flush()
time.sleep(0.5)
stillrunning = copier.poll()
if stillrunning != 0:
raise Exception("Error copying volume")
with subprocess.Popen(['sync']) as syncrun:
stillrunning = syncrun.poll()
while stillrunning is None:
with open('/proc/meminfo') as meminf:
for line in meminf.read().split('\n'):
if line.startswith('Dirty:'):
_, dirty, _ = line.split()
dirty = int(dirty) * 1024
progress = (vol['minsize'] - dirty) / vol['minsize']
if progress < lastprogress:
progress = lastprogress
if progress > 0.99:
progress = 0.99
lastprogress = progress
progress = progress * 100
sys.stdout.write('\x1b[1K\rWriting {0}: {1:3.2f}%'.format(vol['mount'], progress))
sys.stdout.flush()
time.sleep(0.5)
stillrunning = syncrun.poll()
sys.stdout.write('\x1b[1K\rDone writing {0}'.format(vol['mount']))
sys.stdout.write('\n')
sys.stdout.flush()
subprocess.check_call(['umount', '/run/imginst/targ'])
for vol in allvols:
subprocess.check_call(['mount', vol['targetdisk'], '/run/imginst/targ/' + vol['mount']])
fixup('/run/imginst/targ', allvols)
if __name__ == '__main__':
install_to_disk(os.environ['mountsrc'])
@@ -0,0 +1,127 @@
. /lib/dracut-lib.sh
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay
if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then
mount -t tmpfs untethered /mnt/remoteimg
curl https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs
else
confluent_urls="$confluent_urls https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs"
/opt/confluent/bin/urlmount $confluent_urls /mnt/remoteimg
fi
/opt/confluent/bin/confluent_imginfo /mnt/remoteimg/rootimg.sfs > /tmp/rootimg.info
loopdev=$(losetup -f)
export mountsrc=$loopdev
losetup -r $loopdev /mnt/remoteimg/rootimg.sfs
if grep '^Format: confluent_crypted' /tmp/rootimg.info > /dev/null; then
while ! curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$confluent_mgr/confluent-api/self/profileprivate/pending/rootimg.key > /tmp/rootimg.key; do
echo "Unable to retrieve private key from $confluent_mgr (verify that confluent can access /var/lib/confluent/private/$confluent_profile/pending/rootimg.key)"
sleep 1
done
cipher=$(head -n 1 /tmp/rootimg.key)
key=$(tail -n 1 /tmp/rootimg.key)
len=$(wc -c /mnt/remoteimg/rootimg.sfs | awk '{print $1}')
len=$(((len-4096)/512))
dmsetup create cryptimg --table "0 $len crypt $cipher $key 0 $loopdev 8"
/opt/confluent/bin/confluent_imginfo /dev/mapper/cryptimg > /tmp/rootimg.info
mountsrc=/dev/mapper/cryptimg
fi
if grep '^Format: squashfs' /tmp/rootimg.info > /dev/null; then
mount -o ro $mountsrc /mnt/remote
elif grep '^Format: confluent_multisquash' /tmp/rootimg.info; then
tail -n +3 /tmp/rootimg.info | awk '{gsub("/", "_"); print "echo 0 " $4 " linear '$mountsrc' " $3 " | dmsetup create mproot" $7}' > /tmp/setupmount.sh
. /tmp/setupmount.sh
cat /tmp/setupmount.sh |awk '{printf "mount /dev/mapper/"$NF" "; sub("mproot", ""); gsub("_", "/"); print "/mnt/remote"$NF}' > /tmp/mountparts.sh
. /tmp/mountparts.sh
fi
#mount -t tmpfs overlay /mnt/overlay
modprobe zram
memtot=$(grep ^MemTotal: /proc/meminfo|awk '{print $2}')
memtot=$((memtot/2))$(grep ^MemTotal: /proc/meminfo | awk '{print $3'})
echo $memtot > /sys/block/zram0/disksize
mkfs.xfs /dev/zram0 > /dev/null
mount -o discard /dev/zram0 /mnt/overlay
if [ ! -f /tmp/mountparts.sh ]; then
mkdir -p /mnt/overlay/upper /mnt/overlay/work
mount -t overlay -o upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work,lowerdir=/mnt/remote disklessroot /sysroot
else
for srcmount in $(cat /tmp/mountparts.sh | awk '{print $3}'); do
mkdir -p /mnt/overlay${srcmount}/upper /mnt/overlay${srcmount}/work
mount -t overlay -o upperdir=/mnt/overlay${srcmount}/upper,workdir=/mnt/overlay${srcmount}/work,lowerdir=${srcmount} disklesspart /sysroot${srcmount#/mnt/remote}
done
fi
mkdir -p /sysroot/etc/ssh
mkdir -p /sysroot/etc/confluent
mkdir -p /sysroot/root/.ssh
cp /root/.ssh/* /sysroot/root/.ssh
chmod 700 /sysroot/root/.ssh
cp /etc/confluent/* /sysroot/etc/confluent/
cp /etc/ssh/*key* /sysroot/etc/ssh/
for pubkey in /etc/ssh/ssh_host*key.pub; do
certfile=${pubkey/.pub/-cert.pub}
privfile=${pubkey%.pub}
if [ -s $certfile ]; then
echo HostCertificate $certfile >> /sysroot/etc/ssh/sshd_config
fi
echo HostKey $privfile >> /sysroot/etc/ssh/sshd_config
done
mkdir -p /sysroot/dev /sysroot/sys /sysroot/proc /sysroot/run
if [ ! -z "$autocons" ]; then
autocons=${autocons%,*}
mkdir -p /run/systemd/generator/getty.target.wants
ln -s /usr/lib/systemd/system/serial-getty@.service /run/systemd/generator/getty.target.wants/serial-getty@${autocons}.service
fi
while [ ! -e /sysroot/sbin/init ]; do
echo "Failed to access root filesystem or it is missing /sbin/init"
echo "System should be accessible through ssh at port 2222 with the appropriate key"
while [ ! -e /sysroot/sbin/init ]; do
sleep 1
done
done
rootpassword=$(grep ^rootpassword: /etc/confluent/confluent.deploycfg)
rootpassword=${rootpassword#rootpassword: }
if [ "$rootpassword" = "null" ]; then
rootpassword=""
fi
if [ ! -z "$rootpassword" ]; then
sed -i "s@root:[^:]*:@root:$rootpassword:@" /sysroot/etc/shadow
fi
for i in /ssh/*.ca; do
echo '@cert-authority *' $(cat $i) >> /sysroot/etc/ssh/ssh_known_hosts
done
echo HostbasedAuthentication yes >> /sysroot/etc/ssh/sshd_config
echo HostbasedUsesNameFromPacketOnly yes >> /sysroot/etc/ssh/sshd_config
echo IgnoreRhosts no >> /sysroot/etc/ssh/sshd_config
sshconf=/sysroot/etc/ssh/ssh_config
if [ -d /sysroot/etc/ssh/ssh_config.d/ ]; then
sshconf=/sysroot/etc/ssh/ssh_config.d/01-confluent.conf
fi
echo 'Host *' >> $sshconf
echo ' HostbasedAuthentication yes' >> $sshconf
echo ' EnableSSHKeysign yes' >> $sshconf
echo ' HostbasedKeyTypes *ed25519*' >> $sshconf
curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$confluent_mgr/confluent-api/self/nodelist > /sysroot/etc/ssh/shosts.equiv
cp /sysroot/etc/ssh/shosts.equiv /sysroot/root/.shosts
chmod 640 /sysroot/etc/ssh/*_key
chroot /sysroot chgrp ssh_keys /etc/ssh/*_key
cp /tls/*.pem /sysroot/etc/pki/ca-trust/source/anchors/
chroot /sysroot/ update-ca-trust
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/onboot.service > /sysroot/etc/systemd/system/onboot.service
mkdir -p /sysroot/opt/confluent/bin
curl -sf https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/onboot.sh > /sysroot/opt/confluent/bin/onboot.sh
chmod +x /sysroot/opt/confluent/bin/onboot.sh
cp /opt/confluent/bin/apiclient /sysroot/opt/confluent/bin
ln -s /etc/systemd/system/onboot.service /sysroot/etc/systemd/system/multi-user.target.wants/onboot.service
cp /etc/confluent/functions /sysroot/etc/confluent/functions
if grep installtodisk /proc/cmdline > /dev/null; then
. /etc/confluent/functions
run_remote installimage
exec reboot -f
fi
mv /lib/modules/$(uname -r) /lib/modules/$(uname -r)-ramfs
ln -s /sysroot/lib/modules/$(uname -r) /lib/modules/
kill $(grep -l ^/usr/lib/systemd/systemd-udevd /proc/*/cmdline|cut -d/ -f 3)
exec /opt/confluent/bin/start_root
@@ -0,0 +1,43 @@
#!/bin/bash
. /etc/confluent/functions
# the image will be used to deploy itself
# provide both access to image (for parsing metadata)
# and existing mounts of image (to take advantage of caching)
mount -o bind /sys /sysroot/sys
mount -o bind /dev /sysroot/dev
mount -o bind /proc /sysroot/proc
mount -o bind /run /sysroot/run
if [ ! -f /tmp/mountparts.sh ]; then
mkdir -p /sysroot/run/imginst/sources/_
mount -o bind /mnt/remote /sysroot/run/imginst/sources/_
else
for srcmount in $(cat /tmp/mountparts.sh | awk '{print $2}'); do
srcname=${srcmount#/dev/mapper/mproot}
srcdir=$(echo $srcmount | sed -e 's!/dev/mapper/mproot!/mnt/remote!' -e 's!_!/!g')
mkdir -p /sysroot/run/imginst/sources/$srcname
mount -o bind $srcdir /sysroot/run/imginst/sources/$srcname
done
fi
cd /sysroot/run
chroot /sysroot/ bash -c "source /etc/confluent/functions; run_remote_python getinstalldisk"
chroot /sysroot/ bash -c "source /etc/confluent/functions; run_remote_parts pre.d"
if [ ! -f /sysroot/tmp/installdisk ]; then
echo 'Unable to find a suitable installation target device, ssh to port 2222 to investigate'
while [ ! -f /sysroot/tmp/installdisk ]; do
sleep 1
done
fi
lvm vgchange -a n
udevadm control -e
chroot /sysroot /usr/lib/systemd/systemd-udevd --daemon
chroot /sysroot bash -c "source /etc/confluent/functions; run_remote_python image2disk.py"
echo "Port 22" >> /etc/ssh/sshd_config
echo 'Match LocalPort 22' >> /etc/ssh/sshd_config
echo ' ChrootDirectory /sysroot/run/imginst/targ' >> /etc/ssh/sshd_config
kill -HUP $(cat /run/sshd.pid)
chroot /sysroot/run/imginst/targ bash -c "source /etc/confluent/functions; run_remote post.sh"
chroot /sysroot bash -c "umount \$(tac /proc/mounts|awk '{print \$2}'|grep ^/run/imginst/targ)"
@@ -0,0 +1,11 @@
[Unit]
Description=Confluent onboot hook
Requires=network-online.target
After=network-online.target
[Service]
ExecStart=/opt/confluent/bin/onboot.sh
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,36 @@
#!/bin/sh
# This script is executed on each boot as it is
# completed. It is best to edit the middle of the file as
# noted below so custom commands are executed before
# the script notifies confluent that install is fully complete.
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
export nodename confluent_mgr confluent_profile
. /etc/confluent/functions
mkdir -p /var/log/confluent
chmod 700 /var/log/confluent
exec >> /var/log/confluent/confluent-onboot.log
exec 2>> /var/log/confluent/confluent-onboot.log
chmod 600 /var/log/confluent/confluent-onboot.log
tail -f /var/log/confluent/confluent-onboot.log > /dev/console &
logshowpid=$!
rpm --import /etc/pki/rpm-gpg/*
run_remote_python add_local_repositories
run_remote_python syncfileclient
run_remote_python confignet
run_remote onboot.custom
# onboot scripts may be placed into onboot.d, e.g. onboot.d/01-firstaction.sh, onboot.d/02-secondaction.sh
run_remote_parts onboot.d
# Induce execution of remote configuration, e.g. ansible plays in ansible/onboot.d/
run_remote_config onboot.d
#curl -X POST -d 'status: booted' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
kill $logshowpid
@@ -0,0 +1,39 @@
#!/bin/sh
# This script is executed 'chrooted' into a cloned disk target before rebooting
#
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|awk '{print $2}')
export nodename confluent_mgr confluent_profile
. /etc/confluent/functions
mkdir -p /var/log/confluent
chmod 700 /var/log/confluent
exec >> /var/log/confluent/confluent-post.log
exec 2>> /var/log/confluent/confluent-post.log
chmod 600 /var/log/confluent/confluent-post.log
tail -f /var/log/confluent/confluent-post.log > /dev/console &
logshowpid=$!
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/firstboot.service > /etc/systemd/system/firstboot.service
mkdir -p /opt/confluent/bin
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/firstboot.sh > /opt/confluent/bin/firstboot.sh
chmod +x /opt/confluent/bin/firstboot.sh
systemctl enable firstboot
selinuxpolicy=$(grep ^SELINUXTYPE /etc/selinux/config |awk -F= '{print $2}')
if [ ! -z "$selinuxpolicy" ]; then
setfiles /etc/selinux/${selinuxpolicy}/contexts/files/file_contexts /etc/
fi
run_remote_python syncfileclient
run_remote post.custom
# post scripts may be placed into post.d, e.g. post.d/01-firstaction.sh, post.d/02-secondaction.sh
run_remote_parts post.d
# Induce execution of remote configuration, e.g. ansible plays in ansible/post.d/
run_remote_config post.d
curl -sf -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
kill $logshowpid
@@ -0,0 +1,272 @@
#!/usr/bin/python
import importlib
import tempfile
import json
import os
import shutil
import pwd
import grp
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(object):
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]
if not self.targlines:
break
while not self.sourcelines[-1]:
self.sourcelines = self.sourcelines[:-1]
if not self.sourcelines:
break
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 name[0] in ('+', '#', '@'):
self.sourcedata.append(line)
elif 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[0] in ('+', '#', '@'):
continue
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 appendonce(basepath, filename):
with open(filename, 'rb') as filehdl:
thedata = filehdl.read()
targname = filename.replace(basepath, '')
try:
with open(targname, 'rb') as filehdl:
targdata = filehdl.read()
except IOError:
targdata = b''
if thedata in targdata:
return
with open(targname, 'ab') as targhdl:
targhdl.write(thedata)
def synchronize():
tmpdir = tempfile.mkdtemp()
appendoncedir = tempfile.mkdtemp()
try:
ac = apiclient.HTTPSClient()
data = json.dumps({'merge': tmpdir, 'appendonce': appendoncedir})
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles', data)
if status == 202:
lastrsp = ''
while status != 204:
status, rsp = ac.grab_url_with_status('/confluent-api/self/remotesyncfiles')
if not isinstance(rsp, str):
rsp = rsp.decode('utf8')
if status == 200:
lastrsp = rsp
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')
for dirn in os.walk(appendoncedir):
for filen in dirn[2]:
appendonce(appendoncedir, os.path.join(dirn[0], filen))
if lastrsp:
lastrsp = json.loads(lastrsp)
opts = lastrsp.get('options', {})
for fname in opts:
uid = -1
gid = -1
for opt in opts[fname]:
if opt == 'owner':
try:
uid = pwd.getpwnam(opts[fname][opt]['name']).pw_uid
except KeyError:
uid = opts[fname][opt]['id']
elif opt == 'group':
try:
gid = grp.getgrnam(opts[fname][opt]['name']).gr_gid
except KeyError:
gid = opts[fname][opt]['id']
elif opt == 'permissions':
os.chmod(fname, int(opts[fname][opt], 8))
if uid != -1 or gid != -1:
os.chown(fname, uid, gid)
finally:
shutil.rmtree(tmpdir)
shutil.rmtree(appendoncedir)
if __name__ == '__main__':
synchronize()
@@ -0,0 +1,29 @@
# It is advised to avoid /var/lib/confluent/public as a source for syncing. /var/lib/confluent/public
# is served without authentication and thus any sensitive content would be a risk. If wanting to host
# syncfiles on a common share, it is suggested to have /var/lib/confluent be the share and use some other
# subdirectory other than public.
#
# Syncing is performed as the 'confluent' user, so all source files must be accessible by the confluent user.
#
# This file lists files to synchronize or merge to the deployed systems from the deployment server
# To specify taking /some/path/hosts on the deployment server and duplicating it to /etc/hosts:
# Note particularly the use of '->' to delineate source from target.
# /some/path/hosts -> /etc/hosts
# If wanting to simply use the same path for source and destinaiton, the -> may be skipped:
# /etc/hosts
# More function is available, for example to limit the entry to run only on n1 through n8, and to set
# owner, group, and permissions in octal notation:
# /example/source -> n1-n8:/etc/target (owner=root,group=root,permissions=600)
# Entries under APPENDONCE: will be added to specified target, only if the target does not already
# contain the data in the source already in its entirety. This allows append in a fashion that
# is friendly to being run repeatedly
# Entries under MERGE: will attempt to be intelligently merged. This supports /etc/group and /etc/passwd
# Any supporting entries in /etc/shadow or /etc/gshadow are added automatically, with password disabled
# It also will not inject 'system' ids (under 1,000 usually) as those tend to be local and rpm managed.
MERGE:
# /etc/passwd
# /etc/group
@@ -43,6 +43,20 @@ mkdir -p /etc/pki/tls/certs
cat /tls/*.pem > /etc/pki/tls/certs/ca-bundle.crt
TRIES=0
touch /etc/confluent/confluent.info
if [ -e /dev/disk/by-label/CNFLNT_IDNT ]; then
mkdir -p /media/ident
mount /dev/disk/by-label/CNFLNT_IDNT /media/ident
if [ -e /media/ident/genesis_bootstrap.sh ]; then
exec sh /media/ident/genesis_bootstrap.sh
fi
fi
if [ -e /dev/disk/by-label/GENESIS-X86 ]; then
mkdir -p /media/genesis
mount /dev/disk/by-label/GENESIS-X86 /media/genesis
if [ -e /media/genesis/genesis_bootstrap.sh ]; then
exec sh /media/genesis/genesis_bootstrap.sh
fi
fi
cd /sys/class/net
echo -n "Scanning for network configuration..."
while ! grep ^EXTMGRINFO: /etc/confluent/confluent.info | awk -F'|' '{print $3}' | grep 1 >& /dev/null && [ "$TRIES" -lt 30 ]; do
@@ -76,8 +90,10 @@ for hdl in $(tpm2_getcap handles-persistent|awk '{print $2}'); do
done
cd - > /dev/null
rm -rf $tpmdir
/usr/libexec/platform-python /opt/confluent/bin/apiclient /confluent-api/self/deploycfg2 > /etc/confluent/confluent.deploycfg
if [ ! -z "$autocons" ] && grep textconsole: true /etc/confluent/confluent.deploycfg > /dev/null; then /opt/confluent/bin/autocons -c > /dev/null; fi
while ! /usr/libexec/platform-python /opt/confluent/bin/apiclient /confluent-api/self/deploycfg2 > /etc/confluent/confluent.deploycfg; do
sleep 10
done
if [ ! -z "$autocons" ] && grep "textconsole: true" /etc/confluent/confluent.deploycfg > /dev/null; then /opt/confluent/bin/autocons -c > /dev/null; fi
umask $oldumask
new_apikey=$(cat /etc/confluent/confluent.apikey)
if [ "$new_apikey" != "$confluent_apikey" ]; then
@@ -139,6 +155,23 @@ elif [ "$autoconfigmethod" = "static" ]; then
ip route add default via $v4gw
fi
fi
nameserversec=0
while read -r entry; do
if [ $nameserversec = 1 ]; then
if [[ $entry == "-"* ]] && [[ $entry != "- ''" ]]; then
echo nameserver ${entry#- } >> /etc/resolv.conf
continue
fi
fi
nameserversec=0
if [ "${entry%:*}" = "nameservers" ]; then
nameserversec=1
continue
fi
done < /etc/confluent/confluent.deploycfg
dnsdomain=$(grep ^dnsdomain: /etc/confluent/confluent.deploycfg)
dnsdomain=${dnsdomain#dnsdomain: }
echo search $dnsdomain >> /etc/resolv.conf
echo -n "Initializing ssh..."
ssh-keygen -A
for pubkey in /etc/ssh/ssh_host*key.pub; do
@@ -153,8 +186,8 @@ done
/usr/sbin/sshd
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | awk '{print $2}')
export confluent_profile
/usr/libexec/platform-python /opt/confluent/bin/apiclient /confluent-public/os/$confluent_profile/scripts/functions > /tmp/functions
. /tmp/functions
/usr/libexec/platform-python /opt/confluent/bin/apiclient /confluent-public/os/$confluent_profile/scripts/functions > /etc/confluent/functions
. /etc/confluent/functions
set_confluent_vars
export confluent_mgr
echo "Running https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/onboot.sh"
@@ -1,5 +1,5 @@
#!/bin/sh
. /tmp/functions
. /etc/confluent/functions
# This runs whenever this genesis profile boots for customization
# purposes
@@ -119,6 +119,9 @@ proto=$(grep ^protocol: /etc/confluent/confluent.deploycfg)
proto=${proto#protocol: }
append=$(grep ^installedargs: /tmp/profile.yaml | sed -e 's/^installedargs: //' -e 's/#.*//')
if grep console= /etc/fakecmdline >& /dev/null && [[ "$append" != *console=* ]]; then
append="$append console=${autocons#*/dev/}"
fi
if [ -z "$append" ]; then
echo "<bootloader/>" > /tmp/bootloader.xml
else
@@ -38,7 +38,16 @@ if grep ^ntpservers: /etc/confluent/confluent.deploycfg > /dev/null; then
echo '</ntp_servers></ntp-client>' >> /tmp/ntp.cfg
ntpcfg=$(paste -sd '' /tmp/ntp.cfg)
fi
mdadm --assemble --scan
run_remote_python getinstalldisk
if grep ^md /tmp/installdisk > /dev/null; then
for md in /dev/disk/*/*; do
rmd=$(readlink $md)
if echo $rmd|grep $(cat /tmp/installdisk)$ > /dev/null; then
echo ${md#/dev/} > /tmp/installdisk
fi
done
fi
sed -e s'!'%%INSTDISK%%'!'/dev/$(cat /tmp/installdisk)'!' -e s'!'%%NODENAME%%'!'$nodename'!' -e 's!<networking\(.*\)>!'"$ntpcfg"'<networking\1>!' -e "s?%%ROOTPASSWORD%%?${rootpw}?" /tmp/profile/autoinst.xml > /tmp/profile/modified.xml
if grep append /tmp/bootloader.xml > /dev/null; then
sed -i 's@</general>@</general>'"$(tr -d '\n' < /tmp/bootloader.xml)"'@' /tmp/profile/modified.xml
@@ -18,7 +18,8 @@ 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
mkdir -p /mnt/opt/confluent/bin
cp /opt/confluent/bin/apiclient /mnt/opt/confluent/bin/
run_remote setupssh.sh
@@ -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
@@ -0,0 +1,148 @@
<?xml version="1.0"?>
<!DOCTYPE profile>
<!--
This autoyast file will be processed by pre.sh before applying. See pre.sh for
info on modifying its behavior, and also search and replace '%%' to remove
dynamic behavior and replace with static configuration.
-->
<profile xmlns="http://www.suse.com/1.0/yast2ns" xmlns:config="http://www.suse.com/1.0/configns">
<timezone>
<hwclock>UTC</hwclock>
<!--INSERT:/tmp/timezone-->
</timezone>
<general>
<self_update config:type="boolean">false</self_update>
<mode>
<confirm config:type="boolean">false</confirm>
</mode>
</general>
%%IFSLE%%
<add-on>
<add_on_products config:type="list">
<listentry><media_url/><!--INSERT:/tmp/pkgurl--><product>sle-module-basesystem</product><product_dir>/Module-Basesystem</product_dir></listentry>
<listentry><media_url/><!--INSERT:/tmp/pkgurl--><product>sle-module-server-applications</product><product_dir>/Module-Server-Applications</product_dir></listentry>
<listentry><media_url/><!--INSERT:/tmp/pkgurl--><product>sle-module-containers</product><product_dir>/Module-Containers</product_dir></listentry>
<listentry><media_url/><!--INSERT:/tmp/pkgurl--><product>SLES</product><product_dir>/Product-SLES</product_dir></listentry>
<listentry><media_url/><!--INSERT:/tmp/pkgurl--><product>Legacy-Module</product><product_dir>/Module-Legacy</product_dir></listentry>
</add_on_products>
</add-on>
%%ENDIFSLE%%
<!--INSERT:/tmp/bootloader.xml-->
<software>
%%IFSLE%%
<products config:type="list">
<product>SLES</product>
</products>
%%ENDIFSLE%%
<patterns config:type="list">
<pattern>base</pattern>
</patterns>
<packages config:type="list">
<package>openssh</package>
<package>iputils</package>
<package>python3</package>
<package>openssl</package>
<package>chrony</package>
<package>rsync</package>
<package>screen</package>
<package>vim</package>
<package>binutils</package>
<package>pciutils</package>
<package>usbutils</package>
<package>nfs-client</package>
<package>ethtool</package>
</packages>
</software>
<partitioning config:type="list">
<drive>
<device>%%INSTDISK%%</device>
<initialize config:type="boolean">true</initialize>
<use>all</use>
<partitions config:type="list">
<partition>
<filesystem config:type="symbol">xfs</filesystem>
<mount>/</mount>
<size>max</size>
</partition>
<partition>
<mount>swap</mount>
<size>auto</size>
</partition>
<partition>
<mount>/boot</mount>
<size>500M</size>
</partition>
</partitions>
</drive>
</partitioning>
<users config:type="list">
<user>
<username>root</username>
<user_password>%%ROOTPASSWORD%%</user_password>
<encrypted config:type="boolean">true</encrypted>
<!--INSERT:/tmp/rootkeys.xml-->
</user>
</users>
<networking>
<dns>
<hostname>%%NODENAME%%</hostname>
</dns>
<keep_install_network config:type="boolean">true</keep_install_network>
</networking>
<services-manager>
<services>
<enable config:type="list">
<service>sshd</service>
</enable>
</services>
</services-manager>
<scripts>
<pre-scripts config:type="list">
<script>
<filename>preinstall.sh</filename>
<source>
<![CDATA[
#!/bin/sh
confluent_mgr=$(grep ^deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|sed -e 's/^profile: //')
proto=$(grep ^protocol: /etc/confluent/confluent.deploycfg |awk '{print $2}')
curl $proto://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/pre.sh > /tmp/pre.sh
. /tmp/pre.sh
]]>
</source>
</script>
</pre-scripts>
<chroot-scripts config:type="list">
<script>
<filename>chroot.sh</filename>
<source>
<![CDATA[
#!/bin/sh
confluent_mgr=$(grep ^deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|sed -e 's/^profile: //')
proto=$(grep ^protocol: /etc/confluent/confluent.deploycfg |awk '{print $2}')
curl $proto://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/prechroot.sh > /tmp/prechroot.sh
. /tmp/prechroot.sh
curl -f $proto://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/firstboot.sh > /mnt/etc/confluent/firstboot.sh
curl -f $proto://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/post.sh > /mnt/etc/confluent/post.sh
chmod +x /mnt/etc/confluent/firstboot.sh
chmod +x /mnt/etc/confluent/post.sh
cp /mnt/etc/confluent/post.sh /mnt/var/adm/autoinstall/scripts/
]]>
</source>
</script>
<script>
<chrooted config:type="boolean">true</chrooted>
<filename>post.sh</filename>
<location>file:///etc/confluent/post.sh</location>
</script>
</chroot-scripts>
<init-scripts config:type="list">
<script>
<filename>firstboot.sh</filename>
<location>file:///etc/confluent/firstboot.sh</location>
</script>
</init-scripts>
</scripts>
</profile>
@@ -0,0 +1,23 @@
#!/bin/sh
discnum=$(basename $1)
if [ "$discnum" != 1 ]; then exit 0; fi
if [ -e $2/boot/kernel ]; then exit 0; fi
profile=$(basename $2)
if [[ $profile =~ ^sle.* ]]; then
if ls $1/Product-* >& /dev/null; then
ln -s $1 $2/product
else
ln -s ${1%1}2 $2/product
fi
fi
sed -i 's/sle 15/SUSE Linux Enterprise 15/; s/opensuse_leap/openSUSE Leap/' $2/profile.yaml
ln -s $1/boot/x86_64/loader/linux $2/boot/kernel && \
ln -s $1/boot/x86_64/loader/initrd $2/boot/initramfs/distribution && \
mkdir -p $2/boot/efi/boot && \
ln -s $1/EFI/BOOT/bootx64.efi $1/EFI/BOOT/grub.efi $2/boot/efi/boot/
if [[ $profile =~ ^sle.* ]]; then
sed -i 's/%%IFSLE%%//;s/%%ENDIFSLE%%//' $2/autoyast
else
sed -i '/%%IFSLE%%/,/%%ENDIFSLE%%/d' $2/autoyast
fi
@@ -0,0 +1,3 @@
label: %%DISTRO%% %%VERSION%% %%ARCH%% (Default Profile)
kernelargs: quiet # These arguments are passed to the installer
#installedargs: example # These arguments would be added to the installed system
@@ -0,0 +1,9 @@
#!/bin/sh
# This script runs at the end of the final boot
. /etc/confluent/functions
# Custom scripts may go here
# run_remote example.sh
# run_remote_python example.py
@@ -0,0 +1,41 @@
#!/bin/bash
# This script runs at the end of the final boot, updating status
exec >> /var/log/confluent/confluent-firstboot.log
exec 2>> /var/log/confluent/confluent-firstboot.log
chmod 600 /var/log/confluent/confluent-firstboot.log
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
v6cfg=$(grep ^ipv6_method: /etc/confluent/confluent.deploycfg)
v6cfg=${v6cfg#ipv6_method: }
if [ "$v6cfg" = "static" ]; then
confluent_mgr=$(grep ^deploy_server_v6: /etc/confluent/confluent.deploycfg)
confluent_mgr=${confluent_mgr#deploy_server_v6: }
confluent_mgr="[$confluent_mgr]"
else
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg)
confluent_mgr=${confluent_mgr#deploy_server: }
fi
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|sed -e 's/^profile: //')
proto=$(grep ^protocol: /etc/confluent/confluent.deploycfg |awk '{print $2}')
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
. /etc/confluent/functions
while ! ping -c 1 $confluent_mgr >& /dev/null; do
sleep 1
done
for i in /etc/ssh/ssh_host*key.pub; do
certname=${i/.pub/-cert.pub}
curl -f -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" -d @$i https://$confluent_mgr/confluent-api/self/sshcert > $certname
done
systemctl restart sshd
run_remote_python confignet
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.d
# Induce execution of remote configuration, e.g. ansible plays in ansible/firstboot.d/
run_remote_config firstboot.d
curl --capath /etc/confluent/tls -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" -f -X POST -d "status: complete" https://$confluent_mgr/confluent-api/self/updatestatus
@@ -0,0 +1,196 @@
#!/bin/bash
function test_mgr() {
if curl -s https://${1}/confluent-api/ > /dev/null; then
return 0
fi
return 1
}
function confluentpython() {
if [ -x /usr/libexec/platform-python ]; then
/usr/libexec/platform-python $*
elif [ -x /usr/bin/python3 ]; then
/usr/bin/python3 $*
elif [ -x /usr/bin/python ]; then
/usr/bin/python $*
elif [ -x /usr/bin/python2 ]; then
/usr/bin/python2 $*
fi
}
function set_confluent_vars() {
if [ -z "$nodename" ]; then
nodename=$(grep ^NODENAME: /etc/confluent/confluent.info | awk '{print $2}')
fi
if [[ "$confluent_mgr" == *"%"* ]]; then
confluent_mgr=""
fi
if [ -z "$confluent_mgr" ]; then
confluent_mgr=$(grep ^deploy_server: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
if ! test_mgr $confluent_mgr; then
confluent_mgr=$(grep ^deploy_server_v6: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
if [[ "$confluent_mgr" = *":"* ]]; then
confluent_mgr="[$confluent_mgr]"
fi
fi
if ! test_mgr $confluent_mgr; then
BESTMGRS=$(grep ^EXTMGRINFO: /etc/confluent/confluent.info | grep '|1$' | sed -e 's/EXTMGRINFO: //' -e 's/|.*//')
OKMGRS=$(grep ^EXTMGRINFO: /etc/confluent/confluent.info | grep '|0$' | sed -e 's/EXTMGRINFO: //' -e 's/|.*//')
for confluent_mgr in $BESTMGRS $OKMGRS; do
if [[ $confluent_mgr == *":"* ]]; then
confluent_mgr="[$confluent_mgr]"
fi
if test_mgr $confluent_mgr; then
break
fi
done
fi
fi
if [ -z "$confluent_profile" ]; then
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg | sed -e 's/[^ ]*: //')
fi
}
fetch_remote() {
curlargs=""
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://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
}
source_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
fi
scriptlist=$(confluentpython $apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
for script in $scriptlist; do
source_remote $1/$script
done
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
}
run_remote_parts() {
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
apiclient=/opt/confluent/bin/apiclient
if [ -f /etc/confluent/apiclient ]; then
apiclient=/etc/confluent/apiclient
fi
scriptlist=$(confluentpython $apiclient /confluent-api/self/scriptlist/$1|sed -e 's/^- //')
for script in $scriptlist; do
run_remote $1/$script
done
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
}
source_remote() {
set_confluent_vars
unsettmpdir=0
echo
echo '---------------------------------------------------------------------------'
echo Sourcing $1 from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
if [ -z "$confluentscripttmpdir" ]; then
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unsettmpdir=1
fi
echo Sourcing from $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $1
if [ $? != 0 ]; then echo $1 failed to download; return 1; fi
chmod +x $1
cmd=$1
shift
source ./$cmd
cd - > /dev/null
if [ "$unsettmpdir" = 1 ]; then
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
unsettmpdir=0
fi
rm -rf $confluentscripttmpdir
return $retcode
}
run_remote() {
requestedcmd="'$*'"
unsettmpdir=0
set_confluent_vars
echo
echo '---------------------------------------------------------------------------'
echo Running $requestedcmd from https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
if [ -z "$confluentscripttmpdir" ]; then
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
unsettmpdir=1
fi
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
fetch_remote $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=$?
if [ $retcode -ne 0 ]; then
echo "$requestedcmd exited with code $retcode"
fi
cd - > /dev/null
if [ "$unsettmpdir" = 1 ]; then
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
unsettmpdir=0
fi
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://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/
confluentscripttmpdir=$(mktemp -d /tmp/confluentscripts.XXXXXXXXX)
echo Executing in $confluentscripttmpdir
cd $confluentscripttmpdir
mkdir -p $(dirname $1)
curl -f -sS $curlargs https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/$1 > $1
if [ $? != 0 ]; then echo "'$*'" failed to download; return 1; fi
confluentpython $*
retcode=$?
echo "'$*' exited with code $retcode"
cd - > /dev/null
rm -rf $confluentscripttmpdir
unset confluentscripttmpdir
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 $confluent_mgr under profile $confluent_profile
confluentpython $apiclient /confluent-api/self/remoteconfig/"$*" -d {}
confluentpython $apiclient /confluent-api/self/remoteconfig/status -w 204
echo
echo 'Completed remote configuration'
echo '---------------------------------------------------------------------------'
return
}
#If invoked as a command, use the arguments to actually run a function
(return 0 2>/dev/null) || $1 "${@:2}"
@@ -0,0 +1,88 @@
import subprocess
import os
class DiskInfo(object):
def __init__(self, devname):
self.name = devname
self.wwn = None
self.path = None
self.model = ''
self.size = 0
self.driver = None
self.mdcontainer = ''
devnode = '/dev/{0}'.format(devname)
qprop = subprocess.check_output(
['udevadm', 'info', '--query=property', devnode])
if not isinstance(qprop, str):
qprop = qprop.decode('utf8')
for prop in qprop.split('\n'):
if '=' not in prop:
continue
k, v = prop.split('=', 1)
if k == 'DEVTYPE' and v != 'disk':
raise Exception('Not a disk')
elif k == 'DM_NAME':
raise Exception('Device Mapper')
elif k == 'ID_MODEL':
self.model = v
elif k == 'DEVPATH':
self.path = v
elif k == 'ID_WWN':
self.wwn = v
elif k == 'MD_CONTAINER':
self.mdcontainer = v
attrs = subprocess.check_output(['udevadm', 'info', '-a', devnode])
if not isinstance(attrs, str):
attrs = attrs.decode('utf8')
for attr in attrs.split('\n'):
if '==' not in attr:
continue
k, v = attr.split('==', 1)
k = k.strip()
if k == 'ATTRS{size}':
self.size = v.replace('"', '')
elif (k == 'DRIVERS' and not self.driver
and v not in ('"sd"', '""')):
self.driver = v.replace('"', '')
if not self.driver and 'imsm' not in self.mdcontainer:
raise Exception("No driver detected")
@property
def priority(self):
if self.model.lower() in ('thinksystem_m.2_vd', 'thinksystem m.2', 'thinksystem_m.2'):
return 0
if 'imsm' in self.mdcontainer:
return 1
if self.driver == 'ahci':
return 2
if self.driver.startswith('megaraid'):
return 3
if self.driver.startswith('mpt'):
return 4
return 99
def __repr__(self):
return repr({
'name': self.name,
'path': self.path,
'wwn': self.wwn,
'driver': self.driver,
'size': self.size,
'model': self.model,
})
def main():
disks = []
for disk in sorted(os.listdir('/sys/class/block')):
try:
disk = DiskInfo(disk)
disks.append(disk)
except Exception as e:
print("Skipping {0}: {1}".format(disk, str(e)))
nd = [x.name for x in sorted(disks, key=lambda x: x.priority)]
if nd:
open('/tmp/installdisk', 'w').write(nd[0])
if __name__ == '__main__':
main()
@@ -0,0 +1,16 @@
#!/bin/sh
# This script runs at the end of install in the installed system
# but still under the installer kernel.
# This is a good place to run most customizations that do not have any
# dependency upon the install target kernel being active.
# If there are dependencies on the kernel (drivers or special filesystems)
# then firstboot.sh would be the script to customize.
. /etc/confluent/functions
# Examples:
# run_remote script.sh
# run_remote_python script.py
@@ -0,0 +1,41 @@
#!/bin/bash
# This script runs at the end of install in the installed system
# but still under the installer kernel.
# This is a good place to run most customizations that do not have any
# dependency upon the install target kernel being active.
# If there are dependencies on the kernel (drivers or special filesystems)
# then firstboot.sh would be the script to customize.
chmod 700 /var/log/confluent
exec >> /var/log/confluent/confluent-post.log
exec 2>> /var/log/confluent/confluent-post.log
chmod 600 /var/log/confluent/confluent-post.log
confluent_mgr=$(grep ^deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|sed -e 's/^profile: //')
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
confluent_apikey=$(cat /etc/confluent/confluent.apikey)
chmod 700 /etc/confluent
chmod og-rwx /etc/confluent/*
export confluent_mgr confluent_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.d
# Induce execution of remote configuration, e.g. ansible plays in ansible/post.d/
run_remote_config post.d
curl -X POST -d 'status: staged' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
@@ -0,0 +1,57 @@
#!/bin/bash
# This script runs before the installer executes, and sets up ssh during install as well
# as rewriting the autoyast file with any substitutions prior to it being evaluated for real
exec >> /tmp/confluent-pre.log
exec 2>> /tmp/confluent-pre.log
chmod 600 /tmp/confluent-pre.log
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
rootpw=$(grep rootpassword: /etc/confluent/confluent.deploycfg|sed -e 's/^rootpassword: //')
if [ "$rootpw" = "null" ]; then
rootpw="!"
fi
cryptboot=$(grep encryptboot: /etc/confluent/confluent.deploycfg|sed -e 's/^encryptboot: //')
if [ "$cryptboot" != "" ] && [ "$cryptboot" != "none" ] && [ "$cryptboot" != "null" ]; then
echo "****Encrypted boot requested, but not implemented for this OS, halting install" > /dev/console
[ -f '/tmp/autoconsdev' ] && (echo "****Encryptod boot requested, but not implemented for this OS,halting install" >> $(cat /tmp/autoconsdev))
while :; do sleep 86400; done
fi
mkdir ~/.ssh
cat /ssh/*pubkey > ~/.ssh/authorized_keys 2>/dev/null
ssh-keygen -A
for i in /etc/ssh/ssh_host*key.pub; do
certname=${i/.pub/-cert.pub}
curl -f -X POST -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" -d @$i https://$confluent_mgr/confluent-api/self/sshcert > $certname
echo HostKey ${i%.pub} >> /etc/ssh/sshd_config
echo HostCertificate $certname >> /etc/ssh/sshd_config
done
/usr/sbin/sshd
curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/scripts/functions > /tmp/functions
. /tmp/functions
ntpcfg=""
if grep ^ntpservers: /etc/confluent/confluent.deploycfg > /dev/null; then
echo '<ntp-client><ntp_servers config:type="list">' > /tmp/ntp.cfg
sed -n '/^ntpservers:/,/^[^-]/p' /etc/confluent/confluent.deploycfg | sed 1d|sed '$d'| sed -e 's/^- /<ntp_server><address>/' -e 's!$!</address></ntp_server>!' >> /tmp/ntp.cfg
echo '</ntp_servers></ntp-client>' >> /tmp/ntp.cfg
ntpcfg=$(paste -sd '' /tmp/ntp.cfg)
fi
mdadm --assemble --scan
run_remote_python getinstalldisk
if grep ^md /tmp/installdisk > /dev/null; then
for md in /dev/disk/*/*; do
rmd=$(readlink $md)
if echo $rmd|grep $(cat /tmp/installdisk)$ > /dev/null; then
echo ${md#/dev/} > /tmp/installdisk
fi
done
fi
sed -e s'!'%%INSTDISK%%'!'/dev/$(cat /tmp/installdisk)'!' -e s'!'%%NODENAME%%'!'$nodename'!' -e 's!<networking\(.*\)>!'"$ntpcfg"'<networking\1>!' -e "s?%%ROOTPASSWORD%%?${rootpw}?" /tmp/profile/autoinst.xml > /tmp/profile/modified.xml
if grep append /tmp/bootloader.xml > /dev/null; then
sed -i 's@</general>@</general>'"$(tr -d '\n' < /tmp/bootloader.xml)"'@' /tmp/profile/modified.xml
fi
sed -i 's#root</username>#root</username>'"$(tr -d '\n' < /tmp/rootkeys.xml)"'#' /tmp/profile/modified.xml
sed -i 's@/hwclock>@/hwclock>'"$(tr -d '\n' < /tmp/timezone)"'@' /tmp/profile/modified.xml
sed -i 's@<media_url/>@'"$(tr -d '\n' < /tmp/pkgurl)"'@' /tmp/profile/modified.xml
@@ -0,0 +1,33 @@
#!/bin/sh
# This script runs when install is finished, but while the installer
# is still running, with the to-be-booted system mounted in /mnt
# carry over deployment configuration and api key for OS install action
confluent_mgr=$(grep ^deploy_server /etc/confluent/confluent.deploycfg|awk '{print $2}')
confluent_profile=$(grep ^profile: /etc/confluent/confluent.deploycfg|sed -e 's/^profile: //')
nodename=$(grep ^NODENAME /etc/confluent/confluent.info|awk '{print $2}')
export confluent_mgr confluent_profile nodename
mkdir -p /mnt/etc/confluent
chmod 700 /mnt/etc/confluent
cp /tmp/functions /mnt/etc/confluent/
. /tmp/functions
cp -a /etc/confluent/* /mnt/etc/confluent/
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
mkdir -p /mnt/opt/confluent/bin
cp /opt/confluent/bin/apiclient /mnt/opt/confluent/bin/
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)
mkdir -p /mnt/var/log/confluent
cp /tmp/confluent*log /mnt/var/log/confluent

Some files were not shown because too many files have changed in this diff Show More