Skip to content

Add fallback image pull from Azure#1618

Open
truggeri wants to merge 2 commits intomainfrom
truggeri/fallback-image-pull
Open

Add fallback image pull from Azure#1618
truggeri wants to merge 2 commits intomainfrom
truggeri/fallback-image-pull

Conversation

@truggeri
Copy link
Contributor

@truggeri truggeri commented Feb 6, 2026

Summary

We have added a fallback container registry in Azure that hosts a copy of all the container images we pull in this repo. This PR makes use of this fallback registry by,

  1. Allowing the image service to pull from azure-api.net
  2. Updating our main to fallback to pulling from Azure should the first attempt from GHCR fail
  • Images are stored under the same path, so a simple prefix of the Azure api works
  • Provide the Updater code with the image that successfully pulled
  1. Updates tests

* Allow images to be pulled from azure-api.net
* Fallback to pulling images from Azure api
* Tests
@truggeri truggeri self-assigned this Feb 6, 2026
@truggeri truggeri requested a review from a team as a code owner February 6, 2026 20:45
Copilot AI review requested due to automatic review settings February 6, 2026 20:45
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an Azure Container Registry (via *.azure-api.net) as a fallback source for updater/proxy images when the primary pull fails, to improve reliability of image availability for this action.

Changes:

  • Add fallback image-pull behavior in run() to retry pulls from an Azure registry when the primary pull fails.
  • Expand ImageService.pull() repository allowlist to include azure-api.net.
  • Update documentation and tests to reflect the new fallback registry behavior.

Reviewed changes

Copilot reviewed 5 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/main.ts Implements fallback logic for pulling updater/proxy images from Azure and passes the successful image refs to Updater.
src/image-service.ts Updates image allowlist validation to permit azure-api.net registries.
tests/main.test.ts Adjusts workflow tests for image pull failure/fallback scenarios.
tests/image-service.test.ts Updates error-message assertion and adds an Azure-related test case.
docker/README.md Documents existence of Azure fallback registry.
docker/containers.json Formatting-only change (newline/EOF).
dist/main/index.js Updates the bundled Action output to include the new fallback + allowlist logic.
Comments suppressed due to low confidence (1)

src/image-service.ts:8

  • AZURE_REGISTRY_RE is effectively never going to match: the pattern starts with $ (end-of-string anchor), so validImageRepository() will reject all *.azure-api.net/... images. Update this to a start-anchored domain/host check (and preferably use a non-global regex with .test()), otherwise the Azure fallback pulls will always fail validation.
const MAX_RETRIES = 5 // Maximum number of retries
const INITIAL_DELAY_MS = 5000 // Initial delay in milliseconds for backoff
const AZURE_REGISTRY_RE = /$\w*\.azure-api\.net/g


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 112 to +131
core.startGroup('Pulling updater images')
let imagesPulled = false

try {
// Using sendMetricsWithPackageManager wrapper to inject package manager tag ti
// avoid passing additional parameters to ImageService.pull method
await ImageService.pull(updaterImage, sendMetricsWithPackageManager)
await ImageService.pull(PROXY_IMAGE_NAME, sendMetricsWithPackageManager)
} catch (error: unknown) {
if (error instanceof Error) {
await failJob(
apiClient,
'Error fetching updater images',
error,
DependabotErrorType.Image
)
return
await ImageService.pull(proxyImage, sendMetricsWithPackageManager)
imagesPulled = true
} catch {
core.warning('Primary image pull failed, attempting fallback')
}

if (!imagesPulled) {
updaterImage = `${FALLBACK_CONTAINER_REGISTRY}/${updaterImage}`
proxyImage = `${FALLBACK_CONTAINER_REGISTRY}/${proxyImage}`
try {
await ImageService.pull(updaterImage, sendMetricsWithPackageManager)
await ImageService.pull(proxyImage, sendMetricsWithPackageManager)
} catch (error: unknown) {
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

The fallback logic treats the updater+proxy pulls as all-or-nothing via a single imagesPulled flag. If the updater image pulls successfully from GHCR but the proxy image pull fails, the code will still rewrite both image names to the Azure registry and require both Azure pulls to succeed, potentially failing even though one image was already available locally. Track success per image and only fall back (and rewrite the image name passed to Updater) for the specific image(s) that failed to pull.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm okay with the all-or-nothing approach. It's very unlikely one will fail and not the other

Comment on lines 115 to +123
try {
// Using sendMetricsWithPackageManager wrapper to inject package manager tag ti
// avoid passing additional parameters to ImageService.pull method
await ImageService.pull(updaterImage, sendMetricsWithPackageManager)
await ImageService.pull(PROXY_IMAGE_NAME, sendMetricsWithPackageManager)
} catch (error: unknown) {
if (error instanceof Error) {
await failJob(
apiClient,
'Error fetching updater images',
error,
DependabotErrorType.Image
)
return
await ImageService.pull(proxyImage, sendMetricsWithPackageManager)
imagesPulled = true
} catch {
core.warning('Primary image pull failed, attempting fallback')
}
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

catch { core.warning('Primary image pull failed...') } discards the underlying error, which makes diagnosing registry/auth/network issues difficult. Capture the error and include its message (and ideally which image failed) in the warning log.

Copilot uses AI. Check for mistakes.
Comment on lines 112 to 143
core.startGroup('Pulling updater images')
let imagesPulled = false

try {
// Using sendMetricsWithPackageManager wrapper to inject package manager tag ti
// avoid passing additional parameters to ImageService.pull method
await ImageService.pull(updaterImage, sendMetricsWithPackageManager)
await ImageService.pull(PROXY_IMAGE_NAME, sendMetricsWithPackageManager)
} catch (error: unknown) {
if (error instanceof Error) {
await failJob(
apiClient,
'Error fetching updater images',
error,
DependabotErrorType.Image
)
return
await ImageService.pull(proxyImage, sendMetricsWithPackageManager)
imagesPulled = true
} catch {
core.warning('Primary image pull failed, attempting fallback')
}

if (!imagesPulled) {
updaterImage = `${FALLBACK_CONTAINER_REGISTRY}/${updaterImage}`
proxyImage = `${FALLBACK_CONTAINER_REGISTRY}/${proxyImage}`
try {
await ImageService.pull(updaterImage, sendMetricsWithPackageManager)
await ImageService.pull(proxyImage, sendMetricsWithPackageManager)
} catch (error: unknown) {
if (error instanceof Error) {
await failJob(
apiClient,
'Error fetching updater images',
error,
DependabotErrorType.Image
)
return
}
}
}
core.endGroup()
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

If failJob(...) returns early inside the image-pull group, core.endGroup() is skipped, which can leave the GitHub Actions log grouping unbalanced. Consider wrapping the pull logic in a try/finally that always calls core.endGroup().

See below for a potential fix:

        try {
          // Using sendMetricsWithPackageManager wrapper to inject package manager tag ti
          // avoid passing additional parameters to ImageService.pull method
          await ImageService.pull(updaterImage, sendMetricsWithPackageManager)
          await ImageService.pull(proxyImage, sendMetricsWithPackageManager)
          imagesPulled = true
        } catch {
          core.warning('Primary image pull failed, attempting fallback')
        }

        if (!imagesPulled) {
          updaterImage = `${FALLBACK_CONTAINER_REGISTRY}/${updaterImage}`
          proxyImage = `${FALLBACK_CONTAINER_REGISTRY}/${proxyImage}`
          try {
            await ImageService.pull(updaterImage, sendMetricsWithPackageManager)
            await ImageService.pull(proxyImage, sendMetricsWithPackageManager)
          } catch (error: unknown) {
            if (error instanceof Error) {
              await failJob(
                apiClient,
                'Error fetching updater images',
                error,
                DependabotErrorType.Image
              )
              return
            }
          }
        }
      } finally {
        core.endGroup()
      }

Copilot uses AI. Check for mistakes.
Comment on lines +147 to +163
test('can pull images from azure-api.net', async () => {
pullMock.mockResolvedValue(
new Readable({
read() {}
})
)

await expect(
ImageService.fetchImageWithRetry(
'test.azure-api.net/ghcr.io/dependabot/dependabot-updater-npm',
{},
docker,
undefined, // explicitly pass undefined for sendMetrics
'dependabot'
)
).resolves.not.toThrow()
})
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

This test exercises fetchImageWithRetry with an azure-api.net image name, but the new repository allowlist behavior is enforced in ImageService.pull() via validImageRepository(). Add a unit test that calls ImageService.pull() with an *.azure-api.net/... image and asserts it does not throw (mocking Docker as needed), so the allowlist change is actually covered.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think that's necessary.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@truggeri truggeri force-pushed the truggeri/fallback-image-pull branch from fcc0b8e to e5faae2 Compare February 6, 2026 23:25
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.

2 participants