class PullBaseImageStep implements Callable<ImagesAndRegistryClient> {

  @Override
  public ImagesAndRegistryClient call()
      throws IOException, RegistryException, LayerPropertyNotFoundException,
          LayerCountMismatchException, BadContainerConfigurationFormatException,
          CacheCorruptedException, CredentialRetrievalException {
    EventHandlers eventHandlers = buildContext.getEventHandlers();
    // 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);
      }
    }

    try (ProgressEventDispatcher progressEventDispatcher =
            progressEventDispatcherFactory.create("pulling base image manifest", 2);
        TimerEventDispatcher ignored1 = new TimerEventDispatcher(eventHandlers, DESCRIPTION)) {

      // 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();
      try {
        return new ImagesAndRegistryClient(
            pullBaseImages(noAuthRegistryClient, progressEventDispatcher), 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, progressEventDispatcher), 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, progressEventDispatcher), 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, progressEventDispatcher), registryClient);
        }
      }
    }
  }

    PullBaseImageStep(
      BuildContext buildContext, ProgressEventDispatcher.Factory progressEventDispatcherFactory);

}