master 58f8ba1ce813 cached
201 files
1.6 MB
447.5k tokens
66 symbols
1 requests
Download .txt
Showing preview only (1,729K chars total). Download the full file or copy to clipboard to get everything.
Repository: GoogleCloudPlatform/workflows-demos
Branch: master
Commit: 58f8ba1ce813
Files: 201
Total size: 1.6 MB

Directory structure:
gitextract_rgftideu/

├── .github/
│   └── sync-repo-settings.yaml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── batch-translation/
│   ├── README.md
│   └── workflow.yaml
├── bigquery-parallel/
│   ├── README.md
│   ├── workflow-parallel.yaml
│   └── workflow-serial.yaml
├── bigtable-ai/
│   └── vertex-vector-search/
│       ├── tests/
│       │   ├── noxfile.py
│       │   ├── requirements.txt
│       │   └── system/
│       │       ├── workflow-input.json
│       │       └── workflow-test.py
│       └── workflows/
│           ├── README.md
│           ├── batch-export.yaml
│           └── sample-batch-input.json
├── callback-basic/
│   ├── README.md
│   ├── workflow-get.yaml
│   └── workflow-post.yaml
├── callback-event/
│   ├── README.md
│   ├── architecture.drawio
│   ├── callback-event-listener.yaml
│   ├── callback-event-sample.yaml
│   └── setup.sh
├── callback-translation/
│   ├── README.MD
│   ├── architecture-translation.drawio
│   ├── invokeTranslationWorkflow/
│   │   ├── index.js
│   │   └── package.json
│   ├── public/
│   │   ├── index.html
│   │   ├── script.js
│   │   └── style.css
│   ├── translation-validation.yaml
│   └── translationCallbackCall/
│       ├── index.js
│       └── package.json
├── cloud-run-jobs/
│   ├── README.md
│   └── workflow.yaml
├── cloud-run-jobs-payload-gcs/
│   ├── README.md
│   ├── create-trigger.sh
│   ├── deploy-workflow.sh
│   ├── message-payload-job/
│   │   ├── Procfile
│   │   ├── deploy-job.sh
│   │   ├── process.py
│   │   └── requirements.txt
│   └── workflow.yaml
├── connector-compute/
│   ├── README.md
│   ├── create-stop-vm-connector.yaml
│   ├── create-vm-connector.yaml
│   ├── create-vm.yaml
│   └── stop-vm.yaml
├── gcs-dlp/
│   ├── README.md
│   ├── dlp-gcs-workflow.yaml
│   └── functions/
│       ├── get_dlp_job_status.py
│       ├── inspect_gcs_file.py
│       └── trigger-dlp-workflow.py
├── gcs-read-write-json/
│   ├── README.md
│   ├── gcs-env-var-workflow.yaml
│   ├── gcs-read-workflow.yaml
│   └── gcs-write-workflow.yaml
├── gitops/
│   ├── README.md
│   ├── cloudbuild.yaml
│   ├── images/
│   │   └── architecture.drawio
│   ├── setup.sh
│   ├── test-master.sh
│   ├── test-staging.sh
│   └── workflow.yaml
├── long-running-container/
│   ├── PrimeGenService/
│   │   ├── Controllers/
│   │   │   └── PrimeGenController.cs
│   │   ├── Dockerfile
│   │   ├── PrimeGenService.csproj
│   │   └── Program.cs
│   ├── README.md
│   └── prime-generator.yaml
├── multi-env-deployment/
│   ├── README.md
│   ├── cloudbuild.yaml
│   ├── images/
│   │   └── architecture.drawio
│   ├── main.tf
│   ├── setup.sh
│   ├── workflow1.yaml
│   ├── workflow2.yaml
│   └── workflow3.yaml
├── reddit-sentiment/
│   ├── README.md
│   └── workflow.yaml
├── retries-and-saga/
│   ├── CustomerService/
│   │   ├── Controllers/
│   │   │   └── CustomerController.cs
│   │   ├── Credit.cs
│   │   ├── CustomerService.csproj
│   │   ├── Dockerfile
│   │   └── Program.cs
│   ├── OrderService/
│   │   ├── Controllers/
│   │   │   └── OrderController.cs
│   │   ├── Dockerfile
│   │   ├── Order.cs
│   │   ├── OrderService.csproj
│   │   └── Program.cs
│   ├── README.md
│   ├── ordering-v1.yaml
│   ├── ordering-v2.yaml
│   └── ordering-v3.yaml
├── screenshot-jobs/
│   ├── README.md
│   ├── job1.txt
│   ├── job2.txt
│   └── workflow.yaml
├── secretmanager/
│   ├── README.md
│   ├── access-secret.yaml
│   └── create-secret.yaml
├── send-email/
│   ├── README.md
│   └── send-email-workflow.yaml
├── service-chaining/
│   ├── README.md
│   ├── floor/
│   │   ├── Dockerfile
│   │   └── app.py
│   ├── multiply/
│   │   ├── main.py
│   │   └── requirements.txt
│   ├── randomgen/
│   │   ├── main.py
│   │   └── requirements.txt
│   └── workflow.yaml
├── state-management-firestore/
│   ├── README.md
│   └── workflow.yaml
├── syntax-cheat-sheet/
│   ├── printable-pdf/
│   │   ├── index.html
│   │   └── style.css
│   └── workflow.yaml
├── terraform/
│   ├── basic/
│   │   ├── README.md
│   │   ├── main.tf
│   │   └── vars.tf
│   ├── import-multiple-yamls/
│   │   ├── README.md
│   │   ├── main.tf
│   │   ├── subworkflow.yaml
│   │   ├── vars.tf
│   │   └── workflow.yaml
│   └── import-yaml/
│       ├── README.md
│       ├── main.tf
│       ├── vars.tf
│       └── workflow.yaml
├── twitter-sentiment/
│   ├── README.md
│   └── workflow.yaml
├── twitter-sentiment-parallel/
│   ├── README.md
│   ├── workflow-parallel.yaml
│   └── workflow-serial.yaml
├── vertexai/
│   ├── country-histories/
│   │   ├── gemini-pro/
│   │   │   ├── README.md
│   │   │   ├── country-histories-connector.yaml
│   │   │   ├── country-histories.yaml
│   │   │   └── gemini-response.json
│   │   └── text-bison/
│   │       ├── README.md
│   │       ├── country-histories-connector.yaml
│   │       ├── country-histories.yaml
│   │       ├── country-history-connector.yaml
│   │       └── country-history.yaml
│   ├── describe-image/
│   │   ├── README.md
│   │   ├── describe-image-connector.yaml
│   │   ├── describe-image.yaml
│   │   └── gemini-response.json
│   └── parallel-summaries/
│       ├── README.md
│       ├── parallel-summaries-connector.yaml
│       ├── parallel-summaries-initial.yaml
│       ├── parallel-summaries.yaml
│       ├── pride_and_prejudice.txt
│       ├── pride_and_prejudice_1000.txt
│       └── pride_and_prejudice_4500.txt
├── workflow-executes-other-workflows/
│   ├── README.md
│   ├── workflow-child.yaml
│   └── workflow-parent.yaml
├── workflow-tasks-workflow/
│   ├── README.md
│   ├── workflow-child.yaml
│   └── workflow-parent.yaml
├── workflows-bigquery-load/
│   ├── README.md
│   ├── build.sh
│   ├── file_change_handler/
│   │   ├── main.py
│   │   └── requirements.txt
│   ├── generator/
│   │   ├── gen.py
│   │   └── requirements.txt
│   ├── main.tf
│   ├── workflow.yaml
│   └── workflow_handlers/
│       ├── main.py
│       └── requirements.txt
├── workflows-eventarc-integration/
│   ├── event-payload-storer/
│   │   ├── README.md
│   │   ├── event-payload-storer.yaml
│   │   ├── setup.sh
│   │   ├── test_pubsub.sh
│   │   └── test_storage.sh
│   └── workflows-pubsub/
│       ├── README.md
│       └── workflow.yaml
├── workflows-executes-commands/
│   ├── using-cloudbuild-api/
│   │   ├── README.md
│   │   ├── setup.sh
│   │   ├── workflow-gcloud.yaml
│   │   └── workflow-kubectl.yaml
│   └── using-standard-library/
│       ├── README.md
│       ├── setup.sh
│       ├── workflow-gcloud.yaml
│       └── workflow-kubectl.yaml
├── workflows-kubernetes-engine/
│   ├── README.md
│   └── workflow.yaml
└── workspace-integration/
    ├── sheets-to-workflows/
    │   ├── Code.gs
    │   ├── README.md
    │   ├── appscript.json
    │   ├── setup.sh
    │   └── workflow.yaml
    ├── workflows-awaits-sheets-callback/
    │   ├── Code.gs
    │   ├── README.md
    │   ├── appscript.json
    │   ├── setup.sh
    │   └── workflow.yaml
    └── workflows-to-sheets/
        ├── README.md
        ├── setup.sh
        └── workflow.yaml

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/sync-repo-settings.yaml
================================================
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Whether or not rebase-merging is enabled on this repository.
# Defaults to `true`
rebaseMergeAllowed: true

# Whether or not squash-merging is enabled on this repository.
# Defaults to `true`
squashMergeAllowed: true

# Whether or not PRs are merged with a merge commit on this repository.
# Defaults to `false`
mergeCommitAllowed: false

# Rules for main branch protection
branchProtectionRules:
# Identifies the protection rule pattern. Name of the branch to be protected.
# Defaults to `main`
- pattern: main
  # Can admins overwrite branch protection.
  # Defaults to `true`
  isAdminEnforced: false
  # Number of approving reviews required to update matching branches.
  # Defaults to `1`
  requiredApprovingReviewCount: 1
  # Are reviews from code owners required to update matching branches.
  # Defaults to `false`
  requiresCodeOwnerReviews: true
  # Require up to date branches
  requiresStrictStatusChecks: false


================================================
FILE: .gitignore
================================================
# Local .terraform directories
**/.terraform*

# .tfstate files
*.tfstate
*.tfstate.*

# Node.js
**/node_modules/**
npm-debug.log
package-lock.json

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/
.DS_Store

gcs-dlp/LocalTestingFunctions/*
gcs-dlp/supporting-documents/*

# .NET stuff

# User-specific files
*.vs/
*.vscode/
*.suo
*.user
*.sln.docstates
*log.txt

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
x64/
# build/
bld/
[Bb]in/
[Oo]bj/


================================================
FILE: CONTRIBUTING.md
================================================
# How to Contribute

We'd love to accept your patches and contributions to this project. There are
just a few small guidelines you need to follow.

## Contributor License Agreement

Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution;
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to <https://cla.developers.google.com/> to see
your current agreements on file or to sign a new one.

You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.

## Code reviews

All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.

## Community Guidelines

This project follows
[Google's Open Source Community Guidelines](https://opensource.google.com/conduct/).


================================================
FILE: LICENSE
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
# Workflows Samples

![Workflows Logo](Workflows-128-color.png)

[Workflows](https://cloud.google.com/workflows) allow you to orchestrate and
automate Google Cloud and HTTP-based API services with serverless workflows.

This repository contains a collection of samples for Workflows for various use
cases.

## Slides

There's a
[presentation](https://speakerdeck.com/meteatamel/serverless-orchestration-with-workflows)
that explains Workflows.

<a href="https://speakerdeck.com/meteatamel/serverless-orchestration-with-workflows">
    <img alt="Workflows presentation" src="serverless-orchestration-with-workflows.png" width="50%" height="50%">
</a>

## Samples

* [Workflows syntax cheat sheet](syntax-cheat-sheet/workflow.yaml)
* [Create, start, stop VM using Compute Connector](connector-compute)
* [Write and read JSON files into GCS](gcs-read-write-json/)
* [Send an email with SendGrid from a workflow](send-email)
* [Data Loss Prevention workflow](gcs-dlp)
* [Service chaining](service-chaining)
* [Create and Access Secrets in Secret Manager](secretmanager)
* Eventarc and Workflows
  * [Eventarc (Cloud Storage) and Workflows](https://github.com/GoogleCloudPlatform/eventarc-samples/blob/main/eventarc-workflows-integration/eventarc-storage)
  * [Eventarc (AuditLog-Cloud Storage), Cloud Run and Workflows](https://github.com/GoogleCloudPlatform/eventarc-samples/blob/main/eventarc-workflows-integration/eventarc-auditlog-storage-cloudrun)
  * [Eventarc (Pub/Sub) and Workflows](https://github.com/GoogleCloudPlatform/eventarc-samples/blob/main/eventarc-workflows-integration/eventarc-pubsub)
  * [Eventarc (Pub/Sub), Cloud Run and Workflows](https://github.com/GoogleCloudPlatform/eventarc-samples/blob/main/eventarc-workflows-integration/eventarc-pubsub-cloudrun)
  * [Workflows and Eventarc (Pub/Sub)](workflows-eventarc-integration/workflows-pubsub)
  * [Image processing pipeline v2 - Eventarc (Cloud Storage) + Cloud Run + Workflows](https://github.com/GoogleCloudPlatform/eventarc-samples/tree/main/processing-pipelines/image-v2)
  * [Image processing pipeline v3 - Eventarc (Cloud Storage) + Workflows](https://github.com/GoogleCloudPlatform/eventarc-samples/tree/main/processing-pipelines/image-v3)
  * [Event payload storer](workflows-eventarc-integration/event-payload-storer)
* Terraform samples
  * [Basic Terraform](terraform/basic)
  * [Terraform with imported YAML](terraform/import-yaml)
  * [Terraform with multiple imported YAMLs](terraform/import-multiple-yamls)
* Machine Learning and iteration samples
  * [Batch Translation using Translation API connector](batch-translation)
  * [Reddit sentiment analysis using Language API connector and iteration](reddit-sentiment)
  * [Twitter sentiment analysis using Language API connector and iteration](twitter-sentiment)
  * [Twitter sentiment analysis using Language API connector and parallel iteration](twitter-sentiment-parallel)
* Callback samples
  * [Basic callback endpoint sample](callback-basic)
  * [Event callback sample](callback-event)
  * [Human validation of text translation via Workflows callback](callback-translation)
  * [Manager approval of expense reports thanks to Workflows callbacks](https://github.com/GoogleCloudPlatform/smart-expenses)
* [Retries and Saga Pattern in Workflows](retries-and-saga)
* [Long running containers with Workflows and Compute Engine](long-running-container)
* [Workflows state management with Firestore](state-management-firestore)
* Cloud Run and Workflows samples
  * [Execute a Cloud Run job using Workflows](cloud-run-jobs)
  * [Execute a Cloud Run job using Workflows and event payload from Cloud Storage](cloud-run-jobs-payload-gcs)
  * [Take screenshots of webpages with Cloud Run jobs, Workflows and Eventarc](screenshot-jobs)
* Batch and Workflows samples
  * [Batch - simple container](https://github.com/GoogleCloudPlatform/batch-samples/tree/main/busybox)
  * [Batch - prime number generator container](https://github.com/GoogleCloudPlatform/batch-samples/tree/main/primegen)
* [Running BigQuery jobs against Wikipedia dataset with Workflows parallel iteration](bigquery-parallel)
* Workflows lifecycle
  * [Git-driven development, testing & deployment](gitops)
  * [Multi-environment deployment](multi-env-deployment)
* Google Workspace and Workflows
  * [Triggering Workflows from Google Sheets](workspace-integration/sheets-to-workflows)
  * [Writing to Google Sheets from Workflows](workspace-integration/workflows-to-sheets)
  * [Workflows that pause and wait for human approvals from Google Sheets](workspace-integration/workflows-awaits-sheets-callback)
* Workflows executes commands (gcloud, kubectl)
  * [Workflows executes commands (gcloud, kubectl) - using Cloud Build API](workflows-executes-commands/using-cloudbuild-api/)
  * [Workflows executes commands (gcloud, kubectl) - using standard library](workflows-executes-commands/using-standard-library/)
* [Load data from Cloud Storage to BigQuery using Workflows](/workflows-bigquery-load/)
* [A workflow executes other workflows in parallel](./workflow-executes-other-workflows/)
* [Buffer workflow executions with a Cloud Tasks queue](./workflow-tasks-workflow/)
* [Deploy a Kubernetes application with Workflows](./workflows-kubernetes-engine/)
* Vertex AI and Workflows
  * [Call VertexAI PaLM 2 for Text (text-bison) in parallel](./vertexai/country-histories/text-bison/)
  * [Call VertexAI Gemini Pro in parallel](./vertexai/country-histories/gemini-pro/)
  * [Call VertexAI Gemini Pro Vision to describe an image](./vertexai/describe-image/)
  * [Summarize a long document with Gemini Pro](./vertexai/parallel-summaries)
* [Export from Bigtable to Vertex Vector Search](./bigtable-ai/vertex-vector-search/workflows/)

-------

This is not an official Google product.


================================================
FILE: batch-translation/README.md
================================================
# Batch Translation using Cloud Translation API connector

In this sample, you will see how to use [Cloud Translation API
connector](https://cloud.google.com/workflows/docs/reference/googleapis/translate/Overview)
to batch translate a number of text files in an input bucket into multiple
languages and save to an output bucket.

## Enable services

First, enable required services:

```sh
gcloud services enable \
  workflows.googleapis.com \
  translate.googleapis.com
```

## Create input bucket and files to translate

Create a Cloud Storage bucket that will hold the files to translate:

```sh
export BUCKET_INPUT=${GOOGLE_CLOUD_PROJECT}-input-files
gcloud storage buckets create gs://${BUCKET_INPUT}
```

Create two files in English to translate (or you can use your own files) and
upload to the input bucket:

```sh
echo "Dr. Watson, come here" > file1.txt
gcloud storage cp file1.txt gs://${BUCKET_INPUT}
echo "Hello World" > file2.txt
gcloud storage cp file2.txt gs://${BUCKET_INPUT}
```

## Define workflow

Create a `workflow.yaml` to define the workflow.

In the init step, assign some variables:

```yaml
main:
  steps:
  - init:
      assign:
      - projectId: ${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")}
      - location: ${sys.get_env("GOOGLE_CLOUD_LOCATION")}
      - inputBucketName: ${projectId + "-input-files"}
      - outputBucketName: ${projectId + "-output-files-" + string(int(sys.now()))}
```

In the second step, create a unique output bucket for translated texts:

```yaml
  - createOutputBucket:
        call: googleapis.storage.v1.buckets.insert
        args:
          query:
            project: ${projectId}
          body:
            name: ${outputBucketName}
```

Last step is to kick off the batch translation from the files in the input
bucket and save the results to the newly created output bucket. We are
translating English to Spanish and French using the Translation API connector:

```yaml
  - batchTranslateText:
      call: googleapis.translate.v3beta1.projects.locations.batchTranslateText
      args:
          parent: ${"projects/" + projectId + "/locations/" + location}
          body:
              inputConfigs:
                gcsSource:
                  inputUri: ${"gs://" + inputBucketName + "/*"}
              outputConfig:
                  gcsDestination:
                    outputUriPrefix: ${"gs://" + outputBucketName + "/"}
              sourceLanguageCode: "en"
              targetLanguageCodes: ["es", "fr"]
      result: batchTranslateTextResult
```

You can see the full [workflow.yaml](workflow.yaml).

## Deploy and execute workflow

Deploy workflow:

```sh
gcloud workflows deploy batch-translation \
    --source=workflow.yaml
```

Execute workflow:

```sh
gcloud workflows execute batch-translation
```

After a couple of minutes, you should see a new output bucket with translated
files!


================================================
FILE: batch-translation/workflow.yaml
================================================
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# [START workflows_batch_translation]
main:
  steps:
  - init:
      assign:
      - projectId: ${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")}
      - location: ${sys.get_env("GOOGLE_CLOUD_LOCATION")}
      - inputBucketName: ${projectId + "-input-files"}
      - outputBucketName: ${projectId + "-output-files-" + string(int(sys.now()))}
  - createOutputBucket:
        call: googleapis.storage.v1.buckets.insert
        args:
          project: ${projectId}
          body:
            name: ${outputBucketName}
  - batchTranslateText:
      call: googleapis.translate.v3beta1.projects.locations.batchTranslateText
      args:
          parent: ${"projects/" + projectId + "/locations/" + location}
          body:
              inputConfigs:
                gcsSource:
                  inputUri: ${"gs://" + inputBucketName + "/*"}
              outputConfig:
                  gcsDestination:
                    outputUriPrefix: ${"gs://" + outputBucketName + "/"}
              sourceLanguageCode: "en"
              targetLanguageCodes: ["es", "fr"]
      result: batchTranslateTextResult
# [END workflows_batch_translation]


================================================
FILE: bigquery-parallel/README.md
================================================
# Running BigQuery jobs against Wikipedia dataset with Workflows parallel iteration

In this sample, you will see how to use parallel
[iteration](https://cloud.google.com/workflows/docs/reference/syntax/iteration)
to run BigQuery jobs against Wikipedia dataset in parallel.

## Before you start

First, enable required services:

```sh
gcloud services enable \
  workflows.googleapis.com
```

Give the default compute service account the required roles:

```sh
PROJECT_ID=your-project-id
PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com \
    --role roles/logging.logWriter \
    --role roles/bigquery.jobUser
```

## Define workflow

Create a [workflow-parallel.yaml](workflow-parallel.yaml) to define the workflow.

In the `init` step, initialize `results` map to keep track of the results and
tables we want to read from:

```yaml
main:
    steps:
    - init:
        assign:
            - results : {} # result from each iteration keyed by table name
            - tables:
                - 201201h
                - 201202h
                - 201203h
                - 201204h
                - 201205h
```

Next, we define a `runQueries` step with `parallel` keyword with a for loop.
Each iteration of the for loop runs in parallel. We also define `results` as a
shared variable so each parallel iteration can access it.

In each loop, we run a BigQuery job, extract the result and save it to the
`results` map:

```yaml
    - runQueries:
        parallel:
            shared: [results]
            for:
                value: table
                in: ${tables}
                steps:
                - logTable:
                    call: sys.log
                    args:
                        text: ${"Running query for table " + table}
                - runQuery:
                    call: googleapis.bigquery.v2.jobs.query
                    args:
                        projectId: ${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")}
                        body:
                            useLegacySql: false
                            useQueryCache: false
                            timeoutMs: 30000
                            # Find top 100 titles with most views on Wikipedia
                            query: ${
                                "SELECT TITLE, SUM(views)
                                FROM `bigquery-samples.wikipedia_pageviews." + table + "`
                                WHERE LENGTH(TITLE) > 10
                                GROUP BY TITLE
                                ORDER BY SUM(VIEWS) DESC
                                LIMIT 100"
                                }
                    result: queryResult
                - returnResult:
                    assign:
                        # Return the top title from each table
                        - results[table]: {}
                        - results[table].title: ${queryResult.rows[0].f[0].v}
                        - results[table].views: ${queryResult.rows[0].f[1].v}
```

In the last step, we return the `results` map:

```yaml
    - returnResults:
        return: ${results}
```

You can see the full [workflow-parallel.yaml](workflow-parallel.yaml).

## Deploy and run workflow

Deploy workflow:

```sh
gcloud workflows deploy bigquery-parallel \
    --source=workflow-parallel.yaml
```

Run the workflow:

```sh
gcloud workflows run bigquery-parallel
```

Each BigQuery job takes about 20 seconds. Since, they all run in parallel, you
should see the result from all in about 20 seconds. Thanks to the parallel
iteration!

```json
{
  "201201h": {
    "title": "Special:Search",
    "views": "14591339"
  },
  "201202h": {
    "title": "Special:Search",
    "views": "132765420"
  },
  "201203h": {
    "title": "Special:Search",
    "views": "123316818"
  },
  "201204h": {
    "title": "Special:Search",
    "views": "116830614"
  },
  "201205h": {
    "title": "Special:Search",
    "views": "131357063"
  }
}
```

## Compare with non-parallel version

You can deploy and execute [workflow-serial.yaml](workflow-serial.yaml) to
compare the parallel version of the workflow with the non-parallel version.

Deploy workflow:

```sh
gcloud workflows deploy bigquery-serial \
    --source=workflow-serial.yaml
```

Run the workflow:

```sh
gcloud workflows run bigquery-serial
```

This should take about 100 seconds (5 x 20 seconds).


================================================
FILE: bigquery-parallel/workflow-parallel.yaml
================================================
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This is a workflow that queries BigQuery tables in a parallel loop
# and returns the top result from each table
# [START workflows_parallel_bigquery]
main:
    steps:
    - init:
        assign:
            - results : {} # result from each iteration keyed by table name
            - tables:
                - 201201h
                - 201202h
                - 201203h
                - 201204h
                - 201205h
    - runQueries:
        parallel:
            shared: [results]
            for:
                value: table
                in: ${tables}
                steps:
                - logTable:
                    call: sys.log
                    args:
                        text: ${"Running query for table " + table}
                - runQuery:
                    call: googleapis.bigquery.v2.jobs.query
                    args:
                        projectId: ${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")}
                        body:
                            useLegacySql: false
                            useQueryCache: false
                            timeoutMs: 30000
                            # Find top 100 titles with most views on Wikipedia
                            query: ${
                                "SELECT TITLE, SUM(views)
                                FROM `bigquery-samples.wikipedia_pageviews." + table + "`
                                WHERE LENGTH(TITLE) > 10
                                GROUP BY TITLE
                                ORDER BY SUM(VIEWS) DESC
                                LIMIT 100"
                                }
                    result: queryResult
                - returnResult:
                    assign:
                        # Return the top title from each table
                        - results[table]: {}
                        - results[table].title: ${queryResult.rows[0].f[0].v}
                        - results[table].views: ${queryResult.rows[0].f[1].v}
    - returnResults:
        return: ${results}
# [END workflows_parallel_bigquery]


================================================
FILE: bigquery-parallel/workflow-serial.yaml
================================================
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This is a workflow that queries BigQuery tables in a serial loop
# and returns the top result from each table
# [START workflows_serial_bigquery]
main:
    steps:
    - init:
        assign:
            - results : {} # result from each iteration keyed by table name
            - tables:
                - 201201h
                - 201202h
                - 201203h
                - 201204h
                - 201205h
    - runQueries:
        for:
            value: table
            in: ${tables}
            steps:
            - logTable:
                call: sys.log
                args:
                    text: ${"Running query for table " + table}
            - runQuery:
                call: googleapis.bigquery.v2.jobs.query
                args:
                    projectId: ${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")}
                    body:
                        useLegacySql: false
                        useQueryCache: false
                        timeoutMs: 30000
                        # Find top 100 titles with most views on Wikipedia
                        query: ${
                            "SELECT TITLE, SUM(views)
                            FROM `bigquery-samples.wikipedia_pageviews." + table + "`
                            WHERE LENGTH(TITLE) > 10
                            GROUP BY TITLE
                            ORDER BY SUM(VIEWS) DESC
                            LIMIT 100"
                            }
                result: queryResult
            - returnResult:
                assign:
                    # Return the top title from each table
                    - results[table]: {}
                    - results[table].title: ${queryResult.rows[0].f[0].v}
                    - results[table].views: ${queryResult.rows[0].f[1].v}
    - returnResults:
        return: ${results}
# [END workflows_serial_bigquery]


================================================
FILE: bigtable-ai/vertex-vector-search/tests/noxfile.py
================================================
import nox

DEFAULT_PYTHON_VERSION = "3.11"
BLACK_VERSION = "black==22.3.0"
LINT_PATHS = ["system", "noxfile.py"]


@nox.session(python=DEFAULT_PYTHON_VERSION)
def tests(session):
    # Install dependencies
    session.install("-r", "requirements.txt")

    # Run your integration tests
    session.run("pytest", "-s", "system/workflow-test.py")


@nox.session(python=DEFAULT_PYTHON_VERSION)
def blacken(session):
    """Run black. Format code to uniform standard."""
    session.install(BLACK_VERSION)
    session.run(
        "black",
        *LINT_PATHS,
    )


================================================
FILE: bigtable-ai/vertex-vector-search/tests/requirements.txt
================================================
google-cloud-bigtable==2.22.0
google-cloud-workflows==1.12.1
google-cloud-aiplatform
pytest


================================================
FILE: bigtable-ai/vertex-vector-search/tests/system/workflow-input.json
================================================
{
  "project_id": "{{project_id}}",
  "location": "{{location}}",
  "dataflow": {
    "job_name_prefix": "test-bigtable-vvs-integration",
    "temp_location": "gs://bigtable-vertex-vector-search-test-suite/temp/batch-vector-embeddings/"
  },
  "gcs": {
    "output_folder": "gs://bigtable-vertex-vector-search-test-suite/batch-vector-embeddings/",
    "output_file_prefix": "test-vector-embedding-"
  },
  "bigtable": {
    "instance_id": "{{instance_id}}",
    "table_name": "{{table_name}}",
    "app_profile_id": "{{app_profile_id}}",
    "id_column": "_key",
    "embedding_column": "cf:embeddings",
    "crowding_tag_column": "cf:crowding_tag",
    "allow_restricts_mappings": "cf:allow->allow",
    "deny_restricts_mappings": "cf:deny->deny",
    "int_numeric_restricts_mappings": "cf:int->int",
    "float_numeric_restricts_mappings": "cf:float->float",
    "double_numeric_restricts_mappings": "cf:double->double"
  },
  "vertex": {
    "vector_search_index_id": "{{vector_search_index_id}}"
  }
}


================================================
FILE: bigtable-ai/vertex-vector-search/tests/system/workflow-test.py
================================================
# Copyright 2023 Google Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import logging
import os
import random
import struct
import threading
import time
from typing import Iterator, Callable

import pytest
from google.cloud import aiplatform_v1beta1, bigtable, workflows_v1
from google.cloud.bigtable import column_family
from google.cloud.workflows import executions_v1
from google.cloud.workflows.executions_v1 import Execution

# Configure Variables
BIGTABLE_TABLE_NAME = "test_bigtable_vertex_integration_" + str(
    random.randint(10000, 99999)
)
WORKFLOW_NAME = "test-bigtable-vvi-" + str(random.randint(10000, 99999))
WORKFLOW_LOCATION = "us-central1"
VERTEX_VECTOR_SEARCH_INDEX_ENDPOINT = (
    "1597640674.us-central1-818418350420.vdb.vertexai.goog"
)
VERTEX_VECTOR_SEARCH_INDEX = "5500764314786594816"

# Get the directory where this test file is located
THIS_FILE_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
WORKFLOW_INPUT_FILE_PATH = THIS_FILE_DIRECTORY + "/workflow-input.json"
WORKFLOW_YAML_FILE_PATH = (
    os.path.dirname(os.path.dirname(THIS_FILE_DIRECTORY))
    + "/workflows/batch-export.yaml"
)


# Define a custom log format
log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
log_datefmt = "%Y-%m-%d %H:%M:%S"

# Create a logger instance
logger = logging.getLogger(__name__)

# Set the log level to capture all log messages
logger.setLevel(level=logging.DEBUG)

# Create a handler for console output
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter(log_format, log_datefmt))

# Add the console handler and file handler to the logger
logger.addHandler(console_handler)


@pytest.fixture(scope="module")
def project_id() -> Iterator[str]:
    yield get_env_var("PROJECT_ID", "ID of the Cloud Bigtable project")


@pytest.fixture(scope="module")
def instance_id() -> Iterator[str]:
    yield get_env_var("INSTANCE_ID", "ID of the Cloud Bigtable instance")


@pytest.fixture(scope="module")
def setup_workflow(project_id: str) -> Iterator[None]:
    # Deploy Workflow
    deploy_workflow(project_id, WORKFLOW_LOCATION, WORKFLOW_NAME)

    try:
        yield

    finally:
        workflow_client = workflows_v1.WorkflowsClient()

        workflow_full_path = (
            "projects/{project}/locations/{location}/workflows/{workflow_name}".format(
                project=project_id,
                location=WORKFLOW_LOCATION,
                workflow_name=WORKFLOW_NAME,
            )
        )

        # Initialize request argument(s)
        request = workflows_v1.DeleteWorkflowRequest(
            name=workflow_full_path,
        )

        # Make the request
        operation = workflow_client.delete_workflow(request=request)

        logger.info("Delete Cloud Workflow with name: {}.".format(workflow_full_path))

        operation.result()


def generate_vector_data(
    number_of_rows: int, vector_dimension: int, table: bigtable.table.Table
) -> tuple[list[bigtable.row.DirectRow], dict]:
    """Generates vector data for Bigtable table.

    Args:
        number_of_rows: The number of rows to generate.
        vector_dimension: The dimension of the vectors.

    Returns:
        A list of rows, each of which is a bigtable.row.DirectRow, and a dictionary representing their data.
    """

    logger.info(
        "Generating {} vector embeddings each of dimension: {}.".format(
            number_of_rows, vector_dimension
        )
    )

    rows_dict = dict()
    rows = []

    for i in range(number_of_rows):
        # Generating random vector embeddings
        embeddings = [random.uniform(0, 1) for _ in range(vector_dimension)]

        row_key = f"row_key_{i}"
        allow = "thing 1"
        deny = "thing 2"
        int_restrict = 45000
        float_restrict = 3.14
        double_restrict = 2.71
        crowding_tag = "a" if i % 2 == 0 else "b"
        rowEntry = {
            "row_key": row_key,
            "embeddings": embeddings,
            "allow": allow,
            "deny": deny,
            "int": int_restrict,
            "float": float_restrict,
            "double": double_restrict,
            "crowding_tag": crowding_tag,
        }

        row = table.direct_row(str.encode(row_key))
        row.set_cell(
            "cf",
            "embeddings",
            struct.pack(">" + "f" * len(embeddings), *embeddings),
        )

        # Restricts
        row.set_cell("cf", "allow", str.encode(allow))
        row.set_cell("cf", "deny", str.encode(deny))
        row.set_cell("cf", "int", int_restrict.to_bytes(4, "big"))
        row.set_cell("cf", "float", struct.pack(">f", float_restrict))
        row.set_cell("cf", "double", struct.pack(">d", double_restrict))

        # Crowding tag
        row.set_cell("cf", "crowding_tag", str.encode(crowding_tag))

        rows_dict[row_key] = rowEntry
        rows.append(row)

    logger.info("Vector Embeddings generated.")

    return rows, rows_dict


def setup_bigtable(project_id: str, instance_id: str, table_name: str) -> dict:
    """Sets up a Bigtable table with vector embeddings.

    Args:
        project_id: The project ID.
        instance_id: The instance ID.
        table_name: The table name.
    """

    logger.info("Setting up Bigtable Table...")

    NUMBER_OF_ROWS_IN_BIGTABLE = 1000
    VECTOR_DIMENSION = 128

    client = bigtable.Client(admin=True, project=project_id)
    instance = client.instance(instance_id)
    table = instance.table(table_name)
    table.create(
        column_families={
            "cf": column_family.MaxVersionsGCRule(1),
        }
    )

    logger.info("Created {} table on instance {}.".format(table_name, instance_id))

    rows, rows_dict = generate_vector_data(
        NUMBER_OF_ROWS_IN_BIGTABLE, VECTOR_DIMENSION, table
    )

    logger.info(
        "Inserting generated vector embeddings in Bigtable Table: {}.".format(
            table_name
        )
    )
    batcher = table.mutations_batcher()

    # In batcher, mutate will flush current batch if it
    # reaches the max_row_bytes
    batcher.mutate_rows(rows)
    batcher.flush()

    logger.info(
        "Inserted {} records in table {}.".format(
            NUMBER_OF_ROWS_IN_BIGTABLE, table_name
        )
    )

    return rows_dict


def deploy_workflow(project: str, location: str, workflow_name: str) -> None:
    """Deploys a workflow defined in file "https://github.com/GoogleCloudPlatform/cloud-bigtable-examples/bigtable-ai/vertex-vector-search/workflows/batch-export.yaml" to Cloud Workflow.

    Args:
        project: The project ID.
        location: The location of the workflow.
        workflow_name: The name of the workflow.
    """
    logger.info(
        "Deploying workflow with name: {} on project: {} and location: {}.".format(
            workflow_name, project, location
        )
    )
    logger.info(
        "Picking workflow configuration from following path: {}.".format(
            WORKFLOW_YAML_FILE_PATH
        )
    )
    file_content = ""

    with open(WORKFLOW_YAML_FILE_PATH, "r") as file:
        # Read the entire file content
        file_content = file.read()

    # Create a client
    client = workflows_v1.WorkflowsClient()

    # Initialize request argument(s)
    workflow = workflows_v1.Workflow()
    workflow.source_contents = file_content

    request = workflows_v1.CreateWorkflowRequest(
        parent="projects/{project}/locations/{location}".format(
            project=project, location=location
        ),
        workflow=workflow,
        workflow_id=workflow_name,
    )

    # Make the request
    operation = client.create_workflow(request=request)

    logger.info("Waiting for deployment of workflow to complete...")

    operation.result()

    logger.info(
        "Workflow with name: {} deployed successfully on project: {}.".format(
            workflow_name, project
        )
    )


def execute_workflow(
    project: str,
    location: str,
    workflow_name: str,
    bigtable_arguments: dict,
    vertex_index_id: str,
) -> Execution:
    """Executes a workflow.

    Args:
        project: The project ID.
        location: The location of the workflow.
        workflow_name: The name of the workflow.
        bigtable_arguments: The dictionary with Bigtable Arguments.
        vertex_index_id: The Vertex Vector Search Index ID.
    """
    logger.info("Starting execution of workflow with name: {}.".format(workflow_name))

    client = executions_v1.ExecutionsClient()

    json_arguments = dict()

    logger.info(
        "Reading workflow input template json from: {}.".format(
            WORKFLOW_INPUT_FILE_PATH
        )
    )

    with open(WORKFLOW_INPUT_FILE_PATH, "r") as file:
        # Read the entire file content
        json_arguments = json.load(file)

    json_arguments["project_id"] = project
    json_arguments["location"] = location
    json_arguments["bigtable"]["instance_id"] = bigtable_arguments["instance_id"]
    json_arguments["bigtable"]["table_name"] = bigtable_arguments["table_name"]
    json_arguments["bigtable"]["app_profile_id"] = bigtable_arguments["app_profile_id"]
    json_arguments["vertex"]["vector_search_index_id"] = vertex_index_id

    workflow_execution_request = Execution()
    workflow_execution_request.argument = json.dumps(json_arguments, indent=4)

    # Initialize request argument(s)
    request = executions_v1.CreateExecutionRequest(
        parent="projects/{project}/locations/{location}/workflows/{workflow_name}".format(
            project=project, location=location, workflow_name=workflow_name
        ),
        execution=workflow_execution_request,
    )

    response = client.create_execution(request=request)

    logger.info(
        "Execution of workflow with name: {} triggered with following arguments: {}.".format(
            workflow_name, json_arguments
        )
    )

    return response


def get_worfklow_execution(arguments: dict) -> Execution:
    """Gets a workflow execution.

    Args:
        arguments: A dictionary of arguments containg the `execution_id`.

    Returns:
        A workflow execution.
    """

    logger.info(
        "Fetching execution status of workflow with id: {}.".format(
            arguments["execution_id"]
        )
    )
    client = executions_v1.ExecutionsClient()

    # Initialize request argument(s)
    request = executions_v1.GetExecutionRequest(
        name=arguments["execution_id"],
    )

    # Make the request
    response = client.get_execution(request=request)

    # Handle the response
    return response


def workflow_execution_polling_predicate(
    workflow_execution_response: Execution,
) -> bool:
    """A predicate that determines whether a workflow execution has finished.
    Checks whether the workflow state is `Active` or not.

    Args:
        workflow_execution_response: A workflow execution.

    Returns:
        True if the workflow execution has finished, False otherwise.
    """
    if workflow_execution_response.state != Execution.State.ACTIVE:
        return True

    return False


def polling(
    function_to_poll: Callable[[dict], Execution],
    arguments: dict,
    function_poll_predicate: Callable[[Execution], bool],
    max_attempts: int = 100,
    polling_interval: int = 120,
) -> Execution:
    """A polling function that polls a function until a predicate is met.

    Args:
        function_to_poll: A function to poll.
        arguments: A dictionary of arguments to pass to the function to poll.
        function_poll_predicate: A predicate that determines whether the polling should stop.
        max_attempts: The maximum number of attempts to poll.
        polling_interval: The interval between polls.

    Returns:
        The result of the function to poll.
    """
    for attempt in range(max_attempts):
        response = function_to_poll(arguments)

        if function_poll_predicate(response):
            return response  # Desired condition met

        logger.info(
            "Attempt {}: Workflow execution in progress, waiting for workflow to finish...".format(
                attempt + 1
            )
        )
        time.sleep(polling_interval)

    raise TimeoutError("Polling timed out")


def sync_execute_workflow(
    project: str,
    location: str,
    workflow_name: str,
    bigtable_arguments: dict,
    vertex_index_id: str,
) -> None:
    """Synchronously executes a workflow.

    Args:
        project: The project ID.
        location: The location of the workflow.
        workflow_name: The name of the workflow.
        bigtable_arguments: The dictionary with Bigtable Arguments.
        vertex_index_id: The Vertex Vector Search Index ID.
    """
    execute_workflow_response = execute_workflow(
        project, location, workflow_name, bigtable_arguments, vertex_index_id
    )

    try:
        result = polling(
            get_worfklow_execution,
            {"execution_id": execute_workflow_response.name},
            workflow_execution_polling_predicate,
        )
        logger.info("Workflow exeuction finished with result: {}.".format(result))
    except TimeoutError:
        logger.error("Workflow exeuction polling timed out.")


def cleanup_bigtable_resources(
    project_id: str, instance_id: str, table_name: str
) -> None:
    """Cleans up the Bigtable resources.

    Args:
        project_id: The project ID.
        instance_id: The instance ID.
        table_name: The table name.
    """
    client = bigtable.Client(admin=True, project=project_id)
    instance = client.instance(instance_id)
    instance.table(table_name).delete()

    logger.info("Dropped Bigtable table with name: {}.".format(table_name))


def read_index_datapoints(
    api_endpoint: str, keys: list[str]
) -> aiplatform_v1beta1.ReadIndexDatapointsResponse:
    """Reads datapoints from a deployed Vertex Index.

    Args:
      api_endpoint: The AI Platform Index API endpoint.
      keys: A list of datapoint IDs to fetch.

    Returns:
      A ReadIndexDatapointsResponse.
    """
    # Create a client
    client_options = {"api_endpoint": api_endpoint}

    client = aiplatform_v1beta1.MatchServiceClient(client_options=client_options)

    # Initialize request argument(s)
    request = aiplatform_v1beta1.ReadIndexDatapointsRequest(
        deployed_index_id="bigtable_vector_batch_inte_1706731206928",
        ids=keys,
    )

    # Make the request
    response = client.read_index_datapoints(request=request)

    # Handle the response
    return response


@pytest.fixture
def bigtable_vertex_vector_search_data(
    project_id: str, instance_id: str
) -> Iterator[dict]:
    """
    Setting up Bigtable Table with vector embeddings to test the workflow.
    The function does following operations:
    1. Creation of Bigtable table.
    2. Inserting randomly generated vector embeddings data into bigtable table.
    3. Invoke the test to execute workflow and comapre vector embeddings.
    4. Tear down Bigtable Resources.
    """
    # Setup code, e.g., initialize resources
    logger.info("Setting up resources for Integration Tests")

    # 1 Setting up Bigtable
    try:
        rows = setup_bigtable(project_id, instance_id, BIGTABLE_TABLE_NAME)
    except Exception as e:
        logger.error(
            "An exception occurred while setting up Bigtable table: %s",
            str(e),
            exc_info=True,
        )
        pytest.fail(
            "Test failed due to unhandled exception while setting up bigtable table."
        )

    try:
        yield rows  # This is where the test runs
    except Exception as e:
        logger.error(
            "An exception occurred while executing workflow: %s",
            str(e),
            exc_info=True,
        )

    cleanup_bigtable_resources(project_id, instance_id, BIGTABLE_TABLE_NAME)


def compare_float_lists(
    list1: list[float], list2: list[float], tolerance: float = 1e-5
) -> bool:
    """
    Compare two lists of floating-point numbers with a specified tolerance.

    This function compares two lists of floating-point numbers element-wise, allowing for a certain tolerance
    to account for small differences due to floating-point precision. It returns True if all corresponding elements
    in the two lists are within the specified tolerance, indicating that the lists are considered equal.
    If the lists have different lengths, they are not considered equal.

    Parameters:
        list1 (list of float): The first list of floating-point numbers to be compared.
        list2 (list of float): The second list of floating-point numbers to be compared.
        tolerance (float, optional): The allowable absolute difference between corresponding elements
            in the two lists. Default is 1e-5.

    Returns:
        bool: True if the lists are equal within the specified tolerance, False otherwise.

    Example:
        ```python
        list1 = [1.0, 2.00001, 3.00002]
        list2 = [1.00001, 2.0, 3.00003]

        are_equal = compare_float_lists(list1, list2)

        if are_equal:
            print("The lists are equal within the specified tolerance.")
        else:
            print("The lists are not equal within the specified tolerance.")
        ```
    """
    if len(list1) != len(list2):
        return False  # The lists have different lengths, so they can't be equal.

    for elem1, elem2 in zip(list1, list2):
        if abs(elem1 - elem2) > tolerance:
            return False  # The elements are not within the allowable error.

    return True


def read_and_compare_vertex_data(
    bigtable_vertex_vector_search_data: dict, vertex_index_end_point_url: str
) -> None:
    """
    Reads and compares vertex data from a @code{bigtable_vertex_vector_search_data}

    Args:
        bigtable_vertex_vector_search_data (list of tuples): A list of tuples representing vector data
            retrieved from a Bigtable database, where each tuple contains an ID and vector embeddings.
        vertex_index_end_point_url (str): The URL of the Vertex Index endpoint for data retrieval.

    Raises:
        AssertionError: If the actual data retrieved from the Bigtable database is not found in
            the data fetched from the Vertex Index, or if the vector embeddings do not match.

    Returns:
        None: This function does not return a value but raises assertions if comparisons fail.
    """
    data_point_id_list = list(bigtable_vertex_vector_search_data.keys())
    data_point_id_list = [
        # Convert keys to strings
        key
        for key in data_point_id_list
    ]
    # Fetching data from Vertex Index
    vertex_vector_search_data = read_index_datapoints(
        vertex_index_end_point_url, data_point_id_list
    )

    for data_point in vertex_vector_search_data.datapoints:
        actual_data = bigtable_vertex_vector_search_data.get(
            data_point.datapoint_id, None
        )

        assert actual_data is not None

        actual_vector_embeddings = actual_data["embeddings"]
        vertex_index_vector_embeddings = list(data_point.feature_vector)

        assert compare_float_lists(
            actual_vector_embeddings, vertex_index_vector_embeddings
        )

        for restrict in data_point.restricts:
            if restrict.namespace == "allow":
                assert restrict.allow_list[0] == actual_data["allow"]
                assert not restrict.deny_list
            if restrict.namespace == "deny":
                assert restrict.deny_list[0] == actual_data["deny"]
                assert not restrict.allow_list
        for restrict in data_point.numeric_restricts:
            if restrict.namespace == "int":
                assert restrict.value_int == actual_data["int"]
            if restrict.namespace == "float":
                assert compare_float_lists(
                    [restrict.value_float], [actual_data["float"]]
                )
            if restrict.namespace == "double":
                assert restrict.value_double == actual_data["double"]


def test_bigtable_vertex_vector_search_integration(
    project_id: str,
    instance_id: str,
    setup_workflow: None,
    bigtable_vertex_vector_search_data: dict,
) -> None:
    """
    Tests integration between Bigtable and Vertex Vector Search.
    1. Execute the workflow synchronously.
    2. Fetch Vector Embeddings from Vertex Index.
    3. Compare generated embeddings from the embeddings in Vertex Index.
    """
    # Execute Workflow
    sync_execute_workflow(
        project_id,
        WORKFLOW_LOCATION,
        WORKFLOW_NAME,
        {
            "instance_id": instance_id,
            "table_name": BIGTABLE_TABLE_NAME,
            "app_profile_id": "default",
        },
        VERTEX_VECTOR_SEARCH_INDEX,
    )

    read_and_compare_vertex_data(
        bigtable_vertex_vector_search_data, VERTEX_VECTOR_SEARCH_INDEX_ENDPOINT
    )


def setup_and_execute_workflow(
    project: str,
    location: str,
    workflow_name: str,
    bigtable_arguments: dict,
    vertex_index_id: str,
    result_list: list,
) -> None:
    """
    Sets up a Bigtable database and executes a workflow, then appends the result to a list.

    Args:
        project (str): The project ID for GCP.
        location (str): The location where the workflow will be executed.
        workflow_name (str): The name of the workflow to be executed.
        bigtable_arguments (dict): A dictionary containing Bigtable setup parameters, including
            instance_id, and table_name.
        vertex_index_id (str): The ID of the Vertex Index.
        result_list (list): A list to which the result will be appended.

    Returns:
        None: This function does not return a value directly but appends the result rows
        to the result_list.

    Raises:
        Any exceptions raised by the functions called within this function may be propagated.

    Note:
        This function sets up a Bigtable database, executes a workflow, and appends the result rows
        to the provided result_list.
    """

    try:
        # 1 Setting up Bigtable
        rows = setup_bigtable(
            project,
            bigtable_arguments["instance_id"],
            bigtable_arguments["table_name"],
        )

        # 2 Execute Workflow
        sync_execute_workflow(
            project, location, workflow_name, bigtable_arguments, vertex_index_id
        )

        result_list.append(rows)

    finally:
        cleanup_bigtable_resources(
            project,
            bigtable_arguments["instance_id"],
            bigtable_arguments["table_name"],
        )


def test_concurrent_workflow_execution(
    project_id: str, instance_id: str, setup_workflow: None
) -> None:
    """
    Test the concurrent execution of workflow in separate threads.

    Args:
        setup_workflow (fixture): A fixture that sets up the necessary environment for testing.

    Raises:
        AssertionError: If the concurrent workflow execution does not behave as expected.

    Note:
        This test function verifies the behavior of concurrent execution of the
        'setup_and_execute_workflow' function in separate threads. It creates two threads
        to run the function with different parameters and verifies the results.
        The final vertex state should be consistent with the latest bigtable data.
    """

    # Create a thread for the async function without blocking
    result_list1: list[dict] = []
    result_list2: list[dict] = []

    thread_1 = threading.Thread(
        target=setup_and_execute_workflow,
        args=(
            project_id,
            WORKFLOW_LOCATION,
            WORKFLOW_NAME,
            {
                "instance_id": instance_id,
                "table_name": BIGTABLE_TABLE_NAME + "_first",
                "app_profile_id": "default",
            },
            VERTEX_VECTOR_SEARCH_INDEX,
            result_list1,
        ),
    )
    thread_1.start()

    # Wait for 5 minutes (300 seconds)
    time.sleep(60)

    # Create another thread for the async function
    thread_2 = threading.Thread(
        target=setup_and_execute_workflow,
        args=(
            project_id,
            WORKFLOW_LOCATION,
            WORKFLOW_NAME,
            {
                "instance_id": instance_id,
                "table_name": BIGTABLE_TABLE_NAME + "_second",
                "app_profile_id": "default",
            },
            VERTEX_VECTOR_SEARCH_INDEX,
            result_list2,
        ),
    )
    thread_2.start()

    thread_1.join()
    thread_2.join()

    # Vertex should have data points which were latest
    read_and_compare_vertex_data(result_list2[0], VERTEX_VECTOR_SEARCH_INDEX_ENDPOINT)


def get_env_var(key: str, desc: str) -> str:
    v = os.environ.get(key)
    if v is None:
        raise ValueError(f"Must set env var {key} to: {desc}")
    return v


================================================
FILE: bigtable-ai/vertex-vector-search/workflows/README.md
================================================
# Cloud Bigtable to Vertex Vector Search Export README

[Cloud Bigtable](https://cloud.google.com/bigtable) is a sparsely populated table that
can scale to billions of rows and thousands of columns, enabling you
to store terabytes or even petabytes of data. [Vertex AI Vector
Search](https://cloud.google.com/vertex-ai/docs/vector-search/overview)
allows users to search for semantically similar items using vector embeddings.

You can integrate your Cloud Bigtable table with Vector Search to perform
vector similarity search on your Bigtable data. The general workflow is as follows:

1.  Generate and store vector embeddings in Bigtable. You can manage
    them similarly to your operational data.
2.  Export and upload embeddings into a Vector Search index using the
    [workflow](#set-up-cloud-workflow) presented on this page.
3.  Query the Vector Search index for similar items. You can query using
    a [public
    endpoint](https://cloud.google.com/vertex-ai/docs/vector-search/query-index-public-endpoint)
    or through [VPC
    peering](https://cloud.google.com/vertex-ai/docs/vector-search/query-index-vpc).

## Overview

Exporting embeddings from Cloud Bigtable to Vertex AI Vector Search is achieved
by using the Cloud Workflow provided in this repository. For instructions on how
to get started immediately, see [Before you begin](#before-you-begin).

![diagram](bigtable-vector-search-batch-export.svg)

**Figure**: Export and sync Bigtable data into Vector Search workflow.

This tutorial uses billable components of Google Cloud, including:

1.  **Cloud Bigtable**: Store embeddings and operational data in Bigtable.
2.  **Vertex AI**: Generate embeddings using models served by Vertex AI.
3.  **Cloud Dataflow**: Use a dataflow template to export the embeddings from
    Bigtable to Cloud Storage.
4.  **Google Cloud Storage (GCS)**: Store exported embeddings from Bigtable in a GCS
    bucket in the input JSON format expected by Vector Search.
5.  **Cloud Workflow**: Orchestrate these two steps for the end-to-end flow..
    1.  Export embeddings from Bigtable to GCS as JSON.
    2.  Build the Vector Search index from the JSON files in GCS.
6.  **Cloud Scheduler**: Used to trigger the Cloud Workflow.

## Before you begin

1.  Ensure that your account has the required [permissions](#permissions).
2.  Generate and store embeddings in your Bigtable table as `float32` or `float64` array.
    For more details see [Bigtable schema](#bigtable-schema).

## Set up Cloud Workflow

To set up a periodic batch export from Bigtable to a Vertex AI Vector Search
index:

### 1.  Create an empty index:

   Follow the instructions on the
    [Create an index](https://cloud.google.com/vertex-ai/docs/vector-search/create-manage-index#create-index)
    page. In the folder that is passed to `contentsDeltaUri`, create an empty
    file called `empty.json`. This creates an empty index.
    If you already have an index, you can skip this step. The workflow will
    overwrite your index.

### 2.  Clone this git repository:
   There are multiple ways to clone a git repository, one way is to run the
    following command using the [GitHub CLI](https://github.com/cli/cli):

```
gh repo clone GoogleCloudPlatform/workflows-demos
cd GoogleCloudPlatform/workflows-demos/bigtable-ai/vertex-vector-search/workflows
```

   This folder contains two files:

*   `batch-export.yaml`: This is the workflow definition.
*   `sample-batch-input.json`: This is a sample of the workflow input parameters.

### 3.  Setup input.json from the sample file:

First, copy the sample JSON.

```
cp sample-batch-input.json input.json
```

Then edit `input.json` with details for your project. See the [Parameters in the
input.json file](#parameters-in-the-inputjson-file) section for more information.

### 4.  Deploy the workflow:

Deploy the workflow yaml file to your Google Cloud project. You can configure
the region or location where the workflow will run when executed.

```
  gcloud workflows deploy vector-export-workflow \
--source=batch-export.yaml [--location=<cloud region>] [--service account=<service_account>]
```

The workflow is now visible on the [Workflows
page](https://console.cloud.google.com/workflows) in the Google Cloud console.

**Note**: You can also create and deploy the workflow from the Google Cloud
console. Follow the prompts in the Cloud console. For the workflow definition,
copy and paste the contents of `batch-export.yaml`.

### 5.  **Execute the workflow**:

   Run the following command to execute the workflow:

```
gcloud workflows execute \
    vector-export-workflow --data="$(cat input.json)" \
    [--location=<cloud region>]
```

The execution shows up in the `Executions` tab in Workflows where you can
monitor it. For more information, see [Monitor Workflows and Dataflow
jobs](#monitor-workflows-and-dataflow-jobs).


**Note**: You can also execute from the console using the `Execute` button.
Follow the prompts and for the input, copy and paste the contents of your
customized `input.json`.

### 6.  **Schedule the workflow for periodic execution**:

Once the workflow executes successfully, schedule it periodically using Cloud
Scheduler. This prevents your index from becoming stale as your embeddings
change.

```
gcloud scheduler jobs create http vector-export-workflow \
  --message-body="{ argument : $(cat input.json) }" \
  --schedule="0 * * * *" --time-zone="PDT" \
  --uri <invocation_url> [--service account=<service_account>]
```

The schedule argument accepts
[unix-cron](https://cloud.google.com/scheduler/docs/configuring/cron-job-schedules)
format. The time-zone argument must be from the
[tz database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). See
[scheduler help](https://cloud.google.com/sdk/gcloud/reference/scheduler/jobs/create/http)
for more information. The `invocation_url` can be determined from the workflow
details page in the console by clicking on the `Details` tab.

## Appendix
### Permissions

For production environments, we strongly recommend creating a new service
account and granting it one or more IAM roles that contain the minimum
permissions required for managing service. You can also choose to use
different service accounts for different services as described below.

The following roles are needed to complete the instructions on this page.

1.  [Cloud Scheduler Service Account](https://cloud.google.com/run/docs/triggering/using-scheduler#command-line):
    1.  By default uses the **_Compute Engine default service account_**.
    2.  If you use a manually configured service account, you must include the
        following roles:
        1.  Cloud Scheduler Service Agent role.
        2.  To trigger the workflow: **_Workflows Invoker_**.
2.  [Cloud Workflow Service Account](https://cloud.google.com/workflows/docs/authentication#sa-permissions):
    1.  By default uses the **_Compute Engine default service account_**.
    2.  If you use a manually configured service account, you must include the
        following roles:
        1.  To trigger dataflow job: **_Dataflow Admin_**, **_Dataflow Worker_**.
        2.  To impersonate dataflow worker service account: **_Service Account User_**.
        3.  To write Logs: : **_Logs Writer_**.
        4.  To trigger Vertex AI Vector Search rebuild: **_Vertex AI User_**.
3.  [Dataflow Worker Service Account](https://cloud.google.com/dataflow/docs/concepts/security-and-permissions#worker-service-account):
    1.  By default uses the **_Compute Engine default service account_**.
    2.  If you use a manually configured service account, you must include the
        following roles:
        1.  To manage dataflow: **_Dataflow Admin_**, **_Dataflow Worker_**.
        2.  To read data from Bigtable: **_Bigtable Reader_**.
        3.  To write over selected GCS Container Registry: **_GCS Storage Bucket Owner_**.

### Parameters in the input.json file

Input to the workflow is provided using a JSON file. The included
<code>[sample-batch-input.json](sample-batch-input.json)</code>
contains both required and optional parameters. The value for each field in the
sample contains the description of the parameter. Copy the
<code>[sample-batch-input.json](sample-batch-input.json)</code>
and customize it according to the descriptions in the file. Delete the optional
parameters that you don’t want to pass.

#### Required Parameters

The required parameters are organized by product. There are 4 sections -
`dataflow`, `gcs`, `bigtable` and `vertex` - for each component that needs to be
configured. Outside of these sections, we have `location` and `project_id`.
These apply to all sections. The `location` and `project_id` arguments can be
overridden in any section if you need to run in a different location or use
resources from different projects.

##### Bigtable Parameters

Enter the `instance_id` and `table_name`. 
To use a non-default Application Profile, enter the desired Application Profile ID in `app_profile_id`.
The following parameters: `id_column`, `embedding_column`, `crowding_tag_column`
help map which column in Bigtable (cf:col or `_key` for rowkey) to map into id, embedding and crowding_tag respecively.
`id_column` and `embedding_column` are mandatory.
In addition, `allow_restricts_mappings`, `deny_restricts_mappings`, `int_numeric_restricts_mappings`, `float_numeric_restricts_mappings` and `double_numeric_restricts_mappings` are mapping which columns to map to allow, deny, numeric int, float and double restricts respectively.
The format of the `.*_restricts_mappings` parameter is a comma separated list of
fields in the following form:

`<bigtable_column_family>:<bigtable_column_name>-><vertex_field_name>`

For example, if the Bigtable table contains the column family `cf` and in it columns called `color` and `shape` that you wish to add to allow restricts, the `allow_restricts_mappings`
parameter needs to contain the following columns and aliases:

`cf:color->color,cf:shape->shape`

##### Dataflow Parameters

`temp_location`:Google Cloud Storage location to store temporary files
generated by the dataflow job.

```
gs://<bucket_name>/<folder_name>/
```

##### GCS Parameters

`output_folder`: Google Cloud Storage location to store the JSON files generated
by the Dataflow job. To conserve storage space in Google Cloud Storage (GCS), it
is recommended that users configure a two-week TTL (time-to-live) rule for the
parent folder of any subfolders created by workflow runs. This will ensure that
any exported generated embeddings in those subfolders are automatically deleted
after two weeks. For more information on how to configure the TTL for an object,
see [Object Lifecycle
Management](https://cloud.google.com/storage/docs/lifecycle).

```
gs://<bucket_name>/<folder_name>/
```

##### Vertex Parameters

`vector_search_index_id`: Vertex AI Vector Search Index which needs to be
updated.

#### Optional Parameters

The following parameters are optional to the workflow.

##### Bigtable Parameters

*   `project_id`: GCP Project ID which contains a table with vector embeddings.
    By default, it is derived from the project_id specified at the root level of
    the JSON in the required parameters.

##### Dataflow Parameters

*   `service_account_email`: The Dataflow Worker Service Account email. By
    default, use the [Compute Engine default service
    account](https://cloud.google.com/compute/docs/access/service-accounts#default_service_account)
    of your project as the worker service account. The Compute Engine default
    service account has broad access to the resources of your project, which
    makes it easy to get started with Dataflow. However, for production
    workloads, we recommend that you create a new service account with all the
    roles listed in the [Permissions] section.
*   `project_id`: GCP Project ID on which the job runs. By default, it is
    derived from the project_id specified at the root level of the JSON in the
    required parameters.
*   `location`: Project Region from where the job runs. By default, it is derived
    from the location specified at the root level of the JSON in the required
    parameters.
*   `max_workers`: The maximum number of workers to run the job. The default is
    set to 1000.
*   `num_workers`: The initial numbers of workers to start the job. If
    auto-scaling is enabled, this is not required.
    generally not required with auto-scaling is enabled.
*   `job_name_prefix`: Dataflow Job Name Prefix. The default value is
    bigtable-vectors-export. This prefix can be used to filter jobs in the
    Dataflow console.

##### GCS Parameters

`output_file_prefix`: Exported JSON File Name Prefix. The default value is
vector-embeddings.

##### Vertex Parameters

*   `project_id`: GCP Project on which Vertex AI Vector Search index is built
    and deployed. By default, it is derived from the project_id specified at the
    root level of the JSON in the required parameters.
*   `location`: Project Region on which Vertex AI Vector Search index is built
    and deployed. By default, it is derived from the location specified at the
    root level of the JSON in the required parameters.

### Bigtable schema
Vertex AI Vector Search accepts the following
[arguments](https://cloud.google.com/vertex-ai/docs/vector-search/setup/format-structure#json) when creating or
updating the index.

* `id` (required): A string.
* `embedding` (required): An array of floats.
* `restricts` (optional): An array of objects, with each object being a nested
  structure that provides the namespace and the allow/denylist for the
  datapoint.
* `crowding_tag` (optional): A string.

When defining your Bigtable schema, you must have columns that will contain the
data for the required arguments. You
need to alias in the Cloud Workflow parameters as described
in [Bigtable Parameters](#bigtable-parameters).

The data type for each of the columns in the Bigtable schema should be as shown
below.

* `id`: Any data type that can be converted to a string.
* `embedding`: ARRAY<FLOAT64>.
* `restricts`: JSON.
* `crowding_tag`: String.

**Note**: The table may contain columns that are not relevant for the export and
sync workflow. The columns not specified in the `id_column`, `embedding_column`,
`crowding_tag_column` and the various `.*_restricts_mappings` parameters
are ignored.


### Monitor Workflows and Dataflow jobs

After you have deployed a Workflow, you can check the status of your workflow
execution in the Execution tab of the Workflows page in the Cloud console. On
the Execution details page, you can view the results of the execution including
any output, the execution ID and state, and the current or final step of the
workflow execution. Useful information is also printed to the log at the bottom
of the page. If there are errors, they are shown in the log and Output
section. For more information on debugging errors, see [Debug
Workflows](https://cloud.google.com/workflows/docs/debug).

After the workflow starts the Dataflow job, you can check the status of your job
execution from the [Jobs
dashboard](https://console.cloud.google.com/dataflow/jobs) in the Cloud console.
Find the relevant job by filtering for the `job_name_prefix` parameter that you
set in `input.json`. For troubleshooting tips, see [Pipeline troubleshooting and
debugging](https://cloud.google.com/dataflow/docs/guides/troubleshooting-your-pipeline).

After the export completes, the workflow triggers the update of the Vector
Search index. This is a long running operation and regular updates are logged in
the Workflows Execution page until completion.



================================================
FILE: bigtable-ai/vertex-vector-search/workflows/batch-export.yaml
================================================
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

main:
  params: [params]
  steps:
    - initialize_project:
        steps:
          - dataflow_project:
              switch:
                  - condition: ${not ("project_id" in params.dataflow)}
                    assign:
                      - params.dataflow.project_id: ${params.project_id}
          - bigtable_project:
              switch:
                  - condition: ${not ("project_id" in params.bigtable)}
                    assign:
                      - params.bigtable.project_id: ${params.project_id}
          - vertex_project:
              switch:
                  - condition: ${not ("project_id" in params.vertex)}
                    assign:
                      - params.vertex.project_id: ${params.project_id}
    - initialize_location:
        steps:
          - dataflow_location:
              switch:
                  - condition: ${not ("location" in params.dataflow)}
                    assign:
                      - params.dataflow.location: ${params.location}
          - vertex_location:
              switch:
                  - condition: ${not ("location" in params.vertex)}
                    assign:
                      - params.vertex.location: ${params.location}
    - check_vector_search_index_exists:
          call: http.request
          args:
            url: ${"https://" + params.vertex.location + "-aiplatform.googleapis.com/v1/projects/" + params.vertex.project_id + "/locations/" + params.vertex.location + "/indexes/" + params.vertex.vector_search_index_id}
            method: GET
            auth:
              type: OAuth2
          result: index_response
    - define_timestamp:
        assign:
          - current_timestamp: ${time.format(sys.now())}
    - log_timestamp:
        call: sys.log
        args:
          text: ${"Bigtable Version Time chosen for exporting data is " + current_timestamp}
    - initialize_dataflow_job_variables:
        steps:
          - max_workers:
              switch:
                  - condition: ${not ("max_workers" in params.dataflow)}
                    assign:
                      - params.dataflow.max_workers: 0
          - num_workers:
              switch:
                  - condition: ${not ("num_workers" in params.dataflow)}
                    assign:
                      - params.dataflow.num_workers: 0
          - service_account_email:
              switch:
                  - condition: ${not ("service_account_email" in params.dataflow)}
                    assign:
                      - params.dataflow.service_account_email: ""
          - job_name_prefix:
              switch:
              - condition: ${not ("job_name_prefix" in params.dataflow)}
                assign:
                - params.dataflow.job_name_prefix: "bigtable-vectors-export"
          - output_file_prefix:
              switch:
              - condition: ${not ("output_file_prefix" in params.gcs)}
                assign:
                - params.gcs.output_file_prefix: "vector-embeddings"
    - initialize_dataflow_job_name_and_gcs_folder:
        assign:
        - bigtable_export_job_name: ${params.dataflow.job_name_prefix + "_" + current_timestamp}
        - embeddings_output_folder: ${params.gcs.output_folder + "embeddings-" + current_timestamp + "/"}
        - dataflow_template_path: ${"gs://dataflow-templates-us-central1/latest/Cloud_Bigtable_to_Vector_Embeddings"}
    - log_gcs_folder_path:
        call: sys.log
        args:
          text: ${"Exporting Vector Embeddings to gcs folder - " + embeddings_output_folder}
    - dataflow_bigtable_export_job:
        call: googleapis.dataflow.v1b3.projects.locations.templates.create
        args:
          projectId: ${params.dataflow.project_id}
          location: ${params.dataflow.location}
          body:
            jobName: ${bigtable_export_job_name}
            parameters:
              bigtableProjectId: ${params.bigtable.project_id}
              bigtableInstanceId: ${params.bigtable.instance_id}
              bigtableAppProfileId: ${params.bigtable.app_profile_id}
              bigtableTableId: ${params.bigtable.table_name}              
              idColumn: ${params.bigtable.id_column}
              embeddingColumn: ${params.bigtable.embedding_column}
              crowdingTagColumn: ${params.bigtable.crowding_tag_column}
              allowRestrictsMappings: ${params.bigtable.allow_restricts_mappings}
              denyRestrictsMappings: ${params.bigtable.deny_restricts_mappings}
              intNumericRestrictsMappings: ${params.bigtable.int_numeric_restricts_mappings}
              floatNumericRestrictsMappings: ${params.bigtable.float_numeric_restricts_mappings}
              doubleNumericRestrictsMappings: ${params.bigtable.double_numeric_restricts_mappings}
              outputDirectory: ${embeddings_output_folder}
              filenamePrefix: ${params.gcs.output_file_prefix}
            environment:
              tempLocation: ${params.dataflow.temp_location}
              numWorkers: ${params.dataflow.num_workers}
              maxWorkers: ${params.dataflow.max_workers}
              serviceAccountEmail: ${params.dataflow.service_account_email}
            gcsPath: ${dataflow_template_path}
          connector_params:
              timeout: 43200
        result: dataflow_bigtable_export_job_result
    - set_dataflow_bigtable_export_job_id:
        assign:
          - dataflow_export_job_id: ${dataflow_bigtable_export_job_result.id}
    - log_dataflow_job:
        call: sys.log
        args:
          text: ${"Export of data to gcs complete. You can view the job details at " + "https://console.cloud.google.com/dataflow/jobs/" + params.dataflow.location + "/" + dataflow_export_job_id}
    - trigger_vector_search_index_update:
        try:
          call: http.request
          args:
              url: ${"https://" + params.vertex.location + "-aiplatform.googleapis.com/v1/projects/" + params.vertex.project_id + "/locations/" + params.vertex.location + "/indexes/" + params.vertex.vector_search_index_id}
              method: PATCH
              body:
                metadata:
                  contentsDeltaUri: ${embeddings_output_folder}
                  isCompleteOverwrite: true
              auth:
                type: OAuth2
          result: index_update_response
        retry: ${http.default_retry_non_idempotent}
    - set_vector_search_index_update_operation:
        assign:
          - vector_search_operation: ${index_update_response.body.name}
          - vector_search_index_update_completed: False
    - log_vector_search_operation:
        call: sys.log
        args:
          text: ${"Vector search index update operation started - " + vector_search_operation}
    - polling_vector_search_index_update_operation:
        try:
          steps:
            - index_update_lro_url:
                try:
                  call: http.request
                  args:
                    url: ${"https://" + params.vertex.location + "-aiplatform.googleapis.com/v1/" + vector_search_operation}
                    method: GET
                    auth:
                      type: OAuth2
                  result: index_update_operation_response
                retry: ${http.default_retry}
            - index_state:
                switch:
                    - condition: ${"done" in index_update_operation_response.body}
                      assign:
                        - vector_search_index_update_completed:  ${index_update_operation_response.body.done}
            - log_index_state:
                switch:
                      - condition: ${vector_search_index_update_completed != true}
                        call: sys.log
                        args:
                          text: ${"Vertex Vector Search Index update is in progress."}
                      - condition: ${vector_search_index_update_completed == true}
                        call: sys.log
                        args:
                          text: ${"Vertex Vector Search Index update completed."}
            - induce_backoff_retry_if_index_build_not_completed:
                switch:
                  - condition: ${vector_search_index_update_completed != true}
                    raise: False

        retry:
          predicate: ${job_state_predicate}
          max_retries: 1000
          backoff:
            initial_delay: 300
            max_delay: 1800
            multiplier: 1.25
    - workflow_output:
        switch:
            - condition: ${"error" in index_update_operation_response.body}
              return: ${index_update_operation_response.body.error}
            - condition: ${"response" in index_update_operation_response.body}
              return: ${index_update_operation_response.body.response}

job_state_predicate:
  params: [vector_search_index_update_completed]
  steps:
    - condition_to_retry:
        switch:
          - condition: ${vector_search_index_update_completed != true}
            return: True # do retry
    - otherwise:
        return: False # stop retrying


================================================
FILE: bigtable-ai/vertex-vector-search/workflows/sample-batch-input.json
================================================
{
  "project_id": "<GCP PROJECT ID>",
  "location": "<Location/Region for Dataflow and Vertex>",
  "dataflow": {
    "temp_location": "<Cloud Storage Location to store temporary files (eg: gs://<bucket_name>/<folder_name>>/)>"
  },
  "gcs": {
    "output_folder": "<Cloud Storage Location to store generated embeddings (eg: gs://<bucket_name>/<folder_name>/)>"
  },
  "bigtable": {
    "instance_id": "<Bigtable Instance Id>",
    "app_profile_id": "<Bigtable App Profile Id>",
    "table_name": "<Bigtable Table Name>",
    "id_column": "<The fully qualified column name where the ID is stored. In the format cf:col or _key>",
    "embedding_column": "<The fully qualified column name where the embeddings are stored. In the format cf:col or _key>",
    "crowding_tag_column": "<The fully qualified column name where the crowding tag is stored. In the format cf:col or _key>",
    "allow_restricts_mappings": "<Columns to Export from Bigtable Table to allow restricts>",
    "deny_restricts_mappings": "<Columns to Export from Bigtable Table to deny restricts>",
    "int_numeric_restricts_mappings": "<Columns to Export from Bigtable Table to int numeric restricts>",
    "float_numeric_restricts_mappings": "<Columns to Export from Bigtable Table to float numeric restricts>",
    "double_numeric_restricts_mappings": "<Columns to Export from Bigtable Table to double numeric restricts>"
  },
  "vertex": {
    "vector_search_index_id": "<Vertex Vector Search Index ID>"
  }
}


================================================
FILE: callback-basic/README.md
================================================
# Basic callback endpoint sample

In this sample, you will see to create a callback endpoint within your workflow
that can receive HTTP `GET` or `POST` requests and in a later step wait for a request to arrive
at that endpoint.

## Enable services

First, enable required services:

```sh
gcloud services enable workflows.googleapis.com
```

## Define workflow

Create a [workflow-get.yaml](workflow-get.yaml) and
[worklow-post.yaml](workflow-post.yaml] to define workflows accepting HTTP `GET`
or `POST`.

In the `create_callback` step, create an endpoint with `GET`:

```yaml
- create_callback:
    call: events.create_callback_endpoint
    args:
        http_callback_method: "GET"
    result: callback_details
```

Or you could create an endpoint with `POST`:

```yaml
- create_callback:
    call: events.create_callback_endpoint
    args:
        http_callback_method: "POST"
    result: callback_details
```

In the second step, print the `url` for the callback endpoint:

```yaml
- print_callback_details:
    call: sys.log
    args:
        severity: "INFO"
        text: ${"Listening for callbacks on " + callback_details.url}
```

In the third step, wait for the callback to be called with a timeout:

```yaml
- await_callback:
    call: events.await_callback
    args:
        callback: ${callback_details}
        timeout: 3600
    result: callback_request
```

In the last step, print the callback result:

```yaml
- print_callback_result:
    return: ${callback_request.http_request}
```

## Deploy and execute workflow

Deploy workflows:

```sh
gcloud workflows deploy callback-basic-get --source=workflow-get.yaml
gcloud workflows deploy callback-basic-post --source=workflow-post.yaml
```

Run workflows:

```sh
gcloud workflows run callback-basic-get
gcloud workflows run callback-basic-post
```

The workflow should be in waiting state right now. Check the logs to find the
url of the callback.

## Test

Assuming that you have `Workflows Editor` or `Workflows Admin` roles, you can
call the workflow with `gcloud`.

To test the workflow waiting for `GET`:

```sh
CALLBACK_URL=https://workflowexecutions.googleapis.com/v1/projects/...
curl -X GET -H "Authorization: Bearer $(gcloud auth print-access-token)" $CALLBACK_URL
```

To test the workflow waiting for `POST`:

```sh
CALLBACK_URL=https://workflowexecutions.googleapis.com/v1/projects/...
curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $(gcloud auth print-access-token)" -d '{"foo" : "bar"}' $CALLBACK_URL
```

You should see the workflow execution succeed.


================================================
FILE: callback-basic/workflow-get.yaml
================================================
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

- create_callback:
    call: events.create_callback_endpoint
    args:
        http_callback_method: "GET"
    result: callback_details
- print_callback_details:
    call: sys.log
    args:
        severity: "INFO"
        text: ${"Listening for callbacks on " + callback_details.url}
- await_callback:
    call: events.await_callback
    args:
        callback: ${callback_details}
        timeout: 3600
    result: callback_request
- print_callback_result:
    return: ${callback_request.http_request}

================================================
FILE: callback-basic/workflow-post.yaml
================================================
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

- create_callback:
    call: events.create_callback_endpoint
    args:
        http_callback_method: "POST"
    result: callback_details
- print_callback_details:
    call: sys.log
    args:
        severity: "INFO"
        text: ${"Listening for callbacks on " + callback_details.url}
- await_callback:
    call: events.await_callback
    args:
        callback: ${callback_details}
        timeout: 3600
    result: callback_request
- print_callback_result:
    return: ${callback_request.http_request}

================================================
FILE: callback-event/README.md
================================================
# Event callback sample

Workflows [callbacks](https://cloud.google.com/workflows/docs/creating-callback-endpoints)
allow workflow executions to wait for another service to make an HTTP request to the
callback endpoint; that request resumes the execution of the workflow.

This works for HTTP callbacks but wouldn't it be nice if Workflows waited for an
event callback as well such as a message to a Pub/Sub topic or a new file
creation in a Cloud Storage bucket?

In this sample, you'll see how to get Workflows listen for event callbacks from
Pub/Sub and Cloud Storage. The idea is as follows:

![Architecture](architecture.png)

1. A `callback-event-sample` workflow creates a callback for an event source
   that it is interested in waiting events from.
1. It stores the callback for the event source in a document in Firestore.
1. It carries on with its workflow and at some point, starts waiting for an
   event.
1. In the meantime, `callback-event-listener` is waiting for events from a
   Pub/Sub topic and a Cloud Storage bucket with Eventarc.
1. At some point, Eventarc receives an event and passes on the event listener.
1. Finds the document for the event source in Firestore.
1. Calls back all the callback URLs registered with that event source.
1. `callback-event-sample` workflow receives the event and stops waiting.
1. It deletes the callback url from Firestore and continues with its workflow.

## Before you begin

You can use an existing project or create a new project:

```sh
PROJECT_ID=your-project-id
gcloud projects create $PROJECT_ID
```

Make sure your project id is set in `gcloud`:

```sh
gcloud config set project $PROJECT_ID
```

Run [setup.sh](setup.sh) to:

1. Enable required services and initialize a Firestore database.
1. Create a Pub/Sub topic and a Cloud Storage bucket to listen events from.
1. Deploy a `callback-event-listener` workflow.
1. Create Eventarc triggers to listen for Pub/Sub and Cloud Storage events and
   forward to the `callback-event-listener` workflow.
1. Deploy a `callback-event sample` workflow.

## Callback event listener

Callback event listener listens for events from Eventarc, checks if Firestore
has registered callbacks for the event source and if so, calls those callbacks
with the event. You can see
[callback-event-listener.yaml](callback-event-listener.yaml) for details.

## Callback event sample

You can check [callback-event-sample.yaml](callback-event-sample.yaml) for a
sample workflow to wait for an event from a Pub/Sub topic and a Cloud Storage
bucket by creating a callback, storing it to Firestore, waiting for the callback
from the `callback-event-listener` and deleting the callback from Firestore.

## Test

To test the event callbacks, first execute the sample workflow:

```sh
gcloud workflows run callback-event-sample
```

The workflow should just wait and you can also confirm it on Google Cloud
Console:

![Workflow execution](image1.png)

To test Pub/Sub callbacks, send a Pub/Sub message:

```sh
TOPIC=topic-callback
gcloud pubsub topics publish $TOPIC --message="Hello World"
```

You should see that the workflow started and stopped waiting for the event. It
also receives the Pub/Sub message in its callback:

```sh
Started waiting for an event from source topic-callback
Stopped waiting for an event from source topic-callback
```

To test Cloud Storage events, upload a new file to Cloud Storage:

```sh
BUCKET=$PROJECT_ID-bucket-callback
echo "Hello World" > random.txt
gcloud storage cp random.txt gs://$BUCKET/random.txt
```

You should see that the workflow started and stopped waiting for the event. It
also receives the Cloud Storage event in its callback:

```sh
Started waiting for an event from source $PROJECT_ID-bucket-callback
Stopped waiting for an event from source $PROJECT_ID-bucket-callback
```

At this point, the workflow should stop executing and you should also see the
received Pub/Sub and Cloud Storage events in the output:

![Workflow execution output](image2.png)


================================================
FILE: callback-event/architecture.drawio
================================================
<mxfile host="Electron" modified="2022-08-01T14:03:25.985Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/19.0.3 Chrome/102.0.5005.63 Electron/19.0.3 Safari/537.36" etag="kCf76-K6ta2k8DFI6hXn" version="19.0.3" type="device"><diagram id="86gbPJVdL4APfPZtZg9_" name="Page-1">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</diagram></mxfile>

================================================
FILE: callback-event/callback-event-listener.yaml
================================================
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Callback event listener listens for events from Eventarc, checks if Firestore
# has registered callbacks for the event source and if so, calls those callbacks
# with the event
# [START workflows_callback_listener]
main:
  params: [event]
  steps:
    - log_event:
        call: sys.log
        args:
          text: ${event}
          severity: INFO
    - init:
        assign:
          - database_root: ${"projects/" + sys.get_env("GOOGLE_CLOUD_PROJECT_ID") + "/databases/(default)/documents/callbacks/"}
          - event_source_tokens: ${text.split(event.source, "/")}
          - event_source_len: ${len(event_source_tokens)}
          - event_source: ${event_source_tokens[event_source_len - 1]}
          - doc_name: ${database_root + event_source}
    - get_document_for_event_source:
        try:
          call: googleapis.firestore.v1.projects.databases.documents.get
          args:
            name: ${doc_name}
          result: document
        except:
            as: e
            steps:
                - known_errors:
                    switch:
                    - condition: ${e.code == 404}
                      return: ${"No callbacks for event source " + event_source}
                - unhandled_exception:
                    raise: ${e}
    - process_callback_urls:
        steps:
          - check_fields_exist:
              switch:
              - condition: ${not("fields" in document)}
                return: ${"No callbacks for event source " + event_source}
              - condition: true
                next: processFields
          - processFields:
              for:
                  value: key
                  in: ${keys(document.fields)}
                  steps:
                      - extract_callback_url:
                          assign:
                              - callback_url: ${document.fields[key]["stringValue"]}
                      - log_callback_url:
                          call: sys.log
                          args:
                            text: ${"Calling back url " + callback_url}
                            severity: INFO
                      - http_post:
                          call: http.post
                          args:
                              url: ${callback_url}
                              auth:
                                  type: OAuth2
                              body:
                                  event: ${event}
# [END workflows_callback_listener]


================================================
FILE: callback-event/callback-event-sample.yaml
================================================
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# A sample workflow to wait for an event from a a Pub/Sub topic and a Cloud
# Storage bucket by creating a callback, storing it to Firestore, waiting for
# the callback and deleting it from Firestore.
# [START workflows_callback_event_sample]
main:
  steps:
    - init:
        assign:
          - pubsub_topic: topic-callback
          - storage_bucket: ${sys.get_env("GOOGLE_CLOUD_PROJECT_ID") + "-bucket-callback"}
    - await_pubsub_message:
        call: await_callback_event
        args:
          event_source: ${pubsub_topic}
        result: pubsub_event
    - await_storage_bucket:
        call: await_callback_event
        args:
          event_source: ${storage_bucket}
        result: storage_event
    - return_events:
        return:
            pubsub_event: ${pubsub_event}
            storage_event: ${storage_event}

await_callback_event:
    params: [event_source]
    steps:
        - init:
            assign:
              - database_root: ${"projects/" + sys.get_env("GOOGLE_CLOUD_PROJECT_ID") + "/databases/(default)/documents/callbacks/"}
              - doc_name: ${database_root + event_source}
              - execution_id: ${sys.get_env("GOOGLE_CLOUD_WORKFLOW_EXECUTION_ID")}
              - firestore_key: ${"exec_" + text.split(execution_id, "-")[0]}
        - create_callback:
            call: events.create_callback_endpoint
            args:
              http_callback_method: POST
            result: callback_details
        - save_callback_url:
            call: googleapis.firestore.v1.projects.databases.documents.patch
            args:
              name: ${doc_name}
              updateMask:
                fieldPaths: ["${firestore_key}"]
              body:
                fields:
                  ${firestore_key}:
                    stringValue: ${callback_details.url}
        - log_and_await_callback:
            try:
              steps:
                - log_await_start:
                    call: sys.log
                    args:
                      severity: INFO
                      data: ${"Started waiting 1hr for an event from source " + event_source}
                - await_callback:
                    call: events.await_callback
                    args:
                      callback: ${callback_details}
                      timeout: 3600
                    result: callback_request
                - log_await_stop:
                    call: sys.log
                    args:
                      severity: INFO
                      data: ${"Stopped waiting for an event from source " + event_source}
            except:
                as: e
                steps:
                    - log_error:
                        call: sys.log
                        args:
                            severity: "ERROR"
                            text: ${"Received error " + e.message}
        - delete_callback_url:
            call: googleapis.firestore.v1.projects.databases.documents.patch
            args:
              name: ${doc_name}
              updateMask:
                fieldPaths: ["${firestore_key}"]
        - check_null_event:
            switch:
              - condition: ${callback_request == null}
                return: null
        - log_await_result:
            call: sys.log
            args:
              severity: INFO
              data: ${callback_request.http_request.body.event}
        - return_event:
            return: ${callback_request.http_request.body.event}
# [END workflows_callback_event_sample]


================================================
FILE: callback-event/setup.sh
================================================
#!/bin/bash

# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

REGION=us-central1

echo "Get the project id"
PROJECT_ID=$(gcloud config get-value project)

echo "Enable required services"
gcloud services enable \
  appengine.googleapis.com \
  eventarc.googleapis.com \
  firestore.googleapis.com \
  pubsub.googleapis.com \
  workflows.googleapis.com

echo "Create an App Engine app (required for Firestore)"
gcloud app create --region=us-central

echo "Create a Firestore database"
gcloud firestore databases create --region=us-central

echo "Create a Pub/Sub topic"
TOPIC=topic-callback
gcloud pubsub topics create $TOPIC

echo "Create a Cloud Storage bucket"
BUCKET=$PROJECT_ID-bucket-callback
gcloud storage buckets create --location=$REGION gs://$BUCKET

echo "Deploy a callback-event-listener workflow"
WORKFLOW_NAME=callback-event-listener
gcloud workflows deploy $WORKFLOW_NAME \
  --source=$WORKFLOW_NAME.yaml \
  --location=$REGION

echo "Create a service account for Eventarc triggers to use to invoke Workflows"
SERVICE_ACCOUNT=eventarc-workflows

gcloud iam service-accounts create $SERVICE_ACCOUNT \
  --display-name="Eventarc Workflows service account"

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member serviceAccount:$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com \
  --role roles/eventarc.eventReceiver

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member serviceAccount:$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com \
  --role roles/workflows.invoker

echo "Grant the pubsub.publisher role to the Cloud Storage service account needed for Eventarc's Cloud Storage trigger"
SERVICE_ACCOUNT_STORAGE="$(gcloud storage service-agent --project=$PROJECT_ID)"

gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member serviceAccount:$SERVICE_ACCOUNT_STORAGE \
    --role roles/pubsub.publisher

echo "Create an Eventarc trigger to listen for events from the Pub/Sub topic and route to callback-event-listener"
gcloud eventarc triggers create trigger-pubsub-events-listener \
  --location=$REGION \
  --destination-workflow=$WORKFLOW_NAME \
  --destination-workflow-location=$REGION \
  --event-filters="type=google.cloud.pubsub.topic.v1.messagePublished" \
  --transport-topic=$TOPIC \
  --service-account=$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com

echo "Create an Eventarc trigger to listen for events from the Cloud Storage bucket and route to callback-event-listener"
gcloud eventarc triggers create trigger-storage-events-listener \
  --location=$REGION \
  --destination-workflow=$WORKFLOW_NAME \
  --destination-workflow-location=$REGION \
  --event-filters="type=google.cloud.storage.object.v1.finalized" \
  --event-filters="bucket=$BUCKET" \
  --service-account=$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com

echo "Deploy a callback-event-sample workflow"
WORKFLOW_NAME=callback-event-sample
gcloud workflows deploy $WORKFLOW_NAME \
  --source=$WORKFLOW_NAME.yaml \
  --location=$REGION

================================================
FILE: callback-translation/README.MD
================================================
# Human validation of text translation via Webhooks callback endpoints

In this example, you will take advantage of the **callback** feature of Workflows:
by creating an HTTP callback, and waiting for some external user or process 
to call that callback endpoint.
This allows workflow executions to be paused, and then be resumed once the callback endpoint is called.

The use case in this sample is **human validation of automated translation**.
On a web page, a user can enter some text in English, and request its translation in French.
A workflow executes, calls the [Translation API](https://cloud.google.com/translate),
waits for the user to either validate or reject the translation, thanks to a callback.
And the workflow execution finishes with the outcome of this validation.

## Technical overview

This demo consists of:
* a Cloud Firestore database to store the text, the translation, the callback endpoint, the approval status, as those details are available
* the web page is using the Firebase SDK to get updates in realtime when data stored in Firestore is updated
* two Cloud Functions called from the web page:
    * a first function to trigger the workflow execution, passing the input text to translate
    * a second function to call the callback endpoint, passing the approval status
* and a workflow is orchestrating the whole process

## Architecture diagram

![Architecture diagram](architecture-translation.png)

1. First, the user visits the translation web page.
The web page can be hosted anywhere, in a cloud storage bucket, on Firebase hosting, or App Engine.
The user then fills a textarea with the text they want to translate, and click on the *translate* button.
2. Clicking on the button will call a Cloud Function, that will launch an execution of the workflow. The text to translate is passed as a parameter of the function, and as a parameter of the workflow too.
3. The text is saved in Cloud Firestore, and the Translation API is called with the input text, and will return the translation, which will be stored in Firestore as well. The translation appears in the web page.
4. A step in the workflow creates a callback endpoint (also saved in Firestore), so that it can be called to validate or reject the automatic translation. When the callback endpoint is saved in Firestore, the web page displays validation and rejection buttons.
5. The workflow now explicitly awaits the callback endpoint to be called. This pauses the workflow execution.
6. The user decides to either validate or reject the translation. When one of the two buttons is clicked, a Cloud Function is called, with the approval status as parameter, which will in turn call the callback endpoint created by the workflow, also passing the approval status. The workflow resumes its execution, and saves the approval in Firestore. And this is the end of our workflow.

## Creating a workflow callback and awaiting

In order to create a callback, here's the definition of the step in the workflow:

```yaml
- create_callback:
    call: events.create_callback_endpoint
    args:
        http_callback_method: "POST"
    result: callback_details
```

You call the `events.create_callback_endpoint` built-in function.
As argument, you pass the HTTP method you want the caller to use to call your callback endpoint. The `callback_details` result is a dictionary that contains the URL of the callback. At this point, the callback is ready to receive incoming requests.

```yaml
- await_callback:
    call: events.await_callback
    args:
        callback: ${callback_details}
        timeout: 3600
    result: callback_request
```

To explicitly pause the workflow, to await the incoming request on the callback endpoint, you have to call the `events.await_callback` built-in function, passing the callback details as argument, as well as the duration in seconds that you want to wait for the callback to arrive.

## Triggering the execution of a workflow

The first fuction is responsible for launching the execution of the workflow.

```javascript
// require the Workflows NPM module
const {ExecutionsClient} = require('@google-cloud/workflows');
const client = new ExecutionsClient();

// create the workflow execution
const execResponse = await client.createExecution({
    parent: client.workflowPath(PROJECT_ID, CLOUD_REGION, WORKFLOW_NAME),
    execution: {
        argument: JSON.stringify({text})
    }
});

// retrive the execution details, including the execution ID
const execName = execResponse[0].name;
};
```

The `@google-cloud/workflows` NPM module, and its `ExecutionClient` class will create executions of the workflows, via its `createExecution()` method. 

A workflow is identified via its project ID, the cloud region within which it's deployed, and the name of the workflow.

You can pass an argument to the workflow execution, in the form of a JSON object encoded as a string.

The result of this execution creation will return an object that identifies a workflow execution, including its execution ID.

## Calling a workflow callback endpoint

The second cloud function is responsible for calling the callback endpoint. This snippets below show the call made with the `node-fetch` NPM module. But it is also using the `google-auth-library` to make an authenticated call, thanks to an access token.

```javascript
const fetch = require('node-fetch');

// fetch an access token
const {GoogleAuth} = require('google-auth-library');
const auth = new GoogleAuth();
const token = await auth.getAccessToken();

// call the callback endpoint URL
const resp = await fetch(url, {
    method: 'POST',
    headers: {
        'accept': 'application/json',
        'content-type': 'application/json',
        'authorization': `Bearer ${token}`
    },
    body: JSON.stringify({ approved })
});

// check the result to ensure that the call succeeded
const result = await resp.json();
```

## Project setup

The following APIs need to be enabled:

```sh
gcloud services enable \
    appengine.googleapis.com \
    cloudbuild.googleapis.com \
    cloudfunctions.googleapis.com \
    firestore.googleapis.com \
    workflows.googleapis.com \
    translate.googleapis.com
```

> **Note —** Some of the commands below needs adaptation depending on which regions you want to develop the functions, the database, or the workflow definitoin.

### Preparing the Cloud Firestore database

```sh
export REGION=europe-west2

gcloud app create --region=${REGION}
gcloud firestore databases create --region=${REGION}
```

### Deploying the two cloud functions

```sh
gcloud functions deploy invokeTranslationWorkflow \
  --region=${REGION} \
  --source=./functions/invokeTranslationWorkflow \
  --runtime nodejs14 \
  --entry-point=invokeTranslationWorkflow \
  --set-env-vars PROJECT_ID=${GOOGLE_CLOUD_PROJECT},CLOUD_REGION=${REGION},WORKFLOW_NAME=translation_validation \
  --trigger-http \
  --allow-unauthenticated

gcloud functions deploy translationCallbackCall \
  --region=${REGION} \
  --source=./functions/translationCallbackCall \
  --runtime nodejs14 \
  --entry-point=translationCallbackCall \
  --trigger-http \
  --allow-unauthenticated
```

### Hosting the web page

The HTML page, the JavaScript script, the CSS stylesheet, can be hosted anywhere where they can be freely accessible. They can as well be served locally on your own machine for testing purpose. 

When developing this demo, I deployed the static assets on Firebase Hosting, and after installing the Firebase SDK on my machine, was able to run the files locally with:

```sh
firebase serve
```

> **Note —** The `script.js` file should be updated with the right URL pointing at the two functions used to create the workflow execution, and call the callback. 
Search for the `UPDATE_ME` placeholder value, and update both occurrences.

> **Note —** The `index.html` file contains the Firebase configuration. If you're deploying on Firebase Hosting, be sure to update the `XXXX` placeholder values withe the right configuration from Firebase.

### Deploying the workflow definition

```sh
export WORKFLOWS_REGION=europe-west2

gcloud workflows deploy translation_validation \
    --source=translation-validation.yaml \
    --region=${WORKFLOWS_REGION}
```


================================================
FILE: callback-translation/architecture-translation.drawio
================================================
<mxfile host="app.diagrams.net" modified="2021-06-09T12:15:10.547Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36" etag="Qt2Er1eJdrWkfCrzXmCH" version="14.4.8" type="device"><diagram id="FKr-O2-dAN8iAl6xenT2" name="Page-1">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
Download .txt
gitextract_rgftideu/

├── .github/
│   └── sync-repo-settings.yaml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── batch-translation/
│   ├── README.md
│   └── workflow.yaml
├── bigquery-parallel/
│   ├── README.md
│   ├── workflow-parallel.yaml
│   └── workflow-serial.yaml
├── bigtable-ai/
│   └── vertex-vector-search/
│       ├── tests/
│       │   ├── noxfile.py
│       │   ├── requirements.txt
│       │   └── system/
│       │       ├── workflow-input.json
│       │       └── workflow-test.py
│       └── workflows/
│           ├── README.md
│           ├── batch-export.yaml
│           └── sample-batch-input.json
├── callback-basic/
│   ├── README.md
│   ├── workflow-get.yaml
│   └── workflow-post.yaml
├── callback-event/
│   ├── README.md
│   ├── architecture.drawio
│   ├── callback-event-listener.yaml
│   ├── callback-event-sample.yaml
│   └── setup.sh
├── callback-translation/
│   ├── README.MD
│   ├── architecture-translation.drawio
│   ├── invokeTranslationWorkflow/
│   │   ├── index.js
│   │   └── package.json
│   ├── public/
│   │   ├── index.html
│   │   ├── script.js
│   │   └── style.css
│   ├── translation-validation.yaml
│   └── translationCallbackCall/
│       ├── index.js
│       └── package.json
├── cloud-run-jobs/
│   ├── README.md
│   └── workflow.yaml
├── cloud-run-jobs-payload-gcs/
│   ├── README.md
│   ├── create-trigger.sh
│   ├── deploy-workflow.sh
│   ├── message-payload-job/
│   │   ├── Procfile
│   │   ├── deploy-job.sh
│   │   ├── process.py
│   │   └── requirements.txt
│   └── workflow.yaml
├── connector-compute/
│   ├── README.md
│   ├── create-stop-vm-connector.yaml
│   ├── create-vm-connector.yaml
│   ├── create-vm.yaml
│   └── stop-vm.yaml
├── gcs-dlp/
│   ├── README.md
│   ├── dlp-gcs-workflow.yaml
│   └── functions/
│       ├── get_dlp_job_status.py
│       ├── inspect_gcs_file.py
│       └── trigger-dlp-workflow.py
├── gcs-read-write-json/
│   ├── README.md
│   ├── gcs-env-var-workflow.yaml
│   ├── gcs-read-workflow.yaml
│   └── gcs-write-workflow.yaml
├── gitops/
│   ├── README.md
│   ├── cloudbuild.yaml
│   ├── images/
│   │   └── architecture.drawio
│   ├── setup.sh
│   ├── test-master.sh
│   ├── test-staging.sh
│   └── workflow.yaml
├── long-running-container/
│   ├── PrimeGenService/
│   │   ├── Controllers/
│   │   │   └── PrimeGenController.cs
│   │   ├── Dockerfile
│   │   ├── PrimeGenService.csproj
│   │   └── Program.cs
│   ├── README.md
│   └── prime-generator.yaml
├── multi-env-deployment/
│   ├── README.md
│   ├── cloudbuild.yaml
│   ├── images/
│   │   └── architecture.drawio
│   ├── main.tf
│   ├── setup.sh
│   ├── workflow1.yaml
│   ├── workflow2.yaml
│   └── workflow3.yaml
├── reddit-sentiment/
│   ├── README.md
│   └── workflow.yaml
├── retries-and-saga/
│   ├── CustomerService/
│   │   ├── Controllers/
│   │   │   └── CustomerController.cs
│   │   ├── Credit.cs
│   │   ├── CustomerService.csproj
│   │   ├── Dockerfile
│   │   └── Program.cs
│   ├── OrderService/
│   │   ├── Controllers/
│   │   │   └── OrderController.cs
│   │   ├── Dockerfile
│   │   ├── Order.cs
│   │   ├── OrderService.csproj
│   │   └── Program.cs
│   ├── README.md
│   ├── ordering-v1.yaml
│   ├── ordering-v2.yaml
│   └── ordering-v3.yaml
├── screenshot-jobs/
│   ├── README.md
│   ├── job1.txt
│   ├── job2.txt
│   └── workflow.yaml
├── secretmanager/
│   ├── README.md
│   ├── access-secret.yaml
│   └── create-secret.yaml
├── send-email/
│   ├── README.md
│   └── send-email-workflow.yaml
├── service-chaining/
│   ├── README.md
│   ├── floor/
│   │   ├── Dockerfile
│   │   └── app.py
│   ├── multiply/
│   │   ├── main.py
│   │   └── requirements.txt
│   ├── randomgen/
│   │   ├── main.py
│   │   └── requirements.txt
│   └── workflow.yaml
├── state-management-firestore/
│   ├── README.md
│   └── workflow.yaml
├── syntax-cheat-sheet/
│   ├── printable-pdf/
│   │   ├── index.html
│   │   └── style.css
│   └── workflow.yaml
├── terraform/
│   ├── basic/
│   │   ├── README.md
│   │   ├── main.tf
│   │   └── vars.tf
│   ├── import-multiple-yamls/
│   │   ├── README.md
│   │   ├── main.tf
│   │   ├── subworkflow.yaml
│   │   ├── vars.tf
│   │   └── workflow.yaml
│   └── import-yaml/
│       ├── README.md
│       ├── main.tf
│       ├── vars.tf
│       └── workflow.yaml
├── twitter-sentiment/
│   ├── README.md
│   └── workflow.yaml
├── twitter-sentiment-parallel/
│   ├── README.md
│   ├── workflow-parallel.yaml
│   └── workflow-serial.yaml
├── vertexai/
│   ├── country-histories/
│   │   ├── gemini-pro/
│   │   │   ├── README.md
│   │   │   ├── country-histories-connector.yaml
│   │   │   ├── country-histories.yaml
│   │   │   └── gemini-response.json
│   │   └── text-bison/
│   │       ├── README.md
│   │       ├── country-histories-connector.yaml
│   │       ├── country-histories.yaml
│   │       ├── country-history-connector.yaml
│   │       └── country-history.yaml
│   ├── describe-image/
│   │   ├── README.md
│   │   ├── describe-image-connector.yaml
│   │   ├── describe-image.yaml
│   │   └── gemini-response.json
│   └── parallel-summaries/
│       ├── README.md
│       ├── parallel-summaries-connector.yaml
│       ├── parallel-summaries-initial.yaml
│       ├── parallel-summaries.yaml
│       ├── pride_and_prejudice.txt
│       ├── pride_and_prejudice_1000.txt
│       └── pride_and_prejudice_4500.txt
├── workflow-executes-other-workflows/
│   ├── README.md
│   ├── workflow-child.yaml
│   └── workflow-parent.yaml
├── workflow-tasks-workflow/
│   ├── README.md
│   ├── workflow-child.yaml
│   └── workflow-parent.yaml
├── workflows-bigquery-load/
│   ├── README.md
│   ├── build.sh
│   ├── file_change_handler/
│   │   ├── main.py
│   │   └── requirements.txt
│   ├── generator/
│   │   ├── gen.py
│   │   └── requirements.txt
│   ├── main.tf
│   ├── workflow.yaml
│   └── workflow_handlers/
│       ├── main.py
│       └── requirements.txt
├── workflows-eventarc-integration/
│   ├── event-payload-storer/
│   │   ├── README.md
│   │   ├── event-payload-storer.yaml
│   │   ├── setup.sh
│   │   ├── test_pubsub.sh
│   │   └── test_storage.sh
│   └── workflows-pubsub/
│       ├── README.md
│       └── workflow.yaml
├── workflows-executes-commands/
│   ├── using-cloudbuild-api/
│   │   ├── README.md
│   │   ├── setup.sh
│   │   ├── workflow-gcloud.yaml
│   │   └── workflow-kubectl.yaml
│   └── using-standard-library/
│       ├── README.md
│       ├── setup.sh
│       ├── workflow-gcloud.yaml
│       └── workflow-kubectl.yaml
├── workflows-kubernetes-engine/
│   ├── README.md
│   └── workflow.yaml
└── workspace-integration/
    ├── sheets-to-workflows/
    │   ├── Code.gs
    │   ├── README.md
    │   ├── appscript.json
    │   ├── setup.sh
    │   └── workflow.yaml
    ├── workflows-awaits-sheets-callback/
    │   ├── Code.gs
    │   ├── README.md
    │   ├── appscript.json
    │   ├── setup.sh
    │   └── workflow.yaml
    └── workflows-to-sheets/
        ├── README.md
        ├── setup.sh
        └── workflow.yaml
Download .txt
SYMBOL INDEX (66 symbols across 17 files)

FILE: bigtable-ai/vertex-vector-search/tests/noxfile.py
  function tests (line 9) | def tests(session):
  function blacken (line 18) | def blacken(session):

FILE: bigtable-ai/vertex-vector-search/tests/system/workflow-test.py
  function project_id (line 69) | def project_id() -> Iterator[str]:
  function instance_id (line 74) | def instance_id() -> Iterator[str]:
  function setup_workflow (line 79) | def setup_workflow(project_id: str) -> Iterator[None]:
  function generate_vector_data (line 110) | def generate_vector_data(
  function setup_bigtable (line 179) | def setup_bigtable(project_id: str, instance_id: str, table_name: str) -...
  function deploy_workflow (line 229) | def deploy_workflow(project: str, location: str, workflow_name: str) -> ...
  function execute_workflow (line 282) | def execute_workflow(
  function get_worfklow_execution (line 343) | def get_worfklow_execution(arguments: dict) -> Execution:
  function workflow_execution_polling_predicate (line 372) | def workflow_execution_polling_predicate(
  function polling (line 390) | def polling(
  function sync_execute_workflow (line 425) | def sync_execute_workflow(
  function cleanup_bigtable_resources (line 456) | def cleanup_bigtable_resources(
  function read_index_datapoints (line 473) | def read_index_datapoints(
  function bigtable_vertex_vector_search_data (line 504) | def bigtable_vertex_vector_search_data(
  function compare_float_lists (line 543) | def compare_float_lists(
  function read_and_compare_vertex_data (line 586) | def read_and_compare_vertex_data(
  function test_bigtable_vertex_vector_search_integration (line 647) | def test_bigtable_vertex_vector_search_integration(
  function setup_and_execute_workflow (line 677) | def setup_and_execute_workflow(
  function test_concurrent_workflow_execution (line 732) | def test_concurrent_workflow_execution(
  function get_env_var (line 800) | def get_env_var(key: str, desc: str) -> str:

FILE: callback-translation/public/script.js
  function callCallbackUrl (line 130) | async function callCallbackUrl(url, approved) {

FILE: cloud-run-jobs-payload-gcs/message-payload-job/process.py
  function process (line 34) | def process():

FILE: gcs-dlp/functions/get_dlp_job_status.py
  function get_dlp_job_status (line 32) | def get_dlp_job_status(request):

FILE: gcs-dlp/functions/inspect_gcs_file.py
  function inspect_gcs_file (line 27) | def inspect_gcs_file(request):

FILE: gcs-dlp/functions/trigger-dlp-workflow.py
  function trigger_gcs_dlp (line 24) | def trigger_gcs_dlp(event, context):

FILE: long-running-container/PrimeGenService/Controllers/PrimeGenController.cs
  class PrimeGenController (line 20) | [ApiController]
    method PrimeGenController (line 31) | public PrimeGenController(ILogger<PrimeGenController> logger)
    method Start (line 36) | [HttpGet("start")]
    method Stop (line 54) | [HttpGet("stop")]
    method Get (line 62) | [HttpGet]
    method StartPrimeGeneration (line 69) | private void StartPrimeGeneration()
    method StopPrimeGeneration (line 82) | private void StopPrimeGeneration()
    method isPrime (line 89) | private static bool isPrime(BigInteger n)

FILE: retries-and-saga/CustomerService/Controllers/CustomerController.cs
  class CustomerController (line 19) | [ApiController]
    method CustomerController (line 28) | public CustomerController(ILogger<CustomerController> logger)
    method ReserveCreditAlwaysWorks (line 33) | [HttpPost("always-works")]
    method ReserveCreditSometimesWorks (line 40) | [HttpPost("sometimes-works")]

FILE: retries-and-saga/CustomerService/Credit.cs
  class Credit (line 17) | public class Credit

FILE: retries-and-saga/OrderService/Controllers/OrderController.cs
  class OrderController (line 19) | [ApiController]
    method OrderController (line 27) | public OrderController(ILogger<OrderController> logger)
    method Create (line 32) | [HttpPost]
    method Get (line 40) | [HttpGet("{id}")]
    method List (line 48) | [HttpGet]
    method Approve (line 56) | [HttpPut("approve/{id}")]
    method Reject (line 70) | [HttpPut("reject/{id}")]
    method Delete (line 84) | [HttpDelete("{id}")]
    method DeleteAl (line 98) | [HttpDelete]

FILE: retries-and-saga/OrderService/Order.cs
  type Status (line 17) | public enum Status
  class Order (line 24) | public class Order

FILE: service-chaining/floor/app.py
  function handle_post (line 27) | def handle_post():

FILE: service-chaining/multiply/main.py
  function multiply (line 21) | def multiply(request):

FILE: service-chaining/randomgen/main.py
  function randomgen (line 22) | def randomgen(request):

FILE: workflows-bigquery-load/file_change_handler/main.py
  function handle_new_file (line 21) | def handle_new_file(event, context):

FILE: workflows-bigquery-load/workflow_handlers/main.py
  class JobState (line 36) | class JobState(enum.Enum):
  class JobType (line 44) | class JobType(enum.Enum):
  function get_load_job (line 51) | def get_load_job(transaction, ref):
  function change_job_status (line 89) | def change_job_status(transaction, ref, state):
  function load_job (line 114) | def load_job(s_job_id):
  function start_query_job (line 152) | def start_query_job(s_job_id):
  function get_bigquery_job_status (line 180) | def get_bigquery_job_status(s_job_id):
  function create_job (line 233) | def create_job(request):
  function create_query (line 271) | def create_query(request):
  function run_bigquery_job (line 296) | def run_bigquery_job(request):
  function poll_bigquery_job (line 356) | def poll_bigquery_job(request):
Condensed preview — 201 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,766K chars).
[
  {
    "path": ".github/sync-repo-settings.yaml",
    "chars": 1501,
    "preview": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": ".gitignore",
    "chars": 2203,
    "preview": "# Local .terraform directories\n**/.terraform*\n\n# .tfstate files\n*.tfstate\n*.tfstate.*\n\n# Node.js\n**/node_modules/**\nnpm-"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1101,
    "preview": "# How to Contribute\n\nWe'd love to accept your patches and contributions to this project. There are\njust a few small guid"
  },
  {
    "path": "LICENSE",
    "chars": 11358,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "README.md",
    "chars": 5777,
    "preview": "# Workflows Samples\n\n![Workflows Logo](Workflows-128-color.png)\n\n[Workflows](https://cloud.google.com/workflows) allow y"
  },
  {
    "path": "batch-translation/README.md",
    "chars": 2865,
    "preview": "# Batch Translation using Cloud Translation API connector\n\nIn this sample, you will see how to use [Cloud Translation AP"
  },
  {
    "path": "batch-translation/workflow.yaml",
    "chars": 1702,
    "preview": "# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "bigquery-parallel/README.md",
    "chars": 4518,
    "preview": "# Running BigQuery jobs against Wikipedia dataset with Workflows parallel iteration\n\nIn this sample, you will see how to"
  },
  {
    "path": "bigquery-parallel/workflow-parallel.yaml",
    "chars": 2635,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "bigquery-parallel/workflow-serial.yaml",
    "chars": 2453,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "bigtable-ai/vertex-vector-search/tests/noxfile.py",
    "chars": 564,
    "preview": "import nox\n\nDEFAULT_PYTHON_VERSION = \"3.11\"\nBLACK_VERSION = \"black==22.3.0\"\nLINT_PATHS = [\"system\", \"noxfile.py\"]\n\n\n@nox"
  },
  {
    "path": "bigtable-ai/vertex-vector-search/tests/requirements.txt",
    "chars": 92,
    "preview": "google-cloud-bigtable==2.22.0\ngoogle-cloud-workflows==1.12.1\ngoogle-cloud-aiplatform\npytest\n"
  },
  {
    "path": "bigtable-ai/vertex-vector-search/tests/system/workflow-input.json",
    "chars": 1006,
    "preview": "{\n  \"project_id\": \"{{project_id}}\",\n  \"location\": \"{{location}}\",\n  \"dataflow\": {\n    \"job_name_prefix\": \"test-bigtable-"
  },
  {
    "path": "bigtable-ai/vertex-vector-search/tests/system/workflow-test.py",
    "chars": 25347,
    "preview": "# Copyright 2023 Google Inc.\n\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "bigtable-ai/vertex-vector-search/workflows/README.md",
    "chars": 15648,
    "preview": "# Cloud Bigtable to Vertex Vector Search Export README\n\n[Cloud Bigtable](https://cloud.google.com/bigtable) is a sparsel"
  },
  {
    "path": "bigtable-ai/vertex-vector-search/workflows/batch-export.yaml",
    "chars": 9648,
    "preview": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "bigtable-ai/vertex-vector-search/workflows/sample-batch-input.json",
    "chars": 1480,
    "preview": "{\n  \"project_id\": \"<GCP PROJECT ID>\",\n  \"location\": \"<Location/Region for Dataflow and Vertex>\",\n  \"dataflow\": {\n    \"te"
  },
  {
    "path": "callback-basic/README.md",
    "chars": 2562,
    "preview": "# Basic callback endpoint sample\n\nIn this sample, you will see to create a callback endpoint within your workflow\nthat c"
  },
  {
    "path": "callback-basic/workflow-get.yaml",
    "chars": 1078,
    "preview": "# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "callback-basic/workflow-post.yaml",
    "chars": 1079,
    "preview": "# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "callback-event/README.md",
    "chars": 3993,
    "preview": "# Event callback sample\n\nWorkflows [callbacks](https://cloud.google.com/workflows/docs/creating-callback-endpoints)\nallo"
  },
  {
    "path": "callback-event/architecture.drawio",
    "chars": 7928,
    "preview": "<mxfile host=\"Electron\" modified=\"2022-08-01T14:03:25.985Z\" agent=\"5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/5"
  },
  {
    "path": "callback-event/callback-event-listener.yaml",
    "chars": 3047,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "callback-event/callback-event-sample.yaml",
    "chars": 4097,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "callback-event/setup.sh",
    "chars": 3499,
    "preview": "#!/bin/bash\n\n# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
  },
  {
    "path": "callback-translation/README.MD",
    "chars": 8233,
    "preview": "# Human validation of text translation via Webhooks callback endpoints\n\nIn this example, you will take advantage of the "
  },
  {
    "path": "callback-translation/architecture-translation.drawio",
    "chars": 117508,
    "preview": "<mxfile host=\"app.diagrams.net\" modified=\"2021-06-09T12:15:10.547Z\" agent=\"5.0 (Macintosh; Intel Mac OS X 10_15_7) Apple"
  },
  {
    "path": "callback-translation/invokeTranslationWorkflow/index.js",
    "chars": 1669,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "callback-translation/invokeTranslationWorkflow/package.json",
    "chars": 153,
    "preview": "{\n  \"name\": \"launch-translation-workflow\",\n  \"version\": \"0.0.1\",\n  \"dependencies\": {\n    \"@google-cloud/workflows\": \"^1."
  },
  {
    "path": "callback-translation/public/index.html",
    "chars": 2578,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-widt"
  },
  {
    "path": "callback-translation/public/script.js",
    "chars": 5739,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "callback-translation/public/style.css",
    "chars": 216,
    "preview": "* {\n    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Hel"
  },
  {
    "path": "callback-translation/translation-validation.yaml",
    "chars": 4407,
    "preview": "# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "callback-translation/translationCallbackCall/index.js",
    "chars": 1868,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "callback-translation/translationCallbackCall/package.json",
    "chars": 178,
    "preview": "{\n  \"name\": \"approve-translation-workflow\",\n  \"version\": \"0.0.1\",\n  \"dependencies\": {\n    \"cors\": \"^2.8.5\",\n    \"node-fe"
  },
  {
    "path": "cloud-run-jobs/README.md",
    "chars": 2321,
    "preview": "# Execute a Cloud Run job using Workflows\n\nThis sample demonstrates how to execute a Cloud Run job from a workflow.\n\n## "
  },
  {
    "path": "cloud-run-jobs/workflow.yaml",
    "chars": 1881,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "cloud-run-jobs-payload-gcs/README.md",
    "chars": 2202,
    "preview": "# Execute a Cloud Run job using Workflows and event payload from Cloud Storage\n\nThis sample demonstrates how to execute "
  },
  {
    "path": "cloud-run-jobs-payload-gcs/create-trigger.sh",
    "chars": 1598,
    "preview": "#!/bin/bash\n\n# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
  },
  {
    "path": "cloud-run-jobs-payload-gcs/deploy-workflow.sh",
    "chars": 970,
    "preview": "#!/bin/bash\n\n# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
  },
  {
    "path": "cloud-run-jobs-payload-gcs/message-payload-job/Procfile",
    "chars": 120,
    "preview": "# Buildpacks require a web process to be defined\n# but this process will not be used.\nweb: echo \"no web\"\n\npython: python"
  },
  {
    "path": "cloud-run-jobs-payload-gcs/message-payload-job/deploy-job.sh",
    "chars": 1881,
    "preview": "#!/bin/bash\n\n# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
  },
  {
    "path": "cloud-run-jobs-payload-gcs/message-payload-job/process.py",
    "chars": 1431,
    "preview": "#!/usr/bin/env python3\n\n# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n"
  },
  {
    "path": "cloud-run-jobs-payload-gcs/message-payload-job/requirements.txt",
    "chars": 27,
    "preview": "google-cloud-storage==2.3.0"
  },
  {
    "path": "cloud-run-jobs-payload-gcs/workflow.yaml",
    "chars": 2122,
    "preview": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "connector-compute/README.md",
    "chars": 3866,
    "preview": "# Create, start, stop VM using Compute Connector\n\nIn this sample, you will see how to use [Workflows\nConnectors](https:/"
  },
  {
    "path": "connector-compute/create-stop-vm-connector.yaml",
    "chars": 2607,
    "preview": "# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "connector-compute/create-vm-connector.yaml",
    "chars": 1995,
    "preview": "# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "connector-compute/create-vm.yaml",
    "chars": 1891,
    "preview": "# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "connector-compute/stop-vm.yaml",
    "chars": 2918,
    "preview": "# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "gcs-dlp/README.md",
    "chars": 1454,
    "preview": "# Data Loss Prevention workflow\nResponds to Google Cloud Storage(GCS) file upload and runs a Data Loss Prevention(DLP) j"
  },
  {
    "path": "gcs-dlp/dlp-gcs-workflow.yaml",
    "chars": 2929,
    "preview": "# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "gcs-dlp/functions/get_dlp_job_status.py",
    "chars": 1796,
    "preview": "# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "gcs-dlp/functions/inspect_gcs_file.py",
    "chars": 2407,
    "preview": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "gcs-dlp/functions/trigger-dlp-workflow.py",
    "chars": 1082,
    "preview": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "gcs-read-write-json/README.md",
    "chars": 3431,
    "preview": "Although Workflows provides a \n[Google Cloud Storage connector](https://cloud.google.com/workflows/docs/reference/google"
  },
  {
    "path": "gcs-read-write-json/gcs-env-var-workflow.yaml",
    "chars": 1402,
    "preview": "\n# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this "
  },
  {
    "path": "gcs-read-write-json/gcs-read-workflow.yaml",
    "chars": 1074,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "gcs-read-write-json/gcs-write-workflow.yaml",
    "chars": 1016,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "gitops/README.md",
    "chars": 4689,
    "preview": "# Git-driven development, testing & deployment\n\nIn this sample, we show you how to set up a simple Git-driven developmen"
  },
  {
    "path": "gitops/cloudbuild.yaml",
    "chars": 1636,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "gitops/images/architecture.drawio",
    "chars": 1762,
    "preview": "<mxfile host=\"Electron\" modified=\"2022-08-11T09:22:15.846Z\" agent=\"5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/5"
  },
  {
    "path": "gitops/setup.sh",
    "chars": 1443,
    "preview": "#!/bin/bash\n\n# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
  },
  {
    "path": "gitops/test-master.sh",
    "chars": 1622,
    "preview": "#!/bin/bash\n\n# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
  },
  {
    "path": "gitops/test-staging.sh",
    "chars": 1278,
    "preview": "#!/bin/bash\n\n# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
  },
  {
    "path": "gitops/workflow.yaml",
    "chars": 767,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "long-running-container/PrimeGenService/Controllers/PrimeGenController.cs",
    "chars": 2678,
    "preview": "// Copyright 2022 Google LLC\r\n//\r\n// Licensed under the Apache License, Version 2.0 (the \"License\");\r\n// you may not use"
  },
  {
    "path": "long-running-container/PrimeGenService/Dockerfile",
    "chars": 410,
    "preview": "# syntax=docker/dockerfile:1\nFROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env\nWORKDIR /app\n\n# Copy csproj and restore "
  },
  {
    "path": "long-running-container/PrimeGenService/PrimeGenService.csproj",
    "chars": 327,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\r\n\r\n  <PropertyGroup>\r\n    <TargetFramework>net6.0</TargetFramework>\r\n    <Nullable"
  },
  {
    "path": "long-running-container/PrimeGenService/Program.cs",
    "chars": 1334,
    "preview": "// Copyright 2022 Google LLC\r\n//\r\n// Licensed under the Apache License, Version 2.0 (the \"License\");\r\n// you may not use"
  },
  {
    "path": "long-running-container/README.md",
    "chars": 6174,
    "preview": "# Long running containers with Workflows and Compute Engine\n\nSometimes, you need to execute a long-running task for hour"
  },
  {
    "path": "long-running-container/prime-generator.yaml",
    "chars": 4262,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "multi-env-deployment/README.md",
    "chars": 5604,
    "preview": "# Multi-environment deployment\n\nIn this sample, we show you how to use the same worklow in different\nenvironments such a"
  },
  {
    "path": "multi-env-deployment/cloudbuild.yaml",
    "chars": 1052,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "multi-env-deployment/images/architecture.drawio",
    "chars": 1982,
    "preview": "<mxfile host=\"Electron\" modified=\"2022-08-16T10:50:05.635Z\" agent=\"5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/5"
  },
  {
    "path": "multi-env-deployment/main.tf",
    "chars": 1148,
    "preview": "/**\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "multi-env-deployment/setup.sh",
    "chars": 1443,
    "preview": "#!/bin/bash\n\n# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
  },
  {
    "path": "multi-env-deployment/workflow1.yaml",
    "chars": 846,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "multi-env-deployment/workflow2.yaml",
    "chars": 813,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "multi-env-deployment/workflow3.yaml",
    "chars": 920,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "reddit-sentiment/README.md",
    "chars": 4747,
    "preview": "# Reddit sentiment analysis using Language API connector and iteration syntax\n\nIn this sample, you will see how to use ["
  },
  {
    "path": "reddit-sentiment/workflow.yaml",
    "chars": 1639,
    "preview": "main:\n  params: [args]\n  steps:\n    - init:\n        assign:\n          - subreddit: ${args.subreddit}\n          - count: "
  },
  {
    "path": "retries-and-saga/CustomerService/Controllers/CustomerController.cs",
    "chars": 2214,
    "preview": "// Copyright 2022 Google LLC\r\n//\r\n// Licensed under the Apache License, Version 2.0 (the \"License\");\r\n// you may not use"
  },
  {
    "path": "retries-and-saga/CustomerService/Credit.cs",
    "chars": 866,
    "preview": "// Copyright 2022 Google LLC\r\n//\r\n// Licensed under the Apache License, Version 2.0 (the \"License\");\r\n// you may not use"
  },
  {
    "path": "retries-and-saga/CustomerService/CustomerService.csproj",
    "chars": 327,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\r\n\r\n  <PropertyGroup>\r\n    <TargetFramework>net6.0</TargetFramework>\r\n    <Nullable"
  },
  {
    "path": "retries-and-saga/CustomerService/Dockerfile",
    "chars": 410,
    "preview": "# syntax=docker/dockerfile:1\nFROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env\nWORKDIR /app\n\n# Copy csproj and restore "
  },
  {
    "path": "retries-and-saga/CustomerService/Program.cs",
    "chars": 1405,
    "preview": "// Copyright 2022 Google LLC\r\n//\r\n// Licensed under the Apache License, Version 2.0 (the \"License\");\r\n// you may not use"
  },
  {
    "path": "retries-and-saga/OrderService/Controllers/OrderController.cs",
    "chars": 2985,
    "preview": "// Copyright 2022 Google LLC\r\n//\r\n// Licensed under the Apache License, Version 2.0 (the \"License\");\r\n// you may not use"
  },
  {
    "path": "retries-and-saga/OrderService/Dockerfile",
    "chars": 407,
    "preview": "# syntax=docker/dockerfile:1\nFROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env\nWORKDIR /app\n\n# Copy csproj and restore "
  },
  {
    "path": "retries-and-saga/OrderService/Order.cs",
    "chars": 995,
    "preview": "// Copyright 2022 Google LLC\r\n//\r\n// Licensed under the Apache License, Version 2.0 (the \"License\");\r\n// you may not use"
  },
  {
    "path": "retries-and-saga/OrderService/OrderService.csproj",
    "chars": 327,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\r\n\r\n  <PropertyGroup>\r\n    <TargetFramework>net6.0</TargetFramework>\r\n    <Nullable"
  },
  {
    "path": "retries-and-saga/OrderService/Program.cs",
    "chars": 1406,
    "preview": "// Copyright 2022 Google LLC\r\n//\r\n// Licensed under the Apache License, Version 2.0 (the \"License\");\r\n// you may not use"
  },
  {
    "path": "retries-and-saga/README.md",
    "chars": 8277,
    "preview": "# Retries and Saga Pattern in Workflows\n\nIn this tutorial, you will see how to apply retries and the saga pattern in\nWor"
  },
  {
    "path": "retries-and-saga/ordering-v1.yaml",
    "chars": 1663,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "retries-and-saga/ordering-v2.yaml",
    "chars": 1952,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "retries-and-saga/ordering-v3.yaml",
    "chars": 2713,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "screenshot-jobs/README.md",
    "chars": 8414,
    "preview": "# Take screenshots of webpages with Cloud Run jobs, Workflows and Eventarc\n\nIn this tutorial, you will see how to use Cl"
  },
  {
    "path": "screenshot-jobs/job1.txt",
    "chars": 44,
    "preview": "https://example.com\nhttps://cloud.google.com"
  },
  {
    "path": "screenshot-jobs/job2.txt",
    "chars": 81,
    "preview": "https://www.pinterest.com\nhttps://www.apartmenttherapy.com\nhttps://www.google.com"
  },
  {
    "path": "screenshot-jobs/workflow.yaml",
    "chars": 3339,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "secretmanager/README.md",
    "chars": 2291,
    "preview": "# Create and Access Secrets in Secret Manager\n\nIn this example you'll see how to use the [Workflow Connector to Secret M"
  },
  {
    "path": "secretmanager/access-secret.yaml",
    "chars": 932,
    "preview": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "secretmanager/create-secret.yaml",
    "chars": 1141,
    "preview": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "send-email/README.md",
    "chars": 1604,
    "preview": "Sending an email with SendGrid from a workflow\n===\n\nFor notification purposes, a workflow can have steps that send email"
  },
  {
    "path": "send-email/send-email-workflow.yaml",
    "chars": 1281,
    "preview": "\n# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this "
  },
  {
    "path": "service-chaining/README.md",
    "chars": 2445,
    "preview": "# Service chaining\n\nIn this sample, you will orchestrate multiple Cloud Functions, Cloud Run and\nexternal services in a "
  },
  {
    "path": "service-chaining/floor/Dockerfile",
    "chars": 1183,
    "preview": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "service-chaining/floor/app.py",
    "chars": 1265,
    "preview": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "service-chaining/multiply/main.py",
    "chars": 876,
    "preview": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "service-chaining/multiply/requirements.txt",
    "chars": 39,
    "preview": "flask>=1.0.2\nfunctions-framework==3.0.0"
  },
  {
    "path": "service-chaining/randomgen/main.py",
    "chars": 874,
    "preview": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "service-chaining/randomgen/requirements.txt",
    "chars": 39,
    "preview": "flask>=1.0.2\nfunctions-framework==3.0.0"
  },
  {
    "path": "service-chaining/workflow.yaml",
    "chars": 1398,
    "preview": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "state-management-firestore/README.md",
    "chars": 574,
    "preview": "# Workflows state management with Firestore\n\nSometimes, you need to store some state (typically a key/value pair) in a\ns"
  },
  {
    "path": "state-management-firestore/workflow.yaml",
    "chars": 4500,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "syntax-cheat-sheet/printable-pdf/index.html",
    "chars": 16801,
    "preview": "<!DOCTYPE html>\n\n<!-- Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# yo"
  },
  {
    "path": "syntax-cheat-sheet/printable-pdf/style.css",
    "chars": 2418,
    "preview": "/* Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this "
  },
  {
    "path": "syntax-cheat-sheet/workflow.yaml",
    "chars": 11132,
    "preview": "# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "terraform/basic/README.md",
    "chars": 1068,
    "preview": "# Basic Terraform\n\nThis sample shows how to use Terraform's [google_workflows_workflow](https://registry.terraform.io/pr"
  },
  {
    "path": "terraform/basic/main.tf",
    "chars": 2423,
    "preview": "/**\n * Copyright 2021 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "terraform/basic/vars.tf",
    "chars": 639,
    "preview": "/**\n * Copyright 2021 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "terraform/import-multiple-yamls/README.md",
    "chars": 1319,
    "preview": "# Workflows Terraform with multiple external YAMLs\n\nThis sample shows how to use Terraform's [google_workflows_workflow]"
  },
  {
    "path": "terraform/import-multiple-yamls/main.tf",
    "chars": 1547,
    "preview": "/**\n * Copyright 2021 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "terraform/import-multiple-yamls/subworkflow.yaml",
    "chars": 244,
    "preview": "# A subworkflow that is imported by Terraform.\nname_message:\n    params: [first_name, last_name, country: \"England\"]\n   "
  },
  {
    "path": "terraform/import-multiple-yamls/vars.tf",
    "chars": 678,
    "preview": "/**\n * Copyright 2021 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "terraform/import-multiple-yamls/workflow.yaml",
    "chars": 1310,
    "preview": "# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "terraform/import-yaml/README.md",
    "chars": 1072,
    "preview": "# Terraform with imported YAML\n\nThis sample shows how to use Terraform's [google_workflows_workflow](https://registry.te"
  },
  {
    "path": "terraform/import-yaml/main.tf",
    "chars": 1426,
    "preview": "/**\n * Copyright 2021 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "terraform/import-yaml/vars.tf",
    "chars": 678,
    "preview": "/**\n * Copyright 2021 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
  },
  {
    "path": "terraform/import-yaml/workflow.yaml",
    "chars": 1496,
    "preview": "  # Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this"
  },
  {
    "path": "twitter-sentiment/README.md",
    "chars": 5681,
    "preview": "# Twitter sentiment analysis using Language API connector and iteration syntax\n\nIn this sample, you will see how to use "
  },
  {
    "path": "twitter-sentiment/workflow.yaml",
    "chars": 2674,
    "preview": "main:\n  params: [args]\n  steps:\n    - init:\n        assign:\n          - bearerToken: ${args.bearerToken}\n          - twi"
  },
  {
    "path": "twitter-sentiment-parallel/README.md",
    "chars": 8597,
    "preview": "# Twitter sentiment analysis using Language API connector and parallel iteration\n\n> **Note:** Parallel steps/iteration i"
  },
  {
    "path": "twitter-sentiment-parallel/workflow-parallel.yaml",
    "chars": 5366,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "twitter-sentiment-parallel/workflow-serial.yaml",
    "chars": 5144,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "vertexai/country-histories/gemini-pro/README.md",
    "chars": 1635,
    "preview": "# Call VertexAI Gemini Pro from Workflows in parallel\n\nIn this sample, you'll see how to call Vertex AI's Gemini Pro\nin "
  },
  {
    "path": "vertexai/country-histories/gemini-pro/country-histories-connector.yaml",
    "chars": 2223,
    "preview": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "vertexai/country-histories/gemini-pro/country-histories.yaml",
    "chars": 2343,
    "preview": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "vertexai/country-histories/gemini-pro/gemini-response.json",
    "chars": 3535,
    "preview": "{\n  \"candidates\": [\n    {\n      \"content\": {\n        \"parts\": [\n          {\n            \"text\": \"**Pre-Columbian   Perio"
  },
  {
    "path": "vertexai/country-histories/text-bison/README.md",
    "chars": 2177,
    "preview": "# Call VertexAI PaLM 2 for Text (text-bison) from Workflows in parallel\n\nIn this sample, you'll see how to call Vertex A"
  },
  {
    "path": "vertexai/country-histories/text-bison/country-histories-connector.yaml",
    "chars": 2307,
    "preview": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "vertexai/country-histories/text-bison/country-histories.yaml",
    "chars": 2422,
    "preview": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "vertexai/country-histories/text-bison/country-history-connector.yaml",
    "chars": 1640,
    "preview": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "vertexai/country-histories/text-bison/country-history.yaml",
    "chars": 1763,
    "preview": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "vertexai/describe-image/README.md",
    "chars": 1686,
    "preview": "# Call VertexAI Gemini Pro Vision from Workflows to describe an image\n\nIn this sample, you'll see how to call Vertex AI'"
  },
  {
    "path": "vertexai/describe-image/describe-image-connector.yaml",
    "chars": 1825,
    "preview": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "vertexai/describe-image/describe-image.yaml",
    "chars": 1929,
    "preview": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "vertexai/describe-image/gemini-response.json",
    "chars": 1505,
    "preview": "{\n  \"candidates\": [\n    {\n      \"content\": {\n        \"parts\": [\n          {\n            \"text\": \"   The picture shows a "
  },
  {
    "path": "vertexai/parallel-summaries/README.md",
    "chars": 3427,
    "preview": "# Summarize a long document with Workflows and Gemini Pro\n\nIn this sample, you'll see how to take advantage of Workflows"
  },
  {
    "path": "vertexai/parallel-summaries/parallel-summaries-connector.yaml",
    "chars": 5026,
    "preview": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "vertexai/parallel-summaries/parallel-summaries-initial.yaml",
    "chars": 4292,
    "preview": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "vertexai/parallel-summaries/parallel-summaries.yaml",
    "chars": 4443,
    "preview": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "vertexai/parallel-summaries/pride_and_prejudice.txt",
    "chars": 763064,
    "preview": "The Project Gutenberg eBook of Pride and Prejudice\r\n    \r\nThis ebook is for the use of anyone anywhere in the United St"
  },
  {
    "path": "vertexai/parallel-summaries/pride_and_prejudice_1000.txt",
    "chars": 47507,
    "preview": "The Project Gutenberg eBook of Pride and Prejudice\r\n    \r\nThis ebook is for the use of anyone anywhere in the United St"
  },
  {
    "path": "vertexai/parallel-summaries/pride_and_prejudice_4500.txt",
    "chars": 221614,
    "preview": "The Project Gutenberg eBook of Pride and Prejudice\r\n    \r\nThis ebook is for the use of anyone anywhere in the United St"
  },
  {
    "path": "workflow-executes-other-workflows/README.md",
    "chars": 4902,
    "preview": "# A workflow executes other workflows in parallel\n\nA workflow can execute other workflows. This can be useful in batch d"
  },
  {
    "path": "workflow-executes-other-workflows/workflow-child.yaml",
    "chars": 1305,
    "preview": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "workflow-executes-other-workflows/workflow-parent.yaml",
    "chars": 2506,
    "preview": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "workflow-tasks-workflow/README.md",
    "chars": 5175,
    "preview": "# Buffer workflow executions with a Cloud Tasks queue\n\nWhen you receive a heavy burst of traffic, it's tempting to creat"
  },
  {
    "path": "workflow-tasks-workflow/workflow-child.yaml",
    "chars": 1035,
    "preview": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "workflow-tasks-workflow/workflow-parent.yaml",
    "chars": 2480,
    "preview": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "workflows-bigquery-load/README.md",
    "chars": 1091,
    "preview": "# Load data from Cloud Storage to BigQuery using Workflows\n\nThis guide explains how to orchestrate a serverless schedule"
  },
  {
    "path": "workflows-bigquery-load/build.sh",
    "chars": 811,
    "preview": "#!/bin/bash\n# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may no"
  },
  {
    "path": "workflows-bigquery-load/file_change_handler/main.py",
    "chars": 1236,
    "preview": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "workflows-bigquery-load/file_change_handler/requirements.txt",
    "chars": 80,
    "preview": "# Function dependencies, for example:\n# package>=version\ngoogle-cloud-firestore\n"
  },
  {
    "path": "workflows-bigquery-load/generator/gen.py",
    "chars": 3013,
    "preview": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "workflows-bigquery-load/generator/requirements.txt",
    "chars": 56,
    "preview": "wheel\navro-python3\nStringGenerator\ngoogle-cloud-storage\n"
  },
  {
    "path": "workflows-bigquery-load/main.tf",
    "chars": 10489,
    "preview": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "workflows-bigquery-load/workflow.yaml",
    "chars": 4765,
    "preview": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "workflows-bigquery-load/workflow_handlers/main.py",
    "chars": 10303,
    "preview": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "workflows-bigquery-load/workflow_handlers/requirements.txt",
    "chars": 102,
    "preview": "# Function dependencies, for example:\n# package>=version\ngoogle-cloud-bigquery\ngoogle-cloud-firestore\n"
  },
  {
    "path": "workflows-eventarc-integration/event-payload-storer/README.md",
    "chars": 886,
    "preview": "# Event payload storer\n\nThis workflow receives events from Eventarc and stores their payload (the data\nfield of the even"
  },
  {
    "path": "workflows-eventarc-integration/event-payload-storer/event-payload-storer.yaml",
    "chars": 1300,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "workflows-eventarc-integration/event-payload-storer/setup.sh",
    "chars": 1598,
    "preview": "#!/bin/bash\n\n# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
  },
  {
    "path": "workflows-eventarc-integration/event-payload-storer/test_pubsub.sh",
    "chars": 1481,
    "preview": "#!/bin/bash\n\n# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
  },
  {
    "path": "workflows-eventarc-integration/event-payload-storer/test_storage.sh",
    "chars": 2126,
    "preview": "#!/bin/bash\n\n# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
  },
  {
    "path": "workflows-eventarc-integration/workflows-pubsub/README.md",
    "chars": 2584,
    "preview": "# Workflows and Eventarc Pub/Sub Integration\n\nIn this sample, you will see how to connect\n[Workflows](https://cloud.goog"
  },
  {
    "path": "workflows-eventarc-integration/workflows-pubsub/workflow.yaml",
    "chars": 442,
    "preview": "- init:\n    assign:\n      - project: ${sys.get_env(\"GOOGLE_CLOUD_PROJECT_ID\")}\n      - topic: \"workflows-to-eventarc-top"
  },
  {
    "path": "workflows-executes-commands/using-cloudbuild-api/README.md",
    "chars": 5647,
    "preview": "# Workflows executes commands (gcloud, kubectl) - using Cloud Build API\n\n> **Note:** There's a [newer version](../using-"
  },
  {
    "path": "workflows-executes-commands/using-cloudbuild-api/setup.sh",
    "chars": 1459,
    "preview": "#!/bin/bash\n\n# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
  },
  {
    "path": "workflows-executes-commands/using-cloudbuild-api/workflow-gcloud.yaml",
    "chars": 1738,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "workflows-executes-commands/using-cloudbuild-api/workflow-kubectl.yaml",
    "chars": 1725,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "workflows-executes-commands/using-standard-library/README.md",
    "chars": 1367,
    "preview": "# Workflows executes commands (gcloud, kubectl) - using standard library\n\n> **Note:** This sample uses the standard libr"
  },
  {
    "path": "workflows-executes-commands/using-standard-library/setup.sh",
    "chars": 2599,
    "preview": "#!/bin/bash\n\n# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
  },
  {
    "path": "workflows-executes-commands/using-standard-library/workflow-gcloud.yaml",
    "chars": 926,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "workflows-executes-commands/using-standard-library/workflow-kubectl.yaml",
    "chars": 1001,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "workflows-kubernetes-engine/README.md",
    "chars": 5836,
    "preview": "# Deploy a Kubernetes application with Workflows\n\nThis workflow demonstrates how to deploy a Kubernetes application with"
  },
  {
    "path": "workflows-kubernetes-engine/workflow.yaml",
    "chars": 3981,
    "preview": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "workspace-integration/sheets-to-workflows/Code.gs",
    "chars": 1859,
    "preview": "const PROJECT_ID = \"your-project-id\";\nconst REGION = \"us-central1\";\nconst WORKFLOW = \"create-vm-from-form\";\n\nfunction ha"
  },
  {
    "path": "workspace-integration/sheets-to-workflows/README.md",
    "chars": 3390,
    "preview": "# Triggering Workflows from Google Sheets\n\nIn this sample, we show you how to trigger Workflows from Google Sheets.\n\nMor"
  },
  {
    "path": "workspace-integration/sheets-to-workflows/appscript.json",
    "chars": 335,
    "preview": "{\n    \"timeZone\": \"Europe/London\",\n    \"dependencies\": {\n    },\n    \"exceptionLogging\": \"STACKDRIVER\",\n    \"runtimeVersi"
  },
  {
    "path": "workspace-integration/sheets-to-workflows/setup.sh",
    "chars": 890,
    "preview": "#!/bin/bash\n\n# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
  },
  {
    "path": "workspace-integration/sheets-to-workflows/workflow.yaml",
    "chars": 1957,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "workspace-integration/workflows-awaits-sheets-callback/Code.gs",
    "chars": 919,
    "preview": "function handleEdit(e) {\n  var range = e.range.getA1Notation();\n  var sheet = e.source;\n\n  if (range.length > 1 && range"
  },
  {
    "path": "workspace-integration/workflows-awaits-sheets-callback/README.md",
    "chars": 7530,
    "preview": "# Workflows that pause and wait for human approvals from Google Sheets\n\nIn this sample, we show you how to have a workfl"
  },
  {
    "path": "workspace-integration/workflows-awaits-sheets-callback/appscript.json",
    "chars": 313,
    "preview": "{\n  \"timeZone\": \"Europe/London\",\n  \"dependencies\": {\n  },\n  \"exceptionLogging\": \"STACKDRIVER\",\n  \"runtimeVersion\": \"V8\","
  },
  {
    "path": "workspace-integration/workflows-awaits-sheets-callback/setup.sh",
    "chars": 884,
    "preview": "#!/bin/bash\n\n# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
  },
  {
    "path": "workspace-integration/workflows-awaits-sheets-callback/workflow.yaml",
    "chars": 4361,
    "preview": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "workspace-integration/workflows-to-sheets/README.md",
    "chars": 4628,
    "preview": "# Writing to Google Sheets from Workflows\n\nThis samples shows how a workflow can query top names from a public dataset i"
  },
  {
    "path": "workspace-integration/workflows-to-sheets/setup.sh",
    "chars": 923,
    "preview": "#!/bin/bash\n\n# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the GoogleCloudPlatform/workflows-demos GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 201 files (1.6 MB), approximately 447.5k tokens, and a symbol index with 66 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!