Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement using deb822 format in source application add command #457

Merged
merged 14 commits into from
Jan 10, 2024

Conversation

nmgaston
Copy link
Contributor

@nmgaston nmgaston commented Jan 5, 2024

The PR review is to check for sustainability and correctness. Sustainability is actually more business critical as correctness is largely tested into the code over time. Its useful to keep in mind that SW often outlives the HW it was written for and engineers move from job to job so it is critical that code developed for Intel be supportable across many years. It is up to the submitter and reviewer to look at the code from a perspective of what if we have to debug this 3 years from now after the author is no longer available and defect databases have been lost. Yes, that happens all the time when we are working with time scales of more than 2 years. When reviewing your code it is important to look at it from this perspective.

Author Mandatory (to be filled by PR Author/Submitter)

  • Developer who submits the Pull Request for merge is required to mark the checklist below as applicable for the PR changes submitted.
  • Those checklist items which are not marked are considered as not applicable for the PR change.
  • Items marked with an asterisk suffix are mandatory items to check and if not marked will be treated as non-compliant pull requests by the developers for Inner Source Development Model (ISDM) compliance

PULL DESCRIPTION

Adds support for DEB822 format to the 'source application add' command.

REFERENCES

Reference URL for issue tracking (JIRA/HSD/Github): <URL to be filled>

  • Added label to the Pull Request following the template: ISDM_<Complexity>*
    Note-1: Depending on complexity of code changes, use the suitable word for complexity: Low/Medium/High
    Example: PR for Slim boot loader project with medium complexity can have the label as: ISDM_Medium
  • Added label to the Pull Request for easier discoverability and search
  • RTC or HSD number will be included in final merge. HSD must always be included if available.
  • Changelogs are updated (or N/A if not customer visible)
  • inbm/log_changes.txt and inbm-vision/log_changes.txt are updated for potentially Validation-breaking log changes (or N/A if none)

CODE MAINTAINABILITY

  • Commit Message meets guidelines as indicated in the URL*
  • Every commit is a single defect fix and does not mix feature addition or changes*
  • Added required new tests relevant to the changes
    • PR contains URL links to functional tests executed with the new tests
  • Updated Documentation as relevant to the changes
  • Updated Build steps/commands changes as relevant
  • PR change contains code related to security
  • PR introduces changes that breaks compatibility with other modules (If YES, please provide description)
  • Specific instructions or information for code reviewers (If any):
  • Run 'go fmt' or format-python.sh as applicable.
  • New/modified methods and functions should have type annotations on signatures as applicable
  • New/modified methods must have appropriate doc strings (language dependent)

APPLICATION SPECIFIC

  • Does PR change default config files under /etc? If so, will application still work after an upgrade that leaves /etc alone, like a Mender upgrade?
  • Is cloud UI changed? If so, are cloud definition files updated?

Maintainer Mandatory (to be filled by PR Reviewer/Approving Maintainer)

  • Maintainer who approves the Pull Request for merge is required to mark the checklist below as appropriate for the PR change reviewed as key proof of attestation indicating reasons for merge.
  • Those checklist items which are not marked are considered as not applicable for the PR change.
  • Items marked with an asterisk suffix are mandatory items to check and if not marked will be treated as non-compliant pull requests by the maintainers for ISDM compliance.

QUALITY CHECKS

  • Architectural and Design Fit
  • Quality of code (At least one should be checked as applicable)*
    • Commit Message meets guidelines
    • PR changes adhere to industry practices and standards
    • Error and exception code paths implemented correctly
    • Code reviewed for domain or language specific anti-patterns
    • Code is adequately commented
    • Code copyright is correct
    • Confusing logic is explained in comments
    • Commit comment can be used to design a new test case for the changes
  • Test coverage shows adequate coverage with required CI functional tests pass on all supported platforms*
  • Static code scan report shows zero critical issues*
  • Integration tests are passing

CODE REVIEW IMPACT

  • Summary of Defects Detected in Code Review: <%P1xx,P2xx,P3xx,P4xx%>
    Note P1/P2/P3/P4 denotes severity of defects found (Showstopper/High/Medium/Low) and xx denotes number of defects found

SECURITY CHECKS

Please check if your PR fulfills the following requirements:

  • Follow best practices when handling primitive data types
  • Configure minimal permissions when opening pipes and ports
  • Check contents within input structures are valid before use
  • All forms of input validated
  • Avoid inter-process race conditions
  • Error and exception handling implemented
  • Defend against Canonical Representation Issues - Any paths utilized?
  • Follow 'secure by default' - Any configuration values added
  • Fail safe - Any failure scenarios?
  • Clean up temporary files - Any temporary files being used?

Code must act as a teacher for future developers

Copy link
Contributor

@gblewis1 gblewis1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see comments

@nmgaston nmgaston force-pushed the source-app-add-deb822-format branch from 95a61a3 to 2f5b3ec Compare January 10, 2024 22:01
@nmgaston nmgaston merged commit f4d2566 into develop Jan 10, 2024
7 checks passed
@nmgaston nmgaston deleted the source-app-add-deb822-format branch January 10, 2024 23:47
@nmgaston
Copy link
Contributor Author

/review

@nmgaston nmgaston restored the source-app-add-deb822-format branch January 11, 2024 17:34
@nmgaston
Copy link
Contributor Author

/review

@nex-maximus
Copy link
Collaborator

nex-maximus commented Jan 11, 2024

Code Review Analysis

(review updated until commit 2f5b3ec)

  • 🎯 Main theme: Enhancing the 'source application add' command to support DEB822 format and making GPG key parameters optional.
  • 📝 PR summary: This PR introduces the ability to use DEB822 format in the 'source application add' command within the INBC program. It also updates the command to make GPG key parameters optional, reflecting the possibility of not using a GPG key when adding a source application.
  • 📌 Type of PR: Enhancement
  • 🏅 Score: 85
  • 🧪 Relevant tests added: Yes
  • ⏱️ Estimated effort to review [1-5]: 3, because the PR involves changes across multiple files and includes both logic changes and new test cases.
  • 🔒 Security concerns: No

Code Review Feedback

💡 General suggestions: The PR introduces a significant enhancement to the 'source application add' command by supporting DEB822 format and making GPG key parameters optional. This is a welcome change that increases the flexibility of the command. However, it is crucial to ensure that all changes are backward compatible and that existing functionality is not broken. It is also important to verify that the new test cases cover all the new code paths introduced by these changes.

✨ Usage tips:

Tag me in a comment '@nex-maximus' and add one of the following commands:
/review [-i]: Request a review of your Pull Request. For an incremental review, which only considers changes since the last review, include the '-i' option.
/describe: Modify the PR title and description based on the contents of the PR.
For example: /review --pr_reviewer.extra_instructions="focus on the file: ..."

inbc-program/inbc/parser/source_app_parser.py Show resolved Hide resolved
add_gpg_key(parameters.gpg_key_uri, parameters.gpg_key_name)
"""Adds a source file and optional GPG key to be used during Ubuntu application updates."""
# Step 1: Add key (Optional)
if parameters.gpg_key_name and parameters.gpg_key_uri:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider logging an informative message when GPG key parameters are not provided, for better traceability and debugging. [medium]

except SourceError as err:
assert False, f"'UbuntuApplicationSourceManager.add' raised an exception {err}"

def test_add_app_deb_822_format_successfully(self):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a test case to verify the behavior when the GPG key is not provided during the add operation, ensuring the source is still added correctly. [important]

@@ -25,16 +26,16 @@ class SourceParameters:

@dataclass(kw_only=True, frozen=True)
class ApplicationAddSourceParameters:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that the constants and types used throughout the source management commands are consistent with the new optional GPG key parameters. [medium]

Comment on lines +33 to +34
manifest += ('<gpg>' + '{0}' + '{1}' + '</gpg>').format(create_xml_tag(arguments, "uri"),
create_xml_tag(arguments, "keyname"))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Avoid using string concatenation for XML generation to prevent potential XML injection vulnerabilities. [security]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
manifest += ('<gpg>' + '{0}' + '{1}' + '</gpg>').format(create_xml_tag(arguments, "uri"),
create_xml_tag(arguments, "keyname"))
manifest += f'<gpg>{create_xml_tag(arguments, "uri")}{create_xml_tag(arguments, "keyname")}</gpg>'

Comment on lines 56 to +59
manifest = ('<?xml version="1.0" encoding="utf-8"?>' +
'<manifest><type>source</type>' +
'<applicationSource>' +
'<remove><gpg>'
f'{create_xml_tag(arguments, "keyname")}' +
'</gpg><repo>' +
f'{create_xml_tag(arguments, "filename")}'
'</repo>'
'</remove></applicationSource>' +
'</manifest>')
'<remove>')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Refactor the XML generation into a separate function to reduce code duplication and improve maintainability. [maintainability]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
manifest = ('<?xml version="1.0" encoding="utf-8"?>' +
'<manifest><type>source</type>' +
'<applicationSource>' +
'<remove><gpg>'
f'{create_xml_tag(arguments, "keyname")}' +
'</gpg><repo>' +
f'{create_xml_tag(arguments, "filename")}'
'</repo>'
'</remove></applicationSource>' +
'</manifest>')
'<remove>')
def generate_manifest(tag_name, content):
return (f'<?xml version="1.0" encoding="utf-8"?>'
f'<manifest><type>source</type>'
f'<applicationSource>'
f'<{tag_name}>{content}</{tag_name}></applicationSource>'
f'</manifest>')
# Usage example:
manifest = generate_manifest('remove', content)

Comment on lines 27 to +30
manifest = ('<?xml version="1.0" encoding="utf-8"?>' +
'<manifest>' +
'<type>source</type>' +
'<applicationSource>' +
'<add><gpg>'
'{0}' +
'{1}'
'</gpg><repo><repos>').format(create_xml_tag(arguments, "uri"),
create_xml_tag(arguments, "keyname"))
'<applicationSource>' + '<add>')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Replace the manual XML header creation with a constant to avoid repetition. [maintainability]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
manifest = ('<?xml version="1.0" encoding="utf-8"?>' +
'<manifest>' +
'<type>source</type>' +
'<applicationSource>' +
'<add><gpg>'
'{0}' +
'{1}'
'</gpg><repo><repos>').format(create_xml_tag(arguments, "uri"),
create_xml_tag(arguments, "keyname"))
'<applicationSource>' + '<add>')
XML_HEADER = '<?xml version="1.0" encoding="utf-8"?>'
manifest = (f'{XML_HEADER}'
'<manifest><type>source</type>'
'<applicationSource><add>')

Comment on lines 38 to +39
for source in args.sources:
manifest += '<source_pkg>' + source.strip() + '</source_pkg>'
manifest += '<source_pkg>' + source + '</source_pkg>'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Use a list and join to build the XML string to improve performance. [performance]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
for source in args.sources:
manifest += '<source_pkg>' + source.strip() + '</source_pkg>'
manifest += '<source_pkg>' + source + '</source_pkg>'
sources_xml = ''.join(f'<source_pkg>{source}</source_pkg>' for source in args.sources)
manifest += f'<repo><repos>{sources_xml}</repos>'

Comment on lines +123 to +127
expected = '<?xml version="1.0" encoding="utf-8"?><manifest><type>source</type><applicationSource>' \
'<add><repo><repos>' \
'<source_pkg>deb http://example.com/ focal main restricted universe</source_pkg>' \
'<source_pkg>deb-src http://example.com/ focal-security main</source_pkg>' \
'</repos><filename>intel-gpu-jammy.list</filename></repo></add></applicationSource></manifest>'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Avoid using hardcoded XML strings in tests. Use an XML library to create and compare XML documents, which will make the tests less brittle to changes in the XML structure. [maintainability]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
expected = '<?xml version="1.0" encoding="utf-8"?><manifest><type>source</type><applicationSource>' \
'<add><repo><repos>' \
'<source_pkg>deb http://example.com/ focal main restricted universe</source_pkg>' \
'<source_pkg>deb-src http://example.com/ focal-security main</source_pkg>' \
'</repos><filename>intel-gpu-jammy.list</filename></repo></add></applicationSource></manifest>'
from xml.etree.ElementTree import Element, SubElement, tostring
def construct_manifest_xml(sources, filename):
manifest = Element('manifest')
manifest.set('version', '1.0')
manifest.set('encoding', 'utf-8')
application_source = SubElement(manifest, 'applicationSource')
add = SubElement(application_source, 'add')
repo = SubElement(add, 'repo')
repos = SubElement(repo, 'repos')
for source in sources:
source_pkg = SubElement(repos, 'source_pkg')
source_pkg.text = source
filename_element = SubElement(repo, 'filename')
filename_element.text = filename
return tostring(manifest, encoding='utf-8').decode()
sources = [
'deb http://example.com/ focal main restricted universe',
'deb-src http://example.com/ focal-security main'
]
expected = construct_manifest_xml(sources, 'intel-gpu-jammy.list')

Comment on lines +30 to +38
f = self.arg_parser.parse_args(
['source', 'application', 'add',
'--sources', 'X-Repolib-Name: Google Chrome',
'Enabled: yes',
'Types: deb',
'URIs: https://dl-ssl.google.com/linux/linux_signing_key.pub',
'Suites: stable',
'Components: main',
'--filename', 'intel-gpu-jammy.list'])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Consider using a loop to iterate over the 'sources' list to avoid code repetition and improve maintainability. [maintainability]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
f = self.arg_parser.parse_args(
['source', 'application', 'add',
'--sources', 'X-Repolib-Name: Google Chrome',
'Enabled: yes',
'Types: deb',
'URIs: https://dl-ssl.google.com/linux/linux_signing_key.pub',
'Suites: stable',
'Components: main',
'--filename', 'intel-gpu-jammy.list'])
sources_list = [
'X-Repolib-Name: Google Chrome',
'Enabled: yes',
'Types: deb',
'URIs: https://dl-ssl.google.com/linux/linux_signing_key.pub',
'Suites: stable',
'Components: main'
]
f = self.arg_parser.parse_args(
['source', 'application', 'add',
'--sources'] + sources_list +
['--filename', 'intel-gpu-jammy.list'])

Comment on lines +87 to +94
expected = '<?xml version="1.0" encoding="utf-8"?><manifest><type>source</type><applicationSource>' \
'<add><repo><repos><source_pkg>X-Repolib-Name: Google Chrome</source_pkg>' \
'<source_pkg>Enabled: yes</source_pkg>' \
'<source_pkg>Types: deb</source_pkg>'\
'<source_pkg>URIs: https://dl-ssl.google.com/linux/linux_signing_key.pub</source_pkg>' \
'<source_pkg>Suites: stable</source_pkg>' \
'<source_pkg>Components: main</source_pkg>' \
'</repos><filename>google-chrome.sources</filename></repo></add></applicationSource></manifest>'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Refactor the repeated XML manifest string construction into a helper method to avoid duplication and improve code readability. [maintainability]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
expected = '<?xml version="1.0" encoding="utf-8"?><manifest><type>source</type><applicationSource>' \
'<add><repo><repos><source_pkg>X-Repolib-Name: Google Chrome</source_pkg>' \
'<source_pkg>Enabled: yes</source_pkg>' \
'<source_pkg>Types: deb</source_pkg>'\
'<source_pkg>URIs: https://dl-ssl.google.com/linux/linux_signing_key.pub</source_pkg>' \
'<source_pkg>Suites: stable</source_pkg>' \
'<source_pkg>Components: main</source_pkg>' \
'</repos><filename>google-chrome.sources</filename></repo></add></applicationSource></manifest>'
def construct_manifest(source_packages, filename):
source_pkg_elements = ''.join(f'<source_pkg>{pkg}</source_pkg>' for pkg in source_packages)
return f'<?xml version="1.0" encoding="utf-8"?><manifest><type>source</type><applicationSource>' \
f'<add><repo><repos>{source_pkg_elements}</repos><filename>{filename}</filename></repo></add></applicationSource></manifest>'
source_packages = [
'X-Repolib-Name: Google Chrome',
'Enabled: yes',
'Types: deb',
'URIs: https://dl-ssl.google.com/linux/linux_signing_key.pub',
'Suites: stable',
'Components: main'
]
expected = construct_manifest(source_packages, 'google-chrome.sources')

Comment on lines 29 to +32
file_name: str
sources: list[str] = field(default_factory=lambda: [])
gpg_key_uri: Optional[str] = field(default=None)
gpg_key_name: Optional[str] = field(default=None)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Ensure that 'sources' list is validated to contain valid APT source entries to prevent potential issues with malformed sources. [enhancement]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
file_name: str
sources: list[str] = field(default_factory=lambda: [])
gpg_key_uri: Optional[str] = field(default=None)
gpg_key_name: Optional[str] = field(default=None)
@dataclass(kw_only=True, frozen=True)
class ApplicationAddSourceParameters:
file_name: str
sources: list[str] = field(default_factory=list)
gpg_key_uri: Optional[str] = field(default=None)
gpg_key_name: Optional[str] = field(default=None)
def __post_init__(self):
for source in self.sources:
validate_apt_source(source)

Comment on lines +116 to +136
try:
keyname = parsed_head.get_children("applicationSource/remove/gpg")["keyname"]
except XmlException:
# These children may not be present
logger.info(f"Optional GPG key parameters not present in manifest")
filename = parsed_head.get_children("applicationSource/remove/repo")["filename"]
application_source_manager.remove(
ApplicationRemoveSourceParameters(file_name=filename, gpg_key_name=keyname)
)
return Result(status=200, message="SUCCESS")

if "add" in app_action:
gpg_key_uri = parsed_head.get_children("applicationSource/add/gpg")["uri"]
gpg_key_name = parsed_head.get_children("applicationSource/add/gpg")["keyname"]
gpg_key_uri = None
gpg_key_name = None

try:
gpg_key_uri = parsed_head.get_children("applicationSource/add/gpg")["uri"]
gpg_key_name = parsed_head.get_children("applicationSource/add/gpg")["keyname"]
except XmlException:
# These children may not be present
logger.info(f"Optional GPG key parameters not present in manifest")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Refactor the repeated try-except blocks for GPG key parameters into a separate function to improve code maintainability. [maintainability]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
try:
keyname = parsed_head.get_children("applicationSource/remove/gpg")["keyname"]
except XmlException:
# These children may not be present
logger.info(f"Optional GPG key parameters not present in manifest")
filename = parsed_head.get_children("applicationSource/remove/repo")["filename"]
application_source_manager.remove(
ApplicationRemoveSourceParameters(file_name=filename, gpg_key_name=keyname)
)
return Result(status=200, message="SUCCESS")
if "add" in app_action:
gpg_key_uri = parsed_head.get_children("applicationSource/add/gpg")["uri"]
gpg_key_name = parsed_head.get_children("applicationSource/add/gpg")["keyname"]
gpg_key_uri = None
gpg_key_name = None
try:
gpg_key_uri = parsed_head.get_children("applicationSource/add/gpg")["uri"]
gpg_key_name = parsed_head.get_children("applicationSource/add/gpg")["keyname"]
except XmlException:
# These children may not be present
logger.info(f"Optional GPG key parameters not present in manifest")
def get_optional_gpg_keys(parsed_head: XmlHandler, action: str) -> tuple:
gpg_key_uri = None
gpg_key_name = None
try:
gpg_keys = parsed_head.get_children(f"applicationSource/{action}/gpg")
gpg_key_uri = gpg_keys.get("uri")
gpg_key_name = gpg_keys.get("keyname")
except XmlException:
logger.info(f"Optional GPG key parameters not present in manifest for {action}")
return gpg_key_uri, gpg_key_name
# Usage in "remove" action
keyname = get_optional_gpg_keys(parsed_head, "remove")[1]
# Usage in "add" action
gpg_key_uri, gpg_key_name = get_optional_gpg_keys(parsed_head, "add")

Comment on lines +102 to +103
if parameters.gpg_key_name and parameters.gpg_key_uri:
add_gpg_key(parameters.gpg_key_uri, parameters.gpg_key_name)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Consider validating the GPG key URI before attempting to add it. This can prevent potential security risks associated with malformed or malicious URIs. [security]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
if parameters.gpg_key_name and parameters.gpg_key_uri:
add_gpg_key(parameters.gpg_key_uri, parameters.gpg_key_name)
if parameters.gpg_key_name and parameters.gpg_key_uri:
if validate_gpg_key_uri(parameters.gpg_key_uri):
add_gpg_key(parameters.gpg_key_uri, parameters.gpg_key_name)
else:
raise SourceError(f"Invalid GPG key URI: {parameters.gpg_key_uri}")

Comment on lines +142 to +144
if parameters.gpg_key_name:
# Remove the GPG key (Optional)
remove_gpg_key_if_exists(parameters.gpg_key_name)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: To ensure that the file name does not contain any directory traversal characters or sequences, validate parameters.file_name before using it to construct the file path. [security]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
if parameters.gpg_key_name:
# Remove the GPG key (Optional)
remove_gpg_key_if_exists(parameters.gpg_key_name)
if parameters.gpg_key_name:
remove_gpg_key_if_exists(parameters.gpg_key_name)
if not is_valid_filename(parameters.file_name):
raise SourceError(f"Invalid file name: {parameters.file_name}")

remove_gpg_key_if_exists(parameters.gpg_key_name)
if parameters.gpg_key_name:
# Remove the GPG key (Optional)
remove_gpg_key_if_exists(parameters.gpg_key_name)

# Remove the file under /etc/apt/sources.list.d
try:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: To avoid potential race conditions when checking for file existence and then removing it, use a try-except block to handle the removal operation. [robustness]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
try:
try:
remove_file(
get_canonical_representation_of_path(
os.path.join(UBUNTU_APT_SOURCES_LIST_D, parameters.file_name)
)
)
except OSError as e:
raise SourceError(f"Error removing file: {parameters.file_name}: {e}")

"""Adds a source file and optional GPG key to be used during Ubuntu application updates."""
# Step 1: Add key (Optional)
if parameters.gpg_key_name and parameters.gpg_key_uri:
add_gpg_key(parameters.gpg_key_uri, parameters.gpg_key_name)

# Step 2: Add the source
try:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Use a context manager when working with files to ensure that the file is properly closed even if an error occurs. [best practice]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
try:
with open(os.path.join(UBUNTU_APT_SOURCES_LIST_D, parameters.file_name), 'w') as file:
file.writelines(f"{source}\n" for source in parameters.sources)

Comment on lines +102 to 106
if parameters.gpg_key_name and parameters.gpg_key_uri:
add_gpg_key(parameters.gpg_key_uri, parameters.gpg_key_name)

# Step 2: Add the source
try:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: To improve the readability and maintainability of the code, consider refactoring the add method to separate the concerns of adding a GPG key and creating a source file. [maintainability]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
if parameters.gpg_key_name and parameters.gpg_key_uri:
add_gpg_key(parameters.gpg_key_uri, parameters.gpg_key_name)
# Step 2: Add the source
try:
def add_gpg_key_if_provided(self, parameters: ApplicationAddSourceParameters) -> None:
if parameters.gpg_key_name and parameters.gpg_key_uri:
add_gpg_key(parameters.gpg_key_uri, parameters.gpg_key_name)
def create_source_file(self, parameters: ApplicationAddSourceParameters) -> None:
create_file_with_contents(
os.path.join(UBUNTU_APT_SOURCES_LIST_D, parameters.file_name), parameters.sources
)
def add(self, parameters: ApplicationAddSourceParameters) -> None:
self.add_gpg_key_if_provided(parameters)
self.create_source_file(parameters)

Comment on lines +233 to 234
with patch("builtins.open", new_callable=mock_open()):
command.update(params)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The 'test_update_app_source_successfully' method is missing an assertion to verify the expected behavior. Tests should always include assertions to validate the test's intent. [enhancement]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
with patch("builtins.open", new_callable=mock_open()):
command.update(params)
with patch("builtins.open", new_callable=mock_open()) as m:
command.update(params)
m.assert_called_with('expected_file_path', 'w')
m().write.assert_called_with('expected_content')

Comment on lines +207 to +208
except SourceError as err:
assert False, f"'UbuntuApplicationSourceManager.add' raised an exception {err}"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The 'test_add_app_with_gpg_key_successfully' and 'test_add_app_deb_822_format_successfully' methods are catching a broad 'SourceError' exception and asserting False, which is not a common practice in test cases. Instead, use 'pytest.fail' to provide a clearer message on test failure. [best practice]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
except SourceError as err:
assert False, f"'UbuntuApplicationSourceManager.add' raised an exception {err}"
except SourceError as err:
pytest.fail(f"'UbuntuApplicationSourceManager.add' raised an exception {err}")

@nex-maximus
Copy link
Collaborator

Persistent review updated to latest commit 2f5b3ec



class TestSourceApplicationParser(TestCase):
def setUp(self):
self.arg_parser = ArgsParser()
self.maxDiff = None

def test_parse_add_arguments_successfully(self):
def test_parse_add_all_arguments_successfully(self):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add more test cases to cover scenarios where the --sources argument is provided in an incorrect DEB822 format. [important]

add_gpg_key(parameters.gpg_key_uri, parameters.gpg_key_name)
"""Adds a source file and optional GPG key to be used during Ubuntu application updates."""
# Step 1: Add key (Optional)
if parameters.gpg_key_name and parameters.gpg_key_uri:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that the add method handles the case where the DEB822 format is used without a GPG key. [important]

@@ -192,14 +193,44 @@ def test_update_sources_os_error(self):


class TestUbuntuApplicationSourceManager:
@patch("dispatcher.source.ubuntu_source_manager.move_file")
def test_update_app_source_successfully(self, mock_move):
def test_add_app_with_gpg_key_successfully(self):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add unit tests to verify the behavior of the UbuntuApplicationSourceManager.add method when DEB822 format is used. [important]

@@ -333,7 +333,7 @@
<xs:element name="repos" minOccurs="1" maxOccurs="1">
<xs:complexType>
<xs:sequence>
<xs:element name="source_pkg" type="Max200Chars" minOccurs="1" maxOccurs="2"/>
<xs:element name="source_pkg" type="Max200Chars" minOccurs="1" maxOccurs="45"/>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update the XML schema to allow for a variable number of source_pkg elements when using DEB822 format. [important]

Comment on lines +68 to 75
app_add_parser.add_argument('--gpgKeyUri', '-gku', required=False,
type=lambda x: validate_string_less_than_n_characters(
x, 'str', 1500),
help='Uri from which to download GPG key')
app_add_parser.add_argument('--gpgKeyName', '-gkn', required=True,
app_add_parser.add_argument('--gpgKeyName', '-gkn', required=False,
type=lambda x: validate_string_less_than_n_characters(
x, 'str', 200),
help='Name to store the GPG key information')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The 'required' flag for '--gpgKeyUri' and '--gpgKeyName' arguments in the 'app_add_parser' has been changed from True to False. Ensure that the rest of the code properly handles cases where these arguments might not be provided, to prevent runtime errors. [possible issue]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
app_add_parser.add_argument('--gpgKeyUri', '-gku', required=False,
type=lambda x: validate_string_less_than_n_characters(
x, 'str', 1500),
help='Uri from which to download GPG key')
app_add_parser.add_argument('--gpgKeyName', '-gkn', required=True,
app_add_parser.add_argument('--gpgKeyName', '-gkn', required=False,
type=lambda x: validate_string_less_than_n_characters(
x, 'str', 200),
help='Name to store the GPG key information')
# It's assumed that the application_add function is designed to handle optional GPG key parameters.
# If not, the function should be updated accordingly to avoid runtime errors when these arguments are not provided.

inbc-program/inbc/parser/parser.py Show resolved Hide resolved
inbc-program/inbc/parser/source_app_parser.py Show resolved Hide resolved
Comment on lines 38 to +39
for source in args.sources:
manifest += '<source_pkg>' + source.strip() + '</source_pkg>'
manifest += '<source_pkg>' + source + '</source_pkg>'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Ensure that the source variable is stripped of leading and trailing whitespace before being added to the XML manifest. [bug]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
for source in args.sources:
manifest += '<source_pkg>' + source.strip() + '</source_pkg>'
manifest += '<source_pkg>' + source + '</source_pkg>'
for source in args.sources:
manifest += f'<source_pkg>{source.strip()}</source_pkg>'

inbc-program/inbc/parser/source_app_parser.py Show resolved Hide resolved
f'{create_xml_tag(arguments, "filename")}'
'</repo>'
'</remove></applicationSource>' +
'</manifest>')

print(f"manifest {manifest}")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Use a context manager to handle the printing of the manifest to ensure that resources are properly managed. [best practice]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
print(f"manifest {manifest}")
with open('manifest_log.txt', 'a') as log_file:
log_file.write(f"manifest {manifest}\n")

Comment on lines +33 to +34
manifest += ('<gpg>' + '{0}' + '{1}' + '</gpg>').format(create_xml_tag(arguments, "uri"),
create_xml_tag(arguments, "keyname"))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Replace string concatenation with f-strings for better readability and performance. [enhancement]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
manifest += ('<gpg>' + '{0}' + '{1}' + '</gpg>').format(create_xml_tag(arguments, "uri"),
create_xml_tag(arguments, "keyname"))
manifest += f'<gpg>{create_xml_tag(arguments, "uri")}{create_xml_tag(arguments, "keyname")}</gpg>'

Comment on lines 41 to 42
manifest += ('</repos>'
f'{create_xml_tag(arguments, "filename")}</repo>'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Remove unnecessary parentheses from the single-line f-string. [enhancement]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
manifest += ('</repos>'
f'{create_xml_tag(arguments, "filename")}</repo>'
manifest += f'</repos>{create_xml_tag(arguments, "filename")}</repo></add></applicationSource></manifest>'

'<remove>')

if args.gpgKeyName:
manifest += f'<gpg>{create_xml_tag(arguments, "keyname")}</gpg>'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Avoid using f-strings inside expressions where curly braces are used for XML tags, as it might cause confusion. Use format method or concatenate the strings for clarity. [readability]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
manifest += f'<gpg>{create_xml_tag(arguments, "keyname")}</gpg>'
manifest += '<gpg>' + create_xml_tag(arguments, "keyname") + '</gpg>'

Comment on lines +49 to +73
@patch('inbm_lib.mqttclient.mqtt.mqtt.Client.connect')
def test_raise_application_add_with_only_one_gpgKeyUri_param(self, m_connect):
p = self.arg_parser.parse_args(
['source', 'application', 'add',
'--gpgKeyUri', 'https://repositories.intel.com/gpu/intel-graphics.key',
'--sources', 'deb http://example.com/ focal main restricted universe',
'deb-src http://example.com/ focal-security main',
'--filename', 'intel-gpu-jammy.list'])
with pytest.raises(InbcException,
match="Source requires either both gpgKeyUri and gpgKeyName "
"to be provided, or neither of them."):
Inbc(p, 'source', False)

@patch('inbm_lib.mqttclient.mqtt.mqtt.Client.connect')
def test_raise_application_add_with_only_one_gpgKeyName_param(self, m_connect):
p = self.arg_parser.parse_args(
['source', 'application', 'add',
'--gpgKeyName', 'intel-graphics.gpg',
'--sources', 'deb http://example.com/ focal main restricted universe',
'deb-src http://example.com/ focal-security main',
'--filename', 'intel-gpu-jammy.list'])
with pytest.raises(InbcException,
match="Source requires either both gpgKeyUri and gpgKeyName "
"to be provided, or neither of them."):
Inbc(p, 'source', False)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Use a data-driven approach for tests to reduce code duplication and improve maintainability. [enhancement]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
@patch('inbm_lib.mqttclient.mqtt.mqtt.Client.connect')
def test_raise_application_add_with_only_one_gpgKeyUri_param(self, m_connect):
p = self.arg_parser.parse_args(
['source', 'application', 'add',
'--gpgKeyUri', 'https://repositories.intel.com/gpu/intel-graphics.key',
'--sources', 'deb http://example.com/ focal main restricted universe',
'deb-src http://example.com/ focal-security main',
'--filename', 'intel-gpu-jammy.list'])
with pytest.raises(InbcException,
match="Source requires either both gpgKeyUri and gpgKeyName "
"to be provided, or neither of them."):
Inbc(p, 'source', False)
@patch('inbm_lib.mqttclient.mqtt.mqtt.Client.connect')
def test_raise_application_add_with_only_one_gpgKeyName_param(self, m_connect):
p = self.arg_parser.parse_args(
['source', 'application', 'add',
'--gpgKeyName', 'intel-graphics.gpg',
'--sources', 'deb http://example.com/ focal main restricted universe',
'deb-src http://example.com/ focal-security main',
'--filename', 'intel-gpu-jammy.list'])
with pytest.raises(InbcException,
match="Source requires either both gpgKeyUri and gpgKeyName "
"to be provided, or neither of them."):
Inbc(p, 'source', False)
@patch('inbm_lib.mqttclient.mqtt.mqtt.Client.connect')
def test_raise_application_add_with_missing_gpg_params(self, m_connect):
test_cases = [
(['--gpgKeyUri', 'https://repositories.intel.com/gpu/intel-graphics.key'], 'gpgKeyName'),
(['--gpgKeyName', 'intel-graphics.gpg'], 'gpgKeyUri')
]
for args, missing_param in test_cases:
with self.subTest(missing_param=missing_param):
p = self.arg_parser.parse_args(['source', 'application', 'add'] + args + [
'--sources', 'deb http://example.com/ focal main restricted universe',
'deb-src http://example.com/ focal-security main',
'--filename', 'intel-gpu-jammy.list'])
with pytest.raises(InbcException,
match=f"Source requires either both gpgKeyUri and {missing_param} "
"to be provided, or neither of them."):
Inbc(p, 'source', False)

Comment on lines 26 to 27
'deb-src http://example.com/ focal-security main'])
self.assertEqual(f.filename, 'intel-gpu-jammy.list')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Consider using a loop to avoid code duplication when asserting multiple attributes of the 'f' object. [maintainability]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
'deb-src http://example.com/ focal-security main'])
self.assertEqual(f.filename, 'intel-gpu-jammy.list')
expected_values = {
'gpgKeyUri': 'https://repositories.intel.com/gpu/intel-graphics.key',
'gpgKeyName': 'intel-graphics.gpg',
'sources': ['deb http://example.com/ focal main restricted universe',
'deb-src http://example.com/ focal-security main'],
'filename': 'intel-gpu-jammy.list'
}
for attr, expected in expected_values.items():
self.assertEqual(getattr(f, attr), expected)

Comment on lines +87 to +94
expected = '<?xml version="1.0" encoding="utf-8"?><manifest><type>source</type><applicationSource>' \
'<add><repo><repos><source_pkg>X-Repolib-Name: Google Chrome</source_pkg>' \
'<source_pkg>Enabled: yes</source_pkg>' \
'<source_pkg>Types: deb</source_pkg>'\
'<source_pkg>URIs: https://dl-ssl.google.com/linux/linux_signing_key.pub</source_pkg>' \
'<source_pkg>Suites: stable</source_pkg>' \
'<source_pkg>Components: main</source_pkg>' \
'</repos><filename>google-chrome.sources</filename></repo></add></applicationSource></manifest>'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Extract the XML manifest string construction into a separate method to improve readability and maintainability. [maintainability]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
expected = '<?xml version="1.0" encoding="utf-8"?><manifest><type>source</type><applicationSource>' \
'<add><repo><repos><source_pkg>X-Repolib-Name: Google Chrome</source_pkg>' \
'<source_pkg>Enabled: yes</source_pkg>' \
'<source_pkg>Types: deb</source_pkg>'\
'<source_pkg>URIs: https://dl-ssl.google.com/linux/linux_signing_key.pub</source_pkg>' \
'<source_pkg>Suites: stable</source_pkg>' \
'<source_pkg>Components: main</source_pkg>' \
'</repos><filename>google-chrome.sources</filename></repo></add></applicationSource></manifest>'
def construct_manifest_xml(repo_details, filename):
xml_parts = ['<?xml version="1.0" encoding="utf-8"?><manifest><type>source</type><applicationSource>',
'<add><repo><repos>']
for detail in repo_details:
xml_parts.append(f'<source_pkg>{detail}</source_pkg>')
xml_parts.append(f'</repos><filename>{filename}</filename></repo></add></applicationSource></manifest>')
return ''.join(xml_parts)
expected = construct_manifest_xml([
'X-Repolib-Name: Google Chrome',
'Enabled: yes',
'Types: deb',
'URIs: https://dl-ssl.google.com/linux/linux_signing_key.pub',
'Suites: stable',
'Components: main'
], 'google-chrome.sources')

Comment on lines 16 to 18
f = self.arg_parser.parse_args(
['source', 'application', 'add',
'--gpgKeyUri', 'https://repositories.intel.com/gpu/intel-graphics.key',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Refactor the test cases to use a helper method for parsing arguments to reduce redundancy. [maintainability]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
f = self.arg_parser.parse_args(
['source', 'application', 'add',
'--gpgKeyUri', 'https://repositories.intel.com/gpu/intel-graphics.key',
def parse_add_arguments(self, args):
return self.arg_parser.parse_args(['source', 'application', 'add'] + args)
f = self.parse_add_arguments([
'--gpgKeyUri', 'https://repositories.intel.com/gpu/intel-graphics.key',
'--gpgKeyName', 'intel-graphics.gpg',
'--sources', 'deb http://example.com/ focal main restricted universe',
'deb-src http://example.com/ focal-security main',
'--filename', 'intel-gpu-jammy.list'
])

@@ -25,16 +26,16 @@ class SourceParameters:

@dataclass(kw_only=True, frozen=True)
class ApplicationAddSourceParameters:
gpg_key_uri: str
gpg_key_name: str
file_name: str
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Validate the file_name to ensure it ends with '.list' since it is a convention for APT source list files. [validation]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
file_name: str
@dataclass(kw_only=True, frozen=True)
class ApplicationAddSourceParameters:
file_name: str = field(init=False)
source_list_file_name: str = field(default=None)
def __post_init__(self):
if not self.source_list_file_name.endswith('.list'):
raise ValueError("file_name must end with '.list'")
self.file_name = self.source_list_file_name

Comment on lines 28 to +32
class ApplicationAddSourceParameters:
gpg_key_uri: str
gpg_key_name: str
file_name: str
sources: list[str] = field(default_factory=lambda: [])
gpg_key_uri: Optional[str] = field(default=None)
gpg_key_name: Optional[str] = field(default=None)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Consider using a more specific type than list for sources to ensure immutability, such as tuple. [maintainability]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
class ApplicationAddSourceParameters:
gpg_key_uri: str
gpg_key_name: str
file_name: str
sources: list[str] = field(default_factory=lambda: [])
gpg_key_uri: Optional[str] = field(default=None)
gpg_key_name: Optional[str] = field(default=None)
@dataclass(kw_only=True, frozen=True)
class ApplicationAddSourceParameters:
file_name: str
sources: tuple[str, ...] = field(default_factory=tuple)
gpg_key_uri: Optional[str] = field(default=None)
gpg_key_name: Optional[str] = field(default=None)

Comment on lines 35 to +38
@dataclass(kw_only=True, frozen=True)
class ApplicationRemoveSourceParameters:
gpg_key_name: str
file_name: str
gpg_key_name: Optional[str] = field(default=None)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Since the class is frozen, consider using tuple instead of list for the sources field to ensure all elements are immutable. [maintainability]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
@dataclass(kw_only=True, frozen=True)
class ApplicationRemoveSourceParameters:
gpg_key_name: str
file_name: str
gpg_key_name: Optional[str] = field(default=None)
@dataclass(kw_only=True, frozen=True)
class ApplicationRemoveSourceParameters:
source_list_file_name: str
gpg_key_name: Optional[str] = field(default=None)

Comment on lines 114 to 121
if "remove" in app_action:
keyname = parsed_head.get_children("applicationSource/remove/gpg")["keyname"]
keyname = None
try:
keyname = parsed_head.get_children("applicationSource/remove/gpg")["keyname"]
except XmlException:
# These children may not be present
logger.info(f"Optional GPG key parameters not present in manifest")
filename = parsed_head.get_children("applicationSource/remove/repo")["filename"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Refactor the repeated try-except blocks for GPG key retrieval into a separate function to reduce code duplication. [maintainability]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
if "remove" in app_action:
keyname = parsed_head.get_children("applicationSource/remove/gpg")["keyname"]
keyname = None
try:
keyname = parsed_head.get_children("applicationSource/remove/gpg")["keyname"]
except XmlException:
# These children may not be present
logger.info(f"Optional GPG key parameters not present in manifest")
filename = parsed_head.get_children("applicationSource/remove/repo")["filename"]
if "remove" in app_action:
keyname = get_optional_gpg_key(parsed_head, "applicationSource/remove/gpg")
filename = parsed_head.get_children("applicationSource/remove/repo")["filename"]
application_source_manager.remove(
ApplicationRemoveSourceParameters(file_name=filename, gpg_key_name=keyname)
)
return Result(status=200, message="SUCCESS")
def get_optional_gpg_key(parsed_head: XmlHandler, path: str) -> Optional[str]:
try:
return parsed_head.get_children(path)["keyname"]
except XmlException:
logger.info(f"Optional GPG key parameters not present in manifest")
return None

Comment on lines +115 to +120
keyname = None
try:
keyname = parsed_head.get_children("applicationSource/remove/gpg")["keyname"]
except XmlException:
# These children may not be present
logger.info(f"Optional GPG key parameters not present in manifest")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Use a context manager to ensure that the logger's context is properly set during the execution of the function. [best practice]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
keyname = None
try:
keyname = parsed_head.get_children("applicationSource/remove/gpg")["keyname"]
except XmlException:
# These children may not be present
logger.info(f"Optional GPG key parameters not present in manifest")
if "remove" in app_action:
with logger.contextualize(action="remove", component="applicationSource"):
keyname = None
try:
keyname = parsed_head.get_children("applicationSource/remove/gpg")["keyname"]
except XmlException:
# These children may not be present
logger.info("Optional GPG key parameters not present in manifest")

Comment on lines +102 to +103
if parameters.gpg_key_name and parameters.gpg_key_uri:
add_gpg_key(parameters.gpg_key_uri, parameters.gpg_key_name)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Validate the gpg_key_uri to ensure it's a well-formed URL before attempting to add the GPG key. [enhancement]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
if parameters.gpg_key_name and parameters.gpg_key_uri:
add_gpg_key(parameters.gpg_key_uri, parameters.gpg_key_name)
if parameters.gpg_key_name and parameters.gpg_key_uri:
if not is_valid_url(parameters.gpg_key_uri):
raise SourceError(f"Invalid GPG key URI: {parameters.gpg_key_uri}")
add_gpg_key(parameters.gpg_key_uri, parameters.gpg_key_name)

Comment on lines +142 to +144
if parameters.gpg_key_name:
# Remove the GPG key (Optional)
remove_gpg_key_if_exists(parameters.gpg_key_name)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Refactor the code to avoid duplication of the GPG key removal logic. [maintainability]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
if parameters.gpg_key_name:
# Remove the GPG key (Optional)
remove_gpg_key_if_exists(parameters.gpg_key_name)
self._remove_gpg_key_if_exists(parameters.gpg_key_name)
def _remove_gpg_key_if_exists(self, gpg_key_name: str) -> None:
if gpg_key_name:
remove_gpg_key_if_exists(gpg_key_name)

Comment on lines +142 to +144
if parameters.gpg_key_name:
# Remove the GPG key (Optional)
remove_gpg_key_if_exists(parameters.gpg_key_name)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Consider checking if the file exists before attempting to remove it to provide a clearer error message. [enhancement]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
if parameters.gpg_key_name:
# Remove the GPG key (Optional)
remove_gpg_key_if_exists(parameters.gpg_key_name)
if parameters.gpg_key_name:
gpg_key_path = get_gpg_key_path(parameters.gpg_key_name)
if not os.path.exists(gpg_key_path):
logger.warning(f"GPG key file does not exist: {gpg_key_path}")
remove_gpg_key_if_exists(parameters.gpg_key_name)

Comment on lines +212 to +219
params = ApplicationAddSourceParameters(
file_name="google-chrome.sources",
sources="X-Repolib-Name: Google Chrome"
"Enabled: yes"
"Types: deb"
"URIs: https://dl-ssl.google.com/linux/linux_signing_key.pub"
"Suites: stable"
"Components: main",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The string concatenation in the 'sources' field of 'ApplicationAddSourceParameters' seems to be missing spaces between the concatenated strings. This will result in an incorrect single string where the parts are not separated by spaces, which could lead to errors when parsing the sources. [bug]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
params = ApplicationAddSourceParameters(
file_name="google-chrome.sources",
sources="X-Repolib-Name: Google Chrome"
"Enabled: yes"
"Types: deb"
"URIs: https://dl-ssl.google.com/linux/linux_signing_key.pub"
"Suites: stable"
"Components: main",
params = ApplicationAddSourceParameters(
file_name="google-chrome.sources",
sources="X-Repolib-Name: Google Chrome "
"Enabled: yes "
"Types: deb "
"URIs: https://dl-ssl.google.com/linux/linux_signing_key.pub "
"Suites: stable "
"Components: main",
)

Comment on lines +196 to +208
def test_add_app_with_gpg_key_successfully(self):
try:
params = ApplicationAddSourceParameters(
file_name="intel-gpu-jammy.list",
sources="deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main",
gpg_key_uri="https://dl-ssl.google.com/linux/linux_signing_key.pub",
gpg_key_name="google-chrome.gpg"
)
command = UbuntuApplicationSourceManager()
with patch("builtins.open", new_callable=mock_open()):
command.add(params)
except SourceError as err:
assert False, f"'UbuntuApplicationSourceManager.add' raised an exception {err}"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The 'test_add_app_with_gpg_key_successfully' and 'test_add_app_deb_822_format_successfully' methods should not suppress all exceptions with a broad try-except block. Instead, they should let the test framework handle exceptions, which will provide better error reporting and debugging information. [best practice]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
def test_add_app_with_gpg_key_successfully(self):
try:
params = ApplicationAddSourceParameters(
file_name="intel-gpu-jammy.list",
sources="deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main",
gpg_key_uri="https://dl-ssl.google.com/linux/linux_signing_key.pub",
gpg_key_name="google-chrome.gpg"
)
command = UbuntuApplicationSourceManager()
with patch("builtins.open", new_callable=mock_open()):
command.add(params)
except SourceError as err:
assert False, f"'UbuntuApplicationSourceManager.add' raised an exception {err}"
def test_add_app_with_gpg_key_successfully(self):
params = ApplicationAddSourceParameters(
file_name="intel-gpu-jammy.list",
sources="deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main",
gpg_key_uri="https://dl-ssl.google.com/linux/linux_signing_key.pub",
gpg_key_name="google-chrome.gpg"
)
command = UbuntuApplicationSourceManager()
with patch("builtins.open", new_callable=mock_open()):
command.add(params)

Comment on lines +227 to 236
def test_update_app_source_successfully(self):
try:
params = ApplicationUpdateSourceParameters(
file_name="intel-gpu-jammy.list", sources=APP_SOURCE
)
command = UbuntuApplicationSourceManager()
with patch("builtins.open", new_callable=mock_open()) as m:
with patch("builtins.open", new_callable=mock_open()):
command.update(params)
except SourceError as err:
assert False, f"'UbuntuApplicationSourceManager.update' raised an exception {err}"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The 'test_update_app_source_successfully' method should not suppress all exceptions with a broad try-except block. Instead, it should let the test framework handle exceptions, which will provide better error reporting and debugging information. [best practice]
NOTE: Ensure that the suggested code is comprehensive. This serves as an example of the code you could use. The user should verify it after accepting the suggestion.

Suggested change
def test_update_app_source_successfully(self):
try:
params = ApplicationUpdateSourceParameters(
file_name="intel-gpu-jammy.list", sources=APP_SOURCE
)
command = UbuntuApplicationSourceManager()
with patch("builtins.open", new_callable=mock_open()) as m:
with patch("builtins.open", new_callable=mock_open()):
command.update(params)
except SourceError as err:
assert False, f"'UbuntuApplicationSourceManager.update' raised an exception {err}"
def test_update_app_source_successfully(self):
params = ApplicationUpdateSourceParameters(
file_name="intel-gpu-jammy.list", sources=APP_SOURCE
)
command = UbuntuApplicationSourceManager()
with patch("builtins.open", new_callable=mock_open()):
command.update(params)

@gblewis1 gblewis1 deleted the source-app-add-deb822-format branch August 27, 2024 22:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants