Bağlayıcıyı dağıtma

Cloud Search eğitim kılavuzunun bu sayfasında, verileri dizine eklemek için veri kaynağı ve içerik bağlayıcısının nasıl oluşturulacağı gösterilmektedir. Bu eğiticinin başından başlamak için Cloud Search'i kullanmaya başlama eğiticisine göz atın.

Bağlayıcıyı oluşturma

Çalışma dizininizi cloud-search-samples/end-to-end/connector dizinine değiştirin ve bu komutu çalıştırın:

mvn package -DskipTests

Komut, içerik bağlayıcıyı oluşturmak için gereken bağımlılıkları indirir ve kodu derler.

Hizmet hesabı kimlik bilgileri oluşturma

Bağlayıcı, Cloud Search API'lerini çağırmak için hizmet hesabı kimlik bilgilerini gerektirir. Kimlik bilgilerini oluşturmak için:

  1. Google Cloud Console'a dönün.
  2. Soldaki gezinme panelinde Kimlik bilgileri'ni tıklayın. "Kimlik bilgisi" sayfası görünür.
  3. + KİMLİK BİLGİSİ OLUŞTUR açılır listesini tıklayın ve Hizmet hesabı'nı seçin. "Hizmet hesabı oluştur" sayfası görünür.
  4. Hizmet hesabı adı alanına "tutorial" yazın.
  5. Hizmet hesabı kimliği değerini (Hizmet hesabı adının hemen ardından) not edin. Bu değer daha sonra kullanılır.
  6. OLUŞTUR'u tıklayın. "Hizmet hesabı izinleri (isteğe bağlı)" iletişim kutusu görünür.
  7. DEVAM'ı tıklayın. "Kullanıcılara bu hizmet hesabına erişim izni verin (isteğe bağlı)" iletişim kutusu görünür.
  8. BİTTİ'yi tıklayın. "Kimlik bilgileri" ekranı görünür.
  9. Hizmet Hesapları bölümünde, hizmet hesabı e-posta adresini tıklayın. "Hizmet hesabı ayrıntıları" sayfası açılır.
  10. Anahtarlar bölümünde ANAHTAR EKLE açılır listesini tıklayın ve Yeni anahtar oluştur'u seçin. "Özel anahtar oluştur" iletişim kutusu görüntülenir.
  11. OLUŞTUR'u tıklayın.
  12. (isteğe bağlı) "console.cloud.google.com adresinde indirme işlemlerine izin vermek istiyor musunuz?" iletişim kutusu görünürse İzin ver'i tıklayın.
  13. Bilgisayarınıza bir özel anahtar dosyası kaydedilir. İndirilen dosyanın konumunu not edin. Bu dosya, içerik bağlayıcıyı Google Cloud Search API'lerini çağırırken kimliğini doğrulayabilmesi için yapılandırmak amacıyla kullanılır.

Üçüncü taraf desteğini başlatma

Diğer Cloud Search API'lerini çağırabilmek için Google Cloud Search için üçüncü taraf desteğini başlatmanız gerekir.

Cloud Search için üçüncü taraf desteğini başlatmak üzere:

  1. Cloud Search Platform projeniz hizmet hesabı kimlik bilgileri içeriyor. Ancak üçüncü taraf desteğini başlatmak için web uygulaması kimlik bilgileri oluşturmanız gerekir. Web uygulaması kimlik bilgilerini oluşturma talimatları için Kimlik bilgileri oluşturma başlıklı makaleyi inceleyin. Bu adımı tamamladıktan sonra bir istemci kimliğiniz ve istemci gizli anahtar dosyanız olmalıdır.

  2. Erişim jetonu almak için Google'ın OAuth 2 Playground'unu kullanın:

    1. Ayarlar'ı tıklayın ve Kendi kimlik doğrulama kimlik bilgilerinizi kullanın'ı işaretleyin.
    2. 1. adımda kopyaladığınız istemci kimliğini ve istemci gizli anahtarını girin.
    3. Kapat'ı tıklayın.
    4. Kapsamlar alanına https://www.googleapis.com/auth/cloud_search.settings yazın ve Yetkilendir'i tıklayın. OAuth 2 Playground bir yetkilendirme kodu döndürür.
    5. Jetonlar için yetkilendirme kodu değiş tokuşu yap'ı tıklayın. Bir jeton döndürülür.
  3. Cloud Search için üçüncü taraf desteğini başlatmak üzere aşağıdaki curl komutunu kullanın. [YOUR_ACCESS_TOKEN] değerini 2. adımda elde edilen jetonla değiştirdiğinizden emin olun.

    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
    

    Başarılı olursa yanıt gövdesi, operation öğesinin bir örneğini içerir. Örneğin:

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

    Bu işlem başarısız olursa Cloud Search Destek Ekibi ile iletişime geçin.

  4. Üçüncü taraf desteğinin başlatıldığını doğrulamak için operations.get işlevini kullanın:

    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
    

    Üçüncü taraf başlatma işlemi tamamlandığında done alanını true olarak ayarlar. Örneğin:

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

Veri kaynağını oluşturma

Ardından, Yönetici Konsolu'nda bir veri kaynağı oluşturun. Veri kaynağı, bağlayıcıyı kullanarak içeriği dizine eklemek için bir ad alanı sağlar.

  1. Google Yönetici Konsolu'nu açın.
  2. Uygulamalar simgesini tıklayın. "Uygulama yönetimi" sayfası görünür.
  3. Google Workspace'i tıklayın. "Apps Google Workspace yönetimi" sayfası görünür.
  4. Aşağı kaydırın ve Cloud Search'ı tıklayın. "Google Workspace Ayarları" sayfası gösterilir.
  5. Üçüncü taraf veri kaynakları'nı tıklayın. "Veri Kaynakları" sayfası görünür.
  6. Yuvarlak sarı + simgesini tıklayın. "Yeni veri kaynağı ekle" iletişim kutusu görünür.
  7. Görünen ad alanına "tutorial" yazın.
  8. Hizmet hesabı e-posta adresleri alanına, önceki bölümde oluşturduğunuz hizmet hesabının e-posta adresini girin. Hizmet hesabının e-posta adresini bilmiyorsanız değeri hizmet hesapları sayfasında arayın.
  9. EKLE'yi tıklayın. "Veri kaynağı başarıyla oluşturuldu" iletişim kutusu görüntülenir.
  10. *Tamam'ı tıklayın. Yeni oluşturulan veri kaynağının Kaynak Kimliği'ni not edin. Kaynak kimliği, içerik bağlayıcısını yapılandırmak için kullanılır.

GitHub API için kişisel erişim jetonu oluşturma

Bağlantılayıcının yeterli kotaya sahip olması için GitHub API'ye kimlik doğrulaması yapılmış erişim gerekir. Bağlantılayıcı, basitlik sağlamak için OAuth yerine kişisel erişim jetonlarından yararlanır. Kişisel jetonlar, OAuth'a benzer sınırlı izinlere sahip bir kullanıcı olarak kimlik doğrulamayı sağlar.

  1. GitHub'a giriş yapın.
  2. Sağ üst köşeden profil resminizi tıklayın. Açılır menü görüntülenir.
  3. Ayarlar'ı tıklayın.
  4. Geliştirici ayarları'nı tıklayın.
  5. Kişisel erişim jetonları'nı tıklayın.
  6. Kişisel erişim jetonu oluştur'u tıklayın.
  7. Not alanına "Cloud Search eğitimi" yazın.
  8. public_repo kapsamını kontrol edin.
  9. Jeton oluştur'u tıklayın.
  10. Oluşturulan jetonu not edin. GitHub API'lerini çağırmak için bağlayıcı tarafından kullanılır ve dizine ekleme işlemini gerçekleştirmek için API kotası sağlar.

Bağlayıcıyı yapılandırma

Kimlik bilgilerini ve veri kaynağını oluşturduktan sonra, bağlayıcı yapılandırmasını aşağıdaki değerleri içerecek şekilde güncelleyin:

  1. Komut satırından dizini cloud-search-samples/end-to-end/connector/ olarak değiştirin.
  2. sample-config.properties dosyasını bir metin düzenleyicide açın.
  3. api.serviceAccountPrivateKeyFile parametresini, daha önce indirdiğiniz hizmet kimlik bilgilerinin dosya yoluna ayarlayın.
  4. api.sourceId parametresini daha önce oluşturduğunuz veri kaynağının kimliğine ayarlayın.
  5. github.user parametresini GitHub kullanıcı adınıza ayarlayın.
  6. github.token parametresini daha önce oluşturduğunuz erişim jetonu olarak ayarlayın.
  7. Dosyayı kaydedin.

Şemayı güncelleme

Bağlantılayıcı hem yapılandırılmış hem de yapılandırılmamış içerikleri dizine ekler. Verileri dizine eklemeden önce veri kaynağının şemasını güncellemeniz gerekir. Şemayı güncellemek için aşağıdaki komutu çalıştırın:

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

Bağlayıcıyı çalıştırma

Bağlayıcıyı çalıştırmak ve dizine eklemeye başlamak için şu komutu çalıştırın:

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

Bağlayıcının varsayılan yapılandırması, googleworkspace kuruluşundaki tek bir deposu dizine eklemektir. Deponun dizine eklenmesi yaklaşık 1 dakika sürer. İlk dizine ekleme işleminden sonra bağlayıcı, depoda Cloud Search dizine yansıtılması gereken değişiklikler olup olmadığını kontrol etmeye devam eder.

Kodu inceleme

Kalan bölümlerde, konnektörün nasıl oluşturulduğu incelenmektedir.

Uygulamayı başlatma

Bağlayıcının giriş noktası GithubConnector sınıfıdır. main yöntemi, SDK'nın IndexingApplication sınıfını örneklendirir ve başlatır.

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

SDK tarafından sağlanan ListingConnector, dizindeki öğelerin durumunu izlemek için Cloud Search sıralarından yararlanan bir tarama stratejisi uygular. GitHub'daki içeriğe erişmek için örnek bağlayıcı tarafından uygulanan GithubRepository işlevine yetki verir.

GitHub depolarını gezme

Tam tarama sırasında, dizine eklenmeleri gerekebilecek öğeleri sıraya eklemek için getIds() yöntemi çağrılır.

Bağlantılayıcı, birden fazla depoyu veya kuruluşu dizine ekleyebilir. Bir hatanın etkisini en aza indirmek için tek seferde bir GitHub deposu taranır. getIds() çağrılarına sonraki çağrılarda dizine eklenecek depoların listesini içeren bir kontrol noktası, tarama sonuçlarıyla birlikte döndürülür. Bir hata meydana gelirse dizine ekleme işlemi baştan başlamak yerine mevcut depodan devam eder.

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

collectRepositoryItems() yöntemi, tek bir GitHub deposunun taranmasını yönetir. Bu yöntem, sıraya eklenecek öğeleri temsil eden bir ApiOperations koleksiyonu döndürür. Öğeler, kaynak adı ve öğenin mevcut durumunu temsil eden karma oluşturma değeri olarak itilir.

Karma oluşturma değeri, GitHub depolarının sonraki geçişlerinde kullanılır. Bu değer, ek içerik yüklemek zorunda kalmadan içeriğin değişip değişmediğini belirlemek için basit bir kontrol sağlar. Bağlantılayıcı, tüm öğeleri sıraya koyar. Öğe yeniyse veya karma değeri değiştiyse kuyrukta sorgulanabilir hale getirilir. Aksi takdirde öğe değiştirilmemiş kabul edilir.

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

Sırayı işleme

Tam tarama tamamlandıktan sonra bağlayıcı, dizine eklenmesi gereken öğeler için sırayı yoklamaya başlar. getDoc() yöntemi, sıradan alınan her öğe için çağrılır. Yöntem, öğeyi GitHub'dan okur ve dizine ekleme için uygun şekilde dönüştürür.

Bağlayıcı, herhangi bir zamanda değiştirilebilecek canlı verilerle çalıştığından getDoc(), sıradaki öğenin hâlâ geçerli olup olmadığını da doğrular ve artık mevcut olmayan tüm öğeleri dizinden siler.

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

Bağlayıcının dizine eklediği GitHub nesnelerinin her biri için Cloud Search'ın öğe temsilini oluşturma işlemi, ilgili indexItem() yöntemi tarafından yönetilir. Örneğin, içerik öğelerinin temsilini oluşturmak için:

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

Ardından arama arayüzünü dağıtın.

Önceki Sonraki