@Override
  public ImagesAndRegistryClient call()
      throws IOException, RegistryException, LayerPropertyNotFoundException,
          LayerCountMismatchException, BadContainerConfigurationFormatException,
          CacheCorruptedException, CredentialRetrievalException {
    EventHandlers eventHandlers = buildContext.getEventHandlers();
    try (ProgressEventDispatcher progressDispatcher =
            progressDispatcherFactory.create("pulling base image manifest", 4);
        TimerEventDispatcher ignored1 = new TimerEventDispatcher(eventHandlers, DESCRIPTION)) {

      // Skip this step if this is a scratch image
      ImageReference imageReference = buildContext.getBaseImageConfiguration().getImage();
      if (imageReference.isScratch()) {
        Set<Platform> platforms = buildContext.getContainerConfiguration().getPlatforms();
        Verify.verify(!platforms.isEmpty());

        eventHandlers.dispatch(LogEvent.progress("Getting scratch base image..."));
        ImmutableList.Builder<Image> images = ImmutableList.builder();
        for (Platform platform : platforms) {
          Image.Builder imageBuilder = Image.builder(buildContext.getTargetFormat());
          imageBuilder.setArchitecture(platform.getArchitecture()).setOs(platform.getOs());
          images.add(imageBuilder.build());
        }
        return new ImagesAndRegistryClient(images.build(), null);
      }

      eventHandlers.dispatch(
          LogEvent.progress("Getting manifest for base image " + imageReference + "..."));

      if (buildContext.isOffline()) {
        List<Image> images = getCachedBaseImages();
        if (!images.isEmpty()) {
          return new ImagesAndRegistryClient(images, null);
        }
        throw new IOException(
            "Cannot run Jib in offline mode; " + imageReference + " not found in local Jib cache");

      } else if (imageReference.getDigest().isPresent()) {
        List<Image> images = getCachedBaseImages();
        if (!images.isEmpty()) {
          RegistryClient noAuthRegistryClient =
              buildContext.newBaseImageRegistryClientFactory().newRegistryClient();
          // TODO: passing noAuthRegistryClient may be problematic. It may return 401 unauthorized
          // if layers have to be downloaded.
          // https://github.com/GoogleContainerTools/jib/issues/2220
          return new ImagesAndRegistryClient(images, noAuthRegistryClient);
        }
      }

      Optional<ImagesAndRegistryClient> mirrorPull =
          tryMirrors(buildContext, progressDispatcher.newChildProducer());
      if (mirrorPull.isPresent()) {
        return mirrorPull.get();
      }

      try {
        // First, try with no credentials. This works with public GCR images (but not Docker Hub).
        // TODO: investigate if we should just pass credentials up front. However, this involves
        // some risk. https://github.com/GoogleContainerTools/jib/pull/2200#discussion_r359069026
        // contains some related discussions.
        RegistryClient noAuthRegistryClient =
            buildContext.newBaseImageRegistryClientFactory().newRegistryClient();
        return new ImagesAndRegistryClient(
            pullBaseImages(noAuthRegistryClient, progressDispatcher.newChildProducer()),
            noAuthRegistryClient);

      } catch (RegistryUnauthorizedException ex) {
        eventHandlers.dispatch(
            LogEvent.lifecycle(
                "The base image requires auth. Trying again for " + imageReference + "..."));

        Credential credential =
            RegistryCredentialRetriever.getBaseImageCredential(buildContext).orElse(null);
        RegistryClient registryClient =
            buildContext
                .newBaseImageRegistryClientFactory()
                .setCredential(credential)
                .newRegistryClient();

        String wwwAuthenticate = ex.getHttpResponseException().getHeaders().getAuthenticate();
        if (wwwAuthenticate != null) {
          eventHandlers.dispatch(
              LogEvent.debug("WWW-Authenticate for " + imageReference + ": " + wwwAuthenticate));
          registryClient.authPullByWwwAuthenticate(wwwAuthenticate);
          return new ImagesAndRegistryClient(
              pullBaseImages(registryClient, progressDispatcher.newChildProducer()),
              registryClient);

        } else {
          // Not getting WWW-Authenticate is unexpected in practice, and we may just blame the
          // server and fail. However, to keep some old behavior, try a few things as a last resort.
          // TODO: consider removing this fallback branch.
          if (credential != null && !credential.isOAuth2RefreshToken()) {
            eventHandlers.dispatch(
                LogEvent.debug("Trying basic auth as fallback for " + imageReference + "..."));
            registryClient.configureBasicAuth();
            try {
              return new ImagesAndRegistryClient(
                  pullBaseImages(registryClient, progressDispatcher.newChildProducer()),
                  registryClient);
            } catch (RegistryUnauthorizedException ignored) {
              // Fall back to try bearer auth.
            }
          }

          eventHandlers.dispatch(
              LogEvent.debug("Trying bearer auth as fallback for " + imageReference + "..."));
          registryClient.doPullBearerAuth();
          return new ImagesAndRegistryClient(
              pullBaseImages(registryClient, progressDispatcher.newChildProducer()),
              registryClient);
        }
      }
    }
  }
@Test
  public void testCall_offlineMode_cached()
      throws LayerPropertyNotFoundException, RegistryException, LayerCountMismatchException,
          BadContainerConfigurationFormatException, CacheCorruptedException,
          CredentialRetrievalException, InvalidImageReferenceException, IOException {
    ImageReference imageReference = ImageReference.parse("cat");
    Mockito.when(imageConfiguration.getImage()).thenReturn(imageReference);
    Mockito.when(buildContext.isOffline()).thenReturn(true);

    ContainerConfigurationTemplate containerConfigJson = new ContainerConfigurationTemplate();
    containerConfigJson.setArchitecture("slim arch");
    containerConfigJson.setOs("fat system");
    ManifestAndConfigTemplate manifestAndConfig =
        new ManifestAndConfigTemplate(
            new V22ManifestTemplate(), containerConfigJson, "sha256:digest");
    ImageMetadataTemplate imageMetadata =
        new ImageMetadataTemplate(null, Arrays.asList(manifestAndConfig));
    Mockito.when(cache.retrieveMetadata(imageReference)).thenReturn(Optional.of(imageMetadata));
    Mockito.when(cache.areAllLayersCached(manifestAndConfig.getManifest())).thenReturn(true);

    ImagesAndRegistryClient result = pullBaseImageStep.call();
    Assert.assertEquals("fat system", result.images.get(0).getOs());
    Assert.assertNull(result.registryClient);

    Mockito.verify(buildContext, Mockito.never()).newBaseImageRegistryClientFactory();
  }