Implantar o conector

Nesta página do tutorial do Cloud Search, você aprende a configurar uma origem de dados. e conector de conteúdo para indexação de dados. Para começar do início deste tutorial, consulte Tutorial de primeiros passos do Cloud Search

Criar o conector

Mude seu diretório de trabalho para 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 o Cloud Search. APIs de terceiros. Para criar as credenciais:

  1. Voltar para Console do Google Cloud:
  2. No painel de navegação à esquerda, clique em Credenciais. A "credencial" é exibida.
  3. Clique na lista suspensa + CRIAR CREDENCIAIS e selecione Conta de serviço. A opção "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 da conta de serviço). Esse valor será usado mais tarde.
  6. Clique em CRIAR. As "Permissões da conta de serviço (opcional)" é exibida.
  7. Clique em CONTINUAR. A página "Conceder aos usuários acesso a essa conta de serviço (opcional)" é exibida.
  8. Clique em CONCLUÍDO. As "Credenciais" aparece na tela.
  9. Em "Contas de serviço", clique no e-mail da conta. A página detalhes da conta" aplicativos de página.
  10. Em "Chaves", clique na lista suspensa ADICIONAR CHAVE e selecione Crie uma nova chave. O comando "Criar chave privada" é exibida.
  11. Clique em CRIAR.
  12. (opcional) Se a caixa "Você deseja permitir downloads console.cloud.google.com?” clique em Permitir.
  13. Um arquivo de chave privada é salvo no seu computador. Anote o local do arquivo baixado. Esse arquivo é usado na configuração do conector de conteúdo para ele pode se autenticar ao chamar as APIs do Google Cloud Search.

Inicializar o suporte de terceiros

Antes de chamar qualquer outra API do Cloud Search, você precisa inicializar para o Google Cloud Search.

Para inicializar o suporte de terceiros para o Cloud Search:

  1. Seu projeto de plataforma do Cloud Search contém as credenciais da conta de serviço. No entanto, para inicializar o suporte de terceiros, é necessário criar arquivos da Web credenciais do aplicativo. Para instruções sobre como criar um aplicativo da Web credenciais, consulte Crie credenciais. Ao concluir esta etapa, você terá um ID e um arquivo de chave secreta do cliente.

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

    1. Clique em "Configurações" e marque a opção 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 para o Cloud Search, use o seguinte curl: kubectl. Substitua [YOUR_ACCESS_TOKEN] pelo token recebido em 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. Por exemplo:

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

    Se não conseguir, entre em contato com o suporte do Cloud Search.

  4. Use operations.get para verificar se o suporte de terceiros é 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 do terceiro estiver concluída, ela conterá o O campo done foi 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". "Administração do Google Apps" é exibida.
  3. Clique em Google Workspace. "Apps Administração do Google Workspace" é exibida.
  4. Role para baixo e clique em Cloud Search. As "Configurações do Google Workspace" página aparece.
  5. Clique em Origens de dados de terceiros. As "Fontes de dados" é exibida.
  6. Clique no ícone amarelo circular +. A opção "Adicionar nova fonte de dados" é exibida.
  7. No campo Display name, digite "tutorial".
  8. No campo Endereços de e-mail da conta de serviço, digite o endereço de e-mail de a conta de serviço que você criou na seção anterior. Se você não souber o endereço de e-mail da conta de serviço, procure o valor em as contas de serviço página.
  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. A O ID da origem é usado para configurar o conector de conteúdo.

Gerar um token de acesso pessoal para a API GitHub

O conector exige acesso autenticado à API do GitHub para ter cota suficiente. Para simplificar, o conector usa endereços de acesso em vez do OAuth. Tokens pessoais permitem que a autenticação 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 Tokens de acesso pessoal.
  6. Clique em Generate personal access token.
  7. No campo Observação, digite "Tutorial do Cloud Search".
  8. Verifique o escopo public_repo.
  9. Clique em Gerar token.
  10. Anote o token gerado. Ele é usado pelo conector para chamar o GitHub e fornece uma cota de API para realizar a indexação.

Configurar o conector

Depois de criar as credenciais e a fonte de dados, atualize o conector para incluir esses 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 da credenciais de serviço que você transferiu por download anteriormente.
  4. Defina o parâmetro api.sourceId como o ID da fonte de dados que você criados anteriormente.
  5. Defina o parâmetro github.user como seu nome de usuário do GitHub.
  6. Defina o parâmetro github.token como o token de acesso que você criou anteriormente.
  7. Salve o arquivo.

Atualizar o esquema

O conector indexa conteúdo estruturado e não estruturado. Antes da indexação você precisa atualizar o esquema da fonte de dados. Execute este 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 a procurar alterações no repositório que precisa ser refletido no índice do Cloud Search.

Como revisar o código

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

Como iniciar o aplicativo

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

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 fornecidos pelo SDK implementa uma estratégia de travessia que usa as filas do Cloud Search usado 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 travessias completas, o 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 o impacto de uma falha, um repositório do GitHub é transferido por vez. Um checkpoint. é retornado com os resultados da travessia que contém a lista de para serem indexados nas chamadas subsequentes para getIds(). Se ocorrer um erro a indexação é retomada no repositório atual em vez de iniciar desde o 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 uma única 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 do recurso e um valor de hash que representa o estado atual do item.

O valor de hash é usado em travessias subsequentes do GitHub repositórios. Esse valor fornece uma verificação leve para determinar se o conteúdo foi alterado sem precisar enviar conteúdo adicional. O conector às cegas coloca todos os itens na fila. Se o item for novo ou o valor de hash for alterado, ele será disponíveis para consulta na fila. Caso contrário, o item é considerado inalterado.

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 da travessia completa, o conector começa a sondar de itens que precisam ser indexados. O getDoc() é chamado para cada item extraído da fila. O método lê o item do GitHub e o converte na representação correta para indexação.

Como o conector está sendo executado em dados ativos que podem ser alterados a qualquer tempo, 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 O método indexItem() processa a criação da representação do item para Cloud Search. Por exemplo, para construir 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