Merge "Debian: Add modify patch and update iso path"
This commit is contained in:
commit
6b9c40671b
@ -31,11 +31,7 @@ This will create a new commit in the build ostree_repo
|
||||
--clone-repo ostree_test
|
||||
|
||||
Once the script is done the .patch file can be located at:
|
||||
$STX_BUILD_HOME/localdisk/lat/std/deploy/
|
||||
|
||||
Pending items:
|
||||
- Modify patch Status
|
||||
|
||||
$STX_BUILD_HOME/localdisk/deploy/
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
@ -52,10 +48,13 @@ from xml.dom import minidom
|
||||
# Signing function
|
||||
sys.path.insert(0, "../../cgcs-patch")
|
||||
from cgcs_patch.patch_signing import sign_files # noqa: E402 pylint: disable=wrong-import-position
|
||||
from cgcs_patch.patch_verify import verify_files # noqa: E402 pylint: disable=wrong-import-position
|
||||
|
||||
# STATUS_OBSOLETE = 'OBS'
|
||||
# STATUS_RELEASED = 'REL'
|
||||
STATUS_DEVELOPEMENT = 'DEV'
|
||||
PATCH_STATUS = {
|
||||
'release': 'REL',
|
||||
'obsolete': 'OBS',
|
||||
'development': 'DEV'
|
||||
}
|
||||
|
||||
METADATA_TAGS = ['ID', 'SW_VERSION', 'SUMMARY', 'DESCRIPTION', 'INSTALL_INSTRUCTIONS', 'WARNINGS', 'STATUS',
|
||||
'UNREMOVABLE', 'REBOOT_REQUIRED', 'REQUIRES', 'RESTART_SCRIPT', 'APPLY_ACTIVE_RELEASE_ONLY']
|
||||
@ -103,6 +102,21 @@ class PatchRecipeXMLFail(PatchError):
|
||||
pass
|
||||
|
||||
|
||||
class PatchInvalidStatus(PatchError):
|
||||
"""Invalid status"""
|
||||
pass
|
||||
|
||||
|
||||
class PatchModifyError(PatchError):
|
||||
"""Error while modifying patch"""
|
||||
pass
|
||||
|
||||
|
||||
class PatchValidationFailure(PatchError):
|
||||
"""Patch validation failure"""
|
||||
pass
|
||||
|
||||
|
||||
class PatchRecipeData(object):
|
||||
"""
|
||||
Patch data
|
||||
@ -202,7 +216,7 @@ class PatchBuilder(object):
|
||||
def __init__(self, delta_dir="delta_dir"):
|
||||
try:
|
||||
# ostree repo location
|
||||
self.deploy_dir = os.path.join(os.environ["STX_BUILD_HOME"], "localdisk/lat/std/deploy")
|
||||
self.deploy_dir = os.path.join(os.environ["STX_BUILD_HOME"], "localdisk/deploy")
|
||||
self.ostree_repo = os.path.join(self.deploy_dir, "ostree_repo")
|
||||
self.delta_dir = delta_dir
|
||||
self.detached_signature_file = "signature.v2"
|
||||
@ -243,7 +257,7 @@ class PatchBuilder(object):
|
||||
if "STATUS" in self.patch_data.metadata:
|
||||
self.__add_text_tag_to_xml(top, "status", self.patch_data.metadata["STATUS"])
|
||||
else:
|
||||
self.__add_text_tag_to_xml(top, "status", STATUS_DEVELOPEMENT)
|
||||
self.__add_text_tag_to_xml(top, "status", PATCH_STATUS['development'])
|
||||
|
||||
self.__add_text_tag_to_xml(top, "unremovable", self.patch_data.metadata["UNREMOVABLE"])
|
||||
self.__add_text_tag_to_xml(top, "reboot_required", self.patch_data.metadata["REBOOT_REQUIRED"])
|
||||
@ -344,18 +358,59 @@ class PatchBuilder(object):
|
||||
|
||||
return commits_from_base
|
||||
|
||||
def __sign_official_patches(self):
|
||||
def __sign_official_patches(self, patch_file):
|
||||
"""
|
||||
Sign formal patch
|
||||
Called internally once a patch is created and formal flag is set to true
|
||||
:param patch_file full path to the patch file
|
||||
"""
|
||||
log.info("Signing patch %s", self.patch_file_name)
|
||||
log.info("Signing patch %s", patch_file)
|
||||
try:
|
||||
patch_file_path = os.path.join(self.deploy_dir, self.patch_file_name)
|
||||
subprocess.check_call(["sign_patch_formal.sh", patch_file_path])
|
||||
# patch_file_path = os.path.join(self.deploy_dir, self.patch_file_name)
|
||||
subprocess.check_call(["sign_patch_formal.sh", patch_file])
|
||||
except subprocess.CalledProcessError as e:
|
||||
log.exception("Failed to sign official patch. Call to sign_patch_formal.sh process returned non-zero exit status %i", e.returncode)
|
||||
raise SystemExit(e.returncode)
|
||||
except FileNotFoundError:
|
||||
log.exception("sign_patch_formal.sh not found, make sure $STX_BUILD_HOME/repo/cgcs-root/build-tools is in the $PATH")
|
||||
|
||||
def __sign_and_pack(self, patch_file, formal=False):
|
||||
"""
|
||||
Generates the patch signatures and pack the .patch file
|
||||
:param patch_file .patch file full path
|
||||
"""
|
||||
filelist = ["metadata.tar", "software.tar"]
|
||||
# Generate the local signature file
|
||||
sig = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
for f in filelist:
|
||||
sig ^= get_md5(f)
|
||||
|
||||
sigfile = open("signature", "w")
|
||||
sigfile.write("%x" % sig)
|
||||
sigfile.close()
|
||||
|
||||
# this comes from patch_functions write_patch
|
||||
# Generate the detached signature
|
||||
#
|
||||
# Note: if cert_type requests a formal signature, but the signing key
|
||||
# is not found, we'll instead sign with the "dev" key and
|
||||
# need_resign_with_formal is set to True.
|
||||
need_resign_with_formal = sign_files(
|
||||
filelist,
|
||||
self.detached_signature_file,
|
||||
cert_type=None)
|
||||
|
||||
log.debug("Formal signing status %s", need_resign_with_formal)
|
||||
|
||||
# Save files into .patch
|
||||
files = [f for f in os.listdir('.') if os.path.isfile(f)]
|
||||
tar = tarfile.open(patch_file, "w:gz")
|
||||
for file in files:
|
||||
tar.add(file)
|
||||
tar.close()
|
||||
log.info("Patch file created %s", patch_file)
|
||||
if formal:
|
||||
log.info("Trying to sign formal patch")
|
||||
self.__sign_official_patches(patch_file)
|
||||
|
||||
def prepare_env(self, clone_repo="ostree-clone"):
|
||||
"""
|
||||
@ -429,6 +484,7 @@ class PatchBuilder(object):
|
||||
tar = tarfile.open("metadata.tar", "w")
|
||||
tar.add("metadata.xml")
|
||||
tar.close()
|
||||
os.remove("metadata.xml")
|
||||
|
||||
if self.patch_data.restart_script:
|
||||
log.info("Saving restart scripts")
|
||||
@ -437,39 +493,11 @@ class PatchBuilder(object):
|
||||
self.patch_data.restart_script["metadata_name"]
|
||||
)
|
||||
|
||||
filelist = ["metadata.tar", "software.tar"]
|
||||
# Generate the local signature file
|
||||
sig = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
for f in filelist:
|
||||
sig ^= get_md5(f)
|
||||
|
||||
sigfile = open("signature", "w")
|
||||
sigfile.write("%x" % sig)
|
||||
sigfile.close()
|
||||
|
||||
# this comes from patch_functions write_patch
|
||||
# Generate the detached signature
|
||||
#
|
||||
# Note: if cert_type requests a formal signature, but the signing key
|
||||
# is not found, we'll instead sign with the "dev" key and
|
||||
# need_resign_with_formal is set to True.
|
||||
need_resign_with_formal = sign_files(
|
||||
filelist,
|
||||
self.detached_signature_file,
|
||||
cert_type=None)
|
||||
|
||||
log.debug("Formal signing status %s", need_resign_with_formal)
|
||||
|
||||
# Create the patch
|
||||
tar = tarfile.open(os.path.join(self.deploy_dir, self.patch_file_name), "w:gz")
|
||||
for file in filelist:
|
||||
tar.add(file)
|
||||
tar.add("signature")
|
||||
tar.add(self.detached_signature_file)
|
||||
if self.patch_data.restart_script and \
|
||||
os.path.isfile(self.patch_data.restart_script["metadata_name"]):
|
||||
tar.add(self.patch_data.restart_script["metadata_name"])
|
||||
tar.close()
|
||||
# Sign and create the .patch file
|
||||
self.__sign_and_pack(
|
||||
os.path.join(self.deploy_dir, self.patch_file_name),
|
||||
formal
|
||||
)
|
||||
|
||||
os.chdir(self.deploy_dir)
|
||||
shutil.rmtree(tmpdir)
|
||||
@ -477,9 +505,91 @@ class PatchBuilder(object):
|
||||
|
||||
log.info("Patch file created %s at %s", self.patch_file_name, self.deploy_dir)
|
||||
|
||||
if formal:
|
||||
log.info("Trying to sign formal patch")
|
||||
self.__sign_official_patches()
|
||||
def modify_metadata_text(self, filename, key, value):
|
||||
"""
|
||||
Open an xml file, find first element matching 'key' and replace the text with 'value'
|
||||
"""
|
||||
new_filename = "%s.new" % filename
|
||||
tree = ET.parse(filename)
|
||||
|
||||
# Prevent a proliferation of carriage returns when we write this XML back out to file.
|
||||
for e in tree.iter():
|
||||
if e.text is not None:
|
||||
e.text = e.text.rstrip()
|
||||
if e.tail is not None:
|
||||
e.tail = e.tail.rstrip()
|
||||
|
||||
root = tree.getroot()
|
||||
# Make the substitution
|
||||
e = root.find(key)
|
||||
if e is None:
|
||||
msg = "modify_metadata_text: failed to find tag '%s'" % key
|
||||
log.error(msg)
|
||||
raise PatchValidationFailure(msg)
|
||||
e.text = value
|
||||
|
||||
# write the modified file
|
||||
outfile = open(new_filename, 'w')
|
||||
rough_xml = ET.tostring(root)
|
||||
outfile.write(minidom.parseString(rough_xml).toprettyxml(indent=" "))
|
||||
outfile.close()
|
||||
os.rename(new_filename, filename)
|
||||
|
||||
def read_patch(self, path):
|
||||
"""
|
||||
Extract the patch to current dir and validate signature
|
||||
"""
|
||||
# Open the patch file and extract the contents to the current dir
|
||||
tar = tarfile.open(path, "r:gz")
|
||||
tar.extractall()
|
||||
# Checks signature
|
||||
sigfile = open("signature", "r")
|
||||
sig = int(sigfile.read(), 16)
|
||||
sigfile.close()
|
||||
|
||||
filelist = ["metadata.tar", "software.tar"]
|
||||
expected_sig = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
for f in filelist:
|
||||
sig ^= get_md5(f)
|
||||
|
||||
if sig != expected_sig:
|
||||
msg = "Patch failed verification"
|
||||
log.error(msg)
|
||||
raise PatchValidationFailure(msg)
|
||||
|
||||
# Verify detached signature
|
||||
if os.path.exists(self.detached_signature_file):
|
||||
sig_valid = verify_files(
|
||||
filelist,
|
||||
self.detached_signature_file,
|
||||
cert_type=None)
|
||||
sig_valid = True
|
||||
if sig_valid is True:
|
||||
msg = "Signature verified, patch has been signed"
|
||||
else:
|
||||
msg = "Signature check failed"
|
||||
raise PatchValidationFailure(msg)
|
||||
else:
|
||||
msg = "Patch has not been signed"
|
||||
raise PatchValidationFailure(msg)
|
||||
|
||||
# Extract metadata xml
|
||||
tar = tarfile.open("metadata.tar")
|
||||
tar.extractall()
|
||||
|
||||
def write_patch(self, patch_file, formal=False):
|
||||
"""
|
||||
Write files into .patch file and sign
|
||||
"""
|
||||
log.info("Saving patch file")
|
||||
tar = tarfile.open("metadata.tar", "w")
|
||||
tar.add("metadata.xml")
|
||||
tar.close()
|
||||
# remove the xml
|
||||
os.remove("metadata.xml")
|
||||
|
||||
# Sign and create the .patch file
|
||||
self.__sign_and_pack(patch_file, formal)
|
||||
|
||||
|
||||
def handle_create(params):
|
||||
@ -510,6 +620,38 @@ def handle_prepare(params):
|
||||
patch_builder.prepare_env(params.clone_repo)
|
||||
|
||||
|
||||
def handle_modify(params):
|
||||
"""
|
||||
Modify patch status and resigns
|
||||
"""
|
||||
log.info("Modifying patch %s", params.patch_file)
|
||||
if not os.path.isfile(params.patch_file):
|
||||
raise FileNotFoundError("Patch file not found")
|
||||
|
||||
if params.status not in PATCH_STATUS:
|
||||
raise PatchInvalidStatus(f"Supported status are {PATCH_STATUS}")
|
||||
|
||||
# Modify patch
|
||||
orig_wd = os.getcwd()
|
||||
workdir = tempfile.mkdtemp(prefix="patch_modify_")
|
||||
os.chdir(workdir)
|
||||
|
||||
try:
|
||||
p = PatchBuilder()
|
||||
# extract and validate signatures
|
||||
p.read_patch(params.patch_file)
|
||||
log.info("Updating patch status to %s", PATCH_STATUS[params.status])
|
||||
# Update Status
|
||||
p.modify_metadata_text("metadata.xml", "status", PATCH_STATUS[params.status])
|
||||
p.write_patch(params.patch_file, params.formal)
|
||||
|
||||
except PatchModifyError:
|
||||
log.exception("Error while modifying patch")
|
||||
finally:
|
||||
shutil.rmtree(workdir)
|
||||
os.chdir(orig_wd)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Debian make_patch helper")
|
||||
|
||||
@ -532,6 +674,15 @@ if __name__ == "__main__":
|
||||
create_parser.add_argument("-d", "--delta-dir", type=str, help="Delta dir name", default="delta-dir")
|
||||
create_parser.add_argument("-c", "--clone-repo", type=str, help="Clone repo directory name", default=None, required=True)
|
||||
|
||||
# Modify Patch action
|
||||
modify_parser = subparsers.add_parser("modify",
|
||||
add_help=False,
|
||||
description="modify patch status",
|
||||
help="Modify patch status - DEV, REL, OBS")
|
||||
modify_parser.add_argument("-s", "--status", type=str, help="Patch status", required=True)
|
||||
modify_parser.add_argument("-f", "--formal", action="store_true", help="Formal patch flag")
|
||||
modify_parser.add_argument("-pf", "--patch-file", type=str, help="Patch file", required=True)
|
||||
|
||||
args = parser.parse_args()
|
||||
log.debug("Args: %s", args)
|
||||
|
||||
@ -539,5 +690,7 @@ if __name__ == "__main__":
|
||||
handle_create(args)
|
||||
elif args.cmd == "prepare":
|
||||
handle_prepare(args)
|
||||
elif args.cmd == "modify":
|
||||
handle_modify(args)
|
||||
|
||||
log.info("Done")
|
||||
|
@ -252,7 +252,7 @@ if __name__ == "__main__":
|
||||
patch_env = PatchEnv()
|
||||
log.info("Environment: %s", patch_env)
|
||||
|
||||
deploy_dir = os.path.join(patch_env.build_home, "localdisk", "lat", "std", "deploy")
|
||||
deploy_dir = os.path.join(patch_env.build_home, "localdisk", "deploy")
|
||||
ostree_repo_build = os.path.join(deploy_dir, "ostree_repo")
|
||||
|
||||
# Setup env
|
||||
|
@ -1,357 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
'''
|
||||
Create a test patch for Debian
|
||||
|
||||
- fake restart script
|
||||
- fake dettached signature (signature.v2)
|
||||
|
||||
Prereqs:
|
||||
- Requires the ostree tool installed - apt-get install ostree
|
||||
- export STX_BUILD_HOME
|
||||
e.g: export STX_BUILD_HOME=/localdisk/designer/lsampaio/stx-debian
|
||||
- pip3 install pycryptodomex
|
||||
|
||||
Setup Steps:
|
||||
sudo chmod 644 $STX_BUILD_HOME/localdisk/deploy/ostree_repo/.lock
|
||||
sudo chmod 644 $STX_BUILD_HOME/localdisk/lat/std/deploy/ostree_repo/.lock
|
||||
python make_test_patch.py --prepare --repo ostree_repo --clone-repo ostree-clone
|
||||
|
||||
Patch Steps:
|
||||
<make some ostree changes>
|
||||
<build-image>
|
||||
rm -Rf $STX_BUILD_HOME/localdisk/deploy/delta_dir
|
||||
rm -Rf $STX_BUILD_HOME/localdisk/lat/std/deploy/delta_dir
|
||||
python make_test_patch.py --create --repo ostree_repo --clone-repo ostree-clone
|
||||
|
||||
'''
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import logging
|
||||
import tarfile
|
||||
import tempfile
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from xml.dom import minidom
|
||||
|
||||
sys.path.insert(0, "../cgcs-patch")
|
||||
from cgcs_patch.patch_signing import sign_files
|
||||
|
||||
|
||||
# ostree_repo location
|
||||
DEPLOY_DIR = os.path.join(os.environ['STX_BUILD_HOME'], 'localdisk/lat/std/deploy')
|
||||
OSTREE_REPO = os.path.join(DEPLOY_DIR, 'ostree_repo')
|
||||
# Delta dir used by rsync, hardcoded for now
|
||||
DELTA_DIR = 'delta_dir'
|
||||
|
||||
detached_signature_file = 'signature.v2'
|
||||
SOFTWARE_VERSION = '22.06'
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format='%(asctime)s.%(msecs)03d %(levelname)s %(module)s - %(funcName)s: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S',
|
||||
)
|
||||
|
||||
log = logging.getLogger('make_test_patch')
|
||||
|
||||
|
||||
def prepare_env(name='ostree-clone'):
|
||||
'''
|
||||
Generates a copy of the current ostree_repo which is used
|
||||
to create the delta dir during patch creation
|
||||
:param name: name of the cloned directory
|
||||
'''
|
||||
log.info('Preparing ostree clone directory')
|
||||
os.chdir(DEPLOY_DIR)
|
||||
clone_dir = os.path.join(DEPLOY_DIR, name)
|
||||
if os.path.isdir(clone_dir):
|
||||
log.error('Clone directory exists {}'.format(name))
|
||||
exit(1)
|
||||
|
||||
os.mkdir(clone_dir)
|
||||
current_sha = open(os.path.join(OSTREE_REPO, 'refs/heads/starlingx'), 'r').read()
|
||||
log.info('Current SHA: {}'.format(current_sha))
|
||||
log.info('Cloning the directory...')
|
||||
subprocess.call(['rsync', '-a', OSTREE_REPO + '/', clone_dir])
|
||||
|
||||
log.info('Prepared ostree repo clone at {}'.format(clone_dir))
|
||||
|
||||
|
||||
def create_delta_dir(delta_dir='delta_dir', clone_dir='ostree-clone', clean_mode=False):
|
||||
'''
|
||||
Creates the ostree delta directory
|
||||
Contains the changes from the REPO (updated) and the cloned dir (pre update)
|
||||
:param delta_dir: delta directory name
|
||||
:param clone_dir: clone dir name
|
||||
'''
|
||||
log.info('Creating ostree delta')
|
||||
clone_dir = os.path.join(DEPLOY_DIR, clone_dir)
|
||||
|
||||
if os.path.isdir(delta_dir):
|
||||
if clean_mode:
|
||||
log.info('Delta dir exists {}, cleaning it'.format(delta_dir))
|
||||
shutil.rmtree(delta_dir)
|
||||
else:
|
||||
log.error('Delta dir exists {}, clean it up and try again'.format(delta_dir))
|
||||
exit(1)
|
||||
|
||||
if not os.path.isdir(clone_dir):
|
||||
log.error('Clone dir not found')
|
||||
exit(1)
|
||||
|
||||
subprocess.call(['rsync', '-rpgo', '--compare-dest', clone_dir, OSTREE_REPO + '/', delta_dir + '/'])
|
||||
log.info('Delta dir created')
|
||||
|
||||
|
||||
def add_text_tag_to_xml(parent,
|
||||
name,
|
||||
text):
|
||||
'''
|
||||
Utility function for adding a text tag to an XML object
|
||||
:param parent: Parent element
|
||||
:param name: Element name
|
||||
:param text: Text value
|
||||
:return:The created element
|
||||
'''
|
||||
tag = ET.SubElement(parent, name)
|
||||
tag.text = text
|
||||
return tag
|
||||
|
||||
|
||||
def gen_xml(patch_id, ostree_content, file_name="metadata.xml"):
|
||||
'''
|
||||
Generate patch metadata XML file
|
||||
:param file_name: Path to output file
|
||||
'''
|
||||
top = ET.Element("patch")
|
||||
|
||||
add_text_tag_to_xml(top, 'id', patch_id)
|
||||
add_text_tag_to_xml(top, 'sw_version', SOFTWARE_VERSION)
|
||||
add_text_tag_to_xml(top, 'summary', 'Summary text')
|
||||
add_text_tag_to_xml(top, 'description', 'Description text')
|
||||
add_text_tag_to_xml(top, 'install_instructions', 'Install instructions text')
|
||||
add_text_tag_to_xml(top, 'warnings', 'Warnings text')
|
||||
add_text_tag_to_xml(top, 'status', 'DEV')
|
||||
add_text_tag_to_xml(top, 'unremovable', 'N')
|
||||
add_text_tag_to_xml(top, 'reboot_required', 'Y')
|
||||
add_text_tag_to_xml(top, 'apply_active_release_only', '')
|
||||
add_text_tag_to_xml(top, 'restart_script', 'Patch1_Restart_Script.sh')
|
||||
|
||||
# Parse ostree_content
|
||||
content = ET.SubElement(top, 'contents')
|
||||
ostree = ET.SubElement(content, 'ostree')
|
||||
|
||||
add_text_tag_to_xml(ostree, 'number_of_commits', str(len(ostree_content['commits'])))
|
||||
base_commit = ET.SubElement(ostree, 'base')
|
||||
add_text_tag_to_xml(base_commit, 'commit', ostree_content['base']['commit'])
|
||||
add_text_tag_to_xml(base_commit, 'checksum', ostree_content['base']['checksum'])
|
||||
|
||||
for i, c in enumerate(ostree_content['commits']):
|
||||
commit = ET.SubElement(ostree, 'commit' + str(i + 1))
|
||||
add_text_tag_to_xml(commit, 'commit', c['commit'])
|
||||
add_text_tag_to_xml(commit, 'checksum', c['checksum'])
|
||||
|
||||
add_text_tag_to_xml(top, 'requires', '')
|
||||
add_text_tag_to_xml(top, 'semantics', '')
|
||||
|
||||
# print
|
||||
outfile = open(file_name, 'w')
|
||||
tree = ET.tostring(top)
|
||||
outfile.write(minidom.parseString(tree).toprettyxml(indent=" "))
|
||||
|
||||
|
||||
def gen_restart_script(file_name):
|
||||
'''
|
||||
Generate restart script
|
||||
:param file_name: Path to script file
|
||||
'''
|
||||
# print
|
||||
outfile = open(file_name, 'w')
|
||||
r = 'echo test restart script'
|
||||
outfile.write(r)
|
||||
|
||||
|
||||
def get_md5(path):
|
||||
'''
|
||||
Utility function for generating the md5sum of a file
|
||||
:param path: Path to file
|
||||
'''
|
||||
md5 = hashlib.md5()
|
||||
block_size = 8192
|
||||
with open(path, 'rb') as f:
|
||||
for chunk in iter(lambda: f.read(block_size), b''):
|
||||
md5.update(chunk)
|
||||
return int(md5.hexdigest(), 16)
|
||||
|
||||
|
||||
def get_commit_checksum(commit_id, repo='ostree_repo'):
|
||||
'''
|
||||
Get commit checksum from a commit id
|
||||
:param commit_id
|
||||
:param repo
|
||||
'''
|
||||
# get all checksums
|
||||
cmd = 'ostree --repo={} log starlingx | grep -i checksum | sed \'s/.* //\''.format(repo)
|
||||
cksums = subprocess.check_output(cmd, shell=True).decode(sys.stdout.encoding).strip().split('\n')
|
||||
return(cksums[commit_id])
|
||||
|
||||
|
||||
def get_commits_from_base(base_sha, repo='ostree_repo'):
|
||||
'''
|
||||
Get a list of commits from base sha
|
||||
:param base_sha
|
||||
:param repo
|
||||
'''
|
||||
commits_from_base = []
|
||||
|
||||
cmd = 'ostree --repo={} log starlingx | grep commit | sed \'s/.* //\''.format(repo)
|
||||
commits = subprocess.check_output(cmd, shell=True).decode(sys.stdout.encoding).strip().split('\n')
|
||||
|
||||
if commits[0] == base_sha:
|
||||
log.info('base and top commit are the same')
|
||||
return commits_from_base
|
||||
|
||||
# find base and add the commits to the list
|
||||
for i, c in enumerate(commits):
|
||||
if c == base_sha:
|
||||
break
|
||||
log.info('saving commit {}'.format(c))
|
||||
# find commit checksum
|
||||
cksum = get_commit_checksum(i, repo)
|
||||
commits_from_base.append({
|
||||
'commit': c,
|
||||
'checksum': cksum
|
||||
})
|
||||
|
||||
return commits_from_base
|
||||
|
||||
def create_patch(patch_id, patch_file, repo='ostree_repo', clone_dir='ostree-clone', clean_mode=False):
|
||||
'''
|
||||
Creates a debian patch using ostree delta between 2 repos (rsync)
|
||||
:param repo: main ostree_repo where build-image adds new commits
|
||||
:param clone_dir: repo cloned before the changes
|
||||
'''
|
||||
os.chdir(DEPLOY_DIR)
|
||||
# read the base sha from the clone
|
||||
base_sha = open(os.path.join(clone_dir, 'refs/heads/starlingx'), 'r').read().strip()
|
||||
|
||||
log.info('Generating delta dir')
|
||||
create_delta_dir(delta_dir=DELTA_DIR, clone_dir=clone_dir, clean_mode=clean_mode)
|
||||
|
||||
# ostree --repo=ostree_repo show starlingx | grep -i checksum | sed 's/.* //'
|
||||
cmd = 'ostree --repo={} show starlingx | grep -i checksum | sed \'s/.* //\''.format(clone_dir)
|
||||
base_checksum = subprocess.check_output(cmd, shell=True).decode(sys.stdout.encoding).strip()
|
||||
|
||||
commits = get_commits_from_base(base_sha, repo)
|
||||
|
||||
if commits:
|
||||
ostree_content = {
|
||||
'base': {
|
||||
'commit': base_sha,
|
||||
'checksum': base_checksum
|
||||
},
|
||||
}
|
||||
ostree_content['commits'] = commits
|
||||
else:
|
||||
log.info('No changes detected')
|
||||
exit(0)
|
||||
|
||||
log.info('Generating patch file...')
|
||||
# Create software.tar, metadata.tar and signatures
|
||||
# Create a temporary working directory
|
||||
tmpdir = tempfile.mkdtemp(prefix='patch_')
|
||||
# Change to the tmpdir
|
||||
os.chdir(tmpdir)
|
||||
tar = tarfile.open('software.tar', 'w')
|
||||
tar.add(os.path.join(DEPLOY_DIR, DELTA_DIR), arcname='')
|
||||
tar.close
|
||||
|
||||
log.info('Generating xml with ostree content {}'.format(commits))
|
||||
gen_xml(patch_id, ostree_content)
|
||||
tar = tarfile.open('metadata.tar', 'w')
|
||||
tar.add('metadata.xml')
|
||||
tar.close()
|
||||
|
||||
log.info('Saving restart scripts (if any)')
|
||||
# TODO: verify how to handle the restart script
|
||||
gen_restart_script('Patch1_Restart_Script.sh')
|
||||
|
||||
filelist = ['metadata.tar', 'software.tar']
|
||||
# Generate the signature file
|
||||
sig = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
for f in filelist:
|
||||
sig ^= get_md5(f)
|
||||
|
||||
sigfile = open('signature', 'w')
|
||||
sigfile.write('%x' % sig)
|
||||
sigfile.close()
|
||||
|
||||
# this comes from patch_functions write_patch
|
||||
# Generate the detached signature
|
||||
#
|
||||
# Note: if cert_type requests a formal signature, but the signing key
|
||||
# is not found, we'll instead sign with the 'dev' key and
|
||||
# need_resign_with_formal is set to True.
|
||||
need_resign_with_formal = sign_files(
|
||||
filelist,
|
||||
detached_signature_file,
|
||||
cert_type=None)
|
||||
|
||||
# Create the patch
|
||||
tar = tarfile.open(os.path.join(DEPLOY_DIR, patch_file), 'w:gz')
|
||||
for f in filelist:
|
||||
tar.add(f)
|
||||
tar.add('signature')
|
||||
tar.add(detached_signature_file)
|
||||
tar.add('Patch1_Restart_Script.sh')
|
||||
tar.close()
|
||||
|
||||
os.chdir(DEPLOY_DIR)
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
log.info('Patch file created {} at {}'.format(patch_file, DEPLOY_DIR))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Debian make_test_patch helper")
|
||||
|
||||
parser.add_argument('-r', '--repo', type=str,
|
||||
help='Ostree repo name',
|
||||
default=None, required=True)
|
||||
parser.add_argument('-p', '--prepare', action='store_true',
|
||||
help='Prepare the ostree_repo clone directory, should be executed before making changes to the environment')
|
||||
parser.add_argument('-cr', '--clone-repo', type=str,
|
||||
help='Clone repo directory name',
|
||||
default=None, required=True)
|
||||
parser.add_argument('-c', '--create', action='store_true',
|
||||
help='Create patch, should be executed after changes are done to the environment')
|
||||
parser.add_argument('-i', '--id', type=str,
|
||||
help='Patch ID', default='PATCH_0001')
|
||||
parser.add_argument('-cl', '--clean-mode', action='store_true',
|
||||
help='Whether to clean the delta directory automatically')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
log.info('STX_BUILD_HOME: {}'.format(os.environ['STX_BUILD_HOME']))
|
||||
log.info('DEPLOY DIR: {}'.format(DEPLOY_DIR))
|
||||
log.info('DELTA DIR: {}'.format(DELTA_DIR))
|
||||
|
||||
patch_id = args.id
|
||||
patch_file = patch_id + '.patch'
|
||||
|
||||
if args.prepare:
|
||||
log.info('Calling prepare environment')
|
||||
prepare_env(args.clone_repo)
|
||||
elif args.create:
|
||||
log.info('Calling create patch')
|
||||
create_patch(patch_id, patch_file, args.repo, args.clone_repo, args.clean_mode)
|
||||
|
Loading…
Reference in New Issue
Block a user