Browse Source

Fork cgcs-patch package as sw-patch for Debian

The original cgcs-patch is rpm based which requires a
complete re-write to work on ostree/dpkg systems like Debian.

The code has been forked, since the older Centos env and
python2.7 are end-of-life.

Forking the code allows all new development to not
require re-testing on Centos.

The debian folder under cgcs-patch has been moved
under sw-patch

Renaming and refactoring will be done in later commits.

pylint is un-clamped in order to work on python3.9
Some minor pylint suppressions have been added.

Test Plan:
 Verify that this builds on Debian
 Verify that the ISO installs the new content on Debian without
breaking packages that import cgcs_patch.
 Verify patching service runs on Debian

Co-Authored-By: Jessica Castelino <jessica.castelino@windriver.com>
Story: 2009101
Task: 43076
Signed-off-by: Al Bailey <al.bailey@windriver.com>
Change-Id: I3f1bca749404053bae63d4bcc9fb2477cf909fcd
changes/84/835484/18
Al Bailey 3 months ago
parent
commit
be09ccc584
  1. 34
      .zuul.yaml
  2. 2
      bindep.txt
  3. 2
      debian_pkg_dirs
  4. 5
      patch-alarm/patch-alarm/test-requirements.txt
  5. 9
      patch-alarm/patch-alarm/tox.ini
  6. 15
      sw-patch/bin/make_patch
  7. 15
      sw-patch/bin/modify_patch
  8. 52
      sw-patch/bin/patch-functions
  9. 2
      sw-patch/bin/patch-tmpdirs.conf
  10. 16
      sw-patch/bin/patch_build
  11. 27
      sw-patch/bin/patch_check_goenabled.sh
  12. 7
      sw-patch/bin/patching.conf
  13. 15
      sw-patch/bin/patching.logrotate
  14. 19
      sw-patch/bin/pmon-sw-patch-agent.conf
  15. 19
      sw-patch/bin/pmon-sw-patch-controller-daemon.conf
  16. 5
      sw-patch/bin/policy.json
  17. 15
      sw-patch/bin/query_patch
  18. 183
      sw-patch/bin/rpm-audit
  19. 60
      sw-patch/bin/run-patch-scripts
  20. 182
      sw-patch/bin/setup_patch_repo
  21. 16
      sw-patch/bin/sw-patch
  22. 16
      sw-patch/bin/sw-patch-agent
  23. 94
      sw-patch/bin/sw-patch-agent-init.sh
  24. 20
      sw-patch/bin/sw-patch-agent-restart
  25. 16
      sw-patch/bin/sw-patch-agent.service
  26. 16
      sw-patch/bin/sw-patch-controller-daemon
  27. 78
      sw-patch/bin/sw-patch-controller-daemon-init.sh
  28. 20
      sw-patch/bin/sw-patch-controller-daemon-restart
  29. 16
      sw-patch/bin/sw-patch-controller-daemon.service
  30. 106
      sw-patch/bin/sw-patch-controller-init.sh
  31. 14
      sw-patch/bin/sw-patch-controller.service
  32. 178
      sw-patch/bin/sw-patch-init.sh
  33. 148
      sw-patch/bin/sw-patch.completion
  34. 16
      sw-patch/bin/sw-patch.service
  35. 137
      sw-patch/bin/upgrade-start-pkg-extract
  36. 7
      sw-patch/cgcs-patch/.coveragerc
  37. 2
      sw-patch/cgcs-patch/.stestr.conf
  38. 202
      sw-patch/cgcs-patch/LICENSE
  39. 6
      sw-patch/cgcs-patch/cgcs_patch/__init__.py
  40. 30
      sw-patch/cgcs-patch/cgcs_patch/api/__init__.py
  41. 43
      sw-patch/cgcs-patch/cgcs_patch/api/app.py
  42. 23
      sw-patch/cgcs-patch/cgcs_patch/api/config.py
  43. 6
      sw-patch/cgcs-patch/cgcs_patch/api/controllers/__init__.py
  44. 293
      sw-patch/cgcs-patch/cgcs_patch/api/controllers/root.py
  45. 24
      sw-patch/cgcs-patch/cgcs_patch/app.py
  46. 25
      sw-patch/cgcs-patch/cgcs_patch/authapi/__init__.py
  47. 30
      sw-patch/cgcs-patch/cgcs_patch/authapi/acl.py
  48. 77
      sw-patch/cgcs-patch/cgcs_patch/authapi/app.py
  49. 40
      sw-patch/cgcs-patch/cgcs_patch/authapi/auth_token.py
  50. 23
      sw-patch/cgcs-patch/cgcs_patch/authapi/config.py
  51. 100
      sw-patch/cgcs-patch/cgcs_patch/authapi/hooks.py
  52. 117
      sw-patch/cgcs-patch/cgcs_patch/authapi/policy.py
  53. 170
      sw-patch/cgcs-patch/cgcs_patch/base.py
  54. 51
      sw-patch/cgcs-patch/cgcs_patch/certificates.py
  55. 138
      sw-patch/cgcs-patch/cgcs_patch/config.py
  56. 51
      sw-patch/cgcs-patch/cgcs_patch/constants.py
  57. 57
      sw-patch/cgcs-patch/cgcs_patch/exceptions.py
  58. 64
      sw-patch/cgcs-patch/cgcs_patch/messages.py
  59. 941
      sw-patch/cgcs-patch/cgcs_patch/patch_agent.py
  60. 1513
      sw-patch/cgcs-patch/cgcs_patch/patch_client.py
  61. 2713
      sw-patch/cgcs-patch/cgcs_patch/patch_controller.py
  62. 1440
      sw-patch/cgcs-patch/cgcs_patch/patch_functions.py
  63. 90
      sw-patch/cgcs-patch/cgcs_patch/patch_signing.py
  64. 191
      sw-patch/cgcs-patch/cgcs_patch/patch_verify.py
  65. 92
      sw-patch/cgcs-patch/cgcs_patch/templates/query.html
  66. 95
      sw-patch/cgcs-patch/cgcs_patch/templates/query.xml
  67. 32
      sw-patch/cgcs-patch/cgcs_patch/templates/query_agents.html
  68. 75
      sw-patch/cgcs-patch/cgcs_patch/templates/query_hosts.xml
  69. 83
      sw-patch/cgcs-patch/cgcs_patch/templates/show.html
  70. 92
      sw-patch/cgcs-patch/cgcs_patch/templates/show.xml
  71. 0
      sw-patch/cgcs-patch/cgcs_patch/tests/__init__.py
  72. 3422
      sw-patch/cgcs-patch/cgcs_patch/tests/md5test.txt
  73. 25
      sw-patch/cgcs-patch/cgcs_patch/tests/test_basics.py
  74. 30
      sw-patch/cgcs-patch/cgcs_patch/tests/test_patch_agent.py
  75. 22
      sw-patch/cgcs-patch/cgcs_patch/tests/test_patch_controller.py
  76. 146
      sw-patch/cgcs-patch/cgcs_patch/tests/test_patch_utils.py
  77. 83
      sw-patch/cgcs-patch/cgcs_patch/utils.py
  78. 34
      sw-patch/cgcs-patch/cgcs_patch_id/README.txt
  79. 50
      sw-patch/cgcs-patch/cgcs_patch_id/patch_id_allocator.py
  80. 66
      sw-patch/cgcs-patch/cgcs_patch_id/patch_id_allocator_client.py
  81. 16
      sw-patch/cgcs-patch/cgcs_patch_id/patch_id_allocator_server.conf
  82. 45
      sw-patch/cgcs-patch/cgcs_patch_id/patch_id_allocator_server.py
  83. 430
      sw-patch/cgcs-patch/pylint.rc
  84. 10
      sw-patch/cgcs-patch/requirements.txt
  85. 23
      sw-patch/cgcs-patch/setup.cfg
  86. 23
      sw-patch/cgcs-patch/setup.py
  87. 13
      sw-patch/cgcs-patch/test-requirements.txt
  88. 146
      sw-patch/cgcs-patch/tox.ini
  89. 0
      sw-patch/debian/deb_folder/cgcs-patch-agent.dirs
  90. 0
      sw-patch/debian/deb_folder/cgcs-patch-agent.install
  91. 0
      sw-patch/debian/deb_folder/cgcs-patch-agent.lintian-overrides
  92. 0
      sw-patch/debian/deb_folder/cgcs-patch-controller.dirs
  93. 0
      sw-patch/debian/deb_folder/cgcs-patch-controller.install
  94. 0
      sw-patch/debian/deb_folder/cgcs-patch-controller.lintian-overrides
  95. 0
      sw-patch/debian/deb_folder/cgcs-patch.dirs
  96. 0
      sw-patch/debian/deb_folder/cgcs-patch.install
  97. 0
      sw-patch/debian/deb_folder/cgcs-patch.lintian-overrides
  98. 0
      sw-patch/debian/deb_folder/changelog
  99. 0
      sw-patch/debian/deb_folder/control
  100. 0
      sw-patch/debian/deb_folder/copyright
  101. Some files were not shown because too many files have changed in this diff Show More

34
.zuul.yaml

@ -15,6 +15,8 @@
- patch-alarm-tox-pylint
- patch-alarm-tox-py27
- patch-alarm-tox-py39
- sw-patch-tox-pylint
- sw-patch-tox-py39
gate:
jobs:
- openstack-tox-linters
@ -26,6 +28,8 @@
- patch-alarm-tox-pylint
- patch-alarm-tox-py27
- patch-alarm-tox-py39
- sw-patch-tox-pylint
- sw-patch-tox-py39
post:
jobs:
- stx-update-upload-git-mirror
@ -79,7 +83,6 @@
required-projects:
- starlingx/config
- starlingx/fault
- starlingx/root
files:
- cgcs-patch/cgcs-patch/*
vars:
@ -87,6 +90,34 @@
python_version: 3.9
tox_extra_args: -c cgcs-patch/cgcs-patch/tox.ini
- job:
name: sw-patch-tox-py39
parent: tox-py39
nodeset: debian-bullseye
required-projects:
- starlingx/config
- starlingx/fault
files:
- sw-patch/cgcs-patch/*
vars:
tox_envlist: py39
python_version: 3.9
tox_extra_args: -c sw-patch/cgcs-patch/tox.ini
- job:
name: sw-patch-tox-pylint
parent: tox
nodeset: debian-bullseye
required-projects:
- starlingx/config
- starlingx/fault
files:
- sw-patch/cgcs-patch/*
vars:
tox_envlist: pylint
python_version: 3.9
tox_extra_args: -c sw-patch/cgcs-patch/tox.ini
- job:
name: patch-alarm-tox-pylint
@ -121,7 +152,6 @@
required-projects:
- starlingx/config
- starlingx/fault
- starlingx/root
files:
- patch-alarm/patch-alarm/*
vars:

2
bindep.txt

@ -1,5 +1,7 @@
# This is a cross-platform list tracking distribution packages needed for install and tests;
# see https://docs.openstack.org/infra/bindep/ for additional information.
# Do not install python2 rpms in a python3 only environment such as debian-bullseye
python-rpm [platform:dpkg !platform:debian-bullseye]
python3-rpm [platform:dpkg]
rpm-python [platform:rpm]

2
debian_pkg_dirs

@ -1,3 +1,3 @@
cgcs-patch
enable-dev-patch
patch-alarm
sw-patch

5
patch-alarm/patch-alarm/test-requirements.txt

@ -3,9 +3,12 @@
# process, which may cause wedges in the gate later.
hacking>=1.1.0,<=2.0.0 # Apache-2.0
astroid <= 2.2.5
coverage!=4.4,>=4.0 # Apache-2.0
mock>=2.0.0 # BSD
stestr>=1.0.0 # Apache-2.0
testtools>=2.2.0 # MIT
pycryptodomex
isort<5;python_version>="3.0"
pylint<2.1.0;python_version<"3.0" # GPLv2
pylint<2.4.0;python_version>="3.0" # GPLv2

9
patch-alarm/patch-alarm/tox.ini

@ -28,7 +28,7 @@ setenv = VIRTUAL_ENV={envdir}
passenv =
XDG_CACHE_HOME
sitepackages = False
sitepackages = True
install_command = pip install \
-v -v -v \
-c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/stable/stein/upper-constraints.txt} \
@ -64,7 +64,6 @@ install_command = pip install \
-v -v -v \
-c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/starlingx/root/raw/branch/master/build-tools/requirements/debian/upper-constraints.txt} \
{opts} {packages}
{opts} {packages}
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
@ -110,17 +109,13 @@ basepython = python3
deps = {[testenv]deps}
flake8-bugbear
usedevelop = False
#skip_install = True
commands =
flake8 {posargs} .
[testenv:pylint]
basepython = python3
deps = {[testenv]deps}
pylint
basepython = python2.7
sitepackages = False
commands = pylint patch_alarm --rcfile=./pylint.rc
[testenv:cover]

15
sw-patch/bin/make_patch

@ -0,0 +1,15 @@
#!/usr/bin/env python
#
# Copyright (c) 2013-2014 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import sys
from cgcs_make_patch.make_patch_functions import make_patch
if __name__ == "__main__":
sys.exit(make_patch())

15
sw-patch/bin/modify_patch

@ -0,0 +1,15 @@
#!/usr/bin/env python
#
# Copyright (c) 2013-2014 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import sys
from cgcs_make_patch.make_patch_functions import modify_patch
if __name__ == "__main__":
sys.exit(modify_patch())

52
sw-patch/bin/patch-functions

@ -0,0 +1,52 @@
#
# Copyright (c) 2016 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
#
# This bash source file provides variables and functions that
# may be used by in-service patching scripts.
#
# Source platform.conf, for nodetype and subfunctions
. /etc/platform/platform.conf
declare PATCH_SCRIPTDIR=/run/patching/patch-scripts
declare PATCH_FLAGDIR=/run/patching/patch-flags
declare -i PATCH_STATUS_OK=0
declare -i PATCH_STATUS_FAILED=1
declare logfile=/var/log/patching.log
declare NAME=$(basename $0)
function loginfo()
{
echo "`date "+%FT%T.%3N"`: $NAME: $*" >> $logfile
}
function is_controller()
{
[[ $nodetype == "controller" ]]
}
function is_worker()
{
[[ $nodetype == "worker" ]]
}
function is_storage()
{
[[ $nodetype == "storage" ]]
}
function is_cpe()
{
[[ $nodetype == "controller" && $subfunction =~ worker ]]
}
function is_locked()
{
test -f /var/run/.node_locked
}

2
sw-patch/bin/patch-tmpdirs.conf

@ -0,0 +1,2 @@
d /run/patching 0700 root root -

16
sw-patch/bin/patch_build

@ -0,0 +1,16 @@
#!/usr/bin/env python
"""
Copyright (c) 2014 Wind River Systems, Inc.
SPDX-License-Identifier: Apache-2.0
"""
import sys
from cgcs_patch.patch_functions import patch_build
if __name__ == "__main__":
sys.exit(patch_build())

27
sw-patch/bin/patch_check_goenabled.sh

@ -0,0 +1,27 @@
#!/bin/bash
#
# Copyright (c) 2014 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# Patching "goenabled" check.
# If a patch has been applied on this node, it is now out-of-date and should be rebooted.
NAME=$(basename $0)
SYSTEM_CHANGED_FLAG=/var/run/node_is_patched
logfile=/var/log/patching.log
function LOG {
logger "$NAME: $*"
echo "`date "+%FT%T.%3N"`: $NAME: $*" >> $logfile
}
if [ -f $SYSTEM_CHANGED_FLAG ]; then
LOG "Node has been patched. Failing goenabled check."
exit 1
fi
exit 0

7
sw-patch/bin/patching.conf

@ -0,0 +1,7 @@
[runtime]
controller_multicast = 239.1.1.3
agent_multicast = 239.1.1.4
api_port = 5487
controller_port = 5488
agent_port = 5489

15
sw-patch/bin/patching.logrotate

@ -0,0 +1,15 @@
/var/log/patching.log
/var/log/patching-api.log
/var/log/patching-insvc.log
{
nodateext
size 10M
start 1
rotate 10
missingok
notifempty
compress
delaycompress
copytruncate
}

19
sw-patch/bin/pmon-sw-patch-agent.conf

@ -0,0 +1,19 @@
[process]
process = sw-patch-agent
pidfile = /var/run/sw-patch-agent.pid
script = /etc/init.d/sw-patch-agent
style = lsb ; ocf or lsb
severity = major ; Process failure severity
; critical : host is failed
; major : host is degraded
; minor : log is generated
restarts = 3 ; Number of back to back unsuccessful restarts before severity assertion
interval = 5 ; Number of seconds to wait between back-to-back unsuccessful restarts
debounce = 20 ; Number of seconds the process needs to run before declaring
; it as running O.K. after a restart.
; Time after which back-to-back restart count is cleared.
startuptime = 10 ; Seconds to wait after process start before starting the debounce monitor
mode = passive ; Monitoring mode: passive (default) or active
; passive: process death monitoring (default: always)
; active: heartbeat monitoring, i.e. request / response messaging

19
sw-patch/bin/pmon-sw-patch-controller-daemon.conf

@ -0,0 +1,19 @@
[process]
process = sw-patch-controller-daemon
pidfile = /var/run/sw-patch-controller-daemon.pid
script = /etc/init.d/sw-patch-controller-daemon
style = lsb ; ocf or lsb
severity = major ; Process failure severity
; critical : host is failed
; major : host is degraded
; minor : log is generated
restarts = 3 ; Number of back to back unsuccessful restarts before severity assertion
interval = 5 ; Number of seconds to wait between back-to-back unsuccessful restarts
debounce = 20 ; Number of seconds the process needs to run before declaring
; it as running O.K. after a restart.
; Time after which back-to-back restart count is cleared.
startuptime = 10 ; Seconds to wait after process start before starting the debounce monitor
mode = passive ; Monitoring mode: passive (default) or active
; passive: process death monitoring (default: always)
; active: heartbeat monitoring, i.e. request / response messaging

5
sw-patch/bin/policy.json

@ -0,0 +1,5 @@
{
"admin": "role:admin or role:administrator",
"admin_api": "is_admin:True",
"default": "rule:admin_api"
}

15
sw-patch/bin/query_patch

@ -0,0 +1,15 @@
#!/usr/bin/env python
#
# Copyright (c) 2013-2014 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import sys
from cgcs_make_patch.make_patch_functions import query_patch
if __name__ == "__main__":
sys.exit(query_patch())

183
sw-patch/bin/rpm-audit

@ -0,0 +1,183 @@
#!/bin/bash
#
# Copyright (c) 2016 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
if [[ $EUID -ne 0 ]]; then
echo "This utility must be run as root." >&2
exit 1
fi
function show_usage()
{
cat <<EOF
Usage: [ --include-pyc ] [ --include-cfg ] --skip-multi [ pkg ... ]
This utility scans the installed RPMs to compare checksums of files.
By default, files flagged as config are skipped, as are python pyc files.
Optional arguments:
--include-pyc : Include pyc files in check
--include-cfg : Include config files in check
--skip-links : Skip symlink check
--skip-multi : Skip the search for files with multiple owners
pkg : Specify one or more packages to limit the scan
(implies --skip-multi)
EOF
exit 1
}
declare INCLUDE_PYTHON_FILES="no"
declare INCLUDE_CFG_FILES="no"
declare CHECK_FOR_MULTI="yes"
declare CHECK_LINKS="yes"
declare TIS_ONLY="yes"
declare CHECK_RPM=
for arg in "$@"
do
case $arg in
-h|--help)
show_usage
;;
--include-pyc)
INCLUDE_PYTHON_FILES="yes"
;;
--include-cfg)
INCLUDE_CFG_FILES="yes"
;;
--skip-links)
CHECK_LINKS="no"
;;
--skip-multi)
CHECK_FOR_MULTI="no"
;;
--all-rpms)
TIS_ONLY="no"
;;
*)
CHECK_RPM="$CHECK_RPM $arg"
CHECK_FOR_MULTI="no"
;;
esac
done
function rpm_list()
{
if [ -n "$CHECK_RPM" ]
then
for pkg in $CHECK_RPM
do
echo $pkg
done
elif [ "$TIS_ONLY" = "yes" ]
then
rpm -qa | grep '\.tis\.' | sort
else
rpm -qa | sort
fi
}
rpm_list | while read pkg
do
# Get the --dump from the pkg
rpm -q --queryformat "[%{FILENAMES}|%{FILEMD5S}|%{FILEFLAGS:fflags}|%{FILELINKTOS}\n]" $pkg | \
while IFS='|' read pname psum pflags plinkto
do
if [[ $pname == "(contains" ]]
then
# (contains no files)
continue
fi
if [[ $INCLUDE_CFG_FILES == "no" && $pflags =~ c ]]
then
# Skip file already flagged as config
continue
fi
if [[ $INCLUDE_PYTHON_FILES == "no" && $pname =~ \.py[co]$ ]]
then
# Skip python .pyo or .pyc file
continue
fi
# Directories and symlinks will have no checksum
if [[ -z $psum ]]
then
if [[ -n $plinkto && $CHECK_LINKS == "yes" ]]
then
# Check the symlink pointer
flinkto=$(readlink $pname)
if [[ "$flinkto" != "$plinkto" ]]
then
echo "Link Mismatch: $pname ($pkg)"
fi
fi
continue
fi
# Does the file exist?
if [ ! -e "$pname" ]
then
echo "Missing: $pname ($pkg)"
continue
fi
# Has the file been replaced by a symlink? ie. update-alternatives
if [ -L "$pname" ]
then
continue
fi
let -i sumlen=$(echo -n $psum | wc -c)
if [ $sumlen = 64 ]
then
sumcmd=sha256sum
else
sumcmd=md5sum
fi
echo $psum $pname | $sumcmd --check --status
if [ $? -ne 0 ]
then
echo "Mismatch: $pname ($pkg)"
fi
done
done
function check_for_multi_master()
{
# Search for files owned by multiple packages
prev=
rpm_list | xargs rpm -q --queryformat "[%{FILENAMES}|%{=NAME}\n]" | sort | while IFS='|' read f p
do
if [ "$f" = "$prev" ]
then
echo $f
fi
prev=$f
done | sort -u | while read f
do
if [ ! -d "$f" ]
then
echo $f
fi
done
}
if [ $CHECK_FOR_MULTI = "yes" ]
then
echo
echo
echo "The following files belong to multiple packages:"
echo
check_for_multi_master
fi

60
sw-patch/bin/run-patch-scripts

@ -0,0 +1,60 @@
#!/bin/bash
#
# Copyright (c) 2016 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
. /etc/patching/patch-functions
declare SCRIPTS=$(find $PATCH_SCRIPTDIR -type f -executable | sort)
declare -i NUM_SCRIPTS=$(echo "$SCRIPTS" | wc -l)
if [ $NUM_SCRIPTS -eq 0 ]
then
loginfo "No in-service patch scripts found."
exit 0
fi
loginfo "Running $NUM_SCRIPTS in-service patch scripts"
declare SCRIPTLOG=/var/log/patching-insvc.log
cat <<EOF >>$SCRIPTLOG
############################################################
`date "+%FT%T.%3N"`: Running $NUM_SCRIPTS in-service patch scripts:
$SCRIPTS
############################################################
EOF
declare -i FAILURES=0
for cmd in $SCRIPTS
do
cat <<EOF >>$SCRIPTLOG
############################################################
`date "+%FT%T.%3N"`: Running $cmd
EOF
bash -x $cmd >>$SCRIPTLOG 2>&1
rc=$?
if [ $rc -ne $PATCH_STATUS_OK ]
then
let -i FAILURES++
fi
cat <<EOF >>$SCRIPTLOG
`date "+%FT%T.%3N"`: Completed running $cmd (rc=$rc)
############################################################
EOF
done
cat <<EOF >>$SCRIPTLOG
`date "+%FT%T.%3N"`: Completed running scripts with $FAILURES failures
############################################################
EOF
exit $FAILURES

182
sw-patch/bin/setup_patch_repo

@ -0,0 +1,182 @@
#!/usr/bin/env python
"""
Copyright (c) 2018-2020 Wind River Systems, Inc.
SPDX-License-Identifier: Apache-2.0
"""
import getopt
import os
import platform
import rpm
import shutil
import subprocess
import sys
import tempfile
import cgcs_patch.patch_functions as pf
import cgcs_patch.patch_verify as pv
import cgcs_patch.constants as constants
import logging
logging.getLogger('main_logger')
logging.basicConfig(level=logging.INFO)
# Override the pv.dev_certificate_marker so we can verify signatures off-box
cgcs_patch_bindir = os.path.dirname(os.path.abspath(sys.argv[0]))
dev_cert_path = os.path.abspath(os.path.join(cgcs_patch_bindir, '../../enable-dev-patch/enable-dev-patch/dev_certificate_enable.bin'))
pv.dev_certificate_marker = dev_cert_path
def usage():
print "Usage: %s -o <repodir> <patch> ..." \
% os.path.basename(sys.argv[0])
exit(1)
def main():
try:
opts, remainder = getopt.getopt(sys.argv[1:],
'o:',
['output='])
except getopt.GetoptError:
usage()
output = None
for opt, arg in opts:
if opt == "--output" or opt == '-o':
output = arg
if output is None:
usage()
sw_version = os.environ['PLATFORM_RELEASE']
allpatches = pf.PatchData()
output = os.path.abspath(output)
pkgdir = os.path.join(output, 'Packages')
datadir = os.path.join(output, 'metadata')
committed_dir = os.path.join(datadir, 'committed')
if os.path.exists(output):
# Check to see if the expected structure already exists,
# maybe we're appending a patch.
if not os.path.exists(committed_dir) or not os.path.exists(pkgdir):
print "Packages or metadata dir missing from existing %s. Aborting..." % output
exit(1)
# Load the existing metadata
allpatches.load_all_metadata(committed_dir, constants.COMMITTED)
else:
os.mkdir(output, 0o755)
os.mkdir(datadir, 0o755)
os.mkdir(committed_dir, 0o755)
os.mkdir(pkgdir, 0o755)
# Save the current directory, so we can chdir back after
orig_wd = os.getcwd()
tmpdir = None
try:
for p in remainder:
fpath = os.path.abspath(p)
# Create a temporary working directory
tmpdir = tempfile.mkdtemp(prefix="patchrepo_")
# Change to the tmpdir
os.chdir(tmpdir)
print "Parsing %s" % fpath
pf.PatchFile.read_patch(fpath)
thispatch = pf.PatchData()
patch_id = thispatch.parse_metadata("metadata.xml", constants.COMMITTED)
if patch_id in allpatches.metadata:
print "Skipping %s as it's already in the repo" % patch_id
# Change back to original working dir
os.chdir(orig_wd)
shutil.rmtree(tmpdir)
tmpdir = None
continue
patch_sw_version = thispatch.query_line(patch_id, 'sw_version')
if patch_sw_version != sw_version:
raise Exception("%s is for release %s, not %s" % (patch_id, patch_sw_version, sw_version))
# Move the metadata to the "committed" dir, and the rpms to the Packages dir
shutil.move('metadata.xml', os.path.join(committed_dir, "%s-metadata.xml" % patch_id))
for f in thispatch.query_line(patch_id, 'contents'):
shutil.move(f, pkgdir)
allpatches.add_patch(patch_id, thispatch)
# Change back to original working dir
os.chdir(orig_wd)
shutil.rmtree(tmpdir)
tmpdir = None
except:
if tmpdir is not None:
# Change back to original working dir
os.chdir(orig_wd)
shutil.rmtree(tmpdir)
tmpdir = None
raise
allpatches.gen_release_groups_xml(sw_version, output)
# Purge unneeded RPMs
keep = {}
for patch_id in allpatches.metadata.keys():
for rpmname in allpatches.contents[patch_id]:
try:
pkgname, arch, pkgver = pf.parse_rpm_filename(rpmname)
except ValueError as e:
raise e
if pkgname not in keep:
keep[pkgname] = { arch: pkgver }
continue
elif arch not in keep[pkgname]:
keep[pkgname][arch] = pkgver
continue
# Compare versions
keep_pkgver = keep[pkgname][arch]
if pkgver > keep_pkgver:
# Find the rpmname
keep_rpmname = keep_pkgver.generate_rpm_filename(pkgname, arch)
filename = os.path.join(pkgdir, keep_rpmname)
if os.path.exists(filename):
os.remove(filename)
# Keep the new pkgver
keep[pkgname][arch] = pkgver
else:
filename = os.path.join(pkgdir, rpmname)
if os.path.exists(filename):
os.remove(filename)
# Create the repo metadata
if os.path.exists('/usr/bin/createrepo_c'):
createrepo = '/usr/bin/createrepo_c'
else:
createrepo = 'createrepo'
os.chdir(output)
subprocess.check_call([createrepo, '-g', 'comps.xml', '.'])
if __name__ == "__main__":
sys.exit(main())

16
sw-patch/bin/sw-patch

@ -0,0 +1,16 @@
#!/usr/bin/python
"""
Copyright (c) 2014 Wind River Systems, Inc.
SPDX-License-Identifier: Apache-2.0
"""
import sys
from cgcs_patch.patch_client import main
if __name__ == "__main__":
main()

16
sw-patch/bin/sw-patch-agent

@ -0,0 +1,16 @@
#!/usr/bin/python
"""
Copyright (c) 2014 Wind River Systems, Inc.
SPDX-License-Identifier: Apache-2.0
"""
import sys
from cgcs_patch.patch_agent import main
if __name__ == "__main__":
main()

94
sw-patch/bin/sw-patch-agent-init.sh

@ -0,0 +1,94 @@
#!/bin/sh
#
# Copyright (c) 2014-2015 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# chkconfig: 345 26 30
### BEGIN INIT INFO
# Provides: sw-patch-agent
# Required-Start: $syslog
# Required-Stop: $syslog
# Default-Start: 2 3 5
# Default-Stop: 0 1 6
# Short-Description: sw-patch-agent
# Description: Provides the CGCS Patch Agent Daemon
### END INIT INFO
DESC="sw-patch-agent"
DAEMON="/usr/sbin/sw-patch-agent"
PIDFILE="/var/run/sw-patch-agent.pid"
PATCH_INSTALLING_FILE="/var/run/patch_installing"
start()
{
if [ -e $PIDFILE ]; then
PIDDIR=/proc/$(cat $PIDFILE)
if [ -d ${PIDDIR} ]; then
echo "$DESC already running."
exit 1
else
echo "Removing stale PID file $PIDFILE"
rm -f $PIDFILE
fi
fi
echo -n "Starting $DESC..."
start-stop-daemon --start --quiet --background \
--pidfile ${PIDFILE} --make-pidfile --exec ${DAEMON}
if [ $? -eq 0 ]; then
echo "done."
else
echo "failed."
fi
}
stop()
{
if [ -f $PATCH_INSTALLING_FILE ]; then
echo "Patches are installing. Waiting for install to complete."
while [ -f $PATCH_INSTALLING_FILE ]; do
# Verify the agent is still running
pid=$(cat $PATCH_INSTALLING_FILE)
cat /proc/$pid/cmdline 2>/dev/null | grep -q $DAEMON
if [ $? -ne 0 ]; then
echo "Patch agent not running."
break
fi
sleep 1
done
echo "Continuing with shutdown."
fi
echo -n "Stopping $DESC..."
start-stop-daemon --stop --quiet --pidfile $PIDFILE
if [ $? -eq 0 ]; then
echo "done."
else
echo "failed."
fi
rm -f $PIDFILE
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart|force-reload)
stop
start
;;
*)
echo "Usage: $0 {start|stop|force-reload|restart}"
exit 1
;;
esac
exit 0

20
sw-patch/bin/sw-patch-agent-restart

@ -0,0 +1,20 @@
#!/bin/bash
#
# Copyright (c) 2016 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
. /etc/patching/patch-functions
#
# Triggering a restart of the patching daemons is done by
# creating a flag file and letting the daemon handle the restart.
#
loginfo "Requesting restart of patch-agent"
restart_patch_agent_flag="/run/patching/.restart.patch-agent"
touch $restart_patch_agent_flag
exit 0

16
sw-patch/bin/sw-patch-agent.service

@ -0,0 +1,16 @@
[Unit]
Description=StarlingX Patching Agent
After=syslog.target network-online.target sw-patch.service
Before=pmon.service
[Service]
Type=forking
User=root
ExecStart=/etc/init.d/sw-patch-agent start
ExecStop=/etc/init.d/sw-patch-agent stop
ExecReload=/etc/init.d/sw-patch-agent restart
PIDFile=/var/run/sw-patch-agent.pid
[Install]
WantedBy=multi-user.target

16
sw-patch/bin/sw-patch-controller-daemon

@ -0,0 +1,16 @@
#!/usr/bin/python
"""
Copyright (c) 2014 Wind River Systems, Inc.
SPDX-License-Identifier: Apache-2.0
"""
import sys
from cgcs_patch.patch_controller import main
if __name__ == "__main__":
main()

78
sw-patch/bin/sw-patch-controller-daemon-init.sh

@ -0,0 +1,78 @@
#!/bin/sh
#
# Copyright (c) 2014-2015 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# chkconfig: 345 25 30
### BEGIN INIT INFO
# Provides: sw-patch-controller-daemon
# Required-Start: $syslog
# Required-Stop: $syslog
# Default-Start: 2 3 5
# Default-Stop: 0 1 6
# Short-Description: sw-patch-controller-daemon
# Description: Provides the CGCS Patch Controller Daemon
### END INIT INFO
DESC="sw-patch-controller-daemon"
DAEMON="/usr/sbin/sw-patch-controller-daemon"
PIDFILE="/var/run/sw-patch-controller-daemon.pid"
start()
{
if [ -e $PIDFILE ]; then
PIDDIR=/proc/$(cat $PIDFILE)
if [ -d ${PIDDIR} ]; then
echo "$DESC already running."
exit 1
else
echo "Removing stale PID file $PIDFILE"
rm -f $PIDFILE
fi
fi
echo -n "Starting $DESC..."
start-stop-daemon --start --quiet --background \
--pidfile ${PIDFILE} --make-pidfile --exec ${DAEMON}
if [ $? -eq 0 ]; then
echo "done."
else
echo "failed."
fi
}
stop()
{
echo -n "Stopping $DESC..."
start-stop-daemon --stop --quiet --pidfile $PIDFILE
if [ $? -eq 0 ]; then
echo "done."
else
echo "failed."
fi
rm -f $PIDFILE
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart|force-reload)
stop
start
;;
*)
echo "Usage: $0 {start|stop|force-reload|restart}"
exit 1
;;
esac
exit 0

20
sw-patch/bin/sw-patch-controller-daemon-restart

@ -0,0 +1,20 @@
#!/bin/bash
#
# Copyright (c) 2016 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
. /etc/patching/patch-functions
#
# Triggering a restart of the patching daemons is done by
# creating a flag file and letting the daemon handle the restart.
#
loginfo "Requesting restart of patch-controller"
restart_patch_controller_flag="/run/patching/.restart.patch-controller"
touch $restart_patch_controller_flag
exit 0

16
sw-patch/bin/sw-patch-controller-daemon.service

@ -0,0 +1,16 @@
[Unit]
Description=StarlingX Patching Controller Daemon
After=syslog.target network-online.target sw-patch.service sw-patch-controller.service
Before=pmon.service
[Service]
Type=forking
User=root
ExecStart=/etc/init.d/sw-patch-controller-daemon start
ExecStop=/etc/init.d/sw-patch-controller-daemon stop
ExecReload=/etc/init.d/sw-patch-controller-daemon restart
PIDFile=/var/run/sw-patch-controller-daemon.pid
[Install]
WantedBy=multi-user.target

106
sw-patch/bin/sw-patch-controller-init.sh

@ -0,0 +1,106 @@
#!/bin/bash
#
# Copyright (c) 2014 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# StarlingX Patching Controller setup
# chkconfig: 345 20 24
# description: CGCS Patching Controller init script
### BEGIN INIT INFO
# Provides: sw-patch-controller
# Required-Start: $syslog
# Required-Stop: $syslog
# Default-Start: 2 3 5
# Default-Stop: 0 1 6
# Short-Description: sw-patch-controller
# Description: Provides the StarlingX Patch Controller Daemon
### END INIT INFO
. /usr/bin/tsconfig
NAME=$(basename $0)
REPO_ID=updates
REPO_ROOT=/var/www/pages/${REPO_ID}
REPO_DIR=${REPO_ROOT}/rel-${SW_VERSION}
GROUPS_FILE=$REPO_DIR/comps.xml
PATCHING_DIR=/opt/patching
logfile=/var/log/patching.log
function LOG {
logger "$NAME: $*"
echo "`date "+%FT%T.%3N"`: $NAME: $*" >> $logfile
}
function LOG_TO_FILE {
echo "`date "+%FT%T.%3N"`: $NAME: $*" >> $logfile
}
function create_groups {
if [ -f $GROUPS_FILE ]; then
return 0
fi
cat >$GROUPS_FILE <<EOF
<comps>
</comps>
EOF
}
function do_setup {
# Does the repo exist?
if [ ! -d $REPO_DIR ]; then
LOG "Creating repo"
mkdir -p $REPO_DIR
# Setup the groups file
create_groups
createrepo -g $GROUPS_FILE $REPO_DIR >> $logfile 2>&1
fi
if [ ! -d $PATCHING_DIR ]; then
LOG "Creating $PATCHING_DIR"
mkdir -p $PATCHING_DIR
fi
# If we can ping the active controller, sync the repos
LOG_TO_FILE "ping -c 1 -w 1 controller"
ping -c 1 -w 1 controller >> $logfile 2>&1 || ping6 -c 1 -w 1 controller >> $logfile 2>&1
if [ $? -ne 0 ]; then
LOG "Cannot ping controller. Nothing to do"
return 0
fi
# Sync the patching dir
LOG_TO_FILE "rsync -acv --delete rsync://controller/patching/ ${PATCHING_DIR}/"
rsync -acv --delete rsync://controller/patching/ ${PATCHING_DIR}/ >> $logfile 2>&1
# Sync the patching dir
LOG_TO_FILE "rsync -acv --delete rsync://controller/repo/ ${REPO_ROOT}/"
rsync -acv --delete rsync://controller/repo/ ${REPO_ROOT}/ >> $logfile 2>&1
}
case "$1" in
start)
do_setup
;;
status)
;;
stop)
# Nothing to do here
;;
restart)
do_setup
;;
*)
echo "Usage: $0 {status|start|stop|restart}"
exit 1
esac
exit 0

14
sw-patch/bin/sw-patch-controller.service

@ -0,0 +1,14 @@
[Unit]
Description=StarlingX Patching Controller
After=syslog.service network-online.target sw-patch.service
Before=sw-patch-agent.service sw-patch-controller-daemon.service
[Service]
Type=oneshot
User=root
ExecStart=/etc/init.d/sw-patch-controller start
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target

178
sw-patch/bin/sw-patch-init.sh

@ -0,0 +1,178 @@
#!/bin/bash
#
# Copyright (c) 2014-2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# StarlingX Patching
# chkconfig: 345 20 23
# description: StarlingX Patching init script
### BEGIN INIT INFO
# Provides: sw-patch
# Required-Start: $syslog
# Required-Stop: $syslog
# Default-Start: 2 3 5
# Default-Stop: 0 1 6
# Short-Description: sw-patch
# Description: Provides the StarlingX Patching
### END INIT INFO
NAME=$(basename $0)
. /usr/bin/tsconfig
. /etc/platform/platform.conf
logfile=/var/log/patching.log
patch_failed_file=/var/run/patch_install_failed
patched_during_init=/etc/patching/.patched_during_init
function LOG_TO_FILE {
echo "`date "+%FT%T.%3N"`: $NAME: $*" >> $logfile
}
function check_for_rr_patch {
if [ -f /var/run/node_is_patched_rr ]; then
if [ ! -f ${patched_during_init} ]; then
echo
echo "Node has been patched and requires an immediate reboot."
echo
LOG_TO_FILE "Node has been patched, with reboot-required flag set. Rebooting"
touch ${patched_during_init}
/sbin/reboot
else
echo
echo "Node has been patched during init a second consecutive time. Skipping reboot due to possible error"
echo
LOG_TO_FILE "Node has been patched during init a second consecutive time. Skipping reboot due to possible error"
touch ${patch_failed_file}
rm -f ${patched_during_init}
exit 1
fi
else
rm -f ${patched_during_init}
fi
}
function check_install_uuid {
# Check whether our installed load matches the active controller
CONTROLLER_UUID=`curl -sf http://controller:${http_port}/feed/rel-${SW_VERSION}/install_uuid`
if [ $? -ne 0 ]; then
if [ "$HOSTNAME" = "controller-1" ]; then
# If we're on controller-1, controller-0 may not have the install_uuid
# matching this release, if we're in an upgrade. If the file doesn't exist,
# bypass this check
return 0
fi
LOG_TO_FILE "Unable to retrieve installation uuid from active controller"
echo "Unable to retrieve installation uuid from active controller"
return 1
fi
if [ "$INSTALL_UUID" != "$CONTROLLER_UUID" ]; then
LOG_TO_FILE "This node is running a different load than the active controller and must be reinstalled"
echo "This node is running a different load than the active controller and must be reinstalled"
return 1
fi
return 0
}
# Check for installation failure
if [ -f /etc/platform/installation_failed ] ; then
LOG_TO_FILE "/etc/platform/installation_failed flag is set. Aborting."
echo "$(basename $0): Detected installation failure. Aborting."
exit 1
fi
# Clean up the RPM DB
if [ ! -f /var/run/.rpmdb_cleaned ]; then
LOG_TO_FILE "Cleaning RPM DB"
rm -f /var/lib/rpm/__db*
touch /var/run/.rpmdb_cleaned
fi
# For AIO-SX, abort if config is not yet applied and this is running in init
if [ "${system_mode}" = "simplex" -a ! -f ${INITIAL_CONTROLLER_CONFIG_COMPLETE} -a "$1" = "start" ]; then
LOG_TO_FILE "Config is not yet applied. Skipping init patching"
exit 0
fi
# If the management interface is bonded, it may take some time
# before communications can be properly setup.
# Allow up to $DELAY_SEC seconds to reach controller.
DELAY_SEC=120
START=`date +%s`
FOUND=0
while [ $(date +%s) -lt $(( ${START} + ${DELAY_SEC} )) ]; do
ping -c 1 controller > /dev/null 2>&1 || ping6 -c 1 controller > /dev/null 2>&1
if [ $? -eq 0 ]; then
FOUND=1
break
fi
sleep 1
done
if [ ${FOUND} -eq 0 ]; then
# 'controller' is not available, just exit
LOG_TO_FILE "Unable to contact active controller (controller). Boot will continue."
exit 1
fi
RC=0
case "$1" in
start)
if [ "${system_mode}" = "simplex" ]; then
# On a simplex CPE, we need to launch the http server first,
# before we can do the patch installation
LOG_TO_FILE "***** Launching lighttpd *****"
/etc/init.d/lighttpd start
LOG_TO_FILE "***** Starting patch operation *****"
/usr/sbin/sw-patch-agent --install 2>>$logfile
if [ -f ${patch_failed_file} ]; then
RC=1
LOG_TO_FILE "***** Patch operation failed *****"
fi
LOG_TO_FILE "***** Finished patch operation *****"
LOG_TO_FILE "***** Shutting down lighttpd *****"
/etc/init.d/lighttpd stop
else
check_install_uuid
if [ $? -ne 0 ]; then
# The INSTALL_UUID doesn't match the active controller, so exit
exit 1
fi
LOG_TO_FILE "***** Starting patch operation *****"
/usr/sbin/sw-patch-agent --install 2>>$logfile
if [ -f ${patch_failed_file} ]; then
RC=1
LOG_TO_FILE "***** Patch operation failed *****"
fi
LOG_TO_FILE "***** Finished patch operation *****"
fi
check_for_rr_patch
;;
stop)
# Nothing to do here
;;
restart)
LOG_TO_FILE "***** Starting patch operation *****"
/usr/sbin/sw-patch-agent --install 2>>$logfile
if [ -f ${patch_failed_file} ]; then
RC=1
LOG_TO_FILE "***** Patch operation failed *****"
fi
LOG_TO_FILE "***** Finished patch operation *****"
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
esac
exit $RC

148
sw-patch/bin/sw-patch.completion

@ -0,0 +1,148 @@
#
# Copyright (c) 2016 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
#
# This file provides bash-completion functionality for the sw-patch CLI
#
function _swpatch()
{
COMPREPLY=()
local cur="${COMP_WORDS[COMP_CWORD]}"
local prev="${COMP_WORDS[COMP_CWORD-1]}"
local subcommand=${COMP_WORDS[1]}
#
# The available sw-patch subcommands
#
local subcommands="
apply
commit
delete
query
query-dependencies
query-hosts
remove
show
upload
upload-dir
what-requires
drop-host
is-applied
is-available
report-app-dependencies
query-app-dependencies
"
if [ -f /etc/platform/.initial_config_complete ]; then
# Post-config, so the host-install commands are accessible
subcommands="${subcommands} host-install host-install-async"
else
# Pre-config, so the install-local command is accessible
subcommands="${subcommands} install-local"
fi
# Appends the '/' when completing dir names
set mark-directories on
if [ $COMP_CWORD -gt 1 ]; then
#
# Complete the arguments to the subcommands.
#
case "$subcommand" in
apply|delete|show|what-requires|is-applied|is-available)
# Query the list of known patches
local patches=$(sw-patch completion patches 2>/dev/null)
COMPREPLY=( $(compgen -W "${patches}" -- ${cur}) )
return 0
;;
remove)
# Query the list of known patches
local patches=$(sw-patch completion patches 2>/dev/null)
COMPREPLY=( $(compgen -W "--skipappcheck ${patches}" -- ${cur}) )
return 0
;;
host-install|host-install-async|drop-host)
if [ "${prev}" = "${subcommand}" -o "${prev}" = "--force" ]; then
# Query the list of known hosts
local names=$(sw-patch completion hosts 2>/dev/null)
COMPREPLY=( $(compgen -W "${names}" -- ${cur}) )
else
# Only one host can be specified, so no more completion
COMPREPLY=( $(compgen -- ${cur}) )
fi
return 0
;;
upload)
# Allow dirs and files with .patch extension for completion
COMPREPLY=( $(compgen -f -o plusdirs -X '!*.patch' -- ${cur}) )
return 0
;;
upload-dir)
# Allow dirs only for completion
COMPREPLY=( $(compgen -d -- ${cur}) )
return 0
;;
query)
if [ "${prev}" = "--release" ]; then
# If --release has been specified, provide installed releases for completion
local releases=$(/bin/ls -d /var/www/pages/feed/rel-* 2>/dev/null | sed 's#/var/www/pages/feed/rel-##')
COMPREPLY=( $(compgen -W "${releases}" -- ${cur}) )
else
# --release is only completion option for query
COMPREPLY=( $(compgen -W "--release" -- ${cur}) )
fi
return 0
;;
query-hosts|install-local)
# These subcommands have no options/arguments
COMPREPLY=( $(compgen -- ${cur}) )
return 0
;;
query-dependencies)
# Query the list of known patches
local patches=$(sw-patch completion patches 2>/dev/null)
COMPREPLY=( $(compgen -W "--recursive ${patches}" -- ${cur}) )
return 0
;;
commit)
if [ "${prev}" = "--release" ]; then
# If --release has been specified, provide installed releases for completion
local releases=$(/bin/ls -d /var/www/pages/feed/rel-* 2>/dev/null | sed 's#/var/www/pages/feed/rel-##')
COMPREPLY=( $(compgen -W "${releases}" -- ${cur}) )
else
# Query the list of known patches
local patches=$(sw-patch completion patches 2>/dev/null)
COMPREPLY=( $(compgen -W "--all --dry-run --release ${patches}" -- ${cur}) )
fi
return 0
;;
report-app-dependencies)
if [ "${prev}" = "${subcommand}" ]; then