Implantar o conector

Nesta página do tutorial do Cloud Search, mostramos como configurar um conector de conteúdo e uma origem de dados para indexar dados. Para começar do início deste tutorial, consulte Tutorial de introdução do Cloud Search.

Criar o conector

Altere seu diretório de trabalho para o diretório cloud-search-samples/end-to-end/connector e execute este comando:

mvn package -DskipTests

O comando faz o download das dependências necessárias para criar o conector de conteúdo e compila o código.

Criar credenciais de conta de serviço

O conector exige credenciais da conta de serviço para chamar as APIs do Cloud Search. Para criar as credenciais:

  1. Volte para o console do Google Cloud.
  2. No menu de navegação à esquerda, clique em Credenciais. A página "Credencial" será exibida.
  3. Clique na lista suspensa + CRIAR CREDENCIAIS e selecione Conta de serviço. A página "Criar conta de serviço" é exibida.
  4. No campo Nome da conta de serviço, digite "tutorial".
  5. Anote o valor do ID da conta de serviço, logo após o nome dessa conta. Esse valor será usado posteriormente.
  6. Clique em CRIAR. A caixa de diálogo "Permissões da conta de serviço (opcional)" é exibida.
  7. Clique em CONTINUAR. A caixa de diálogo "Conceda aos usuários acesso a essa conta de serviço (opcional)" será exibida.
  8. Clique em CONCLUÍDO. A tela "Credenciais" será exibida.
  9. Em "Contas de serviço", clique no e-mail da conta. A página de detalhes da conta de serviço é exibida.
  10. Em "Chaves", clique na lista suspensa ADICIONAR CHAVE e selecione Criar nova chave. A caixa de diálogo "Criar chave privada" será exibida.
  11. Clique em CRIAR.
  12. (Opcional) Se a caixa de diálogo "Quer permitir downloads em console.cloud.google.com?" aparecer, clique em Permitir.
  13. Um arquivo de chave privada é salvo no seu computador. Anote o local do arquivo transferido por download. Esse arquivo é usado na configuração do conector de conteúdo para que ele possa se autenticar ao chamar as APIs do Google Cloud Search.

Inicializar o suporte de terceiros

Antes de chamar qualquer outra API do Cloud Search, inicialize o suporte de terceiros para o Google Cloud Search.

Para inicializar o suporte de terceiros ao Cloud Search, faça o seguinte:

  1. Seu projeto da plataforma do Cloud Search contém credenciais de conta de serviço. No entanto, para inicializar o suporte de terceiros, é preciso criar credenciais de aplicativos da Web. Consulte Criar credenciais para instruções sobre como criar credenciais de aplicativos da Web. Ao concluir essa etapa, você terá um ID e um arquivo de chave secreta do cliente.

  2. Use o OAuth 2 Playground do Google para receber um token de acesso:

    1. Clique em "Configurações" e marque Usar suas próprias credenciais de autenticação.
    2. Insira o ID e a chave secreta do cliente da etapa 1.
    3. Clique em Fechar.
    4. No campo de escopos, digite https://www.googleapis.com/auth/cloud_search.settings e clique em Autorizar. O OAuth 2 Playground retorna um código de autorização.
    5. Clique em Trocar código de autorização dos tokens. Um token é retornado.
  3. Para inicializar o suporte de terceiros do Cloud Search, use o seguinte comando curl. Substitua [YOUR_ACCESS_TOKEN] pelo token fornecido na etapa 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
    

    Se funcionar, o corpo da resposta vai ter uma instância de operation. Exemplo:

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

    Caso contrário, entre em contato com o suporte do Cloud Search.

  4. Use operations.get para verificar se o suporte de terceiros foi inicializado:

    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
    

    Quando a inicialização de terceiros for concluída, ele conterá o campo done definido como true. Exemplo:

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

Criar a fonte de dados

Em seguida, crie uma fonte de dados no Admin Console. A fonte de dados fornece um namespace para indexar conteúdo usando o conector.

  1. Abra o Google Admin Console.
  2. Clique no ícone "Apps". A página "Administração dos apps" é exibida.
  3. Clique em Google Workspace. A página "Apps de administração do Google Workspace" aparece.
  4. Role para baixo e clique em Cloud Search. A página "Configurações do Google Workspace" é exibida.
  5. Clique em Origens de dados de terceiros. A página "Fontes de dados" é exibida.
  6. Clique no botão amarelo redondo +. A caixa de diálogo "Adicionar nova fonte de dados" será exibida.
  7. No campo Nome de exibição, digite "tutorial".
  8. No campo Endereços de e-mail da conta de serviço, insira o endereço de e-mail da conta de serviço criada na seção anterior. Se você não souber o endereço de e-mail da conta de serviço, procure o valor na página de contas de serviço.
  9. Clique em ADICIONAR. A caixa de diálogo "Fonte de dados criada com sucesso" é exibida.
  10. Clique em *OK. Anote o ID da fonte recém-criada. O ID da origem é usado para configurar o conector de conteúdo.

Gerar um token de acesso pessoal para a API do GitHub

O conector requer acesso autenticado à API do GitHub para ter cota suficiente. Para simplificar, o conector aproveita tokens de acesso pessoal em vez do OAuth. Os tokens pessoais permitem a autenticação como um usuário com um conjunto limitado de permissões semelhante ao OAuth.

  1. Faça login no GitHub.
  2. No canto superior direito, clique na sua foto do perfil. Será exibido um menu suspenso.
  3. Clique em Configurações.
  4. Clique em Configurações do desenvolvedor.
  5. Clique em Personal access tokens.
  6. Clique em Gerar token de acesso pessoal.
  7. No campo Observação, digite "Tutorial do Cloud Search".
  8. Confira o escopo public_repo.
  9. Clique em Gerar token.
  10. Anote o token gerado. Ele é usado pelo conector para chamar as APIs do GitHub e fornece cota de API para realizar a indexação.

Configurar o conector

Depois de criar as credenciais e a fonte de dados, atualize a configuração do conector para incluir estes valores:

  1. Na linha de comando, altere o diretório para cloud-search-samples/end-to-end/connector/.
  2. Abra o arquivo sample-config.properties com um editor de texto.
  3. Defina o parâmetro api.serviceAccountPrivateKeyFile como o caminho do arquivo das credenciais do serviço que você transferiu por download anteriormente.
  4. Defina o parâmetro api.sourceId como o ID da fonte de dados que você criou anteriormente.
  5. Defina o parâmetro github.user com seu nome de usuário do GitHub.
  6. Defina o parâmetro github.token como o token de acesso criado anteriormente.
  7. Salve o arquivo.

Atualizar o esquema

O conector indexa conteúdo estruturado e não estruturado. Antes de indexar os dados, é necessário atualizar o esquema da fonte de dados. Execute o seguinte comando para atualizar o esquema:

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

Executar o conector.

Para executar o conector e iniciar a indexação, execute o comando:

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

A configuração padrão do conector é indexar um único repositório na organização googleworkspace. A indexação do repositório leva cerca de um minuto. Após a indexação inicial, o conector continua pesquisando alterações no repositório que precisam ser refletidas no índice do Cloud Search.

Como revisar o código

As seções restantes examinam como o conector é criado.

Como iniciar o aplicativo

O ponto de entrada do conector é a classe GithubConnector. O método main instancia o IndexingApplication do SDK e o inicia.

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();
}

O ListingConnector fornecido pelo SDK implementa uma estratégia de travessia que usa as filas do Cloud Search para rastrear o estado dos itens no índice. Ele delega para GithubRepository, implementado pelo conector de amostra, para acessar o conteúdo do GitHub.

Como percorrer os repositórios do GitHub

Durante as travessias completas, o método getIds() é chamado para enviar itens que talvez precisem ser indexados na fila.

O conector pode indexar vários repositórios ou organizações. Para minimizar o impacto de uma falha, um repositório do GitHub é transferido de cada vez. Um checkpoint é retornado com os resultados da travessia que contém a lista de repositórios a serem indexados nas chamadas subsequentes para getIds(). Se ocorrer um erro, a indexação será retomada no repositório atual em vez de começar do início.

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));
  }
}

O método collectRepositoryItems() processa a travessia de um único repositório do GitHub. Esse método retorna uma coleção de ApiOperations que representa os itens a serem enviados para a fila. Os itens são enviados como um nome de recurso e um valor de hash que representa o estado atual do item.

O valor de hash é usado em travessias subsequentes dos repositórios do GitHub. Esse valor fornece uma verificação leve para determinar se o conteúdo foi alterado sem precisar fazer upload de mais conteúdo. O conector coloca todos os itens na fila sem às vezes. Se o item for novo ou o valor de hash tiver sido alterado, ele será disponibilizado para pesquisa na fila. Caso contrário, o item não é considerado modificado.

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;
}

Como processar a fila

Após a conclusão do traversal completo, o conector começa a pesquisar na fila os itens que precisam ser indexados. O método getDoc() é chamado para cada item extraído da fila. O método lê o item do GitHub e o converte na representação adequada para indexação.

Como o conector está sendo executado em dados ativos que podem ser alterados a qualquer momento, o getDoc() também verifica se o item na fila ainda é válido e exclui todos os itens do índice que não existem mais.

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));
  }
}

Para cada um dos objetos do GitHub que o conector indexa, o método indexItem() correspondente processa a criação da representação de item do Cloud Search. Por exemplo, para criar a representação de itens de conteúdo:

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();
}

Em seguida, implante a interface de pesquisa.

Anterior Próxima