Wdrażanie oprogramowania sprzęgającego

Na tej stronie samouczka Cloud Search pokazujemy, jak skonfigurować źródło danych oraz łącznik treści na potrzeby indeksowania danych. Aby zacząć od początku tego samouczka, zapoznaj się z artykułem Samouczek dla początkujących w Cloud Search

Tworzenie oprogramowania sprzęgającego

Zmień katalog roboczy na cloud-search-samples/end-to-end/connector i uruchom to polecenie:

mvn package -DskipTests

Polecenie pobiera zależności wymagane do utworzenia łącznik treści i kompiluje kod.

Utwórz dane logowania do konta usługi

Oprogramowanie sprzęgające wymaga danych logowania konta usługi do wywoływania Cloud Search API. Aby utworzyć dane logowania:

  1. Wróć do Konsola Google Cloud.
  2. W menu po lewej stronie kliknij Dane logowania. „Dane logowania”
  3. Kliknij listę + UTWÓRZ DANE LOGOWANIA i wybierz Konto usługi. „Utwórz konto usługi”
  4. W polu Nazwa konta usługi wpisz „tutorial”.
  5. Zapisz wartość identyfikatora konta usługi (zaraz po nazwie konta usługi). Zostanie ona użyta później.
  6. Kliknij UTWÓRZ. „Uprawnienia konta usługi (opcjonalne)” które się wyświetla.
  7. Kliknij DALEJ. Opcja „Przyznaj użytkownikom dostęp do tego konta usługi” (opcjonalnie)” które się wyświetla.
  8. Kliknij GOTOWE. Strona „Dane logowania” ekranu.
  9. W sekcji Konta usługi kliknij adres e-mail konta usługi. Usługa szczegóły konta” aplikacji na stronie.
  10. W sekcji Klucze kliknij listę DODAJ KLUCZ i wybierz Utwórz nowy klucz. Opcja „Utwórz klucz prywatny” które się wyświetla.
  11. Kliknij UTWÓRZ.
  12. (opcjonalnie) Jeśli w polu „Czy chcesz zezwolić na pobieranie console.cloud.google.com?” kliknij Zezwól.
  13. Plik z kluczem prywatnym jest zapisywany na Twoim komputerze. Zapisz lokalizację pobranego pliku. Ten plik jest używany do konfigurowania łącznika treści, może uwierzytelnić się podczas wywoływania interfejsów Google Cloud Search API.

Inicjowanie pomocy zewnętrznej

Zanim będzie można wywołać inne interfejsy API Cloud Search, musisz zainicjować obsługę Google Cloud Search.

Aby zainicjować obsługę Cloud Search przez inną firmę:

  1. Twój projekt platformy Cloud Search zawiera dane logowania do konta usługi. Aby jednak zainicjować pomoc zewnętrzną, musisz utworzyć dane logowania do aplikacji. Instrukcje tworzenia aplikacji internetowej dane logowania, zapoznaj się z artykułem Utwórz dane logowania. Po wykonaniu tej czynności powinien być już identyfikator klienta i plik z tajnym kluczem klienta.

  2. Używaj Protokół Google OAuth 2 aby uzyskać token dostępu:

    1. Kliknij ustawienia i zaznacz Użyj własnych danych uwierzytelniających.
    2. Wpisz identyfikator klienta i tajny klucz klienta z kroku 1.
    3. Kliknij Zamknij.
    4. W polu zakresów wpisz https://www.googleapis.com/auth/cloud_search.settings i kliknij Autoryzuj. Narzędzie do testowania OAuth 2 zwraca kod autoryzacji.
    5. Kliknij Exchange authorization code for token (Kod autoryzacji giełdy dla tokenów). Zwracany jest token.
  3. Aby zainicjować obsługę Cloud Search przez inną firmę, użyj tego curl . Pamiętaj, aby zastąpić [YOUR_ACCESS_TOKEN] tokenem uzyskanym w kroku 2.

    curl --request POST \
    'https://cloudsearch.googleapis.com/v1:initializeCustomer' \
      --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
      --header 'Accept: application/json' \
      --header 'Content-Type: application/json' \
      --data '{}' \
      --compressed
    

    Jeśli operacja się uda, treść odpowiedzi będzie zawierała wystąpienie obiektu operation. Przykład:

    {
    name: "operations/customers/01b3fqdm/lro/AOIL6eBv7fEfiZ_hUSpm8KQDt1Mnd6dj5Ru3MXf-jri4xK6Pyb2-Lwfn8vQKg74pgxlxjrY"
    }
    

    W przypadku niepowodzenia skontaktuj się z zespołem pomocy Cloud Search.

  4. Użyj pliku operations.get, aby to sprawdzić. zostanie zainicjowana pomoc innych firm:

    curl \
    'https://cloudsearch.googleapis.com/v1/operations/customers/01b3fqdm/lro/AOIL6eBv7fEfiZ_hUSpm8KQDt1Mnd6dj5Ru3MXf-jri4xK6Pyb2-Lwfn8vQKg74pgxlxjrY?key=
    [YOUR_API_KEY]' \
    --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
    --header 'Accept: application/json' \
    --compressed
    

    Po zakończeniu inicjowania usługi zewnętrznej zawiera pole done zostało ustawione na true. Na przykład:

    {
    name: "operations/customers/01b3fqdm/lro/AOIL6eBv7fEfiZ_hUSpm8KQDt1Mnd6dj5Ru3MXf-jri4xK6Pyb2-Lwfn8vQKg74pgxlxjrY"
    done: true
    }
    

Tworzenie źródła danych

Następnie utwórz źródło danych w konsoli administracyjnej. Źródło danych zapewnia przestrzeń nazw do indeksowania treści przy użyciu oprogramowania sprzęgającego.

  1. Otwórz konsolę administracyjną Google.
  2. Kliknij ikonę Aplikacje. W sekcji „Administracja aplikacjami”
  3. Kliknij Google Workspace. Sekcja „Administracja aplikacjami Google Workspace”
  4. Przewiń w dół i kliknij Cloud Search. Ustawienia Google Workspace strona
  5. Kliknij Zewnętrzne źródła danych. „Źródła danych”
  6. Kliknij okrągły żółty symbol +. Opcja „Dodaj nowe źródło danych” które się wyświetla.
  7. W polu Wyświetlana nazwa wpisz „tutorial”.
  8. W polu Adresy e-mail konta usługi wpisz adres e-mail konto usługi utworzone w poprzedniej sekcji. Jeśli nie znasz adresu e-mail konta usługi, wyszukaj wartość w argumencie konta usługi stronę.
  9. Kliknij DODAJ. Informacja „Utworzono źródło danych” które się wyświetla.
  10. Kliknij *OK. Zapisz identyfikator źródła dla nowo utworzonego źródła danych. Identyfikator źródła jest używany do konfigurowania łącznika treści.

Generowanie osobistego tokena dostępu dla interfejsu GitHub API

Oprogramowanie sprzęgające wymaga uwierzytelnionego dostępu do interfejsu GitHub API, aby aby zapewnić odpowiedni limit. Dla uproszczenia oprogramowanie sprzęgające wykorzystuje tokeny dostępu zamiast OAuth. Tokeny osobiste umożliwiają uwierzytelnianie jako użytkownika z ograniczonym zestawem uprawnień podobnych do uprawnień OAuth.

  1. Zaloguj się w GitHubie.
  2. W prawym górnym rogu kliknij swoje zdjęcie profilowe. Menu
  3. Kliknij Ustawienia.
  4. Kliknij Ustawienia programisty.
  5. Kliknij Osobiste tokeny dostępu.
  6. Kliknij Generate personal access token (Wygeneruj osobisty token dostępu).
  7. W polu Notatka wpisz „Samouczek Cloud Search”.
  8. Sprawdź zakres public_repo.
  9. Kliknij Wygeneruj token.
  10. Zapisz wygenerowany token. Jest używany przez oprogramowanie sprzęgające do wywoływania GitHuba interfejsów API i udostępnia limit interfejsu API na potrzeby indeksowania.

Konfigurowanie oprogramowania sprzęgającego

Po utworzeniu danych logowania i źródła danych zaktualizuj oprogramowanie sprzęgające , aby uwzględnić te wartości:

  1. W wierszu poleceń zmień katalog na cloud-search-samples/end-to-end/connector/
  2. Otwórz plik sample-config.properties w edytorze tekstu.
  3. Ustaw parametr api.serviceAccountPrivateKeyFile na ścieżkę pliku komponentu dane logowania do usługi.
  4. Ustaw parametr api.sourceId na identyfikator źródła danych utworzone wcześniej.
  5. Ustaw parametr github.user na swoją nazwę użytkownika GitHub.
  6. Ustaw parametr github.token na utworzony wcześniej token dostępu.
  7. Zapisz plik.

Zaktualizuj schemat

Oprogramowanie sprzęgające indeksuje zarówno treści uporządkowane, jak i nieuporządkowane. Przed indeksowaniem , musisz zaktualizować schemat źródła danych. Uruchom to polecenie aby zaktualizować schemat:

mvn exec:java -Dexec.mainClass=com.google.cloudsearch.tutorial.SchemaTool \
    -Dexec.args="-Dconfig=sample-config.properties"

Uruchamianie oprogramowania sprzęgającego

Aby uruchomić oprogramowanie sprzęgające i rozpocząć indeksowanie, uruchom polecenie:

mvn exec:java -Dexec.mainClass=com.google.cloudsearch.tutorial.GithubConnector \
    -Dexec.args="-Dconfig=sample-config.properties"

Domyślną konfiguracją oprogramowania sprzęgającego jest indeksowanie pojedynczego repozytorium w organizacji googleworkspace. Indeksowanie repozytorium trwa około minuty. Po wstępnym indeksowaniu oprogramowanie sprzęgające kontynuuje sondowanie w poszukiwaniu zmian które musi być odzwierciedlone w indeksie Cloud Search.

Sprawdzanie kodu

W pozostałych sekcjach omawiamy sposób tworzenia oprogramowania sprzęgającego.

Uruchamianie aplikacji

Punktem wejścia oprogramowania sprzęgającego jest klasa GithubConnector. Metoda main tworzy instancję pakietu SDK IndexingApplication i ją uruchamia.

GithubConnector.java
/**
 * Main entry point for the connector. Creates and starts an indexing
 * application using the {@code ListingConnector} template and the sample's
 * custom {@code Repository} implementation.
 *
 * @param args program command line arguments
 * @throws InterruptedException thrown if an abort is issued during initialization
 */
public static void main(String[] args) throws InterruptedException {
  Repository repository = new GithubRepository();
  IndexingConnector connector = new ListingConnector(repository);
  IndexingApplication application = new IndexingApplication.Builder(connector, args)
      .build();
  application.start();
}

ListingConnector udostępniana przez pakiet SDK implementuje strategię przemierzania wykorzystujący kolejki Cloud Search. do śledzenia stanu elementów w indeksie. Przekazuje dane do GithubRepository, implementowane przez przykładowe oprogramowanie sprzęgające, aby umożliwić dostęp do treści z GitHuba.

Przeglądanie repozytoriów GitHub

Podczas pełnych przemierzania adresów URL getIds() jest wywoływana w celu przekazania do kolejki elementów, które być może trzeba zindeksować.

Oprogramowanie sprzęgające może indeksować wiele repozytoriów lub organizacji. Aby zminimalizować skutków awarii, eksploatowane jest 1 repozytorium GitHub naraz. Punkt kontroli jest zwracany z wynikami przemierzania zawierającymi listę do zindeksowania w kolejnych wywołaniach getIds(). Jeśli wystąpi błąd gdy wystąpi, indeksowanie zostanie wznowione w bieżącym repozytorium, zamiast zacząć od początku.

GithubRepository.java
/**
 * Gets all of the existing item IDs from the data repository. While
 * multiple repositories are supported, only one repository is traversed
 * per call. The remaining repositories are saved in the checkpoint
 * are traversed on subsequent calls. This minimizes the amount of
 * data that needs to be reindex in the event of an error.
 *
 * <p>This method is called by {@link ListingConnector#traverse()} during
 * <em>full traversals</em>. Every document ID and metadata hash value in
 * the <em>repository</em> is pushed to the Cloud Search queue. Each pushed
 * document is later polled and processed in the {@link #getDoc(Item)} method.
 * <p>
 * The metadata hash values are pushed to aid document change detection. The
 * queue sets the document status depending on the hash comparison. If the
 * pushed ID doesn't yet exist in Cloud Search, the document's status is
 * set to <em>new</em>. If the ID exists but has a mismatched hash value,
 * its status is set to <em>modified</em>. If the ID exists and matches
 * the hash value, its status is unchanged.
 *
 * <p>In every case, the pushed content hash value is only used for
 * comparison. The hash value is only set in the queue during an
 * update (see {@link #getDoc(Item)}).
 *
 * @param checkpoint value defined and maintained by this connector
 * @return this is typically a {@link PushItems} instance
 */
@Override
public CheckpointCloseableIterable<ApiOperation> getIds(byte[] checkpoint)
    throws RepositoryException {
  List<String> repositories;
  // Decode the checkpoint if present to get the list of remaining
  // repositories to index.
  if (checkpoint != null) {
    try {
      FullTraversalCheckpoint decodedCheckpoint = FullTraversalCheckpoint
          .fromBytes(checkpoint);
      repositories = decodedCheckpoint.getRemainingRepositories();
    } catch (IOException e) {
      throw new RepositoryException.Builder()
          .setErrorMessage("Unable to deserialize checkpoint")
          .setCause(e)
          .build();
    }
  } else {
    // No previous checkpoint, scan for repositories to index
    // based on the connector configuration.
    try {
      repositories = scanRepositories();
    } catch (IOException e) {
      throw toRepositoryError(e, Optional.of("Unable to scan repositories"));
    }
  }

  if (repositories.isEmpty()) {
    // Nothing left to index. Reset the checkpoint to null so the
    // next full traversal starts from the beginning
    Collection<ApiOperation> empty = Collections.emptyList();
    return new CheckpointCloseableIterableImpl.Builder<>(empty)
        .setCheckpoint((byte[]) null)
        .setHasMore(false)
        .build();
  }

  // Still have more repositories to index. Pop the next repository to
  // index off the list. The remaining repositories make up the next
  // checkpoint.
  String repositoryToIndex = repositories.get(0);
  repositories = repositories.subList(1, repositories.size());

  try {
    log.info(() -> String.format("Traversing repository %s", repositoryToIndex));
    Collection<ApiOperation> items = collectRepositoryItems(repositoryToIndex);
    FullTraversalCheckpoint newCheckpoint = new FullTraversalCheckpoint(repositories);
    return new CheckpointCloseableIterableImpl.Builder<>(items)
        .setHasMore(true)
        .setCheckpoint(newCheckpoint.toBytes())
        .build();
  } catch (IOException e) {
    String errorMessage = String.format("Unable to traverse repo: %s",
        repositoryToIndex);
    throw toRepositoryError(e, Optional.of(errorMessage));
  }
}

Metoda collectRepositoryItems() obsługuje przemierzanie pojedynczego obiektu repozytorium GitHub. Ta metoda zwraca kolekcję ApiOperations oznacza elementy do przekazania do kolejki. Elementy są przekazywane jako nazwę zasobu i wartość skrótu reprezentującą bieżący stan elementu.

Wartość skrótu jest używana podczas kolejnych prób przemierzania GitHuba repozytoriów. Ta wartość umożliwia sprawną kontrolę nad tym, czy treść została zmieniona bez konieczności przesyłania dodatkowych treści. Łącznik na ślepo umieści wszystkie elementy w kolejce. Jeśli element jest nowy lub wartość skrótu uległa zmianie, zostaje utworzony do odpytywania w kolejce. W przeciwnym razie produkt zostanie uznany za niezmodyfikowany.

GithubRepository.java
/**
 * Fetch IDs to  push in to the queue for all items in the repository.
 * Currently captures issues & content in the master branch.
 *
 * @param name Name of repository to index
 * @return Items to push into the queue for later indexing
 * @throws IOException if error reading issues
 */
private Collection<ApiOperation> collectRepositoryItems(String name)
    throws IOException {
  List<ApiOperation> operations = new ArrayList<>();
  GHRepository repo = github.getRepository(name);

  // Add the repository as an item to be indexed
  String metadataHash = repo.getUpdatedAt().toString();
  String resourceName = repo.getHtmlUrl().getPath();
  PushItem repositoryPushItem = new PushItem()
      .setMetadataHash(metadataHash);
  PushItems items = new PushItems.Builder()
      .addPushItem(resourceName, repositoryPushItem)
      .build();

  operations.add(items);
  // Add issues/pull requests & files
  operations.add(collectIssues(repo));
  operations.add(collectContent(repo));
  return operations;
}

Przetwarzam kolejkę

Po zakończeniu pełnego przemierzania oprogramowanie sprzęgające rozpoczyna odpytywanie w kolejce dla elementów wymagających indeksowania. getDoc() dla każdego elementu pobranego z kolejki. Metoda odczytuje wybiorę element z GitHuba i przekształci on go w odpowiednią reprezentację, do indeksowania.

Ponieważ oprogramowanie sprzęgające działa w przypadku danych bieżących, które mogą zostać zmienione w dowolnym momencie getDoc() sprawdza też, czy element w kolejce jest nadal prawidłowy i usuwa z indeksu wszystkie elementy, które już nie istnieją.

GithubRepository.java
/**
 * Gets a single data repository item and indexes it if required.
 *
 * <p>This method is called by the {@link ListingConnector} during a poll
 * of the Cloud Search queue. Each queued item is processed
 * individually depending on its state in the data repository.
 *
 * @param item the data repository item to retrieve
 * @return the item's state determines which type of
 * {@link ApiOperation} is returned:
 * {@link RepositoryDoc}, {@link DeleteItem}, or {@link PushItem}
 */
@Override
public ApiOperation getDoc(Item item) throws RepositoryException {
  log.info(() -> String.format("Processing item: %s ", item.getName()));
  Object githubObject;
  try {
    // Retrieve the item from GitHub
    githubObject = getGithubObject(item.getName());
    if (githubObject instanceof GHRepository) {
      return indexItem((GHRepository) githubObject, item);
    } else if (githubObject instanceof GHPullRequest) {
      return indexItem((GHPullRequest) githubObject, item);
    } else if (githubObject instanceof GHIssue) {
      return indexItem((GHIssue) githubObject, item);
    } else if (githubObject instanceof GHContent) {
      return indexItem((GHContent) githubObject, item);
    } else {
      String errorMessage = String.format("Unexpected item received: %s",
          item.getName());
      throw new RepositoryException.Builder()
          .setErrorMessage(errorMessage)
          .setErrorType(RepositoryException.ErrorType.UNKNOWN)
          .build();
    }
  } catch (FileNotFoundException e) {
    log.info(() -> String.format("Deleting item: %s ", item.getName()));
    return ApiOperations.deleteItem(item.getName());
  } catch (IOException e) {
    String errorMessage = String.format("Unable to retrieve item: %s",
        item.getName());
    throw toRepositoryError(e, Optional.of(errorMessage));
  }
}

Dla każdego obiektu GitHub indeksowane przez oprogramowanie sprzęgające jest odpowiedni parametr Metoda indexItem() obsługuje tworzenie reprezentacji elementu Cloud Search. Aby na przykład utworzyć reprezentację elementów treści:

GithubRepository.java
/**
 * Build the ApiOperation to index a content item (file).
 *
 * @param content      Content item to index
 * @param previousItem Previous item state in the index
 * @return ApiOperation (RepositoryDoc if indexing,  PushItem if not modified)
 * @throws IOException if unable to create operation
 */
private ApiOperation indexItem(GHContent content, Item previousItem)
    throws IOException {
  String metadataHash = content.getSha();

  // If previously indexed and unchanged, just requeue as unmodified
  if (canSkipIndexing(previousItem, metadataHash)) {
    return notModified(previousItem.getName());
  }

  String resourceName = new URL(content.getHtmlUrl()).getPath();
  FieldOrValue<String> title = FieldOrValue.withValue(content.getName());
  FieldOrValue<String> url = FieldOrValue.withValue(content.getHtmlUrl());

  String containerName = content.getOwner().getHtmlUrl().getPath();
  String programmingLanguage = FileExtensions.getLanguageForFile(content.getName());

  // Structured data based on the schema
  Multimap<String, Object> structuredData = ArrayListMultimap.create();
  structuredData.put("organization", content.getOwner().getOwnerName());
  structuredData.put("repository", content.getOwner().getName());
  structuredData.put("path", content.getPath());
  structuredData.put("language", programmingLanguage);

  Item item = IndexingItemBuilder.fromConfiguration(resourceName)
      .setTitle(title)
      .setContainerName(containerName)
      .setSourceRepositoryUrl(url)
      .setItemType(IndexingItemBuilder.ItemType.CONTAINER_ITEM)
      .setObjectType("file")
      .setValues(structuredData)
      .setVersion(Longs.toByteArray(System.currentTimeMillis()))
      .setHash(content.getSha())
      .build();

  // Index the file content too
  String mimeType = FileTypeMap.getDefaultFileTypeMap()
      .getContentType(content.getName());
  AbstractInputStreamContent fileContent = new InputStreamContent(
      mimeType, content.read())
      .setLength(content.getSize())
      .setCloseInputStream(true);
  return new RepositoryDoc.Builder()
      .setItem(item)
      .setContent(fileContent, IndexingService.ContentFormat.RAW)
      .setRequestMode(IndexingService.RequestMode.SYNCHRONOUS)
      .build();
}

Następnie wdróż interfejs wyszukiwania.

Wstecz Dalej