Skip to content

Conversation

Williangalvani
Copy link
Member

@Williangalvani Williangalvani commented Aug 13, 2025

"Fix" #3489
Screenshot 2025-08-13 at 06 46 54

Summary by Sourcery

Handle DockerHub API change by falling back to placeholder TagMetadata for tags with no image data and add an integration test to validate TagFetcher against a real repository

Bug Fixes:

  • Add fallback logic in fetch_remote_tags to include tags with empty "images" by creating placeholder TagMetadata entries

Tests:

  • Introduce TestTagFetcher integration test to fetch and verify real tags from DockerHub for bluerobotics/blueos-core, skipping on network issues

Copy link

sourcery-ai bot commented Aug 13, 2025

Reviewer's Guide

This PR adds a workaround for DockerHub API changes by injecting placeholder tag metadata when no image entries are returned, and introduces an integration test for the TagFetcher to validate real-world behavior.

Sequence diagram for fetch_remote_tags handling DockerHub API changes

sequenceDiagram
    participant TagFetcher
    participant DockerHubAPI
    participant TagMetadata
    TagFetcher->>DockerHubAPI: Request tags for repository
    DockerHubAPI-->>TagFetcher: Return tags (some with empty images[])
    TagFetcher->>TagMetadata: Create placeholder TagMetadata if images[] is empty
    TagFetcher->>TagMetadata: Create TagMetadata for valid architecture if images[] present
    TagFetcher-->>TagFetcher: Append TagMetadata to valid_images
Loading

Class diagram for TagMetadata and fetch_remote_tags workaround

classDiagram
    class TagMetadata {
        +repository: str
        +image: str
        +tag: str
        +last_modified: str
        +sha: str | None
        +digest: str
    }
    class TagFetcher {
        +fetch_remote_tags(repository: str, local_images: List[str])
    }
    TagFetcher --> TagMetadata : creates
Loading

File-Level Changes

Change Details Files
Add integration tests for TagFetcher
  • Imported TagFetcher and TagMetadata into test file
  • Defined TestTagFetcher with an async test to fetch and assert tag metadata from a real repository
  • Handled network failures by skipping the test on exceptions
core/services/versionchooser/test_versionchooser.py
Work around missing image entries in DockerHub responses
  • Detect when tag["images"] is empty and treat it as a fallback case
  • Construct TagMetadata with placeholder digest and no SHA for empty-image tags
  • Append the fallback TagMetadata to valid_images and continue processing
core/services/versionchooser/utils/dockerhub.py

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@Williangalvani Williangalvani marked this pull request as ready for review August 13, 2025 23:53
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • The hardcoded digest "------" may lead to unexpected downstream behavior; consider using None or a dedicated sentinel and handling it explicitly in consumer logic instead of a magic string.
  • TestTagFetcher currently makes live calls to DockerHub and risks CI flakiness; either mock the network responses or mark this as an integration test that’s skipped by default.
  • It might be clearer to extract the empty-images handling into a separate helper function with its own tests and documentation to encapsulate this workaround.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The hardcoded digest "------" may lead to unexpected downstream behavior; consider using None or a dedicated sentinel and handling it explicitly in consumer logic instead of a magic string.
- TestTagFetcher currently makes live calls to DockerHub and risks CI flakiness; either mock the network responses or mark this as an integration test that’s skipped by default.
- It might be clearer to extract the empty-`images` handling into a separate helper function with its own tests and documentation to encapsulate this workaround.

## Individual Comments

### Comment 1
<location> `core/services/versionchooser/utils/dockerhub.py:114` </location>
<code_context>
                 valid_images = []
                 for tag in tags:
+                    images = tag["images"]
+                    if len(images) == 0:
+                        # this is a hack to deal with https://github.com/docker/hub-feedback/issues/2484
+                        # we lost the ability to properly identify the images as we dont have the digest,
+                        # and also the ability to filter for compatible architectures.
</code_context>

<issue_to_address>
Consider logging or surfacing when tags have no images for better traceability.

Logging a warning when this fallback is triggered will help with monitoring and debugging, especially if Docker Hub's behavior changes or unexpected tags appear.

Suggested implementation:

```python
                import logging
                logger = logging.getLogger(__name__)

                my_architecture = get_current_arch()
                valid_images = []
                for tag in tags:
                    images = tag["images"]
                    if len(images) == 0:
                        logger.warning(
                            "Tag '%s' in repository '%s' has no images. Fallback logic triggered. See https://github.com/docker/hub-feedback/issues/2484",
                            tag.get("name", "<unknown>"),
                            repository,
                        )
                        # this is a hack to deal with https://github.com/docker/hub-feedback/issues/2484
                        # we lost the ability to properly identify the images as we dont have the digest,
                        # and also the ability to filter for compatible architectures.
                        # so we just add the tag and hope for the best.
                        tag = TagMetadata(
                            repository=repository,
                            image=repository.split("/")[-1],
                            tag=tag["name"],
                            last_modified=tag["last_updated"],
                            sha=None,
                            digest="------",
                        )
                        valid_images.append(tag)

```

If your project already has a logger instance (e.g., `logger` defined at the module level), use that instead of creating a new one with `logging.getLogger(__name__)`. Remove the import and logger assignment if not needed.
</issue_to_address>

### Comment 2
<location> `core/services/versionchooser/test_versionchooser.py:258` </location>
<code_context>
+            assert len(tags) > 0, "Should have found some tags for bluerobotics/blueos-core"
+
+            # Verify tag structure
+            for tag in tags[:3]:  # Check first 3 tags
+                assert isinstance(tag, TagMetadata)
+                assert tag.repository == "bluerobotics/blueos-core"
+                assert tag.image == "blueos-core"
+                assert tag.tag is not None
+                assert len(tag.tag) > 0
+                assert tag.last_modified is not None
+                assert tag.digest is not None
+
+            # Should find the 'master' tag
</code_context>

<issue_to_address>
Test does not cover edge case for tags with missing images (digest set to '------').

Please add a test for tags with missing images (digest set to '------') to confirm TagMetadata is constructed correctly and downstream code handles this case.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +114 to +115
if len(images) == 0:
# this is a hack to deal with https://github.com/docker/hub-feedback/issues/2484
Copy link

Choose a reason for hiding this comment

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

suggestion: Consider logging or surfacing when tags have no images for better traceability.

Logging a warning when this fallback is triggered will help with monitoring and debugging, especially if Docker Hub's behavior changes or unexpected tags appear.

Suggested implementation:

                import logging
                logger = logging.getLogger(__name__)

                my_architecture = get_current_arch()
                valid_images = []
                for tag in tags:
                    images = tag["images"]
                    if len(images) == 0:
                        logger.warning(
                            "Tag '%s' in repository '%s' has no images. Fallback logic triggered. See https://github.com/docker/hub-feedback/issues/2484",
                            tag.get("name", "<unknown>"),
                            repository,
                        )
                        # this is a hack to deal with https://github.com/docker/hub-feedback/issues/2484
                        # we lost the ability to properly identify the images as we dont have the digest,
                        # and also the ability to filter for compatible architectures.
                        # so we just add the tag and hope for the best.
                        tag = TagMetadata(
                            repository=repository,
                            image=repository.split("/")[-1],
                            tag=tag["name"],
                            last_modified=tag["last_updated"],
                            sha=None,
                            digest="------",
                        )
                        valid_images.append(tag)

If your project already has a logger instance (e.g., logger defined at the module level), use that instead of creating a new one with logging.getLogger(__name__). Remove the import and logger assignment if not needed.

Comment on lines +258 to +265
for tag in tags[:3]: # Check first 3 tags
assert isinstance(tag, TagMetadata)
assert tag.repository == "bluerobotics/blueos-core"
assert tag.image == "blueos-core"
assert tag.tag is not None
assert len(tag.tag) > 0
assert tag.last_modified is not None
assert tag.digest is not None
Copy link

Choose a reason for hiding this comment

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

suggestion (testing): Test does not cover edge case for tags with missing images (digest set to '------').

Please add a test for tags with missing images (digest set to '------') to confirm TagMetadata is constructed correctly and downstream code handles this case.

@ES-Alexander
Copy link
Collaborator

I've mentioned some relevant ideas here and here.

I think having a test for this is useful, but agree with you that the implemented fix approach is not great, and given the API rollback doesn't seem necessary. I think you should either remove/replace the fix, or change this PR back to a draft (in case it becomes relevant again) :-)

Copy link
Member

@joaoantoniocardoso joaoantoniocardoso left a comment

Choose a reason for hiding this comment

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

so we just add the tag and hope for the best.

I'm not against this quick fallback, but I want to understand what can happen in this case.

# we lost the ability to properly identify the images as we dont have the digest,
# and also the ability to filter for compatible architectures.
# so we just add the tag and hope for the best.
tag = TagMetadata(
Copy link
Member

Choose a reason for hiding this comment

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

We should log a warning here

@joaoantoniocardoso
Copy link
Member

so we just add the tag and hope for the best.

I'm not against this quick fallback, but I want to understand what can happen in this case.

Okay, I'm good with the behavior -- versionchooser lists the wrong architecture images, but it fails when trying to apply. It would only be better if it had warnings to tell us (and also the frontend) what's happening.

@patrickelectric patrickelectric added the move-to-stable Needs to be cherry-picked and move to stable label Aug 15, 2025
@patrickelectric
Copy link
Member

We should move this to 1.4 and 1.3

@joaoantoniocardoso joaoantoniocardoso merged commit f6130cd into bluerobotics:master Aug 22, 2025
6 checks passed
@patrickelectric patrickelectric removed the move-to-stable Needs to be cherry-picked and move to stable label Sep 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants