2
0
mirror of https://github.com/xcat2/confluent.git synced 2024-11-25 02:52:07 +00:00

Implement rebase feature ofr osdeploy

Permit user to opt into a rebase of a
profile, to pick up potential updates
from the confluent packaged stock
profiles for files the user has not yet
customized.
This commit is contained in:
Jarrod Johnson 2022-08-25 15:21:49 -04:00
parent e774add916
commit 352da94005
10 changed files with 377 additions and 26 deletions

View File

@ -142,7 +142,7 @@ _confluent_osimage_completion()
{
_confluent_get_args
if [ $NUMARGS == 2 ]; then
COMPREPLY=($(compgen -W "initialize import updateboot" -- ${COMP_WORDS[COMP_CWORD]}))
COMPREPLY=($(compgen -W "initialize import updateboot rebase" -- ${COMP_WORDS[COMP_CWORD]}))
return
elif [ ${CMPARGS[1]} == 'initialize' ]; then
COMPREPLY=($(compgen -W "-h -u -s -t -i" -- ${COMP_WORDS[COMP_CWORD]}))
@ -150,7 +150,7 @@ _confluent_osimage_completion()
compopt -o default
COMPREPLY=()
return
elif [ ${CMPARGS[1]} == 'updateboot' ]; then
elif [ ${CMPARGS[1]} == 'updateboot' -o ${CMPARGS[1]} == 'rebase' ]; then
COMPREPLY=($(compgen -W "-n $(confetty show /deployment/profiles|sed -e 's/\///')" -- "${COMP_WORDS[COMP_CWORD]}"))
return
fi

View File

@ -0,0 +1,132 @@
<?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>
<!--INSERT:/tmp/bootloader.xml-->
<software>
<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>

View File

@ -16,7 +16,6 @@ dynamic behavior and replace with static configuration.
<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>
@ -27,14 +26,11 @@ dynamic behavior and replace with static configuration.
<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>SLE_HPC</product>
</products>
%%ENDIFSLE%%
<patterns config:type="list">
<pattern>base</pattern>
</patterns>

View File

@ -17,7 +17,7 @@ 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
ln -s autoyast.sle $2/autoyast
else
sed -i '/%%IFSLE%%/,/%%ENDIFSLE%%/d' $2/autoyast
ln -s autoyast.leap $2/autoyast
fi

View File

@ -0,0 +1,132 @@
<?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>
<!--INSERT:/tmp/bootloader.xml-->
<software>
<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>

View File

@ -16,7 +16,6 @@ dynamic behavior and replace with static configuration.
<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>
@ -26,14 +25,11 @@ dynamic behavior and replace with static configuration.
<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>

View File

@ -17,7 +17,7 @@ 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
ln -s autoyast.sle $2/autoyast
else
sed -i '/%%IFSLE%%/,/%%ENDIFSLE%%/d' $2/autoyast
ln -s autoyast.leap $2/autoyast
fi

View File

@ -56,6 +56,8 @@ def main(args):
help='Push profile.yaml of the named profile data into boot assets as appropriate')
upb.add_argument('profile', help='Profile to update boot assets')
osls = sp.add_parser('list', help='List OS images available for deployment')
ubp = sp.add_parser('rebase', help='Update stock profile content from packaged updates')
ubp.add_argument('profile', help='Profile to rebase from packaged content')
cmdset = ap.parse_args()
if cmdset.command == 'list':
@ -66,6 +68,8 @@ def main(args):
return initialize(cmdset)
if cmdset.command == 'updateboot':
return updateboot(cmdset.profile)
if cmdset.command == 'rebase':
return rebase(cmdset.profile)
ap.print_help()
@ -404,6 +408,19 @@ def updateboot(profilename):
print(repr(rsp))
def rebase(profilename):
c = client.Command()
for rsp in c.update('/deployment/profiles/{0}'.format(profilename), {'rebase': 1}):
if 'updated' in rsp:
print('Updated: {0}'.format(rsp['updated']))
elif 'customized' in rsp:
print('Skipping update of {0} as current copy was customized or no manifest data was available'.format(rsp['customized']))
elif 'error' in rsp:
sys.stderr.write(rsp['error'] + '\n')
sys.exit(rsp['errorcode'])
else:
print(repr(rsp))
def oslist():
c = client.Command()
print("Distributions:")

View File

@ -192,10 +192,21 @@ def handle_deployment(configmanager, inputdata, pathcomponents,
return
if len(pathcomponents) == 3:
profname = pathcomponents[-1]
if operation == 'update' and 'updateboot' in inputdata:
osimage.update_boot(profname)
yield msg.KeyValueData({'updated': profname})
return
if operation == 'update':
if 'updateboot' in inputdata:
osimage.update_boot(profname)
yield msg.KeyValueData({'updated': profname})
return
elif 'rebase' in inputdata:
try:
updated, customized = osimage.rebase_profile(profname)
except osimage.ManifestMissing:
raise exc.InvalidArgumentException('Specified profile {0} does not have a manifest.yaml for rebase'.format(profname))
for upd in updated:
yield msg.KeyValueData({'updated': upd})
for cust in customized:
yield msg.KeyValueData({'customized': cust})
return
if pathcomponents[1] == 'importing':
if len(pathcomponents) == 2 or not pathcomponents[-1]:
if operation == 'retrieve':

View File

@ -44,7 +44,7 @@ def relax_umask():
def makedirs(path, mode):
try:
os.makedirs(path, 0o755)
os.makedirs(path, mode)
except OSError as e:
if e.errno != 17:
raise
@ -645,6 +645,78 @@ def get_profile_label(profile):
importing = {}
class ManifestMissing(Exception):
pass
def copy_file(src, dst):
newdir = os.path.dirname(dst)
makedirs(newdir, 0o755)
shutil.copy2(src, dst)
def get_hash(fname):
currhash = hashlib.sha512()
with open(fname, 'rb') as currf:
currd = currf.read(2048)
while currd:
currhash.update(currd)
currd = currf.read(2048)
return currhash.hexdigest()
def rebase_profile(dirname):
if dirname.startswith('/var/lib/confluent/public'):
profiledir = dirname
else:
profiledir = '/var/lib/confluent/public/os/{0}'.format(dirname)
currhashes = get_hashes(profiledir)
festfile = os.path.join(profiledir, 'manifest.yaml')
try:
with open(festfile, 'r') as festfile:
manifest = yaml.safe_load(festfile)
except IOError:
raise ManifestMissing()
distdir = manifest['distdir']
newdisthashes = get_hashes(distdir)
olddisthashes = manifest['disthashes']
customized = []
newmanifest = []
updated = []
for updatecandidate in newdisthashes:
newfilename = os.path.join(profiledir, updatecandidate)
distfilename = os.path.join(distdir, updatecandidate)
newdisthash = newdisthashes[updatecandidate]
currhash = currhashes.get(updatecandidate, None)
olddisthash = olddisthashes.get(updatecandidate, None)
if not currhash: # file does not exist yet
copy_file(distfilename, newfilename)
newmanifest.append(updatecandidate)
updated.append(updatecandidate)
elif currhash == newdisthash:
newmanifest.append(updatecandidate)
elif currhash != olddisthash:
customized.append(updatecandidate)
else:
copy_file(distfilename, newfilename)
updated.append(updatecandidate)
newmanifest.append(updatecandidate)
for nf in newmanifest:
nfname = os.path.join(profiledir, nf)
currhash = get_hash(nfname)
manifest['disthashes'][nf] = currhash
with open('{0}/manifest.yaml'.format(profiledir), 'w') as yout:
yout.write('# This manifest enables rebase to know original source of profile data and if any customizations have been done\n')
yout.write(yaml.dump(manifest, default_flow_style=False))
return updated, customized
# if currhash == disthash:
# no update required, update manifest
# elif currhash != olddisthash:
# customization detected, skip
# else
# update required, manifest update
def get_hashes(dirname):
hashmap = {}
for dname, _, fnames in os.walk(dirname):
@ -653,13 +725,8 @@ def get_hashes(dirname):
continue
fullname = os.path.join(dname, fname)
currhash = hashlib.sha512()
with open(fullname, 'rb') as currf:
currd = currf.read(2048)
while currd:
currhash.update(currd)
currd = currf.read(2048)
subname = fullname.replace(dirname + '/', '')
hashmap[subname] = currhash.hexdigest()
subname = fullname.replace(dirname + '/', '')
hashmap[subname] = get_hash(fullname)
return hashmap